import { SHOW_DICE, SYSTEM_RDD } from "../constants.js"; import { Grammar } from "../grammar.js"; import { Misc } from "../misc.js"; import { RdDDice } from "../rdd-dice.js"; export const WORLD_TIMESTAMP_SETTING = "calendrier"; const RDD_JOURS_PAR_AN = 336; //RDD_JOURS_PAR_MOIS * RDD_MOIS_PAR_AN; const RDD_MOIS_PAR_AN = 12; export const RDD_JOURS_PAR_MOIS = 28; export const RDD_HEURES_PAR_JOUR = 12; export const MAX_NOMBRE_ASTRAL = 12; export const RDD_MINUTES_PAR_HEURES = 120; export const RDD_MINUTES_PAR_JOUR = 1440; //RDD_HEURES_PAR_JOUR * RDD_MINUTES_PAR_HEURES; const ROUNDS_PAR_MINUTE = 10; const DEFINITION_HEURES = [ { key: "vaisseau", label: "Vaisseau", lettreFont: 'v', saison: "Printemps", darkness: 0.9 }, { key: "sirene", label: "Sirène", lettreFont: 'i', saison: "Printemps", darkness: 0.1 }, { key: "faucon", label: "Faucon", lettreFont: 'f', saison: "Printemps", darkness: 0 }, { key: "couronne", label: "Couronne", lettreFont: '', saison: "Eté", darkness: 0 }, { key: "dragon", label: "Dragon", lettreFont: 'd', saison: "Eté", darkness: 0 }, { key: "epees", label: "Epées", lettreFont: 'e', saison: "Eté", darkness: 0 }, { key: "lyre", label: "Lyre", lettreFont: 'l', saison: "Automne", darkness: 0.1 }, { key: "serpent", label: "Serpent", lettreFont: 's', saison: "Automne", darkness: 0.9 }, { key: "poissonacrobate", label: "Poisson Acrobate", lettreFont: 'p', saison: "Automne", darkness: 1 }, { key: "araignee", label: "Araignée", lettreFont: 'a', saison: "Hiver", darkness: 1 }, { key: "roseau", label: "Roseau", lettreFont: 'r', saison: "Hiver", darkness: 1 }, { key: "chateaudormant", label: "Château Dormant", lettreFont: 'c', saison: "Hiver", darkness: 1 }, ] const FORMULES_DUREE = [ { code: "", label: "", calcul: async (t, actor) => t.addJours(100 * RDD_JOURS_PAR_AN) }, { code: "jour", label: "1 jour", calcul: async (t, actor) => t.nouveauJour().addJours(1) }, { code: "1d7jours", label: "1d7 jour", calcul: async (t, actor) => t.nouveauJour().addJours(await RdDDice.rollTotal('1d7', { showDice: SHOW_DICE })) }, { code: "1ddr", label: "Un dé draconique jours", calcul: async (t, actor) => t.nouveauJour().addJours(await RdDDice.rollTotal('1dr+7', { showDice: SHOW_DICE })) }, { code: "hn", label: "Fin de l'Heure de Naissance", calcul: async (t, actor) => t.finHeure(actor.getHeureNaissance()) }, // { code: "1h", label: "Une heure", calcul: async (t, actor) => t.nouvelleHeure().addHeures(1) }, // { code: "12h", label: "12 heures", calcul: async (t, actor) => t.nouvelleHeure().addHeures(12) }, // { code: "chateaudormant", label: "Fin Chateau dormant", calcul: async (t, actor) => t.nouveauJour() }, // { code: "special", label: "Spéciale", calcul: async (t, actor) => t.addJours(100 * RDD_JOURS_PAR_AN) }, ] const FORMULES_PERIODE = [ { code: 'round', label: "Rounds", calcul: async (t, nombre) => t.addMinutes(nombre / 10) }, { code: 'minute', label: "Minutes", calcul: async (t, nombre) => t.addMinutes(nombre) }, { code: 'heure', label: "Heures", calcul: async (t, nombre) => t.addHeures(nombre) }, { code: 'jour', label: "Jours", calcul: async (t, nombre) => t.addJours(nombre) }, ] export class RdDTimestamp { static init() { game.settings.register(SYSTEM_RDD, WORLD_TIMESTAMP_SETTING, { name: WORLD_TIMESTAMP_SETTING, scope: "world", config: false, default: { indexDate: 0, indexMinute: 0 }, type: Object }); for (let i = 0; i < DEFINITION_HEURES.length; i++) { DEFINITION_HEURES[i].heure = i; DEFINITION_HEURES[i].hh = RdDTimestamp.hh(i); DEFINITION_HEURES[i].icon = RdDTimestamp.iconeHeure(i); DEFINITION_HEURES[i].webp = DEFINITION_HEURES[i].icon.replace(".svg", ".webp"); } } static hh(heure) { return heure < 9 ? `0${heure + 1}` : `${heure + 1}`; } static iconeHeure(heure) { return `systems/foundryvtt-reve-de-dragon/icons/heures/hd${RdDTimestamp.hh(heure)}.svg`; } static definitions() { return DEFINITION_HEURES } static formulesDuree() { return FORMULES_DUREE } static formulesPeriode() { return FORMULES_PERIODE } static heures() { return Misc.intArray(0, RDD_HEURES_PAR_JOUR) } /** * @param signe * @returns L'entrée de DEFINITION_HEURES correspondant au signe */ static definition(signe) { if (signe == undefined) { signe = 0 } if (Number.isInteger(signe)) { return DEFINITION_HEURES[Misc.modulo(signe, RDD_HEURES_PAR_JOUR)] } let definition = DEFINITION_HEURES.find(it => it.key == signe); if (!definition) { definition = Misc.findFirstLike(signe, DEFINITION_HEURES, { mapper: it => it.label, description: 'signe' }); } return definition } static imgSigneHeure(heure) { return RdDTimestamp.imgSigne(RdDTimestamp.definition(heure)); } static imgSigne(signe) { return signe == undefined ? '' : `` } static ajustementAstrologiqueHeure(hn, nbAstral, heure) { let ecart = Misc.modulo(hn + nbAstral - heure, RDD_HEURES_PAR_JOUR); switch (ecart) { case 0: return 4; case 4: case 8: return 2; case 6: return -4; case 3: case 9: return -2; } return 0; } static handleTimestampEditor(html, path, consumeTimestamp = async (path, timestamp) => { }) { const fields = { annee: html.find(`input[name="${path}.annee"]`), mois: html.find(`select[name="${path}.mois"]`), jourDuMois: html.find(`input[name="${path}.jourDuMois"]`), heure: html.find(`select[name="${path}.heure"]`), minute: html.find(`input[name="${path}.minute"]`) }; async function onChangeTimestamp(fields, path) { const annee = Number(fields.annee.val()); const mois = fields.mois.val(); const jour = Number(fields.jourDuMois.val()); const heure = fields.heure.val(); const minute = Number(fields.minute.val()); await consumeTimestamp(path, RdDTimestamp.timestamp(annee, mois, jour, heure, minute)); } fields.annee.change(async (event) => await onChangeTimestamp(fields, path)); fields.mois.change(async (event) => await onChangeTimestamp(fields, path)); fields.jourDuMois.change(async (event) => await onChangeTimestamp(fields, path)); fields.heure.change(async (event) => await onChangeTimestamp(fields, path)); fields.minute.change(async (event) => await onChangeTimestamp(fields, path)); } static defHeure(heure) { heure = Misc.modulo(heure, RDD_HEURES_PAR_JOUR); return DEFINITION_HEURES.find(it => it.heure == heure) } static findHeure(heure) { heure = Grammar.toLowerCaseNoAccentNoSpace(heure); let parHeureOuLabel = DEFINITION_HEURES.filter(it => Grammar.toLowerCaseNoAccentNoSpace(it.label) == heure || it.heure == Misc.modulo(parseInt(heure), RDD_HEURES_PAR_JOUR)); if (parHeureOuLabel.length == 1) { return parHeureOuLabel[0]; } let parLabelPartiel = DEFINITION_HEURES.filter(it => Grammar.toLowerCaseNoAccentNoSpace(it.label).includes(heure)); if (parLabelPartiel.length > 0) { parLabelPartiel.sort(Misc.ascending(h => h.label.length)); return parLabelPartiel[0]; } return undefined; } /** * @param indexDate: la date (depuis le jour 0) * @return la version formattée de la date */ static formatIndexDate(indexDate) { return new RdDTimestamp({ indexDate }).formatDate() } static splitIndexDate(indexDate) { const timestamp = new RdDTimestamp({ indexDate }); return { jour: timestamp.jour + 1, mois: RdDTimestamp.definition(timestamp.mois).key } } static getWorldTime() { let worldTime = game.settings.get(SYSTEM_RDD, WORLD_TIMESTAMP_SETTING); if (worldTime.indexJour != undefined && worldTime.heureRdD != undefined) { // Migration worldTime = { indexDate: worldTime.indexJour, indexMinute: worldTime.heureRdD * 120 + worldTime.minutesRelative }; RdDTimestamp.setWorldTime(new RdDTimestamp(worldTime)) } return new RdDTimestamp(worldTime); } static setWorldTime(timestamp) { game.settings.set(SYSTEM_RDD, WORLD_TIMESTAMP_SETTING, foundry.utils.duplicate(timestamp)); } /** construit un RdDTimestamp à partir de l'année/mois/jour/heure?/minute? */ static timestamp(annee, mois, jour, heure = 0, minute = 0) { mois = this.definition(mois)?.heure heure = this.definition(heure)?.heure return new RdDTimestamp({ indexDate: (jour - 1) + (mois + annee * RDD_MOIS_PAR_AN) * RDD_JOURS_PAR_MOIS, indexMinute: heure * RDD_MINUTES_PAR_HEURES + minute }) } /** * Constructeur d'un timestamp. * Selon les paramètres, l'objet construit se base su: * - le timestamp * - la date numérique + minute (dans la journée) * @param indexDate: la date à utiliser pour ce timestamp * @param indexMinute: la minute de la journée à utiliser pour ce timestamp * */ constructor({ indexDate, indexMinute = undefined }) { this.indexDate = indexDate this.indexMinute = indexMinute ?? 0 } get annee() { return Math.floor(this.indexDate / RDD_JOURS_PAR_AN) } get mois() { return Math.floor(Misc.modulo(this.indexDate, RDD_JOURS_PAR_AN) / RDD_JOURS_PAR_MOIS) } get nomMois() { return Math.floor(Misc.modulo(this.indexDate, RDD_JOURS_PAR_AN) / RDD_JOURS_PAR_MOIS) } get jour() { return Misc.modulo(Misc.modulo(this.indexDate, RDD_JOURS_PAR_AN), RDD_JOURS_PAR_MOIS) } get heure() { return Math.floor(this.indexMinute / RDD_MINUTES_PAR_HEURES) } get minute() { return Misc.modulo(this.indexMinute, RDD_MINUTES_PAR_HEURES) } get round() { return ROUNDS_PAR_MINUTE * (this.indexMinute - Math.floor(this.indexMinute)) } get angleHeure() { return this.indexMinute / RDD_MINUTES_PAR_JOUR * 360 - 45 } get angleMinute() { return this.indexMinute / RDD_MINUTES_PAR_HEURES * 360 + 45 } get darkness() { const darknessDebut = 100 * RdDTimestamp.definition(this.heure).darkness const darknessFin = 100 * RdDTimestamp.definition(this.heure + 1).darkness const darknessMinute = Math.round((darknessFin - darknessDebut) * this.minute / RDD_MINUTES_PAR_HEURES); return (darknessDebut + darknessMinute) / 100 } /** * Convertit le timestamp en une structure avec les informations utiles * pour afficher la date et l'heure */ toCalendrier() { return { timestamp: this, annee: this.annee, mois: RdDTimestamp.definition(this.mois), jour: this.jour, jourDuMois: this.jour + 1, heure: RdDTimestamp.definition(this.heure), minute: this.minute }; } formatDate() { const jour = this.jour + 1; const mois = RdDTimestamp.definition(this.mois).label; const annee = this.annee ?? ''; return `${jour} ${mois}` + (annee ? ' ' + annee : ''); } formatDateHeure() { return `${RdDTimestamp.definition(this.heure).label}, ${this.formatDate()}`; } nouveauJour() { return new RdDTimestamp({ indexDate: this.indexDate + 1, indexMinute: 0 }) } nouvelleHeure() { return this.heure >= RDD_HEURES_PAR_JOUR ? this.nouveauJour() : new RdDTimestamp({ indexDate: this.indexDate, indexMinute: (this.heure + 1) * RDD_MINUTES_PAR_HEURES }) } addJours(jours) { return jours == 0 ? this : new RdDTimestamp({ indexDate: this.indexDate + jours, indexMinute: this.indexMinute }) } addHeures(heures) { if (heures == 0) { return this } const heure = this.heure + heures; return new RdDTimestamp({ indexDate: this.indexDate + Math.floor(heure / RDD_HEURES_PAR_JOUR), indexMinute: this.indexMinute + Misc.modulo(heure, RDD_HEURES_PAR_JOUR) * RDD_MINUTES_PAR_HEURES }) } addMinutes(minutes) { if (minutes == 0) { return this; } const indexMinute = this.indexMinute + minutes; const jours = Math.floor(indexMinute / RDD_MINUTES_PAR_JOUR) return new RdDTimestamp({ indexDate: this.indexDate + jours, indexMinute: indexMinute - (jours * RDD_MINUTES_PAR_JOUR) }) } addPeriode(nombre, unite) { const formule = FORMULES_PERIODE.find(it => it.code == unite); if (formule) { return formule.calcul(this, nombre); } else { ui.notifications.info(`Pas de période pour ${unite ?? 'Aucune uinité définie'}`) } return this; } finHeure(heure) { return this.nouvelleHeure().addHeures((12 + heure - this.heure) % 12); } async appliquerDuree(duree, actor) { const formule = FORMULES_DUREE.find(it => it.code == duree) ?? FORMULES_DUREE.find(it => it.code == ""); return await formule.calcul(this, actor); } compare(timestamp) { let diff = this.indexDate - timestamp.indexDate if (diff == 0) { diff = this.indexMinute - timestamp.indexMinute } return diff < 0 ? -1 : diff > 0 ? 1 : 0; } difference(timestamp) { const jours = this.indexDate - timestamp.indexDate; const minutes = this.indexMinute - timestamp.indexMinute; return { jours: jours, heures: Math.floor(minutes / RDD_MINUTES_PAR_HEURES), minutes: Misc.modulo(minutes, RDD_MINUTES_PAR_HEURES) } } }