1025 lines
36 KiB
JavaScript
1025 lines
36 KiB
JavaScript
import { RollDataAjustements } from "./rolldata-ajustements.js";
|
|
import { RdDUtility } from "./rdd-utility.js";
|
|
import { TMRUtility, 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";
|
|
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";
|
|
|
|
/* -------------------------------------------- */
|
|
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.actor.setStatusDemiReve(true);
|
|
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() {
|
|
if (!this.cacheTMR) {
|
|
this._setTokenPosition(this.demiReve);
|
|
}
|
|
}
|
|
|
|
_getActorCoord() {
|
|
return Misc.data(this.actor).data.reve.tmrpos.coord;
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async moveFromKey(move) {
|
|
let pos = TMRUtility.convertToCellPos(this._getActorCoord());
|
|
|
|
if (move == 'top') pos.y -= 1;
|
|
if (move == 'bottom') pos.y += 1;
|
|
if (move.includes('left')) pos.x -= 1;
|
|
if (move.includes('right')) pos.x += 1;
|
|
if (pos.x % 2 == 1) {
|
|
if (move == 'top-left') pos.y -= 1;
|
|
if (move == 'top-right') pos.y -= 1;
|
|
} else {
|
|
if (move == 'bottom-left') pos.y += 1;
|
|
if (move == 'bottom-right') pos.y += 1;
|
|
}
|
|
|
|
let targetCoord = TMRUtility.convertToTMRCoord(pos);
|
|
await this._deplacerDemiReve(targetCoord, 'normal');
|
|
this.checkQuitterTMR();
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async activateListeners(html) {
|
|
super.activateListeners(html);
|
|
|
|
HtmlUtility._showControlWhen($(".appliquerFatigue"), ReglesOptionelles.isUsing("appliquer-fatigue"));
|
|
document.getElementById("tmrrow1").insertCell(0).append(this.pixiApp.view);
|
|
|
|
if (this.viewOnly) {
|
|
html.find('#lancer-sort').remove();
|
|
}
|
|
else {
|
|
// Roll Sort
|
|
html.find('#lancer-sort').click((event) => {
|
|
this.actor.rollUnSort(Misc.data(this.actor).data.reve.tmrpos.coord);
|
|
});
|
|
}
|
|
if (this.viewOnly) {
|
|
return;
|
|
}
|
|
|
|
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);
|
|
});
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
updateValuesDisplay() {
|
|
let ptsreve = document.getElementById("tmr-pointsreve-value");
|
|
const actorData = Misc.data(this.actor);
|
|
ptsreve.innerHTML = actorData.data.reve.reve.value;
|
|
const coord = this._getActorCoord();
|
|
|
|
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() {
|
|
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");
|
|
}
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
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(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.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,
|
|
tmr: TMRUtility.getTMR(this._getActorCoord())
|
|
}
|
|
|
|
await this._tentativeMaitrise(rencontreData);
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async _tentativeMaitrise(rencData, presentCite) {
|
|
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 myRoll = new Roll("1d7").evaluate({ async: false }).total;
|
|
if (TMRUtility.isForceRencontre() || myRoll == 7) {
|
|
return await this.rencontreTMRRoll(tmr, this.actor.isRencontreSpeciale());
|
|
}
|
|
let locTMR = (this.cacheTMR) ? "??" : tmr.label + " (" + tmr.coord + ")";
|
|
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 = 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 = 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 origEvent = event.data.originalEvent;
|
|
let tmrObject = this;
|
|
|
|
let eventPos = RdDTMRDialog._computeEventPos(origEvent);
|
|
await tmrObject._onClickTMRPos(eventPos); // Vérifier l'état des compteurs reve/fatigue/vie
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async _onClickTMRPos(eventPos) {
|
|
let currentPos = TMRUtility.convertToCellPos(this._getActorCoord());
|
|
|
|
console.log("deplacerDemiReve >>>>", currentPos, eventPos);
|
|
|
|
let targetCoord = TMRUtility.convertToTMRCoord(eventPos);
|
|
let currentCoord = TMRUtility.convertToTMRCoord(currentPos);
|
|
|
|
// Validation de la case de destination (gestion du cas des rencontres qui peuvent téléporter)
|
|
let deplacementType = this._calculDeplacement(targetCoord, currentCoord, currentPos, eventPos);
|
|
|
|
// 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, currentPos, eventPos) {
|
|
let isInArea = this.rencontreState == 'aucune'
|
|
? this.isTerreAttache(targetCoord) || this.isConnaissanceFleuve(currentCoord, targetCoord) || !RdDTMRDialog._horsDePortee(currentPos, eventPos)
|
|
: 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)
|
|
*/
|
|
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 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 _computeEventPos(origEvent) {
|
|
let canvasRect = origEvent.target.getBoundingClientRect();
|
|
let x = origEvent.clientX - canvasRect.left;
|
|
let y = origEvent.clientY - canvasRect.top;
|
|
let cellx = Math.floor(x / tmrConstants.cellw); // [From 0 -> 12]
|
|
y -= (cellx % 2 == 0) ? tmrConstants.col1_y : tmrConstants.col2_y;
|
|
let celly = Math.floor(y / tmrConstants.cellh); // [From 0 -> 14]
|
|
return { x: cellx, y: celly };
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
static _horsDePortee(origin, target) {
|
|
return Math.abs(target.x - origin.x) > 1
|
|
|| Math.abs(target.y - origin.y) > 1
|
|
|| (origin.y == 0 && target.y > origin.y && target.x != origin.x && origin.x % 2 == 0)
|
|
|| (target.y == 0 && target.y < origin.y && target.x != origin.x && origin.x % 2 == 1);
|
|
}
|
|
|
|
|
|
/* -------------------------------------------- */
|
|
/** Retourne les coordonnées x, h, w, h du rectangle d'une case donnée */
|
|
_getCaseRectangleCoord(coord) {
|
|
return this.pixiTMR.getCaseRectangle(TMRUtility.convertToCellPos(coord));
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
_setTokenPosition(token) {
|
|
if (!this.cacheTMR) {
|
|
this.pixiTMR.setPosition(token.sprite, TMRUtility.convertToCellPos(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);
|
|
}
|
|
}
|
|
|
|
|