Gestion défense et compteur de possession

This commit is contained in:
2025-12-02 23:58:17 +01:00
parent 706aa657b1
commit 19cabe816e
18 changed files with 282 additions and 137 deletions

View File

@@ -600,24 +600,7 @@ export class RdDBaseActorReve extends RdDBaseActor {
}
rollPossession() {
Targets.selectOneTargetToken(async target => {
const selectedToken = RdDUtility.getSelectedToken(this)
RollDialog.create(
{
ids: {
actorId: this.id,
actorTokenId: selectedToken.id,
opponentId: target.actor.id,
opponentTokenId: target.id
},
type: { allowed: [ROLL_TYPE_POSSESSION], current: ROLL_TYPE_POSSESSION, possession: { action: ACTIONS_POSSESSION.ATTAQUE } },
passeArme: foundry.utils.randomID(16),
},
{
onRollDone: RollDialog.onRollDoneClose,
callbacks: [async (roll) => await RdDPossessionV2.chatMessageDefensePossession(roll)]
})
})
RdDPossessionV2.rollAttaquePossession(this)
}
verifierForceMin(item) { }

View File

@@ -76,6 +76,15 @@ export class ChatUtility {
ChatUtility.removeMessages({ part: part });
}
static remover(chatMessage) {
const messageId = chatMessage.id;
if (messageId) {
return (..._) => ChatUtility.removeMessages({ messageId: messageId });
}
return (..._) => { }
}
static removeChatMessageId(messageId) {
if (messageId) {
ChatUtility.removeMessages({ messageId: messageId });

View File

@@ -369,8 +369,7 @@ export class RdDCombat {
}
static rddCombatForAttackV2(attackerRoll) {
const defenderRoll = RollBasicParts.prepareDefense(attackerRoll)
return RdDCombat.rddCombatForDefenseV2(defenderRoll)
return RdDCombat.rddCombatForDefenseV2(RollBasicParts.prepareDefense(attackerRoll))
}
static rddCombatForDefenseV2(defenderRoll) {
@@ -755,7 +754,7 @@ export class RdDCombat {
}
async _chatMessageDefenseV2(paramDemandeDefense) {
const attackerRoll = paramDemandeDefense.attackerRoll;
const attackerRoll = paramDemandeDefense.attackerRoll
RollBasicParts.loadSurprises(attackerRoll)
attackerRoll.dmg = RdDBonus.dmgRollV2(attackerRoll, attackerRoll.current.attaque)
@@ -1086,7 +1085,6 @@ export class RdDCombat {
async defenseV2(attackerRoll, callbacks = []) {
// this._prepareParade(attackerRoll, arme, competence);
RollDialog.loadRollData(attackerRoll)
await this.doRollDefense({
ids: {
actorId: this.defender.id,

View File

@@ -89,20 +89,20 @@ export class RdDEmpoignade {
const chatMessage = ChatUtility.getChatMessage(event);
const rollData = RdDEmpoignade.$readRollEmpoignade(chatMessage);
RdDEmpoignade.entrainerAuSol(rollData)
ChatUtility.removeChatMessageId(chatMessage.id)
ChatUtility.remover(chatMessage)()
});
$(html).on("click", '.empoignade-projeter-sol', event => {
const chatMessage = ChatUtility.getChatMessage(event);
const rollData = RdDEmpoignade.$readRollEmpoignade(chatMessage);
RdDEmpoignade.projeterAuSol(rollData)
ChatUtility.removeChatMessageId(chatMessage.id)
ChatUtility.remover(chatMessage)()
});
$(html).on("change", '.empoignade-perte-endurance', event => {
const chatMessage = ChatUtility.getChatMessage(event);
const rollData = RdDEmpoignade.$readRollEmpoignade(chatMessage);
if (event.currentTarget.value && event.currentTarget.value != "none") {
RdDEmpoignade.perteEndurance(rollData, event.currentTarget.value)
//ChatUtility.removeChatMessageId(chatMessage.id)
ChatUtility.remover(chatMessage)()
}
});
}

View File

@@ -1,8 +1,10 @@
import { ITEM_TYPES, RDD_CONFIG } from "./constants.js";
import { RdDRollResult } from "./rdd-roll-result.js";
import { RollBasicParts } from "./roll/roll-basic-parts.mjs";
import { ChatUtility } from "./chat-utility.js";
import RollDialog from "./roll/roll-dialog.mjs";
import { Targets } from "./targets.js";
import { RdDUtility } from "./rdd-utility.js";
import { ROLL_TYPE_POSSESSION } from "./roll/roll-constants.mjs";
export const ACTIONS_POSSESSION = {
ATTAQUE: 'attaque',
@@ -30,9 +32,9 @@ export class RdDPossessionV2 {
return victime.itemTypes[ITEM_TYPES.possession].find(poss => poss.system.entiteid == entite.id)
}
static async createPossession(entite, victime) {
static async createPossessionIfMissing(entite, victime) {
if (RdDPossessionV2.$isInverse(entite, victime)) {
return await RdDPossessionV2.createPossession(victime, entite)
return await RdDPossessionV2.createPossessionIfMissing(victime, entite)
}
const existing = RdDPossessionV2.findPossession(entite, victime)
if (!existing) {
@@ -45,21 +47,24 @@ export class RdDPossessionV2 {
}
}
static getPossessionDetails(rollData, action) {
const isEntite = rollData.active.actor.isEntiteNonIncarnee() && !rollData.opponent.actor.isEntiteNonIncarnee()
const itemPossession = RdDPossessionV2.findPossession(rollData.active.actor, rollData.opponent.actor)
const compteur = itemPossession?.system.compteur ?? 0
const isAttaque = RdDPossessionV2.isAttaque(action)
action = RdDPossessionV2.$getAction(isAttaque, isEntite)
static getTypePossession(activeActor, opponentActor) {
const isEntite = activeActor.isEntiteNonIncarnee() && !opponentActor.isEntiteNonIncarnee();
const itemPossession = RdDPossessionV2.findPossession(activeActor, opponentActor);
const compteur = itemPossession?.system.compteur ?? 0;
return {
action: action,
isEntite: isEntite,
isAttaque: isAttaque,
isPersonnage: rollData.active.actor.isPersonnage(),
isPersonnage: activeActor.isPersonnage(),
isCompteurPossession: Math.sign(compteur) >= 0,
compteur: Math.abs(compteur)
}
};
}
static getTypePossessionAction(roll, action) {
const possession = RdDPossessionV2.getTypePossession(roll.active.actor, roll.opponent.actor)
possession.isAttaque = RdDPossessionV2.isAttaque(action)
possession.action = RdDPossessionV2.$getAction(possession.isAttaque, possession.isEntite)
return possession
}
static $getAction(isAttaque, isEntite) {
@@ -80,34 +85,106 @@ export class RdDPossessionV2 {
return "lutte contre"
}
static isDefense(action) {
return [ACTIONS_POSSESSION.DEFENSE, ACTIONS_POSSESSION.DEFENSE_POSSESSION, ACTIONS_POSSESSION.DEFENSE_CONJURATION].includes(action)
}
static isAttaque(action) {
return ![ACTIONS_POSSESSION.DEFENSE, ACTIONS_POSSESSION.DEFENSE_POSSESSION, ACTIONS_POSSESSION.DEFENSE_CONJURATION].includes(action)
return [ACTIONS_POSSESSION.ATTAQUE, ACTIONS_POSSESSION.POSSEDER, ACTIONS_POSSESSION.CONJURER].includes(action)
}
static async addPointPossession(entite, victime, add) {
static async rollAttaquePossession(actor) {
Targets.selectOneTargetToken(async target => {
const selectedToken = RdDUtility.getSelectedToken(actor)
RollDialog.create(
{
ids: {
actorId: actor.id,
actorTokenId: selectedToken.id,
opponentId: target.actor.id,
opponentTokenId: target.id
},
passeArme: foundry.utils.randomID(16),
type: { allowed: [ROLL_TYPE_POSSESSION], current: ROLL_TYPE_POSSESSION, possession: { action: ACTIONS_POSSESSION.ATTAQUE } },
},
{
onRollDone: RollDialog.onRollDoneClose,
callbacks: [
async (roll) => await RdDPossessionV2.createPossessionIfMissing(roll.active.actor, roll.opponent.actor),
async (roll) => RdDPossessionV2.$setParticuliereFinesse(roll),
async (roll) => await RdDPossessionV2.chatMessageDefensePossession(roll)
]
})
})
}
static $setParticuliereFinesse(roll) {
if (roll.rolled.isPart) {
roll.particuliere = RDD_CONFIG.particuliere.finesse.key
}
}
static async rollDefensePossession(savedRoll, chatMessage) {
RollBasicParts.restore(savedRoll)
const attackerRoll = savedRoll.attackerRoll
RollDialog.create(
{
ids: savedRoll.ids,
passeArme: attackerRoll.passeArme,
type: { allowed: [ROLL_TYPE_POSSESSION], current: ROLL_TYPE_POSSESSION, possession: { action: ACTIONS_POSSESSION.DEFENSE } },
selected: { diff: { value: attackerRoll.selected?.diff?.value ?? 0 } },
attackerRoll: attackerRoll
},
{
onRollDone: RollDialog.onRollDoneClose, callbacks: [
async roll => await RdDPossessionV2.onRollDefense(roll),
ChatUtility.remover(chatMessage)
]
});
}
static async onRollDefense(defense) {
if (defense.rolled.isEchec) {
await RdDPossessionV2.addPointPossession(defense.opponent.actor, defense.active.actor)
}
RdDPossessionV2.resetPossession(defense)
}
static async onMarquerPointPossession(roll) {
roll.type = {}
await RdDPossessionV2.addPointPossession(roll.opponent.actor, roll.active.actor)
RdDPossessionV2.resetPossession(roll)
await ChatMessage.create({
// message privé: du défenseur à lui même (et aux GMs)
speaker: ChatMessage.getSpeaker({ actor: roll.active.actor, token: roll.active.token }),
alias: roll.opponent.name,
whisper: ChatUtility.getOwners(roll.active.actor),
content: await renderTemplate(`systems/foundryvtt-reve-de-dragon/templates/possession/chat-message-marquer.hbs`, roll)
})
}
static resetPossession(roll) {
roll.type.possession = foundry.utils.mergeObject(
roll.type.possession ?? {},
RdDPossessionV2.getTypePossession(roll.opponent.actor, roll.active.actor))
}
static async addPointPossession(entite, victime, points = 1) {
if (RdDPossessionV2.$isInverse(entite, victime)) {
return await RdDPossessionV2.addPointPossession(victime, entite, /* negate?*/ add)
return await RdDPossessionV2.addPointPossession(victime, entite, - points)
}
const existing = RdDPossessionV2.findPossession(entite, victime)
if (!existing) {
return
if (existing) {
const compteur = (existing.system.compteur ?? 0) + points
await victime.updateEmbeddedDocuments('Item', [{ _id: existing.id, 'system.compteur': compteur }])
}
const points = add ? 1 : -1
const compteur = (existing.system.compteur ?? 0) + points
await victime.updateEmbeddedDocuments('Item', [{ id: existing.id, 'system.compteur': compteur }])
}
static callbacksOnDefensePossession() {
return [async roll => {
}]
}
static async chatMessageDefensePossession(attackerRoll) {
const defense = RollBasicParts.prepareDefense(attackerRoll)
defense.type = {
possession: RdDPossessionV2.getPossessionDetails(defense, ACTIONS_POSSESSION.DEFENSE)
possession: RdDPossessionV2.getTypePossessionAction(defense, ACTIONS_POSSESSION.DEFENSE)
}
const chatDemandeDefense = await ChatMessage.create({
@@ -115,7 +192,7 @@ export class RdDPossessionV2 {
speaker: ChatMessage.getSpeaker({ actor: defense.active.actor, token: defense.active.token }),
alias: attackerRoll.active.name,
whisper: ChatUtility.getOwners(defense.active.actor),
content: await renderTemplate('systems/foundryvtt-reve-de-dragon/templates/chat-demande-defense-possession.hbs', defense)
content: await renderTemplate('systems/foundryvtt-reve-de-dragon/templates/possession/chat-demande-defense.hbs', defense)
});
ChatUtility.setMessageData(chatDemandeDefense, 'demande-defense', true)
// // flag pour garder les jets d'attaque/defense
@@ -126,7 +203,4 @@ export class RdDPossessionV2 {
})
}
static async chatMarquerPointPossession(roll) {
}
}

View File

@@ -776,8 +776,9 @@ export class RdDUtility {
let sommeAPayer = Number(event.currentTarget.attributes['data-somme-a-payer']?.value ?? 0);
let actor = RdDUtility.getSelectedActor("Pour effectuer le paiement:");
if (actor) {
actor.payerSols(sommeAPayer);
ChatUtility.removeChatMessageId(RdDUtility.findChatMessageId(event.currentTarget));
actor.payerSols(sommeAPayer)
const chatMessage = RdDUtility.findChatMessageId(event.currentTarget)
ChatUtility.remover(chatMessage)()
}
});
$(html).on("click", '.rdd-world-content-link', async event => {

View File

@@ -13,6 +13,8 @@ import { PART_ATTAQUE } from "./roll-part-attaque.mjs"
import { RdDRollTables } from "../rdd-rolltables.js"
import { RdDEmpoignade } from "../rdd-empoignade.js"
import { Misc } from "../misc.js"
import { RollBasicParts } from "./roll-basic-parts.mjs"
import { RdDPossessionV2 } from "../rdd-possession-v2.mjs"
export default class ChatRollResult {
static init() {
@@ -134,6 +136,8 @@ export default class ChatRollResult {
$(html).on("click", '.appel-chance', event => this.onClickAppelChance(event))
$(html).on("click", '.appel-destinee', event => this.onClickAppelDestinee(event))
$(html).on("click", '.button-defense', event => this.onClickDefense(event))
$(html).on("click", '.button-defense-possession', event => this.onClickDefensePossession(event))
$(html).on("click", '.marquer-point-possession', event => this.onClickMarquerPointPossession(event))
$(html).on("click", '.encaissement', event => this.onClickEncaissement(event))
$(html).on("click", '.resister-recul', event => this.onClickRecul(event))
$(html).on("click", '.choix-particuliere', event => this.onClickChoixParticuliere(event))
@@ -187,7 +191,7 @@ export default class ChatRollResult {
console.log('onAppelChanceSuccess savedRoll', savedRoll)
reRoll.type.retry = true
await this.updateChatMessage(chatMessage, reRoll)
const callbacks = [r => ChatUtility.removeChatMessageId(chatMessage.id)]
const callbacks = [ChatUtility.remover(chatMessage)]
// TODO: annuler les effets
switch (reRoll.type.current) {
@@ -226,12 +230,28 @@ export default class ChatRollResult {
async onClickDefense(event) {
const chatMessage = ChatUtility.getChatMessage(event)
const savedRoll = this.loadChatMessageRoll(chatMessage)
RollBasicParts.restore(savedRoll)
const attackerRoll = savedRoll.attackerRoll
RollDialog.loadRollData(attackerRoll)
this.getCombat(attackerRoll)?.defenseV2(attackerRoll,
[roll => { ChatUtility.removeChatMessageId(chatMessage.id) }]
[ChatUtility.remover(chatMessage)]
)
}
async onClickDefensePossession(event) {
const chatMessage = ChatUtility.getChatMessage(event)
const savedRoll = this.loadChatMessageRoll(chatMessage)
await RdDPossessionV2.rollDefensePossession(savedRoll, chatMessage)
}
async onClickMarquerPointPossession(event) {
const chatMessage = ChatUtility.getChatMessage(event)
const savedRoll = this.loadChatMessageRoll(chatMessage)
RollBasicParts.restore(savedRoll)
await RdDPossessionV2.onMarquerPointPossession(savedRoll)
ChatUtility.remover(chatMessage)()
}
async onClickEncaissement(event) {
const chatMessage = ChatUtility.getChatMessage(event)
const isMessageDemande = ChatUtility.getMessageData(chatMessage, 'demande-defense')
@@ -254,7 +274,7 @@ export default class ChatRollResult {
break
}
if (isMessageDemande) {
ChatUtility.removeChatMessageId(chatMessage.id)
ChatUtility.remover(chatMessage)()
} else {
savedRoll.done.encaissement = true
await this.updateChatMessage(chatMessage, savedRoll)

View File

@@ -11,7 +11,7 @@ export class RollBasicParts {
rollData.ids.sceneId = rollData.ids.sceneId ?? canvas.scene.id
rollData.active = RollBasicParts.getTokenActor(rollData)
rollData.opponent = RollBasicParts.getTokenActorOpponent(rollData)
if (rollData.type.opposed == undefined) {
if (rollData.type && rollData.type.opposed == undefined) {
rollData.type.opposed = rollData.opponent != null
}
}
@@ -46,10 +46,10 @@ export class RollBasicParts {
type: rollData.type,
ids: {
sceneId: rollData.ids.sceneId,
actorId: rollData.active.id,
actorTokenId: rollData.active.tokenId,
opponentId: isOpposed ? rollData.opponent.id : undefined,
opponentTokenId: isOpposed ? rollData.opponent.tokenId : undefined,
actorId: rollData.ids.actorId,
actorTokenId: rollData.ids.actorTokenId,
opponentId: isOpposed ? rollData.ids.opponentId : undefined,
opponentTokenId: isOpposed ? rollData.ids.opponentTokenId : undefined,
}
}
}

View File

@@ -318,15 +318,33 @@ export default class RollDialog extends HandlebarsApplicationMixin(ApplicationV2
return rollData
}
static saveParts(rollData, impacts) {
static saveParts(rollData, impacts = undefined) {
if (rollData == undefined) {
return undefined
}
function saveBasics(from, to) {
if (from) {
to.passeArme = from.passeArme
to.rolled = from.rolled
to.particuliere = from.particuliere
to.result = from.result
to.done = from.done ?? {}
to.dmg = from.dmg
if (from.attackerRoll) {
to.attackerRoll = {}
saveBasics(from.attackerRoll, to.attackerRoll)
}
}
}
const target = RollBasicParts.initFrom(rollData)
ROLL_PARTS.filter(p => p.isActive(rollData))
.forEach(p => p.storeClean(rollData, target))
target.attackerRoll = rollData.attackerRoll
target.rolled = rollData.rolled
target.result = rollData.result
target.done = rollData.done ?? {}
target.dmg = rollData.dmg
if (rollData.current) {
// stockage de current
ROLL_PARTS.filter(p => p.isActive(rollData))
.forEach(p => p.storeClean(rollData, target))
}
saveBasics(rollData, target)
if (impacts) {
target.reverse = {
active: impacts.active?.reverseImpacts(),
@@ -334,6 +352,7 @@ export default class RollDialog extends HandlebarsApplicationMixin(ApplicationV2
}
}
return target
}
constructor(rollData, rollOptions) {
@@ -352,7 +371,7 @@ export default class RollDialog extends HandlebarsApplicationMixin(ApplicationV2
}
this.chatRollResult = new ChatRollResult()
this.selectType()
this.registerHooks(rollData);
this.registerHooks(rollData)
}
registerHooks(rollData) {

View File

@@ -16,8 +16,8 @@ export class RollPartDefense extends RollPartSelect {
get code() { return PART_DEFENSE }
get section() { return ROLLDIALOG_SECTION.CHOIX }
isValid(rollData) { return rollData.attackerRoll != undefined }
visible(rollData) { return this.isRollType(rollData, ROLL_TYPE_DEFENSE) }
isValid(rollData) { return this.isRollType(rollData, ROLL_TYPE_DEFENSE) && rollData.attackerRoll != undefined }
visible(rollData) { return this.isRollType(rollData, ROLL_TYPE_DEFENSE) && rollData.attackerRoll != undefined }
static getDiffAttaque(attackerRoll) {
// TODO: rollDataV2?

View File

@@ -62,7 +62,7 @@ export class RollPartPossession extends RollPartSelect {
})
button?.addEventListener("click", async e => {
e.preventDefault()
await RdDPossessionV2.createPossession(rollData.active.actor, rollData.opponent.actor)
await RdDPossessionV2.createPossessionIfMissing(rollData.active.actor, rollData.opponent.actor)
rollDialog.render()
})
}

View File

@@ -1,8 +1,9 @@
import { RDD_CONFIG } from "../constants.js"
import { Misc } from "../misc.js"
import { RdDPossessionV2 } from "../rdd-possession-v2.mjs"
import { ReglesOptionnelles } from "../settings/regles-optionnelles.js"
import { demiReveStatusEffect, StatusEffects } from "../settings/status-effects.js"
import { ROLL_TYPE_ATTAQUE, ROLL_TYPE_DEFENSE } from "./roll-constants.mjs"
import { StatusEffects } from "../settings/status-effects.js"
import { ROLL_TYPE_ATTAQUE, ROLL_TYPE_DEFENSE, ROLL_TYPE_POSSESSION } from "./roll-constants.mjs"
import { ROLLDIALOG_SECTION, RollPart } from "./roll-part.mjs"
export const PART_SIGN = "sign"
@@ -25,7 +26,7 @@ export class RollPartSign extends RollPart {
}
isCombat(rollData) {
return [ROLL_TYPE_ATTAQUE, ROLL_TYPE_DEFENSE].includes(rollData.type.current) || rollData.type.isCombat
return [ROLL_TYPE_ATTAQUE, ROLL_TYPE_DEFENSE, ROLL_TYPE_POSSESSION].includes(rollData.type.current)
}
prepareContext(rollData) {
@@ -40,7 +41,7 @@ export class RollPartSign extends RollPart {
const isCombat = this.isCombat(rollData)
const current = this.getCurrent(rollData)
current.armeDisparate = isCombat && current.armeDisparate
current.surprise = actor.getSurprise(isCombat, current.forceRequise ?? 0)
current.surprise = actor.getSurprise(isCombat, current.forceRequise ?? 0)
current.reasons = actor.getEffects(it => StatusEffects.niveauSurprise(it, isCombat) > 0, current.forceRequise ?? 0)
.map(it => { return { img: it.img, label: game.i18n.localize(it.name) } })
current.diviseur = 1
@@ -67,7 +68,16 @@ export class RollPartSign extends RollPart {
}
isAttaqueFinesse(rollData) {
return ROLL_TYPE_DEFENSE == rollData.type.current && rollData.attaque?.particuliere == 'finesse'
const attackerRoll = rollData.attackerRoll
if (rollData.attackerRoll){
switch (rollData.type.current) {
case ROLL_TYPE_DEFENSE:
return attackerRoll?.particuliere == RDD_CONFIG.particuliere.finesse.key
case ROLL_TYPE_POSSESSION:
return RdDPossessionV2.isDefense(rollData.type.possession.action) && attackerRoll?.particuliere == RDD_CONFIG.particuliere.finesse.key
}
}
return false
}
isParadeArmeDisparate(current) {

View File

@@ -7,7 +7,7 @@ export class RollTypePossession extends RollType {
get name() { return "Posséder" }
prepare(rollData) {
rollData.type.possession = RdDPossessionV2.getPossessionDetails(rollData, rollData.type.possession?.action)
rollData.type.possession = RdDPossessionV2.getTypePossessionAction(rollData, rollData.type.possession?.action)
}
title(rollData) {