import { RdDCalendrierEditeur } from "./rdd-calendrier-editeur.js"; import { RdDAstrologieEditeur } from "./rdd-astrologie-editeur.js"; import { RdDResolutionTable } from "./rdd-resolution-table.js"; import { RdDUtility } from "./rdd-utility.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 } from "./rdd-timestamp.js"; /* -------------------------------------------- */ const saisonsDef = { "printemps": { label: "Printemps" }, "ete": { label: "Eté" }, "automne": { label: "Automne" }, "hiver": { label: "Hiver" } }; const RDD_JOUR_PAR_MOIS = 28; const RDD_HEURES_PAR_JOUR = 12; 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 { static get defaultOptions() { return mergeObject(super.defaultOptions, { template: "systems/foundryvtt-reve-de-dragon/templates/calendar-template.html", popOut: false, resizable: false }); } static createCalendrierPos() { return { top: 200, left: 200 }; } constructor() { super(); // position this.calendrierPos = duplicate(game.settings.get(SYSTEM_RDD, "calendrier-pos")); if (this.calendrierPos == undefined || this.calendrierPos.top == undefined) { this.calendrierPos = RdDCalendrier.createCalendrierPos(); game.settings.set(SYSTEM_RDD, "calendrier-pos", this.calendrierPos); } // Calendrier this.timestamp = new RdDTimestamp({}); if (Misc.isUniqueConnectedGM()) { // Uniquement si GM RdDTimestamp.setWorldTime(this.timestamp); this.listeNombreAstral = this.getListeNombreAstral(); this.rebuildListeNombreAstral(HIDE_DICE); // Ensure always up-to-date } console.log('RdDCalendrier.constructor()', this.timestamp, this.timestamp.toOldCalendrier(), this.calendrierPos, this.listeNombreAstral); } getCalendrier() { return this.timestamp.toOldCalendrier(); } /* -------------------------------------------- */ /** @override */ async activateListeners(html) { super.activateListeners(html); this.html = html; this.updateDisplay(); this.html.find('.ajout-chronologie').click(ev => DialogChronologie.create()); this.html.find('.calendar-btn').click(ev => this.onCalendarButton(ev)); this.html.find('.calendar-btn-edit').click(ev => { ev.preventDefault(); this.showCalendarEditor(); }); this.html.find('.astrologie-btn-edit').click(ev => { ev.preventDefault(); this.showAstrologieEditor(); }); this.html.find('#calendar-move-handle').mousedown(ev => { ev.preventDefault(); ev = ev || window.event; let isRightMB = false; if ("which" in ev) { // Gecko (Firefox), WebKit (Safari/Chrome) & Opera isRightMB = ev.which == 3; } else if ("button" in ev) { // IE, Opera isRightMB = ev.button == 2; } if (!isRightMB) { dragElement(document.getElementById("calendar-time-container")); let pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0; function dragElement(elmnt) { elmnt.onmousedown = dragMouseDown; function dragMouseDown(e) { e = e || window.event; e.preventDefault(); pos3 = e.clientX; pos4 = e.clientY; document.onmouseup = closeDragElement; document.onmousemove = elementDrag; } function elementDrag(e) { e = e || window.event; e.preventDefault(); // calculate the new cursor position: pos1 = pos3 - e.clientX; pos2 = pos4 - e.clientY; pos3 = e.clientX; pos4 = e.clientY; // set the element's new position: elmnt.style.bottom = undefined elmnt.style.top = (elmnt.offsetTop - pos2) + "px"; elmnt.style.left = (elmnt.offsetLeft - pos1) + "px"; } function closeDragElement() { // stop moving when mouse button is released: elmnt.onmousedown = undefined; document.onmouseup = undefined; document.onmousemove = undefined; let xPos = (elmnt.offsetLeft - pos1) > window.innerWidth ? window.innerWidth - 200 : (elmnt.offsetLeft - pos1); let yPos = (elmnt.offsetTop - pos2) > window.innerHeight - 20 ? window.innerHeight - 100 : (elmnt.offsetTop - pos2) xPos = xPos < 0 ? 0 : xPos; yPos = yPos < 0 ? 0 : yPos; if (xPos != (elmnt.offsetLeft - pos1) || yPos != (elmnt.offsetTop - pos2)) { elmnt.style.top = (yPos) + "px"; elmnt.style.left = (xPos) + "px"; } game.system.rdd.calendrier.calendrierPos.top = yPos; game.system.rdd.calendrier.calendrierPos.left = xPos; if (game.user.isGM) { game.settings.set(SYSTEM_RDD, "calendrier-pos", duplicate(game.system.rdd.calendrier.calendrierPos)); } } } } else if (isRightMB) { game.system.rdd.calendrier.calendrierPos.top = 200; game.system.rdd.calendrier.calendrierPos.left = 200; if (game.user.isGM) { game.settings.set(SYSTEM_RDD, "calendrier-pos", duplicate(game.system.rdd.calendrier.calendrierPos)); } this.setPos(game.system.rdd.calendrier.calendrierPos); } }); } /* -------------------------------------------- */ getListeNombreAstral() { return game.settings.get(SYSTEM_RDD, "liste-nombre-astral") ?? []; } /* -------------------------------------------- */ dateCourante() { return this.timestamp.formatDate(); } isAfterIndexDate(indexDate) { return indexDate < this.timestamp.indexDate; } /* -------------------------------------------- */ heureCourante() { return RdDTimestamp.definition(this.timestamp.heure); } /* -------------------------------------------- */ getCurrentMinute() { return this.timestamp.indexMinute; } getTimestampFinChateauDormant(nbJours = 0) { return this.timestamp.nouveauJour().addJour(nbJours); } getTimestampFinHeure(nbHeures = 0) { return this.timestamp.nouvelleHeure().addHeures(nbHeures); } /* -------------------------------------------- */ getIndexFromDate(jour, mois) { 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(count) { let jours = []; 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(indexDate, showDice = SHOW_DICE) { const nombreAstral = await RdDDice.rollTotal("1dh", { showDice: showDice, rollMode: "selfroll" }); const dateFuture = RdDTimestamp.formatIndexDate(indexDate); if (showDice != HIDE_DICE) { ChatMessage.create({ whisper: ChatMessage.getWhisperRecipients("GM"), content: `Le chiffre astrologique du ${dateFuture} sera le ${nombreAstral}` }); } return { nombreAstral: nombreAstral, valeursFausses: [], index: indexDate } } /* -------------------------------------------- */ getCurrentNombreAstral() { return this.getNombreAstral(this.timestamp.indexDate); } /* -------------------------------------------- */ resetNombreAstral() { this.listeNombreAstral = []; game.settings.set(SYSTEM_RDD, "liste-nombre-astral", this.listeNombreAstral); game.socket.emit(SYSTEM_SOCKET_ID, { msg: "msg_reset_nombre_astral", data: {} }); } /* -------------------------------------------- */ getNombreAstral(indexDate) { const listNombreAstral = this.getListeNombreAstral(); let astralData = listNombreAstral.find((nombreAstral, i) => nombreAstral.index == indexDate); return astralData?.nombreAstral; } /* -------------------------------------------- */ async rebuildListeNombreAstral(showDice = HIDE_DICE) { if (Misc.isUniqueConnectedGM()) { let newList = []; for (let i = 0; i < MAX_NOMBRE_ASTRAL; i++) { let dayIndex = this.timestamp.indexDate + i; let na = this.listeNombreAstral.find(n => n.index == dayIndex); if (na) { newList[i] = na; } else { newList[i] = await this.ajouterNombreAstral(dayIndex, showDice); } } this.listeNombreAstral = newList; game.settings.set(SYSTEM_RDD, "liste-nombre-astral", newList); } } /* -------------------------------------------- */ async setNewTimestamp(newTimestamp) { this.checkMaladiePoison(this.timestamp, newTimestamp); this.checkMaladie("round"); this.checkMaladie("minute"); if (this.timestamp.heure != newTimestamp.heure || this.timestamp.indexDate != newTimestamp.indexDate) { this.checkMaladie("heure"); } if (this.timestamp.indexDate != newTimestamp.indexDate) { this.checkMaladie("jour"); } RdDTimestamp.setWorldTime(newTimestamp); this.timestamp = newTimestamp; await this.rebuildListeNombreAstral(); 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.postItemToChat('gmroll'); } } } } checkMaladiePoison(oldTimestamp, newTimestamp) { // TODO const isInPeriod = maladie => { //TODO: utiliser les timestamp return false; } game.actors.filter(it => it.type == 'personnage') .forEach(actor => { actor.items.filter(it => it.type == 'maladie' || (it.type == 'poison' && it.system.active)) .filter(m => isInPeriod(m)) .forEach(m => { if (m.system.identifie) { ChatMessage.create({ content: `${actor.name} souffre de ${m.name} (${m.type}): vérifiez que les effets ne se sont pas aggravés !` }); } else { ChatMessage.create({ content: `${actor.name} souffre d'un mal inconnu (${m.type}): vérifiez que les effets ne se sont pas aggravés !` }); } let itemMaladie = actor.getItem(m.id) itemMaladie.postItemToChat('gmroll'); }) }); } /* -------------------------------------------- */ async onCalendarButton(ev) { ev.preventDefault(); const calendarAvance = ev.currentTarget.attributes['data-calendar-avance']; const calendarSet = ev.currentTarget.attributes['data-calendar-set']; if (calendarAvance) { await this.incrementTime(Number(calendarAvance.value)); } else if (calendarSet) { this.positionnerHeure(Number(calendarSet.value)); } this.updateDisplay(); } /* -------------------------------------------- */ async incrementTime(minutes = 0) { await this.setNewTimestamp(this.timestamp.addMinutes(minutes)); } /* -------------------------------------------- */ async incrementerJour() { await this.setNewTimestamp(this.timestamp.nouveauJour()); } /* -------------------------------------------- */ syncPlayerTime(timestamp) { this.timestamp = new RdDTimestamp(timestamp); this.updateDisplay(); } /* -------------------------------------------- */ async positionnerHeure(indexHeure) { await this.setNewTimestamp(new RdDTimestamp({ indexDate: this.timestamp.indexDate + (this.timestamp.heure < indexHeure ? 0 : 1) }).addHeures(indexHeure)) } /* -------------------------------------------- */ fillCalendrierData(formData = {}) { const mois = RdDTimestamp.definition(this.timestamp.mois); const heure = RdDTimestamp.definition(this.timestamp.heure); formData.annee = this.timestamp.annee; formData.moisKey = mois.key; formData.nomMois = mois.label; // heures et mois nommés identiques formData.iconMois = mois.icon; formData.nomSaison = saisonsDef[mois.saison].label; formData.jourMois = this.timestamp.jour + 1; formData.heureKey = heure.key; formData.heureRdD = this.timestamp.heure; formData.nomHeure = heure.label; formData.iconHeure = heure.icon; formData.minutesRelative = this.timestamp.minute; formData.isGM = game.user.isGM; console.log('fillCalendrierData', this.timestamp, mois, heure, formData); return formData; } /* -------------------------------------------- */ getLectureAstrologieDifficulte(dateIndex) { let indexNow = this.timestamp.indexDate; let diffDay = dateIndex - indexNow; return - Math.floor(diffDay / 2); } /* -------------------------------------------- */ async requestNombreAstral(request) { const actor = game.actors.get(request.id); if (Misc.isUniqueConnectedGM()) { // Only once console.log(request); let jourDiff = this.getLectureAstrologieDifficulte(request.date); let niveau = Number(request.astrologie.system.niveau) + Number(request.conditions) + Number(jourDiff) + Number(request.etat); let rollData = { caracValue: request.carac_vue, finalLevel: niveau, showDice: HIDE_DICE, rollMode: "blindroll" }; await RdDResolutionTable.rollData(rollData); request.rolled = rollData.rolled; request.isValid = request.rolled.isSuccess; request.nbAstral = this.getNombreAstral(request.date); if (request.rolled.isSuccess) { if (request.rolled.isPart) { // Gestion expérience (si existante) request.competence = actor.getCompetence("astrologie") request.selectedCarac = actor.system.carac["vue"]; actor.appliquerAjoutExperience(request, 'hide'); } } else { request.nbAstral = await RdDDice.rollTotal("1dhr" + request.nbAstral, { rollMode: "selfroll", showDice: HIDE_DICE }); // Mise à jour des nombres astraux du joueur this.addNbAstralIncorect(request.id, request.date, request.nbAstral); } if (Misc.getActiveUser(request.userId)?.isGM) { RdDUtility.responseNombreAstral(request); } else { game.socket.emit(SYSTEM_SOCKET_ID, { msg: "msg_response_nombre_astral", data: request }); } } } addNbAstralIncorect(actorId, date, nbAstral) { let astralData = this.listeNombreAstral.find((nombreAstral, i) => nombreAstral.index == date); astralData.valeursFausses.push({ actorId: actorId, nombreAstral: nbAstral }); game.settings.set(SYSTEM_RDD, "liste-nombre-astral", this.listeNombreAstral); } getHeureChance(heure) { return heure + (this.getCurrentNombreAstral() ?? 1) - 1; } /* -------------------------------------------- */ getHeuresChanceMalchance(heureNaissance) { let defHeure = RdDTimestamp.findHeure(heureNaissance); if (defHeure) { 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 []; } /* -------------------------------------------- */ getAjustementAstrologique(heureNaissance, name = undefined) { let defHeure = RdDTimestamp.findHeure(heureNaissance); if (defHeure) { 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; case 6: return -4; case 3: case 9: return -2; } } else if (name) { ui.notifications.warn(name + " n'a pas d'heure de naissance, ou elle est incorrecte : " + heureNaissance); } else { ui.notifications.warn(heureNaissance + " ne correspond pas à une heure de naissance"); } return 0; } /* -------------------------------------------- */ getData() { let formData = super.getData(); this.fillCalendrierData(formData); this.setPos(this.calendrierPos); return formData; } /* -------------------------------------------- */ setPos(pos) { return new Promise(resolve => { function check() { let elmnt = document.getElementById("calendar-time-container"); if (elmnt) { elmnt.style.bottom = undefined; let xPos = (pos.left) > window.innerWidth ? window.innerWidth - 200 : pos.left; let yPos = (pos.top) > window.innerHeight - 20 ? window.innerHeight - 100 : pos.top; elmnt.style.top = (yPos) + "px"; elmnt.style.left = (xPos) + "px"; resolve(); } else { setTimeout(check, 30); } } check(); }); } /* -------------------------------------------- */ updateDisplay() { let calendrier = this.fillCalendrierData(); // Rebuild text du calendrier let dateHTML = `${calendrier.jourMois} ${calendrier.nomMois} ${calendrier.annee} (${calendrier.nomSaison})` if (game.user.isGM) { dateHTML = dateHTML + " - NA: " + (this.getCurrentNombreAstral() ?? "indéterminé"); } 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; } for (const minute of document.getElementsByClassName("calendar-time-disp")) { minute.innerHTML = `${calendrier.minutesRelative} minutes`; } for (const heureImg of document.getElementsByClassName("calendar-heure-img")) { heureImg.src = calendrier.iconHeure; } } /* -------------------------------------------- */ async saveEditeur(calendrierData) { const newTimestamp = RdDTimestamp.timestamp( Number.parseInt(calendrierData.annee), RdDTimestamp.definition(calendrierData.moisKey).heure, Number.parseInt(calendrierData.jourMois), RdDTimestamp.definition(calendrierData.heureKey).heure, Number.parseInt(calendrierData.minutesRelative) ); await this.setNewTimestamp(newTimestamp); } /* -------------------------------------------- */ async showCalendarEditor() { let calendrierData = this.fillCalendrierData(); if (this.editeur == undefined) { let html = await renderTemplate('systems/foundryvtt-reve-de-dragon/templates/calendar-editor-template.html', calendrierData); this.editeur = new RdDCalendrierEditeur(html, this, calendrierData) } this.editeur.updateData(calendrierData); this.editeur.render(true); } static buildJoursMois() { return JOURS_DU_MOIS; } /* -------------------------------------------- */ async showAstrologieEditor() { const calendrierData = duplicate(this.fillCalendrierData()); this.listeNombreAstral = this.listeNombreAstral || []; 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"; } return astro; }); calendrierData.heuresParActeur = {}; game.actors.filter(it => it.isPersonnage() && it.hasPlayerOwner).forEach(actor => { let heureNaissance = actor.getHeureNaissance(); if (heureNaissance) { calendrierData.heuresParActeur[actor.name] = this.getHeuresChanceMalchance(heureNaissance); } }) 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); astrologieEditeur.render(true); } }