diff --git a/assets/ui/maladresse.svg b/assets/ui/maladresse.svg new file mode 100644 index 00000000..62aa72b8 --- /dev/null +++ b/assets/ui/maladresse.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/changelog.md b/changelog.md index 008982a7..ac57dcb5 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,17 @@ # 13.0 +## 13.0.12 - La méditation d'Illysis + +- les signes draconiques éphémères de 1 round sont supprimés à la descente des TMRs +- Générateur de description + - correction des termes pour les couleurs des yeux/cheveux + - ajout de boutons pour forcer le sexe masculin/féminin +- Nouvelle fenêtre de jets de dés + - les méditations proposent un bouton pour monter dans les TMRs + - fenêtre de lancer de sorts + - Correction: les compétences de jeux ne remplacent plus les compétences en dehors des jets de jeu + - gestion des maladresses d'attaque et défense + ## 13.0.11 - Le gambit d'Illysis - Nouvelle fenêtre de jets de dés diff --git a/css/foundryvtt-reve-de-dragon.css b/css/foundryvtt-reve-de-dragon.css index f2231fad..52b95706 100644 --- a/css/foundryvtt-reve-de-dragon.css +++ b/css/foundryvtt-reve-de-dragon.css @@ -924,6 +924,12 @@ select, .system-foundryvtt-reve-de-dragon a:hover { text-shadow: 1px 0px 0px #ff6600; } +.system-foundryvtt-reve-de-dragon .tabs .item.active img, +.system-foundryvtt-reve-de-dragon .blessures-list li ul li:first-child:hover img, +.system-foundryvtt-reve-de-dragon i.moral-radio-checkmark-off:hover img, +.system-foundryvtt-reve-de-dragon a:hover img { + filter: drop-shadow(1px 0px 0px #ff6600); +} .system-foundryvtt-reve-de-dragon .rollable:hover, .system-foundryvtt-reve-de-dragon .rollable:focus { color: #000; @@ -1448,12 +1454,27 @@ select, .system-foundryvtt-reve-de-dragon .competence-list .item-controls.hidden-controls { display: none !important; } -.system-foundryvtt-reve-de-dragon .item-actions-controls a.actionItem i:is(.fas, .fa, .fa-solid, .fa-regular), +.system-foundryvtt-reve-de-dragon .item-actions-controls, +.system-foundryvtt-reve-de-dragon .item-controls { + vertical-align: super; +} +.system-foundryvtt-reve-de-dragon .item-actions-controls img, +.system-foundryvtt-reve-de-dragon .item-controls img { + display: inline; + max-width: 1rem; + max-height: 1rem; + margin: 0 0.1rem; + border: none; + filter: invert(0.8); +} +.system-foundryvtt-reve-de-dragon .item-actions-controls i:is(.fas, .fa, .fa-solid, .fa-regular), .system-foundryvtt-reve-de-dragon .item-controls i:is(.fas, .fa, .fa-solid, .fa-regular) { font-size: 0.8em; color: var(--color-controls-light); } -.system-foundryvtt-reve-de-dragon .item-actions-controls a.actionItem i:is(.fas, .fa, .fa-solid, .fa-regular):hover, +.system-foundryvtt-reve-de-dragon .item-actions-controls img:hover, +.system-foundryvtt-reve-de-dragon .item-controls img:hover, +.system-foundryvtt-reve-de-dragon .item-actions-controls i:is(.fas, .far, .fa-solid, .fa-regular):hover, .system-foundryvtt-reve-de-dragon .item-controls i:is(.fas, .far, .fa-solid, .fa-regular):hover { opacity: 0.6; } @@ -1596,6 +1617,9 @@ select, color: var(--color-text-dark-primary); border-radius: 0.2rem; } +.system-foundryvtt-reve-de-dragon form.app-personnage-aleatoire h2 { + min-width: 30rem; +} .system-foundryvtt-reve-de-dragon .app-calendar-astrologie div.theme-astral { width: 14rem; margin: 0.4rem; @@ -2737,11 +2761,9 @@ select, } .system-foundryvtt-reve-de-dragon .chat-card-button:hover { background: var(--background-custom-button-hover); - background-color: red; } .system-foundryvtt-reve-de-dragon .chat-card-button-pushed:hover { background: var(--background-custom-button-hover); - background-color: red; } .system-foundryvtt-reve-de-dragon .chat-card-button:active, .system-foundryvtt-reve-de-dragon .chat-card-button-pushed:active { diff --git a/less/foundryvtt-reve-de-dragon.less b/less/foundryvtt-reve-de-dragon.less index fd300b3e..125de5ec 100644 --- a/less/foundryvtt-reve-de-dragon.less +++ b/less/foundryvtt-reve-de-dragon.less @@ -200,6 +200,9 @@ i.moral-radio-checkmark-off:hover, a:hover { text-shadow: 1px 0px 0px #ff6600; + img { + filter: drop-shadow(1px 0px 0px #ff6600); + } } .rollable:hover, .rollable:focus { @@ -741,11 +744,11 @@ .foundryvtt-reve-de-dragon .item-list .item img { display: inline; } - + .foundryvtt-reve-de-dragon .item-list .item-name { margin: 0; } - + .competence-list .item-controls, .competence-list .item-actions-controls { display: contents !important; @@ -754,15 +757,30 @@ .competence-list .item-controls.hidden-controls { display: none !important; } + .item-actions-controls, + .item-controls { + vertical-align: super; + // a { + // } - .item-actions-controls a.actionItem i:is(.fas, .fa, .fa-solid, .fa-regular), - .item-controls i:is(.fas, .fa, .fa-solid, .fa-regular) { - font-size: 0.8em; - color: var(--color-controls-light); - } - .item-actions-controls a.actionItem i:is(.fas, .fa, .fa-solid, .fa-regular):hover, - .item-controls i:is(.fas, .far, .fa-solid, .fa-regular):hover { - opacity: 0.6; + img { + display: inline; + max-width: 1rem; + max-height: 1rem; + margin: 0 0.1rem; + border: none; + filter: invert(0.8); + } + + i:is(.fas, .fa, .fa-solid, .fa-regular) { + font-size: 0.8em; + color: var(--color-controls-light); + } + + img:hover, + i:is(.fas, .far, .fa-solid, .fa-regular):hover { + opacity: 0.6; + } } .rdd-roll-dialog .description-sort { @@ -910,6 +928,11 @@ color: var(--color-text-dark-primary); border-radius: 0.2rem; } + form.app-personnage-aleatoire { + h2 { + min-width: 30rem, + } + } .app-calendar-astrologie{ div.theme-astral{ width: 14rem; @@ -1998,12 +2021,10 @@ .chat-card-button:hover { background: var(--background-custom-button-hover); - background-color: red; } .chat-card-button-pushed:hover { background: var(--background-custom-button-hover); - background-color: red; } .chat-card-button:active, .chat-card-button-pushed:active { diff --git a/module/actor.js b/module/actor.js index 8374e42c..914aa630 100644 --- a/module/actor.js +++ b/module/actor.js @@ -47,11 +47,12 @@ import { RdDRollResult } from "./rdd-roll-result.js"; import { RdDInitiative } from "./initiative.mjs"; import RollDialog from "./roll/roll-dialog.mjs"; import { OptionsAvancees, ROLL_DIALOG_V2, ROLL_DIALOG_V2_TEST } from "./settings/options-avancees.js"; -import { ROLL_TYPE_JEU, ROLL_TYPE_MEDITATION } from "./roll/roll-constants.mjs"; +import { ROLL_TYPE_JEU, ROLL_TYPE_MEDITATION, ROLL_TYPE_SORT } from "./roll/roll-constants.mjs"; import { PART_TACHE } from "./roll/roll-part-tache.mjs"; import { PART_COMP } from "./roll/roll-part-comp.mjs"; import { PART_OEUVRE } from "./roll/roll-part-oeuvre.mjs"; import { PART_CUISINE } from "./roll/roll-part-cuisine.mjs"; +import { PART_SORT } from "./roll/roll-part-sort.mjs"; export const MAINS_DIRECTRICES = ['Droitier', 'Gaucher', 'Ambidextre'] @@ -741,12 +742,7 @@ export class RdDActor extends RdDBaseActorSang { /* -------------------------------------------- */ async sortMisEnReserve(sort, draconic, coord, ptreve) { - await this.createEmbeddedDocuments("Item", [{ - type: ITEM_TYPES.sortreserve, - name: sort.name, - img: sort.img, - system: { sortid: sort._id, draconic: (draconic?.name ?? sort.system.draconic), ptreve: ptreve, coord: coord, heurecible: 'Vaisseau' } - }], + await this.createEmbeddedDocuments("Item", [RdDItemSort.prepareSortEnReserve(sort, draconic, ptreve, coord)], { renderSheet: false }); this.tmrApp.updateTokens(); } @@ -1712,15 +1708,18 @@ export class RdDActor extends RdDBaseActorSang { ui.notifications.error("Une queue ou un souffle vous empèche de lancer de sort!") return } + if (OptionsAvancees.isUsing(ROLL_DIALOG_V2)) { + return await this.rollUnSortV2(); + } // Duplication car les pts de reve sont modifiés dans le sort! let sorts = foundry.utils.duplicate(this.itemTypes[ITEM_TYPES.sort].filter(it => RdDItemSort.isSortOnCoord(it, coord))) if (sorts.length == 0) { ui.notifications.info(`Aucun sort disponible en ${TMRUtility.getTMR(coord).label} !`); - return; + return } - const draconicList = this.computeDraconicAndSortIndex(sorts); - const reve = foundry.utils.duplicate(this.system.carac.reve); + const draconicList = this.computeDraconicAndSortIndex(sorts) + const reve = foundry.utils.duplicate(this.system.carac.reve) const dialog = await this.openRollDialog({ name: 'lancer-un-sort', @@ -1743,6 +1742,27 @@ export class RdDActor extends RdDBaseActorSang { this.tmrApp?.setTMRPendingAction(dialog); } + async rollUnSortV2() { + const rollData = { + ids: { actorId: this.id }, + type: { allowed: [ROLL_TYPE_SORT], current: ROLL_TYPE_SORT } + }; + const dialog = await RollDialog.create(rollData, { + callbacks: [roll => { + this.tmrApp?.restoreTMRAfterAction(); + if (roll.closeTMR) { + this.tmrApp?.close(); + this.tmrApp = undefined; + } + } ], + onRollDone: RollDialog.onRollDoneClose, + onClose: () => { + this.tmrApp?.restoreTMRAfterAction(); + } + }); + this.tmrApp?.setTMRPendingAction(dialog); + } + /* -------------------------------------------- */ isMauvaiseRencontre() { // Gestion queue/souffle 'Mauvaise Rencontre en Perpective' let addMsg = ""; @@ -1814,11 +1834,7 @@ export class RdDActor extends RdDBaseActorSang { } else { console.log('lancement de sort', rollData.selectedSort) - const precedents = rollData.selectedSort.system.lancements ?? [] - const lancements = [...precedents, { - timestamp: game.system.rdd.calendrier.getTimestamp(), - reve: rollData.selectedSort.system.ptreve_reel - }] + const lancements = RdDItemSort.prepareSortAddLancement(rollData.selectedSort, rollData.selectedSort.system.ptreve_reel) await this.updateEmbeddedDocuments('Item', [{ _id: rollData.selectedSort._id, 'system.lancements': lancements }] ) @@ -2109,7 +2125,7 @@ export class RdDActor extends RdDBaseActorSang { /* -------------------------------------------- */ _getSignesDraconiques(coord) { const type = TMRUtility.getTMRType(coord); - return this.itemTypes["signedraconique"].filter(it => it.system.typesTMR.includes(type)); + return this.itemTypes[ITEM_TYPES.signedraconique].filter(it => it.system.typesTMR.includes(type)); } /* -------------------------------------------- */ @@ -2121,18 +2137,19 @@ export class RdDActor extends RdDBaseActorSang { async rollLireSigneDraconique(coord) { if (!this.isHautRevant()) { ui.notifications.info("Seul un haut rêvant peut lire un signe draconique!"); - return; + return } - let signes = this._getSignesDraconiques(coord); + let signes = this._getSignesDraconiques(coord) if (signes.length == 0) { - ui.notifications.info(`Aucun signe draconiques en ${coord} !`); - return; + ui.notifications.info(`Aucun signe draconiques en ${coord} !`) + return } + let draconicList = this.getDraconics() .map(draconic => { - let draconicLecture = foundry.utils.duplicate(draconic); - draconicLecture.system.defaut_carac = "intellect"; - return draconicLecture; + let draconicLecture = foundry.utils.duplicate(draconic) + draconicLecture.system.defaut_carac = "intellect" + return draconicLecture }); const intellect = this.system.carac.intellect; @@ -2406,7 +2423,7 @@ export class RdDActor extends RdDBaseActorSang { if (this.tmrApp) { ui.notifications.warn("Vous êtes déja dans les TMR....") this.tmrApp.forceTMRDisplay() - return + return false } if (mode != 'visu' && this.isDemiReve()) { ui.notifications.warn("Le personnage est déjà dans les Terres Médianes, elles s'affichent en visualisation") @@ -2414,6 +2431,7 @@ export class RdDActor extends RdDBaseActorSang { } if (mode == 'visu') { await this._doDisplayTMR(mode) + return false } else { const rencontre = this.getRencontreTMREnAttente(); @@ -2426,6 +2444,7 @@ export class RdDActor extends RdDBaseActorSang { buttonLabel: 'Monter dans les TMR', onAction: async () => await this._doDisplayTMR(mode) }) + return true } } @@ -2463,6 +2482,29 @@ export class RdDActor extends RdDBaseActorSang { await this.tmrApp.onDeplacement() } + async quitterTMR(message, viewOnly, cumulFatigue) { + if (this.tmrApp) { + this.tmrApp = undefined + const appliquerFatigue = ReglesOptionnelles.isUsing("appliquer-fatigue"); + await this.santeIncDec( + appliquerFatigue ? "fatigue" : "endurance", + (appliquerFatigue ? 1 : -1) * cumulFatigue) + if (!viewOnly) { + await this.supprimerSignesDraconiques(it => it.system.ephemere && it.system.duree == '1 round', { render: false }) + await this.setEffect(STATUSES.StatusDemiReve, false) + ChatUtility.tellToUserAndGM(message) + } + } + } + + async supprimerSignesDraconiques(filter = it => true, options = { render: true }) { + const signes = this.itemTypes[ITEM_TYPES.signedraconique].filter(filter) + if (signes.length > 0) { + this.deleteEmbeddedDocuments("Item", signes.map(item => item.id), options) + } + } + + /* -------------------------------------------- */ async rollSoins(blesse, blessureId) { const blessure = blesse.blessuresASoigner().find(it => it.id == blessureId); diff --git a/module/actor/base-actor-reve.js b/module/actor/base-actor-reve.js index 5e548843..bc9b256b 100644 --- a/module/actor/base-actor-reve.js +++ b/module/actor/base-actor-reve.js @@ -246,10 +246,10 @@ export class RdDBaseActorReve extends RdDBaseActor { if (this.isEffectAllowed(statusId)) { const effect = this.getEffectByStatus(statusId); if (!status && effect) { - await this.deleteEmbeddedDocuments('ActiveEffect', [effect.id]); + await this.deleteEmbeddedDocuments('ActiveEffect', [effect.id], { render: true}) } if (status && !effect) { - await this.createEmbeddedDocuments("ActiveEffect", [StatusEffects.prepareActiveEffect(statusId)]); + await this.createEmbeddedDocuments("ActiveEffect", [StatusEffects.prepareActiveEffect(statusId)], { render: true}) } } } diff --git a/module/actor/base-actor-sang.js b/module/actor/base-actor-sang.js index a3f00555..59bdcbb5 100644 --- a/module/actor/base-actor-sang.js +++ b/module/actor/base-actor-sang.js @@ -116,6 +116,7 @@ export class RdDBaseActorSang extends RdDBaseActorReve { blessure: blessure }); } + /* -------------------------------------------- */ async santeIncDec(name, inc, isCritique = false) { if (name == 'fatigue' && !ReglesOptionnelles.isUsing("appliquer-fatigue")) { @@ -179,6 +180,26 @@ export class RdDBaseActorSang extends RdDBaseActorReve { return Math.max(0, Math.min(maxEndVie, maxEndGraves, maxEndCritiques)); } + async onCreateItem(item, options, id) { + switch (item.type) { + case ITEM_TYPES.blessure: + await this.changeBleedingState() + break + } + } + + async onUpdateItem(item, options, id) { + switch (item.type) { + case ITEM_TYPES.blessure: + await this.changeBleedingState() + break + } + } + + async changeBleedingState() { + const bleeding = this.itemTypes[ITEM_TYPES.blessure].find(it => it.isBleeding()) + await this.setEffect(STATUSES.StatusBleeding, bleeding ? true : false) + } /* -------------------------------------------- */ async ajouterBlessure(encaissement, attackerToken = undefined) { diff --git a/module/actor/base-actor.js b/module/actor/base-actor.js index b0d8cab0..a1a9b6cb 100644 --- a/module/actor/base-actor.js +++ b/module/actor/base-actor.js @@ -248,28 +248,12 @@ export class RdDBaseActor extends Actor { /* -------------------------------------------- */ async onPreUpdateItem(item, change, options, id) { } - async onCreateItem(item, options, id) { - switch (item.type) { - case ITEM_TYPES.blessure: - await this.changeBleedingState() - break - } - } + async onCreateItem(item, options, id) { } - async onUpdateItem(item, options, id) { - switch (item.type) { - case ITEM_TYPES.blessure: - await this.changeBleedingState() - break - } - } - - async changeBleedingState() { - const bleeding = this.itemTypes[ITEM_TYPES.blessure].find(it => it.isBleeding()) - await this.setEffect(STATUSES.StatusBleeding, bleeding ? true : false) - } + async onUpdateItem(item, options, id) { } async onUpdateActor(update, options, actorId) { } + async onDeleteItem(item, options, id) { if (item.isInventaire()) { await this._removeItemFromConteneur(item) diff --git a/module/actor/random/app-personnage-aleatoire.js b/module/actor/random/app-personnage-aleatoire.js index 8a988d5a..fabd2219 100644 --- a/module/actor/random/app-personnage-aleatoire.js +++ b/module/actor/random/app-personnage-aleatoire.js @@ -20,14 +20,15 @@ const PATHS = [ const RANDOM_VALUES = { 'system.sexe': { 'masculin': 1, 'féminin': 1 }, 'system.main': { 'droitier': 51, 'gaucher': 15, 'ambidextre': 6 }, - 'system.cheveux': { 'noirs': 2, 'bruns': 5, 'châtains clair': 5, 'blonds': 4, 'blonds très clair': 1, 'roux carotte': 1, 'roux cuivré': 3 }, - 'system.yeux': { 'noirs': 2, 'noisettes': 3, 'bruns vert': 4, 'verts': 3, 'bleus clair': 3, 'bleus gris': 2, 'gris': 1, 'mauves': 1, 'indigos': 1 }, + 'system.cheveux': { 'noirs': 2, 'bruns': 5, 'châtains': 3, 'châtain clair': 5, 'blonds': 4, 'blond platine': 1, 'roux carotte': 1, 'roux cuivré': 3, 'chauve': 1 }, + 'system.yeux': { 'noirs': 2, 'noisette': 3, 'brun vert': 4, 'verts': 3, 'bleu clair': 3, 'bleu gris': 2, 'gris': 1, 'mauves': 1, 'indigos': 1 }, } export class AppPersonnageAleatoire extends FormApplication { static preloadHandlebars() { foundry.applications.handlebars.loadTemplates([ 'systems/foundryvtt-reve-de-dragon/templates/actor/random/champ-aleatoire.hbs', + 'systems/foundryvtt-reve-de-dragon/templates/actor/random/sexe-aleatoire.hbs', ]) } @@ -49,14 +50,14 @@ export class AppPersonnageAleatoire extends FormApplication { this.current = foundry.utils.duplicate(actor) this.checked = { 'name': false, - 'system.sexe': true, - 'system.age': true, - 'system.taille': true, - 'system.poids': true, - 'system.main': true, - 'system.heure': true, - 'system.cheveux': true, - 'system.yeux': true + 'system.sexe': (this.actor.system.sexe ?? '') == '', + 'system.age': this.actor.system.age == 0, + 'system.taille': (this.actor.system.taille ?? '') == '', + 'system.poids': (this.actor.system.poids ?? '') == '', + 'system.main': (this.actor.system.main ?? '') == '', + 'system.heure': (this.actor.system.heure ?? '') == '', + 'system.cheveux': (this.actor.system.cheveux ?? '') == '', + 'system.yeux': (this.actor.system.yeux ?? '') == '', } } @@ -76,6 +77,8 @@ export class AppPersonnageAleatoire extends FormApplication { this.html.find("button.button-apply").click(async event => await this.onApply()) this.html.find("input.current-value").change(async event => await this.onChange(event)) this.html.find("div.random-field[data-path='system.heure'] select.current-value").change(async event => await this.onChange(event)) + this.html.find('a[data-action="sexe-masculin"]').click(async event => await this.onSexe('masculin')) + this.html.find('a[data-action="sexe-feminin"]').click(async event => await this.onSexe('féminin')) this.html.find("a.random").click(async event => await this.onRandom(event)) this.html.find("a.reset").click(async event => await this.onReset(event)) this.html.find("a.randomize-selected").click(async event => await this.onRandomizeSelected()) @@ -96,6 +99,10 @@ export class AppPersonnageAleatoire extends FormApplication { const fields = this.html.find(selector).parents("div.random-field:first") return fields[0].attributes['data-path'].value } + async onSexe(sexe) { + this.current['system.sexe'] = sexe + this.render() + } async onChange(event) { const path = this.getPath(event.currentTarget) @@ -180,8 +187,9 @@ export class AppPersonnageAleatoire extends FormApplication { const variation = Math.floor((caracTaille.poidsMax - caracTaille.poidsMin + base / 5) / 2) const total = await RdDDice.rollTotal(`2d${variation} + ${base}`) const cm = total % 100 + const dm = cm < 10 ? '0' : '' const m = (total - cm) / 100 - return `${m}m${cm}` + return `${m}m${dm}${cm}` } diff --git a/module/chat-utility.js b/module/chat-utility.js index 68f88d4f..bd3e97c8 100644 --- a/module/chat-utility.js +++ b/module/chat-utility.js @@ -106,6 +106,25 @@ export class ChatUtility { return await ChatMessage.create(messageData) } + static tellToUser(message) { + ChatMessage.create({ content: message, user: game.user.id, whisper: [game.user.id] }); + } + + static tellToGM(message) { + ChatMessage.create({ + user: game.user.id, + content: message, + whisper: ChatUtility.getGMs() + }); + } + + static tellToUserAndGM(message) { + ChatMessage.create({ + user: game.user.id, + content: message, + whisper: ChatUtility.getUserAndGMs() + }) + } static getOwners(document) { return document ? game.users.filter(it => document.getUserLevel(it) == CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER) : [game.user] } diff --git a/module/item-sort.js b/module/item-sort.js index ec4f5cdd..0dd015d0 100644 --- a/module/item-sort.js +++ b/module/item-sort.js @@ -148,10 +148,17 @@ export class RdDItemSort extends Item { /* -------------------------------------------- */ static incrementBonusCase(actor, sort, coord) { + let bonuscase = RdDItemSort.calculBonuscase(sort, coord) + + actor.updateEmbeddedDocuments('Item', [{ _id: sort._id, 'system.bonuscase': bonuscase }]); + } + + + static calculBonuscase(sort, coord) { if (TMRUtility.isFleuve(coord)) { - coord = FLEUVE_COORD; + coord = FLEUVE_COORD } - let list = RdDItemSort.stringToBonuscases(sort.system.bonuscase); + let list = RdDItemSort.stringToBonuscases(sort.system.bonuscase) const existing = list.find(it => it.case == coord) const bonus = Number(existing?.bonus ?? 0) + 1 if (existing) { @@ -160,11 +167,9 @@ export class RdDItemSort extends Item { else { list.push({ case: coord, bonus: 1 }) } - - actor.updateEmbeddedDocuments('Item', [{ _id: sort._id, 'system.bonuscase': RdDItemSort.bonuscasesToString(list) }]); + return RdDItemSort.bonuscasesToString(list) } - /* -------------------------------------------- */ static getCaseBonus(sort, coord) { const search = TMRUtility.isFleuve(coord) @@ -189,4 +194,22 @@ export class RdDItemSort extends Item { .map(it => it.split(':')) .map(it => { return { case: it[0], bonus: it[1] } }); } + + static prepareSortEnReserve(sort, draconic, ptreve, coord) { + return { + type: ITEM_TYPES.sortreserve, + name: sort.name, + img: sort.img, + system: { sortid: sort._id, draconic: (draconic?.name ?? sort.system.draconic), ptreve: ptreve, coord: coord, heurecible: 'Vaisseau' } + }; + } + + static prepareSortAddLancement(sort, reveSort) { + const precedents = sort.system.lancements ?? [] + const lancements = [...precedents, { + timestamp: game.system.rdd.calendrier.getTimestamp(), + reve: reveSort + }] + return lancements + } } \ No newline at end of file diff --git a/module/rdd-commands.js b/module/rdd-commands.js index 762f8d23..2f889ba1 100644 --- a/module/rdd-commands.js +++ b/module/rdd-commands.js @@ -461,12 +461,7 @@ export class RdDCommands { async supprimerSignesDraconiquesEphemeres() { if (game.user.isGM) { - game.actors.forEach(actor => { - const ephemeres = actor.items.filter(item => item.type = 'signedraconique' && item.system.ephemere); - if (ephemeres.length > 0) { - actor.deleteEmbeddedDocuments("Item", ephemeres.map(item => item.id)); - } - }); + game.actors.forEach(actor => actor.supprimerSignesDraconiques(it => it.system.ephemere)) } else { ui.notifications.warn("Seul le MJ est autorisé à utiliser la commande /signe"); diff --git a/module/rdd-resolution-table.js b/module/rdd-resolution-table.js index 18c7545f..b3adb522 100644 --- a/module/rdd-resolution-table.js +++ b/module/rdd-resolution-table.js @@ -156,11 +156,16 @@ export class RdDResolutionTable { } } + /* -------------------------------------------- */ + static replaceParticuliereDemiSurprise(chances){ + foundry.utils.mergeObject(chances, reussites.find(x => x.code == 'part'), { overwrite: true }); + } + /* -------------------------------------------- */ static significativeRequise(chances) { chances.roll = Math.min(chances.part + 1, chances.sign) foundry.utils.mergeObject(chances, reussites.find(x => x.code == 'sign'), { overwrite: true }); - } + } /* -------------------------------------------- */ static succesRequis(chances) { diff --git a/module/rdd-roll-ethylisme.js b/module/rdd-roll-ethylisme.js index 440d7a08..67a56c28 100644 --- a/module/rdd-roll-ethylisme.js +++ b/module/rdd-roll-ethylisme.js @@ -25,7 +25,7 @@ export class RdDRollDialogEthylisme extends Dialog { activateListeners(html) { super.activateListeners(html); this.html = html; - this.bringToTop(); + this.bringToFront(); this.html.find(".force-alcool").change((event) => { this.rollData.forceAlcool = Misc.toInt(event.currentTarget.value); diff --git a/module/rdd-roll-resolution-table.js b/module/rdd-roll-resolution-table.js index 337a9568..2334614f 100644 --- a/module/rdd-roll-resolution-table.js +++ b/module/rdd-roll-resolution-table.js @@ -22,7 +22,7 @@ export class RdDRollResolutionTable extends Dialog { RdDRollResolutionTable.resolutionTable.render(true); } else{ - RdDRollResolutionTable.resolutionTable.bringToTop(); + RdDRollResolutionTable.resolutionTable.bringToFront(); } } @@ -70,7 +70,7 @@ export class RdDRollResolutionTable extends Dialog { activateListeners(html) { super.activateListeners(html); this.html = html; - this.bringToTop(); + this.bringToFront(); this.html.find("[name='diffLibre']").val(Misc.toInt(this.rollData.diffLibre)); diff --git a/module/rdd-roll.js b/module/rdd-roll.js index 65c08de6..3c369ecd 100644 --- a/module/rdd-roll.js +++ b/module/rdd-roll.js @@ -128,7 +128,7 @@ export class RdDRoll extends Dialog { activateListeners(html) { super.activateListeners(html); this.html = html; - this.bringToTop(); + this.bringToFront(); console.log('RdDRoll.activateListeners', this.rollData); diff --git a/module/rdd-tmr-dialog.js b/module/rdd-tmr-dialog.js index fbc88b7f..1303882a 100644 --- a/module/rdd-tmr-dialog.js +++ b/module/rdd-tmr-dialog.js @@ -82,7 +82,7 @@ export class RdDTMRDialog extends Dialog { this.subdialog = undefined this.displaySize = undefined if (!this.viewOnly && !game.user.isGM) { - this.$tellToGM(this.actor.name + " monte dans les terres médianes (" + tmrData.mode + ")"); + ChatUtility.tellToGM(this.actor.name + " monte dans les terres médianes (" + tmrData.mode + ")"); } this.callbacksOnAnimate = []; const displaySize = TMR_DISPLAY_SIZE.clamp(game.settings.get(SYSTEM_RDD, TMR_DISPLAY_SIZE.code) ?? TMR_DISPLAY_SIZE.def); @@ -125,7 +125,11 @@ export class RdDTMRDialog extends Dialog { HtmlUtility.showControlWhen(this.html.find(".appliquerFatigue"), ReglesOptionnelles.isUsing("appliquer-fatigue")); HtmlUtility.showControlWhen(this.html.find(".lire-signe-draconique"), this.actor.isResonanceSigneDraconique(this._getCoordActor())); - this.html.find('form.tmr-dialog *').click(event => this.subdialog?.bringToTop()); + this.html.find('form.tmr-dialog *').click(event => { + if (this.subdialog?.rendered){ + this.subdialog?.bringToFront() + } + }) // Roll Sort this.html.find('.lancer-sort').click(event => this.lancerUnSort()); @@ -169,26 +173,26 @@ export class RdDTMRDialog extends Dialog { async forceTMRDisplay() { if (this.rendered) { - this.bringToTop() - this.bringSubDialogToTop(); + this.bringToFront() + this.bringSubDialogToFront(); } } - bringSubDialogToTop() { - if (this.subdialog?.bringToTop && this.subdialog?.element && this.subdialog?.element[0]) { - this.subdialog.bringToTop(); + bringSubDialogToFront() { + if (this.subdialog?.bringToFront && this.subdialog?.element && this.subdialog?.element[0]) { + this.subdialog.bringToFront(); } } async restoreTMRAfterAction() { this.subdialog = undefined await this.maximize() - this.bringToTop() + this.bringToFront() } forceTMRContinueAction() { ui.notifications.warn('Vous devez finir votre action avant de continuer dans les TMR'); - this.bringSubDialogToTop(); + this.bringSubDialogToFront(); return false } @@ -343,19 +347,8 @@ export class RdDTMRDialog extends Dialog { this.forceTMRContinueAction() return false } - this.descenteTMR = true; - if (this.actor.tmrApp) { - this.actor.tmrApp = undefined // Cleanup reference - const appliquerFatigue = ReglesOptionnelles.isUsing("appliquer-fatigue") - await this.actor.santeIncDec( - appliquerFatigue ? "fatigue" : "endurance", - (appliquerFatigue ? 1 : -1) * this.cumulFatigue) - if (!this.viewOnly) { - await this.actor.setEffect(STATUSES.StatusDemiReve, false) - this.$tellToUserAndGM(message) - } - - } + this.descenteTMR = true + await await this.actor.quitterTMR(message, this.viewOnly, this.cumulFatigue) this.pixiTMR.close(); this.pixiTMR = undefined await super.close(); @@ -412,7 +405,7 @@ export class RdDTMRDialog extends Dialog { async $ignorerRencontre() { if (this.currentRencontre) { console.log("-> ignorer", this.currentRencontre); - this.$tellToGM(this.actor.name + " a ignoré: " + this.currentRencontre.name); + ChatUtility.tellToGM(this.actor.name + " a ignoré: " + this.currentRencontre.name); await this.$deleteRencontreTMRAtPosition() this.updateTokens(); this.$updateValuesDisplay(); @@ -578,29 +571,6 @@ export class RdDTMRDialog extends Dialog { }, 500); } - /* -------------------------------------------- */ - _tellToUser(message) { - ChatMessage.create({ content: message, user: game.user.id, whisper: [game.user.id] }); - } - - /* -------------------------------------------- */ - $tellToGM(message) { - ChatMessage.create({ - user: game.user.id, - content: message, - whisper: ChatUtility.getGMs() - }); - } - - /* -------------------------------------------- */ - $tellToUserAndGM(message) { - ChatMessage.create({ - user: game.user.id, - content: message, - whisper: ChatUtility.getUserAndGMs() - }) - } - /* -------------------------------------------- */ async manageRencontre(tmr) { if (this.viewOnly) { @@ -676,14 +646,14 @@ export class RdDTMRDialog extends Dialog { ? TMRUtility.getTMRType(tmr.coord) + " ??" : tmr.label + " (" + tmr.coord + ")"); - this.setTMRPendingAction({ bringToTop: () => { } }) + this.setTMRPendingAction({ bringToFront: () => { } }) const myRoll = await RdDDice.rollTotal("1dt", { showDice: SHOW_DICE }); this.restoreTMRAfterAction() if (myRoll == 7) { - this._tellToUser(myRoll + ": Rencontre en " + coordTMR); + ChatUtility.tellToUser(myRoll + ": Rencontre en " + coordTMR); return await game.system.rdd.rencontresTMR.getRencontreAleatoire(tmr, this.actor.isMauvaiseRencontre()) } else { - this._tellToUser(myRoll + ": Pas de rencontre en " + coordTMR); + ChatUtility.tellToUser(myRoll + ": Pas de rencontre en " + coordTMR); return undefined; } } diff --git a/module/roll/chat-roll-result.mjs b/module/roll/chat-roll-result.mjs index 0371c911..8ebd592e 100644 --- a/module/roll/chat-roll-result.mjs +++ b/module/roll/chat-roll-result.mjs @@ -8,6 +8,10 @@ import { RDD_CONFIG, renderTemplate } from "../constants.js" import { EMPOIGNADE } from "../item/arme.js" import { RdDTextEditor } from "../apps/rdd-text-roll-editor.js" import { RollTypeCuisine } from "./roll-type-cuisine.mjs" +import { RollTypeMeditation } from "./roll-type-meditation.mjs" +import { PART_DEFENSE } from "./roll-part-defense.mjs" +import { PART_ATTAQUE } from "./roll-part-attaque.mjs" +import { RdDRollTables } from "../rdd-rolltables.js" export default class ChatRollResult { static init() { @@ -20,6 +24,7 @@ export default class ChatRollResult { 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-maladresse': 'systems/foundryvtt-reve-de-dragon/templates/roll/result/partial-maladresse.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', @@ -38,7 +43,7 @@ export default class ChatRollResult { ) const save = RollDialog.saveParts(roll, impacts) - ChatUtility.setMessageData(chatMessage, 'rollData', save) + await this.saveChatMessageRoll(chatMessage, save) return chatMessage } @@ -48,7 +53,7 @@ export default class ChatRollResult { roll.show.chance = this.isAppelChancePossible(roll) roll.show.encaissement = this.isShowEncaissement(roll) roll.show.recul = this.getRecul(roll) - //roll.show.particuliere = roll.show.particuliere ?? [] + roll.show.maladresse = this.getMaladresse(roll) } isAppelChancePossible(roll) { @@ -65,6 +70,23 @@ export default class ChatRollResult { return false } + getMaladresse(roll) { + switch (roll.type.current) { + case ROLL_TYPE_DEFENSE: + if (roll.rolled.isETotal) { + const arme = roll.current[PART_DEFENSE].arme + return arme ? 'avec-arme' : 'sans-arme' + } + break + case ROLL_TYPE_ATTAQUE: + if (roll.rolled.isETotal || (roll.rolled.isEchec && roll.active.surprise == 'demi')) { + const arme = roll.current[PART_ATTAQUE].arme + return arme.system.baseInit > 4 ? 'avec-arme' : 'sans-arme' + } + break + } + return undefined + } getRecul(roll, defender = roll.active.actor, attacker = roll.opponent?.actor) { switch (roll.type.current) { @@ -112,6 +134,9 @@ export default class ChatRollResult { $(html).on("click", '.resister-recul', event => this.onClickRecul(event)) $(html).on("click", '.choix-particuliere', event => this.onClickChoixParticuliere(event)) $(html).on("click", '.faire-gouter', event => this.onClickFaireGouter(event)) + $(html).on("click", '.monter-tmr-normale', event => this.onClickMonteeTMR(event, 'normal')) + $(html).on("click", '.monter-tmr-rapide', event => this.onClickMonteeTMR(event, 'rapide')) + $(html).on("click", '.tirer-maladresse', event => this.onClickTirerMaladresse(event)) } @@ -125,8 +150,16 @@ export default class ChatRollResult { return undefined } + async saveChatMessageRoll(chatMessage, savedRoll) { + await ChatUtility.setMessageData(chatMessage, 'rollData', savedRoll) + } + + loadChatMessageRoll(chatMessage) { + return ChatUtility.getMessageData(chatMessage, 'rollData') + } + async updateChatMessage(chatMessage, savedRoll) { - ChatUtility.setMessageData(chatMessage, 'rollData', savedRoll) + await this.saveChatMessageRoll(chatMessage, savedRoll) const copy = foundry.utils.duplicate(savedRoll) RollDialog.loadRollData(copy) this.prepareDisplay(copy) @@ -136,7 +169,7 @@ export default class ChatRollResult { onClickAppelChance(event) { const chatMessage = ChatUtility.getChatMessage(event) - const savedRoll = ChatUtility.getMessageData(chatMessage, 'rollData') + const savedRoll = this.loadChatMessageRoll(chatMessage) const actor = game.actors.get(savedRoll.ids.actorId) actor.rollAppelChance( () => this.onAppelChanceSuccess(savedRoll, chatMessage), @@ -144,11 +177,13 @@ export default class ChatRollResult { event.preventDefault() } - onAppelChanceSuccess(savedRoll, chatMessage) { + async onAppelChanceSuccess(savedRoll, chatMessage) { const reRoll = foundry.utils.duplicate(savedRoll) console.log('onAppelChanceSuccess savedRoll', savedRoll) reRoll.type.retry = true + await this.updateChatMessage(chatMessage, reRoll) const callbacks = [r => ChatUtility.removeChatMessageId(chatMessage.id)] + // TODO: annuler les effets switch (reRoll.type.current) { case ROLL_TYPE_DEFENSE: @@ -171,7 +206,7 @@ export default class ChatRollResult { onClickAppelDestinee(event) { const chatMessage = ChatUtility.getChatMessage(event) - const savedRoll = ChatUtility.getMessageData(chatMessage, 'rollData') + const savedRoll = this.loadChatMessageRoll(chatMessage) const actor = game.actors.get(savedRoll.ids.actorId) actor.appelDestinee(async () => { @@ -184,7 +219,7 @@ export default class ChatRollResult { async onClickEncaissement(event) { const chatMessage = ChatUtility.getChatMessage(event) - const savedRoll = ChatUtility.getMessageData(chatMessage, 'rollData') + const savedRoll = this.loadChatMessageRoll(chatMessage) const attaque = savedRoll.attackerRoll const defender = game.actors.get(savedRoll.ids.actorId) const attacker = game.actors.get(savedRoll.ids.opponentId) @@ -198,7 +233,7 @@ export default class ChatRollResult { async onClickRecul(event) { const chatMessage = ChatUtility.getChatMessage(event) - const savedRoll = ChatUtility.getMessageData(chatMessage, 'rollData') + const savedRoll = this.loadChatMessageRoll(chatMessage) const defender = game.actors.get(savedRoll.ids.actorId) const attacker = game.actors.get(savedRoll.ids.opponentId) savedRoll.done.recul = await defender.encaisserRecul(attacker.getForce(), savedRoll.attackerRoll.dmg.dmgArme) @@ -209,15 +244,38 @@ export default class ChatRollResult { async onClickChoixParticuliere(event) { const choix = event.currentTarget.attributes['data-particuliere'].value const chatMessage = ChatUtility.getChatMessage(event) - const savedRoll = ChatUtility.getMessageData(chatMessage, 'rollData') + const savedRoll = this.loadChatMessageRoll(chatMessage) savedRoll.particuliere = choix savedRoll.particulieres = [RDD_CONFIG.particuliere[choix]] await this.updateChatMessage(chatMessage, savedRoll) await this.getCombat(savedRoll)?.onAttaqueV2(savedRoll, callbacks) } + async onClickFaireGouter(event) { const chatMessage = ChatUtility.getChatMessage(event) - const savedRoll = ChatUtility.getMessageData(chatMessage, 'rollData') + const savedRoll = this.loadChatMessageRoll(chatMessage) + if (!savedRoll.type.retry) { + savedRoll.type.retry = true + await this.updateChatMessage(chatMessage, savedRoll) + } await new RollTypeCuisine().onFaireGouter(savedRoll) } + + async onClickMonteeTMR(event, mode) { + const chatMessage = ChatUtility.getChatMessage(event) + const savedRoll = this.loadChatMessageRoll(chatMessage) + if (await new RollTypeMeditation().onMonteeTMR(savedRoll, mode)) { + savedRoll.done.meditation = true + await this.updateChatMessage(chatMessage, savedRoll) + } + } + async onClickTirerMaladresse(event) { + const chatMessage = ChatUtility.getChatMessage(event) + const typeMaladresse = event.currentTarget.attributes['data-maladresse'].value + const savedRoll = this.loadChatMessageRoll(chatMessage) + await RdDRollTables.getMaladresse({ arme: typeMaladresse == 'avec-arme' }) + savedRoll.type.maladresse = true + savedRoll.type.retry = true + await this.updateChatMessage(chatMessage, savedRoll) + } } \ No newline at end of file diff --git a/module/roll/roll-dialog-adapter.mjs b/module/roll/roll-dialog-adapter.mjs index e9de2eb5..e6252591 100644 --- a/module/roll/roll-dialog-adapter.mjs +++ b/module/roll/roll-dialog-adapter.mjs @@ -27,6 +27,7 @@ export class RollDialogAdapter { RollDialogAdapter.setRollDataRolled(rollData, rolled, rollTitle) RollDialogAdapter.adjustRollDataForV1(rollData) RollDialogAdapter.adjustAttaqueParticuliere(rollData) + RollDialogAdapter.adjustDemiSurprise(rollData) return rolled } @@ -105,6 +106,12 @@ export class RollDialogAdapter { }) } + static adjustDemiSurprise(rollData) { + if (rollData.active.surprise == 'demi' && rollData.rolled.isPart) { + RdDResolutionTable.replaceParticuliereDemiSurprise(rollData.rolled) + } + } + static adjustAttaqueParticuliere(rollData) { if (rollData.type.current != ROLL_TYPE_ATTAQUE || !rollData.rolled.isPart) { return diff --git a/module/roll/roll-dialog.mjs b/module/roll/roll-dialog.mjs index 03c6a1e5..deffa22a 100644 --- a/module/roll/roll-dialog.mjs +++ b/module/roll/roll-dialog.mjs @@ -173,6 +173,8 @@ const ROLL_PARTS = [ /* -------------------------------------------- */ export default class RollDialog extends HandlebarsApplicationMixin(ApplicationV2) { + static onCloseDoNothing() { + } static onRollDoneDoNothing(dialog) { dialog.render() } @@ -181,7 +183,6 @@ export default class RollDialog extends HandlebarsApplicationMixin(ApplicationV2 dialog.close() } - static init() { } @@ -236,6 +237,7 @@ export default class RollDialog extends HandlebarsApplicationMixin(ApplicationV2 static async create(rollData, rollOptions = {}) { const rollDialog = new RollDialog(rollData, rollOptions) rollDialog.render(true) + return rollDialog } static get PARTS() { @@ -322,7 +324,8 @@ export default class RollDialog extends HandlebarsApplicationMixin(ApplicationV2 ...(rollOptions.callbacks ?? []) ], customChatMessage: rollOptions.customChatMessage, - onRollDone: rollOptions.onRollDone ?? RollDialog.onRollDoneDoNothing + onRollDone: rollOptions.onRollDone ?? RollDialog.onRollDoneDoNothing, + onClose: rollOptions.onClose ?? RollDialog.onCloseDoNothing } this.chatRollResult = new ChatRollResult(); this.selectType() @@ -440,20 +443,24 @@ export default class RollDialog extends HandlebarsApplicationMixin(ApplicationV2 return ALL_ROLL_TYPES.find(m => m.code == this.rollData.type.current) } + async close(options){ + if (this.rollOptions.onClose){ + this.rollOptions.onClose() + } + return await super.close(options) + } + async roll() { const roll = RollDialog.saveParts(this.rollData) - this.loadRollData(roll) + RollDialog.loadRollData(roll) const selectedRollType = this.getSelectedType(roll); selectedRollType.onSelect(roll) roll.current.resultat = this.rollData.current[PART_TRICHER]?.resultat ?? -1 roll.choix = {} roll.rolled = await RollDialogAdapter.rollDice(roll, this.rollTitle(roll)) - const impacts = { - active: new ActorImpacts(roll.active), - opponent: roll.opponent ? new ActorImpacts(roll.opponent) : undefined - } + const impacts = new ActorImpacts(roll.active) roll.result = selectedRollType.getResult(roll, impacts) @@ -465,14 +472,13 @@ export default class RollDialog extends HandlebarsApplicationMixin(ApplicationV2 await Promise.all(callbacks.map(async callback => await callback(roll))) - await impacts.active?.applyImpacts() - await impacts.opponent?.applyImpacts() + await impacts.applyImpacts() selectedRollType.onApplyImpacts(roll, impacts) await this.chatRollResult.display(roll, impacts) this.rollOptions.onRollDone(this) } - loadRollData(roll) { + static loadRollData(roll) { RollDialog.$prepareRollData(roll) RollDialog.calculAjustements(roll) roll.v2 = true diff --git a/module/roll/roll-part-attaque.mjs b/module/roll/roll-part-attaque.mjs index a9f69731..4eedb0ad 100644 --- a/module/roll/roll-part-attaque.mjs +++ b/module/roll/roll-part-attaque.mjs @@ -50,14 +50,6 @@ export class RollPartAttaque extends RollPartSelect { choices(refs) { return refs.attaques } static $extractAttaque(attaque, actor) { - // const extracted = foundry.utils.mergeObject({ - // key: `${attaque.action}::${attaque.label}`, - // tactique: TACTIQUES[0] - // }, - // attaque - // ) - // return extracted - // extracted.initialDiff = attaque.comp?.system.default_diffLibre ?? 0 attaque.key = `${attaque.action}::${attaque.label}` attaque.tactique = TACTIQUES[0] attaque.initialDiff = attaque.comp?.system.default_diffLibre ?? 0 diff --git a/module/roll/roll-part-defense.mjs b/module/roll/roll-part-defense.mjs index e03dcfb0..5b63e7ef 100644 --- a/module/roll/roll-part-defense.mjs +++ b/module/roll/roll-part-defense.mjs @@ -1,9 +1,6 @@ import { ITEM_TYPES } from "../constants.js" -import { Grammar } from "../grammar.js" import { ATTAQUE_TYPE, RdDItemArme } from "../item/arme.js" -import { RdDBonus } from "../rdd-bonus.js" import { CARACS } from "../rdd-carac.js" -import { StatusEffects } from "../settings/status-effects.js" import { DIFF, ROLL_TYPE_DEFENSE } from "./roll-constants.mjs" import { PART_CARAC } from "./roll-part-carac.mjs" import { PART_COMP } from "./roll-part-comp.mjs" diff --git a/module/roll/roll-part-jeu.mjs b/module/roll/roll-part-jeu.mjs index 0d3564ee..f20d13b5 100644 --- a/module/roll/roll-part-jeu.mjs +++ b/module/roll/roll-part-jeu.mjs @@ -81,7 +81,9 @@ export class RollPartJeu extends RollPartSelect { $selectJeu(rollData, key) { this.selectByKey(rollData, key, 0) - RollPartJeu.forceCompJeu(rollData) + if (rollData.type.current == ROLL_TYPE_JEU) { + RollPartJeu.forceCompJeu(rollData) + } } static forceCompJeu(rollData) { diff --git a/module/roll/roll-part-sort.mjs b/module/roll/roll-part-sort.mjs index 181af63d..ac7627a4 100644 --- a/module/roll/roll-part-sort.mjs +++ b/module/roll/roll-part-sort.mjs @@ -22,9 +22,27 @@ export class RollPartSort extends RollPartSelect { isValid(rollData) { return rollData.active.actor.isPersonnage() && rollData.active.actor.isHautRevant() } visible(rollData) { return this.isRollType(rollData, ROLL_TYPE_SORT) } + restore(rollData) { + const saved = this.getSaved(rollData) + this.setCurrent(rollData, { + key: saved.key, + isReserve: saved.isReserve, + ptreve: saved.ptreve + }) + } + + store(rollData, targetData) { + const current = this.getCurrent(rollData) + this.setSaved(targetData, { + key: current.key, + isReserve: current.isReserve, + ptreve: current.ptreve + }) + } + loadRefs(rollData) { const refs = this.getRefs(rollData) - const coord = rollData.active.actor.system.reve.tmrpos.coord + const coord = this.getCoord(rollData) const draconics = rollData.active.actor.getDraconics() const sorts = rollData.active.actor.itemTypes[ITEM_TYPES.sort] .map(s => RollPartSort.$extractSort(s, coord, draconics)) @@ -33,7 +51,7 @@ export class RollPartSort extends RollPartSelect { { coord: coord, tmr: TMRUtility.getTMR(coord), - reve: rollData.active.actor.system.reve.reve.value, + reve: this.getReveActuel(rollData), draconics: draconics, all: sorts, sorts: sorts.filter(it => RdDItemSort.isSortOnCoord(it.sort, coord)) @@ -41,10 +59,18 @@ export class RollPartSort extends RollPartSelect { { inplace: true } ) if (refs.sorts.length > 0) { - this.$selectSort(rollData) + this.$selectSort(rollData, this.getSaved(rollData)) } } + getReveActuel(rollData) { + return rollData.active.actor.system.reve.reve.value + } + + getCoord(rollData) { + return rollData.active.actor.system.reve.tmrpos.coord + } + choices(refs) { return refs.sorts } static $extractSort(sort, coord, draconics) { @@ -56,11 +82,12 @@ export class RollPartSort extends RollPartSelect { value: isDiffVariable ? -7 : parseInt(sort.system.difficulte), ptreve: isReveVariable ? 1 : sort.system.ptreve, caseTMR: RdDItemSort.getCaseTMR(sort), + bonusCase: RdDItemSort.getCaseBonus(sort, coord), isDiffVariable: isDiffVariable, isReveVariable: isReveVariable, isReserve: false, sort: sort, - draconics: RdDItemSort.getDraconicsSort(draconics, sort).map(it => it.name) + draconics: RdDItemSort.getDraconicsSort(draconics, sort).map(it => it.name), } } @@ -68,25 +95,27 @@ export class RollPartSort extends RollPartSelect { const current = this.getCurrent(rollData) if (current) { const reserve = current.isReserve ? - [{ label: `Mise en réserve en ${current.coord}` }] : [] + [{ label: `Mise en réserve en ${this.getCoord(rollData)}` }] : [] const bonusCase = current.bonusCase ? [{ label: `Bonus case +${current.bonusCase}%` }] : [] return [ { label: current.label, diff: current.value }, - { label: `r${current.ptreve}` }, ...bonusCase, + { label: `Rêve ${current.ptreve}` }, ...reserve ] } return [] } - $selectSort(rollData, key) { - const previous = this.getCurrent(rollData) - const current = this.selectByKey(rollData, key, -7) - if (current.key != previous.key) { } - current.bonusCase = RdDItemSort.getCaseBonus(current.sort, - rollData.active.actor.system.reve.tmrpos.coord) + $selectSort(rollData, values) { + const current = this.selectByKey(rollData, values.key) + if (values.ptreve) { + current.ptreve = values.ptreve + } + if (values.isReserve != undefined) { + current.isReserve = values.isReserve + } } async _onRender(rollDialog, context, options) { @@ -99,7 +128,7 @@ export class RollPartSort extends RollPartSelect { selectSort.addEventListener("change", e => { const selectOptions = e.currentTarget.options const index = selectOptions.selectedIndex - this.$selectSort(rollDialog.rollData, selectOptions[index]?.value) + this.$selectSort(rollDialog.rollData, { key: selectOptions[index]?.value }) rollDialog.render() }) diff --git a/module/roll/roll-type-attaque.mjs b/module/roll/roll-type-attaque.mjs index f761e9be..794e8fb8 100644 --- a/module/roll/roll-type-attaque.mjs +++ b/module/roll/roll-type-attaque.mjs @@ -1,5 +1,4 @@ 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 { diff --git a/module/roll/roll-type-cuisine.mjs b/module/roll/roll-type-cuisine.mjs index bcb84ae6..c4b38d1f 100644 --- a/module/roll/roll-type-cuisine.mjs +++ b/module/roll/roll-type-cuisine.mjs @@ -32,17 +32,17 @@ export class RollTypeCuisine extends RollType { if (current.fabriquer) { const plat = this.$prepareNourriture(rollData.active.name, current, result) result.messages.push(`${plat.system.quantite} ${plat.name} ont été préparés dans l'équipement`) - impacts.active.addCreatedItem(plat) + impacts.addCreated('Item', plat) result.plat = { id: plat.id } } if (current.ingredient) { const quantite = Math.min(current.proportions, current.ingredient.system.quantite) if (quantite == current.ingredient.system.quantite) { - impacts.active.addDeletedItem(current.ingredient) + impacts.addDeleted('Item', current.ingredient) result.messages.push(`Il n'y a plus de ${ingredient.name}`) } else { - impacts.active.addItemDelta(current.ingredient, 'system.quantite', -current.proportions) + impacts.addDelta('Item', current.ingredient, 'system.quantite', -current.proportions) result.messages.push(`${current.proportions} ${current.ingredient.name} ont été utilisés`) } } @@ -51,7 +51,7 @@ export class RollTypeCuisine extends RollType { onApplyImpacts(roll, impacts) { if (roll.result.plat) { // le plat n'est pas créé immédiatement, il faut donc retrouver l'id - roll.result.plat.id = impacts.active.itemCreates.find(it => it.id = roll.result.plat.id)?.createdId + roll.result.plat.id = impacts.findCreatedId('Item', roll.result.plat.id) } } diff --git a/module/roll/roll-type-meditation.mjs b/module/roll/roll-type-meditation.mjs index ab310ccc..c3238487 100644 --- a/module/roll/roll-type-meditation.mjs +++ b/module/roll/roll-type-meditation.mjs @@ -1,4 +1,5 @@ import { RdDItemSigneDraconique } from "../item/signedraconique.js" +import { RollBasicParts } from "./roll-basic-parts.mjs" import { DIFF, ROLL_TYPE_MEDITATION } from "./roll-constants.mjs" import { PART_MEDITATION } from "./roll-part-meditation.mjs" import { RollType } from "./roll-type.mjs" @@ -26,7 +27,7 @@ export class RollTypeMeditation extends RollType { const rolled = rollData.rolled if (meditation && rolled) { if (rolled.isSuccess) { - await actor.createEmbeddedDocuments("Item", [RdDItemSigneDraconique.prepareSigneDraconiqueMeditation(meditation, rolled)]) + await actor.createEmbeddedDocuments('Item', [RdDItemSigneDraconique.prepareSigneDraconiqueMeditation(meditation, rolled)]) } if (rolled.isEPart) { await actor.updateEmbeddedDocuments('Item', [{ _id: meditation._id, 'system.malus': meditation.system.malus - 1 }]) @@ -34,4 +35,9 @@ export class RollTypeMeditation extends RollType { await actor.santeIncDec("fatigue", 2) } } + + async onMonteeTMR(savedRoll, mode){ + const actor = RollBasicParts.getTokenActor(savedRoll).actor + return actor?.displayTMR(mode) + } } \ No newline at end of file diff --git a/module/roll/roll-type-sort.mjs b/module/roll/roll-type-sort.mjs index 728b3e3e..88164496 100644 --- a/module/roll/roll-type-sort.mjs +++ b/module/roll/roll-type-sort.mjs @@ -1,4 +1,9 @@ +import { RdDItemCompetence } from "../item-competence.js" +import { RdDItemSort } from "../item-sort.js" +import { RdDResolutionTable } from "../rdd-resolution-table.js" import { DIFF, ROLL_TYPE_SORT } from "./roll-constants.mjs" +import { PART_COMP } from "./roll-part-comp.mjs" +import { PART_SORT } from "./roll-part-sort.mjs" import { RollType } from "./roll-type.mjs" export class RollTypeSort extends RollType { @@ -11,4 +16,69 @@ export class RollTypeSort extends RollType { onSelect(rollData) { this.setDiffType(rollData, DIFF.AUCUN) } + + getResult(rollData, impacts) { + const rolled = rollData.rolled + const actor = rollData.active.actor + const coord = actor.system.reve.tmrpos.coord + const reveActuel = parseInt(actor.system.reve.reve.value) + const draconic = rollData.current[PART_COMP].comp + + const current = rollData.current[PART_SORT] + const sort = current.sort + const isReserve = current.isReserve + const reveSort = current.ptreve + const isThanatos = RdDItemCompetence.isThanatos(draconic) + + actor.tmrApp?.restoreTMRAfterAction() + + current.depenseReve = reveSort + if (isThanatos) { // Si Thanatos + impacts.addActorUpdate("system.reve.reve.thanatosused", true) + } + + if (rolled.isSuccess) { // Réussite du sort ! + if (rolled.isPart) { + current.depenseReve = Math.max(Math.floor(current.depenseReve / 2), 1) + } + if (isReserve) { + current.depenseReve++ + } + + if (reveActuel > current.depenseReve) { + // Incrémenter/gére le bonus de case + impacts.addUpdate('Item', sort, 'system.bonuscase', RdDItemSort.calculBonuscase(sort, coord)) + + if (isReserve) { + impacts.addCreated('Item', RdDItemSort.prepareSortEnReserve(sort, draconic, reveSort, coord)) + } + else { + rollData.closeTMR = true + if (sort.system.isrituel) { + impacts.addUpdate('Item', sort, 'system.lancements', RdDItemSort.prepareSortAddLancement(sort, reveSort)) + } + } + } + else { + current.depenseReve = 0; + rollData.show.reveInsuffisant = true + foundry.utils.mergeObject(rollData.rolled, RdDResolutionTable.getResultat("echec"), { overwrite: true }) + } + } else { + rollData.closeTMR = true + if (rolled.isETotal) { // Echec total ! + current.depenseReve = Math.min(reveActuel, Math.floor(current.depenseReve * 1.5)) + // TODO: mise en réserve d'un échec total... + } else { + current.depenseReve = 0 + } + } + + impacts.addActorDelta("system.reve.reve.value", - Math.min(current.depenseReve, reveActuel)) + + if (current.depenseReve >= reveActuel) { // 0 points de reve + rollData.show.zeroReve = true + } + } + } \ No newline at end of file diff --git a/module/technical/actor-impacts.mjs b/module/technical/actor-impacts.mjs index 43420384..31652ee6 100644 --- a/module/technical/actor-impacts.mjs +++ b/module/technical/actor-impacts.mjs @@ -1,15 +1,25 @@ +const ACTOR_EMBEDDED_DOCTYPES = ['Item', 'ActiveEffect'] /** * class designed to store actor modification instructions, to apply them in a single operation, and have the ability to revert these */ export class ActorImpacts { + static $newDocumentImpacts(docType) { + return { creates: [], deletes: [], updates: [], docType: docType } + } + static $checkDocType(docType) { + if (!ACTOR_EMBEDDED_DOCTYPES.includes(docType)) { + throw `Unsupported document type ${docType}` + } + } + constructor(actorToken) { this.actorToken = actorToken this.updates = [] this.deltas = [] - this.itemCreates = [] - this.itemUpdates = [] - this.itemDeletes = [] + ACTOR_EMBEDDED_DOCTYPES.forEach( + docType => this[docType] = ActorImpacts.$newDocumentImpacts(docType) + ) } addActorUpdate(path, value) { @@ -27,46 +37,55 @@ export class ActorImpacts { } } - addDeletedItem(item) { - this.itemDeletes.push(item) - } - addCreatedItem(item) { - this.itemCreates.push(item) + addDeleted(docType, document) { + ActorImpacts.$checkDocType(docType) + this[docType].deletes.push(document) } - addItemUpdate(item, path, value) { - const existing = this.itemUpdates.find(it => it.id == item.id) + addCreated(docType, document) { + ActorImpacts.$checkDocType(docType) + this[docType].creates.push(document) + } + + addUpdate(docType, document, path, value) { + ActorImpacts.$checkDocType(docType) const update = [path, value] + const existing = this[docType].updates.find(it => it.id == document.id) if (existing) { existing.updates.push(update) } else { - this.itemUpdates.push({ id: item.id, updates: [update], deltas: [] }) + this[docType].updates.push({ id: document.id, updates: [update], deltas: [] }) } } - addItemDelta(item, path, value) { + addDelta(document, path, value) { + ActorImpacts.$checkDocType(document) const intValue = Number.parseInt(value) if (Number.isInteger(intValue) && intValue != 0) { const delta = [path, intValue] - const existing = this.itemUpdates.find(it => it.id == item.id) + const existing = this[docType].updates.find(it => it.id == document.id) if (existing) { existing.deltas.push(delta) } else { - this.itemUpdates.push({ id: item.id, updates: [], deltas: [delta] }) + this[docType].updates.push({ id: document.id, updates: [], deltas: [delta] }) } } else { - console.error('Cannot use non integer value {} for delta update', value) + console.error('Cannot use non-integer value {} for delta update', value) } } reverseImpacts() { const reverse = ActorImpacts.$computeReverts(new ActorImpacts(this.actorToken), this, __ => this.actorToken.actor) - reverse.itemCreates = this.itemDeletes.map(it => foundry.utils.duplicate(it)) - reverse.itemDeletes = this.itemCreates.map(it => { return { id: it.id } }) - reverse.itemUpdates = this.itemUpdates.map(it => ActorImpacts.$computeReverts({ id: it.id }, it, id => this.$getActorItem(id))) + ACTOR_EMBEDDED_DOCTYPES.forEach( + docType => { + reverse[docType].creates = this[docType].deletes.map(it => foundry.utils.duplicate(it)) + reverse[docType].deletes = this[docType].creates.map(it => { return { id: it.id } }) + reverse[docType].updates = this[docType].updates.map(it => ActorImpacts.$computeReverts({ id: it.id }, it, id => this.$getEmbeddedDocument(docType, id))) + } + ) return reverse } @@ -77,40 +96,44 @@ export class ActorImpacts { async applyImpacts() { const actor = this.actorToken.actor - const isItemsDelete = this.itemDeletes.length > 0 - const isItemsCreate = this.itemCreates.length > 0 - const isItemsUpdate = this.itemUpdates.length > 0 - const isActorUpdate = this.updates.length > 0 || this.deltas.length > 0 + await Promise.all(ACTOR_EMBEDDED_DOCTYPES.map(async docType => await this.$applyDocumentsImpacts(actor, docType))) + const updates = ActorImpacts.$computeUpdates(this, id => actor) + await actor.update(updates, { render: true }) + } - if (isItemsDelete) { - const deletes = this.itemDeletes.map(it => it.id) - await actor.deleteEmbeddedDocuments('Item', deletes, { render: !(isItemsCreate || isItemsUpdate || isActorUpdate) }) + + async $applyDocumentsImpacts(actor, docType) { + if (this[docType].deletes.length > 0) { + const deletes = this[docType].deletes.map(it => it.id) + await actor.deleteEmbeddedDocuments(docType, deletes, { render: false }) } - if (isItemsCreate) { - const creates = this.itemCreates - const created = await actor.createEmbeddedDocuments('Item', creates, { render: !(isItemsUpdate || isActorUpdate)}) - for (let i=0; i 0) { + const creates = this[docType].creates + const created = await actor.createEmbeddedDocuments(docType, creates, { render: false }) + for (let i = 0; i < creates.length; i++) { creates[i].createdId = created[i].id } } - if (isItemsUpdate) { - const updates = this.itemUpdates.map(u => ActorImpacts.$computeUpdates(u, id => this.$getActorItem(id))) - await actor.updateEmbeddedDocuments('Item', updates, { render: !isActorUpdate }) - } - - if (isActorUpdate) { - const updates = ActorImpacts.$computeUpdates(this, id => actor) - await actor.update(updates, { render: true }) + if (this[docType].updates.length > 0) { + const updates = this[docType].updates.map(u => ActorImpacts.$computeUpdates(u, id => this.$getEmbeddedDocument(docType, id))) + await actor.updateEmbeddedDocuments(docType, updates, { render: false }) } } - $getActorItem(id) { - return this.actorToken.actor.items.get(id) + findCreatedId(docType, origId){ + return this[docType].creates.find(it => it.id = origId)?.createdId + } + + $getEmbeddedDocument(docType, id) { + return this.actorToken.actor.getEmbeddedDocument(docType, id) } static $computeUpdates(u, getSource) { + if (u.updates.length == 0 && u.deltas.length == 0) { + return {} + } const source = getSource(u.id) const instruction = { _id: u.id } u.updates.forEach(u => instruction[u[0]] = u[1]) @@ -124,5 +147,4 @@ export class ActorImpacts { target.deltas = u.deltas.map(d => [d[0], -d[1]]) return target } - } diff --git a/templates/actor/random/app-personnage-aleatoire.hbs b/templates/actor/random/app-personnage-aleatoire.hbs index 4aa71b0b..5eb6655b 100644 --- a/templates/actor/random/app-personnage-aleatoire.hbs +++ b/templates/actor/random/app-personnage-aleatoire.hbs @@ -1,12 +1,12 @@
-

Génération aléatoire pour {{actor.name}}

+

{{actor.name}}

{{#if options.isGM}} {{>"systems/foundryvtt-reve-de-dragon/templates/actor/random/champ-aleatoire.hbs" label="Nom" path="name" type="text" value=current.name checked=checked.name }} {{/if}} - {{>"systems/foundryvtt-reve-de-dragon/templates/actor/random/champ-aleatoire.hbs" + {{>"systems/foundryvtt-reve-de-dragon/templates/actor/random/sexe-aleatoire.hbs" label="Sexe" path="system.sexe" type="text" value=current.system.sexe checked=checked.system.sexe }} diff --git a/templates/actor/random/champ-aleatoire.hbs b/templates/actor/random/champ-aleatoire.hbs index 30d1b1c8..8523e73a 100644 --- a/templates/actor/random/champ-aleatoire.hbs +++ b/templates/actor/random/champ-aleatoire.hbs @@ -12,8 +12,8 @@ {{/if}}
+ -
\ No newline at end of file diff --git a/templates/actor/random/sexe-aleatoire.hbs b/templates/actor/random/sexe-aleatoire.hbs new file mode 100644 index 00000000..da5bd6ef --- /dev/null +++ b/templates/actor/random/sexe-aleatoire.hbs @@ -0,0 +1,11 @@ +
+ + +
+ + + + + +
+
\ No newline at end of file diff --git a/templates/roll/result/chat-attaque.hbs b/templates/roll/result/chat-attaque.hbs index 26c7f82f..07fac787 100644 --- a/templates/roll/result/chat-attaque.hbs +++ b/templates/roll/result/chat-attaque.hbs @@ -22,15 +22,10 @@ {{#if (eq current.dmg.mortalite 'empoignade')}} ou {{active.name}} marquera un point d'empoignade {{else if (eq current.dmg.mortalite 'non-mortel')}} - ou encaisser à {{plusMoins current.dmg.total}} (non-mortel) + ou encaisser à {{plusMoins dmg.total}} (non-mortel) {{else}} - {{!-- {{~#if (eq current.dmg.mortalite 'mortel')}} --}} - ou encaisser à {{plusMoins current.dmg.total}} - {{!-- {{~#if (eq current.dmg.mortalite 'cauchemar')}} --}} - {{!-- {{else}} - {{plusMoins dmg.total}} (entités de cauchemar) --}} + ou encaisser à {{plusMoins dmg.total}} {{/if}} - {{#if show.recul}} @@ -65,6 +60,7 @@
{{> 'partial-attaque-particuliere'}} + {{> 'partial-maladresse'}} {{!-- TODO: maladresses --}}
diff --git a/templates/roll/result/chat-cuisine.hbs b/templates/roll/result/chat-cuisine.hbs index d3c707a3..5e6e8c47 100644 --- a/templates/roll/result/chat-cuisine.hbs +++ b/templates/roll/result/chat-cuisine.hbs @@ -21,12 +21,12 @@

{{active.name}} {{#if rolled.isSuccess}} - réussit la recette, pour un plat de qualité {{result.qualite~}} - {{#if (lt result.exotisme 0)}}et d'exotisme {{result.exotisme}}{{/if~}} + réussit la recette, pour un plat de qualité {{result.qualite}} + {{#if (lt result.exotisme 0)}}et d'exotisme {{result.exotisme}}{{/if}} {{else}} - fait un piètre cuisinier, et obtient un plat de qualité {{result.qualite~}} - {{#if (lt result.exotisme 0)}}à l'exotisme certain ({{result.exotisme}}){{/if~}} - {{/if~}}. + fait un piètre cuisinier, et obtient un plat de qualité {{result.qualite}} + {{#if (lt result.exotisme 0)}}à l'exotisme certain ({{result.exotisme}}){{/if}} + {{~/if~}}. {{#if (lt result.exotisme 0)}}
Au vu de l'exotisme du plat, les convives devront réussir un jet de @roll[Volonté/Cuisine/{{result.exotisme}}]. En cas d'échec, ils peuvent se forcer à faire plaisir à {{active.name}}, mais devront faire un jet de moral Malheureux. diff --git a/templates/roll/result/chat-defense.hbs b/templates/roll/result/chat-defense.hbs index a16bee1e..dfa1cd4f 100644 --- a/templates/roll/result/chat-defense.hbs +++ b/templates/roll/result/chat-defense.hbs @@ -44,7 +44,7 @@ {{> 'partial-info-appel-moral'}}

- {{!-- TODO: maladresses --}} + {{> 'partial-maladresse'}} {{> 'partial-recul-choc'}} {{> 'partial-encaissement'}}
diff --git a/templates/roll/result/chat-meditation.hbs b/templates/roll/result/chat-meditation.hbs index 7826a90f..7fb38db2 100644 --- a/templates/roll/result/chat-meditation.hbs +++ b/templates/roll/result/chat-meditation.hbs @@ -39,6 +39,19 @@ {{/if}} +
+ {{#if rolled.isSuccess}} + {{#unless done.meditation}} + +  Montée normale dans les TMR + + +  Montée rapide dans les TMR + + {{/unless}} + {{/if}} +
+
diff --git a/templates/roll/result/chat-sort.hbs b/templates/roll/result/chat-sort.hbs index e69de29b..e862cd9d 100644 --- a/templates/roll/result/chat-sort.hbs +++ b/templates/roll/result/chat-sort.hbs @@ -0,0 +1,47 @@ +
+
+ + +
+ +
+ {{active.name}} + {{#if current.sort.isReserve}}met en réserve{{else}}lance{{/if}} + le {{#if current.sort.sort.system.isrituel}}rituel{{else}}sort{{/if}} + {{current.sort.label}} + {{#if (ne current.sort.sort.system.draconic current.comp.comp.name)}} + avec la voie de {{current.comp.comp.name}} + {{/if}} +
+ +
+ {{current.carac.label}} / {{current.comp.label}} à {{current.diff.value}} +
{{> "systems/foundryvtt-reve-de-dragon/templates/chat-infojet.hbs"}} +
+ +
+
+

+ {{#if rolled.isETotal}}Echec TOTAL + {{else if rolled.isEchec}}Echec + {{else}}Réussite{{/if}} + du {{#if current.sort.sort.system.isrituel}}rituel{{else}}sort{{/if}}, + {{#if rolled.isSuccess}} + le sort est {{#if current.sort.isReserve}}mis en réserve{{else}}lancé{{/if}} + avec {{current.sort.ptreve}} point{{~#if (gt current.sort.ptreve 1)}}s{{/if}} de rêve en {{caseTmr-label refs.sort.coord}} ({{refs.sort.coord}}). + {{/if}} +

+

+ {{#if (eq current.sort.depenseReve 0)}}Pas de dépense de rêve + {{else if (eq current.sort.depenseReve 1)}}1 point de rêve a été dépensé + {{else}}{{current.sort.depenseReve}} points de rêve ont été dépensés + {{~/if}}. + {{#if show.reveInsuffisant}}Pas assez de rêve!{{/if}} +

+ + {{> "systems/foundryvtt-reve-de-dragon/templates/chat-description.hbs" current.sort.sort.system}} +
+ +
+
+
\ No newline at end of file diff --git a/templates/roll/result/partial-maladresse.hbs b/templates/roll/result/partial-maladresse.hbs new file mode 100644 index 00000000..9f85f7e8 --- /dev/null +++ b/templates/roll/result/partial-maladresse.hbs @@ -0,0 +1,13 @@ +{{#if show.maladresse}} + {{#if type.maladresse}} + + Maladresse! + + {{else}} + + Tirer une maladresse + {{#if (eq show.maladresse 'avec-arme')}}armé{{else}}non armé{{/if}} + + {{/if}} +{{/if}} \ No newline at end of file