300419cbad
Décompter uniquemlent si le jet n'est pas une particulière Sur attaque particulière, décompter si le choix n'est pas une attaque en rapidité
3220 lines
120 KiB
JavaScript
3220 lines
120 KiB
JavaScript
import { RdDUtility } from "./rdd-utility.js";
|
|
import { TMRUtility } from "./tmr-utility.js";
|
|
import { RdDRollDialogEthylisme } from "./rdd-roll-ethylisme.js";
|
|
import { RdDRoll } from "./rdd-roll.js";
|
|
import { RdDTMRDialog } from "./rdd-tmr-dialog.js";
|
|
import { Misc } from "./misc.js";
|
|
import { RdDAstrologieJoueur } from "./rdd-astrologie-joueur.js";
|
|
import { RdDResolutionTable } from "./rdd-resolution-table.js";
|
|
import { RdDDice } from "./rdd-dice.js";
|
|
import { RdDRollTables } from "./rdd-rolltables.js";
|
|
import { ChatUtility } from "./chat-utility.js";
|
|
import { RdDItemSort } from "./item-sort.js";
|
|
import { Grammar } from "./grammar.js";
|
|
import { RdDEncaisser } from "./rdd-roll-encaisser.js";
|
|
import { RdDCombat } from "./rdd-combat.js";
|
|
import { DeDraconique } from "./de-draconique.js";
|
|
import { RdDAudio } from "./rdd-audio.js";
|
|
import { RdDItemCompetence } from "./item-competence.js";
|
|
import { RdDItemArme } from "./item-arme.js";
|
|
import { RdDAlchimie } from "./rdd-alchimie.js";
|
|
import { StatusEffects } from "./status-effects.js";
|
|
import { RdDItemCompetenceCreature } from "./item-competencecreature.js";
|
|
import { ReglesOptionelles } from "./regles-optionelles.js";
|
|
import { TMRRencontres } from "./tmr-rencontres.js";
|
|
import { Poetique } from "./poetique.js";
|
|
import { EffetsDraconiques } from "./tmr/effets-draconiques.js";
|
|
import { Draconique } from "./tmr/draconique.js";
|
|
|
|
|
|
/* -------------------------------------------- */
|
|
/**
|
|
* Extend the base Actor entity by defining a custom roll data structure which is ideal for the Simple system.
|
|
* @extends {Actor}
|
|
*/
|
|
export class RdDActor extends Actor {
|
|
/* -------------------------------------------- */
|
|
static init() {
|
|
Hooks.on("deleteActiveEffect", (actor, effect, options) => actor.onDeleteActiveEffect(effect, options));
|
|
Hooks.on("createActiveEffect", (actor, effect, options) => actor.onCreateActiveEffect(effect, options));
|
|
Hooks.on("createOwnedItem", (actor, item, options, id) => actor.onCreateOwnedItem(item, options, id));
|
|
Hooks.on("deleteOwnedItem", (actor, item, options, id) => actor.onDeleteOwnedItem(item, options, id));
|
|
Hooks.on("updateActor", (actor, update, options, actorId) => actor.onUpdateActor(update, options, actorId));
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
/**
|
|
* Override the create() function to provide additional RdD functionality.
|
|
*
|
|
* This overrided create() function adds initial items
|
|
* Namely: Basic skills, money,
|
|
*
|
|
* @param {Object} data Barebones actor data which this function adds onto.
|
|
* @param {Object} options (Unused) Additional options which customize the creation workflow.
|
|
*
|
|
*/
|
|
|
|
static async create(data, options) {
|
|
|
|
// Case of compendium global import
|
|
if (data instanceof Array) {
|
|
return super.create(data, options);
|
|
}
|
|
// If the created actor has items (only applicable to duplicated actors) bypass the new actor creation logic
|
|
if (data.items) {
|
|
let actor = super.create(data, options);
|
|
if (data.type == "personnage") {
|
|
await actor.checkMonnaiePresence(data.items);
|
|
}
|
|
return actor;
|
|
}
|
|
|
|
let compendiumName;
|
|
if (data.type == "personnage") {
|
|
compendiumName = "foundryvtt-reve-de-dragon.competences";
|
|
} else if (data.type == "creature") {
|
|
compendiumName = "foundryvtt-reve-de-dragon.competences-creatures";
|
|
} else if (data.type == "entite") {
|
|
compendiumName = "foundryvtt-reve-de-dragon.competences-entites";
|
|
}
|
|
if (compendiumName) {
|
|
data.items = await RdDUtility.loadCompendium(compendiumName);
|
|
}
|
|
// Ajout monnaie
|
|
if (data.type == "personnage" && data.items) {
|
|
await RdDActor.ajouterMonnaie(data.items);
|
|
}
|
|
|
|
return super.create(data, options);
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
prepareData() {
|
|
super.prepareData();
|
|
|
|
const actorData = this.data;
|
|
|
|
// Dynamic computing fields
|
|
this.encTotal = 0;
|
|
this.prixTotalEquipement = 0;
|
|
|
|
/*
|
|
// Auto-resize token
|
|
if (this.isToken) {
|
|
let tokenSize = actorData.data.carac.taille.value/10;
|
|
this.token.update({height: tokenSize, width: tokenSize } );
|
|
}*/
|
|
|
|
// Make separate methods for each Actor type (character, npc, etc.) to keep
|
|
// things organized.
|
|
if (actorData.type === 'personnage') this._prepareCharacterData(actorData);
|
|
if (actorData.type === 'creature') this._prepareCreatureData(actorData);
|
|
if (actorData.type === 'vehicule') this._prepareVehiculeData(actorData);
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
setRollWindowsOpened( flag ) {
|
|
this.rollWindowsOpened = flag;
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
isRollWindowsOpened( ) {
|
|
return this.rollWindowsOpened;
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
_prepareCreatureData(actorData) {
|
|
this.computeEncombrementTotalEtMalusArmure();
|
|
this.computeEtatGeneral();
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
_prepareVehiculeData(actorData) {
|
|
this.computeEncombrementTotalEtMalusArmure();
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
/**
|
|
* Prepare Character type specific data
|
|
*/
|
|
async _prepareCharacterData(actorData) {
|
|
// Initialize empty items
|
|
RdDUtility.computeCarac(actorData.data);
|
|
this.computeEncombrementTotalEtMalusArmure();
|
|
this.computePrixTotalEquipement();
|
|
this.computeEtatGeneral();
|
|
// Sanity check
|
|
await this.checkMonnaiePresence(actorData.items);
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async checkMonnaiePresence(items) { // Ajout opportuniste si les pièces n'existent pas.
|
|
if (!items) return; // Sanity check during import
|
|
let piece = items.find(item => item.type == 'monnaie' && Number(item.data.valeur_deniers) == 1);
|
|
let newMonnaie = [];
|
|
if (!piece) {
|
|
newMonnaie.push(RdDUtility.createMonnaie("Etain (1 denier)", 1, "systems/foundryvtt-reve-de-dragon/icons/objets/piece_etain_poisson.webp"));
|
|
}
|
|
piece = items.find(item => item.type == 'monnaie' && Number(item.data.valeur_deniers) == 10);
|
|
if (!piece) {
|
|
newMonnaie.push(RdDUtility.createMonnaie("Bronze (10 deniers)", 10, "systems/foundryvtt-reve-de-dragon/icons/objets/piece_bronze_epees.webp"));
|
|
}
|
|
piece = items.find(item => item.type == 'monnaie' && Number(item.data.valeur_deniers) == 100);
|
|
if (!piece) {
|
|
newMonnaie.push(RdDUtility.createMonnaie("Argent (1 sol)", 100, "systems/foundryvtt-reve-de-dragon/icons/objets/piece_argent_sol.webp"));
|
|
}
|
|
piece = items.find(item => item.type == 'monnaie' && Number(item.data.valeur_deniers) == 1000);
|
|
if (!piece) {
|
|
newMonnaie.push(RdDUtility.createMonnaie("Or (10 sols)", 1000, "systems/foundryvtt-reve-de-dragon/icons/objets/piece_or_sol.webp"));
|
|
}
|
|
if (newMonnaie.length > 0) {
|
|
await this.createOwnedItem(newMonnaie);
|
|
}
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
static async ajouterMonnaie(items) { // Creation auto à la création du personnage
|
|
let etain = RdDUtility.createMonnaie("Etain (1 denier)", 1, "systems/foundryvtt-reve-de-dragon/icons/objets/piece_etain_poisson.webp");
|
|
items.push(etain);
|
|
|
|
let bronze = RdDUtility.createMonnaie("Bronze (10 deniers)", 10, "systems/foundryvtt-reve-de-dragon/icons/objets/piece_bronze_epees.webp");
|
|
items.push(bronze);
|
|
|
|
let argent = RdDUtility.createMonnaie("Argent (1 sol)", 100, "systems/foundryvtt-reve-de-dragon/icons/objets/piece_argent_sol.webp");
|
|
items.push(argent);
|
|
|
|
let or = RdDUtility.createMonnaie("Or (10 sols)", 1000, "systems/foundryvtt-reve-de-dragon/icons/objets/piece_or_sol.webp");
|
|
items.push(or);
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
isCreature() {
|
|
return this.data.type == 'creature' || this.data.type == 'entite';
|
|
}
|
|
/* -------------------------------------------- */
|
|
isPersonnage() {
|
|
return this.data.type == 'personnage';
|
|
}
|
|
/* -------------------------------------------- */
|
|
getFatigueActuelle() {
|
|
if (!this.isPersonnage()) {
|
|
return 0;
|
|
}
|
|
return Misc.toInt(this.data.data.sante.fatigue?.value);
|
|
}
|
|
/* -------------------------------------------- */
|
|
getFatigueMax() {
|
|
if (!this.isPersonnage()) {
|
|
return 1;
|
|
}
|
|
return Misc.toInt(this.data.data.sante.fatigue?.max);
|
|
}
|
|
/* -------------------------------------------- */
|
|
getReveActuel() {
|
|
return Misc.toInt(this.data.data.reve?.reve?.value ?? this.data.data.carac.reve.value);
|
|
}
|
|
/* -------------------------------------------- */
|
|
getChanceActuel() {
|
|
return Misc.toInt(this.data.data.compteurs.chance?.value ?? 10);
|
|
}
|
|
/* -------------------------------------------- */
|
|
getTaille() {
|
|
return Misc.toInt(this.data.data.carac.taille?.value);
|
|
}
|
|
/* -------------------------------------------- */
|
|
getForce() {
|
|
if (this.isEntiteCauchemar()) {
|
|
return Misc.toInt(this.data.data.carac.reve?.value);
|
|
}
|
|
return Misc.toInt(this.data.data.carac.force?.value);
|
|
}
|
|
/* -------------------------------------------- */
|
|
getAgilite() {
|
|
switch (this.data.type) {
|
|
case 'personnage': return Misc.toInt(this.data.data.carac.agilite?.value);
|
|
case 'creature': return Misc.toInt(this.data.data.carac.force?.value);
|
|
case 'entite': return Misc.toInt(this.data.data.carac.reve?.value);
|
|
}
|
|
return 10;
|
|
}
|
|
/* -------------------------------------------- */
|
|
getChance() {
|
|
return Misc.toInt(this.data.data.carac.chance?.value ?? 10);
|
|
}
|
|
getMoralTotal() {
|
|
return Misc.toInt(this.data.data.compteurs.moral?.value);
|
|
}
|
|
/* -------------------------------------------- */
|
|
getBonusDegat() {
|
|
// TODO: gérer séparation et +dom créature/entité indépendament de la compétence
|
|
return Misc.toInt(this.data.data.attributs.plusdom.value);
|
|
}
|
|
/* -------------------------------------------- */
|
|
getProtectionNaturelle() {
|
|
return Misc.toInt(this.data.data.attributs.protection.value);
|
|
}
|
|
/* -------------------------------------------- */
|
|
getEtatGeneral() {
|
|
return Misc.toInt(this.data.data.compteurs.etat?.value);
|
|
}
|
|
getMalusArmure() {
|
|
return Misc.toInt(this.data.data.attributs?.malusarmure?.value);
|
|
}
|
|
getEncTotal() {
|
|
return Math.floor(this.encTotal ?? 0);
|
|
}
|
|
getPrixTotalEquipement() {
|
|
return Math.floor(this.prixTotalEquipement ?? 0);
|
|
}
|
|
getSurenc() {
|
|
return Misc.toInt(this.data.data.compteurs.surenc?.value);
|
|
}
|
|
/* -------------------------------------------- */
|
|
loadCompendiumNames() {
|
|
return this.data.items.filter((item) => item.type == 'competence');
|
|
}
|
|
/* -------------------------------------------- */
|
|
getCompetence(name) {
|
|
return RdDItemCompetence.findCompetence(this.data.items, name);
|
|
}
|
|
/* -------------------------------------------- */
|
|
getTache(id) {
|
|
return this.data.items.find(item => item.type == 'tache' && item._id == id);
|
|
}
|
|
getMeditation(id) {
|
|
return this.data.items.find(item => item.type == 'meditation' && item._id == id);
|
|
}
|
|
getChant(id) {
|
|
return this.data.items.find(item => item.type == 'chant' && item._id == id);
|
|
}
|
|
getDanse(id) {
|
|
return this.data.items.find(item => item.type == 'danse' && item._id == id);
|
|
}
|
|
getMusique(id) {
|
|
return this.data.items.find(item => item.type == 'musique' && item._id == id);
|
|
}
|
|
getOeuvre(id, type = 'oeuvre') {
|
|
return this.data.items.find(item => item.type == type && item._id == id);
|
|
}
|
|
getJeu(id) {
|
|
return this.data.items.find(item => item.type == 'jeu' && item._id == id);
|
|
}
|
|
getRecetteCuisine(id) {
|
|
return this.data.items.find(item => item.type == 'recettecuisine' && item._id == id);
|
|
}
|
|
/* -------------------------------------------- */
|
|
getBestDraconic() {
|
|
const list = this.getDraconicList().sort((a, b) => b.data.niveau - a.data.niveau);
|
|
if (list.length == 0) {
|
|
return { name: "none", data: { niveau: -11 } };
|
|
}
|
|
return duplicate(list[0]);
|
|
}
|
|
/* -------------------------------------------- */
|
|
async deleteSortReserve(sortReserve) {
|
|
let reserve = duplicate(this.data.data.reve.reserve);
|
|
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 });
|
|
}
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
getSurprise(isCombat = undefined) {
|
|
let niveauSurprise = Array.from(this.effects?.values() ?? [])
|
|
.map(effect => StatusEffects.valeurSurprise(effect.data, isCombat))
|
|
.reduce((a, b) => a + b, 0);
|
|
if (niveauSurprise > 1) {
|
|
return 'totale';
|
|
}
|
|
if (niveauSurprise == 1 || this.getSonne()) {
|
|
return 'demi';
|
|
}
|
|
return '';
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async dormirChateauDormant() {
|
|
let message = {
|
|
whisper: ChatUtility.getWhisperRecipientsAndGMs(this.name),
|
|
content: ""
|
|
};
|
|
|
|
const blessures = duplicate(this.data.data.blessures);
|
|
console.log("dormirChateauDormant", blessures)
|
|
await this._recupererBlessures(message, "legere", blessures.legeres.liste.filter(b => b.active), []);
|
|
await this._recupererBlessures(message, "grave", blessures.graves.liste.filter(b => b.active), blessures.legeres.liste);
|
|
await this._recupererBlessures(message, "critique", blessures.critiques.liste.filter(b => b.active), blessures.graves.liste);
|
|
await this.update({ "data.blessures": blessures });
|
|
await this._recupererVie(message);
|
|
await this.jetDeMoral('neutre');
|
|
|
|
// On ne récupère un point de chance que si aucun appel à la chance dans la journée
|
|
let utilisationChance = duplicate(this.getFlag('foundryvtt-reve-de-dragon', 'utilisationChance') ?? false);
|
|
if ( !utilisationChance ) {
|
|
await this.chanceActuelleIncDec(1);
|
|
}
|
|
await this.unsetFlag('foundryvtt-reve-de-dragon', 'utilisationChance'); // Nouveau jour, suppression du flag
|
|
|
|
this.transformerStress();
|
|
await this.retourSeuilDeReve(message);
|
|
message.content = `A la fin Chateau Dormant, ${message.content}<br>Un nouveau jour se lève`;
|
|
ChatMessage.create(message);
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async _recupererBlessures(message, type, liste, moindres) {
|
|
let count = 0;
|
|
const definitions = RdDUtility.getDefinitionsBlessures();
|
|
let definition = definitions.find(d => d.type == type);
|
|
for (let blessure of liste) {
|
|
if (blessure.jours >= definition.facteur) {
|
|
let rolled = await this._jetRecuperationConstitution(Misc.toInt(blessure.soins_complets), message);
|
|
blessure.soins_complets = 0;
|
|
if (rolled.isSuccess && this._retrograderBlessure(type, blessure, moindres)) {
|
|
message.content += ` -- une blessure ${type} cicatrise`;
|
|
count++;
|
|
}
|
|
else if (rolled.isETotal) {
|
|
message.content += ` -- une blessure ${type} s'infecte (temps de guérison augmenté de ${definition.facteur} jours, perte de vie)`;
|
|
blessure.jours = 0;
|
|
await this.santeIncDec("vie", -1);
|
|
}
|
|
else {
|
|
message.content += ` -- une blessure ${type} reste stable`;
|
|
}
|
|
}
|
|
else {
|
|
blessure.jours++;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
_retrograderBlessure(type, blessure, blessuresMoindres) {
|
|
if (type != "legere") {
|
|
let retrograde = blessuresMoindres.find(b => !b.active);
|
|
if (!retrograde) {
|
|
return false;
|
|
}
|
|
mergeObject(retrograde, { "active": true, "premiers_soins": 0, "soins_complets": 0, "jours": 0, "loc": blessure.loc });
|
|
}
|
|
this._supprimerBlessure(blessure);
|
|
return true;
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
_supprimerBlessure(blessure) {
|
|
mergeObject(blessure, { "active": false, "premiers_soins": 0, "soins_complets": 0, "jours": 0, "loc": "" });
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async _recupererVie(message) {
|
|
let blessures = [].concat(this.data.data.blessures.legeres.liste).concat(this.data.data.blessures.graves.liste).concat(this.data.data.blessures.critiques.liste);
|
|
let nbBlessures = blessures.filter(b => b.active);
|
|
let vieManquante = this.data.data.sante.vie.max - this.data.data.sante.vie.value;
|
|
if (nbBlessures == 0 && vieManquante > 0) {
|
|
let bonusSoins = 0;
|
|
for (let b of blessures) {
|
|
bonusSoins = Math.max(bonusSoins, Misc.toInt(b.soins_complets));
|
|
}
|
|
let rolled = await this._jetRecuperationConstitution(bonusSoins, message)
|
|
if (rolled.isSuccess) {
|
|
const gain = Math.min(rolled.isPart ? 2 : 1, vieManquante);
|
|
message.content += " -- récupération de vie: " + gain;
|
|
await this.santeIncDec("vie", gain);
|
|
}
|
|
else if (rolled.isETotal) {
|
|
message.content += " -- perte de vie: 1";
|
|
await this.santeIncDec("vie", -1);
|
|
}
|
|
else {
|
|
message.content += " -- vie stationnaire ";
|
|
}
|
|
}
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async _jetRecuperationConstitution(bonusSoins, message = undefined) {
|
|
let difficulte = Misc.toInt(bonusSoins) + Math.min(0, this.data.data.sante.vie.value - this.data.data.sante.vie.max);
|
|
let rolled = await RdDResolutionTable.roll(this.data.data.carac.constitution.value, difficulte);
|
|
if (message) {
|
|
message.content += RdDResolutionTable.explain(rolled).replace(/Jet :/, "Constitution :");
|
|
}
|
|
return rolled;
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async remiseANeuf() {
|
|
let message = {
|
|
whisper: ChatUtility.getWhisperRecipientsAndGMs(this.name),
|
|
content: "Remise à neuf de " + this.name
|
|
};
|
|
if (this.isEntiteCauchemar()) {
|
|
await this.santeIncDec("endurance", this.data.data.sante.endurance.max - this.data.data.sante.endurance.value);
|
|
}
|
|
else {
|
|
|
|
if (this.data.data.blessures) {
|
|
const blessures = duplicate(this.data.data.blessures);
|
|
for (let listeBlessures of [blessures.legeres.liste, blessures.graves.liste, blessures.critiques.liste]) {
|
|
for (let blessure of listeBlessures) {
|
|
this._supprimerBlessure(blessure);
|
|
}
|
|
}
|
|
await this.update({ "data.blessures": blessures });
|
|
}
|
|
if (this.isPersonnage()) {
|
|
await this.setEthylisme(1);
|
|
}
|
|
|
|
await this.santeIncDec("vie", this.data.data.sante.vie.max - this.data.data.sante.vie.value);
|
|
await this.santeIncDec("endurance", this.data.data.sante.endurance.max - this.data.data.sante.endurance.value);
|
|
if (this.data.data.sante.fatigue) {
|
|
let fatigue = duplicate(this.data.data.sante.fatigue)
|
|
fatigue.value = 0;
|
|
await this.update({ "data.sante.fatigue": fatigue });
|
|
}
|
|
}
|
|
ChatMessage.create(message);
|
|
}
|
|
|
|
|
|
/* -------------------------------------------- */
|
|
async dormir(heures = 1) {
|
|
let message = {
|
|
whisper: ChatUtility.getWhisperRecipientsAndGMs(this.name),
|
|
content: `${this.name}: Vous dormez ${heures == 1 ? 'une': heures} heure${heures == 1 ? '': 's'}.`
|
|
};
|
|
await this.recupereEndurance(message);
|
|
for (let i = 0; i < heures; i++) {
|
|
await this._recupererEthylisme(message);
|
|
await this.recupererFatigue(message);
|
|
await this.recuperationReve(message);
|
|
if (EffetsDraconiques.isDonDoubleReve(this)) {
|
|
await this.recuperationReve(message);
|
|
}
|
|
}
|
|
ChatMessage.create(message);
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async _recupererEthylisme(message) {
|
|
let ethylisme = duplicate(this.data.data.compteurs.ethylisme);
|
|
ethylisme.nb_doses = 0;
|
|
ethylisme.jet_moral = false;
|
|
if (ethylisme.value < 1) {
|
|
ethylisme.value = Math.min(ethylisme.value + 1, 1);
|
|
if (ethylisme.value <= 0) {
|
|
message.content += `Vous dégrisez un peu (${RdDUtility.getNomEthylisme(ethylisme.value)}). `;
|
|
}
|
|
}
|
|
await this.update({ "data.compteurs.ethylisme": ethylisme });
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async recupereEndurance(message) {
|
|
const manquant = this._computeEnduranceMax() - this.data.data.sante.endurance.value;
|
|
if (manquant > 0) {
|
|
await this.santeIncDec("endurance", manquant);
|
|
message.content += "Vous récuperez " + manquant + " points d'endurance. ";
|
|
}
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async recupererFatigue(message) {
|
|
let fatigue = duplicate(this.data.data.sante.fatigue)
|
|
const fatigueMin = this._computeFatigueMin();
|
|
if (fatigue.value <= fatigueMin) {
|
|
message.content += "Vous êtes déjà reposé. ";
|
|
return;
|
|
}
|
|
fatigue.value = Math.max(fatigueMin, this._calculRecuperationSegment(fatigue.value));
|
|
console.log("recupererFatigue", fatigue)
|
|
await this.update({ "data.sante.fatigue": fatigue });
|
|
if (fatigue.value == 0) {
|
|
message.content += "Vous êtes complêtement reposé. ";
|
|
}
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
_calculRecuperationSegment(actuel) {
|
|
const segments = RdDUtility.getSegmentsFatigue(this.data.data.sante.endurance.max);
|
|
let cumul = 0;
|
|
let i;
|
|
for (i = 0; i < 11; i++) {
|
|
cumul += segments[i];
|
|
let diff = cumul - actuel;
|
|
if (diff >= 0) {
|
|
const limit2Segments = Math.floor(segments[i] / 2);
|
|
if (diff > limit2Segments && i > 0) {
|
|
cumul -= segments[i - 1]; // le segment est à moins de la moitié, il est récupéré
|
|
}
|
|
cumul -= segments[i];
|
|
break;
|
|
}
|
|
};
|
|
return cumul;
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async recuperationReve(message) {
|
|
const seuil = this.data.data.reve.seuil.value;
|
|
const reveActuel = this.getReveActuel();
|
|
if (reveActuel >= seuil) {
|
|
message.content += `Vous avez suffisament rêvé, au delà de votre seuil. `;
|
|
}
|
|
else {
|
|
let deRecuperation = (await DeDraconique.ddr("selfroll")).total;
|
|
console.log("recuperationReve", deRecuperation);
|
|
if (deRecuperation >= 7) {
|
|
// Rêve de Dragon !
|
|
message.content += `Vous faites un <strong>Rêve de Dragon</strong> de ${deRecuperation} Points de rêve! `;
|
|
await this.combattreReveDeDragon(deRecuperation);
|
|
}
|
|
else {
|
|
message.content += `Vous récupérez ${deRecuperation} Points de rêve. `;
|
|
await this.reveActuelIncDec(deRecuperation);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async retourSeuilDeReve(message) {
|
|
const seuil = this.data.data.reve.seuil.value;
|
|
const reveActuel = this.getReveActuel();
|
|
if (reveActuel > seuil) {
|
|
message.content += `<br>Votre rêve redescend vers son seuil naturel (${seuil}, nouveau rêve actuel ${(reveActuel - 1)})`;
|
|
await this.reveActuelIncDec(-1);
|
|
}
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async combattreReveDeDragon(force) {
|
|
let rollData = {
|
|
actor: this,
|
|
competence: duplicate(this.getBestDraconic()),
|
|
canClose: false,
|
|
rencontre: duplicate(TMRRencontres.getRencontre('rdd')),
|
|
tmr: true,
|
|
use: { libre: false, conditions: false },
|
|
forceCarac: { 'reve-actuel': { label: "Rêve Actuel", value: this.getReveActuel() } }
|
|
}
|
|
rollData.rencontre.force = force;
|
|
rollData.competence.data.defaut_carac = 'reve-actuel';
|
|
|
|
const dialog = await RdDRoll.create(this, rollData,
|
|
{
|
|
html: 'systems/foundryvtt-reve-de-dragon/templates/dialog-roll-reve-de-dragon.html',
|
|
options: { height: 400 }
|
|
},
|
|
{
|
|
name: 'maitrise',
|
|
label: 'Maîtriser le Rêve de Dragon',
|
|
callbacks: [
|
|
this.createCallbackExperience(),
|
|
{ action: r => this.resultCombatReveDeDragon(r) }
|
|
]
|
|
}
|
|
);
|
|
dialog.render(true);
|
|
}
|
|
|
|
async resultCombatReveDeDragon(rollData) {
|
|
rollData.queues = [];
|
|
if (rollData.rolled.isEchec) {
|
|
rollData.queues.push(await this.ajouterQueue());
|
|
}
|
|
if (rollData.rolled.isETotal) {
|
|
rollData.queues.push(await this.ajouterQueue());
|
|
}
|
|
if (rollData.rolled.isSuccess) {
|
|
await this.updatePointDeSeuil();
|
|
await this.reveActuelIncDec(rollData.rencontre.force);
|
|
}
|
|
if (rollData.rolled.isPart) {
|
|
// TODO: un dialogue pour demander le type de tête?
|
|
rollData.tete = true;
|
|
}
|
|
rollData.poesie = Poetique.getExtrait();
|
|
|
|
ChatMessage.create({
|
|
whisper: ChatUtility.getWhisperRecipientsAndGMs(game.user.name),
|
|
content: await renderTemplate(`systems/foundryvtt-reve-de-dragon/templates/chat-resultat-reve-de-dragon.html`, rollData)
|
|
});
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async appliquerReveDeDragon(roll, force) {
|
|
let message = "";
|
|
if (roll.isSuccess) {
|
|
message += "<br>Vous gagnez " + force + " points de Rêve";
|
|
await this.updatePointDeSeuil();
|
|
await this.reveActuelIncDec(force);
|
|
}
|
|
if (roll.isPart) {
|
|
// TODO: Dialog pour choix entre HR opu général?
|
|
message += "<br>Vous gagnez une Tête de dragon: Demander à votre MJ d'effectuer un tirage sur la table des Hauts Rêvants ou des Vrais Rêvants, selon votre choix.";
|
|
}
|
|
if (roll.isEchec) {
|
|
message += "<br>Vous subissez une Queue de Dragon: " + await this.ajouterQueue();
|
|
}
|
|
if (roll.isETotal) {
|
|
message += "<br>A cause de votre échec total, vous subissez une deuxième Queue de Dragon: " + await this.ajouterQueue();
|
|
}
|
|
ChatMessage.create({
|
|
whisper: ChatUtility.getWhisperRecipientsAndGMs(this.name),
|
|
content: message
|
|
});
|
|
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async sortMisEnReserve(rollData, sort) {
|
|
let reserve = duplicate(this.data.data.reve.reserve);
|
|
reserve.list.push({ coord: rollData.tmr.coord, sort: sort, draconic: duplicate(rollData.competence) });
|
|
await this.update({ "data.reve.reserve": reserve });
|
|
this.currentTMR.updateTokens();
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async updateCarac(caracName, caracValue) {
|
|
let caracpath = "data.carac." + caracName + ".value"
|
|
if (caracName == "force") {
|
|
if (Number(caracValue) > this.getTaille() + 4) {
|
|
ui.notifications.warn("Votre FORCE doit être au maximum de TAILLE+4");
|
|
return;
|
|
}
|
|
}
|
|
if (caracName == "reve") {
|
|
if (caracValue > Misc.toInt(this.data.data.reve.seuil.value)) {
|
|
this.setPointsDeSeuil(caracValue);
|
|
}
|
|
}
|
|
if (caracName == "chance") {
|
|
if (caracValue > Misc.toInt(this.data.data.compteurs.chance.value)) {
|
|
this.setPointsDeChance(caracValue);
|
|
}
|
|
}
|
|
await this.update({ [caracpath]: caracValue });
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async updateCaracXP(caracName, caracXP) {
|
|
if (caracName == 'Taille') {
|
|
return;
|
|
}
|
|
let caracpath = "data.carac." + caracName + ".xp";
|
|
await this.update({ [caracpath]: caracXP });
|
|
this.checkCaracXP(caracName);
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async updateCreatureCompetence(compName, fieldName, compValue) {
|
|
let comp = this.getCompetence(compName);
|
|
//console.log(comp);
|
|
if (comp) {
|
|
const update = { _id: comp._id }
|
|
if (fieldName == "niveau")
|
|
update['data.niveau'] = compValue;
|
|
else if (fieldName == "dommages")
|
|
update['data.dommages'] = compValue;
|
|
else
|
|
update['data.carac_value'] = compValue;
|
|
//console.log(update);
|
|
const updated = await this.updateEmbeddedEntity("OwnedItem", update); // Updates one EmbeddedEntity
|
|
}
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async updateCompetence(compName, compValue) {
|
|
let comp = this.getCompetence(compName);
|
|
if (comp) {
|
|
let troncList = RdDItemCompetence.isTronc(compName);
|
|
let maxNiveau = compValue;
|
|
if (troncList) {
|
|
let message = "Vous avez modifié une compétence 'tronc'. Vérifiez que les compétences suivantes évoluent ensemble jusqu'au niveau 0 : ";
|
|
for (let troncName of troncList) {
|
|
message += "<br>" + troncName;
|
|
}
|
|
ChatMessage.create({
|
|
whisper: ChatMessage.getWhisperRecipients(game.user.name),
|
|
content: message
|
|
});
|
|
}
|
|
const update = { _id: comp._id, 'data.niveau': maxNiveau };
|
|
const updated = await this.updateEmbeddedEntity("OwnedItem", update); // Updates one EmbeddedEntity
|
|
} else {
|
|
console.log("Competence not found", compName);
|
|
}
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async updateCompetenceXP(compName, compValue) {
|
|
let comp = this.getCompetence(compName);
|
|
if (comp) {
|
|
this.checkCompetenceXP(compName, compValue);
|
|
const update = { _id: comp._id, 'data.xp': compValue };
|
|
const updated = await this.updateEmbeddedEntity("OwnedItem", update); // Updates one EmbeddedEntity
|
|
} else {
|
|
console.log("Competence not found", compName);
|
|
}
|
|
RdDUtility.checkThanatosXP(compName);
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async updateCompetenceXPSort(compName, compValue) {
|
|
let comp = this.getCompetence(compName);
|
|
if (comp) {
|
|
const update = { _id: comp._id, 'data.xp_sort': compValue };
|
|
const updated = await this.updateEmbeddedEntity("OwnedItem", update); // Updates one EmbeddedEntity
|
|
} else {
|
|
console.log("Competence not found", compName);
|
|
}
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async updateCompetenceArchetype(compName, compValue) {
|
|
let comp = this.getCompetence(compName);
|
|
if (comp) {
|
|
const update = { _id: comp._id, 'data.niveau_archetype': compValue };
|
|
const updated = await this.updateEmbeddedEntity("OwnedItem", update); // Updates one EmbeddedEntity
|
|
} else {
|
|
console.log("Competence not found", compName);
|
|
}
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async updateCompteurValue(fieldName, fieldValue) {
|
|
//console.log("Update", fieldName, fieldValue);
|
|
let compteurs = duplicate(this.data.data.compteurs);
|
|
compteurs[fieldName].value = fieldValue;
|
|
await this.update({ "data.compteurs": compteurs });
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async updateProtectionValue(fieldName, fieldValue) {
|
|
let attributs = duplicate(this.data.data.attributs);
|
|
attributs[fieldName].value = fieldValue;
|
|
await this.update({ "data.attributs": attributs });
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
validateConteneur(itemId, conteneurId) {
|
|
let conteneurDest = this.items.find(conteneur => conteneurId == conteneur._id); // recup conteneur
|
|
let conteneurSrc = this.items.find(conteneur => itemId == conteneur._id && conteneur.type == 'conteneur');
|
|
if (conteneurSrc) { // Si c'est un conteneur, il faut vérifier qu'on ne le déplace pas vers un sous-conteneur lui appartenant
|
|
for (let id of conteneurSrc.data.data.contenu) {
|
|
let subObjet = this.items.find(subobj => id == subobj._id);
|
|
if (subObjet && subObjet._id == conteneurDest._id) {
|
|
ui.notifications.warn("Impossible de déplacer un conteneur parent dans son fils !");
|
|
return false; // Loop detected !
|
|
}
|
|
if (subObjet && subObjet.type == 'conteneur') {
|
|
return this.validateConteneur(subObjet._id, conteneurId);
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
getRecursiveEnc(objet) {
|
|
let sumEnc = 0;
|
|
if (objet.type == 'conteneur') {
|
|
for (let id of objet.data.data.contenu) {
|
|
let subobjet = this.items.find(objet => (id == objet._id));
|
|
if (subobjet) {
|
|
if (subobjet && subobjet.type == 'conteneur') {
|
|
sumEnc += this.getRecursiveEnc(subobjet);
|
|
} else {
|
|
sumEnc += Number(subobjet.data.data.encombrement) * Number(subobjet.data.data.quantite);
|
|
}
|
|
}
|
|
}
|
|
sumEnc += Number(objet.data.data.encombrement)
|
|
} else {
|
|
sumEnc += Number(objet.data.data.encombrement) * Number(objet.data.data.quantite);
|
|
}
|
|
return sumEnc;
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
/** Teste si le conteneur de destination a suffisament de capacité
|
|
* pour recevoir le nouvel objet
|
|
*/
|
|
testConteneurCapacite(itemId, conteneurId) {
|
|
if (!conteneurId) return true; // pas de conteneur (porté sur soi), donc toujours OK.
|
|
let conteneur = this.items.find(conteneur => conteneurId == conteneur._id); // recup conteneur
|
|
//console.log("Conteneur trouvé : ", conteneur);
|
|
if (conteneur && conteneur.type == "conteneur") {
|
|
// Calculer le total actuel des contenus
|
|
let encContenu = this.getRecursiveEnc(conteneur) - Number(conteneur.data.data.encombrement);
|
|
let nouvelObjet = this.items.find(objet => (itemId == objet._id)); // On chope l'objet
|
|
let newEnc = (nouvelObjet) ? this.getRecursiveEnc(nouvelObjet) : 0; // Calculer le total actuel du nouvel objet
|
|
//console.log( currentEnc, newEnc, conteneur.data.data.capacite, conteneur.name);
|
|
if (nouvelObjet && ((encContenu + newEnc) > Number(conteneur.data.data.capacite))) {
|
|
ui.notifications.warn("Capacité d'encombrement insuffisante dans le conteneur !");
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
buildSubConteneurObjetList(conteneurId, deleteList) {
|
|
let conteneur = this.items.find(conteneur => conteneurId == conteneur._id); // recup conteneur
|
|
if (conteneur && conteneur.type == 'conteneur') { // Si présent
|
|
for (let subId of conteneur.data.data.contenu) {
|
|
let subObj = this.items.find(subobjet => subId == subobjet._id); // recup conteneur
|
|
if (subObj && subObj.type == 'conteneur') {
|
|
this.buildSubConteneurObjetList(subId, deleteList);
|
|
}
|
|
if (subObj) // Robust...
|
|
deleteList.push({ id: subId, conteneurId: conteneurId });
|
|
}
|
|
}
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async deleteAllConteneur(itemId) {
|
|
let list = [];
|
|
list.push({ id: itemId, conteneurId: undefined }); // Init list
|
|
this.buildSubConteneurObjetList(itemId, list);
|
|
//console.log("List to delete", list);
|
|
for (let item of list) {
|
|
await this.deleteOwnedItem(item.id);
|
|
}
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
/** Supprime un item d'un conteneur, sur la base
|
|
* de leurs ID */
|
|
async enleverDeConteneur(itemId, conteneurId) {
|
|
if (!conteneurId) return; // pas de conteneur (porté sur soi)
|
|
let conteneur = this.items.find(conteneur => conteneurId == conteneur._id); // recup conteneur
|
|
if (conteneur) { // Si présent
|
|
let data2use = duplicate(conteneur.data);
|
|
//console.log("Suppression du conteneur1", conteneurId, itemId, conteneur.data.data.contenu);
|
|
let contenu = data2use.data.contenu;
|
|
let index = contenu.indexOf(itemId);
|
|
while (index >= 0) { // Force cleanup, itemId is unique
|
|
contenu.splice(index, 1);
|
|
index = contenu.indexOf(itemId);
|
|
}
|
|
await this.updateEmbeddedEntity("OwnedItem", data2use);
|
|
}
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
/** Ajoute un item dans un conteneur, sur la base
|
|
* de leurs ID */
|
|
async ajouterAConteneur(itemId, conteneurId) {
|
|
if (!conteneurId) return; // pas de conteneur (porté sur soi)
|
|
let conteneur = this.items.find(conteneur => conteneurId == conteneur._id);
|
|
if (conteneur && conteneur.type == 'conteneur') {
|
|
let data2use = duplicate(conteneur.data);
|
|
data2use.data.contenu.push(itemId);
|
|
await this.updateEmbeddedEntity("OwnedItem", data2use);
|
|
}
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
/** Fonction de remise à plat de l'équipement (ie vide les champs 'contenu') */
|
|
async nettoyerConteneurs() {
|
|
let conteneurList = this.items.filter(conteneur => conteneur.type == 'conteneur');
|
|
let conteneurFixedList = [];
|
|
for (let conteneur of conteneurList) {
|
|
if (conteneur.data.data.contenu.length > 0) {
|
|
conteneurFixedList.push({ _id: conteneur._id, 'data.contenu': [] });
|
|
}
|
|
}
|
|
if (conteneurFixedList.length > 0)
|
|
await this.updateOwnedItem(conteneurFixedList);
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async moveItemsBetweenActors(itemId, sourceActorId) {
|
|
let itemsList = []
|
|
let sourceActor = game.actors.get(sourceActorId);
|
|
itemsList.push({ id: itemId, conteneurId: undefined }); // Init list
|
|
sourceActor.buildSubConteneurObjetList(itemId, itemsList); // Get itemId list
|
|
|
|
let itemMap = {};
|
|
for (let item of itemsList) {
|
|
let srcItem = sourceActor.data.items.find(subItem => subItem._id == item.id);
|
|
let newItem = await this.createOwnedItem(duplicate(srcItem));
|
|
console.log('New object', newItem, srcItem);
|
|
itemMap[srcItem._id] = newItem._id; // Pour garder le lien ancien / nouveau
|
|
}
|
|
for (let item of itemsList) { // Second boucle pour traiter la remise en conteneurs
|
|
// gestion conteneur/contenu
|
|
if (item.conteneurId) { // l'Objet était dans un conteneur
|
|
let newConteneurId = itemMap[item.conteneurId]; // Get conteneur
|
|
let newConteneur = this.data.items.find(subItem => subItem._id == newConteneurId);
|
|
|
|
let newItemId = itemMap[item.id]; // Get newItem
|
|
|
|
console.log('New conteneur filling!', newConteneur, newItemId, item);
|
|
let contenu = duplicate(newConteneur.data.contenu);
|
|
contenu.push(newItemId);
|
|
await this.updateOwnedItem({ _id: newConteneurId, 'data.contenu': contenu });
|
|
}
|
|
}
|
|
for (let item of itemsList) {
|
|
await sourceActor.deleteOwnedItem(item.id);
|
|
}
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
detectSurEncombrement() {
|
|
let maxEnc = 0;
|
|
if (this.data.type == 'vehicule')
|
|
maxEnc = this.data.data.capacite_encombrement;
|
|
else
|
|
maxEnc = this.data.data.attributs.encombrement.value;
|
|
let diffEnc = Number(this.encTotal) - Number(maxEnc);
|
|
return Math.max(0, Math.ceil(diffEnc));
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async computeEncombrementTotalEtMalusArmure() {
|
|
let encTotal = 0;
|
|
|
|
let malusArmureData = (this.data.data.attributs && this.data.data.attributs.malusarmure) ? duplicate(this.data.data.attributs.malusarmure) : {};
|
|
let newMalusArmure = 0;
|
|
for (const item of this.data.items) {
|
|
if (item.type == 'armure' && item.data.equipe) { // Armure équipée, intégration du malus armure total
|
|
newMalusArmure += item.data.malus;
|
|
}
|
|
// Calcul encombrement
|
|
if (item.data && item.data.encombrement != undefined) {
|
|
if (!Number(item.data.encombrement)) item.data.encombrement = 0; // Auto-fix
|
|
if (item.data.quantite == undefined) item.data.quantite = 1; // Auto-fix
|
|
if (item.data.quantite < 0) item.data.quantite = 0; // Auto-fix
|
|
item.data.encTotal = Number(item.data.encombrement) * Number(item.data.quantite);
|
|
//console.log("Enc:", item.name, item.data.encombrement, item.data.quantite, item.data.encTotal);
|
|
encTotal += item.data.encTotal;
|
|
} else {
|
|
item.data.encTotal = 0; // Force default enc
|
|
}
|
|
}
|
|
// Mise à jour valeur totale et états
|
|
this.encTotal = encTotal;
|
|
this.detectSurEncombrement();
|
|
// Mise à jour éventuelle du malus armure
|
|
if (this.data.data.attributs && this.data.data.attributs.malusarmure && newMalusArmure != malusArmureData.value) {
|
|
malusArmureData.value = newMalusArmure;
|
|
await this.update({ "data.attributs.malusarmure": malusArmureData });
|
|
}
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async computePrixTotalEquipement() {
|
|
let prixTotalEquipement = 0;
|
|
|
|
// prix total de l'équipement est la somme du cout de chaque équipement multiplié par sa quantité.
|
|
for (const item of this.data.items) {
|
|
if (item.data && item.data.cout != undefined) {
|
|
if (!Number(item.data.cout)) item.data.cout = 0; // Auto-fix
|
|
if (item.data.quantite == undefined) item.data.quantite = 1; // Auto-fix
|
|
if (item.data.cout < 0) item.data.cout = 0; // Auto-fix
|
|
prixTotalEquipement += Number(item.data.cout) * Number(item.data.quantite);
|
|
}
|
|
}
|
|
// Mise à jour valeur totale de l'équipement
|
|
this.prixTotalEquipement = prixTotalEquipement;
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
computeResumeBlessure(blessures = this.data.data.blessures) {
|
|
let nbLegeres = this.countBlessures(blessures.legeres.liste);
|
|
let nbGraves = this.countBlessures(blessures.graves.liste);
|
|
let nbCritiques = this.countBlessures(blessures.critiques.liste);
|
|
|
|
let resume = "Blessures:";
|
|
if (nbCritiques > 0 || nbGraves > 0 || nbLegeres > 0) {
|
|
if (nbLegeres > 0) {
|
|
resume += " " + nbLegeres + " légères";
|
|
}
|
|
if (nbGraves > 0) {
|
|
if (nbLegeres > 0)
|
|
resume += ",";
|
|
resume += " " + nbGraves + " graves";
|
|
}
|
|
if (nbCritiques > 0) {
|
|
if (nbGraves > 0 || nbLegeres > 0)
|
|
resume += ",";
|
|
resume += " une CRITIQUE !";
|
|
}
|
|
}
|
|
else {
|
|
resume += " aucune";
|
|
}
|
|
return resume;
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
computeEtatGeneral() {
|
|
let data = this.data.data;
|
|
// Pas d'état général pour les entités forçage à 0
|
|
if (this.data.type == 'entite') {
|
|
data.compteurs.etat.value = 0;
|
|
return;
|
|
}
|
|
// Pour les autres
|
|
let state = - (data.sante.vie.max - data.sante.vie.value);
|
|
if (data.sante.fatigue) {
|
|
// Creatures n'ont pas de fatigue
|
|
state += RdDUtility.currentFatigueMalus(data.sante.fatigue.value, data.sante.endurance.max);
|
|
}
|
|
// Ajout de l'éthylisme
|
|
state += Math.min(0, (data.compteurs.ethylisme?.value ?? 0));
|
|
|
|
data.compteurs.etat.value = state;
|
|
if (data.compteurs && data.compteurs.surenc) {
|
|
data.compteurs.surenc.value = - this.detectSurEncombrement();
|
|
}
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async ajouterRefoulement(value = 1) {
|
|
let ret = "none";
|
|
|
|
let refoulement = duplicate(this.data.data.reve.refoulement);
|
|
refoulement.value = refoulement.value + value;
|
|
|
|
let total = new Roll("1d20").roll().total;
|
|
if (total <= refoulement.value) {
|
|
refoulement.value = 0;
|
|
this.ajouterSouffle({ chat: true });
|
|
ret = "souffle";
|
|
}
|
|
|
|
await this.update({ "data.reve.refoulement": refoulement });
|
|
return ret;
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async ajouterSouffle(options = { chat: false }) {
|
|
let souffle = await RdDRollTables.getSouffle();
|
|
await this.createOwnedItem(souffle);
|
|
if (options.chat) {
|
|
ChatMessage.create({
|
|
whisper: ChatUtility.getWhisperRecipientsAndGMs(game.user.name),
|
|
content: this.name + " subit un Souffle de Dragon : " + souffle.name
|
|
});
|
|
}
|
|
// TODO: fermeture cité
|
|
return souffle;
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async ajouterQueue(options = { chat: false }) {
|
|
let queue;
|
|
if (this.data.data.reve.reve.thanatosused) {
|
|
queue = await RdDRollTables.getOmbre();
|
|
let myReve = duplicate(this.data.data.reve.reve);
|
|
myReve.thanatosused = false;
|
|
await this.update({ "data.reve.reve": myReve } );
|
|
}
|
|
else {
|
|
queue = await RdDRollTables.getQueue();
|
|
}
|
|
await this.createOwnedItem(queue);
|
|
if (options.chat) {
|
|
ChatMessage.create({
|
|
whisper: ChatUtility.getWhisperRecipientsAndGMs(game.user.name),
|
|
content: this.name + " subit une Queue de Dragon : " + queue.name
|
|
});
|
|
}
|
|
return queue;
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async reinsertionAleatoire(raison) {
|
|
ChatMessage.create({
|
|
content: `${raison} : ré-insertion aléatoire.`,
|
|
whisper: ChatUtility.getWhisperRecipientsAndGMs(game.user.name)
|
|
});
|
|
const innaccessible = this.buildTMRInnaccessible();
|
|
let tmr = TMRUtility.getTMRAleatoire(tmr => !innaccessible.includes(tmr.coord) );
|
|
this.updateCoordTMR(tmr.coord);
|
|
return tmr;
|
|
}
|
|
|
|
buildTMRInnaccessible() {
|
|
const tmrInnaccessibles = this.data.items.filter(it => Draconique.isCaseTMR(it) &&
|
|
EffetsDraconiques.isInnaccessible(it));
|
|
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;
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async deleteTMRRencontreAtPosition() {
|
|
let rencontres = duplicate(this.getTMRRencontres());
|
|
let len = rencontres.list.length;
|
|
let i = 0;
|
|
//console.log("List", rencontres, len);
|
|
let newTable = [];
|
|
for (i = 0; i < len; i++) {
|
|
if (rencontres.list[i].coord != this.data.data.reve.tmrpos.coord)
|
|
newTable.push(rencontres.list[i]);
|
|
}
|
|
if (newTable.length != len) {
|
|
rencontres.list = newTable;
|
|
//console.log("Result: ", rencontres);
|
|
await this.update({ "data.reve.rencontre": rencontres });
|
|
}
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async addTMRRencontre(currentRencontre) {
|
|
let rencontres = duplicate(this.getTMRRencontres());
|
|
let len = rencontres.list.length;
|
|
let i = 0;
|
|
let already = false;
|
|
for (i = 0; i < len; i++) {
|
|
if (rencontres.list[i].coord == this.data.data.reve.tmrpos.coord)
|
|
already = true;
|
|
}
|
|
if (!already) {
|
|
rencontres.list.push(currentRencontre);
|
|
await this.update({ "data.reve.rencontre": rencontres });
|
|
}
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async deleteTMRRencontre(rencontreKey) {
|
|
let list = duplicate(this.data.data.reve.rencontre.list);
|
|
let newList = [];
|
|
for (let i = 0; i < list.length; i++) {
|
|
if ( i != rencontreKey)
|
|
newList.push( list[i]);
|
|
}
|
|
await this.update({ "data.reve.rencontre.list": newList });
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async updateCoordTMR(coord) {
|
|
let tmrPos = duplicate(this.data.data.reve.tmrpos);
|
|
tmrPos.coord = coord;
|
|
await this.update({ "data.reve.tmrpos": tmrPos });
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async reveActuelIncDec(value) {
|
|
let reve = duplicate(this.data.data.reve.reve);
|
|
reve.value = Math.max(reve.value + value, 0);
|
|
await this.update({ "data.reve.reve": reve });
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async updatePointDeSeuil(value = 1) {
|
|
const seuil = Misc.toInt(this.data.data.reve.seuil.value);
|
|
const reve = Misc.toInt(this.data.data.carac.reve.value);
|
|
if (seuil < reve) {
|
|
await this.setPointsDeSeuil(Math.min(seuil + value, reve));
|
|
}
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async setPointsDeSeuil(value) {
|
|
let seuil = duplicate(this.data.data.reve.seuil);
|
|
seuil.value = value;
|
|
await this.update({ "data.reve.seuil": seuil });
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async setPointsDeChance(value) {
|
|
let chance = duplicate(this.data.data.compteurs.chance);
|
|
chance.value = value;
|
|
await this.update({ "data.compteurs.chance": chance });
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
getSonne() {
|
|
return !this.isEntiteCauchemar() && (this.data.data.sante.sonne?.value ?? false);
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
getSonneRound() {
|
|
return !this.isEntiteCauchemar() && (this.data.data.sante.sonne?.round ?? false);
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async verifierSonneRound( round ) {
|
|
if ( this.getSonne() ) {
|
|
if ( round >= this.getSonneRound() + 1) {
|
|
await this.setSonne( false, -1 ); // Nettoyer l'état sonné
|
|
ChatMessage.create( { content: `${this.name} n'est plus sonné ce round !`} );
|
|
}
|
|
}
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async setSonne(sonne = true) {
|
|
if (this.isEntiteCauchemar()) {
|
|
return;
|
|
}
|
|
let round = (sonne && game.combat) ? game.combat.current.round : -1; // Sauvegarde du round de sonné en cas de combat
|
|
await this.setStatusSonne(sonne);
|
|
await this.setStateSonne(sonne, round);
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async setStateSonne(sonne, round = -1) {
|
|
if (this.isEntiteCauchemar()) {
|
|
return;
|
|
}
|
|
let sonneData = duplicate(this.data.data.sante.sonne);
|
|
sonneData.value = sonne;
|
|
sonneData.round = round;
|
|
await this.update({ "data.sante.sonne": sonneData });
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
getSConst() {
|
|
if (this.isEntiteCauchemar()) {
|
|
return 0;
|
|
}
|
|
return RdDUtility.calculSConst(this.data.data.carac.constitution.value);
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async testSiSonne(sante, endurance) {
|
|
const roll = new Roll("1d20").roll();
|
|
roll.showDice = true;
|
|
RdDDice.show(roll);
|
|
let result = {
|
|
roll: roll,
|
|
sonne: roll.total > endurance || roll.total == 20 // 20 is always a failure
|
|
}
|
|
if (roll.total == 1) {
|
|
let xp = Misc.toInt(this.data.data.carac.constitution.xp) + 1;
|
|
this.update({ "data.carac.constitution.xp": xp }); // +1 XP !
|
|
ChatMessage.create( { content: `${this.name} a obenu 1 sur son Jet d'Endurance et a gagné 1 point d'Expérience en Constitution. Ce point d'XP a été ajouté automatiquement).`});
|
|
}
|
|
if (result.sonne) {
|
|
|
|
await this.setSonne();
|
|
sante.sonne.value = true;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
countBlessures(blessuresListe) {
|
|
return blessuresListe.filter(b => b.active).length
|
|
}
|
|
/* -------------------------------------------- */
|
|
countBlessuresByName(name) {
|
|
return this.countBlessures(this.data.data.blessures[name].liste);
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async jetEndurance() {
|
|
let myRoll = new Roll("1d20").roll();
|
|
myRoll.showDice = true;
|
|
await RdDDice.show(myRoll);
|
|
|
|
let msgText = "Jet d'Endurance : " + myRoll.total + " / " + this.data.data.sante.endurance.value + "<br>";
|
|
if (myRoll.total == 1 || (myRoll.total != 20 && myRoll.total <= this.data.data.sante.endurance.value)) {
|
|
msgText += `${this.name} a réussi son Jet d'Endurance !`;
|
|
if (myRoll.total == 1) {
|
|
msgText += `et gagne 1 Point d'Experience en Constitution`;
|
|
let constit = duplicate(this.data.data.carac.constitution)
|
|
constit.xp += 1;
|
|
await this.update({ "data.carac.constitution": constit });
|
|
}
|
|
} else {
|
|
msgText += `${this.name} a échoué son Jet d'Endurance et devient Sonné`;
|
|
await this.setSonne();
|
|
}
|
|
const message = {
|
|
content: msgText,
|
|
whisper: ChatMessage.getWhisperRecipients(game.user.name)
|
|
};
|
|
ChatMessage.create(message);
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async jetVie() {
|
|
let myRoll = new Roll("1d20").roll();
|
|
myRoll.showDice = true;
|
|
await RdDDice.show(myRoll);
|
|
|
|
let msgText = "Jet de Vie : " + myRoll.total + " / " + this.data.data.sante.vie.value + "<br>";
|
|
if (myRoll.total <= this.data.data.sante.vie.value) {
|
|
msgText += "Jet réussi, pas de perte de point de vie (prochain jet dans 1 round pour 1 critique, SC minutes pour une grave)";
|
|
if (myRoll.total == 1) {
|
|
msgText += "La durée entre 2 jets de vie est multipliée par 20 (20 rounds pour une critique, SCx20 minutes pour une grave)";
|
|
}
|
|
} else {
|
|
msgText += "Jet échoué, vous perdez 1 point de vie";
|
|
await this.santeIncDec("vie", -1);
|
|
if (myRoll.total == 20) {
|
|
msgText += "Votre personnage est mort !!!!!";
|
|
}
|
|
}
|
|
const message = {
|
|
content: msgText,
|
|
whisper: ChatMessage.getWhisperRecipients(game.user.name)
|
|
};
|
|
ChatMessage.create(message);
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async santeIncDec(name, inc, isCritique = false) {
|
|
|
|
const sante = duplicate(this.data.data.sante);
|
|
let compteur = sante[name];
|
|
if (!compteur) {
|
|
return;
|
|
}
|
|
let result = {
|
|
sonne: false,
|
|
};
|
|
|
|
let minValue = name == "vie" ? -this.getSConst() - 1 : 0;
|
|
|
|
result.newValue = Math.max(minValue, Math.min(compteur.value + inc, compteur.max));
|
|
//console.log("New value ", inc, minValue, result.newValue);
|
|
let fatigue = 0;
|
|
if (name == "endurance" && !this.isEntiteCauchemar()) {
|
|
if (result.newValue == 0 && inc < 0 && !isCritique) { // perte endurance et endurance devient 0 (sauf critique) -> -1 vie
|
|
sante.vie.value--;
|
|
}
|
|
result.newValue = Math.max(0, result.newValue);
|
|
if (inc > 0) { // le max d'endurance s'applique seulement à la récupération
|
|
result.newValue = Math.min(result.newValue, this._computeEnduranceMax())
|
|
}
|
|
const perte = compteur.value - result.newValue;
|
|
if (perte > 1) {
|
|
// Peut-être sonné si 2 points d'endurance perdus d'un coup
|
|
const testIsSonne = await this.testSiSonne(sante, result.newValue);
|
|
result.sonne = testIsSonne.sonne;
|
|
result.jetEndurance = testIsSonne.roll.total;
|
|
} else if (inc > 0) {
|
|
await this.setSonne(false);
|
|
sante.sonne.value = false;
|
|
}
|
|
if (sante.fatigue && inc < 0) { // Each endurance lost -> fatigue lost
|
|
fatigue = perte;
|
|
}
|
|
}
|
|
compteur.value = result.newValue;
|
|
//console.log(name, inc, data.value, result.newValue, minValue, data.max);
|
|
// If endurance lost, then the same amount of fatigue cannot be recovered
|
|
if (sante.fatigue && fatigue > 0) {
|
|
sante.fatigue.value = Math.max(sante.fatigue.value + fatigue, this._computeFatigueMin());
|
|
}
|
|
await this.update({ "data.sante": sante });
|
|
if (this.isDead()) {
|
|
await this.addStatusEffectById('dead');
|
|
}
|
|
return result;
|
|
}
|
|
|
|
isDead() {
|
|
return !this.isEntiteCauchemar() && this.data.data.sante.vie.value < -this.getSConst()
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
_computeFatigueMin() {
|
|
return this.data.data.sante.endurance.max - this.data.data.sante.endurance.value;
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
_computeEnduranceMax() {
|
|
let blessures = this.data.data.blessures;
|
|
let diffVie = this.data.data.sante.vie.max - this.data.data.sante.vie.value;
|
|
let maxEndVie = this.data.data.sante.endurance.max - (diffVie * 2);
|
|
let nbGraves = this.countBlessures(blessures.graves.liste);
|
|
let nbCritiques = this.countBlessures(blessures.critiques.liste);
|
|
let maxEndGraves = Math.floor(this.data.data.sante.endurance.max / (2 * nbGraves));
|
|
let maxEndCritiques = nbCritiques > 0 ? 1 : this.data.data.sante.endurance.max;
|
|
return Math.max(0, Math.min(maxEndVie, maxEndGraves, maxEndCritiques));
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async manageBlessureFromSheet(bType, index, active) {
|
|
let bList = duplicate(this.data.data.blessures);
|
|
let blessure = bList[bType + "s"].liste[index];
|
|
blessure.active = !blessure.active;
|
|
if (!blessure.active) {
|
|
blessure.premiers_soins = 0;
|
|
blessure.soins_complets = 0;
|
|
blessure.jours = 0;
|
|
blessure.loc = "";
|
|
}
|
|
//console.log("Blessure update", bType, index, blessure, bList );
|
|
await this.update({ 'data.blessures': bList });
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async setDataBlessureFromSheet(bType, index, psoins, pcomplets, jours, loc) {
|
|
let bList = duplicate(this.data.data.blessures);
|
|
let blessure = bList[bType + "s"].liste[index];
|
|
blessure.premiers_soins = psoins;
|
|
blessure.soins_complets = pcomplets;
|
|
blessure.jours = jours;
|
|
blessure.loc = loc;
|
|
await this.update({ 'data.blessures': bList });
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async jetDeMoral(situation) {
|
|
let jetMoral = new Roll("1d20").roll();
|
|
RdDDice.show(jetMoral);
|
|
let moralActuel = Misc.toInt(this.data.data.compteurs.moral.value);
|
|
const difficulte = 10 + moralActuel;
|
|
const succes = jetMoral.total <= difficulte;
|
|
|
|
let ajustementMoral = this._calculAjustementMoral(succes, moralActuel, situation);
|
|
|
|
await this.moralIncDec(ajustementMoral);
|
|
ChatMessage.create({
|
|
whisper: ChatUtility.getWhisperRecipientsAndGMs(game.user.name),
|
|
content: `Jet de moral ${succes ? "réussi" : "manqué"} en situation ${situation} (${jetMoral.total}/${difficulte}), vous ${ajustementMoral > 0 ? "gagnez du moral" : ajustementMoral < 0 ? "perdez du moral" : "gardez votre moral"}`
|
|
});
|
|
return ajustementMoral;
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async moralIncDec(ajustementMoral) {
|
|
let compteurs = duplicate(this.data.data.compteurs);
|
|
compteurs.moral.value = Misc.toInt(compteurs.moral.value);;
|
|
|
|
if (ajustementMoral != 0) {
|
|
compteurs.moral.value += ajustementMoral;
|
|
if (compteurs.moral.value > 3) {
|
|
// exaltation
|
|
compteurs.moral.value--;
|
|
compteurs.exaltation.value = Misc.toInt(compteurs.exaltation.value) + 1;
|
|
}
|
|
if (compteurs.moral.value < -3) {
|
|
// dissolution
|
|
compteurs.moral.value++;
|
|
compteurs.dissolution.value = Misc.toInt(compteurs.dissolution.value) + 1;
|
|
}
|
|
await this.update({ 'data.compteurs': compteurs });
|
|
}
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
_calculAjustementMoral(succes, moral, situation) {
|
|
switch (situation) {
|
|
case 'heureuse': return succes ? 1 : 0;
|
|
case 'malheureuse': return succes ? 0 : -1;
|
|
case 'neutre':
|
|
if (succes && moral <= 0) return 1;
|
|
if (!succes && moral > 0) return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async setEthylisme(degre) {
|
|
let ethylisme = duplicate(this.data.data.compteurs.ethylisme);
|
|
ethylisme.value = degre;
|
|
ethylisme.nb_doses = 0;
|
|
if (degre == 1) {
|
|
ethylisme.jet_moral = false;
|
|
}
|
|
await this.update({ "data.compteurs.ethylisme": ethylisme });
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async ethylismeTest() {
|
|
let rollData = {
|
|
vieValue: this.data.data.sante.vie.value,
|
|
etat: this.getEtatGeneral() - Math.min(0, this.data.data.compteurs.ethylisme.value), // Pour les jets d'Ethylisme, on ignore le degré d'éthylisme (p.162)
|
|
diffNbDoses: -Number(this.data.data.compteurs.ethylisme.nb_doses || 0),
|
|
finalLevel: 0,
|
|
diffConditions: 0,
|
|
ajustementsConditions: CONFIG.RDD.ajustementsConditions,
|
|
forceAlcool: 0
|
|
}
|
|
let html = await renderTemplate('systems/foundryvtt-reve-de-dragon/templates/dialog-roll-ethylisme.html', rollData);
|
|
new RdDRollDialogEthylisme(html, rollData, this).render(true);
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async performEthylisme(rollData) {
|
|
let ethylisme = duplicate(this.data.data.compteurs.ethylisme);
|
|
|
|
// Je d'ethylisme
|
|
let rollEthylisme = await RdDResolutionTable.roll(rollData.vieValue, rollData.finalLevel);
|
|
let msgText = RdDResolutionTable.explain(rollEthylisme) + "<br>";
|
|
if (rollEthylisme.isSuccess) {
|
|
ethylisme.nb_doses++;
|
|
msgText += `Vous avez réussi votre jet d'éthylisme, vous avez consommé ${ethylisme.nb_doses} doses sans effet.`;
|
|
} else {
|
|
ethylisme.value = Math.max(ethylisme.value - 1, -7);
|
|
ethylisme.nb_doses = 0;
|
|
|
|
let enduranceLost = new Roll("1d6").roll().total;
|
|
await this.santeIncDec("endurance", -enduranceLost);
|
|
const ajustementEthylique = ethylisme.value;
|
|
// Qui a bu boira (p 164)
|
|
let rollVolonte = await RdDResolutionTable.roll(this.data.data.carac.volonte.value, Math.min(ajustementEthylique, 0) + this.data.data.compteurs.moral.value);
|
|
msgText += `Vous avez échoué à votre jet d'éthylisme, vous êtes maintenant ${RdDUtility.getNomEthylisme(ajustementEthylique)} (${ajustementEthylique}).`
|
|
msgText += "<br>" + RdDResolutionTable.explain(rollVolonte) + "<br>";
|
|
msgText += "Qui a bu boira : " + (rollVolonte.isSuccess
|
|
? "vous êtes libre de continuer à boire ou pas."
|
|
: "vous avez une envie irrépréssible de reprendre un verre.");
|
|
}
|
|
|
|
ChatMessage.create({
|
|
content: msgText,
|
|
whisper: ChatUtility.getWhisperRecipientsAndGMs(game.user.name)
|
|
});
|
|
|
|
if (rollEthylisme.isEchec) {
|
|
await this._jetDeMoralEthylique(ethylisme);
|
|
}
|
|
await this.update({ 'data.compteurs.ethylisme': ethylisme });
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async _jetDeMoralEthylique(ethylisme) {
|
|
if (ethylisme.value >= -1 && !ethylisme.jet_moral) {
|
|
let adjust = await this.jetDeMoral('heureuse');
|
|
if (adjust > 0 || ethylisme.value == -1) {
|
|
ethylisme.jet_moral = true;
|
|
}
|
|
if (ethylisme.value == -1 && adjust <= 0) {
|
|
// alcool triste
|
|
ChatMessage.create({
|
|
content: "Décidément, vous avez l'alcool triste, vous perdez finalement un point de moral!",
|
|
whisper: ChatUtility.getWhisperRecipientsAndGMs(game.user.name)
|
|
});
|
|
this.moralIncDec(-1);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async transformerStress() {
|
|
const stress = Misc.toInt(this.data.data.compteurs.stress.value);
|
|
if (stress <= 0) {
|
|
return;
|
|
}
|
|
|
|
const stressRoll = await this._stressRoll(this.getReveActuel());
|
|
|
|
const conversion = Math.floor(stress * stressRoll.factor / 100);
|
|
let dissolution = Math.max(0, Misc.toInt(this.data.data.compteurs.dissolution.value));
|
|
let exaltation = Math.max(0, Misc.toInt(this.data.data.compteurs.exaltation.value));
|
|
const annule = Math.min(dissolution, exaltation);
|
|
dissolution -= annule;
|
|
exaltation -= annule;
|
|
const perteDissolution = Math.max(0, Math.min(dissolution, conversion));
|
|
|
|
let stressRollData = {
|
|
alias: this.name,
|
|
selectedCarac: this.data.data.carac.reve,
|
|
rolled: stressRoll,
|
|
stress: stress,
|
|
perte: Math.min(conversion, stress),
|
|
convertis: conversion - perteDissolution,
|
|
xp: conversion - perteDissolution + exaltation,
|
|
dissolution: dissolution,
|
|
exaltation: exaltation
|
|
};
|
|
|
|
ChatMessage.create({
|
|
whisper: ChatUtility.getWhisperRecipientsAndGMs(game.user.name),
|
|
content: await renderTemplate(`systems/foundryvtt-reve-de-dragon/templates/chat-resultat-transformer-stress.html`, stressRollData)
|
|
});
|
|
|
|
let compteurs = duplicate(this.data.data.compteurs);
|
|
compteurs.stress.value = Math.max(stress - stressRollData.perte - 1, 0);
|
|
compteurs.experience.value += stressRollData.xp;
|
|
compteurs.dissolution.value = dissolution - perteDissolution;
|
|
compteurs.exaltation.value = 0;
|
|
await this.update({ "data.compteurs": compteurs });
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async _stressRoll(reveActuel) {
|
|
let result = await RdDResolutionTable.roll(reveActuel, 0);
|
|
if (result.isPart) {
|
|
result.second = await RdDResolutionTable.roll(reveActuel, 0);
|
|
}
|
|
result.factor = this._getFacteurStress(result);
|
|
return result;
|
|
}
|
|
|
|
_getFacteurStress(stressRoll) {
|
|
switch (stressRoll.code) {
|
|
case "sign": return 75;
|
|
case "norm": return 50;
|
|
case "echec": return 20;
|
|
case "epart": return 10;
|
|
case "etotal": return 0;
|
|
case "part":
|
|
if (stressRoll.second.isSign) {
|
|
stressRoll.quality = "Double Particulière";
|
|
return 150;
|
|
}
|
|
return 100;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
createCallbackExperience() {
|
|
return {
|
|
condition: r => r.rolled.isPart && r.finalLevel < 0 && game.settings.get("core", "rollMode") != 'selfroll',
|
|
action: r => this._appliquerAjoutExperience(r, game.settings.get("core", "rollMode") != 'blindroll')
|
|
};
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
createCallbackAppelAuMoral() { /* Si l'appel au moral est utilisé, on l'affiche dans le chat et on diminue éventuellement le moral */
|
|
return {
|
|
condition: r => r.use.appelAuMoral && game.settings.get("core", "rollMode") != 'selfroll',
|
|
action: r => { this.displayAppelAuMoral ; this._appliquerAppelMoral(r, game.settings.get("core", "rollMode") != 'blindroll') }
|
|
};
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async checkCaracXP(caracName) {
|
|
let carac = this.data.data.carac[caracName];
|
|
if (carac && carac.xp > 0) {
|
|
let xpNeeded = RdDUtility.getCaracNextXp(carac.value + 1);
|
|
if (carac.xp >= xpNeeded) {
|
|
carac = duplicate(carac);
|
|
carac.value = Number(carac.value) + 1;
|
|
|
|
let xpData = {
|
|
alias: this.name,
|
|
carac: caracName,
|
|
value: carac.value,
|
|
xp: carac.xp
|
|
}
|
|
ChatUtility.createChatMessage(this.name, "default", {
|
|
content: await renderTemplate(`systems/foundryvtt-reve-de-dragon/templates/chat-actor-carac-xp.html`, xpData)
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async checkCompetenceXP(compName, newXP = undefined) {
|
|
let competence = RdDItemCompetence.findCompetence(this.data.items, compName);
|
|
if (competence && newXP && newXP == competence.data.xp) { // Si édition, mais sans changement XP
|
|
return;
|
|
}
|
|
newXP = (newXP) ? newXP : competence.data.xp;
|
|
if (competence && newXP > 0) {
|
|
let xpNeeded = RdDItemCompetence.getCompetenceNextXp(competence.data.niveau + 1);
|
|
if (newXP >= xpNeeded) {
|
|
let newCompetence = duplicate(competence);
|
|
newCompetence.data.niveau += 1;
|
|
newCompetence.data.xp = newXP;
|
|
|
|
let xpData = {
|
|
alias: this.name,
|
|
competence: newCompetence.name,
|
|
niveau: newCompetence.data.niveau,
|
|
xp: newCompetence.data.xp,
|
|
archetype: newCompetence.data.niveau_archetype,
|
|
archetypeWarning: newCompetence.data.niveau > competence.data.niveau_archetype
|
|
}
|
|
ChatUtility.createChatMessage(this.name, "default", {
|
|
content: await renderTemplate(`systems/foundryvtt-reve-de-dragon/templates/chat-actor-competence-xp.html`, xpData)
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async _appliquerAjoutExperience(rollData, display = true) {
|
|
if (!this.isPersonnage()) return;
|
|
let xpResult = this.appliquerExperience(rollData.rolled, rollData.selectedCarac.label, rollData.competence);
|
|
if (display && xpResult.result) {
|
|
let xpmsg = "<br>Points d'expérience gagnés ! Carac: " + xpResult.xpCarac + ", Comp: " + xpResult.xpCompetence;
|
|
let message = {
|
|
whisher: ChatMessage.getWhisperRecipients(["GM", this.name]),
|
|
content: "<strong>" + rollData.selectedCarac.label + "</strong>" + xpmsg,
|
|
}
|
|
ChatMessage.create(message);
|
|
}
|
|
if (xpResult && xpResult.xpComp > 0 && rollData.competence) {
|
|
this.checkCompetenceXP(rollData.competence.name);
|
|
}
|
|
if (xpResult && xpResult.xpCarac > 0 && rollData.selectedCarac) {
|
|
this.checkCaracXP(rollData.selectedCarac.name);
|
|
}
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async _appliquerAppelMoral(rollData, display = true) {
|
|
if (!this.isPersonnage()) return;
|
|
if (!rollData.rolled.isEchec) return;
|
|
this.moralIncDec(-1); /* L'appel au moral a échoué. Le personnage perd un point de moral */
|
|
rollData.jetEchouerMoralDiminuer = true;
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
filterSortList(sortList, coord) {
|
|
let tmr = TMRUtility.getTMR(coord);
|
|
let letfilteredList = []
|
|
for (let sort of sortList) {
|
|
//console.log(sort.name, sort.data.caseTMR.toLowerCase(), sort.data.caseTMRspeciale.toLowerCase(), coord.toLowerCase() );
|
|
if (sort.data.caseTMR.toLowerCase().includes('variable')) {
|
|
letfilteredList.push(sort);
|
|
} else if (sort.data.caseTMRspeciale.toLowerCase().includes('variable')) {
|
|
letfilteredList.push(sort);
|
|
} else if (sort.data.caseTMR.toLowerCase() == tmr.type) {
|
|
letfilteredList.push(sort);
|
|
} else if (sort.data.caseTMR.toLowerCase().includes('special') && sort.data.caseTMRspeciale.toLowerCase().includes(coord.toLowerCase())) {
|
|
letfilteredList.push(sort);
|
|
}
|
|
}
|
|
|
|
return letfilteredList;
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
filterDraconicList(sortList) {
|
|
let draconicDone = {};
|
|
let newDraconicList = [];
|
|
let draconicList = this.getDraconicList();
|
|
let bestDraconic = this.getBestDraconic();
|
|
for (let sort of sortList) {
|
|
let voie = sort.data.draconic.toLowerCase();
|
|
let draconic = draconicList.find(item => item.data.categorie == 'draconic' && item.name.toLowerCase().includes(voie));
|
|
if (sort.name.toLowerCase().includes('aura')) {
|
|
draconic = bestDraconic;
|
|
}
|
|
draconic = duplicate(draconic);
|
|
if (draconicDone[draconic.name] == undefined) {
|
|
draconic.data.defaut_carac = 'reve';
|
|
newDraconicList.push(draconic);
|
|
draconicDone[draconic.name] = newDraconicList.length - 1; // Patch local pour relier facilement voie/compétence
|
|
}
|
|
sort.data.listIndex = draconicDone[draconic.name] || 0;
|
|
}
|
|
return newDraconicList;
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async rollUnSort(coord) {
|
|
let sortList = duplicate(this.getSortList()); // Duplication car les pts de reve sont modifiés dans le sort
|
|
if (!sortList || sortList.length == 0) {
|
|
ui.notifications.info("Aucun sort disponible!");
|
|
return;
|
|
}
|
|
sortList = this.filterSortList(sortList, coord);
|
|
if (!sortList || sortList.length == 0) {
|
|
ui.notifications.info("Aucun sort disponible pour cette case !");
|
|
return;
|
|
}
|
|
if (EffetsDraconiques.isSortImpossible(this)) {
|
|
ui.notifications.error("Une queue ou un souffle vous empèche de lancer de sort!");
|
|
return;
|
|
}
|
|
if (this.currentTMR) this.currentTMR.minimize(); // Hide
|
|
|
|
let draconicList = this.filterDraconicList(sortList);
|
|
let rollData = {
|
|
forceCarac: { 'reve': duplicate(this.data.data.carac.reve) },
|
|
selectedCarac: duplicate(this.data.data.carac.reve),
|
|
draconicList: draconicList,
|
|
sortList: sortList,
|
|
competence: draconicList[0],
|
|
selectedSort: sortList[0],
|
|
tmr: TMRUtility.getTMR(coord),
|
|
diffLibre: sortList[0].data.difficulte, // Per default at startup
|
|
coutreve: Array(20).fill().map((item, index) => 1 + index),
|
|
carac: { 'reve': duplicate(this.data.data.carac.reve) }
|
|
}
|
|
|
|
if (this.currentTMR) this.currentTMR.minimize(); // Hide
|
|
const dialog = await RdDRoll.create(this, rollData,
|
|
{
|
|
html: 'systems/foundryvtt-reve-de-dragon/templates/dialog-roll-sort.html',
|
|
close: html => { this.currentTMR.maximize() } // Re-display TMR
|
|
},
|
|
{
|
|
name: 'lancer-un-sort',
|
|
label: 'Lancer un sort',
|
|
callbacks: [
|
|
this.createCallbackExperience(),
|
|
{ action: r => this._rollUnSortResult(r, false) }
|
|
]
|
|
},
|
|
{
|
|
name: 'mettre-en-reserve',
|
|
label: 'Mettre un sort en réserve',
|
|
callbacks: [
|
|
this.createCallbackExperience(),
|
|
{ action: r => this._rollUnSortResult(r, true) }
|
|
]
|
|
},
|
|
);
|
|
dialog.render(true);
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
isRencontreSpeciale() { // Gestion queue/souffle 'Mauvaise Rencontre en Perpective'
|
|
let addMsg = "";
|
|
let rencSpecial = this.data.items.find(item => EffetsDraconiques.isMauvaiseRencontre(item));
|
|
if (rencSpecial) {
|
|
rencSpecial = duplicate(rencSpecial); // To keep it
|
|
if (rencSpecial.type != 'souffle') {
|
|
this.deleteOwnedItem(rencSpecial._id); // Suppression dans la liste des queues
|
|
addMsg = " La queue a été supprimée de votre fiche automatiquement";
|
|
} else {
|
|
addMsg = " Vous devez gérer manuellement le décompte de mauvaises rencontres manuellement.";
|
|
}
|
|
ChatMessage.create({
|
|
content: "Vous êtes sous le coup d'une Mauvaise Rencontre en Persective." + addMsg,
|
|
whisper: ChatMessage.getWhisperRecipients(game.user.name)
|
|
});
|
|
}
|
|
return rencSpecial;
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
getTMRFatigue() { // Pour l'instant uniquement Inertie Draconique
|
|
let countInertieDraconique = EffetsDraconiques.countInertieDraconique(this);
|
|
if (countInertieDraconique > 0) {
|
|
ChatMessage.create({
|
|
content: `Vous êtes sous le coup d'Inertie Draconique : vous perdrez ${countInertieDraconique + 1} cases de Fatigue par déplacement au lieu d'une.`,
|
|
whisper: ChatMessage.getWhisperRecipients(game.user.name)
|
|
});
|
|
}
|
|
return countInertieDraconique + 1;
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async checkSoufflePeage(tmr) {
|
|
let peage = this.data.items.find(item => EffetsDraconiques.isPeage(item));
|
|
if (peage && (tmr.type == 'pont' || tmr.type == 'cite')) {
|
|
await this.reveActuelIncDec(-1);
|
|
ChatMessage.create({
|
|
content: "Vous êtes sous le coup d'un Péage : l'entrée sur cette case vous a coûté 1 Point de Rêve (déduit automatiquement).",
|
|
whisper: ChatMessage.getWhisperRecipients(game.user.name)
|
|
});
|
|
}
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async _rollUnSortResult(rollData, isSortReserve = false) {
|
|
let rolled = rollData.rolled;
|
|
let selectedSort = rollData.selectedSort;
|
|
let closeTMR = !isSortReserve;
|
|
if (selectedSort.data.isrituel && isSortReserve) {
|
|
ui.notifications.error("Impossible de mettre le rituel '" + selectedSort.name + "' en réserve");
|
|
this.currentTMR.close(); // Close TMR !
|
|
return;
|
|
}
|
|
|
|
rollData.isSortReserve = isSortReserve;
|
|
rollData.show = {}
|
|
rollData.depenseReve = Number(selectedSort.data.ptreve_reel);
|
|
|
|
let myReve = duplicate(this.data.data.reve.reve);
|
|
if (rolled.isSuccess) { // Réussite du sort !
|
|
if (rolled.isPart) {
|
|
rollData.depenseReve = Math.max(Math.floor(rollData.depenseReve / 2), 1);
|
|
}
|
|
if (rollData.isSortReserve) {
|
|
rollData.depenseReve++;
|
|
}
|
|
if ( rollData.competence.name.includes('Thanatos')) { // Si Thanatos
|
|
myReve.thanatosused = true;
|
|
}
|
|
if (myReve.value > rollData.depenseReve) {
|
|
// Incrémenter/gére le bonus de case
|
|
RdDItemSort.incrementBonusCase(this, selectedSort, rollData.tmr.coord);
|
|
|
|
if (rollData.isSortReserve) {
|
|
await this.sortMisEnReserve(rollData, selectedSort);
|
|
closeTMR = false;
|
|
}
|
|
}
|
|
else {
|
|
rollData.depenseReve = 0;
|
|
rollData.show.reveInsuffisant = true;
|
|
mergeObject(rollData.rolled, RdDResolutionTable.getResultat("echec"), { overwrite: true });
|
|
}
|
|
} else {
|
|
if (rolled.isETotal) { // Echec total !
|
|
rollData.depenseReve = Math.min(myReve.value, Math.floor(rollData.depenseReve * 1.5))
|
|
// TODO: mise en réserve d'un échec total...
|
|
} else {
|
|
rollData.depenseReve = 0
|
|
}
|
|
}
|
|
|
|
myReve.value = Math.max(myReve.value - rollData.depenseReve, 0);
|
|
await this.update({ "data.reve.reve": myReve });
|
|
|
|
if (closeTMR) {
|
|
this.currentTMR.close(); // Close TMR !
|
|
} else {
|
|
this.currentTMR.maximize(); // Re-display TMR
|
|
}
|
|
// Final chat message
|
|
RdDResolutionTable.displayRollData(rollData, this, 'chat-resultat-sort.html');
|
|
|
|
if (myReve.value == 0) { // 0 points de reve
|
|
ChatMessage.create({ content: this.name + " est réduit à 0 Points de Rêve, et tombe endormi !" });
|
|
closeTMR = true;
|
|
}
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async rollCarac(caracName) {
|
|
let rollData = { selectedCarac: this.getCaracByName(caracName) };
|
|
|
|
const dialog = await RdDRoll.create(this, rollData,
|
|
{ html: 'systems/foundryvtt-reve-de-dragon/templates/dialog-roll-carac.html' },
|
|
{
|
|
name: 'jet-' + caracName,
|
|
label: 'Jet ' + Grammar.apostrophe('de', rollData.selectedCarac.label),
|
|
callbacks: [
|
|
this.createCallbackExperience(),
|
|
{ action: r => this._onRollCaracResult(r) }
|
|
]
|
|
}
|
|
);
|
|
dialog.render(true);
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async _onRollCaracResult(rollData) {
|
|
// Final chat message
|
|
RdDResolutionTable.displayRollData(rollData, this, 'chat-resultat-general.html');
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async rollCompetence(name) {
|
|
let rollData = { competence: this.getCompetence(name) }
|
|
|
|
if (rollData.competence.type == 'competencecreature') {
|
|
if (rollData.competence.data.iscombat) {
|
|
const arme = RdDItemCompetenceCreature.toArme(rollData.competence);
|
|
RdDCombat.createUsingTarget(this).attaque(rollData.competence, arme);
|
|
return;
|
|
}
|
|
// Fake competence pour créature
|
|
RdDItemCompetenceCreature.setRollDataCreature(rollData);
|
|
}
|
|
else {
|
|
rollData.carac = this.data.data.carac;
|
|
}
|
|
console.log("rollCompetence !!!", rollData);
|
|
|
|
const dialog = await RdDRoll.create(this, rollData, { html: 'systems/foundryvtt-reve-de-dragon/templates/dialog-competence.html' }, {
|
|
name: 'jet-competence',
|
|
label: 'Jet ' + Grammar.apostrophe('de', name),
|
|
callbacks: [
|
|
this.createCallbackExperience(),
|
|
this.createCallbackAppelAuMoral(),
|
|
{ action: r => this._competenceResult(r) }
|
|
]
|
|
});
|
|
dialog.render(true);
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async creerTacheDepuisLivre(item) {
|
|
let tache = {
|
|
name: "Lire " + item.name, type: 'tache',
|
|
data: {
|
|
carac: 'intellect',
|
|
competence: 'Ecriture',
|
|
difficulte: item.data.data.difficulte,
|
|
periodicite: "60 minutes",
|
|
fatigue: 2,
|
|
points_de_tache: item.data.data.points_de_tache,
|
|
points_de_tache_courant: 0,
|
|
description: "Lecture du livre " + item.name +
|
|
" - XP : " + item.data.data.xp + " - Compétences : " + item.data.data.competence
|
|
}
|
|
}
|
|
await this.createOwnedItem(tache, { renderSheet: true });
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async rollTache(id) {
|
|
let tache = duplicate(this.getTache(id));
|
|
let competence = duplicate(this.getCompetence(tache.data.competence));
|
|
competence.data.defaut_carac = tache.data.carac; // Patch !
|
|
let rollData = {
|
|
competence: competence,
|
|
tache: tache,
|
|
diffConditions: tache.data.difficulte,
|
|
use: { libre: false, conditions: false },
|
|
carac: {}
|
|
};
|
|
rollData.carac[tache.data.carac] = duplicate(this.data.data.carac[tache.data.carac]); // Single carac
|
|
|
|
console.log("rollTache !!!", rollData);
|
|
|
|
const dialog = await RdDRoll.create(this, rollData, { html: 'systems/foundryvtt-reve-de-dragon/templates/dialog-competence.html' }, {
|
|
name: 'jet-competence',
|
|
label: 'Jet de Tâche ' + tache.name,
|
|
callbacks: [
|
|
this.createCallbackExperience(),
|
|
{ condition: r => r.rolled.isETotal, action: r => this._tacheETotal(r) },
|
|
{ action: r => this._tacheResult(r) }
|
|
]
|
|
});
|
|
dialog.render(true);
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async _tacheResult(rollData) {
|
|
// Mise à jour de la tache
|
|
rollData.tache.data.points_de_tache_courant += rollData.rolled.ptTache;
|
|
this.updateEmbeddedEntity("OwnedItem", rollData.tache);
|
|
this.santeIncDec("fatigue", rollData.tache.data.fatigue);
|
|
|
|
RdDResolutionTable.displayRollData(rollData, this, 'chat-resultat-tache.html');
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
_tacheETotal(rollData) {
|
|
rollData.tache.data.difficulte--;
|
|
this.updateEmbeddedEntity("OwnedItem", rollData.tache);
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async _rollArt(artData, selected, oeuvre, callBackResult = r => this._resultArt(r)) {
|
|
mergeObject(artData, {
|
|
oeuvre: oeuvre,
|
|
art: oeuvre.type,
|
|
competence: duplicate(this.getCompetence(oeuvre.data.competence ?? artData.art)),
|
|
diffLibre: - (oeuvre.data.niveau ?? 0),
|
|
diffConditions: 0,
|
|
use: { libre: false, conditions: true },
|
|
selectedCarac: duplicate(this.data.data.carac[selected])
|
|
});
|
|
artData.competence.data.defaut_carac = selected;
|
|
if ( !artData.forceCarac ) {
|
|
artData.forceCarac = {};
|
|
artData.forceCarac[selected] = duplicate(this.data.data.carac[selected]);
|
|
}
|
|
|
|
console.log("rollArt !!!", artData);
|
|
|
|
const dialog = await RdDRoll.create(this, artData, { html: `systems/foundryvtt-reve-de-dragon/templates/dialog-roll-${artData.art}.html` }, {
|
|
name: `jet-${artData.art}`,
|
|
label: `${artData.verbe} ${oeuvre.name}`,
|
|
height: 600,
|
|
callbacks: [
|
|
this.createCallbackExperience(),
|
|
{ action: r => callBackResult(r) }
|
|
]
|
|
});
|
|
dialog.render(true);
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async _resultArt(artData) {
|
|
const baseQualite = (artData.rolled.isSuccess ? artData.oeuvre.data.niveau : artData.competence.data.niveau);
|
|
artData.qualiteFinale = Math.min(baseQualite, artData.oeuvre.data.niveau) + artData.rolled.ptQualite;
|
|
|
|
console.log("OEUVRE", artData.art, artData)
|
|
RdDResolutionTable.displayRollData(artData, this.name, `chat-resultat-${artData.art}.html`);
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async rollChant(id) {
|
|
const artData = { art: 'chant', verbe: 'Chanter' };
|
|
const oeuvre = duplicate(this.getChant(id));
|
|
await this._rollArt(artData, "ouie", oeuvre);
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async rollDanse(id) {
|
|
const artData = { art: 'danse', verbe: 'Danser', forceCarac: {} };
|
|
const oeuvre = duplicate(this.getOeuvre(id, artData.art));
|
|
const selectedCarac = this._getCaracDanse(oeuvre);
|
|
if ( oeuvre.data.agilite) {
|
|
artData.forceCarac['agilite'] = duplicate(this.data.data.carac.agilite );
|
|
}
|
|
if ( oeuvre.data.apparence) {
|
|
artData.forceCarac['apparence'] = duplicate(this.data.data.carac.apparence );
|
|
}
|
|
await this._rollArt(artData, selectedCarac, oeuvre);
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
_getCaracDanse(oeuvre) {
|
|
if (oeuvre.data.agilite) { return "agilite"; }
|
|
else if (oeuvre.data.apparence) { return "apparence"; }
|
|
const competence = this.getCompetence(oeuvre.data.competence);
|
|
return competence.data.defaut_carac;
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async rollMusique(id) {
|
|
const artData = { art: 'musique', verbe: 'Jouer' };
|
|
const oeuvre = duplicate(this.getOeuvre(id, artData.art));
|
|
await this._rollArt(artData, "ouie", oeuvre);
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async rollRecetteCuisine(id) {
|
|
const artData = { art: 'cuisine', verbe: 'Cuisiner' };
|
|
const oeuvre = duplicate(this.getRecetteCuisine(id));
|
|
await this._rollArt(artData, 'odoratgout', oeuvre, r => this._resultRecetteCuisine(r));
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async _resultRecetteCuisine(artData) {
|
|
const baseQualite = (artData.rolled.isSuccess ? artData.oeuvre.data.niveau : artData.competence.data.niveau);
|
|
artData.qualiteFinale = Math.min(baseQualite, artData.oeuvre.data.niveau) + artData.rolled.ptQualite;
|
|
artData.exotismeFinal = Math.min(Math.min(artData.qualiteFinale, -Math.abs(artData.oeuvre.data.exotisme ?? 0)), 0);
|
|
console.log("OEUVRE", artData.art, artData)
|
|
RdDResolutionTable.displayRollData(artData, this.name, `chat-resultat-${artData.art}.html`);
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async rollJeu(id) {
|
|
const artData = {
|
|
art: 'jeu', verbe: 'Jeu',
|
|
use: { libre: true, conditions: true, },
|
|
};
|
|
const oeuvre = duplicate(this.getJeu(id));
|
|
await this._rollArt(artData, oeuvre.data?.caraccomp.toLowerCase() ?? 'chance', oeuvre);
|
|
}
|
|
|
|
async rollOeuvre(id) {
|
|
const artData = { art: 'oeuvre', verbe: 'Interpréter' };
|
|
const oeuvre = duplicate(this.getOeuvre(id));
|
|
await this._rollArt(artData, oeuvre.data.default_carac, oeuvre);
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async rollMeditation(id) {
|
|
let meditation = duplicate(this.getMeditation(id));
|
|
let competence = duplicate(this.getCompetence(meditation.data.competence));
|
|
competence.data.defaut_carac = "intellect"; // Meditation = tjs avec intellect
|
|
let meditationData = {
|
|
competence: competence,
|
|
meditation: meditation,
|
|
conditionMeditation: {
|
|
isHeure: false,
|
|
isVeture: false,
|
|
isComportement: false,
|
|
isPurification: false,
|
|
},
|
|
diffConditions: 0,
|
|
use: { libre: false, conditions: true, },
|
|
carac: {}
|
|
};
|
|
meditationData.carac["intellect"] = duplicate(this.data.data.carac["intellect"]);
|
|
|
|
console.log("rollMeditation !!!", meditationData);
|
|
|
|
const dialog = await RdDRoll.create(this, meditationData, { html: 'systems/foundryvtt-reve-de-dragon/templates/dialog-roll-meditation.html' }, {
|
|
name: 'jet-meditation',
|
|
label: 'Jet de Meditation ' + meditation.name,
|
|
height: 600,
|
|
callbacks: [
|
|
this.createCallbackExperience(),
|
|
{ condition: r => r.rolled.isETotal, action: r => this._meditationETotal(r) },
|
|
{ action: r => this._meditationResult(r) }
|
|
]
|
|
});
|
|
dialog.render(true);
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async _meditationResult(meditationData) {
|
|
this.santeIncDec("fatigue", 2);
|
|
|
|
meditationData.diffLecture = -7;
|
|
if (meditationData.rolled.isPart)
|
|
meditationData.diffLecture = 0;
|
|
else if (meditationData.rolled.isSign)
|
|
meditationData.diffLecture = -3;
|
|
|
|
RdDResolutionTable.displayRollData(meditationData, this.name, 'chat-resultat-meditation.html');
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
_meditationETotal(meditationData) {
|
|
meditationData.meditation.data.malus--;
|
|
this.updateEmbeddedEntity("OwnedItem", meditationData.meditation);
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async _competenceResult(rollData) {
|
|
RdDResolutionTable.displayRollData(rollData, this, 'chat-resultat-competence.html')
|
|
}
|
|
async _moralDecrease(rollData) {
|
|
RdDResolutionTable.displayRollData(rollData, this, 'chat-resultat-competence.html')
|
|
}
|
|
/* -------------------------------------------- */
|
|
async rollAppelChance(onSuccess = () => { }, onEchec = () => { }) {
|
|
// Stocke si utilisation de la chance
|
|
await this.unsetFlag('foundryvtt-reve-de-dragon', 'utilisationChance');
|
|
await this.setFlag('foundryvtt-reve-de-dragon', 'utilisationChance', true );
|
|
|
|
let rollData = { selectedCarac: this.getCaracByName('chance-actuelle'), surprise: '' };
|
|
const dialog = await RdDRoll.create(this, rollData,
|
|
{ html: 'systems/foundryvtt-reve-de-dragon/templates/dialog-roll-carac.html' },
|
|
{
|
|
name: 'appelChance',
|
|
label: 'Appel à la chance',
|
|
callbacks: [
|
|
this.createCallbackExperience(),
|
|
{ action: r => this._appelChanceResult(r, onSuccess, onEchec) },
|
|
]
|
|
}
|
|
);
|
|
dialog.render(true);
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async _appelChanceResult(rollData, onSuccess = () => { }, onEchec = () => { }) {
|
|
await RdDResolutionTable.displayRollData(rollData, this, 'chat-resultat-appelchance.html')
|
|
if (rollData.rolled.isSuccess) {
|
|
await this.chanceActuelleIncDec(-1);
|
|
onSuccess();
|
|
}
|
|
else {
|
|
onEchec();
|
|
}
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async chanceActuelleIncDec(value, limit = true) {
|
|
let chance = duplicate(this.data.data.compteurs.chance);
|
|
chance.value = Math.max(chance.value + value, 0);
|
|
if (limit) {
|
|
chance.value = Math.min(chance.value, this.getChance())
|
|
}
|
|
await this.update({ "data.compteurs.chance": chance });
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async appelDestinee(onSuccess = () => { }, onEchec = () => { }) {
|
|
if (this.data.data.compteurs.destinee?.value ?? 0 > 0) {
|
|
ChatMessage.create({ content: `<span class="rdd-roll-part">${this.name} a fait appel à la Destinée !</span>` });
|
|
let destinee = duplicate(this.data.data.compteurs.destinee);
|
|
destinee.value = destinee.value - 1;
|
|
await this.update({ "data.compteurs.destinee": destinee });
|
|
onSuccess();
|
|
}
|
|
else {
|
|
onEchec();
|
|
}
|
|
}
|
|
|
|
|
|
/* -------------------------------------------- */
|
|
ajustementAstrologique() {
|
|
if (this.isCreature()) {
|
|
return 0;
|
|
}
|
|
// selon l'heure de naissance...
|
|
return game.system.rdd.calendrier.getAjustementAstrologique(this.data.data.heure, this.data.name);
|
|
}
|
|
/* -------------------------------------------- */
|
|
checkDesirLancinant() {
|
|
let queue = this.data.items.filter((item) => item.name.toLowerCase().includes('lancinant'));
|
|
return (queue.length > 0);
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async appliquerExperience(rolled, caracName, competence = undefined) {
|
|
if (!this.isPersonnage()) return;
|
|
if (rolled.isPart && rolled.finalLevel < 0) {
|
|
// Cas de désir lancinant, pas d'expérience sur particulière
|
|
if (this.checkDesirLancinant()) {
|
|
ChatMessage.create({
|
|
content: `Vous souffrez au moins d'un Désir Lancinant, vous ne pouvez pas gagner d'expérience sur une Particulière tant que le désir n'est pas assouvi`,
|
|
whisper: ChatMessage.getWhisperRecipients(game.user.name)
|
|
});
|
|
return { result: false, xpcarac: 0, xpCompetence: 0 };
|
|
}
|
|
|
|
if (caracName == 'derobee') caracName = 'agilite';
|
|
let xp = Math.abs(rolled.finalLevel);
|
|
let xpCarac = Math.floor(xp / 2); // impair: arrondi inférieur en carac
|
|
let xpComp = 0;
|
|
if (competence) {
|
|
xpComp = xp - xpCarac;
|
|
competence = duplicate(competence);
|
|
competence.data.xp = Misc.toInt(competence.data.xp) + xpComp;
|
|
await this.updateEmbeddedEntity("OwnedItem", competence);
|
|
} else {
|
|
xpCarac = Math.max(xpCarac, 1);
|
|
}
|
|
if (xpCarac > 0) {
|
|
let carac = duplicate(this.data.data.carac);
|
|
let selectedCarac = RdDActor._findCaracByName(carac, caracName);
|
|
if (!selectedCarac.derivee) {
|
|
selectedCarac.xp = Misc.toInt(selectedCarac.xp) + xpCarac;
|
|
await this.update({ "data.carac": carac });
|
|
} else {
|
|
ChatMessage.create({
|
|
content: `Vous avez ${xpCarac} à répartir pour la caractéristique dérivée ${caracName}. Vous devez le faire manuellement.`,
|
|
whisper: ChatMessage.getWhisperRecipients(game.user.name)
|
|
});
|
|
}
|
|
}
|
|
return { result: true, xpCarac: xpCarac, xpCompetence: xpComp }; //XP
|
|
}
|
|
return { result: false, xpCarac: 0, xpCompetence: 0 }; // Pas d'XP
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async ajouteNombreAstral(data) {
|
|
// Gestion expérience (si existante)
|
|
data.competence = RdDItemCompetence.findCompetence(this.data.items, "astrologie");
|
|
data.selectedCarac = this.data.data.carac["vue"];
|
|
this._appliquerAjoutExperience(data);
|
|
|
|
// Ajout du nombre astral
|
|
const item = {
|
|
name: "Nombre Astral", type: "nombreastral", data:
|
|
{ value: data.nbAstral, istrue: data.isvalid, jourindex: Number(data.date), jourlabel: game.system.rdd.calendrier.getDateFromIndex(Number(data.date)) }
|
|
};
|
|
await this.createEmbeddedEntity("OwnedItem", item);
|
|
|
|
// Suppression des anciens nombres astraux
|
|
let toDelete = this.data.items.filter((item) => item.data.jourindex < game.system.rdd.calendrier.getCurrentDayIndex());
|
|
const deletions = toDelete.map(i => i._id);
|
|
await this.deleteEmbeddedEntity("OwnedItem", deletions);
|
|
|
|
// Affichage Dialog
|
|
this.astrologieNombresAstraux();
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async astrologieNombresAstraux() {
|
|
// Afficher l'interface spéciale
|
|
const astrologieDialog = await RdDAstrologieJoueur.create(this, {});
|
|
astrologieDialog.render(true);
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
getCaracByName(caracName) {
|
|
switch (caracName) {
|
|
case 'reve-actuel': case 'Rêve actuel':
|
|
return {
|
|
label: 'Rêve actuel',
|
|
value: this.getReveActuel(),
|
|
type: "number"
|
|
};
|
|
case 'chance-actuelle': case 'Chance actuelle':
|
|
return {
|
|
label: 'Chance actuelle',
|
|
value: this.getChanceActuel(),
|
|
type: "number"
|
|
};
|
|
}
|
|
return RdDActor._findCaracByName(this.data.data.carac, caracName);
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
static _findCaracByName(carac, name) {
|
|
name = name.toLowerCase();
|
|
switch (name) {
|
|
case 'reve-actuel': case 'rêve actuel':
|
|
return carac.reve;
|
|
case 'chance-actuelle': case 'chance actuelle':
|
|
return carac.chance;
|
|
}
|
|
for (const [key, value] of Object.entries(carac)) {
|
|
if (name == key || name == value.label.toLowerCase()) {
|
|
return carac[key];
|
|
}
|
|
}
|
|
return carac[name]; // Per default
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
getSortList() {
|
|
return this.data.items.filter(item => item.type == "sort");
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
getDraconicList() {
|
|
return this.data.items.filter(item => item.data.categorie == 'draconic')
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
countMonteeLaborieuse() { // Return +1 par queue/ombre/souffle Montée Laborieuse présente
|
|
let countMonteeLaborieuse = this.data.items.filter(item => EffetsDraconiques.isMonteeLaborieuse(item)).length;
|
|
if (countMonteeLaborieuse > 0) {
|
|
ChatMessage.create({
|
|
content: `Vous êtes sous le coup d'une Montée Laborieuse : vos montées en TMR coûtent ${countMonteeLaborieuse} Point de Rêve de plus.`,
|
|
whisper: ChatMessage.getWhisperRecipients(game.user.name)
|
|
});
|
|
}
|
|
return countMonteeLaborieuse;
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
refreshTMRView(tmrData) {
|
|
console.log("REFRESH !!!!");
|
|
if (this.currentTMR) {
|
|
this.currentTMR.forceDemiRevePositionView();
|
|
}
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async displayTMR(mode = "normal") {
|
|
let isRapide = mode == "rapide";
|
|
if (mode != "visu") {
|
|
let minReveValue = (isRapide && !EffetsDraconiques.isDeplacementAccelere(this) ? 3 : 2) + this.countMonteeLaborieuse();
|
|
if (this.getReveActuel() < minReveValue) {
|
|
ChatMessage.create({
|
|
content: `Vous n'avez les ${minReveValue} Points de Reve nécessaires pour monter dans les Terres Médianes`,
|
|
whisper: ChatMessage.getWhisperRecipients(game.user.name)
|
|
});
|
|
return;
|
|
}
|
|
}
|
|
|
|
let data = {
|
|
mode: mode,
|
|
fatigue: {
|
|
malus: RdDUtility.calculMalusFatigue(this.data.data.sante.fatigue.value, this.data.data.sante.endurance.max),
|
|
html: "<table class='table-fatigue'>" + RdDUtility.makeHTMLfatigueMatrix(this.data.data.sante.fatigue.value, this.data.data.sante.endurance.max).html() + "</table>"
|
|
},
|
|
draconic: this.getDraconicList(),
|
|
sort: this.getSortList(),
|
|
caracReve: this.data.data.carac.reve.value,
|
|
pointsReve: this.getReveActuel(),
|
|
isRapide: isRapide
|
|
}
|
|
let html = await renderTemplate('systems/foundryvtt-reve-de-dragon/templates/dialog-tmr.html', data);
|
|
this.currentTMR = await RdDTMRDialog.create(html, this, data);
|
|
this.currentTMR.render(true);
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
rollArme(compName, armeName = undefined) {
|
|
let arme = armeName ? this.data.items.find(item => item.name == armeName && RdDItemArme.isArme(item)) : undefined;
|
|
let competence = this.getCompetence(compName);
|
|
|
|
if (arme || armeName || (competence.type == 'competencecreature' && competence.data.iscombat)) {
|
|
RdDCombat.createUsingTarget(this).attaque(competence, arme);
|
|
} else {
|
|
this.rollCompetence(competence.name);
|
|
}
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
_getTarget() {
|
|
if (game.user.targets && game.user.targets.size == 1) {
|
|
for (let target of game.user.targets) {
|
|
return target;
|
|
}
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
getArmeParade(armeParadeId) {
|
|
const item = armeParadeId ? this.getOwnedItem(armeParadeId) : undefined;
|
|
return RdDItemArme.getArmeData(item);
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
verifierForceMin( item ) {
|
|
if ( item.type == 'arme' && item.data.force > this.data.data.carac.force.value ) {
|
|
ChatMessage.create( { content: `<strong>${this.name} s'est équipé(e) de l'arme ${item.name}, mais n'a pas une force suffisante pour l'utiliser normalement </strong>
|
|
(${item.data.force} nécessaire pour une Force de ${this.data.data.carac.force.value})` } );
|
|
}
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async equiperObjet(itemID) {
|
|
let item = this.getOwnedItem(itemID);
|
|
if (item && item.data.data) {
|
|
let update = { _id: item._id, "data.equipe": !item.data.data.equipe };
|
|
await this.updateEmbeddedEntity("OwnedItem", update);
|
|
this.computeEncombrementTotalEtMalusArmure(); // Mise à jour encombrement
|
|
this.computePrixTotalEquipement(); // Mis à jour du prix total de l'équipement
|
|
if ( item.data.data.equipe )
|
|
this.verifierForceMin( item.data );
|
|
}
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
computeArmure(attackerRoll) {
|
|
let dmg = (attackerRoll.dmg.dmgArme ?? 0) + (attackerRoll.dmg.dmgActor ?? 0);
|
|
let arme = attackerRoll.arme;
|
|
let protection = 0;
|
|
const armures = this.data.items.filter(it => it.type == "armure" && it.data.equipe);
|
|
for (const item of armures) {
|
|
protection += new Roll(item.data.protection.toString()).roll().total;
|
|
if (dmg > 0) {
|
|
this._deteriorerArmure(item, dmg);
|
|
dmg = 0;
|
|
}
|
|
}
|
|
const penetration = arme ? Misc.toInt(arme.data.penetration) : 0;
|
|
protection = Math.max(protection - penetration, 0);
|
|
protection += this.getProtectionNaturelle();
|
|
// Gestion des cas particuliers sur la fenêtre d'encaissement
|
|
if ( attackerRoll.dmg.encaisserSpecial && attackerRoll.dmg.encaisserSpecial == "noarmure") {
|
|
protection = 0;
|
|
}
|
|
if ( attackerRoll.dmg.encaisserSpecial && attackerRoll.dmg.encaisserSpecial == "chute" && Number(protection) > 2) {
|
|
protection = 2;
|
|
}
|
|
console.log("Final protect", protection, attackerRoll);
|
|
return protection;
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
_deteriorerArmure(item, dmg) {
|
|
if (!ReglesOptionelles.isUsing('deteriorationArmure')) {
|
|
return;
|
|
}
|
|
let update = duplicate(item);
|
|
update.data.deterioration = (update.data.deterioration ?? 0) + dmg;
|
|
if (update.data.deterioration >= 10) {
|
|
update.data.deterioration = 0;
|
|
let res = /\d+/.exec(update.data.protection);
|
|
if (!res) {
|
|
update.data.protection = "1d" + update.data.protection;
|
|
}
|
|
else if (res = /(\d+d\d+)(\-\d+)?/.exec(update.data.protection)) {
|
|
let malus = Misc.toInt(res[2]) - 1;
|
|
update.data.protection = res[1] + malus;
|
|
}
|
|
else {
|
|
ui.notifications.warn(`La valeur d'armure de votre ${item.name} est incorrecte`);
|
|
}
|
|
ChatMessage.create({ content: "Votre armure s'est détériorée, elle protège maintenant de " + update.data.protection });
|
|
}
|
|
this.updateEmbeddedEntity("OwnedItem", update);
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async encaisser() {
|
|
let data = { ajustementsEncaissement: RdDUtility.getAjustementsEncaissement() };
|
|
let html = await renderTemplate('systems/foundryvtt-reve-de-dragon/templates/dialog-roll-encaisser.html', data);
|
|
new RdDEncaisser(html, this).render(true);
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async encaisserDommages(rollData, attacker = undefined, defenderRoll = undefined) {
|
|
if (attacker && !await attacker.accorder(this, 'avant-encaissement')) {
|
|
return;
|
|
}
|
|
|
|
console.log("encaisserDommages", rollData)
|
|
|
|
let santeOrig = duplicate(this.data.data.sante);
|
|
let encaissement = this.jetEncaissement(rollData);
|
|
|
|
this.ajouterBlessure(encaissement); // Will upate the result table
|
|
const perteVie = this.isEntiteCauchemar()
|
|
? { newValue: 0 }
|
|
: await this.santeIncDec("vie", - encaissement.vie);
|
|
const perteEndurance = await this.santeIncDec("endurance", -encaissement.endurance, (encaissement.critiques > 0));
|
|
|
|
this.computeEtatGeneral();
|
|
this.sheet.render(false);
|
|
|
|
mergeObject(encaissement, {
|
|
alias: this.data.name,
|
|
hasPlayerOwner: this.hasPlayerOwner,
|
|
resteEndurance: this.data.data.sante.endurance.value,
|
|
sonne: perteEndurance.sonne,
|
|
jetEndurance: perteEndurance.jetEndurance,
|
|
endurance: santeOrig.endurance.value - perteEndurance.newValue,
|
|
vie: this.isEntiteCauchemar() ? 0 : (santeOrig.vie.value - perteVie.newValue),
|
|
show: defenderRoll?.show ?? {}
|
|
});
|
|
|
|
ChatUtility.createChatWithRollMode(this.name, {
|
|
roll: encaissement.roll,
|
|
content: await renderTemplate('systems/foundryvtt-reve-de-dragon/templates/chat-resultat-encaissement.html', encaissement)
|
|
});
|
|
|
|
if (!encaissement.hasPlayerOwner && encaissement.endurance != 0) {
|
|
encaissement = duplicate(encaissement);
|
|
encaissement.isGM = true;
|
|
ChatMessage.create({
|
|
whisper: ChatMessage.getWhisperRecipients("GM"),
|
|
content: await renderTemplate('systems/foundryvtt-reve-de-dragon/templates/chat-resultat-encaissement.html', encaissement)
|
|
});
|
|
}
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
jetEncaissement(rollData) {
|
|
const roll = new Roll("2d10").roll();
|
|
roll.showDice = true;
|
|
RdDDice.show(roll, game.settings.get("core", "rollMode"));
|
|
|
|
const armure = this.computeArmure(rollData);
|
|
const jetTotal = roll.total + rollData.dmg.total - armure;
|
|
|
|
let encaissement = RdDUtility.selectEncaissement(jetTotal, rollData.dmg.mortalite)
|
|
let over20 = Math.max(jetTotal - 20, 0);
|
|
encaissement.dmg = rollData.dmg;
|
|
encaissement.dmg.loc = rollData.dmg.loc ?? RdDUtility.getLocalisation( this.data.type );
|
|
encaissement.dmg.loc.label = encaissement.dmg.loc.label ?? 'Corps;'
|
|
encaissement.roll = roll;
|
|
encaissement.armure = armure;
|
|
encaissement.total = jetTotal;
|
|
encaissement.vie = RdDUtility._evaluatePerte(encaissement.vie, over20);
|
|
encaissement.endurance = RdDUtility._evaluatePerte(encaissement.endurance, over20);
|
|
encaissement.penetration = rollData.arme?.data.penetration ?? 0;
|
|
|
|
return encaissement;
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
ajouterBlessure(encaissement) {
|
|
// Fast exit
|
|
if (this.data.type == 'entite') return; // Une entité n'a pas de blessures
|
|
if (encaissement.legeres + encaissement.graves + encaissement.critiques == 0) return;
|
|
|
|
const endActuelle = Number(this.data.data.sante.endurance.value);
|
|
let blessures = duplicate(this.data.data.blessures);
|
|
|
|
let count = encaissement.legeres;
|
|
// Manage blessures
|
|
while (count > 0) {
|
|
let legere = blessures.legeres.liste.find(it => !it.active);
|
|
if (legere) {
|
|
this._setBlessure(legere, encaissement);
|
|
count--;
|
|
}
|
|
else {
|
|
encaissement.graves += count;
|
|
encaissement.legeres -= count;
|
|
break;
|
|
}
|
|
}
|
|
|
|
count = encaissement.graves;
|
|
while (count > 0) {
|
|
let grave = blessures.graves.liste.find(it => !it.active);
|
|
if (grave) {
|
|
this._setBlessure(grave, encaissement);
|
|
count--;
|
|
}
|
|
else {
|
|
encaissement.critiques += count;
|
|
encaissement.graves -= count;
|
|
encaissement.endurance = -endActuelle;
|
|
encaissement.vie = -4;
|
|
break;
|
|
}
|
|
}
|
|
|
|
count = encaissement.critiques;
|
|
while (count > 0) {
|
|
let critique = blessures.critiques.liste[0];
|
|
if (!critique.active) {
|
|
this._setBlessure(critique, encaissement);
|
|
count--;
|
|
} else {
|
|
// TODO: status effect dead
|
|
this.addStatusEffectById('dead');
|
|
ChatMessage.create({
|
|
content: `<img class="chat-icon" src="icons/svg/skull.svg" alt="charge" />
|
|
<strong>${this.name} vient de succomber à une seconde blessure critique ! Que les Dragons gardent son Archétype en paix !</strong>`
|
|
});
|
|
encaissement.critiques -= count;
|
|
encaissement.mort = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
encaissement.endurance = Math.max(encaissement.endurance, -endActuelle);
|
|
this.update({ "data.blessures": blessures });
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
_setBlessure(blessure, encaissement) {
|
|
blessure.active = true;
|
|
blessure.loc = encaissement.locName;
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
/** @override */
|
|
getRollData() {
|
|
const data = super.getRollData();
|
|
return data;
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async resetItemUse( ) {
|
|
await this.unsetFlag('foundryvtt-reve-de-dragon', 'itemUse');
|
|
await this.setFlag('foundryvtt-reve-de-dragon', 'itemUse', {} );
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async incDecItemUse( itemId, inc = 1 ) {
|
|
let itemUse = duplicate(this.getFlag('foundryvtt-reve-de-dragon', 'itemUse') ?? {});
|
|
itemUse[itemId] = (itemUse[itemId] ?? 0) + inc;
|
|
await this.setFlag( 'foundryvtt-reve-de-dragon', 'itemUse', itemUse);
|
|
console.log("ITEM USE INC", inc, itemUse);
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
getItemUse( itemId ) {
|
|
let itemUse = this.getFlag('foundryvtt-reve-de-dragon', 'itemUse') ?? {};
|
|
console.log("ITEM USE GET", itemUse);
|
|
return itemUse[itemId] ?? 0;
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
/* -- entites -- */
|
|
/* retourne true si on peut continuer, false si on ne peut pas continuer */
|
|
async targetEntiteNonAccordee(target, when = 'avant-encaissement') {
|
|
if (target) {
|
|
return !await this.accorder(target.actor, when);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async accorder(entite, when = 'avant-encaissement') {
|
|
if (when != game.settings.get("foundryvtt-reve-de-dragon", "accorder-entite-cauchemar")
|
|
|| !entite.isEntiteCauchemar()
|
|
|| entite.isEntiteCauchemarAccordee(this)) {
|
|
return true;
|
|
}
|
|
|
|
let rolled = await RdDResolutionTable.roll(this.getReveActuel(), - Number(entite.data.data.carac.niveau.value));
|
|
const rollData = {
|
|
alias: this.name,
|
|
rolled: rolled,
|
|
entite: entite.name,
|
|
selectedCarac: this.data.data.carac.reve
|
|
};
|
|
|
|
if (rolled.isSuccess) {
|
|
await entite.setEntiteReveAccordee(this);
|
|
}
|
|
|
|
await RdDResolutionTable.displayRollData(rollData, this, 'chat-resultat-accorder-cauchemar.html');
|
|
if (rolled.isPart) {
|
|
await this._appliquerAjoutExperience(rollData, true);
|
|
}
|
|
return rolled.isSuccess;
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
isEntiteCauchemar() {
|
|
return this.data.type == 'entite';
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
isEntiteCauchemarAccordee(attaquant) {
|
|
if (!this.isEntiteCauchemar()) { return true; }
|
|
let resonnance = this.data.data.sante.resonnance;
|
|
return (resonnance.actors.find(it => it == attaquant._id));
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async setEntiteReveAccordee(attaquant) {
|
|
if (!this.isEntiteCauchemar()) {
|
|
ui.notifications.error("Impossible de s'accorder à " + this.name + ": ce n'est pas une entite de cauchemer/rêve");
|
|
return;
|
|
}
|
|
let resonnance = duplicate(this.data.data.sante.resonnance);
|
|
if (resonnance.actors.find(it => it == attaquant._id)) {
|
|
// déjà accordé
|
|
return;
|
|
}
|
|
resonnance.actors.push(attaquant._id);
|
|
await this.update({ "data.sante.resonnance": resonnance });
|
|
return;
|
|
}
|
|
/* -------------------------------------------- */
|
|
async optimizeArgent(sumDenier) {
|
|
let sols = Math.floor(sumDenier / 100);
|
|
let deniers = sumDenier - (sols * 100);
|
|
let nbOr = Math.floor(sols / 10);
|
|
let nbArgent = sols - (nbOr * 10);
|
|
let nbBronze = Math.floor(deniers / 10);
|
|
let nbEtain = deniers - (nbBronze * 10);
|
|
|
|
// console.log("ARGENT", nbOr, nbArgent, nbBronze, nbEtain);
|
|
let piece = this.data.items.find(item => item.type == 'monnaie' && item.data.valeur_deniers == 1000);
|
|
if (piece) {
|
|
let update = { _id: piece._id, 'data.quantite': nbOr };
|
|
const updated = await this.updateEmbeddedEntity("OwnedItem", update);
|
|
}
|
|
|
|
piece = this.data.items.find(item => item.type == 'monnaie' && item.data.valeur_deniers == 100);
|
|
if (piece) {
|
|
let update = { _id: piece._id, 'data.quantite': nbArgent };
|
|
const updated = await this.updateEmbeddedEntity("OwnedItem", update);
|
|
}
|
|
|
|
piece = this.data.items.find(item => item.type == 'monnaie' && item.data.valeur_deniers == 10);
|
|
if (piece) {
|
|
let update = { _id: piece._id, 'data.quantite': nbBronze };
|
|
const updated = await this.updateEmbeddedEntity("OwnedItem", update);
|
|
}
|
|
|
|
piece = this.data.items.find(item => item.type == 'monnaie' && item.data.valeur_deniers == 1);
|
|
if (piece) {
|
|
let update = { _id: piece._id, 'data.quantite': nbEtain };
|
|
const updated = await this.updateEmbeddedEntity("OwnedItem", update);
|
|
}
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async payerDenier(sumDenier, dataObj = undefined, quantite = 1) {
|
|
sumDenier = Number(sumDenier);
|
|
let denierDisponible = 0;
|
|
|
|
let monnaie = this.data.items.filter(item => item.type == 'monnaie');
|
|
for (let piece of monnaie) {
|
|
denierDisponible += piece.data.valeur_deniers * Number(piece.data.quantite);
|
|
}
|
|
console.log("DENIER", game.user.character, sumDenier, denierDisponible);
|
|
|
|
let msg = "";
|
|
let isPayed = false;
|
|
if (denierDisponible >= sumDenier) {
|
|
denierDisponible -= sumDenier;
|
|
this.optimizeArgent(denierDisponible);
|
|
msg = `Vous avez payé <strong>${sumDenier} Deniers</strong>, qui ont été soustraits de votre argent.`;
|
|
RdDAudio.PlayContextAudio("argent"); // Petit son
|
|
isPayed = true;
|
|
} else {
|
|
msg = "Vous n'avez pas assez d'argent pour paye cette somme !";
|
|
}
|
|
|
|
if (dataObj && isPayed) {
|
|
dataObj.payload.data.cout = sumDenier / 100; // Mise à jour du prix en sols , avec le prix acheté
|
|
dataObj.payload.data.quantite = quantite;
|
|
await this.createOwnedItem(dataObj.payload);
|
|
msg += `<br>Et l'objet <strong>${dataObj.payload.name}</strong> a été ajouté à votre inventaire.`;
|
|
}
|
|
|
|
let message = {
|
|
whisper: ChatUtility.getWhisperRecipientsAndGMs(this.name),
|
|
content: msg
|
|
};
|
|
ChatMessage.create(message);
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async monnaieIncDec(id, value) {
|
|
let monnaie = this.data.items.find(item => item.type == 'monnaie' && item._id == id);
|
|
if (monnaie) {
|
|
monnaie.data.quantite += value;
|
|
if (monnaie.data.quantite < 0) monnaie.data.quantite = 0; // Sanity check
|
|
const update = { _id: monnaie._id, 'data.quantite': monnaie.data.quantite };
|
|
const updated = await this.updateEmbeddedEntity("OwnedItem", update);
|
|
}
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async effectuerTacheAlchimie(recetteId, alchimieName, alchimieData) {
|
|
let recette = this.data.items.find(item => item.type == 'recettealchimique' && item._id == recetteId);
|
|
if (recette) {
|
|
let competence = this.data.items.find(item => item.type == 'competence' && item.name.toLowerCase() == "alchimie");
|
|
let diffAlchimie = RdDAlchimie.getDifficulte(alchimieData);
|
|
let rollData = {
|
|
recette: recette,
|
|
competence: competence,
|
|
diffLibre: diffAlchimie // Per default at startup
|
|
}
|
|
if (alchimieName == "couleur") {
|
|
rollData.selectedCarac = this.data.data.carac.vue,
|
|
rollData.alchimieTexte = `Couleurs ${alchimieData} (${diffAlchimie}) (Malus de -4 si vous ne possédez pas de Cristal Alchimique)`;
|
|
} else {
|
|
rollData.selectedCarac = this.data.data.carac.dexterite,
|
|
rollData.alchimieTexte = `Consistances ${alchimieData} (${diffAlchimie})`;
|
|
}
|
|
|
|
const dialog = await RdDRoll.create(this, rollData,
|
|
{
|
|
html: 'systems/foundryvtt-reve-de-dragon/templates/dialog-roll-alchimie.html',
|
|
},
|
|
{
|
|
name: 'tache-alchimique',
|
|
label: 'Tache Alchimique',
|
|
callbacks: [
|
|
this.createCallbackExperience(),
|
|
{ action: r => this._alchimieResult(r, false) }
|
|
]
|
|
}
|
|
);
|
|
dialog.render(true);
|
|
}
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
_alchimieResult(rollData) {
|
|
RdDResolutionTable.displayRollData(rollData, this, 'chat-resultat-alchimie.html');
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
buildVehiculesList() {
|
|
return this._buildActorLinksList(
|
|
this.data.data.subacteurs?.vehicules ?? [],
|
|
vehicle => {
|
|
return {
|
|
id: vehicle.id, name: vehicle.data.name, categorie: vehicle.data.data.categorie,
|
|
structure: vehicle.data.data.structure, img: vehicle.data.img
|
|
};
|
|
});
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
buildSuivantsList() {
|
|
return this._buildActorLinksList(this.data.data.subacteurs?.suivants ?? []);
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
buildMonturesList() {
|
|
return this._buildActorLinksList(this.data.data.subacteurs?.montures ?? []);
|
|
}
|
|
|
|
_buildActorLinksList(links, actorTransformation = it => { return { id: it.id, name: it.data.name, img: it.data.img }; }) {
|
|
return links.map(link => game.actors.get(link.id))
|
|
.filter(it => it != null)
|
|
.map(actorTransformation);
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async pushSubacteur(actor, dataArray, dataPath, dataName) {
|
|
let alreadyPresent = dataArray.find(attached => attached.id == actor.data._id);
|
|
if (!alreadyPresent) {
|
|
let newArray = duplicate(dataArray);
|
|
newArray.push({ id: actor.data._id });
|
|
await this.update({ [dataPath]: newArray });
|
|
} else {
|
|
ui.notifications.warn(dataName + " est déja attaché à ce Personnage.");
|
|
}
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
addSubacteur(actorId) {
|
|
let actor = game.actors.get(actorId);
|
|
//console.log("Ajout acteur : ", actor, this);
|
|
if (actor && actor.owner) {
|
|
if (actor.data.type == 'vehicule') {
|
|
this.pushSubacteur(actor, this.data.data.subacteurs.vehicules, 'data.subacteurs.vehicules', 'Ce Véhicule');
|
|
} else if (actor.data.type == 'creature') {
|
|
this.pushSubacteur(actor, this.data.data.subacteurs.montures, 'data.subacteurs.montures', 'Cette Monture');
|
|
} else if (actor.data.type == 'personnage') {
|
|
this.pushSubacteur(actor, this.data.data.subacteurs.suivants, 'data.subacteurs.suivants', 'Ce Suivant');
|
|
}
|
|
} else {
|
|
ui.notifications.warn("Vous n'avez pas les droits sur l'acteur que vous attachez.")
|
|
}
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async removeSubacteur(actorId) {
|
|
let newVehicules = this.data.data.subacteurs.vehicules.filter(function (obj, index, arr) { return obj.id != actorId });
|
|
let newSuivants = this.data.data.subacteurs.suivants.filter(function (obj, index, arr) { return obj.id != actorId });
|
|
let newMontures = this.data.data.subacteurs.montures.filter(function (obj, index, arr) { return obj.id != actorId });
|
|
await this.update({ 'data.subacteurs.vehicules': newVehicules });
|
|
await this.update({ 'data.subacteurs.suivants': newSuivants });
|
|
await this.update({ 'data.subacteurs.montures': newMontures });
|
|
}
|
|
/* -------------------------------------------- */
|
|
async onUpdateActor(update, options, actorId) {
|
|
const updatedEndurance = update?.data?.sante?.endurance;
|
|
if (updatedEndurance && options.diff) {
|
|
this.forceStatusEffectId('unconscious', updatedEndurance.value == 0);
|
|
}
|
|
}
|
|
/* -------------------------------------------- */
|
|
async onCreateActiveEffect(effect, options) {
|
|
switch (StatusEffects.statusId(effect)) {
|
|
case 'sonne':
|
|
await this.setStateSonne(true);
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async onDeleteActiveEffect(effect, options) {
|
|
switch (StatusEffects.statusId(effect)) {
|
|
case 'sonne':
|
|
await this.setStateSonne(false);
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
enleverTousLesEffets() {
|
|
this.deleteEmbeddedEntity('ActiveEffect', Array.from(this.effects?.keys() ?? []));
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
listeEffets(matching = it => true) {
|
|
const all = Array.from(this.effects?.values() ?? []);
|
|
const filtered = all.filter(it => matching(it.data));
|
|
return filtered;
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async setStatusDemiReve(status) {
|
|
if (status) {
|
|
await this.addStatusEffect(StatusEffects.demiReve())
|
|
}
|
|
else {
|
|
this.deleteStatusEffect(StatusEffects.demiReve())
|
|
}
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async setStatusSonne(sonne) {
|
|
if (this.isEntiteCauchemar()) {
|
|
return;
|
|
}
|
|
await this.forceStatusEffectId('sonne', sonne);
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async forceStatusEffectId(statusId, sonne) {
|
|
if (sonne) {
|
|
await this.addStatusEffectById(statusId);
|
|
}
|
|
else {
|
|
this.deleteStatusEffectById(statusId);
|
|
}
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
deleteStatusEffectById(id, options = { renderSheet: true }) {
|
|
const effects = Array.from(this.effects?.values())
|
|
.filter(it => it.data.flags.core?.statusId == id);
|
|
this._deleteStatusEffects(effects, options);
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
deleteStatusEffect(effect, options = { renderSheet: true }) {
|
|
const toDelete = Array.from(this.effects?.values())
|
|
.filter(it => StatusEffects.statusId(it.data) == StatusEffects.statusId(effect));
|
|
this._deleteStatusEffects(toDelete, options);
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
_deleteStatusEffects(effects, options) {
|
|
this._deleteStatusEffectsByIds(effects.map(it => it.id), options);
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
_deleteStatusEffectsByIds(effectIds, options) {
|
|
this.deleteEmbeddedEntity('ActiveEffect', effectIds, options);
|
|
this.applyActiveEffects();
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async addStatusEffectById(id, options = { renderSheet: true }) {
|
|
const statusEffect = CONFIG.statusEffects.find(it => it.id == id);
|
|
await this.addStatusEffect(statusEffect, options);
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async addStatusEffect(statusEffect, options = { renderSheet: true }) {
|
|
this.deleteStatusEffectById(statusEffect.id, options);
|
|
const effet = duplicate(statusEffect);
|
|
effet["flags.core.statusId"] = effet.id;
|
|
await this.createEmbeddedEntity('ActiveEffect', effet, options);
|
|
this.applyActiveEffects();
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async updateEmbeddedEntity(embeddedName, data, options) {
|
|
if (data && data['data.defaut_carac'] && data['data.xp']) { // C'est une compétence
|
|
this.checkCompetenceXP(data['name'], data['data.xp']);
|
|
}
|
|
return super.updateEmbeddedEntity(embeddedName, data, options);
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async onCreateOwnedItem(item, options, id) {
|
|
switch (item.type) {
|
|
case 'tete':
|
|
case 'queue':
|
|
case 'ombre':
|
|
case 'souffle':
|
|
await this.onCreateOwnedDraconique(item, options, id);
|
|
break;
|
|
}
|
|
}
|
|
|
|
async onDeleteOwnedItem(item, options, id) {
|
|
switch (item.type) {
|
|
case 'tete':
|
|
case 'queue':
|
|
case 'ombre':
|
|
case 'souffle':
|
|
await this.onDeleteOwnedDraconique(item, options, id);
|
|
break;
|
|
}
|
|
}
|
|
|
|
async onCreateOwnedDraconique(item, options, id) {
|
|
|
|
let draconique = Draconique.all().find(it => it.match(item));
|
|
if (draconique) {
|
|
draconique.onActorCreateOwned(this, item)
|
|
|
|
this.notifyGestionTeteSouffleQueue(item, draconique.manualMessage());
|
|
}
|
|
}
|
|
|
|
async onDeleteOwnedDraconique(item, options, id) {
|
|
|
|
let draconique = Draconique.all().find(it => it.match(item));
|
|
if (draconique) {
|
|
draconique.onActorDeleteOwned(this, item)
|
|
}
|
|
}
|
|
|
|
notifyGestionTeteSouffleQueue(item, manualMessage=true){
|
|
ChatMessage.create({
|
|
whisper: ChatUtility.getWhisperRecipientsAndGMs(game.user.name),
|
|
content: `${this.name} a reçu un/une ${item.type}: ${item.name}, qui ${manualMessage ? "n'est pas" : "est" } géré automatiquement. ${manualMessage ?? ''}`
|
|
});
|
|
}}
|
|
|