Elles sont souvent touchées ensemble Préparation aux changements pour utiliser les Combatants (pour gérer les empoignades/possessions en cours) Renommages: - RdDCombat -> PasseArmes - RdDCombatManager -> RdDCombat
299 lines
11 KiB
JavaScript
299 lines
11 KiB
JavaScript
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(`<div data-combatid="${this.id}" data-combatmessage="actor-turn-summary">`)
|
|
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}<br>`
|
|
},
|
|
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 = `<h4>L'initiative de ${combatant.actor.getAlias()} a été modifiée !</h4>
|
|
<hr>
|
|
<div>
|
|
Etant donné son ${action.name}, son initative pour ce premier round est désormais de ${initData.init}.
|
|
</div>`
|
|
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 = '<i class="far fa-question-circle"></i>'
|
|
option.callback = target => {
|
|
RdDCombat.displayInitiativeMenu(html, target.data('combatant-id'))
|
|
}
|
|
}
|
|
}
|
|
options = [
|
|
{ name: "Incrémenter initiative", condition: true, icon: '<i class="fa-solid fa-plus"></i>', callback: target => { RdDCombat.incDecInit(target.data('combatant-id'), +1); } },
|
|
{ name: "Décrémenter initiative", condition: true, icon: '<i class="fa-solid fa-minus"></i>', 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: "<i class='fas fa-dice-d6'></i>",
|
|
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)
|
|
}
|
|
}
|