Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f47f2906a4 | ||
|
|
0228f351c7 | ||
|
|
570ca6c953 | ||
|
|
4fd997d169 |
@@ -17,7 +17,7 @@
|
|||||||
"target": "attack"
|
"target": "attack"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"description": "Possible Lethal or Vital Strike or Add D20E to Attack"
|
"description": "Possible Lethal or Vital Strike and Add D20E to Attack"
|
||||||
},
|
},
|
||||||
"ranged_attack": {
|
"ranged_attack": {
|
||||||
"type": "choice",
|
"type": "choice",
|
||||||
@@ -35,7 +35,7 @@
|
|||||||
"target": "attack"
|
"target": "attack"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"description": "Possible Lethal or Vital Strike or Add D20E to Attack"
|
"description": "Possible Lethal or Vital Strike and Add D20E to Attack"
|
||||||
},
|
},
|
||||||
"melee_defense": {
|
"melee_defense": {
|
||||||
"type": "choice",
|
"type": "choice",
|
||||||
@@ -53,7 +53,7 @@
|
|||||||
"target": "defense"
|
"target": "defense"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"description": "Possible Flawless or Legendary Defense or Add D20E to Defense"
|
"description": "Possible Flawless or Legendary Defense and Add D20E to Defense"
|
||||||
},
|
},
|
||||||
"arcane_spell_attack": {
|
"arcane_spell_attack": {
|
||||||
"type": "choice",
|
"type": "choice",
|
||||||
@@ -71,7 +71,7 @@
|
|||||||
"target": "spell_attack"
|
"target": "spell_attack"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"description": "Possible Lethal or Vital Magical Strike or Add D20E to Spell Attack"
|
"description": "Possible Lethal or Vital Magical Strike and Add D20E to Spell Attack"
|
||||||
},
|
},
|
||||||
"skill_rolls": {
|
"skill_rolls": {
|
||||||
"type": "skill_auto_success",
|
"type": "skill_auto_success",
|
||||||
@@ -93,7 +93,7 @@
|
|||||||
"target": "defense"
|
"target": "defense"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"description": "Possible Flawless or Legendary Defense or Add D20E to Defense"
|
"description": "Possible Flawless or Legendary Defense and Add D20E to Defense"
|
||||||
},
|
},
|
||||||
"arcane_spell_defense": {
|
"arcane_spell_defense": {
|
||||||
"type": "choice",
|
"type": "choice",
|
||||||
@@ -107,7 +107,7 @@
|
|||||||
"target": "spell_defense"
|
"target": "spell_defense"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"description": "Possible Spell Catastrophe or adds D20E to Spell Defense"
|
"description": "Possible Spell Catastrophe and adds D20E to Spell Defense"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"29": {
|
"29": {
|
||||||
@@ -257,7 +257,7 @@
|
|||||||
"target": "attack"
|
"target": "attack"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"description": "Possible Vicious Strike or Add D12 to attack"
|
"description": "Possible Vicious Strike and Add D12 to attack"
|
||||||
},
|
},
|
||||||
"ranged_attack": {
|
"ranged_attack": {
|
||||||
"type": "choice",
|
"type": "choice",
|
||||||
@@ -274,7 +274,7 @@
|
|||||||
"target": "attack"
|
"target": "attack"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"description": "Possible Vicious Strike or add D12 to attack"
|
"description": "Possible Vicious Strike and add D12 to attack"
|
||||||
},
|
},
|
||||||
"melee_defense": {
|
"melee_defense": {
|
||||||
"type": "choice",
|
"type": "choice",
|
||||||
@@ -291,7 +291,7 @@
|
|||||||
"target": "defense"
|
"target": "defense"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"description": "Possible 20/20 defense that avoids Any Attack Except a Lethal Strike or adds D12 to defense"
|
"description": "Possible 20/20 defense that avoids Any Attack Except a Lethal Strike and adds D12 to defense"
|
||||||
},
|
},
|
||||||
"arcane_spell_attack": {
|
"arcane_spell_attack": {
|
||||||
"type": "choice",
|
"type": "choice",
|
||||||
@@ -308,7 +308,7 @@
|
|||||||
"target": "spell_attack"
|
"target": "spell_attack"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"description": "Possible Vicious Application of a Magical Attack or add D12 to attack"
|
"description": "Possible Vicious Application of a Magical Attack and add D12 to attack"
|
||||||
},
|
},
|
||||||
"skill_rolls": {
|
"skill_rolls": {
|
||||||
"type": "bonus_flat",
|
"type": "bonus_flat",
|
||||||
@@ -331,7 +331,7 @@
|
|||||||
"target": "defense"
|
"target": "defense"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"description": "Possible 20/20 defense that avoids Any Attack Except a Lethal Strike or adds D12 to defense"
|
"description": "Possible 20/20 defense that avoids Any Attack Except a Lethal Strike and adds D12 to defense"
|
||||||
},
|
},
|
||||||
"arcane_spell_defense": {
|
"arcane_spell_defense": {
|
||||||
"type": "choice",
|
"type": "choice",
|
||||||
@@ -348,7 +348,7 @@
|
|||||||
"target": "spell_defense"
|
"target": "spell_defense"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"description": "Possible 20/20 Spell defense that Saves Against Any Magical Attack Except a Lethal Magical Strike or add D12 to spell defense"
|
"description": "Possible 20/20 Spell defense that Saves Against Any Magical Attack Except a Lethal Magical Strike and add D12 to spell defense"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"15": {
|
"15": {
|
||||||
@@ -446,31 +446,31 @@
|
|||||||
"8": {
|
"8": {
|
||||||
"melee_attack": {
|
"melee_attack": {
|
||||||
"type": "mulligan",
|
"type": "mulligan",
|
||||||
"description": "Mulligan, Can Choose to Re-roll This Attack"
|
"description": "Mulligan, Can Choose to Re-roll This Attack (players only)"
|
||||||
},
|
},
|
||||||
"ranged_attack": {
|
"ranged_attack": {
|
||||||
"type": "mulligan",
|
"type": "mulligan",
|
||||||
"description": "Mulligan, Can Choose to Re-Roll This Attack"
|
"description": "Mulligan, Can Choose to Re-Roll This Attack (players only)"
|
||||||
},
|
},
|
||||||
"melee_defense": {
|
"melee_defense": {
|
||||||
"type": "mulligan",
|
"type": "mulligan",
|
||||||
"description": "Mulligan, Can Choose to Re-Roll This Defense"
|
"description": "Mulligan, Can Choose to Re-Roll This Defense (players only)"
|
||||||
},
|
},
|
||||||
"arcane_spell_attack": {
|
"arcane_spell_attack": {
|
||||||
"type": "mulligan",
|
"type": "mulligan",
|
||||||
"description": "Mulligan, Can Re-Roll This Spell Attack"
|
"description": "Mulligan, Can Re-Roll This Spell Attack (players only)"
|
||||||
},
|
},
|
||||||
"skill_rolls": {
|
"skill_rolls": {
|
||||||
"type": "mulligan",
|
"type": "mulligan",
|
||||||
"description": "Mulligan, Can Re-Roll This Skill roll"
|
"description": "Mulligan, Can Re-Roll This Skill roll (players only)"
|
||||||
},
|
},
|
||||||
"ranged_defense": {
|
"ranged_defense": {
|
||||||
"type": "mulligan",
|
"type": "mulligan",
|
||||||
"description": "Mulligan, Can Choose to Re-Roll This Defense"
|
"description": "Mulligan, Can Choose to Re-Roll This Defense (players only)"
|
||||||
},
|
},
|
||||||
"arcane_spell_defense": {
|
"arcane_spell_defense": {
|
||||||
"type": "mulligan",
|
"type": "mulligan",
|
||||||
"description": "Mulligan, Can Re-Roll This Spell Defense"
|
"description": "Mulligan, Can Re-Roll This Spell Defense (players only)"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"7": {
|
"7": {
|
||||||
|
|||||||
@@ -163,7 +163,7 @@ export default class LethalFantasyRoll extends Roll {
|
|||||||
// D'abord, ajouter les combattants du combat actif
|
// D'abord, ajouter les combattants du combat actif
|
||||||
if (game?.combat?.combatants) {
|
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 && !c.isDefeated) {
|
||||||
combatants.push({ id: c.id, name: c.name, tokenId: c.token.id })
|
combatants.push({ id: c.id, name: c.name, tokenId: c.token.id })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -174,6 +174,9 @@ export default class LethalFantasyRoll extends Roll {
|
|||||||
const existingTokenIds = new Set(combatants.map(c => c.tokenId))
|
const existingTokenIds = new Set(combatants.map(c => c.tokenId))
|
||||||
for (let token of canvas.scene.tokens) {
|
for (let token of canvas.scene.tokens) {
|
||||||
if (token.actorId !== this.actorId && !existingTokenIds.has(token.id)) {
|
if (token.actorId !== this.actorId && !existingTokenIds.has(token.id)) {
|
||||||
|
// Skip dead tokens: monsters at 0 HP are dead; characters use the defeated flag
|
||||||
|
const tokenActor = game.actors.get(token.actorId)
|
||||||
|
if (tokenActor?.type === "monster" && (Number(tokenActor?.system?.hp?.value) || 0) <= 0) continue
|
||||||
combatants.push({
|
combatants.push({
|
||||||
id: token.id,
|
id: token.id,
|
||||||
name: token.name,
|
name: token.name,
|
||||||
|
|||||||
@@ -33,38 +33,12 @@ Hooks.on("renderChatMessageHTML", (message, html, data) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (const btn of html.querySelectorAll(".apply-wounds-btn")) {
|
for (const btn of html.querySelectorAll(".apply-wounds-btn")) {
|
||||||
btn.addEventListener("mouseenter", () => {
|
|
||||||
const combatantId = btn.dataset.combatantId
|
|
||||||
if (combatantId && game.combat) {
|
|
||||||
const combatant = game.combat.combatants.get(combatantId)
|
|
||||||
if (combatant?.token) {
|
|
||||||
const token = canvas.tokens.get(combatant.token.id)
|
|
||||||
if (token) {
|
|
||||||
token.control({ releaseOthers: true })
|
|
||||||
canvas.animatePan(token.center)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
btn.addEventListener("mouseleave", () => canvas.tokens.releaseAll())
|
|
||||||
btn.addEventListener("click", event => LethalFantasyUtils.applyDamage(message, event))
|
btn.addEventListener("click", event => LethalFantasyUtils.applyDamage(message, event))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Gestion du survol et du clic sur les boutons de défense
|
// Gestion du survol et du clic sur les boutons de défense
|
||||||
for (const btn of html.querySelectorAll(".request-defense-btn")) {
|
for (const btn of html.querySelectorAll(".request-defense-btn")) {
|
||||||
btn.addEventListener("mouseenter", () => {
|
|
||||||
const tokenId = btn.dataset.tokenId
|
|
||||||
if (tokenId) {
|
|
||||||
const token = canvas.tokens.get(tokenId)
|
|
||||||
if (token) {
|
|
||||||
token.control({ releaseOthers: true })
|
|
||||||
canvas.animatePan(token.center)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
btn.addEventListener("mouseleave", () => canvas.tokens.releaseAll())
|
|
||||||
|
|
||||||
// Gestionnaire pour les boutons de demande de défense
|
// Gestionnaire pour les boutons de demande de défense
|
||||||
btn.addEventListener("click", async event => {
|
btn.addEventListener("click", async event => {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
|
|||||||
@@ -105,8 +105,26 @@ export async function handleAttackBoosted(msg) {
|
|||||||
} = msg
|
} = msg
|
||||||
|
|
||||||
const defender = game.actors.get(defenderId)
|
const defender = game.actors.get(defenderId)
|
||||||
|
const attacker = game.actors.get(attackerId)
|
||||||
if (!defender) return
|
if (!defender) return
|
||||||
|
|
||||||
|
// The createChatMessage hook is suppressed on this client in two cases:
|
||||||
|
// 1. Attacker is a PC on another client — the hook's defense reaction loop
|
||||||
|
// and shouldCreateMessage are both skipped.
|
||||||
|
// 2. Attacker is GM-controlled with D30 — the hook skips everything and
|
||||||
|
// waits for this socket handler.
|
||||||
|
// In both cases, this handler must run to show the defense dialog and create
|
||||||
|
// the comparison message. The common condition: the current user is the
|
||||||
|
// defender's primary controller.
|
||||||
|
const _isPrimaryController = actor => {
|
||||||
|
if (!actor) return false
|
||||||
|
const activePlayerOwners = game.users.filter(u => u.active && !u.isGM && actor.testUserPermission(u, "OWNER"))
|
||||||
|
if (activePlayerOwners.length > 0) return activePlayerOwners[0].id === game.user.id
|
||||||
|
return game.user.isGM
|
||||||
|
}
|
||||||
|
const defenderIsMine = _isPrimaryController(defender)
|
||||||
|
if (!defenderIsMine) return
|
||||||
|
|
||||||
let updatedDefenseRoll = defenseRoll
|
let updatedDefenseRoll = defenseRoll
|
||||||
let shieldBlocked = false
|
let shieldBlocked = false
|
||||||
let shieldReaction = null
|
let shieldReaction = null
|
||||||
@@ -237,6 +255,7 @@ export async function handleAttackBoosted(msg) {
|
|||||||
await ChatMessage.create({content: rmContent, speaker: ChatMessage.getSpeaker({actor: defender})})
|
await ChatMessage.create({content: rmContent, speaker: ChatMessage.getSpeaker({actor: defender})})
|
||||||
}
|
}
|
||||||
if (d30Result.specialEffect === "drMultiplier") {
|
if (d30Result.specialEffect === "drMultiplier") {
|
||||||
|
defenseDrMultiplier = d30Result.multiplier
|
||||||
const rmContent = await foundry.applications.handlebars.renderTemplate("systems/fvtt-lethal-fantasy/templates/chat/reaction-message.hbs", {type:"d30DRMultiplier", actorName:defenderName, value:d30Result.multiplier})
|
const rmContent = await foundry.applications.handlebars.renderTemplate("systems/fvtt-lethal-fantasy/templates/chat/reaction-message.hbs", {type:"d30DRMultiplier", actorName:defenderName, value:d30Result.multiplier})
|
||||||
await ChatMessage.create({content: rmContent, speaker: ChatMessage.getSpeaker({actor: defender})})
|
await ChatMessage.create({content: rmContent, speaker: ChatMessage.getSpeaker({actor: defender})})
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user