Gestion attaques v2 et initiative

This commit is contained in:
2025-09-30 01:33:08 +02:00
parent d0ba1ebf99
commit 7370b633db
44 changed files with 1033 additions and 515 deletions

View File

@@ -16,10 +16,11 @@ export default class ChatRollResult {
static onReady() {
foundry.applications.handlebars.loadTemplates({
'partial-appel-chance': 'systems/foundryvtt-reve-de-dragon/templates/roll/result/partial-appel-chance.hbs',
'partial-attaque-particuliere': 'systems/foundryvtt-reve-de-dragon/templates/roll/result/partial-attaque-particuliere.hbs',
'partial-encaissement': 'systems/foundryvtt-reve-de-dragon/templates/roll/result/partial-encaissement.hbs',
'partial-recul-choc': 'systems/foundryvtt-reve-de-dragon/templates/roll/result/partial-recul-choc.hbs',
'partial-info-appel-moral': 'systems/foundryvtt-reve-de-dragon/templates/roll/result/partial-info-appel-moral.hbs',
})
})
}
async display(roll) {
@@ -38,12 +39,12 @@ export default class ChatRollResult {
}
prepareDisplay(roll) {
roll.done = roll.done || {}
roll.show = roll.show || {}
roll.done = roll.done ?? {}
roll.show = roll.show ?? {}
roll.show.chance = this.isAppelChancePossible(roll)
roll.show.encaissement = this.isShowEncaissement(roll)
roll.show.recul = this.getReculChoc(roll)
roll.show.recul = this.getRecul(roll)
roll.show.particuliere = roll.show.particuliere ?? []
}
isAppelChancePossible(roll) {
@@ -57,20 +58,36 @@ export default class ChatRollResult {
roll.attackerRoll?.dmg.mortalite != 'empoignade'
}
getReculChoc(roll, defender = roll.active.actor, attacker = roll.opponent.actor) {
const attaque = roll.attackerRoll
if (attaque &&
(roll.rolled.isEchec || !roll.current.defense.isEsquive) &&
(attaque.particuliere == 'force' || 'charge' == attaque.tactique?.key)) {
const taille = defender.system.carac.taille.value
const impact = attacker.system.carac.force.value + roll.attackerRoll?.dmg.dmgArme
return {
raison: 'charge' == attaque.tactique?.key ? 'charge' : 'particulière en force',
taille: taille,
impact: impact,
chances: RdDResolutionTable.computeChances(10, taille-impact).norm,
diff: taille - impact
}
getRecul(roll, defender = roll.active.actor, attacker = roll.opponent.actor) {
switch (roll.type.current) {
case ROLL_TYPE_DEFENSE:
{
const attaque = roll.attackerRoll
if (attaque &&
(roll.rolled.isEchec || !roll.current.defense.isEsquive) &&
(attaque.particuliere == 'force' || 'charge' == attaque.tactique?.key)) {
const taille = defender.system.carac.taille.value
const impact = attacker.system.carac.force.value + roll.attackerRoll?.dmg.dmgArme
return {
raison: 'charge' == attaque.tactique?.key ? 'charge' : 'particulière en force',
taille: taille,
impact: impact,
chances: RdDResolutionTable.computeChances(10, taille - impact).norm,
diff: taille - impact
}
}
break
}
case ROLL_TYPE_ATTAQUE:
{
const attaque = roll
if (attaque.particuliere == 'force' || 'charge' == attaque.tactique?.key) {
return {
raison: 'charge' == attaque.tactique?.key ? 'charge' : 'particulière en force',
}
}
}
}
return undefined
}
@@ -85,6 +102,7 @@ export default class ChatRollResult {
$(html).on("click", '.appel-destinee', event => this.onClickAppelDestinee(event))
$(html).on("click", '.encaissement', event => this.onClickEncaissement(event))
$(html).on("click", '.resister-recul', event => this.onClickRecul(event))
$(html).on("click", '.choix-particuliere', event => this.onClickChoixParticuliere(event))
}
@@ -178,4 +196,13 @@ export default class ChatRollResult {
await this.updateChatMessage(chatMessage, savedRoll)
}
async onClickChoixParticuliere(event) {
const choix = event.currentTarget.attributes['data-particuliere'].value
const chatMessage = ChatUtility.getChatMessage(event)
const savedRoll = ChatUtility.getMessageData(chatMessage, 'rollData')
savedRoll.particuliere = choix
savedRoll.particulieres = [RDD_CONFIG.particuliere[choix]]
await this.updateChatMessage(chatMessage, savedRoll)
await this.getCombat(savedRoll)?.onAttaqueV2(savedRoll, callbacks)
}
}

View File

@@ -4,9 +4,10 @@ import { ROLL_TYPE_ATTAQUE, ROLL_TYPE_DEFENSE } from "./roll-constants.mjs"
import { PART_ATTAQUE } from "./roll-part-attaque.mjs"
import { PART_DEFENSE } from "./roll-part-defense.mjs"
export class RollBasicParts {
restore(rollData) {
static restore(rollData) {
rollData.ids.sceneId = rollData.ids.sceneId ?? canvas.scene.id
rollData.active = RollBasicParts.$getActor(rollData)
rollData.opponent = RollBasicParts.$getOpponent(rollData)
@@ -15,14 +16,14 @@ export class RollBasicParts {
}
}
loadSurprises(rollData, type) {
static loadSurprises(rollData, type = rollData.type.current) {
if (!rollData.type.passif) {
this.loadSurprise(rollData.active, this.getForceRequiseActiveActor(rollData, type))
this.loadSurprise(rollData.opponent, 0)
RollBasicParts.loadSurprise(rollData.active, RollBasicParts.getForceRequiseActiveActor(rollData, type))
RollBasicParts.loadSurprise(rollData.opponent, 0)
}
}
loadSurprise(who, forceRequise) {
static loadSurprise(who, forceRequise) {
if (who?.actor) {
foundry.utils.mergeObject(who,
StatusEffects.getActorEffetSurprise(who.actor, forceRequise),
@@ -30,15 +31,15 @@ export class RollBasicParts {
}
}
getForceRequiseActiveActor(rollData, type) {
static getForceRequiseActiveActor(rollData, type) {
switch (type) {
case ROLL_TYPE_ATTAQUE: return rollData.current[PART_ATTAQUE].attaque.forceRequise
case ROLL_TYPE_ATTAQUE: return rollData.current[PART_ATTAQUE].forceRequise
case ROLL_TYPE_DEFENSE: return rollData.current[PART_DEFENSE].forceRequise
default: return 0
}
}
initFrom(rollData) {
static initFrom(rollData) {
return {
selected: {},
type: rollData.type,
@@ -52,6 +53,16 @@ export class RollBasicParts {
}
}
static reverseIds(rollData) {
return {
sceneId: rollData.ids.sceneId,
actorId: rollData.ids.opponentId,
actorTokenId: rollData.ids.opponentTokenId,
opponentId: rollData.ids.actorId,
opponentTokenId: rollData.actorTokenId
}
}
static $getActor(rollData) {
if (rollData.ids.actorTokenId) {
return ActorToken.fromTokenId(rollData.ids.actorTokenId, rollData.ids.sceneId)

View File

@@ -6,6 +6,10 @@ import { ReglesOptionnelles } from "../settings/regles-optionnelles.js";
import { PART_OEUVRE } from "./roll-part-oeuvre.mjs";
import { RdDItemArme } from "../item/arme.js";
import { RdDBonus } from "../rdd-bonus.js";
import { ITEM_TYPES, RDD_CONFIG } from "../constants.js";
import { CARACS } from "../rdd-carac.js";
import { ROLL_TYPE_ATTAQUE } from "./roll-constants.mjs";
import { PART_ATTAQUE } from "./roll-part-attaque.mjs";
/* -------------------------------------------- */
export class RollDialogAdapter {
@@ -15,21 +19,21 @@ export class RollDialogAdapter {
carac: rollData.current.carac.value,
diff: rollData.current.totaldiff,
bonus: rollData.current.bonus,
sign: rollData.current.sign,
showDice: rollData.options.showDice,
rollMode: rollData.current.rollmode.key
})
const rolled = await RollDialogAdapter.rollChances(rollData, chances)
RollDialogAdapter.adjustRollDataForV1(rollData, rolled, rollTitle)
RollDialogAdapter.setRollDataRolled(rollData, rolled, rollTitle)
RollDialogAdapter.adjustRollDataForV1(rollData)
RollDialogAdapter.adjustAttaqueParticuliere(rollData)
return rolled
}
static computeChances({ carac, diff, bonus, sign, showDice, rollMode }) {
static computeChances({ carac, diff, bonus, showDice, rollMode }) {
const chances = foundry.utils.duplicate(RdDResolutionTable.computeChances(carac, diff))
RdDResolutionTable._updateChancesWithBonus(chances, bonus, diff)
RdDResolutionTable._updateChancesFactor(chances, sign)
chances.showDice = showDice
chances.rollMode = rollMode
return chances
@@ -37,7 +41,7 @@ export class RollDialogAdapter {
static async rollChances(rollData, chances) {
const rolled = await RdDResolutionTable.rollChances(chances,
rollData.current.sign,
rollData.current.sign.diviseur,
rollData.current.resultat)
rolled.caracValue = rollData.current.carac.value
rolled.finalLevel = rollData.current.totaldiff
@@ -46,13 +50,20 @@ export class RollDialogAdapter {
return rolled
}
static adjustRollDataForV1(rollData, rolled, rollTitle) {
static setRollDataRolled(rollData, rolled, rollTitle) {
rollData.rolled = rolled
rollData.choix = rollData.choix ?? {}
rollData.show = rollData.show ?? {}
rollData.show.title = rollTitle
}
static adjustRollDataForV1(rollData) {
const rolled = rollData.rolled
// temporaire pour être homogène roll v1
rollData.alias = rollData.active.actor.getAlias()
// pour experience
rollData.finalLevel = rollData.current.totaldiff
if (rollData.use == undefined) { rollData.use = {} }
if (rollData.show == undefined) { rollData.show = {} }
if (rollData.ajustements == undefined) {
rollData.ajustements = {}
}
@@ -75,7 +86,6 @@ export class RollDialogAdapter {
if (rollData.current[PART_APPELMORAL]?.checked) {
rollData.use.moral = true
}
rollData.rolled = rolled
if (ReglesOptionnelles.isUsing("afficher-colonnes-reussite")) {
rolled.niveauNecessaire = this.findNiveauNecessaire(carac, rolled.roll)
rolled.ajustementNecessaire = rolled.niveauNecessaire - diff
@@ -88,7 +98,39 @@ export class RollDialogAdapter {
descr: aj.diff == undefined ? aj.label : undefined
}
})
rollData.show.title = rollTitle
}
static adjustAttaqueParticuliere(rollData) {
if (rollData.type.current != ROLL_TYPE_ATTAQUE || !rollData.rolled.isPart) {
return
}
const attaque = rollData.current.attaque;
const choix = []
const isEmpoignade = attaque.dmg.mortalite == 'empoignade';
const isCharge = attaque.tactique == 'charge'
/* TODO: cas de créatures faisant des lancers, Glou, Glipzouk */
const isMeleeDiffNegative = (attaque.comp.type == ITEM_TYPES.competencecreature || rollData.current.carac.key == CARACS.MELEE)
&& rollData.current.diff.value < 0
// force toujours, sauf empoignade
if (!isEmpoignade) {
choix.push(RDD_CONFIG.particuliere.force)
}
// finesse seulement en mélée, pour l'empoignade, ou si la difficulté libre est de -1 minimum
if (!isCharge && (isEmpoignade || isMeleeDiffNegative)) {
choix.push(RDD_CONFIG.particuliere.finesse)
}
// rapidité seulement en mêlée, si l'arme le permet, et si la difficulté libre est de -1 minimum
if (!isCharge && !isEmpoignade && isMeleeDiffNegative && attaque.arme.system.rapide) {
choix.push(RDD_CONFIG.particuliere.rapidite)
}
if (choix.length == 1) {
rollData.particuliere = choix[0].key
}
rollData.particulieres = choix
}
static mapActionAttaque(attackerRoll) {
@@ -99,17 +141,15 @@ export class RollDialogAdapter {
return {
// correspond à l'attaque de RollPartAttaque (dans rollDta.current.attaque)
label: label,
attaque: {
// correspond aux actions d'attaques dans RdDActor.listActionsAttaque
name: label,
// action: 'attaque',
arme: attackerRoll.arme,
comp: attackerRoll.competence,
main: RdDItemArme.getMainAttaque(attackerRoll.competence),
equipe: attackerRoll.arme.system.equipe,
// carac: { key: caracCode, value: caracValue },
// dommagesArme: dommagesArme,
},
// correspond aux actions d'attaques dans RdDActor.listActionsAttaque
name: label,
// action: 'attaque',
arme: attackerRoll.arme,
comp: attackerRoll.competence,
main: RdDItemArme.getMainAttaque(attackerRoll.competence),
equipe: attackerRoll.arme.system.equipe,
// carac: { key: caracCode, value: caracValue },
// dommagesArme: dommagesArme,
diff: attackerRoll.diffLibre,
particuliere: attackerRoll.particuliere,
tactique: RdDBonus.find(attackerRoll.tactique),

View File

@@ -37,7 +37,7 @@ import { RollPartAttaque } from "./roll-part-attaque.mjs";
import { RollPartDefense } from "./roll-part-defense.mjs";
import { RollDialogAdapter } from "./roll-dialog-adapter.mjs";
import { ROLLDIALOG_SECTION } from "./roll-part.mjs";
import { ROLL_TYPE_COMP } from "./roll-constants.mjs";
import { ROLL_TYPE_ATTAQUE, ROLL_TYPE_COMP } from "./roll-constants.mjs";
import ChatRollResult from "./chat-roll-result.mjs";
import { renderTemplate } from "../constants.js";
@@ -58,8 +58,6 @@ const ALL_ROLL_TYPES = [
// new RollTypeFixedCarac ??
]
const BASIC_PARTS = new RollBasicParts()
const ROLL_PARTS = [
new RollPartActor(),
new RollPartAction(),
@@ -257,7 +255,7 @@ export default class RollDialog extends HandlebarsApplicationMixin(ApplicationV2
rollData.selected = rollData.selected ?? {}
rollData.type = rollData.type ?? {}
rollData.type.retry = rollData.type.retry ?? false
BASIC_PARTS.restore(rollData)
RollBasicParts.restore(rollData)
const potential = ALL_ROLL_TYPES.find(m => m.code == rollData.type.current)?.code
const allowed = rollData.type.retry && potential
@@ -283,13 +281,14 @@ export default class RollDialog extends HandlebarsApplicationMixin(ApplicationV2
}
static saveParts(rollData) {
const target = BASIC_PARTS.initFrom(rollData)
const target = RollBasicParts.initFrom(rollData)
ROLL_PARTS.filter(p => p.isActive(rollData))
.forEach(p => p.storeClean(rollData, target))
target.attackerRoll = rollData.attackerRoll
target.rolled = rollData.rolled
target.result = rollData.result
target.done = target.done ?? {}
target.done = rollData.done ?? {}
target.dmg = rollData.dmg
return target
}
@@ -377,7 +376,7 @@ export default class RollDialog extends HandlebarsApplicationMixin(ApplicationV2
const types = ALL_ROLL_TYPES.filter(m => m.isAllowed(rollData) && m.visible(rollData))
.map(m => m.toTypeData(rollData))
BASIC_PARTS.loadSurprises(rollData, this.getSelectedType().code)
RollBasicParts.loadSurprises(rollData, this.getSelectedType().code)
rollData.type.label = this.getSelectedType()?.title(rollData)
//TOCHECK: set type.label ?
const visibleRollParts = RollDialog.getActiveParts(rollData)
@@ -423,18 +422,17 @@ export default class RollDialog extends HandlebarsApplicationMixin(ApplicationV2
}
async roll() {
// ROLL_PARTS.filter(p => p.isActive(this.rollData))
// .forEach(p => p.validate(this.rollData))
const roll = RollDialog.saveParts(this.rollData)
RollDialog.loadRollData(roll)
roll.current.resultat = this.rollData.current[PART_TRICHER]?.resultat ?? -1
roll.rolled = await this.$rollDice(roll)
roll.choix = {}
roll.rolled = await RollDialogAdapter.rollDice(roll, this.rollTitle(roll))
roll.result = this.getSelectedType(roll).getResult(roll)
console.info('RollDialog.roll:', roll)
await Promise.all(this.rollOptions.callbacks.map(async callback => await callback(roll)))
await this.chatRollResult.display(roll)
console.info('RollDialog.roll:', roll)
await this.chatRollResult.display(roll)
await Promise.all(this.rollOptions.callbacks.map(async callback => await callback(roll)))
this.rollOptions.onRollDone(this)
}
@@ -444,12 +442,9 @@ export default class RollDialog extends HandlebarsApplicationMixin(ApplicationV2
roll.v2 = true
}
async defaultCallback(rollData, rolled) {
await rollData.active.actor.appliquerAjoutExperience(rollData)
await rollData.active.actor.appliquerAppelMoral(rollData)
async defaultCallback(roll, rolled) {
await roll.active.actor.appliquerAjoutExperience(roll)
await roll.active.actor.appliquerAppelMoral(roll)
}
async $rollDice(rollData) {
return await RollDialogAdapter.rollDice(rollData, this.rollTitle(rollData))
}
}

View File

@@ -1,5 +1,4 @@
import { RdDBonus } from "../rdd-bonus.js"
import { ReglesOptionnelles } from "../settings/regles-optionnelles.js"
import { ROLL_TYPE_ATTAQUE } from "./roll-constants.mjs"
import { PART_CARAC } from "./roll-part-carac.mjs"
import { PART_COMP } from "./roll-part-comp.mjs"
@@ -40,12 +39,12 @@ export class RollPartAttaque extends RollPartSelect {
choices(refs) { return refs.attaques }
static $extractAttaque(attaque, actor) {
return {
key: `${attaque.action}::${attaque.name}`,
label: attaque.name,
attaque: attaque,
tactique: TACTIQUES[0],
}
return foundry.utils.mergeObject({
key: `${attaque.action}::${attaque.label}`,
tactique: TACTIQUES[0]
},
attaque
)
}
prepareContext(rollData) {
@@ -100,8 +99,8 @@ export class RollPartAttaque extends RollPartSelect {
if (this.visible(rollData)) {
const current = this.getCurrent(rollData)
switch (part.code) {
case PART_CARAC: return part.filterCaracs(rollData, [current.attaque.carac.key])
case PART_COMP: return part.filterComps(rollData, [current.attaque.comp?.name])
case PART_CARAC: return part.filterCaracs(rollData, [current.carac.key])
case PART_COMP: return part.filterComps(rollData, [current.comp.name])
}
}
return undefined

View File

@@ -34,7 +34,7 @@ export class RollPartDefense extends RollPartSelect {
.map(it => RollPartDefense.$extractEsquive(it, defenseur))
const parades = defenseur.items.filter(it => it.isParade() && (!refs.isDistance || it.isBouclier()))
.map(it => RollPartDefense.$extractParade(it, attackerRoll?.attaque.arme, defenseur))
.map(it => RollPartDefense.$extractParade(it, attackerRoll?.arme, defenseur))
refs.defenses = [...esquives, ...parades].filter(it => it != undefined)
this.$selectDefense(rollData)
@@ -113,17 +113,16 @@ export class RollPartDefense extends RollPartSelect {
isArmeDisparate(rollData) {
const armeDefense = this.getCurrent(rollData).arme
if (armeDefense) {
const armeAttaque = rollData.attackerRoll?.attaque.arme
const armeAttaque = rollData.attackerRoll?.arme
return RdDItemArme.defenseArmeParade(armeAttaque, armeDefense) == 'sign'
}
return false
}
getDiffDefense(rollData) {
const current = this.getCurrent(rollData)
const refs = this.getRefs(rollData)
if (refs.isDistance || !rollData.attackerRoll) {
// Déterminer la difficulté de parade
// TODO: Déterminer la difficulté de parade
return { diff: 0, type: DIFF.LIBRE }
}
else {

View File

@@ -1,4 +1,5 @@
import { DIFF, ROLL_TYPE_ATTAQUE } from "./roll-constants.mjs"
import { PART_ATTAQUE } from "./roll-part-attaque.mjs"
import { RollType } from "./roll-type.mjs"
export class RollTypeAttaque extends RollType {
@@ -10,4 +11,5 @@ export class RollTypeAttaque extends RollType {
onSelect(rollData) {
this.setDiffType(rollData, DIFF.ATTAQUE)
}
}

View File

@@ -11,7 +11,7 @@ export class RollType {
get name() { return this.code }
get icon() { return `systems/foundryvtt-reve-de-dragon/assets/actions/${this.code}.svg` }
get chatResultTemplate() { return `systems/foundryvtt-reve-de-dragon/templates/roll/result/chat-${this.code}.hbs` }
toTypeData(rollData) {
return { code: this.code, name: this.name, icon: this.icon, section: 'type', template: this.template, selected: this.isSelected(rollData) }
}
@@ -33,11 +33,10 @@ export class RollType {
this.typeFromOpponents(rollData),
rollData.selected[PART_DIFF].type
]
const type = possibleTypes.find(m => DEFAULT_DIFF_TYPES.includes(m)) ??DIFF.DEFAUT
const type = possibleTypes.find(m => DEFAULT_DIFF_TYPES.includes(m)) ?? DIFF.DEFAUT
this.setDiffType(rollData, type)
}
typeFromOpponents(rollData) {
if (rollData.type.opposed) {
if (rollData.type.resistance) {
@@ -53,7 +52,7 @@ export class RollType {
this.setRollDataType(rollData)
}
getResult(rollData){
getResult(rollData) {
return undefined
}
}