";
@@ -483,9 +599,10 @@ export class RdDTMRDialog extends Dialog {
}
}
}
+
/* -------------------------------------------- */
lancerSortEnReserve(coordTMR, sortId) {
- let sortReserveList = TMRUtility.getSortReserveList(this.sortReserves, coordTMR);
+ let sortReserveList = TMRUtility.getSortReserveList(this.sortsReserves, coordTMR);
let sortReserve = sortReserveList.find(sortReserve => sortReserve.sort._id == sortId);
//console.log("SORT RESA", sortReserveList, coordTMR, sortId, sortReserve);
if (sortReserve) {
@@ -535,24 +652,34 @@ export class RdDTMRDialog extends Dialog {
}
/* -------------------------------------------- */
- isTerreAttache(coordTMR) {
- for (let caseTMR of this.casesSpeciales) {
- if (caseTMR.data.specific == 'attache' && caseTMR.data.coord == coordTMR) { // Match !
- return true;
- }
- }
- return false;
+ isCaseInondee(coord) {
+ return this.casesSpeciales.find(c => EffetsDraconiques.isCaseInondee(c, coord));
+ }
+
+ isCiteFermee(coord) {
+ return this.casesSpeciales.find(c => EffetsDraconiques.isCiteFermee(c, coord));
}
/* -------------------------------------------- */
- checkConnaissanceFleuve(currentTMR, nextTMR) {
- if (this.actor.isConnaissanceFleuve()) {
- //console.log(currentTMR, nextTMR );
- if (TMRUtility.getTMR(currentTMR).type == 'fleuve' && TMRUtility.getTMR(nextTMR).type == 'fleuve') {
- return true;
- }
- }
- return false;
+ isTerreAttache(coord) {
+ return this.casesSpeciales.find(c => EffetsDraconiques.isTerreAttache(c, coord));
+ }
+
+ /* -------------------------------------------- */
+ isCaseMaitrisee(coord) {
+ return this.casesSpeciales.find(c => EffetsDraconiques.isCaseMaitrisee(c, coord));
+ }
+
+ /* -------------------------------------------- */
+ isReserveExtensible(coord) {
+ return this.casesSpeciales.find(c => EffetsDraconiques.isReserveExtensible(c, coord));
+ }
+
+ /* -------------------------------------------- */
+ isConnaissanceFleuve(currentTMR, nextTMR) {
+ return TMRUtility.getTMR(currentTMR).type == 'fleuve' &&
+ TMRUtility.getTMR(nextTMR).type == 'fleuve' &&
+ EffetsDraconiques.isConnaissanceFleuve(this.actor);
}
/* -------------------------------------------- */
@@ -562,7 +689,7 @@ export class RdDTMRDialog extends Dialog {
}
let origEvent = event.data.originalEvent;
- let tmrObject = event.target.tmrObject;
+ let tmrObject = this;
let eventPos = RdDTMRDialog._computeEventPos(origEvent);
await tmrObject._onClickTMRPos(eventPos); // Vérifier l'état des compteurs reve/fatigue/vie
@@ -579,7 +706,7 @@ export class RdDTMRDialog extends Dialog {
// Validation de la case de destination (gestion du cas des rencontres qui peuvent téléporter)
let deplacementType = 'erreur';
if (this.rencontreState == 'aucune') { // Pas de recontre en post-processing, donc deplacement normal
- if (!RdDTMRDialog._horsDePortee(currentPos, eventPos) || this.isTerreAttache(targetCoordTMR) || this.checkConnaissanceFleuve(currentCoordTMR, targetCoordTMR)) {
+ if (this.isTerreAttache(targetCoordTMR) || this.isConnaissanceFleuve(currentCoordTMR, targetCoordTMR) || !RdDTMRDialog._horsDePortee(currentPos, eventPos)) {
deplacementType = 'normal';
}
} else {
@@ -644,6 +771,7 @@ export class RdDTMRDialog extends Dialog {
async postRencontre(tmr) {
await this.manageCaseHumide(tmr);
+ await this.conquerirCiteFermee(tmr);
await this.declencheSortEnReserve(tmr.coord);
await this.actor.checkSoufflePeage(tmr);
}
@@ -658,76 +786,11 @@ export class RdDTMRDialog extends Dialog {
await this.actor.updateCoordTMR(coordTMR);
this._updateDemiReve();
let tmr = TMRUtility.getTMR(coordTMR);
- this.manageCaseHumide(tmr);
+ await this.manageCaseHumide(tmr);
+ await this.conquerirCiteFermee(tmr);
await this.declencheSortEnReserve(tmr.coord);
}
- /* -------------------------------------------- */
- async activateListeners(html) {
- super.activateListeners(html);
-
- var row = document.getElementById("tmrrow1");
- var cell1 = row.insertCell(1);
- cell1.append(this.pixiApp.view);
-
- if (this.viewOnly) {
- html.find('#lancer-sort').remove();
- }
- else {
- // Roll Sort
- html.find('#lancer-sort').click((event) => {
- this.actor.rollUnSort(this.actor.data.data.reve.tmrpos.coord);
- });
- }
-
- // load the texture we need
- await this.pixiApp.loader
- .add('tmr', 'systems/foundryvtt-reve-de-dragon/styles/img/ui/tmp_main_r1.webp')
- .add('demi-reve', "icons/svg/sun.svg")
- .load((loader, resources) => {
-
- // This creates a texture from a TMR image
- const mytmr = new PIXI.Sprite(resources.tmr.texture);
- // Setup the position of the TMR
- mytmr.x = 0;
- mytmr.y = 0;
- mytmr.width = 720;
- mytmr.height = 860;
- // Rotate around the center
- mytmr.anchor.x = 0;
- mytmr.anchor.y = 0;
- mytmr.interactive = true;
- mytmr.buttonMode = true;
- mytmr.tmrObject = this;
- if (!this.viewOnly) {
- mytmr.on('pointerdown', this.onClickTMR);
- }
- this.pixiApp.stage.addChild(mytmr);
-
- this._addDemiReve();
- this.displayPreviousRencontres();
- this.displaySortReserve();
- this.displaySpecificCase();
- });
-
- if (this.viewOnly) {
- return;
- }
-
- // Gestion du cout de montée en points de rêve
- let reveCout = (this.tmrdata.isRapide && !this.actor.checkTeteDeplacementAccelere()) ? -2 : -1;
- this.cumulFatigue += this.fatigueParCase;
- reveCout -= this.actor.checkMonteeLaborieuse();
- await this.actor.reveActuelIncDec(reveCout);
- // Le reste...
- this.updateValuesDisplay();
- let tmr = TMRUtility.getTMR(this.actor.data.data.reve.tmrpos.coord);
- await this.manageRencontre(tmr, () => {
- this.postRencontre(tmr);
- this.actor.displayTMRQueueSouffleInformation();
- });
- }
-
/* -------------------------------------------- */
static _computeEventPos(origEvent) {
let canvasRect = origEvent.target.getBoundingClientRect();
@@ -747,125 +810,16 @@ export class RdDTMRDialog extends Dialog {
|| (target.y == 0 && target.y < origin.y && target.x != origin.x && origin.x % 2 == 1);
}
- /* -------------------------------------------- */
- _tokenRencontre(rencontre) {
- let sprite = new PIXI.Graphics();
- sprite.beginFill(0x767610, 0.6);
- sprite.drawCircle(0, 0, 6);
- sprite.endFill();
- sprite.decallage = {
- x: (tmrConstants.cellw / 2) - 16,
- y: 16 - (tmrConstants.cellh / 2)
- };
- return { sprite: sprite, rencontre: rencontre, coordTMR: () => rencontre.coord };
- }
-
- /* -------------------------------------------- */
- _tokenTrouNoir(coord) {
- let sprite = new PIXI.Graphics();
- sprite.beginFill(0x050505, 0.8);
- sprite.drawCircle(0, 0, (tmrConstants.cellw / 2) - 2);
- sprite.endFill();
- sprite.decallage = {
- x: 0,
- y: 2
- }
- return { sprite: sprite, coordTMR: () => coord }
- }
-
- /* -------------------------------------------- */
- _tokenDebordement(coord) {
- let sprite = new PIXI.Graphics();
- sprite.beginFill(0x0101FE, 0.3);
- sprite.drawCircle(0, 0, (tmrConstants.cellw / 2) - 2);
- sprite.endFill();
- sprite.decallage = {
- x: 0,
- y: 2
- }
- return { sprite: sprite, coordTMR: () => coord }
- }
-
- /* -------------------------------------------- */
- _tokenMaitrisee(coord) {
- let sprite = new PIXI.Graphics();
- sprite.beginFill(0x1010F0, 0.8);
- sprite.drawCircle(0, 0, 6);
- sprite.endFill();
- sprite.decallage = {
- x: 16 - (tmrConstants.cellw / 2),
- y: 16 - (tmrConstants.cellh / 2)
- }
- return { sprite: sprite, coordTMR: () => coord }
- }
-
- /* -------------------------------------------- */
- _tokenTerreAttache(coord) {
- let sprite = new PIXI.Graphics();
- sprite.beginFill(0x10F010, 0.8);
- sprite.drawCircle(0, 0, 6);
- sprite.endFill();
- sprite.decallage = {
- x: 16 - (tmrConstants.cellw / 2),
- y: 16 - (tmrConstants.cellh / 2)
- }
- return { sprite: sprite, coordTMR: () => coord }
- }
-
- /* -------------------------------------------- */
- _tokenSortEnReserve(sort) {
- let sprite = new PIXI.Graphics();
- sprite.beginFill(0x101010, 0.8);
- sprite.drawCircle(0, 0, 6);
- sprite.endFill();
- sprite.decallage = {
- x: 16 - (tmrConstants.cellw / 2),
- y: 16 - (tmrConstants.cellh / 2)
- }
- return { sprite: sprite, sort: sort, coordTMR: () => sort.coord }
- }
-
- /* -------------------------------------------- */
- _tokenDemiReve() {
- let texture = PIXI.utils.TextureCache['demi-reve'];
- let sprite = new PIXI.Sprite(texture);
- sprite.width = tmrConstants.cellw * 0.7;
- sprite.height = tmrConstants.cellh * 0.7;
- sprite.anchor.set(0.5);
- sprite.tint = 0x00FFEE;
- return { sprite: sprite, actor: this.actor, coordTMR: () => this.actor.data.data.reve.tmrpos.coord }
- }
-
- /* -------------------------------------------- */
- _addDemiReve() {
- this.demiReve = this._tokenDemiReve();
- this._setTokenPosition(this.demiReve);
- this.pixiApp.stage.addChild(this.demiReve.sprite);
- }
-
- /* -------------------------------------------- */
- _updateDemiReve() {
- this._setTokenPosition(this.demiReve);
- }
/* -------------------------------------------- */
/** Retourne les coordonnées x, h, w, h du rectangle d'une case donnée */
_getCaseRectangleCoord(coord) {
- let coordXY = TMRUtility.convertToCellPos(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 }
+ return this.pixiTMR.getCaseRectangle(TMRUtility.convertToCellPos(coord));
}
/* -------------------------------------------- */
_setTokenPosition(token) {
- let coordXY = TMRUtility.convertToCellPos(token.coordTMR());
- let decallagePairImpair = (coordXY.x % 2 == 0) ? tmrConstants.col1_y : tmrConstants.col2_y;
- let dx = (token.sprite.decallage == undefined) ? 0 : token.sprite.decallage.x;
- let dy = (token.sprite.decallage == undefined) ? 0 : token.sprite.decallage.y;
- token.sprite.x = tmrConstants.gridx + (coordXY.x * tmrConstants.cellw) + dx;
- token.sprite.y = tmrConstants.gridy + (coordXY.y * tmrConstants.cellh) + dy + decallagePairImpair;
+ this.pixiTMR.setPosition(token.sprite, TMRUtility.convertToCellPos(token.coordTMR()));
}
/* -------------------------------------------- */
@@ -878,10 +832,9 @@ export class RdDTMRDialog extends Dialog {
/* -------------------------------------------- */
_trackToken(token) {
- this.allTokens.push(token)
+ this.allTokens.push(token);
this._setTokenPosition(token);
- this.pixiApp.stage.addChild(token.sprite);
}
-
}
+
diff --git a/module/rdd-tmr-rencontre-dialog.js b/module/rdd-tmr-rencontre-dialog.js
index ba381889..4c0b1fa8 100644
--- a/module/rdd-tmr-rencontre-dialog.js
+++ b/module/rdd-tmr-rencontre-dialog.js
@@ -9,7 +9,7 @@ export class RdDTMRRencontreDialog extends Dialog {
buttons: {
derober: { icon: '', label: "Se dérober", callback: () => { this.onButtonFuir(() => tmrApp.derober()); } },
refouler: { icon: '', label: "Refouler", callback: () => this.onButtonAction(() => tmrApp.refouler()) },
- maitiser: { icon: '', label: "Maîtriser", callback: () => this.onButtonAction(() => tmrApp.maitriser()) }
+ maitiser: { icon: '', label: "Maîtriser", callback: () => this.onButtonAction(() => tmrApp.maitriserRencontre()) }
},
default: "derober"
};
diff --git a/module/rolldata-ajustements.js b/module/rolldata-ajustements.js
index ecc58c77..e96be96f 100644
--- a/module/rolldata-ajustements.js
+++ b/module/rolldata-ajustements.js
@@ -112,8 +112,8 @@ export const referenceAjustements = {
getDescr: (rollData, actor) => RdDBonus.find(actor.getSurprise()).descr
},
bonusCase: {
- isUsed: (rollData, actor) => rollData.selectedSort && rollData.coord,
- getDescr: (rollData, actor) => rollData.selectedSort && rollData.coord ? `Bonus de case: ${RdDItemSort.getCaseBonus(rollData.selectedSort, rollData.coord)}%` : ''
+ isUsed: (rollData, actor) => rollData.selectedSort && rollData.tmr.coord,
+ getDescr: (rollData, actor) => rollData.selectedSort && rollData.tmr.coord ? `Bonus de case: ${RdDItemSort.getCaseBonus(rollData.selectedSort, rollData.tmr.coord)}%` : ''
},
rencontreTMR: {
isVisible: (rollData, actor) => rollData.tmr && rollData.rencontre?.name,
diff --git a/module/tmr-rencontres.js b/module/tmr-rencontres.js
index b70d49d3..a3883396 100644
--- a/module/tmr-rencontres.js
+++ b/module/tmr-rencontres.js
@@ -86,7 +86,7 @@ const typeRencontres = {
changeur: {
msgSucces: (data) => `Le ${data.rencontre.name} vaincu accepte de vous déplacer sur une autre ${TMRType[data.tmr.type].name} de votre choix en échange de sa liberté.`,
msgEchec: (data) => {
- data.newTMR = TMRUtility.getTMRAleatoire(data.tmr.type);
+ data.newTMR = TMRUtility.getTMRAleatoire(it => it.type = 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) => {
@@ -230,7 +230,7 @@ const typeRencontres = {
msgSucces: (data) => `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`,
msgEchec: (data) => `A tout seigneur, tout honneur, vous faites face à un ${data.rencontre.name}. La rencontre tourne au cauchemar, dans la lutte épique, vous subissez ${data.rolled.isETotal ? 'deux queues' : 'une queue'} de dragon!`,
postSucces: (tmrDialog, data) => TMRRencontres.onPostSuccessReveDeDragon(tmrDialog, data),
- postEchec: (tmrDialog, data) => TMRRencontres.onPostSuccessReveDeDragon(tmrDialog, data),
+ postEchec: (tmrDialog, data) => TMRRencontres.onPostEchecReveDeDragon(tmrDialog, data),
poesieSucces: {
reference: "Rêve de Dragon, Denis Gerfaud",
extrait: `Le monde est Rêve de Dragons, mais nous ne savons
@@ -475,11 +475,11 @@ export class TMRRencontres {
if (data.rolled.isPart) {
await data.actor.appliquerExperience(data.rolled, 'reve', data.competence);
}
- await data.actor.resultCombattreReveDeDragon(data.rolled);
+ await data.actor.resultCombatReveDeDragon(data);
}
static async onPostEchecReveDeDragon(tmrDialog, data) {
- await data.actor.resultCombattreReveDeDragon(data.rolled);
+ await data.actor.resultCombatReveDeDragon(data);
tmrDialog.close();
}
}
diff --git a/module/tmr-utility.js b/module/tmr-utility.js
index 33f5e1da..a2d2d013 100644
--- a/module/tmr-utility.js
+++ b/module/tmr-utility.js
@@ -227,68 +227,6 @@ export const TMRType = {
desolation: { name: "désolation", genre: "f" }
}
-export const poesieHautReve = [
- {
- reference: 'Le Ratier Bretonien',
- extrait: `Le courant du Fleuve
- Te domine et te Porte
- Avant que tu te moeuves
- Combat le, ou il t'emporte`
- },
- {
- reference: 'Incompatibilité, Charles Beaudelaire',
- extrait: `Et lorsque par hasard une nuée errante
- Assombrit dans son vol le lac silencieux,
- On croirait voir la robe ou l'ombre transparente
- D'un esprit qui voyage et passe dans les cieux.`
- },
- {
- reference: 'Au fleuve de Loire, Joachim du Bellay',
- extrait: `Ô de qui la vive course
- Prend sa bienheureuse source,
- D’une argentine fontaine,
- Qui d’une fuite lointaine,
- Te rends au sein fluctueux
- De l’Océan monstrueux`
- },
- {
- reference: 'Denis Gerfaud',
- extrait: `Et l'on peut savoir qui est le maître d'Oniros, c'est le Fleuve de l'Oubli.
- Et l'on sait qui est le créateur du Fleuve de l'Oubli, c'est Hypnos et Narcos.
- Mais l'on ne sait pas qui est le maître du Fleuve de l'Oubli,
- sinon peut-être lui-même, ou peut-être Thanatos` },
- {
- reference: 'Denis Gerfaud',
- extrait: `Narcos est la source du Fleuve de l'Oubli et Hypnos l'embouchure
- Remonter le Fleuve est la Voie de la Nuit, la Voie du Souvenir.
- Descendre le Fleuve est la Voie du Jour, la Voie de l'Oubli`
- },
- {
- reference: 'Denis Gerfaud',
- extrait: `Narcos engendre le fils dont il est la mère à l'heure du Vaisseau,
- car Oniros s'embarque pour redescendre le Fleuve
- vers son père Hypnos sur la Voie de l'Oubli`
- },
- {
- reference: 'Denis Gerfaud',
- extrait: `Hypnos engendre le fils dont il est la mère à l'heure du Serpent, car
- tel les serpents, Oniros commence à remonter le Fleuve
- sur le Voie du Souvenir vers son père Narcos`
- },
- {
- reference: 'Denis Gerfaud',
- extrait: `Ainsi se cuccèdent les Jours et les Ages. Les jours des Dragons sont les Ages des Hommes`
- },
- {
- reference: 'Denis Gerfaud',
- extrait: `Ainsi parlent les sages:
- «Les Dragons sont créateurs de leurs rêves, mais ils ne sont pas créateurs d'Oniros
- Les Dragons ne sont pas les maîtres de leurs rêvezs, car ils ne sont pas maîtres d'Oniros.
- Nul ne sait qui est le créateur des Dragons, ni qui est leur maître.
- Mais l'on peut supposer qui est le maître du Rêve des Dragons, c'est Oniros»`
- },
-]
-
/* -------------------------------------------- */
const caseSpecificModes = ["attache", "trounoir", "debordement", "reserve_extensible", "maitrisee"];
@@ -309,9 +247,36 @@ export const tmrConstants = {
cellw: 55,
cellh: 55,
gridx: 28,
- gridy: 28
+ gridy: 28,
+ // tailles
+ third: 18,
+ half: 27.5,
+ twoThird: 36,
+ full: 55,
+ // decallages
+ center: { x: 0, y: 0 },
+ top: { x: 0, y: -11.5 },
+ topLeft: { x: -11.5, y: -11.5 },
+ left: { x: -11.5, y: 0 },
+ bottomLeft: { x: -11.5, y: 11.5 },
+ bottom: { x: 0, y: 11.5 },
+ bottomRight: { x: 11.5, y: 11.5 },
+ right: { x: 11.5, y: 0 },
+ topRight: { x: 11.5, y: -11.5 },
}
+// couleurs
+export const tmrColors = {
+ sort: 0xFF8800,
+ tetes: 0xA000FF,
+ souffle: 0x804040,
+ trounoir: 0x401060,
+ demireve: 0x00FFEE,
+ rencontre: 0xFF0000,
+ casehumide: 0x1050F0,
+}
+
+
/* -------------------------------------------- */
/* -------------------------------------------- */
@@ -355,6 +320,9 @@ export class TMRUtility {
static getTMR(coordTMR) {
return TMRMapping[coordTMR];
}
+ static isCaseHumide(tmr) {
+ return tmr.type == 'fleuve' || tmr.type == 'lac' || tmr.type == 'marais';
+ }
/* -------------------------------------------- */
/** Some debug functions */
@@ -416,12 +384,16 @@ export class TMRUtility {
return TMRType[terrain].list;
}
- static getListCoordTMR(terrain) {
- return this.getListTMR(terrain).map(it => it.coord);
+ static filterTMR(filter) {
+ return Object.values(TMRMapping).filter(filter);
}
- static getTMRAleatoire(terrain = undefined) {
- let list = terrain ? TMRUtility.getListTMR(terrain) : Object.values(TMRMapping);
+ static filterTMRCoord(filter) {
+ return TMRUtility.filterTMR(filter).map(it => it.coord);
+ }
+
+ static getTMRAleatoire(filter = undefined) {
+ let list = TMRUtility.filterTMR(filter);
let index = new Roll("1d" + list.length).evaluate().total - 1;
return list[index];
}
diff --git a/module/tmr/carte-tmr.js b/module/tmr/carte-tmr.js
new file mode 100644
index 00000000..7b2d10d9
--- /dev/null
+++ b/module/tmr/carte-tmr.js
@@ -0,0 +1,20 @@
+import { Draconique } from "./draconique.js";
+
+export class CarteTmr extends Draconique {
+
+ constructor() {
+ super();
+ }
+
+ type() { return '' }
+ match(item) { return false; }
+ manualMessage() { return false }
+ async onActorCreateOwned(actor, item) { }
+
+ code() { return 'tmr' }
+ img() { return 'systems/foundryvtt-reve-de-dragon/styles/img/ui/tmp_main_r1.webp' }
+
+ _createSprite(pixiTMR) {
+ return pixiTMR.carteTmr(this.code());
+ }
+}
diff --git a/module/tmr/debordement.js b/module/tmr/debordement.js
new file mode 100644
index 00000000..c4c257b5
--- /dev/null
+++ b/module/tmr/debordement.js
@@ -0,0 +1,31 @@
+import { tmrColors, tmrConstants, TMRUtility } from "../tmr-utility.js";
+import { Draconique } from "./draconique.js";
+
+export class Debordement extends Draconique {
+
+ constructor() {
+ super();
+ }
+
+ type() { return 'souffle' }
+ match(item) { return Draconique.isSouffleDragon(item) && item.name.toLowerCase().includes('trou noir'); }
+ manualMessage() { return false }
+ async onActorCreateOwned(actor, item) { await this._creerCaseTmr(actor); }
+
+ code() { return 'debordement' }
+ tooltip(linkData) { return `Débordement en ${TMRUtility.getTMR(linkData.data.coord).label}` }
+ img() { return 'systems/foundryvtt-reve-de-dragon/icons/svg/wave.svg' }
+
+ _createSprite(pixiTMR) {
+ return pixiTMR.sprite(this.code(),
+ {
+ color: tmrColors.casehumide, alpha: 0.5, taille: tmrConstants.twoThird, decallage: tmrConstants.bottom
+ });
+ }
+
+ async _creerCaseTmr(actor) {
+ const existants = actor.data.items.filter(it => this.isCase(it)).map(it => it.data.coord);
+ const tmr = TMRUtility.getTMRAleatoire(it => !(TMRUtility.isCaseHumide(it) || existants.includes(it.coord)));
+ await this.createCaseTmr(actor, 'Debordement: ' + tmr.label, tmr);
+ }
+}
diff --git a/module/tmr/demi-reve.js b/module/tmr/demi-reve.js
new file mode 100644
index 00000000..e84c7d88
--- /dev/null
+++ b/module/tmr/demi-reve.js
@@ -0,0 +1,27 @@
+import { tmrColors, tmrConstants } from "../tmr-utility.js";
+import { Draconique } from "./draconique.js";
+
+export class DemiReve extends Draconique {
+
+ constructor() {
+ super();
+ }
+
+ type() { return '' }
+ match(item) { return false; }
+ manualMessage() { return false }
+ async onActorCreateOwned(actor, item) { }
+
+ code() { return 'demi-reve' }
+ tooltip(linkData) { return `Demi-rêve` }
+ img() { return 'icons/svg/sun.svg' }
+
+ _createSprite(pixiTMR) {
+ const sprite = pixiTMR.sprite(this.code(), {
+ color: tmrColors.demireve,
+ taille: (tmrConstants.full * 0.7)
+ });
+ pixiTMR.animate(pixiApp => pixiApp.ticker.add((delta) => sprite.rotation -= 0.01 * delta));
+ return sprite;
+ }
+}
diff --git a/module/tmr/draconique.js b/module/tmr/draconique.js
new file mode 100644
index 00000000..0dacd3c3
--- /dev/null
+++ b/module/tmr/draconique.js
@@ -0,0 +1,118 @@
+import { PixiTMR } from "./pixi-tmr.js";
+
+const registeredEffects = [
+]
+
+/**
+ * Définition des informations d'une "draconique" (queue, ombre, tête, souffle) qui influence les TMR
+ */
+export class Draconique
+{
+ static isCaseTMR(element) { return element.type == 'casetmr'; }
+ static isQueueDragon(element) { return element.type == 'queue' || element.type == 'ombre'; }
+ static isSouffleDragon(element) { return element.type == 'souffle'; }
+ static isTeteDragon(element) { return element.type == 'tete'; }
+ static isQueueSouffle(it) { return Draconique.isQueueDragon(it) || Draconique.isSouffleDragon(it); }
+
+ static register(draconique) {
+ registeredEffects[draconique.code()] = draconique;
+ if (draconique.img()) {
+ PixiTMR.register(draconique.code(), draconique.img())
+ }
+ return draconique;
+ }
+
+ static all() {
+ return Object.values(registeredEffects);
+ }
+ static get(code) {
+ return registeredEffects[code];
+ }
+
+ /**
+ * @param item un Item quelconque
+ * @returns true si l'item correspond
+ */
+ match(item) {
+ return Draconique.isQueueDragon(item) || Draconique.isSouffleDragon(item) || Draconique.isTeteDragon(item);
+ }
+
+ /**
+ * @returns un message à afficher si la draconique doit être gérée manuellement.
+ */
+ manualMessage() {
+ return false;
+ }
+
+ /**
+ * Méthode responsable de gérer une draconique (par exemple, ajouter des casetmr pour la fermeture des cités).
+ * @param actor auquel la draconique est ajoutée
+ */
+ async onActorCreateOwned(actor) {
+ return false;
+ }
+ async onActorDeleteOwned(actor) {
+ return false;
+ }
+ /**
+ * @return le code interne utilisé pour les casetmr correpondant
+ */
+ code() { return undefined }
+
+ /**
+ * @param {*} linkData données associées au token pixi (une casetmr, un sort en réserve, une rencontre en attente)
+ * @returns un tooltip à afficher au dessus du token
+ */
+ tooltip(linkData) { return undefined }
+
+ /**
+ * @param {*} img l'url du fichier image à utiliser pour le token. Si indéfini (et si createSprite n'est pas surchargé),
+ * un disque est utilisé.
+ */
+ img() { return undefined }
+
+ /**
+ * factory d'élément graphique PIXI correpsondant à l'objet draconique
+ * @param {*} pixiTMR instance de PixiTMR qui gère les tooltips, les méthodes de création de sprite standard, les clicks.
+ */
+ token(pixiTMR, linkData, coordTMR, type = undefined) {
+ const token = {
+ sprite: this._createSprite(pixiTMR),
+ coordTMR: coordTMR
+ };
+ token[type ?? this.code()] = linkData;
+ pixiTMR.addTooltip(token.sprite, this.tooltip(linkData));
+ return token;
+
+ return sprite;
+ }
+ /**
+ * factory d'élément graphique PIXI correpsondant à l'objet draconique
+ * @param {*} pixiTMR instance de PixiTMR qui gère les tooltips, les méthodes de création de sprite standard, les clicks.
+ */
+ _createSprite(pixiTMR) {
+ if (this.img()) {
+ return pixiTMR.sprite(this.code());
+ }
+ else{
+ return pixiTMR.circle()
+ }
+ }
+
+ /**
+ *
+ * @param {*} it un item à tester
+ * @param {*} coord les coordonnées d'une case. Si undefined toute case du type correspondra,
+ */
+ isCase(it, coord = undefined) {
+ return Draconique.isCaseTMR(it) && it.data.specific == this.code() && (coord ? it.data.coord == coord : true);
+ }
+
+ async createCaseTmr(actor, label, tmr) {
+ await actor.createOwnedItem({
+ name: label, type: 'casetmr', img: this.img(), _id: randomID(16),
+ data: { coord: tmr.coord, specific: this.code() }
+ });
+ }
+
+}
\ No newline at end of file
diff --git a/module/tmr/effets-draconiques.js b/module/tmr/effets-draconiques.js
new file mode 100644
index 00000000..cec1edb3
--- /dev/null
+++ b/module/tmr/effets-draconiques.js
@@ -0,0 +1,152 @@
+import { Debordement } from "./debordement.js";
+import { FermetureCites } from "./fermeture-cites.js";
+import { QueteEaux } from "./quete-eaux.js";
+import { TerreAttache } from "./terre-attache.js";
+import { ReserveExtensible } from "./reserve-extensible.js";
+import { DemiReve } from "./demi-reve.js";
+import { TrouNoir } from "./trou-noir.js";
+import { Rencontre } from "./rencontre.js";
+import { SortReserve } from "./sort-reserve.js";
+import { CarteTmr } from "./carte-tmr.js";
+import { PontImpraticable } from "./pont-impraticable.js";
+import { Draconique } from "./draconique.js";
+import { PresentCites } from "./present-cites.js";
+
+
+
+export class EffetsDraconiques {
+ static carteTmr = new CarteTmr();
+ static demiReve = new DemiReve();
+ static rencontre = new Rencontre();
+ static sortReserve = new SortReserve();
+ static debordement = new Debordement();
+ static presentCites = new PresentCites();
+ static fermetureCites = new FermetureCites();
+ static queteEaux = new QueteEaux();
+ static reserveExtensible = new ReserveExtensible();
+ static terreAttache = new TerreAttache();
+ static trouNoir = new TrouNoir();
+ static pontImpraticable = new PontImpraticable();
+
+ static init() {
+ Draconique.register(EffetsDraconiques.carteTmr);
+ Draconique.register(EffetsDraconiques.demiReve);
+ Draconique.register(EffetsDraconiques.rencontre);
+ Draconique.register(EffetsDraconiques.sortReserve);
+ Draconique.register(EffetsDraconiques.debordement);
+ Draconique.register(EffetsDraconiques.fermetureCites);
+ Draconique.register(EffetsDraconiques.queteEaux);
+ Draconique.register(EffetsDraconiques.reserveExtensible);
+ Draconique.register(EffetsDraconiques.terreAttache);
+ Draconique.register(EffetsDraconiques.trouNoir);
+ Draconique.register(EffetsDraconiques.pontImpraticable);
+ Draconique.register(EffetsDraconiques.presentCites);
+ }
+
+ /* -------------------------------------------- */
+ static isCaseInondee(caseTMR, coord) {
+ return EffetsDraconiques.debordement.isCase(caseTMR, coord) || EffetsDraconiques.pontImpraticable.isCase(caseTMR, coord);
+ }
+
+ static isCaseTrouNoir(caseTMR, coord) {
+ return EffetsDraconiques.trouNoir.isCase(caseTMR, coord);
+ }
+
+ static isReserveExtensible(caseTMR, coord) {
+ return EffetsDraconiques.reserveExtensible.isCase(caseTMR, coord);
+ }
+
+ static isTerreAttache(caseTMR, coord) {
+ return EffetsDraconiques.terreAttache.isCase(caseTMR, coord);
+ }
+
+ static isCiteFermee(caseTMR, coord) {
+ return EffetsDraconiques.fermetureCites.isCase(caseTMR, coord);
+ }
+
+ static isPresentCite(caseTMR, coord) {
+ return EffetsDraconiques.presentCites.isCase(caseTMR, coord);
+ }
+ /* -------------------------------------------- */
+ static isMauvaiseRencontre(element) {
+ return EffetsDraconiques.isMatching(element, it => Draconique.isQueueSouffle(it) && it.name.toLowerCase().includes('mauvaise rencontre'));
+ }
+
+ static isMonteeLaborieuse(element) {
+ return EffetsDraconiques.isMatching(element, it => Draconique.isQueueSouffle(it) && it.name.toLowerCase().includes('montée laborieuse'));
+ }
+
+ static isFermetureCite(element) {
+ /* -------------------------------------------- */
+ return EffetsDraconiques.isMatching(element, it => EffetsDraconiques.fermetureCites.match(it));
+ }
+
+ static isPontImpraticable(element) {
+ return EffetsDraconiques.isMatching(element, it => EffetsDraconiques.pontImpraticable.match(it));
+ }
+
+ static isDoubleResistanceFleuve(element) {
+ return EffetsDraconiques.isMatching(element, it => Draconique.isSouffleDragon(it) && it.name.toLowerCase().includes('résistance du fleuve'));
+ }
+
+ static isPeage(element) {
+ return EffetsDraconiques.isMatching(element, it => Draconique.isSouffleDragon(it) && it.name.toLowerCase().includes('péage'));
+ }
+
+ static isPeriple(element) {
+ // TODO
+ return EffetsDraconiques.isMatching(element, it => Draconique.isSouffleDragon(it) && ir.name.toLowerCase() == 'périple');
+ }
+
+ static isDesorientation(element) {
+ // TODO
+ return EffetsDraconiques.isMatching(element, it => Draconique.isSouffleDragon(it) && it.name.toLowerCase() == 'désorientation');
+ }
+
+ /* -------------------------------------------- */
+ static isConquete(element) {
+ // TODO
+ return EffetsDraconiques.isMatching(element, it => Draconique.isQueueDragon(it) && it.name.toLowerCase() == 'conquête');
+ }
+
+ static isPelerinage(element) {
+ return EffetsDraconiques.isMatching(element, it => Draconique.isQueueDragon(it) && it.name.toLowerCase() == 'pélerinage');
+ }
+
+ static countInertieDraconique(element) {
+ return EffetsDraconiques.count(element, it => Draconique.isQueueDragon(it) && it.name.toLowerCase().includes('inertie draconique'));
+ }
+
+ static isUrgenceDraconique(element) {
+ return EffetsDraconiques.isMatching(element, it => Draconique.isQueueDragon(it) && it.name.toLowerCase() == 'urgence draconique');
+ }
+
+ /* -------------------------------------------- */
+ static isDonDoubleReve(element) {
+ return EffetsDraconiques.isMatching(element, it => Draconique.isTeteDragon(it) && it.name == 'Don de double-rêve');
+ }
+
+ static isConnaissanceFleuve(element) {
+ return EffetsDraconiques.isMatching(element, it => Draconique.isTeteDragon(it) && it.name.toLowerCase().includes('connaissance du fleuve'));
+ }
+
+ static isReserveEnSecurite(element) {
+ return EffetsDraconiques.isMatching(element, it => Draconique.isTeteDragon(it) && it.name.toLowerCase().includes(' en sécurité'));
+ }
+
+ static isDeplacementAccelere(element) {
+ return EffetsDraconiques.isMatching(element, it => Draconique.isTeteDragon(it) && it.name.toLowerCase().includes(' déplacement accéléré'));
+ }
+
+ static isMatching(element, matcher) {
+ return EffetsDraconiques.toItems(element).find(matcher);
+ }
+ static count(element, matcher) {
+ return EffetsDraconiques.toItems(element).filter(matcher).length;
+ }
+
+ static toItems(element) {
+ return (element?.entity === 'Actor') ? element.data.items : (element?.entity === 'Item') ? [element] : [];
+ }
+
+}
\ No newline at end of file
diff --git a/module/tmr/fermeture-cites.js b/module/tmr/fermeture-cites.js
new file mode 100644
index 00000000..dc95bc37
--- /dev/null
+++ b/module/tmr/fermeture-cites.js
@@ -0,0 +1,33 @@
+import { tmrColors, tmrConstants, TMRUtility } from "../tmr-utility.js";
+import { Draconique } from "./draconique.js";
+
+export class FermetureCites extends Draconique {
+
+ constructor() {
+ super();
+ }
+
+ type() { return 'souffle' }
+ match(item) { return Draconique.isSouffleDragon(item) && item.name.toLowerCase() == 'fermeture des cités'; }
+ manualMessage() { return false }
+ async onActorCreateOwned(actor, item) { await this._fermerLesCites(actor); }
+
+ code() { return 'fermeture' }
+ tooltip(linkData) { return `La ${TMRUtility.getTMR(linkData.data.coord).label} est fermée` }
+ img() { return 'icons/svg/door-closed.svg' }
+
+ _createSprite(pixiTMR) {
+ return pixiTMR.sprite(this.code(),
+ {
+ color: tmrColors.souffle, alpha: 0.9, taille: tmrConstants.full, decallage: { x: 2, y: 0 }
+ });
+ }
+
+ async _fermerLesCites(actor) {
+ let existants = actor.data.items.filter(it => this.isCase(it)).map(it => it.data.coord);
+ let ouvertes = TMRUtility.filterTMR(it => it.type == 'cite' && !existants.includes(it.coord));
+ for (let tmr of ouvertes) {
+ await this.createCaseTmr(actor, 'Fermeture: ' + tmr.label, tmr);
+ }
+ }
+}
diff --git a/module/tmr/pixi-tmr.js b/module/tmr/pixi-tmr.js
new file mode 100644
index 00000000..40cdbe67
--- /dev/null
+++ b/module/tmr/pixi-tmr.js
@@ -0,0 +1,148 @@
+import { tmrConstants } from "../tmr-utility.js";
+
+const tooltipStyle = new PIXI.TextStyle({
+ fontFamily: 'CaslonAntique',
+ fontSize: 18,
+ fill: '#FFFFFF',
+ stroke: '#000000',
+ strokeThickness: 3
+});
+
+
+export class PixiTMR {
+
+ static textures = []
+
+ constructor(tmrObject, pixiApp) {
+ this.tmrObject = tmrObject;
+ this.pixiApp = pixiApp ?? tmrObject.pixiApp;
+ this.callbacksOnAnimate = [];
+ }
+
+ load(onLoad = (loader, resources) => {}) {
+ let loader = this.pixiApp.loader;
+ for (const [name, img] of Object.entries(PixiTMR.textures)) {
+ loader = loader.add(name, img);
+ }
+ loader.load((loader, resources) => {
+ onLoad(loader, resources);
+ for (let onAnimate of this.callbacksOnAnimate) {
+ onAnimate();
+ }
+ });
+ }
+
+ static register(name, img) {
+ PixiTMR.textures[name] = img;
+ }
+
+ animate(animation = pixiApp=>{})
+ {
+ this.callbacksOnAnimate.push(() => animation(this.pixiApp));
+ }
+
+ carteTmr(code) {
+ const carteTmr = new PIXI.Sprite(PIXI.utils.TextureCache[code]);
+ // Setup the position of the TMR
+ carteTmr.x = 0;
+ carteTmr.y = 0;
+ carteTmr.width = 720;
+ carteTmr.height = 860;
+ // Rotate around the center
+ carteTmr.anchor.set(0);
+ carteTmr.interactive = true;
+ carteTmr.buttonMode = true;
+ carteTmr.tmrObject = this;
+ // atténue les couleurs des TMRs
+ const tmrColorFilter = new PIXI.filters.ColorMatrixFilter();
+ tmrColorFilter.contrast(1);
+ tmrColorFilter.brightness(0.2);
+ tmrColorFilter.saturate(-0.5);
+ carteTmr.filters = [tmrColorFilter];
+ if (!this.tmrObject.viewOnly) {
+ carteTmr.on('pointerdown', event => this.onClickBackground(event));
+ }
+ this.pixiApp.stage.addChild(carteTmr);
+ return carteTmr;
+ }
+
+ sprite(code, options = {}) {
+ const texture = PIXI.utils.TextureCache[code];
+ if (!texture) {
+ console.error("Texture manquante", code)
+ return;
+ }
+ let sprite = new PIXI.Sprite(texture);
+ sprite.width = options.taille ?? tmrConstants.half;
+ sprite.height = options.taille ?? tmrConstants.half;
+ sprite.anchor.set(0.5);
+ sprite.tint = options.color ?? 0x000000;
+ sprite.alpha = options.alpha ?? 0.75;
+ sprite.decallage = options.decallage ?? tmrConstants.center;
+ this.pixiApp.stage.addChild(sprite);
+ return sprite;
+ }
+
+ circle(name, options = {}) {
+ let sprite = new PIXI.Graphics();
+ sprite.beginFill(options.color, options.opacity);
+ sprite.drawCircle(0, 0, (options.taille ?? 12) / 2);
+ sprite.endFill();
+ sprite.decallage = options.decallage ?? tmrConstants.topLeft;
+ this.pixiApp.stage.addChild(sprite);
+ return sprite;
+ }
+
+ addTooltip(sprite, text) {
+ if (text) {
+ sprite.tooltip = new PIXI.Text(text, tooltipStyle);
+ sprite.isOver = false;
+ sprite.interactive = true;
+ sprite.on('pointerdown', event => this.onClickBackground(event))
+ .on('pointerover', () => this.onShowTooltip(sprite))
+ .on('pointerout', () => this.onHideTooltip(sprite));
+ }
+ }
+
+
+ onClickBackground(event) {
+ this.tmrObject.onClickTMR(event)
+ }
+
+ onShowTooltip(sprite) {
+ if (sprite.tooltip) {
+
+ if (!sprite.isOver) {
+ sprite.tooltip.x = sprite.x;
+ sprite.tooltip.y = sprite.y;
+ this.pixiApp.stage.addChild(sprite.tooltip);
+ }
+ sprite.isOver = true;
+ }
+ }
+
+ onHideTooltip(sprite) {
+ if (sprite.tooltip) {
+ if (sprite.isOver) {
+ this.pixiApp.stage.removeChild(sprite.tooltip);
+ }
+ sprite.isOver = false;
+ }
+ }
+
+ setPosition( sprite, pos) {
+ let decallagePairImpair = (pos.x % 2 == 0) ? tmrConstants.col1_y : tmrConstants.col2_y;
+ let dx = (sprite.decallage == undefined) ? 0 : sprite.decallage.x;
+ let dy = (sprite.decallage == undefined) ? 0 : sprite.decallage.y;
+ sprite.x = tmrConstants.gridx + (pos.x * tmrConstants.cellw) + dx;
+ sprite.y = tmrConstants.gridy + (pos.y * tmrConstants.cellh) + dy + decallagePairImpair;
+ }
+
+ getCaseRectangle(pos) {
+ let decallagePairImpair = (pos.x % 2 == 0) ? tmrConstants.col1_y : tmrConstants.col2_y;
+ let x = tmrConstants.gridx + (pos.x * tmrConstants.cellw) - (tmrConstants.cellw / 2);
+ let y = tmrConstants.gridy + (pos.y * tmrConstants.cellh) - (tmrConstants.cellh / 2) + decallagePairImpair;
+ return { x: x, y: y, w: tmrConstants.cellw, h: tmrConstants.cellh };
+ }
+
+}
\ No newline at end of file
diff --git a/module/tmr/pont-impraticable.js b/module/tmr/pont-impraticable.js
new file mode 100644
index 00000000..f9251742
--- /dev/null
+++ b/module/tmr/pont-impraticable.js
@@ -0,0 +1,40 @@
+import { tmrColors, tmrConstants, TMRUtility } from "../tmr-utility.js";
+import { Draconique } from "./draconique.js";
+
+export class PontImpraticable extends Draconique {
+
+ constructor() {
+ super();
+ }
+
+ type() { return 'souffle' }
+ match(item) { return Draconique.isSouffleDragon(item) && item.name.toLowerCase().includes('impraticabilité des ponts'); }
+
+ async onActorCreateOwned(actor, item) { await this._creerCaseTmr(actor); }
+ async onActorDeleteOwned(actor, item) { await this._supprimerCaseTmr(actor); }
+
+ code() { return 'pont-impraticable' }
+ tooltip(linkData) { return `${TMRUtility.getTMR(linkData.data.coord).label} impraticable` }
+ img() { return 'systems/foundryvtt-reve-de-dragon/icons/svg/wave.svg' }
+
+ _createSprite(pixiTMR) {
+ return pixiTMR.sprite(this.code(),
+ {
+ color: tmrColors.casehumide, alpha: 0.5, taille: tmrConstants.twoThird, decallage: tmrConstants.bottom
+ });
+ }
+
+ async _creerCaseTmr(actor) {
+ const ponts = TMRUtility.getListTMR('pont');
+ for (let tmr of ponts) {
+ await this.createCaseTmr(actor, 'Pont impraticable: ' + tmr.label, tmr);
+ }
+ }
+
+ async _supprimerCaseTmr(actor) {
+ const existants = actor.data.items.filter(it => this.isCase(it));
+ for (let caseTMR of existants) {
+ await actor.deleteOwnedItem(caseTMR._id);
+ }
+ }
+}
diff --git a/module/tmr/present-cites.js b/module/tmr/present-cites.js
new file mode 100644
index 00000000..105f7bba
--- /dev/null
+++ b/module/tmr/present-cites.js
@@ -0,0 +1,42 @@
+import { ChatUtility } from "../chat-utility.js";
+import { tmrColors, tmrConstants, TMRUtility } from "../tmr-utility.js";
+import { Draconique } from "./draconique.js";
+
+export class PresentCites extends Draconique {
+
+ constructor() {
+ super();
+ }
+
+ type() { return 'tete' }
+ match(item) { return Draconique.isTeteDragon(item) && item.name.toLowerCase() == 'présent des cités'; }
+ manualMessage() { return false }
+ async onActorCreateOwned(actor, item) { await this._ajouterPresents(actor); }
+
+ code() { return 'present-cites' }
+ tooltip(linkData) { return `La ${TMRUtility.getTMR(linkData.data.coord).label} a un présent` }
+ img() { return 'systems/foundryvtt-reve-de-dragon/icons/svg/gift.svg' }
+
+ _createSprite(pixiTMR) {
+ return pixiTMR.sprite(this.code(),
+ {
+ color: tmrColors.tetes, alpha: 0.7, taille: tmrConstants.third, decallage:tmrConstants.topRight
+ });
+ }
+
+ async _ajouterPresents(actor) {
+ let existants = actor.data.items.filter(it => this.isCase(it)).map(it => it.data.coord);
+ if (existants.length >0 ) {
+ ChatMessage.create({
+ whisper: ChatUtility.getWhisperRecipientsAndGMs(game.user),
+ content: "Vous avez encore des présents dans des cités, vous devrez tirer une autre tête pour remplacer celle ci!"
+ })
+ }
+ else{
+ let cites = TMRUtility.filterTMR(it => it.type == 'cite');
+ for (let tmr of cites) {
+ await this.createCaseTmr(actor, 'Présent: ' + tmr.label, tmr);
+ }
+ }
+ }
+}
diff --git a/module/tmr/quete-eaux.js b/module/tmr/quete-eaux.js
new file mode 100644
index 00000000..481502c9
--- /dev/null
+++ b/module/tmr/quete-eaux.js
@@ -0,0 +1,25 @@
+import { tmrColors, tmrConstants, TMRUtility } from "../tmr-utility.js";
+import { Draconique } from "./draconique.js";
+
+export class QueteEaux extends Draconique {
+ constructor() {
+ super();
+ }
+
+ type() { return 'tete' }
+ match(item) { return Draconique.isTeteDragon(item) && item.name.toLowerCase().includes("quête des eaux"); }
+ manualMessage() { return "Vous devrez re-configurer votre Quête des Eaux une fois un lac ou marais vaincu" }
+ async onActorCreateOwned(actor, item) { await this._creerCaseTmr(actor); }
+
+ code() { return 'maitrisee' }
+ tooltip(linkData) { return `Quête des eaux, le ${TMRUtility.getTMR(linkData.data.coord).label} est maîtrisé` }
+ img() { return 'icons/svg/bridge.svg' }
+
+ _createSprite(pixiTMR) {
+ return pixiTMR.sprite(this.code(), { color: tmrColors.tetes, decallage: tmrConstants.topRight });
+ }
+
+ async _creerCaseTmr(actor) {
+ await this.createCaseTmr(actor, "Quête des eaux à déterminer", {coord:'A0'});
+ }
+}
diff --git a/module/tmr/rencontre.js b/module/tmr/rencontre.js
new file mode 100644
index 00000000..3c6c4ab8
--- /dev/null
+++ b/module/tmr/rencontre.js
@@ -0,0 +1,22 @@
+import { tmrColors, tmrConstants } from "../tmr-utility.js";
+import { Draconique } from "./draconique.js";
+
+export class Rencontre extends Draconique {
+
+ constructor() {
+ super();
+ }
+
+ type() { return '' }
+ match(item) { return false; }
+ manualMessage() { return false }
+ async onActorCreateOwned(actor, item) { }
+
+ code() { return 'rencontre' }
+ tooltip(linkData) { return `${linkData.name} de force ${linkData.force}` }
+ img() { return 'systems/foundryvtt-reve-de-dragon/icons/heures/hd06.svg' }
+
+ _createSprite(pixiTMR) {
+ return pixiTMR.sprite(this.code(), { color: tmrColors.rencontre, taille: tmrConstants.full, decallage: { x: 2, y: 2 } });
+ }
+}
diff --git a/module/tmr/reserve-extensible.js b/module/tmr/reserve-extensible.js
new file mode 100644
index 00000000..7fbce50f
--- /dev/null
+++ b/module/tmr/reserve-extensible.js
@@ -0,0 +1,28 @@
+import { tmrColors, tmrConstants, TMRUtility } from "../tmr-utility.js";
+import { Draconique } from "./draconique.js";
+
+export class ReserveExtensible extends Draconique {
+ constructor() {
+ super();
+ }
+
+ type() { return 'tete' }
+ match(item) { return Draconique.isTeteDragon(item) && item.name.toLowerCase().includes("réserve extensible"); }
+ manualMessage() { return "Vous pouvez re-configurer votre Réserve extensible" }
+ async onActorCreateOwned(actor, item) { await this._creerCaseTmr(actor); }
+
+ code() { return 'reserve_extensible' }
+ tooltip(linkData) { return `Réserve extensible en ${TMRUtility.getTMR(linkData.data.coord).label} !` }
+ img() { return 'icons/svg/chest.svg' }
+
+ _createSprite(pixiTMR) {
+ return pixiTMR.sprite(this.code(), { color: tmrColors.tetes, decallage: tmrConstants.left});
+ }
+
+ async _creerCaseTmr(actor) {
+ const existants = actor.data.items.filter(it => this.isCase(it)).map(it => it.data.coord);
+ const tmr = TMRUtility.getTMRAleatoire(it => !(it.type == 'fleuve' || existants.includes(it.coord)));
+ await this.createCaseTmr(actor, "Nouvelle Réserve extensible", tmr);
+ }
+
+}
diff --git a/module/tmr/sort-reserve.js b/module/tmr/sort-reserve.js
new file mode 100644
index 00000000..4b1ee48b
--- /dev/null
+++ b/module/tmr/sort-reserve.js
@@ -0,0 +1,22 @@
+import { tmrColors, tmrConstants } from "../tmr-utility.js";
+import { Draconique } from "./draconique.js";
+
+export class SortReserve extends Draconique {
+
+ constructor() {
+ super();
+ }
+
+ type() { return '' }
+ match(item) { return false; }
+ manualMessage() { return false }
+ async onActorCreateOwned(actor, item) { }
+
+ code() { return 'sort' }
+ tooltip(linkData) { return `${linkData.name}, r${linkData.data.ptreve_reel}` }
+ img() { return 'icons/svg/book.svg' }
+
+ _createSprite(pixiTMR) {
+ return pixiTMR.sprite(this.code(), { color: tmrColors.sort, decallage: tmrConstants.right });
+ }
+}
diff --git a/module/tmr/terre-attache.js b/module/tmr/terre-attache.js
new file mode 100644
index 00000000..34416fec
--- /dev/null
+++ b/module/tmr/terre-attache.js
@@ -0,0 +1,25 @@
+import { tmrColors, tmrConstants, TMRUtility } from "../tmr-utility.js";
+import { Draconique } from "./draconique.js";
+
+export class TerreAttache extends Draconique {
+ constructor() {
+ super();
+ }
+
+ type() { return 'tete' }
+ match(item) { return Draconique.isTeteDragon(item) && item.name.toLowerCase().includes("terre d'attache"); }
+ manualMessage() { return "Vous pouvez re-configurer votre Terre d'Attache" }
+ async onActorCreateOwned(actor, item) { await this._creerCaseTmr(actor); }
+
+ code() { return 'attache' }
+ tooltip(linkData) { return `Terre d'attache en ${TMRUtility.getTMR(linkData.data.coord).label} !` }
+ img() { return 'icons/svg/anchor.svg' }
+
+ _createSprite(pixiTMR) {
+ return pixiTMR.sprite(this.code(), { color: tmrColors.tetes, decallage: tmrConstants.topLeft });
+ }
+
+ async _creerCaseTmr(actor) {
+ await this.createCaseTmr(actor, "Terre d'attache à déterminer", {coord:'A0'});
+ }
+}
diff --git a/module/tmr/trou-noir.js b/module/tmr/trou-noir.js
new file mode 100644
index 00000000..02637da1
--- /dev/null
+++ b/module/tmr/trou-noir.js
@@ -0,0 +1,30 @@
+import { tmrColors, tmrConstants, TMRUtility } from "../tmr-utility.js";
+import { Draconique } from "./draconique.js";
+
+export class TrouNoir extends Draconique {
+ constructor() {
+ super();
+ }
+
+ type() { return 'souffle' }
+ match(item) { return Draconique.isSouffleDragon(item) && item.name.toLowerCase().includes('trou noir'); }
+ manualMessage() { return false }
+ async onActorCreateOwned(actor, item) { await this._creerCaseTmr(actor); }
+
+ code() { return 'trounoir' }
+ tooltip(linkData) { return `Trou noir en ${TMRUtility.getTMR(linkData.data.coord).label} !` }
+ img() { return 'icons/svg/explosion.svg' }
+
+ _createSprite(pixiTMR) {
+ return pixiTMR.sprite(this.code(),
+ {
+ color: tmrColors.trounoir, alpha: 1, taille: tmrConstants.full, decallage: { x: 2, y: 2 },
+ });
+ }
+
+ async _creerCaseTmr(actor) {
+ const existants = actor.data.items.filter(it => this.isCase(it)).map(it => it.data.coord);
+ const tmr = TMRUtility.getTMRAleatoire(it => !(TMRUtility.isCaseHumide(it) || existants.includes(it.coord)));
+ await this.createCaseTmr(actor, 'Trou noir: ' + tmr.label, tmr);
+ }
+}
diff --git a/packs/tetes-de-dragon-pour-haut-revants.db b/packs/tetes-de-dragon-pour-haut-revants.db
index 314ab02a..aeaa1281 100644
--- a/packs/tetes-de-dragon-pour-haut-revants.db
+++ b/packs/tetes-de-dragon-pour-haut-revants.db
@@ -1,10 +1,11 @@
-{"_id":"5JccZSafqCXYqrwU","name":"Connaissance du fleuve","permission":{"default":0,"jOzRscDxoXZWpGS6":3},"type":"tete","data":{"description":"
Cette tête permet de téléporter instantanément son demi-rêve de n’importe quelle case de fleuve à n’importe quelle autre. Il s’agit du fleuve seul, à l’exclusion des cases de lac et marais. La téléportation remplace le mouvement normal et coûte donc un point de fatigue. Comme après un mouvement normal, une rencontre doit être tirée dans la case de fleuve d’arrivée, puis cette dernière doit être maîtrisée selon la règle usuelle. Noter enfin que la téléportation n’est possible que si le demi-rêve est libre de son mouvement, c’est-à-dire s’il n’est sous l’emprise ni d’un Reflet, ni d’un Tourbillon. Unique.
","refoulement":null},"flags":{},"img":"systems/foundryvtt-reve-de-dragon/icons/tete_dragon.webp"}
-{"_id":"BT18LAdIqEgSG2Hh","name":"Réserve en sécurité","permission":{"default":0,"jOzRscDxoXZWpGS6":3},"type":"tete","data":{"description":"
Un haut-rêvant possédant cette tête peut pénétrer sur une case où il a un sort en réserve sans forcément le déclencher s’il ne le souhaite pas. Même chose si un Tourbillon l’abandonne sur une case de réserve. Déclencher un sort en réserve devient un acte volontaire. Même chose pour un échec total en réserve. Cette tête s’applique à toutes les cases. Unique.
","refoulement":null},"flags":{},"img":"systems/foundryvtt-reve-de-dragon/icons/tete_dragon.webp"}
-{"_id":"SFxPAvWpEGYHI8mO","name":"Connaissance intuitive d'un nouveau sort","permission":{"default":0,"jOzRscDxoXZWpGS6":3},"type":"tete","data":{"description":"
Le haut-rêvant bénéficiaire de cette tête se retrouve en possession d’un nouveau sort ou rituel, déterminé par un procédé aléatoire quelconque.
","refoulement":null},"flags":{},"img":"systems/foundryvtt-reve-de-dragon/icons/tete_dragon.webp"}
-{"_id":"VWOXA0q6GB7o8oxz","name":"Don de double-rêve","permission":{"default":0,"jOzRscDxoXZWpGS6":3},"type":"tete","data":{"description":"
Lors de la récupération des points de rêve, permet de jeter 1ddr toutes les demi-heures draconiques au lieu de toutes les heures. Unique.
Par cette tête, le haut-rêvant peut désigner une case des TMR de son choix à l’exception d’une case humide. Ensuite, à n’importe quel round de son périple en TMR, il peut s’y téléporter directement quel que soit le nombre de cases qui l’en sépare. Cette téléportation instantanée remplace son mouvement normal et coûte donc un point de fatigue. De même, tout comme après un mouvement normal, 1d7 de rencontre doit être tiré dès l’arrivée dans la terre d’attache. Enfin, le haut-rêvant ne peut s’y téléporter que s’il est libre de son mouvement, c’est-à-dire s’il n’est sous l’emprise ni d’un Reflet d’ancien rêve, ni d’un Tourbillon. Cumulable : on peut avoir plusieurs terres d’attache.
","refoulement":null},"flags":{},"img":"systems/foundryvtt-reve-de-dragon/icons/tete_dragon.webp"}
-{"_id":"ZVh8PLlAzAJulr37","name":"Don de déplacement accéléré","permission":{"default":0,"jOzRscDxoXZWpGS6":3},"type":"tete","data":{"description":"
Permet à tout moment l’option du déplacement accéléré dans les TMR sans avoir à dépenser un point de rêve supplémentaire. Unique.
","refoulement":null},"flags":{},"img":"systems/foundryvtt-reve-de-dragon/icons/tete_dragon.webp"}
-{"_id":"Zlt01O2sFrVR9pus","name":"Augmentation du seuil de rêve","permission":{"default":0,"jOzRscDxoXZWpGS6":3},"type":"tete","data":{"description":"
Permet d’augmenter le seuil de rêve de 2 points. Cumulable jusqu’à un maximum du double de la caractéristique Rêve.
","refoulement":null},"flags":{},"img":"systems/foundryvtt-reve-de-dragon/icons/tete_dragon.webp"}
-{"_id":"ZuTV36GyOhFgTlE7","name":"Présents de cités","permission":{"default":0,"jOzRscDxoXZWpGS6":3},"type":"tete","data":{"description":"
Par cette tête, chaque cité offre automatiquement soit une Fleur des rêves, soit un Passeur, soit un Messager, dès qu’elle est visitée par le demi-rêve du haut-rêvant. Il n’y a pas de d7 à tirer, le haut-rêvant choisit librement ce qu’il préfère, et la rencontre est considérée automatiquement maîtrisée, Fleurs, Messagers et Passeurs ayant une force de 2d6 points. Dès qu’elle a offert son présent, la cité redevient normale en ce qui concerne le tirage des rencontres; il n’y a qu’un seul présent par cité. La tête de Dragon prend fin dès que toutes les cités (22) ont été visitées; entretemps, la tête ne peut être ré-obtenue.
","refoulement":null},"flags":{},"img":"systems/foundryvtt-reve-de-dragon/icons/tete_dragon.webp"}
-{"_id":"a3Y5W0AX5EKxZRSL","name":"Quête des eaux","permission":{"default":0,"jOzRscDxoXZWpGS6":3},"type":"tete","data":{"description":"
Sous l’influence de cette tête, le haut-rêvant est invité à se rendre dans un lac ou dans un marais de son choix. Dès que cette case, lac ou marais, a été maîtrisée selon les règles usuelles, la victoire devient définitive, et la case n’aura plus jamais besoin d’être maîtrisée. Une fois le lac ou le marais choisi, le haut-rêvant ne peut en changer, mais a droit à un nombre illimité d’essais pour le maîtriser. Cumulable : on peut maîtriser définitivement plusieurs lacs ou marais.
Une (seule) case spécifique librement choisie par le haut-rêvant à l’exception d’une case de fleuve, n’est plus dorénavant limitée à un seul sort en réserve. Le haut-rêvant peut y en stocker autant qu’il le désire (dans les limites du nombre permis par ses niveaux). En conséquence, tant que ce maximum n’est pas atteint, le demi-rêve peut entrer dans la case concernée sans déclencher les sorts qui s’y trouvent. Le haut-rêvant peut se contenter d’y passer, ou rajouter un sort aux précédents. Dès que le maximum permis est atteint, le fait de passer par la case déclenche automatiquement l’un des sorts (au choix). Les sorts peuvent être déclenchés l’un après l’autre (rappelons que déclencher un sort fait obligatoirement redescendre le demi-rêve), ou déclenchés ensemble (tout ou partie d’entre eux) à la condition expresse qu’ils aient la même cible (même créature, objet, ou centre de zone). Cumulable : on peut avoir plusieurs cases de réserve extensible.
","refoulement":null},"flags":{},"img":"systems/foundryvtt-reve-de-dragon/icons/tete_dragon.webp"}
+{"_id":"5JccZSafqCXYqrwU","name":"Connaissance du fleuve","permission":{"default":0,"jOzRscDxoXZWpGS6":3},"type":"tete","data":{"description":"
Cette tête permet de téléporter instantanément son demi-rêve de n’importe quelle case de fleuve à n’importe quelle autre. Il s’agit du fleuve seul, à l’exclusion des cases de lac et marais. La téléportation remplace le mouvement normal et coûte donc un point de fatigue. Comme après un mouvement normal, une rencontre doit être tirée dans la case de fleuve d’arrivée, puis cette dernière doit être maîtrisée selon la règle usuelle. Noter enfin que la téléportation n’est possible que si le demi-rêve est libre de son mouvement, c’est-à-dire s’il n’est sous l’emprise ni d’un Reflet, ni d’un Tourbillon. Unique.
","refoulement":null},"flags":{},"img":"systems/foundryvtt-reve-de-dragon/icons/tete_dragon.webp","effects":[]}
+{"_id":"BT18LAdIqEgSG2Hh","name":"Réserve en sécurité","permission":{"default":0,"jOzRscDxoXZWpGS6":3},"type":"tete","data":{"description":"
Un haut-rêvant possédant cette tête peut pénétrer sur une case où il a un sort en réserve sans forcément le déclencher s’il ne le souhaite pas. Même chose si un Tourbillon l’abandonne sur une case de réserve. Déclencher un sort en réserve devient un acte volontaire. Même chose pour un échec total en réserve. Cette tête s’applique à toutes les cases. Unique.
","refoulement":null},"flags":{},"img":"systems/foundryvtt-reve-de-dragon/icons/tete_dragon.webp","effects":[]}
+{"name":"Présent des cités","permission":{"default":0,"Q2G6GTdrotKzYGUC":3},"type":"tete","data":{"description":"
Par cette tête, chaque cité offre automatiquement soit une Fleur des rêves, soit un Passeur, soit un Messager, dès qu’elle est visitée par le demi-rêve du haut-rêvant. Il n’y a pas de d7 à tirer, le haut-rêvant choisit librement ce qu’il préfère, et la rencontre est considérée automatiquement maîtrisée, Fleurs, Messagers et Passeurs ayant une force de 2d6 points. Dès qu’elle a offert son présent, la cité redevient normale en ce qui concerne le tirage des rencontres; il n’y a qu’un seul présent par cité. La tête de Dragon prend fin dès que toutes les cités (22) ont été visitées; entretemps, la tête ne peut être ré-obtenue.
"},"flags":{},"img":"systems/foundryvtt-reve-de-dragon/icons/tete_dragon.webp","effects":[],"_id":"E4a4O1IdrgbNGpVy"}
+{"_id":"SFxPAvWpEGYHI8mO","name":"Connaissance intuitive d'un nouveau sort","permission":{"default":0,"jOzRscDxoXZWpGS6":3},"type":"tete","data":{"description":"
Le haut-rêvant bénéficiaire de cette tête se retrouve en possession d’un nouveau sort ou rituel, déterminé par un procédé aléatoire quelconque.
","refoulement":null},"flags":{},"img":"systems/foundryvtt-reve-de-dragon/icons/tete_dragon.webp","effects":[]}
+{"_id":"VWOXA0q6GB7o8oxz","name":"Don de double-rêve","permission":{"default":0,"jOzRscDxoXZWpGS6":3},"type":"tete","data":{"description":"
Lors de la récupération des points de rêve, permet de jeter 1ddr toutes les demi-heures draconiques au lieu de toutes les heures. Unique.
Par cette tête, le haut-rêvant peut désigner une case des TMR de son choix à l’exception d’une case humide. Ensuite, à n’importe quel round de son périple en TMR, il peut s’y téléporter directement quel que soit le nombre de cases qui l’en sépare. Cette téléportation instantanée remplace son mouvement normal et coûte donc un point de fatigue. De même, tout comme après un mouvement normal, 1d7 de rencontre doit être tiré dès l’arrivée dans la terre d’attache. Enfin, le haut-rêvant ne peut s’y téléporter que s’il est libre de son mouvement, c’est-à-dire s’il n’est sous l’emprise ni d’un Reflet d’ancien rêve, ni d’un Tourbillon. Cumulable : on peut avoir plusieurs terres d’attache.
","refoulement":null},"flags":{},"img":"systems/foundryvtt-reve-de-dragon/icons/tete_dragon.webp","effects":[]}
+{"_id":"ZVh8PLlAzAJulr37","name":"Don de déplacement accéléré","permission":{"default":0,"jOzRscDxoXZWpGS6":3},"type":"tete","data":{"description":"
Permet à tout moment l’option du déplacement accéléré dans les TMR sans avoir à dépenser un point de rêve supplémentaire. Unique.
","refoulement":null},"flags":{},"img":"systems/foundryvtt-reve-de-dragon/icons/tete_dragon.webp","effects":[]}
+{"_id":"Zlt01O2sFrVR9pus","name":"Augmentation du seuil de rêve","permission":{"default":0,"jOzRscDxoXZWpGS6":3},"type":"tete","data":{"description":"
Permet d’augmenter le seuil de rêve de 2 points. Cumulable jusqu’à un maximum du double de la caractéristique Rêve.
","refoulement":null},"flags":{},"img":"systems/foundryvtt-reve-de-dragon/icons/tete_dragon.webp","effects":[]}
+{"_id":"ZuTV36GyOhFgTlE7","name":"Présents de cités","permission":{"default":0,"jOzRscDxoXZWpGS6":3},"type":"tete","data":{"description":"
Par cette tête, chaque cité offre automatiquement soit une Fleur des rêves, soit un Passeur, soit un Messager, dès qu’elle est visitée par le demi-rêve du haut-rêvant. Il n’y a pas de d7 à tirer, le haut-rêvant choisit librement ce qu’il préfère, et la rencontre est considérée automatiquement maîtrisée, Fleurs, Messagers et Passeurs ayant une force de 2d6 points. Dès qu’elle a offert son présent, la cité redevient normale en ce qui concerne le tirage des rencontres; il n’y a qu’un seul présent par cité. La tête de Dragon prend fin dès que toutes les cités (22) ont été visitées; entretemps, la tête ne peut être ré-obtenue.
","refoulement":null},"flags":{},"img":"systems/foundryvtt-reve-de-dragon/icons/tete_dragon.webp","effects":[]}
+{"_id":"a3Y5W0AX5EKxZRSL","name":"Quête des eaux","permission":{"default":0,"jOzRscDxoXZWpGS6":3},"type":"tete","data":{"description":"
Sous l’influence de cette tête, le haut-rêvant est invité à se rendre dans un lac ou dans un marais de son choix. Dès que cette case, lac ou marais, a été maîtrisée selon les règles usuelles, la victoire devient définitive, et la case n’aura plus jamais besoin d’être maîtrisée. Une fois le lac ou le marais choisi, le haut-rêvant ne peut en changer, mais a droit à un nombre illimité d’essais pour le maîtriser. Cumulable : on peut maîtriser définitivement plusieurs lacs ou marais.
Une (seule) case spécifique librement choisie par le haut-rêvant à l’exception d’une case de fleuve, n’est plus dorénavant limitée à un seul sort en réserve. Le haut-rêvant peut y en stocker autant qu’il le désire (dans les limites du nombre permis par ses niveaux). En conséquence, tant que ce maximum n’est pas atteint, le demi-rêve peut entrer dans la case concernée sans déclencher les sorts qui s’y trouvent. Le haut-rêvant peut se contenter d’y passer, ou rajouter un sort aux précédents. Dès que le maximum permis est atteint, le fait de passer par la case déclenche automatiquement l’un des sorts (au choix). Les sorts peuvent être déclenchés l’un après l’autre (rappelons que déclencher un sort fait obligatoirement redescendre le demi-rêve), ou déclenchés ensemble (tout ou partie d’entre eux) à la condition expresse qu’ils aient la même cible (même créature, objet, ou centre de zone). Cumulable : on peut avoir plusieurs cases de réserve extensible.
","refoulement":null},"flags":{},"img":"systems/foundryvtt-reve-de-dragon/icons/tete_dragon.webp","effects":[]}
diff --git a/templates/casetmr-specific-list.html b/templates/casetmr-specific-list.html
index 64a4e6d2..d69feec5 100644
--- a/templates/casetmr-specific-list.html
+++ b/templates/casetmr-specific-list.html
@@ -3,3 +3,9 @@
+
+
+
+
+
+
diff --git a/templates/chat-fleuve-tmr.html b/templates/chat-resultat-maitrise-tmr.html
similarity index 62%
rename from templates/chat-fleuve-tmr.html
rename to templates/chat-resultat-maitrise-tmr.html
index 9cc96e2a..e5f4fa6f 100644
--- a/templates/chat-fleuve-tmr.html
+++ b/templates/chat-resultat-maitrise-tmr.html
@@ -1,20 +1,20 @@
- {{alias}} tente de maîtriser {{le tmr.genre}} {{tmr.label}} ({{tmr.coord}})
+ {{alias}} tente de {{maitrise.verbe}} {{le tmr.genre}} {{tmr.label}} ({{tmr.coord}})
{{#if previous}}
- {{#each previous as |rolled key|}}
+ {{#with previous}}
{{> "systems/foundryvtt-reve-de-dragon/templates/chat-infojet.html"}}
- Double résistance du fleuve!
- {{/each}}
+ Double résistance du fleuve!
+ {{/with}}
{{/if}}
{{> "systems/foundryvtt-reve-de-dragon/templates/chat-infojet.html"}}
{{#if rolled.isSuccess}}
- Vous avez maîtrisé {{le tmr.genre}} {{#if (eq tmr.type 'fleuve')}}Fleuve de l'Oubli{{else}}{{tmr.label}}{{/if}} !
+ Vous parvenez à {{maitrise.verbe}} {{le tmr.genre}} {{tmr.label}} !
{{else}}
- Vous ne parvenez pas à surmonter {{le tmr.genre}} {{#if (eq tmr.type 'fleuve')}}Fleuve de l'Oubli{{else}}{{tmr.label}}{{/if}}.
+ Vous ne parvenez pas à {{maitrise.verbe}} {{le tmr.genre}} {{tmr.label}}.
Vous quittez les Terres Médianes !
{{#if souffle}}
De plus, votre échec total vous fait subir un Souffle de Dragon : {{souffle.name}}
diff --git a/templates/dialog-roll-tmr-humide.html b/templates/dialog-roll-maitrise-tmr.html
similarity index 100%
rename from templates/dialog-roll-tmr-humide.html
rename to templates/dialog-roll-maitrise-tmr.html