Correction de combat

- filtrage des parades selon l'attaque
- gestion des tactiques
This commit is contained in:
2025-12-15 01:09:35 +01:00
parent dbe5d0b974
commit 0563286cca
9 changed files with 116 additions and 78 deletions
+2 -1
View File
@@ -10,7 +10,8 @@
- Fenêtre de jets V2 - Fenêtre de jets V2
- les soins de blessures sont correctement appliqués - les soins de blessures sont correctement appliqués
- correction d'affichage jet sans compétence/avec compétence - correction d'affichage jet sans compétence/avec compétence
- correction de la prise en compte des tactiques
- correction du filtrage des défenses selon l'attaque
## 13.0.22 - Les reflets d'Illysis ## 13.0.22 - Les reflets d'Illysis
+16 -10
View File
@@ -45,7 +45,7 @@ export class RdDItemArme extends RdDItem {
isParade() { return this.system.resistance > 0 && this.system.categorie_parade } isParade() { return this.system.resistance > 0 && this.system.categorie_parade }
isBouclier() { return RdDItemArme.getCategorieParade(this).includes('bouclier') } isBouclier() { return RdDItemArme.getCategorieParade(this).includes('bouclier') }
getCompetenceAction(main) { getCompetenceAction(main) {
switch (main) { switch (main) {
case ATTAQUE_TYPE.UNE_MAIN: return this.competence1Mains() case ATTAQUE_TYPE.UNE_MAIN: return this.competence1Mains()
@@ -174,24 +174,30 @@ export class RdDItemArme extends RdDItem {
static defenseArmeParade(armeAttaque, armeParade) { static defenseArmeParade(armeAttaque, armeParade) {
const defCategory = RdDItemArme.getCategorieParade(armeParade) const defCategory = RdDItemArme.getCategorieParade(armeParade)
if (defCategory == 'boucliers') { switch (defCategory) {
return 'norm' case 'boucliers':
return 'norm'
case '':
return 'impossible'
} }
if (armeAttaque?.system?.competence?.toLowerCase().match(/(fléau)/)) { if (RdDItemArme.$isFleau(armeAttaque)) {
return '' return 'impossible'
}
if (armeParade.system?.tir) {
return ''
} }
const attCategory = RdDItemArme.getCategorieParade(armeAttaque) const attCategory = RdDItemArme.getCategorieParade(armeAttaque)
switch (attCategory) { switch (attCategory) {
case 'armes-naturelles': case 'sans-armes': case 'sans-armes':
return defCategory == 'sans-armes' ? 'norm' : '' case 'armes-naturelles':
return defCategory == attCategory ? 'norm' : 'impossible'
default: default:
return RdDItemArme.needParadeSignificative(armeAttaque, armeParade) ? 'sign' : 'norm' return RdDItemArme.needParadeSignificative(armeAttaque, armeParade) ? 'sign' : 'norm'
} }
} }
static $isFleau(armeAttaque) {
return armeAttaque?.system?.competence?.toLowerCase().match(/(fléau)/);
}
/* -------------------------------------------- */ /* -------------------------------------------- */
static needParadeSignificative(armeAttaque, armeParade) { static needParadeSignificative(armeAttaque, armeParade) {
if (!armeAttaque || !armeParade) { if (!armeAttaque || !armeParade) {
+1 -1
View File
@@ -775,7 +775,7 @@ export class RdDCombat {
/* -------------------------------------------- */ /* -------------------------------------------- */
_prepareAttaque(competence, arme) { _prepareAttaque(competence, arme) {
let rollData = { let rollData = {
mode: ROLL_TYPE_ATTAQUE, mode: 'attaque',
alias: this.attacker?.getAlias(), alias: this.attacker?.getAlias(),
passeArme: foundry.utils.randomID(16), passeArme: foundry.utils.randomID(16),
mortalite: arme?.system.mortalite, mortalite: arme?.system.mortalite,
+28 -28
View File
@@ -49,9 +49,8 @@ export default class ChatRollResult {
roll.active.actor, roll.active.actor,
roll.current?.rollmode?.key roll.current?.rollmode?.key
) )
const save = RollDialog.saveParts(roll, impacts)
await this.saveChatMessageRoll(chatMessage, save) await this.saveChatMessageRoll(chatMessage, roll, impacts)
return chatMessage return chatMessage
} }
@@ -166,8 +165,20 @@ export default class ChatRollResult {
return roll.active?.actor ?? (roll.ids?.actorId ? game.actors.get(roll.ids.actorId) : undefined) return roll.active?.actor ?? (roll.ids?.actorId ? game.actors.get(roll.ids.actorId) : undefined)
} }
async saveChatMessageRoll(chatMessage, savedRoll) { async updateChatMessage(chatMessage, savedRoll) {
await ChatUtility.setMessageData(chatMessage, 'rollData', savedRoll) RollDialog.loadRollData(savedRoll)
savedRoll.dmg = savedRoll.current.attaque?.dmg
await this.saveChatMessageRoll(chatMessage, savedRoll)
this.prepareDisplay(savedRoll)
chatMessage.update({ content: await this.buildRollHtml(savedRoll) })
chatMessage.render(true)
}
async saveChatMessageRoll(chatMessage, roll, impacts = undefined) {
const save = RollDialog.saveParts(roll, impacts)
await ChatUtility.setMessageData(chatMessage, 'rollData', save)
} }
loadChatMessageRoll(chatMessage) { loadChatMessageRoll(chatMessage) {
@@ -176,16 +187,6 @@ export default class ChatRollResult {
return savedRoll return savedRoll
} }
async updateChatMessage(chatMessage, savedRoll) {
await this.saveChatMessageRoll(chatMessage, savedRoll)
const copy = foundry.utils.duplicate(savedRoll)
RollDialog.loadRollData(copy)
savedRoll.dmg = copy.current.attaque?.dmg
this.prepareDisplay(copy)
chatMessage.update({ content: await this.buildRollHtml(copy) })
chatMessage.render(true)
}
onClickAppelChance(event) { onClickAppelChance(event) {
event.preventDefault() event.preventDefault()
const chatMessage = ChatUtility.getChatMessage(event) const chatMessage = ChatUtility.getChatMessage(event)
@@ -197,28 +198,28 @@ export default class ChatRollResult {
) )
} }
async onAppelChanceSuccess(reRoll, chatMessage) { async onAppelChanceSuccess(roll, chatMessage) {
reRoll.type.retry = true roll.type.retry = true
await this.updateChatMessage(chatMessage, reRoll) await this.updateChatMessage(chatMessage, roll)
const callbacks = [ChatUtility.remover(chatMessage)] const callbacks = [ChatUtility.remover(chatMessage)]
// TODO: annuler les effets... // TODO: annuler les effets...
switch (reRoll.type.current) { switch (roll.type.current) {
case ROLL_TYPE_DEFENSE: case ROLL_TYPE_DEFENSE:
this.getCombat(reRoll)?.doRollDefense(reRoll, callbacks) this.getCombat(roll)?.doRollDefense(roll, callbacks)
break break
case ROLL_TYPE_ATTAQUE: case ROLL_TYPE_ATTAQUE:
this.getCombat(reRoll)?.doRollAttaque(reRoll, callbacks) this.getCombat(roll)?.doRollAttaque(roll, callbacks)
break break
default: { default: {
await RollDialog.create(reRoll, { onRollDone: RollDialog.onRollDoneClose, callbacks }) await RollDialog.create(roll, { onRollDone: RollDialog.onRollDoneClose, callbacks })
} }
} }
} }
async onAppelChanceEchec(reRoll, chatMessage) { async onAppelChanceEchec(roll, chatMessage) {
reRoll.type.retry = true roll.type.retry = true
await this.updateChatMessage(chatMessage, reRoll) await this.updateChatMessage(chatMessage, roll)
} }
onClickAppelDestinee(event) { onClickAppelDestinee(event) {
@@ -229,10 +230,9 @@ export default class ChatRollResult {
const actor = this.getActiveActor(savedRoll) const actor = this.getActiveActor(savedRoll)
Misc.doIfOwner(actor, it => it.appelDestinee(async () => { Misc.doIfOwner(actor, it => it.appelDestinee(async () => {
const reRoll = foundry.utils.duplicate(savedRoll) savedRoll.type.retry = true
reRoll.type.retry = true RdDResolutionTable.significativeRequise(savedRoll.rolled)
RdDResolutionTable.significativeRequise(reRoll.rolled) await this.updateChatMessage(chatMessage, savedRoll)
await this.updateChatMessage(chatMessage, reRoll)
})) }))
} }
+29 -21
View File
@@ -198,9 +198,7 @@ export default class RollDialog extends HandlebarsApplicationMixin(ApplicationV2
} }
static onRollDoneClose(dialog, roll) { static onRollDoneClose(dialog, roll) {
if (roll.type.retry) { dialog.close()
dialog.close()
}
} }
static init() { } static init() { }
@@ -288,6 +286,13 @@ export default class RollDialog extends HandlebarsApplicationMixin(ApplicationV2
// rien pour l'instant // rien pour l'instant
} }
static getAllowedParts(rollData) {
return rollData.type?.allowed
? ROLL_PARTS.filter(p => RollDialog.$isIntersecting(rollData.type.allowed, p.rollTypes))
: ROLL_PARTS
}
/** pre-configure les paramètres des différentes parties de la fenêtre (par exemple, prépare les listes de caractéristiques/compétences */ /** pre-configure les paramètres des différentes parties de la fenêtre (par exemple, prépare les listes de caractéristiques/compétences */
static $prepareRollData(rollData) { static $prepareRollData(rollData) {
rollData.current = rollData.current ?? {} rollData.current = rollData.current ?? {}
@@ -301,17 +306,17 @@ export default class RollDialog extends HandlebarsApplicationMixin(ApplicationV2
? [potential] ? [potential]
: (rollData.type.allowed ?? ALL_ROLL_TYPES.filter(m => m.isAllowed(rollData) && m.visible(rollData)).map(m => m.code) ?? [ROLL_TYPE_COMP]) : (rollData.type.allowed ?? ALL_ROLL_TYPES.filter(m => m.isAllowed(rollData) && m.visible(rollData)).map(m => m.code) ?? [ROLL_TYPE_COMP])
const rollType = allowed.find(c => c == rollData.type.current) ?? allowed[0] const rollType = allowed.find(c => c == rollData.type.current) ?? allowed[0]
const allowedRollParts = RollDialog.getAllowedParts(rollData)
rollData.type.allowed = allowed rollData.type.allowed = allowed
rollData.type.current = rollType rollData.type.current = rollType
ALL_ROLL_TYPES.find(m => m.code == rollType).setRollDataType(rollData) ALL_ROLL_TYPES.find(m => m.code == rollType).setRollDataType(rollData)
rollData.refs = foundry.utils.mergeObject(rollData.refs ?? {}, Object.fromEntries(ROLL_PARTS.map(p => [p.code, {}]))); rollData.refs = foundry.utils.mergeObject(rollData.refs ?? {}, Object.fromEntries(allowedRollParts.map(p => [p.code, {}])));
rollData.options = rollData.options ?? { rollMode: game.settings.get("core", "rollMode") } rollData.options = rollData.options ?? { rollMode: game.settings.get("core", "rollMode") }
ROLL_PARTS.forEach(p => RollDialog.$initializeRollPart(rollData, p.code)) allowedRollParts.forEach(p => RollDialog.$initializeRollPart(rollData, p.code))
ROLL_PARTS allowedRollParts
.filter(p => RollDialog.$isIntersecting(allowed, p.rollTypes))
.filter(p => p.isValid(rollData)) .filter(p => p.isValid(rollData))
.forEach(p => { .forEach(p => {
p.restore(rollData) p.restore(rollData)
@@ -349,11 +354,13 @@ export default class RollDialog extends HandlebarsApplicationMixin(ApplicationV2
} }
if (from.current) { if (from.current) {
// stockage de current // stockage de current
ROLL_PARTS.filter(p => p.isActive(from)) RollDialog.getAllowedParts(from)
.filter(p => p.isActive(from))
.forEach(p => p.storeClean(from, target)) .forEach(p => p.storeClean(from, target))
} }
} }
} }
const target = RollBasicParts.initFrom(rollData) const target = RollBasicParts.initFrom(rollData)
saveBasics(rollData, target) saveBasics(rollData, target)
if (impacts) { if (impacts) {
@@ -381,12 +388,14 @@ export default class RollDialog extends HandlebarsApplicationMixin(ApplicationV2
onClose: rollOptions.onClose ?? RollDialog.onCloseDoNothing onClose: rollOptions.onClose ?? RollDialog.onCloseDoNothing
} }
this.chatRollResult = new ChatRollResult() this.chatRollResult = new ChatRollResult()
this.rollParts = RollDialog.getAllowedParts(rollData)
this.selectType() this.selectType()
this.registerHooks(rollData) this.registerHooks(rollData)
} }
registerHooks(rollData) { registerHooks(rollData) {
ROLL_PARTS.filter(p => p.isValid(rollData)) this.rollParts.filter(p => p.isValid(rollData))
.forEach(p => p.getHooks(this).forEach(h => { .forEach(p => p.getHooks(this).forEach(h => {
const hook = h.hook; const hook = h.hook;
const id = Hooks.on(hook, h.fn) const id = Hooks.on(hook, h.fn)
@@ -406,13 +415,12 @@ export default class RollDialog extends HandlebarsApplicationMixin(ApplicationV2
selectedType.onSelect(this.rollData) selectedType.onSelect(this.rollData)
this.rollData.type.label = selectedType.title(this.rollData) this.rollData.type.label = selectedType.title(this.rollData)
ROLL_PARTS.find(it => it.code == PART_CARAC).filterCaracs(this.rollData) this.rollParts.find(it => it.code == PART_CARAC).filterCaracs(this.rollData)
ROLL_PARTS.find(it => it.code == PART_COMP).filterComps(this.rollData) this.rollParts.find(it => it.code == PART_COMP).filterComps(this.rollData)
} }
getActiveParts() {
static getActiveParts(rollData) { return this.rollParts.filter(p => p.isActive(this.rollData))
return ROLL_PARTS.filter(p => p.isActive(rollData))
} }
rollTitle(rollData) { rollTitle(rollData) {
@@ -447,12 +455,13 @@ export default class RollDialog extends HandlebarsApplicationMixin(ApplicationV2
)) ))
Promise.all( Promise.all(
RollDialog.getActiveParts(this.rollData).map(async p => await p._onRender(this, context, options)) this.getActiveParts().map(async p => await p._onRender(this, context, options))
) )
} }
static getAjustements(rollData) { static getAjustements(rollData) {
return RollDialog.getActiveParts(rollData) return RollDialog.getAllowedParts(rollData)
.filter(p => p.isActive(rollData))
.map(p => p.getAjustements(rollData)) .map(p => p.getAjustements(rollData))
.reduce((a, b) => a.concat(b)) .reduce((a, b) => a.concat(b))
.sort((a, b) => a.value == undefined ? 1 : b.value == undefined ? -1 : 0) .sort((a, b) => a.value == undefined ? 1 : b.value == undefined ? -1 : 0)
@@ -469,8 +478,8 @@ export default class RollDialog extends HandlebarsApplicationMixin(ApplicationV2
.map(m => m.toTypeData(rollData)) .map(m => m.toTypeData(rollData))
RollBasicParts.loadSurprises(rollData, this.getSelectedType().code) RollBasicParts.loadSurprises(rollData, this.getSelectedType().code)
rollData.type.label = this.getSelectedType()?.title(rollData) rollData.type.label = this.getSelectedType()?.title(rollData)
//TOCHECK: set type.label ?
const visibleRollParts = RollDialog.getActiveParts(rollData) const visibleRollParts = this.getActiveParts()
visibleRollParts.forEach(p => p.applyExternalImpacts(visibleRollParts, rollData)) visibleRollParts.forEach(p => p.applyExternalImpacts(visibleRollParts, rollData))
this.setSpecialComp(visibleRollParts); this.setSpecialComp(visibleRollParts);
@@ -479,7 +488,7 @@ export default class RollDialog extends HandlebarsApplicationMixin(ApplicationV2
RollDialog.calculAjustement(rollData) RollDialog.calculAjustement(rollData)
const templates = RollDialog.getActiveParts(rollData).map(p => p.toTemplateData()) const templates = visibleRollParts.map(p => p.toTemplateData())
const context = await super._prepareContext() const context = await super._prepareContext()
return foundry.utils.mergeObject( return foundry.utils.mergeObject(
{ {
@@ -493,8 +502,7 @@ export default class RollDialog extends HandlebarsApplicationMixin(ApplicationV2
const specialComp = visibleRollParts.map(p => p.getSpecialComp(this.rollData)) const specialComp = visibleRollParts.map(p => p.getSpecialComp(this.rollData))
.reduce((a, b) => a.concat(b)) .reduce((a, b) => a.concat(b))
if (specialComp.length > 0) { if (specialComp.length > 0) {
const rollPartComp = RollDialog.getActiveParts(this.rollData) const rollPartComp = this.getActiveParts().find(it => it.code == PART_COMP)
.find(it => it.code == PART_COMP);
rollPartComp?.setSpecialComp(this.rollData, specialComp) rollPartComp?.setSpecialComp(this.rollData, specialComp)
} }
} }
+19 -9
View File
@@ -46,19 +46,25 @@ export class RollPartAttaque extends RollPartSelect {
return it.arme.isEmpoignade() return it.arme.isEmpoignade()
} }
store(rollData, targetData) { restore(rollData) {
super.store(rollData, targetData) const saved = this.getSaved(rollData) ?? {}
this.getSaved(targetData).dmg = this.getCurrent(rollData).dmg this.setCurrent(rollData, {
key: saved.key,
tactique: saved.tactique,
dmg: saved.dmg
})
} }
restore(rollData) { store(rollData, targetData) {
const saved = this.getSaved(rollData) const current = this.getCurrent(rollData)
super.restore(rollData) this.setSaved(targetData, {
if (saved.dmg != undefined) { key: current.key,
this.getCurrent(rollData).dmg = this.getSaved(rollData).dmg tactique: current.tactique,
} dmg: current.dmg
})
} }
findAttaque(attaques, saved) { findAttaque(attaques, saved) {
return attaques.find(at => at.arme.id == saved?.arme?.id && return attaques.find(at => at.arme.id == saved?.arme?.id &&
at.comp.id == saved?.comp?.id at.comp.id == saved?.comp?.id
@@ -102,7 +108,11 @@ export class RollPartAttaque extends RollPartSelect {
$selectAttaque(rollData, key) { $selectAttaque(rollData, key) {
const tactique = this.getCurrent(rollData).tactique
this.selectByKey(rollData, key) this.selectByKey(rollData, key)
if (tactique) {
this.getCurrent(rollData).tactique = tactique
}
} }
async _onRender(rollDialog, context, options) { async _onRender(rollDialog, context, options) {
+13 -5
View File
@@ -36,7 +36,7 @@ export class RollPartDefense extends RollPartSelect {
: defenseur.getCompetencesEsquive() : defenseur.getCompetencesEsquive()
const parades = isEmpoignade const parades = isEmpoignade
? [RdDItemArme.empoignade(defenseur)] ? [RdDItemArme.empoignade(defenseur)]
: defenseur.items.filter(it => it.isParade() && (!refs.distance || it.isBouclier())) : this.$getParades(defenseur, attackerRoll, refs.distance)
refs.defenses = [ refs.defenses = [
...esquives.map(it => RollPartDefense.$extractEsquive(it, defenseur, attackerRoll)), ...esquives.map(it => RollPartDefense.$extractEsquive(it, defenseur, attackerRoll)),
@@ -45,6 +45,14 @@ export class RollPartDefense extends RollPartSelect {
this.$selectDefense(rollData) this.$selectDefense(rollData)
} }
$getParades(defenseur, attackerRoll, distance) {
const parades = defenseur.items.filter(it => it.isParade() && (!distance || it.isBouclier()))
const armeAttaque = attackerRoll?.current?.attaque?.arme
return armeAttaque
? parades.filter(armeDefense => RdDItemArme.defenseArmeParade(armeAttaque, armeDefense) != 'impossible')
: parades
}
static $extractEsquive(esquive, defenseur, attackerRoll) { static $extractEsquive(esquive, defenseur, attackerRoll) {
const defense = { const defense = {
key: esquive.id, key: esquive.id,
@@ -124,19 +132,19 @@ export class RollPartDefense extends RollPartSelect {
case PART_CARAC: return part.filterCaracs(rollData, refs.defenses.length > 0 ? [current.carac] : ['impossible']) case PART_CARAC: return part.filterCaracs(rollData, refs.defenses.length > 0 ? [current.carac] : ['impossible'])
case PART_COMP: return part.filterComps(rollData, refs.defenses.length > 0 ? [current.comp?.name] : ['impossible']) case PART_COMP: return part.filterComps(rollData, refs.defenses.length > 0 ? [current.comp?.name] : ['impossible'])
case PART_DIFF: return part.setDiff(rollData, this.getDiffDefense(rollData)) case PART_DIFF: return part.setDiff(rollData, this.getDiffDefense(rollData))
case PART_SIGN: return part.setArme(rollData, this.isArmeDisparate(rollData), current.forceRequise) case PART_SIGN: return part.setArme(rollData, this.getArmeDisparate(rollData), current.forceRequise)
} }
} }
return undefined return undefined
} }
isArmeDisparate(rollData) { getArmeDisparate(rollData) {
const armeDefense = this.getCurrent(rollData).arme const armeDefense = this.getCurrent(rollData).arme
if (armeDefense) { if (armeDefense) {
const armeAttaque = rollData.attackerRoll?.current.attaque.arme const armeAttaque = rollData.attackerRoll?.current.attaque.arme
return RdDItemArme.defenseArmeParade(armeAttaque, armeDefense) == 'sign' return RdDItemArme.defenseArmeParade(armeAttaque, armeDefense)
} }
return false return 'norm'
} }
getDiffDefense(rollData) { getDiffDefense(rollData) {
+2 -2
View File
@@ -40,7 +40,7 @@ export class RollPartSign extends RollPart {
const actor = rollData.active.actor; const actor = rollData.active.actor;
const isCombat = this.isCombat(rollData) const isCombat = this.isCombat(rollData)
const current = this.getCurrent(rollData) const current = this.getCurrent(rollData)
current.armeDisparate = isCombat && current.armeDisparate current.armeDisparate = isCombat ? current.armeDisparate : 'norm'
current.surprise = actor.getSurprise(isCombat, current.forceRequise ?? 0) current.surprise = actor.getSurprise(isCombat, current.forceRequise ?? 0)
current.reasons = actor.getEffects(it => StatusEffects.niveauSurprise(it, isCombat) > 0, current.forceRequise ?? 0) current.reasons = actor.getEffects(it => StatusEffects.niveauSurprise(it, isCombat) > 0, current.forceRequise ?? 0)
.map(it => { return { img: it.img, label: game.i18n.localize(it.name) } }) .map(it => { return { img: it.img, label: game.i18n.localize(it.name) } })
@@ -81,7 +81,7 @@ export class RollPartSign extends RollPart {
} }
isParadeArmeDisparate(current) { isParadeArmeDisparate(current) {
return current.armeDisparate return current.armeDisparate == 'sign'
} }
getAjustements(rollData) { getAjustements(rollData) {
+6 -1
View File
@@ -11,8 +11,13 @@
{{#if (eq active.surprise.key 'totale')}} {{#if (eq active.surprise.key 'totale')}}
<span><strong>{{active.name}}</strong> est totalement surpris</span> <span><strong>{{active.name}}</strong> est totalement surpris</span>
{{else}} {{else}}
{{log 'attackerRoll' attackerRoll}}
<span><strong>{{active.name}}</strong> doit se défendre <span><strong>{{active.name}}</strong> doit se défendre
{{~#if (eq active.surprise.key 'demi')}} avec une significative {{/if}} d'une attaque {{~#if (eq active.surprise.key 'demi')}} avec une significative {{/if}} d'une
{{#if (eq attackerRoll.current.attaque.tactique.key 'charge')}}charge
{{else if (eq attackerRoll.current.attaque.tactique.key 'feinte')}}feinte
{{else}}attaque
{{/if}}
{{~#if attackerRoll.particuliere}} <strong>particulière en {{~#if attackerRoll.particuliere}} <strong>particulière en
{{~#if (eq attackerRoll.particuliere 'finesse')}} finesse {{~#if (eq attackerRoll.particuliere 'finesse')}} finesse
{{else if (eq attackerRoll.particuliere 'force')}} force {{else if (eq attackerRoll.particuliere 'force')}} force