import { STATUSES } from "./settings/status-effects.js"; import { ITEM_TYPES, renderTemplate } from "./constants.js"; import { ChatUtility } from "./chat-utility.js"; import { RdDRollResult } from "./rdd-roll-result.js"; import { RdDRoll } from "./rdd-roll.js"; import { MappingCreatureArme } from "./item/mapping-creature-arme.mjs"; import { MAP_PHASE } from "./initiative.mjs"; import { RdDCombatManager } from "./rdd-combat.js"; /* -------------------------------------------- */ export class RdDEmpoignade { /* -------------------------------------------- */ static init() { } /* -------------------------------------------- */ static getActorEmpoignade(actorId) { const actor = game.actors.get(actorId) if (actor == undefined) { ui.notifications.warn(`Impossible de retrouver l'acteur ${actorId}, l'empoignade ne peut pas être continuée.`) } return actor } static isCombatantEmpoignade(actorId, tokenId) { const combatant = RdDCombatManager.getCombatant(actorId, tokenId) return combatant && MAP_PHASE.empoignade.rang == combatant?.system.init?.rang } static async ajustementEmpoignade(attacker, defender, adjust = 1) { let empoignade = RdDEmpoignade.getEmpoignade(attacker, defender) if (empoignade?.system.empoigneurid == defender.id) { empoignade = RdDEmpoignade.getEmpoignade(defender, attacker) return await RdDEmpoignade.$ajustementEmpoignade(empoignade, defender, attacker, - adjust); } return await RdDEmpoignade.$ajustementEmpoignade(empoignade, attacker, defender, adjust); } static async $ajustementEmpoignade(empoignade, attacker, defender, adjust) { const empId = empoignade?.system.empoignadeid ?? foundry.utils.randomID(16); const empFin = (empoignade?.system.pointsemp ?? 0) + adjust if (empoignade) { empoignade.system.pointsemp = empFin; await RdDEmpoignade.$updateEtatEmpoignade(empoignade, attacker, defender); } else { await RdDEmpoignade.$createEtatEmpoignade({ name: `Empoignade de ${attacker.name} sur ${defender.name}`, type: ITEM_TYPES.empoignade, system: { description: "", empoignadeid: empId, empoigneurid: attacker.id, empoigneid: defender.id, pointsemp: empFin, empoigneurname: attacker.name, empoignename: defender.name } }, attacker, defender); } if (adjust != 0 && empFin == 2) { await RdDEmpoignade.proposerEntrainerAuSol(attacker, defender, empoignade); } const result = RdDEmpoignade.getEmpoignadeById(defender, empId); const defGrappled = result.system.pointsemp == (result.system.empoigneid == defender.id ? 2 : -2); const attGrappled = result.system.pointsemp == (result.system.empoigneurid == attacker.id ? -2 : 2); const grappling = Math.abs(result.system.pointsemp) > 0; await defender.setEffect(STATUSES.StatusGrappling, grappling && !defGrappled); await attacker.setEffect(STATUSES.StatusGrappling, grappling && !attGrappled); await defender.setEffect(STATUSES.StatusGrappled, defGrappled); await attacker.setEffect(STATUSES.StatusGrappled, attGrappled); return result } /* -------------------------------------------- */ static registerChatCallbacks(html) { $(html).on("click", '.defense-empoignade-cac', event => { const chatMessage = ChatUtility.getChatMessage(event); const rollData = RdDEmpoignade.$readRollEmpoignade(chatMessage); let defenseMode = event.currentTarget.attributes['data-defense-mode'].value RdDEmpoignade.onDefenseEmpoignade(rollData, defenseMode, "Corps à corps", "melee") }) $(html).on("click", '.defense-empoignade-esquive', event => { const chatMessage = ChatUtility.getChatMessage(event); const rollData = RdDEmpoignade.$readRollEmpoignade(chatMessage); let defenseMode = event.currentTarget.attributes['data-defense-mode'].value RdDEmpoignade.onDefenseEmpoignade(rollData, defenseMode, "Esquive", "derobee") }) $(html).on("click", '.empoignade-poursuivre', event => { const attacker = RdDEmpoignade.getActorFromEventTag(event, 'data-attackerId') const defender = RdDEmpoignade.getActorFromEventTag(event, 'data-defenderId') if (attacker && defender) { RdDEmpoignade.onAttaqueEmpoignadeValidee(attacker, defender) } }) $(html).on("click", '.empoignade-entrainer-sol', event => { const chatMessage = ChatUtility.getChatMessage(event); const rollData = RdDEmpoignade.$readRollEmpoignade(chatMessage); RdDEmpoignade.entrainerAuSol(rollData) ChatUtility.remover(chatMessage)() }) $(html).on("click", '.empoignade-projeter-sol', event => { const chatMessage = ChatUtility.getChatMessage(event); const rollData = RdDEmpoignade.$readRollEmpoignade(chatMessage); RdDEmpoignade.projeterAuSol(rollData) 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.remover(chatMessage)() } }) } static getActorFromEventTag(event, tag) { return RdDEmpoignade.getActorEmpoignade(event.currentTarget.attributes[tag]?.value ?? '') } /* -------------------------------------------- */ static checkEmpoignadeEnCours(actor) { // TODO: autoriser la perception? la comédie/séduction? if (RdDEmpoignade.isEmpoignadeEnCours(actor)) { ui.notifications.warn("Une empoignade est en cours ! Normalement, vous ne pouvez rien faire d'autre que continuer l'empoignade ou la rompre.") return true } return false } /* -------------------------------------------- */ static $storeRollEmpoignade(msg, rollData) { ChatUtility.setMessageData(msg, "empoignade-roll-data", RdDEmpoignade.$reduceActorToIds(rollData)) } static $reduceActorToIds(rollData) { rollData.attacker = { id: rollData.attacker.id } rollData.defender = { id: rollData.defender.id } return rollData } /* -------------------------------------------- */ static $readRollEmpoignade(msg) { return RdDEmpoignade.$replaceIdsWithActors(ChatUtility.getMessageData(msg, 'empoignade-roll-data')) } static $replaceIdsWithActors(rollData) { rollData.attacker = RdDEmpoignade.getActorEmpoignade(rollData.attacker.id) rollData.defender = RdDEmpoignade.getActorEmpoignade(rollData.defender.id) return rollData } /* -------------------------------------------- */ static isEmpoignadeEnCours(actor) { return actor.itemTypes[ITEM_TYPES.empoignade].find(it => it.system.pointsemp > 0) } /* -------------------------------------------- */ static getEmpoignadeById(actor, id) { return actor?.itemTypes[ITEM_TYPES.empoignade].find(it => it.system.empoignadeid == id) } /* -------------------------------------------- */ static getEmpoignade(attacker, defender) { if (attacker && defender) { const empoignade = attacker.itemTypes[ITEM_TYPES.empoignade].find(it => (it.system.empoigneurid == attacker.id && it.system.empoigneid == defender.id) || (it.system.empoigneurid == defender.id && it.system.empoigneid == attacker.id) ) if (empoignade) { return foundry.utils.duplicate(empoignade) } } return undefined } /* -------------------------------------------- */ static getMalusTaille(emp, attacker, defender) { // Si pas empoigné, alors 0 if (emp.system.pointsemp == 0) { return 0 } // p135: Malus de -1 par point de taille de différence de taille au delà de 1 (donc -2 pour une différence de 3, ...) const diffTaille = attacker.system.carac.taille.value - defender.system.carac.taille.value; const diffTailleAbs = Math.abs(diffTaille) const signDiff = diffTaille > 0 ? 1 : -1 if (diffTailleAbs > 2) { return signDiff * (diffTailleAbs - 1) } return 0 } static isActionAutorisee(mode, attacker, defender) { if (!defender || !attacker) { return false } const acting = RdDEmpoignade.isActionDefenseur(mode) ? defender : attacker if (acting.getUserLevel(game.user) < CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER) { ui.notifications.warn(`Vous n'êtes pas autorisé à choisir l'action de ${acting.name}`) return false } return true } static isActionDefenseur(mode) { switch (mode) { case "liberer": case "contrer-empoigner": return true; } return false; } /* -------------------------------------------- */ static async onAttaqueEmpoignade(attacker, defender) { if (!RdDEmpoignade.isActionAutorisee("empoigner", attacker, defender)) { return } let empoignade = RdDEmpoignade.getEmpoignade(attacker, defender) const isNouvelle = empoignade == undefined; empoignade = empoignade ?? (await RdDEmpoignade.createEmpoignade(attacker, defender)) if ((isNouvelle || empoignade.system.pointsemp == 0) && defender.hasArmeeMeleeEquipee()) { await ChatMessage.create(ChatUtility.adaptVisibility( { content: await renderTemplate(`systems/foundryvtt-reve-de-dragon/templates/chat-empoignade-valider.hbs`, { attacker: attacker, defender: defender }) }, { actor: attacker } )) } else { await this.onAttaqueEmpoignadeValidee(attacker, defender) } } /* -------------------------------------------- */ static async onAttaqueEmpoignadeValidee(attacker, defender) { let empoignade = RdDEmpoignade.getEmpoignade(attacker, defender) const isNouvelle = empoignade == undefined; empoignade = empoignade ?? (await RdDEmpoignade.createEmpoignade(attacker, defender)) let mode = (empoignade && empoignade.system.empoigneurid == attacker.id) ? "empoigner" : "liberer" if (!RdDEmpoignade.isActionAutorisee(mode, attacker, defender)) { return } let rollData = { mode, empoignade, attacker, defender, isEmpoignade: true, competence: attacker.getCompetenceCorpsACorps(), selectedCarac: attacker.system.carac.melee, malusTaille: RdDEmpoignade.getMalusTaille(empoignade, attacker, defender) } if (attacker.isCreatureOuEntite()) { MappingCreatureArme.setRollDataCreature(rollData) } if (empoignade.system.pointsemp >= 2) { await RdDEmpoignade.proposerEntrainerAuSol(attacker, defender, empoignade) } else { await RdDEmpoignade.$rollAttaqueEmpoignade(attacker, rollData, isNouvelle) } } static async proposerEntrainerAuSol(attacker, defender, empoignade) { if (!empoignade.system.ausol) { const mode = (empoignade && empoignade.system.empoigneurid == attacker.id) ? "empoigner" : "liberer" const rollData = { mode, empoignade, attacker, defender, isEmpoignade: true, competence: attacker.getCompetenceCorpsACorps(), selectedCarac: attacker.system.carac.melee, malusTaille: RdDEmpoignade.getMalusTaille(empoignade, attacker, defender) } const msg = await RdDRollResult.displayRollData(rollData, attacker, 'chat-empoignade-entrainer.hbs'); RdDEmpoignade.$storeRollEmpoignade(msg, rollData); } } /* -------------------------------------------- */ static async onAttaqueEmpoignadeFromItem(empoignade) { const attacker = RdDEmpoignade.getActorEmpoignade(empoignade.system.empoigneurid) const defender = RdDEmpoignade.getActorEmpoignade(empoignade.system.empoigneid) if (attacker && defender) { await this.onAttaqueEmpoignadeValidee(attacker, defender) } } static async onImmobilisation(attacker, empoignade) { const defender = RdDEmpoignade.getActorEmpoignade(empoignade.system.empoigneid) if (defender) { const empDefenseur = defender.itemTypes[ITEM_TYPES.empoignade] .find(it => it.system.empoignadeid == empoignade.system.empoignadeid); await defender.updateEmbeddedDocuments('Item', [{ _id: empDefenseur.id, 'system.immobilise': true }]) const rollData = { mode: "immobilise", empoignade, attacker, defender, isEmpoignade: true, competence: attacker.getCompetenceCorpsACorps() } const msg = await ChatMessage.create({ whisper: ChatUtility.getOwners(attacker), content: await renderTemplate(`systems/foundryvtt-reve-de-dragon/templates/chat-empoignade-immobilise.hbs`, rollData) }) RdDEmpoignade.$storeRollEmpoignade(msg, rollData); } } /* -------------------------------------------- */ static async $rollAttaqueEmpoignade(attacker, rollData, isNouvelle = false) { const dialog = await RdDRoll.create(attacker, rollData, { html: 'systems/foundryvtt-reve-de-dragon/templates/dialog-roll-competence.hbs' }, { name: 'jet-empoignade', label: 'Empoigner', callbacks: [{ action: async (r) => await RdDEmpoignade.$onRollEmpoignade(r, isNouvelle) },] }); dialog.render(true); } /* -------------------------------------------- */ static async $onRollEmpoignade(rollData, isNouvelle = false) { const attacker = RdDEmpoignade.getActorEmpoignade(rollData.attacker.id) const defender = RdDEmpoignade.getActorEmpoignade(rollData.defender.id) if (attacker && defender) { if (rollData.rolled.isSuccess && isNouvelle) { RdDEmpoignade.$createEtatEmpoignade(rollData.empoignade) } rollData.empoignade.isSuccess = rollData.rolled.isSuccess; if (rollData.rolled.isPart) { rollData.particuliere = "finesse"; } let msg = await RdDRollResult.displayRollData(rollData, defender, 'chat-empoignade-resultat.hbs'); RdDEmpoignade.$storeRollEmpoignade(msg, rollData); } } /* -------------------------------------------- */ static async onDefenseEmpoignade(attackerRoll, mode, competenceName = "Corps à corps", carac = "melee") { const attacker = RdDEmpoignade.getActorEmpoignade(rollData.attacker.id) const defender = RdDEmpoignade.getActorEmpoignade(rollData.defender.id) if (!RdDEmpoignade.isActionAutorisee(mode, attacker, defender)) { return } let empoignade = RdDEmpoignade.getEmpoignade(attacker, defender) if (!empoignade) { ui.notifications.warn("Une erreur s'est produite : Aucune empoignade trouvée !!") return } empoignade = foundry.utils.duplicate(empoignade) let defenderRoll = { mode, attacker, defender, empoignade, attackerRoll, diffLibre: attackerRoll.diffLibre, attaqueParticuliere: attackerRoll.particuliere, competence: defender.getCompetence(competenceName), surprise: defender.getSurprise(true), carac: defender.system.carac, selectedCarac: defender.system.carac[carac], malusTaille: RdDEmpoignade.getMalusTaille(empoignade, defender, attacker), show: {} }; await RdDEmpoignade.$rollDefenseEmpoignade(defender, defenderRoll); } /* -------------------------------------------- */ static async $rollDefenseEmpoignade(defender, defenderRoll) { const dialog = await RdDRoll.create(defender, defenderRoll, { html: 'systems/foundryvtt-reve-de-dragon/templates/dialog-roll-defense-empoignade.hbs' }, { name: 'empoignade', label: 'Contrer', callbacks: [ { action: async (r) => await RdDEmpoignade.$onRollContrerLiberer(r) } ] } ); dialog.render(true); } /* -------------------------------------------- */ static async $onRollContrerLiberer(rollData) { const empoignade = rollData.empoignade if (rollData.mode == "contrer-empoigner" && !rollData.rolled.isSuccess) { empoignade.system.pointsemp++ RdDEmpoignade.$updateEtatEmpoignade(empoignade) } if (rollData.mode == "contrer-liberer" && !rollData.rolled.isSuccess) { empoignade.system.pointsemp-- RdDEmpoignade.$updateEtatEmpoignade(empoignade) } await RdDRollResult.displayRollData(rollData, rollData.defender, 'chat-empoignade-resultat.hbs') if (empoignade.system.pointsemp >= 2) { let msg = await RdDRollResult.displayRollData(rollData, rollData.attacker, 'chat-empoignade-entrainer.hbs'); RdDEmpoignade.$storeRollEmpoignade(msg, rollData); } } /* -------------------------------------------- */ static async $createEtatEmpoignade(empoignade) { console.log("CREATE Empoignade", empoignade) const attacker = RdDEmpoignade.getActorEmpoignade(empoignade.system.empoigneurid) const defender = RdDEmpoignade.getActorEmpoignade(empoignade.system.empoigneid) if (attacker && defender) { // Creer l'empoignade sur attaquant et defenseur await attacker.creerObjetParMJ(empoignade) await defender.creerObjetParMJ(empoignade) } } /* -------------------------------------------- */ static async $updateEtatEmpoignade(empoignade, attacker, defender) { attacker = attacker ?? RdDEmpoignade.getActorEmpoignade(empoignade.system.empoigneurid) defender = defender ?? RdDEmpoignade.getActorEmpoignade(empoignade.system.empoigneid) if (attacker && defender) { const belligerants = [attacker, defender] const removeEmp = empoignade.system.pointsemp == 0 if (removeEmp) { const emp = RdDEmpoignade.getEmpoignadeById(attacker, empoignade.system.empoignadeid) return await attacker.deleteEmbeddedDocuments('Item', [emp.id]) } else { await Promise.all( belligerants.map(async belligerant => { const emp = RdDEmpoignade.getEmpoignadeById(belligerant, empoignade.system.empoignadeid) return await belligerant.updateEmbeddedDocuments('Item', [{ _id: emp.id, "system.pointsemp": empoignade.system.pointsemp, "system.ausol": empoignade.system.ausol }]) })) } } } /* -------------------------------------------- */ static async $deleteEmpoignade(empoignade) { console.log("DELETE Empoignade", empoignade) const defender = RdDEmpoignade.getActorEmpoignade(empoignade.system.empoigneid) if (defender) { const emp = RdDEmpoignade.getEmpoignadeById(defender, empoignade.system.empoignadeid) await defender.deleteEmbeddedDocuments('Item', [emp.id]) } } /* -------------------------------------------- */ static async entrainerAuSol(rollData) { const attacker = RdDEmpoignade.getActorEmpoignade(rollData.attacker.id) const defender = RdDEmpoignade.getActorEmpoignade(rollData.defender.id) if (!RdDEmpoignade.isActionAutorisee("immobilise", attacker, defender)) { return } const empoignade = RdDEmpoignade.getEmpoignade(attacker, defender) empoignade.system.ausol = true await this.$updateEtatEmpoignade(empoignade) await attacker.setEffect(STATUSES.StatusProne, true); await defender.setEffect(STATUSES.StatusProne, true); const msg = await RdDRollResult.displayRollData(rollData, attacker, 'chat-empoignade-entrainer-sol.hbs'); RdDEmpoignade.$storeRollEmpoignade(msg, rollData); } /* -------------------------------------------- */ static async projeterAuSol(rollData) { const attacker = RdDEmpoignade.getActorEmpoignade(rollData.attacker.id) const defender = RdDEmpoignade.getActorEmpoignade(rollData.defender.id) if (!RdDEmpoignade.isActionAutorisee("immobilise", attacker, defender)) { return } let empoignade = RdDEmpoignade.getEmpoignade(attacker, defender) await defender.setEffect(STATUSES.StatusProne, true); await this.$deleteEmpoignade(empoignade) let msg = await RdDRollResult.displayRollData(rollData, attacker, 'chat-empoignade-projeter-sol.hbs'); RdDEmpoignade.$storeRollEmpoignade(msg, rollData); } /* -------------------------------------------- */ static async perteEndurance(rollData, perteMode) { const attacker = RdDEmpoignade.getActorEmpoignade(rollData.attacker.id) const defender = RdDEmpoignade.getActorEmpoignade(rollData.defender.id) if (perteMode == "none" || !RdDEmpoignade.isActionAutorisee("immobilise", attacker, defender)) { return } const perteEndurance = await RdDEmpoignade.$calcPerteEnd(perteMode, defender.system.sante.endurance.value) await defender.santeIncDec("endurance", -perteEndurance) await RdDRollResult.displayRollData({ attacker, defender, perteEndurance }, attacker, 'chat-empoignade-perte-endurance.hbs') } static async $calcPerteEnd(perteMode, endValue) { switch (perteMode) { case "none": return 0 case "end0": return endValue case "end1": return (endValue - 1) case "3/4": return Math.floor(3 * endValue / 4) case "1/2": return Math.floor(endValue / 2) case "1/4": return Math.floor(endValue / 4) } const rolled = await (new Roll(perteMode).evaluate()) return rolled.total } /* -------------------------------------------- */ static async deleteAllEmpoignades() { for (let actor of game.actors) { let empIds = actor.itemTypes["empoignade"].map(it => it.id) await actor.deleteEmbeddedDocuments('Item', empIds) } } /* -------------------------------------------- */ static async deleteLinkedEmpoignade(actorId, empoignade) { const actorDeleteId = (actorId == empoignade.system.empoigneurid) ? empoignade.system.empoigneid : empoignade.system.empoigneurid const actor = RdDEmpoignade.getActorEmpoignade(actorDeleteId) const empoignadeOpposant = this.getEmpoignadeById(actor, empoignade.system.empoignadeid) if (actor && empoignadeOpposant) { await actor.deleteEmbeddedDocuments('Item', [empoignadeOpposant.id]) } } /* -------------------------------------------- */ static async createEmpoignade(attacker, defender) { return await Item.create({ name: "Empoignade de " + attacker.name + ' sur ' + defender.name, type: ITEM_TYPES.empoignade, system: { description: "", empoignadeid: foundry.utils.randomID(16), empoigneurid: attacker.id, empoigneid: defender.id, pointsemp: 0, empoigneurname: attacker.name, empoignename: defender.name } }, { temporary: true }) } }