import { TMRRencontres } from "./tmr-rencontres.js"; import { Misc } from "./misc.js"; import { Grammar } from "./grammar.js"; import { RdDDice } from "./rdd-dice.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 de l'Oubli" }, E1: { type: "monts", label: "Monts de Kanaï" }, F1: { type: "cite", label: "Cité Glauque" }, G1: { type: "desolation", label: "Désolation de Jamais" }, 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" }, 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" }, 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" }, 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" }, 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 Toujours" }, 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" }, 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 Poor" }, L6: { type: "foret", label: "Forêt Gueuse" }, M6: { type: "desolation", label: "Désolation de Presque" }, 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’Affa" }, 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" }, 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" }, 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 Sel" }, 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" }, 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" }, A11: { type: "desolation", label: "Désolation d’Hier" }, B11: { type: "cite", label: "Cité de Brilz" }, C11: { type: "pont", label: "Pont de Roï" }, D11: { type: "desolation", label: "Désolation de Partout" }, 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" }, 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 d’Amour" }, L12: { type: "plaines", label: "Plaines Venteuses" }, M12: { type: "collines", label: "Collines Révulsantes" }, 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 Rien" }, 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" }, 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 d’Après" }, 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" } } export const TMRType = { cite: { name: "cité", genre: "f" }, sanctuaire: { name: "sanctuaire", genre: 'm' }, plaines: { name: "plaines", genre: "fp" }, 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" } } /* -------------------------------------------- */ const caseSpecificModes = ["attache", "trounoir", "debordement", "reserve_extensible", "maitrisee"]; /* -------------------------------------------- */ const tmrRandomMovePatten = [{ name: 'top', col: 0, row: -1 }, { name: 'topright', col: 1, row: -1 }, { name: 'botright', col: 1, row: 1 }, { name: 'bot', col: 0, row: 1 }, { name: 'botleft', col: -1, row: 1 }, { name: 'topleft', col: -1, row: -1 } ] /* -------------------------------------------- */ /* -------------------------------------------- */ export class TMRUtility { static init() { for (let coord in TMRMapping) { const tmr = TMRMapping[coord]; tmr.coord = coord; tmr.oddq = TMRUtility.coordTMRToOddq(coord); tmr.genre = TMRType[tmr.type].genre; } let tmrByType = Misc.classify(Object.values(TMRMapping)); for (const [type, list] of Object.entries(tmrByType)) { TMRType[type].list = list; } } /* -------------------------------------------- */ static verifyTMRCoord(coord) { let TMRregexp = new RegExp(/([A-M])(\d+)/g); let res = TMRregexp.exec(coord); if (res && res[1] && res[2]) { if (res[2] > 0 && res[2] < 16) { return true; } } return false; } /* -------------------------------------------- */ static getTMR(coord) { return TMRMapping[coord]; } static getTMRLabel(coord) { return TMRMapping[coord]?.label ?? (coord + ": case inconnue"); } static getTMRType(coord) { const tmr = TMRMapping[coord]; return Misc.upperFirst(TMRType[tmr.type].name); } static getTMRDescr(coord) { const tmr = TMRMapping[coord]; return Grammar.articleDetermine(tmr.type) + ' ' + tmr.label; } static typeTmrName(type) { return Misc.upperFirst(TMRType[Grammar.toLowerCaseNoAccent(type)].name); } static listSelectedTMR(typesTMR) { return Object.values(TMRType).map(value => Misc.upperFirst(value.name)) .sort() .map(name => { return { name: name, selected: typesTMR.includes(name) } }); } static isCaseHumide(tmr) { return tmr.type == 'fleuve' || tmr.type == 'lac' || tmr.type == 'marais'; } /* -------------------------------------------- */ /** Some debug functions */ static async setForceRencontre(index, force = undefined) { this.prochaineRencontre = TMRRencontres.getRencontre(index); if (this.prochaineRencontre) { if (force) { this.prochaineRencontre.force = force; } 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); } } /* -------------------------------------------- */ static isForceRencontre() { return this.prochaineRencontre; } /* -------------------------------------------- */ static utiliseForceRencontre() { const rencontre = this.prochaineRencontre; this.prochaineRencontre = undefined; return rencontre; } /* -------------------------------------------- */ static async getDirectionPattern() { return await RdDDice.rollOneOf(tmrRandomMovePatten); } /* -------------------------------------------- */ static async deplaceTMRAleatoire(actor, coord) { return TMRUtility.deplaceTMRSelonPattern(actor, coord, await TMRUtility.getDirectionPattern(), 1); } /* -------------------------------------------- */ static async deplaceTMRSelonPattern(actor, coordTMR, direction, nTime) { let coord; for (let i = 0; i < nTime; i++) { let currentOddq = TMRUtility.coordTMRToOddq(coordTMR); currentOddq.col = currentOddq.col + direction.col; currentOddq.row = currentOddq.row + direction.row; if (this.isOddqInTMR(currentOddq)) { // Sortie de carte ! Ré-insertion aléatoire coord = TMRUtility.getTMR(TMRUtility.oddqToCoordTMR(currentOddq)); } else { coord = await actor.reinsertionAleatoire('Sortie de carte'); } console.log("Nouvelle case iteration !!!", i, coord); } return coord; } /* -------------------------------------------- */ static getListTMR(terrain) { return TMRType[terrain].list; } static filterTMR(filter) { return Object.values(TMRMapping).filter(filter); } static findTMR(search) { const labelSearch = Grammar.toLowerCaseNoAccent(search) return TMRUtility.filterTMR(it => Grammar.toLowerCaseNoAccent(it.label).match(labelSearch) || it.coord == search); } static filterTMRCoord(filter) { return TMRUtility.filterTMR(filter).map(it => it.coord); } static async getTMRAleatoire(filter = it => true) { return await RdDDice.rollOneOf(TMRUtility.filterTMR(filter)) } /* -------------------------------------------- */ static getSortsReserve(reserveList, coord) { // TODO : Gérer les têtes spéciales réserve! let tmrDescr = this.getTMR(coord); //console.log("Sort réserve : ", tmrDescr); if (tmrDescr.type == 'fleuve') { // Gestion de la reserve en Fleuve return reserveList.filter(it => TMRUtility.getTMR(it.coord).type == 'fleuve'); } // Reserve sur un case "normale" return reserveList.filter(it => it.coord == coord); } /* -------------------------------------------- */ /** Returns a list of case inside a given distance * */ static getTMRPortee(coord, portee) { let centerOddq = this.coordTMRToOddq(coord); let caseList = []; for (let dcol = -portee; dcol <= portee; dcol++) { // rows for (let drow = -portee; drow <= portee; drow++) { // columns const currentOddq = { col: centerOddq.col + dcol, row: centerOddq.row + drow }; if (this.isOddqInTMR(currentOddq)) { let dist = this.distanceOddq(centerOddq, currentOddq); if (dist <= portee) { caseList.push(this.oddqToCoordTMR(currentOddq)); // Inside the area } } } } return caseList; } /* -------------------------------------------- */ // https://www.redblobgames.com/grids/hexagons/#distances // TMR Letter-row correspond to "odd-q" grid (letter => col, numeric => row ) /* -------------------------------------------- */ static coordTMRToOddq(coordTMR) { let col = coordTMR.charCodeAt(0) - 65; let row = coordTMR.substr(1) - 1; return { col: col, row: row } } /* -------------------------------------------- */ static oddqToCoordTMR(oddq) { let letterX = String.fromCharCode(65 + (oddq.col)); return letterX + (oddq.row + 1) } /* -------------------------------------------- */ static isOddqInTMR(oddq) { const col = oddq.col; const row = oddq.row; return ( col >= 0 && col < 13 && row >= 0 && (row + col % 2 <= 14) ); // 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 distanceCoordTMR(coord1, coord2) { let oddq1 = this.coordTMRToOddq(coord1); let oddq2 = this.coordTMRToOddq(coord2); return this.distanceOddq(oddq1, oddq2); } /* -------------------------------------------- */ static distanceOddq(oddq1, oddq2) { const axial1 = TMRUtility.oddqToAxial(oddq1); const axial2 = TMRUtility.oddqToAxial(oddq2); return TMRUtility.distanceAxial(axial1, axial2); // const dx = oddq2.col - oddq1.col; // const dy = oddq2.row - oddq1.row; // const abs_dx = Math.abs(dx); // const abs_dy = Math.abs(dy); // const distance = Math.sign(dx) == Math.sign(dy) ? Math.max(abs_dx, abs_dy) : (abs_dx + abs_dy); // return distance; } static oddqToAxial(pos) { return { q: pos.col, r: pos.row - (pos.col - (pos.col & 1)) / 2 } } static distanceAxial(a, b) { const vector = TMRUtility.axial_subtract(a, b) return (Math.abs(vector.q) + Math.abs(vector.q + vector.r) + Math.abs(vector.r)) / 2 } static axial_subtract(a, b) { return { q: a.q- b.q, r: a.r - b.r }; } // function axial_to_cube(hex): // var q = hex.q // var r = hex.r // var s = -q - r // return Cube(q, r, s) // } // /* -------------------------------------------- */ // static computeRealPictureCoordinates(coordOddq) { // let decallagePairImpair = (coordOddq.col % 2 == 0) ? tmrConstants.col1_y : tmrConstants.col2_y; // return { // x: tmrConstants.gridx + (coordOddq.col * tmrConstants.cellw), // y: tmrConstants.gridy + (coordOddq.row * tmrConstants.cellh) + decallagePairImpair // } // } }