/** * Extend the base Dialog entity by defining a custom window to perform spell. * @extends {Dialog} */ import { RdDUtility } from "./rdd-utility.js"; import { TMRUtility } from "./tmr-utility.js"; import { RdDRollTables } from "./rdd-rolltables.js"; import { RdDResolutionTable } from "./rdd-resolution-table.js"; import { RdDTMRRencontreDialog } from "./rdd-tmr-rencontre-dialog.js"; /* -------------------------------------------- */ const tmrConstants = { col1_y: 30, col2_y: 55, cellw: 55, cellh: 55, gridx: 28, gridy: 28 } /* -------------------------------------------- */ 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.nbFatigue = this.viewOnly ? 0 : 1; // 1 premier point de fatigue du à la montée 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.nbFatigue).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(data) { this._tellToGM(this.actor.name + " a refoulé : " + this.currentRencontre.name ); await this.actor.deleteTMRRencontreAtPosition(); // Remove the stored rencontre if necessary let result = await this.actor.ajouterRefoulement( this.currentRencontre.data.refoulement ); this.updatePreviousRencontres(); console.log("-> refouler", this.currentRencontre) 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 } } /* -------------------------------------------- */ async gererTourbillon( value ) { this.nbFatigue += 1; await this.actor.reveActuelIncDec( -value ); if ( !this.currentRencontre.tourbillonDirection ) { this.currentRencontre.tourbillonDirection = TMRUtility.getDirectionPattern(); } let tmrPos = this.actor.data.data.reve.tmrpos; tmrPos.coord = TMRUtility.deplaceTMRSelonPattern( tmrPos.coord, this.currentRencontre.tourbillonDirection, value ); await this.actor.update({ "data.reve.tmrpos": tmrPos }); console.log("NEWPOS", tmrPos); } /* -------------------------------------------- */ async gererTourbillonRouge( ) { this.nbFatigue += 1; await this.actor.reveActuelIncDec( -2 ); // -2 pts de Reve a chaque itération if ( !this.currentRencontre.tourbillonDirection ) { this.currentRencontre.tourbillonDirection = TMRUtility.getDirectionPattern(); } let tmrPos = this.actor.data.data.reve.tmrpos; tmrPos.coord = TMRUtility.deplaceTMRSelonPattern( tmrPos.coord, this.currentRencontre.tourbillonDirection, 4 ); // Depl. 4 cases. await this.actor.update({ "data.reve.tmrpos": tmrPos }); await this.actor.santeIncDec( "vie", -1); // Et -1 PV console.log("TOURBILLON ROUGE", tmrPos); } /* -------------------------------------------- */ /** Gère les rencontres avec du post-processing graphique (passeur, messagers, tourbillons, ...) */ async rencontrePostProcess( rencontreData) { if (!rencontreData) return; // Sanity check this.rencontreState = rencontreData.state; // garder la trace de l'état en cours let locList if ( this.rencontreState == 'passeur' || this.rencontreState == 'messager' ) { // Récupère la liste des cases à portées locList = TMRUtility.getTMRArea(this.actor.data.data.reve.tmrpos.coord, this.currentRencontre.force, tmrConstants ); } else if ( this.rencontreState == 'passeurfou' ) { // Cas spécial du passeur fou let sortReserve = this.actor.data.data.reve.reserve[0]; let tmrPos if ( sortReserve ) { tmrPos = sortReserve.coord; // Passeur fou positionne sur la case d'un ort en réserve (TODO : Choisir le plus loin) } else { let direction = TMRUtility.getDirectionPattern(); // Déplacement aléatoire de la force du Passeur Fou tmrPos = TMRUtility.deplaceTMRSelonPattern(this.actor.data.data.reve.tmrpos.coord, direction, this.currentRencontre.force ); } await this.actor.update({ "data.reve.tmrpos": tmrPos }); } else if ( this.rencontreState == 'changeur' ) { // Liste des cases de même type locList = TMRUtility.getLocationTypeList( this.actor.data.data.reve.tmrpos.coord ); } else if ( this.rencontreState == 'reflet' ) { this.nbFatigue += 1; } else if ( this.rencontreState == 'tourbillonblanc' ) { await this.gererTourbillon(1); } else if ( this.rencontreState == 'tourbillonnoir' ) { await this.gererTourbillon(2); } else if ( this.rencontreState == 'tourbillonrouge' ) { await this.gererTourbillonRouge(); } else { this.currentRencontre = undefined; // Cleanup, not used anymore } if ( locList ) this.colorierZoneRencontre( locList ); } /* -------------------------------------------- */ checkQuitterTMR() { if ( this.actor.data.data.reve.reve.value == 0) { this._tellToGM("Vos Points de Rêve sont à 0 : vous quittez les Terres médianes !"); this.close(); } if ( this.nbFatigue == this.actor.data.data.sante.fatigue.max ) { this._tellToGM("Vous vous écroulez de fatigue : vous quittez les Terres médianes !"); this.close(); } if ( this.actor.data.data.sante.vie.value == 0 ) { this._tellToGM("Vous n'avez plus de Points de Vie : vous quittez les Terres médianes !"); this.close(); } } /* -------------------------------------------- */ async maitriser(data) { this.actor.deleteTMRRencontreAtPosition(); // Remove the stored rencontre if necessary this.updatePreviousRencontres(); const draconic = this.actor.getBestDraconic(); const carac = this.actor.getReveActuel(); const etatGeneral = this.actor.getEtatGeneral(); const difficulte = draconic.data.niveau - this.currentRencontre.force + etatGeneral; console.log("Maitriser", carac, draconic.data.niveau, this.currentRencontre.force, etatGeneral); let rolled = await RdDResolutionTable.roll(carac, difficulte); let message = "
Test : Rêve actuel / " + draconic.name + " / " + this.currentRencontre.name + "" + "
" + RdDResolutionTable.explain(rolled); let rencontreData if (rolled.isEchec) { rencontreData = await TMRUtility.processRencontreEchec(this.actor, this.currentRencontre, rolled, this); message += rencontreData.message; this._tellToGM("Vous avez échoué à maîtriser un " + this.currentRencontre.name + " de force " + this.currentRencontre.force + message); if (this.currentRencontre.data.quitterTMR) { // Selon les rencontres, quitter TMR ou pas this.close(); } } else { rencontreData = await TMRUtility.processRencontreReussite(this.actor, this.currentRencontre, rolled); message += rencontreData.message; this._tellToGM("Vous avez réussi à maîtriser un " + this.currentRencontre.name + " de force " + this.currentRencontre.force + message); } await this.rencontrePostProcess( rencontreData ); console.log("-> matriser", this.currentRencontre); this.updateValuesDisplay(); this.checkQuitterTMR(); if ( this.rencontreState == 'reflet' || this.rencontreState == 'tourbillonblanc' || this.rencontreState == 'tourbillonnoir' ) this.maitriser(); } /* -------------------------------------------- */ _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(coordTMR, cellDescr) { if (this.viewOnly) { return; } this.currentRencontre = undefined; let rencontre = await this._jetDeRencontre(coordTMR, cellDescr); 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); dialog.render(true); } } /* -------------------------------------------- */ async _jetDeRencontre(coordTMR, cellDescr) { let rencontre = this.rencontresExistantes.find(prev => prev.coord == coordTMR); if (rencontre == undefined) { let myRoll = new Roll("1d7").roll(); if (myRoll.total == 7) { let isSpecial = this.actor.isRencontreSpeciale(); rencontre = await TMRUtility.rencontreTMRRoll(coordTMR, cellDescr, isSpecial ); } else { this._tellToUser(myRoll.total + ": Pas de rencontre en " + cellDescr.label + " (" + coordTMR + ")"); } } if (TMRUtility.isForceRencontre()) { return await TMRUtility.rencontreTMRRoll(coordTMR, cellDescr); } return rencontre; } /* -------------------------------------------- */ 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.getTMRDescription(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( cellDescr, coordTMR ) { for( let caseTMR of this.casesSpeciales) { if (caseTMR.data.coord == coordTMR) { // Match ! if (caseTMR.data.specific == 'trounoir') { let newTMR = TMRUtility.getTMRAleatoire(); let tmrPos = duplicate(this.actor.data.data.reve.tmrpos); tmrPos.coord = newTMR; 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) { for( let caseTMR of this.casesSpeciales) { if (caseTMR.data.coord == coordTMR && caseTMR.data.specific == 'maitrisee') { return true; } } return false; } /* -------------------------------------------- */ manageCaseHumideResult() { if (this.toclose) this.close(); } /* -------------------------------------------- */ async manageCaseHumide(cellDescr, coordTMR) { if (this.viewOnly) { return; } let isHumide = this.actor.checkIsAdditionnalHumide(cellDescr, coordTMR); if (cellDescr.type == "lac" || cellDescr.type == "fleuve" || cellDescr.type == "marais" || isHumide) { if ( this.isCaseMaitrisee( coordTMR ) ) { ChatMessage.create( { content: "Cette case humide est déja maitrisée grâce à votre Tête Quête des Eaux", whisper: ChatMessage.getWhisperRecipients(game.user.name) } ); return; } // 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 + " / " + cellDescr.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, seletedCarac: { 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); } } /* -------------------------------------------- */ 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 :