526 lines
18 KiB
JavaScript
526 lines
18 KiB
JavaScript
import { ENTITE_INCARNE, SHOW_DICE, SYSTEM_RDD } from "../constants.js";
|
|
import { Grammar } from "../grammar.js";
|
|
import { Misc } from "../misc.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 { ITEM_TYPES } from "../item.js";
|
|
import { RdDItemCompetence } from "../item-competence.js";
|
|
import { RdDItemCompetenceCreature } from "../item-competencecreature.js";
|
|
import { RdDItemArme } from "../item-arme.js";
|
|
import { StatusEffects } from "../settings/status-effects.js";
|
|
import { Targets } from "../targets.js";
|
|
import { RdDConfirm } from "../rdd-confirm.js";
|
|
import { RdDCarac } from "../rdd-carac.js";
|
|
|
|
import { ChatUtility } from "../chat-utility.js";
|
|
import { DialogValidationEncaissement } from "../dialog-validation-encaissement.js";
|
|
import { RdDCombat } from "../rdd-combat.js";
|
|
import { RdDEmpoignade } from "../rdd-empoignade.js";
|
|
import { RdDPossession } from "../rdd-possession.js";
|
|
import { BASE_CORPS_A_CORPS, BASE_ESQUIVE, POSSESSION_SANS_DRACONIC } from "../item/base-items.js";
|
|
|
|
/**
|
|
* 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 {
|
|
|
|
prepareActorData() {
|
|
super.prepareActorData()
|
|
this.system.attributs.plusdom.value = this.getBonusDegat()
|
|
this.system.sante.endurance.max = this.getEnduranceMax()
|
|
this.system.sante.endurance.value = Math.min(this.system.sante.endurance.value, this.system.sante.endurance.max)
|
|
}
|
|
|
|
getCarac() {
|
|
return foundry.utils.mergeObject(this.system.carac,
|
|
{
|
|
'reve-actuel': this.getCaracReveActuel(),
|
|
'chance-actuelle': this.getCaracChanceActuelle()
|
|
},
|
|
{ inplace: false })
|
|
}
|
|
|
|
getCaracChanceActuelle() {
|
|
return {
|
|
label: 'Chance actuelle',
|
|
value: this.getChanceActuel(),
|
|
type: "number"
|
|
};
|
|
}
|
|
|
|
getCaracReveActuel() {
|
|
return {
|
|
label: 'Rêve actuel',
|
|
value: this.getReveActuel(),
|
|
type: "number"
|
|
};
|
|
}
|
|
|
|
getTaille() { return Misc.toInt(this.system.carac.taille?.value) }
|
|
getConstitution() { return this.getReve() }
|
|
getForce() { return this.getReve() }
|
|
getAgilite() { return this.getForce() }
|
|
getReve() { return Misc.toInt(this.system.carac.reve?.value) }
|
|
getChance() { return this.getReve() }
|
|
|
|
getReveActuel() { return this.getReve() }
|
|
getChanceActuel() { return this.getChance() }
|
|
|
|
getEnduranceMax() { return Math.max(1, this.getTaille() + this.getConstitution()) }
|
|
getEncombrementMax() { return (this.getForce() + this.getTaille()) / 2 }
|
|
getBonusDegat() { return RdDCarac.getCaracDerivee(this.getEncombrementMax()).plusdom }
|
|
|
|
getMoralTotal() { return 0 }
|
|
getProtectionNaturelle() { return Number(this.system.attributs?.protection?.value ?? 0) }
|
|
getSConst() { 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, options = { onMessage: message => { } }) {
|
|
return RdDItemCompetence.findCompetences(this.items, name, options)
|
|
}
|
|
|
|
getCompetenceCorpsACorps(options = { onMessage: message => { } }) {
|
|
return this.getCompetence(BASE_CORPS_A_CORPS.name, options) ?? BASE_CORPS_A_CORPS
|
|
}
|
|
|
|
getCompetencesEsquive(options = { onMessage: message => { } }) {
|
|
return this.getCompetences(BASE_ESQUIVE.name, options) ?? [BASE_ESQUIVE]
|
|
}
|
|
|
|
getArmeParade(armeParadeId) {
|
|
return RdDItemArme.getArme(armeParadeId ? this.getEmbeddedDocument('Item', armeParadeId) : undefined)
|
|
}
|
|
|
|
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)
|
|
});
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
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.getNiveau()));
|
|
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");
|
|
}
|
|
|
|
}
|