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
- les soins de blessures sont correctement appliqués
- 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
+14 -8
View File
@@ -174,24 +174,30 @@ export class RdDItemArme extends RdDItem {
static defenseArmeParade(armeAttaque, armeParade) {
const defCategory = RdDItemArme.getCategorieParade(armeParade)
if (defCategory == 'boucliers') {
switch (defCategory) {
case 'boucliers':
return 'norm'
case '':
return 'impossible'
}
if (armeAttaque?.system?.competence?.toLowerCase().match(/(fléau)/)) {
return ''
}
if (armeParade.system?.tir) {
return ''
if (RdDItemArme.$isFleau(armeAttaque)) {
return 'impossible'
}
const attCategory = RdDItemArme.getCategorieParade(armeAttaque)
switch (attCategory) {
case 'armes-naturelles': case 'sans-armes':
return defCategory == 'sans-armes' ? 'norm' : ''
case 'sans-armes':
case 'armes-naturelles':
return defCategory == attCategory ? 'norm' : 'impossible'
default:
return RdDItemArme.needParadeSignificative(armeAttaque, armeParade) ? 'sign' : 'norm'
}
}
static $isFleau(armeAttaque) {
return armeAttaque?.system?.competence?.toLowerCase().match(/(fléau)/);
}
/* -------------------------------------------- */
static needParadeSignificative(armeAttaque, armeParade) {
if (!armeAttaque || !armeParade) {
+1 -1
View File
@@ -775,7 +775,7 @@ export class RdDCombat {
/* -------------------------------------------- */
_prepareAttaque(competence, arme) {
let rollData = {
mode: ROLL_TYPE_ATTAQUE,
mode: 'attaque',
alias: this.attacker?.getAlias(),
passeArme: foundry.utils.randomID(16),
mortalite: arme?.system.mortalite,
+28 -28
View File
@@ -49,9 +49,8 @@ export default class ChatRollResult {
roll.active.actor,
roll.current?.rollmode?.key
)
const save = RollDialog.saveParts(roll, impacts)
await this.saveChatMessageRoll(chatMessage, save)
await this.saveChatMessageRoll(chatMessage, roll, impacts)
return chatMessage
}
@@ -166,8 +165,20 @@ export default class ChatRollResult {
return roll.active?.actor ?? (roll.ids?.actorId ? game.actors.get(roll.ids.actorId) : undefined)
}
async saveChatMessageRoll(chatMessage, savedRoll) {
await ChatUtility.setMessageData(chatMessage, 'rollData', savedRoll)
async updateChatMessage(chatMessage, 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) {
@@ -176,16 +187,6 @@ export default class ChatRollResult {
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) {
event.preventDefault()
const chatMessage = ChatUtility.getChatMessage(event)
@@ -197,28 +198,28 @@ export default class ChatRollResult {
)
}
async onAppelChanceSuccess(reRoll, chatMessage) {
reRoll.type.retry = true
await this.updateChatMessage(chatMessage, reRoll)
async onAppelChanceSuccess(roll, chatMessage) {
roll.type.retry = true
await this.updateChatMessage(chatMessage, roll)
const callbacks = [ChatUtility.remover(chatMessage)]
// TODO: annuler les effets...
switch (reRoll.type.current) {
switch (roll.type.current) {
case ROLL_TYPE_DEFENSE:
this.getCombat(reRoll)?.doRollDefense(reRoll, callbacks)
this.getCombat(roll)?.doRollDefense(roll, callbacks)
break
case ROLL_TYPE_ATTAQUE:
this.getCombat(reRoll)?.doRollAttaque(reRoll, callbacks)
this.getCombat(roll)?.doRollAttaque(roll, callbacks)
break
default: {
await RollDialog.create(reRoll, { onRollDone: RollDialog.onRollDoneClose, callbacks })
await RollDialog.create(roll, { onRollDone: RollDialog.onRollDoneClose, callbacks })
}
}
}
async onAppelChanceEchec(reRoll, chatMessage) {
reRoll.type.retry = true
await this.updateChatMessage(chatMessage, reRoll)
async onAppelChanceEchec(roll, chatMessage) {
roll.type.retry = true
await this.updateChatMessage(chatMessage, roll)
}
onClickAppelDestinee(event) {
@@ -229,10 +230,9 @@ export default class ChatRollResult {
const actor = this.getActiveActor(savedRoll)
Misc.doIfOwner(actor, it => it.appelDestinee(async () => {
const reRoll = foundry.utils.duplicate(savedRoll)
reRoll.type.retry = true
RdDResolutionTable.significativeRequise(reRoll.rolled)
await this.updateChatMessage(chatMessage, reRoll)
savedRoll.type.retry = true
RdDResolutionTable.significativeRequise(savedRoll.rolled)
await this.updateChatMessage(chatMessage, savedRoll)
}))
}
+28 -20
View File
@@ -198,10 +198,8 @@ export default class RollDialog extends HandlebarsApplicationMixin(ApplicationV2
}
static onRollDoneClose(dialog, roll) {
if (roll.type.retry) {
dialog.close()
}
}
static init() { }
@@ -288,6 +286,13 @@ export default class RollDialog extends HandlebarsApplicationMixin(ApplicationV2
// 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 */
static $prepareRollData(rollData) {
rollData.current = rollData.current ?? {}
@@ -301,17 +306,17 @@ export default class RollDialog extends HandlebarsApplicationMixin(ApplicationV2
? [potential]
: (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 allowedRollParts = RollDialog.getAllowedParts(rollData)
rollData.type.allowed = allowed
rollData.type.current = rollType
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") }
ROLL_PARTS.forEach(p => RollDialog.$initializeRollPart(rollData, p.code))
ROLL_PARTS
.filter(p => RollDialog.$isIntersecting(allowed, p.rollTypes))
allowedRollParts.forEach(p => RollDialog.$initializeRollPart(rollData, p.code))
allowedRollParts
.filter(p => p.isValid(rollData))
.forEach(p => {
p.restore(rollData)
@@ -349,11 +354,13 @@ export default class RollDialog extends HandlebarsApplicationMixin(ApplicationV2
}
if (from.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))
}
}
}
const target = RollBasicParts.initFrom(rollData)
saveBasics(rollData, target)
if (impacts) {
@@ -381,12 +388,14 @@ export default class RollDialog extends HandlebarsApplicationMixin(ApplicationV2
onClose: rollOptions.onClose ?? RollDialog.onCloseDoNothing
}
this.chatRollResult = new ChatRollResult()
this.rollParts = RollDialog.getAllowedParts(rollData)
this.selectType()
this.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 => {
const hook = h.hook;
const id = Hooks.on(hook, h.fn)
@@ -406,13 +415,12 @@ export default class RollDialog extends HandlebarsApplicationMixin(ApplicationV2
selectedType.onSelect(this.rollData)
this.rollData.type.label = selectedType.title(this.rollData)
ROLL_PARTS.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_CARAC).filterCaracs(this.rollData)
this.rollParts.find(it => it.code == PART_COMP).filterComps(this.rollData)
}
static getActiveParts(rollData) {
return ROLL_PARTS.filter(p => p.isActive(rollData))
getActiveParts() {
return this.rollParts.filter(p => p.isActive(this.rollData))
}
rollTitle(rollData) {
@@ -447,12 +455,13 @@ export default class RollDialog extends HandlebarsApplicationMixin(ApplicationV2
))
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) {
return RollDialog.getActiveParts(rollData)
return RollDialog.getAllowedParts(rollData)
.filter(p => p.isActive(rollData))
.map(p => p.getAjustements(rollData))
.reduce((a, b) => a.concat(b))
.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))
RollBasicParts.loadSurprises(rollData, this.getSelectedType().code)
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))
this.setSpecialComp(visibleRollParts);
@@ -479,7 +488,7 @@ export default class RollDialog extends HandlebarsApplicationMixin(ApplicationV2
RollDialog.calculAjustement(rollData)
const templates = RollDialog.getActiveParts(rollData).map(p => p.toTemplateData())
const templates = visibleRollParts.map(p => p.toTemplateData())
const context = await super._prepareContext()
return foundry.utils.mergeObject(
{
@@ -493,8 +502,7 @@ export default class RollDialog extends HandlebarsApplicationMixin(ApplicationV2
const specialComp = visibleRollParts.map(p => p.getSpecialComp(this.rollData))
.reduce((a, b) => a.concat(b))
if (specialComp.length > 0) {
const rollPartComp = RollDialog.getActiveParts(this.rollData)
.find(it => it.code == PART_COMP);
const rollPartComp = this.getActiveParts().find(it => it.code == PART_COMP)
rollPartComp?.setSpecialComp(this.rollData, specialComp)
}
}
+19 -9
View File
@@ -46,19 +46,25 @@ export class RollPartAttaque extends RollPartSelect {
return it.arme.isEmpoignade()
}
store(rollData, targetData) {
super.store(rollData, targetData)
this.getSaved(targetData).dmg = this.getCurrent(rollData).dmg
restore(rollData) {
const saved = this.getSaved(rollData) ?? {}
this.setCurrent(rollData, {
key: saved.key,
tactique: saved.tactique,
dmg: saved.dmg
})
}
restore(rollData) {
const saved = this.getSaved(rollData)
super.restore(rollData)
if (saved.dmg != undefined) {
this.getCurrent(rollData).dmg = this.getSaved(rollData).dmg
}
store(rollData, targetData) {
const current = this.getCurrent(rollData)
this.setSaved(targetData, {
key: current.key,
tactique: current.tactique,
dmg: current.dmg
})
}
findAttaque(attaques, saved) {
return attaques.find(at => at.arme.id == saved?.arme?.id &&
at.comp.id == saved?.comp?.id
@@ -102,7 +108,11 @@ export class RollPartAttaque extends RollPartSelect {
$selectAttaque(rollData, key) {
const tactique = this.getCurrent(rollData).tactique
this.selectByKey(rollData, key)
if (tactique) {
this.getCurrent(rollData).tactique = tactique
}
}
async _onRender(rollDialog, context, options) {
+13 -5
View File
@@ -36,7 +36,7 @@ export class RollPartDefense extends RollPartSelect {
: defenseur.getCompetencesEsquive()
const parades = isEmpoignade
? [RdDItemArme.empoignade(defenseur)]
: defenseur.items.filter(it => it.isParade() && (!refs.distance || it.isBouclier()))
: this.$getParades(defenseur, attackerRoll, refs.distance)
refs.defenses = [
...esquives.map(it => RollPartDefense.$extractEsquive(it, defenseur, attackerRoll)),
@@ -45,6 +45,14 @@ export class RollPartDefense extends RollPartSelect {
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) {
const defense = {
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_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_SIGN: return part.setArme(rollData, this.isArmeDisparate(rollData), current.forceRequise)
case PART_SIGN: return part.setArme(rollData, this.getArmeDisparate(rollData), current.forceRequise)
}
}
return undefined
}
isArmeDisparate(rollData) {
getArmeDisparate(rollData) {
const armeDefense = this.getCurrent(rollData).arme
if (armeDefense) {
const armeAttaque = rollData.attackerRoll?.current.attaque.arme
return RdDItemArme.defenseArmeParade(armeAttaque, armeDefense) == 'sign'
return RdDItemArme.defenseArmeParade(armeAttaque, armeDefense)
}
return false
return 'norm'
}
getDiffDefense(rollData) {
+2 -2
View File
@@ -40,7 +40,7 @@ export class RollPartSign extends RollPart {
const actor = rollData.active.actor;
const isCombat = this.isCombat(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.reasons = actor.getEffects(it => StatusEffects.niveauSurprise(it, isCombat) > 0, current.forceRequise ?? 0)
.map(it => { return { img: it.img, label: game.i18n.localize(it.name) } })
@@ -81,7 +81,7 @@ export class RollPartSign extends RollPart {
}
isParadeArmeDisparate(current) {
return current.armeDisparate
return current.armeDisparate == 'sign'
}
getAjustements(rollData) {
+6 -1
View File
@@ -11,8 +11,13 @@
{{#if (eq active.surprise.key 'totale')}}
<span><strong>{{active.name}}</strong> est totalement surpris</span>
{{else}}
{{log 'attackerRoll' attackerRoll}}
<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 (eq attackerRoll.particuliere 'finesse')}} finesse
{{else if (eq attackerRoll.particuliere 'force')}} force