Merge branch 'rencontres' into 'v1.3'

Rencontres

See merge request LeRatierBretonnien/foundryvtt-reve-de-dragon!142
This commit is contained in:
Leratier Bretonnien 2021-02-03 19:58:11 +00:00
commit 2fd2de29ad
10 changed files with 874 additions and 520 deletions

View File

@ -473,9 +473,15 @@ export class RdDActor extends Actor {
await this._recupererEthylisme(message);
await this.recupererFatigue(message);
await this.recuperationReve(message);
if (this.isDonDoubleReve()) {
await this.recuperationReve(message);
}
}
ChatMessage.create(message);
}
isDonDoubleReve() {
return this.data.items.find(item => item.type == 'tete' && item.name == 'Don de double-rêve');
}
/* -------------------------------------------- */
async _recupererEthylisme(message) {
@ -549,7 +555,7 @@ export class RdDActor extends Actor {
if (deRecuperation >= 7) {
// Rêve de Dragon !
message.content += `<br>Vous faites un <strong>Rêve de Dragon</strong> de ${deRecuperation} Points de rêve`;
message.content += await this.combattreReveDeDragon(deRecuperation);
/*message.content += */await this.combattreReveDeDragon(deRecuperation);
}
else {
message.content += `<br>Vous récupérez ${deRecuperation} Points de rêve`;
@ -599,7 +605,11 @@ export class RdDActor extends Actor {
if (roll.isETotal) {
message += "<br>A cause de votre échec total, vous subissez une deuxième Queue de Dragon: " + await this.ajouterQueue();
}
return message;
ChatMessage.create({
whisper: ChatUtility.getWhisperRecipientsAndGMs(this.name),
content: message
});
}
/* -------------------------------------------- */
@ -1019,7 +1029,7 @@ export class RdDActor extends Actor {
await this.createOwnedItem(souffle);
if (options.chat) {
ChatMessage.create({
whisper: ChatMessage.getWhisperRecipients([ "GM", game.user.name] ),
whisper: ChatUtility.getWhisperRecipientsAndGMs(game.user.name),
content: this.name + " subit un Souffle de Dragon : " + souffle.name
});
}
@ -1041,7 +1051,7 @@ export class RdDActor extends Actor {
await this.createOwnedItem(queue);
if (options.chat) {
ChatMessage.create({
whisper: ChatMessage.getWhisperRecipients([ "GM", game.user.name] ),
whisper: ChatUtility.getWhisperRecipientsAndGMs(game.user.name),
content: this.name + " subit une Queue de Dragon : " + queue.name
});
}
@ -1075,7 +1085,7 @@ export class RdDActor extends Actor {
}
if (content) {
ChatMessage.create({
whisper: ChatMessage.getWhisperRecipients([ "GM", game.user.name] ),
whisper: ChatUtility.getWhisperRecipientsAndGMs(game.user.name),
content: content
});
}
@ -1704,7 +1714,7 @@ export class RdDActor extends Actor {
competence: this.getBestDraconic(),
selectedSort: sortList[0],
coord: coord,
coordLabel: TMRUtility.getTMRDescription(coord).label,
coordLabel: TMRUtility.getTMR(coord).label,
diffLibre: sortList[0].data.difficulte, // Per default at startup
coutreve: Array(20).fill().map((item, index) => 1 + index)
}

View File

@ -9,6 +9,7 @@ import { RdDResolutionTable } from "./rdd-resolution-table.js";
import { RdDRollResolutionTable } from "./rdd-roll-resolution-table.js";
import { RdDRollTables } from "./rdd-rolltables.js";
import { RdDUtility } from "./rdd-utility.js";
import { TMRRencontres } from "./tmr-rencontres.js";
import { TMRUtility } from "./tmr-utility.js";
const rddRollNumeric = /(\d+)\s*([\+\-]?\d+)?\s*(s)?/;
@ -187,7 +188,7 @@ export class RdDCommands {
/* -------------------------------------------- */
async getRencontreTMR(params) {
if (params.length == 1 || params.length ==2) {
return TMRUtility.getRencontre(params[0], params[1])
return TMRRencontres.rollRencontre(params[0], params[1])
}
else {
return false;

View File

@ -26,6 +26,7 @@ import { RdDItemCompetence } from "./item-competence.js";
import { StatusEffects } from "./status-effects.js";
import { RddCompendiumOrganiser } from "./rdd-compendium-organiser.js";
import { ReglesOptionelles } from "./regles-optionelles.js";
import { TMRRencontres } from "./tmr-rencontres.js";
/* -------------------------------------------- */
/* Foundry VTT Initialization */
@ -108,7 +109,10 @@ Hooks.once("init", async function () {
// preload handlebars templates
RdDUtility.preloadHandlebarsTemplates();
// Create useful storage space
game.system.rdd = { TMRUtility: TMRUtility }
game.system.rdd = {
TMRUtility,
RdDUtility
}
/* -------------------------------------------- */
game.settings.register("foundryvtt-reve-de-dragon", "accorder-entite-cauchemar", {
@ -225,6 +229,8 @@ Hooks.once("init", async function () {
RdDActor.init();
RddCompendiumOrganiser.init();
ReglesOptionelles.init();
TMRUtility.init();
TMRRencontres.init();
});
/* -------------------------------------------- */

View File

@ -2,21 +2,14 @@
* Extend the base Dialog entity by defining a custom window to perform spell.
* @extends {Dialog}
*/
import { RollDataAjustements } from "./rolldata-ajustements.js";
import { RdDUtility } from "./rdd-utility.js";
import { TMRUtility } from "./tmr-utility.js";
import { RdDRollTables } from "./rdd-rolltables.js";
import { tmrConstants } from "./tmr-utility.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
}
import { TMRRencontres } from "./tmr-rencontres.js";
import { ChatUtility } from "./chat-utility.js";
/* -------------------------------------------- */
export class RdDTMRDialog extends Dialog {
@ -46,11 +39,11 @@ export class RdDTMRDialog extends Dialog {
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.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){
if (!this.viewOnly) {
this.actor.setStatusDemiReve(true);
this._tellToGM(this.actor.name + " monte dans les terres médianes (" + mode + ")");
}
@ -61,7 +54,7 @@ export class RdDTMRDialog extends Dialog {
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) {
if (!this.viewOnly) {
this._tellToGM(this.actor.name + " a quitté les terres médianes");
}
}
@ -78,14 +71,14 @@ export class RdDTMRDialog extends Dialog {
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 ));
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));
}
}
}
@ -120,10 +113,10 @@ export class RdDTMRDialog extends Dialog {
this.close();
}
/* -------------------------------------------- */
async refouler(data) {
this._tellToGM(this.actor.name + " a refoulé : " + this.currentRencontre.name );
async refouler() {
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 );
await this.actor.ajouterRefoulement(this.currentRencontre.refoulement ?? 1);
this.updatePreviousRencontres();
console.log("-> refouler", this.currentRencontre)
this.updateValuesDisplay();
@ -131,11 +124,20 @@ export class RdDTMRDialog extends Dialog {
}
/* -------------------------------------------- */
colorierZoneRencontre( locList) {
async ignorerRencontre() {
this._tellToGM(this.actor.name + " a ignoré : " + this.currentRencontre.name);
await this.actor.deleteTMRRencontreAtPosition(); // Remove the stored rencontre if necessary
this.updatePreviousRencontres();
this.updateValuesDisplay();
this.nettoyerRencontre();
}
/* -------------------------------------------- */
colorierZoneRencontre(locList) {
this.currentRencontre.graphics = []; // Keep track of rectangles to delete it
this.currentRencontre.locList = duplicate(locList); // And track of allowed location
for (let loc of locList) {
let rect = this._getCaseRectangleCoord( loc);
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
@ -148,132 +150,118 @@ export class RdDTMRDialog extends Dialog {
}
/* -------------------------------------------- */
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);
// garder la trace de l'état en cours
setStateRencontre(state) {
this.rencontreState = state;
}
/* -------------------------------------------- */
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);
async choisirCasePortee(coord, portee) {
// Récupère la liste des cases à portées
let locList = TMRUtility.getTMRPortee(coord, portee);
this.colorierZoneRencontre(locList);
}
/* -------------------------------------------- */
/** 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 );
async choisirCaseType(type) {
const locList = TMRUtility.getListCoordTMR(type);
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 !");
if (this.actor.isDead()) {
this._tellToGM("Vous êtes mort : vous quittez les Terres médianes !");
this.close();
return true;
}
if ( this.nbFatigue == this.actor.data.data.sante.fatigue.max ) {
const resteAvantInconscience = this.actor.getFatigueMax() - this.actor.getFatigueActuelle() - this.nbFatigue;
if (resteAvantInconscience <= 0) {
this._tellToGM("Vous vous écroulez de fatigue : vous quittez les Terres médianes !");
this.close();
this.quitterLesTMRInconscient();
return true;
}
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();
if (this.actor.getReveActuel() == 0) {
this._tellToGM("Vos Points de Rêve sont à 0 : vous quittez les Terres médianes !");
this.quitterLesTMRInconscient();
return true;
}
return false;
}
async quitterLesTMRInconscient() {
if (this.currentRencontre?.isPersistant) {
await this.refouler();
}
this.close();
}
/* -------------------------------------------- */
async maitriser(data) {
this.actor.deleteTMRRencontreAtPosition(); // Remove the stored rencontre if necessary
async maitriser() {
this.actor.deleteTMRRencontreAtPosition();
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 = "<br><strong>Test : Rêve actuel / " + draconic.name + " / " + this.currentRencontre.name + "</strong>" + "<br>"
+ 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 <strong>échoué</strong> à 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 <strong>réussi</strong> à maîtriser un " + this.currentRencontre.name + " de force " + this.currentRencontre.force + message);
let rencontreData = {
actor: this.actor,
alias: this.actor.name,
reveDepart: this.actor.getReveActuel(),
competence: this.actor.getBestDraconic(),
rencontre: this.currentRencontre,
nbRounds: 1,
tmr: TMRUtility.getTMR(this.actor.data.data.reve.tmrpos.coord)
}
await this.rencontrePostProcess( rencontreData );
await this._tentativeMaitrise(rencontreData);
}
async _tentativeMaitrise(rencontreData) {
console.log("-> matriser", rencontreData);
rencontreData.reve = this.actor.getReveActuel();
rencontreData.etat = this.actor.getEtatGeneral();
RollDataAjustements.calcul(rencontreData, this.actor);
rencontreData.rolled = await RdDResolutionTable.roll(rencontreData.reve, RollDataAjustements.sum(rencontreData.ajustements));
let postProcess = await TMRRencontres.gererRencontre(this, rencontreData);
ChatMessage.create({
whisper: ChatUtility.getWhisperRecipientsAndGMs(game.user.name),
content: await renderTemplate(`systems/foundryvtt-reve-de-dragon/templates/chat-rencontre-tmr.html`, rencontreData)
});
if (postProcess) {
/** Gère les rencontres avec du post-processing (passeur, messagers, tourbillons, ...) */
await postProcess(this, rencontreData);
}
else {
this.currentRencontre = undefined;
}
console.log("-> matriser", this.currentRencontre);
this.updateValuesDisplay();
if (this.checkQuitterTMR()) {
return;
}
else if (rencontreData.rolled.isEchec && rencontreData.rencontre.isPersistant) {
setTimeout(() => {
rencontreData.nbRounds++;
this.nbFatigue += 1;
this._tentativeMaitrise(rencontreData);
setTimeout(() => this._deleteTmrMessages(rencontreData.actor, rencontreData.nbRounds), 500);
}, 2000);
}
}
this.checkQuitterTMR();
if ( this.rencontreState == 'reflet' || this.rencontreState == 'tourbillonblanc' || this.rencontreState == 'tourbillonnoir' )
this.maitriser();
_deleteTmrMessages(actor, nbRounds = -1) {
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}">`);
}
}
}
/* -------------------------------------------- */
@ -306,21 +294,19 @@ export class RdDTMRDialog extends Dialog {
/* -------------------------------------------- */
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;
let rencontre = this.rencontresExistantes.find(prev => prev.coord == coordTMR);
if (rencontre) {
return rencontre;
}
let myRoll = new Roll("1d7").evaluate();
if (myRoll.total == 7) {
let isMauvaise = this.actor.isRencontreSpeciale();
return await TMRUtility.rencontreTMRRoll(coordTMR, cellDescr, isMauvaise);
}
this._tellToUser(myRoll.total + ": Pas de rencontre en " + cellDescr.label + " (" + coordTMR + ")");
}
/* -------------------------------------------- */
@ -329,7 +315,7 @@ export class RdDTMRDialog extends Dialog {
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);
let tmr = TMRUtility.getTMR(this.actor.data.data.reve.tmrpos.coord);
tmrpos.innerHTML = this.actor.data.data.reve.tmrpos.coord + " (" + tmr.label + ")";
let etat = document.getElementById("tmr-etatgeneral-value");
@ -344,25 +330,26 @@ export class RdDTMRDialog extends Dialog {
}
/* -------------------------------------------- */
async manageCaseSpeciale( cellDescr, coordTMR ) {
for( let caseTMR of this.casesSpeciales) {
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( {
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) } );
whisper: ChatMessage.getWhisperRecipients(game.user.name)
});
}
}
}
}
/* -------------------------------------------- */
isCaseMaitrisee( coordTMR) {
for( let caseTMR of this.casesSpeciales) {
isCaseMaitrisee(coordTMR) {
for (let caseTMR of this.casesSpeciales) {
if (caseTMR.data.coord == coordTMR && caseTMR.data.specific == 'maitrisee') {
return true;
}
@ -378,15 +365,16 @@ export class RdDTMRDialog extends Dialog {
/* -------------------------------------------- */
async manageCaseHumide(cellDescr, coordTMR) {
if (this.viewOnly) {
if (this.viewOnly || this.currentRencontre) {
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( {
if (this.isCaseMaitrisee(coordTMR)) {
ChatMessage.create({
content: "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) } );
whisper: ChatMessage.getWhisperRecipients(game.user.name)
});
return;
}
// TODO: permettre de choisir la voie de draconic?
@ -398,7 +386,7 @@ export class RdDTMRDialog extends Dialog {
let rolled = await RdDResolutionTable.roll(carac, difficulte);
// Gestion du souffle Double Résistance du Fleuve
if ( this.actor.isDoubleResistanceFleuve() ) {
if (this.actor.isDoubleResistanceFleuve()) {
let rolled2 = await RdDResolutionTable.roll(carac, difficulte);
if (rolled2.isEchec)
rolled = rolled;
@ -420,18 +408,18 @@ export class RdDTMRDialog extends Dialog {
+ RdDResolutionTable.explain(rolled);
if (rolled.isETotal) {
let souffle = await this.actor.ajouterSouffle({chat: false});
let souffle = await this.actor.ajouterSouffle({ chat: false });
explication += "<br>Vous avez fait un Echec Total. Vous subissez un Souffle de Dragon : " + souffle.name;
msg2MJ += "<br>Et a reçu un Souffle de Dragon : " + souffle.name;
}
if (rolled.isPart) {
explication += "<br>Vous avez fait une Réussite Particulière";
this.actor._appliquerAjoutExperience({ rolled: rolled, seletedCarac: { label: 'reve'}, competence: draconic.name })
this.actor._appliquerAjoutExperience({ rolled: rolled, seletedCarac: { label: 'reve' }, competence: draconic.name })
msg2MJ += "<br>Et a fait une réussite particulière";
}
// Notification au MJ
ChatMessage.create( { content: msg2MJ, whisper: ChatMessage.getWhisperRecipients("GM") } );
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",
@ -445,9 +433,9 @@ export class RdDTMRDialog extends Dialog {
}
}
/* -------------------------------------------- */
isReserveExtensible( coordTMR) {
for( let caseTMR of this.casesSpeciales) {
if (caseTMR.data.specific == 'reserve_extensible' && caseTMR.data.coord == coordTMR )
isReserveExtensible(coordTMR) {
for (let caseTMR of this.casesSpeciales) {
if (caseTMR.data.specific == 'reserve_extensible' && caseTMR.data.coord == coordTMR)
return true;
}
return false;
@ -459,54 +447,56 @@ export class RdDTMRDialog extends Dialog {
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 <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>";
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 <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 sortReserveList) {
msg += "<li><a class='chat-card-button' id='sort-reserve' data-actor-id='"+this.actor._id+"' data-tmr-coord='"+coordTMR+"' data-sort-id='"+sortReserve.sort._id+"'>"+sortReserve.sort.name+"</a></li>";
msg += "<li><a class='chat-card-button' id='sort-reserve' data-actor-id='" + this.actor._id + "' data-tmr-coord='" + coordTMR + "' data-sort-id='" + sortReserve.sort._id + "'>" + sortReserve.sort.name + "</a></li>";
}
msg += "</ol>";
ChatMessage.create( {
ChatMessage.create({
content: msg,
whisper: ChatMessage.getWhisperRecipients(game.user.name) } );
whisper: ChatMessage.getWhisperRecipients(game.user.name)
});
} else {
await this.processSortReserve( sortReserveList[0] );
await this.processSortReserve(sortReserveList[0]);
}
}
}
/* -------------------------------------------- */
lancerSortEnReserve( coordTMR, sortId ) {
let sortReserveList = TMRUtility.getSortReserveList( this.sortReserves, coordTMR );
let sortReserve = sortReserveList.find( sortReserve => sortReserve.sort._id == sortId);
lancerSortEnReserve(coordTMR, sortId) {
let sortReserveList = TMRUtility.getSortReserveList(this.sortReserves, coordTMR);
let sortReserve = sortReserveList.find(sortReserve => sortReserve.sort._id == sortId);
//console.log("SORT RESA", sortReserveList, coordTMR, sortId, sortReserve);
if ( sortReserve) {
this.processSortReserve( sortReserve );
if (sortReserve) {
this.processSortReserve(sortReserve);
} else {
ChatMessage.create( {
ChatMessage.create({
content: "Une erreur est survenue : impossible de récupérer le sort en réserve demandé.",
whisper: ChatMessage.getWhisperRecipients(game.user.name) } );
whisper: ChatMessage.getWhisperRecipients(game.user.name)
});
}
}
/* -------------------------------------------- */
async processSortReserve( sortReserve ) {
async processSortReserve(sortReserve) {
await this.actor.deleteSortReserve(sortReserve);
this.updateSortReserve();
console.log("declencheSortEnReserve", sortReserve)
const declenchementSort = "Vous avez déclenché le sort <strong>" + sortReserve.sort.name
+ "</strong> en réserve en " + sortReserve.coord + " (" + TMRUtility.getTMRDescription(sortReserve.coord).label
+ "</strong> en réserve en " + sortReserve.coord + " (" + TMRUtility.getTMR(sortReserve.coord).label
+ ") avec " + sortReserve.sort.data.ptreve_reel + " points de Rêve";
this._tellToGM(declenchementSort);
this.close();
}
/* -------------------------------------------- */
nettoyerRencontre( ) {
if ( !this.currentRencontre) return; // Sanity check
if ( this.currentRencontre.graphics) {
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.pixiApp.stage.removeChild(drawRect);
}
}
this.currentRencontre = undefined; // Nettoyage de la structure
@ -514,21 +504,20 @@ export class RdDTMRDialog extends Dialog {
}
/* -------------------------------------------- */
processClickPostRencontre( coord ) {
let deplacementType = "erreur";
processClickPostRencontre(coord) {
if (this.rencontreState == 'passeur' || this.rencontreState == 'messager' || this.rencontreState == 'changeur') {
console.log("Searching", this.currentRencontre.locList, coord);
let isInArea = this.currentRencontre.locList.find(locCoord => locCoord == coord );
if ( isInArea ) { // OK !
deplacementType = (this.rencontreState == 'messager') ? 'messager' : 'saut';
let isInArea = this.currentRencontre.locList.find(locCoord => locCoord == coord);
if (isInArea) { // OK !
return (this.rencontreState == 'messager') ? 'messager' : 'saut';
}
}
return deplacementType;
return "erreur";
}
/* -------------------------------------------- */
isTerreAttache( coordTMR ) {
for( let caseTMR of this.casesSpeciales) {
isTerreAttache(coordTMR) {
for (let caseTMR of this.casesSpeciales) {
if (caseTMR.data.specific == 'attache' && caseTMR.data.coord == coordTMR) { // Match !
return true;
}
@ -537,10 +526,10 @@ export class RdDTMRDialog extends Dialog {
}
/* -------------------------------------------- */
checkConnaissanceFleuve( currentTMR, nextTMR ) {
if ( this.actor.isConnaissanceFleuve() ) {
checkConnaissanceFleuve(currentTMR, nextTMR) {
if (this.actor.isConnaissanceFleuve()) {
//console.log(currentTMR, nextTMR );
if ( TMRUtility.getTMRDescription(currentTMR).type == 'fleuve' && TMRUtility.getTMRDescription(nextTMR).type == 'fleuve') {
if (TMRUtility.getTMR(currentTMR).type == 'fleuve' && TMRUtility.getTMR(nextTMR).type == 'fleuve') {
return true;
}
}
@ -562,22 +551,22 @@ export class RdDTMRDialog extends Dialog {
console.log("deplacerDemiReve >>>>", cellx, celly);
let currentPos = TMRUtility.convertToCellCoord(myself.actor.data.data.reve.tmrpos.coord);
let coordTMR = TMRUtility.convertToTMRCoord(cellx, celly);
let currentTMR = TMRUtility.convertToTMRCoord( currentPos.x, currentPos.y);
let currentTMR = TMRUtility.convertToTMRCoord(currentPos.x, currentPos.y);
// Validation de la case de destination (gestion du cas des rencontres qui peuvent téléporter)
let deplacementType = 'erreur';
if ( myself.rencontreState == 'aucune') { // Pas de recontre en post-processing, donc deplacement normal
if ( !RdDTMRDialog._horsDePortee(currentPos, cellx, celly) || myself.isTerreAttache(coordTMR) || myself.checkConnaissanceFleuve(currentTMR,coordTMR ) ) {
if (myself.rencontreState == 'aucune') { // Pas de recontre en post-processing, donc deplacement normal
if (!RdDTMRDialog._horsDePortee(currentPos, cellx, celly) || myself.isTerreAttache(coordTMR) || myself.checkConnaissanceFleuve(currentTMR, coordTMR)) {
deplacementType = 'normal';
}
} else {
deplacementType = myself.processClickPostRencontre( coordTMR );
deplacementType = myself.processClickPostRencontre(coordTMR);
}
// Si le deplacement est valide
if ( deplacementType == 'normal' || deplacementType == 'saut') {
if ( myself.currentRencontre != 'normal' )
if (deplacementType == 'normal' || deplacementType == 'saut') {
if (myself.currentRencontre != 'normal')
myself.nettoyerRencontre();
let cellDescr = TMRUtility.getTMRDescription(coordTMR);
let cellDescr = TMRUtility.getTMR(coordTMR);
await myself.manageCaseSpeciale(cellDescr, coordTMR); // Gestion cases spéciales type Trou noir, etc
@ -591,12 +580,12 @@ export class RdDTMRDialog extends Dialog {
myself.updateValuesDisplay();
game.socket.emit("system.foundryvtt-reve-de-dragon", {
msg: "msg_tmr_move", data: {
actorId: myself.actor.data._id,
actorId: myself.actor.data._id,
tmrPos: tmrPos
}
});
if ( deplacementType == 'normal') { // Pas de rencontres après un saut de type passeur/changeur/...
if (deplacementType == 'normal') { // Pas de rencontres après un saut de type passeur/changeur/...
await myself.manageRencontre(coordTMR, cellDescr);
}
await myself.manageCaseHumide(cellDescr, coordTMR);
@ -609,7 +598,7 @@ export class RdDTMRDialog extends Dialog {
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 myself.actor.rollUnSort( coordTMR );
await myself.actor.rollUnSort(coordTMR);
myself.nettoyerRencontre();
} else {
@ -621,16 +610,15 @@ export class RdDTMRDialog extends Dialog {
}
/* -------------------------------------------- */
async forceDemiRevePositionView( coordTMR ) {
async forceDemiRevePositionView(coordTMR) {
this._updateDemiReve(this);
}
/* -------------------------------------------- */
async forceDemiRevePosition( coordTMR ) {
async forceDemiRevePosition(coordTMR) {
await this.actor.updateCoordTMR(coordTMR);
this._updateDemiReve(this);
let cellDescr = TMRUtility.getTMRDescription(coordTMR);
await this.manageRencontre(coordTMR, cellDescr);
let cellDescr = TMRUtility.getTMR(coordTMR);
this.manageCaseHumide(cellDescr, coordTMR);
await this.declencheSortEnReserve(coordTMR);
}
@ -689,17 +677,17 @@ export class RdDTMRDialog extends Dialog {
// Gestion du cout de montée en points de rêve
let reveCout = -1;
if ( this.actor.checkTeteDeplacementAccelere() ) {
if (this.actor.checkTeteDeplacementAccelere()) {
reveCout = -1;
} else {
reveCout = (this.tmrdata.isRapide) ? -2 : -1;
}
reveCout -= this.actor.checkMonteeLaborieuse();
await this.actor.reveActuelIncDec( reveCout );
await this.actor.reveActuelIncDec(reveCout);
// Le reste...
this.updateValuesDisplay();
let coordTMR = this.actor.data.data.reve.tmrpos.coord;
let cellDescr = TMRUtility.getTMRDescription(coordTMR);
let cellDescr = TMRUtility.getTMR(coordTMR);
await this.manageRencontre(coordTMR, cellDescr);
this.manageCaseHumide(cellDescr, coordTMR);
// Mise à jour du nb de cases de Fatigue
@ -740,7 +728,7 @@ export class RdDTMRDialog extends Dialog {
}
/* -------------------------------------------- */
_tokenTrouNoir( coord ) {
_tokenTrouNoir(coord) {
let sprite = new PIXI.Graphics();
sprite.beginFill(0x050505, 0.8);
sprite.drawCircle(0, 0, (tmrConstants.cellw / 2) - 2);
@ -753,7 +741,7 @@ export class RdDTMRDialog extends Dialog {
}
/* -------------------------------------------- */
_tokenDebordement( coord ) {
_tokenDebordement(coord) {
let sprite = new PIXI.Graphics();
sprite.beginFill(0x0101FE, 0.3);
sprite.drawCircle(0, 0, (tmrConstants.cellw / 2) - 2);
@ -829,12 +817,12 @@ export class RdDTMRDialog extends Dialog {
/* -------------------------------------------- */
/** Retourne les coordonnées x, h, w, h du rectangle d'une case donnée */
_getCaseRectangleCoord( coord ) {
let coordXY = TMRUtility.convertToCellCoord( coord );
_getCaseRectangleCoord(coord) {
let coordXY = TMRUtility.convertToCellCoord(coord);
let decallagePairImpair = (coordXY.x % 2 == 0) ? tmrConstants.col1_y : tmrConstants.col2_y;
let x = tmrConstants.gridx + (coordXY.x * tmrConstants.cellw) - (tmrConstants.cellw /2);
let y = tmrConstants.gridy + (coordXY.y * tmrConstants.cellh) - (tmrConstants.cellh /2) + decallagePairImpair;
return {x: x, y: y, w: tmrConstants.cellw, h: tmrConstants.cellh}
let x = tmrConstants.gridx + (coordXY.x * tmrConstants.cellw) - (tmrConstants.cellw / 2);
let y = tmrConstants.gridy + (coordXY.y * tmrConstants.cellh) - (tmrConstants.cellh / 2) + decallagePairImpair;
return { x: x, y: y, w: tmrConstants.cellw, h: tmrConstants.cellh }
}
/* -------------------------------------------- */

View File

@ -2,17 +2,20 @@
export class RdDTMRRencontreDialog extends Dialog {
/* -------------------------------------------- */
constructor(html, tmrApp, rencontreData) {
constructor(html, tmrApp, rencontre) {
const dialogConf = {
title: "Rencontre en TMR!",
content: "Vous recontrez un " + rencontreData.name + " de force " + rencontreData.force + "<br>",
content: "Vous recontrez un " + rencontre.name + " de force " + rencontre.force + "<br>",
buttons: {
derober: { icon: '<i class="fas fa-check"></i>', label: "Se dérober", callback: () => { this.toClose = true; this.tmrApp.derober() } },
refouler: { icon: '<i class="fas fa-check"></i>', label: "Refouler", callback: () => { this.toClose = true;this.tmrApp.refouler() } },
maitiser: { icon: '<i class="fas fa-check"></i>', label: "Maîtriser", callback: () => { this.toClose = true;this.tmrApp.maitriser() } }
refouler: { icon: '<i class="fas fa-check"></i>', label: "Refouler", callback: () => { this.toClose = true; this.tmrApp.refouler() } },
maitiser: { icon: '<i class="fas fa-check"></i>', label: "Maîtriser", callback: () => { this.toClose = true; this.tmrApp.maitriser() } }
},
default: "derober"
}
if (rencontre.ignorer) {
dialogConf.buttons.ignorer = { icon: '<i class="fas fa-check"></i>', label: "Ignorer", callback: () => { this.toClose = true; this.tmrApp.ignorerRencontre() }};
}
const dialogOptions = {
classes: ["tmrrencdialog"],
@ -22,7 +25,7 @@ export class RdDTMRRencontreDialog extends Dialog {
super(dialogConf, dialogOptions);
this.toClose = false;
this.rencontreData = duplicate(rencontreData);
this.rencontreData = duplicate(rencontre);
this.tmrApp = tmrApp;
this.tmrApp.minimize();
}

View File

@ -114,6 +114,12 @@ export const referenceAjustements = {
bonusCase: {
isUsed: (rollData, actor) => rollData.selectedSort && rollData.coord,
getDescr: (rollData, actor) => rollData.selectedSort && rollData.coord ? `Bonus de case: ${RdDItemSort.getCaseBonus(rollData.selectedSort, rollData.coord)}%` : ''
},
rencontreTMR: {
isVisible: (rollData, actor) => rollData.tmr && rollData.rencontre.name,
isUsed: (rollData, actor) => rollData.tmr && rollData.rencontre.name,
getLabel: (rollData, actor) => rollData.rencontre?.name,
getValue: (rollData, actor) => - (rollData.rencontre?.force ?? 0)
}
}

487
module/tmr-rencontres.js Normal file
View File

@ -0,0 +1,487 @@
import { DeDraconique } from "./de-draconique.js";
import { Grammar } from "./grammar.js";
import { TMRUtility } from "./tmr-utility.js";
import { TMRType } from "./tmr-utility.js";
/* -------------------------------------------- */
const typeRencontres = {
messager: {
msgSucces: (data) => `Le ${data.rencontre.name} vous propose d'emmener le message de votre un sort à ${data.rencontre.force} cases ${data.tmr.label}.`,
msgEchec: (data) => `Le ${data.rencontre.name} est pressé et continue son chemin d'une traite sans vous accorder un regard.`,
postSucces: (tmrDialog, data) => {
tmrDialog.setStateRencontre(data.rencontre.type);
tmrDialog.choisirCasePortee(data.tmr.coord, data.rencontre.force);
},
poesieSucces: {
reference: "La chevelure, Charles Baudelaire",
extrait: `J'irai là-bas où l'arbre et l'homme, pleins de sève,
<br>Se pâment longuement sous l'ardeur des climats ;
<br>Fortes tresses, soyez la houle qui m'enlève !`
},
poesieEchec: {
reference: "Rêve de Dragon, Denis Gerfaud",
extrait: `En réalité, tous les éléments du rêve des Dragons expriment
le Draconic : chaque pierre, chaque fleur, chaque goutte d'eau,
chaque nuage est porteur d'un message dans la langue des Dragons`}
},
passeur: {
msgSucces: (data) => `Le ${data.rencontre.name} vous propose de vous transporter à ${data.rencontre.force} cases des ${data.tmr.label}.`,
msgEchec: (data) => `Le prix que demande le ${data.rencontre.name} est trop élevé, vous êtes réduit à poursuivre votre chemin par vos propres moyens.`,
postSucces: (tmrDialog, data) => {
tmrDialog.setStateRencontre(data.rencontre.type);
tmrDialog.choisirCasePortee(data.tmr.coord, data.rencontre.force);
},
poesieSucces: {
reference: "Femmes damnées (2), Charles Baudelaire",
extrait: `Comme je descendais des Fleuves impassibles,
<br>Je ne me sentis plus guidé par les haleurs :
<br>Des Peaux-Rouges criards les avaient pris pour cibles,
<br>Les ayant cloués nus aux poteaux de couleurs.`},
poesieEchec: {
reference: "Le bateau ivre, Arthur Rimbaud",
extrait: `Loin des peuples vivants, errantes, condamnées,
<br>A travers les déserts courez comme les loups ;
<br>Faites votre destin, âmes désordonnées,
<br>Et fuyez l'infini que vous portez en vous !`}
},
fleur: {
msgSucces: (data) => `Vous cueillez la ${data.rencontre.name}, son parfum vous apporte ${data.rencontre.force} points de Rêve.`,
msgEchec: (data) => `La ${data.rencontre.name} se fâne et disparaît entre vos doigts.`,
postSucces: (tmrDialog, data) => tmrDialog.actor.reveActuelIncDec(data.rencontre.force),
poesieSucces: {
reference: "L'Ennemi, Charles Baudelaire",
extrait: `Et qui sait si les fleurs nouvelles que je rêve
<br>Trouveront dans ce sol lavé comme une grève
<br>Le mystique aliment qui ferait leur vigueur ?`},
poesieEchec: {
reference: "Une charogne, Charles Baudelaire",
extrait: `Et le ciel regardait la carcasse superbe
<br>Comme une fleur s'épanouir.
<br>La puanteur était si forte, que sur l'herbe
<br>Vous crûtes vous évanouir.`},
},
mangeur: {
msgSucces: (data) => `Le ${data.rencontre.name} claque de sa machoire dans le vide avant de fuir.`,
msgEchec: (data) => `Le ${data.rencontre.name} croque votre Rêve ! Il emporte ${data.rencontre.force} de vos points de rêve actuels`,
postEchec: (tmrDialog, data) => tmrDialog.actor.reveActuelIncDec(-data.rencontre.force),
poesieSucces: {
reference: "Conseil, Victor Hugo",
extrait: `Rois ! la bure est souvent jalouse du velours.
<br>Le peuple a froid l'hiver, le peuple a faim toujours.
<br>Rendez-lui son sort plus facile.
<br>Le peuple souvent porte un bien rude collier.
<br>Ouvrez l'école aux fils, aux pères l'atelier,
<br>À tous vos bras, auguste asile !`},
poesieEchec: {
reference: "El Desdichado, Gérard de Nerval",
extrait: `Suis-je Amour ou Phébus ?... Lusignan ou Biron ?
<br>Mon front est rouge encor du baiser de la Reine ;
<br>J'ai rêvé dans la Grotte nage la sirène...`}
},
changeur: {
msgSucces: (data) => `Le ${data.rencontre.name} vaincu accepte de vous déplacer sur une autre ${TMRType[data.tmr.type]} de votre choix en échange de sa liberté.`,
msgEchec: (data) => {
data.newTMR = TMRUtility.getTMRAleatoire(data.tmr.type);
return `Le ${data.rencontre.name} vous embobine avec des promesses, et vous transporte en ${data.newTMR.label} sans attendre votre avis.`;
},
postSucces: (tmrDialog, data) => {
tmrDialog.setStateRencontre(data.rencontre.type);
tmrDialog.choisirCaseType(data.tmr.type);
},
postEchec: (tmrDialog, data) => tmrDialog.forceDemiRevePosition(data.newTMR.coord),
poesieSucces: {
reference: "Caligula - IIIème chant, Gérard de Nerval",
extrait: `Allez, que le caprice emporte
<br>Chaque âme selon son désir,
<br>Et que, close après vous, la porte
<br>Ne se rouvre plus qu'au plaisir.`},
poesieEchec: {
reference: "Rêve de Dragon, Denis Gerfaud",
extrait: `Les sages ont encore coutume de dire :
<br>&laquo; Mais comment les Dragons peuvent-ils
être influencés par une créature qui, tout
bien considéré, n'existe pas vraiment pour eux,
qui n'est que le fantasme de leur activité nocturne ? &raquo;`}
},
briseur: {
msgSucces: (data) => `Le ${data.rencontre.name} tente vainement de vous déconcentrer, avant de fuir sans demander son reste.`,
msgEchec: (data) => `Le ${data.rencontre.name} vous déconcentre au point de briser votre demi-rêve.`,
postEchec: (tmrDialog, data) => tmrDialog.close(),
poesieSucces: {
reference: "Rêve de Dragon, Denis Gerfaud",
extrait: `La légende affirme que ce sont les Gnomes qui furent
les premiers haut-rêvants. En observant les pierres précieuses,
les gemmes qui sont les larmes de joie des Dragons, ils parvinrent à
en comprendre la langue. Et l'ayant comprise, ils purent s'en servir
pour influencer le cours du rêve`},
poesieEchec: {
reference: "Quand le rêve se brise, Cypora Sebagh",
extrait: `Quand le rêve se brise,
<br>Dans la plainte du jour,
<br>Ma mémoire devient grise
<br>Et sombre, tour à tour,
<br>Dans le puits du silence
<br>Et de la solitude ;
<br>Elle reprend son errance
<br>Parmi la multitude.`}
},
reflet: {
msgSucces: (data) => `Le ${data.rencontre.name} s'estompe dans l'oubli.`,
msgEchec: (data) => `Vous êtes submergé par un ${data.rencontre.name}, les souvenirs vous retiennent tant qu'il ne sera pas vaincu!`,
poesieSucces: {
reference: "Une charogne, Charles Baudelaire",
extrait: `Les formes s'effaçaient et n'étaient plus qu'un rêve,
<br>Une ébauche lente à venir
<br>Sur la toile oubliée, et que l'artiste achève
<br>Seulement par le souvenir.`},
poesieEchec: {
reference: "La chevelure, Charles Baudelaire",
extrait: `Longtemps ! toujours ! ma main dans ta crinière lourde
<br>Sèmera le rubis, la perle et le saphir,
<br>Afin qu'à mon désir tu ne sois jamais sourde !
<br>N'es-tu pas l'oasis je rêve, et la gourde
<br> je hume à longs traits le vin du souvenir`}
},
passeurfou: {
msgSucces: (data) => `Le ${data.rencontre.name} tente vainement de découvrir où vous avez caché vos réserves. Vous le chassez, et en déroute il part harceler un autre voyageur du rêve.`,
msgEchec: (data) => TMRRencontres.msgEchecPasseurFou(data),
postEchec: (tmrDialog, data) => TMRRencontres.postEchecPasseurFou(tmrDialog, data),
poesieSucces: {
reference: "Un Fou et un Sage, Jean de La Fontaine",
extrait: `Certain Fou poursuivait à coups de pierre un Sage.
<br>Le Sage se retourne et lui dit : Mon ami,
<br>C'est fort bien fait à toi ; reçois cet écu-ci :
<br>Tu fatigues assez pour gagner davantage.`},
poesieEchec: {
reference: "Guitare, Victor Hugo",
extrait: `Je la voyais passer de ma demeure,
<br>Et c'était tout.
<br>Mais à présent je m'ennuie à toute heure,
<br>Plein de dégoût,
<br>Rêveur oisif, l'âme dans la campagne,
<br>La dague au clou ...
<br>Le vent qui vient à travers la montagne
<br>M'a rendu fou !`}
},
tbblanc: {
msgSucces: (data) => `Le ${data.rencontre.name} souleve une poussière blanche, vous tenez bon, et il tourbillonne en s'éloignant.`,
msgEchec: (data) => `Le souffle du ${data.rencontre.name} vous déstabilise et vous emmène dans un nuage de poussière.`,
postEchec: (tmrDialog, data) => TMRRencontres.onPostEchecTourbillon(tmrDialog, data, 1),
poesieSucces: {
reference: "Rêve de Dragon, Denis Gerfaud",
extrait: `Le Premier Âge fut appelé l'Âge des Dragons. Ce fut le commencement
des temps, le commencement des rêves. Durant cette période plus mythique
que réellement historique, les Dragons aimaient à se rêver eux-mêmes.`},
poesieEchec: {
reference: "Les Djinns, Victor Hugo",
extrait: `C'est l'essaim des Djinns qui passe,
<br>Et tourbillonne en sifflant !
<br>Les ifs, que leur vol fracasse,
<br>Craquent comme un pin brûlant.`},
},
tbnoir: {
msgSucces: (data) => `Le ${data.rencontre.name} orageux vous enveloppe de fureur et d'éclairs, vous tenez bon face à la tempête qui s'éloigne sans vous éloigner de votre chemin.`,
msgEchec: (data) => `Le ${data.rencontre.name} furieux vous secoue tel un fichu de paille malmené par les vents, et vous emporte dans la tourmente.`,
postEchec: (tmrDialog, data) => TMRRencontres.onPostEchecTourbillon(tmrDialog, data, 2),
poesieSucces: {
reference: "Rêve de Dragon, Denis Gerfaud",
extrait: `Car le Second Âge fut bel et bien celui des Magiciens. Durant cette période, les
Gnomes s'enfoncèrent profondément sous les montagnes et la magie passa aux
mains des Humains qui en usèrent et abusèrent, se croyant devenus les maîtres du monde`},
poesieEchec: {
reference: "Lily, Pierre Perret",
extrait: `Elle aurait pas cru sans le voir
<br>Que la couleur du désespoir
<br>-bas aussi ce fût le noir.`},
},
tbrouge: {
msgSucces: (data) => `Le ${data.rencontre.name} s'abat avec violence mais vous êtes plus rapide et parvenez à lui échapper.`,
msgEchec: (data) => `Le ${data.rencontre.name} vous frappe de milliers de morsure et vous malmène à travers les terres médianes.`,
postEchec: (tmrDialog, data) => TMRRencontres.onPostEchecTourbillonRouge(tmrDialog, data),
poesieSucces: {
reference: "Qu'est-ce de votre vie ? une bouteille molle, Jean-Baptiste Chassignet",
extrait: `Qu'est-ce de votre vie ? un tourbillon rouant
<br>De fumière à flot gris, parmi l'air se jouant,
<br>Qui passe plus soudain que foudre meurtrière.`},
poesieEchec: {
reference: "Les Djinns, poème Victor Hugo",
extrait: `Cris de l'enfer! voix qui hurle et qui pleure !
<br>L'horrible essaim, poussé par l'aquilon,
<br>Sans doute, ô ciel ! s'abat sur ma demeure.
<br>Le mur fléchit sous le noir bataillon.
<br>La maison crie et chancelle penchée,
<br>Et l'on dirait que, du sol arrachée,
<br>Ainsi qu'il chasse une feuille séchée,
<br>Le vent la roule avec leur tourbillon !`},
},
rdd: {
msgSucces: (data) => TMRRencontres.onSuccessReveDeDragon(data),
msgEchec: (data) => TMRRencontres.onEchecReveDeDragon(data),
postEchec: (tmrDialog, data) => tmrDialog.close(),
poesieSucces: {
reference: "Rêve de Dragon, Denis Gerfaud",
extrait: `Le monde est Rêve de Dragons, mais nous ne savons
<br>ni leur apparence ni qui sont les dragons.
<br>En dépit de l'iconographie qui les clame
<br>immenses créatures ailées crachant des flammes`},
poesieEchec: {
reference: "El Desdichado, Gérard de Nerval",
extrait: `Je suis le Ténébreux, le Veuf, l'Inconsolé,
<br>Le Prince d'Aquitaine à la Tour abolie :
<br>Ma seule Etoile est morte, et mon luth constellé
<br>Porte le Soleil noir de la Mélancolie.`}
},
}
/* -------------------------------------------- */
const mauvaisesRencontres = [
{ code: "mangeur1d6", name: "Mangeur de Rêve", type: "mangeur", genre: "m", force: "1d6", refoulement: 2, isMauvaise: true },
{ code: "mangeur2d6", name: "Mangeur de Rêve", type: "mangeur", genre: "m", force: "2d6", refoulement: 2, isMauvaise: true },
{ code: "reflet2d6+4", name: "Reflet d'ancien Rêve", type: "reflet", genre: "m", force: "2d6+4", refoulement: 2, isPersistant: true, isMauvaise: true },
{ code: "tbblanc2d6+4", name: "Tourbillon blanc", type: "tbblanc", genre: "m", force: "2d6+4", refoulement: 2, isPersistant: true, isMauvaise: true },
{ code: "tbnoir2d8+4", name: "Tourbillon noir", type: "tbnoir", genre: "m", force: "2d8+4", refoulement: 2, isPersistant: true, isMauvaise: true },
{ code: "passfou2d8", name: "Passeur fou", type: "passeurfou", genre: "m", force: "2d8", refoulement: 2, isMauvaise: true },
{ code: "tbrouge2d8", name: "Tourbillon rouge", type: "tbrouge", genre: "m", force: "2d8", refoulement: 3, isPersistant: true, isMauvaise: true }
]
/* -------------------------------------------- */
const rencontresStandard = [
{ code: "messager2d4", name: "Messager des Rêves", type: "messager", genre: "m", force: "2d4", ignorer: true },
{ code: "passeur2d4", name: "Passeur des Rêves", type: "passeur", genre: "m", force: "2d4", ignorer: true },
{ code: "fleur1d6", name: "Fleur des Rêves", type: "fleur", genre: "f", force: "1d6", ignorer: true },
{ code: "mangeur1d6", name: "Mangeur de Rêve", type: "mangeur", genre: "m", force: "1d6" },
{ code: "changeur2d6", name: "Changeur de Rêve", type: "changeur", genre: "m", force: "2d6" },
{ code: "briseur2d6", name: "Briseur de Rêve", type: "briseur", genre: "m", force: "2d6", quitterTMR: true },
{ code: "reflet1d6", name: "Reflet d'ancien Rêve", type: "reflet", genre: "m", force: "2d6", isPersistant: true },
{ code: "tbblanc2d6", name: "Tourbillon blanc", type: "tbblanc", genre: "m", force: "2d6", isPersistant: true },
{ code: "tbnoir2d8", name: "Tourbillon noir", type: "tbnoir", genre: "m", force: "2d8", isPersistant: true },
{ code: "rdd1ddr+7", name: "Rêve de Dragon", type: "rdd", genre: "m", force: "1ddr + 7", refoulement: 2, quitterTMR: true }
];
const tableRencontres = {
cite: [{ code: 'messager2d4', range: [1, 25] }, { code: 'passeur2d4', range: [26, 50] }, { code: 'fleur1d6', range: [51, 65] }, { code: 'mangeur1d6', range: [66, 70] }, { code: 'changeur2d6', range: [71, 80] }, { code: 'briseur2d6', range: [81, 85] }, { code: 'reflet2d6', range: [86, 90] }, { code: 'tbblanc2d6', range: [91, 94] }, { code: 'tbnoir2d8', range: [95, 97] }, { code: 'rdd1ddr+7', range: [98, 100] }],
sanctuaire: [{ code: 'messager2d4', range: [1, 25] }, { code: 'passeur2d4', range: [26, 50] }, { code: 'fleur1d6', range: [51, 65] }, { code: 'mangeur1d6', range: [66, 70] }, { code: 'changeur2d6', range: [71, 80] }, { code: 'briseur2d6', range: [81, 85] }, { code: 'reflet2d6', range: [86, 90] }, { code: 'tbblanc2d6', range: [91, 94] }, { code: 'tbnoir2d8', range: [95, 97] }, { code: 'rdd1ddr+7', range: [98, 100] }],
plaines: [{ code: 'messager2d4', range: [1, 20] }, { code: 'passeur2d4', range: [21, 40] }, { code: 'fleur1d6', range: [41, 55] }, { code: 'mangeur1d6', range: [56, 60] }, { code: 'changeur2d6', range: [61, 75] }, { code: 'briseur2d6', range: [76, 82] }, { code: 'reflet2d6', range: [83, 88] }, { code: 'tbblanc2d6', range: [89, 93] }, { code: 'tbnoir2d8', range: [94, 97] }, { code: 'rdd1ddr+7', range: [98, 100] }],
pont: [{ code: 'messager2d4', range: [1, 20] }, { code: 'passeur2d4', range: [21, 40] }, { code: 'fleur1d6', range: [41, 55] }, { code: 'mangeur1d6', range: [56, 60] }, { code: 'changeur2d6', range: [61, 75] }, { code: 'briseur2d6', range: [76, 82] }, { code: 'reflet2d6', range: [83, 88] }, { code: 'tbblanc2d6', range: [89, 93] }, { code: 'tbnoir2d8', range: [94, 97] }, { code: 'rdd1ddr+7', range: [98, 100] }],
collines: [{ code: 'messager2d4', range: [1, 15] }, { code: 'passeur2d4', range: [16, 30] }, { code: 'fleur1d6', range: [31, 42] }, { code: 'mangeur1d6', range: [43, 54] }, { code: 'changeur2d6', range: [55, 69] }, { code: 'briseur2d6', range: [70, 82] }, { code: 'reflet2d6', range: [83, 88] }, { code: 'tbblanc2d6', range: [89, 93] }, { code: 'tbnoir2d8', range: [94, 97] }, { code: 'rdd1ddr+7', range: [98, 100] }],
foret: [{ code: 'messager2d4', range: [1, 15] }, { code: 'passeur2d4', range: [16, 30] }, { code: 'fleur1d6', range: [31, 42] }, { code: 'mangeur1d6', range: [43, 54] }, { code: 'changeur2d6', range: [55, 69] }, { code: 'briseur2d6', range: [70, 82] }, { code: 'reflet2d6', range: [83, 88] }, { code: 'tbblanc2d6', range: [89, 93] }, { code: 'tbnoir2d8', range: [94, 97] }, { code: 'rdd1ddr+7', range: [98, 100] }],
monts: [{ code: 'messager2d4', range: [1, 10] }, { code: 'passeur2d4', range: [11, 20] }, { code: 'fleur1d6', range: [21, 26] }, { code: 'mangeur1d6', range: [27, 44] }, { code: 'changeur2d6', range: [45, 59] }, { code: 'briseur2d6', range: [60, 75] }, { code: 'reflet2d6', range: [76, 85] }, { code: 'tbblanc2d6', range: [86, 92] }, { code: 'tbnoir2d8', range: [93, 97] }, { code: 'rdd1ddr+7', range: [98, 100] }],
desert: [{ code: 'messager2d4', range: [1, 10] }, { code: 'passeur2d4', range: [11, 20] }, { code: 'fleur1d6', range: [21, 26] }, { code: 'mangeur1d6', range: [27, 44] }, { code: 'changeur2d6', range: [45, 59] }, { code: 'briseur2d6', range: [60, 75] }, { code: 'reflet2d6', range: [76, 85] }, { code: 'tbblanc2d6', range: [86, 92] }, { code: 'tbnoir2d8', range: [93, 97] }, { code: 'rdd1ddr+7', range: [98, 100] }],
fleuve: [{ code: 'messager2d4', range: [1, 5] }, { code: 'passeur2d4', range: [6, 10] }, { code: 'fleur1d6', range: [11, 13] }, { code: 'mangeur1d6', range: [14, 37] }, { code: 'changeur2d6', range: [38, 49] }, { code: 'briseur2d6', range: [50, 65] }, { code: 'reflet2d6', range: [66, 79] }, { code: 'tbblanc2d6', range: [80, 89] }, { code: 'tbnoir2d8', range: [90, 97] }, { code: 'rdd1ddr+7', range: [98, 100] }],
lac: [{ code: 'messager2d4', range: [1, 5] }, { code: 'passeur2d4', range: [6, 10] }, { code: 'fleur1d6', range: [11, 13] }, { code: 'mangeur1d6', range: [14, 37] }, { code: 'changeur2d6', range: [38, 49] }, { code: 'briseur2d6', range: [50, 65] }, { code: 'reflet2d6', range: [66, 79] }, { code: 'tbblanc2d6', range: [80, 89] }, { code: 'tbnoir2d8', range: [90, 97] }, { code: 'rdd1ddr+7', range: [98, 100] }],
marais: [{ code: 'messager2d4', range: [1, 2] }, { code: 'passeur2d4', range: [3, 4] }, { code: 'fleur1d6', range: [5, 5] }, { code: 'mangeur1d6', range: [6, 29] }, { code: 'changeur2d6', range: [30, 39] }, { code: 'briseur2d6', range: [40, 60] }, { code: 'reflet2d6', range: [61, 75] }, { code: 'tbblanc2d6', range: [76, 86] }, { code: 'tbnoir2d8', range: [87, 97] }, { code: 'rdd1ddr+7', range: [98, 100] }],
gouffre: [{ code: 'messager2d4', range: [1, 2] }, { code: 'passeur2d4', range: [3, 4] }, { code: 'fleur1d6', range: [5, 5] }, { code: 'mangeur1d6', range: [6, 29] }, { code: 'changeur2d6', range: [30, 39] }, { code: 'briseur2d6', range: [40, 60] }, { code: 'reflet2d6', range: [61, 75] }, { code: 'tbblanc2d6', range: [76, 86] }, { code: 'tbnoir2d8', range: [87, 97] }, { code: 'rdd1ddr+7', range: [98, 100] }],
necropole: [{ code: 'mangeur1d6', range: [1, 20] }, { code: 'changeur2d6', range: [21, 30] }, { code: 'briseur2d6', range: [31, 50] }, { code: 'reflet2d6', range: [51, 65] }, { code: 'tbblanc2d6', range: [66, 80] }, { code: 'tbnoir2d8', range: [81, 97] }, { code: 'rdd1ddr+7', range: [98, 100] }],
desolation: [{ code: 'mangeur1d6', range: [1, 20] }, { code: 'changeur2d6', range: [21, 30] }, { code: 'briseur2d6', range: [31, 50] }, { code: 'reflet2d6', range: [51, 65] }, { code: 'tbblanc2d6', range: [66, 80] }, { code: 'tbnoir2d8', range: [81, 97] }, { code: 'rdd1ddr+7', range: [98, 100] }]
}
/* -------------------------------------------- */
export class TMRRencontres {
static gestionRencontre = {}
/* -------------------------------------------- */
static init() {
for (let type in typeRencontres) {
TMRRencontres.register(type, typeRencontres[type]);
}
}
/* -------------------------------------------- */
static register(type, rencontre) {
TMRRencontres.gestionRencontre[type] = rencontre;
}
/* -------------------------------------------- */
/**
* Retourne une recontre en fonction de la case et du tirage
* @param {*} terrain
* @param {*} roll
*/
static async rollRencontre(terrain, roll = undefined) {
if (!terrain) {
ChatMessage.create({ content: "Un type de case doit être indiqué (par exemple sanctuaire, desert ou cité)" });
return false;
}
if (!roll || roll <= 0 || roll > 100) {
roll = new Roll("1d100").evaluate().total;
}
let rencontre = TMRRencontres.getRencontreAleatoire(terrain, roll);
ChatMessage.create({
user: game.user._id,
whisper: [game.user._id],
content: `Rencontre en ${terrain} (jet : ${roll}%)<br>Vous rencontrez un ${rencontre.name} de ${force} Points de Rêve`
});
return false;
}
/* -------------------------------------------- */
static getRencontre(index) {
let rencontre;
if (isNaN(index)) {
rencontre = rencontresStandard.find(r => r.type == index)
if (!rencontre) {
rencontre = mauvaisesRencontres.find(r => r.type == index)
}
}
else {
if (0 <= index && index < rencontresStandard.length) {
rencontre = rencontresStandard[index];
}
else if (rencontresStandard.length <= index && index < rencontresStandard.length + mauvaisesRencontres.length) {
rencontre = mauvaisesRencontres[index - rencontresStandard.length];
}
}
if (rencontre) {
return duplicate(rencontre);
}
return undefined;
}
/* -------------------------------------------- */
static async getRencontreAleatoire(terrain, roll = undefined) {
if (!roll || roll <= 0 || roll > 100) {
roll = new Roll("1d100").evaluate().total;
}
terrain = Grammar.toLowerCaseNoAccent(terrain);
console.log("getRencontreAleatoire", terrain, roll);
const code = tableRencontres[terrain].find(it => it.range[0] <= roll && roll <= it.range[1]).code;
const rencontre = duplicate(rencontresStandard.find(it => it.code == code));
rencontre.roll = roll;
await TMRRencontres.evaluerForceRencontre(rencontre);
return rencontre;
}
/* -------------------------------------------- */
static async getMauvaiseRencontre(index = undefined) {
if (index == undefined || index >= mauvaisesRencontres.length) {
index = new Roll("1d" + mauvaisesRencontres.length).roll().total - 1;
}
const rencontre = duplicate(mauvaisesRencontres[index]);
await TMRRencontres.evaluerForceRencontre(rencontre);
return rencontre;
}
/* -------------------------------------------- */
static async evaluerForceRencontre(rencontre) {
if (TMRRencontres.isReveDeDragon(rencontre)) {
rencontre.force = await DeDraconique.ddr("selfroll").total + 7;
}
else {
rencontre.force = new Roll(rencontre.force).evaluate().total;
}
return rencontre.force;
}
/* -------------------------------------------- */
static isReveDeDragon(rencontre) {
return rencontre.type == "rdd";
}
/* -------------------------------------------- */
static getGestionRencontre(name) {
let gestion = TMRRencontres.gestionRencontre[name];
if (!gestion) {
ui.notifications.error(`La rencontre ${name} est inconnue, pas de méthode de gestion associée`)
gestion = TMRRencontres.gestionRencontre['messager'];
}
return gestion;
}
static async gererRencontre(tmrDialog, data) {
let gestion = TMRRencontres.getGestionRencontre(data.rencontre.type);
if (data.rolled.isSuccess) {
data.message = gestion.msgSucces(data);
if (data.nbRounds > 1) {
data.message += ` Au total, vous avez passé ${data.nbRounds} rounds à vous battre!`;
}
data.poesie = gestion.poesieSucces;
return gestion.postSucces;
}
data.message = gestion.msgEchec(data);
if (data.nbRounds > 1) {
data.message += ` Vous avez passé ${data.nbRounds} rounds à lutter!`;
}
data.poesie = gestion.poesieEchec;
return gestion.postEchec;
}
static msgEchecPasseurFou(data) {
data.sortReserve = data.actor.data.data.reve.reserve.list[0];
if (data.sortReserve) {
// Passeur fou positionne sur la case d'un ort en réserve // TODO : Choisir le sort le plus loin ou au hasard
data.newTMR = TMRUtility.getTMR(data.sortReserve.coord);
} else {
// Déplacement aléatoire de la force du Passeur Fou
const locList = TMRUtility.getTMRPortee(data.tmr.coord, data.rencontre.force);
const newCoord = locList[new Roll("1d" + locList.length).evaluate().total - 1];
data.newTMR = TMRUtility.getTMR(newCoord);
}
if (data.sortReserve) {
return `Le ${data.rencontre.name} vous dérobe la clé de vos sorts. Vous vous saisissez de lui, mais dans un nuage violet, il vous emporte en ${data.newTMR.label} déclencher votre sort en réserve de ${data.sortReserve.name}.`;
}
else {
return `Le ${data.rencontre.name} tente de vous dérober la clé de vos sorts. Ne la trouvant pas, il déclenche un nuage violet et vous emporte en ${data.newTMR.label}`;
}
}
static async postEchecPasseurFou(tmrDialog, data) {
if (data.sortReserve) {
await tmrDialog.processSortReserve(data.sortReserve);
}
await tmrDialog.forceDemiRevePosition(data.newTMR.coord);
if (data.sortReserve) {
tmrDialog.close();
}
}
/* -------------------------------------------- */
static async onPostEchecTourbillon(tmrDialog, data, cases) {
await data.actor.reveActuelIncDec(-cases);
await TMRRencontres._toubillonner(tmrDialog, data.actor, cases);
}
/* -------------------------------------------- */
static async onPostEchecTourbillonRouge(tmrDialog, data) {
await data.actor.reveActuelIncDec(-2); // -2 pts de Reve a chaque itération
TMRRencontres._toubillonner(tmrDialog, data.actor, 4);
await data.actor.santeIncDec("vie", -1); // Et -1 PV
}
static async _toubillonner(tmrDialog, actor, cases) {
let coord = actor.data.data.reve.tmrpos.coord;
for (let i = 0; i < cases; i++) {
coord = TMRUtility.deplaceTMRAleatoire(coord);
}
await tmrDialog.forceDemiRevePosition(coord)
}
static async onSuccessReveDeDragon(data) {
await data.actor.appliquerReveDeDragon(data.rolled, data.rencontre.force);
if (data.rolled.isPart) {
await data.actor.appliquerExperience(data.rolled, 'reve', data.competence);
}
return `A tout seigneur, tout honneur, vous faites face à un ${data.rencontre.name}. Vous le maîtrisez et récupérez ses rêves. Vous gagnez ses ${data.rencontre.force} points de rêve, votre nouveau total est de " + tmrDialog.actor.data.data.reve.reve.value `;
}
static async onEchecReveDeDragon(data) {
await data.actor.appliquerReveDeDragon(data.rolled, data.rencontre.force);
const queues = data.rolled.isETotal ? 'deux queues' : 'une queue';
return `A tout seigneur, tout honneur, vous faites face à un ${data.rencontre.name}. La rencontre tourne au cauchemar, dans la lutte épique, vous subissez ${queues} de dragon!`;
}
}

View File

@ -1,4 +1,5 @@
import { DeDraconique } from "./de-draconique.js";
import { TMRRencontres } from "./tmr-rencontres.js";
import { Grammar } from "./grammar.js";
/* -------------------------------------------- */
@ -208,71 +209,55 @@ const TMRMapping = {
M15: { type: "cite", label: "Cité de Klana"}
}
/* -------------------------------------------- */
const rencontresSpeciale = [
{name:"Mangeur de Rêve", data: { force: "1d6", ignorer: false, derober: true, refoulement: 2, quitterTMR: false } },
{name:"Mangeur de Rêve", data: { force: "2d6", ignorer: false, derober: true, refoulement: 2, quitterTMR: false } },
{name:"Reflet d'ancien Rêve", data: { force: "2d6+4", ignorer: false, derober: true, refoulement: 2, quitterTMR: false } },
{name:"Tourbillon blanc", data: { force: "2d6+4", ignorer: false, derober: true, refoulement: 2, quitterTMR: false } },
{name:"Tourbillon noir", data: { force: "2d8+4", ignorer: false, derober: true, refoulement: 2, quitterTMR: false } },
{name:"Passeur fou", data: { force: "2d8", ignorer: false, derober: true, refoulement: 2, quitterTMR: false } },
{name:"Tourbillon rouge", data: { force: "2d8", ignorer: false, derober: true, refoulement: 3, quitterTMR: false } }
]
/* -------------------------------------------- */
const rencontresTable = [
{name:"Messagers des Rêves", data: { force: "2d4", ignorer: true, derober: true, refoulement: 1, quitterTMR: false,
cite: "01-25", sanctuaire: "01-25", plaines: "01-20", pont: "01-20", collines: "01-15", foret: "01-15", monts: "01-10", desert: "01-10", fleuve: "01-05",
lac: "01-05", marais: "01-02", gouffre: "01-02", necropole: "00-00", desolation: "00-00" } },
{name:"Passeur des Rêves", data: { force: "2d4", ignorer: true, derober: true, refoulement: 1, quitterTMR: false,
cite: "26-50", sanctuaire: "26-50", plaines: "21-40", pont: "21-40", collines: "16-30", foret: "16-30", monts: "11-20", desert: "11-20", fleuve: "06-10",
lac: "06-10", marais: "03-04", gouffre: "03-04", necropole: "00-00", desolation: "00-00" } },
{name:"Fleur des Rêves", data: { force: "1d6", ignorer: true, derober: true, refoulement: 1, quitterTMR: false,
cite: "51-65", sanctuaire: "51-65", plaines: "41-55", pont: "41-55", collines: "31-42", foret: "31-42", monts: "21-26", desert: "21-26", fleuve: "11-13",
lac: "11-13", marais: "05-05", gouffre: "05-05", necropole: "00-00", desolation: "00-00" } },
{name:"Mangeur de Rêve", data: { force: "1d6", ignorer: false, derober: true, refoulement: 1, quitterTMR: false,
cite: "66-70", sanctuaire: "66-70", plaines: "56-60", pont: "56-60", collines: "43-54", foret: "43-54", monts: "27-44", desert: "27-44", fleuve: "14-37",
lac: "14-37", marais: "06-29", gouffre: "06-29", necropole: "01-20", desolation: "01-20" } },
{name:"Changeur de Rêve", data: { force: "2d6", ignorer: false, derober: true, refoulement: 1, quitterTMR: false,
cite: "71-80", sanctuaire: "71-80", plaines: "61-75", pont: "61-75", collines: "55-69", foret: "55-69", monts: "45-59", desert: "45-59", fleuve: "38-49",
lac: "38-49", marais: "30-39", gouffre: "30-39", necropole: "21-30", desolation: "21-30" } },
{name:"Briseur de Rêve", data: { force: "2d6", ignorer: false, derober: true, refoulement: 1, quitterTMR: true,
cite: "81-85", sanctuaire: "81-85", plaines: "76-82", pont: "76-82", collines: "70-82", foret: "70-82", monts: "60-75", desert: "60-75", fleuve: "50-65",
lac: "50-65", marais: "40-60", gouffre: "40-60", necropole: "31-50", desolation: "31-50" } },
{name:"Reflet d'ancien Rêve", data: { force: "2d6", ignorer: false, derober: true, refoulement: 1,quitterTMR: false,
cite: "86-90", sanctuaire: "86-90", plaines: "83-88", pont: "83-88", collines: "83-88", foret: "83-88", monts: "76-85", desert: "76-85", fleuve: "66-79",
lac: "66-79", marais: "61-75", gouffre: "61-75", necropole: "51-65", desolation: "51-65" } },
{name:"Tourbillon blanc", data: { force: "2d6", ignorer: false, derober: true, refoulement: 1, quitterTMR: false,
cite: "91-94", sanctuaire: "91-94", plaines: "89-93", pont: "89-93", collines: "89-93", foret: "89-93", monts: "86-92", desert: "86-92", fleuve: "80-89",
lac: "80-89", marais: "76-86", gouffre: "76-86", necropole: "66-80", desolation: "66-80" } },
{name:"Tourbillon noir", data: { force: "2d8", ignorer: false, derober: true, refoulement: 1, quitterTMR: false,
cite: "95-97", sanctuaire: "95-97", plaines: "94-97", pont: "94-97", collines: "94-97", foret: "94-97", monts: "93-97", desert: "93-97", fleuve: "90-97",
lac: "90-97", marais: "87-97", gouffre: "90-97", necropole: "81-97", desolation: "81-97" } },
{name:"Rêve de Dragon", data: { force: "1ddr + 7", ignorer: false, derober: true, refoulement: 2, quitterTMR: true,
cite: "98-00", sanctuaire: "98-00", plaines: "98-00", pont: "98-00", collines: "98-00", foret: "98-00", monts: "98-00", desert: "98-00", fleuve: "98-00",
lac: "98-00", marais: "98-00", gouffre: "98-00", necropole: "98-00", desolation: "98-00" } }
]
export const TMRType = {
cite: "cité",
sanctuaire: "sanctuaire",
plaines: "plaines",
pont: "pont",
collines: "collines",
foret: "forêt",
monts: "monts",
desert: "désert",
fleuve: "fleuve",
lac: "lac",
marais: "marais",
gouffre: "gouffre",
necropole: "nécropole",
desolation: "désolation"
}
/* -------------------------------------------- */
const caseSpecificModes = [ "attache", "trounoir", "debordement", "reserve_extensible", "maitrisee" ];
/* -------------------------------------------- */
const tmrMovePattern =
const tmrRandomMovePatten =
[ { name: 'top', x: 0, y: -1 },
{ name: 'topright', x: 1, y: -1 },
{ name: 'left', x: 1, y: 'alt' },
{ name: 'botright', x: 1, y: 1 },
{ name: 'bot', x: 0, y: 1 },
{ name: 'botleft', x: -1, y: 1 },
{ name: 'left', x: -1, y: 'alt' },
{ name: 'topleft', x: -1, y: -1 }
]
/* -------------------------------------------- */
export const tmrConstants = {
col1_y: 30,
col2_y: 55,
cellw: 55,
cellh: 55,
gridx: 28,
gridy: 28
}
/* -------------------------------------------- */
/* -------------------------------------------- */
export class TMRUtility {
static init() {
for (let coord in TMRMapping) {
TMRMapping[coord].coord = coord;
}
}
/* -------------------------------------------- */
static convertToTMRCoord( x, y )
@ -303,261 +288,100 @@ export class TMRUtility {
}
/* -------------------------------------------- */
static getTMRDescription( coordTMR)
static getTMR( coordTMR)
{
return TMRMapping[coordTMR];
}
/* -------------------------------------------- */
/** Some debug functions */
static setForceRencontre( id, force ) {
this.forceRencontre = { id: id, force: force}
}
/* -------------------------------------------- */
static clearForceRencontre( id, force ) {
this.forceRencontre = undefined
static async setForceRencontre( index, force = undefined ) {
this.prochaineRencontre = TMRRencontres.getRencontre( index );
if (this.prochaineRencontre ) {
if (force) {
this.prochaineRencontre.force = force;
}
else{
await TMRRencontres.evaluerForceRencontre(this.prochaineRencontre)
}
console.log("La prochaine rencontre sera:", this.prochaineRencontre.name, " force:", this.prochaineRencontre.force);
}
else {
ui.notifications.warn("Pas de prochaine rencontre valide pour "+index);
}
}
/* -------------------------------------------- */
static isForceRencontre() {
return this.forceRencontre
return this.prochaineRencontre
}
/* -------------------------------------------- */
static getDirectionPattern() {
let index = new Roll("1d"+tmrMovePattern.length+" -1").roll().total;
return tmrMovePattern[index];
let roll = new Roll("1d"+tmrRandomMovePatten.length).evaluate().total;
return tmrRandomMovePatten[roll -1];
}
static deplaceTMRAleatoire(coord) {
return TMRUtility.deplaceTMRSelonPattern(coord, TMRUtility.getDirectionPattern(), 1);
}
/* -------------------------------------------- */
static deplaceTMRSelonPattern( pos, pattern, nTime ) {
static deplaceTMRSelonPattern( coord, direction, nTime ) {
for (let i=0; i <nTime; i++) {
let currentPosXY = TMRUtility.convertToCellCoord(pos);
currentPosXY.x = currentPosXY.x + pattern.x;
if (pattern.y == 'alt' ) { // Alternate version
pattern.y += (pattern.x % 2 == 0 ) ? -1 : 1;
} else {
currentPosXY.y = currentPosXY.y + pattern.y;
}
let currentPosXY = TMRUtility.convertToCellCoord(coord);
currentPosXY.x = currentPosXY.x + direction.x;
currentPosXY.y = currentPosXY.y + direction.y;
if ( this._checkTMRCoord(currentPosXY.x, currentPosXY.y) ) { // Sortie de carte ! Ré-insertion aléatoire
pos = TMRUtility.convertToTMRCoord(currentPosXY.x, currentPosXY.y);
coord = TMRUtility.convertToTMRCoord(currentPosXY.x, currentPosXY.y);
} else {
pos = this.getTMRAleatoire();
coord = this.getTMRAleatoire();
}
console.log("Nouvelle case iteration !!!", i, pos);
console.log("Nouvelle case iteration !!!", i, coord);
}
return pos;
return coord;
}
/* -------------------------------------------- */
static async rencontreTMRRoll( coordTMR, cellDescr, isSpecial = false )
static async rencontreTMRRoll( coordTMR, cellDescr, isMauvaise = false )
{
if ( this.forceRencontre ) {
// Forced
let rencontre = duplicate(rencontresTable[this.forceRencontre.id]);
rencontre.force = this.forceRencontre.force;
rencontre.coord = coordTMR;
rencontre.nbCases = 0; // Utilisé pour les Tourbillons
return rencontre;
}
let rencontre;
if ( isSpecial ) {
let index = new Roll("1d7").roll().total;
rencontre = rencontresSpeciale[index-1];
} else {
rencontre = await this.rencontreTMRTypeCase(cellDescr.type);
}
//let rencontre = rencontresTable[4];
if (rencontre) {
rencontre = duplicate(rencontre);
rencontre.force = await this.evaluerForceRencontre(rencontre);
if ( this.prochaineRencontre ) {
rencontre = this.prochaineRencontre;
rencontre.coord = coordTMR;
rencontre.nbCases = 0; // Utilisé pour les Tourbillons
rencontre.isSpecial = isSpecial; // Garder l'information
this.prochaineRencontre = undefined;
}
else if ( isMauvaise ) {
rencontre = await TMRRencontres.getMauvaiseRencontre();
} else {
rencontre = await TMRRencontres.getRencontreAleatoire(cellDescr.type);
}
rencontre.coord = coordTMR;
return rencontre;
}
/* -------------------------------------------- */
static async rencontreTMRTypeCase(typeTMR, roll=undefined) {
if (!roll) {
//roll = await RdDDice.show(new Roll("d100").evaluate()).total;
roll = new Roll("1d100").roll().total;
console.log("rencontreTMRTypeCase", roll);
}
typeTMR = Grammar.toLowerCaseNoAccent(typeTMR);
for( let rencontre of rencontresTable) {
console.log("TMR !!!", typeTMR, roll);
let scoreDef = rencontre.data[typeTMR];
let min = scoreDef.substr(0,2);
let max = scoreDef.substr(3,2);
if (min=="00") min = 101;
if (max=="00") max = 100;
if (roll >= min && roll <= max) {
return rencontre;
static getListTMR(terrain) {
let list = [];
for (let index in TMRMapping) {
if (TMRMapping[index].type == terrain){
list.push(TMRMapping[index]);
}
}
return list;
}
static getListCoordTMR(terrain) {
return this.getListTMR(terrain).map(it=>it.coord);
}
/* -------------------------------------------- */
/**
* Retourne une recontre en fonction de la case et du tirage
* @param {*} terrain
* @param {*} roll
*/
static async getRencontre( terrain, roll ) {
if ( !terrain) {
ChatMessage.create({ content: "Un nom de case doit être indiqué (ie /tmrr desert ou /tmrr cite)" });
return false;
}
roll = roll ?? new Roll("1d100").evaluate().total;
roll = Math.max(1, Math.min(roll, 100));
let rencontre = await this.rencontreTMRTypeCase(terrain, roll);
if (rencontre) {
let force = await this.evaluerForceRencontre(rencontre);
ChatMessage.create({
user: game.user._id,
whisper: [game.user._id],
content: `Rencontre en ${terrain} (jet : ${roll}%)<br>Vous rencontrez un ${rencontre.name} de ${force} Points de Rêve`});
}
return false;
}
/* -------------------------------------------- */
static getLocationTypeList( coordTMR ) {
let descr = this.getTMRDescription( coordTMR );
let typeList = [];
for (let index in TMRMapping) {
let caseTMR = TMRMapping[index];
if (caseTMR.type == descr.type)
typeList.push(index)
}
return typeList;
}
/* -------------------------------------------- */
static async evaluerForceRencontre(rencontre) {
if (this.isReveDeDragon(rencontre)) {
let ddr = await DeDraconique.ddr("selfroll");
return ddr.total + 7;
}
else {
const roll = new Roll(rencontre.data.force).evaluate();
return roll.total;
}
}
/* -------------------------------------------- */
static isReveDeDragon(rencontre) {
return rencontre.name.toLowerCase() == "Rêve de Dragon".toLowerCase();
}
/* -------------------------------------------- */
static async processRencontreReussite( actor, rencontre, rolled ) {
let message = "<br>";
let state = "aucune";
console.log("processRencontreReussite", actor, rencontre);
if (rencontre.name == "Messagers des Rêves") {
message += "Le Messager des Rêves vous permet de lancer votre sort à " + rencontre.force + " cases !";
state = 'messager';
} else if (rencontre.name == "Passeur des Rêves") {
message += "Le Passeur des Rêves vous permet de vous téléporter à " + rencontre.force + " cases !";
state = 'passeur';
} else if (rencontre.name == "Fleur des Rêves") {
await actor.reveActuelIncDec( rencontre.force );
message += "La Fleur des rêves s'évanouit en vous fournissant " + rencontre.force + " Points de Rêve";
} else if (rencontre.name == "Mangeur de Rêve") {
message += "Ce Mangeur des Rêves disparait !"
} else if (rencontre.name == "Changeur de Rêve") {
message += "Ce Changeur des Rêves vous propose de vous déplacer sur une autre case de même type."
state = 'changeur';
} else if (rencontre.name == "Briseur de Rêve") {
message += "Ce Briseur des Rêves disparait !"
} else if (rencontre.name == "Reflet d'ancien Rêve") {
message += "Ce Reflet d'ancien Rêve disparait !"
} else if (rencontre.name == "Tourbillon blanc") {
message += "Ce Tourbillon Blanc disparait !"
} else if (rencontre.name == "Tourbillon noir") {
message += "Ce Tourbillon Noir disparait !"
} else if (rencontre.name == "Rêve de Dragon") {
// TODO: xp particulière
message += "Vous maîtrisez le Rêve de Dragon !"
message += await actor.appliquerReveDeDragon(rolled, rencontre.force);
}
return { message: message, state: state };
}
/* -------------------------------------------- */
static async processRencontreEchec( actor, rencontre, rolled, tmrDialog ) {
let message = "<br>";
let state = "aucune";
if (rencontre.name == "Messagers des Rêves") {
message += "Le Messager des Rêves s'éloigne de vous !";
} else if (rencontre.name == "Passeur des Rêves") {
message += "Le Passeur des Rêves s'éloigne de vous !";
} else if (rencontre.name == "Fleur des Rêves") {
message += "La Fleur des rêves s'éloigne de vous et se perd dans les Terres Médianes";
} else if (rencontre.name == "Mangeur de Rêve") {
await actor.reveActuelIncDec( -rencontre.force );
message += "Ce Mangeur des Rêves croque votre Rêve ! Vous perdez " + rencontre.force + " points de rêve actuels, votre nouveau total est de " + actor.data.data.reve.reve.value;
} else if (rencontre.name == "Changeur de Rêve") {
message += "Ce Changeur des Rêves vous déplace sur un autre case du même type.<br>"
let locList = this.getLocationTypeList( actor.data.data.reve.tmrpos.coord );
let index = new Roll("1d"+locList.length + " - 1").roll().total;
let newCoord = locList[index];
tmrDialog.forceDemiRevePosition(newCoord);
let cellDescr = TMRUtility.getTMRDescription(newCoord);
message += "Vous avez été téléporté en " + newCoord + " - " + cellDescr.label;
} else if (rencontre.name == "Briseur de Rêve") {
message += "Votre Rêve est Brisé, vous quittez les Terres Médianes";
} else if (rencontre.name == "Passeur fou") {
message += "Vous êtes déplacé sur la case de votre sort en réserve le plus proche, ou sinon aléatoirement dans une direction";
state = "passeurfou";
} else if (rencontre.name == "Reflet d'ancien Rêve") {
message += "Votre Rêve est figé, vous restez sur cette case tant que ce Reflet n'est pas vaincu!";
state = "reflet";
} else if (rencontre.name == "Tourbillon blanc") {
message += "Vous êtes emporté par le Tourbillon...";
state = "tourbillonblanc";
} else if (rencontre.name == "Tourbillon noir") {
message += "Vous êtes emporté par le Tourbillon...";
state = "tourbillonnoir";
} else if (rencontre.name == "Tourbillon rouge") {
message += "Vous êtes emporté par le Tourbillon...";
state = "tourbillonrouge";
} else if (rencontre.name == "Rêve de Dragon") {
message += "Le Rêve de Dragon tourne au cauchemar !"
message += actor.appliquerReveDeDragon(rolled, rencontre.force);
}
return { message: message, state: state };
}
/* -------------------------------------------- */
static getTMRAleatoire()
static getTMRAleatoire(terrain=undefined)
{
if (terrain) {
let list = TMRUtility.getListTMR(terrain);
let index = new Roll("1d" + list.length).evaluate().total - 1;
return list[index];
}
let num = new Roll("1d15").roll().total;
let letter, letterValue;
if ( num == 15) {
@ -575,7 +399,8 @@ export class TMRUtility {
/* -------------------------------------------- */
static _checkTMRCoord( x, y ) {
if (x >= 0 && x < 13 && y >= 0 && y < 15 ) return true;
if (x >= 0 && x < 13 && y >= 0 && y < 14 ) return true;
if (x >= 0 && x < 13 && x%2 == 0 && y == 14 ) return true;
return false;
}
@ -592,10 +417,10 @@ export class TMRUtility {
static getSortReserveList( reserveList, coordTMR ) {
// TODO : Gérer les têtes spéciales réserve!
let sortReserveList
let tmrDescr = this.getTMRDescription(coordTMR);
let tmrDescr = this.getTMR(coordTMR);
//console.log("Sort réserve : ", tmrDescr);
if ( tmrDescr.type == 'fleuve') { // Gestion de la reserve en Fleuve
sortReserveList = reserveList.filter(it => TMRUtility.getTMRDescription(it.coord).type == 'fleuve' );
sortReserveList = reserveList.filter(it => TMRUtility.getTMR(it.coord).type == 'fleuve' );
} else { // Reserve sur un case "normale"
sortReserveList = reserveList.filter(it => it.coord == coordTMR);
}
@ -607,6 +432,10 @@ export class TMRUtility {
/** Returns a list of case inside a given distance
*
*/
static getTMRPortee(coord, portee) {
return TMRUtility.getTMRArea(coord, portee, tmrConstants);
}
static getTMRArea( coord, distance, tmrConstants ) {
let pos = this.convertToCellCoord( coord );
let posPic = this.computeRealPictureCoordinates( pos, tmrConstants );

View File

@ -431,6 +431,14 @@ table {border: 1px solid #7a7971;}
padding: 5px;
}
.poesie-extrait {
font-size: 0.85rem;
font-style: italic;
}
.poesie-reference{
font-size: 0.70rem;
text-align: right;
}
/* ======================================== */
/* Sheet */
.window-app.sheet .window-content .sheet-header{

View File

@ -0,0 +1,16 @@
<img class="chat-icon" src="{{competence.img}}" alt="{{competence.name}}"/>
<h4 data-categorie="tmr" data-actor-id="{{actor._id}}" data-rencontre-round="{{nbRounds}}">
{{alias}} rencontre {{#if (eq genre 'f')}}une{{else}}un{{/if}} {{rencontre.name}} de force {{rencontre.force}}
</h4>
{{> "systems/foundryvtt-reve-de-dragon/templates/chat-infojet.html"}}
<hr>
<span>
{{message}}
</span>
{{#if poesie}}
<hr>
<span class="poesie-extrait">
{{{poesie.extrait}}}
<p class="poesie-reference">{{poesie.reference}}</p>
</span>
{{/if}}