- dans la liste d'équipement, liste d'actions fournie par code - support d'actions multiples - listener générique Utilisation du mécanisme pour tous les items, standardisation de l'apparence des boutons d'items
		
			
				
	
	
		
			363 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			363 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| 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", article: "du ", label: "Vaisseau", lettreFont: 'v', saison: "Printemps", darkness: 0.9 },
 | |
|   { key: "sirene", article: "de la ", label: "Sirène", lettreFont: 'i', saison: "Printemps", darkness: 0.1 },
 | |
|   { key: "faucon", article: "du ", label: "Faucon", lettreFont: 'f', saison: "Printemps", darkness: 0 },
 | |
|   { key: "couronne", article: "de la ", label: "Couronne", lettreFont: '', saison: "Eté", darkness: 0 },
 | |
|   { key: "dragon", article: "du ", label: "Dragon", lettreFont: 'd', saison: "Eté", darkness: 0 },
 | |
|   { key: "epees", article: "des ", label: "Epées", lettreFont: 'e', saison: "Eté", darkness: 0 },
 | |
|   { key: "lyre", article: "de la ", label: "Lyre", lettreFont: 'l', saison: "Automne", darkness: 0.1 },
 | |
|   { key: "serpent", article: "du ", label: "Serpent", lettreFont: 's', saison: "Automne", darkness: 0.9 },
 | |
|   { key: "poissonacrobate", article: "du ", label: "Poisson Acrobate", lettreFont: 'p', saison: "Automne", darkness: 1 },
 | |
|   { key: "araignee", article: "de l'", label: "Araignée", lettreFont: 'a', saison: "Hiver", darkness: 1 },
 | |
|   { key: "roseau", article: "du ", label: "Roseau", lettreFont: 'r', saison: "Hiver", darkness: 1 },
 | |
|   { key: "chateaudormant", article: "du ", 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 initSettings() {
 | |
|     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");
 | |
|       DEFINITION_HEURES[i].avecArticle = DEFINITION_HEURES[i].article + DEFINITION_HEURES[i].label
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   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 ? '' : `<img class="img-signe-heure" src="${signe.webp}" data-tooltip="${signe.label}"/>`
 | |
|   }
 | |
| 
 | |
|   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) {
 | |
|     let filtered
 | |
|     if (Number.isInteger(Number(heure))) {
 | |
|       filtered = DEFINITION_HEURES.filter(it => it.heure == Misc.modulo(Number(heure) - 1, RDD_HEURES_PAR_JOUR))
 | |
|     }
 | |
|     else {
 | |
|       heure = Grammar.toLowerCaseNoAccentNoSpace(heure);
 | |
|       filtered = DEFINITION_HEURES.filter(it => Grammar.toLowerCaseNoAccentNoSpace(it.label) == heure || it.heure == Misc.modulo(parseInt(heure), RDD_HEURES_PAR_JOUR));
 | |
|     }
 | |
|     if (filtered.length == 1) {
 | |
|       return filtered[0]
 | |
|     }
 | |
|     filtered = DEFINITION_HEURES.filter(it => Grammar.toLowerCaseNoAccentNoSpace(it.label).includes(heure));
 | |
|     if (filtered.length > 0) {
 | |
|       filtered.sort(Misc.ascending(h => h.label.length));
 | |
|       return filtered[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);
 | |
|   }
 | |
| 
 | |
|   debutJournee() {
 | |
|     return RdDTimestamp.timestamp(this.annee, this.mois, this.jour)
 | |
|   }
 | |
| 
 | |
|   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)
 | |
|     }
 | |
|   }
 | |
| } |