System development, WIP
This commit is contained in:
@@ -0,0 +1,139 @@
|
||||
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 `<option value="${value}">${value}</option>`
|
||||
}).join("")
|
||||
|
||||
return `
|
||||
<form class="mgne-flee-dialog">
|
||||
<div class="form-group">
|
||||
<label>${t("MGNE.Combat.StudyActions")}</label>
|
||||
<select name="studyActions">${options}</select>
|
||||
<p class="notes">${t("MGNE.Combat.StudyHelp")}</p>
|
||||
</div>
|
||||
</form>
|
||||
`
|
||||
}
|
||||
|
||||
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: `<p>${f("MGNE.Initiative.SideRoll", { roll: initiativeState.sideRoll })}</p><p>${t(initiativeState.playersActFirst ? "MGNE.Initiative.PlayersFirst" : "MGNE.Initiative.EnemiesFirst")}</p><p>${t("MGNE.Initiative.TieBreak")}</p>`,
|
||||
})
|
||||
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 }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user