Vincent Vandemeulebrouck
dca9505925
Pour permettre de masquer les informations sur les PNJs secondaires, et ne pas dévoiler le nom de l'acteur
523 lines
18 KiB
JavaScript
523 lines
18 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, RdDCombatManager } 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 }
|
|
isSonne() { return false }
|
|
blessuresASoigner() { return [] }
|
|
getEtatGeneral(options = { ethylisme: false }) { return 0 }
|
|
isActorCombat() { return true }
|
|
|
|
getCaracInit(competence) {
|
|
if (!competence){
|
|
return 0
|
|
}
|
|
if (competence.type == ITEM_TYPES.competencecreature) {
|
|
return competence.system.carac_value
|
|
}
|
|
return this.system.carac[competence.system.defaut_carac].value;
|
|
}
|
|
listActionsCombat() {
|
|
return this.itemTypes[ITEM_TYPES.competencecreature]
|
|
.filter(it => RdDItemCompetenceCreature.isAttaque(it))
|
|
.map(it => RdDItemCompetenceCreature.armeCreature(it))
|
|
.filter(it => it != undefined);
|
|
}
|
|
|
|
|
|
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.getAlias()} 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);
|
|
}
|
|
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.getAlias()}`,
|
|
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.isUtilisable(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 encaisser() { await RdDEncaisser.encaisser(this) }
|
|
|
|
async encaisserDommages(rollData, attacker = undefined, show = undefined, attackerToken = undefined, defenderToken = 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, show, attackerToken, defenderToken);
|
|
}
|
|
else {
|
|
const jet = await RdDUtility.jetEncaissement(this, rollData, armure, { showDice: SHOW_DICE });
|
|
await this.$onEncaissement(jet, show, attackerToken, defenderToken)
|
|
}
|
|
}
|
|
|
|
async encaisserDommagesValidationGR(rollData, armure, show, attackerToken, defenderToken) {
|
|
if (!game.user.isGM) {
|
|
RdDBaseActor.remoteActorCall({
|
|
tokenId: this.token?.id,
|
|
actorId: this.id,
|
|
method: 'encaisserDommagesValidationGR',
|
|
args: [rollData, armure, show, attackerToken, defenderToken]
|
|
})
|
|
} else {
|
|
DialogValidationEncaissement.validerEncaissement(this, rollData, armure,
|
|
jet => this.$onEncaissement(jet, show, attackerToken, defenderToken));
|
|
}
|
|
}
|
|
|
|
async $onEncaissement(jet, show, attackerToken, defenderToken) {
|
|
await this.onAppliquerJetEncaissement(jet, attackerToken);
|
|
await this.$afficherEncaissement(jet, show, defenderToken);
|
|
}
|
|
|
|
async onAppliquerJetEncaissement(encaissement, attackerToken) { }
|
|
|
|
async $afficherEncaissement(encaissement, show, defenderToken) {
|
|
foundry.utils.mergeObject(encaissement, {
|
|
alias: defenderToken?.name ?? this.getAlias(),
|
|
hasPlayerOwner: this.hasPlayerOwner,
|
|
show: show ?? {}
|
|
}, { overwrite: false });
|
|
|
|
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.getAlias(),
|
|
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.getAlias() + ": ce n'est pas une entité incarnée");
|
|
}
|
|
|
|
}
|