Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| df6f8e5710 | |||
| 52877e3a68 | |||
| a06dfa0ae9 | |||
| 0836cada75 |
@@ -1 +1,7 @@
|
|||||||
<svg style="height: 512px; width: 512px;" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><g class="" style="" transform="translate(0,0)"><path d="M373.47 25.5c-33.475-.064-67.614 13.444-94.44 43.156l37.22 145.156-33.437.032 35.343 132.093-116.718-188.375 50.03 5.375L202.5 47.312C120.437-1.43 4.756 40.396 8.5 158.156c4.402 138.44 191.196 184.6 247.406 331.625 59.376-147.035 251.26-184.33 246.656-331.624-2.564-82.042-64.6-132.532-129.093-132.656z" fill="#fff" fill-opacity="1"></path></g></svg>
|
<svg style="height: 512px; width: 512px;" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
|
||||||
|
<g class="" style="" transform="translate(0,0)">
|
||||||
|
<path
|
||||||
|
d="M373.47 25.5c-33.475-.064-67.614 13.444-94.44 43.156l37.22 145.156-33.437.032 35.343 132.093-116.718-188.375 50.03 5.375L202.5 47.312C120.437-1.43 4.756 40.396 8.5 158.156c4.402 138.44 191.196 184.6 247.406 331.625 59.376-147.035 251.26-184.33 246.656-331.624-2.564-82.042-64.6-132.532-129.093-132.656z"
|
||||||
|
fill="#dc2626" fill-opacity="1"></path>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 506 B After Width: | Height: | Size: 533 B |
10
assets/icons/health-increase.svg
Normal file
10
assets/icons/health-increase.svg
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor"
|
||||||
|
stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<!-- Heart shape -->
|
||||||
|
<path
|
||||||
|
d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"
|
||||||
|
fill="#4ade80" stroke="#22c55e" />
|
||||||
|
<!-- Plus sign inside heart -->
|
||||||
|
<line x1="12" y1="8" x2="12" y2="14" stroke="#ffffff" stroke-width="2.5" />
|
||||||
|
<line x1="9" y1="11" x2="15" y2="11" stroke="#ffffff" stroke-width="2.5" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 572 B |
File diff suppressed because it is too large
Load Diff
12
lang/en.json
12
lang/en.json
@@ -283,6 +283,7 @@
|
|||||||
"Label": {
|
"Label": {
|
||||||
"agility": "Dexterity",
|
"agility": "Dexterity",
|
||||||
"applyDamage": "Apply damage to:",
|
"applyDamage": "Apply damage to:",
|
||||||
|
"selectTarget": "Select target for attack:",
|
||||||
"rollDamage": "Roll Damage",
|
"rollDamage": "Roll Damage",
|
||||||
"gotoToken": "Go to token",
|
"gotoToken": "Go to token",
|
||||||
"combatAction": "Combat action",
|
"combatAction": "Combat action",
|
||||||
@@ -876,6 +877,17 @@
|
|||||||
"withArmor": "With Armor DR only",
|
"withArmor": "With Armor DR only",
|
||||||
"withAll": "With Armor + Shield DR",
|
"withAll": "With Armor + Shield DR",
|
||||||
"damage": "damage"
|
"damage": "damage"
|
||||||
|
},
|
||||||
|
"DamageApplied": {
|
||||||
|
"subtitle": "suffered damage",
|
||||||
|
"damageDealt": "Damage dealt",
|
||||||
|
"from": "from"
|
||||||
|
},
|
||||||
|
"ProgressionMessage": {
|
||||||
|
"canAct": "ready to act!",
|
||||||
|
"cannotAct": "cannot act this second",
|
||||||
|
"diceResult": "Dice result",
|
||||||
|
"progressionCount": "Progression count:"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"TYPES": {
|
"TYPES": {
|
||||||
|
|||||||
@@ -110,6 +110,9 @@ function preLocalizeConfig() {
|
|||||||
Hooks.once("ready", function () {
|
Hooks.once("ready", function () {
|
||||||
console.info("LETHAL FANTASY | Ready")
|
console.info("LETHAL FANTASY | Ready")
|
||||||
|
|
||||||
|
// Initialiser la table des résultats D30
|
||||||
|
documents.D30Roll.initialize()
|
||||||
|
|
||||||
if (!SYSTEM.DEV_MODE) {
|
if (!SYSTEM.DEV_MODE) {
|
||||||
registerWorldCount("lethalFantasy")
|
registerWorldCount("lethalFantasy")
|
||||||
}
|
}
|
||||||
@@ -186,22 +189,148 @@ Hooks.on(hookName, (message, html, data) => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Gestionnaire pour les boutons de jet de dégâts
|
// Gestion du survol et du clic sur les boutons de défense
|
||||||
$(html).find(".damage-roll-btn").click(async (event) => {
|
$(html).find(".request-defense-btn").hover(
|
||||||
|
function (event) {
|
||||||
|
// Mouse enter - select the token and pan to it
|
||||||
|
let tokenId = $(this).data("token-id")
|
||||||
|
if (tokenId) {
|
||||||
|
let token = canvas.tokens.get(tokenId)
|
||||||
|
if (token) {
|
||||||
|
token.control({ releaseOthers: true })
|
||||||
|
canvas.animatePan(token.center)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
function (event) {
|
||||||
|
// Mouse leave - release selection
|
||||||
|
canvas.tokens.releaseAll()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// Gestionnaire pour les boutons de demande de défense
|
||||||
|
$(html).find(".request-defense-btn").off("click").on("click", (event) => {
|
||||||
|
event.preventDefault()
|
||||||
|
event.stopPropagation()
|
||||||
|
|
||||||
|
const button = $(event.currentTarget)
|
||||||
|
const combatantId = button.data("combatant-id")
|
||||||
|
const tokenId = button.data("token-id")
|
||||||
|
|
||||||
|
// Récupérer le combattant soit du combat, soit directement du token
|
||||||
|
let combatant = null
|
||||||
|
let token = null
|
||||||
|
|
||||||
|
if (game.combat && combatantId) {
|
||||||
|
combatant = game.combat.combatants.get(combatantId)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Si pas de combattant trouvé, chercher le token directement
|
||||||
|
if (!combatant && tokenId) {
|
||||||
|
token = canvas.tokens.get(tokenId)
|
||||||
|
if (token) {
|
||||||
|
// Créer un pseudo-combattant avec les infos du token
|
||||||
|
combatant = {
|
||||||
|
actor: token.actor,
|
||||||
|
name: token.name,
|
||||||
|
token: token,
|
||||||
|
actorId: token.actorId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!combatant) return
|
||||||
|
|
||||||
|
// Récupérer les informations de l'attaquant depuis le message
|
||||||
|
const attackerName = message.rolls[0]?.actorName || "Unknown"
|
||||||
|
const attackerId = message.rolls[0]?.actorId
|
||||||
|
const weaponName = message.rolls[0]?.rollName || "weapon"
|
||||||
|
const attackRoll = message.rolls[0]?.rollTotal || 0
|
||||||
|
const defenderName = combatant.name
|
||||||
|
const attackWeaponId = message.rolls[0]?.rollTarget?.weapon?.id || message.rolls[0]?.rollTarget?.weapon?._id
|
||||||
|
const attackRollType = message.rolls[0]?.type
|
||||||
|
const attackRollKey = message.rolls[0]?.rollTarget?.rollKey
|
||||||
|
|
||||||
|
// Préparer le message de demande de défense
|
||||||
|
const defenseMsg = {
|
||||||
|
type: "requestDefense",
|
||||||
|
attackerName: attackerName,
|
||||||
|
attackerId: attackerId,
|
||||||
|
defenderName: defenderName,
|
||||||
|
weaponName: weaponName,
|
||||||
|
attackRoll: attackRoll,
|
||||||
|
attackWeaponId: attackWeaponId,
|
||||||
|
attackRollType: attackRollType,
|
||||||
|
attackRollKey: attackRollKey,
|
||||||
|
combatantId: combatantId,
|
||||||
|
tokenId: tokenId
|
||||||
|
}
|
||||||
|
|
||||||
|
// Envoyer le message socket à l'utilisateur contrôlant le combatant
|
||||||
|
const owners = game.users.filter(u =>
|
||||||
|
combatant.actor.testUserPermission(u, "OWNER")
|
||||||
|
)
|
||||||
|
|
||||||
|
// Récupérer l'acteur attaquant pour vérifier qui l'a lancé
|
||||||
|
const attacker = game.actors.get(attackerId)
|
||||||
|
const attackerOwners = attacker ? game.users.filter(u => attacker.testUserPermission(u, "OWNER")).map(u => u.id) : []
|
||||||
|
|
||||||
|
let messageSent = false
|
||||||
|
owners.forEach(owner => {
|
||||||
|
// Ne pas afficher le dialogue à l'attaquant lui-même s'il contrôle aussi le défenseur
|
||||||
|
if (attackerOwners.includes(owner.id) && owner.id === game.user.id) {
|
||||||
|
// Ne rien faire - on ne veut pas que l'attaquant se défende contre lui-même
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (owner.id === game.user.id) {
|
||||||
|
// Si l'utilisateur actuel est le propriétaire du défenseur (mais pas l'attaquant), appeler directement
|
||||||
|
LethalFantasyUtils.showDefenseRequest({ ...defenseMsg, userId: owner.id })
|
||||||
|
messageSent = true
|
||||||
|
} else {
|
||||||
|
// Sinon, envoyer via socket
|
||||||
|
game.socket.emit(`system.${SYSTEM.id}`, {
|
||||||
|
...defenseMsg,
|
||||||
|
userId: owner.id
|
||||||
|
})
|
||||||
|
messageSent = true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Notification pour l'attaquant
|
||||||
|
if (messageSent) {
|
||||||
|
ui.notifications.info(`Defense request sent to ${defenderName}'s controller`)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Gestionnaire pour les boutons de jet de dégâts (armes et résultats de combat)
|
||||||
|
$(html).find(".damage-roll-btn, .roll-damage-btn").off("click").on("click", async (event) => {
|
||||||
|
event.preventDefault()
|
||||||
|
event.stopPropagation()
|
||||||
|
|
||||||
const button = $(event.currentTarget)
|
const button = $(event.currentTarget)
|
||||||
const weaponId = button.data("weapon-id")
|
const weaponId = button.data("weapon-id")
|
||||||
|
const attackKey = button.data("attack-key")
|
||||||
|
let attackerId = button.data("attacker-id")
|
||||||
|
const defenderId = button.data("defender-id")
|
||||||
const damageType = button.data("damage-type")
|
const damageType = button.data("damage-type")
|
||||||
const damageFormula = button.data("damage-formula")
|
const damageFormula = button.data("damage-formula")
|
||||||
const damageModifier = button.data("damage-modifier")
|
const damageModifier = button.data("damage-modifier")
|
||||||
const isMonster = button.data("is-monster")
|
const isMonster = button.data("is-monster")
|
||||||
|
|
||||||
// Récupérer l'acteur qui a fait le jet initial
|
// Récupérer l'acteur (soit depuis le message, soit depuis attackerId)
|
||||||
const actor = game.actors.get(message.rolls[0]?.actorId)
|
let actor = attackerId ? game.actors.get(attackerId) : game.actors.get(message.rolls[0]?.actorId)
|
||||||
if (!actor) {
|
if (!actor) {
|
||||||
ui.notifications.error("Actor not found")
|
ui.notifications.error("Actor not found")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Pour les boutons de résultat de combat (monster damage)
|
||||||
|
if (damageType === "monster" && attackKey) {
|
||||||
|
await actor.system.prepareMonsterRoll("monster-damage", attackKey, undefined, undefined, undefined, defenderId)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Pour les monstres, utiliser prepareMonsterRoll
|
// Pour les monstres, utiliser prepareMonsterRoll
|
||||||
if (isMonster || actor.type === "monster") {
|
if (isMonster || actor.type === "monster") {
|
||||||
await actor.system.prepareMonsterRoll("monster-damage", weaponId, undefined, undefined, damageModifier)
|
await actor.system.prepareMonsterRoll("monster-damage", weaponId, undefined, undefined, damageModifier)
|
||||||
@@ -217,7 +346,21 @@ Hooks.on(hookName, (message, html, data) => {
|
|||||||
|
|
||||||
// Lancer les dégâts avec la bonne méthode
|
// Lancer les dégâts avec la bonne méthode
|
||||||
const rollType = damageType === "small" ? "weapon-damage-small" : "weapon-damage-medium"
|
const rollType = damageType === "small" ? "weapon-damage-small" : "weapon-damage-medium"
|
||||||
await actor.prepareRoll(rollType, weaponId)
|
await actor.prepareRoll(rollType, weaponId, undefined, defenderId)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Masquer les boutons de dommages dans les messages de résultat de combat si l'utilisateur n'est pas l'attaquant
|
||||||
|
$(html).find(".roll-damage-btn").each(function() {
|
||||||
|
const button = $(this)
|
||||||
|
const attackerId = button.data("attacker-id")
|
||||||
|
|
||||||
|
if (attackerId) {
|
||||||
|
const attacker = game.actors.get(attackerId)
|
||||||
|
// Masquer le bouton si l'utilisateur n'est pas GM et ne possède pas l'attaquant
|
||||||
|
if (!game.user.isGM && !attacker?.testUserPermission(game.user, "OWNER")) {
|
||||||
|
button.hide()
|
||||||
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -225,6 +368,209 @@ Hooks.on("getCombatTrackerEntryContext", (html, options) => {
|
|||||||
LethalFantasyUtils.pushCombatOptions(html, options);
|
LethalFantasyUtils.pushCombatOptions(html, options);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Hook pour ajouter les données d'attaque au message de défense
|
||||||
|
Hooks.on("preCreateChatMessage", (message) => {
|
||||||
|
const rollType = message.rolls[0]?.options?.rollType
|
||||||
|
|
||||||
|
// Si c'est un message de défense et qu'on a des données en attente
|
||||||
|
if ((rollType === "weapon-defense" || rollType === "monster-defense") && game.lethalFantasy?.nextDefenseData) {
|
||||||
|
// Ajouter les données dans les flags du message
|
||||||
|
message.updateSource({
|
||||||
|
[`flags.${SYSTEM.id}.attackData`]: game.lethalFantasy.nextDefenseData
|
||||||
|
})
|
||||||
|
|
||||||
|
console.log("Added attack data to defense message:", game.lethalFantasy.nextDefenseData)
|
||||||
|
|
||||||
|
// Nettoyer
|
||||||
|
delete game.lethalFantasy.nextDefenseData
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Hook global pour gérer l'offre de Grit à l'attaquant après une défense
|
||||||
|
Hooks.on("createChatMessage", async (message) => {
|
||||||
|
const rollType = message.rolls[0]?.options?.rollType
|
||||||
|
|
||||||
|
console.log("Defense hook checking message, rollType:", rollType)
|
||||||
|
|
||||||
|
// Vérifier si c'est un message de défense
|
||||||
|
if (rollType !== "weapon-defense" && rollType !== "monster-defense") return
|
||||||
|
|
||||||
|
// Récupérer les données d'attaque depuis les flags
|
||||||
|
const attackData = message.flags?.[SYSTEM.id]?.attackData
|
||||||
|
|
||||||
|
console.log("Defense message confirmed, attackData:", attackData)
|
||||||
|
|
||||||
|
if (!attackData) {
|
||||||
|
console.log("No attack data found in message flags")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const { attackerId, attackRoll, attackerName, defenderName, attackWeaponId, attackRollType, attackRollKey, defenderId } = attackData
|
||||||
|
let defenseRoll = message.rolls[0]?.options?.rollTotal || message.rolls[0]?.total || 0
|
||||||
|
|
||||||
|
console.log("Processing defense:", { attackRoll, defenseRoll, attackerId, defenderId })
|
||||||
|
|
||||||
|
// Attendre l'animation 3D
|
||||||
|
if (game?.dice3d) {
|
||||||
|
await game.dice3d.waitFor3DAnimationByMessageID(message.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Récupérer le défenseur et l'attaquant
|
||||||
|
const defender = game.actors.get(defenderId)
|
||||||
|
const attacker = game.actors.get(attackerId)
|
||||||
|
|
||||||
|
// Si le défenseur est un personnage qui perd, proposer Grit/Luck (seulement s'il a des points)
|
||||||
|
// Seulement si l'utilisateur actuel est le propriétaire du défenseur
|
||||||
|
let defenderHandledBonus = false
|
||||||
|
if (defender?.type === "character" && defenseRoll < attackRoll && !game.user.isGM && defender.isOwner) {
|
||||||
|
const hasGritOrLuck = (defender.system.grit.current > 0) || (defender.system.luck.current > 0)
|
||||||
|
|
||||||
|
if (hasGritOrLuck) {
|
||||||
|
const bonusRoll = await LethalFantasyUtils.offerGritLuckBonus(
|
||||||
|
defender,
|
||||||
|
attackRoll,
|
||||||
|
defenseRoll,
|
||||||
|
attackerName,
|
||||||
|
defenderName
|
||||||
|
)
|
||||||
|
if (bonusRoll > 0) {
|
||||||
|
defenseRoll += bonusRoll
|
||||||
|
}
|
||||||
|
}
|
||||||
|
defenderHandledBonus = true
|
||||||
|
}
|
||||||
|
|
||||||
|
let attackRollFinal = attackRoll
|
||||||
|
let attackerHandledBonus = false
|
||||||
|
|
||||||
|
// Si l'attaquant est un personnage qui perd et a du Grit
|
||||||
|
// Seulement si l'utilisateur actuel est le propriétaire de l'attaquant (pas le MJ)
|
||||||
|
if (attacker?.type === "character" && attackRollFinal <= defenseRoll && attacker.system.grit.current > 0) {
|
||||||
|
// Vérifier si l'utilisateur est un propriétaire non-GM de l'attaquant
|
||||||
|
const isAttackerOwner = !game.user.isGM && attacker.testUserPermission(game.user, "OWNER")
|
||||||
|
|
||||||
|
if (isAttackerOwner) {
|
||||||
|
console.log("Offering Grit to attacker")
|
||||||
|
|
||||||
|
const attackBonus = await LethalFantasyUtils.offerAttackerGritBonus(
|
||||||
|
attacker,
|
||||||
|
attackRollFinal,
|
||||||
|
defenseRoll,
|
||||||
|
attackerName,
|
||||||
|
defenderName
|
||||||
|
)
|
||||||
|
|
||||||
|
attackRollFinal += attackBonus
|
||||||
|
attackerHandledBonus = true
|
||||||
|
} else {
|
||||||
|
console.log("Not attacker owner or is GM, skipping Grit offer")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Créer le message de comparaison - uniquement par le client qui a géré le dernier bonus
|
||||||
|
// Priorité: attaquant si il a géré le bonus, sinon défenseur si il a géré le bonus, sinon défenseur
|
||||||
|
const shouldCreateMessage = attackerHandledBonus || (!attackerHandledBonus && defenderHandledBonus) || (!attackerHandledBonus && !defenderHandledBonus && defender.isOwner)
|
||||||
|
|
||||||
|
if (shouldCreateMessage) {
|
||||||
|
console.log("Creating comparison message", { attackerHandledBonus, defenderHandledBonus, isDefenderOwner: defender.isOwner })
|
||||||
|
|
||||||
|
await LethalFantasyUtils.compareAttackDefense({
|
||||||
|
attackerName,
|
||||||
|
attackerId,
|
||||||
|
attackRoll: attackRollFinal,
|
||||||
|
attackWeaponId,
|
||||||
|
attackRollType,
|
||||||
|
attackRollKey,
|
||||||
|
defenderName,
|
||||||
|
defenderId,
|
||||||
|
defenseRoll
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
console.log("Skipping message creation", { attackerHandledBonus, defenderHandledBonus })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Hook pour appliquer automatiquement les dégâts si une cible est définie
|
||||||
|
Hooks.on("createChatMessage", async (message) => {
|
||||||
|
// Vérifier si c'est un message de dégâts avec un defenderId
|
||||||
|
const defenderId = message.rolls[0]?.options?.defenderId
|
||||||
|
const isDamage = message.rolls[0]?.options?.rollData?.isDamage
|
||||||
|
|
||||||
|
console.log("Auto-damage hook:", { defenderId, isDamage, rollType: message.rolls[0]?.options?.rollType })
|
||||||
|
|
||||||
|
if (!defenderId || !isDamage) return
|
||||||
|
|
||||||
|
// Récupérer l'attaquant depuis le roll
|
||||||
|
const attackerId = message.rolls[0]?.options?.actorId
|
||||||
|
const attacker = attackerId ? game.actors.get(attackerId) : null
|
||||||
|
|
||||||
|
// Déterminer qui doit appliquer les dégâts :
|
||||||
|
// 1. Si l'attaquant a un propriétaire joueur, seul ce joueur applique
|
||||||
|
// 2. Si l'attaquant n'a que le MJ comme propriétaire (monstre), seul le MJ applique
|
||||||
|
const attackerOwners = attacker ? game.users.filter(u =>
|
||||||
|
!u.isGM && attacker.testUserPermission(u, "OWNER")
|
||||||
|
) : []
|
||||||
|
|
||||||
|
let shouldApplyDamage = false
|
||||||
|
if (attackerOwners.length > 0) {
|
||||||
|
// L'attaquant a des propriétaires joueurs, seul le premier propriétaire applique
|
||||||
|
shouldApplyDamage = attackerOwners[0].id === game.user.id
|
||||||
|
} else {
|
||||||
|
// L'attaquant n'a que le MJ, seul le MJ applique
|
||||||
|
shouldApplyDamage = game.user.isGM
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!shouldApplyDamage) {
|
||||||
|
console.log("Auto-damage hook: Not responsible for applying damage, skipping")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("Auto-damage hook: Applying damage as responsible user")
|
||||||
|
|
||||||
|
// Attendre l'animation 3D avant d'appliquer les dégâts
|
||||||
|
if (game?.dice3d) {
|
||||||
|
await game.dice3d.waitFor3DAnimationByMessageID(message.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Récupérer le défenseur
|
||||||
|
const defender = game.actors.get(defenderId)
|
||||||
|
if (!defender) {
|
||||||
|
console.warn("Defender not found:", defenderId)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Récupérer les dégâts (utiliser rollTotal qui contient le total calculé)
|
||||||
|
const damageTotal = message.rolls[0]?.options?.rollTotal || message.rolls[0]?.total || 0
|
||||||
|
const weaponName = message.rolls[0]?.options?.rollName || "Unknown Weapon"
|
||||||
|
const attackerName = message.rolls[0]?.options?.actorName || "Unknown Attacker"
|
||||||
|
|
||||||
|
// Calculer les DR
|
||||||
|
const armorDR = defender.computeDamageReduction() || 0
|
||||||
|
|
||||||
|
// Appliquer les dégâts avec armure DR par défaut
|
||||||
|
const finalDamage = Math.max(0, damageTotal - armorDR)
|
||||||
|
await defender.applyDamage(-finalDamage)
|
||||||
|
|
||||||
|
// Créer un message de confirmation
|
||||||
|
const messageContent = await foundry.applications.handlebars.renderTemplate(
|
||||||
|
"systems/fvtt-lethal-fantasy/templates/damage-applied-message.hbs",
|
||||||
|
{
|
||||||
|
targetName: defender.name,
|
||||||
|
damage: finalDamage,
|
||||||
|
drText: armorDR > 0 ? `Armor DR: ${armorDR}` : "",
|
||||||
|
weaponName: weaponName,
|
||||||
|
attackerName: attackerName,
|
||||||
|
rawDamage: damageTotal
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
await ChatMessage.create({
|
||||||
|
content: messageContent,
|
||||||
|
speaker: ChatMessage.getSpeaker({ actor: defender })
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a macro when dropping an entity on the hotbar
|
* Create a macro when dropping an entity on the hotbar
|
||||||
* Item - open roll dialog
|
* Item - open roll dialog
|
||||||
|
|||||||
@@ -99,12 +99,22 @@ export default class LethalFantasyCharacterSheet extends LethalFantasyActorSheet
|
|||||||
context.damageReduction = this.actor.computeDamageReduction()
|
context.damageReduction = this.actor.computeDamageReduction()
|
||||||
context.damageReductionShield = this.actor.getShieldDR()
|
context.damageReductionShield = this.actor.getShieldDR()
|
||||||
break
|
break
|
||||||
case "skills":
|
case "skills": {
|
||||||
context.tab = context.tabs.skills
|
context.tab = context.tabs.skills
|
||||||
context.skills = doc.itemTypes.skill
|
context.skills = doc.itemTypes.skill
|
||||||
|
// Organiser les skills par catégorie
|
||||||
|
const categories = ['layperson', 'professional', 'weapon', 'armor', 'resist']
|
||||||
|
context.skillsByCategory = categories.map(cat => {
|
||||||
|
return {
|
||||||
|
category: cat,
|
||||||
|
label: `LETHALFANTASY.Skill.Category.${cat}`,
|
||||||
|
skills: context.skills.filter(s => s.system.category === cat)
|
||||||
|
}
|
||||||
|
}).filter(catData => catData.skills.length > 0)
|
||||||
context.gifts = doc.itemTypes.gift
|
context.gifts = doc.itemTypes.gift
|
||||||
context.vulnerabilities = doc.itemTypes.vulnerability
|
context.vulnerabilities = doc.itemTypes.vulnerability
|
||||||
break
|
break
|
||||||
|
}
|
||||||
case "spells":
|
case "spells":
|
||||||
context.tab = context.tabs.spells
|
context.tab = context.tabs.spells
|
||||||
context.spells = doc.itemTypes.spell
|
context.spells = doc.itemTypes.spell
|
||||||
|
|||||||
152
module/config/d30_results_tables.json
Normal file
152
module/config/d30_results_tables.json
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
{
|
||||||
|
"d30_dice_results": {
|
||||||
|
"30": {
|
||||||
|
"melee_attack": "Possible Lethal or Vital Strike or Add D20E to Attack",
|
||||||
|
"ranged_attack": "Possible Lethal or Vital Strike or Add D20E to Attack",
|
||||||
|
"melee_defense": "Possible Flawless or Legendary Defense or Add D20E to Defense",
|
||||||
|
"arcane_spell_attack": "Possible Lethal or Vital Magical Strike or Add D20E to Spell Attack",
|
||||||
|
"arcane_spell_defense": "Possible Spell Catastrophe or adds D20E to Spell Defense",
|
||||||
|
"skill_rolls": "Skill Succeeds Regardless of Opposing Roll / Success at highest level / Matching 30s cancel each other out"
|
||||||
|
},
|
||||||
|
"29": {
|
||||||
|
"melee_attack": "Gain 1 Grit",
|
||||||
|
"ranged_attack": "Gain 1 Grit",
|
||||||
|
"melee_defense": "Gain 1 Grit",
|
||||||
|
"arcane_spell_attack": "Gain 1 Grit",
|
||||||
|
"arcane_spell_defense": "Gain 1 Grit",
|
||||||
|
"skill_rolls": "Gain 1 Grit"
|
||||||
|
},
|
||||||
|
"28": {
|
||||||
|
"melee_attack": "Shield Destruction",
|
||||||
|
"ranged_attack": "empty",
|
||||||
|
"melee_defense": "empty",
|
||||||
|
"arcane_spell_attack": "empty",
|
||||||
|
"arcane_spell_defense": "empty",
|
||||||
|
"skill_rolls": "empty"
|
||||||
|
},
|
||||||
|
"27": {
|
||||||
|
"melee_attack": "Granted D6 (1-6) Attack Modifier for This Melee Attack",
|
||||||
|
"ranged_attack": "Granted D6 (1-6) Attack Modifier for This Ranged Attack",
|
||||||
|
"melee_defense": "Granted 1 Luck dice for Use in This Combat Only",
|
||||||
|
"arcane_spell_attack": "No Spell Lethargy (the Aether Approves)",
|
||||||
|
"arcane_spell_defense": "Caster Suffers Severe pain and will be under a flash of pain for 1D6E seconds",
|
||||||
|
"skill_rolls": "Granted D6 (1-6) Skill Modifier for this Skill Attempt"
|
||||||
|
},
|
||||||
|
"26": {
|
||||||
|
"melee_attack": "Shield Destruction",
|
||||||
|
"ranged_attack": "empty",
|
||||||
|
"melee_defense": "empty",
|
||||||
|
"arcane_spell_attack": "empty",
|
||||||
|
"arcane_spell_defense": "empty",
|
||||||
|
"skill_rolls": "empty"
|
||||||
|
},
|
||||||
|
"25": {
|
||||||
|
"melee_attack": "Bleed, Knock-Back on Hit",
|
||||||
|
"ranged_attack": "Bleed",
|
||||||
|
"melee_defense": "Kick, Punch or Shield Bash",
|
||||||
|
"arcane_spell_attack": "empty",
|
||||||
|
"arcane_spell_defense": "empty",
|
||||||
|
"skill_rolls": "Add 1 to Skill Roll"
|
||||||
|
},
|
||||||
|
"21": {
|
||||||
|
"melee_attack": "Hit Inflicts Flash of Pain 1D6E seconds",
|
||||||
|
"ranged_attack": "Hit Inflicts Flash of Pain 1D6E seconds",
|
||||||
|
"melee_defense": "Defender Recovers or ignores any flash of pain",
|
||||||
|
"arcane_spell_attack": "Magical Damage inflicts Flash of pain 1D6E seconds",
|
||||||
|
"arcane_spell_defense": "Caster Suffers Severe pain and will be under a flash of pain for 1D6E seconds",
|
||||||
|
"skill_rolls": "empty"
|
||||||
|
},
|
||||||
|
"20": {
|
||||||
|
"melee_attack": "Possible Vicious Strike. Bleed, Knock-back on Hit",
|
||||||
|
"ranged_attack": "Possible Vicious Strike. Bleeding wound inflicted on hit.",
|
||||||
|
"melee_defense": "Possible 20/20 defense (avoids Any Attack Except a Lethal Strike). Grants a Kick, Punch or Shield Bash counter",
|
||||||
|
"arcane_spell_attack": "Possible Vicious Application of a Magical Attack",
|
||||||
|
"arcane_spell_defense": "Possible 20/20 Spell defense (Saves Against Any Magical Attack Except a Lethal Magical Strike)",
|
||||||
|
"skill_rolls": "20 Added to Skill Roll"
|
||||||
|
},
|
||||||
|
"15": {
|
||||||
|
"melee_attack": "Bleed, Knock-back on Hit",
|
||||||
|
"ranged_attack": "Bleed",
|
||||||
|
"melee_defense": "Kick, Punch or Shield Bash",
|
||||||
|
"arcane_spell_attack": "empty",
|
||||||
|
"arcane_spell_defense": "empty",
|
||||||
|
"skill_rolls": "Add 1 to Skill Roll"
|
||||||
|
},
|
||||||
|
"13": {
|
||||||
|
"melee_attack": "empty",
|
||||||
|
"ranged_attack": "empty",
|
||||||
|
"melee_defense": "empty",
|
||||||
|
"arcane_spell_attack": "empty",
|
||||||
|
"arcane_spell_defense": "empty",
|
||||||
|
"skill_rolls": "empty"
|
||||||
|
},
|
||||||
|
"11": {
|
||||||
|
"melee_attack": "Flurry Attack or Hit to Miss",
|
||||||
|
"ranged_attack": "Roll 2x Damage Dice",
|
||||||
|
"melee_defense": "empty",
|
||||||
|
"arcane_spell_attack": "empty",
|
||||||
|
"arcane_spell_defense": "empty",
|
||||||
|
"skill_rolls": "empty"
|
||||||
|
},
|
||||||
|
"10": {
|
||||||
|
"melee_attack": "Bleed, Knock-back on Hit",
|
||||||
|
"ranged_attack": "Bleed",
|
||||||
|
"melee_defense": "Kick, Punch or Shield Bash",
|
||||||
|
"arcane_spell_attack": "empty",
|
||||||
|
"arcane_spell_defense": "empty",
|
||||||
|
"skill_rolls": "Add 1 to Skill Roll"
|
||||||
|
},
|
||||||
|
"8": {
|
||||||
|
"melee_attack": "Mulligan, Can Choose to Re-roll This Attack",
|
||||||
|
"ranged_attack": "Mulligan, Can Choose to Re-Roll This Attack",
|
||||||
|
"melee_defense": "Mulligan, Can Choose to Re-Roll This Defense",
|
||||||
|
"arcane_spell_attack": "Mulligan, Can Re-Roll This Spell Attack",
|
||||||
|
"arcane_spell_defense": "Mulligan, Can Re-Roll This Spell Defense",
|
||||||
|
"skill_rolls": "Mulligan, Can Re-Roll This Skill roll"
|
||||||
|
},
|
||||||
|
"7": {
|
||||||
|
"melee_attack": "Flurry Attack on Hit or Miss",
|
||||||
|
"ranged_attack": "Roll 2x Double Damage Dice",
|
||||||
|
"melee_defense": "empty",
|
||||||
|
"arcane_spell_attack": "empty",
|
||||||
|
"arcane_spell_defense": "empty",
|
||||||
|
"skill_rolls": "empty"
|
||||||
|
},
|
||||||
|
"5": {
|
||||||
|
"melee_attack": "Bleed, Knock-back on Hit",
|
||||||
|
"ranged_attack": "Bleed",
|
||||||
|
"melee_defense": "Kick, Punch, or Shield Bash",
|
||||||
|
"arcane_spell_attack": "empty",
|
||||||
|
"arcane_spell_defense": "empty",
|
||||||
|
"skill_rolls": "Add 1 to Skill Roll"
|
||||||
|
},
|
||||||
|
"3": {
|
||||||
|
"melee_attack": "Triple Damage",
|
||||||
|
"ranged_attack": "Triple Damage",
|
||||||
|
"melee_defense": "DR Tripled including Shield",
|
||||||
|
"arcane_spell_attack": "Triple Damage on Spell Damage",
|
||||||
|
"arcane_spell_defense": "D12 Added to Spell Defense Modifier",
|
||||||
|
"skill_rolls": "empty"
|
||||||
|
},
|
||||||
|
"2": {
|
||||||
|
"melee_attack": "Double Damage",
|
||||||
|
"ranged_attack": "Double Damage",
|
||||||
|
"melee_defense": "DR Doubled including Shield",
|
||||||
|
"arcane_spell_attack": "Double Damage on Spell Damage",
|
||||||
|
"arcane_spell_defense": "D6 Added to Spell Defense Modifier",
|
||||||
|
"skill_rolls": "empty"
|
||||||
|
},
|
||||||
|
"1": {
|
||||||
|
"melee_attack": "empty",
|
||||||
|
"ranged_attack": "Possible Fumble Ranged ammo is broken unrecoverable",
|
||||||
|
"melee_defense": "empty",
|
||||||
|
"arcane_spell_attack": "Possible Spell Calamity or Catastrophe",
|
||||||
|
"arcane_spell_defense": "empty",
|
||||||
|
"skill_rolls": "empty"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"definitions": {
|
||||||
|
"flash_of_pain": "Causes the victim to defend with disfavor. They can only walk and cannot attack, cast spells, call miracles or perform skills.",
|
||||||
|
"shield_destruction_condition": "Occurs only if damage exceeds the shields DR."
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,3 +2,4 @@ export { default as LethalFantasyActor } from "./actor.mjs"
|
|||||||
export { default as LethalFantasyItem } from "./item.mjs"
|
export { default as LethalFantasyItem } from "./item.mjs"
|
||||||
export { default as LethalFantasyRoll } from "./roll.mjs"
|
export { default as LethalFantasyRoll } from "./roll.mjs"
|
||||||
export { default as LethalFantasyChatMessage } from "./chat-message.mjs"
|
export { default as LethalFantasyChatMessage } from "./chat-message.mjs"
|
||||||
|
export { default as D30Roll } from "./d30-roll.mjs"
|
||||||
|
|||||||
@@ -153,8 +153,8 @@ export default class LethalFantasyActor extends Actor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* *************************************************/
|
/* *************************************************/
|
||||||
async prepareRoll(rollType, rollKey, rollDice) {
|
async prepareRoll(rollType, rollKey, rollDice, defenderId) {
|
||||||
console.log("Preparing roll", rollType, rollKey, rollDice)
|
console.log("Preparing roll", rollType, rollKey, rollDice, defenderId)
|
||||||
let rollTarget
|
let rollTarget
|
||||||
switch (rollType) {
|
switch (rollType) {
|
||||||
case "granted":
|
case "granted":
|
||||||
@@ -268,7 +268,7 @@ export default class LethalFantasyActor extends Actor {
|
|||||||
rollTarget.magicUser = this.system.biodata.magicUser
|
rollTarget.magicUser = this.system.biodata.magicUser
|
||||||
rollTarget.actorModifiers = foundry.utils.duplicate(this.system.modifiers)
|
rollTarget.actorModifiers = foundry.utils.duplicate(this.system.modifiers)
|
||||||
rollTarget.actorLevel = this.system.biodata.level
|
rollTarget.actorLevel = this.system.biodata.level
|
||||||
await this.system.roll(rollType, rollTarget)
|
await this.system.roll(rollType, rollTarget, defenderId)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
215
module/documents/d30-roll.mjs
Normal file
215
module/documents/d30-roll.mjs
Normal file
@@ -0,0 +1,215 @@
|
|||||||
|
/**
|
||||||
|
* Classe pour gérer les résultats du D30 dans Lethal Fantasy
|
||||||
|
*/
|
||||||
|
export default class D30Roll {
|
||||||
|
/**
|
||||||
|
* Table des résultats D30 chargée depuis le fichier JSON
|
||||||
|
* @type {Object}
|
||||||
|
*/
|
||||||
|
static resultsTable = null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Définitions des conditions spéciales
|
||||||
|
* @type {Object}
|
||||||
|
*/
|
||||||
|
static definitions = null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Types de jets supportés
|
||||||
|
* @type {Object}
|
||||||
|
*/
|
||||||
|
static ROLL_TYPES = {
|
||||||
|
MELEE_ATTACK: "melee_attack",
|
||||||
|
RANGED_ATTACK: "ranged_attack",
|
||||||
|
MELEE_DEFENSE: "melee_defense",
|
||||||
|
ARCANE_SPELL_ATTACK: "arcane_spell_attack",
|
||||||
|
ARCANE_SPELL_DEFENSE: "arcane_spell_defense",
|
||||||
|
SKILL_ROLLS: "skill_rolls"
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialise la classe en chargeant la table des résultats
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
static async initialize() {
|
||||||
|
try {
|
||||||
|
const response = await fetch("systems/fvtt-lethal-fantasy/module/config/d30_results_tables.json")
|
||||||
|
const data = await response.json()
|
||||||
|
|
||||||
|
this.resultsTable = data.d30_dice_results
|
||||||
|
this.definitions = data.definitions
|
||||||
|
|
||||||
|
console.log("D30Roll | D30 results table loaded successfully")
|
||||||
|
} catch (error) {
|
||||||
|
console.error("D30Roll | Error loading D30 table:", error)
|
||||||
|
ui.notifications.error("Unable to load D30 results table")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Récupère le résultat d'un jet de D30
|
||||||
|
* @param {number} diceValue La valeur du dé (1-30)
|
||||||
|
* @param {string} rollType Le type de jet externe (ex: "weapon-attack", "spell-attack", etc.)
|
||||||
|
* @param {Object} weapon L'arme ou l'objet utilisé (optionnel, nécessaire pour certains types)
|
||||||
|
* @returns {string|null} Le résultat correspondant ou null si vide/non trouvé
|
||||||
|
*/
|
||||||
|
static getResult(diceValue, rollType, weapon = null) {
|
||||||
|
if (!this.resultsTable) {
|
||||||
|
console.warn("D30Roll | Results table is not initialized. Call D30Roll.initialize() first.")
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validation des paramètres
|
||||||
|
if (diceValue < 1 || diceValue > 30) {
|
||||||
|
console.warn(`D30Roll | Invalid dice value: ${diceValue}. Must be between 1 and 30.`)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert external rollType to internal rollType
|
||||||
|
const internalType = this.convertToInternalType(rollType, weapon)
|
||||||
|
|
||||||
|
if (!internalType) {
|
||||||
|
console.warn(`D30Roll | Could not convert roll type: ${rollType}`)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Object.values(this.ROLL_TYPES).includes(internalType)) {
|
||||||
|
console.warn(`D30Roll | Invalid internal roll type: ${internalType}`)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const resultEntry = this.resultsTable[diceValue]
|
||||||
|
if (!resultEntry) {
|
||||||
|
console.warn(`D30Roll | No entry found for value ${diceValue}`)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = resultEntry[internalType]
|
||||||
|
|
||||||
|
// Retourne null si le résultat est "empty"
|
||||||
|
if (result === "empty" || !result) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convertit un rollType externe en rollType interne
|
||||||
|
* @param {string} externalType Le type de jet externe (ex: "weapon-attack")
|
||||||
|
* @param {Object} weapon L'arme ou l'objet utilisé (optionnel)
|
||||||
|
* @returns {string|null} Le type interne correspondant ou null
|
||||||
|
*/
|
||||||
|
static convertToInternalType(externalType, weapon = null) {
|
||||||
|
// Attack types - need weapon to determine if melee or ranged
|
||||||
|
if (externalType === "weapon-attack") {
|
||||||
|
if (!weapon) {
|
||||||
|
console.warn("D30Roll | Weapon object required for weapon-attack type")
|
||||||
|
return this.ROLL_TYPES.MELEE_ATTACK // Default to melee
|
||||||
|
}
|
||||||
|
return weapon.system?.weaponType === "ranged"
|
||||||
|
? this.ROLL_TYPES.RANGED_ATTACK
|
||||||
|
: this.ROLL_TYPES.MELEE_ATTACK
|
||||||
|
}
|
||||||
|
|
||||||
|
// Monster attacks - default to melee
|
||||||
|
if (externalType === "monster-attack") {
|
||||||
|
// Check if weapon object has range information
|
||||||
|
if (weapon?.system?.weaponType === "ranged") {
|
||||||
|
return this.ROLL_TYPES.RANGED_ATTACK
|
||||||
|
}
|
||||||
|
return this.ROLL_TYPES.MELEE_ATTACK
|
||||||
|
}
|
||||||
|
|
||||||
|
// Defense types
|
||||||
|
if (externalType === "weapon-defense" || externalType === "monster-defense") {
|
||||||
|
return this.ROLL_TYPES.MELEE_DEFENSE
|
||||||
|
}
|
||||||
|
|
||||||
|
// Spell types
|
||||||
|
if (externalType === "spell-attack" || externalType === "spell" || externalType === "spell-power") {
|
||||||
|
return this.ROLL_TYPES.ARCANE_SPELL_ATTACK
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skill types
|
||||||
|
if (externalType === "skill" || externalType === "monster-skill" ||
|
||||||
|
externalType === "save" || externalType === "challenge") {
|
||||||
|
return this.ROLL_TYPES.SKILL_ROLLS
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no match, return null
|
||||||
|
console.warn(`D30Roll | Unknown external roll type: ${externalType}`)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Récupère toutes les informations pour une valeur de dé donnée
|
||||||
|
* @param {number} diceValue La valeur du dé (1-30)
|
||||||
|
* @returns {Object|null} Tous les résultats pour cette valeur ou null
|
||||||
|
*/
|
||||||
|
static getAllResultsForValue(diceValue) {
|
||||||
|
if (!this.resultsTable) {
|
||||||
|
console.warn("D30Roll | Results table is not initialized.")
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (diceValue < 1 || diceValue > 30) {
|
||||||
|
console.warn(`D30Roll | Invalid dice value: ${diceValue}`)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.resultsTable[diceValue]
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Récupère la définition d'une condition spéciale
|
||||||
|
* @param {string} definitionKey La clé de la définition (ex: "flash_of_pain")
|
||||||
|
* @returns {string|null} La définition ou null
|
||||||
|
*/
|
||||||
|
static getDefinition(definitionKey) {
|
||||||
|
if (!this.definitions) {
|
||||||
|
console.warn("D30Roll | Definitions are not initialized.")
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.definitions[definitionKey] || null
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vérifie si un résultat est vide
|
||||||
|
* @param {string} result Le résultat à vérifier
|
||||||
|
* @returns {boolean} True si le résultat est vide
|
||||||
|
*/
|
||||||
|
static isEmptyResult(result) {
|
||||||
|
return !result || result === "empty"
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Récupère un résultat formaté pour l'affichage
|
||||||
|
* @param {number} diceValue La valeur du dé (1-30)
|
||||||
|
* @param {string} rollType Le type de jet externe
|
||||||
|
* @param {Object} weapon L'arme ou l'objet utilisé (optionnel)
|
||||||
|
* @returns {Object} Un objet avec le résultat et des informations de formatage
|
||||||
|
*/
|
||||||
|
static getFormattedResult(diceValue, rollType, weapon = null) {
|
||||||
|
const result = this.getResult(diceValue, rollType, weapon)
|
||||||
|
const internalType = this.convertToInternalType(rollType, weapon)
|
||||||
|
|
||||||
|
return {
|
||||||
|
value: diceValue,
|
||||||
|
rollType: rollType,
|
||||||
|
internalType: internalType,
|
||||||
|
result: result,
|
||||||
|
isEmpty: this.isEmptyResult(result),
|
||||||
|
hasResult: !this.isEmptyResult(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vérifie si la table est chargée
|
||||||
|
* @returns {boolean} True si la table est chargée
|
||||||
|
*/
|
||||||
|
static isInitialized() {
|
||||||
|
return this.resultsTable !== null && this.definitions !== null
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import { SYSTEM } from "../config/system.mjs"
|
import { SYSTEM } from "../config/system.mjs"
|
||||||
import LethalFantasyUtils from "../utils.mjs"
|
import LethalFantasyUtils from "../utils.mjs"
|
||||||
|
import D30Roll from "./d30-roll.mjs"
|
||||||
|
|
||||||
export default class LethalFantasyRoll extends Roll {
|
export default class LethalFantasyRoll extends Roll {
|
||||||
/**
|
/**
|
||||||
@@ -92,6 +93,10 @@ export default class LethalFantasyRoll extends Roll {
|
|||||||
return this.options.D30result
|
return this.options.D30result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get D30message() {
|
||||||
|
return this.options.D30message
|
||||||
|
}
|
||||||
|
|
||||||
get badResult() {
|
get badResult() {
|
||||||
return this.options.badResult
|
return this.options.badResult
|
||||||
}
|
}
|
||||||
@@ -100,6 +105,10 @@ export default class LethalFantasyRoll extends Roll {
|
|||||||
return this.options.rollData
|
return this.options.rollData
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get defenderId() {
|
||||||
|
return this.options.defenderId
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prompt the user with a dialog to configure and execute a roll.
|
* Prompt the user with a dialog to configure and execute a roll.
|
||||||
*
|
*
|
||||||
@@ -544,6 +553,14 @@ export default class LethalFantasyRoll extends Roll {
|
|||||||
game.dice3d.showForRoll(rollD30, game.user, true)
|
game.dice3d.showForRoll(rollD30, game.user, true)
|
||||||
}
|
}
|
||||||
options.D30result = rollD30.total
|
options.D30result = rollD30.total
|
||||||
|
|
||||||
|
// Récupérer le message D30 correspondant
|
||||||
|
const d30Message = D30Roll.getResult(
|
||||||
|
rollD30.total,
|
||||||
|
options.rollType,
|
||||||
|
options.rollTarget?.weapon
|
||||||
|
)
|
||||||
|
options.D30message = d30Message
|
||||||
}
|
}
|
||||||
|
|
||||||
let rollTotal = 0
|
let rollTotal = 0
|
||||||
@@ -599,8 +616,10 @@ export default class LethalFantasyRoll extends Roll {
|
|||||||
rollBase.options.rollTarget = options.rollTarget
|
rollBase.options.rollTarget = options.rollTarget
|
||||||
rollBase.options.titleFormula = titleFormula
|
rollBase.options.titleFormula = titleFormula
|
||||||
rollBase.options.D30result = options.D30result
|
rollBase.options.D30result = options.D30result
|
||||||
|
rollBase.options.D30message = options.D30message
|
||||||
rollBase.options.badResult = badResult
|
rollBase.options.badResult = badResult
|
||||||
rollBase.options.rollData = foundry.utils.duplicate(rollData)
|
rollBase.options.rollData = foundry.utils.duplicate(rollData)
|
||||||
|
rollBase.options.defenderId = options.defenderId
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A hook event that fires after the roll has been made.
|
* A hook event that fires after the roll has been made.
|
||||||
@@ -862,8 +881,17 @@ export default class LethalFantasyRoll extends Roll {
|
|||||||
let toCompare = Math.min(currentAction.progressionCount, max)
|
let toCompare = Math.min(currentAction.progressionCount, max)
|
||||||
if (roll.total <= toCompare) {
|
if (roll.total <= toCompare) {
|
||||||
// Notify that the player can act now with a chat message
|
// Notify that the player can act now with a chat message
|
||||||
let message = game.i18n.format("LETHALFANTASY.Notifications.messageLethargyOK", { name: combatant.actor.name, spellName: currentAction.name, roll: roll.total })
|
const messageContent = await foundry.applications.handlebars.renderTemplate(
|
||||||
ChatMessage.create({ content: message, speaker: ChatMessage.getSpeaker({ actor: combatant.actor }) })
|
"systems/fvtt-lethal-fantasy/templates/progression-message.hbs",
|
||||||
|
{
|
||||||
|
success: true,
|
||||||
|
actorName: combatant.actor.name,
|
||||||
|
weaponName: currentAction.name,
|
||||||
|
rollResult: roll.total,
|
||||||
|
isLethargy: true
|
||||||
|
}
|
||||||
|
)
|
||||||
|
ChatMessage.create({ content: messageContent, speaker: ChatMessage.getSpeaker({ actor: combatant.actor }) })
|
||||||
// Update the combatant progression count
|
// Update the combatant progression count
|
||||||
await combatant.setFlag(SYSTEM.id, "currentAction", "")
|
await combatant.setFlag(SYSTEM.id, "currentAction", "")
|
||||||
// Display the action selection window again
|
// Display the action selection window again
|
||||||
@@ -872,8 +900,18 @@ export default class LethalFantasyRoll extends Roll {
|
|||||||
// Notify that the player cannot act now with a chat message
|
// Notify that the player cannot act now with a chat message
|
||||||
currentAction.progressionCount += 1
|
currentAction.progressionCount += 1
|
||||||
await combatant.setFlag(SYSTEM.id, "currentAction", foundry.utils.duplicate(currentAction))
|
await combatant.setFlag(SYSTEM.id, "currentAction", foundry.utils.duplicate(currentAction))
|
||||||
let message = game.i18n.format("LETHALFANTASY.Notifications.messageLethargyKO", { name: combatant.actor.name, spellName: currentAction.name, roll: roll.total })
|
const messageContent = await foundry.applications.handlebars.renderTemplate(
|
||||||
ChatMessage.create({ content: message, speaker: ChatMessage.getSpeaker({ actor: combatant.actor }) })
|
"systems/fvtt-lethal-fantasy/templates/progression-message.hbs",
|
||||||
|
{
|
||||||
|
success: false,
|
||||||
|
actorName: combatant.actor.name,
|
||||||
|
weaponName: currentAction.name,
|
||||||
|
rollResult: roll.total,
|
||||||
|
progressionCount: currentAction.progressionCount,
|
||||||
|
isLethargy: true
|
||||||
|
}
|
||||||
|
)
|
||||||
|
ChatMessage.create({ content: messageContent, speaker: ChatMessage.getSpeaker({ actor: combatant.actor }) })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -919,16 +957,33 @@ export default class LethalFantasyRoll extends Roll {
|
|||||||
|
|
||||||
if (roll.total <= max) {
|
if (roll.total <= max) {
|
||||||
// Notify that the player can act now with a chat message
|
// Notify that the player can act now with a chat message
|
||||||
let message = game.i18n.format("LETHALFANTASY.Notifications.messageProgressionOK", { isMonster, name: combatant.actor.name, weapon: currentAction.name, roll: roll.total })
|
const messageContent = await foundry.applications.handlebars.renderTemplate(
|
||||||
ChatMessage.create({ content: message, speaker: ChatMessage.getSpeaker({ actor: combatant.actor }) })
|
"systems/fvtt-lethal-fantasy/templates/progression-message.hbs",
|
||||||
|
{
|
||||||
|
success: true,
|
||||||
|
actorName: combatant.actor.name,
|
||||||
|
weaponName: currentAction.name,
|
||||||
|
rollResult: roll.total
|
||||||
|
}
|
||||||
|
)
|
||||||
|
ChatMessage.create({ content: messageContent, speaker: ChatMessage.getSpeaker({ actor: combatant.actor }) })
|
||||||
await combatant.setFlag(SYSTEM.id, "currentAction", "")
|
await combatant.setFlag(SYSTEM.id, "currentAction", "")
|
||||||
combatant.actor.prepareRoll(currentAction.type === "weapon" ? "weapon-attack" : "spell-attack", currentAction._id)
|
combatant.actor.prepareRoll(currentAction.type === "weapon" ? "weapon-attack" : "spell-attack", currentAction._id)
|
||||||
} else {
|
} else {
|
||||||
// Notify that the player cannot act now with a chat message
|
// Notify that the player cannot act now with a chat message
|
||||||
currentAction.progressionCount += 1
|
currentAction.progressionCount += 1
|
||||||
combatant.setFlag(SYSTEM.id, "currentAction", foundry.utils.duplicate(currentAction))
|
combatant.setFlag(SYSTEM.id, "currentAction", foundry.utils.duplicate(currentAction))
|
||||||
let message = game.i18n.format("LETHALFANTASY.Notifications.messageProgressionKO", { isMonster, name: combatant.actor.name, weapon: currentAction.name, roll: roll.total })
|
const messageContent = await foundry.applications.handlebars.renderTemplate(
|
||||||
ChatMessage.create({ content: message, speaker: ChatMessage.getSpeaker({ actor: combatant.actor }) })
|
"systems/fvtt-lethal-fantasy/templates/progression-message.hbs",
|
||||||
|
{
|
||||||
|
success: false,
|
||||||
|
actorName: combatant.actor.name,
|
||||||
|
weaponName: currentAction.name,
|
||||||
|
rollResult: roll.total,
|
||||||
|
progressionCount: currentAction.progressionCount
|
||||||
|
}
|
||||||
|
)
|
||||||
|
ChatMessage.create({ content: messageContent, speaker: ChatMessage.getSpeaker({ actor: combatant.actor }) })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1132,10 +1187,28 @@ export default class LethalFantasyRoll extends Roll {
|
|||||||
async _getChatCardData(isPrivate) {
|
async _getChatCardData(isPrivate) {
|
||||||
// Générer la liste des combatants de la scène
|
// Générer la liste des combatants de la scène
|
||||||
let combatants = []
|
let combatants = []
|
||||||
if (game?.combat?.combatants && this.rollData?.isDamage) {
|
let isAttack = this.type === "weapon-attack" || this.type === "monster-attack" || this.type === "spell-attack" || this.type === "miracle-attack"
|
||||||
|
if (this.rollData?.isDamage || isAttack) {
|
||||||
|
// D'abord, ajouter les combattants du combat actif
|
||||||
|
if (game?.combat?.combatants) {
|
||||||
for (let c of game.combat.combatants) {
|
for (let c of game.combat.combatants) {
|
||||||
if (c.actorId !== this.actorId) {
|
if (c.actorId !== this.actorId) {
|
||||||
combatants.push({ id: c.id, name: c.name })
|
combatants.push({ id: c.id, name: c.name, tokenId: c.token.id })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensuite, ajouter tous les tokens de la scène active qui ne sont pas déjà dans la liste
|
||||||
|
if (canvas?.scene?.tokens) {
|
||||||
|
const existingTokenIds = new Set(combatants.map(c => c.tokenId))
|
||||||
|
for (let token of canvas.scene.tokens) {
|
||||||
|
if (token.actorId !== this.actorId && !existingTokenIds.has(token.id)) {
|
||||||
|
combatants.push({
|
||||||
|
id: token.id,
|
||||||
|
name: token.name,
|
||||||
|
tokenId: token.id
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1184,11 +1257,16 @@ export default class LethalFantasyRoll extends Roll {
|
|||||||
targetName: this.targetName,
|
targetName: this.targetName,
|
||||||
targetArmor: this.targetArmor,
|
targetArmor: this.targetArmor,
|
||||||
D30result: this.D30result,
|
D30result: this.D30result,
|
||||||
|
D30message: this.D30message,
|
||||||
badResult: this.badResult,
|
badResult: this.badResult,
|
||||||
rollData: this.rollData,
|
rollData: this.rollData,
|
||||||
isPrivate: isPrivate,
|
isPrivate: isPrivate,
|
||||||
combatants: combatants,
|
combatants: combatants,
|
||||||
weaponDamageOptions: weaponDamageOptions
|
weaponDamageOptions: weaponDamageOptions,
|
||||||
|
isAttack: isAttack,
|
||||||
|
defenderId: this.defenderId,
|
||||||
|
// Vérifier si l'utilisateur peut sélectionner une cible (est GM ou possède l'acteur)
|
||||||
|
canSelectTarget: game.user.isGM || game.actors.get(this.actorId)?.testUserPermission(game.user, "OWNER")
|
||||||
}
|
}
|
||||||
cardData.cssClass = cardData.css.join(" ")
|
cardData.cssClass = cardData.css.join(" ")
|
||||||
cardData.tooltip = isPrivate ? "" : await this.getTooltip()
|
cardData.tooltip = isPrivate ? "" : await this.getTooltip()
|
||||||
|
|||||||
@@ -273,7 +273,7 @@ export default class LethalFantasyCharacter extends foundry.abstract.TypeDataMod
|
|||||||
* @param {"="|"+"|"++"|"-"|"--"} rollAdvantage If there is an avantage (+), a disadvantage (-), a double advantage (++), a double disadvantage (--) or a normal roll (=).
|
* @param {"="|"+"|"++"|"-"|"--"} rollAdvantage If there is an avantage (+), a disadvantage (-), a double advantage (++), a double disadvantage (--) or a normal roll (=).
|
||||||
* @returns {Promise<null>} - A promise that resolves to null if the roll is cancelled.
|
* @returns {Promise<null>} - A promise that resolves to null if the roll is cancelled.
|
||||||
*/
|
*/
|
||||||
async roll(rollType, rollTarget) {
|
async roll(rollType, rollTarget, defenderId) {
|
||||||
const hasTarget = false
|
const hasTarget = false
|
||||||
let roll = await LethalFantasyRoll.prompt({
|
let roll = await LethalFantasyRoll.prompt({
|
||||||
rollType,
|
rollType,
|
||||||
@@ -282,7 +282,8 @@ export default class LethalFantasyCharacter extends foundry.abstract.TypeDataMod
|
|||||||
actorName: this.parent.name,
|
actorName: this.parent.name,
|
||||||
actorImage: this.parent.img,
|
actorImage: this.parent.img,
|
||||||
hasTarget,
|
hasTarget,
|
||||||
target: false
|
target: false,
|
||||||
|
defenderId
|
||||||
})
|
})
|
||||||
if (!roll) return null
|
if (!roll) return null
|
||||||
|
|
||||||
@@ -311,6 +312,13 @@ export default class LethalFantasyCharacter extends foundry.abstract.TypeDataMod
|
|||||||
}
|
}
|
||||||
|
|
||||||
async rollProgressionDice(combatId, combatantId, rollProgressionCount) {
|
async rollProgressionDice(combatId, combatantId, rollProgressionCount) {
|
||||||
|
let combatant = game.combats.get(combatId)?.combatants?.get(combatantId)
|
||||||
|
|
||||||
|
// Don't roll if the combatant is defeated
|
||||||
|
if (combatant?.isDefeated) {
|
||||||
|
ui.notifications.warn(`${this.parent.name} is defeated and cannot attack.`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Get all weapons from the actor
|
// Get all weapons from the actor
|
||||||
let weapons = this.parent.items.filter(i => i.type === "weapon" && i.system.weaponType === "melee")
|
let weapons = this.parent.items.filter(i => i.type === "weapon" && i.system.weaponType === "melee")
|
||||||
|
|||||||
@@ -122,6 +122,10 @@ export default class LethalFantasyMonster extends foundry.abstract.TypeDataModel
|
|||||||
shieldDamageReduction: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
|
shieldDamageReduction: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
|
||||||
shieldDefenseDice: new fields.StringField({ required: true, nullable: false, initial: "d4" })
|
shieldDefenseDice: new fields.StringField({ required: true, nullable: false, initial: "d4" })
|
||||||
})
|
})
|
||||||
|
schema.combatHTH = new fields.SchemaField({
|
||||||
|
attack1: attackField("1"),
|
||||||
|
attack2: attackField("2")
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
return schema
|
return schema
|
||||||
@@ -137,7 +141,7 @@ export default class LethalFantasyMonster extends foundry.abstract.TypeDataModel
|
|||||||
* @param {"="|"+"|"++"|"-"|"--"} rollAdvantage If there is an avantage (+), a disadvantage (-), a double advantage (++), a double disadvantage (--) or a normal roll (=).
|
* @param {"="|"+"|"++"|"-"|"--"} rollAdvantage If there is an avantage (+), a disadvantage (-), a double advantage (++), a double disadvantage (--) or a normal roll (=).
|
||||||
* @returns {Promise<null>} - A promise that resolves to null if the roll is cancelled.
|
* @returns {Promise<null>} - A promise that resolves to null if the roll is cancelled.
|
||||||
*/
|
*/
|
||||||
async roll(rollType, rollTarget) {
|
async roll(rollType, rollTarget, defenderId = undefined) {
|
||||||
const hasTarget = false
|
const hasTarget = false
|
||||||
let roll = await LethalFantasyRoll.prompt({
|
let roll = await LethalFantasyRoll.prompt({
|
||||||
rollType,
|
rollType,
|
||||||
@@ -146,14 +150,15 @@ export default class LethalFantasyMonster extends foundry.abstract.TypeDataModel
|
|||||||
actorName: this.parent.name,
|
actorName: this.parent.name,
|
||||||
actorImage: this.parent.img,
|
actorImage: this.parent.img,
|
||||||
hasTarget,
|
hasTarget,
|
||||||
target: false
|
target: false,
|
||||||
|
defenderId
|
||||||
})
|
})
|
||||||
if (!roll) return null
|
if (!roll) return null
|
||||||
|
|
||||||
await roll.toMessage({}, { rollMode: roll.options.rollMode })
|
await roll.toMessage({}, { rollMode: roll.options.rollMode })
|
||||||
}
|
}
|
||||||
|
|
||||||
async prepareMonsterRoll(rollType, rollKey, rollDice = undefined, tokenId = undefined, damageModifier = undefined) {
|
async prepareMonsterRoll(rollType, rollKey, rollDice = undefined, tokenId = undefined, damageModifier = undefined, defenderId = undefined) {
|
||||||
let rollTarget
|
let rollTarget
|
||||||
switch (rollType) {
|
switch (rollType) {
|
||||||
case "monster-attack":
|
case "monster-attack":
|
||||||
@@ -166,6 +171,18 @@ export default class LethalFantasyMonster extends foundry.abstract.TypeDataModel
|
|||||||
rollTarget.damageModifier = damageModifier
|
rollTarget.damageModifier = damageModifier
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
|
case "monster-attack-hth":
|
||||||
|
case "monster-defense-hth":
|
||||||
|
case "monster-damage-hth":
|
||||||
|
rollTarget = foundry.utils.duplicate(this.combatHTH[rollKey])
|
||||||
|
rollTarget.rollKey = rollKey
|
||||||
|
// Si damageModifier est fourni (depuis le chat), l'utiliser au lieu de celui de la fiche
|
||||||
|
if (damageModifier !== undefined && rollType === "monster-damage-hth") {
|
||||||
|
rollTarget.damageModifier = damageModifier
|
||||||
|
}
|
||||||
|
// Convertir le type de roll pour utiliser les mêmes handlers que les attaques normales
|
||||||
|
rollType = rollType.replace("-hth", "")
|
||||||
|
break
|
||||||
case "monster-skill":
|
case "monster-skill":
|
||||||
rollTarget = foundry.utils.duplicate(this.resists[rollKey])
|
rollTarget = foundry.utils.duplicate(this.resists[rollKey])
|
||||||
rollTarget.rollKey = rollKey
|
rollTarget.rollKey = rollKey
|
||||||
@@ -237,7 +254,7 @@ export default class LethalFantasyMonster extends foundry.abstract.TypeDataModel
|
|||||||
if (rollTarget) {
|
if (rollTarget) {
|
||||||
rollTarget.tokenId = tokenId
|
rollTarget.tokenId = tokenId
|
||||||
console.log(rollTarget)
|
console.log(rollTarget)
|
||||||
await this.roll(rollType, rollTarget)
|
await this.roll(rollType, rollTarget, defenderId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -261,6 +278,13 @@ export default class LethalFantasyMonster extends foundry.abstract.TypeDataModel
|
|||||||
}
|
}
|
||||||
|
|
||||||
async rollProgressionDice(combatId, combatantId) {
|
async rollProgressionDice(combatId, combatantId) {
|
||||||
|
let combatant = game.combats.get(combatId)?.combatants?.get(combatantId)
|
||||||
|
|
||||||
|
// Don't roll if the combatant is defeated
|
||||||
|
if (combatant?.isDefeated) {
|
||||||
|
ui.notifications.warn(`${this.parent.name} is defeated and cannot attack.`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const rollModes = foundry.utils.duplicate(CONFIG.Dice.rollModes)
|
const rollModes = foundry.utils.duplicate(CONFIG.Dice.rollModes)
|
||||||
const fieldRollMode = new foundry.data.fields.StringField({
|
const fieldRollMode = new foundry.data.fields.StringField({
|
||||||
@@ -271,7 +295,6 @@ export default class LethalFantasyMonster extends foundry.abstract.TypeDataModel
|
|||||||
|
|
||||||
let roll = new Roll("1D12")
|
let roll = new Roll("1D12")
|
||||||
await roll.evaluate()
|
await roll.evaluate()
|
||||||
let combatant = game.combats.get(combatId)?.combatants?.get(combatantId)
|
|
||||||
|
|
||||||
let msg = await roll.toMessage({ flavor: `Progression Roll for ${this.parent.name}` })
|
let msg = await roll.toMessage({ flavor: `Progression Roll for ${this.parent.name}` })
|
||||||
if (game?.dice3d) {
|
if (game?.dice3d) {
|
||||||
@@ -283,8 +306,16 @@ export default class LethalFantasyMonster extends foundry.abstract.TypeDataModel
|
|||||||
let attack = this.attacks[key]
|
let attack = this.attacks[key]
|
||||||
if (attack.enabled && attack.attackScore > 0 && attack.attackScore === roll.total) {
|
if (attack.enabled && attack.attackScore > 0 && attack.attackScore === roll.total) {
|
||||||
hasAttack = true
|
hasAttack = true
|
||||||
let message = game.i18n.format("LETHALFANTASY.Notifications.messageProgressionOKMonster", { isMonster: true, name: this.parent.name, weapon: attack.name, roll: roll.total })
|
const messageContent = await foundry.applications.handlebars.renderTemplate(
|
||||||
ChatMessage.create({ content: message, speaker: ChatMessage.getSpeaker({ actor: this.parent }) })
|
"systems/fvtt-lethal-fantasy/templates/progression-message.hbs",
|
||||||
|
{
|
||||||
|
success: true,
|
||||||
|
actorName: this.parent.name,
|
||||||
|
weaponName: attack.name,
|
||||||
|
rollResult: roll.total
|
||||||
|
}
|
||||||
|
)
|
||||||
|
ChatMessage.create({ content: messageContent, speaker: ChatMessage.getSpeaker({ actor: this.parent }) })
|
||||||
let token = combatant?.token
|
let token = combatant?.token
|
||||||
this.prepareMonsterRoll("monster-attack", key, undefined, token?.id)
|
this.prepareMonsterRoll("monster-attack", key, undefined, token?.id)
|
||||||
if (token?.object) {
|
if (token?.object) {
|
||||||
@@ -293,9 +324,41 @@ export default class LethalFantasyMonster extends foundry.abstract.TypeDataModel
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Check Hand To Hand attacks as well
|
||||||
if (!hasAttack) {
|
if (!hasAttack) {
|
||||||
let message = game.i18n.format("LETHALFANTASY.Notifications.messageProgressionKOMonster", { isMonster: true, name: this.parent.name, roll: roll.total })
|
for (let key in this.combatHTH) {
|
||||||
ChatMessage.create({ content: message, speaker: ChatMessage.getSpeaker({ actor: this.parent }) })
|
let attack = this.combatHTH[key]
|
||||||
|
if (attack.enabled && attack.attackScore > 0 && attack.attackScore === roll.total) {
|
||||||
|
hasAttack = true
|
||||||
|
const messageContent = await foundry.applications.handlebars.renderTemplate(
|
||||||
|
"systems/fvtt-lethal-fantasy/templates/progression-message.hbs",
|
||||||
|
{
|
||||||
|
success: true,
|
||||||
|
actorName: this.parent.name,
|
||||||
|
weaponName: `${attack.name} (HTH)`,
|
||||||
|
rollResult: roll.total
|
||||||
|
}
|
||||||
|
)
|
||||||
|
ChatMessage.create({ content: messageContent, speaker: ChatMessage.getSpeaker({ actor: this.parent }) })
|
||||||
|
let token = combatant?.token
|
||||||
|
this.prepareMonsterRoll("monster-attack-hth", key, undefined, token?.id)
|
||||||
|
if (token?.object) {
|
||||||
|
token.object?.control({ releaseOthers: true });
|
||||||
|
return canvas.animatePan(token.object.center);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!hasAttack) {
|
||||||
|
const messageContent = await foundry.applications.handlebars.renderTemplate(
|
||||||
|
"systems/fvtt-lethal-fantasy/templates/progression-message.hbs",
|
||||||
|
{
|
||||||
|
success: false,
|
||||||
|
actorName: this.parent.name,
|
||||||
|
rollResult: roll.total
|
||||||
|
}
|
||||||
|
)
|
||||||
|
ChatMessage.create({ content: messageContent, speaker: ChatMessage.getSpeaker({ actor: this.parent }) })
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
521
module/utils.mjs
521
module/utils.mjs
@@ -1,3 +1,9 @@
|
|||||||
|
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 {
|
export default class LethalFantasyUtils {
|
||||||
|
|
||||||
@@ -22,6 +28,7 @@ export default class LethalFantasyUtils {
|
|||||||
static setHookListeners() {
|
static setHookListeners() {
|
||||||
|
|
||||||
Hooks.on('renderTokenHUD', async (hud, html, token) => {
|
Hooks.on('renderTokenHUD', async (hud, html, token) => {
|
||||||
|
// HP Loss Button (existing)
|
||||||
const lossHPButton = await foundry.applications.handlebars.renderTemplate('systems/fvtt-lethal-fantasy/templates/loss-hp-hud.hbs', {})
|
const lossHPButton = await foundry.applications.handlebars.renderTemplate('systems/fvtt-lethal-fantasy/templates/loss-hp-hud.hbs', {})
|
||||||
$(html).find('div.left').append(lossHPButton);
|
$(html).find('div.left').append(lossHPButton);
|
||||||
$(html).find('img.lethal-hp-loss-hud').click((event) => {
|
$(html).find('img.lethal-hp-loss-hud').click((event) => {
|
||||||
@@ -59,6 +66,45 @@ export default class LethalFantasyUtils {
|
|||||||
$(html).find('.hp-loss-wrap')[2].classList.add('hp-loss-hud-disabled');
|
$(html).find('.hp-loss-wrap')[2].classList.add('hp-loss-hud-disabled');
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// HP Gain Button (new)
|
||||||
|
const gainHPButton = await foundry.applications.handlebars.renderTemplate('systems/fvtt-lethal-fantasy/templates/gain-hp-hud.hbs', {})
|
||||||
|
$(html).find('div.left').append(gainHPButton);
|
||||||
|
$(html).find('img.lethal-hp-gain-hud').click((event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
let hpMenu = $(html).find('.hp-gain-wrap')[0]
|
||||||
|
if (hpMenu.classList.contains("hp-gain-hud-disabled")) {
|
||||||
|
$(html).find('.hp-gain-wrap')[0].classList.add('hp-gain-hud-active');
|
||||||
|
$(html).find('.hp-gain-wrap')[0].classList.remove('hp-gain-hud-disabled');
|
||||||
|
$(html).find('.hp-gain-wrap')[1].classList.add('hp-gain-hud-active');
|
||||||
|
$(html).find('.hp-gain-wrap')[1].classList.remove('hp-gain-hud-disabled');
|
||||||
|
$(html).find('.hp-gain-wrap')[2].classList.add('hp-gain-hud-active');
|
||||||
|
$(html).find('.hp-gain-wrap')[2].classList.remove('hp-gain-hud-disabled');
|
||||||
|
} else {
|
||||||
|
$(html).find('.hp-gain-wrap')[0].classList.remove('hp-gain-hud-active');
|
||||||
|
$(html).find('.hp-gain-wrap')[0].classList.add('hp-gain-hud-disabled');
|
||||||
|
$(html).find('.hp-gain-wrap')[1].classList.remove('hp-gain-hud-active');
|
||||||
|
$(html).find('.hp-gain-wrap')[1].classList.add('hp-gain-hud-disabled');
|
||||||
|
$(html).find('.hp-gain-wrap')[2].classList.remove('hp-gain-hud-active');
|
||||||
|
$(html).find('.hp-gain-wrap')[2].classList.add('hp-gain-hud-disabled');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
$(html).find('.gain-hp-hud-click').click((event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
let hpGain = 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(hpGain)); // Positive value to add HP
|
||||||
|
$(html).find('.hp-gain-wrap')[0].classList.remove('hp-gain-hud-active');
|
||||||
|
$(html).find('.hp-gain-wrap')[0].classList.add('hp-gain-hud-disabled');
|
||||||
|
$(html).find('.hp-gain-wrap')[1].classList.remove('hp-gain-hud-active');
|
||||||
|
$(html).find('.hp-gain-wrap')[1].classList.add('hp-gain-hud-disabled');
|
||||||
|
$(html).find('.hp-gain-wrap')[2].classList.remove('hp-gain-hud-active');
|
||||||
|
$(html).find('.hp-gain-wrap')[2].classList.add('hp-gain-hud-disabled');
|
||||||
|
}
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,7 +121,466 @@ export default class LethalFantasyUtils {
|
|||||||
actor = game.actors.get(msg.actorId)
|
actor = game.actors.get(msg.actorId)
|
||||||
actor.system.rollProgressionDice(msg.combatId, msg.combatantId, msg.rollProgressionCount)
|
actor.system.rollProgressionDice(msg.combatId, msg.combatantId, msg.rollProgressionCount)
|
||||||
break
|
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() {
|
static registerHandlebarsHelpers() {
|
||||||
@@ -328,16 +833,26 @@ export default class LethalFantasyUtils {
|
|||||||
// Message de confirmation
|
// Message de confirmation
|
||||||
let drText = ""
|
let drText = ""
|
||||||
if (result.drType === "armor") {
|
if (result.drType === "armor") {
|
||||||
drText = ` (Armor DR: ${armorDR})`
|
drText = `Armor DR: ${armorDR}`
|
||||||
} else if (result.drType === "all") {
|
} else if (result.drType === "all") {
|
||||||
drText = ` (Total DR: ${totalDR})`
|
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({
|
ChatMessage.create({
|
||||||
user: game.user.id,
|
user: game.user.id,
|
||||||
speaker: { alias: targetActor.name },
|
speaker: { alias: targetActor.name },
|
||||||
rollMode: "gmroll",
|
rollMode: "gmroll",
|
||||||
content: `${targetActor.name} takes ${result.damage} damage${drText} from ${weaponName}`
|
content: messageContent
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
@@ -1 +1 @@
|
|||||||
MANIFEST-000477
|
MANIFEST-000539
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
2025/12/19-14:37:50.298740 7f24377fe6c0 Recovering log #475
|
2026/02/06-21:03:10.117032 7f71b6ffd6c0 Recovering log #537
|
||||||
2025/12/19-14:37:50.308335 7f24377fe6c0 Delete type=3 #473
|
2026/02/06-21:03:10.127068 7f71b6ffd6c0 Delete type=3 #535
|
||||||
2025/12/19-14:37:50.308381 7f24377fe6c0 Delete type=0 #475
|
2026/02/06-21:03:10.127128 7f71b6ffd6c0 Delete type=0 #537
|
||||||
2025/12/19-15:40:35.866244 7f2436ffd6c0 Level-0 table #480: started
|
2026/02/06-21:51:23.216143 7f71b67fc6c0 Level-0 table #542: started
|
||||||
2025/12/19-15:40:35.866281 7f2436ffd6c0 Level-0 table #480: 0 bytes OK
|
2026/02/06-21:51:23.216190 7f71b67fc6c0 Level-0 table #542: 0 bytes OK
|
||||||
2025/12/19-15:40:35.872420 7f2436ffd6c0 Delete type=0 #478
|
2026/02/06-21:51:23.251149 7f71b67fc6c0 Delete type=0 #540
|
||||||
2025/12/19-15:40:35.879555 7f2436ffd6c0 Manual compaction at level-0 from '!folders!ATr9wZhg5uTVTksM' @ 72057594037927935 : 1 .. '!items!zw9RQocTdz3HRjZK' @ 0 : 0; will stop at (end)
|
2026/02/06-21:51:23.327713 7f71b67fc6c0 Manual compaction at level-0 from '!folders!ATr9wZhg5uTVTksM' @ 72057594037927935 : 1 .. '!items!zw9RQocTdz3HRjZK' @ 0 : 0; will stop at (end)
|
||||||
2025/12/19-15:40:35.879588 7f2436ffd6c0 Manual compaction at level-1 from '!folders!ATr9wZhg5uTVTksM' @ 72057594037927935 : 1 .. '!items!zw9RQocTdz3HRjZK' @ 0 : 0; will stop at (end)
|
2026/02/06-21:51:23.327750 7f71b67fc6c0 Manual compaction at level-1 from '!folders!ATr9wZhg5uTVTksM' @ 72057594037927935 : 1 .. '!items!zw9RQocTdz3HRjZK' @ 0 : 0; will stop at (end)
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
2025/12/15-21:20:57.107042 7f17053ff6c0 Recovering log #471
|
2026/02/04-08:49:16.805619 7f3bddbff6c0 Recovering log #533
|
||||||
2025/12/15-21:20:57.117492 7f17053ff6c0 Delete type=3 #469
|
2026/02/04-08:49:16.866468 7f3bddbff6c0 Delete type=3 #531
|
||||||
2025/12/15-21:20:57.117544 7f17053ff6c0 Delete type=0 #471
|
2026/02/04-08:49:16.866605 7f3bddbff6c0 Delete type=0 #533
|
||||||
2025/12/15-22:37:50.650614 7f16eeffd6c0 Level-0 table #476: started
|
2026/02/04-08:52:25.382505 7f3946fff6c0 Level-0 table #538: started
|
||||||
2025/12/15-22:37:50.650646 7f16eeffd6c0 Level-0 table #476: 0 bytes OK
|
2026/02/04-08:52:25.382568 7f3946fff6c0 Level-0 table #538: 0 bytes OK
|
||||||
2025/12/15-22:37:50.656552 7f16eeffd6c0 Delete type=0 #474
|
2026/02/04-08:52:25.390046 7f3946fff6c0 Delete type=0 #536
|
||||||
2025/12/15-22:37:50.669781 7f16eeffd6c0 Manual compaction at level-0 from '!folders!ATr9wZhg5uTVTksM' @ 72057594037927935 : 1 .. '!items!zw9RQocTdz3HRjZK' @ 0 : 0; will stop at (end)
|
2026/02/04-08:52:25.403667 7f3946fff6c0 Manual compaction at level-0 from '!folders!ATr9wZhg5uTVTksM' @ 72057594037927935 : 1 .. '!items!zw9RQocTdz3HRjZK' @ 0 : 0; will stop at (end)
|
||||||
2025/12/15-22:37:50.669815 7f16eeffd6c0 Manual compaction at level-1 from '!folders!ATr9wZhg5uTVTksM' @ 72057594037927935 : 1 .. '!items!zw9RQocTdz3HRjZK' @ 0 : 0; will stop at (end)
|
2026/02/04-08:52:25.403757 7f3946fff6c0 Manual compaction at level-1 from '!folders!ATr9wZhg5uTVTksM' @ 72057594037927935 : 1 .. '!items!zw9RQocTdz3HRjZK' @ 0 : 0; will stop at (end)
|
||||||
|
|||||||
Binary file not shown.
BIN
packs-system/lf-equipment/MANIFEST-000539
Normal file
BIN
packs-system/lf-equipment/MANIFEST-000539
Normal file
Binary file not shown.
@@ -1 +1 @@
|
|||||||
MANIFEST-000476
|
MANIFEST-000536
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
2025/12/19-14:37:50.313071 7f2437fff6c0 Recovering log #474
|
2026/02/06-21:03:10.133019 7f71b7fff6c0 Recovering log #534
|
||||||
2025/12/19-14:37:50.323320 7f2437fff6c0 Delete type=3 #472
|
2026/02/06-21:03:10.144336 7f71b7fff6c0 Delete type=3 #532
|
||||||
2025/12/19-14:37:50.323373 7f2437fff6c0 Delete type=0 #474
|
2026/02/06-21:03:10.144427 7f71b7fff6c0 Delete type=0 #534
|
||||||
2025/12/19-15:40:35.872568 7f2436ffd6c0 Level-0 table #479: started
|
2026/02/06-21:51:23.291316 7f71b67fc6c0 Level-0 table #539: started
|
||||||
2025/12/19-15:40:35.872597 7f2436ffd6c0 Level-0 table #479: 0 bytes OK
|
2026/02/06-21:51:23.291356 7f71b67fc6c0 Level-0 table #539: 0 bytes OK
|
||||||
2025/12/19-15:40:35.879415 7f2436ffd6c0 Delete type=0 #477
|
2026/02/06-21:51:23.327518 7f71b67fc6c0 Delete type=0 #537
|
||||||
2025/12/19-15:40:35.879563 7f2436ffd6c0 Manual compaction at level-0 from '!folders!yPWGvxHJbDNHVSnY' @ 72057594037927935 : 1 .. '!items!x5gLtqlW4sdDmHTd' @ 0 : 0; will stop at (end)
|
2026/02/06-21:51:23.327733 7f71b67fc6c0 Manual compaction at level-0 from '!folders!yPWGvxHJbDNHVSnY' @ 72057594037927935 : 1 .. '!items!x5gLtqlW4sdDmHTd' @ 0 : 0; will stop at (end)
|
||||||
2025/12/19-15:40:35.879582 7f2436ffd6c0 Manual compaction at level-1 from '!folders!yPWGvxHJbDNHVSnY' @ 72057594037927935 : 1 .. '!items!x5gLtqlW4sdDmHTd' @ 0 : 0; will stop at (end)
|
2026/02/06-21:51:23.327757 7f71b67fc6c0 Manual compaction at level-1 from '!folders!yPWGvxHJbDNHVSnY' @ 72057594037927935 : 1 .. '!items!x5gLtqlW4sdDmHTd' @ 0 : 0; will stop at (end)
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
2025/12/15-21:20:57.122365 7f16effff6c0 Recovering log #470
|
2026/02/04-08:49:16.884043 7f3bdd3fe6c0 Recovering log #530
|
||||||
2025/12/15-21:20:57.134320 7f16effff6c0 Delete type=3 #468
|
2026/02/04-08:49:16.942005 7f3bdd3fe6c0 Delete type=3 #528
|
||||||
2025/12/15-21:20:57.134393 7f16effff6c0 Delete type=0 #470
|
2026/02/04-08:49:16.942138 7f3bdd3fe6c0 Delete type=0 #530
|
||||||
2025/12/15-22:37:50.644221 7f16eeffd6c0 Level-0 table #475: started
|
2026/02/04-08:52:25.418216 7f3946fff6c0 Level-0 table #535: started
|
||||||
2025/12/15-22:37:50.644288 7f16eeffd6c0 Level-0 table #475: 0 bytes OK
|
2026/02/04-08:52:25.418276 7f3946fff6c0 Level-0 table #535: 0 bytes OK
|
||||||
2025/12/15-22:37:50.650497 7f16eeffd6c0 Delete type=0 #473
|
2026/02/04-08:52:25.424939 7f3946fff6c0 Delete type=0 #533
|
||||||
2025/12/15-22:37:50.669770 7f16eeffd6c0 Manual compaction at level-0 from '!folders!yPWGvxHJbDNHVSnY' @ 72057594037927935 : 1 .. '!items!x5gLtqlW4sdDmHTd' @ 0 : 0; will stop at (end)
|
2026/02/04-08:52:25.432353 7f3946fff6c0 Manual compaction at level-0 from '!folders!yPWGvxHJbDNHVSnY' @ 72057594037927935 : 1 .. '!items!x5gLtqlW4sdDmHTd' @ 0 : 0; will stop at (end)
|
||||||
2025/12/15-22:37:50.669829 7f16eeffd6c0 Manual compaction at level-1 from '!folders!yPWGvxHJbDNHVSnY' @ 72057594037927935 : 1 .. '!items!x5gLtqlW4sdDmHTd' @ 0 : 0; will stop at (end)
|
2026/02/04-08:52:25.432442 7f3946fff6c0 Manual compaction at level-1 from '!folders!yPWGvxHJbDNHVSnY' @ 72057594037927935 : 1 .. '!items!x5gLtqlW4sdDmHTd' @ 0 : 0; will stop at (end)
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
@@ -1 +1 @@
|
|||||||
MANIFEST-000478
|
MANIFEST-000541
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
2025/12/19-14:37:50.285216 7f244c9fe6c0 Recovering log #476
|
2026/02/06-21:03:10.101791 7f71b77fe6c0 Recovering log #539
|
||||||
2025/12/19-14:37:50.295747 7f244c9fe6c0 Delete type=3 #474
|
2026/02/06-21:03:10.111914 7f71b77fe6c0 Delete type=3 #537
|
||||||
2025/12/19-14:37:50.295805 7f244c9fe6c0 Delete type=0 #476
|
2026/02/06-21:03:10.111991 7f71b77fe6c0 Delete type=0 #539
|
||||||
2025/12/19-15:40:35.852984 7f2436ffd6c0 Level-0 table #481: started
|
2026/02/06-21:51:23.251285 7f71b67fc6c0 Level-0 table #544: started
|
||||||
2025/12/19-15:40:35.853042 7f2436ffd6c0 Level-0 table #481: 0 bytes OK
|
2026/02/06-21:51:23.251325 7f71b67fc6c0 Level-0 table #544: 0 bytes OK
|
||||||
2025/12/19-15:40:35.859968 7f2436ffd6c0 Delete type=0 #479
|
2026/02/06-21:51:23.291180 7f71b67fc6c0 Delete type=0 #542
|
||||||
2025/12/19-15:40:35.879535 7f2436ffd6c0 Manual compaction at level-0 from '!folders!7j8H7DbmBb9Uza2X' @ 72057594037927935 : 1 .. '!items!zt8s7564ep1La4XQ' @ 0 : 0; will stop at (end)
|
2026/02/06-21:51:23.327724 7f71b67fc6c0 Manual compaction at level-0 from '!folders!7j8H7DbmBb9Uza2X' @ 72057594037927935 : 1 .. '!items!zt8s7564ep1La4XQ' @ 0 : 0; will stop at (end)
|
||||||
2025/12/19-15:40:35.879576 7f2436ffd6c0 Manual compaction at level-1 from '!folders!7j8H7DbmBb9Uza2X' @ 72057594037927935 : 1 .. '!items!zt8s7564ep1La4XQ' @ 0 : 0; will stop at (end)
|
2026/02/06-21:51:23.327764 7f71b67fc6c0 Manual compaction at level-1 from '!folders!7j8H7DbmBb9Uza2X' @ 72057594037927935 : 1 .. '!items!zt8s7564ep1La4XQ' @ 0 : 0; will stop at (end)
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
2025/12/15-21:20:57.092288 7f16ef7fe6c0 Recovering log #472
|
2026/02/04-08:49:16.723698 7f3bdcbfd6c0 Recovering log #535
|
||||||
2025/12/15-21:20:57.103016 7f16ef7fe6c0 Delete type=3 #470
|
2026/02/04-08:49:16.780187 7f3bdcbfd6c0 Delete type=3 #533
|
||||||
2025/12/15-21:20:57.103072 7f16ef7fe6c0 Delete type=0 #472
|
2026/02/04-08:49:16.780329 7f3bdcbfd6c0 Delete type=0 #535
|
||||||
2025/12/15-22:37:50.663349 7f16eeffd6c0 Level-0 table #477: started
|
2026/02/04-08:52:25.375521 7f3946fff6c0 Level-0 table #540: started
|
||||||
2025/12/15-22:37:50.663375 7f16eeffd6c0 Level-0 table #477: 0 bytes OK
|
2026/02/04-08:52:25.375622 7f3946fff6c0 Level-0 table #540: 0 bytes OK
|
||||||
2025/12/15-22:37:50.669678 7f16eeffd6c0 Delete type=0 #475
|
2026/02/04-08:52:25.382202 7f3946fff6c0 Delete type=0 #538
|
||||||
2025/12/15-22:37:50.669808 7f16eeffd6c0 Manual compaction at level-0 from '!folders!7j8H7DbmBb9Uza2X' @ 72057594037927935 : 1 .. '!items!zt8s7564ep1La4XQ' @ 0 : 0; will stop at (end)
|
2026/02/04-08:52:25.403638 7f3946fff6c0 Manual compaction at level-0 from '!folders!7j8H7DbmBb9Uza2X' @ 72057594037927935 : 1 .. '!items!zt8s7564ep1La4XQ' @ 0 : 0; will stop at (end)
|
||||||
2025/12/15-22:37:50.669842 7f16eeffd6c0 Manual compaction at level-1 from '!folders!7j8H7DbmBb9Uza2X' @ 72057594037927935 : 1 .. '!items!zt8s7564ep1La4XQ' @ 0 : 0; will stop at (end)
|
2026/02/04-08:52:25.403738 7f3946fff6c0 Manual compaction at level-1 from '!folders!7j8H7DbmBb9Uza2X' @ 72057594037927935 : 1 .. '!items!zt8s7564ep1La4XQ' @ 0 : 0; will stop at (end)
|
||||||
|
|||||||
Binary file not shown.
BIN
packs-system/lf-skills/MANIFEST-000541
Normal file
BIN
packs-system/lf-skills/MANIFEST-000541
Normal file
Binary file not shown.
@@ -1 +1 @@
|
|||||||
MANIFEST-000176
|
MANIFEST-000236
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
2025/12/19-14:37:50.338179 7f244d1ff6c0 Recovering log #174
|
2026/02/06-21:03:10.160494 7f71b6ffd6c0 Recovering log #234
|
||||||
2025/12/19-14:37:50.348596 7f244d1ff6c0 Delete type=3 #172
|
2026/02/06-21:03:10.170771 7f71b6ffd6c0 Delete type=3 #232
|
||||||
2025/12/19-14:37:50.348672 7f244d1ff6c0 Delete type=0 #174
|
2026/02/06-21:03:10.170846 7f71b6ffd6c0 Delete type=0 #234
|
||||||
2025/12/19-15:40:35.860117 7f2436ffd6c0 Level-0 table #179: started
|
2026/02/06-21:51:23.435610 7f71b67fc6c0 Level-0 table #239: started
|
||||||
2025/12/19-15:40:35.860149 7f2436ffd6c0 Level-0 table #179: 0 bytes OK
|
2026/02/06-21:51:23.435649 7f71b67fc6c0 Level-0 table #239: 0 bytes OK
|
||||||
2025/12/19-15:40:35.866102 7f2436ffd6c0 Delete type=0 #177
|
2026/02/06-21:51:23.473038 7f71b67fc6c0 Delete type=0 #237
|
||||||
2025/12/19-15:40:35.879546 7f2436ffd6c0 Manual compaction at level-0 from '!folders!37mu4dxsSuftlnmP' @ 72057594037927935 : 1 .. '!items!zKOpU34oLziGJW6y' @ 0 : 0; will stop at (end)
|
2026/02/06-21:51:23.473211 7f71b67fc6c0 Manual compaction at level-0 from '!folders!37mu4dxsSuftlnmP' @ 72057594037927935 : 1 .. '!items!zKOpU34oLziGJW6y' @ 0 : 0; will stop at (end)
|
||||||
2025/12/19-15:40:35.879569 7f2436ffd6c0 Manual compaction at level-1 from '!folders!37mu4dxsSuftlnmP' @ 72057594037927935 : 1 .. '!items!zKOpU34oLziGJW6y' @ 0 : 0; will stop at (end)
|
2026/02/06-21:51:23.473250 7f71b67fc6c0 Manual compaction at level-1 from '!folders!37mu4dxsSuftlnmP' @ 72057594037927935 : 1 .. '!items!zKOpU34oLziGJW6y' @ 0 : 0; will stop at (end)
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
2025/12/15-21:20:57.154335 7f1704bfe6c0 Recovering log #170
|
2026/02/04-08:49:17.043224 7f3bc7fff6c0 Recovering log #230
|
||||||
2025/12/15-21:20:57.164315 7f1704bfe6c0 Delete type=3 #168
|
2026/02/04-08:49:17.105842 7f3bc7fff6c0 Delete type=3 #228
|
||||||
2025/12/15-21:20:57.164371 7f1704bfe6c0 Delete type=0 #170
|
2026/02/04-08:49:17.106088 7f3bc7fff6c0 Delete type=0 #230
|
||||||
2025/12/15-22:37:50.692660 7f16eeffd6c0 Level-0 table #175: started
|
2026/02/04-08:52:25.396931 7f3946fff6c0 Level-0 table #235: started
|
||||||
2025/12/15-22:37:50.692695 7f16eeffd6c0 Level-0 table #175: 0 bytes OK
|
2026/02/04-08:52:25.396997 7f3946fff6c0 Level-0 table #235: 0 bytes OK
|
||||||
2025/12/15-22:37:50.698767 7f16eeffd6c0 Delete type=0 #173
|
2026/02/04-08:52:25.403390 7f3946fff6c0 Delete type=0 #233
|
||||||
2025/12/15-22:37:50.710785 7f16eeffd6c0 Manual compaction at level-0 from '!folders!37mu4dxsSuftlnmP' @ 72057594037927935 : 1 .. '!items!zKOpU34oLziGJW6y' @ 0 : 0; will stop at (end)
|
2026/02/04-08:52:25.403717 7f3946fff6c0 Manual compaction at level-0 from '!folders!37mu4dxsSuftlnmP' @ 72057594037927935 : 1 .. '!items!zKOpU34oLziGJW6y' @ 0 : 0; will stop at (end)
|
||||||
2025/12/15-22:37:50.710818 7f16eeffd6c0 Manual compaction at level-1 from '!folders!37mu4dxsSuftlnmP' @ 72057594037927935 : 1 .. '!items!zKOpU34oLziGJW6y' @ 0 : 0; will stop at (end)
|
2026/02/04-08:52:25.403823 7f3946fff6c0 Manual compaction at level-1 from '!folders!37mu4dxsSuftlnmP' @ 72057594037927935 : 1 .. '!items!zKOpU34oLziGJW6y' @ 0 : 0; will stop at (end)
|
||||||
|
|||||||
Binary file not shown.
@@ -1 +1 @@
|
|||||||
MANIFEST-000475
|
MANIFEST-000535
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
2025/12/19-14:37:50.326173 7f244c9fe6c0 Recovering log #473
|
2026/02/06-21:03:10.146976 7f71b77fe6c0 Recovering log #533
|
||||||
2025/12/19-14:37:50.335692 7f244c9fe6c0 Delete type=3 #471
|
2026/02/06-21:03:10.156549 7f71b77fe6c0 Delete type=3 #531
|
||||||
2025/12/19-14:37:50.335763 7f244c9fe6c0 Delete type=0 #473
|
2026/02/06-21:03:10.156610 7f71b77fe6c0 Delete type=0 #533
|
||||||
2025/12/19-15:40:35.879641 7f2436ffd6c0 Level-0 table #478: started
|
2026/02/06-21:51:23.327858 7f71b67fc6c0 Level-0 table #538: started
|
||||||
2025/12/19-15:40:35.879661 7f2436ffd6c0 Level-0 table #478: 0 bytes OK
|
2026/02/06-21:51:23.327892 7f71b67fc6c0 Level-0 table #538: 0 bytes OK
|
||||||
2025/12/19-15:40:35.886005 7f2436ffd6c0 Delete type=0 #476
|
2026/02/06-21:51:23.359727 7f71b67fc6c0 Delete type=0 #536
|
||||||
2025/12/19-15:40:35.909027 7f2436ffd6c0 Manual compaction at level-0 from '!folders!mnO9OzE7BEE2KDfh' @ 72057594037927935 : 1 .. '!items!zkK6ixtCsCw3RH9X' @ 0 : 0; will stop at (end)
|
2026/02/06-21:51:23.473179 7f71b67fc6c0 Manual compaction at level-0 from '!folders!mnO9OzE7BEE2KDfh' @ 72057594037927935 : 1 .. '!items!zkK6ixtCsCw3RH9X' @ 0 : 0; will stop at (end)
|
||||||
2025/12/19-15:40:35.927361 7f2436ffd6c0 Manual compaction at level-1 from '!folders!mnO9OzE7BEE2KDfh' @ 72057594037927935 : 1 .. '!items!zkK6ixtCsCw3RH9X' @ 0 : 0; will stop at (end)
|
2026/02/06-21:51:23.473221 7f71b67fc6c0 Manual compaction at level-1 from '!folders!mnO9OzE7BEE2KDfh' @ 72057594037927935 : 1 .. '!items!zkK6ixtCsCw3RH9X' @ 0 : 0; will stop at (end)
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
2025/12/15-21:20:57.138258 7f16ef7fe6c0 Recovering log #469
|
2026/02/04-08:49:16.956252 7f3bdcbfd6c0 Recovering log #529
|
||||||
2025/12/15-21:20:57.151734 7f16ef7fe6c0 Delete type=3 #467
|
2026/02/04-08:49:17.018344 7f3bdcbfd6c0 Delete type=3 #527
|
||||||
2025/12/15-21:20:57.151800 7f16ef7fe6c0 Delete type=0 #469
|
2026/02/04-08:49:17.018475 7f3bdcbfd6c0 Delete type=0 #529
|
||||||
2025/12/15-22:37:50.656644 7f16eeffd6c0 Level-0 table #474: started
|
2026/02/04-08:52:25.390221 7f3946fff6c0 Level-0 table #534: started
|
||||||
2025/12/15-22:37:50.656668 7f16eeffd6c0 Level-0 table #474: 0 bytes OK
|
2026/02/04-08:52:25.390273 7f3946fff6c0 Level-0 table #534: 0 bytes OK
|
||||||
2025/12/15-22:37:50.663254 7f16eeffd6c0 Delete type=0 #472
|
2026/02/04-08:52:25.396704 7f3946fff6c0 Delete type=0 #532
|
||||||
2025/12/15-22:37:50.669789 7f16eeffd6c0 Manual compaction at level-0 from '!folders!mnO9OzE7BEE2KDfh' @ 72057594037927935 : 1 .. '!items!zkK6ixtCsCw3RH9X' @ 0 : 0; will stop at (end)
|
2026/02/04-08:52:25.403694 7f3946fff6c0 Manual compaction at level-0 from '!folders!mnO9OzE7BEE2KDfh' @ 72057594037927935 : 1 .. '!items!zkK6ixtCsCw3RH9X' @ 0 : 0; will stop at (end)
|
||||||
2025/12/15-22:37:50.669836 7f16eeffd6c0 Manual compaction at level-1 from '!folders!mnO9OzE7BEE2KDfh' @ 72057594037927935 : 1 .. '!items!zkK6ixtCsCw3RH9X' @ 0 : 0; will stop at (end)
|
2026/02/04-08:52:25.403801 7f3946fff6c0 Manual compaction at level-1 from '!folders!mnO9OzE7BEE2KDfh' @ 72057594037927935 : 1 .. '!items!zkK6ixtCsCw3RH9X' @ 0 : 0; will stop at (end)
|
||||||
|
|||||||
Binary file not shown.
275
styles/chat.less
275
styles/chat.less
@@ -39,3 +39,278 @@
|
|||||||
font-family: var(--font-secondary);
|
font-family: var(--font-secondary);
|
||||||
font-size: calc(var(--font-size-standard) * 1.2);
|
font-size: calc(var(--font-size-standard) * 1.2);
|
||||||
}
|
}
|
||||||
|
.defense-request {
|
||||||
|
padding: 12px;
|
||||||
|
background: linear-gradient(to bottom, #3a3930 0%, #2a2920 100%);
|
||||||
|
border: 2px solid #d4af37;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.4);
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
margin: 0 0 10px 0;
|
||||||
|
color: #d4af37;
|
||||||
|
font-size: calc(var(--font-size-standard) * 1.1);
|
||||||
|
font-weight: 700;
|
||||||
|
text-align: center;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
|
||||||
|
i {
|
||||||
|
margin-right: 6px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin: 8px 0;
|
||||||
|
color: #f0e6d2;
|
||||||
|
font-size: calc(var(--font-size-standard) * 0.95);
|
||||||
|
line-height: 1.4;
|
||||||
|
|
||||||
|
strong {
|
||||||
|
color: #d4af37;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.defense-prompt {
|
||||||
|
margin-top: 12px;
|
||||||
|
padding: 8px;
|
||||||
|
background: rgba(212, 175, 55, 0.1);
|
||||||
|
border-left: 3px solid #d4af37;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #d4af37;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.defense-request-dialog {
|
||||||
|
.attack-info {
|
||||||
|
padding: 12px;
|
||||||
|
background: linear-gradient(to bottom, rgba(42, 41, 32, 0.8) 0%, rgba(26, 25, 16, 0.9) 100%);
|
||||||
|
border: 1px solid rgba(212, 175, 55, 0.5);
|
||||||
|
border-radius: 6px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin: 6px 0;
|
||||||
|
color: #f0e6d2;
|
||||||
|
font-size: calc(var(--font-size-standard) * 0.95);
|
||||||
|
|
||||||
|
strong {
|
||||||
|
color: #d4af37;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.weapon-selection {
|
||||||
|
label {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
color: #d4af37;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: calc(var(--font-size-standard) * 0.95);
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
width: 100%;
|
||||||
|
padding: 8px 12px;
|
||||||
|
background: #3a3930 !important;
|
||||||
|
border: 1px solid #d4af37;
|
||||||
|
border-radius: 4px;
|
||||||
|
color: #ffffff !important;
|
||||||
|
font-size: calc(var(--font-size-standard) * 0.95);
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: #f0e6d2;
|
||||||
|
box-shadow: 0 0 0 2px rgba(212, 175, 55, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
option {
|
||||||
|
background: #3a3930 !important;
|
||||||
|
color: #ffffff !important;
|
||||||
|
padding: 6px;
|
||||||
|
|
||||||
|
&:checked,
|
||||||
|
&:hover {
|
||||||
|
background: #4a4940 !important;
|
||||||
|
color: #ffffff !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.grit-luck-dialog {
|
||||||
|
.combat-status {
|
||||||
|
padding: 12px;
|
||||||
|
background: linear-gradient(to bottom, rgba(42, 41, 32, 0.8) 0%, rgba(26, 25, 16, 0.9) 100%);
|
||||||
|
border: 1px solid rgba(212, 175, 55, 0.5);
|
||||||
|
border-radius: 6px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin: 6px 0;
|
||||||
|
color: #f0e6d2;
|
||||||
|
font-size: calc(var(--font-size-standard) * 0.95);
|
||||||
|
|
||||||
|
strong {
|
||||||
|
color: #d4af37;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.bonus-info {
|
||||||
|
color: #90EE90;
|
||||||
|
font-style: italic;
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.offer-text {
|
||||||
|
color: #f0e6d2;
|
||||||
|
font-size: calc(var(--font-size-standard) * 1);
|
||||||
|
text-align: center;
|
||||||
|
font-weight: 600;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.attack-result {
|
||||||
|
padding: 16px;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.4);
|
||||||
|
margin: 8px 0;
|
||||||
|
|
||||||
|
&.attack-success {
|
||||||
|
background: linear-gradient(to bottom, rgba(139, 0, 0, 0.2) 0%, rgba(100, 0, 0, 0.3) 100%);
|
||||||
|
border: 2px solid rgba(220, 20, 60, 0.6);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.attack-failure {
|
||||||
|
background: linear-gradient(to bottom, rgba(0, 100, 139, 0.2) 0%, rgba(0, 70, 100, 0.3) 100%);
|
||||||
|
border: 2px solid rgba(70, 130, 180, 0.6);
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
margin: 0 0 16px 0;
|
||||||
|
color: #d4af37;
|
||||||
|
font-size: calc(var(--font-size-standard) * 1.2);
|
||||||
|
font-weight: 700;
|
||||||
|
text-align: center;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
|
||||||
|
i {
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.combat-comparison {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-around;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
padding: 12px;
|
||||||
|
background: rgba(0, 0, 0, 0.3);
|
||||||
|
border-radius: 6px;
|
||||||
|
|
||||||
|
.combat-side {
|
||||||
|
flex: 1;
|
||||||
|
text-align: center;
|
||||||
|
padding: 12px;
|
||||||
|
border-radius: 6px;
|
||||||
|
|
||||||
|
&.winner {
|
||||||
|
background: rgba(0, 255, 0, 0.1);
|
||||||
|
border: 2px solid rgba(0, 255, 0, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.loser {
|
||||||
|
background: rgba(255, 0, 0, 0.1);
|
||||||
|
border: 2px solid rgba(255, 0, 0, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.side-label {
|
||||||
|
font-size: calc(var(--font-size-standard) * 0.8);
|
||||||
|
color: #aaa;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.side-name {
|
||||||
|
font-size: calc(var(--font-size-standard) * 1);
|
||||||
|
color: #f0e6d2;
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.side-roll {
|
||||||
|
font-size: calc(var(--font-size-standard) * 1.5);
|
||||||
|
color: #d4af37;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.combat-vs {
|
||||||
|
font-size: calc(var(--font-size-standard) * 1.2);
|
||||||
|
color: #d4af37;
|
||||||
|
font-weight: 700;
|
||||||
|
padding: 0 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.combat-result-text {
|
||||||
|
text-align: center;
|
||||||
|
font-size: calc(var(--font-size-standard) * 1.1);
|
||||||
|
color: #f0e6d2;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
padding: 12px;
|
||||||
|
background: rgba(0, 0, 0, 0.2);
|
||||||
|
border-radius: 6px;
|
||||||
|
|
||||||
|
i {
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
strong {
|
||||||
|
color: #d4af37;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.attack-result-damage {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
.roll-damage-btn {
|
||||||
|
padding: 10px 16px;
|
||||||
|
background: linear-gradient(to bottom, #8b0000 0%, #660000 100%);
|
||||||
|
border: 1px solid #ff0000;
|
||||||
|
border-radius: 6px;
|
||||||
|
color: #f0e6d2;
|
||||||
|
font-weight: 600;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: linear-gradient(to bottom, #a00000 0%, #7b0000 100%);
|
||||||
|
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.4);
|
||||||
|
transform: translateY(-2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
i {
|
||||||
|
margin-right: 6px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -47,7 +47,7 @@ i.lethalfantasy {
|
|||||||
|
|
||||||
.application.dialog.lethalfantasy {
|
.application.dialog.lethalfantasy {
|
||||||
font-family: var(--font-primary);
|
font-family: var(--font-primary);
|
||||||
font-size: calc(var(--font-size-standard) * 1.0);
|
font-size: calc(var(--font-size-standard) * 1);
|
||||||
background-image: var(--background-image-base);
|
background-image: var(--background-image-base);
|
||||||
button:hover {
|
button:hover {
|
||||||
background: var(--color-dark-6);
|
background: var(--color-dark-6);
|
||||||
|
|||||||
@@ -42,3 +42,49 @@
|
|||||||
padding-left: 8px;
|
padding-left: 8px;
|
||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* HP Gain Styles */
|
||||||
|
#token-hud .hp-gain-wrap {
|
||||||
|
position: absolute;
|
||||||
|
left: 75px;
|
||||||
|
display: none;
|
||||||
|
top: 50%;
|
||||||
|
width: 48px;
|
||||||
|
text-align: start;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
#token-hud .hp-gain-wrap-col1 {
|
||||||
|
transform: translate(-200%, -50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
#token-hud .hp-gain-wrap-col2 {
|
||||||
|
transform: translate(-300%, -50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
#token-hud .hp-gain-wrap-col3 {
|
||||||
|
transform: translate(-400%, -50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
#token-hud .hp-gain-hud-active {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
#token-hud .hp-gain-hud-disabled {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#token-hud .hud-gain-hp-button-select {
|
||||||
|
max-width: 40px;
|
||||||
|
background-image: var(--background-image-base);
|
||||||
|
padding-top: 0;
|
||||||
|
padding-bottom: 0;
|
||||||
|
width: max-content;
|
||||||
|
margin: 0;
|
||||||
|
color: #252424;
|
||||||
|
}
|
||||||
|
|
||||||
|
#token-hud .hp-gain-wrap .hud-gain-hp-button-select {
|
||||||
|
padding-left: 8px;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|||||||
1090
styles/roll.less
1090
styles/roll.less
File diff suppressed because it is too large
Load Diff
@@ -1,15 +1,34 @@
|
|||||||
<section class="tab character-{{tab.id}} {{tab.cssClass}}" data-tab="skills" data-group="sheet">
|
<section
|
||||||
|
class="tab character-{{tab.id}} {{tab.cssClass}}"
|
||||||
|
data-tab="skills"
|
||||||
|
data-group="sheet"
|
||||||
|
>
|
||||||
<div class="main-div">
|
<div class="main-div">
|
||||||
|
|
||||||
|
{{#each skillsByCategory as |categoryData|}}
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<legend data-tooltip="{{localize " LETHALFANTASY.Tooltip.skills"}}" data-tooltip-direction="UP">{{localize
|
<legend
|
||||||
"LETHALFANTASY.Label.skills"}}</legend>
|
data-tooltip="{{localize ' LETHALFANTASY.Tooltip.skills'}}"
|
||||||
|
data-tooltip-direction="UP"
|
||||||
|
>{{localize categoryData.label}}</legend>
|
||||||
<div class="skills">
|
<div class="skills">
|
||||||
{{#each skills as |item|}}
|
{{#each categoryData.skills as |item|}}
|
||||||
<div class="skill " data-item-id="{{item.id}}" data-item-uuid="{{item.uuid}}">
|
<div
|
||||||
<img class="item-img" src="{{item.img}}" data-tooltip="{{item.name}}" />
|
class="skill"
|
||||||
|
data-item-id="{{item.id}}"
|
||||||
|
data-item-uuid="{{item.uuid}}"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
class="item-img"
|
||||||
|
src="{{item.img}}"
|
||||||
|
data-tooltip="{{item.name}}"
|
||||||
|
/>
|
||||||
<div class="name">
|
<div class="name">
|
||||||
<a class="rollable" data-roll-type="skill" data-roll-key="{{item.id}}">
|
<a
|
||||||
|
class="rollable"
|
||||||
|
data-roll-type="skill"
|
||||||
|
data-roll-key="{{item.id}}"
|
||||||
|
>
|
||||||
<i class="lf-roll-small fa-duotone fa-solid fa-dice-d10"></i>
|
<i class="lf-roll-small fa-duotone fa-solid fa-dice-d10"></i>
|
||||||
{{item.name}}
|
{{item.name}}
|
||||||
</a>
|
</a>
|
||||||
@@ -18,31 +37,62 @@
|
|||||||
+{{item.system.skillTotal}}
|
+{{item.system.skillTotal}}
|
||||||
</div>
|
</div>
|
||||||
<div class="controls">
|
<div class="controls">
|
||||||
<a data-tooltip="{{localize 'LETHALFANTASY.Edit'}}" data-action="edit" data-item-id="{{item.id}}"
|
<a
|
||||||
data-item-uuid="{{item.uuid}}"><i class="fas fa-edit"></i></a>
|
data-tooltip="{{localize 'LETHALFANTASY.Edit'}}"
|
||||||
<a data-tooltip="{{localize 'LETHALFANTASY.Delete'}}" data-action="delete" data-item-id="{{item.id}}"
|
data-action="edit"
|
||||||
data-item-uuid="{{item.uuid}}"><i class="fas fa-trash"></i></a>
|
data-item-id="{{item.id}}"
|
||||||
|
data-item-uuid="{{item.uuid}}"
|
||||||
|
><i class="fas fa-edit"></i></a>
|
||||||
|
<a
|
||||||
|
data-tooltip="{{localize 'LETHALFANTASY.Delete'}}"
|
||||||
|
data-action="delete"
|
||||||
|
data-item-id="{{item.id}}"
|
||||||
|
data-item-uuid="{{item.uuid}}"
|
||||||
|
><i class="fas fa-trash"></i></a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
{{/each}}
|
||||||
|
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<legend data-tooltip="{{localize " LETHALFANTASY.Tooltip.gifts"}}" data-tooltip-direction="UP">{{localize
|
<legend
|
||||||
"LETHALFANTASY.Label.gifts"}}</legend>
|
data-tooltip="{{localize ' LETHALFANTASY.Tooltip.gifts'}}"
|
||||||
|
data-tooltip-direction="UP"
|
||||||
|
>{{localize "LETHALFANTASY.Label.gifts"}}</legend>
|
||||||
<div class="gifts">
|
<div class="gifts">
|
||||||
{{#each gifts as |item|}}
|
{{#each gifts as |item|}}
|
||||||
<div class="gift " data-item-id="{{item.id}}" data-item-uuid="{{item.uuid}}">
|
<div
|
||||||
<img class="item-img" src="{{item.img}}" data-tooltip="{{item.name}}" />
|
class="gift"
|
||||||
<div class="name" data-tooltip="{{{item.description}}}<br><br>{{item.path}}" data-tooltip-direction="UP">
|
data-item-id="{{item.id}}"
|
||||||
|
data-item-uuid="{{item.uuid}}"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
class="item-img"
|
||||||
|
src="{{item.img}}"
|
||||||
|
data-tooltip="{{item.name}}"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="name"
|
||||||
|
data-tooltip="{{{item.description}}}<br><br>{{item.path}}"
|
||||||
|
data-tooltip-direction="UP"
|
||||||
|
>
|
||||||
{{item.name}}
|
{{item.name}}
|
||||||
</div>
|
</div>
|
||||||
<div class="controls">
|
<div class="controls">
|
||||||
<a data-tooltip="{{localize 'LETHALFANTASY.Edit'}}" data-action="edit" data-item-id="{{item.id}}"
|
<a
|
||||||
data-item-uuid="{{item.uuid}}"><i class="fas fa-edit"></i></a>
|
data-tooltip="{{localize 'LETHALFANTASY.Edit'}}"
|
||||||
<a data-tooltip="{{localize 'LETHALFANTASY.Delete'}}" data-action="delete" data-item-id="{{item.id}}"
|
data-action="edit"
|
||||||
data-item-uuid="{{item.uuid}}"><i class="fas fa-trash"></i></a>
|
data-item-id="{{item.id}}"
|
||||||
|
data-item-uuid="{{item.uuid}}"
|
||||||
|
><i class="fas fa-edit"></i></a>
|
||||||
|
<a
|
||||||
|
data-tooltip="{{localize 'LETHALFANTASY.Delete'}}"
|
||||||
|
data-action="delete"
|
||||||
|
data-item-id="{{item.id}}"
|
||||||
|
data-item-uuid="{{item.uuid}}"
|
||||||
|
><i class="fas fa-trash"></i></a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
@@ -50,20 +100,42 @@
|
|||||||
</fieldset>
|
</fieldset>
|
||||||
|
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<legend data-tooltip="{{localize " LETHALFANTASY.Tooltip.vulnerabilities"}}" data-tooltip-direction="UP">{{localize
|
<legend
|
||||||
"LETHALFANTASY.Label.vulnerabilities"}}</legend>
|
data-tooltip="{{localize ' LETHALFANTASY.Tooltip.vulnerabilities'}}"
|
||||||
|
data-tooltip-direction="UP"
|
||||||
|
>{{localize "LETHALFANTASY.Label.vulnerabilities"}}</legend>
|
||||||
<div class="vulnerabilities">
|
<div class="vulnerabilities">
|
||||||
{{#each vulnerabilities as |item|}}
|
{{#each vulnerabilities as |item|}}
|
||||||
<div class="vulnerability " data-item-id="{{item.id}}" data-item-uuid="{{item.uuid}}">
|
<div
|
||||||
<img class="item-img" src="{{item.img}}" data-tooltip="{{item.name}}" />
|
class="vulnerability"
|
||||||
<div class="name" data-tooltip="{{{item.description}}}<br><br>{{item.path}}" data-tooltip-direction="UP">
|
data-item-id="{{item.id}}"
|
||||||
|
data-item-uuid="{{item.uuid}}"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
class="item-img"
|
||||||
|
src="{{item.img}}"
|
||||||
|
data-tooltip="{{item.name}}"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="name"
|
||||||
|
data-tooltip="{{{item.description}}}<br><br>{{item.path}}"
|
||||||
|
data-tooltip-direction="UP"
|
||||||
|
>
|
||||||
{{item.name}}
|
{{item.name}}
|
||||||
</div>
|
</div>
|
||||||
<div class="controls">
|
<div class="controls">
|
||||||
<a data-tooltip="{{localize 'LETHALFANTASY.Edit'}}" data-action="edit" data-item-id="{{item.id}}"
|
<a
|
||||||
data-item-uuid="{{item.uuid}}"><i class="fas fa-edit"></i></a>
|
data-tooltip="{{localize 'LETHALFANTASY.Edit'}}"
|
||||||
<a data-tooltip="{{localize 'LETHALFANTASY.Delete'}}" data-action="delete" data-item-id="{{item.id}}"
|
data-action="edit"
|
||||||
data-item-uuid="{{item.uuid}}"><i class="fas fa-trash"></i></a>
|
data-item-id="{{item.id}}"
|
||||||
|
data-item-uuid="{{item.uuid}}"
|
||||||
|
><i class="fas fa-edit"></i></a>
|
||||||
|
<a
|
||||||
|
data-tooltip="{{localize 'LETHALFANTASY.Delete'}}"
|
||||||
|
data-action="delete"
|
||||||
|
data-item-id="{{item.id}}"
|
||||||
|
data-item-uuid="{{item.uuid}}"
|
||||||
|
><i class="fas fa-trash"></i></a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
|
|||||||
@@ -1,125 +1,189 @@
|
|||||||
{{!log 'chat-message' this}}
|
{{!log 'chat-message' this}}
|
||||||
{{!log 'weaponDamageOptions' weaponDamageOptions}}
|
{{!log 'weaponDamageOptions' weaponDamageOptions}}
|
||||||
<div class="{{cssClass}}">
|
<div class="{{cssClass}}">
|
||||||
<div class="intro-chat">
|
<!-- Header with character info -->
|
||||||
<div class="intro-img">
|
<div class="chat-header">
|
||||||
|
<div class="character-info">
|
||||||
|
<div class="character-avatar">
|
||||||
<img src="{{actingCharImg}}" data-tooltip="{{actingCharName}}" />
|
<img src="{{actingCharImg}}" data-tooltip="{{actingCharName}}" />
|
||||||
</div>
|
</div>
|
||||||
|
<div class="character-details">
|
||||||
<div class="intro-right">
|
<div class="character-name">{{actingCharName}}</div>
|
||||||
<span><STRONG>{{actingCharName}} - {{upperFirst rollName}}</STRONG></span>
|
<div class="roll-type-badge">
|
||||||
|
|
||||||
{{#if (match rollType "attack")}}
|
{{#if (match rollType "attack")}}
|
||||||
<span>Attack roll !</span>
|
<i class="fa-solid fa-swords"></i>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{#if (match rollType "defense")}}
|
{{#if (match rollType "defense")}}
|
||||||
<span>Defense roll !</span>
|
<i class="fa-solid fa-shield"></i>
|
||||||
|
{{/if}}
|
||||||
|
{{#if rollData.isDamage}}
|
||||||
|
<i class="fa-solid fa-burst"></i>
|
||||||
|
{{/if}}
|
||||||
|
{{#if (eq rollType "skill")}}
|
||||||
|
<i class="fa-solid fa-hand-sparkles"></i>
|
||||||
|
{{/if}}
|
||||||
|
{{upperFirst rollName}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Roll details and modifiers -->
|
||||||
|
<div class="roll-details">
|
||||||
|
{{#if rollTarget.weapon}}
|
||||||
|
<div class="detail-item weapon-name">
|
||||||
|
<i class="fa-solid fa-sword"></i>
|
||||||
|
<span>{{rollTarget.weapon.name}}</span>
|
||||||
|
</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
{{#if (eq rollData.favor "favor")}}
|
{{#if (eq rollData.favor "favor")}}
|
||||||
<span><strong>Favor roll</strong></span>
|
<div class="detail-badge favor-badge">
|
||||||
|
<i class="fa-solid fa-sparkles"></i>
|
||||||
|
<span>Favor</span>
|
||||||
|
</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{#if (eq rollData.favor "disfavor")}}
|
{{#if (eq rollData.favor "disfavor")}}
|
||||||
<span><strong>Disfavor roll</strong></span>
|
<div class="detail-badge disfavor-badge">
|
||||||
{{/if}}
|
<i class="fa-solid fa-skull"></i>
|
||||||
{{#if badResult}}
|
<span>Disfavor</span>
|
||||||
<span><strong>{{localize "LETHALFANTASY.Label.otherResult"}}</strong>
|
</div>
|
||||||
:
|
|
||||||
{{badResult}}</span>
|
|
||||||
{{/if}}
|
|
||||||
|
|
||||||
{{#if rollTarget.weapon}}
|
|
||||||
<span>{{rollTarget.weapon.name}}</span>
|
|
||||||
{{/if}}
|
|
||||||
|
|
||||||
{{#if rollData.isDamage}}
|
|
||||||
<span><strong>Damage Roll</strong></span>
|
|
||||||
{{/if}}
|
|
||||||
{{#if rollData.damageSmall}}
|
|
||||||
<span><strong>{{localize
|
|
||||||
"LETHALFANTASY.Label.weapon-damage-small"
|
|
||||||
}}</strong></span>
|
|
||||||
{{/if}}
|
|
||||||
{{#if rollData.damageMedium}}
|
|
||||||
<span><strong>{{localize
|
|
||||||
"LETHALFANTASY.Label.weapon-damage-medium"
|
|
||||||
}}</strong></span>
|
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
{{#if rollData.letItFly}}
|
{{#if rollData.letItFly}}
|
||||||
<span>Let It Fly attack ! </span>
|
<div class="detail-badge special-badge">
|
||||||
|
<i class="fa-solid fa-bow-arrow"></i>
|
||||||
|
<span>Let It Fly!</span>
|
||||||
|
</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{#if rollData.pointBlank}}
|
{{#if rollData.pointBlank}}
|
||||||
<span>Point Blank Range Attack !</span>
|
<div class="detail-badge special-badge">
|
||||||
|
<i class="fa-solid fa-bullseye-arrow"></i>
|
||||||
|
<span>Point Blank</span>
|
||||||
|
</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{#if rollData.beyondSkill}}
|
{{#if rollData.beyondSkill}}
|
||||||
<span>Beyond Skill Range Attack !</span>
|
<div class="detail-badge special-badge">
|
||||||
|
<i class="fa-solid fa-target-lock"></i>
|
||||||
|
<span>Beyond Skill</span>
|
||||||
|
</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
<span><strong>Formula</strong> : {{titleFormula}}</span>
|
{{#if rollData.damageSmall}}
|
||||||
|
<div class="detail-badge damage-badge">
|
||||||
|
<i class="fa-solid fa-dice-d6"></i>
|
||||||
|
<span>{{localize "LETHALFANTASY.Label.weapon-damage-small"}}</span>
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
{{#if rollData.damageMedium}}
|
||||||
|
<div class="detail-badge damage-badge">
|
||||||
|
<i class="fa-solid fa-dice-d20"></i>
|
||||||
|
<span>{{localize "LETHALFANTASY.Label.weapon-damage-medium"}}</span>
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
{{#if badResult}}
|
||||||
|
<div class="detail-item other-result">
|
||||||
|
<strong>{{localize "LETHALFANTASY.Label.otherResult"}}</strong>: {{badResult}}
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Formula and dice results -->
|
||||||
|
<div class="formula-section">
|
||||||
|
<div class="formula-display">
|
||||||
|
<i class="fa-solid fa-dice-d20"></i>
|
||||||
|
<span class="formula-text">{{titleFormula}}</span>
|
||||||
|
</div>
|
||||||
|
{{#if diceResults}}
|
||||||
|
<div class="dice-breakdown">
|
||||||
{{#each diceResults as |result|}}
|
{{#each diceResults as |result|}}
|
||||||
<span>{{result.dice}} : {{result.value}}</span>
|
<div class="dice-item">
|
||||||
|
<span class="dice-type">{{result.dice}}</span>
|
||||||
|
<span class="dice-separator">→</span>
|
||||||
|
<span class="dice-value">{{result.value}}</span>
|
||||||
|
</div>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
<!-- Result section -->
|
||||||
|
{{#unless isPrivate}}
|
||||||
|
<div class="result-section">
|
||||||
|
<div class="main-result">
|
||||||
|
<div class="result-label">Total</div>
|
||||||
|
<div class="result-value {{#if (eq resultType 'success')}}success{{else}}failure{{/if}}">
|
||||||
|
{{total}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{#if D30result}}
|
||||||
|
<div class="d30-result">
|
||||||
|
<div class="d30-header">
|
||||||
|
<i class="fa-solid fa-dice-d20"></i>
|
||||||
|
<span class="d30-label">D30 Special</span>
|
||||||
|
<span class="d30-value">{{D30result}}</span>
|
||||||
</div>
|
</div>
|
||||||
|
{{#if D30message}}
|
||||||
|
<div class="d30-message">
|
||||||
|
<i class="fa-solid fa-wand-magic-sparkles"></i>
|
||||||
|
<span>{{D30message}}</span>
|
||||||
</div>
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
|
||||||
{{#if isSave}}
|
{{#if isSave}}
|
||||||
<div class="result">
|
<div class="outcome-badge {{#if (eq resultType 'success')}}success-outcome{{else}}failure-outcome{{/if}}">
|
||||||
{{#if (eq resultType "success")}}
|
{{#if (eq resultType "success")}}
|
||||||
{{#if isPrivate}}?{{else}}{{localize
|
<i class="fa-solid fa-circle-check"></i>
|
||||||
"LETHALFANTASY.Roll.success"
|
{{localize "LETHALFANTASY.Roll.success"}}
|
||||||
}}{{/if}}
|
|
||||||
{{else}}
|
{{else}}
|
||||||
{{#if isPrivate}}?{{else}}{{localize
|
<i class="fa-solid fa-circle-xmark"></i>
|
||||||
"LETHALFANTASY.Roll.failure"
|
{{localize "LETHALFANTASY.Roll.failure"}}
|
||||||
}}{{/if}}
|
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</div>
|
</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
{{#if isResource}}
|
{{#if isResource}}
|
||||||
<div class="result">
|
<div class="outcome-badge {{#if (eq resultType 'success')}}success-outcome{{else}}failure-outcome{{/if}}">
|
||||||
{{#if (eq resultType "success")}}
|
{{#if (eq resultType "success")}}
|
||||||
{{#if isPrivate}}?{{else}}{{localize
|
<i class="fa-solid fa-circle-check"></i>
|
||||||
"LETHALFANTASY.Roll.success"
|
{{localize "LETHALFANTASY.Roll.success"}}
|
||||||
}}{{/if}}
|
|
||||||
{{else}}
|
{{else}}
|
||||||
{{#if isPrivate}}?{{else}}{{localize "LETHALFANTASY.Roll.failure"}}{{#if
|
<i class="fa-solid fa-circle-xmark"></i>
|
||||||
isFailure
|
{{localize "LETHALFANTASY.Roll.failure"}}
|
||||||
}} ({{localize "LETHALFANTASY.Roll.resourceLost"}}){{/if}}{{/if}}
|
{{#if isFailure}}
|
||||||
|
<span class="resource-lost">({{localize "LETHALFANTASY.Roll.resourceLost"}})</span>
|
||||||
|
{{/if}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</div>
|
</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
{{#if isDamage}}
|
{{#if isDamage}}
|
||||||
<div>
|
|
||||||
{{#if (and isGM hasTarget)}}
|
{{#if (and isGM hasTarget)}}
|
||||||
|
<div class="target-info">
|
||||||
|
<i class="fa-solid fa-crosshairs"></i>
|
||||||
{{{localize
|
{{{localize
|
||||||
"LETHALFANTASY.Roll.displayArmor"
|
"LETHALFANTASY.Roll.displayArmor"
|
||||||
targetName=targetName
|
targetName=targetName
|
||||||
targetArmor=targetArmor
|
targetArmor=targetArmor
|
||||||
realDamage=realDamage
|
realDamage=realDamage
|
||||||
}}}
|
}}}
|
||||||
{{/if}}
|
|
||||||
</div>
|
</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
{{#unless isPrivate}}
|
|
||||||
<div class="dice-result">
|
|
||||||
<h4 class="dice-total">{{total}}</h4>
|
|
||||||
</div>
|
|
||||||
{{#if D30result}}
|
|
||||||
<div class="dice-result">
|
|
||||||
<h4 class="dice-total">D30 result: {{D30result}}</h4>
|
|
||||||
</div>
|
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
{{else}}
|
||||||
|
<div class="private-result">
|
||||||
|
<i class="fa-solid fa-eye-slash"></i>
|
||||||
|
<span>Private Roll</span>
|
||||||
|
</div>
|
||||||
{{/unless}}
|
{{/unless}}
|
||||||
|
|
||||||
{{#if weaponDamageOptions}}
|
{{#if weaponDamageOptions}}
|
||||||
|
{{#if canSelectTarget}}
|
||||||
<div class="damage-buttons">
|
<div class="damage-buttons">
|
||||||
<div class="damage-buttons-title">{{localize
|
|
||||||
"LETHALFANTASY.Label.rollDamage"
|
|
||||||
}}</div>
|
|
||||||
<div class="damage-buttons-grid {{#if weaponDamageOptions.isMonster}}monster-damage{{/if}}">
|
<div class="damage-buttons-grid {{#if weaponDamageOptions.isMonster}}monster-damage{{/if}}">
|
||||||
{{#if weaponDamageOptions.isMonster}}
|
{{#if weaponDamageOptions.isMonster}}
|
||||||
<button
|
<button
|
||||||
@@ -129,7 +193,7 @@
|
|||||||
data-damage-formula="{{weaponDamageOptions.damageFormula}}"
|
data-damage-formula="{{weaponDamageOptions.damageFormula}}"
|
||||||
data-damage-modifier="{{weaponDamageOptions.damageModifier}}"
|
data-damage-modifier="{{weaponDamageOptions.damageModifier}}"
|
||||||
data-is-monster="true"
|
data-is-monster="true"
|
||||||
title="{{weaponDamageOptions.weaponName}}"
|
title="{{localize 'LETHALFANTASY.Label.rollDamage'}} - {{weaponDamageOptions.weaponName}}"
|
||||||
>
|
>
|
||||||
<i class="fa-solid fa-dice"></i>
|
<i class="fa-solid fa-dice"></i>
|
||||||
Damage:
|
Damage:
|
||||||
@@ -145,10 +209,10 @@
|
|||||||
data-damage-type="small"
|
data-damage-type="small"
|
||||||
data-damage-formula="{{weaponDamageOptions.damageS}}"
|
data-damage-formula="{{weaponDamageOptions.damageS}}"
|
||||||
data-is-monster="false"
|
data-is-monster="false"
|
||||||
title="{{localize 'LETHALFANTASY.Label.weapon-damage-small'}}"
|
title="{{localize 'LETHALFANTASY.Label.rollDamage'}}"
|
||||||
>
|
>
|
||||||
<i class="fa-solid fa-dice-d6"></i>
|
<i class="fa-solid fa-dice-d6"></i>
|
||||||
{{localize "LETHALFANTASY.Label.weapon-damage-small"}}
|
Damage Small
|
||||||
</button>
|
</button>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{#if weaponDamageOptions.damageM}}
|
{{#if weaponDamageOptions.damageM}}
|
||||||
@@ -158,18 +222,27 @@
|
|||||||
data-damage-type="medium"
|
data-damage-type="medium"
|
||||||
data-damage-formula="{{weaponDamageOptions.damageM}}"
|
data-damage-formula="{{weaponDamageOptions.damageM}}"
|
||||||
data-is-monster="false"
|
data-is-monster="false"
|
||||||
title="{{localize 'LETHALFANTASY.Label.weapon-damage-medium'}}"
|
title="{{localize 'LETHALFANTASY.Label.rollDamage'}}"
|
||||||
>
|
>
|
||||||
<i class="fa-solid fa-dice-d20"></i>
|
<i class="fa-solid fa-dice-d20"></i>
|
||||||
{{localize "LETHALFANTASY.Label.weapon-damage-medium"}}
|
Damage Medium
|
||||||
</button>
|
</button>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
{{#if rollData.isDamage}}
|
{{#if rollData.isDamage}}
|
||||||
|
{{#if defenderId}}
|
||||||
|
<div class="damage-result auto-applied">
|
||||||
|
<div class="auto-damage-notice">
|
||||||
|
<i class="fa-solid fa-check-circle"></i>
|
||||||
|
<span>Damage automatically applied to defender (with Armor DR)</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{else}}
|
||||||
<div class="damage-result">
|
<div class="damage-result">
|
||||||
<ul>
|
<ul>
|
||||||
<li class="li-apply-wounds">
|
<li class="li-apply-wounds">
|
||||||
@@ -189,5 +262,30 @@
|
|||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
{{#if isAttack}}
|
||||||
|
{{#if canSelectTarget}}
|
||||||
|
<div class="attack-targets">
|
||||||
|
<ul>
|
||||||
|
<li class="li-select-target">
|
||||||
|
<div>{{localize "LETHALFANTASY.Label.selectTarget"}}</div>
|
||||||
|
<div class="combatants-grid">
|
||||||
|
{{#each combatants}}
|
||||||
|
<button
|
||||||
|
class="request-defense-btn"
|
||||||
|
data-combatant-id="{{this.id}}"
|
||||||
|
data-token-id="{{this.tokenId}}"
|
||||||
|
title="{{this.name}}"
|
||||||
|
>
|
||||||
|
{{this.name}}
|
||||||
|
</button>
|
||||||
|
{{/each}}
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
36
templates/damage-applied-message.hbs
Normal file
36
templates/damage-applied-message.hbs
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
<div class="fvtt-lethal-fantasy damage-applied-message">
|
||||||
|
<div class="damage-header">
|
||||||
|
<div class="damage-icon">
|
||||||
|
<i class="fa-solid fa-heart-crack"></i>
|
||||||
|
</div>
|
||||||
|
<div class="damage-info">
|
||||||
|
<div class="target-name">{{targetName}}</div>
|
||||||
|
<div class="damage-subtitle">{{localize "LETHALFANTASY.DamageApplied.subtitle"}}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="damage-details">
|
||||||
|
<div class="damage-amount">
|
||||||
|
<span class="damage-label">{{localize "LETHALFANTASY.DamageApplied.damageDealt"}}</span>
|
||||||
|
<span class="damage-value">{{damage}}</span>
|
||||||
|
{{#if rawDamage}}
|
||||||
|
<span class="damage-raw">({{rawDamage}} raw)</span>
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{#if drText}}
|
||||||
|
<div class="damage-reduction">
|
||||||
|
<i class="fa-solid fa-shield"></i>
|
||||||
|
<span>{{drText}}</span>
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
<div class="damage-source">
|
||||||
|
<i class="fa-solid fa-crosshairs"></i>
|
||||||
|
<span>{{localize "LETHALFANTASY.DamageApplied.from"}}
|
||||||
|
{{#if attackerName}}<strong>{{attackerName}}</strong> with {{/if}}
|
||||||
|
<strong>{{weaponName}}</strong>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
43
templates/gain-hp-hud.hbs
Normal file
43
templates/gain-hp-hud.hbs
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
<div class="control-icon" data-action="lethal-gain-hp-hud">
|
||||||
|
<img
|
||||||
|
class="lethal-hp-gain-hud"
|
||||||
|
src="systems/fvtt-lethal-fantasy/assets/icons/health-increase.svg"
|
||||||
|
width="36"
|
||||||
|
height="36"
|
||||||
|
title="Restore HP"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div class="hp-gain-wrap hp-gain-wrap-col1 hp-gain-hud-disabled">
|
||||||
|
{{#for 1 11 1}}
|
||||||
|
<button
|
||||||
|
class="hud-gain-hp-button-select gain-hp-hud-click"
|
||||||
|
data-hp-value="{{this}}"
|
||||||
|
>
|
||||||
|
<span class="">+{{this}}</span>
|
||||||
|
</button>
|
||||||
|
{{/for}}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="hp-gain-wrap hp-gain-wrap-col2 hp-gain-hud-disabled">
|
||||||
|
{{#for 11 21 1}}
|
||||||
|
<button
|
||||||
|
class="hud-gain-hp-button-select gain-hp-hud-click"
|
||||||
|
data-hp-value="{{this}}"
|
||||||
|
>
|
||||||
|
<span class="">+{{this}}</span>
|
||||||
|
</button>
|
||||||
|
{{/for}}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="hp-gain-wrap hp-gain-wrap-col3 hp-gain-hud-disabled">
|
||||||
|
{{#for 21 31 1}}
|
||||||
|
<button
|
||||||
|
class="hud-gain-hp-button-select gain-hp-hud-click"
|
||||||
|
data-hp-value="{{this}}"
|
||||||
|
>
|
||||||
|
<span class="">+{{this}}</span>
|
||||||
|
</button>
|
||||||
|
{{/for}}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
@@ -22,7 +22,7 @@
|
|||||||
{{#each system.attacks as |item key|}}
|
{{#each system.attacks as |item key|}}
|
||||||
<div class="attack" data-attack-key="{{key}}" >
|
<div class="attack" data-attack-key="{{key}}" >
|
||||||
<div class="">
|
<div class="">
|
||||||
<input type="checkbox" name="system.attacks.{{item.key}}.enabled" value="{{item.enabled}}" data-tooltip="Attack enabled/disabled" />
|
<input type="checkbox" name="system.attacks.{{item.key}}.enabled" {{checked item.enabled}} data-tooltip="Attack enabled/disabled" />
|
||||||
</div>
|
</div>
|
||||||
<div class="name">
|
<div class="name">
|
||||||
<input type="text" name="system.attacks.{{item.key}}.name" value="{{item.name}}" data-tooltip="Attack name" />
|
<input type="text" name="system.attacks.{{item.key}}.name" value="{{item.name}}" data-tooltip="Attack name" />
|
||||||
@@ -65,4 +65,54 @@
|
|||||||
{{/each}}
|
{{/each}}
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|
||||||
|
<fieldset>
|
||||||
|
<legend>Hand To Hand Attacks</legend>
|
||||||
|
<div class="attacks">
|
||||||
|
{{#each system.combatHTH as |item key|}}
|
||||||
|
<div class="attack" data-attack-key="{{key}}" >
|
||||||
|
<div class="">
|
||||||
|
<input type="checkbox" name="system.combatHTH.{{item.key}}.enabled" {{checked item.enabled}} data-tooltip="HTH Attack enabled/disabled" />
|
||||||
|
</div>
|
||||||
|
<div class="name">
|
||||||
|
<input type="text" name="system.combatHTH.{{item.key}}.name" value="{{item.name}}" data-tooltip="HTH Attack name" />
|
||||||
|
</div>
|
||||||
|
<div class="numeric">
|
||||||
|
<input type="number" name="system.combatHTH.{{item.key}}.attackScore" value="{{item.attackScore}}" data-tooltip="Progression number" />
|
||||||
|
</div>
|
||||||
|
<div class="numeric">
|
||||||
|
<input type="number" name="system.combatHTH.{{item.key}}.attackModifier" value="{{item.attackModifier}}" data-tooltip="Attack modifier" />
|
||||||
|
</div>
|
||||||
|
<div class="numeric">
|
||||||
|
<input type="number" name="system.combatHTH.{{item.key}}.defenseModifier" value="{{item.defenseModifier}}" data-tooltip="Defense modifier"/>
|
||||||
|
</div>
|
||||||
|
<div class="damage-dice">
|
||||||
|
<input type="text" name="system.combatHTH.{{item.key}}.damageDice" value="{{item.damageDice}}" data-tooltip="Damage dice"/>
|
||||||
|
</div>
|
||||||
|
<div class="numeric">
|
||||||
|
<input type="number" name="system.combatHTH.{{item.key}}.damageModifier" value="{{item.damageModifier}}" data-tooltip="Damage modifier"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="attack-icons">
|
||||||
|
|
||||||
|
<a class="rollable" data-roll-type="monster-attack-hth" data-roll-key="{{item.key}}" data-tooltip="Roll HTH Attack">
|
||||||
|
<i class="lf-roll-small fa-solid fa-hand-fist" data-roll-type="monster-attack-hth" data-roll-key="{{item.key}}"></i>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<a class="rollable" data-roll-type="monster-defense-hth" data-roll-key="{{item.key}}" data-tooltip="Roll HTH Defense">
|
||||||
|
<i class="fa-solid fa-shield-halved" data-roll-type="monster-defense-hth" data-roll-key="{{item.key}}"></i>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<a class="rollable" data-roll-type="monster-damage-hth" data-roll-key="{{item.key}}"
|
||||||
|
data-tooltip="Roll HTH Damage">
|
||||||
|
<i class="fa-regular fa-face-head-bandage" data-roll-type="monster-damage-hth"
|
||||||
|
data-roll-key="{{item.key}}"></i>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
{{/each}}
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
</section>
|
</section>
|
||||||
@@ -291,13 +291,6 @@
|
|||||||
disabled=isPlayMode
|
disabled=isPlayMode
|
||||||
data-char-id="int"
|
data-char-id="int"
|
||||||
}}
|
}}
|
||||||
|
|
||||||
{{formField
|
|
||||||
systemFields.characteristics.fields.int.fields.percent
|
|
||||||
value=system.characteristics.int.percent
|
|
||||||
disabled=isPlayMode
|
|
||||||
type="number"
|
|
||||||
}}
|
|
||||||
</div>
|
</div>
|
||||||
<div class="monster-characteristic">
|
<div class="monster-characteristic">
|
||||||
<span>{{localize "LETHALFANTASY.Label.dex"}}</span>
|
<span>{{localize "LETHALFANTASY.Label.dex"}}</span>
|
||||||
@@ -307,13 +300,6 @@
|
|||||||
disabled=isPlayMode
|
disabled=isPlayMode
|
||||||
data-char-id="wis"
|
data-char-id="wis"
|
||||||
}}
|
}}
|
||||||
|
|
||||||
{{formField
|
|
||||||
systemFields.characteristics.fields.dex.fields.percent
|
|
||||||
value=system.characteristics.dex.percent
|
|
||||||
disabled=isPlayMode
|
|
||||||
type="number"
|
|
||||||
}}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<legend>{{localize "LETHALFANTASY.Label.Movement"}}</legend>
|
<legend>{{localize "LETHALFANTASY.Label.Movement"}}</legend>
|
||||||
|
|||||||
44
templates/progression-message.hbs
Normal file
44
templates/progression-message.hbs
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
<div class="fvtt-lethal-fantasy progression-message {{#if success}}progression-success{{else}}progression-failure{{/if}}">
|
||||||
|
<div class="progression-header">
|
||||||
|
<div class="progression-icon">
|
||||||
|
{{#if success}}
|
||||||
|
<i class="fa-solid fa-check-circle"></i>
|
||||||
|
{{else}}
|
||||||
|
<i class="fa-solid fa-hourglass-half"></i>
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
<div class="progression-info">
|
||||||
|
<div class="actor-name">{{actorName}}</div>
|
||||||
|
<div class="progression-subtitle">
|
||||||
|
{{#if success}}
|
||||||
|
{{localize "LETHALFANTASY.ProgressionMessage.canAct"}}
|
||||||
|
{{else}}
|
||||||
|
{{localize "LETHALFANTASY.ProgressionMessage.cannotAct"}}
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="progression-details">
|
||||||
|
{{#if weaponName}}
|
||||||
|
<div class="progression-weapon">
|
||||||
|
<i class="fa-solid fa-sword"></i>
|
||||||
|
<span><strong>{{weaponName}}</strong></span>
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
{{#if rollResult}}
|
||||||
|
<div class="progression-roll">
|
||||||
|
<span class="roll-label">{{localize "LETHALFANTASY.ProgressionMessage.diceResult"}}</span>
|
||||||
|
<span class="roll-value">{{rollResult}}</span>
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
{{#if progressionCount}}
|
||||||
|
<div class="progression-count">
|
||||||
|
<i class="fa-solid fa-dice-d20"></i>
|
||||||
|
<span>{{localize "LETHALFANTASY.ProgressionMessage.progressionCount"}} <strong>{{progressionCount}}</strong></span>
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
Reference in New Issue
Block a user