Nouveau roll-dialog: mode attaque

This commit is contained in:
2025-09-21 00:15:21 +02:00
parent 607eedc6d2
commit 5d8da860c4
20 changed files with 233 additions and 83 deletions

View File

@@ -49,7 +49,7 @@ export class RdDActorSheet extends RdDBaseActorSangSheet {
});
foundry.utils.mergeObject(formData.calc, {
surenc: this.actor.computeMalusSurEncombrement(),
surprise: RdDBonus.find(this.actor.getSurprise(false)).descr,
surprise: RdDBonus.find(this.actor.getSurprise(false)).label,
resumeBlessures: this.actor.computeResumeBlessure(this.actor.system.blessures),
caracTotal: RdDCarac.computeTotal(this.actor.system.carac, this.actor.system.beaute),
surEncombrementMessage: this.actor.isSurenc() ? "Sur-Encombrement!" : "",

View File

@@ -208,7 +208,7 @@ export class RdDActor extends RdDBaseActorSang {
ui.notifications.info(`Les dommages de l'arme à 1/2 mains ${arme.name} ne sont pas corrects (ie sous la forme X/Y)`)
}
const tableauDommages = dommages.includes("/") ? dommages.split("/") : [dommages, dommages]
const dmg = main == '(2 mains)' ? tableauDommages[1] : tableauDommages[0]
const dommagesArme = parseInt(main == '(2 mains)' ? tableauDommages[1] : tableauDommages[0])
const niveau = comp?.system.niveau ?? (['(lancer)', '(tir)'].includes(main) ? -8 : -6)
const ajustement = (arme.parent?.getEtatGeneral() ?? 0) + (arme.system.magique) ? arme.system.ecaille_efficacite : 0
@@ -221,7 +221,7 @@ export class RdDActor extends RdDBaseActorSang {
main: main,
carac: { key: caracCode, value: caracValue },
equipe: arme.system.equipe,
dmg: dmg,
dommagesArme: dommagesArme,
initiative: RdDInitiative.calculInitiative(niveau, caracValue, ajustement)
}
}

View File

@@ -54,34 +54,37 @@ export class RdDItemCompetenceCreature extends Item {
const categorieAttaque = RdDItemCompetenceCreature.getCategorieAttaque(comp)
if (categorieAttaque != undefined) {
const initative = RdDInitiative.calculInitiative(comp.system.niveau, comp.system.carac_value);
return {
const armeComp = new RdDItem({
name: comp.name,
type: ITEM_TYPES.arme,
img: comp.img,
system: {
competence: comp.name,
cac: categorieAttaque == "naturelle" ? "naturelle" : "",
niveau: comp.system.niveau,
initiative: initative,
mortalite: comp.system.mortalite,
dommages: comp.system.dommages,
equipe: true,
resistance: 100,
penetration: 0,
force: 0,
rapide: true,
}
});
const attaque = {
name: comp.name,
action: comp.isCompetencePossession() ? 'possession' : 'attaque',
initOnly: false,
arme: new RdDItem({
name: comp.name,
type: ITEM_TYPES.arme,
img: comp.img,
system: {
competence: comp.name,
cac: categorieAttaque == "naturelle" ? "naturelle" : "",
niveau: comp.system.niveau,
initiative: initative,
equipe: true,
resistance: 100,
dommagesReels: comp.system.dommages,
penetration: 0,
force: 0,
rapide: true,
}
}),
arme: armeComp,
comp: comp,
// main: '',
carac: { key: comp.name, value: comp.system.carac_value },
equipe: true,
mortalite: comp.system.mortalite,
dmg: comp.system.dommages,
initiative: initative
}
};
return attaque
}
return undefined;
}

View File

@@ -30,6 +30,10 @@ export class RdDItemArme extends RdDItem {
//return "systems/foundryvtt-reve-de-dragon/icons/armes_armure/epee_sord.webp";
}
penetration() {
return parseInt(this.system.penetration ?? 0);
}
/* -------------------------------------------- */
static getArme(arme) {

View File

@@ -1,21 +1,23 @@
import { RdDCarac } from "./rdd-carac.js";
import { RdDPossession } from "./rdd-possession.js";
const conditionsTactiques = [
{ type: '', descr: '', dmg: 0, attaque: 0, parade: 0, esquive: true },
{ type: 'charge', descr: 'Charge', dmg: 2, attaque: 4, parade: -4, esquive: false },
{ type: 'feinte', descr: 'Feinte', dmg: 1, attaque: 1, parade: 0, esquive: true },
{ type: 'pret', descr: 'prêt', dmg: 0, attaque: 0, parade: 0, esquive: true },
{ type: 'demi', descr: 'Demi-surprise', dmg: 1, attaque: 0, parade: 0, esquive: true },
{ type: 'totale', descr: 'Surprise totale', dmg: 10, attaque: 6, parade: 0, esquive: true },
{ key: '', label: '', dmg: 0, attaque: 0, parade: 0, esquive: true, isTactique: false },
{ key: 'normale', label: 'Attaque normale', dmg: 0, attaque: 0, parade: 0, esquive: true, isTactique: true },
{ key: 'charge', label: 'Charge', dmg: 2, attaque: 4, parade: -4, esquive: false, isTactique: true },
{ key: 'feinte', label: 'Feinte', dmg: 1, attaque: 1, parade: 0, esquive: true, isTactique: true },
{ key: 'pret', label: 'prêt', dmg: 0, attaque: 0, parade: 0, esquive: true, isTactique: false },
{ key: 'demi', label: 'Demi-surprise', dmg: 1, attaque: 0, parade: 0, esquive: true, isTactique: false },
{ key: 'totale', label: 'Surprise totale', dmg: 10, attaque: 6, parade: 0, esquive: true, isTactique: false },
];
/* -------------------------------------------- */
export class RdDBonus {
static get tactiques(){
return conditionsTactiques.filter(it => it.isTactique)
}
/* -------------------------------------------- */
static find(condition) {
return conditionsTactiques.find(e => e.type == condition) || conditionsTactiques.find(e => e.type == 'pret');
return conditionsTactiques.find(e => e.key == condition) || conditionsTactiques[0];
}
@@ -32,7 +34,7 @@ export class RdDBonus {
/* -------------------------------------------- */
static dmg(rollData, actor, isEntiteIncarnee = false) {
const dmgArme = RdDBonus.dmgArme(rollData.arme)
const dmgArme = RdDBonus.dmgArme(rollData.arme, rollData.arme?.system.dommagesReels)
let dmg = {
total: 0,
dmgArme: dmgArme,
@@ -49,31 +51,35 @@ export class RdDBonus {
/* -------------------------------------------- */
static description(condition) {
return RdDBonus.find(condition).descr;
return RdDBonus.find(condition).label
}
/* -------------------------------------------- */
static dmgBonus(condition) {
return RdDBonus.find(condition).dmg;
return RdDBonus.find(condition).dmg
}
/* -------------------------------------------- */
static bonusAttaque(condition) {
return RdDBonus.find(condition).attaque;
return RdDBonus.find(condition).attaque
}
/* -------------------------------------------- */
static _calculMortalite(rollData, isEntiteIncarnee) {
return RdDBonus.mortalite(rollData.dmg?.mortalite, rollData.arme?.system.mortalite, isEntiteIncarnee)
}
static mortalite(mortaliteSelect, mortaliteArme, isEntiteIncarnee) {
return isEntiteIncarnee ? "entiteincarnee"
: rollData.dmg?.mortalite
?? rollData.arme?.system.mortalite
: mortaliteSelect
?? mortaliteArme
?? "mortel";
}
/* -------------------------------------------- */
static dmgArme(arme) {
static dmgArme(arme, dommagesMain) {
if (arme) {
let dmgBase = arme.system.dommagesReels ?? Number(arme.system.dommages ?? 0);
let dmgBase = dommagesMain ?? Number(arme.system.dommages ?? 0);
//Le bonus dégats magiques ne peut pas faire dépasser le bonus de l'arme (cf p.278)
return dmgBase + Math.min(dmgBase, arme.system.magique ? arme.system.ecaille_efficacite : 0);
}
@@ -92,8 +98,8 @@ export class RdDBonus {
return 0
}
switch (categorie) {
case "tir": return 0;
case "lancer": return Math.max(0, Math.min(dmgArme, dmgActor));
case "(tir)": case "tir": return 0
case "(lancer)": case "lancer": return Math.max(0, Math.min(dmgArme, dmgActor));
}
return dmgActor;
}

View File

@@ -187,7 +187,12 @@ export default class RollDialog extends HandlebarsApplicationMixin(ApplicationV2
foundry.applications.handlebars.loadTemplates(ROLL_PARTS.map(p => p.template))
ROLL_PARTS.forEach(p => p.onReady())
Handlebars.registerHelper('roll-centered-array', (base, show) => RollDialog.centeredArray(base, show))
Handlebars.registerHelper('roll-centered-array', (base, show) => {
show = Math.abs(show)
const start = base - show
return [...Array(2 * show + 1).keys()].map(it => start + it)
})
Handlebars.registerHelper('roll-list-item-value', (list, key, path = undefined) => {
const selected = list.find(p => p.key == key)
if (selected && path && path != '') {
@@ -195,6 +200,7 @@ export default class RollDialog extends HandlebarsApplicationMixin(ApplicationV2
}
return selected
})
Handlebars.registerHelper('roll-part-context', (rollData, code) => {
const rollPart = ROLL_PARTS.find(it => it.code == code)
if (rollPart == undefined) {
@@ -211,12 +217,7 @@ export default class RollDialog extends HandlebarsApplicationMixin(ApplicationV2
})
}
static centeredArray(base, show) {
show = Math.abs(show)
const start = base - show
return [...Array(2 * show + 1).keys()].map(it => start + it)
}
static async create(rollData, rollOptions = {}) {
const rollDialog = new RollDialog(rollData, rollOptions)
rollDialog.render(true)
@@ -267,30 +268,33 @@ export default class RollDialog extends HandlebarsApplicationMixin(ApplicationV2
/** pre-configure les paramètres des différentes parties de la fenêtre (par exemple, prépare les listes de caractéristiques/compétences */
$loadParts() {
const rollData = this.rollData;
const loadedMode = rollData.mode?.current
rollData.current = rollData.current ?? {}
rollData.selected = rollData.selected ?? {}
rollData.mode = rollData.mode ?? {}
rollData.mode.retry = rollData.mode.retry ?? false
BASIC_PARTS.restore(rollData)
const loadedMode = ROLL_MODE_TABS.find(m => m.code == rollData.mode?.current)?.code
const allowedModes = ROLL_MODE_TABS.filter(m => m.isAllowed(rollData) && m.visible(rollData)).map(m => m.code)
rollData.mode.allowed = rollData.mode.retry ? [loadedMode] : rollData.mode.allowed ?? ROLL_MODE_TABS.map(m => m.code)
rollData.mode.current = loadedMode ?? ROLL_MODE_TABS.find(m => m.isAllowed(rollData) && m.visible(rollData))?.code ?? ROLL_MODE_COMP
rollData.mode.current = allowedModes.find(m => m == rollData.mode?.current) ?? (allowedModes.length > 0 ? allowedModes[0] : ROLL_MODE_COMP)
this.getSelectedMode().setRollDataMode(rollData)
rollData.refs = this.$prepareRefs(rollData)
rollData.options = rollData.options ?? { showDice: true, rollMode: game.settings.get("core", "rollMode") }
ROLL_PARTS.forEach(p => p.initialize(rollData))
ROLL_PARTS.forEach(p => p.restore(rollData))
ROLL_PARTS.filter(p => p.isValid(rollData))
.forEach(p => {
p.loadRefs(rollData)
p.prepareContext(rollData)
})
.forEach(p => {
p.loadRefs(rollData)
p.prepareContext(rollData)
})
this.selectMode();
}
selectMode() {
this.rollData.mode.label = this.getSelectedMode().title(this.rollData)
this.getSelectedMode().setRollDataMode(this.rollData)

View File

@@ -37,6 +37,7 @@ export class RollPartAppelMoral extends RollPartCheckbox {
}
return '<i class="fa-regular fa-face-meh"></i>'
}
getCheckboxLabel(rollData) { return "Appel au moral" }
getCheckboxValue(rollData) { return 1 }
}

View File

@@ -1,12 +1,16 @@
import { Grammar } from "../grammar.js"
import { RdDBonus } from "../rdd-bonus.js"
import { StatusEffects } from "../settings/status-effects.js"
import { ROLL_MODE_ATTAQUE } from "./roll-constants.mjs"
import { PART_CARAC } from "./roll-part-carac.mjs"
import { PART_COMP } from "./roll-part-comp.mjs"
import { RollPartSelect } from "./roll-part-select.mjs"
import { ROLLDIALOG_SECTION, RollPart } from "./roll-part.mjs"
import { ROLLDIALOG_SECTION } from "./roll-part.mjs"
export const PART_ATTAQUE = 'attaque'
const TACTIQUES = RdDBonus.tactiques.filter(it => it.isTactique)
export class RollPartAttaque extends RollPartSelect {
get code() { return PART_ATTAQUE }
@@ -18,7 +22,8 @@ export class RollPartAttaque extends RollPartSelect {
const refs = this.getRefs(rollData)
const attaques = rollData.active.actor.listAttaques()
refs.attaques = attaques.map(it => RollPartAttaque.$extractAttaque(it, rollData.active.actor))
if (refs.attaques.length>0){
refs.tactiques = TACTIQUES
if (refs.attaques.length > 0) {
this.$selectAttaque(rollData)
}
}
@@ -27,20 +32,69 @@ export class RollPartAttaque extends RollPartSelect {
static $extractAttaque(action, actor) {
return {
key: `${action.action}::${action.arme.id}::${action.comp.id}`,
key: `${action.action}::${action.name}`,
label: action.name,
action: action,
tactique: TACTIQUES[0],
arme: action.arme,
comp: action.comp,
}
}
prepareContext(rollData) {
const current = this.getCurrent(rollData)
const effetsSurprise = rollData.opponent?.actor?.getEffects(it => StatusEffects.niveauSurprise(it, true) > 0) ?? []
current.defenseur = effetsSurprise
? {
surprise: RdDBonus.find(rollData.opponent?.actor?.getSurprise(true)),
effects: effetsSurprise
}
: undefined
current.dmg = this.dmgRollV2(rollData, current)
}
getAjustements(rollData) {
const current = this.getCurrent(rollData)
const tactique = current.tactique
const surprise = current.defenseur?.surprise
const ajustements = []
if (tactique) {
ajustements.push({ label: tactique.label, diff: tactique.attaque })
}
if (surprise) {
ajustements.push({ label: surprise.label, diff: surprise.attaque })
}
return ajustements
}
dmgRollV2(rollData, current) {
const actor = rollData.active.actor
const defender = rollData.opponent.actor
const dmgArme = RdDBonus.dmgArme(current.arme, current.action.dommagesArme)
const dmg = {
total: 0,
dmgArme: dmgArme,
penetration: current.arme.penetration(),
dmgTactique: current.tactique?.dmg ?? 0,
dmgParticuliere: 0, // TODO RdDBonus._dmgParticuliere(rollData),
dmgSurprise: current.defenseur?.surprise.dmg,
mortalite: RdDBonus.mortalite(current.dmg?.mortalite, current.arme.system.mortalite, defender?.isEntite()),
dmgActor: RdDBonus.bonusDmg(actor, current.action.carac.key, dmgArme)
}
dmg.total = dmg.dmgSurprise + dmg.dmgTactique + dmg.dmgArme + dmg.dmgActor + dmg.dmgParticuliere
return dmg;
}
$selectAttaque(rollData, key) {
this.selectByKey(rollData, key, 0)
}
async _onRender(rollDialog, context, options) {
const selectAttaque = rollDialog.element.querySelector(`roll-section[name="${this.code}"] select[name="select-attaque"]`)
const selectTactique = rollDialog.element.querySelector(`roll-section[name="${this.code}"] select[name="select-tactique"]`)
const checkMortalite = rollDialog.element.querySelector(`roll-section[name="${this.code}"] input[name="check-mortalite"]`)
const current = this.getCurrent(rollDialog.rollData)
selectAttaque.addEventListener("change", e => {
const selectOptions = e.currentTarget.options
@@ -49,6 +103,18 @@ export class RollPartAttaque extends RollPartSelect {
rollDialog.setModeTitle()
rollDialog.render()
})
selectTactique.addEventListener("change", e => {
const selectOptions = e.currentTarget.options
const index = selectOptions.selectedIndex
current.tactique = RdDBonus.find(selectOptions[index]?.value)
rollDialog.render()
})
checkMortalite?.addEventListener("change", e => {
current.dmg.mortalite = (e.currentTarget.checked ? 'mortel' : 'non-mortel')
rollDialog.render()
})
}
getExternalPartsFilter(partCode, rollData) {

View File

@@ -18,8 +18,8 @@ export class RollPartCheckbox extends RollPart {
}
loadRefs(rollData) {
const current = this.getCurrent(rollData)
current.label = this.getCheckboxLabel(rollData)
const refs = this.getRefs(rollData)
refs.label = this.getCheckboxLabel(rollData)
}
prepareContext(rollData) {
@@ -43,7 +43,7 @@ export class RollPartCheckbox extends RollPart {
}
getCheckboxLabelAjustement(rollData) {
return `${this.getCheckboxIcon(rollData)} ${this.getCurrent(rollData).label}`
return `${this.getCheckboxIcon(rollData)} ${this.getRefs(rollData).label}`
}
async _onRender(rollDialog, context, options) {

View File

@@ -14,4 +14,8 @@ export class RollPartDefense extends RollPart {
refs.defenses =[]
}
isForceInsuffisante(rollData) {
return true
}
}

View File

@@ -43,15 +43,15 @@ export class RollPartOeuvre extends RollPartSelect {
loadRefs(rollData) {
const refs = this.getRefs(rollData)
refs.oeuvres = rollData.active.actor.items
.filter(it => it.isOeuvre() && RollPartOeuvre.getArt(it))
.map(it => RollPartOeuvre.$extractOeuvre(it, rollData.active.actor))
.filter(it => it.isOeuvre() && RollPartOeuvre.getArt(it))
.map(it => RollPartOeuvre.$extractOeuvre(it, rollData.active.actor))
if (refs.oeuvres.length > 0) {
this.$selectOeuvre(rollData)
}
}
choices(refs) { return refs.oeuvres }
static $extractOeuvre(oeuvre, actor) {
const art = RollPartOeuvre.getArt(oeuvre)
return {
@@ -64,7 +64,7 @@ export class RollPartOeuvre extends RollPartSelect {
comp: actor.getCompetence(art.competence(oeuvre))
}
}
static getArt(oeuvre) {
return ARTS.find(it => it.type == oeuvre.type)
}

View File

@@ -27,7 +27,7 @@ export class RollPartSelect extends RollPart {
const current = this.getCurrent(rollData)
const newChoice = (choices.length == 0)
? { key: '', value: defValue }
: this.$getSelectedChoice(choices, key ?? current?.key ?? refs.key ?? '')
: this.$getSelectedChoice(choices, key ?? current?.key ?? refs?.key ?? '')
this.setCurrent(rollData, newChoice)
return newChoice
}

View File

@@ -1,6 +1,7 @@
import { Misc } from "../misc.js"
import { StatusEffects } from "../settings/status-effects.js"
import { ROLL_MODE_ATTAQUE, ROLL_MODE_DEFENSE } from "./roll-constants.mjs"
import { PART_ATTAQUE, RollPartAttaque } from "./roll-part-attaque.mjs"
import { ROLLDIALOG_SECTION, RollPart } from "./roll-part.mjs"
export const PART_SIGN = "sign"
@@ -64,8 +65,17 @@ export class RollPartSign extends RollPart {
}
isForceInsuffisante(rollData) {
//this.isCombat(rollData) && ... arme avec force min
return this.isCombat(rollData) && true
if ([ROLL_MODE_ATTAQUE, ROLL_MODE_DEFENSE].includes(rollData.mode.current)) {
const arme = rollData.current[rollData.mode.current]?.arme
const actor = rollData.active.actor
if (actor?.isPersonnage() && arme) {
const requise = parseInt(arme?.system.force ?? 0)
const force = parseInt(actor.system.carac.force.value)
return requise > force
}
}
return false
}
isAttaqueFinesse(rollData) {

View File

@@ -121,7 +121,7 @@ export const referenceAjustements = {
},
tactique: {
isUsed: (rollData, actor) => rollData.tactique,
getLabel: (rollData, actor) => RdDBonus.find(rollData.tactique).descr,
getLabel: (rollData, actor) => RdDBonus.find(rollData.tactique).label,
getValue: (rollData, actor) => RdDBonus.find(rollData.tactique).attaque,
},
finesse: {
@@ -130,11 +130,11 @@ export const referenceAjustements = {
},
surprise: {
isUsed: (rollData, actor) => actor.getSurprise(rollData.passeArme),
getDescr: (rollData, actor) => RdDBonus.find(actor.getSurprise()).descr
getDescr: (rollData, actor) => RdDBonus.find(actor.getSurprise()).label
},
attaqueDefenseurSurpris: {
isUsed: (rollData, actor) => rollData.surpriseDefenseur,
getLabel: (rollData, actor) => RdDBonus.find(rollData.surpriseDefenseur).descr + (rollData.attackerRoll ? '' : ' défenseur'),
getLabel: (rollData, actor) => RdDBonus.find(rollData.surpriseDefenseur).label + (rollData.attackerRoll ? '' : ' défenseur'),
getValue: (rollData, actor) => RdDBonus.find(rollData.surpriseDefenseur).attaque,
},
armeParade: {