diff --git a/.gitignore b/.gitignore index f47effc4..d7a00050 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,7 @@ todo.md /.vscode /ignored/ +/node_modules/ +/jsconfig.json +/package.json +/package-lock.json diff --git a/module/actor-entite-sheet.js b/module/actor-entite-sheet.js index 0b0e54c2..d7a96ce0 100644 --- a/module/actor-entite-sheet.js +++ b/module/actor-entite-sheet.js @@ -3,20 +3,20 @@ import { RdDActorSheet } from "./actor-sheet.js"; export class RdDActorEntiteSheet extends RdDActorSheet { /** @override */ - static get defaultOptions() { - return mergeObject(super.defaultOptions, { - classes: ["rdd", "sheet", "actor"], - template: "systems/foundryvtt-reve-de-dragon/templates/actor-entite-sheet.html", + static get defaultOptions() { + return mergeObject(super.defaultOptions, { + classes: ["rdd", "sheet", "actor"], + template: "systems/foundryvtt-reve-de-dragon/templates/actor-entite-sheet.html", width: 640, height: 720, - tabs: [{navSelector: ".sheet-tabs", contentSelector: ".sheet-body", initial: "carac"}], - dragDrop: [{dragSelector: ".item-list .item", dropSelector: undefined}] + tabs: [{ navSelector: ".sheet-tabs", contentSelector: ".sheet-body", initial: "carac" }], + dragDrop: [{ dragSelector: ".item-list .item", dropSelector: undefined }] }); } /* -------------------------------------------- */ /** @override */ - activateListeners(html) { + activateListeners(html) { super.activateListeners(html); // Everything below here is only needed if the sheet is editable @@ -24,17 +24,17 @@ export class RdDActorEntiteSheet extends RdDActorSheet { // On competence change this.html.find('.creature-carac').change(async event => { - let compName = event.currentTarget.attributes.compname.value; - this.actor.updateCreatureCompetence( compName, "carac_value", parseInt(event.target.value) ); - } ); + let compName = event.currentTarget.attributes.compname.value; + this.actor.updateCreatureCompetence(compName, "carac_value", parseInt(event.target.value)); + }); this.html.find('.creature-niveau').change(async event => { - let compName = event.currentTarget.attributes.compname.value; - this.actor.updateCreatureCompetence( compName, "niveau", parseInt(event.target.value) ); - } ); - this.html.find('.creature-dommages').change(async event => { - let compName = event.currentTarget.attributes.compname.value; - this.actor.updateCreatureCompetence( compName, "dommages", parseInt(event.target.value) ); - } ); + let compName = event.currentTarget.attributes.compname.value; + this.actor.updateCreatureCompetence(compName, "niveau", parseInt(event.target.value)); + }); + this.html.find('.creature-dommages').change(async event => { + let compName = event.currentTarget.attributes.compname.value; + this.actor.updateCreatureCompetence(compName, "dommages", parseInt(event.target.value)); + }); } } diff --git a/module/actor.js b/module/actor.js index dca482fb..dcf92e44 100644 --- a/module/actor.js +++ b/module/actor.js @@ -18,7 +18,7 @@ import { RdDItemArme } from "./item-arme.js"; import { RdDAlchimie } from "./rdd-alchimie.js"; import { STATUSES, StatusEffects } from "./settings/status-effects.js"; import { RdDItemCompetenceCreature } from "./item-competencecreature.js"; -import { RdDItemSigneDraconique } from "./item-signedraconique.js"; +import { RdDItemSigneDraconique } from "./item/item-signedraconique.js"; import { ReglesOptionelles } from "./settings/regles-optionelles.js"; import { EffetsDraconiques } from "./tmr/effets-draconiques.js"; import { Draconique } from "./tmr/draconique.js"; @@ -30,10 +30,11 @@ import { RdDPossession } from "./rdd-possession.js"; import { ENTITE_BLURETTE, ENTITE_INCARNE, ENTITE_NONINCARNE, SHOW_DICE, SYSTEM_RDD, SYSTEM_SOCKET_ID } from "./constants.js"; import { RdDConfirm } from "./rdd-confirm.js"; import { DialogValidationEncaissement } from "./dialog-validation-encaissement.js"; -import { RdDRencontre } from "./item-rencontre.js"; +import { RdDRencontre } from "./item/item-rencontre.js"; import { Targets } from "./targets.js"; import { DialogRepos } from "./dialog-repos.js"; import { RdDBaseActor } from "./actor/base-actor.js"; +import { RdDTimestamp } from "./rdd-timestamp.js"; const POSSESSION_SANS_DRACONIC = { img: 'systems/foundryvtt-reve-de-dragon/icons/entites/possession.webp', @@ -846,17 +847,21 @@ export class RdDActor extends RdDBaseActor { } /* -------------------------------------------- */ - async updateCreatureCompetence(idOrName, fieldName, compValue) { + async updateCreatureCompetence(idOrName, fieldName, value) { let competence = this.getCompetence(idOrName); if (competence) { - const update = { _id: competence.id } - if (fieldName == "niveau") - update['system.niveau'] = compValue; - else if (fieldName == "dommages") - update['system.dommages'] = compValue; - else - update['system.carac_value'] = compValue; - await this.updateEmbeddedDocuments('Item', [update]); // updates one EmbeddedEntity + function getPath(fieldName) { + switch (fieldName) { + case "niveau": return 'system.niveau'; + case "dommages": return 'system.dommages'; + case "carac_value": return 'system.carac_value'; + } + return undefined + } + const path = getPath(fieldName); + if (path){ + await this.updateEmbeddedDocuments('Item', [{ _id: competence.id, [path]: value }]); // updates one EmbeddedEntity + } } } @@ -933,7 +938,7 @@ export class RdDActor extends RdDBaseActor { let expLog = duplicate(this.system.experiencelog); expLog.push({ mode: Misc.upperFirst(modeXP), valeur: valeurXP, raison: Misc.upperFirst(raisonXP), - daterdd: game.system.rdd.calendrier.getDateFromIndex(), + daterdd: game.system.rdd.calendrier.dateCourante(), datereel: `${d.getDate()}/${d.getMonth() + 1}/${d.getFullYear()}` }); await this.update({ [`system.experiencelog`]: expLog }); @@ -2727,10 +2732,10 @@ export class RdDActor extends RdDBaseActor { /* -------------------------------------------- */ getHeureNaissance() { - if (this.isCreatureEntite()) { - return 0; + if (this.isPersonnage()) { + return this.system.heure; } - return this.system.heure; + return 0; } /* -------------------------------------------- */ @@ -2818,10 +2823,16 @@ export class RdDActor extends RdDBaseActor { /* -------------------------------------------- */ async ajouteNombreAstral(callData) { + const indexDate = Number.parseInt(callData.date); // Ajout du nombre astral const item = { name: "Nombre Astral", type: "nombreastral", system: - { value: callData.nbAstral, istrue: callData.isvalid, jourindex: Number(callData.date), jourlabel: game.system.rdd.calendrier.getDateFromIndex(Number(callData.date)) } + { + value: callData.nbAstral, + istrue: callData.isvalid, + jourindex: indexDate, + jourlabel: RdDTimestamp.formatIndexDate(indexDate) + } }; await this.createEmbeddedDocuments("Item", [item]); // Affichage Dialog @@ -2830,7 +2841,7 @@ export class RdDActor extends RdDBaseActor { async supprimerAnciensNombresAstraux() { const toDelete = this.listItems('nombreastral') - .filter(it => it.system.jourindex < game.system.rdd.calendrier.getCurrentDayIndex()) + .filter(it => game.system.rdd.calendrier.isAfterIndexDate(it.system.jourindex)) .map(it => it._id); await this.deleteEmbeddedDocuments("Item", toDelete); } @@ -3694,6 +3705,7 @@ export class RdDActor extends RdDBaseActor { await this.onCreateOwnedDraconique(item, options, id); break; } + await item.onCreateItemTemporel(this); await item.onCreateDecoupeComestible(this); } diff --git a/module/actor/base-actor.js b/module/actor/base-actor.js index 5740b70f..b360811c 100644 --- a/module/actor/base-actor.js +++ b/module/actor/base-actor.js @@ -1,7 +1,6 @@ import { ChatUtility } from "../chat-utility.js"; import { SYSTEM_SOCKET_ID } from "../constants.js"; import { Monnaie } from "../item-monnaie.js"; -import { RdDItem } from "../item.js"; import { Misc } from "../misc.js"; import { RdDAudio } from "../rdd-audio.js"; import { RdDUtility } from "../rdd-utility.js"; @@ -137,6 +136,11 @@ export class RdDBaseActor extends Actor { async onUpdateActor(update, options, actorId) { } + async onTimeChanging(oldTimestamp, newTimestamp) { + this.items.filter(it => it.isFinPeriode(oldTimestamp, newTimestamp)) + .forEach(async it => await it.onFinPeriodeTemporel(oldTimestamp, newTimestamp)) + } + /* -------------------------------------------- */ getFortune() { return Monnaie.getFortune(this.itemTypes['monnaie']); diff --git a/module/dialog-chronologie.js b/module/dialog-chronologie.js index c86dd519..374c1f9e 100644 --- a/module/dialog-chronologie.js +++ b/module/dialog-chronologie.js @@ -1,5 +1,6 @@ import { SYSTEM_RDD } from "./constants.js"; import { Grammar } from "./grammar.js"; +import { RdDTimestamp } from "./rdd-timestamp.js"; const LATEST_USED_JOURNAL_ID = "chronologie-dernier-journal"; @@ -16,16 +17,13 @@ export class DialogChronologie extends Dialog { }); } static async create() { - const dateRdD = game.system.rdd.calendrier.getCalendrier(); const dialogData = { auteur: game.user.name, isGM: game.user.isGM, information: "", journalId: game.settings.get(SYSTEM_RDD, LATEST_USED_JOURNAL_ID), journaux: game.journal.filter(it => it.testUserPermission(game.user, CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER)), - dateRdD: dateRdD, - jourRdD: dateRdD.jour +1, - heureRdD: game.system.rdd.calendrier.getCurrentHeure(), + timestamp: game.system.rdd.calendrier.timestamp, dateReel: DialogChronologie.getCurrentDateTime() }; const html = await renderTemplate("systems/foundryvtt-reve-de-dragon/templates/dialog-chronologie.html", dialogData); @@ -93,11 +91,12 @@ export class DialogChronologie extends Dialog { auteur: this.html.find("form.rdddialogchrono :input[name='auteur']").val(), information: this.html.find("form.rdddialogchrono :input[name='information']").val(), dateRdD: { - jour: this.html.find("form.rdddialogchrono :input[name='jourRdD']").val(), - moisRdD: this.html.find("form.rdddialogchrono :input[name='dateRdD.moisRdD.key']").val(), - annee: this.html.find("form.rdddialogchrono :input[name='dateRdD.annee']").val() + jour: this.html.find("form.rdddialogchrono :input[name='chronologie.jourDuMois']").val(), + mois: RdDTimestamp.definition(this.html.find("form.rdddialogchrono :input[name='chronologie.mois']").val()), + annee: this.html.find("form.rdddialogchrono :input[name='chronologie.annee']").val(), + heure: RdDTimestamp.definition(this.html.find("form.rdddialogchrono :input[name='chronologie.heure']").val()), + minute: this.html.find("form.rdddialogchrono :input[name='chronologie.minute']").val(), }, - heureRdD: this.html.find("form.rdddialogchrono :input[name='heureRdD']").val(), dateReel: this.html.find("form.rdddialogchrono :input[name='dateReel']").val().replace('T', ' ') } } diff --git a/module/dialog-create-signedraconique.js b/module/dialog-create-signedraconique.js index 86b98d45..fa012e9d 100644 --- a/module/dialog-create-signedraconique.js +++ b/module/dialog-create-signedraconique.js @@ -1,6 +1,6 @@ import { ChatUtility } from "./chat-utility.js"; import { HtmlUtility } from "./html-utility.js"; -import { RdDItemSigneDraconique } from "./item-signedraconique.js"; +import { RdDItemSigneDraconique } from "./item/item-signedraconique.js"; import { TMRUtility } from "./tmr-utility.js"; export class DialogCreateSigneDraconique extends Dialog { diff --git a/module/item-rencontre-sheet.js b/module/item-rencontre-sheet.js index 50a34834..c134300d 100644 --- a/module/item-rencontre-sheet.js +++ b/module/item-rencontre-sheet.js @@ -1,4 +1,4 @@ -import { RdDRencontre } from "./item-rencontre.js"; +import { RdDRencontre } from "./item/item-rencontre.js"; import { RdDItemSheet } from "./item-sheet.js"; export class RdDRencontreItemSheet extends RdDItemSheet { diff --git a/module/item-sheet.js b/module/item-sheet.js index b803de32..c5f96b64 100644 --- a/module/item-sheet.js +++ b/module/item-sheet.js @@ -10,6 +10,7 @@ import { SYSTEM_RDD } from "./constants.js"; import { RdDSheetUtility } from "./rdd-sheet-utility.js"; import { SystemCompendiums } from "./settings/system-compendiums.js"; import { Misc } from "./misc.js"; +import { RdDTimestamp } from "./rdd-timestamp.js"; /** * Extend the basic ItemSheet for RdD specific items @@ -135,11 +136,7 @@ export class RdDItemSheet extends ItemSheet { RdDGemme.calculDataDerivees(this.item); } if (this.item.type == 'potion') { - if (this.dateUpdated) { - formData.system.prdate = this.dateUpdated; - this.dateUpdated = undefined; - } - await RdDHerbes.updatePotionData(formData); + await RdDHerbes.addPotionFormData(formData); } if (formData.isOwned && this.item.type == 'herbe' && (formData.system.categorie == 'Soin' || formData.system.categorie == 'Repos')) { formData.isIngredientPotionBase = true; @@ -185,10 +182,12 @@ export class RdDItemSheet extends ItemSheet { } }) - this.html.find('.enchanteDate').change((event) => { - let jour = Number(this.html.find('[name="splitDate.day"]').val()); - let mois = this.html.find('[name="splitDate.month"]').val(); - this.dateUpdated = game.system.rdd.calendrier.getIndexFromDate(jour, mois); + this.html.find('.date-enchantement').change((event) => { + const jour = Number(this.html.find('input.date-enchantement[name="enchantement.jour"]').val()); + const mois = RdDTimestamp.definition(this.html.find('select.date-enchantement[name="enchantement.mois"]').val()); + const indexDate = game.system.rdd.calendrier.getIndexFromDate(jour, mois.heure); + this.item.update({ 'system.prdate': indexDate }); + console.warn(`Date d'enchantement modifiée ${jour}/${mois.heure}: ${indexDate}`) }); this.html.find('.creer-tache-livre').click((event) => this._getEventActor(event).creerTacheDepuisLivre(this.item)); @@ -213,6 +212,11 @@ export class RdDItemSheet extends ItemSheet { this.html.find('.item-vendre').click(async event => RdDSheetUtility.getItem(event, this.actor)?.proposerVente()); this.html.find('.item-montrer').click(async event => RdDSheetUtility.getItem(event, this.actor)?.postItemToChat()); this.html.find('.item-action').click(async event => RdDSheetUtility.getItem(event, this.actor)?.actionPrincipale(this.actor, async () => this.render(true))); + + const updateItemTimestamp = (path, timestamp) => this.item.update({ [path]: duplicate(timestamp) }) + + RdDTimestamp.handleTimestampEditor(this.html, 'system.temporel.debut', updateItemTimestamp); + RdDTimestamp.handleTimestampEditor(this.html, 'system.temporel.fin', updateItemTimestamp); } _getEventActor(event) { @@ -221,6 +225,7 @@ export class RdDItemSheet extends ItemSheet { return actor; } + /* -------------------------------------------- */ async _onSelectCategorie(event) { event.preventDefault(); diff --git a/module/item-signedraconique-sheet.js b/module/item-signedraconique-sheet.js index 119a446c..6052079a 100644 --- a/module/item-signedraconique-sheet.js +++ b/module/item-signedraconique-sheet.js @@ -1,5 +1,5 @@ import { RdDItemSheet } from "./item-sheet.js"; -import { RdDItemSigneDraconique } from "./item-signedraconique.js"; +import { RdDItemSigneDraconique } from "./item/item-signedraconique.js"; import { TMRUtility } from "./tmr-utility.js"; /** diff --git a/module/item.js b/module/item.js index 03edc4fa..729e9385 100644 --- a/module/item.js +++ b/module/item.js @@ -2,6 +2,7 @@ import { DialogItemVente } from "./dialog-item-vente.js"; import { Grammar } from "./grammar.js"; import { Misc } from "./misc.js"; import { RdDHerbes } from "./rdd-herbes.js"; +import { RdDTimestamp } from "./rdd-timestamp.js"; import { RdDUtility } from "./rdd-utility.js"; import { SystemCompendiums } from "./settings/system-compendiums.js"; @@ -30,6 +31,7 @@ const typesObjetsDraconiques = ["queue", "ombre", "souffle", "tete", "signedraco const typesObjetsConnaissance = ["meditation", "recettealchimique", "sort"] const typesObjetsEffet = ["possession", "poison", "maladie"] const typesObjetsCompetence = ["competence", "competencecreature"] +const typesObjetsTemporels = ["poison", "maladie", "queue", "ombre", "souffle", "signedraconique", "rencontre"] const encBrin = 0.00005; // un brin = 1 décigramme = 1/10g = 1/10000kg = 1/20000 enc const encPepin = 0.0007; /* un pépin de gemme = 1/10 cm3 = 1/1000 l = 3.5/1000 kg = 7/2000 kg = 7/1000 enc densité 3.5 (~2.3 à 4, parfois plus) -- https://www.juwelo.fr/guide-des-pierres/faits-et-chiffres/ @@ -150,26 +152,20 @@ export class RdDItem extends Item { isCompetenceCreature() { return this.type == 'competencecreature' } isConteneur() { return this.type == 'conteneur'; } isMonnaie() { return this.type == 'monnaie'; } + isPotion() { return this.type == 'potion'; } isNourritureBoisson() { return this.type == 'nourritureboisson'; } isService() { return this.type == 'service'; } - isCompetence() { - return typesObjetsCompetence.includes(this.type) - } - isInventaire(mode = 'materiel') { - return RdDItem.getItemTypesInventaire(mode).includes(this.type); - } - isOeuvre() { - return typesObjetsOeuvres.includes(this.type) - } - isDraconique() { - return typesObjetsDraconiques.includes(this.type) - } - isEffet() { - return typesObjetsEffet.includes(this.type) - } - isConnaissance() { - return typesObjetsConnaissance.includes(this.type) - } + + isCompetence() { return typesObjetsCompetence.includes(this.type) } + isTemporel() { return typesObjetsTemporels.includes(this.type) } + isOeuvre() { return typesObjetsOeuvres.includes(this.type) } + isDraconique() { return typesObjetsDraconiques.includes(this.type) } + isEffet() { return typesObjetsEffet.includes(this.type) } + isConnaissance() { return typesObjetsConnaissance.includes(this.type) } + + isInventaire(mode = 'materiel') { return RdDItem.getItemTypesInventaire(mode).includes(this.type); } + isAlcool() { return this.isNourritureBoisson() && this.system.boisson && this.system.alcoolise; } + isHerbeAPotion() { return this.type == 'herbe' && (this.system.categorie == 'Soin' || this.system.categorie == 'Repos'); } getItemGroup() { if (this.isInventaire()) return "equipement"; @@ -181,16 +177,43 @@ export class RdDItem extends Item { return "autres"; } - isConteneurNonVide() { - return this.isConteneur() && (this.system.contenu?.length ?? 0) > 0; + isConteneurNonVide() { return this.isConteneur() && (this.system.contenu?.length ?? 0) > 0; } + isConteneurVide() { return this.isConteneur() && (this.system.contenu?.length ?? 0) == 0; } + isVideOuNonConteneur() { return !this.isConteneur() || (this.system.contenu?.length ?? 0) == 0; } + + isFinPeriode(oldTimestamp, newTimestamp) { + if (!this.isTemporel()) { + return false; + } + const finPeriode = new RdDTimestamp(this.system.temporel.fin); + return oldTimestamp.compare(finPeriode) < 0 && finPeriode.compare(newTimestamp) <= 0 } - isConteneurVide() { - return this.isConteneur() && (this.system.contenu?.length ?? 0) == 0; + async onCreateItemTemporel(actor) { + if (this.isTemporel()) { + const timestampDebut = game.system.rdd.calendrier.timestamp; + const timestampFin = await this.calculerFinPeriodeTemporel(timestampDebut); + await actor.updateEmbeddedDocuments('Item', [{ + _id: this.id, + 'system.temporel.debut': duplicate(timestampDebut), + 'system.temporel.fin': duplicate(timestampFin), + }]) + } } - isVideOuNonConteneur() { - return !this.isConteneur() || (this.system.contenu?.length ?? 0) == 0; + async calculerFinPeriodeTemporel(timestampDebut) { + return timestampDebut; + } + + async onFinPeriodeTemporel(oldTimestamp, newTimestamp) { + if (this.isTemporel() && this.actor) { + await this.onFinPeriode(oldTimestamp, newTimestamp); + } + } + + async onFinPeriode(oldTimestamp, newTimestamp) { + console.log(`${this.actor.name}: l'objet ${this.name} a expiré et été supprimé`); + await this.actor?.deleteEmbeddedDocuments('Item', [this.id]); } isComestible() { @@ -204,16 +227,6 @@ export class RdDItem extends Item { return ''; } - isAlcool() { - return this.isNourritureBoisson() && this.system.boisson && this.system.alcoolise; - } - - isHerbeAPotion() { - return this.type == 'herbe' && (this.system.categorie == 'Soin' || this.system.categorie == 'Repos'); - } - isPotion() { - return this.type == 'potion'; - } isCristalAlchimique() { return this.type == 'objet' && Grammar.toLowerCaseNoAccent(this.name) == 'cristal alchimique' && this.system.quantite > 0; } @@ -222,6 +235,10 @@ export class RdDItem extends Item { return this.system.magique } + isItemCommerce() { + return this.parent?.type == 'commerce'; + } + getQuantite() { return this.isService() ? undefined : Math.round(this.system.quantite ?? 0) } @@ -259,10 +276,6 @@ export class RdDItem extends Item { return this.system.cout ?? 0 } - isItemCommerce() { - return this.parent?.type == 'commerce'; - } - calculerPrixCommercant() { if (this.isItemCommerce()) { // appliquer le pourcentage diff --git a/module/item-rencontre.js b/module/item/item-rencontre.js similarity index 93% rename from module/item-rencontre.js rename to module/item/item-rencontre.js index 519b5129..bcd0ac7b 100644 --- a/module/item-rencontre.js +++ b/module/item/item-rencontre.js @@ -1,4 +1,5 @@ -import { EffetsRencontre } from "./effets-rencontres.js"; +import { EffetsRencontre } from "../effets-rencontres.js"; +import { RdDItem } from "../item.js"; const tableEffets = [ { code: "messager", resultat: "succes", description: "Envoie un message à (force) cases", method: EffetsRencontre.messager }, @@ -36,7 +37,11 @@ const tableEffets = [ // { code: "epart-souffle", resultat: "echec", description: "Souffle de dragon sur échec particulier" }, ]; -export class RdDRencontre { +export class RdDRencontre extends RdDItem { + + static get defaultIcon() { + return "systems/foundryvtt-reve-de-dragon/icons/tete_dragon.webp"; + } static getEffetsSucces() { return RdDRencontre.getEffets("succes"); } static getEffetsEchec() { return RdDRencontre.getEffets("echec"); } @@ -68,4 +73,8 @@ export class RdDRencontre { } } + async calculerFinPeriodeTemporel(debut) { + return await debut.nouvelleHeure().addHeures(12); + } + } diff --git a/module/item-service.js b/module/item/item-service.js similarity index 92% rename from module/item-service.js rename to module/item/item-service.js index 95e192b7..5c57f90c 100644 --- a/module/item-service.js +++ b/module/item/item-service.js @@ -1,4 +1,4 @@ -import { RdDItem } from "./item.js"; +import { RdDItem } from "../item.js"; export class RdDItemService extends RdDItem { diff --git a/module/item-signedraconique.js b/module/item/item-signedraconique.js similarity index 82% rename from module/item-signedraconique.js rename to module/item/item-signedraconique.js index 8bed1ed0..31eaf436 100644 --- a/module/item-signedraconique.js +++ b/module/item/item-signedraconique.js @@ -1,8 +1,9 @@ -import { defaultItemImg } from "./item.js"; -import { Misc } from "./misc.js"; -import { RdDDice } from "./rdd-dice.js"; -import { RdDRollTables } from "./rdd-rolltables.js"; -import { TMRType, TMRUtility } from "./tmr-utility.js"; +import { RdDItem, defaultItemImg } from "../item.js"; +import { Misc } from "../misc.js"; +import { RdDDice } from "../rdd-dice.js"; +import { RdDRollTables } from "../rdd-rolltables.js"; +import { RdDTimestamp } from "../rdd-timestamp.js"; +import { TMRType, TMRUtility } from "../tmr-utility.js"; const tableSignesIndicatifs = [ { rarete: "Très facile", difficulte: 0, xp: 6, nbCases: 14 }, @@ -15,7 +16,17 @@ const tableSignesIndicatifs = [ const DIFFICULTE_LECTURE_SIGNE_MANQUE = +11; -export class RdDItemSigneDraconique { +export class RdDItemSigneDraconique extends RdDItem { + + static get defaultIcon() { + return "systems/foundryvtt-reve-de-dragon/icons/tmr/signe_draconique.webp"; + } + + + async calculerFinPeriodeTemporel(debut) { + // TODO + return RdDTimestamp.formulesDuree().find(it => it.code == "").calcul(debut, this.actor); + } static prepareSigneDraconiqueMeditation(meditation, rolled) { return { @@ -96,6 +107,6 @@ export class RdDItemSigneDraconique { static async randomSigneDescription() { return await RdDRollTables.drawTextFromRollTable("Signes draconiques", false); - } + } } \ No newline at end of file diff --git a/module/item/maladie.js b/module/item/maladie.js new file mode 100644 index 00000000..d7f15c93 --- /dev/null +++ b/module/item/maladie.js @@ -0,0 +1,47 @@ +import { RdDItem } from "../item.js"; +import { Misc } from "../misc.js"; +import { RdDTimestamp } from "../rdd-timestamp.js"; + +export class RdDItemMaladie extends RdDItem { + + static get defaultIcon() { + return "systems/foundryvtt-reve-de-dragon/icons/maladies_venins/maladie.webp"; + } + + async calculerFinPeriodeTemporel(debut) { + return await debut.addPeriode(this.system.periode.nombre, this.system.periode.unite); + } + + async onFinPeriode(oldTimestamp, newTimestamp) { + await RdDItemMaladie.notifierMaladiePoison(this, oldTimestamp, newTimestamp) + } + + static async notifierMaladiePoison(mal, oldTimestamp, newTimestamp) { + if (mal.actor) { + const souffrance = mal.system.identifie + ? `de ${mal.name}` + : `d'un mal inconnu` + ChatMessage.create({ content: `${mal.actor.name} souffre ${souffrance} (${Misc.typeName('Item', mal.type)}): vérifiez que les effets ne se sont pas aggravés !` }); + mal.postItemToChat('gmroll'); + await RdDItemMaladie.prolongerPeriode(mal,oldTimestamp, newTimestamp); + } + } + + static async prolongerPeriode(mal, oldTimestamp, newTimestamp) { + if (mal.actor) { + // TODO: déterminer le nombre de périodes écoulées + console.log(`${mal.actor.name}: le mal ${mal.name} a atteint la fin de sa période et été prolongé`); + const current = newTimestamp; + const finPeriode = new RdDTimestamp(mal.system.temporel.fin) + const periodeSuivante = (finPeriode.compare(current) > 0 ? finPeriode : current); + const timestampFin = await mal.calculerFinPeriodeTemporel(periodeSuivante); + + await mal.actor.updateEmbeddedDocuments('Item', [{ + _id: mal.id, + 'system.temporel.fin': duplicate(timestampFin), + }]) + } + } + + +} \ No newline at end of file diff --git a/module/item/ombre.js b/module/item/ombre.js new file mode 100644 index 00000000..b543901a --- /dev/null +++ b/module/item/ombre.js @@ -0,0 +1,11 @@ +import { RdDItem } from "../item.js"; + +export class RdDItemOmbre extends RdDItem { + static get defaultIcon() { + return "systems/foundryvtt-reve-de-dragon/icons/queue_dragon.webp"; + } + + async calculerFinPeriodeTemporel(debut) { + return await debut.appliquerDuree(this.system.duree, this.parent); + } +} diff --git a/module/item/poison.js b/module/item/poison.js new file mode 100644 index 00000000..75d7bf83 --- /dev/null +++ b/module/item/poison.js @@ -0,0 +1,17 @@ +import { RdDItem } from "../item.js"; +import { RdDItemMaladie } from "./maladie.js"; + +export class RdDItemPoison extends RdDItem { + + static get defaultIcon() { + return "systems/foundryvtt-reve-de-dragon/icons/maladies_venins/venin.webp"; + } + + async calculerFinPeriodeTemporel(debut) { + return await debut.addPeriode(this.system.periode.nombre, this.system.periode.unite) ; + } + + async onFinPeriode(oldTimestamp, newTimestamp) { + RdDItemMaladie.notifierMaladiePoison(this, oldTimestamp, newTimestamp) + } +} \ No newline at end of file diff --git a/module/item/queue.js b/module/item/queue.js new file mode 100644 index 00000000..c21d1dbd --- /dev/null +++ b/module/item/queue.js @@ -0,0 +1,13 @@ +import { RdDItem } from "../item.js"; + +export class RdDItemQueue extends RdDItem { + + static get defaultIcon() { + return "systems/foundryvtt-reve-de-dragon/icons/queue_dragon.webp"; + } + + async calculerFinPeriodeTemporel(debut) { + return await debut.appliquerDuree(this.system.duree, this.parent); + } + +} \ No newline at end of file diff --git a/module/item/souffle.js b/module/item/souffle.js new file mode 100644 index 00000000..a0ff90c9 --- /dev/null +++ b/module/item/souffle.js @@ -0,0 +1,13 @@ +import { RdDItem } from "../item.js"; + +export class RdDItemSouffle extends RdDItem { + + static get defaultIcon() { + return "systems/foundryvtt-reve-de-dragon/icons/souffle_dragon.webp"; + } + + async calculerFinPeriodeTemporel(debut) { + return await debut.appliquerDuree(this.system.duree, this.parent); + } + +} \ No newline at end of file diff --git a/module/migrations.js b/module/migrations.js index 9a553f80..b8c0bbd5 100644 --- a/module/migrations.js +++ b/module/migrations.js @@ -4,6 +4,7 @@ import { Environnement } from "./environnement.js"; import { Grammar } from "./grammar.js"; import { Monnaie } from "./item-monnaie.js"; import { RdDItem } from "./item.js"; +import { RdDTimestamp } from "./rdd-timestamp.js"; class Migration { get code() { return "sample"; } @@ -368,6 +369,51 @@ class _10_4_6_ServicesEnCommerces extends Migration { return itemToCreate; } } +class _10_5_0_UpdatePeriodicite extends Migration { + get code() { return "migration-periodicite-poisons-maladies"; } + get version() { return "10.5.0"; } + + async migrate() { + await this.applyItemsUpdates(items => this._updatePeriodicite(items)); + } + + _updatePeriodicite(items) { + return items.filter(it => ['poison', 'maladie'].includes(it.type)) + .filter(it => it.system.periodicite != "") + .map(it => { + let [incubation, periodicite] = this.getPeriodicite(it); + const periode = periodicite.split(' '); + let unite = periode.length == 2 + ? RdDTimestamp.formulesPeriode().find(it => Grammar.includesLowerCaseNoAccent(periode[1], it.code))?.code + : undefined + if (unite && Number(periode[0])) { + return { + _id: it.id, + 'system.periodicite': undefined, + 'system.incubation': incubation, + 'system.periode.nombre': Number.parseInt(periode[0]), + 'system.periode.unite': unite + }; + } + else { + return { + _id: it.id, + 'system.periodicite': undefined, + 'system.incubation': it.system.periodicite + }; + } + }).filter(it => it != undefined); + } + + getPeriodicite(it) { + let p = it.system.periodicite.split(/[\/\\]/); + switch (p.length) { + case 2: return [p[0].trim(), p[1].trim()]; + case 1: return ["", it.system.periodicite.trim()]; + default: return [it.system.periodicite.trim(), ""]; + } + } +} export class Migrations { static getMigrations() { @@ -383,6 +429,7 @@ export class Migrations { new _10_3_0_FrequenceEnvironnement(), new _10_3_17_Monnaies(), new _10_4_6_ServicesEnCommerces(), + new _10_5_0_UpdatePeriodicite(), ]; } diff --git a/module/rdd-astrologie-editeur.js b/module/rdd-astrologie-editeur.js index b2dd6260..d681af82 100644 --- a/module/rdd-astrologie-editeur.js +++ b/module/rdd-astrologie-editeur.js @@ -3,23 +3,28 @@ * Extend the base Dialog entity by defining a custom window to perform roll. * @extends {Dialog} */ - export class RdDAstrologieEditeur extends Dialog { +export class RdDAstrologieEditeur extends Dialog { /* -------------------------------------------- */ constructor(html, calendrier, calendrierData) { let myButtons = { - resetButton: { label: "Re-tirer les nombres astraux", callback: html => this.resetNombreAstraux() }, - saveButton: { label: "Fermer", callback: html => this.fillData() } - }; + resetButton: { label: "Re-tirer les nombres astraux", callback: html => this.resetNombreAstraux() }, + saveButton: { label: "Fermer", callback: html => this.fillData() } + }; // Common conf let dialogConf = { content: html, title: "Editeur d'Astrologie", buttons: myButtons, default: "saveButton" }; - let dialogOptions = { classes: ["rdd-roll-dialog"], width: 600, height: 300, 'z-index': 99999 } + let dialogOptions = { + classes: ["rdd-roll-dialog"], width: 600, + height: 'fit-content', + 'max-height': 800, + 'z-index': 99999 + } super(dialogConf, dialogOptions) - + this.calendrier = calendrier; - this.updateData( calendrierData ); + this.updateData(calendrierData); } activateListeners(html) { @@ -28,7 +33,7 @@ } - /* -------------------------------------------- */ + /* -------------------------------------------- */ async resetNombreAstraux() { game.system.rdd.calendrier.resetNombreAstral(); await game.system.rdd.calendrier.rebuildListeNombreAstral(); @@ -36,12 +41,12 @@ game.system.rdd.calendrier.showAstrologieEditor(); } - /* -------------------------------------------- */ - fillData( ) { + /* -------------------------------------------- */ + fillData() { } /* -------------------------------------------- */ - updateData( calendrierData ) { + updateData(calendrierData) { this.calendrierData = duplicate(calendrierData); } diff --git a/module/rdd-calendrier-editeur.js b/module/rdd-calendrier-editeur.js index d097a1d3..d6347ffa 100644 --- a/module/rdd-calendrier-editeur.js +++ b/module/rdd-calendrier-editeur.js @@ -1,4 +1,4 @@ -import { Misc } from "./misc.js"; +import { RdDTimestamp } from "./rdd-timestamp.js"; /** * Extend the base Dialog entity by defining a custom window to perform roll. @@ -12,7 +12,7 @@ export class RdDCalendrierEditeur extends Dialog { content: html, title: "Editeur de date/heure", buttons: { - save: { label: "Enregistrer", callback: html => this.fillData() } + save: { label: "Enregistrer", callback: html => this.saveCalendrier() } }, default: "save" }; @@ -26,22 +26,37 @@ export class RdDCalendrierEditeur extends Dialog { activateListeners(html) { super.activateListeners(html); this.html = html; - this.html.find("input[name='nomMois']").val(this.calendrierData.moisKey); - this.html.find("select[name='nomHeure']").val(this.calendrierData.heureKey); - this.html.find("select[name='jourMois']").val(this.calendrierData.jourMois); - this.html.find("select[name='minutesRelative']").val(this.calendrierData.minutesRelative); - this.html.find("select[name='annee']").val(this.calendrierData.annee); + + this.html.find("input[name='calendar.annee']").val(this.calendrierData.annee); + this.html.find("select[name='calendar.mois']").val(this.calendrierData.mois.key); + this.html.find("select[name='calendar.heure']").val(this.calendrierData.heure.key); + RdDCalendrierEditeur.setLimited(this.html.find("input[name='calendar.jourDuMois']"), this.calendrierData.jourDuMois, 1, 28); + RdDCalendrierEditeur.setLimited(this.html.find("input[name='calendar.minute']"), this.calendrierData.minute, 0, 119); + } + + static setLimited(input, init, min, max) { + input.val(init); + input.change(event => { + const val = Number.parseInt(input.val()); + if (val < min) { + input.val(min); + } + if (val > max) { + input.val(max); + } + }); + } /* -------------------------------------------- */ - fillData() { - this.calendrierData.annee = this.html.find("input[name='annee']").val(); - this.calendrierData.moisKey = this.html.find("select[name='nomMois']").val(); - this.calendrierData.heureKey = this.html.find("select[name='nomHeure']").val(); - this.calendrierData.jourMois = this.html.find("select[name='jourMois']").val(); - this.calendrierData.minutesRelative = this.html.find("select[name='minutesRelative']").val(); + saveCalendrier() { + const annee = Number.parseInt(this.html.find("input[name='calendar.annee']").val()); + const mois = this.html.find("select[name='calendar.mois']").val(); + const jour = Number.parseInt(this.html.find("input[name='calendar.jourDuMois']").val()); + const heure = this.html.find("select[name='calendar.heure']").val(); + const minute = Number.parseInt(this.html.find("input[name='calendar.minute']").val()); - this.calendrier.saveEditeur(this.calendrierData) + this.calendrier.setNewTimestamp(RdDTimestamp.timestamp(annee, mois, jour, heure, minute)) } /* -------------------------------------------- */ diff --git a/module/rdd-calendrier.js b/module/rdd-calendrier.js index a4996963..32bb13ea 100644 --- a/module/rdd-calendrier.js +++ b/module/rdd-calendrier.js @@ -1,44 +1,17 @@ -/* -------------------------------------------- */ import { RdDCalendrierEditeur } from "./rdd-calendrier-editeur.js"; import { RdDAstrologieEditeur } from "./rdd-astrologie-editeur.js"; -import { HtmlUtility } from "./html-utility.js"; import { RdDResolutionTable } from "./rdd-resolution-table.js"; import { RdDUtility } from "./rdd-utility.js"; -import { Grammar } from "./grammar.js"; import { RdDDice } from "./rdd-dice.js"; import { Misc } from "./misc.js"; import { HIDE_DICE, SHOW_DICE, SYSTEM_RDD, SYSTEM_SOCKET_ID } from "./constants.js"; import { DialogChronologie } from "./dialog-chronologie.js"; +import { RdDTimestamp, WORLD_TIMESTAMP_SETTING } from "./rdd-timestamp.js"; -/* -------------------------------------------- */ -const dossierIconesHeures = 'systems/foundryvtt-reve-de-dragon/icons/heures/' -const heuresList = ["vaisseau", "sirene", "faucon", "couronne", "dragon", "epees", "lyre", "serpent", "poissonacrobate", "araignee", "roseau", "chateaudormant"]; -const heuresDef = { - "vaisseau": { key: "vaisseau", label: "Vaisseau", lettreFont: 'v', saison: "printemps", heure: 0, icon: 'hd01.svg' }, - "sirene": { key: "sirene", label: "Sirène", lettreFont: 'i', saison: "printemps", heure: 1, icon: 'hd02.svg' }, - "faucon": { key: "faucon", label: "Faucon", lettreFont: 'f', saison: "printemps", heure: 2, icon: 'hd03.svg' }, - "couronne": { key: "couronne", label: "Couronne", lettreFont: '', saison: "ete", heure: 3, icon: 'hd04.svg' }, - "dragon": { key: "dragon", label: "Dragon", lettreFont: 'd', saison: "ete", heure: 4, icon: 'hd05.svg' }, - "epees": { key: "epees", label: "Epées", lettreFont: 'e', saison: "ete", heure: 5, icon: 'hd06.svg' }, - "lyre": { key: "lyre", label: "Lyre", lettreFont: 'l', saison: "automne", heure: 6, icon: 'hd07.svg' }, - "serpent": { key: "serpent", label: "Serpent", lettreFont: 's', saison: "automne", heure: 7, icon: 'hd08.svg' }, - "poissonacrobate": { key: "poissonacrobate", label: "Poisson Acrobate", lettreFont: 'p', saison: "automne", heure: 8, icon: 'hd09.svg' }, - "araignee": { key: "araignee", label: "Araignée", lettreFont: 'a', saison: "hiver", heure: 9, icon: 'hd10.svg' }, - "roseau": { key: "roseau", label: "Roseau", lettreFont: 'r', saison: "hiver", heure: 10, icon: 'hd11.svg' }, - "chateaudormant": { key: "chateaudormant", label: "Château Dormant", lettreFont: 'c', saison: "hiver", heure: 11, icon: 'hd12.svg' } -}; -const saisonsDef = { - "printemps": { label: "Printemps" }, - "ete": { label: "Eté" }, - "automne": { label: "Automne" }, - "hiver": { label: "Hiver" } -}; - -const RDD_MOIS_PAR_AN = 12; -export const RDD_JOUR_PAR_MOIS = 28; +const RDD_JOUR_PAR_MOIS = 28; const RDD_HEURES_PAR_JOUR = 12; -const RDD_MINUTES_PAR_HEURES = 120; const MAX_NOMBRE_ASTRAL = 12; +const JOURS_DU_MOIS = Array(RDD_JOUR_PAR_MOIS).fill().map((item, index) => 1 + index); /* -------------------------------------------- */ export class RdDCalendrier extends Application { @@ -55,57 +28,6 @@ export class RdDCalendrier extends Application { return { top: 200, left: 200 }; } - static getDefSigne(chiffre) { - chiffre = chiffre % RDD_MOIS_PAR_AN; - return Object.values(heuresDef).find(h => h.heure == chiffre); - } - - static getSigneAs(key, value) { - const heure = (typeof value == 'string' || typeof value == 'number') && Number.isInteger(Number(value)) - ? Number(value) - : (typeof value == 'string') ? RdDCalendrier.getChiffreFromSigne(value) - : undefined - - if (heure != undefined && ['key', 'label', 'lettreFont', 'saison', 'heure', 'icon'].includes(key)) { - return RdDCalendrier.getDefSigne(heure)[key] - } - if (heure != undefined && ['webp'].includes(key)) { - return RdDCalendrier.getDefSigne(heure)['icon'].replace('svg', 'webp'); - } - console.error(`Appel à getSigneAs('${key}', ${value}) avec une clé/heure incorrects`); - return value; - - } - static getChiffreFromSigne(signe) { - return heuresList.indexOf(signe); - } - - static createCalendrierInitial() { - return { - heureRdD: 0, - minutesRelative: 0, - indexJour: 0, - annee: 0, - moisRdD: 0, - moisLabel: heuresDef["vaisseau"].label, - jour: 0 - } - } - - getCalendrier(index) { - index = index ?? this.getCurrentDayIndex(); - const mois = Math.floor(index / RDD_JOUR_PAR_MOIS) % RDD_MOIS_PAR_AN; - return { - heureRdD: 0, // Index dans heuresList / heuresDef[x].heure - minutesRelative: 0, - indexJour: index, - annee: Math.floor(index / (RDD_JOUR_PAR_MOIS * RDD_MOIS_PAR_AN)), - moisRdD: RdDCalendrier.getDefSigne(mois).heure, - moisLabel: RdDCalendrier.getDefSigne(mois).label, - jour: (index % RDD_JOUR_PAR_MOIS) // Le calendrier stocke le jour en 0-27, mais en 1-28 à l'affichage - } - } - constructor() { super(); // position @@ -114,19 +36,23 @@ export class RdDCalendrier extends Application { this.calendrierPos = RdDCalendrier.createCalendrierPos(); game.settings.set(SYSTEM_RDD, "calendrier-pos", this.calendrierPos); } - // Calendrier - this.calendrier = duplicate(game.settings.get(SYSTEM_RDD, "calendrier") ?? RdDCalendrier.createCalendrierInitial()); - this.calendrier.annee = this.calendrier.annee ?? Math.floor((this.calendrier.moisRdD ?? 0) / RDD_MOIS_PAR_AN); - this.calendrier.moisRdD = (this.calendrier.moisRdD ?? 0) % RDD_MOIS_PAR_AN; + this.timestamp = RdDTimestamp.getWorldTime(); if (Misc.isUniqueConnectedGM()) { // Uniquement si GM - game.settings.set(SYSTEM_RDD, "calendrier", this.calendrier); - + RdDTimestamp.setWorldTime(this.timestamp); this.listeNombreAstral = this.getListeNombreAstral(); this.rebuildListeNombreAstral(HIDE_DICE); // Ensure always up-to-date } - console.log('RdDCalendrier.constructor()', this.calendrier, this.calendrierPos, this.listeNombreAstral); + console.log('RdDCalendrier.constructor()', this.timestamp, this.timestamp.toCalendrier(), this.calendrierPos, this.listeNombreAstral); + Hooks.on('updateSetting', async (setting, update, options, id) => this.onUpdateSetting(setting, update, options, id)); + } + + async onUpdateSetting(setting, update, options, id) { + if (setting.key == SYSTEM_RDD + '.' + WORLD_TIMESTAMP_SETTING) { + this.timestamp = RdDTimestamp.getWorldTime(); + this.updateDisplay(); + } } /* -------------------------------------------- */ @@ -227,48 +153,50 @@ export class RdDCalendrier extends Application { } /* -------------------------------------------- */ - getDateFromIndex(index) { - const dateRdD = this.getCalendrier(index); - return (dateRdD.jour + 1) + ' ' + dateRdD.moisLabel; + dateCourante() { + return this.timestamp.formatDate(); + } + + isAfterIndexDate(indexDate) { + // TODO: standardize + return indexDate < this.timestamp.indexDate; } /* -------------------------------------------- */ - getDayMonthFromIndex(index = undefined) { - const dateRdD = this.getCalendrier(index); - return { - day: dateRdD.jour + 1, - month: heuresList[dateRdD.moisRdD] - } - } + heureCourante() { return RdDTimestamp.definition(this.timestamp.heure); } /* -------------------------------------------- */ - getCurrentHeure() { - return heuresList[this.calendrier.heureRdD]; + getCurrentMinute() { return this.timestamp.indexMinute; } + + getTimestampFinChateauDormant(nbJours = 0) { + return this.timestamp.nouveauJour().addJour(nbJours); } - /* -------------------------------------------- */ - getCurrentDayIndex() { - return (((this.calendrier.annee ?? 0) * RDD_MOIS_PAR_AN + (this.calendrier.moisRdD ?? 0)) * RDD_JOUR_PAR_MOIS) + (this.calendrier.jour ?? 0); + getTimestampFinHeure(nbHeures = 0) { + return this.timestamp.nouvelleHeure().addHeures(nbHeures); } /* -------------------------------------------- */ getIndexFromDate(jour, mois) { - return (heuresDef[mois].heure * RDD_JOUR_PAR_MOIS) + jour - 1; + const addYear = mois < this.timestamp.mois || (mois == this.timestamp.mois && jour < this.timestamp.jour) + const time = RdDTimestamp.timestamp(this.timestamp.annee + (addYear ? 1 : 0), mois, jour); + return time.indexDate; } + /* -------------------------------------------- */ - getJoursSuivants(num) { + getJoursSuivants(count) { let jours = []; - let index = this.getCurrentDayIndex(); - for (let i = 0; i < num; i++) { - jours[i] = { label: this.getDateFromIndex(index + i), index: index + i }; + let indexDate = this.timestamp.indexDate; + for (let i = 0; i < count; i++, indexDate++) { + jours[i] = { label: RdDTimestamp.formatIndexDate(indexDate), index: indexDate }; } return jours; } /* -------------------------------------------- */ - async ajouterNombreAstral(index, showDice = SHOW_DICE) { + async ajouterNombreAstral(indexDate, showDice = SHOW_DICE) { const nombreAstral = await RdDDice.rollTotal("1dh", { showDice: showDice, rollMode: "selfroll" }); - const dateFuture = this.getDateFromIndex(index); + const dateFuture = RdDTimestamp.formatIndexDate(indexDate); if (showDice != HIDE_DICE) { ChatMessage.create({ whisper: ChatMessage.getWhisperRecipients("GM"), @@ -278,14 +206,13 @@ export class RdDCalendrier extends Application { return { nombreAstral: nombreAstral, valeursFausses: [], - index: index + index: indexDate } } /* -------------------------------------------- */ getCurrentNombreAstral() { - let indexDate = this.getCurrentDayIndex(); - return this.getNombreAstral(indexDate); + return this.getNombreAstral(this.timestamp.indexDate); } /* -------------------------------------------- */ @@ -309,10 +236,9 @@ export class RdDCalendrier extends Application { /* -------------------------------------------- */ async rebuildListeNombreAstral(showDice = HIDE_DICE) { if (Misc.isUniqueConnectedGM()) { - let jourCourant = this.getCurrentDayIndex(); let newList = []; for (let i = 0; i < MAX_NOMBRE_ASTRAL; i++) { - let dayIndex = jourCourant + i; + let dayIndex = this.timestamp.indexDate + i; let na = this.listeNombreAstral.find(n => n.index == dayIndex); if (na) { newList[i] = na; @@ -320,11 +246,20 @@ export class RdDCalendrier extends Application { newList[i] = await this.ajouterNombreAstral(dayIndex, showDice); } } - game.settings.set(SYSTEM_RDD, "liste-nombre-astral", newList); this.listeNombreAstral = newList; + game.settings.set(SYSTEM_RDD, "liste-nombre-astral", newList); } } + /* -------------------------------------------- */ + async setNewTimestamp(newTimestamp) { + game.actors.forEach(actor => actor.onTimeChanging(this.timestamp, newTimestamp)); + RdDTimestamp.setWorldTime(newTimestamp); + this.timestamp = newTimestamp; + await this.rebuildListeNombreAstral(); + this.updateDisplay(); + } + /* -------------------------------------------- */ async onCalendarButton(ev) { ev.preventDefault(); @@ -339,95 +274,31 @@ export class RdDCalendrier extends Application { this.updateDisplay(); } - /* -------------------------------------------- */ - checkMaladie(periode) { - for (let actor of game.actors) { - if (actor.type == 'personnage') { - let maladies = actor.items.filter(item => (item.type == 'maladie' || (item.type == 'poison' && item.system.active)) && item.system.periodicite.toLowerCase().includes(periode)); - for (let maladie of maladies) { - if (maladie.system.identifie) { - ChatMessage.create({ content: `${actor.name} souffre de ${maladie.name} (${maladie.type}): vérifiez que les effets ne se sont pas aggravés !` }); - } else { - ChatMessage.create({ content: `${actor.name} souffre d'un mal inconnu (${maladie.type}): vérifiez que les effets ne se sont pas aggravés !` }); - } - let itemMaladie = actor.getItem(maladie.id) - itemMaladie.postItem('gmroll'); - } - } - } - } - /* -------------------------------------------- */ async incrementTime(minutes = 0) { - this.calendrier.minutesRelative += minutes; - this.checkMaladie("round"); - this.checkMaladie("minute"); - if (this.calendrier.minutesRelative >= RDD_MINUTES_PAR_HEURES) { - this.calendrier.minutesRelative -= RDD_MINUTES_PAR_HEURES; - this.calendrier.heureRdD += 1; - this.checkMaladie("heure"); - } - if (this.calendrier.heureRdD >= RDD_HEURES_PAR_JOUR) { - this.calendrier.heureRdD -= RDD_HEURES_PAR_JOUR; - await this.incrementerJour(); - this.checkMaladie("heure"); - this.checkMaladie("jour"); - } - game.settings.set(SYSTEM_RDD, "calendrier", duplicate(this.calendrier)); - // Notification aux joueurs // TODO: replace with Hook on game settings update - game.socket.emit(SYSTEM_SOCKET_ID, { - msg: "msg_sync_time", - data: duplicate(this.calendrier) - }); + await this.setNewTimestamp(this.timestamp.addMinutes(minutes)); } /* -------------------------------------------- */ async incrementerJour() { - const index = this.getCurrentDayIndex() + 1; - this.calendrier = this.getCalendrier(index); - await this.rebuildListeNombreAstral(); - } - - /* -------------------------------------------- */ - syncPlayerTime(calendrier) { - this.calendrier = duplicate(calendrier); // Local copy update - this.updateDisplay(); + await this.setNewTimestamp(this.timestamp.nouveauJour()); } /* -------------------------------------------- */ async positionnerHeure(indexHeure) { - if (indexHeure <= this.calendrier.heureRdD) { - await this.incrementerJour(); - } - this.calendrier.heureRdD = indexHeure; - this.calendrier.minutesRelative = 0; - game.settings.set(SYSTEM_RDD, "calendrier", duplicate(this.calendrier)); + await this.setNewTimestamp(new RdDTimestamp({ indexDate: this.timestamp.indexDate + (this.timestamp.heure < indexHeure ? 0 : 1) }).addHeures(indexHeure)) } /* -------------------------------------------- */ fillCalendrierData(formData = {}) { - const mois = RdDCalendrier.getDefSigne(this.calendrier.moisRdD); - const heure = RdDCalendrier.getDefSigne(this.calendrier.heureRdD); - console.log('fillCalendrierData', this.calendrier, mois, heure); - - formData.heureKey = heure.key; - formData.moisKey = mois.key; - formData.jourMois = this.calendrier.jour + 1; - formData.nomMois = mois.label; // heures et mois nommés identiques - formData.annee = this.calendrier.annee; - formData.iconMois = dossierIconesHeures + mois.icon; - formData.nomHeure = heure.label; - formData.iconHeure = dossierIconesHeures + heure.icon; - formData.nomSaison = saisonsDef[mois.saison].label; - formData.heureRdD = this.calendrier.heureRdD; - formData.minutesRelative = this.calendrier.minutesRelative; + mergeObject(formData, this.timestamp.toCalendrier()); formData.isGM = game.user.isGM; return formData; } /* -------------------------------------------- */ getLectureAstrologieDifficulte(dateIndex) { - let indexNow = this.getCurrentDayIndex(); + let indexNow = this.timestamp.indexDate; let diffDay = dateIndex - indexNow; return - Math.floor(diffDay / 2); } @@ -449,9 +320,9 @@ export class RdDCalendrier extends Application { request.rolled = rollData.rolled; request.isValid = request.rolled.isSuccess; request.nbAstral = this.getNombreAstral(request.date); - + if (request.rolled.isSuccess) { - if (request.rolled.isPart){ + if (request.rolled.isPart) { // Gestion expérience (si existante) request.competence = actor.getCompetence("astrologie") request.selectedCarac = actor.system.carac["vue"]; @@ -483,55 +354,32 @@ export class RdDCalendrier extends Application { game.settings.set(SYSTEM_RDD, "liste-nombre-astral", this.listeNombreAstral); } - /* -------------------------------------------- */ - findHeure(heure) { - heure = Grammar.toLowerCaseNoAccentNoSpace(heure); - let parHeureOuLabel = Object.values(heuresDef).filter(it => (it.heure + 1) == parseInt(heure) || Grammar.toLowerCaseNoAccentNoSpace(it.label) == heure); - if (parHeureOuLabel.length == 1) { - return parHeureOuLabel[0]; - } - let parLabelPartiel = Object.values(heuresDef).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; - } - /* -------------------------------------------- */ - getHeureNumber(hNum) { - let heure = Object.values(heuresDef).find(it => (it.heure) == hNum); - return heure + getHeureChance(heure) { + return heure + (this.getCurrentNombreAstral() ?? 1) - 1; } /* -------------------------------------------- */ getHeuresChanceMalchance(heureNaissance) { - let heuresChancesMalchances = []; - let defHeure = this.findHeure(heureNaissance); + let defHeure = RdDTimestamp.findHeure(heureNaissance); if (defHeure) { - let hn = defHeure.heure; - let chiffreAstral = this.getCurrentNombreAstral() ?? 0; - heuresChancesMalchances[0] = { value: "+4", heures: [this.getHeureNumber((hn + chiffreAstral) % RDD_HEURES_PAR_JOUR).label] }; - heuresChancesMalchances[1] = { - value: "+2", heures: [this.getHeureNumber((hn + chiffreAstral + 4) % RDD_HEURES_PAR_JOUR).label, - this.getHeureNumber((hn + chiffreAstral + 8) % RDD_HEURES_PAR_JOUR).label] - }; - heuresChancesMalchances[2] = { value: "-4", heures: [this.getHeureNumber((hn + chiffreAstral + 6) % RDD_HEURES_PAR_JOUR).label] }; - heuresChancesMalchances[3] = { - value: "-2", heures: [this.getHeureNumber((hn + chiffreAstral + 3) % RDD_HEURES_PAR_JOUR).label, - this.getHeureNumber((hn + chiffreAstral + 9) % RDD_HEURES_PAR_JOUR).label] - }; + const signe = h => h % RDD_HEURES_PAR_JOUR; + const chance = this.getHeureChance(defHeure.heure); + return [ + { ajustement: "+4", heures: [signe(chance)] }, + { ajustement: "+2", heures: [signe(chance + 4), signe(chance + 8)] }, + { ajustement: "-4", heures: [signe(chance + 6)] }, + { ajustement: "-2", heures: [signe(chance + 3), signe(chance + 9)] } + ]; } - return heuresChancesMalchances; + return []; } /* -------------------------------------------- */ getAjustementAstrologique(heureNaissance, name = undefined) { - let defHeure = this.findHeure(heureNaissance); + let defHeure = RdDTimestamp.findHeure(heureNaissance); if (defHeure) { - let hn = defHeure.heure; - let chiffreAstral = this.getCurrentNombreAstral() ?? 0; - let heureCourante = this.calendrier.heureRdD; - let ecartChance = (hn + chiffreAstral - heureCourante) % RDD_HEURES_PAR_JOUR; + const chance = this.getHeureChance(defHeure.heure); + const ecartChance = (chance - this.timestamp.heure) % RDD_HEURES_PAR_JOUR; switch (ecartChance) { case 0: return 4; case 4: case 8: return 2; @@ -551,9 +399,7 @@ export class RdDCalendrier extends Application { /* -------------------------------------------- */ getData() { let formData = super.getData(); - this.fillCalendrierData(formData); - this.setPos(this.calendrierPos); return formData; } @@ -582,50 +428,40 @@ export class RdDCalendrier extends Application { updateDisplay() { let calendrier = this.fillCalendrierData(); // Rebuild text du calendrier - let dateHTML = `${calendrier.jourMois} ${calendrier.nomMois} ${calendrier.annee} (${calendrier.nomSaison})` + let dateHTML = `${calendrier.jourDuMois} ${calendrier.mois.label} ${calendrier.annee} (${calendrier.mois.saison})` if (game.user.isGM) { - dateHTML = dateHTML + " - NA: " + (this.getCurrentNombreAstral() ?? "indéterminé"); + dateHTML = dateHTML + " - NA: " + (this.getCurrentNombreAstral() ?? "?"); } for (let handle of document.getElementsByClassName("calendar-date-rdd")) { handle.innerHTML = dateHTML; } for (let heure of document.getElementsByClassName("calendar-heure-texte")) { - heure.innerHTML = calendrier.nomHeure; + heure.innerHTML = calendrier.heure.label; } for (const minute of document.getElementsByClassName("calendar-time-disp")) { - minute.innerHTML = `${calendrier.minutesRelative} minutes`; + minute.innerHTML = `${calendrier.minute} minutes`; } for (const heureImg of document.getElementsByClassName("calendar-heure-img")) { - heureImg.src = calendrier.iconHeure; + heureImg.src = calendrier.heure.icon; } } /* -------------------------------------------- */ async saveEditeur(calendrierData) { - this.calendrier.minutesRelative = Number(calendrierData.minutesRelative); - this.calendrier.jour = Number(calendrierData.jourMois) - 1; - this.calendrier.moisRdD = RdDCalendrier.getChiffreFromSigne(calendrierData.moisKey); - this.calendrier.annee = Number(calendrierData.annee); - this.calendrier.heureRdD = RdDCalendrier.getChiffreFromSigne(calendrierData.heureKey); - game.settings.set(SYSTEM_RDD, "calendrier", duplicate(this.calendrier)); - - await this.rebuildListeNombreAstral(); - - game.socket.emit(SYSTEM_SOCKET_ID, { - msg: "msg_sync_time", - data: duplicate(this.calendrier) - }); - - this.updateDisplay(); + const newTimestamp = RdDTimestamp.timestamp( + Number.parseInt(calendrierData.annee), + calendrierData.mois.heure, + Number.parseInt(calendrierData.jourMois), + calendrierData.heure.heure, + Number.parseInt(calendrierData.minutes) + ); + await this.setNewTimestamp(newTimestamp); } /* -------------------------------------------- */ async showCalendarEditor() { - let calendrierData = duplicate(this.fillCalendrierData()); + let calendrierData = this.fillCalendrierData(); if (this.editeur == undefined) { - calendrierData.jourMoisOptions = RdDCalendrier.buildJoursMois(); - calendrierData.heuresOptions = [0, 1]; - calendrierData.minutesOptions = Array(RDD_MINUTES_PAR_HEURES).fill().map((item, index) => 0 + index); let html = await renderTemplate('systems/foundryvtt-reve-de-dragon/templates/calendar-editor-template.html', calendrierData); this.editeur = new RdDCalendrierEditeur(html, this, calendrierData) } @@ -633,33 +469,30 @@ export class RdDCalendrier extends Application { this.editeur.render(true); } - static buildJoursMois() { - return Array(RDD_JOUR_PAR_MOIS).fill().map((item, index) => 1 + index); - } + static buildJoursMois() { return JOURS_DU_MOIS; } /* -------------------------------------------- */ async showAstrologieEditor() { - let calendrierData = duplicate(this.fillCalendrierData()); - let astrologieArray = []; + const calendrierData = duplicate(this.fillCalendrierData()); this.listeNombreAstral = this.listeNombreAstral || []; - for (let astralData of this.listeNombreAstral) { - astralData.humanDate = this.getDateFromIndex(astralData.index); - for (let vf of astralData.valeursFausses) { + + calendrierData.astrologieData = this.listeNombreAstral.map(astro => { + const timestamp = new RdDTimestamp({ indexDate: astro.index }); + astro.date = { mois: timestamp.mois, jour: timestamp.jour + 1 } + for (let vf of astro.valeursFausses) { let actor = game.actors.get(vf.actorId); vf.actorName = (actor) ? actor.name : "Inconnu"; } - astrologieArray.push(duplicate(astralData)); - } - let heuresParActeur = {}; - for (let actor of game.actors) { + return astro; + }); + + calendrierData.heuresParActeur = {}; + game.actors.filter(it => it.isPersonnage() && it.hasPlayerOwner).forEach(actor => { let heureNaissance = actor.getHeureNaissance(); if (heureNaissance) { - heuresParActeur[actor.name] = this.getHeuresChanceMalchance(heureNaissance); + calendrierData.heuresParActeur[actor.name] = this.getHeuresChanceMalchance(heureNaissance); } - } - //console.log("ASTRO", astrologieArray); - calendrierData.astrologieData = astrologieArray; - calendrierData.heuresParActeur = heuresParActeur; + }) let html = await renderTemplate('systems/foundryvtt-reve-de-dragon/templates/calendar-astrologie-template.html', calendrierData); let astrologieEditeur = new RdDAstrologieEditeur(html, this, calendrierData) astrologieEditeur.updateData(calendrierData); diff --git a/module/rdd-commands.js b/module/rdd-commands.js index c24e8af0..c92ca693 100644 --- a/module/rdd-commands.js +++ b/module/rdd-commands.js @@ -206,16 +206,16 @@ export class RdDCommands { let rollMode = game.settings.get("core", "rollMode"); if (["gmroll", "blindroll"].includes(rollMode)) { msg["whisper"] = ChatMessage.getWhisperRecipients("GM"); - } - if (rollMode === "blindroll"){ + } + if (rollMode === "blindroll") { msg["blind"] = true; - } + } msg["type"] = 0; if (!this.commandsTable) { this._registerCommands(); } - + let command = commandLine[0].toLowerCase(); if (this._isCommandHandled(command)) { let params = commandLine.slice(1); @@ -225,7 +225,7 @@ export class RdDCommands { return false; } - _isCommandHandled(command){ + _isCommandHandled(command) { return this.commandsTable[command] != undefined; } @@ -331,7 +331,7 @@ export class RdDCommands { diff = 0; } const caracName = params[0]; - let competence = length > 1 ? actors[0].getCompetence(Misc.join(params.slice(1, length), ' ')) : {name:undefined}; + let competence = length > 1 ? actors[0].getCompetence(Misc.join(params.slice(1, length), ' ')) : { name: undefined }; if (competence) { for (let actor of actors) { await actor.rollCaracCompetence(caracName, competence.name, diff); @@ -448,17 +448,27 @@ export class RdDCommands { } async creerSignesDraconiques() { - DialogCreateSigneDraconique.createSigneForActors(); + if (game.user.isGM) { + DialogCreateSigneDraconique.createSigneForActors(); + } + else { + ui.notifications.warn("Seul le MJ est autorisé à utiliser la commande /signe"); + } return true; } async supprimerSignesDraconiquesEphemeres() { - game.actors.forEach(actor => { - const ephemeres = actor.items.filter(item => item.type = 'signedraconique' && item.system.ephemere); - if (ephemeres.length > 0) { - actor.deleteEmbeddedDocuments("Item", ephemeres.map(item => item.id)); - } - }); + if (game.user.isGM) { + game.actors.forEach(actor => { + const ephemeres = actor.items.filter(item => item.type = 'signedraconique' && item.system.ephemere); + if (ephemeres.length > 0) { + actor.deleteEmbeddedDocuments("Item", ephemeres.map(item => item.id)); + } + }); + } + else { + ui.notifications.warn("Seul le MJ est autorisé à utiliser la commande /signe"); + } return true; } diff --git a/module/rdd-herbes.js b/module/rdd-herbes.js index 5210e03e..859cccea 100644 --- a/module/rdd-herbes.js +++ b/module/rdd-herbes.js @@ -1,7 +1,6 @@ -import { RdDUtility } from "./rdd-utility.js"; -import { RdDCalendrier } from "./rdd-calendrier.js"; import { Grammar } from "./grammar.js"; import { SystemCompendiums } from "./settings/system-compendiums.js"; +import { RdDTimestamp } from "./rdd-timestamp.js"; /* -------------------------------------------- */ export class RdDHerbes extends Item { @@ -29,7 +28,7 @@ export class RdDHerbes extends Item { } /* -------------------------------------------- */ - static async updatePotionData(formData) { + static async addPotionFormData(formData) { formData.isSoins = formData.system.categorie.includes('Soin'); formData.isRepos = formData.system.categorie.includes('Repos'); if (formData.isSoins) { @@ -40,9 +39,8 @@ export class RdDHerbes extends Item { } formData.herbesSoins = RdDHerbes.buildHerbesList(this.herbesSoins, 12); formData.herbesRepos = RdDHerbes.buildHerbesList(this.herbesRepos, 7); - formData.jourMoisOptions = RdDCalendrier.buildJoursMois(); - formData.dateActuelle = game.system.rdd.calendrier.getDateFromIndex(); - formData.splitDate = game.system.rdd.calendrier.getDayMonthFromIndex(formData.system.prdate); + formData.dateActuelle = game.system.rdd.calendrier.dateCourante(); + formData.enchantement = RdDTimestamp.splitIndexDate(formData.system.prdate); } /* -------------------------------------------- */ diff --git a/module/rdd-main.js b/module/rdd-main.js index 2b13a1d2..54684146 100644 --- a/module/rdd-main.js +++ b/module/rdd-main.js @@ -1,10 +1,6 @@ import { SYSTEM_RDD, SYSTEM_SOCKET_ID } from "./constants.js"; import { RdDActor } from "./actor.js"; -import { RdDItemSheet } from "./item-sheet.js"; -import { RdDActorSheet } from "./actor-sheet.js"; -import { RdDActorCreatureSheet } from "./actor-creature-sheet.js"; -import { RdDActorVehiculeSheet } from "./actor-vehicule-sheet.js"; -import { RdDActorEntiteSheet } from "./actor-entite-sheet.js"; +import { RdDItem } from "./item.js"; import { RdDUtility } from "./rdd-utility.js"; import { TMRUtility } from "./tmr-utility.js"; import { RdDCalendrier } from "./rdd-calendrier.js"; @@ -19,26 +15,40 @@ import { ReglesOptionelles } from "./settings/regles-optionelles.js"; import { RdDHotbar } from "./rdd-hotbar-drop.js" import { EffetsDraconiques } from "./tmr/effets-draconiques.js"; import { RdDHerbes } from "./rdd-herbes.js"; -import { RdDItem } from "./item.js"; import { RdDDice } from "./rdd-dice.js"; import { RdDPossession } from "./rdd-possession.js"; -import { RdDSigneDraconiqueItemSheet } from "./item-signedraconique-sheet.js"; import { Misc } from "./misc.js"; import { Migrations } from './migrations.js'; import { DialogChronologie } from "./dialog-chronologie.js"; import { SystemCompendiums } from "./settings/system-compendiums.js"; -import { RdDRencontreItemSheet } from "./item-rencontre-sheet.js"; -import { TMRRencontres } from "./tmr-rencontres.js"; -import { RdDHerbeItemSheet } from "./item-herbe-sheet.js"; import { Environnement } from "./environnement.js"; -import { RdDIngredientItemSheet } from "./item-ingredient-sheet.js"; -import { RdDFauneItemSheet } from "./item-faune-sheet.js"; -import { RdDConteneurItemSheet } from "./item-conteneur-sheet.js"; -import { RdDServiceItemSheet } from "./item-service-sheet.js"; -import { RdDItemService } from "./item-service.js"; +import { RdDItemService } from "./item/item-service.js"; import { RdDBaseActor } from "./actor/base-actor.js"; import { RdDCommerceSheet } from "./actor/commerce-sheet.js"; import { RdDCommerce } from "./actor/commerce.js"; +import { RdDTimestamp } from "./rdd-timestamp.js"; +import { RdDItemMaladie } from "./item/maladie.js"; +import { RdDItemPoison } from "./item/poison.js"; +import { RdDItemSigneDraconique } from "./item/item-signedraconique.js"; +import { RdDItemQueue } from "./item/queue.js"; +import { RdDItemOmbre } from "./item/ombre.js"; +import { RdDItemSouffle } from "./item/souffle.js"; +import { RdDRencontre } from "./item/item-rencontre.js"; + +import { RdDActorSheet } from "./actor-sheet.js"; +import { RdDActorCreatureSheet } from "./actor-creature-sheet.js"; +import { RdDActorVehiculeSheet } from "./actor-vehicule-sheet.js"; +import { RdDActorEntiteSheet } from "./actor-entite-sheet.js"; +import { RdDItemSheet } from "./item-sheet.js"; +import { RdDServiceItemSheet } from "./item-service-sheet.js"; +import { RdDHerbeItemSheet } from "./item-herbe-sheet.js"; +import { RdDRencontreItemSheet } from "./item-rencontre-sheet.js"; +import { RdDIngredientItemSheet } from "./item-ingredient-sheet.js"; +import { RdDFauneItemSheet } from "./item-faune-sheet.js"; +import { RdDConteneurItemSheet } from "./item-conteneur-sheet.js"; +import { RdDSigneDraconiqueItemSheet } from "./item-signedraconique-sheet.js"; + +import { TMRRencontres } from "./tmr-rencontres.js"; /** * RdD system @@ -57,7 +67,14 @@ export class SystemReveDeDragon { this.RdDUtility = RdDUtility; this.RdDHotbar = RdDHotbar; this.itemClasses = { - service: RdDItemService + service: RdDItemService, + maladie: RdDItemMaladie, + poison: RdDItemPoison, + queue: RdDItemQueue, + ombre: RdDItemOmbre, + souffle: RdDItemSouffle, + signedraconique: RdDItemSigneDraconique, + rencontre: RdDRencontre } this.actorClasses = { creature: RdDActor, @@ -145,6 +162,7 @@ export class SystemReveDeDragon { CONFIG.Combat.documentClass = RdDCombatManager; // préparation des différents modules + RdDTimestamp.init(); SystemCompendiums.init(); DialogChronologie.init(); ReglesOptionelles.init(); @@ -181,15 +199,6 @@ export class SystemReveDeDragon { default: "avant-encaissement" }); - /* -------------------------------------------- */ - game.settings.register(SYSTEM_RDD, "calendrier", { - name: "calendrier", - scope: "world", - config: false, - default: RdDCalendrier.createCalendrierInitial(), - type: Object - }); - /* -------------------------------------------- */ game.settings.register(SYSTEM_RDD, "liste-nombre-astral", { name: "liste-nombre-astral", diff --git a/module/rdd-timestamp.js b/module/rdd-timestamp.js new file mode 100644 index 00000000..37ec8504 --- /dev/null +++ b/module/rdd-timestamp.js @@ -0,0 +1,307 @@ +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 + } + } +} \ No newline at end of file diff --git a/module/rdd-tmr-dialog.js b/module/rdd-tmr-dialog.js index 0e7ffb31..c6c9f5d9 100644 --- a/module/rdd-tmr-dialog.js +++ b/module/rdd-tmr-dialog.js @@ -15,8 +15,9 @@ import { HtmlUtility } from "./html-utility.js"; import { ReglesOptionelles } from "./settings/regles-optionelles.js"; import { RdDDice } from "./rdd-dice.js"; import { STATUSES } from "./settings/status-effects.js"; -import { RdDRencontre } from "./item-rencontre.js"; +import { RdDRencontre } from "./item/item-rencontre.js"; import { RdDCalendrier } from "./rdd-calendrier.js"; +import { RdDTimestamp } from "./rdd-timestamp.js"; /* -------------------------------------------- */ @@ -840,7 +841,7 @@ export class RdDTMRDialog extends Dialog { async processSortReserve(sortReserve) { await this.actor.deleteEmbeddedDocuments('Item', [sortReserve.id]); console.log("declencheSortEnReserve", sortReserve); - const heureCible = RdDCalendrier.getSigneAs('label', sortReserve.system.heurecible); + const heureCible = RdDTimestamp.definition(sortReserve.system.heurecible).label; this._tellToUserAndGM(`Vous avez déclenché ${sortReserve.system.echectotal ? "l'échec total!" : "le sort"} en réserve ${sortReserve.name} diff --git a/module/rdd-utility.js b/module/rdd-utility.js index d8cb280c..8fa4c4a5 100644 --- a/module/rdd-utility.js +++ b/module/rdd-utility.js @@ -12,11 +12,10 @@ import { Monnaie } from "./item-monnaie.js"; import { RdDPossession } from "./rdd-possession.js"; import { RdDNameGen } from "./rdd-namegen.js"; import { RdDConfirm } from "./rdd-confirm.js"; -import { RdDCalendrier } from "./rdd-calendrier.js"; import { Environnement } from "./environnement.js"; import { RdDItemCompetence } from "./item-competence.js"; import { RdDResolutionTable } from "./rdd-resolution-table.js"; -import { RdDCommerce } from "./actor/commerce.js"; +import { RdDTimestamp } from "./rdd-timestamp.js"; /* -------------------------------------------- */ // This table starts at 0 -> niveau -10 @@ -177,9 +176,11 @@ export class RdDUtility { 'systems/foundryvtt-reve-de-dragon/templates/scripts/autocomplete-script.hbs', 'systems/foundryvtt-reve-de-dragon/templates/scripts/autocomplete.hbs', 'systems/foundryvtt-reve-de-dragon/templates/item/boutons-comestible.html', + 'systems/foundryvtt-reve-de-dragon/templates/item/temporel.hbs', 'systems/foundryvtt-reve-de-dragon/templates/item/partial-inventaire.html', 'systems/foundryvtt-reve-de-dragon/templates/item/partial-environnement.html', 'systems/foundryvtt-reve-de-dragon/templates/item/partial-tab-environnement.html', + 'systems/foundryvtt-reve-de-dragon/templates/item-queue-sheet.html', 'systems/foundryvtt-reve-de-dragon/templates/header-item.html', // partial enums 'systems/foundryvtt-reve-de-dragon/templates/enum-caracteristiques.html', @@ -198,8 +199,12 @@ export class RdDUtility { 'systems/foundryvtt-reve-de-dragon/templates/enum-categorie-queue.html', 'systems/foundryvtt-reve-de-dragon/templates/enum-draconic.html', 'systems/foundryvtt-reve-de-dragon/templates/enum-tmr-type.html', + 'systems/foundryvtt-reve-de-dragon/templates/enum-periode.html', 'systems/foundryvtt-reve-de-dragon/templates/enum-tmr-effet.html', // Partials + 'systems/foundryvtt-reve-de-dragon/templates/common/timestamp.hbs', + 'systems/foundryvtt-reve-de-dragon/templates/common/periodicite.hbs', + 'systems/foundryvtt-reve-de-dragon/templates/common/enum-duree.hbs', 'systems/foundryvtt-reve-de-dragon/templates/common/compendium-link.hbs', 'systems/foundryvtt-reve-de-dragon/templates/partial-description-overflow.html', 'systems/foundryvtt-reve-de-dragon/templates/partial-description-sort.html', @@ -279,7 +284,13 @@ export class RdDUtility { Handlebars.registerHelper('caseTmr-type', coord => TMRUtility.getTMRType(coord)); Handlebars.registerHelper('typeTmr-name', type => TMRUtility.typeTmrName(type)); Handlebars.registerHelper('effetRencontre-name', coord => TMRUtility.typeTmrName(coord)); - Handlebars.registerHelper('signeHeure', (key, heure) => RdDCalendrier.getSigneAs(key, heure)); + + Handlebars.registerHelper('timestamp-imgSigneHeure', (heure) => { return new Handlebars.SafeString(RdDTimestamp.imgSigneHeure(heure)) }); + Handlebars.registerHelper('timestamp-imgSigne', (heure) => { return new Handlebars.SafeString(RdDTimestamp.imgSigne(heure)) }); + Handlebars.registerHelper('timestamp-extract', timestamp => new RdDTimestamp(timestamp).toCalendrier()); + Handlebars.registerHelper('timestamp-formulesDuree', () => RdDTimestamp.formulesDuree()); + Handlebars.registerHelper('timestamp-formulesPeriode', () => RdDTimestamp.formulesPeriode()); + Handlebars.registerHelper('min', (...args) => Math.min(...args.slice(0, -1))); Handlebars.registerHelper('regle-optionnelle', (option) => ReglesOptionelles.isUsing(option)); Handlebars.registerHelper('trier', list => list.sort((a, b) => a.name.localeCompare(b.name))); @@ -338,69 +349,6 @@ export class RdDUtility { return undefined; } - /* -------------------------------------------- */ - static filterItemsPerTypeForSheet(formData, itemTypes) { - - RdDUtility.filterEquipementParType(formData, itemTypes); - - formData.sorts = this.arrayOrEmpty(itemTypes['sort']); - formData.rencontres = this.arrayOrEmpty(itemTypes['rencontre']); - formData.casestmr = this.arrayOrEmpty(itemTypes['casetmr']); - formData.signesdraconiques = this.arrayOrEmpty(itemTypes['signedraconique']); - formData.queues = this.arrayOrEmpty(itemTypes['queue']); - formData.souffles = this.arrayOrEmpty(itemTypes['souffle']); - formData.ombres = this.arrayOrEmpty(itemTypes['ombre']); - formData.tetes = this.arrayOrEmpty(itemTypes['tete']); - formData.taches = this.arrayOrEmpty(itemTypes['tache']); - formData.meditations = this.arrayOrEmpty(itemTypes['meditation']); - formData.chants = this.arrayOrEmpty(itemTypes['chant']); - formData.danses = this.arrayOrEmpty(itemTypes['danse']); - formData.musiques = this.arrayOrEmpty(itemTypes['musique']); - formData.oeuvres = this.arrayOrEmpty(itemTypes['oeuvre']); - formData.jeux = this.arrayOrEmpty(itemTypes['jeu']); - - formData.services = this.arrayOrEmpty(itemTypes['service']); - formData.recettescuisine = this.arrayOrEmpty(itemTypes['recettecuisine']); - formData.recettesAlchimiques = this.arrayOrEmpty(itemTypes['recettealchimique']); - formData.maladies = this.arrayOrEmpty(itemTypes['maladie']); - formData.poisons = this.arrayOrEmpty(itemTypes['poison']); - formData.possessions = this.arrayOrEmpty(itemTypes['possession']); - formData.maladiesPoisons = formData.maladies.concat(formData.poisons); - formData.competences = (itemTypes['competence'] ?? []).concat(itemTypes['competencecreature'] ?? []); - formData.sortsReserve = this.arrayOrEmpty(itemTypes['sortreserve']); - } - - static filterEquipementParType(formData, itemTypes) { - formData.conteneurs = this.arrayOrEmpty(itemTypes['conteneur']); - - formData.materiel = this.arrayOrEmpty(itemTypes['objet']); - formData.armes = this.arrayOrEmpty(itemTypes['arme']); - formData.armures = this.arrayOrEmpty(itemTypes['armure']); - formData.munitions = this.arrayOrEmpty(itemTypes['munition']); - formData.livres = this.arrayOrEmpty(itemTypes['livre']); - formData.potions = this.arrayOrEmpty(itemTypes['potion']); - formData.ingredients = this.arrayOrEmpty(itemTypes['ingredient']); - formData.faunes = this.arrayOrEmpty(itemTypes['faune']); - formData.herbes = this.arrayOrEmpty(itemTypes['herbe']); - formData.monnaie = this.arrayOrEmpty(itemTypes['monnaie']).sort(Monnaie.triValeurEntiere()); - formData.nourritureboissons = this.arrayOrEmpty(itemTypes['nourritureboisson']); - formData.gemmes = this.arrayOrEmpty(itemTypes['gemme']); - - formData.objets = formData.conteneurs - .concat(formData.materiel) - .concat(formData.armes) - .concat(formData.armures) - .concat(formData.munitions) - .concat(formData.livres) - .concat(formData.potions) - .concat(formData.ingredients) - .concat(formData.herbes) - .concat(formData.faunes) - .concat(formData.monnaie) - .concat(formData.nourritureboissons) - .concat(formData.gemmes); - } - /* -------------------------------------------- */ static buildArbreDeConteneurs(conteneurs, objets) { let objetVersConteneur = {}; @@ -474,7 +422,7 @@ export class RdDUtility { objet.niveau = profondeur; const display = afficherContenu ? 'item-display-show' : 'item-display-hide'; - let strContenu = `