38d0ba2734
Utiliser les différents repères de cases: - coordonnées TMR A5 - oddq pour les coordonnées de case (ligne, colonne) - axial (q,r) pour effectuer les calculs de distance utiliser x, y rend la distinction de positions de pixels vs position dans la grille parfois ardue. Utilisation des coordonnées axiales pour le calcul de distance.
1038 lines
37 KiB
JavaScript
1038 lines
37 KiB
JavaScript
import { RollDataAjustements } from "./rolldata-ajustements.js";
|
|
import { RdDUtility } from "./rdd-utility.js";
|
|
import { TMRUtility } from "./tmr-utility.js";
|
|
import { tmrConstants } from "./tmr-constants.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";
|
|
import { RdDRoll } from "./rdd-roll.js";
|
|
import { Poetique } from "./poetique.js";
|
|
import { EffetsDraconiques } from "./tmr/effets-draconiques.js";
|
|
import { PixiTMR } from "./tmr/pixi-tmr.js";
|
|
import { Draconique } from "./tmr/draconique.js";
|
|
import { Misc } from "./misc.js";
|
|
import { HtmlUtility } from "./html-utility.js";
|
|
import { ReglesOptionelles } from "./regles-optionelles.js";
|
|
import { RdDDice } from "./rdd-dice.js";
|
|
/* -------------------------------------------- */
|
|
|
|
export class RdDTMRDialog extends Dialog {
|
|
|
|
static async create(html, actor, tmrData) {
|
|
|
|
if (tmrData.mode != 'visu') {
|
|
// Notification au MJ
|
|
ChatMessage.create({ content: actor.name + " est monté dans les TMR en mode : " + tmrData.mode, whisper: ChatMessage.getWhisperRecipients("GM") });
|
|
}
|
|
|
|
return new RdDTMRDialog(html, actor, tmrData);
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
constructor(html, actor, tmrData) {
|
|
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': 40
|
|
}
|
|
super(dialogConf, dialogOptions);
|
|
|
|
this.tmrdata = duplicate(tmrData);
|
|
this.actor = actor;
|
|
this.actor.tmrApp = this; // reference this app in the actor structure
|
|
this.viewOnly = tmrData.mode == "visu"
|
|
this.fatigueParCase = this.viewOnly || !ReglesOptionelles.isUsing("appliquer-fatigue") ? 0 : this.actor.getTMRFatigue();
|
|
this.cumulFatigue = 0;
|
|
this.loadRencontres();
|
|
this.loadSortsReserve();
|
|
this.loadCasesSpeciales();
|
|
this.allTokens = [];
|
|
this.rencontreState = 'aucune';
|
|
this.pixiApp = new PIXI.Application({ width: 720, height: 860 });
|
|
|
|
this.pixiTMR = new PixiTMR(this, this.pixiApp);
|
|
this.cacheTMR = (game.user.isGM) ? false : actor.isTMRCache();
|
|
|
|
this.callbacksOnAnimate = [];
|
|
if (!this.viewOnly) {
|
|
this._tellToGM(this.actor.name + " monte dans les terres médianes (" + tmrData.mode + ")");
|
|
}
|
|
|
|
// load the texture we need
|
|
this.pixiTMR.load((loader, resources) => this.createPixiSprites());
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
loadCasesSpeciales() {
|
|
this.casesSpeciales = this.actor.data.items.filter(item => Draconique.isCaseTMR(item));
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
loadSortsReserve() {
|
|
this.sortsReserves = Misc.data(this.actor).data.reve.reserve.list;
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
loadRencontres() {
|
|
this.rencontresExistantes = this.actor.getTMRRencontres();
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
createPixiSprites() {
|
|
EffetsDraconiques.carteTmr.createSprite(this.pixiTMR);
|
|
this.updateTokens();
|
|
this.demiReve = this._tokenDemiReve();
|
|
this._updateDemiReve();
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
_createTokens() {
|
|
let tokens = this._getTokensCasesTmr()
|
|
.concat(this._getTokensRencontres())
|
|
.concat(this._getTokensSortsReserve());
|
|
|
|
for (let t of tokens) {
|
|
this._trackToken(t);
|
|
}
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
updateTokens() {
|
|
this._removeTokens(t => true);
|
|
this.loadRencontres();
|
|
this.loadSortsReserve();
|
|
this.loadCasesSpeciales();
|
|
this._createTokens();
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
removeToken(tmr, casetmr) {
|
|
this._removeTokens(t => t.coordTMR() == tmr.coord && t.caseSpeciale?._id == casetmr._id);
|
|
this.updateTokens()
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
_getTokensCasesTmr() {
|
|
return this.casesSpeciales.map(c => this._tokenCaseSpeciale(c)).filter(token => token);
|
|
}
|
|
_getTokensRencontres() {
|
|
return this.rencontresExistantes.map(it => this._tokenRencontre(it));
|
|
}
|
|
_getTokensSortsReserve() {
|
|
return this.sortsReserves.map(it => this._tokenSortEnReserve(it));
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
_tokenRencontre(rencontre) {
|
|
return EffetsDraconiques.rencontre.token(this.pixiTMR, rencontre, () => rencontre.coord);
|
|
}
|
|
_tokenCaseSpeciale(casetmr) {
|
|
const caseData = Misc.data(casetmr);
|
|
const draconique = Draconique.get(caseData.data.specific);
|
|
return draconique?.token(this.pixiTMR, caseData, () => caseData.data.coord);
|
|
}
|
|
_tokenSortEnReserve(sortEnReserve) {
|
|
return EffetsDraconiques.sortReserve.token(this.pixiTMR, sortEnReserve.sort, () => sortEnReserve.coord);
|
|
}
|
|
|
|
_tokenDemiReve() {
|
|
const actorData = Misc.data(this.actor);
|
|
return EffetsDraconiques.demiReve.token(this.pixiTMR, actorData, () => actorData.data.reve.tmrpos.coord);
|
|
}
|
|
|
|
_updateDemiReve() {
|
|
this.notifierResonanceSigneDraconique(this._getActorCoord());
|
|
|
|
if (!this.cacheTMR) {
|
|
this._setTokenPosition(this.demiReve);
|
|
}
|
|
}
|
|
|
|
_getActorCoord() {
|
|
return Misc.data(this.actor).data.reve.tmrpos.coord;
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async moveFromKey(move) {
|
|
let oddq = TMRUtility.coordTMRToOddq(this._getActorCoord());
|
|
|
|
if (move == 'top') oddq.row -= 1;
|
|
if (move == 'bottom') oddq.row += 1;
|
|
if (move.includes('left')) oddq.col -= 1;
|
|
if (move.includes('right')) oddq.col += 1;
|
|
if (oddq.col % 2 == 1) {
|
|
if (move == 'top-left') oddq.row -= 1;
|
|
if (move == 'top-right') oddq.row -= 1;
|
|
} else {
|
|
if (move == 'bottom-left') oddq.row += 1;
|
|
if (move == 'bottom-right') oddq.row += 1;
|
|
}
|
|
|
|
let targetCoord = TMRUtility.oddqToCoordTMR(oddq);
|
|
await this._deplacerDemiReve(targetCoord, 'normal');
|
|
this.checkQuitterTMR();
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async activateListeners(html) {
|
|
super.activateListeners(html);
|
|
|
|
document.getElementById("tmrrow1").insertCell(0).append(this.pixiApp.view);
|
|
|
|
if (this.viewOnly) {
|
|
html.find('.lancer-sort').remove();
|
|
html.find('.lire-signe-draconique').remove();
|
|
return;
|
|
}
|
|
|
|
HtmlUtility._showControlWhen($(".appliquerFatigue"), ReglesOptionelles.isUsing("appliquer-fatigue"));
|
|
HtmlUtility._showControlWhen($(".lire-signe-draconique"), this.actor.isResonanceSigneDraconique(this._getActorCoord()));
|
|
|
|
// Roll Sort
|
|
html.find('.lancer-sort').click((event) => {
|
|
this.actor.rollUnSort(this._getActorCoord());
|
|
});
|
|
html.find('.lire-signe-draconique').click((event) => {
|
|
this.actor.rollLireSigneDraconique(this._getActorCoord());
|
|
});
|
|
|
|
html.find('#dir-top').click((event) => {
|
|
this.moveFromKey("top");
|
|
});
|
|
html.find('#dir-top-left').click((event) => {
|
|
this.moveFromKey("top-left");
|
|
});
|
|
html.find('#dir-top-right').click((event) => {
|
|
this.moveFromKey("top-right");
|
|
});
|
|
html.find('#dir-bottom-left').click((event) => {
|
|
this.moveFromKey("bottom-left");
|
|
});
|
|
html.find('#dir-bottom-right').click((event) => {
|
|
this.moveFromKey("bottom-right");
|
|
});
|
|
html.find('#dir-bottom').click((event) => {
|
|
this.moveFromKey("bottom");
|
|
});
|
|
|
|
// Gestion du cout de montée en points de rêve
|
|
let reveCout = ((this.tmrdata.isRapide && !EffetsDraconiques.isDeplacementAccelere(this.actor)) ? -2 : -1) - this.actor.countMonteeLaborieuse();
|
|
if (ReglesOptionelles.isUsing("appliquer-fatigue")) {
|
|
this.cumulFatigue += this.fatigueParCase;
|
|
}
|
|
await this.actor.reveActuelIncDec(reveCout);
|
|
|
|
// Le reste...
|
|
this.updateValuesDisplay();
|
|
let tmr = TMRUtility.getTMR(this._getActorCoord());
|
|
await this.manageRencontre(tmr, () => {
|
|
this.postRencontre(tmr);
|
|
});
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async updateValuesDisplay() {
|
|
const coord = this._getActorCoord();
|
|
const actorData = Misc.data(this.actor);
|
|
|
|
HtmlUtility._showControlWhen($(".lire-signe-draconique"), this.actor.isResonanceSigneDraconique(coord));
|
|
|
|
let ptsreve = document.getElementById("tmr-pointsreve-value");
|
|
ptsreve.innerHTML = actorData.data.reve.reve.value;
|
|
|
|
let tmrpos = document.getElementById("tmr-pos");
|
|
if (this.cacheTMR) {
|
|
tmrpos.innerHTML = '?? (' + TMRUtility.getTMRType(coord) + ')';
|
|
} else {
|
|
tmrpos.innerHTML = coord + " (" + TMRUtility.getTMRLabel(coord) + ")";
|
|
}
|
|
|
|
let etat = document.getElementById("tmr-etatgeneral-value");
|
|
etat.innerHTML = this.actor.getEtatGeneral();
|
|
|
|
let refoulement = document.getElementById("tmr-refoulement-value");
|
|
refoulement.innerHTML = actorData.data.reve.refoulement.value;
|
|
|
|
if (ReglesOptionelles.isUsing("appliquer-fatigue")) {
|
|
let fatigueItem = document.getElementById("tmr-fatigue-table");
|
|
//console.log("Refresh : ", actorData.data.sante.fatigue.value);
|
|
fatigueItem.innerHTML = "<table class='table-fatigue'>" + RdDUtility.makeHTMLfatigueMatrix(actorData.data.sante.fatigue.value, actorData.data.sante.endurance.max).html() + "</table>";
|
|
}
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
close() {
|
|
if ( this.actor.tmrApp ) {
|
|
this.actor.tmrApp = undefined; // Cleanup reference
|
|
if ( !this.viewOnly ) {
|
|
this.actor.setStatusEffect("EFFECT.StatusDemiReve", false);
|
|
this._tellToGM(this.actor.name + " a quitté les terres médianes");
|
|
}
|
|
this.actor.santeIncDec("fatigue", this.cumulFatigue).then(super.close()); // moving 1 cell costs 1 fatigue
|
|
}
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
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.updateTokens();
|
|
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.updateTokens();
|
|
this.updateValuesDisplay();
|
|
this.nettoyerRencontre();
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
colorierZoneRencontre(listCoordTMR) {
|
|
this.currentRencontre.graphics = []; // Keep track of rectangles to delete it
|
|
this.currentRencontre.locList = duplicate(listCoordTMR); // And track of allowed location
|
|
for (let coordTMR of listCoordTMR) {
|
|
let rect = this._getCaseRectangleCoord(coordTMR);
|
|
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.filterTMR(it => it.type == type).map(it => it.coord);
|
|
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 (ReglesOptionelles.isUsing("appliquer-fatigue") && 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 maitriserRencontre() {
|
|
this.actor.deleteTMRRencontreAtPosition();
|
|
this.updateTokens();
|
|
|
|
let rencontreData = {
|
|
actor: this.actor,
|
|
alias: this.actor.name,
|
|
reveDepart: this.actor.getReveActuel(),
|
|
competence: this.actor.getBestDraconic(),
|
|
rencontre: this.currentRencontre,
|
|
nbRounds: 1,
|
|
canClose: false,
|
|
selectedCarac: {label: "reve-actuel"},
|
|
tmr: TMRUtility.getTMR(this._getActorCoord())
|
|
}
|
|
|
|
await this._tentativeMaitrise(rencontreData);
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async _tentativeMaitrise(rencData) {
|
|
console.log("-> matriser", rencData);
|
|
|
|
rencData.reve = this.actor.getReveActuel();
|
|
rencData.etat = this.actor.getEtatGeneral();
|
|
|
|
RollDataAjustements.calcul(rencData, this.actor);
|
|
|
|
rencData.rolled = rencData.presentCite
|
|
? this._rollPresentCite(rencData)
|
|
: await RdDResolutionTable.roll(rencData.reve, RollDataAjustements.sum(rencData.ajustements));
|
|
|
|
let postProcess = await TMRRencontres.gererRencontre(this, rencData);
|
|
|
|
ChatMessage.create({
|
|
whisper: ChatUtility.getWhisperRecipientsAndGMs(game.user.name),
|
|
content: await renderTemplate(`systems/foundryvtt-reve-de-dragon/templates/chat-rencontre-tmr.html`, rencData)
|
|
});
|
|
|
|
if (postProcess) {
|
|
/** Gère les rencontres avec du post-processing (passeur, messagers, tourbillons, ...) */
|
|
await postProcess(this, rencData);
|
|
}
|
|
else {
|
|
this.currentRencontre = undefined;
|
|
}
|
|
|
|
this.updateValuesDisplay();
|
|
if (this.checkQuitterTMR()) {
|
|
return;
|
|
}
|
|
else if (rencData.rolled.isEchec && rencData.rencontre.isPersistant) {
|
|
setTimeout(() => {
|
|
rencData.nbRounds++;
|
|
if (ReglesOptionelles.isUsing("appliquer-fatigue")) {
|
|
this.cumulFatigue += this.fatigueParCase;
|
|
}
|
|
this._tentativeMaitrise(rencData);
|
|
this._deleteTmrMessages(rencData.actor, rencData.nbRounds);
|
|
}, 2000);
|
|
}
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
_rollPresentCite(rencontreData) {
|
|
let rolled = RdDResolutionTable.computeChances(rencontreData.reve, 0);
|
|
mergeObject(rolled, { caracValue: rencontreData.reve, finalLevel: 0, roll: rolled.score });
|
|
RdDResolutionTable.succesRequis(rolled);
|
|
return rolled;
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
_deleteTmrMessages(actor, nbRounds = -1) {
|
|
setTimeout(() => {
|
|
if (nbRounds < 0) {
|
|
ChatUtility.removeChatMessageContaining(`<h4 data-categorie="tmr" data-actor-id="${actor._id}"`);
|
|
}
|
|
else {
|
|
for (let i = 1; i < nbRounds; i++) {
|
|
ChatUtility.removeChatMessageContaining(`<h4 data-categorie="tmr" data-actor-id="${actor._id}" data-rencontre-round="${i}">`);
|
|
}
|
|
}
|
|
}, 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") });
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
_tellToUserAndGM(message) {
|
|
ChatMessage.create({ content: message, user: game.user.id, whisper: [game.user.id].concat(ChatMessage.getWhisperRecipients("GM")) });
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async manageRencontre(tmr, postRencontre) {
|
|
if (this.viewOnly) {
|
|
return;
|
|
}
|
|
this.currentRencontre = undefined;
|
|
if (this._presentCite(tmr, postRencontre)) {
|
|
return;
|
|
}
|
|
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);
|
|
}
|
|
else {
|
|
postRencontre();
|
|
}
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
_presentCite(tmr, postRencontre) {
|
|
const presentCite = this.casesSpeciales.find(c => EffetsDraconiques.presentCites.isCase(c, tmr.coord));
|
|
if (presentCite) {
|
|
this.minimize();
|
|
EffetsDraconiques.presentCites.choisirUnPresent(presentCite, (type => this._utiliserPresentCite(presentCite, type, tmr, postRencontre)));
|
|
}
|
|
return presentCite;
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async _utiliserPresentCite(presentCite, typeRencontre, tmr, postRencontre) {
|
|
this.currentRencontre = TMRRencontres.getRencontre(typeRencontre);
|
|
await TMRRencontres.evaluerForceRencontre(this.currentRencontre);
|
|
await EffetsDraconiques.presentCites.ouvrirLePresent(this.actor, presentCite);
|
|
this.removeToken(tmr, presentCite);
|
|
|
|
// simuler une rencontre
|
|
let rencontreData = {
|
|
actor: this.actor,
|
|
alias: this.actor.name,
|
|
reveDepart: this.actor.getReveActuel(),
|
|
competence: this.actor.getBestDraconic(),
|
|
rencontre: this.currentRencontre,
|
|
tmr: tmr,
|
|
presentCite: presentCite
|
|
};
|
|
await this._tentativeMaitrise(rencontreData);
|
|
|
|
this.maximize();
|
|
postRencontre();
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async _jetDeRencontre(tmr) {
|
|
let rencontre = this.rencontresExistantes.find(prev => prev.coord == tmr.coord);
|
|
if (rencontre) {
|
|
return rencontre;
|
|
}
|
|
let locTMR = (this.cacheTMR) ? Misc.upperFirst(tmr.type) + " ??" : tmr.label + " (" + tmr.coord + ")";
|
|
|
|
let myRoll = await RdDDice.rollTotal("1dt");
|
|
if (TMRUtility.isForceRencontre() || myRoll == 7) {
|
|
this._tellToUser(myRoll + ": Rencontre en " + locTMR);
|
|
return await this.rencontreTMRRoll(tmr, this.actor.isRencontreSpeciale());
|
|
} else {
|
|
this._tellToUser(myRoll + ": Pas de rencontre en " + locTMR);
|
|
}
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async rencontreTMRRoll(tmr, isMauvaise = false) {
|
|
let rencontre = TMRUtility.utiliseForceRencontre() ??
|
|
(isMauvaise
|
|
? await TMRRencontres.getMauvaiseRencontre()
|
|
: await TMRRencontres.getRencontreAleatoire(tmr.type));
|
|
rencontre.coord = tmr.coord;
|
|
rencontre.date = game.system.rdd.calendrier.getDateFromIndex();
|
|
rencontre.heure = game.system.rdd.calendrier.getCurrentHeure();
|
|
return rencontre;
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async manageTmrInnaccessible(tmr) {
|
|
const caseTmrInnaccessible = this.casesSpeciales.find(c => EffetsDraconiques.isInnaccessible(c, tmr.coord));
|
|
if (caseTmrInnaccessible) {
|
|
return await this.actor.reinsertionAleatoire(caseTmrInnaccessible.name);
|
|
}
|
|
return tmr;
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async manageCaseHumide(tmr) {
|
|
if (this.isCaseHumide(tmr)) {
|
|
let rollData = {
|
|
actor: this.actor,
|
|
competence: duplicate(this.actor.getBestDraconic()),
|
|
tmr: tmr,
|
|
canClose: false,
|
|
diffLibre: -7,
|
|
forceCarac: { 'reve-actuel': { label: "Rêve Actuel", value: this.actor.getReveActuel() } },
|
|
maitrise: { verbe: 'maîtriser', action: 'Maîtriser le fleuve' }
|
|
}
|
|
rollData.double = EffetsDraconiques.isDoubleResistanceFleuve(this.actor) ? true : undefined,
|
|
rollData.competence.data.defaut_carac = 'reve-actuel';
|
|
await this._rollMaitriseCaseHumide(rollData);
|
|
}
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async _rollMaitriseCaseHumide(rollData) {
|
|
await this._maitriserTMR(rollData, r => this._resultatMaitriseCaseHumide(r));
|
|
}
|
|
|
|
async _resultatMaitriseCaseHumide(rollData) {
|
|
await this.souffleSiEchecTotal(rollData);
|
|
this.toclose = rollData.rolled.isEchec;
|
|
if (rollData.rolled.isSuccess && rollData.double) {
|
|
rollData.previous = { rolled: rollData.rolled, ajustements: rollData.ajustements };
|
|
rollData.double = undefined;
|
|
await this._rollMaitriseCaseHumide(rollData);
|
|
return;
|
|
}
|
|
rollData.poesie = await Poetique.getExtrait();
|
|
ChatMessage.create({
|
|
whisper: ChatUtility.getWhisperRecipientsAndGMs(game.user.name),
|
|
content: await renderTemplate(`systems/foundryvtt-reve-de-dragon/templates/chat-resultat-maitrise-tmr.html`, rollData)
|
|
});
|
|
if (rollData.rolled.isEchec) {
|
|
this.close();
|
|
}
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async souffleSiEchecTotal(rollData) {
|
|
if (rollData.rolled.isETotal) {
|
|
rollData.souffle = await this.actor.ajouterSouffle({ chat: false });
|
|
}
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
isCaseHumide(tmr) {
|
|
if (!(TMRUtility.isCaseHumide(tmr) || this.isCaseHumideAdditionelle(tmr))) {
|
|
return undefined;
|
|
}
|
|
if (this.isCaseMaitrisee(tmr.coord)) {
|
|
ChatMessage.create({
|
|
content: tmr.label + ": cette case humide est déja maitrisée grâce à votre Tête <strong>Quête des Eaux</strong>",
|
|
whisper: ChatMessage.getWhisperRecipients(game.user.name)
|
|
});
|
|
return undefined;
|
|
}
|
|
return -7;
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
isCaseHumideAdditionelle(tmr) {
|
|
if (tmr.type == 'pont' && EffetsDraconiques.isPontImpraticable(this.actor)) {
|
|
ChatMessage.create({
|
|
content: tmr.label + ": Vous êtes sous le coup d'une Impraticabilité des Ponts : ce pont doit être maîtrisé comme une case humide.",
|
|
whisper: ChatMessage.getWhisperRecipients(game.user.name)
|
|
});
|
|
return true;
|
|
}
|
|
if (this.isCaseInondee(tmr.coord)) {
|
|
ChatMessage.create({
|
|
content: tmr.label + ": cette case est inondée, elle doit être maîtrisée comme une case humide.",
|
|
whisper: ChatMessage.getWhisperRecipients(game.user.name)
|
|
});
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async conquerirCiteFermee(tmr) {
|
|
if (EffetsDraconiques.fermetureCites.find(this.casesSpeciales, tmr.coord)) {
|
|
await this._conquerir(tmr, {
|
|
difficulte: -9,
|
|
action: 'Conquérir la cité',
|
|
onConqueteReussie: r => EffetsDraconiques.fermetureCites.onVisiteSupprimer(r.actor, tmr, (casetmr) => this.removeToken(tmr, casetmr)),
|
|
onConqueteEchec: r => {
|
|
this.souffleSiEchecTotal(rollData);
|
|
this.close()
|
|
},
|
|
canClose: false
|
|
});
|
|
}
|
|
}
|
|
/* -------------------------------------------- */
|
|
async purifierPeriple(tmr) {
|
|
if (EffetsDraconiques.periple.find(this.casesSpeciales, tmr.coord)) {
|
|
await this._conquerir(tmr, {
|
|
difficulte: EffetsDraconiques.periple.getDifficulte(tmr),
|
|
action: 'Purifier ' + TMRUtility.getTMRDescr(tmr.coord),
|
|
onConqueteReussie: r => EffetsDraconiques.periple.onVisiteSupprimer(r.actor, tmr, (casetmr) => this.removeToken(tmr, casetmr)),
|
|
onConqueteEchec: r => {
|
|
this.souffleSiEchecTotal(rollData);
|
|
this.close()
|
|
},
|
|
canClose: false
|
|
});
|
|
}
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async conquerirTMR(tmr) {
|
|
if (EffetsDraconiques.conquete.find(this.casesSpeciales, tmr.coord)) {
|
|
await this._conquerir(tmr, {
|
|
difficulte: -7,
|
|
action: 'Conquérir',
|
|
onConqueteReussie: r => EffetsDraconiques.conquete.onVisiteSupprimer(r.actor, tmr, (casetmr) => this.removeToken(tmr, casetmr)),
|
|
onConqueteEchec: r => this.close(),
|
|
canClose: false
|
|
});
|
|
}
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async _conquerir(tmr, options) {
|
|
let rollData = {
|
|
actor: this.actor,
|
|
competence: duplicate(this.actor.getBestDraconic()),
|
|
tmr: tmr,
|
|
canClose: options.canClose ?? false,
|
|
diffLibre: options.difficulte ?? -7,
|
|
forceCarac: { 'reve-actuel': { label: "Rêve Actuel", value: this.actor.getReveActuel() } },
|
|
maitrise: { verbe: 'conquérir', action: options.action }
|
|
};
|
|
rollData.competence.data.defaut_carac = 'reve-actuel';
|
|
|
|
await this._maitriserTMR(rollData, r => this._onResultatConquerir(r, options));
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async _onResultatConquerir(rollData, options) {
|
|
if (rollData.rolled.isETotal) {
|
|
rollData.souffle = await this.actor.ajouterSouffle({ chat: false });
|
|
}
|
|
this.toclose = rollData.rolled.isEchec;
|
|
|
|
rollData.poesie = await Poetique.getExtrait();
|
|
ChatMessage.create({
|
|
whisper: ChatUtility.getWhisperRecipientsAndGMs(game.user.name),
|
|
content: await renderTemplate(`systems/foundryvtt-reve-de-dragon/templates/chat-resultat-maitrise-tmr.html`, rollData)
|
|
});
|
|
if (rollData.rolled.isEchec) {
|
|
options.onConqueteEchec(rollData, options.effetDraconique);
|
|
}
|
|
else {
|
|
await options.onConqueteReussie(rollData, options.effetDraconique);
|
|
this.updateTokens();
|
|
}
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async _maitriserTMR(rollData, callbackMaitrise) {
|
|
this.minimize(); // Hide
|
|
const dialog = await RdDRoll.create(this.actor, rollData,
|
|
{
|
|
html: 'systems/foundryvtt-reve-de-dragon/templates/dialog-roll-maitrise-tmr.html',
|
|
options: { height: 420 },
|
|
close: html => { this.maximize(); } // Re-display TMR
|
|
},
|
|
{
|
|
name: rollData.maitrise.verbe, label: rollData.maitrise.action,
|
|
callbacks: [
|
|
this.actor.createCallbackExperience(),
|
|
{ action: callbackMaitrise }
|
|
]
|
|
}
|
|
);
|
|
dialog.render(true);
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async validerVisite(tmr) {
|
|
await EffetsDraconiques.pelerinage.onVisiteSupprimer(this.actor, tmr, (casetmr) => this.removeToken(tmr, casetmr));
|
|
await EffetsDraconiques.urgenceDraconique.onVisiteSupprimer(this.actor, tmr, (casetmr) => this.removeToken(tmr, casetmr));
|
|
}
|
|
|
|
|
|
/* -------------------------------------------- */
|
|
async declencheSortEnReserve(coord) {
|
|
|
|
let sortsEnCoord = TMRUtility.getSortsReserve(this.sortsReserves, coord);
|
|
if (sortsEnCoord.length > 0) {
|
|
if (EffetsDraconiques.isSortReserveImpossible(this.actor)) {
|
|
ui.notifications.error("Une queue ou un souffle vous empèche de déclencher de sort!");
|
|
return;
|
|
}
|
|
if (!EffetsDraconiques.isUrgenceDraconique(this.actor) &&
|
|
(EffetsDraconiques.isReserveEnSecurite(this.actor) || this.isReserveExtensible(coord))) {
|
|
let msg = "Vous êtes sur une case avec un Sort en Réserve. Grâce à votre Tête <strong>Reserve en Sécurité</strong> ou <strong>Réserve Exensible</strong>, vous pouvez contrôler le déclenchement. Cliquez si vous souhaitez le déclencher : <ul>";
|
|
for (let sortReserve of sortsEnCoord) {
|
|
msg += "<li><a class='chat-card-button' id='sort-reserve' data-actor-id='" + this.actor._id + "' data-tmr-coord='" + coord + "' data-sort-id='" + sortReserve.sort._id + "'>" + sortReserve.sort.name + "</a></li>";
|
|
}
|
|
msg += "</ol>";
|
|
ChatMessage.create({
|
|
content: msg,
|
|
whisper: ChatMessage.getWhisperRecipients(game.user.name)
|
|
});
|
|
return;
|
|
}
|
|
await this.processSortReserve(sortsEnCoord[0]);
|
|
}
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
lancerSortEnReserve(coord, sortId) {
|
|
let sortEnCoord = TMRUtility.getSortsReserve(this.sortsReserves, coord);
|
|
let sortReserve = sortEnCoord.find(sortReserve => sortReserve.sort._id == sortId);
|
|
if (sortReserve) {
|
|
this.processSortReserve(sortReserve);
|
|
} else {
|
|
ChatMessage.create({
|
|
content: "Une erreur est survenue : impossible de récupérer le sort en réserve demandé.",
|
|
whisper: ChatMessage.getWhisperRecipients(game.user.name)
|
|
});
|
|
}
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async processSortReserve(sortReserve) {
|
|
await this.actor.deleteSortReserve(sortReserve);
|
|
//this.updateSortReserve();
|
|
console.log("declencheSortEnReserve", sortReserve)
|
|
this._tellToUserAndGM(`Vous avez déclenché le sort en réserve <strong> ${sortReserve.sort.name}</strong>
|
|
avec ${sortReserve.sort.data.ptreve_reel} points de Rêve
|
|
en ${sortReserve.coord} (${TMRUtility.getTMRLabel(sortReserve.coord)})
|
|
`);
|
|
this.close();
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
nettoyerRencontre() {
|
|
if (!this.currentRencontre) return; // Sanity check
|
|
if (this.currentRencontre.graphics) {
|
|
for (let drawRect of this.currentRencontre.graphics) { // Suppression des dessins des zones possibles
|
|
this.pixiApp.stage.removeChild(drawRect);
|
|
}
|
|
}
|
|
this.currentRencontre = undefined; // Nettoyage de la structure
|
|
this.rencontreState = 'aucune'; // Et de l'état
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
isCaseInondee(coord) {
|
|
return EffetsDraconiques.debordement.find(this.casesSpeciales, coord);
|
|
}
|
|
|
|
isCiteFermee(coord) {
|
|
return EffetsDraconiques.fermetureCites.find(this.casesSpeciales, coord);
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
isTerreAttache(coord) {
|
|
return EffetsDraconiques.terreAttache.find(this.casesSpeciales, coord);
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
isCaseMaitrisee(coord) {
|
|
return EffetsDraconiques.queteEaux.find(this.casesSpeciales, coord);
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
isReserveExtensible(coord) {
|
|
return EffetsDraconiques.reserveExtensible.find(this.casesSpeciales, coord);
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
isConnaissanceFleuve(currentTMR, nextTMR) {
|
|
return TMRUtility.getTMR(currentTMR).type == 'fleuve' &&
|
|
TMRUtility.getTMR(nextTMR).type == 'fleuve' &&
|
|
EffetsDraconiques.isConnaissanceFleuve(this.actor);
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async onClickTMR(event) {
|
|
if (this.viewOnly) {
|
|
return;
|
|
}
|
|
|
|
let clickOddq = RdDTMRDialog._computeEventOddq(event.data.originalEvent);
|
|
await this._onClickTMRPos(clickOddq); // Vérifier l'état des compteurs reve/fatigue/vie
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async _onClickTMRPos(clickOddq) {
|
|
let currentOddq = TMRUtility.coordTMRToOddq(this._getActorCoord());
|
|
|
|
console.log("deplacerDemiReve >>>>", currentOddq, clickOddq);
|
|
|
|
let targetCoord = TMRUtility.oddqToCoordTMR(clickOddq);
|
|
let currentCoord = TMRUtility.oddqToCoordTMR(currentOddq);
|
|
|
|
// Validation de la case de destination (gestion du cas des rencontres qui peuvent téléporter)
|
|
let deplacementType = this._calculDeplacement(targetCoord, currentCoord, currentOddq, clickOddq);
|
|
|
|
// Si le deplacement est valide
|
|
if (deplacementType == 'normal' || deplacementType == 'saut') {
|
|
await this._deplacerDemiReve(targetCoord, deplacementType);
|
|
} else if (deplacementType == 'messager') { // Dans ce cas, ouverture du lancement de sort sur la case visée
|
|
await this._messagerDemiReve(targetCoord);
|
|
} else {
|
|
ui.notifications.error("Vous ne pouvez vous déplacer que sur des cases adjacentes à votre position ou valides dans le cas d'une rencontre");
|
|
console.log("STATUS :", this.rencontreState, this.currentRencontre);
|
|
}
|
|
this.checkQuitterTMR();
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
_calculDeplacement(targetCoord, currentCoord, fromOddq, toOddq) {
|
|
const isInArea = this.rencontreState == 'aucune'
|
|
? (this.isTerreAttache(targetCoord) || this.isConnaissanceFleuve(currentCoord, targetCoord) || TMRUtility.distanceOddq(fromOddq, toOddq) <= 1)
|
|
: this.currentRencontre?.locList.find(coord => coord == targetCoord) ?? false
|
|
if (isInArea) {
|
|
switch (this.rencontreState) {
|
|
case 'aucune': return 'normal';
|
|
case 'messager': return 'messager';
|
|
case 'passeur': case 'changeur': return 'saut';
|
|
}
|
|
}
|
|
return 'erreur'
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async _messagerDemiReve(targetCoord) {
|
|
/*
|
|
TODO: si la case a un sort en réserve, lancer ce sort.
|
|
Si la case est le demi-rêve, ne pas lancer de sort.
|
|
Si un lancement de sort est en cours, trouver un moyen de réafficher cette fenêtre si on essaie de lancer un sort (ou bloquer le lancer de sort)
|
|
*/
|
|
this.notifierResonanceSigneDraconique(targetCoord);
|
|
await this.actor.rollUnSort(targetCoord);
|
|
this.nettoyerRencontre();
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
externalRefresh(tmrData) {
|
|
this.cacheTMR = (game.user.isGM) ? false : this.actor.isTMRCache();
|
|
this.createPixiSprites();
|
|
this.forceDemiRevePositionView();
|
|
this.updateValuesDisplay();
|
|
this.updateTokens();
|
|
console.log("TMR REFRESHED !!!");
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async _deplacerDemiReve(targetCoord, deplacementType) {
|
|
if (this.currentRencontre != 'normal') {
|
|
this.nettoyerRencontre();
|
|
}
|
|
let tmr = TMRUtility.getTMR(targetCoord);
|
|
//console.log("deplacerDemiReve", tmr, this);
|
|
// Gestion cases spéciales type Trou noir, etc
|
|
tmr = await this.manageTmrInnaccessible(tmr);
|
|
|
|
await this.actor.updateCoordTMR(tmr.coord);
|
|
|
|
this._updateDemiReve();
|
|
if (ReglesOptionelles.isUsing("appliquer-fatigue")) {
|
|
this.cumulFatigue += this.fatigueParCase;
|
|
}
|
|
this.updateValuesDisplay();
|
|
game.socket.emit("system.foundryvtt-reve-de-dragon", {
|
|
msg: "msg_tmr_move", data: {
|
|
actorId: this.actor.data._id,
|
|
tmrPos: Misc.data(this.actor).data.reve.tmrpos
|
|
}
|
|
});
|
|
|
|
if (deplacementType == 'normal') { // Pas de rencontres après un saut de type passeur/changeur/...
|
|
await this.manageRencontre(tmr, () => this.postRencontre(tmr));
|
|
}
|
|
else {
|
|
await this.postRencontre(tmr);
|
|
}
|
|
}
|
|
|
|
async notifierResonanceSigneDraconique(coord) {
|
|
if (this.actor.isResonanceSigneDraconique(coord)) {
|
|
ChatMessage.create({
|
|
whisper: ChatUtility.getWhisperRecipientsAndGMs(game.user.name),
|
|
content: await renderTemplate(`systems/foundryvtt-reve-de-dragon/templates/chat-signe-draconique-resonance.html`, { alias: this.actor.name, typeTMR: TMRUtility.getTMRType(coord) })
|
|
});
|
|
}
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async postRencontre(tmr) {
|
|
if (!(this.viewOnly || this.currentRencontre)) {
|
|
await this.manageCaseHumide(tmr);
|
|
await this.conquerirCiteFermee(tmr);
|
|
await this.purifierPeriple(tmr);
|
|
await this.conquerirTMR(tmr);
|
|
await this.validerVisite(tmr);
|
|
await this.declencheSortEnReserve(tmr.coord);
|
|
await this.actor.checkSoufflePeage(tmr);
|
|
}
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async forceDemiRevePositionView() {
|
|
this._updateDemiReve();
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async forceDemiRevePosition(coord) {
|
|
await this.actor.updateCoordTMR(coord);
|
|
this._updateDemiReve();
|
|
let tmr = TMRUtility.getTMR(coord);
|
|
await this.postRencontre(tmr);
|
|
return tmr;
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
static _computeEventOddq(origEvent) {
|
|
let canvasRect = origEvent.target.getBoundingClientRect();
|
|
let x = origEvent.clientX - canvasRect.left;
|
|
let y = origEvent.clientY - canvasRect.top;
|
|
let col = Math.floor(x / tmrConstants.cellw); // [From 0 -> 12]
|
|
y -= (col % 2 == 0) ? tmrConstants.col1_y : tmrConstants.col2_y;
|
|
let row = Math.floor(y / tmrConstants.cellh); // [From 0 -> 14]
|
|
return { col: col, row: row };
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
/** Retourne les coordonnées x, h, w, h du rectangle d'une case donnée */
|
|
_getCaseRectangleCoord(coord) {
|
|
return this.pixiTMR.getCaseRectangle(TMRUtility.coordTMRToOddq(coord));
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
_setTokenPosition(token) {
|
|
if (!this.cacheTMR) {
|
|
this.pixiTMR.setPosition(token.sprite, TMRUtility.coordTMRToOddq(token.coordTMR()));
|
|
}
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
_removeTokens(filter) {
|
|
const tokensToRemove = this.allTokens.filter(filter);
|
|
for (let token of tokensToRemove) {
|
|
this.pixiApp.stage.removeChild(token.sprite);
|
|
}
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
_trackToken(token) {
|
|
this.allTokens.push(token);
|
|
this._setTokenPosition(token);
|
|
}
|
|
}
|