import { ChatUtility } from "../chat-utility.js"; import { Grammar } from "../grammar.js"; import { Misc } from "../misc.js"; import { RdDEmpoignade } from "./rdd-empoignade.js"; import { RdDInitiative } from "./initiative.mjs"; import { PasseArme } from "./passe-arme.mjs"; /* -------------------------------------------- */ const premierRoundInit = [ { pattern: 'hast' }, { pattern: 'lance' }, { pattern: 'baton' }, { pattern: 'doubledragonne' }, { pattern: 'esparlongue' }, { pattern: 'epeedragonne' }, { pattern: 'epeebatarde' }, { pattern: 'epeecyane' }, { pattern: 'epeesorde' }, { pattern: 'grandehache' }, { pattern: 'bataille' }, { pattern: 'epeegnome' }, { pattern: 'masse' }, { pattern: 'gourdin' }, { pattern: 'fleau' }, { pattern: 'dague' }, { pattern: 'autre' }, ]; /* -------------------------------------------- */ export class RdDCombat extends Combat { static init() { /* -------------------------------------------- */ Hooks.on("getCombatTrackerEntryContext", (html, options) => { RdDCombat.pushInitiativeOptions(html, options); }); Hooks.on("updateCombat", (combat, change, options, userId) => { PasseArme.onUpdateCombat(combat, change, options, userId) }); Hooks.on("preDeleteCombat", (combat, html, id) => { combat.onPreDeleteCombat() }) Hooks.on("deleteCombat", (combat, html, id) => { combat.onDeleteCombat() }) for (let i = 0.0; i < premierRoundInit.length; i++) { premierRoundInit[i].init = 5.99 - i / 100 } } static getCombatant(actorId, tokenId) { return game.combat?.combatants.find(it => it.actorId == actorId && it.tokenId == tokenId ) } static getRangInitiativeCombatant(actorId, tokenId) { const combatant = RdDCombat.getCombatant(actorId, tokenId) return combatant?.system.init?.rang } /* -------------------------------------------- */ async nextRound() { await this.finDeRound(); return await super.nextRound(); } /* -------------------------------------------- */ async onPreDeleteCombat() { if (Misc.isFirstConnectedGM()) { await this.finDeRound({ terminer: true }) ChatUtility.removeChatMessageContaining(`
`) game.messages.filter(m => ChatUtility.getMessageData(m, 'rollData') != undefined && ChatUtility.getMessageData(m, 'rollData') != undefined) .forEach(it => it.delete()) RdDEmpoignade.deleteAllEmpoignades() } } async onDeleteCombat() { if (Misc.isFirstConnectedGM()) { if (game.combats.size <= 1) { game.actors.forEach(actor => actor.resetItemUse()) } } } /* -------------------------------------------- */ async finDeRound(options = { terminer: false }) { this.combatants.map(it => RdDCombat.getActorCombatant(it, { warning: false })) .filter(it => it != undefined) .forEach(async actor => { await actor.finDeRound(options) await actor.resetItemUse() }) } static getActorCombatant(combatant, options = { warning: true }) { if (!combatant.actor) { if (options.warning) { ui.notifications.warn(`Le combatant ${combatant.name} n'est pas associé à un acteur!`) } return undefined } else if (!combatant.actor.isActorCombat()) { if (options.warning) { ui.notifications.warn(`L'acteur ${combatant.name} ne peut pas combattre!`) } return undefined } return combatant.actor } static bonusArme(arme) { return (arme?.system.magique) ? arme.system.ecaille_efficacite : 0 } static etatGeneral(actor) { return actor.getEtatGeneral() ?? 0; } /** *********************************************************************************** * @override lance l'initiative de plusieurs combattants (tous/ous les PNJs) en une fois */ async rollInitiative(ids, messageOptions = {}) { console.log(`${game.system.title} | Combat.rollInitiative()`, ids, messageOptions) ids = typeof ids === "string" ? [ids] : ids Promise.all(ids.map(id => this.rollInitRdD(id, undefined, messageOptions))) return this } async rollInitRdD(id, formule, messageOptions = {}) { const combatant = this.combatants.get(id); const actor = RdDCombat.getActorCombatant(combatant) if (actor) { formule = formule ?? RdDCombat.getFirstInitRollFormula(actor) const init = await RdDInitiative.roll(formule) await this.updateEmbeddedDocuments("Combatant", [{ _id: combatant.id, initiative: init.init, 'system.init': init }]) // Send a chat message let rollMode = messageOptions.rollMode || game.settings.get("core", "messageMode"); let messageData = foundry.utils.mergeObject({ speaker: { scene: canvas?.scene?.id, actor: combatant.actor?.id, token: combatant.token.id, alias: combatant.token?.name, sound: CONFIG.sounds.dice, }, flavor: `${combatant.token?.name} a une initiative de ${init.value} : ${messageOptions.info}
` }, messageOptions); init.roll.toMessage(messageData, { rollMode, create: true }); RdDCombat.processPremierRoundInit(); } return this; } static getFirstInitRollFormula(actor) { const actions = actor.listActions() if (actions.length > 0) { const action = actions[0] const init = RdDCombat.getInitData(actor, action) const ajustement = RdDCombat.bonusArme(action.arme) + RdDCombat.etatGeneral(actor) return RdDInitiative.formule(init.phase, init.carac, init.niveau, ajustement); } return RdDInitiative.formule('autre', 10, 0, actor.getEtatGeneral() ?? 0); } /* -------------------------------------------- */ static processPremierRoundInit() { // Check if we have the whole init ! if (Misc.isFirstConnectedGM() && game.combat.current.round == 1) { let initMissing = game.combat.combatants.find(it => !it.initiative); if (!initMissing) { // Premier round ! for (let combatant of game.combat.combatants) { if (combatant.initiativeData?.arme?.type == "arme") { // TODO: get init data premier round const action = combatant.initiativeData.arme; const fromArme = Grammar.toLowerCaseNoAccentNoSpace(action.system.initpremierround) const initData = premierRoundInit.find(it => fromArme.includes(initData.pattern)) if (initData) { let msg = `

L'initiative de ${combatant.actor.getAlias()} a été modifiée !


Etant donné son ${action.name}, son initative pour ce premier round est désormais de ${initData.init}.
` ChatMessage.create({ content: msg }) game.combat.setInitiative(combatant.id, initData.init) } } } } } } /* -------------------------------------------- */ static applyInitiativeCommand(combatantId, command, commandValue) { switch (command) { case 'delta': return RdDCombat.incDecInit(combatantId, commandValue); case 'autre': return RdDCombat.rollInitiativeAction(combatantId, { name: "Autre action", action: 'autre', system: { initOnly: true, competence: "Autre action" } }); } } static async incDecInit(combatantId, incDecValue) { const combatant = game.combat.combatants.get(combatantId) if (combatant?.initiative && incDecValue != 0) { const value = combatant.system.init.value + incDecValue const newInit = combatant.initiative + incDecValue / 100; await game.combat.updateEmbeddedDocuments("Combatant", [{ _id: combatantId, initiative: newInit, 'system.init.value': value, 'system.init.init': newInit, }]) } } /* -------------------------------------------- */ static pushInitiativeOptions(html, options) { for (let i = 0; i < options.length; i++) { let option = options[i] if (option.name == 'COMBAT.CombatantReroll') { // Replace ! option.name = "Sélectionner l'initiative..." option.condition = true option.icon = '' option.callback = target => { RdDCombat.displayInitiativeMenu(html, target.data('combatant-id')) } } } options = [ { name: "Incrémenter initiative", condition: true, icon: '', callback: target => { RdDCombat.incDecInit(target.data('combatant-id'), +1); } }, { name: "Décrémenter initiative", condition: true, icon: '', callback: target => { RdDCombat.incDecInit(target.data('combatant-id'), -1); } } ].concat(options); } /* -------------------------------------------- */ static async rollInitiativeAction(combatantId, action) { const combatant = game.combat.combatants.get(combatantId) const actor = RdDCombat.getActorCombatant(combatant) if (actor == undefined) { return } const init = RdDCombat.getInitData(actor, action) const ajustement = RdDCombat.bonusArme(actor, action.arme) + RdDCombat.etatGeneral(actor) const formule = RdDInitiative.formule(init.phase, init.carac, init.niveau, ajustement); await game.combat.rollInitRdD(combatantId, formule, init); combatant.initiativeData = { action, formule } // pour reclasser l'init au round 0 } static getInitData(actor, action) { if (actor.getSurprise() == "totale") { return { phase: RdDInitiative.PHASES.totale, info: "Surprise Totale", carac: 0, niveau: 0 } } if (actor.getSurprise() == "demi") { return { phase: RdDInitiative.PHASES.demi, info: "Demi Surprise", carac: 0, niveau: 0 } } if (action.action == 'autre') { return { phase: RdDInitiative.PHASES.autre, info: "Autre Action", carac: 0, niveau: 0 } } if (action.action == 'possession') { return { phase: RdDInitiative.PHASES.possession, info: "Possession", carac: actor.getReveActuel(), niveau: 0 } } if (action.action == 'haut-reve') { return { phase: RdDInitiative.PHASES.draconic, info: "Draconic", carac: actor.getReveActuel(), niveau: 0 } } const comp = action.comp return { phase: RdDInitiative.phaseArme(comp?.system.categorie, action.arme), info: action.label, carac: actor.getCaracInit(comp), niveau: comp?.system.niveau ?? (['(lancer)', '(tir)'].includes(action.main) ? -8 : -6) } } /* -------------------------------------------- */ static displayInitiativeMenu(html, combatantId) { const combatant = game.combat.combatants.get(combatantId) const actor = RdDCombat.getActorCombatant(combatant, { warning: false }) if (actor) { const actions = RdDCombat.listActionsActorCombatant(actor) // Build the relevant submenu const menuItems = actions.map(action => { return { name: action.system.competence, icon: "", callback: target => { RdDCombat.rollInitiativeAction(combatantId, action) } } }) if (menuItems.length > 0) { new ContextMenu(html, ".directory-list", menuItems).render(); } } } /* -------------------------------------------- */ static listActionsActorCombatant(actor) { const possessions = actor.listActionsPossessions() const actions = possessions.length > 0 ? possessions : actor.listActions() return Misc.indexed(actions) } }