Vincent Vandemeulebrouck
3b06bd382b
Dans les messages d'automatisation de combat, le nom des tokens est utilisé au lieu d'utiliser le nom de l'acteur. Ceci permet de ne pas dévoiler un nom générique (Villageois) si le token a un nom personnalisé.
513 lines
17 KiB
JavaScript
513 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 { ITEM_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[ITEM_TYPES.empoignade].filter(it => it.system.pointsemp <= 0))
|
|
.concat(this.itemTypes[ITEM_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[ITEM_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[ITEM_TYPES.possession].find(it => it.system.possessionid == possessionId);
|
|
}
|
|
getPossessions() {
|
|
return this.itemTypes[ITEM_TYPES.possession];
|
|
}
|
|
getEmpoignades() {
|
|
return this.itemTypes[ITEM_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) {
|
|
if (Grammar.equalsInsensitive(caracName, 'taille')) {
|
|
return
|
|
}
|
|
RdDEmpoignade.checkEmpoignadeEnCours(this)
|
|
let selectedCarac = this.getCaracByName(caracName)
|
|
console.log("selectedCarac", selectedCarac)
|
|
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, arme: undefined }) {
|
|
RdDEmpoignade.checkEmpoignadeEnCours(this)
|
|
const competence = this.getCompetence(idOrName);
|
|
let rollData = { carac: this.system.carac, competence: competence, arme: options.arme }
|
|
if (competence.type == ITEM_TYPES.competencecreature) {
|
|
const token = RdDUtility.getSelectedToken(this)
|
|
const arme = RdDItemCompetenceCreature.armeCreature(competence)
|
|
if (arme && options.tryTarget && Targets.hasTargets()) {
|
|
Targets.selectOneTargetToken(target => {
|
|
if (arme.action == "possession") {
|
|
RdDPossession.onAttaquePossession(target, this, competence)
|
|
}
|
|
else {
|
|
RdDCombat.rddCombatTarget(target, this, token).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, token) {
|
|
token = token ?? RdDUtility.getSelectedToken(this)
|
|
const 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, arme: arme })
|
|
}
|
|
});
|
|
return
|
|
}
|
|
|
|
Targets.selectOneTargetToken(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, token).attaque(competence, arme);
|
|
})
|
|
}
|
|
|
|
$getCompetenceArme(arme, competenceName) {
|
|
return RdDItemArme.getCompetenceArme(arme, competenceName)
|
|
}
|
|
|
|
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(
|
|
{
|
|
roll: encaissement.roll,
|
|
content: await renderTemplate('systems/foundryvtt-reve-de-dragon/templates/chat-resultat-encaissement.html', encaissement)
|
|
},
|
|
this
|
|
)
|
|
|
|
if (!encaissement.hasPlayerOwner && encaissement.endurance != 0) {
|
|
encaissement = foundry.utils.duplicate(encaissement)
|
|
encaissement.isGM = true
|
|
ChatMessage.create({
|
|
whisper: ChatUtility.getGMs(),
|
|
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(actor) {
|
|
ui.notifications.error("Impossible de s'accorder à " + this.name + ": ce n'est pas une entité incarnée");
|
|
}
|
|
|
|
}
|