diff --git a/changelog.md b/changelog.md index 21df76f5..7ca985f0 100644 --- a/changelog.md +++ b/changelog.md @@ -3,6 +3,11 @@ ## 13.0.9 - Le combat d'Illysis - Fix - La montée en TMR fonctionne +- Nouvelle fenêtre de jets de dés + - avancement du mode attaque + - choix de tactique + - choix des dommages, affichage + - affichage de la surprise du défenseur ## 13.0.8 - Le renouveau d'Illysis diff --git a/css/foundryvtt-reve-de-dragon.css b/css/foundryvtt-reve-de-dragon.css index 25b4833c..d67ed8c4 100644 --- a/css/foundryvtt-reve-de-dragon.css +++ b/css/foundryvtt-reve-de-dragon.css @@ -526,6 +526,7 @@ select, grid-area: selection; display: flex; flex-direction: row; + margin: 0.1rem 0; } .system-foundryvtt-reve-de-dragon .roll-dialog roll-choix roll-section roll-part-img { display: flex; @@ -549,6 +550,7 @@ select, .system-foundryvtt-reve-de-dragon .roll-dialog roll-choix roll-section roll-part-detail subline { display: flex; flex-direction: row; + margin: 0.1rem 0; } .system-foundryvtt-reve-de-dragon .roll-dialog roll-choix roll-section roll-part-detail subline div.poesie-extrait { display: flex; @@ -608,6 +610,11 @@ select, margin: 0 0.2rem; padding: 0; } +.system-foundryvtt-reve-de-dragon .roll-dialog :is(roll-choix, roll-conditions, roll-carac, roll-comp) img.button-effect-img { + max-width: 1rem; + max-height: 1rem; + margin: 0 0.1rem; +} .system-foundryvtt-reve-de-dragon .roll-dialog roll-carac select[name="select-carac"] { max-width: 6rem; } diff --git a/less/roll-dialog.less b/less/roll-dialog.less index 891320aa..b214bae9 100644 --- a/less/roll-dialog.less +++ b/less/roll-dialog.less @@ -83,11 +83,14 @@ gap: 0.2rem; align-items: start; + subline { grid-area: selection; display: flex; flex-direction: row; + margin: 0.1rem 0; } + roll-part-img { display: flex; flex-direction: column; @@ -110,6 +113,7 @@ subline { display: flex; flex-direction: row; + margin: 0.1rem 0; div.poesie-extrait{ display: flex; flex-direction: column; @@ -178,6 +182,11 @@ margin: 0 0.2rem; padding: 0; } + img.button-effect-img { + max-width: 1rem; + max-height: 1rem; + margin: 0 0.1rem; + } } roll-carac select[name="select-carac"] { diff --git a/module/actor.js b/module/actor.js index 2d8181fa..3e0c77a5 100644 --- a/module/actor.js +++ b/module/actor.js @@ -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) } } diff --git a/module/item-competencecreature.js b/module/item-competencecreature.js index 6beb4ac3..322502cf 100644 --- a/module/item-competencecreature.js +++ b/module/item-competencecreature.js @@ -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; } diff --git a/module/item/arme.js b/module/item/arme.js index ed36fbb2..74d4be1d 100644 --- a/module/item/arme.js +++ b/module/item/arme.js @@ -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) { diff --git a/module/rdd-bonus.js b/module/rdd-bonus.js index 1d69e3ea..39fad9e3 100644 --- a/module/rdd-bonus.js +++ b/module/rdd-bonus.js @@ -1,4 +1,3 @@ -import { RdDCarac } from "./rdd-carac.js"; import { RdDPossession } from "./rdd-possession.js"; const conditionsTactiques = [ @@ -32,18 +31,20 @@ export class RdDBonus { /* -------------------------------------------- */ static dmg(rollData, actor, isEntiteIncarnee = false) { - const dmgArme = RdDBonus.dmgArme(rollData.arme) - let dmg = { - total: 0, - dmgArme: dmgArme, - penetration: RdDBonus._peneration(rollData), - dmgTactique: RdDBonus.dmgBonus(rollData.tactique), - dmgParticuliere: RdDBonus._dmgParticuliere(rollData), - dmgSurprise: RdDBonus.dmgBonus(rollData.ajustements?.attaqueDefenseurSurpris.used), - mortalite: RdDBonus._calculMortalite(rollData, isEntiteIncarnee), - dmgActor: RdDBonus.bonusDmg(actor, rollData.selectedCarac?.label.toLowerCase(), dmgArme) + if (arme){ + const dmgArme = RdDBonus.dmgArme(rollData.arme, rollData.arme.system.dommagesReels) + let dmg = { + total: 0, + dmgArme: dmgArme, + penetration: RdDBonus._peneration(rollData), + dmgTactique: RdDBonus.dmgBonus(rollData.tactique), + dmgParticuliere: RdDBonus._dmgParticuliere(rollData), + dmgSurprise: RdDBonus.dmgBonus(rollData.ajustements?.attaqueDefenseurSurpris.used), + mortalite: RdDBonus._calculMortalite(rollData, isEntiteIncarnee), + dmgActor: RdDBonus.bonusDmg(actor, rollData.selectedCarac?.label.toLowerCase(), dmgArme) + } + dmg.total = dmg.dmgSurprise + dmg.dmgTactique + dmg.dmgArme + dmg.dmgActor + dmg.dmgParticuliere; } - dmg.total = dmg.dmgSurprise + dmg.dmgTactique + dmg.dmgArme + dmg.dmgActor + dmg.dmgParticuliere; return dmg; } @@ -64,16 +65,20 @@ export class RdDBonus { /* -------------------------------------------- */ 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 +97,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; } diff --git a/module/roll/roll-dialog.mjs b/module/roll/roll-dialog.mjs index f939aa5e..fbceca1b 100644 --- a/module/roll/roll-dialog.mjs +++ b/module/roll/roll-dialog.mjs @@ -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) diff --git a/module/roll/roll-part-appelmoral.mjs b/module/roll/roll-part-appelmoral.mjs index 25a633db..12d4a0bd 100644 --- a/module/roll/roll-part-appelmoral.mjs +++ b/module/roll/roll-part-appelmoral.mjs @@ -37,6 +37,7 @@ export class RollPartAppelMoral extends RollPartCheckbox { } return '' } + getCheckboxLabel(rollData) { return "Appel au moral" } getCheckboxValue(rollData) { return 1 } } diff --git a/module/roll/roll-part-attaque.mjs b/module/roll/roll-part-attaque.mjs index db627c7f..f499599a 100644 --- a/module/roll/roll-part-attaque.mjs +++ b/module/roll/roll-part-attaque.mjs @@ -1,4 +1,6 @@ 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" @@ -7,6 +9,12 @@ import { ROLLDIALOG_SECTION, RollPart } from "./roll-part.mjs" export const PART_ATTAQUE = 'attaque' +const TACTIQUES = [ + { key: "", label: "Attaque normale" }, + { key: "charge", label: "Charge" }, + { key: "feinte", label: "Feinte" }, +] + export class RollPartAttaque extends RollPartSelect { get code() { return PART_ATTAQUE } @@ -18,7 +26,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 +36,54 @@ 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: "", arme: action.arme, comp: action.comp, } } + prepareContext(rollData) { + const current = this.getCurrent(rollData) + const defenseurSurpris = rollData.opponent?.actor?.getSurprise(true) ?? '' + current.defenseur = defenseurSurpris + ? { + surprise: RdDBonus.find(defenseurSurpris), + effects: rollData.opponent.actor.getEffects(it => StatusEffects.niveauSurprise(it, true) > 0) + } + : undefined + current.dmg = this.dmgRollV2(rollData, current) + } + + 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: RdDBonus.dmgBonus(current.tactique), + dmgParticuliere: 0, // TODO RdDBonus._dmgParticuliere(rollData), + dmgSurprise: RdDBonus.dmgBonus(current.defenseur.surprise), + 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 +92,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 = TACTIQUES[index].key + rollDialog.render() + }) + + checkMortalite?.addEventListener("change", e => { + current.dmg.mortalite = (e.currentTarget.checked ? 'mortel' : 'non-mortel') + rollDialog.render() + }) } getExternalPartsFilter(partCode, rollData) { diff --git a/module/roll/roll-part-checkbox.mjs b/module/roll/roll-part-checkbox.mjs index 1f8ae1e1..1d128941 100644 --- a/module/roll/roll-part-checkbox.mjs +++ b/module/roll/roll-part-checkbox.mjs @@ -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) { diff --git a/module/roll/roll-part-defense.mjs b/module/roll/roll-part-defense.mjs index b924a815..07d294a3 100644 --- a/module/roll/roll-part-defense.mjs +++ b/module/roll/roll-part-defense.mjs @@ -14,4 +14,8 @@ export class RollPartDefense extends RollPart { refs.defenses =[] } + isForceInsuffisante(rollData) { + return true + } + } diff --git a/module/roll/roll-part-oeuvre.mjs b/module/roll/roll-part-oeuvre.mjs index 97eaa782..ed81b497 100644 --- a/module/roll/roll-part-oeuvre.mjs +++ b/module/roll/roll-part-oeuvre.mjs @@ -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) } diff --git a/module/roll/roll-part-select.mjs b/module/roll/roll-part-select.mjs index c8239de3..6427983d 100644 --- a/module/roll/roll-part-select.mjs +++ b/module/roll/roll-part-select.mjs @@ -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 } diff --git a/module/roll/roll-part-sign.mjs b/module/roll/roll-part-sign.mjs index 588e5bab..4a6d3b1a 100644 --- a/module/roll/roll-part-sign.mjs +++ b/module/roll/roll-part-sign.mjs @@ -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) { diff --git a/templates/actor/header-effects.hbs b/templates/actor/header-effects.hbs index 04c5e4de..32295fc5 100644 --- a/templates/actor/header-effects.hbs +++ b/templates/actor/header-effects.hbs @@ -1,6 +1,6 @@