/** * Extend the base Dialog entity by defining a custom window to perform spell. * @extends {Dialog} */ import { RollDataAjustements } from "./rolldata-ajustements.js"; import { RdDUtility } from "./rdd-utility.js"; import { TMRUtility } from "./tmr-utility.js"; import { tmrConstants } from "./tmr-utility.js"; import { RdDResolutionTable } from "./rdd-resolution-table.js"; import { RdDTMRRencontreDialog } from "./rdd-tmr-rencontre-dialog.js"; import { TMRRencontres } from "./tmr-rencontres.js"; import { ChatUtility } from "./chat-utility.js"; /* -------------------------------------------- */ export class RdDTMRDialog extends Dialog { /* -------------------------------------------- */ constructor(html, actor, tmrData, mode) { const dialogConf = { title: "Terres Médianes de Rêve", content: html, buttons: { closeButton: { label: "Fermer", callback: html => this.close(html) } }, default: "closeButton" } const dialogOptions = { classes: ["tmrdialog"], width: 920, height: 980, 'z-index': 20 } super(dialogConf, dialogOptions); this.tmrdata = duplicate(tmrData); this.actor = actor; this.actor.tmrApp = this; // reference this app in the actor structure this.viewOnly = mode == "visu" this.fatigueParCase = this.viewOnly ? 0 : this.actor.getTMRFatigue(); this.cumulFatigue = 0; this.rencontresExistantes = duplicate(this.actor.data.data.reve.rencontre.list); this.sortReserves = duplicate(this.actor.data.data.reve.reserve.list); this.casesSpeciales = this.actor.data.items.filter(item => item.type == 'casetmr'); this.allTokens = []; this.rencontreState = 'aucune'; this.pixiApp = new PIXI.Application({ width: 720, height: 860 }); if (!this.viewOnly) { this.actor.setStatusDemiReve(true); this._tellToGM(this.actor.name + " monte dans les terres médianes (" + mode + ")"); } } /* -------------------------------------------- */ close() { this.actor.santeIncDec("fatigue", this.cumulFatigue).then(super.close()); // moving 1 cell costs 1 fatigue this.actor.tmrApp = undefined; // Cleanup reference this.actor.setStatusDemiReve(false); if (!this.viewOnly) { this._tellToGM(this.actor.name + " a quitté les terres médianes"); } } /* -------------------------------------------- */ displaySortReserve() { console.debug("displaySortReserve", this.sortReserves); for (let sort of this.sortReserves) { this._trackToken(this._tokenSortEnReserve(sort)); } } /* -------------------------------------------- */ displaySpecificCase() { for (let caseTMR of this.casesSpeciales) { console.log("SPEC CASE ", caseTMR); if (caseTMR.data.specific == 'trounoir') { this._trackToken(this._tokenTrouNoir(caseTMR.data.coord)); } else if (caseTMR.data.specific == 'attache') { this._trackToken(this._tokenTerreAttache(caseTMR.data.coord)); } else if (caseTMR.data.specific == 'debordement') { this._trackToken(this._tokenDebordement(caseTMR.data.coord)); } else if (caseTMR.data.specific == 'maitrisee') { this._trackToken(this._tokenMaitrisee(caseTMR.data.coord)); } } } /* -------------------------------------------- */ displayPreviousRencontres() { console.debug("displayPreviousRencontres", this.rencontresExistantes); for (let rencontre of this.rencontresExistantes) { this._trackToken(this._tokenRencontre(rencontre)); } } /* -------------------------------------------- */ updatePreviousRencontres() { this._removeTokens(t => t.rencontre != undefined); this.rencontresExistantes = duplicate(this.actor.data.data.reve.rencontre.list); this.displayPreviousRencontres(); } /* -------------------------------------------- */ updateSortReserve() { this._removeTokens(t => t.sort != undefined); this.sortReserves = duplicate(this.actor.data.data.reve.reserve.list); this.displaySortReserve(); } /* -------------------------------------------- */ async derober() { await this.actor.addTMRRencontre(this.currentRencontre); console.log("-> derober", this.currentRencontre); this._tellToGM(this.actor.name + " s'est dérobé et quitte les TMR."); this.close(); } /* -------------------------------------------- */ async refouler() { this._tellToGM(this.actor.name + " a refoulé : " + this.currentRencontre.name); await this.actor.deleteTMRRencontreAtPosition(); // Remove the stored rencontre if necessary await this.actor.ajouterRefoulement(this.currentRencontre.refoulement ?? 1); this.updatePreviousRencontres(); console.log("-> refouler", this.currentRencontre) this.updateValuesDisplay(); this.nettoyerRencontre(); } /* -------------------------------------------- */ async ignorerRencontre() { this._tellToGM(this.actor.name + " a ignoré : " + this.currentRencontre.name); await this.actor.deleteTMRRencontreAtPosition(); // Remove the stored rencontre if necessary this.updatePreviousRencontres(); this.updateValuesDisplay(); this.nettoyerRencontre(); } /* -------------------------------------------- */ colorierZoneRencontre(locList) { this.currentRencontre.graphics = []; // Keep track of rectangles to delete it this.currentRencontre.locList = duplicate(locList); // And track of allowed location for (let loc of locList) { let rect = this._getCaseRectangleCoord(loc); var rectDraw = new PIXI.Graphics(); rectDraw.beginFill(0xFFFF00, 0.3); // set the line style to have a width of 5 and set the color to red rectDraw.lineStyle(5, 0xFF0000); // draw a rectangle rectDraw.drawRect(rect.x, rect.y, rect.w, rect.h); this.pixiApp.stage.addChild(rectDraw); this.currentRencontre.graphics.push(rectDraw); // garder les objets pour gestion post-click } } /* -------------------------------------------- */ // garder la trace de l'état en cours setStateRencontre(state) { this.rencontreState = state; } async choisirCasePortee(coord, portee) { // Récupère la liste des cases à portées let locList = TMRUtility.getTMRPortee(coord, portee); this.colorierZoneRencontre(locList); } async choisirCaseType(type) { const locList = TMRUtility.getListCoordTMR(type); this.colorierZoneRencontre(locList); } /* -------------------------------------------- */ checkQuitterTMR() { if (this.actor.isDead()) { this._tellToGM("Vous êtes mort : vous quittez les Terres médianes !"); this.close(); return true; } const resteAvantInconscience = this.actor.getFatigueMax() - this.actor.getFatigueActuelle() - this.cumulFatigue; if (resteAvantInconscience <= 0) { this._tellToGM("Vous vous écroulez de fatigue : vous quittez les Terres médianes !"); this.quitterLesTMRInconscient(); return true; } if (this.actor.getReveActuel() == 0) { this._tellToGM("Vos Points de Rêve sont à 0 : vous quittez les Terres médianes !"); this.quitterLesTMRInconscient(); return true; } return false; } async quitterLesTMRInconscient() { if (this.currentRencontre?.isPersistant) { await this.refouler(); } this.close(); } /* -------------------------------------------- */ async maitriser() { this.actor.deleteTMRRencontreAtPosition(); this.updatePreviousRencontres(); let rencontreData = { actor: this.actor, alias: this.actor.name, reveDepart: this.actor.getReveActuel(), competence: this.actor.getBestDraconic(), rencontre: this.currentRencontre, nbRounds: 1, tmr: TMRUtility.getTMR(this.actor.data.data.reve.tmrpos.coord) } await this._tentativeMaitrise(rencontreData); } async _tentativeMaitrise(rencontreData) { console.log("-> matriser", rencontreData); rencontreData.reve = this.actor.getReveActuel(); rencontreData.etat = this.actor.getEtatGeneral(); RollDataAjustements.calcul(rencontreData, this.actor); rencontreData.rolled = await RdDResolutionTable.roll(rencontreData.reve, RollDataAjustements.sum(rencontreData.ajustements)); let postProcess = await TMRRencontres.gererRencontre(this, rencontreData); ChatMessage.create({ whisper: ChatUtility.getWhisperRecipientsAndGMs(game.user.name), content: await renderTemplate(`systems/foundryvtt-reve-de-dragon/templates/chat-rencontre-tmr.html`, rencontreData) }); if (postProcess) { /** Gère les rencontres avec du post-processing (passeur, messagers, tourbillons, ...) */ await postProcess(this, rencontreData); } else { this.currentRencontre = undefined; } this.updateValuesDisplay(); if (this.checkQuitterTMR()) { return; } else if (rencontreData.rolled.isEchec && rencontreData.rencontre.isPersistant) { setTimeout(() => { rencontreData.nbRounds++; this.cumulFatigue += this.fatigueParCase; this._tentativeMaitrise(rencontreData); this._deleteTmrMessages(rencontreData.actor, rencontreData.nbRounds); }, 2000); } } _deleteTmrMessages(actor, nbRounds = -1) { setTimeout(() => { if (nbRounds < 0) { ChatUtility.removeChatMessageContaining(`

`); } } }, 500); } /* -------------------------------------------- */ _tellToUser(message) { ChatMessage.create({ content: message, user: game.user._id, whisper: [game.user._id] }); } /* -------------------------------------------- */ _tellToGM(message) { ChatMessage.create({ content: message, user: game.user._id, whisper: ChatMessage.getWhisperRecipients("GM") }); } /* -------------------------------------------- */ async manageRencontre(tmr, postRencontre) { if (this.viewOnly) { return; } this.currentRencontre = undefined; let rencontre = await this._jetDeRencontre(tmr); if (rencontre) { // Manages it if (rencontre.rencontre) rencontre = rencontre.rencontre; // Manage stored rencontres console.log("manageRencontre", rencontre); this.currentRencontre = duplicate(rencontre); let dialog = new RdDTMRRencontreDialog("", this, this.currentRencontre, postRencontre); dialog.render(true); } } /* -------------------------------------------- */ async _jetDeRencontre(tmr) { if (TMRUtility.isForceRencontre()) { return await TMRUtility.rencontreTMRRoll(tmr.coord, tmr); } let rencontre = this.rencontresExistantes.find(prev => prev.coord == tmr.coord); if (rencontre) { return rencontre; } let myRoll = new Roll("1d7").evaluate(); if (myRoll.total == 7) { let isMauvaise = this.actor.isRencontreSpeciale(); return await TMRUtility.rencontreTMRRoll(tmr.coord, tmr, isMauvaise); } this._tellToUser(myRoll.total + ": Pas de rencontre en " + tmr.label + " (" + tmr.coord + ")"); } /* -------------------------------------------- */ updateValuesDisplay() { let ptsreve = document.getElementById("tmr-pointsreve-value"); ptsreve.innerHTML = this.actor.data.data.reve.reve.value; let tmrpos = document.getElementById("tmr-pos"); let tmr = TMRUtility.getTMR(this.actor.data.data.reve.tmrpos.coord); tmrpos.innerHTML = this.actor.data.data.reve.tmrpos.coord + " (" + tmr.label + ")"; let etat = document.getElementById("tmr-etatgeneral-value"); etat.innerHTML = this.actor.getEtatGeneral(); let refoulement = document.getElementById("tmr-refoulement-value"); refoulement.innerHTML = this.actor.data.data.reve.refoulement.value; let fatigueItem = document.getElementById("tmr-fatigue-table"); //console.log("Refresh : ", this.actor.data.data.sante.fatigue.value); fatigueItem.innerHTML = "" + RdDUtility.makeHTMLfatigueMatrix(this.actor.data.data.sante.fatigue.value, this.actor.data.data.sante.endurance.max).html() + "
"; } /* -------------------------------------------- */ async manageCaseSpeciale(tmr) { for (let caseTMR of this.casesSpeciales) { if (caseTMR.data.coord == tmr.coord) { // Match ! if (caseTMR.data.specific == 'trounoir') { let newTMR = TMRUtility.getTMRAleatoire(); let tmrPos = duplicate(this.actor.data.data.reve.tmrpos); tmrPos.coord = newTMR.coord; await this.actor.update({ "data.reve.tmrpos": tmrPos }); ChatMessage.create({ content: "Vous êtes rentré sur un Trou Noir : ré-insertion aléatoire.", whisper: ChatMessage.getWhisperRecipients(game.user.name) }); } } } } /* -------------------------------------------- */ isCaseMaitrisee(coordTMR) { return this.casesSpeciales.find(it => it.data.coord = coordTMR && it.data.specific == 'maitrisee'); } /* -------------------------------------------- */ manageCaseHumideResult() { if (this.toclose) this.close(); } /* -------------------------------------------- */ async manageCaseHumide(tmr) { if (this.viewOnly || this.currentRencontre) { return; } if (this.isCaseHumide(tmr)) { // TODO: permettre de choisir la voie de draconic? let draconic = this.actor.getBestDraconic(); let carac = this.actor.getReveActuel(); const etatGeneral = this.actor.getEtatGeneral(); let difficulte = draconic.data.niveau - 7; let rolled = await RdDResolutionTable.roll(carac, difficulte); // Gestion du souffle Double Résistance du Fleuve if (this.actor.isDoubleResistanceFleuve()) { let rolled2 = await RdDResolutionTable.roll(carac, difficulte); if (rolled2.isEchec) rolled = rolled; } console.log("manageCaseHumide >>", rolled); let explication = ""; let msg2MJ = ""; this.toclose = rolled.isEchec; if (rolled.isEchec) { explication += "Vous êtes entré sur une case humide, et vous avez raté votre maîtrise ! Vous quittez les Terres Médianes !" msg2MJ += game.user.name + " est rentré sur une case humides : Echec !"; } else { explication += "Vous êtes entré sur une case humide, et vous avez réussi votre maîtrise !" msg2MJ += game.user.name + " est rentré sur une case humides : Réussite !"; } explication += "
Test : Rêve actuel / " + draconic.name + " / " + tmr.type + "" + RdDResolutionTable.explain(rolled); if (rolled.isETotal) { let souffle = await this.actor.ajouterSouffle({ chat: false }); explication += "
Vous avez fait un Echec Total. Vous subissez un Souffle de Dragon : " + souffle.name; msg2MJ += "
Et a reçu un Souffle de Dragon : " + souffle.name; } if (rolled.isPart) { explication += "
Vous avez fait une Réussite Particulière"; this.actor._appliquerAjoutExperience({ rolled: rolled, selectedCarac: { label: 'reve' }, competence: draconic.name }) msg2MJ += "
Et a fait une réussite particulière"; } // Notification au MJ ChatMessage.create({ content: msg2MJ, whisper: ChatMessage.getWhisperRecipients("GM") }); // Et au joueur (ca pourrait être un message de tchat d'ailleurs) let humideDiag = new Dialog({ title: "Case humide", content: explication, buttons: { choice: { icon: '', label: "Fermer", callback: () => this.manageCaseHumideResult() } } } ); humideDiag.render(true); } } isCaseHumide(tmr) { if (this.isCaseMaitrisee(tmr.coord)) { ChatMessage.create({ content: tmr.label + ": cette case humide est déja maitrisée grâce à votre Tête Quête des Eaux", whisper: ChatMessage.getWhisperRecipients(game.user.name) }); return false; } if (this.actor.isCaseHumideAdditionelle(tmr)) { return true; } return tmr.type == "lac" || tmr.type == "fleuve" || tmr.type == "marais"; } /* -------------------------------------------- */ isReserveExtensible(coordTMR) { for (let caseTMR of this.casesSpeciales) { if (caseTMR.data.specific == 'reserve_extensible' && caseTMR.data.coord == coordTMR) return true; } return false; } /* -------------------------------------------- */ async declencheSortEnReserve(coordTMR) { if (this.viewOnly) { return; } let sortReserveList = TMRUtility.getSortReserveList(this.sortReserves, coordTMR); if (sortReserveList.length > 0) { if (this.actor.isReserveEnSecurite() || this.isReserveExtensible(coordTMR)) { let msg = "Vous êtes sur une case avec un Sort en Réserve. Grâce à votre Tête Reserve en Sécurité ou Réserve Exensible, vous pouvez contrôler le déclenchement. Cliquez si vous souhaitez le déclencher :