import MGNERoll from "./roll.mjs" import { SYSTEM_ID } from "../config/system.mjs" const t = key => game.i18n.localize(key) const f = (key, data = {}) => game.i18n.format(key, data) const INITIATIVE_FLAG = "initiativeState" const SIDE_BONUS = 20 const FLEE_BASE_TARGET = 5 const FLEE_MIN_TARGET = 2 function isPlayerSide(combatant) { return Boolean(combatant?.actor?.hasPlayerOwner) } function fleeDialogContent() { const options = Array.from({ length: FLEE_BASE_TARGET - FLEE_MIN_TARGET + 1 }, (_, index) => { const value = index return `` }).join("") return `
` } export default class MGNECombat extends Combat { getFleeCombatant(combatantId = null) { if (combatantId) return this.combatants.get(combatantId) ?? null const controlledCombatant = canvas.tokens?.controlled?.[0]?.combatant ?? null return this.combatant ?? controlledCombatant } async getInitiativeState() { const existing = this.getFlag(SYSTEM_ID, INITIATIVE_FLAG) if (existing && Number.isFinite(existing.sideRoll)) return existing const sideRoll = await (new Roll("1d6")).evaluate() const initiativeState = { sideRoll: sideRoll.total, playersActFirst: sideRoll.total >= 4, } await this.setFlag(SYSTEM_ID, INITIATIVE_FLAG, initiativeState) await ChatMessage.create({ speaker: ChatMessage.getSpeaker(), content: `${f("MGNE.Initiative.SideRoll", { roll: initiativeState.sideRoll })}
${t(initiativeState.playersActFirst ? "MGNE.Initiative.PlayersFirst" : "MGNE.Initiative.EnemiesFirst")}
${t("MGNE.Initiative.TieBreak")}
`, }) return initiativeState } async rollInitiative(ids, { updateTurn = true } = {}) { const combatantIds = typeof ids === "string" ? [ids] : Array.from(ids ?? []) if (!combatantIds.length) return this const currentCombatantId = this.combatant?.id ?? null const initiativeState = await this.getInitiativeState() const updates = [] for (const id of combatantIds) { const combatant = this.combatants.get(id) if (!combatant) continue const agility = combatant.actor?.system?.abilities?.agility?.value ?? 0 const roll = await (new Roll("1d6 + @agility", { agility })).evaluate() const actsWithPriority = isPlayerSide(combatant) === initiativeState.playersActFirst updates.push({ _id: id, initiative: roll.total + (actsWithPriority ? SIDE_BONUS : 0), }) } if (updates.length) await this.updateEmbeddedDocuments("Combatant", updates) if (updateTurn && currentCombatantId) { const turn = this.turns.findIndex(combatant => combatant.id === currentCombatantId) if (turn >= 0) await this.update({ turn }) } return this } async rollFlee({ combatantId = null } = {}) { const combatant = this.getFleeCombatant(combatantId) if (!combatant?.actor) { ui.notifications.warn(t("MGNE.Combat.FleeNoCombatant")) return null } const dialogData = await foundry.applications.api.DialogV2.wait({ window: { title: t("MGNE.Combat.Flee") }, classes: ["mgne", "roll-dialog"], content: fleeDialogContent(), buttons: [{ label: t("MGNE.Combat.Flee"), icon: "fa-solid fa-person-running", callback: (_event, button) => { const value = Number.parseInt(button.form?.elements.studyActions?.value ?? "0", 10) return { studyActions: Math.max(0, Math.min(FLEE_BASE_TARGET - FLEE_MIN_TARGET, value || 0)) } }, }], rejectClose: false, }) if (!dialogData) return null const studyActions = dialogData.studyActions ?? 0 const target = Math.max(FLEE_MIN_TARGET, FLEE_BASE_TARGET - studyActions) const roll = await (new Roll("1d6")).evaluate() const escaped = roll.total >= target if (escaped) { await combatant.delete() } else { await combatant.actor.update({ "system.hp.value": 0 }) await combatant.update({ defeated: true }) } await MGNERoll.createActionCard({ mode: "flee", actor: combatant.actor, label: f("MGNE.Roll.FleeLabel", { actor: combatant.name }), subtitle: f("MGNE.Roll.FleeSubtitle", { target }), roll, outcome: t(escaped ? "MGNE.Roll.FleeEscaped" : "MGNE.Roll.FleeKilled"), specialText: studyActions > 0 ? f("MGNE.Roll.FleeStudyActions", { count: studyActions }) : t("MGNE.Roll.FleeNoStudyActions"), }) return { combatant, roll, escaped, target, studyActions } } }