Montée TMR sur méditation

ajout d'un bouton pour aller dans les TMR lors d'une
méditation réussie
This commit is contained in:
2025-10-15 00:27:37 +02:00
parent 3bc1c4871b
commit 68c01fc930
7 changed files with 129 additions and 61 deletions

View File

@@ -8,6 +8,7 @@ import { RDD_CONFIG, renderTemplate } from "../constants.js"
import { EMPOIGNADE } from "../item/arme.js" import { EMPOIGNADE } from "../item/arme.js"
import { RdDTextEditor } from "../apps/rdd-text-roll-editor.js" import { RdDTextEditor } from "../apps/rdd-text-roll-editor.js"
import { RollTypeCuisine } from "./roll-type-cuisine.mjs" import { RollTypeCuisine } from "./roll-type-cuisine.mjs"
import { RollTypeMeditation } from "./roll-type-meditation.mjs"
export default class ChatRollResult { export default class ChatRollResult {
static init() { static init() {
@@ -38,7 +39,7 @@ export default class ChatRollResult {
) )
const save = RollDialog.saveParts(roll, impacts) const save = RollDialog.saveParts(roll, impacts)
ChatUtility.setMessageData(chatMessage, 'rollData', save) await this.saveChatMessageRoll(chatMessage, save)
return chatMessage return chatMessage
} }
@@ -112,6 +113,8 @@ export default class ChatRollResult {
$(html).on("click", '.resister-recul', event => this.onClickRecul(event)) $(html).on("click", '.resister-recul', event => this.onClickRecul(event))
$(html).on("click", '.choix-particuliere', event => this.onClickChoixParticuliere(event)) $(html).on("click", '.choix-particuliere', event => this.onClickChoixParticuliere(event))
$(html).on("click", '.faire-gouter', event => this.onClickFaireGouter(event)) $(html).on("click", '.faire-gouter', event => this.onClickFaireGouter(event))
$(html).on("click", '.monter-tmr-normale', event => this.onClickMonteeTMR(event, 'normal'))
$(html).on("click", '.monter-tmr-rapide', event => this.onClickMonteeTMR(event, 'rapide'))
} }
@@ -125,8 +128,16 @@ export default class ChatRollResult {
return undefined return undefined
} }
async saveChatMessageRoll(chatMessage, savedRoll) {
await ChatUtility.setMessageData(chatMessage, 'rollData', savedRoll)
}
loadChatMessageRoll(chatMessage) {
return ChatUtility.getMessageData(chatMessage, 'rollData')
}
async updateChatMessage(chatMessage, savedRoll) { async updateChatMessage(chatMessage, savedRoll) {
ChatUtility.setMessageData(chatMessage, 'rollData', savedRoll) await this.saveChatMessageRoll(chatMessage, savedRoll)
const copy = foundry.utils.duplicate(savedRoll) const copy = foundry.utils.duplicate(savedRoll)
RollDialog.loadRollData(copy) RollDialog.loadRollData(copy)
this.prepareDisplay(copy) this.prepareDisplay(copy)
@@ -136,7 +147,7 @@ export default class ChatRollResult {
onClickAppelChance(event) { onClickAppelChance(event) {
const chatMessage = ChatUtility.getChatMessage(event) const chatMessage = ChatUtility.getChatMessage(event)
const savedRoll = ChatUtility.getMessageData(chatMessage, 'rollData') const savedRoll = this.loadChatMessageRoll(chatMessage)
const actor = game.actors.get(savedRoll.ids.actorId) const actor = game.actors.get(savedRoll.ids.actorId)
actor.rollAppelChance( actor.rollAppelChance(
() => this.onAppelChanceSuccess(savedRoll, chatMessage), () => this.onAppelChanceSuccess(savedRoll, chatMessage),
@@ -144,11 +155,13 @@ export default class ChatRollResult {
event.preventDefault() event.preventDefault()
} }
onAppelChanceSuccess(savedRoll, chatMessage) { async onAppelChanceSuccess(savedRoll, chatMessage) {
const reRoll = foundry.utils.duplicate(savedRoll) const reRoll = foundry.utils.duplicate(savedRoll)
console.log('onAppelChanceSuccess savedRoll', savedRoll) console.log('onAppelChanceSuccess savedRoll', savedRoll)
reRoll.type.retry = true reRoll.type.retry = true
await this.updateChatMessage(chatMessage, reRoll)
const callbacks = [r => ChatUtility.removeChatMessageId(chatMessage.id)] const callbacks = [r => ChatUtility.removeChatMessageId(chatMessage.id)]
// TODO: annuler les effets // TODO: annuler les effets
switch (reRoll.type.current) { switch (reRoll.type.current) {
case ROLL_TYPE_DEFENSE: case ROLL_TYPE_DEFENSE:
@@ -171,7 +184,7 @@ export default class ChatRollResult {
onClickAppelDestinee(event) { onClickAppelDestinee(event) {
const chatMessage = ChatUtility.getChatMessage(event) const chatMessage = ChatUtility.getChatMessage(event)
const savedRoll = ChatUtility.getMessageData(chatMessage, 'rollData') const savedRoll = this.loadChatMessageRoll(chatMessage)
const actor = game.actors.get(savedRoll.ids.actorId) const actor = game.actors.get(savedRoll.ids.actorId)
actor.appelDestinee(async () => { actor.appelDestinee(async () => {
@@ -184,7 +197,7 @@ export default class ChatRollResult {
async onClickEncaissement(event) { async onClickEncaissement(event) {
const chatMessage = ChatUtility.getChatMessage(event) const chatMessage = ChatUtility.getChatMessage(event)
const savedRoll = ChatUtility.getMessageData(chatMessage, 'rollData') const savedRoll = this.loadChatMessageRoll(chatMessage)
const attaque = savedRoll.attackerRoll const attaque = savedRoll.attackerRoll
const defender = game.actors.get(savedRoll.ids.actorId) const defender = game.actors.get(savedRoll.ids.actorId)
const attacker = game.actors.get(savedRoll.ids.opponentId) const attacker = game.actors.get(savedRoll.ids.opponentId)
@@ -198,7 +211,7 @@ export default class ChatRollResult {
async onClickRecul(event) { async onClickRecul(event) {
const chatMessage = ChatUtility.getChatMessage(event) const chatMessage = ChatUtility.getChatMessage(event)
const savedRoll = ChatUtility.getMessageData(chatMessage, 'rollData') const savedRoll = this.loadChatMessageRoll(chatMessage)
const defender = game.actors.get(savedRoll.ids.actorId) const defender = game.actors.get(savedRoll.ids.actorId)
const attacker = game.actors.get(savedRoll.ids.opponentId) const attacker = game.actors.get(savedRoll.ids.opponentId)
savedRoll.done.recul = await defender.encaisserRecul(attacker.getForce(), savedRoll.attackerRoll.dmg.dmgArme) savedRoll.done.recul = await defender.encaisserRecul(attacker.getForce(), savedRoll.attackerRoll.dmg.dmgArme)
@@ -209,15 +222,29 @@ export default class ChatRollResult {
async onClickChoixParticuliere(event) { async onClickChoixParticuliere(event) {
const choix = event.currentTarget.attributes['data-particuliere'].value const choix = event.currentTarget.attributes['data-particuliere'].value
const chatMessage = ChatUtility.getChatMessage(event) const chatMessage = ChatUtility.getChatMessage(event)
const savedRoll = ChatUtility.getMessageData(chatMessage, 'rollData') const savedRoll = this.loadChatMessageRoll(chatMessage)
savedRoll.particuliere = choix savedRoll.particuliere = choix
savedRoll.particulieres = [RDD_CONFIG.particuliere[choix]] savedRoll.particulieres = [RDD_CONFIG.particuliere[choix]]
await this.updateChatMessage(chatMessage, savedRoll) await this.updateChatMessage(chatMessage, savedRoll)
await this.getCombat(savedRoll)?.onAttaqueV2(savedRoll, callbacks) await this.getCombat(savedRoll)?.onAttaqueV2(savedRoll, callbacks)
} }
async onClickFaireGouter(event) { async onClickFaireGouter(event) {
const chatMessage = ChatUtility.getChatMessage(event) const chatMessage = ChatUtility.getChatMessage(event)
const savedRoll = ChatUtility.getMessageData(chatMessage, 'rollData') const savedRoll = this.loadChatMessageRoll(chatMessage)
if (!savedRoll.type.retry) {
savedRoll.type.retry = true
await this.updateChatMessage(chatMessage, savedRoll)
}
await new RollTypeCuisine().onFaireGouter(savedRoll) await new RollTypeCuisine().onFaireGouter(savedRoll)
} }
async onClickMonteeTMR(event, mode) {
const chatMessage = ChatUtility.getChatMessage(event)
const savedRoll = this.loadChatMessageRoll(chatMessage)
if (await new RollTypeMeditation().onMonteeTMR(savedRoll, mode)) {
savedRoll.done.meditation = true
await this.updateChatMessage(chatMessage, savedRoll)
}
}
} }

View File

@@ -443,7 +443,7 @@ export default class RollDialog extends HandlebarsApplicationMixin(ApplicationV2
async roll() { async roll() {
const roll = RollDialog.saveParts(this.rollData) const roll = RollDialog.saveParts(this.rollData)
this.loadRollData(roll) RollDialog.loadRollData(roll)
const selectedRollType = this.getSelectedType(roll); const selectedRollType = this.getSelectedType(roll);
selectedRollType.onSelect(roll) selectedRollType.onSelect(roll)
roll.current.resultat = this.rollData.current[PART_TRICHER]?.resultat ?? -1 roll.current.resultat = this.rollData.current[PART_TRICHER]?.resultat ?? -1
@@ -472,7 +472,7 @@ export default class RollDialog extends HandlebarsApplicationMixin(ApplicationV2
this.rollOptions.onRollDone(this) this.rollOptions.onRollDone(this)
} }
loadRollData(roll) { static loadRollData(roll) {
RollDialog.$prepareRollData(roll) RollDialog.$prepareRollData(roll)
RollDialog.calculAjustements(roll) RollDialog.calculAjustements(roll)
roll.v2 = true roll.v2 = true

View File

@@ -32,17 +32,17 @@ export class RollTypeCuisine extends RollType {
if (current.fabriquer) { if (current.fabriquer) {
const plat = this.$prepareNourriture(rollData.active.name, current, result) const plat = this.$prepareNourriture(rollData.active.name, current, result)
result.messages.push(`${plat.system.quantite} ${plat.name} ont été préparés dans l'équipement`) result.messages.push(`${plat.system.quantite} ${plat.name} ont été préparés dans l'équipement`)
impacts.active.addCreatedItem(plat) impacts.active.addCreated('Item', plat)
result.plat = { id: plat.id } result.plat = { id: plat.id }
} }
if (current.ingredient) { if (current.ingredient) {
const quantite = Math.min(current.proportions, current.ingredient.system.quantite) const quantite = Math.min(current.proportions, current.ingredient.system.quantite)
if (quantite == current.ingredient.system.quantite) { if (quantite == current.ingredient.system.quantite) {
impacts.active.addDeletedItem(current.ingredient) impacts.active.addDeleted('Item', current.ingredient)
result.messages.push(`Il n'y a plus de ${ingredient.name}`) result.messages.push(`Il n'y a plus de ${ingredient.name}`)
} }
else { else {
impacts.active.addItemDelta(current.ingredient, 'system.quantite', -current.proportions) impacts.active.addDelta('Item', current.ingredient, 'system.quantite', -current.proportions)
result.messages.push(`${current.proportions} ${current.ingredient.name} ont été utilisés`) result.messages.push(`${current.proportions} ${current.ingredient.name} ont été utilisés`)
} }
} }
@@ -51,7 +51,7 @@ export class RollTypeCuisine extends RollType {
onApplyImpacts(roll, impacts) { onApplyImpacts(roll, impacts) {
if (roll.result.plat) { if (roll.result.plat) {
// le plat n'est pas créé immédiatement, il faut donc retrouver l'id // le plat n'est pas créé immédiatement, il faut donc retrouver l'id
roll.result.plat.id = impacts.active.itemCreates.find(it => it.id = roll.result.plat.id)?.createdId roll.result.plat.id = impacts.active.findCreatedId('Item', roll.result.plat.id)
} }
} }

View File

@@ -1,4 +1,5 @@
import { RdDItemSigneDraconique } from "../item/signedraconique.js" import { RdDItemSigneDraconique } from "../item/signedraconique.js"
import { RollBasicParts } from "./roll-basic-parts.mjs"
import { DIFF, ROLL_TYPE_MEDITATION } from "./roll-constants.mjs" import { DIFF, ROLL_TYPE_MEDITATION } from "./roll-constants.mjs"
import { PART_MEDITATION } from "./roll-part-meditation.mjs" import { PART_MEDITATION } from "./roll-part-meditation.mjs"
import { RollType } from "./roll-type.mjs" import { RollType } from "./roll-type.mjs"
@@ -26,7 +27,7 @@ export class RollTypeMeditation extends RollType {
const rolled = rollData.rolled const rolled = rollData.rolled
if (meditation && rolled) { if (meditation && rolled) {
if (rolled.isSuccess) { if (rolled.isSuccess) {
await actor.createEmbeddedDocuments("Item", [RdDItemSigneDraconique.prepareSigneDraconiqueMeditation(meditation, rolled)]) await actor.createEmbeddedDocuments('Item', [RdDItemSigneDraconique.prepareSigneDraconiqueMeditation(meditation, rolled)])
} }
if (rolled.isEPart) { if (rolled.isEPart) {
await actor.updateEmbeddedDocuments('Item', [{ _id: meditation._id, 'system.malus': meditation.system.malus - 1 }]) await actor.updateEmbeddedDocuments('Item', [{ _id: meditation._id, 'system.malus': meditation.system.malus - 1 }])
@@ -34,4 +35,9 @@ export class RollTypeMeditation extends RollType {
await actor.santeIncDec("fatigue", 2) await actor.santeIncDec("fatigue", 2)
} }
} }
async onMonteeTMR(savedRoll, mode){
const actor = RollBasicParts.getTokenActor(savedRoll).actor
return actor?.displayTMR(mode)
}
} }

View File

@@ -1,15 +1,25 @@
const ACTOR_EMBEDDED_DOCTYPES = ['Item', 'ActiveEffect']
/** /**
* class designed to store actor modification instructions, to apply them in a single operation, and have the ability to revert these * class designed to store actor modification instructions, to apply them in a single operation, and have the ability to revert these
*/ */
export class ActorImpacts { export class ActorImpacts {
static $newDocumentImpacts(docType) {
return { creates: [], deletes: [], updates: [], docType: docType }
}
static $checkDocType(docType) {
if (!ACTOR_EMBEDDED_DOCTYPES.includes(docType)) {
throw `Unsupported document type ${docType}`
}
}
constructor(actorToken) { constructor(actorToken) {
this.actorToken = actorToken this.actorToken = actorToken
this.updates = [] this.updates = []
this.deltas = [] this.deltas = []
this.itemCreates = [] ACTOR_EMBEDDED_DOCTYPES.forEach(
this.itemUpdates = [] docType => this[docType] = ActorImpacts.$newDocumentImpacts(docType)
this.itemDeletes = [] )
} }
addActorUpdate(path, value) { addActorUpdate(path, value) {
@@ -27,46 +37,55 @@ export class ActorImpacts {
} }
} }
addDeletedItem(item) { addDeleted(docType, document) {
this.itemDeletes.push(item) ActorImpacts.$checkDocType(docType)
} this[docType].deletes.push(document)
addCreatedItem(item) {
this.itemCreates.push(item)
} }
addItemUpdate(item, path, value) { addCreated(docType, document) {
const existing = this.itemUpdates.find(it => it.id == item.id) ActorImpacts.$checkDocType(docType)
this[docType].creates.push(document)
}
addUpdate(docType, document, path, value) {
ActorImpacts.$checkDocType(docType)
const update = [path, value] const update = [path, value]
const existing = this[docType].updates.find(it => it.id == document.id)
if (existing) { if (existing) {
existing.updates.push(update) existing.updates.push(update)
} }
else { else {
this.itemUpdates.push({ id: item.id, updates: [update], deltas: [] }) this[docType].updates.push({ id: document.id, updates: [update], deltas: [] })
} }
} }
addItemDelta(item, path, value) { addDelta(document, path, value) {
ActorImpacts.$checkDocType(document)
const intValue = Number.parseInt(value) const intValue = Number.parseInt(value)
if (Number.isInteger(intValue) && intValue != 0) { if (Number.isInteger(intValue) && intValue != 0) {
const delta = [path, intValue] const delta = [path, intValue]
const existing = this.itemUpdates.find(it => it.id == item.id) const existing = this[docType].updates.find(it => it.id == document.id)
if (existing) { if (existing) {
existing.deltas.push(delta) existing.deltas.push(delta)
} }
else { else {
this.itemUpdates.push({ id: item.id, updates: [], deltas: [delta] }) this[docType].updates.push({ id: document.id, updates: [], deltas: [delta] })
} }
} }
else { else {
console.error('Cannot use non integer value {} for delta update', value) console.error('Cannot use non-integer value {} for delta update', value)
} }
} }
reverseImpacts() { reverseImpacts() {
const reverse = ActorImpacts.$computeReverts(new ActorImpacts(this.actorToken), this, __ => this.actorToken.actor) const reverse = ActorImpacts.$computeReverts(new ActorImpacts(this.actorToken), this, __ => this.actorToken.actor)
reverse.itemCreates = this.itemDeletes.map(it => foundry.utils.duplicate(it)) ACTOR_EMBEDDED_DOCTYPES.forEach(
reverse.itemDeletes = this.itemCreates.map(it => { return { id: it.id } }) docType => {
reverse.itemUpdates = this.itemUpdates.map(it => ActorImpacts.$computeReverts({ id: it.id }, it, id => this.$getActorItem(id))) reverse[docType].creates = this[docType].deletes.map(it => foundry.utils.duplicate(it))
reverse[docType].deletes = this[docType].creates.map(it => { return { id: it.id } })
reverse[docType].updates = this[docType].updates.map(it => ActorImpacts.$computeReverts({ id: it.id }, it, id => this.$getEmbeddedDocument(docType, id)))
}
)
return reverse return reverse
} }
@@ -77,40 +96,44 @@ export class ActorImpacts {
async applyImpacts() { async applyImpacts() {
const actor = this.actorToken.actor const actor = this.actorToken.actor
const isItemsDelete = this.itemDeletes.length > 0 await Promise.all(ACTOR_EMBEDDED_DOCTYPES.map(async docType => await this.$applyDocumentsImpacts(actor, docType)))
const isItemsCreate = this.itemCreates.length > 0 const updates = ActorImpacts.$computeUpdates(this, id => actor)
const isItemsUpdate = this.itemUpdates.length > 0 await actor.update(updates, { render: true })
const isActorUpdate = this.updates.length > 0 || this.deltas.length > 0 }
if (isItemsDelete) {
const deletes = this.itemDeletes.map(it => it.id) async $applyDocumentsImpacts(actor, docType) {
await actor.deleteEmbeddedDocuments('Item', deletes, { render: !(isItemsCreate || isItemsUpdate || isActorUpdate) }) if (this[docType].deletes.length > 0) {
const deletes = this[docType].deletes.map(it => it.id)
await actor.deleteEmbeddedDocuments(docType, deletes, { render: false })
} }
if (isItemsCreate) { if (this[docType].creates.length > 0) {
const creates = this.itemCreates const creates = this[docType].creates
const created = await actor.createEmbeddedDocuments('Item', creates, { render: !(isItemsUpdate || isActorUpdate)}) const created = await actor.createEmbeddedDocuments(docType, creates, { render: false })
for (let i=0; i<creates.length; i++){ for (let i = 0; i < creates.length; i++) {
creates[i].createdId = created[i].id creates[i].createdId = created[i].id
} }
} }
if (isItemsUpdate) { if (this[docType].updates.length > 0) {
const updates = this.itemUpdates.map(u => ActorImpacts.$computeUpdates(u, id => this.$getActorItem(id))) const updates = this[docType].updates.map(u => ActorImpacts.$computeUpdates(u, id => this.$getEmbeddedDocument(docType, id)))
await actor.updateEmbeddedDocuments('Item', updates, { render: !isActorUpdate }) await actor.updateEmbeddedDocuments(docType, updates, { render: false })
}
if (isActorUpdate) {
const updates = ActorImpacts.$computeUpdates(this, id => actor)
await actor.update(updates, { render: true })
} }
} }
$getActorItem(id) { findCreatedId(docType, origId){
return this.actorToken.actor.items.get(id) return this[docType].creates.find(it => it.id = origId)?.createdId
}
$getEmbeddedDocument(docType, id) {
return this.actorToken.actor.getEmbeddedDocument(docType, id)
} }
static $computeUpdates(u, getSource) { static $computeUpdates(u, getSource) {
if (u.updates.length == 0 && u.deltas.length == 0) {
return {}
}
const source = getSource(u.id) const source = getSource(u.id)
const instruction = { _id: u.id } const instruction = { _id: u.id }
u.updates.forEach(u => instruction[u[0]] = u[1]) u.updates.forEach(u => instruction[u[0]] = u[1])
@@ -124,5 +147,4 @@ export class ActorImpacts {
target.deltas = u.deltas.map(d => [d[0], -d[1]]) target.deltas = u.deltas.map(d => [d[0], -d[1]])
return target return target
} }
} }

View File

@@ -21,12 +21,12 @@
<p> <p>
{{active.name}} {{active.name}}
{{#if rolled.isSuccess}} {{#if rolled.isSuccess}}
réussit la recette, pour un plat de qualité {{result.qualite~}} réussit la recette, pour un plat de qualité {{result.qualite}}
{{#if (lt result.exotisme 0)}}et d'exotisme {{result.exotisme}}{{/if~}} {{#if (lt result.exotisme 0)}}et d'exotisme {{result.exotisme}}{{/if}}
{{else}} {{else}}
fait un piètre cuisinier, et obtient un plat de qualité {{result.qualite~}} fait un piètre cuisinier, et obtient un plat de qualité {{result.qualite}}
{{#if (lt result.exotisme 0)}}à l'exotisme certain ({{result.exotisme}}){{/if~}} {{#if (lt result.exotisme 0)}}à l'exotisme certain ({{result.exotisme}}){{/if}}
{{/if~}}. {{~/if~}}.
{{#if (lt result.exotisme 0)}} {{#if (lt result.exotisme 0)}}
<br>Au vu de l'exotisme du plat, les convives devront réussir un jet de @roll[Volonté/Cuisine/{{result.exotisme}}]. <br>Au vu de l'exotisme du plat, les convives devront réussir un jet de @roll[Volonté/Cuisine/{{result.exotisme}}].
En cas d'échec, ils peuvent se forcer à faire plaisir à {{active.name}}, mais devront faire un jet de moral Malheureux. En cas d'échec, ils peuvent se forcer à faire plaisir à {{active.name}}, mais devront faire un jet de moral Malheureux.

View File

@@ -39,6 +39,19 @@
{{/if}} {{/if}}
</div> </div>
<div class="chat-actions">
{{#if rolled.isSuccess}}
{{#unless done.meditation}}
<a class='chat-card-button monter-tmr-normale' data-tooltip="Montée rapide dans les TMR" >
<img src="systems/foundryvtt-reve-de-dragon/assets/actions/sort.svg"/>&nbsp;Montée normale dans les TMR
</a>
<a class='chat-card-button monter-tmr-rapide' data-tooltip="Montée rapide dans les TMR" >
<img src="systems/foundryvtt-reve-de-dragon/assets/actions/sort.svg"/>&nbsp;Montée rapide dans les TMR
</a>
{{/unless}}
{{/if}}
</div>
<div class="chat-buttons"> <div class="chat-buttons">
</div> </div>
</div> </div>