From 863fc65844d41e5580389c69300b2eaa8c290046 Mon Sep 17 00:00:00 2001 From: Vincent Vandemeulebrouck Date: Sat, 6 Feb 2021 21:53:25 +0100 Subject: [PATCH] =?UTF-8?q?Message=20pour=20ma=C3=AEtrise=20Fleuve=20de=20?= =?UTF-8?q?l'Oubli?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- module/grammar.js | 19 +- module/item-sort.js | 22 +- module/rdd-tmr-dialog.js | 177 ++++---- module/rdd-utility.js | 13 +- module/tmr-rencontres.js | 59 +-- module/tmr-utility.js | 614 ++++++++++++++------------ templates/chat-fleuve-tmr.html | 30 ++ templates/dialog-roll-tmr-humide.html | 28 ++ templates/item-meditation-sheet.html | 15 +- templates/item-sort-sheet.html | 1 + templates/sort-tmr.html | 4 +- 11 files changed, 551 insertions(+), 431 deletions(-) create mode 100644 templates/chat-fleuve-tmr.html create mode 100644 templates/dialog-roll-tmr-humide.html diff --git a/module/grammar.js b/module/grammar.js index a5bb7472..df4162a2 100644 --- a/module/grammar.js +++ b/module/grammar.js @@ -18,6 +18,23 @@ export class Grammar { } static toLowerCaseNoAccent(words) { - return words?.toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "") ?? words; + return words?.toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "") ?? words; + } + static articleDetermine(genre) { + switch (genre?.toLowerCase()) { + case 'f': case 'feminin': return 'la'; + case 'p': case 'pluriel': return 'les'; + default: + case 'm': case 'masculin': return 'le'; + } + } + static articleIndétermine(genre) { + switch (genre?.toLowerCase()) { + case 'f': case 'feminin': return 'une'; + case 'p': case 'pluriel': return 'des'; + case 'n': case 'neutre': return 'du' + default: + case 'm': case 'masculin': return 'un'; + } } } \ No newline at end of file diff --git a/module/item-sort.js b/module/item-sort.js index 1ebf950b..d529f879 100644 --- a/module/item-sort.js +++ b/module/item-sort.js @@ -68,13 +68,13 @@ export class RdDItemSort extends Item { let list = []; let caseCheck = {}; for(let i=0; i 0 && caseCheck[caseTMR] == undefined ) { - caseCheck[caseTMR] = bonus; - list.push( caseTMR+":"+bonus ); + if ( bonus > 0 && caseCheck[coord] == undefined ) { + caseCheck[coord] = bonus; + list.push( coord+":"+bonus ); } } } @@ -86,21 +86,21 @@ export class RdDItemSort extends Item { } /* -------------------------------------------- */ - static incrementBonusCase( actor, sort, coordTMR ) { + static incrementBonusCase( actor, sort, coord ) { let bonusCaseList = this.buildBonusCaseList(sort.data.bonuscase, false); //console.log("ITEMSORT", sort, bonusCaseList); let found = false; let StringList = []; for( let bc of bonusCaseList) { - if (bc.case == coordTMR) { // Case existante + if (bc.case == coord) { // Case existante found = true; bc.bonus = Number(bc.bonus) + 1; } StringList.push( bc.case+':'+bc.bonus ); } if ( !found) { //Nouvelle case, bonus de 1 - StringList.push(coordTMR+':1'); + StringList.push(coord+':1'); } // Sauvegarde/update @@ -110,10 +110,10 @@ export class RdDItemSort extends Item { } /* -------------------------------------------- */ - static getCaseBonus( sort, coordTMR) { + static getCaseBonus( sort, coord) { let bonusCaseList = this.buildBonusCaseList(sort.data.bonuscase, false); for( let bc of bonusCaseList) { - if (bc.case == coordTMR) { // Case existante + if (bc.case == coord) { // Case existante return Number(bc.bonus); } } diff --git a/module/rdd-tmr-dialog.js b/module/rdd-tmr-dialog.js index 1018ee18..a3b1b18e 100644 --- a/module/rdd-tmr-dialog.js +++ b/module/rdd-tmr-dialog.js @@ -4,12 +4,14 @@ */ import { RollDataAjustements } from "./rolldata-ajustements.js"; import { RdDUtility } from "./rdd-utility.js"; -import { TMRUtility } from "./tmr-utility.js"; +import { poesieCaseHumide, TMRUtility } from "./tmr-utility.js"; import { tmrConstants } from "./tmr-utility.js"; import { RdDResolutionTable } from "./rdd-resolution-table.js"; import { RdDTMRRencontreDialog } from "./rdd-tmr-rencontre-dialog.js"; import { TMRRencontres } from "./tmr-rencontres.js"; import { ChatUtility } from "./chat-utility.js"; +import { RdDRoll } from "./rdd-roll.js"; + /* -------------------------------------------- */ export class RdDTMRDialog extends Dialog { @@ -70,16 +72,16 @@ export class RdDTMRDialog extends Dialog { /* -------------------------------------------- */ displaySpecificCase() { - for (let caseTMR of this.casesSpeciales) { - console.log("SPEC CASE ", caseTMR); - if (caseTMR.data.specific == 'trounoir') { - this._trackToken(this._tokenTrouNoir(caseTMR.data.coord)); - } else if (caseTMR.data.specific == 'attache') { - this._trackToken(this._tokenTerreAttache(caseTMR.data.coord)); - } else if (caseTMR.data.specific == 'debordement') { - this._trackToken(this._tokenDebordement(caseTMR.data.coord)); - } else if (caseTMR.data.specific == 'maitrisee') { - this._trackToken(this._tokenMaitrisee(caseTMR.data.coord)); + for (let caseSpeciale of this.casesSpeciales) { + console.log("SPEC CASE ", caseSpeciale); + if (caseSpeciale.data.specific == 'trounoir') { + this._trackToken(this._tokenTrouNoir(caseSpeciale.data.coord)); + } else if (caseSpeciale.data.specific == 'attache') { + this._trackToken(this._tokenTerreAttache(caseSpeciale.data.coord)); + } else if (caseSpeciale.data.specific == 'debordement') { + this._trackToken(this._tokenDebordement(caseSpeciale.data.coord)); + } else if (caseSpeciale.data.specific == 'maitrisee') { + this._trackToken(this._tokenMaitrisee(caseSpeciale.data.coord)); } } } @@ -208,6 +210,7 @@ export class RdDTMRDialog extends Dialog { competence: this.actor.getBestDraconic(), rencontre: this.currentRencontre, nbRounds: 1, + canClose: false, tmr: TMRUtility.getTMR(this.actor.data.data.reve.tmrpos.coord) } @@ -299,19 +302,25 @@ export class RdDTMRDialog extends Dialog { /* -------------------------------------------- */ async _jetDeRencontre(tmr) { - if (TMRUtility.isForceRencontre()) { - return await TMRUtility.rencontreTMRRoll(tmr.coord, tmr); - } let rencontre = this.rencontresExistantes.find(prev => prev.coord == tmr.coord); if (rencontre) { return rencontre; } - let myRoll = new Roll("1d7").evaluate(); - if (myRoll.total == 7) { - let isMauvaise = this.actor.isRencontreSpeciale(); - return await TMRUtility.rencontreTMRRoll(tmr.coord, tmr, isMauvaise); + let myRoll = new Roll("1d7").evaluate().total; + if (TMRUtility.isForceRencontre() || myRoll== 7) { + return await this.rencontreTMRRoll(tmr, this.actor.isRencontreSpeciale()); } - this._tellToUser(myRoll.total + ": Pas de rencontre en " + tmr.label + " (" + tmr.coord + ")"); + this._tellToUser(myRoll + ": Pas de rencontre en " + tmr.label + " (" + tmr.coord + ")"); + } + + + /* -------------------------------------------- */ + async rencontreTMRRoll(tmr, isMauvaise = false) { + let rencontre = TMRUtility.utiliseForceRencontre() ?? + isMauvaise ? await TMRRencontres.getMauvaiseRencontre() + : await TMRRencontres.getRencontreAleatoire(tmr.type); + rencontre.coord = tmr.coord; + return rencontre; } /* -------------------------------------------- */ @@ -352,79 +361,27 @@ export class RdDTMRDialog extends Dialog { } } - /* -------------------------------------------- */ - isCaseMaitrisee(coordTMR) { - return this.casesSpeciales.find(it => it.data.coord = coordTMR && it.data.specific == 'maitrisee'); - } - - /* -------------------------------------------- */ - manageCaseHumideResult() { - if (this.toclose) - this.close(); - } - /* -------------------------------------------- */ async manageCaseHumide(tmr) { if (this.viewOnly || this.currentRencontre) { return; } if (this.isCaseHumide(tmr)) { - // TODO: permettre de choisir la voie de draconic? - let draconic = this.actor.getBestDraconic(); + let rollData = { + actor: this.actor, + competence: duplicate(this.actor.getBestDraconic()), + tmr: tmr, + canClose: false, + diffLibre: -7, + forceCarac: { "reveactuel": { label: "Rêve Actuel", value: this.actor.getReveActuel() } } + } + rollData.competence.data.defaut_carac = "reveactuel"; - let carac = this.actor.getReveActuel(); - const etatGeneral = this.actor.getEtatGeneral(); - let difficulte = draconic.data.niveau - 7; - let rolled = await RdDResolutionTable.roll(carac, difficulte); - - // Gestion du souffle Double Résistance du Fleuve - if (this.actor.isDoubleResistanceFleuve()) { - let rolled2 = await RdDResolutionTable.roll(carac, difficulte); - if (rolled2.isEchec) - rolled = rolled; - } - console.log("manageCaseHumide >>", rolled); - - let explication = ""; - let msg2MJ = ""; - this.toclose = rolled.isEchec; - if (rolled.isEchec) { - explication += "Vous êtes entré sur une case humide, et vous avez raté votre maîtrise ! Vous quittez les Terres Médianes !" - msg2MJ += game.user.name + " est rentré sur une case humides : Echec !"; - } - else { - explication += "Vous êtes entré sur une case humide, et vous avez réussi votre maîtrise !" - msg2MJ += game.user.name + " est rentré sur une case humides : Réussite !"; - } - explication += "
Test : Rêve actuel / " + draconic.name + " / " + tmr.type + "" - + RdDResolutionTable.explain(rolled); - - if (rolled.isETotal) { - let souffle = await this.actor.ajouterSouffle({ chat: false }); - explication += "
Vous avez fait un Echec Total. Vous subissez un Souffle de Dragon : " + souffle.name; - msg2MJ += "
Et a reçu un Souffle de Dragon : " + souffle.name; - } - if (rolled.isPart) { - explication += "
Vous avez fait une Réussite Particulière"; - this.actor._appliquerAjoutExperience({ rolled: rolled, selectedCarac: { label: 'reve' }, competence: draconic.name }) - msg2MJ += "
Et a fait une réussite particulière"; - } - - // Notification au MJ - ChatMessage.create({ content: msg2MJ, whisper: ChatMessage.getWhisperRecipients("GM") }); - // Et au joueur (ca pourrait être un message de tchat d'ailleurs) - let humideDiag = new Dialog({ - title: "Case humide", - content: explication, - buttons: { - choice: { icon: '', label: "Fermer", callback: () => this.manageCaseHumideResult() } - } - } - ); - humideDiag.render(true); + await this._rollMaitriseCaseHumide(rollData); } } + /* -------------------------------------------- */ isCaseHumide(tmr) { if (this.isCaseMaitrisee(tmr.coord)) { ChatMessage.create({ @@ -439,6 +396,60 @@ export class RdDTMRDialog extends Dialog { return tmr.type == "lac" || tmr.type == "fleuve" || tmr.type == "marais"; } + /* -------------------------------------------- */ + isCaseMaitrisee(coordTMR) { + return this.casesSpeciales.find(it => it.data.coord = coordTMR && it.data.specific == 'maitrisee'); + } + + async _rollMaitriseCaseHumide(rollData) { + this.minimize(); // Hide + const dialog = await RdDRoll.create(this.actor, rollData, + { + html: 'systems/foundryvtt-reve-de-dragon/templates/dialog-roll-tmr-humide.html', + options:{ height: 350 }, + close: html => { this.maximize(); } // Re-display TMR + }, + { + name: 'maitrise', + label: 'Maîtriser le fleuve', + callbacks: [ + this.actor.createCallbackExperience(), + { action: r => this._maitriseCaseHumide(r) } + ] + } + ); + dialog.render(true); + } + + async _maitriseCaseHumide(rollData) { + if (rollData.rolled.isETotal) { + rollData.souffle = await this.actor.ajouterSouffle({ chat: false }); + } + this.toclose = rollData.rolled.isEchec; + if (rollData.rolled.isSuccess) { + if (!rollData.previous && this.actor.isDoubleResistanceFleuve()) { + ChatMessage.create({ + content: `Double résistance du fleuve: `, + whisper: ChatUtility.getWhisperRecipientsAndGMs(game.user.name) + }); + rollData.previous = [rollData.rolled]; + await this._rollMaitriseCaseHumide(rollData); + return; + } + } + rollData.poesie = poesieCaseHumide[new Roll("1d" + poesieCaseHumide.length).evaluate().total - 1]; + const whisperTo = ChatUtility.getWhisperRecipientsAndGMs(game.user.name); + const content = await renderTemplate(`systems/foundryvtt-reve-de-dragon/templates/chat-fleuve-tmr.html`, rollData); + ChatMessage.create({ + whisper: whisperTo, + content: content + }); + if (rollData.rolled.isEchec) { + this.close(); + } + + } + /* -------------------------------------------- */ isReserveExtensible(coordTMR) { for (let caseTMR of this.casesSpeciales) { @@ -625,7 +636,7 @@ export class RdDTMRDialog extends Dialog { if (deplacementType == 'normal') { // Pas de rencontres après un saut de type passeur/changeur/... await this.manageRencontre(tmr, () => this.postRencontre(tmr)); } - else{ + else { await this.postRencontre(tmr); } } diff --git a/module/rdd-utility.js b/module/rdd-utility.js index db036798..6fdc870e 100644 --- a/module/rdd-utility.js +++ b/module/rdd-utility.js @@ -8,6 +8,7 @@ import { RdDItemCompetenceCreature } from "./item-competencecreature.js"; import { RdDItemArme } from "./item-arme.js"; import { RdDItemCompetence } from "./item-competence.js"; import { Misc } from "./misc.js"; +import { Grammar } from "./grammar.js"; /* -------------------------------------------- */ const categorieCompetences = { @@ -96,7 +97,6 @@ function _buildAllSegmentsFatigue(max) { ligneFatigue[caseIncrementee + 6]++; ligneFatigue.fatigueMax = 2 * (i + 1); fatigue[i + 1] = ligneFatigue; - } return fatigue; } @@ -253,13 +253,10 @@ export class RdDUtility { 'systems/foundryvtt-reve-de-dragon/templates/chat-actor-carac-xp.html' ]; - Handlebars.registerHelper('upperFirst', function (str) { - return Misc.upperFirst(str ?? 'null') - }) - - Handlebars.registerHelper('upper', function (str) { - return str?.toUpperCase() ?? 'NULL' - }) + Handlebars.registerHelper('upperFirst', str=> Misc.upperFirst(str ?? 'Null')); + Handlebars.registerHelper('upper', str => str?.toUpperCase() ?? 'NULL' ); + Handlebars.registerHelper('le', str => Grammar.articleDetermine(str) ); + Handlebars.registerHelper('un', str => Grammar.articleIndetermine(str) ); return loadTemplates(templatePaths); } diff --git a/module/tmr-rencontres.js b/module/tmr-rencontres.js index cab3a844..6d55b615 100644 --- a/module/tmr-rencontres.js +++ b/module/tmr-rencontres.js @@ -248,44 +248,44 @@ const typeRencontres = { /* -------------------------------------------- */ const mauvaisesRencontres = [ - { code: "mangeur1d6", name: "Mangeur de Rêve", type: "mangeur", genre: "m", force: "1d6", refoulement: 2, isMauvaise: true }, + { code: "mangeur", name: "Mangeur de Rêve", type: "mangeur", genre: "m", force: "1d6", refoulement: 2, isMauvaise: true }, { code: "mangeur2d6", name: "Mangeur de Rêve", type: "mangeur", genre: "m", force: "2d6", refoulement: 2, isMauvaise: true }, - { code: "reflet2d6+4", name: "Reflet d'ancien Rêve", type: "reflet", genre: "m", force: "2d6+4", refoulement: 2, isPersistant: true, isMauvaise: true }, - { code: "tbblanc2d6+4", name: "Tourbillon blanc", type: "tbblanc", genre: "m", force: "2d6+4", refoulement: 2, isPersistant: true, isMauvaise: true }, - { code: "tbnoir2d8+4", name: "Tourbillon noir", type: "tbnoir", genre: "m", force: "2d8+4", refoulement: 2, isPersistant: true, isMauvaise: true }, + { code: "reflet+4", name: "Reflet d'ancien Rêve", type: "reflet", genre: "m", force: "2d6+4", refoulement: 2, isPersistant: true, isMauvaise: true }, + { code: "tbblanc+4", name: "Tourbillon blanc", type: "tbblanc", genre: "m", force: "2d6+4", refoulement: 2, isPersistant: true, isMauvaise: true }, + { code: "tbnoir+4", name: "Tourbillon noir", type: "tbnoir", genre: "m", force: "2d8+4", refoulement: 2, isPersistant: true, isMauvaise: true }, { code: "passfou2d8", name: "Passeur fou", type: "passeurfou", genre: "m", force: "2d8", refoulement: 2, isMauvaise: true }, { code: "tbrouge2d8", name: "Tourbillon rouge", type: "tbrouge", genre: "m", force: "2d8", refoulement: 3, isPersistant: true, isMauvaise: true } ] /* -------------------------------------------- */ const rencontresStandard = [ - { code: "messager2d4", name: "Messager des Rêves", type: "messager", genre: "m", force: "2d4", ignorer: true }, - { code: "passeur2d4", name: "Passeur des Rêves", type: "passeur", genre: "m", force: "2d4", ignorer: true }, - { code: "fleur1d6", name: "Fleur des Rêves", type: "fleur", genre: "f", force: "1d6", ignorer: true }, - { code: "mangeur1d6", name: "Mangeur de Rêve", type: "mangeur", genre: "m", force: "1d6" }, - { code: "changeur2d6", name: "Changeur de Rêve", type: "changeur", genre: "m", force: "2d6" }, - { code: "briseur2d6", name: "Briseur de Rêve", type: "briseur", genre: "m", force: "2d6", quitterTMR: true }, - { code: "reflet1d6", name: "Reflet d'ancien Rêve", type: "reflet", genre: "m", force: "2d6", isPersistant: true }, - { code: "tbblanc2d6", name: "Tourbillon blanc", type: "tbblanc", genre: "m", force: "2d6", isPersistant: true }, - { code: "tbnoir2d8", name: "Tourbillon noir", type: "tbnoir", genre: "m", force: "2d8", isPersistant: true }, - { code: "rdd1ddr+7", name: "Rêve de Dragon", type: "rdd", genre: "m", force: "1ddr + 7", refoulement: 2, quitterTMR: true } + { code: "messager", name: "Messager des Rêves", type: "messager", genre: "m", force: "2d4", ignorer: true }, + { code: "passeur", name: "Passeur des Rêves", type: "passeur", genre: "m", force: "2d4", ignorer: true }, + { code: "fleur", name: "Fleur des Rêves", type: "fleur", genre: "f", force: "1d6", ignorer: true }, + { code: "mangeur", name: "Mangeur de Rêve", type: "mangeur", genre: "m", force: "1d6" }, + { code: "changeur", name: "Changeur de Rêve", type: "changeur", genre: "m", force: "2d6" }, + { code: "briseur", name: "Briseur de Rêve", type: "briseur", genre: "m", force: "2d6", quitterTMR: true }, + { code: "reflet", name: "Reflet d'ancien Rêve", type: "reflet", genre: "m", force: "2d6", isPersistant: true }, + { code: "tbblanc", name: "Tourbillon blanc", type: "tbblanc", genre: "m", force: "2d6", isPersistant: true }, + { code: "tbnoir", name: "Tourbillon noir", type: "tbnoir", genre: "m", force: "2d8", isPersistant: true }, + { code: "rdd", name: "Rêve de Dragon", type: "rdd", genre: "m", force: "1ddr + 7", refoulement: 2, quitterTMR: true } ]; const tableRencontres = { - cite: [{ code: 'messager2d4', range: [1, 25] }, { code: 'passeur2d4', range: [26, 50] }, { code: 'fleur1d6', range: [51, 65] }, { code: 'mangeur1d6', range: [66, 70] }, { code: 'changeur2d6', range: [71, 80] }, { code: 'briseur2d6', range: [81, 85] }, { code: 'reflet2d6', range: [86, 90] }, { code: 'tbblanc2d6', range: [91, 94] }, { code: 'tbnoir2d8', range: [95, 97] }, { code: 'rdd1ddr+7', range: [98, 100] }], - sanctuaire: [{ code: 'messager2d4', range: [1, 25] }, { code: 'passeur2d4', range: [26, 50] }, { code: 'fleur1d6', range: [51, 65] }, { code: 'mangeur1d6', range: [66, 70] }, { code: 'changeur2d6', range: [71, 80] }, { code: 'briseur2d6', range: [81, 85] }, { code: 'reflet2d6', range: [86, 90] }, { code: 'tbblanc2d6', range: [91, 94] }, { code: 'tbnoir2d8', range: [95, 97] }, { code: 'rdd1ddr+7', range: [98, 100] }], - plaines: [{ code: 'messager2d4', range: [1, 20] }, { code: 'passeur2d4', range: [21, 40] }, { code: 'fleur1d6', range: [41, 55] }, { code: 'mangeur1d6', range: [56, 60] }, { code: 'changeur2d6', range: [61, 75] }, { code: 'briseur2d6', range: [76, 82] }, { code: 'reflet2d6', range: [83, 88] }, { code: 'tbblanc2d6', range: [89, 93] }, { code: 'tbnoir2d8', range: [94, 97] }, { code: 'rdd1ddr+7', range: [98, 100] }], - pont: [{ code: 'messager2d4', range: [1, 20] }, { code: 'passeur2d4', range: [21, 40] }, { code: 'fleur1d6', range: [41, 55] }, { code: 'mangeur1d6', range: [56, 60] }, { code: 'changeur2d6', range: [61, 75] }, { code: 'briseur2d6', range: [76, 82] }, { code: 'reflet2d6', range: [83, 88] }, { code: 'tbblanc2d6', range: [89, 93] }, { code: 'tbnoir2d8', range: [94, 97] }, { code: 'rdd1ddr+7', range: [98, 100] }], - collines: [{ code: 'messager2d4', range: [1, 15] }, { code: 'passeur2d4', range: [16, 30] }, { code: 'fleur1d6', range: [31, 42] }, { code: 'mangeur1d6', range: [43, 54] }, { code: 'changeur2d6', range: [55, 69] }, { code: 'briseur2d6', range: [70, 82] }, { code: 'reflet2d6', range: [83, 88] }, { code: 'tbblanc2d6', range: [89, 93] }, { code: 'tbnoir2d8', range: [94, 97] }, { code: 'rdd1ddr+7', range: [98, 100] }], - foret: [{ code: 'messager2d4', range: [1, 15] }, { code: 'passeur2d4', range: [16, 30] }, { code: 'fleur1d6', range: [31, 42] }, { code: 'mangeur1d6', range: [43, 54] }, { code: 'changeur2d6', range: [55, 69] }, { code: 'briseur2d6', range: [70, 82] }, { code: 'reflet2d6', range: [83, 88] }, { code: 'tbblanc2d6', range: [89, 93] }, { code: 'tbnoir2d8', range: [94, 97] }, { code: 'rdd1ddr+7', range: [98, 100] }], - monts: [{ code: 'messager2d4', range: [1, 10] }, { code: 'passeur2d4', range: [11, 20] }, { code: 'fleur1d6', range: [21, 26] }, { code: 'mangeur1d6', range: [27, 44] }, { code: 'changeur2d6', range: [45, 59] }, { code: 'briseur2d6', range: [60, 75] }, { code: 'reflet2d6', range: [76, 85] }, { code: 'tbblanc2d6', range: [86, 92] }, { code: 'tbnoir2d8', range: [93, 97] }, { code: 'rdd1ddr+7', range: [98, 100] }], - desert: [{ code: 'messager2d4', range: [1, 10] }, { code: 'passeur2d4', range: [11, 20] }, { code: 'fleur1d6', range: [21, 26] }, { code: 'mangeur1d6', range: [27, 44] }, { code: 'changeur2d6', range: [45, 59] }, { code: 'briseur2d6', range: [60, 75] }, { code: 'reflet2d6', range: [76, 85] }, { code: 'tbblanc2d6', range: [86, 92] }, { code: 'tbnoir2d8', range: [93, 97] }, { code: 'rdd1ddr+7', range: [98, 100] }], - fleuve: [{ code: 'messager2d4', range: [1, 5] }, { code: 'passeur2d4', range: [6, 10] }, { code: 'fleur1d6', range: [11, 13] }, { code: 'mangeur1d6', range: [14, 37] }, { code: 'changeur2d6', range: [38, 49] }, { code: 'briseur2d6', range: [50, 65] }, { code: 'reflet2d6', range: [66, 79] }, { code: 'tbblanc2d6', range: [80, 89] }, { code: 'tbnoir2d8', range: [90, 97] }, { code: 'rdd1ddr+7', range: [98, 100] }], - lac: [{ code: 'messager2d4', range: [1, 5] }, { code: 'passeur2d4', range: [6, 10] }, { code: 'fleur1d6', range: [11, 13] }, { code: 'mangeur1d6', range: [14, 37] }, { code: 'changeur2d6', range: [38, 49] }, { code: 'briseur2d6', range: [50, 65] }, { code: 'reflet2d6', range: [66, 79] }, { code: 'tbblanc2d6', range: [80, 89] }, { code: 'tbnoir2d8', range: [90, 97] }, { code: 'rdd1ddr+7', range: [98, 100] }], - marais: [{ code: 'messager2d4', range: [1, 2] }, { code: 'passeur2d4', range: [3, 4] }, { code: 'fleur1d6', range: [5, 5] }, { code: 'mangeur1d6', range: [6, 29] }, { code: 'changeur2d6', range: [30, 39] }, { code: 'briseur2d6', range: [40, 60] }, { code: 'reflet2d6', range: [61, 75] }, { code: 'tbblanc2d6', range: [76, 86] }, { code: 'tbnoir2d8', range: [87, 97] }, { code: 'rdd1ddr+7', range: [98, 100] }], - gouffre: [{ code: 'messager2d4', range: [1, 2] }, { code: 'passeur2d4', range: [3, 4] }, { code: 'fleur1d6', range: [5, 5] }, { code: 'mangeur1d6', range: [6, 29] }, { code: 'changeur2d6', range: [30, 39] }, { code: 'briseur2d6', range: [40, 60] }, { code: 'reflet2d6', range: [61, 75] }, { code: 'tbblanc2d6', range: [76, 86] }, { code: 'tbnoir2d8', range: [87, 97] }, { code: 'rdd1ddr+7', range: [98, 100] }], - necropole: [{ code: 'mangeur1d6', range: [1, 20] }, { code: 'changeur2d6', range: [21, 30] }, { code: 'briseur2d6', range: [31, 50] }, { code: 'reflet2d6', range: [51, 65] }, { code: 'tbblanc2d6', range: [66, 80] }, { code: 'tbnoir2d8', range: [81, 97] }, { code: 'rdd1ddr+7', range: [98, 100] }], - desolation: [{ code: 'mangeur1d6', range: [1, 20] }, { code: 'changeur2d6', range: [21, 30] }, { code: 'briseur2d6', range: [31, 50] }, { code: 'reflet2d6', range: [51, 65] }, { code: 'tbblanc2d6', range: [66, 80] }, { code: 'tbnoir2d8', range: [81, 97] }, { code: 'rdd1ddr+7', range: [98, 100] }] + cite: [{ code: 'messager', range: [1, 25] }, { code: 'passeur', range: [26, 50] }, { code: 'fleur', range: [51, 65] }, { code: 'mangeur', range: [66, 70] }, { code: 'changeur', range: [71, 80] }, { code: 'briseur', range: [81, 85] }, { code: 'reflet', range: [86, 90] }, { code: 'tbblanc', range: [91, 94] }, { code: 'tbnoir', range: [95, 97] }, { code: 'rdd', range: [98, 100] }], + sanctuaire: [{ code: 'messager', range: [1, 25] }, { code: 'passeur', range: [26, 50] }, { code: 'fleur', range: [51, 65] }, { code: 'mangeur', range: [66, 70] }, { code: 'changeur', range: [71, 80] }, { code: 'briseur', range: [81, 85] }, { code: 'reflet', range: [86, 90] }, { code: 'tbblanc', range: [91, 94] }, { code: 'tbnoir', range: [95, 97] }, { code: 'rdd', range: [98, 100] }], + plaines: [{ code: 'messager', range: [1, 20] }, { code: 'passeur', range: [21, 40] }, { code: 'fleur', range: [41, 55] }, { code: 'mangeur', range: [56, 60] }, { code: 'changeur', range: [61, 75] }, { code: 'briseur', range: [76, 82] }, { code: 'reflet', range: [83, 88] }, { code: 'tbblanc', range: [89, 93] }, { code: 'tbnoir', range: [94, 97] }, { code: 'rdd', range: [98, 100] }], + pont: [{ code: 'messager', range: [1, 20] }, { code: 'passeur', range: [21, 40] }, { code: 'fleur', range: [41, 55] }, { code: 'mangeur', range: [56, 60] }, { code: 'changeur', range: [61, 75] }, { code: 'briseur', range: [76, 82] }, { code: 'reflet', range: [83, 88] }, { code: 'tbblanc', range: [89, 93] }, { code: 'tbnoir', range: [94, 97] }, { code: 'rdd', range: [98, 100] }], + collines: [{ code: 'messager', range: [1, 15] }, { code: 'passeur', range: [16, 30] }, { code: 'fleur', range: [31, 42] }, { code: 'mangeur', range: [43, 54] }, { code: 'changeur', range: [55, 69] }, { code: 'briseur', range: [70, 82] }, { code: 'reflet', range: [83, 88] }, { code: 'tbblanc', range: [89, 93] }, { code: 'tbnoir', range: [94, 97] }, { code: 'rdd', range: [98, 100] }], + foret: [{ code: 'messager', range: [1, 15] }, { code: 'passeur', range: [16, 30] }, { code: 'fleur', range: [31, 42] }, { code: 'mangeur', range: [43, 54] }, { code: 'changeur', range: [55, 69] }, { code: 'briseur', range: [70, 82] }, { code: 'reflet', range: [83, 88] }, { code: 'tbblanc', range: [89, 93] }, { code: 'tbnoir', range: [94, 97] }, { code: 'rdd', range: [98, 100] }], + monts: [{ code: 'messager', range: [1, 10] }, { code: 'passeur', range: [11, 20] }, { code: 'fleur', range: [21, 26] }, { code: 'mangeur', range: [27, 44] }, { code: 'changeur', range: [45, 59] }, { code: 'briseur', range: [60, 75] }, { code: 'reflet', range: [76, 85] }, { code: 'tbblanc', range: [86, 92] }, { code: 'tbnoir', range: [93, 97] }, { code: 'rdd', range: [98, 100] }], + desert: [{ code: 'messager', range: [1, 10] }, { code: 'passeur', range: [11, 20] }, { code: 'fleur', range: [21, 26] }, { code: 'mangeur', range: [27, 44] }, { code: 'changeur', range: [45, 59] }, { code: 'briseur', range: [60, 75] }, { code: 'reflet', range: [76, 85] }, { code: 'tbblanc', range: [86, 92] }, { code: 'tbnoir', range: [93, 97] }, { code: 'rdd', range: [98, 100] }], + fleuve: [{ code: 'messager', range: [1, 5] }, { code: 'passeur', range: [6, 10] }, { code: 'fleur', range: [11, 13] }, { code: 'mangeur', range: [14, 37] }, { code: 'changeur', range: [38, 49] }, { code: 'briseur', range: [50, 65] }, { code: 'reflet', range: [66, 79] }, { code: 'tbblanc', range: [80, 89] }, { code: 'tbnoir', range: [90, 97] }, { code: 'rdd', range: [98, 100] }], + lac: [{ code: 'messager', range: [1, 5] }, { code: 'passeur', range: [6, 10] }, { code: 'fleur', range: [11, 13] }, { code: 'mangeur', range: [14, 37] }, { code: 'changeur', range: [38, 49] }, { code: 'briseur', range: [50, 65] }, { code: 'reflet', range: [66, 79] }, { code: 'tbblanc', range: [80, 89] }, { code: 'tbnoir', range: [90, 97] }, { code: 'rdd', range: [98, 100] }], + marais: [{ code: 'messager', range: [1, 2] }, { code: 'passeur', range: [3, 4] }, { code: 'fleur', range: [5, 5] }, { code: 'mangeur', range: [6, 29] }, { code: 'changeur', range: [30, 39] }, { code: 'briseur', range: [40, 60] }, { code: 'reflet', range: [61, 75] }, { code: 'tbblanc', range: [76, 86] }, { code: 'tbnoir', range: [87, 97] }, { code: 'rdd', range: [98, 100] }], + gouffre: [{ code: 'messager', range: [1, 2] }, { code: 'passeur', range: [3, 4] }, { code: 'fleur', range: [5, 5] }, { code: 'mangeur', range: [6, 29] }, { code: 'changeur', range: [30, 39] }, { code: 'briseur', range: [40, 60] }, { code: 'reflet', range: [61, 75] }, { code: 'tbblanc', range: [76, 86] }, { code: 'tbnoir', range: [87, 97] }, { code: 'rdd', range: [98, 100] }], + necropole: [{ code: 'mangeur', range: [1, 20] }, { code: 'changeur', range: [21, 30] }, { code: 'briseur', range: [31, 50] }, { code: 'reflet', range: [51, 65] }, { code: 'tbblanc', range: [66, 80] }, { code: 'tbnoir', range: [81, 97] }, { code: 'rdd', range: [98, 100] }], + desolation: [{ code: 'mangeur', range: [1, 20] }, { code: 'changeur', range: [21, 30] }, { code: 'briseur', range: [31, 50] }, { code: 'reflet', range: [51, 65] }, { code: 'tbblanc', range: [66, 80] }, { code: 'tbnoir', range: [81, 97] }, { code: 'rdd', range: [98, 100] }] } @@ -378,7 +378,8 @@ export class TMRRencontres { /* -------------------------------------------- */ static async evaluerForceRencontre(rencontre) { if (TMRRencontres.isReveDeDragon(rencontre)) { - rencontre.force = await DeDraconique.ddr("selfroll").total + 7; + const ddr = await DeDraconique.ddr("selfroll") + rencontre.force = 7 + ddr.total; } else { rencontre.force = new Roll(rencontre.force).evaluate().total; diff --git a/module/tmr-utility.js b/module/tmr-utility.js index 4cedfaa7..34c32967 100644 --- a/module/tmr-utility.js +++ b/module/tmr-utility.js @@ -4,241 +4,303 @@ import { Grammar } from "./grammar.js"; import { Misc } from "./misc.js"; /* -------------------------------------------- */ -const TMRMapping = { - A1: { type: "cite", label: "Cité Vide"}, - B1: { type: "plaines", label: "Plaines d’Assorh"}, - C1: { type: "necropole", label: "Nécropole de Kroak"}, - D1: { type: "fleuve", label: "Fleuve"}, - E1: { type: "monts", label: "Monts de Kanaï"}, - F1: { type: "cite", label: "Cité Glauque"}, - G1: { type: "desolation", label: "Désolation de Demain"}, - H1: { type: "lac", label: "Lac d’Anticalme"}, - I1: { type: "plaines", label: "Plaines Grises"}, - J1: { type: "monts", label: "Monts Fainéants"}, - K1: { type: "cite", label: "Cité d’Onkause"}, - L1: { type: "fleuve", label: "Fleuve"}, - M1: { type: "cite", label: "Cité Jalouse"}, - - A2: { type: "desert", label: "Désert de Mieux"}, - B2: { type: "collines", label: "Collines de Dawell"}, - C2: { type: "marais", label: "Marais Glignants"}, - D2: { type: "cite", label: "Cité de Frost"}, - E2: { type: "plaines", label: "Plaines de Fiask"}, - F2: { type: "lac", label: "Lac de Misère"}, - G2: { type: "marais", label: "Marais Nuisants"}, - H2: { type: "collines", label: "Collines de Parta"}, - I2: { type: "foret", label: "Forêt Fade"}, - J2: { type: "desert", label: "Désert de Poly"}, - K2: { type: "foret", label: "Forêt Tamée"}, - L2: { type: "fleuve", label: "Fleuve"}, - M2: { type: "necropole", label: "Nécropole de Logos"}, +const TMRMapping = { + A1: { type: "cite", label: "Cité Vide" }, + B1: { type: "plaines", label: "Plaines d’Assorh" }, + C1: { type: "necropole", label: "Nécropole de Kroak" }, + D1: { type: "fleuve", label: "Fleuve de l'Oubli" }, + E1: { type: "monts", label: "Monts de Kanaï" }, + F1: { type: "cite", label: "Cité Glauque" }, + G1: { type: "desolation", label: "Désolation de Demain" }, + H1: { type: "lac", label: "Lac d’Anticalme" }, + I1: { type: "plaines", label: "Plaines Grises" }, + J1: { type: "monts", label: "Monts Fainéants" }, + K1: { type: "cite", label: "Cité d’Onkause" }, + L1: { type: "fleuve", label: "Fleuve de l'Oubli" }, + M1: { type: "cite", label: "Cité Jalouse" }, - A3: { type: "desolation", label: "Désolation de Demain"}, - B3: { type: "plaines", label: "Plaines de Rubéga"}, - C3: { type: "fleuve", label: "Fleuve"}, - D3: { type: "gouffre", label: "Gouffre d’Oki"}, - E3: { type: "foret", label: "Forêt d’Estoubh"}, - F3: { type: "fleuve", label: "Fleuve"}, - G3: { type: "gouffre", label: "Gouffre de Sun"}, - H3: { type: "foret", label: "Forêt de Ganna"}, - I3: { type: "monts", label: "Monts Grinçants"}, - J3: { type: "cite", label: "Cité Venin"}, - K3: { type: "plaines", label: "Plaines de Dois"}, - L3: { type: "lac", label: "Lac Laineux"}, - M3: { type: "monts", label: "Monts de Vdah"}, + A2: { type: "desert", label: "Désert de Mieux" }, + B2: { type: "collines", label: "Collines de Dawell" }, + C2: { type: "marais", label: "Marais Glignants" }, + D2: { type: "cite", label: "Cité de Frost" }, + E2: { type: "plaines", label: "Plaines de Fiask" }, + F2: { type: "lac", label: "Lac de Misère" }, + G2: { type: "marais", label: "Marais Nuisants" }, + H2: { type: "collines", label: "Collines de Parta" }, + I2: { type: "foret", label: "Forêt Fade" }, + J2: { type: "desert", label: "Désert de Poly" }, + K2: { type: "foret", label: "Forêt Tamée" }, + L2: { type: "fleuve", label: "Fleuve de l'Oubli" }, + M2: { type: "necropole", label: "Nécropole de Logos" }, - A4: { type: "foret", label: "Forêt de Falconax"}, - B4: { type: "monts", label: "Monts Crâneurs"}, - C4: { type: "pont", label: "Pont de Giolii"}, - D4: { type: "lac", label: "Lac de Foam"}, - E4: { type: "plaines", label: "Plaines d’Orti"}, - F4: { type: "fleuve", label: "Fleuve"}, - G4: { type: "sanctuaire", label: "Sanctuaire Blanc"}, - H4: { type: "plaines", label: "Plaines de Psark"}, - I4: { type: "plaines", label: "Plaines de Xiax"}, - J4: { type: "collines", label: "Collines d’Encre"}, - K4: { type: "pont", label: "Pont de Fah"}, - L4: { type: "sanctuaire", label: "Sanctuaire Mauve"}, - M4: { type: "gouffre", label: "Gouffre Grisant"}, + A3: { type: "desolation", label: "Désolation de Demain" }, + B3: { type: "plaines", label: "Plaines de Rubéga" }, + C3: { type: "fleuve", label: "Fleuve de l'Oubli" }, + D3: { type: "gouffre", label: "Gouffre d’Oki" }, + E3: { type: "foret", label: "Forêt d’Estoubh" }, + F3: { type: "fleuve", label: "Fleuve de l'Oubli" }, + G3: { type: "gouffre", label: "Gouffre de Sun" }, + H3: { type: "foret", label: "Forêt de Ganna" }, + I3: { type: "monts", label: "Monts Grinçants" }, + J3: { type: "cite", label: "Cité Venin" }, + K3: { type: "plaines", label: "Plaines de Dois" }, + L3: { type: "lac", label: "Lac Laineux" }, + M3: { type: "monts", label: "Monts de Vdah" }, - A5: { type: "plaines", label: "Plaines de Trilkh"}, - B5: { type: "collines", label: "Collines de Tanegy"}, - C5: { type: "marais", label: "Marais Flouants"}, - D5: { type: "fleuve", label: "Fleuve"}, - E5: { type: "monts", label: "Monts Brûlants"}, - F5: { type: "cite", label: "Cité de Panople"}, - G5: { type: "pont", label: "Pont d’Ik"}, - H5: { type: "desert", label: "Désert de Krane"}, - I5: { type: "desolation", label: "Désolation de Demain"}, - J5: { type: "marais", label: "Marais de Jab"}, - K5: { type: "fleuve", label: "Fleuve"}, - L5: { type: "collines", label: "Collines Suaves"}, - M5: { type: "cite", label: "Cité Rimarde"}, + A4: { type: "foret", label: "Forêt de Falconax" }, + B4: { type: "monts", label: "Monts Crâneurs" }, + C4: { type: "pont", label: "Pont de Giolii" }, + D4: { type: "lac", label: "Lac de Foam" }, + E4: { type: "plaines", label: "Plaines d’Orti" }, + F4: { type: "fleuve", label: "Fleuve de l'Oubli" }, + G4: { type: "sanctuaire", label: "Sanctuaire Blanc" }, + H4: { type: "plaines", label: "Plaines de Psark" }, + I4: { type: "plaines", label: "Plaines de Xiax" }, + J4: { type: "collines", label: "Collines d’Encre" }, + K4: { type: "pont", label: "Pont de Fah" }, + L4: { type: "sanctuaire", label: "Sanctuaire Mauve" }, + M4: { type: "gouffre", label: "Gouffre Grisant" }, - A6: { type: "necropole", label: "Nécropole de Zniak"}, - B6: { type: "foret", label: "Forêt de Bust"}, - C6: { type: "cite", label: "Cité Pavois"}, - D6: { type: "fleuve", label: "Fleuve"}, - E6: { type: "sanctuaire", label: "Sanctuaire de Plaine"}, - F6: { type: "fleuve", label: "Fleuve"}, - G6: { type: "marais", label: "Marais Glutants"}, - H6: { type: "monts", label: "Monts Gurdes"}, - I6: { type: "necropole", label: "Nécropole de Xotar"}, - J6: { type: "lac", label: "Lac d’Iaupe"}, - K6: { type: "desolation", label: "Désolation de Demain"}, - L6: { type: "foret", label: "Forêt Gueuse"}, - M6: { type: "desolation", label: "Désolation de Demain"}, + A5: { type: "plaines", label: "Plaines de Trilkh" }, + B5: { type: "collines", label: "Collines de Tanegy" }, + C5: { type: "marais", label: "Marais Flouants" }, + D5: { type: "fleuve", label: "Fleuve de l'Oubli" }, + E5: { type: "monts", label: "Monts Brûlants" }, + F5: { type: "cite", label: "Cité de Panople" }, + G5: { type: "pont", label: "Pont d’Ik" }, + H5: { type: "desert", label: "Désert de Krane" }, + I5: { type: "desolation", label: "Désolation de Demain" }, + J5: { type: "marais", label: "Marais de Jab" }, + K5: { type: "fleuve", label: "Fleuve de l'Oubli" }, + L5: { type: "collines", label: "Collines Suaves" }, + M5: { type: "cite", label: "Cité Rimarde" }, - A7: { type: "plaines", label: "Plaines de l’Arc"}, - B7: { type: "marais", label: "Marais Bluants"}, - C7: { type: "fleuve", label: "Fleuve"}, - D7: { type: "plaines", label: "Plaines d’A!a"}, - E7: { type: "foret", label: "Forêt de Glusks"}, - F7: { type: "fleuve", label: "Fleuve"}, - G7: { type: "cite", label: "Cité de Terwa"}, - H7: { type: "gouffre", label: "Gouffre de Kapfa"}, - I7: { type: "plaines", label: "Plaines de Troo"}, - J7: { type: "fleuve", label: "Fleuve"}, - K7: { type: "cite", label: "Cité de Kolix"}, - L7: { type: "gouffre", label: "Gouffre d’Episophe"}, - M7: { type: "desert", label: "Désert de Lave"}, + A6: { type: "necropole", label: "Nécropole de Zniak" }, + B6: { type: "foret", label: "Forêt de Bust" }, + C6: { type: "cite", label: "Cité Pavois" }, + D6: { type: "fleuve", label: "Fleuve de l'Oubli" }, + E6: { type: "sanctuaire", label: "Sanctuaire de Plaine" }, + F6: { type: "fleuve", label: "Fleuve de l'Oubli" }, + G6: { type: "marais", label: "Marais Glutants" }, + H6: { type: "monts", label: "Monts Gurdes" }, + I6: { type: "necropole", label: "Nécropole de Xotar" }, + J6: { type: "lac", label: "Lac d’Iaupe" }, + K6: { type: "desolation", label: "Désolation de Demain" }, + L6: { type: "foret", label: "Forêt Gueuse" }, + M6: { type: "desolation", label: "Désolation de Demain" }, - A8: { type: "gouffre", label: "Gouffre de Shok"}, - B8: { type: "fleuve", label: "Fleuve"}, - C8: { type: "foret", label: "Forêt Turmide"}, - D8: { type: "cite", label: "Cité d’Olak"}, - E8: { type: "plaines", label: "Plaines d’Iolise"}, - F8: { type: "lac", label: "Lac des Chats"}, - G8: { type: "plaines", label: "Plaines Sans Joie"}, - H8: { type: "foret", label: "Forêt d’Ourf"}, - I8: { type: "fleuve", label: "Fleuve"}, - J8: { type: "monts", label: "Monts Barask"}, - K8: { type: "desert", label: "Désert de Fumée"}, - L8: { type: "monts", label: "Monts Tavelés"}, - M8: { type: "plaines", label: "Plaines Lavées"}, + A7: { type: "plaines", label: "Plaines de l’Arc" }, + B7: { type: "marais", label: "Marais Bluants" }, + C7: { type: "fleuve", label: "Fleuve de l'Oubli" }, + D7: { type: "plaines", label: "Plaines d’A!a" }, + E7: { type: "foret", label: "Forêt de Glusks" }, + F7: { type: "fleuve", label: "Fleuve de l'Oubli" }, + G7: { type: "cite", label: "Cité de Terwa" }, + H7: { type: "gouffre", label: "Gouffre de Kapfa" }, + I7: { type: "plaines", label: "Plaines de Troo" }, + J7: { type: "fleuve", label: "Fleuve de l'Oubli" }, + K7: { type: "cite", label: "Cité de Kolix" }, + L7: { type: "gouffre", label: "Gouffre d’Episophe" }, + M7: { type: "desert", label: "Désert de Lave" }, - A9: { type: "collines", label: "Collines de Korrex"}, - B9: { type: "lac", label: "Lac de Lucre"}, - C9: { type: "monts", label: "Monts Tuméfiés"}, - D9: { type: "pont", label: "Pont d’Orx"}, - E9: { type: "fleuve", label: "Fleuve"}, - F9: { type: "plaines", label: "Plaines de Foe"}, - G9: { type: "desolation", label: "Désolation de Demain"}, - H9: { type: "collines", label: "Collines de Noirseul"}, - I9: { type: "fleuve", label: "Fleuve"}, - J9: { type: "marais", label: "Marais Gronchants"}, - K9: { type: "sanctuaire", label: "Sanctuaire Noir"}, - L9: { type: "collines", label: "Collines Cornues"}, - M9: { type: "necropole", label: "Nécropole de Zonar"}, + A8: { type: "gouffre", label: "Gouffre de Shok" }, + B8: { type: "fleuve", label: "Fleuve de l'Oubli" }, + C8: { type: "foret", label: "Forêt Turmide" }, + D8: { type: "cite", label: "Cité d’Olak" }, + E8: { type: "plaines", label: "Plaines d’Iolise" }, + F8: { type: "lac", label: "Lac des Chats" }, + G8: { type: "plaines", label: "Plaines Sans Joie" }, + H8: { type: "foret", label: "Forêt d’Ourf" }, + I8: { type: "fleuve", label: "Fleuve de l'Oubli" }, + J8: { type: "monts", label: "Monts Barask" }, + K8: { type: "desert", label: "Désert de Fumée" }, + L8: { type: "monts", label: "Monts Tavelés" }, + M8: { type: "plaines", label: "Plaines Lavées" }, - A10: { type: "sanctuaire", label: "Sanctuaire d’Olis"}, - B10: { type: "monts", label: "Monts Salés"}, - C10: { type: "marais", label: "Marais de Dom"}, - D10: { type: "fleuve", label: "Fleuve"}, - E10: { type: "gouffre", label: "Gouffre de Junk"}, - F10: { type: "marais", label: "Marais Zultants"}, - G10: { type: "cite", label: "Cité de Sergal"}, - H10: { type: "plaines", label: "Plaines Noires"}, - I10: { type: "lac", label: "Lac Wanito"}, - J10: { type: "fleuve", label: "Fleuve"}, - K10: { type: "plaines", label: "Plaines Jaunes"}, - L10: { type: "desert", label: "Désert de Nicrop"}, - M10: { type: "foret", label: "Forêt de Jajou"}, + A9: { type: "collines", label: "Collines de Korrex" }, + B9: { type: "lac", label: "Lac de Lucre" }, + C9: { type: "monts", label: "Monts Tuméfiés" }, + D9: { type: "pont", label: "Pont d’Orx" }, + E9: { type: "fleuve", label: "Fleuve de l'Oubli" }, + F9: { type: "plaines", label: "Plaines de Foe" }, + G9: { type: "desolation", label: "Désolation de Demain" }, + H9: { type: "collines", label: "Collines de Noirseul" }, + I9: { type: "fleuve", label: "Fleuve de l'Oubli" }, + J9: { type: "marais", label: "Marais Gronchants" }, + K9: { type: "sanctuaire", label: "Sanctuaire Noir" }, + L9: { type: "collines", label: "Collines Cornues" }, + M9: { type: "necropole", label: "Nécropole de Zonar" }, - A11: { type: "desolation", label: "Désolation de Demain"}, - B11: { type: "cite", label: "Cité de Brilz"}, - C11: { type: "pont", label: "Pont de Roï"}, - D11: { type: "desolation", label: "Désolation de Demain"}, - E11: { type: "lac", label: "Lac de Glinster"}, - F11: { type: "cite", label: "Cité de Noape"}, - G11: { type: "fleuve", label: "Fleuve"}, - H11: { type: "fleuve", label: "Fleuve"}, - I11: { type: "pont", label: "Pont de Yalm"}, - J11: { type: "plaines", label: "Plaines de Miltiar"}, - K11: { type: "cite", label: "Cité Tonnerre"}, - L11: { type: "collines", label: "Collines de Kol"}, - M11: { type: "cite", label: "Cité Crapaud"}, + A10: { type: "sanctuaire", label: "Sanctuaire d’Olis" }, + B10: { type: "monts", label: "Monts Salés" }, + C10: { type: "marais", label: "Marais de Dom" }, + D10: { type: "fleuve", label: "Fleuve de l'Oubli" }, + E10: { type: "gouffre", label: "Gouffre de Junk" }, + F10: { type: "marais", label: "Marais Zultants" }, + G10: { type: "cite", label: "Cité de Sergal" }, + H10: { type: "plaines", label: "Plaines Noires" }, + I10: { type: "lac", label: "Lac Wanito" }, + J10: { type: "fleuve", label: "Fleuve de l'Oubli" }, + K10: { type: "plaines", label: "Plaines Jaunes" }, + L10: { type: "desert", label: "Désert de Nicrop" }, + M10: { type: "foret", label: "Forêt de Jajou" }, - A12: { type: "plaines", label: "Plaines Sages"}, - B12: { type: "fleuve", label: "Fleuve"}, - C12: { type: "lac", label: "Lac de Fricassa"}, - D12: { type: "collines", label: "Collines d’Huaï"}, - E12: { type: "monts", label: "Monts Ajourés"}, - F12: { type: "necropole", label: "Nécropole de Troat"}, - G12: { type: "plaines", label: "Plaines de Lufmil"}, - H12: { type: "collines", label: "Collines de Tooth"}, - I12: { type: "gouffre", label: "Gouffre Abimeux"}, - J12: { type: "cite", label: "Cité Folle"}, - K12: { type: "desolation", label: "Désolation de Demain"}, - L12: { type: "plaines", label: "Plaines Venteuses"}, - M12: { type: "collines", label: "Collines Révulsantes"}, + A11: { type: "desolation", label: "Désolation de Demain" }, + B11: { type: "cite", label: "Cité de Brilz" }, + C11: { type: "pont", label: "Pont de Roï" }, + D11: { type: "desolation", label: "Désolation de Demain" }, + E11: { type: "lac", label: "Lac de Glinster" }, + F11: { type: "cite", label: "Cité de Noape" }, + G11: { type: "fleuve", label: "Fleuve de l'Oubli" }, + H11: { type: "fleuve", label: "Fleuve de l'Oubli" }, + I11: { type: "pont", label: "Pont de Yalm" }, + J11: { type: "plaines", label: "Plaines de Miltiar" }, + K11: { type: "cite", label: "Cité Tonnerre" }, + L11: { type: "collines", label: "Collines de Kol" }, + M11: { type: "cite", label: "Cité Crapaud" }, - A13: { type: "fleuve", label: "Fleuve"}, - B13: { type: "gouffre", label: "Gouffre des Litiges"}, - C13: { type: "desert", label: "Désert de Neige"}, - D13: { type: "cite", label: "Cité Sordide"}, - E13: { type: "plaines", label: "Plaines de Xnez"}, - F13: { type: "foret", label: "Forêt des Cris"}, - G13: { type: "plaines", label: "Plaines Calcaires"}, - H13: { type: "desolation", label: "Désolation de Demain"}, - I13: { type: "monts", label: "Monts Bigleux"}, - J13: { type: "gouffre", label: "Gouffre de Gromph"}, - K13: { type: "foret", label: "Forêt de Kluth"}, - L13: { type: "monts", label: "Monts Dormants"}, - M13: { type: "plaines", label: "Plaines d’Anjou"}, + A12: { type: "plaines", label: "Plaines Sages" }, + B12: { type: "fleuve", label: "Fleuve de l'Oubli" }, + C12: { type: "lac", label: "Lac de Fricassa" }, + D12: { type: "collines", label: "Collines d’Huaï" }, + E12: { type: "monts", label: "Monts Ajourés" }, + F12: { type: "necropole", label: "Nécropole de Troat" }, + G12: { type: "plaines", label: "Plaines de Lufmil" }, + H12: { type: "collines", label: "Collines de Tooth" }, + I12: { type: "gouffre", label: "Gouffre Abimeux" }, + J12: { type: "cite", label: "Cité Folle" }, + K12: { type: "desolation", label: "Désolation de Demain" }, + L12: { type: "plaines", label: "Plaines Venteuses" }, + M12: { type: "collines", label: "Collines Révulsantes" }, - A14: { type: "collines", label: "Collines de Stolis"}, - B14: { type: "necropole", label: "Nécropole de Gorlo"}, - C14: { type: "foret", label: "Forêt de Bissam"}, - D14: { type: "sanctuaire", label: "Sanctuaire Plat"}, - E14: { type: "monts", label: "Monts de Quath"}, - F14: { type: "plaines", label: "Plaines Brisées"}, - G14: { type: "desert", label: "Désert de Sek"}, - H14: { type: "plaines", label: "Plaines Blanches"}, - I14: { type: "cite", label: "Cité Destituée"}, - J14: { type: "desert", label: "Désert de Sank"}, - K14: { type: "necropole", label: "Nécropole d’Antinéar"}, - L14: { type: "plaines", label: "Plaines de Jislith"}, - M14: { type: "desolation", label: "Désolation de Demain"}, + A13: { type: "fleuve", label: "Fleuve de l'Oubli" }, + B13: { type: "gouffre", label: "Gouffre des Litiges" }, + C13: { type: "desert", label: "Désert de Neige" }, + D13: { type: "cite", label: "Cité Sordide" }, + E13: { type: "plaines", label: "Plaines de Xnez" }, + F13: { type: "foret", label: "Forêt des Cris" }, + G13: { type: "plaines", label: "Plaines Calcaires" }, + H13: { type: "desolation", label: "Désolation de Demain" }, + I13: { type: "monts", label: "Monts Bigleux" }, + J13: { type: "gouffre", label: "Gouffre de Gromph" }, + K13: { type: "foret", label: "Forêt de Kluth" }, + L13: { type: "monts", label: "Monts Dormants" }, + M13: { type: "plaines", label: "Plaines d’Anjou" }, - A15: { type: "cite", label: "Cité de Mielh"}, - C15: { type: "plaines", label: "Plaines de Toué"}, - E15: { type: "foret", label: "Forêt des Furies"}, - G15: { type: "plaines", label: "Plaines des Soupirs"}, - I15: { type: "monts", label: "Monts des Dragées"}, - K15: { type: "collines", label: "Collines Pourpres"}, - M15: { type: "cite", label: "Cité de Klana"} - } + A14: { type: "collines", label: "Collines de Stolis" }, + B14: { type: "necropole", label: "Nécropole de Gorlo" }, + C14: { type: "foret", label: "Forêt de Bissam" }, + D14: { type: "sanctuaire", label: "Sanctuaire Plat" }, + E14: { type: "monts", label: "Monts de Quath" }, + F14: { type: "plaines", label: "Plaines Brisées" }, + G14: { type: "desert", label: "Désert de Sek" }, + H14: { type: "plaines", label: "Plaines Blanches" }, + I14: { type: "cite", label: "Cité Destituée" }, + J14: { type: "desert", label: "Désert de Sank" }, + K14: { type: "necropole", label: "Nécropole d’Antinéar" }, + L14: { type: "plaines", label: "Plaines de Jislith" }, + M14: { type: "desolation", label: "Désolation de Demain" }, -export const TMRType = { - cite: {name:"cité"}, - sanctuaire: {name:"sanctuaire"}, - plaines: {name:"plaines"}, - pont: {name:"pont"}, - collines: {name:"collines"}, - foret: {name:"forêt"}, - monts: {name:"monts"}, - desert: {name:"désert"}, - fleuve: {name:"fleuve"}, - lac: {name:"lac"}, - marais: {name:"marais"}, - gouffre: {name:"gouffre"}, - necropole: {name:"nécropole"}, - desolation: {name:"désolation"} + A15: { type: "cite", label: "Cité de Mielh" }, + C15: { type: "plaines", label: "Plaines de Toué" }, + E15: { type: "foret", label: "Forêt des Furies" }, + G15: { type: "plaines", label: "Plaines des Soupirs" }, + I15: { type: "monts", label: "Monts des Dragées" }, + K15: { type: "collines", label: "Collines Pourpres" }, + M15: { type: "cite", label: "Cité de Klana" } } -/* -------------------------------------------- */ -const caseSpecificModes = [ "attache", "trounoir", "debordement", "reserve_extensible", "maitrisee" ]; +export const TMRType = { + cite: { name: "cité", genre: "f" }, + sanctuaire: { name: "sanctuaire" }, + plaines: { name: "plaines", genre: "p" }, + pont: { name: "pont", genre: "m" }, + collines: { name: "collines", genre: "p" }, + foret: { name: "forêt", genre: "f" }, + monts: { name: "monts", genre: "p" }, + desert: { name: "désert", genre: "m" }, + fleuve: { name: "fleuve", genre: "m" }, + lac: { name: "lac", genre: "m" }, + marais: { name: "marais", genre: "m" }, + gouffre: { name: "gouffre", genre: "m" }, + necropole: { name: "nécropole", genre: "f" }, + desolation: { name: "désolation", genre: "f" } +} + +export const poesieCaseHumide = [ + { + reference: 'Le Ratier Bretonien', + extrait: `Le courant du Fleuve +
Te domine et te Porte +
Avant que tu te moeuves +
Combat le, ou il t'emporte` + }, + { + reference: 'Incompatibilité, Charles Beaudelaire', + extrait: `Et lorsque par hasard une nuée errante +
Assombrit dans son vol le lac silencieux, +
On croirait voir la robe ou l'ombre transparente +
D'un esprit qui voyage et passe dans les cieux.` + }, + { + reference: 'Au fleuve de Loire, Joachim du Bellay', + extrait: `Ô de qui la vive course +
Prend sa bienheureuse source, +
D’une argentine fontaine, +
Qui d’une fuite lointaine, +
Te rends au sein fluctueux +
De l’Océan monstrueux` + }, + { + reference: 'Denis Gerfaud', + extrait: `Et l'on peut savoir qui est le maître d'Oniros, c'est le Fleuve de l'Oubli. + Et l'on sait qui est le créateur du Fleuve de l'Oubli, c'est Hypnos et Narcos. + Mais l'on ne sait pas qui est le maître du Fleuve de l'Oubli, + sinon peut-être lui-même, ou peut-être Thanatos` }, + { + reference: 'Denis Gerfaud', + extrait: `Narcos est la source du Fleuve de l'Oubli et Hypnos l'embouchure + Remonter le Fleuve est la Voie de la Nuit, la Voie du Souvenir. + Descendre le Fleuve est la Voie du Jour, la Voie de l'Oubli` + }, + { + reference: 'Denis Gerfaud', + extrait: `Narcos engendre le fils dont il est la mère à l'heure du Vaisseau, + car Oniros s'embarque pour redescendre le Fleuve + vers son père Hypnos sur la Voie de l'Oubli` + }, + { + reference: 'Denis Gerfaud', + extrait: `Hypnos engendre le fils dont il est la mère à l'heure du Serpent, car + tel les serpents, Oniros commence à remonter le Fleuve + sur le Voie du Souvenir vers son père Narcos` + }, + { + reference: 'Denis Gerfaud', + extrait: `Ainsi se cuccèdent les Jours et les Ages. Les jours des Dragons sont les Ages des Hommes` + }, + { + reference: 'Denis Gerfaud', + extrait: `Ainsi parlent les sages: + «Les Dragons sont créateurs de leurs rêves, mais ils ne sont pas créateurs d'Oniros + Les Dragons ne sont pas les maîtres de leurs rêvezs, car ils ne sont pas maîtres d'Oniros. + Nul ne sait qui est le créateur des Dragons, ni qui est leur maître. + Mais l'on peut supposer qui est le maître du Rêve des Dragons, c'est Oniros»` + }, +] /* -------------------------------------------- */ -const tmrRandomMovePatten = - [ { name: 'top', x: 0, y: -1 }, - { name: 'topright', x: 1, y: -1 }, - { name: 'botright', x: 1, y: 1 }, - { name: 'bot', x: 0, y: 1 }, - { name: 'botleft', x: -1, y: 1 }, - { name: 'topleft', x: -1, y: -1 } - ] +const caseSpecificModes = ["attache", "trounoir", "debordement", "reserve_extensible", "maitrisee"]; + +/* -------------------------------------------- */ +const tmrRandomMovePatten = + [{ name: 'top', x: 0, y: -1 }, + { name: 'topright', x: 1, y: -1 }, + { name: 'botright', x: 1, y: 1 }, + { name: 'bot', x: 0, y: 1 }, + { name: 'botleft', x: -1, y: 1 }, + { name: 'topleft', x: -1, y: -1 } + ] /* -------------------------------------------- */ export const tmrConstants = { @@ -253,7 +315,7 @@ export const tmrConstants = { /* -------------------------------------------- */ /* -------------------------------------------- */ -export class TMRUtility { +export class TMRUtility { static init() { for (let coord in TMRMapping) { TMRMapping[coord].coord = coord; @@ -265,64 +327,68 @@ export class TMRUtility { } /* -------------------------------------------- */ - static convertToTMRCoord( pos ) - { - let letterX = String.fromCharCode(65+ (pos.x)); - return letterX + (pos.y +1) + static convertToTMRCoord(pos) { + let letterX = String.fromCharCode(65 + (pos.x)); + return letterX + (pos.y + 1) } /* -------------------------------------------- */ - static verifyTMRCoord( coord ) { + static verifyTMRCoord(coord) { let TMRregexp = new RegExp(/([A-M])(\d+)/g); - let res = TMRregexp.exec( coord ); + let res = TMRregexp.exec(coord); if (res && res[1] && res[2]) { if (res[2] > 0 && res[2] < 16) { return true; } } return false; - } + } /* -------------------------------------------- */ - static convertToCellPos( coordTMR ) - { + static convertToCellPos(coordTMR) { let x = coordTMR.charCodeAt(0) - 65; let y = coordTMR.substr(1) - 1; - return {x: x, y: y} + return { x: x, y: y } } - + /* -------------------------------------------- */ - static getTMR( coordTMR) - { + static getTMR(coordTMR) { return TMRMapping[coordTMR]; } - + /* -------------------------------------------- */ /** Some debug functions */ - static async setForceRencontre( index, force = undefined ) { - this.prochaineRencontre = TMRRencontres.getRencontre( index ); - if (this.prochaineRencontre ) { + static async setForceRencontre(index, force = undefined) { + this.prochaineRencontre = TMRRencontres.getRencontre(index); + if (this.prochaineRencontre) { if (force) { this.prochaineRencontre.force = force; } - else{ + else { await TMRRencontres.evaluerForceRencontre(this.prochaineRencontre) } console.log("La prochaine rencontre sera:", this.prochaineRencontre.name, " force:", this.prochaineRencontre.force); } else { - ui.notifications.warn("Pas de prochaine rencontre valide pour "+index); + ui.notifications.warn("Pas de prochaine rencontre valide pour " + index); } } + /* -------------------------------------------- */ static isForceRencontre() { - return this.prochaineRencontre + return this.prochaineRencontre; + } + /* -------------------------------------------- */ + static utiliseForceRencontre() { + const rencontre = this.prochaineRencontre; + this.prochaineRencontre = undefined; + return rencontre; } /* -------------------------------------------- */ static getDirectionPattern() { - let roll = new Roll("1d"+tmrRandomMovePatten.length).evaluate().total; - return tmrRandomMovePatten[roll -1]; + let roll = new Roll("1d" + tmrRandomMovePatten.length).evaluate().total; + return tmrRandomMovePatten[roll - 1]; } static deplaceTMRAleatoire(coord) { @@ -330,12 +396,12 @@ export class TMRUtility { } /* -------------------------------------------- */ - static deplaceTMRSelonPattern( coord, direction, nTime ) { - for (let i=0; i it.coord); + return this.getListTMR(terrain).map(it => it.coord); } static getTMRAleatoire(terrain = undefined) { let list = terrain ? TMRUtility.getListTMR(terrain) : Object.values(TMRMapping); let index = new Roll("1d" + list.length).evaluate().total - 1; - return list[index]; + return list[index]; } /* -------------------------------------------- */ - static _checkTMRCoord( x, y ) { - if (x >= 0 && x < 13 && y >= 0 && y < 14 ) return true; - if (x >= 0 && x < 13 && x%2 == 0 && y == 14 ) return true; + static _checkTMRCoord(x, y) { + if (x >= 0 && x < 13 && y >= 0 && y < 14) return true; + if (x >= 0 && x < 13 && x % 2 == 0 && y == 14) return true; return false; } /* -------------------------------------------- */ - static computeRealPictureCoordinates( coordXY, tmrConstants ) { + static computeRealPictureCoordinates(coordXY, tmrConstants) { let decallagePairImpair = (coordXY.x % 2 == 0) ? tmrConstants.col1_y : tmrConstants.col2_y; - return { + return { x: tmrConstants.gridx + (coordXY.x * tmrConstants.cellw), y: tmrConstants.gridy + (coordXY.y * tmrConstants.cellh) + decallagePairImpair } } /* -------------------------------------------- */ - static getSortReserveList( reserveList, coordTMR ) { + static getSortReserveList(reserveList, coordTMR) { // TODO : Gérer les têtes spéciales réserve! let sortReserveList let tmrDescr = this.getTMR(coordTMR); //console.log("Sort réserve : ", tmrDescr); - if ( tmrDescr.type == 'fleuve') { // Gestion de la reserve en Fleuve - sortReserveList = reserveList.filter(it => TMRUtility.getTMR(it.coord).type == 'fleuve' ); + if (tmrDescr.type == 'fleuve') { // Gestion de la reserve en Fleuve + sortReserveList = reserveList.filter(it => TMRUtility.getTMR(it.coord).type == 'fleuve'); } else { // Reserve sur un case "normale" - sortReserveList = reserveList.filter(it => it.coord == coordTMR); + sortReserveList = reserveList.filter(it => it.coord == coordTMR); } //console.log("Sort réserve : ", tmrDescr, sortReserve, reserveList); return sortReserveList; @@ -417,18 +465,18 @@ export class TMRUtility { return TMRUtility.getTMRArea(centerCoord, portee, tmrConstants); } - static getTMRArea( centerCoord, distance, tmrConstants ) { - let centerPos = this.convertToCellPos( centerCoord ); - let posPic = this.computeRealPictureCoordinates( centerPos, tmrConstants ); + static getTMRArea(centerCoord, distance, tmrConstants) { + let centerPos = this.convertToCellPos(centerCoord); + let posPic = this.computeRealPictureCoordinates(centerPos, tmrConstants); let caseList = []; - for (let dx=-distance; dx<=distance; dx++ ) { // Loop thru lines - for (let dy=-distance; dy<=distance; dy++ ) { // Loop thru lines - const currentPos = { x: centerPos.x+dx, y: centerPos.y+dy }; - if ( this._checkTMRCoord(currentPos.x, currentPos.y) ) { // Coordinate is valie - let posPicNow = this.computeRealPictureCoordinates( currentPos, tmrConstants ); - let dist = Math.sqrt(Math.pow(posPicNow.x - posPic.x,2) + Math.pow(posPicNow.y - posPic.y, 2)) / tmrConstants.cellw; - if ( dist < distance+0.5) { - caseList.push( this.convertToTMRCoord(currentPos) ); // Inside the area + for (let dx = -distance; dx <= distance; dx++) { // Loop thru lines + for (let dy = -distance; dy <= distance; dy++) { // Loop thru lines + const currentPos = { x: centerPos.x + dx, y: centerPos.y + dy }; + if (this._checkTMRCoord(currentPos.x, currentPos.y)) { // Coordinate is valie + let posPicNow = this.computeRealPictureCoordinates(currentPos, tmrConstants); + let dist = Math.sqrt(Math.pow(posPicNow.x - posPic.x, 2) + Math.pow(posPicNow.y - posPic.y, 2)) / tmrConstants.cellw; + if (dist < distance + 0.5) { + caseList.push(this.convertToTMRCoord(currentPos)); // Inside the area } } } diff --git a/templates/chat-fleuve-tmr.html b/templates/chat-fleuve-tmr.html new file mode 100644 index 00000000..9cc96e2a --- /dev/null +++ b/templates/chat-fleuve-tmr.html @@ -0,0 +1,30 @@ +{{competence.name}} +

+ {{alias}} tente de maîtriser {{le tmr.genre}} {{tmr.label}} ({{tmr.coord}}) +

+{{#if previous}} + {{#each previous as |rolled key|}} + {{> "systems/foundryvtt-reve-de-dragon/templates/chat-infojet.html"}} +
Double résistance du fleuve! + {{/each}} +{{/if}} +{{> "systems/foundryvtt-reve-de-dragon/templates/chat-infojet.html"}} +
+ +{{#if rolled.isSuccess}} + Vous avez maîtrisé {{le tmr.genre}} {{#if (eq tmr.type 'fleuve')}}Fleuve de l'Oubli{{else}}{{tmr.label}}{{/if}} ! + {{else}} + Vous ne parvenez pas à surmonter {{le tmr.genre}} {{#if (eq tmr.type 'fleuve')}}Fleuve de l'Oubli{{else}}{{tmr.label}}{{/if}}. + Vous quittez les Terres Médianes ! + {{#if souffle}} +
De plus, votre échec total vous fait subir un Souffle de Dragon : {{souffle.name}} + {{/if}} +{{/if}} +
+{{#if poesie}} +
+ + {{{poesie.extrait}}} +

{{poesie.reference}}

+
+{{/if}} diff --git a/templates/dialog-roll-tmr-humide.html b/templates/dialog-roll-tmr-humide.html new file mode 100644 index 00000000..314668dc --- /dev/null +++ b/templates/dialog-roll-tmr-humide.html @@ -0,0 +1,28 @@ +
+
+ + + + + + + +
+ {{>"systems/foundryvtt-reve-de-dragon/templates/dialog-roll-surenc.html"}} +
+
+
+
+
+
+
+ + + \ No newline at end of file diff --git a/templates/item-meditation-sheet.html b/templates/item-meditation-sheet.html index 1893346e..0b484acb 100644 --- a/templates/item-meditation-sheet.html +++ b/templates/item-meditation-sheet.html @@ -50,20 +50,7 @@ diff --git a/templates/item-sort-sheet.html b/templates/item-sort-sheet.html index 728b0d73..a77801a0 100644 --- a/templates/item-sort-sheet.html +++ b/templates/item-sort-sheet.html @@ -21,6 +21,7 @@ diff --git a/templates/sort-tmr.html b/templates/sort-tmr.html index c716adbc..0231eb92 100644 --- a/templates/sort-tmr.html +++ b/templates/sort-tmr.html @@ -2,7 +2,7 @@ - + @@ -12,4 +12,4 @@ - +