import { SHOW_DICE, SYSTEM_RDD, SYSTEM_SOCKET_ID } 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_MOIS_PAR_AN = 12; const RDD_JOURS_PAR_MOIS = 28; const RDD_JOURS_PAR_AN = 336; //RDD_JOURS_PAR_MOIS * RDD_MOIS_PAR_AN; const RDD_HEURES_PAR_JOUR = 12; const RDD_MINUTES_PAR_HEURES = 120; 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" }, { key: "sirene", label: "Sirène", lettreFont: 'i', saison: "Printemps" }, { key: "faucon", label: "Faucon", lettreFont: 'f', saison: "Printemps" }, { key: "couronne", label: "Couronne", lettreFont: '', saison: "Eté" }, { key: "dragon", label: "Dragon", lettreFont: 'd', saison: "Eté" }, { key: "epees", label: "Epées", lettreFont: 'e', saison: "Eté" }, { key: "lyre", label: "Lyre", lettreFont: 'l', saison: "Automne" }, { key: "serpent", label: "Serpent", lettreFont: 's', saison: "Automne" }, { key: "poissonacrobate", label: "Poisson Acrobate", lettreFont: 'p', saison: "Automne" }, { key: "araignee", label: "Araignée", lettreFont: 'a', saison: "Hiver" }, { key: "roseau", label: "Roseau", lettreFont: 'r', saison: "Hiver" }, { key: "chateaudormant", label: "Château Dormant", lettreFont: 'c', saison: "Hiver" }, ] 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 iconeHeure(heure) { return `systems/foundryvtt-reve-de-dragon/icons/heures/hd${heure < 9 ? '0' : ''}${heure + 1}.svg`; } 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].icon = RdDTimestamp.iconeHeure(i); DEFINITION_HEURES[i].webp = DEFINITION_HEURES[i].icon.replace(".svg", ".webp"); } } /** * @param signe * @returns L'entrée de DEFINITION_HEURES correspondant au signe */ static definition(signe) { if (Number.isInteger(signe)) { return DEFINITION_HEURES[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 formulesDuree() { return FORMULES_DUREE } static formulesPeriode() { return FORMULES_PERIODE } static imgSigneHeure(heure) { return RdDTimestamp.imgSigne(RdDTimestamp.definition(heure)); } static imgSigne(signe) { return `${signe.label}` } 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 findHeure(heure) { heure = Grammar.toLowerCaseNoAccentNoSpace(heure); let parHeureOuLabel = DEFINITION_HEURES.filter(it => (it.heure) == parseInt(heure) % RDD_HEURES_PAR_JOUR || Grammar.toLowerCaseNoAccentNoSpace(it.label) == heure); 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 && worldTime.heureRdD) { // 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, 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 } /** * 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 }; } get annee() { return Math.floor(this.indexDate / RDD_JOURS_PAR_AN) } get mois() { return Math.floor((this.indexDate % RDD_JOURS_PAR_AN) / RDD_JOURS_PAR_MOIS) } get jour() { return (this.indexDate % RDD_JOURS_PAR_AN) % RDD_JOURS_PAR_MOIS } get heure() { return Math.floor(this.indexMinute / RDD_MINUTES_PAR_HEURES) } get minute() { return this.indexMinute % RDD_MINUTES_PAR_HEURES } get round() { return ROUNDS_PAR_MINUTE * (this.indexMinute - Math.floor(this.indexMinute)) } formatDate() { const jour = this.jour + 1; const mois = RdDTimestamp.definition(this.mois).label; const annee = this.annee ?? ''; return `${jour} ${mois}` + (annee ? ' ' + annee : ''); } 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 + (heure % RDD_HEURES_PAR_JOUR)) % (RDD_MINUTES_PAR_JOUR) }) } 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: minutes % RDD_MINUTES_PAR_HEURES } } }