Vincent Vandemeulebrouck
a103239288
Quand mergeObject est utilisé pour retourner une valeur, faire très attention à ne pas passer un Item/Actor, ou une de ses sous parties en premier paramètre sans préciser l'option { inplace: false } Sinon, le premier paramètre subit une mutation!
516 lines
17 KiB
JavaScript
516 lines
17 KiB
JavaScript
import { ChatUtility } from "../chat-utility.js";
|
|
import { DialogValidationEncaissement } from "../dialog-validation-encaissement.js";
|
|
import { Grammar } from "../grammar.js";
|
|
import { RdDItemCompetence } from "../item-competence.js";
|
|
import { Misc } from "../misc.js";
|
|
import { RdDEmpoignade } from "../rdd-empoignade.js";
|
|
import { RdDResolutionTable } from "../rdd-resolution-table.js";
|
|
import { RdDEncaisser } from "../rdd-roll-encaisser.js";
|
|
import { RdDRoll } from "../rdd-roll.js";
|
|
import { RdDUtility } from "../rdd-utility.js";
|
|
import { ReglesOptionnelles } from "../settings/regles-optionnelles.js";
|
|
import { RdDBaseActor } from "./base-actor.js";
|
|
import { RdDItemCompetenceCreature } from "../item-competencecreature.js";
|
|
import { StatusEffects } from "../settings/status-effects.js";
|
|
import { TYPES } from "../item.js";
|
|
import { Targets } from "../targets.js";
|
|
import { RdDPossession } from "../rdd-possession.js";
|
|
import { RdDCombat } from "../rdd-combat.js";
|
|
import { RdDConfirm } from "../rdd-confirm.js";
|
|
import { ENTITE_INCARNE, SHOW_DICE, SYSTEM_RDD } from "../constants.js";
|
|
import { RdDItemArme } from "../item-arme.js";
|
|
|
|
const POSSESSION_SANS_DRACONIC = {
|
|
img: 'systems/foundryvtt-reve-de-dragon/icons/entites/possession.webp',
|
|
name: 'Sans draconic',
|
|
system: {
|
|
niveau: 0,
|
|
defaut_carac: "reve-actuel",
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Classe de base pour les acteurs disposant de rêve (donc, pas des objets)
|
|
* - Entités de rêve
|
|
* - Créatures de "sang": créatures et humanoides
|
|
*/
|
|
export class RdDBaseActorReve extends RdDBaseActor {
|
|
|
|
getCaracChanceActuelle() {
|
|
return {
|
|
label: 'Chance actuelle',
|
|
value: this.getChanceActuel(),
|
|
type: "number"
|
|
};
|
|
}
|
|
|
|
getCaracReveActuel() {
|
|
return {
|
|
label: 'Rêve actuel',
|
|
value: this.getReveActuel(),
|
|
type: "number"
|
|
};
|
|
}
|
|
|
|
getReveActuel() { return this.getReve() }
|
|
getChanceActuel() { return this.getChance() }
|
|
|
|
getReve() { return Number(this.system.carac.reve?.value ?? 0) }
|
|
getForce() { return this.getReve() }
|
|
getTaille() { return Number(this.system.carac.taille?.value ?? 0) }
|
|
getAgilite() { return this.getForce() }
|
|
getChance() { return this.getReve() }
|
|
getMoralTotal() { return 0 }
|
|
getBonusDegat() { return Number(this.system.attributs?.plusdom?.value ?? 0) }
|
|
getProtectionNaturelle() { return Number(this.system.attributs?.protection?.value ?? 0) }
|
|
getSConst() { return 0 }
|
|
|
|
/* -------------------------------------------- */
|
|
getEncombrementMax() { return 0 }
|
|
isSurenc() { return false }
|
|
computeMalusSurEncombrement() { return 0 }
|
|
|
|
ajustementAstrologique() { return 0 }
|
|
getMalusArmure() { return 0 }
|
|
|
|
getEnduranceActuelle() {
|
|
return Number(this.system.sante?.endurance?.value ?? 0);
|
|
}
|
|
async jetEndurance(resteEndurance = undefined) { return { jetEndurance: 0, sonne: false } }
|
|
isDead() { return false }
|
|
blessuresASoigner() { return [] }
|
|
getEtatGeneral(options = { ethylisme: false }) { return 0 }
|
|
|
|
async computeArmure(attackerRoll) { return this.getProtectionNaturelle() }
|
|
async remiseANeuf() { }
|
|
async appliquerAjoutExperience(rollData, hideChatMessage = 'show') { }
|
|
|
|
async santeIncDec(name, inc, isCritique = false) { }
|
|
|
|
async finDeRound(options = { terminer: false }) {
|
|
await this.$finDeRoundSuppressionEffetsTermines(options);
|
|
await this.finDeRoundBlessures();
|
|
await this.$finDeRoundSupprimerObsoletes();
|
|
await this.$finDeRoundEmpoignade();
|
|
}
|
|
|
|
async $finDeRoundSuppressionEffetsTermines(options) {
|
|
for (let effect of this.getEffects()) {
|
|
if (effect.duration.type !== 'none' && (effect.duration.remaining <= 0 || options.terminer)) {
|
|
await effect.delete();
|
|
ChatMessage.create({ content: `${this.name} n'est plus ${Misc.lowerFirst(game.i18n.localize(effect.system.label))} !` });
|
|
}
|
|
}
|
|
}
|
|
|
|
async finDeRoundBlessures() {
|
|
}
|
|
|
|
async $finDeRoundSupprimerObsoletes() {
|
|
const obsoletes = []
|
|
.concat(this.itemTypes[TYPES.empoignade].filter(it => it.system.pointsemp <= 0))
|
|
.concat(this.itemTypes[TYPES.possession].filter(it => it.system.compteur < -2 || it.system.compteur > 2))
|
|
.map(it => it.id);
|
|
await this.deleteEmbeddedDocuments('Item', obsoletes);
|
|
}
|
|
|
|
async $finDeRoundEmpoignade() {
|
|
const immobilisations = this.itemTypes[TYPES.empoignade].filter(it => it.system.pointsemp >= 2 && it.system.empoigneurid == this.id);
|
|
immobilisations.forEach(emp => RdDEmpoignade.onImmobilisation(this,
|
|
game.actors.get(emp.system.empoigneid),
|
|
emp
|
|
))
|
|
}
|
|
|
|
async setSonne(sonne = true) { }
|
|
|
|
/* -------------------------------------------- */
|
|
getCompetence(idOrName, options = {}) {
|
|
if (idOrName instanceof Item) {
|
|
return idOrName.isCompetence() ? idOrName : undefined
|
|
}
|
|
return RdDItemCompetence.findCompetence(this.items, idOrName, options)
|
|
}
|
|
getCompetences(name) {
|
|
return RdDItemCompetence.findCompetences(this.items, name)
|
|
}
|
|
getCompetenceCorpsACorps(options = {}) {
|
|
return this.getCompetence("Corps à corps", options)
|
|
}
|
|
getCompetencesEsquive() {
|
|
return this.getCompetences("esquive")
|
|
}
|
|
|
|
getArmeParade(armeParadeId) {
|
|
const item = armeParadeId ? this.getEmbeddedDocument('Item', armeParadeId) : undefined;
|
|
return RdDItemArme.getArme(item);
|
|
}
|
|
|
|
getDraconicOuPossession() {
|
|
return POSSESSION_SANS_DRACONIC
|
|
}
|
|
|
|
getPossession(possessionId) {
|
|
return this.itemTypes[TYPES.possession].find(it => it.system.possessionid == possessionId);
|
|
}
|
|
getPossessions() {
|
|
return this.itemTypes[TYPES.possession];
|
|
}
|
|
getEmpoignades() {
|
|
return this.itemTypes[TYPES.empoignade];
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async updateCreatureCompetence(idOrName, fieldName, value) {
|
|
let competence = this.getCompetence(idOrName);
|
|
if (competence) {
|
|
function getFieldPath(fieldName) {
|
|
switch (fieldName) {
|
|
case "niveau": return 'system.niveau';
|
|
case "dommages": return 'system.dommages';
|
|
case "carac_value": return 'system.carac_value';
|
|
}
|
|
return undefined
|
|
}
|
|
const path = getFieldPath(fieldName);
|
|
if (path) {
|
|
await competence.update({ [path]: value });
|
|
}
|
|
}
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
isEffectAllowed(effectId) { return false }
|
|
|
|
getEffects(filter = e => true) {
|
|
return this.getEmbeddedCollection("ActiveEffect").filter(filter);
|
|
}
|
|
|
|
getEffect(effectId) {
|
|
return this.getEmbeddedCollection("ActiveEffect").find(it => it.statuses?.has(effectId));
|
|
}
|
|
|
|
async setEffect(effectId, status) {
|
|
if (this.isEffectAllowed(effectId)) {
|
|
const effect = this.getEffect(effectId);
|
|
if (!status && effect) {
|
|
await this.deleteEmbeddedDocuments('ActiveEffect', [effect.id]);
|
|
}
|
|
if (status && !effect) {
|
|
await this.createEmbeddedDocuments("ActiveEffect", [StatusEffects.prepareActiveEffect(effectId)]);
|
|
}
|
|
}
|
|
}
|
|
|
|
async removeEffect(id) {
|
|
const effect = this.getEmbeddedCollection("ActiveEffect").find(it => it.id == id);
|
|
if (effect) {
|
|
await this.deleteEmbeddedDocuments('ActiveEffect', [id]);
|
|
}
|
|
}
|
|
|
|
async removeEffects(filter = e => true) {
|
|
if (game.user.isGM) {
|
|
const ids = this.getEffects(filter).map(it => it.id);
|
|
await this.deleteEmbeddedDocuments('ActiveEffect', ids);
|
|
}
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
getSurprise(isCombat = undefined) {
|
|
let niveauSurprise = this.getEffects()
|
|
.map(effect => StatusEffects.valeurSurprise(effect, isCombat))
|
|
.reduce(Misc.sum(), 0);
|
|
if (niveauSurprise > 1) {
|
|
return 'totale';
|
|
}
|
|
if (niveauSurprise == 1) {
|
|
return 'demi';
|
|
}
|
|
return '';
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async computeEtatGeneral() {
|
|
// Par défaut, on ne calcule pas d'état général, seuls les personnages/créatures sont affectés
|
|
this.system.compteurs.etat.value = 0;
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async openRollDialog({ name, label, template, rollData, callbackAction }) {
|
|
const dialog = await RdDRoll.create(this, rollData,
|
|
{ html: template, close: async html => await this._onCloseRollDialog(html) },
|
|
{
|
|
name: name,
|
|
label: label,
|
|
callbacks: [
|
|
this.createCallbackExperience(),
|
|
this.createCallbackAppelAuMoral(),
|
|
{ action: callbackAction }
|
|
]
|
|
});
|
|
dialog.render(true);
|
|
return dialog
|
|
}
|
|
|
|
createEmptyCallback() {
|
|
return {
|
|
condition: r => false,
|
|
action: r => { }
|
|
};
|
|
}
|
|
createCallbackExperience() { return this.createEmptyCallback(); }
|
|
createCallbackAppelAuMoral() { return this.createEmptyCallback(); }
|
|
async _onCloseRollDialog(html) { }
|
|
|
|
/* -------------------------------------------- */
|
|
async roll() {
|
|
RdDEmpoignade.checkEmpoignadeEnCours(this)
|
|
|
|
const carac = this.getCarac()
|
|
const selectedCaracName = ['apparence', 'perception', 'force', 'reve'].find(it => carac[it] != undefined)
|
|
|
|
await this.openRollDialog({
|
|
name: `jet-${this.id}`,
|
|
label: `Jet de ${this.name}`,
|
|
template: 'systems/foundryvtt-reve-de-dragon/templates/dialog-roll.html',
|
|
rollData: {
|
|
carac: carac,
|
|
selectedCarac: carac[selectedCaracName],
|
|
selectedCaracName: selectedCaracName,
|
|
competences: this.itemTypes['competence']
|
|
},
|
|
callbackAction: r => this.$onRollCaracResult(r)
|
|
});
|
|
}
|
|
|
|
getCarac() {
|
|
// TODO: le niveau d'une entité de cauchemar devrait être exclu...
|
|
return foundry.utils.mergeObject(this.system.carac,
|
|
{
|
|
'reve-actuel': this.getCaracReveActuel(),
|
|
'chance-actuelle': this.getCaracChanceActuelle()
|
|
},
|
|
{ inplace: false })
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async rollCarac(caracName, jetResistance = undefined) {
|
|
RdDEmpoignade.checkEmpoignadeEnCours(this)
|
|
let selectedCarac = this.getCaracByName(caracName)
|
|
await this.openRollDialog({
|
|
name: 'jet-' + caracName,
|
|
label: 'Jet ' + Grammar.apostrophe('de', selectedCarac.label),
|
|
template: 'systems/foundryvtt-reve-de-dragon/templates/dialog-roll-carac.html',
|
|
rollData: {
|
|
selectedCarac: selectedCarac,
|
|
competences: this.itemTypes['competence'],
|
|
jetResistance: jetResistance ? caracName : undefined
|
|
},
|
|
callbackAction: r => this.$onRollCaracResult(r)
|
|
});
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async $onRollCaracResult(rollData) {
|
|
// Final chat message
|
|
await RdDResolutionTable.displayRollData(rollData, this, 'chat-resultat-general.html');
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async rollCompetence(idOrName, options = { tryTarget: true }) {
|
|
RdDEmpoignade.checkEmpoignadeEnCours(this)
|
|
const competence = this.getCompetence(idOrName);
|
|
let rollData = { carac: this.system.carac, competence: competence }
|
|
if (competence.type == TYPES.competencecreature) {
|
|
const arme = RdDItemCompetenceCreature.armeCreature(competence)
|
|
if (arme && options.tryTarget && Targets.hasTargets()) {
|
|
Targets.selectOneToken(target => {
|
|
if (arme.action == "possession") {
|
|
RdDPossession.onAttaquePossession(target, this, competence)
|
|
}
|
|
else {
|
|
RdDCombat.rddCombatTarget(target, this).attaque(competence, arme)
|
|
}
|
|
});
|
|
return;
|
|
}
|
|
// Transformer la competence de créature
|
|
RdDItemCompetenceCreature.setRollDataCreature(rollData)
|
|
}
|
|
|
|
await this.openRollDialog({
|
|
name: 'jet-competence',
|
|
label: 'Jet ' + Grammar.apostrophe('de', competence.name),
|
|
template: 'systems/foundryvtt-reve-de-dragon/templates/dialog-roll-competence.html',
|
|
rollData: rollData,
|
|
callbackAction: r => this.$onRollCompetence(r, options)
|
|
});
|
|
}
|
|
|
|
async $onRollCompetence(rollData, options) {
|
|
await RdDResolutionTable.displayRollData(rollData, this, 'chat-resultat-competence.html')
|
|
if (options?.onRollAutomate) {
|
|
options.onRollAutomate(rollData);
|
|
}
|
|
}
|
|
|
|
/** --------------------------------------------
|
|
* @param {*} arme item d'arme/compétence de créature
|
|
* @param {*} categorieArme catégorie d'attaque à utiliser: competence (== melee), lancer, tir; naturelle, possession
|
|
* @returns
|
|
*/
|
|
rollArme(arme, categorieArme = "competence") {
|
|
let compToUse = this.$getCompetenceArme(arme, categorieArme)
|
|
if (!RdDItemArme.isArmeUtilisable(arme)) {
|
|
ui.notifications.warn(`Arme inutilisable: ${arme.name} a une résistance de 0 ou moins`)
|
|
return
|
|
}
|
|
if (!Targets.hasTargets()) {
|
|
RdDConfirm.confirmer({
|
|
settingConfirmer: "confirmer-combat-sans-cible",
|
|
content: `<p>Voulez vous faire un jet de ${compToUse} sans choisir de cible valide?
|
|
<br>Tous les jets de combats devront être gérés à la main
|
|
</p>`,
|
|
title: 'Ne pas utiliser les automatisation de combat',
|
|
buttonLabel: "Pas d'automatisation",
|
|
onAction: async () => {
|
|
this.rollCompetence(compToUse, { tryTarget: false })
|
|
}
|
|
});
|
|
return
|
|
}
|
|
|
|
Targets.selectOneToken(target => {
|
|
if (Targets.isTargetEntite(target)) {
|
|
ui.notifications.warn(`Vous ne pouvez pas attaquer une entité non incarnée avec votre ${arme.name}!!!!`);
|
|
return
|
|
}
|
|
|
|
const competence = this.getCompetence(compToUse)
|
|
if (competence.isCompetencePossession()) {
|
|
return RdDPossession.onAttaquePossession(target, this, competence);
|
|
}
|
|
RdDCombat.rddCombatTarget(target, this).attaque(competence, arme);
|
|
})
|
|
}
|
|
|
|
$getCompetenceArme(arme, competenceName) {
|
|
switch (arme.type) {
|
|
case TYPES.competencecreature:
|
|
return arme.name
|
|
case TYPES.arme:
|
|
switch (competenceName) {
|
|
case 'competence': return arme.system.competence;
|
|
case 'unemain': return RdDItemArme.competence1Mains(arme);
|
|
case 'deuxmains': return RdDItemArme.competence2Mains(arme);
|
|
case 'tir': return arme.system.tir;
|
|
case 'lancer': return arme.system.lancer;
|
|
}
|
|
}
|
|
return undefined
|
|
}
|
|
|
|
verifierForceMin(item) {
|
|
}
|
|
/* -------------------------------------------- */
|
|
async resetItemUse() { }
|
|
async incDecItemUse(itemId, inc = 1) { }
|
|
getItemUse(itemId) { return 0; }
|
|
|
|
/* -------------------------------------------- */
|
|
async encaisser() { await RdDEncaisser.encaisser(this) }
|
|
|
|
async encaisserDommages(rollData, attacker = undefined, show = undefined) {
|
|
if (attacker && !await attacker.accorder(this, 'avant-encaissement')) {
|
|
return;
|
|
}
|
|
const armure = await this.computeArmure(rollData);
|
|
if (ReglesOptionnelles.isUsing('validation-encaissement-gr')){
|
|
await this.encaisserDommagesValidationGR(rollData, armure, attacker?.id, show);
|
|
}
|
|
else {
|
|
const jet = await RdDUtility.jetEncaissement(rollData, armure, { showDice: SHOW_DICE });
|
|
await this.$onEncaissement(jet, show, attacker);
|
|
}
|
|
}
|
|
|
|
async encaisserDommagesValidationGR(rollData, armure, attackerId, show) {
|
|
if (!game.user.isGM) {
|
|
RdDBaseActor.remoteActorCall({
|
|
tokenId: this.token?.id,
|
|
actorId: this.id,
|
|
method: 'encaisserDommagesValidationGR',
|
|
args: [rollData, armure, attackerId, show]
|
|
});
|
|
} else {
|
|
const attacker = game.actors.get(attackerId);
|
|
DialogValidationEncaissement.validerEncaissement(this, rollData, armure,
|
|
jet => this.$onEncaissement(jet, show, attacker));
|
|
}
|
|
}
|
|
|
|
async $onEncaissement(jet, show, attacker) {
|
|
await this.onAppliquerJetEncaissement(jet, attacker);
|
|
await this.$afficherEncaissement(jet, show);
|
|
}
|
|
|
|
async onAppliquerJetEncaissement(encaissement, attacker) { }
|
|
|
|
async $afficherEncaissement(encaissement, show) {
|
|
foundry.utils.mergeObject(encaissement, {
|
|
alias: this.name,
|
|
hasPlayerOwner: this.hasPlayerOwner,
|
|
show: show ?? {}
|
|
});
|
|
|
|
await 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 = foundry.utils.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)
|
|
});
|
|
}
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async accorder(entite, when = 'avant-encaissement') {
|
|
if (when != game.settings.get(SYSTEM_RDD, "accorder-entite-cauchemar")
|
|
|| entite == undefined
|
|
|| !entite.isEntite([ENTITE_INCARNE])
|
|
|| entite.isEntiteAccordee(this)) {
|
|
return true;
|
|
}
|
|
const rolled = await RdDResolutionTable.roll(this.getReveActuel(), - Number(entite.system.carac.niveau.value));
|
|
const rollData = {
|
|
alias: this.name,
|
|
rolled: rolled,
|
|
entite: entite.name,
|
|
selectedCarac: this.system.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;
|
|
}
|
|
|
|
isEntiteAccordee(attacker) { return true }
|
|
|
|
async setEntiteReveAccordee(attacker) {
|
|
ui.notifications.error("Impossible de s'accorder à " + this.name + ": ce n'est pas une entite de cauchemer/rêve");
|
|
}
|
|
|
|
}
|