Merge branch 'v1.4' into 'dev_0.8'

# Conflicts:
#   module/actor-sheet.js
#   system.json
#   templates/actor-sheet.html
This commit is contained in:
2021-03-02 22:44:57 +00:00
36 changed files with 933 additions and 590 deletions

View File

@@ -9,6 +9,7 @@ import { RdDItemArme } from "./item-arme.js";
import { RdDItemCompetence } from "./item-competence.js";
import { RdDBonus } from "./rdd-bonus.js";
import { Misc } from "./misc.js";
import { RdDCombatManager } from "./rdd-combat.js";
/* -------------------------------------------- */
export class RdDActorSheet extends ActorSheet {
@@ -41,11 +42,17 @@ export class RdDActorSheet extends ActorSheet {
data.itemsByType = Misc.classify(data.items);
// Competence per category
data.data.comptageArchetype = RdDUtility.getLimitesArchetypes();
data.data.competenceXPTotal = 0;
data.competenceByCategory = Misc.classify(
data.itemsByType.competence,
item => item.data.categorie,
item => {
let archetypeKey = (item.data.niveau_archetype < 0) ? 0 : item.data.niveau_archetype;
if (data.data.comptageArchetype[archetypeKey] == undefined) {
data.data.comptageArchetype[archetypeKey] = { "niveau": archetypeKey, "nombreMax": 0, "nombre": 0};
}
data.data.comptageArchetype[archetypeKey].nombre = (data.data.comptageArchetype[archetypeKey]?.nombre??0) + 1; //Comptage archetype
item.data.xpNext = RdDItemCompetence.getCompetenceNextXp(item.data.niveau);
item.data.isLevelUp = item.data.xp >= item.data.xpNext; // Flag de niveau à MAJ
//this.actor.checkCompetenceXP(item.name); // Petite vérification experience
@@ -67,6 +74,7 @@ export class RdDActorSheet extends ActorSheet {
currentCarac.xpNext = RdDUtility.getCaracNextXp(currentCarac.value);
currentCarac.isLevelUp = (currentCarac.xp >= currentCarac.xpNext);
}
sum += (data.data.beaute >= 0) ? (data.data.beaute - 10) : 0;
data.data.caracSum = sum;
// Force empty arme, at least for Esquive
@@ -89,12 +97,12 @@ export class RdDActorSheet extends ActorSheet {
// To avoid armour and so on...
data.data.combat = duplicate(RdDUtility.checkNull(data.itemsByType['arme']));
data.data.combat = RdDUtility._finalizeArmeList(data.data.combat, data.itemsByType.competence, data.data.carac);
data.data.combat = RdDCombatManager.finalizeArmeList(data.data.combat, data.itemsByType.competence, data.data.carac);
data.esquive = { name: "Esquive", niveau: data.competenceByCategory?.melee.find(it => it.name == 'Esquive')?.data.niveau ?? -6};
let corpsACorps = data.competenceByCategory?.melee.find(it => it.name == 'Corps à corps');
if (corpsACorps) {
let cc_init = RdDUtility.calculInitiative(corpsACorps.data.niveau, caracList['melee'].value);
let cc_init = RdDCombatManager.calculInitiative(corpsACorps.data.niveau, data.data.carac['melee'].value);
data.data.combat.push(RdDItemArme.mainsNues({ niveau: corpsACorps.data.niveau, initiative: cc_init }));
}
this.armesList = duplicate(data.data.combat);
@@ -352,7 +360,7 @@ export class RdDActorSheet extends ActorSheet {
if (combatant) {
let armeName = event.currentTarget.attributes['data-arme-name'].value;
let arme = this.armesList.find(a => a.name == armeName);
RdDUtility.rollInitiativeCompetence(combatant._id, arme);
RdDCombatManager.rollInitiativeCompetence(combatant._id, arme);
} else {
ui.notifications.info("Impossible de lancer l'initiative sans être dans un combat.");
}

View File

@@ -114,6 +114,16 @@ export class RdDActor extends Actor {
if (actorData.type === 'vehicule') this._prepareVehiculeData(actorData);
}
/* -------------------------------------------- */
setRollWindowsOpened( flag ) {
this.rollWindowsOpened = flag;
}
/* -------------------------------------------- */
isRollWindowsOpened( ) {
return this.rollWindowsOpened;
}
/* -------------------------------------------- */
_prepareCreatureData(actorData) {
this.computeEncombrementTotalEtMalusArmure();
@@ -305,18 +315,19 @@ export class RdDActor extends Actor {
}
return duplicate(list[0]);
}
getDemiReve() {
return this.data.data.reve.tmrpos.coord;
}
/* -------------------------------------------- */
async deleteSortReserve(sortReserve) {
let reserve = duplicate(this.data.data.reve.reserve);
let len = reserve.list.length;
let i = 0;
let newTable = [];
for (i = 0; i < len; i++) {
if (reserve.list[i].coord != sortReserve.coord && reserve.list[i].sort.name != sortReserve.sort.name)
newTable.push(reserve.list[i]);
}
if (newTable.length != len) {
reserve.list = newTable;
let tmr = TMRUtility.getTMR(sortReserve.coord);
let index = reserve.list.findIndex(tmr.type == 'fleuve'
? sort => (TMRUtility.getTMR(sort.coord).type == 'fleuve' && sort.sort.name == sortReserve.sort.name)
: sort => (sort.coord == sortReserve.coord && sort.sort.name == sortReserve.sort.name)
);
if (index >=0 ) {
reserve.list.splice(index,1);
await this.update({ "data.reve.reserve": reserve });
}
}
@@ -486,7 +497,7 @@ export class RdDActor extends Actor {
async dormir(heures = 1) {
let message = {
whisper: ChatUtility.getWhisperRecipientsAndGMs(this.name),
content: this.name + ": Vous dormez " + heures + (heures > 1 ? " heures. " : "heure. ")
content: `${this.name}: Vous dormez ${heures == 1 ? 'une': heures} heure${heures == 1 ? '': 's'}.`
};
await this.recupereEndurance(message);
for (let i = 0; i < heures; i++) {
@@ -1151,26 +1162,6 @@ export class RdDActor extends Actor {
return tmrInnaccessibles.map(it => it.data.coord);
}
/* -------------------------------------------- */
displayTMRQueueSouffleInformation() {
let messages = [];
for (let item of this.data.items) {
if (EffetsDraconiques.isUrgenceDraconique(item)) {
messages.push("Vous souffrez d'une <strong>Urgence Draconique</strong> : " + item.data.description);
}
if (EffetsDraconiques.isPeriple(item)) {
messages.push("Vous souffrez du Souffle <strong>Périple</strong>. Vous devez gérer manuellement le détail du Périple.<br>" + item.data.description);
}
}
if (messages.length > 0) {
ChatMessage.create({
whisper: ChatUtility.getWhisperRecipientsAndGMs(game.user.name),
content: "RAPPEL !<br>" + messages.join('<hr>')
});
}
}
/* -------------------------------------------- */
getTMRRencontres() {
return this.data.data.reve.rencontre;
@@ -1184,7 +1175,7 @@ export class RdDActor extends Actor {
//console.log("List", rencontres, len);
let newTable = [];
for (i = 0; i < len; i++) {
if (rencontres.list[i].coord != this.data.data.reve.tmrpos.coord)
if (rencontres.list[i].coord != this.getDemiReve())
newTable.push(rencontres.list[i]);
}
if (newTable.length != len) {
@@ -1201,7 +1192,7 @@ export class RdDActor extends Actor {
let i = 0;
let already = false;
for (i = 0; i < len; i++) {
if (rencontres.list[i].coord == this.data.data.reve.tmrpos.coord)
if (rencontres.list[i].coord == this.getDemiReve())
already = true;
}
if (!already) {
@@ -1223,9 +1214,7 @@ export class RdDActor extends Actor {
/* -------------------------------------------- */
async updateCoordTMR(coord) {
let tmrPos = duplicate(this.data.data.reve.tmrpos);
tmrPos.coord = coord;
await this.update({ "data.reve.tmrpos": tmrPos });
await this.update({ "data.reve.tmrpos.coord": coord });
}
/* -------------------------------------------- */
@@ -2784,6 +2773,14 @@ export class RdDActor extends Actor {
await this.setFlag('foundryvtt-reve-de-dragon', 'itemUse', {} );
}
/* -------------------------------------------- */
async decItemUse( itemId ) {
let itemUse = duplicate(this.getFlag('foundryvtt-reve-de-dragon', 'itemUse') ?? {});
itemUse[itemId] = (itemUse[itemId] ?? 0) - 1;
await this.setFlag( 'foundryvtt-reve-de-dragon', 'itemUse', itemUse);
console.log("ITEM USE DEC", itemUse);
}
/* -------------------------------------------- */
async incItemUse( itemId ) {
let itemUse = duplicate(this.getFlag('foundryvtt-reve-de-dragon', 'itemUse') ?? {});
@@ -3186,6 +3183,9 @@ export class RdDActor extends Actor {
case 'souffle':
await this.onDeleteOwnedDraconique(item, options, id);
break;
case 'casetmr':
await this.onDeleteOwnedCaseTmr(item, options, id);
break;
}
}
@@ -3207,6 +3207,13 @@ export class RdDActor extends Actor {
}
}
async onDeleteOwnedCaseTmr(item, options, id) {
let draconique = Draconique.all().find(it => it.isCase(item));
if (draconique) {
draconique.onActorDeleteCaseTmr(this, item)
}
}
notifyGestionTeteSouffleQueue(item, manualMessage=true){
ChatMessage.create({
whisper: ChatUtility.getWhisperRecipientsAndGMs(game.user.name),

View File

@@ -1,11 +1,12 @@
const articlesApostrophes = {
'de' : 'd\'',
'le' : 'l\'',
'la' : 'l\''
'de': 'd\'',
'le': 'l\'',
'la': 'l\''
}
export class Grammar {
/* -------------------------------------------- */
static apostrophe(article, word) {
if (articlesApostrophes[article] && Grammar.startsWithVoyel(word)) {
return articlesApostrophes[article] + word
@@ -13,28 +14,58 @@ export class Grammar {
return article + ' ' + word;
}
/* -------------------------------------------- */
static startsWithVoyel(word) {
return word.match(/^[aeiouy]/i)
}
/* -------------------------------------------- */
static toLowerCaseNoAccent(words) {
return words?.toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "") ?? words;
}
/* -------------------------------------------- */
static articleDetermine(genre) {
switch (genre?.toLowerCase()) {
switch (Grammar.toLowerCaseNoAccent(genre)) {
case 'f': case 'feminin': return 'la';
case 'p': case 'pluriel': return 'les';
case 'p': case 'mp': case 'fp': case 'pluriel': return 'les';
default:
case 'm': case 'masculin': return 'le';
}
}
static articleIndétermine(genre) {
switch (genre?.toLowerCase()) {
/* -------------------------------------------- */
static articleIndetermine(genre) {
switch (Grammar.toLowerCaseNoAccent(genre)) {
case 'f': case 'feminin': return 'une';
case 'p': case 'pluriel': return 'des';
case 'p': case 'fp': case 'mp': case 'pluriel': return 'des';
case 'n': case 'neutre': return 'du'
default:
case 'm': case 'masculin': return 'un';
}
}
/* -------------------------------------------- */
/**
* renvoie un des mots en fonction du genre:
*
* - masculin/neutre/m/n : mots[0]
* - feminin/f : mots[1]
* - pluriel/mp/p : mots[2]
* - fp : mots[3]
*
* @param {*} genre
* @param {...any} mots
*/
static accord(genre, ...mots) {
switch (Grammar.toLowerCaseNoAccent(genre)) {
default:
case 'n': case 'neutre':
case 'm': case 'masculin': return mots[0];
case 'f': case 'feminin': return mots[1];
case 'p': case 'mp': case 'pluriel': return mots[2]
case 'fp': return mots[3];
}
}
}

View File

@@ -145,7 +145,7 @@ export class RdDItemArme extends Item {
}
static isArmeUtilisable(item) {
return item.type == 'arme' && item.data.resistance > 0;
return item.type == 'arme' && (item.data.resistance > 0 || item.data.portee_courte>0);
}
static mainsNues(actorData={}) {

View File

@@ -4,25 +4,25 @@ const poesieHautReve = [
{
reference: 'Le Ratier Bretonien',
extrait: `Le courant du Fleuve
<br>Te domine et te Porte
<br>Avant que tu te moeuves
<br>Combat le, ou il t'emporte`
<br>Te domine et te Porte
<br>Avant que tu te moeuves
<br>Combat le, ou il t'emporte`
},
{
reference: 'Incompatibilité, Charles Beaudelaire',
extrait: `Et lorsque par hasard une nuée errante
<br>Assombrit dans son vol le lac silencieux,
<br>On croirait voir la robe ou l'ombre transparente
<br>D'un esprit qui voyage et passe dans les cieux.`
<br>Assombrit dans son vol le lac silencieux,
<br>On croirait voir la robe ou l'ombre transparente
<br>D'un esprit qui voyage et passe dans les cieux.`
},
{
reference: 'Au fleuve de Loire, Joachim du Bellay',
extrait: `Ô de qui la vive course
<br>Prend sa bienheureuse source,
<br>Dune argentine fontaine,
<br>Qui dune fuite lointaine,
<br>Te rends au sein fluctueux
<br>De lOcéan monstrueux`
<br>Prend sa bienheureuse source,
<br>Dune argentine fontaine,
<br>Qui dune fuite lointaine,
<br>Te rends au sein fluctueux
<br>De lOcéan monstrueux`
},
{
reference: 'Denis Gerfaud',
@@ -61,10 +61,188 @@ const poesieHautReve = [
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&raquo;`
},
{
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 !`
},
{
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`
},
{
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.`
},
{
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 !`
},
{
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 ?`
},
{
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.`
},
{
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 !`
},
{
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 où nage la sirène...`
},
{
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.`
},
{
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;`
},
{
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`,
},
{
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.`
}
,
{
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.`
},
{
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 où je rêve, et la gourde
<br>Où je hume à longs traits le vin du souvenir`
},
{
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.`
},
{
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 !`
},
{
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.`
},
{
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.`},
{
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`
},
{
reference: "Lily, Pierre Perret",
extrait: `Elle aurait pas cru sans le voir
<br>Que la couleur du désespoir
<br>Là-bas aussi ce fût le noir.`
},
{
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.`
},
{
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 !`
},
{
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`
},
{
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.`
},
]
export class Poetique {
static getExtrait(){
static getExtrait() {
return Misc.rollOneOf(poesieHautReve);
}

View File

@@ -10,19 +10,47 @@ import { RdDRollTables } from "./rdd-rolltables.js";
import { ReglesOptionelles } from "./regles-optionelles.js";
/* -------------------------------------------- */
export class RdDCombatManager extends Combat {
const premierRoundInit = [
{ pattern: 'hast', init: 3.90 },
{ pattern: 'lance', init: 3.85 },
{ pattern: 'baton', init: 3.80 },
{ pattern: 'doubledragonne', init: 3.75 },
{ pattern: 'esparlongue', init: 3.70 },
{ pattern: 'epeedragonne', init: 3.65 },
{ pattern: 'epeebatarde', init: 3.60 },
{ pattern: 'epeecyane', init: 3.55 },
{ pattern: 'epeesorde', init: 3.50 },
{ pattern: 'grandehache', init: 3.45 },
{ pattern: 'bataille', init: 3.40 },
{ pattern: 'epeegnome', init: 3.35 },
{ pattern: 'masse', init: 3.30 },
{ pattern: 'gourdin', init: 3.25 },
{ pattern: 'fléau', init: 3.20 },
{ pattern: 'dague', init: 3.15 },
{ pattern: 'autre', init: 3.10 },
];
/* -------------------------------------------- */
export class RdDCombatManager extends Combat {
static init() {
/* -------------------------------------------- */
Hooks.on("getCombatTrackerEntryContext", (html, options) => {
RdDCombatManager.pushInitiativeOptions(html, options);
});
}
/* -------------------------------------------- */
cleanItemUse() {
for(let turn of this.turns) {
for (let turn of this.turns) {
turn.actor.resetItemUse()
}
}
/* -------------------------------------------- */
cleanSonne( ) {
cleanSonne() {
for (let combatant of this.data.combatants) {
combatant.actor.verifierSonneRound( this.current.round );
combatant.actor.verifierSonneRound(this.current.round);
}
}
@@ -31,7 +59,269 @@ export class RdDCombatManager extends Combat {
//console.log('New round !');s
this.cleanItemUse();
this.cleanSonne();
return super.nextRound();
}
/************************************************************************************/
async rollInitiative(ids, formula = undefined, messageOptions = {}) {
console.log(`${game.data.system.data.title} | Combat.rollInitiative()`, ids, formula, messageOptions);
// Structure input data
ids = typeof ids === "string" ? [ids] : ids;
const currentId = this.combatant._id;
// calculate initiative
for (let cId = 0; cId < ids.length; cId++) {
const c = this.getCombatant(ids[cId]);
//if (!c) return results;
let rollFormula = formula; // Init per default
if (!rollFormula) {
let armeCombat, competence;
if (c.actor.data.type == 'creature' || c.actor.data.type == 'entite') {
for (const competenceItem of c.actor.data.items) {
if (competenceItem.data.iscombat) {
competence = duplicate(competenceItem);
}
}
rollFormula = "2+( (" + RdDCombatManager.calculInitiative(competence.data.niveau, competence.data.carac_value) + ")/100)";
} else {
for (const item of c.actor.data.items) {
if (item.type == "arme" && item.data.equipe) {
armeCombat = duplicate(item);
}
}
let compName = (armeCombat == undefined) ? "Corps à corps" : armeCombat.data.competence;
competence = RdDItemCompetence.findCompetence(c.actor.data.items, compName);
let bonusEcaille = (armeCombat && armeCombat.data.magique) ? armeCombat.data.ecaille_efficacite : 0;
rollFormula = "2+( (" + RdDCombatManager.calculInitiative(competence.data.niveau, c.actor.data.data.carac[competence.data.defaut_carac].value, bonusEcaille) + ")/100)";
}
}
//console.log("Combatat", c);
const roll = super._getInitiativeRoll(c, rollFormula);
if (roll.total <= 0) roll.total = 0.00;
console.log("Compute init for", rollFormula, roll.total);
await this.updateEmbeddedEntity("Combatant", { _id: c._id, initiative: roll.total });
// Send a chat message
let rollMode = messageOptions.rollMode || game.settings.get("core", "rollMode");
let messageData = mergeObject(
{
speaker: {
scene: canvas.scene._id,
actor: c.actor ? c.actor._id : null,
token: c.token._id,
alias: c.token.name,
sound: CONFIG.sounds.dice,
},
flavor: `${c.token.name} a fait son jet d'Initiative (${messageOptions.initInfo})
<br>
`,
},
messageOptions
);
roll.toMessage(messageData, { rollMode, create: true });
RdDCombatManager.processPremierRoundInit();
}
return this;
};
/* -------------------------------------------- */
static calculInitiative(niveau, caracValue, bonusEcaille = 0) {
let base = niveau + Math.floor(caracValue / 2);
base += bonusEcaille;
return "1d6" + (base >= 0 ? "+" : "") + base;
}
/* -------------------------------------------- */
/** Retourne une liste triée d'armes avec le split arme1 main / arme 2 main */
static finalizeArmeList(armes, competences, carac) {
// Gestion des armes 1/2 mains
let armesEquipe = [];
for (const arme of armes) {
if (arme.data.equipe) {
armesEquipe.push(arme);
let comp = competences.find(c => c.name == arme.data.competence);
arme.data.initiative = RdDCombatManager.calculInitiative(arme.data.niveau, carac[comp.data.defaut_carac].value);
// Dupliquer les armes pouvant être à 1 main et 2 mains en patchant la compétence
if (arme.data.unemain && !arme.data.deuxmains) {
arme.data.mainInfo = "(1m)";
} else if (!arme.data.unemain && arme.data.deuxmains) {
arme.data.mainInfo = "(2m)";
} else if (arme.data.unemain && arme.data.deuxmains) {
arme.data.mainInfo = "(1m)";
let arme2main = duplicate(arme);
arme2main.data.mainInfo = "(2m)";
arme2main.data.dommages = arme2main.data.dommages.split("/")[1]; // Existence temporaire uniquement dans la liste des armes, donc OK
arme2main.data.competence = arme2main.data.competence.replace(" 1 main", " 2 mains"); // Replace !
let comp = competences.find(c => c.name == arme2main.data.competence);
arme2main.data.niveau = comp.data.niveau;
arme2main.data.initiative = RdDCombatManager.calculInitiative(arme2main.data.niveau, carac[comp.data.defaut_carac].value);
armesEquipe.push(arme2main);
}
}
}
return armesEquipe.sort((a, b) => {
const nameA = a.name + (a.data.mainInfo ?? '');
const nameB = b.name + (b.data.mainInfo ?? '');
if (nameA > nameB) return 1;
if (nameA < nameB) return -1;
return 0;
});
}
/* -------------------------------------------- */
static buildListeActionsCombat(combatant) {
const actor = combatant.actor; // Easy access
let items = actor.data.items;
let actions = []
if (actor.isCreature()) {
actions = actions.concat(items.filter(it => RdDItemCompetenceCreature.isCompetenceAttaque(it))
.map(competence => RdDItemCompetenceCreature.toArme(competence)));
} else {
// Recupération des items 'arme'
let armes = items.filter(it => RdDItemArme.isArmeUtilisable(it))
.map(arme => duplicate(arme)) /* pas de changements aux armes d'origine */
.concat(RdDItemArme.mainsNues());
let competences = items.filter(it => it.type == 'competence');
actions = actions.concat(RdDCombatManager.finalizeArmeList(armes, competences, actor.data.data.carac));
actions.push({ name: "Draconic", data: { initOnly: true, competence: "Draconic" } });
}
actions.push({ name: "Autre action", data: { initOnly: true, competence: "Autre action" } });
for (let index = 0; index < actions.length; index++) {
actions[index].index = index;
}
return actions;
}
/* -------------------------------------------- */
static processPremierRoundInit() {
// Check if we have the whole init !
if (game.user.isGM && game.combat.current.round == 1) {
let initMissing = game.combat.data.combatants.find(it => !it.initiative);
if (!initMissing) { // Premier round !
for (let combatant of game.combat.data.combatants) {
let arme = combatant.initiativeData?.arme;
//console.log("Parsed !!!", combatant, initDone, game.combat.current, arme);
if (arme && arme.type == "arme") {
for (let initData of premierRoundInit) {
if (arme.data.initpremierround.toLowerCase().includes(initData.pattern)) {
let msg = `<h4>L'initiative de ${combatant.actor.name} a été modifiée !</h4>
<hr>
<div>
Etant donné son ${arme.name}, son initative pour ce premier round est désormais de ${initData.init}.
</div>`
ChatMessage.create({ content: msg });
game.combat.setInitiative(combatant._id, initData.init);
}
}
}
}
}
}
}
/* -------------------------------------------- */
static incDecInit(combatantId, incDecValue) {
const combatant = game.combat.getCombatant(combatantId);
let initValue = combatant.initiative + incDecValue;
game.combat.setInitiative(combatantId, initValue);
}
/* -------------------------------------------- */
static pushInitiativeOptions(html, options) {
for (let i = 0; i < options.length; i++) {
let option = options[i];
if (option.name == 'COMBAT.CombatantReroll') { // Replace !
option.name = "Sélectionner l'initiative...";
option.condition = true;
option.icon = '<i class="far fa-question-circle"></i>';
option.callback = target => {
RdDCombatManager.displayInitiativeMenu(html, target.data('combatant-id'));
}
}
}
options = [
{ name: "Incrémenter initiative", condition: true, icon: '<i class="fas fa-plus"></i>', callback: target => { RdDCombatManager.incDecInit(target.data('combatant-id'), +0.01); } },
{ name: "Décrémenter initiative", condition: true, icon: '<i class="fas fa-minus"></i>', callback: target => { RdDCombatManager.incDecInit(target.data('combatant-id'), -0.01); } }
].concat(options);
}
/* -------------------------------------------- */
static rollInitiativeCompetence(combatantId, arme) {
const combatant = game.combat.getCombatant(combatantId);
const actor = combatant.actor;
let initInfo = "";
let initOffset = 0;
let caracForInit = 0;
let compNiveau = 0;
let competence = { name: "Aucune" };
if (actor.getSurprise() == "totale") {
initOffset = -1; // To force 0
initInfo = "Surprise Totale"
} else if (actor.getSurprise() == "demi") {
initOffset = 0;
initInfo = "Demi Surprise"
} else if (arme.name == "Autre action") {
initOffset = 2;
initInfo = "Autre Action"
} else if (arme.name == "Draconic") {
initOffset = 7;
initInfo = "Draconic"
} else {
initOffset = 3; // Melée = 3.XX
competence = RdDItemCompetence.findCompetence(combatant.actor.data.items, arme.data.competence);
compNiveau = competence.data.niveau;
initInfo = arme.name + " / " + arme.data.competence;
if (actor.data.type == 'creature' || actor.data.type == 'entite') {
caracForInit = competence.data.carac_value;
if (competence.data.categorie == "lancer") {
initOffset = 5;
}
} else {
caracForInit = actor.data.data.carac[competence.data.defaut_carac].value;
if (competence.data.categorie == "lancer") { // Offset de principe pour les armes de jet
initOffset = 4;
}
if (competence.data.categorie == "tir") { // Offset de principe pour les armes de jet
initOffset = 5;
}
if (competence.data.categorie == "melee") { // Offset de principe pour les armes de jet
initOffset = 3;
}
}
}
let malus = actor.getEtatGeneral(); // Prise en compte état général
// Cas des créatures et entités vs personnages
let rollFormula = initOffset + "+ ( (" + RdDCombatManager.calculInitiative(compNiveau, caracForInit) + " + " + malus + ") /100)";
// Garder la trace de l'arme/compétence utilisée pour l'iniative
combatant.initiativeData = { arme: arme } // pour reclasser l'init au round 0
game.combat.rollInitiative(combatantId, rollFormula, { initInfo: initInfo });
}
/* -------------------------------------------- */
static displayInitiativeMenu(html, combatantId) {
console.log("Combatant ; ", combatantId);
const combatant = game.combat.getCombatant(combatantId);
let armesList = RdDCombatManager.buildListeActionsCombat(combatant);
// Build the relevant submenu
if (armesList) {
let menuItems = [];
for (let arme of armesList) {
menuItems.push({
name: arme.data.competence,
icon: "<i class='fas fa-dice-d6'></i>",
callback: target => { RdDCombatManager.rollInitiativeCompetence(combatantId, arme) }
});
}
new ContextMenu(html, ".directory-list", menuItems).render();
}
}
}
/* -------------------------------------------- */
@@ -384,8 +674,8 @@ export class RdDCombat {
let rollData = this._prepareAttaque(competence, arme);
console.log("RdDCombat.attaque >>>", rollData);
this.attacker.incItemUse( arme._id ); // Usage
this.attacker.verifierForceMin( arme );
this.attacker.incItemUse(arme._id); // Usage
this.attacker.verifierForceMin(arme);
const dialog = await RdDRoll.create(this.attacker, rollData,
{
@@ -435,6 +725,7 @@ export class RdDCombat {
/* -------------------------------------------- */
async _onAttaqueParticuliere(rollData) {
RdDCombat._storeAttaque(this.attackerId, rollData);
this.attacker.decItemUse( rollData.arme._id ); // Usage décrémenté sur particulière
// Finesse et Rapidité seulement en mêlée et si la difficulté libre est de -1 minimum
const isMeleeDiffNegative = rollData.selectedCarac.label == "Mêlée" && rollData.diffLibre < 0;
@@ -490,7 +781,7 @@ export class RdDCombat {
let esquiveUsage = 0;
let esquive = this.defender.getCompetence("esquive");
if (esquive) {
esquiveUsage = this.defender.getItemUse( esquive._id);
esquiveUsage = this.defender.getItemUse(esquive._id);
}
const paramChatDefense = {
@@ -550,8 +841,8 @@ export class RdDCombat {
_filterArmesParade(defender, competence) {
let items = defender.data.items;
items = items.filter(item => RdDItemArme.isArmeUtilisable(item) || RdDItemCompetenceCreature.isCompetenceParade(item));
for( let item of items) {
item.data.nbUsage = defender.getItemUse( item._id); // Ajout du # d'utilisation ce round
for (let item of items) {
item.data.nbUsage = defender.getItemUse(item._id); // Ajout du # d'utilisation ce round
}
switch (competence.data.categorie) {
case 'tir':
@@ -571,9 +862,8 @@ export class RdDCombat {
RdDCombat._storeAttaque(this.attackerId, attackerRoll);
// Finesse et Rapidité seulement en mêlée et si la difficulté libre est de -1 minimum
ChatMessage.create({
whisper: ChatMessage.getWhisperRecipients(this.attacker.name),
whisper: ChatUtility.getWhisperRecipientsAndGMs(this.attacker.name),
content: await renderTemplate('systems/foundryvtt-reve-de-dragon/templates/chat-demande-attaque-etotal.html', {
attackerId: this.attackerId,
attacker: this.attacker,
@@ -591,7 +881,7 @@ export class RdDCombat {
const avecArme = arme?.data.categorie_parade != 'sans-armes';
const action = (rollData.attackerRoll ? (arme ? "la parade" : "l'esquive") : "l'attaque");
ChatUtility.createChatWithRollMode(this.defender.name, {
content: `<strong>Echec total à ${action}!</strong> ` + await RdDRollTables.getMaladresse({ arme: avecArme })
content: `<strong>Maladresse à ${action}!</strong> ` + await RdDRollTables.getMaladresse({ arme: avecArme })
});
}
@@ -616,7 +906,7 @@ export class RdDCombat {
let arme = this.defender.getArmeParade(armeParadeId);
console.log("RdDCombat.parade >>>", attackerRoll, armeParadeId, arme);
this.defender.incItemUse( armeParadeId ); // Usage
this.defender.incItemUse(armeParadeId); // Usage
let rollData = this._prepareParade(attackerRoll, arme);
@@ -724,7 +1014,7 @@ export class RdDCombat {
}
console.log("RdDCombat.esquive >>>", attackerRoll, esquive);
let rollData = this._prepareEsquive(attackerRoll, esquive);
this.defender.incItemUse( esquive._id ); // Usage
this.defender.incItemUse(esquive._id); // Usage
const dialog = await RdDRoll.create(this.defender, rollData,
{ html: 'systems/foundryvtt-reve-de-dragon/templates/dialog-competence.html' }, {
@@ -803,10 +1093,10 @@ export class RdDCombat {
const dmg = attackerRoll.dmg.dmgArme + attackerRoll.dmg.dmgActor;
let arme = defenderRoll.arme;
let msg = "";
if ( arme.data.magique ) {
if (arme.data.magique) {
defenderRoll.show.deteriorationArme = 'resiste'; // Par défaut
if (arme.data.resistance_magique == undefined) arme.data.resistance_magique = 0; // Quick fix
if ( dmg > arme.data.resistance_magique) { // Jet uniquement si dommages supérieur à résistance magique (cf. 274)
if (dmg > arme.data.resistance_magique) { // Jet uniquement si dommages supérieur à résistance magique (cf. 274)
let resistance = Misc.toInt(arme.data.resistance);
// Jet de résistance de l'arme de parade (p.132)
let resistRoll = await RdDResolutionTable.rollData({
@@ -814,14 +1104,14 @@ export class RdDCombat {
finalLevel: - dmg,
showDice: false
});
if ( !resistRoll.rolled.isSuccess) {
let perteResistance = ( dmg - arme.data.resistance_magique)
if (!resistRoll.rolled.isSuccess) {
let perteResistance = (dmg - arme.data.resistance_magique)
resistance -= perteResistance;
defenderRoll.show.deteriorationArme = resistance <= 0 ? 'brise': 'perte';
defenderRoll.show.deteriorationArme = resistance <= 0 ? 'brise' : 'perte';
defenderRoll.show.perteResistance = perteResistance;
this.defender.updateEmbeddedEntity("OwnedItem", { _id: defenderRoll.arme._id, 'data.resistance': resistance });
}
}
}
} else {
let resistance = Misc.toInt(arme.data.resistance);
// Jet de résistance de l'arme de parade (p.132)
@@ -834,7 +1124,7 @@ export class RdDCombat {
defenderRoll.show.deteriorationArme = 'resiste';
} else {
resistance -= dmg;
defenderRoll.show.deteriorationArme = resistance <= 0 ? 'brise': 'perte';
defenderRoll.show.deteriorationArme = resistance <= 0 ? 'brise' : 'perte';
defenderRoll.show.perteResistance = dmg;
this.defender.updateEmbeddedEntity("OwnedItem", { _id: defenderRoll.arme._id, 'data.resistance': resistance });
}

View File

@@ -23,6 +23,8 @@ export class RdDCommands {
rddCommands.registerCommand({ path: ["/aide"], func: (content, msg, params) => rddCommands.help(msg), descr: "Affiche l'aide pour toutes les commandes" });
rddCommands.registerCommand({ path: ["/help"], func: (content, msg, params) => rddCommands.help(msg), descr: "Affiche l'aide pour toutes les commandes" });
rddCommands.registerCommand({ path: ["/table", "queues"], func: (content, msg, params) => RdDRollTables.getQueue(true), descr: "Tire une Queue de Dragon" });
rddCommands.registerCommand({ path: ["/table", "ideefixe"], func: (content, msg, params) => RdDRollTables.getIdeeFixe(true), descr: "Tire une Idée fixe" });
rddCommands.registerCommand({ path: ["/table", "desir"], func: (content, msg, params) => RdDRollTables.getDesirLancinant(true), descr: "Tire un Désir Lancinant" });
rddCommands.registerCommand({ path: ["/table", "ombre"], func: (content, msg, params) => RdDRollTables.getOmbre(true), descr: "Tire une Ombre de Dragon" });
rddCommands.registerCommand({ path: ["/table", "tetehr"], func: (content, msg, params) => RdDRollTables.getTeteHR(true), descr: "Tire une Tête de Dragon pour Hauts Revants" });
rddCommands.registerCommand({ path: ["/table", "tete"], func: (content, msg, params) => RdDRollTables.getTete(true), descr: "Tire une Tête de Dragon" });

View File

@@ -34,77 +34,7 @@ import { EffetsDraconiques } from "./tmr/effets-draconiques.js";
/* Foundry VTT Initialization */
/* -------------------------------------------- */
/************************************************************************************/
const _patch_initiative = () => {
Combat.prototype.rollInitiative = async function (
ids,
formula = undefined,
messageOptions = {}
) {
console.log(
`${game.data.system.data.title} | Combat.rollInitiative()`,
ids,
formula,
messageOptions
);
// Structure input data
ids = typeof ids === "string" ? [ids] : ids;
const currentId = this.combatant._id;
// calculate initiative
for (let cId = 0; cId < ids.length; cId++) {
const c = this.getCombatant(ids[cId]);
//if (!c) return results;
let rollFormula = formula; // Init per default
if (!rollFormula) {
let armeCombat, competence;
if (c.actor.data.type == 'creature' || c.actor.data.type == 'entite') {
for (const competenceItem of c.actor.data.items) {
if (competenceItem.data.iscombat) {
competence = duplicate(competenceItem);
}
}
rollFormula = "2+( ("+RdDUtility.calculInitiative(competence.data.niveau, competence.data.carac_value)+")/100)";
} else {
for (const item of c.actor.data.items) {
if (item.type == "arme" && item.data.equipe) {
armeCombat = duplicate(item);
}
}
let compName = (armeCombat == undefined) ? "Corps à corps" : armeCombat.data.competence;
competence = RdDItemCompetence.findCompetence(c.actor.data.items, compName);
let bonusEcaille = (armeCombat && armeCombat.data.magique) ? armeCombat.data.ecaille_efficacite : 0;
rollFormula = "2+( ("+RdDUtility.calculInitiative(competence.data.niveau, c.actor.data.data.carac[competence.data.defaut_carac].value, bonusEcaille) + ")/100)";
}
}
//console.log("Combatat", c);
const roll = this._getInitiativeRoll(c, rollFormula);
if (roll.total <= 0) roll.total = 0.00;
console.log("Compute init for", rollFormula, roll.total);
await this.updateEmbeddedEntity("Combatant", { _id: c._id, initiative: roll.total });
// Send a chat message
let rollMode = messageOptions.rollMode || game.settings.get("core", "rollMode");
let messageData = mergeObject(
{
speaker: {
scene: canvas.scene._id,
actor: c.actor ? c.actor._id : null,
token: c.token._id,
alias: c.token.name,
sound: CONFIG.sounds.dice,
},
flavor: `${c.token.name} a fait son jet d'Initiative (${messageOptions.initInfo})`,
},
messageOptions
);
roll.toMessage(messageData, { rollMode, create: true });
RdDUtility.processPremierRoundInit( );
}
return this;
};
}
/************************************************************************************/
Hooks.once("init", async function () {
@@ -224,15 +154,10 @@ Hooks.once("init", async function () {
//CONFIG.Combat.entityClass = RdDCombatManager;
CONFIG.Combat.documentClass = RdDCombatManager;
// Handlebar function pour container
Handlebars.registerHelper('buildConteneur', (objet) => { return RdDUtility.buildConteneur(objet); });
// Patch the initiative formula
_patch_initiative();
// préparation des différents modules
RdDCommands.init();
RdDCombat.init();
RdDCombatManager.init(),
RdDTokenHud.init();
RdDActor.init();
RddCompendiumOrganiser.init();
@@ -301,10 +226,6 @@ Hooks.on("chatMessage", (html, content, msg) => {
return true;
});
/* -------------------------------------------- */
Hooks.on("getCombatTrackerEntryContext", (html, options) => {
RdDUtility.pushInitiativeOptions(html, options);
});
/* -------------------------------------------- */
Hooks.on("renderChatMessage", async (app, html, msg) => {

View File

@@ -125,7 +125,7 @@ export class RdDResolutionTable {
/* -------------------------------------------- */
static _updateChancesFactor(chances, diviseur) {
if (diviseur && diviseur > 1) {
if (chances.level > -11 && diviseur && diviseur > 1) {
let newScore = Math.floor(chances.score / diviseur);
mergeObject(chances, this._computeCell(null, newScore), { overwrite: true });
}

View File

@@ -18,6 +18,12 @@ export class RdDRoll extends Dialog {
/* -------------------------------------------- */
static async create(actor, rollData, dialogConfig, ...actions) {
if (actor.isRollWindowsOpened() ) {
ui.notifications.warn("Vous avez déja une fenêtre de Test ouverte, il faut la fermer avant d'en ouvrir une autre.")
return;
}
actor.setRollWindowsOpened(true);
RdDRoll._ensureCorrectActions(actions);
RdDRoll._setDefaultOptions(actor, rollData);
@@ -102,6 +108,7 @@ export class RdDRoll extends Dialog {
close() {
if (this.rollData.canClose) {
this.actor.setRollWindowsOpened(false);
return super.close();
}
ui.notifications.info("Vous devez faire ce jet de dés!");
@@ -112,7 +119,8 @@ export class RdDRoll extends Dialog {
async onAction(action, html) {
await RdDResolutionTable.rollData(this.rollData);
console.log("RdDRoll -=>", this.rollData, this.rollData.rolled);
this.actor.setRollWindowsOpened(false);
if (action.callbacks)
for (let callback of action.callbacks) {
if (callback.condition == undefined || callback.condition(this.rollData)) {

View File

@@ -48,21 +48,29 @@ export class RdDRollTables {
static async getQueue(toChat = false) {
let queue = await RdDRollTables.drawItemFromRollTable("Queues de dragon", toChat);
if (queue.name.toLowerCase().includes('lancinant') ) {
queue = await RdDRollTables.drawItemFromRollTable("Désirs lancinants", toChat);
return await RdDRollTables.getDesirLancinant(toChat);
}
if (queue.name.toLowerCase().includes('fixe') ) {
queue = await RdDRollTables.drawItemFromRollTable("Idées fixes", toChat);
return await RdDRollTables.getIdeeFixe(toChat);
}
return queue;
}
/* -------------------------------------------- */
static async getTete(toChat = false) {
return await RdDRollTables.drawItemFromRollTable("Têtes de Dragon pour haut-rêvants", toChat);
static async getDesirLancinant(toChat = false) {
return await RdDRollTables.drawItemFromRollTable("Désirs lancinants", toChat);
}
static async getIdeeFixe(toChat = false) {
return await RdDRollTables.drawItemFromRollTable("Idées fixes", toChat);
}
/* -------------------------------------------- */
static async getTeteHR(toChat = false) {
return await RdDRollTables.drawItemFromRollTable("Têtes de Dragon pour haut-rêvants", toChat);
}
/* -------------------------------------------- */
static async getTete(toChat = false) {
return await RdDRollTables.drawItemFromRollTable("Têtes de Dragon pour tous personnages", toChat);
}

View File

@@ -108,6 +108,11 @@ export class RdDTMRDialog extends Dialog {
this._createTokens();
}
removeToken(tmr, casetmr) {
this._removeTokens(t => t.coordTMR() == tmr.coord && t.caseSpeciale?._id == casetmr._id);
this.updateTokens()
}
/* -------------------------------------------- */
_getTokensCasesTmr() {
return this.casesSpeciales.map(c => this._tokenCaseSpeciale(c)).filter(token => token);
@@ -142,7 +147,7 @@ export class RdDTMRDialog extends Dialog {
async activateListeners(html) {
super.activateListeners(html);
document.getElementById("tmrrow1").insertCell(1).append(this.pixiApp.view);
document.getElementById("tmrrow1").insertCell(0).append(this.pixiApp.view);
if (this.viewOnly) {
html.find('#lancer-sort').remove();
@@ -168,7 +173,6 @@ export class RdDTMRDialog extends Dialog {
let tmr = TMRUtility.getTMR(this.actor.data.data.reve.tmrpos.coord);
await this.manageRencontre(tmr, () => {
this.postRencontre(tmr);
this.actor.displayTMRQueueSouffleInformation();
});
}
@@ -498,9 +502,7 @@ export class RdDTMRDialog extends Dialog {
}
async _resultatMaitriseCaseHumide(rollData) {
if (rollData.rolled.isETotal) {
rollData.souffle = await this.actor.ajouterSouffle({ chat: false });
}
await this.souffleSiEchecTotal(rollData);
this.toclose = rollData.rolled.isEchec;
if (rollData.rolled.isSuccess && rollData.double) {
rollData.previous = { rolled: rollData.rolled, ajustements: rollData.ajustements };
@@ -518,6 +520,12 @@ export class RdDTMRDialog extends Dialog {
}
}
async souffleSiEchecTotal(rollData) {
if (rollData.rolled.isETotal) {
rollData.souffle = await this.actor.ajouterSouffle({ chat: false });
}
}
/* -------------------------------------------- */
isCaseHumide(tmr) {
if (!(TMRUtility.isCaseHumide(tmr) || this.isCaseHumideAdditionelle(tmr))) {
@@ -558,16 +566,29 @@ export class RdDTMRDialog extends Dialog {
await this._conquerir(tmr, {
difficulte: -9,
action: 'Conquérir la cité',
onConqueteReussie: r => EffetsDraconiques.fermetureCites.onConquete(r.actor, tmr, (casetmr) => this.removeToken(tmr, casetmr)),
onConqueteEchec: r => this.close(),
onConqueteReussie: r => EffetsDraconiques.fermetureCites.onVisiteSupprimer(r.actor, tmr, (casetmr) => this.removeToken(tmr, casetmr)),
onConqueteEchec: r => {
this.souffleSiEchecTotal(rollData);
this.close()
},
canClose: false
});
}
}
removeToken(tmr, casetmr) {
this._removeTokens(t => t.coordTMR() == tmr.coord && t.caseSpeciale?._id == casetmr._id);
this.updateTokens()
/* -------------------------------------------- */
async purifierPeriple(tmr) {
if (EffetsDraconiques.periple.find(this.casesSpeciales, tmr.coord)) {
await this._conquerir(tmr, {
difficulte: EffetsDraconiques.periple.getDifficulte(tmr),
action: 'Purifier ' + TMRUtility.getTMRDescr(tmr.coord),
onConqueteReussie: r => EffetsDraconiques.periple.onVisiteSupprimer(r.actor, tmr, (casetmr) => this.removeToken(tmr, casetmr)),
onConqueteEchec: r => {
this.souffleSiEchecTotal(rollData);
this.close()
},
canClose: false
});
}
}
/* -------------------------------------------- */
@@ -576,8 +597,8 @@ export class RdDTMRDialog extends Dialog {
await this._conquerir(tmr, {
difficulte: -7,
action: 'Conquérir',
onConqueteReussie: r => EffetsDraconiques.conquete.onConquete(r.actor, tmr, (casetmr) => this.removeToken(tmr, casetmr)),
onConqueteEchec: r => { },
onConqueteReussie: r => EffetsDraconiques.conquete.onVisiteSupprimer(r.actor, tmr, (casetmr) => this.removeToken(tmr, casetmr)),
onConqueteEchec: r => this.close(),
canClose: false
});
}
@@ -639,8 +660,9 @@ export class RdDTMRDialog extends Dialog {
dialog.render(true);
}
async validerPelerinage(tmr) {
await EffetsDraconiques.pelerinage.onFinPelerinage(this.actor, tmr, (casetmr) => this.removeToken(tmr, casetmr));
async validerVisite(tmr) {
await EffetsDraconiques.pelerinage.onVisiteSupprimer(this.actor, tmr, (casetmr) => this.removeToken(tmr, casetmr));
await EffetsDraconiques.urgenceDraconique.onVisiteSupprimer(this.actor, tmr, (casetmr) => this.removeToken(tmr, casetmr));
}
@@ -649,11 +671,12 @@ export class RdDTMRDialog extends Dialog {
let sortReserveList = TMRUtility.getSortReserveList(this.sortsReserves, coord);
if (sortReserveList.length > 0) {
if (EffetsDraconiques.isSortImpossible(this.actor)) {
if (EffetsDraconiques.isSortReserveImpossible(this.actor)) {
ui.notifications.error("Une queue ou un souffle vous empèche de déclencher de sort!");
return;
}
if (EffetsDraconiques.isReserveEnSecurite(this.actor) || this.isReserveExtensible(coord)) {
if (!EffetsDraconiques.isUrgenceDraconique(this.actor) &&
(EffetsDraconiques.isReserveEnSecurite(this.actor) || this.isReserveExtensible(coord))) {
let msg = "Vous êtes sur une case avec un Sort en Réserve. Grâce à votre Tête <strong>Reserve en Sécurité</strong> ou <strong>Réserve Exensible</strong>, vous pouvez contrôler le déclenchement. Cliquez si vous souhaitez le déclencher : <ul>";
for (let sortReserve of sortReserveList) {
msg += "<li><a class='chat-card-button' id='sort-reserve' data-actor-id='" + this.actor._id + "' data-tmr-coord='" + coord + "' data-sort-id='" + sortReserve.sort._id + "'>" + sortReserve.sort.name + "</a></li>";
@@ -663,9 +686,9 @@ export class RdDTMRDialog extends Dialog {
content: msg,
whisper: ChatMessage.getWhisperRecipients(game.user.name)
});
} else {
await this.processSortReserve(sortReserveList[0]);
return;
}
await this.processSortReserve(sortReserveList[0]);
}
}
@@ -835,18 +858,19 @@ export class RdDTMRDialog extends Dialog {
if (!(this.viewOnly || this.currentRencontre)) {
await this.manageCaseHumide(tmr);
await this.conquerirCiteFermee(tmr);
await this.purifierPeriple(tmr);
await this.conquerirTMR(tmr);
await this.validerPelerinage(tmr);
await this.validerVisite(tmr);
await this.declencheSortEnReserve(tmr.coord);
await this.actor.checkSoufflePeage(tmr);
}
}
/* -------------------------------------------- */
async forceDemiRevePositionView() {
this._updateDemiReve();
}
/* -------------------------------------------- */
async forceDemiRevePosition(coord) {
await this.actor.updateCoordTMR(coord);

View File

@@ -1,5 +1,6 @@
/* -------------------------------------------- */
import { HtmlUtility } from "./html-utility.js";
import { RdDCombatManager } from "./rdd-combat.js";
import { RdDUtility } from "./rdd-utility.js";
/* -------------------------------------------- */
@@ -26,7 +27,7 @@ export class RdDTokenHud {
let combatant = game.combat.data.combatants.find(c => c.tokenId == token.data._id);
app.hasExtension = true;
let armesList = RdDUtility.buildListeActionsCombat(combatant) ;
let armesList = RdDCombatManager.buildListeActionsCombat(combatant) ;
const hudData = { combatant: combatant, armes: armesList,
commandes: [{ name: 'Initiative +1', command: 'inc', value: 0.01}, { name: 'Initiative -1',command: 'dec', value: -0.01}] };
@@ -38,11 +39,11 @@ export class RdDTokenHud {
if ( !initCommand ) {
let armeIndex = event.currentTarget.attributes['data-arme-id'].value;
let arme = armesList[armeIndex];
RdDUtility.rollInitiativeCompetence(combatantId, arme);
RdDCombatManager.rollInitiativeCompetence(combatantId, arme);
} else if (initCommand == 'inc') {
RdDUtility.incDecInit( combatantId, 0.01 );
RdDCombatManager.incDecInit( combatantId, 0.01 );
} else if ( initCommand == 'dec') {
RdDUtility.incDecInit( combatantId, -0.01 );
RdDCombatManager.incDecInit( combatantId, -0.01 );
}
});

View File

@@ -2,7 +2,7 @@
import { RdDRollTables } from "./rdd-rolltables.js";
import { ChatUtility } from "./chat-utility.js";
import { RdDCombat } from "./rdd-combat.js";
import { RdDCombat, RdDCombatManager } from "./rdd-combat.js";
import { RdDRollResolutionTable } from "./rdd-roll-resolution-table.js";
import { RdDItemCompetenceCreature } from "./item-competencecreature.js";
import { RdDItemArme } from "./item-arme.js";
@@ -22,6 +22,22 @@ const categorieCompetences = {
"lancer": { level: "-8", label: "Lancer" }
}
/* -------------------------------------------- */
const limitesArchetypes = [
{ "niveau": 0, "nombreMax": 100, "nombre": 0 },
{ "niveau": 1, "nombreMax": 10, "nombre": 0 },
{ "niveau": 2, "nombreMax": 9, "nombre": 0 },
{ "niveau": 3, "nombreMax": 8, "nombre": 0 },
{ "niveau": 4, "nombreMax": 7, "nombre": 0 },
{ "niveau": 5, "nombreMax": 6, "nombre": 0 },
{ "niveau": 6, "nombreMax": 5, "nombre": 0 },
{ "niveau": 7, "nombreMax": 4, "nombre": 0 },
{ "niveau": 8, "nombreMax": 3, "nombre": 0 },
{ "niveau": 9, "nombreMax": 2, "nombre": 0 },
{ "niveau": 10, "nombreMax": 1, "nombre": 0 },
{ "niveau": 11, "nombreMax": 1, "nombre": 0 }
];
/* -------------------------------------------- */
// This table starts at 0 -> niveau -10
const carac_array = ["taille", "apparence", "constitution", "force", "agilite", "dexterite", "vue", "ouie", "odoratgout", "volonte", "intellect", "empathie", "reve", "chance", "melee", "tir", "lancer", "derobee"];
@@ -65,27 +81,6 @@ const tableCaracDerivee = {
32: { xp: 180, poids: "1501-2000", plusdom: +11, sconst: 10, sust: 17 }
}
/* -------------------------------------------- */
const premierRoundInit = [
{ pattern: 'hast', init: 3.90 },
{ pattern: 'lance', init: 3.85 },
{ pattern: 'baton', init: 3.80 },
{ pattern: 'doubledragonne', init: 3.75 },
{ pattern: 'esparlongue', init: 3.70 },
{ pattern: 'epeedragonne', init: 3.65 },
{ pattern: 'epeebatarde', init: 3.60 },
{ pattern: 'epeecyane', init: 3.55 },
{ pattern: 'epeesorde', init: 3.50 },
{ pattern: 'grandehache', init: 3.45 },
{ pattern: 'bataille', init: 3.40 },
{ pattern: 'epeegnome', init: 3.35 },
{ pattern: 'masse', init: 3.30 },
{ pattern: 'gourdin', init: 3.25 },
{ pattern: 'fléau', init: 3.20 },
{ pattern: 'dague', init: 3.15 },
{ pattern: 'autre', init: 3.10 },
];
/* -------------------------------------------- */
function _buildAllSegmentsFatigue(max) {
const cycle = [5, 2, 4, 1, 3, 0];
@@ -235,6 +230,7 @@ export class RdDUtility {
'systems/foundryvtt-reve-de-dragon/templates/hud-actor-attaque.html',
// messages tchat
'systems/foundryvtt-reve-de-dragon/templates/chat-infojet.html',
'systems/foundryvtt-reve-de-dragon/templates/chat-poesie.html',
'systems/foundryvtt-reve-de-dragon/templates/chat-info-appel-au-moral.html',
'systems/foundryvtt-reve-de-dragon/templates/chat-demande-defense.html',
'systems/foundryvtt-reve-de-dragon/templates/chat-demande-attaque-particuliere.html',
@@ -258,10 +254,17 @@ export class RdDUtility {
Handlebars.registerHelper('upper', str => str?.toUpperCase() ?? 'NULL');
Handlebars.registerHelper('le', str => Grammar.articleDetermine(str));
Handlebars.registerHelper('un', str => Grammar.articleIndetermine(str));
Handlebars.registerHelper('accord', (genre, ...args) => Grammar.accord(genre, args));
Handlebars.registerHelper('buildConteneur', (objet) => { return RdDUtility.buildConteneur(objet); });
return loadTemplates(templatePaths);
}
/* -------------------------------------------- */
static getLimitesArchetypes() {
return duplicate(limitesArchetypes);
}
/* -------------------------------------------- */
static checkNull(items) {
if (items && items.length) {
@@ -437,50 +440,6 @@ export class RdDUtility {
return tableCaracDerivee[targetValue]?.xp ?? 200;
}
/* -------------------------------------------- */
/** Retourne une liste triée d'armes avec le split arme1 main / arme 2 main */
static _finalizeArmeList(armes, competences, carac) {
// Gestion des armes 1/2 mains
let armesEquipe = [];
for (const arme of armes) {
if (arme.data.equipe) {
armesEquipe.push(arme);
let comp = competences.find(c => c.name == arme.data.competence);
arme.data.initiative = RdDUtility.calculInitiative(arme.data.niveau, carac[comp.data.defaut_carac].value);
// Dupliquer les armes pouvant être à 1 main et 2 mains en patchant la compétence
if (arme.data.unemain && !arme.data.deuxmains) {
arme.data.mainInfo = "(1m)";
} else if (!arme.data.unemain && arme.data.deuxmains) {
arme.data.mainInfo = "(2m)";
} else if (arme.data.unemain && arme.data.deuxmains) {
arme.data.mainInfo = "(1m)";
let arme2main = duplicate(arme);
arme2main.data.mainInfo = "(2m)";
arme2main.data.dommages = arme2main.data.dommages.split("/")[1]; // Existence temporaire uniquement dans la liste des armes, donc OK
arme2main.data.competence = arme2main.data.competence.replace(" 1 main", " 2 mains"); // Replace !
let comp = competences.find(c => c.name == arme2main.data.competence);
arme2main.data.niveau = comp.data.niveau;
arme2main.data.initiative = RdDUtility.calculInitiative(arme2main.data.niveau, carac[comp.data.defaut_carac].value);
armesEquipe.push(arme2main);
}
}
}
return armesEquipe.sort((a, b) => {
const nameA = a.name + (a.data.mainInfo ?? '');
const nameB = b.name + (b.data.mainInfo ?? '');
if (nameA > nameB) return 1;
if (nameA < nameB) return -1;
return 0;
});
}
/* -------------------------------------------- */
static calculInitiative(niveau, caracValue, bonusEcaille = 0) {
let base = niveau + Math.floor(caracValue / 2);
base += bonusEcaille;
return "1d6" + (base >= 0 ? "+" : "") + base;
}
/* -------------------------------------------- */
static computeCarac(data) {
data.carac.force.value = Math.min(data.carac.force.value, parseInt(data.carac.taille.value) + 4);
@@ -687,173 +646,8 @@ export class RdDUtility {
}
}
/* -------------------------------------------- */
static processPremierRoundInit() {
// Check if we have the whole init !
if (game.user.isGM) {
let initDone = true;
for (let combatant of game.combat.data.combatants) {
if (!combatant.initiative) initDone = false;
}
if (initDone && game.combat.current.round == 1) { // Premier round !
for (let combatant of game.combat.data.combatants) {
let arme = combatant.initiativeData.arme;
//console.log("Parsed !!!", combatant, initDone, game.combat.current, arme);
if (arme && arme.type == "arme") {
for (let initData of premierRoundInit) {
if (arme.data.initpremierround.toLowerCase().includes(initData.pattern)) {
let msg = `<h4>L'initiative de ${combatant.actor.name} a été modifiée !</h4>
<hr>
<div>
Etant donné son ${arme.name}, son initative pour ce premier round est désormais de ${initData.init}.
</div>`
ChatMessage.create({ content: msg });
game.combat.setInitiative(combatant._id, initData.init);
}
}
}
}
}
}
}
/* -------------------------------------------- */
static rollInitiativeCompetence(combatantId, arme) {
const combatant = game.combat.getCombatant(combatantId);
const actor = combatant.actor;
let initInfo = "";
let initOffset = 0;
let caracForInit = 0;
let compNiveau = 0;
let competence = { name: "Aucune"};
if (actor.getSurprise() == "totale") {
initOffset = -1; // To force 0
initInfo = "Surprise Totale"
} else if (actor.getSurprise() == "demi") {
initOffset = 0;
initInfo = "Demi Surprise"
} else if (arme.name == "Autre action") {
initOffset = 2;
initInfo = "Autre Action"
} else if (arme.name == "Draconic") {
initOffset = 7;
initInfo = "Draconic"
} else {
initOffset = 3; // Melée = 3.XX
competence = RdDItemCompetence.findCompetence(combatant.actor.data.items, arme.data.competence);
compNiveau = competence.data.niveau;
initInfo = arme.name + " / " + arme.data.competence;
if (actor.data.type == 'creature' || actor.data.type == 'entite') {
caracForInit = competence.data.carac_value;
if (competence.data.categorie == "lancer") {
initOffset = 5;
}
} else {
caracForInit = actor.data.data.carac[competence.data.defaut_carac].value;
if (competence.data.categorie == "lancer") { // Offset de principe pour les armes de jet
initOffset = 4;
}
if (competence.data.categorie == "tir") { // Offset de principe pour les armes de jet
initOffset = 5;
}
if (competence.data.categorie == "melee") { // Offset de principe pour les armes de jet
initOffset = 3;
}
}
}
let malus = actor.getEtatGeneral(); // Prise en compte état général
// Cas des créatures et entités vs personnages
let rollFormula = initOffset + "+ ( (" + RdDUtility.calculInitiative(compNiveau, caracForInit) + " + " + malus + ") /100)";
// Garder la trace de l'arme/compétence utilisée pour l'iniative
combatant.initiativeData = { arme: arme } // pour reclasser l'init au round 0
game.combat.rollInitiative(combatantId, rollFormula, { initInfo: initInfo});
}
/* -------------------------------------------- */
static buildListeActionsCombat(combatant) {
const actor = combatant.actor; // Easy access
let items = actor.data.items;
let actions = []
if (actor.isCreature()) {
actions = actions.concat(items.filter(it => RdDItemCompetenceCreature.isCompetenceAttaque(it))
.map(competence => RdDItemCompetenceCreature.toArme(competence)));
} else {
// Recupération des items 'arme'
let armes = items.filter(it => RdDItemArme.isArmeUtilisable(it))
.map(arme => duplicate(arme)) /* pas de changements aux armes d'origine */
.concat(RdDItemArme.mainsNues());
let competences = items.filter(it => it.type == 'competence');
actions = actions.concat(this._finalizeArmeList(armes, competences, actor.data.data.carac));
actions.push({ name: "Draconic", data: { initOnly: true, competence: "Draconic" } });
}
actions.push({ name: "Autre action", data: { initOnly: true, competence: "Autre action" } });
for (let index = 0; index < actions.length; index++) {
actions[index].index = index;
}
return actions;
}
/* -------------------------------------------- */
static displayInitiativeMenu(html, combatantId) {
const combatant = game.combat.getCombatant(combatantId);
let armesList = this.buildListeActionsCombat(combatant);
// Build the relevant submenu
if (armesList) {
let menuItems = [];
for (let arme of armesList) {
menuItems.push({
name: arme.data.competence,
icon: "<i class='fas fa-dice-d6'></i>",
callback: target => { RdDUtility.rollInitiativeCompetence(combatantId, arme) }
});
}
new ContextMenu(html, ".directory-list", menuItems).render();
}
}
/* -------------------------------------------- */
static incDecInit(combatantId, incDecValue) {
const combatant = game.combat.getCombatant(combatantId);
let initValue = combatant.initiative + incDecValue;
game.combat.setInitiative(combatantId, initValue);
}
/* -------------------------------------------- */
static pushInitiativeOptions(html, options) {
for (let i = 0; i < options.length; i++) {
let option = options[i];
if (option.name == 'COMBAT.CombatantReroll') { // Replace !
option.name = "Sélectionner l'initiative...";
option.condition = true;
option.icon = '<i class="far fa-question-circle"></i>';
option.callback = target => {
RdDUtility.displayInitiativeMenu(html, target.data('combatant-id'));
}
}
}
options.push({
name: "Incrémenter initiative",
condition: true,
icon: '<i class="fas fa-plus"></i>',
callback: target => {
RdDUtility.incDecInit(target.data('combatant-id'), +0.01);
}
});
options.push({
name: "Décrémenter initiative",
condition: true,
icon: '<i class="fas fa-minus"></i>',
callback: target => {
RdDUtility.incDecInit(target.data('combatant-id'), -0.01);
}
});
}
/* -------------------------------------------- */
static async chatListeners(html) {

View File

@@ -1,5 +1,6 @@
import { TMRRencontres } from "./tmr-rencontres.js";
import { Misc } from "./misc.js";
import { Grammar } from "./grammar.js";
/* -------------------------------------------- */
const TMRMapping = {
@@ -210,24 +211,21 @@ const TMRMapping = {
export const TMRType = {
cite: { name: "cité", genre: "f" },
sanctuaire: { name: "sanctuaire" },
plaines: { name: "plaines", genre: "p" },
sanctuaire: { name: "sanctuaire", genre: 'm' },
plaines: { name: "plaines", genre: "fp" },
pont: { name: "pont", genre: "m" },
collines: { name: "collines", genre: "p" },
collines: { name: "collines", genre: "fp" },
foret: { name: "forêt", genre: "f" },
monts: { name: "monts", genre: "p" },
monts: { name: "monts", genre: "mp" },
desert: { name: "désert", genre: "m" },
fleuve: { name: "fleuve", genre: "m" },
lac: { name: "lac", genre: "m" },
marais: { name: "marais", genre: "m" },
marais: { name: "marais", genre: "mp" },
gouffre: { name: "gouffre", genre: "m" },
necropole: { name: "nécropole", genre: "f" },
desolation: { name: "désolation", genre: "f" }
}
/* -------------------------------------------- */
const caseSpecificModes = ["attache", "trounoir", "debordement", "reserve_extensible", "maitrisee"];
/* -------------------------------------------- */
const tmrRandomMovePatten =
[{ name: 'top', x: 0, y: -1 },
@@ -282,6 +280,7 @@ export const tmrTokenZIndex = {
rencontre: 50,
trounoir: 60,
demireve: 70,
tooltip: 100,
}
/* -------------------------------------------- */
@@ -331,7 +330,12 @@ export class TMRUtility {
}
static getTMRLabel(coord) {
return TMRMapping[coord]?.label ?? (coord+": case inconnue");
return TMRMapping[coord]?.label ?? (coord + ": case inconnue");
}
static getTMRDescr(coord) {
const tmr = TMRMapping[coord];
return Grammar.articleDetermine(tmr.genre) + ' ' + tmr.label;
}
static isCaseHumide(tmr) {
@@ -383,7 +387,7 @@ export class TMRUtility {
currentPos.x = currentPos.x + direction.x;
currentPos.y = currentPos.y + direction.y;
if (this._checkTMRCoord(currentPos.x, currentPos.y)) { // Sortie de carte ! Ré-insertion aléatoire
coord = TMRUtility.getTMR(TMRUtility.convertToTMRCoord(currentPos));
coord = TMRUtility.getTMR(TMRUtility.convertToTMRCoord(currentPos));
} else {
coord = await actor.reinsertionAleatoire('Sortie de carte');
}
@@ -434,7 +438,7 @@ export class TMRUtility {
return reserveList.filter(it => TMRUtility.getTMR(it.coord).type == 'fleuve');
}
// Reserve sur un case "normale"
return reserveList.filter(it => it.coord == coord);
return reserveList.filter(it => it.coord == coord);
}
/* -------------------------------------------- */
@@ -449,9 +453,8 @@ export class TMRUtility {
for (let dy = -portee; dy <= portee; dy++) { // Loop thru lines
const currentPos = { x: centerPos.x + dx, y: centerPos.y + dy };
if (this._checkTMRCoord(currentPos.x, currentPos.y)) { // Coordinate is valie
let posPicNow = this.computeRealPictureCoordinates(currentPos, tmrConstants);
let dist = Math.sqrt(Math.pow(posPicNow.x - posPic.x, 2) + Math.pow(posPicNow.y - posPic.y, 2)) / tmrConstants.cellw;
if (dist < portee + 0.5) {
let dist = this.distancePosTMR(centerPos, currentPos);
if (dist <= portee) {
caseList.push(this.convertToTMRCoord(currentPos)); // Inside the area
}
}
@@ -459,5 +462,21 @@ export class TMRUtility {
}
return caseList;
}
static distanceTMR(coord1, coord2) {
let pos1 = this.convertToCellPos(coord1);
let pos2 = this.convertToCellPos(coord2);
return this.distancePosTMR(pos1, pos2);
}
static distancePosTMR(pos1, pos2) {
const dx = pos2.x - pos1.x;
const dy = pos2.y - pos1.y;
const abs_dx = Math.abs(dx);
const abs_dy = Math.abs(dy);
const distance = Math.sign(dx) == Math.sign(dy) ? Math.max(abs_dx, abs_dy) : (abs_dx + abs_dy);
return distance;
}
}

View File

@@ -35,11 +35,8 @@ export class Conquete extends Draconique {
await this.createCaseTmr(actor, 'Conquête: ' + conquete.label, conquete, queue._id);
}
async onConquete(actor, tmr, onRemoveToken) {
let existants = actor.data.items.filter(it => this.isCase(it, tmr.coord));
for (let casetmr of existants) {
await actor.deleteOwnedItem(casetmr.data.sourceid);
onRemoveToken(tmr, casetmr);
}
async onActorDeleteCaseTmr(actor, casetmr) {
await actor.deleteOwnedItem(casetmr.data.sourceid);
}
}

View File

@@ -24,10 +24,6 @@ export class Desorientation extends Draconique {
return Object.keys(TMRType).filter(it => !dejaDesorientes.includes(it));
}
async onActorDeleteOwned(actor, souffle) {
await this._supprimerCasesTmr(actor, souffle);
}
code() { return 'desorientation' }
tooltip(linkData) { return `Désorientation, cette case n'existe plus !` }
img() { return 'icons/svg/explosion.svg' }
@@ -51,11 +47,5 @@ export class Desorientation extends Draconique {
}
}
async _supprimerCasesTmr(actor, souffle) {
let caseTmrs = actor.data.items.filter(it => it.data.sourceId == souffle._id);
for (let casetmr of caseTmrs) {
await actor.deleteOwnedItem(casetmr._id);
}
}
}

View File

@@ -7,8 +7,7 @@ const registeredEffects = [
/**
* Définition des informations d'une "draconique" (queue, ombre, tête, souffle) qui influence les TMR
*/
export class Draconique
{
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'; }
@@ -16,6 +15,7 @@ export class Draconique
static isQueueSouffle(it) { return Draconique.isQueueDragon(it) || Draconique.isSouffleDragon(it); }
tmrLabel(linkData) { return TMRUtility.getTMRLabel(linkData.data.coord); }
tmrDescr(linkData) { return TMRUtility.getTMRDescr(linkData.data.coord); }
static register(draconique) {
registeredEffects[draconique.code()] = draconique;
@@ -28,6 +28,7 @@ export class Draconique
static all() {
return Object.values(registeredEffects);
}
static get(code) {
return registeredEffects[code];
}
@@ -56,10 +57,11 @@ export class Draconique
}
async onActorDeleteOwned(actor, item) {
let caseTmrs = actor.data.items.filter(it => this.isCase(it) && it.data.sourceid == item._id);
for (let casetmr of caseTmrs) {
await actor.deleteOwnedItem(casetmr._id);
}
this.deleteCasesTmr(actor, item);
return false;
}
async onActorDeleteCaseTmr(actor, casetmr) {
return false;
}
/**
@@ -72,7 +74,7 @@ export class Draconique
* @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é.
@@ -102,7 +104,7 @@ export class Draconique
if (this.img()) {
return pixiTMR.sprite(this.code());
}
else{
else {
return pixiTMR.circle()
}
}
@@ -120,11 +122,25 @@ export class Draconique
return list.find(c => this.isCase(c, coord));
}
async createCaseTmr(actor, label, tmr, sourceId=undefined) {
async createCaseTmr(actor, label, tmr, sourceId = undefined) {
await actor.createOwnedItem({
name: label, type: 'casetmr', img: this.img(), _id: randomID(16),
data: { coord: tmr.coord, specific: this.code(), sourceid:sourceId }
data: { coord: tmr.coord, specific: this.code(), sourceid: sourceId }
});
}
async deleteCasesTmr(actor, draconique) {
let caseTmrs = actor.data.items.filter(it => this.isCase(it) && it.data.sourceid == draconique._id);
for (let casetmr of caseTmrs) {
await actor.deleteOwnedItem(casetmr._id);
}
}
async onVisiteSupprimer(actor, tmr, onRemoveToken) {
let existants = actor.data.items.filter(it => this.isCase(it, tmr.coord));
for (let casetmr of existants) {
await actor.deleteOwnedItem(casetmr._id);
onRemoveToken(tmr, casetmr);
}
}
}

View File

@@ -14,6 +14,8 @@ import { PresentCites } from "./present-cites.js";
import { Desorientation } from "./desorientation.js";
import { Conquete } from "./conquete.js";
import { Pelerinage } from "./pelerinage.js";
import { Periple } from "./periple.js";
import { UrgenceDraconique } from "./urgence-draconique.js";
export class EffetsDraconiques {
@@ -32,6 +34,8 @@ export class EffetsDraconiques {
static desorientation = new Desorientation();
static conquete = new Conquete();
static pelerinage = new Pelerinage();
static periple = new Periple();
static urgenceDraconique = new UrgenceDraconique();
static init() {
Draconique.register(EffetsDraconiques.carteTmr);
@@ -49,6 +53,8 @@ export class EffetsDraconiques {
Draconique.register(EffetsDraconiques.desorientation);
Draconique.register(EffetsDraconiques.conquete);
Draconique.register(EffetsDraconiques.pelerinage);
Draconique.register(EffetsDraconiques.periple);
Draconique.register(EffetsDraconiques.urgenceDraconique);
}
/* -------------------------------------------- */
@@ -112,8 +118,7 @@ export class EffetsDraconiques {
}
static isPeriple(element) {
// TODO
return EffetsDraconiques.isMatching(element, it => Draconique.isSouffleDragon(it) && ir.name.toLowerCase() == 'périple');
return EffetsDraconiques.isMatching(element, it => EffetsDraconiques.periple.match(it));
}
static isDesorientation(element) {
@@ -122,7 +127,19 @@ export class EffetsDraconiques {
/* -------------------------------------------- */
static isSortImpossible(element) {
return EffetsDraconiques.isMatching(element, it => EffetsDraconiques.conquete.match(it) || EffetsDraconiques.pelerinage.match(it));
return EffetsDraconiques.isMatching(element, it =>
EffetsDraconiques.conquete.match(it) ||
EffetsDraconiques.periple.match(it) ||
EffetsDraconiques.urgenceDraconique.match(it) ||
EffetsDraconiques.pelerinage.match(it)
);
}
static isSortReserveImpossible(element) {
return EffetsDraconiques.isMatching(element, it =>
EffetsDraconiques.conquete.match(it) ||
EffetsDraconiques.periple.match(it) ||
EffetsDraconiques.pelerinage.match(it)
);
}
static isConquete(element) {
@@ -138,7 +155,7 @@ export class EffetsDraconiques {
}
static isUrgenceDraconique(element) {
return EffetsDraconiques.isMatching(element, it => Draconique.isQueueDragon(it) && it.name.toLowerCase() == 'urgence draconique');
return EffetsDraconiques.isMatching(element, it => EffetsDraconiques.urgenceDraconique.match(it));
}
/* -------------------------------------------- */

View File

@@ -35,11 +35,4 @@ export class FermetureCites extends Draconique {
await this.createCaseTmr(actor, 'Fermeture: ' + tmr.label, tmr, souffle._id);
}
}
async onConquete(actor, tmr, onRemoveToken) {
const citeFermee = actor.data.items.find(it => this.isCase(it, tmr.coord));
await actor.deleteOwnedItem(citeFermee._id);
onRemoveToken(tmr, citeFermee);
}
}

View File

@@ -31,12 +31,8 @@ export class Pelerinage extends Draconique {
});
}
async onFinPelerinage(actor, tmr, onRemoveToken) {
const pelerinages = actor.data.items.filter(it => this.isCase(it, tmr.coord));
for (let p of pelerinages){
await actor.deleteOwnedItem(p.data.sourceid);
onRemoveToken(tmr, p);
}
async onActorDeleteCaseTmr(actor, casetmr) {
await actor.deleteOwnedItem(casetmr.data.sourceid);
}
}

44
module/tmr/periple.js Normal file
View File

@@ -0,0 +1,44 @@
import { Grammar } from "../grammar.js";
import { tmrColors, tmrConstants, tmrTokenZIndex, TMRUtility } from "../tmr-utility.js";
import { Draconique } from "./draconique.js";
export class Periple extends Draconique {
constructor() {
super();
}
type() { return 'souffle' }
match(item) { return Draconique.isSouffleDragon(item) && Grammar.toLowerCaseNoAccent(item.name).includes('periple'); }
manualMessage() { return false }
async onActorCreateOwned(actor, souffle) {
let terrain = new Roll("1d2").evaluate().total == 1 ? 'sanctuaire' : 'necropole';
let tmrs = TMRUtility.getListTMR(terrain);
for (let tmr of tmrs) {
await this.createCaseTmr(actor, 'Périple: ' + tmr.label, tmr, souffle._id);
}
}
code() { return 'periple' }
tooltip(linkData) { return `Votre Périple passe par ${this.tmrDescr(linkData)}` }
img() { return 'icons/svg/acid.svg' }
createSprite(pixiTMR) {
return pixiTMR.sprite(this.code(), {
zIndex: tmrTokenZIndex.conquete,
alpha: 1,
color: tmrColors.souffle,
taille: tmrConstants.twoThird,
decallage: tmrConstants.right
});
}
getDifficulte(tmr) {
switch (tmr.type) {
case 'sanctuaire': return -3;
case 'necropole': return -5;
}
return 0;
}
}

View File

@@ -100,6 +100,7 @@ export class PixiTMR {
addTooltip(sprite, text) {
if (text) {
sprite.tooltip = new PIXI.Text(text, tooltipStyle);
sprite.tooltip.zIndex = tmrTokenZIndex.tooltip;
sprite.isOver = false;
sprite.interactive = true;
sprite.on('pointerdown', event => this.onClickBackground(event))

View File

@@ -0,0 +1,55 @@
import { ChatUtility } from "../chat-utility.js";
import { Grammar } from "../grammar.js";
import { Misc } from "../misc.js";
import { RdDRollTables } from "../rdd-rolltables.js";
import { tmrColors, tmrConstants, tmrTokenZIndex, TMRUtility } from "../tmr-utility.js";
import { Draconique } from "./draconique.js";
export class UrgenceDraconique extends Draconique {
constructor() {
super();
}
type() { return 'queue' }
match(item) { return Draconique.isQueueDragon(item) && Grammar.toLowerCaseNoAccent(item.name).includes('urgence draconique'); }
manualMessage() { return false }
async onActorCreateOwned(actor, queue) {
let coordSortsReserve = (actor.data.data.reve.reserve?.list.map(it => it.coord)) ?? [];
if (coordSortsReserve.length == 0) {
// La queue se transforme en idée fixe
let ideeFixe = await RdDRollTables.getIdeeFixe();
ChatMessage.create({
whisper: ChatUtility.getWhisperRecipientsAndGMs(game.user.name),
content: `En l'absence de sorts en réserve, l'urgence draconique de ${actor.name} se transforme en ${queue.name}`
});
await actor.createOwnedItem(ideeFixe);
await actor.deleteOwnedItem(queue._id);
return;
}
else {
let demiReve = actor.getDemiReve();
coordSortsReserve.sort((a, b) => TMRUtility.distanceTMR(a, demiReve) - TMRUtility.distanceTMR(b, demiReve));
let tmr = TMRUtility.getTMR(coordSortsReserve[0]);
await this.createCaseTmr(actor, 'Urgence draconique: ' + tmr.label, tmr, queue._id);
}
}
async onActorDeleteCaseTmr(actor, casetmr) {
await actor.deleteOwnedItem(casetmr.data.sourceid);
}
code() { return 'urgence' }
tooltip(linkData) { return `Urgence draconique!` }
img() { return 'icons/svg/hazard.svg' }
createSprite(pixiTMR) {
return pixiTMR.sprite(this.code(),
{
zIndex: tmrTokenZIndex.conquete,
color: tmrColors.queues,
taille: tmrConstants.full,
decallage: { x: 2, y: 0 }
});
}
}