import { Misc } from "./misc.js"; import { Grammar } from "./grammar.js"; import { RdDDice } from "./rdd-dice.js"; export const TMRType = { cite: { type: 'cite', name: "cité", genre: "f" }, sanctuaire: { type: 'sanctuaire', name: "sanctuaire", genre: 'm' }, plaines: { type: 'plaines', name: "plaines", genre: "fp" }, pont: { type: 'pont', name: "pont", genre: "m" }, collines: { type: 'collines', name: "collines", genre: "p" }, foret: { type: 'foret', name: "forêt", genre: "f" }, monts: { type: 'monts', name: "monts", genre: "p" }, desert: { type: 'desert', name: "désert", genre: "m" }, fleuve: { type: 'fleuve', name: "fleuve", genre: "m" }, lac: { type: 'lac', name: "lac", genre: "m" }, marais: { type: 'marais', name: "marais", genre: "m" }, gouffre: { type: 'gouffre', name: "gouffre", genre: "m" }, necropole: { type: 'necropole', name: "nécropole", genre: "f" }, desolation: { type: 'desolation', name: "désolation", genre: "f" } } export const FLEUVE_COORD = 'Fleuve' const TMRMapping = { Fleuve: { type: TMRType.fleuve.type, label: "Fleuve de l'Oubli" }, A1: { type: TMRType.cite.type, label: "Cité Vide" }, B1: { type: TMRType.plaines.type, label: "Plaines d’Assorh" }, C1: { type: TMRType.necropole.type, label: "Nécropole de Kroak" }, D1: { type: TMRType.fleuve.type, label: "Fleuve de l'Oubli" }, E1: { type: TMRType.monts.type, label: "Monts de Kanaï" }, F1: { type: TMRType.cite.type, label: "Cité Glauque" }, G1: { type: TMRType.desolation.type, label: "Désolation de Jamais" }, H1: { type: TMRType.lac.type, label: "Lac d’Anticalme" }, I1: { type: TMRType.plaines.type, label: "Plaines Grises" }, J1: { type: TMRType.monts.type, label: "Monts Fainéants" }, K1: { type: TMRType.cite.type, label: "Cité d’Onkause" }, L1: { type: TMRType.fleuve.type, label: "Fleuve de l'Oubli" }, M1: { type: TMRType.cite.type, label: "Cité Jalouse" }, A2: { type: TMRType.desert.type, label: "Désert de Mieux" }, B2: { type: TMRType.collines.type, label: "Collines de Dawell" }, C2: { type: TMRType.marais.type, label: "Marais Glignants" }, D2: { type: TMRType.cite.type, label: "Cité de Frost" }, E2: { type: TMRType.plaines.type, label: "Plaines de Fiask" }, F2: { type: TMRType.lac.type, label: "Lac de Misère" }, G2: { type: TMRType.marais.type, label: "Marais Nuisants" }, H2: { type: TMRType.collines.type, label: "Collines de Parta" }, I2: { type: TMRType.foret.type, label: "Forêt Fade" }, J2: { type: TMRType.desert.type, label: "Désert de Poly" }, K2: { type: TMRType.foret.type, label: "Forêt Tamée" }, L2: { type: TMRType.fleuve.type, label: "Fleuve de l'Oubli" }, M2: { type: TMRType.necropole.type, label: "Nécropole de Logos" }, A3: { type: TMRType.desolation.type, label: "Désolation de Demain" }, B3: { type: TMRType.plaines.type, label: "Plaines de Rubéga" }, C3: { type: TMRType.fleuve.type, label: "Fleuve de l'Oubli" }, D3: { type: TMRType.gouffre.type, label: "Gouffre d’Oki" }, E3: { type: TMRType.foret.type, label: "Forêt d’Estoubh" }, F3: { type: TMRType.fleuve.type, label: "Fleuve de l'Oubli" }, G3: { type: TMRType.gouffre.type, label: "Gouffre de Sun" }, H3: { type: TMRType.foret.type, label: "Forêt de Ganna" }, I3: { type: TMRType.monts.type, label: "Monts Grinçants" }, J3: { type: TMRType.cite.type, label: "Cité Venin" }, K3: { type: TMRType.plaines.type, label: "Plaines de Dois" }, L3: { type: TMRType.lac.type, label: "Lac Laineux" }, M3: { type: TMRType.monts.type, label: "Monts de Vdah" }, A4: { type: TMRType.foret.type, label: "Forêt de Falconax" }, B4: { type: TMRType.monts.type, label: "Monts Crâneurs" }, C4: { type: TMRType.pont.type, label: "Pont de Giolii" }, D4: { type: TMRType.lac.type, label: "Lac de Foam" }, E4: { type: TMRType.plaines.type, label: "Plaines d’Orti" }, F4: { type: TMRType.fleuve.type, label: "Fleuve de l'Oubli" }, G4: { type: TMRType.sanctuaire.type, label: "Sanctuaire Blanc" }, H4: { type: TMRType.plaines.type, label: "Plaines de Psark" }, I4: { type: TMRType.plaines.type, label: "Plaines de Xiax" }, J4: { type: TMRType.collines.type, label: "Collines d’Encre" }, K4: { type: TMRType.pont.type, label: "Pont de Fah" }, L4: { type: TMRType.sanctuaire.type, label: "Sanctuaire Mauve" }, M4: { type: TMRType.gouffre.type, label: "Gouffre Grisant" }, A5: { type: TMRType.plaines.type, label: "Plaines de Trilkh" }, B5: { type: TMRType.collines.type, label: "Collines de Tanegy" }, C5: { type: TMRType.marais.type, label: "Marais Flouants" }, D5: { type: TMRType.fleuve.type, label: "Fleuve de l'Oubli" }, E5: { type: TMRType.monts.type, label: "Monts Brûlants" }, F5: { type: TMRType.cite.type, label: "Cité de Panople" }, G5: { type: TMRType.pont.type, label: "Pont d’Ik" }, H5: { type: TMRType.desert.type, label: "Désert de Krane" }, I5: { type: TMRType.desolation.type, label: "Désolation de Toujours" }, J5: { type: TMRType.marais.type, label: "Marais de Jab" }, K5: { type: TMRType.fleuve.type, label: "Fleuve de l'Oubli" }, L5: { type: TMRType.collines.type, label: "Collines Suaves" }, M5: { type: TMRType.cite.type, label: "Cité Rimarde" }, A6: { type: TMRType.necropole.type, label: "Nécropole de Zniak" }, B6: { type: TMRType.foret.type, label: "Forêt de Bust" }, C6: { type: TMRType.cite.type, label: "Cité Pavois" }, D6: { type: TMRType.fleuve.type, label: "Fleuve de l'Oubli" }, E6: { type: TMRType.sanctuaire.type, label: "Sanctuaire de Plaine" }, F6: { type: TMRType.fleuve.type, label: "Fleuve de l'Oubli" }, G6: { type: TMRType.marais.type, label: "Marais Glutants" }, H6: { type: TMRType.monts.type, label: "Monts Gurdes" }, I6: { type: TMRType.necropole.type, label: "Nécropole de Xotar" }, J6: { type: TMRType.lac.type, label: "Lac d’Iaupe" }, K6: { type: TMRType.desolation.type, label: "Désolation de Poor" }, L6: { type: TMRType.foret.type, label: "Forêt Gueuse" }, M6: { type: TMRType.desolation.type, label: "Désolation de Presque" }, A7: { type: TMRType.plaines.type, label: "Plaines de l’Arc" }, B7: { type: TMRType.marais.type, label: "Marais Bluants" }, C7: { type: TMRType.fleuve.type, label: "Fleuve de l'Oubli" }, D7: { type: TMRType.plaines.type, label: "Plaines d’Affa" }, E7: { type: TMRType.foret.type, label: "Forêt de Glusks" }, F7: { type: TMRType.fleuve.type, label: "Fleuve de l'Oubli" }, G7: { type: TMRType.cite.type, label: "Cité de Terwa" }, H7: { type: TMRType.gouffre.type, label: "Gouffre de Kapfa" }, I7: { type: TMRType.plaines.type, label: "Plaines de Troo" }, J7: { type: TMRType.fleuve.type, label: "Fleuve de l'Oubli" }, K7: { type: TMRType.cite.type, label: "Cité de Kolix" }, L7: { type: TMRType.gouffre.type, label: "Gouffre d’Episophe" }, M7: { type: TMRType.desert.type, label: "Désert de Lave" }, A8: { type: TMRType.gouffre.type, label: "Gouffre de Shok" }, B8: { type: TMRType.fleuve.type, label: "Fleuve de l'Oubli" }, C8: { type: TMRType.foret.type, label: "Forêt Turmide" }, D8: { type: TMRType.cite.type, label: "Cité d’Olak" }, E8: { type: TMRType.plaines.type, label: "Plaines d’Iolise" }, F8: { type: TMRType.lac.type, label: "Lac des Chats" }, G8: { type: TMRType.plaines.type, label: "Plaines Sans Joie" }, H8: { type: TMRType.foret.type, label: "Forêt d’Ourf" }, I8: { type: TMRType.fleuve.type, label: "Fleuve de l'Oubli" }, J8: { type: TMRType.monts.type, label: "Monts Barask" }, K8: { type: TMRType.desert.type, label: "Désert de Fumée" }, L8: { type: TMRType.monts.type, label: "Monts Tavelés" }, M8: { type: TMRType.plaines.type, label: "Plaines Lavées" }, A9: { type: TMRType.collines.type, label: "Collines de Korrex" }, B9: { type: TMRType.lac.type, label: "Lac de Lucre" }, C9: { type: TMRType.monts.type, label: "Monts Tuméfiés" }, D9: { type: TMRType.pont.type, label: "Pont d’Orx" }, E9: { type: TMRType.fleuve.type, label: "Fleuve de l'Oubli" }, F9: { type: TMRType.plaines.type, label: "Plaines de Foe" }, G9: { type: TMRType.desolation.type, label: "Désolation de Sel" }, H9: { type: TMRType.collines.type, label: "Collines de Noirseul" }, I9: { type: TMRType.fleuve.type, label: "Fleuve de l'Oubli" }, J9: { type: TMRType.marais.type, label: "Marais Gronchants" }, K9: { type: TMRType.sanctuaire.type, label: "Sanctuaire Noir" }, L9: { type: TMRType.collines.type, label: "Collines Cornues" }, M9: { type: TMRType.necropole.type, label: "Nécropole de Zonar" }, A10: { type: TMRType.sanctuaire.type, label: "Sanctuaire d’Olis" }, B10: { type: TMRType.monts.type, label: "Monts Salés" }, C10: { type: TMRType.marais.type, label: "Marais de Dom" }, D10: { type: TMRType.fleuve.type, label: "Fleuve de l'Oubli" }, E10: { type: TMRType.gouffre.type, label: "Gouffre de Junk" }, F10: { type: TMRType.marais.type, label: "Marais Zultants" }, G10: { type: TMRType.cite.type, label: "Cité de Sergal" }, H10: { type: TMRType.plaines.type, label: "Plaines Noires" }, I10: { type: TMRType.lac.type, label: "Lac Wanito" }, J10: { type: TMRType.fleuve.type, label: "Fleuve de l'Oubli" }, K10: { type: TMRType.plaines.type, label: "Plaines Jaunes" }, L10: { type: TMRType.desert.type, label: "Désert de Nicrop" }, M10: { type: TMRType.foret.type, label: "Forêt de Jajou" }, A11: { type: TMRType.desolation.type, label: "Désolation d’Hier" }, B11: { type: TMRType.cite.type, label: "Cité de Brilz" }, C11: { type: TMRType.pont.type, label: "Pont de Roï" }, D11: { type: TMRType.desolation.type, label: "Désolation de Partout" }, E11: { type: TMRType.lac.type, label: "Lac de Glinster" }, F11: { type: TMRType.cite.type, label: "Cité de Noape" }, G11: { type: TMRType.fleuve.type, label: "Fleuve de l'Oubli" }, H11: { type: TMRType.fleuve.type, label: "Fleuve de l'Oubli" }, I11: { type: TMRType.pont.type, label: "Pont de Yalm" }, J11: { type: TMRType.plaines.type, label: "Plaines de Miltiar" }, K11: { type: TMRType.cite.type, label: "Cité Tonnerre" }, L11: { type: TMRType.collines.type, label: "Collines de Kol" }, M11: { type: TMRType.cite.type, label: "Cité Crapaud" }, A12: { type: TMRType.plaines.type, label: "Plaines Sages" }, B12: { type: TMRType.fleuve.type, label: "Fleuve de l'Oubli" }, C12: { type: TMRType.lac.type, label: "Lac de Fricassa" }, D12: { type: TMRType.collines.type, label: "Collines d’Huaï" }, E12: { type: TMRType.monts.type, label: "Monts Ajourés" }, F12: { type: TMRType.necropole.type, label: "Nécropole de Throat" }, G12: { type: TMRType.plaines.type, label: "Plaines de Lufmil" }, H12: { type: TMRType.collines.type, label: "Collines de Tooth" }, I12: { type: TMRType.gouffre.type, label: "Gouffre Abimeux" }, J12: { type: TMRType.cite.type, label: "Cité Folle" }, K12: { type: TMRType.desolation.type, label: "Désolation d’Amour" }, L12: { type: TMRType.plaines.type, label: "Plaines Venteuses" }, M12: { type: TMRType.collines.type, label: "Collines Révulsantes" }, A13: { type: TMRType.fleuve.type, label: "Fleuve de l'Oubli" }, B13: { type: TMRType.gouffre.type, label: "Gouffre des Litiges" }, C13: { type: TMRType.desert.type, label: "Désert de Neige" }, D13: { type: TMRType.cite.type, label: "Cité Sordide" }, E13: { type: TMRType.plaines.type, label: "Plaines de Xnez" }, F13: { type: TMRType.foret.type, label: "Forêt des Cris" }, G13: { type: TMRType.plaines.type, label: "Plaines Calcaires" }, H13: { type: TMRType.desolation.type, label: "Désolation de Rien" }, I13: { type: TMRType.monts.type, label: "Monts Bigleux" }, J13: { type: TMRType.gouffre.type, label: "Gouffre de Gromph" }, K13: { type: TMRType.foret.type, label: "Forêt de Kluth" }, L13: { type: TMRType.monts.type, label: "Monts Dormants" }, M13: { type: TMRType.plaines.type, label: "Plaines d’Anjou" }, A14: { type: TMRType.collines.type, label: "Collines de Stolis" }, B14: { type: TMRType.necropole.type, label: "Nécropole de Gorlo" }, C14: { type: TMRType.foret.type, label: "Forêt de Bissam" }, D14: { type: TMRType.sanctuaire.type, label: "Sanctuaire Plat" }, E14: { type: TMRType.monts.type, label: "Monts de Quath" }, F14: { type: TMRType.plaines.type, label: "Plaines Brisées" }, G14: { type: TMRType.desert.type, label: "Désert de Sek" }, H14: { type: TMRType.plaines.type, label: "Plaines Blanches" }, I14: { type: TMRType.cite.type, label: "Cité Destituée" }, J14: { type: TMRType.desert.type, label: "Désert de Sank" }, K14: { type: TMRType.necropole.type, label: "Nécropole d’Antinéar" }, L14: { type: TMRType.plaines.type, label: "Plaines de Jislith" }, M14: { type: TMRType.desolation.type, label: "Désolation d’Après" }, A15: { type: TMRType.cite.type, label: "Cité de Mielh" }, C15: { type: TMRType.plaines.type, label: "Plaines de Toué" }, E15: { type: TMRType.foret.type, label: "Forêt des Furies" }, G15: { type: TMRType.plaines.type, label: "Plaines des Soupirs" }, I15: { type: TMRType.monts.type, label: "Monts des Dragées" }, K15: { type: TMRType.collines.type, label: "Collines Pourpres" }, M15: { type: TMRType.cite.type, label: "Cité de Klana" } } /* -------------------------------------------- */ const TMR_MOVE = { "top": { even: { row: -1, col: 0 }, odd: { row: -1, col: 0 }, }, "topleft": { even: { row: -1, col: -1 }, odd: { row: 0, col: -1 }, }, "topright": { even: { row: -1, col: 1 }, odd: { row: 0, col: 1 }, }, "bottomleft": { even: { row: 0, col: -1 }, odd: { row: 1, col: -1 }, }, "bottomright": { even: { row: 0, col: 1 }, odd: { row: 1, col: 1 }, }, "bottom": { even: { row: 1, col: 0 }, odd: { row: 1, col: 0 }, }, } /* -------------------------------------------- * Pour comprendre les conversions entre coordonnées * - "TMR" A1, ... M15 * - oddq: {col, row} * - axial: { q, r ) * * Un site intéressant: https://www.redblobgames.com/grids/hexagons/#distances * * Pour être concis, le code TMR lettre(colonne)-ligne correspond à une grille hexagonale en coordonnées "odd-q" * (lettre => col, ligne => row). * * Pour les calculs de distance, les coordonnées axiales sont beaucoup plus pratiques. */ export class TMRUtility { static init() { for (let coord in TMRMapping) { const tmr = TMRMapping[coord] tmr.coord = coord tmr.genre = TMRType[tmr.type].genre if (coord != FLEUVE_COORD) { tmr.oddq = TMRUtility.coordTMRToOddq(coord) } } let tmrByType = Misc.classify(Object.values(TMRMapping).filter(it => it.coord != FLEUVE_COORD)) for (const [type, list] of Object.entries(tmrByType)) { TMRType[type].list = list; } } /* -------------------------------------------- */ static verifyTMRCoord(coord) { return Grammar.equalsInsensitive(coord, FLEUVE_COORD) || TMRUtility.getTMR(coord); } /* -------------------------------------------- */ static getTMR(coord) { return coord == FLEUVE_COORD ? TMRMapping['D1'] : TMRMapping[coord]; } static isFleuve(coord) { return TMRMapping[coord]?.type == TMRType.fleuve.type } static getTMRLabel(coord) { return TMRUtility.getTMR(coord)?.label ?? (coord + ": case inconnue"); } static getTMRType(coord) { const tmr = TMRUtility.getTMR(coord); return Misc.upperFirst(TMRType[tmr.type].name); } static getTMRDescr(coord) { const tmr = TMRUtility.getTMR(coord); return Grammar.articleDetermine(tmr.type) + ' ' + tmr.label; } static findTMRLike(type, options = { inclusMauvaise: true }) { const choix = [...Object.values(TMRType)] if (options.inclusMauvaise) { choix.push({ name: 'Mauvaise', type: 'mauvaise'}); } const selection = Misc.findAllLike(type, choix) if (selection.length == 0) { ui.notifications.warn(`Un type de TMR doit être indiqué, '${type}' n'est pas trouvé dans ${choix}`); return undefined } if (selection.length > 1) { ui.notifications.warn(`Plusieurs types de TMR pourraient correspondre à '${type}': ${selection.map(it => it.name)}`); return undefined; } return selection[0] } static typeTmrName(type) { return Misc.upperFirst(TMRType[Grammar.toLowerCaseNoAccent(type)].name); } static buildSelectionTypesTMR(typesTMR) { typesTMR = typesTMR ?? []; return Object.values(TMRType).map(value => Misc.upperFirst(value.name)) .sort() .map(name => { return { name: name, selected: typesTMR.includes(name) } }); } static buildListTypesTMRSelection(selectionTMRs) { return selectionTMRs.filter(it => it.selected).map(it => it.name).join(" "); } static isCaseHumide(tmr) { return tmr.type == 'fleuve' || tmr.type == 'lac' || tmr.type == 'marais'; } /* -------------------------------------------- */ static deplacement(coordOrig, moveName) { const tmrMove = TMR_MOVE[moveName]; if (!tmrMove) { ui.notifications.error(`Le déplacement dans les TMR '${moveName}' est inconnu`) return coordOrig } const fromOddq = TMRUtility.coordTMRToOddq(coordOrig); const move = TMRUtility.getOddqMove(tmrMove, fromOddq); const toOddq = TMRUtility.addOddq(fromOddq, move); return TMRUtility.oddqToCoordTMR(toOddq); } static getOddqMove(tmrMove, oddq) { return oddq.col % 2 == 1 ? tmrMove.odd : tmrMove.even; } static async getDirectionPattern(oddq) { const tmrMove = await RdDDice.rollOneOf(Object.values(TMR_MOVE)); return TMRUtility.getOddqMove(tmrMove, oddq); } /* -------------------------------------------- */ static async deplaceTMRAleatoire(actor, coord) { const oddq = TMRUtility.coordTMRToOddq(coord); const direction = await TMRUtility.getDirectionPattern(oddq); const currentOddq = TMRUtility.addOddq(oddq, direction) if (TMRUtility.isOddqInTMR(currentOddq)) { // Sortie de carte ! Ré-insertion aléatoire return TMRUtility.getTMR(TMRUtility.oddqToCoordTMR(currentOddq)); } else { return await actor.reinsertionAleatoire('Sortie de carte'); } } /* -------------------------------------------- */ static getListTMR(terrain) { return TMRType[terrain].list; } static filterTMR(filter) { return Object.values(TMRMapping).filter(filter); } static getCasesType(type) { return TMRUtility.filterTMR(it => it.type == type).map(it => it.coord); } static findTMR(search) { return TMRUtility.filterTMR(it => Grammar.includesLowerCaseNoAccent(it.label, search) || 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)) } /* -------------------------------------------- */ /** Returns a list of case inside a given distance * */ static getTMRPortee(coord, portee) { let centerOddq = TMRUtility.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 (TMRUtility.isOddqInTMR(currentOddq)) { let dist = TMRUtility.distanceOddq(centerOddq, currentOddq); if (dist <= portee) { caseList.push(TMRUtility.oddqToCoordTMR(currentOddq)); // Inside the area } } } } return caseList; } /* -------------------------------------------- */ 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) ); } /* -------------------------------------------- */ static distanceCoordTMR(coord1, coord2) { let oddq1 = TMRUtility.coordTMRToOddq(coord1); let oddq2 = TMRUtility.coordTMRToOddq(coord2); return TMRUtility.distanceOddq(oddq1, oddq2); } /* -------------------------------------------- */ static distanceOddq(oddq1, oddq2) { const axial1 = TMRUtility.oddqToAxial(oddq1); const axial2 = TMRUtility.oddqToAxial(oddq2); return TMRUtility.distanceAxial(axial1, axial2); } static addOddq(move, oddq) { return { row: oddq.row + move.row, col: oddq.col + move.col } } 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 }; } }