Initial release

This commit is contained in:
2026-05-08 23:05:29 +02:00
parent c0223977d2
commit 2953481a1c
157 changed files with 1940 additions and 392 deletions
@@ -27,6 +27,7 @@ export default class MGNEActorSheet extends HandlebarsApplicationMixin(foundry.a
syncArtifact: MGNEActorSheet.onSyncArtifact,
resetDaily: MGNEActorSheet.onResetDaily,
rollResonancePerDay: MGNEActorSheet.onRollResonancePerDay,
clearResonationBlock: MGNEActorSheet.onClearResonationBlock,
quickRest: MGNEActorSheet.onQuickRest,
fullRest: MGNEActorSheet.onFullRest,
},
@@ -146,6 +147,10 @@ export default class MGNEActorSheet extends HandlebarsApplicationMixin(foundry.a
return this.document.rollResonancePerDay()
}
static async onClearResonationBlock() {
return this.document.update({ "system.resonance.blocked": false })
}
static async onQuickRest() {
return this.document.quickRest()
}
+10 -1
View File
@@ -171,6 +171,10 @@ export default class MGNEActor extends Actor {
ui.notifications.warn(f("MGNE.Notification.ItemBurnedOut", { item: item.name }))
return null
}
if (this.system.resonance?.blocked) {
ui.notifications.warn(f("MGNE.Notification.ResonationBlocked", { actor: this.name }))
return null
}
if ((this.system.resonance?.used ?? 0) >= (this.system.resonance?.max ?? 0)) {
ui.notifications.warn(f("MGNE.Notification.ResonancePerDayReached", { actor: this.name }))
return null
@@ -187,7 +191,10 @@ export default class MGNEActor extends Actor {
await this.update({ "system.resonance.used": (this.system.resonance.used ?? 0) + 1 })
if (!result.success) {
await this.applyDamage(1, { sourceItem: item, ignoreArmor: true, chat: false })
const feedbackRoll = await (new Roll("1d2")).evaluate()
await this.applyDamage(feedbackRoll.total, { sourceItem: item, ignoreArmor: true, chat: true })
await this.update({ "system.resonance.blocked": true })
ui.notifications.warn(f("MGNE.Notification.ResonationFeedbackBlocked", { actor: this.name }))
}
return result
@@ -245,6 +252,7 @@ export default class MGNEActor extends Actor {
await this.update({
"system.hp.value": newHp,
"system.omens.current": omenRoll.total,
"system.resonance.blocked": false,
})
await MGNERoll.createRestCard({
@@ -297,6 +305,7 @@ export default class MGNEActor extends Actor {
"system.omens.current": omenRoll.total,
"system.resonance.max": resonanceMax,
"system.resonance.used": 0,
"system.resonance.blocked": false,
"system.artifactSync.used": 0,
"system.survival.salvationUsed": false,
})
+42 -4
View File
@@ -97,15 +97,18 @@ export default class MGNERoll {
const modifier = Number.parseInt(dialogData.modifier ?? 0, 10) || 0
const spendOmen = Boolean(dialogData.spendOmen)
const dr = (Number.parseInt(dialogData.dr ?? baseDR, 10) || baseDR) - (spendOmen ? 4 : 0)
// Re-read omens after dialog close to avoid race condition (omen could have changed)
const currentOmensAfterDialog = actor.system.omens?.current ?? 0
const canSpendOmen = spendOmen && currentOmensAfterDialog > 0
const dr = (Number.parseInt(dialogData.dr ?? baseDR, 10) || baseDR) - (canSpendOmen ? 4 : 0)
const abilityValue = actor.system.abilities?.[abilityId]?.value ?? 0
const sign = modifier >= 0 ? "+" : "-"
const formula = modifier === 0 ? `1d20 + ${abilityValue}` : `1d20 + ${abilityValue} ${sign} ${Math.abs(modifier)}`
const roll = await (new Roll(formula)).evaluate()
const natural = roll.dice?.[0]?.results?.[0]?.result ?? roll.total
if (spendOmen && (actor.system.omens?.current ?? 0) > 0) {
await actor.update({ "system.omens.current": Math.max(0, actor.system.omens.current - 1) })
if (canSpendOmen) {
await actor.update({ "system.omens.current": Math.max(0, currentOmensAfterDialog - 1) })
}
const critical = natural === 20
@@ -126,6 +129,15 @@ export default class MGNERoll {
specialText = rollType === "attack" ? t("MGNE.Roll.AttackFumble") : rollType === "defense" ? t("MGNE.Roll.DefenseFumble") : pickRandom(SYSTEM.tables.mishaps)
}
const actorOmens = actor.system.omens?.current ?? 0
let omenNeutralizeReminder = ""
let omenRerollReminder = ""
if (actorOmens > 0) {
if (critical) omenNeutralizeReminder = f("MGNE.Roll.OmenNeutralizeCrit", { omens: actorOmens })
else if (fumble) omenNeutralizeReminder = f("MGNE.Roll.OmenNeutralizeFumble", { omens: actorOmens })
else omenRerollReminder = f("MGNE.Roll.OmenRerollReminder", { omens: actorOmens })
}
const showDamageButton = rollType === "attack" && (success || critical) && !!item
const contentHtml = await renderCard({
mode: "check",
@@ -137,6 +149,8 @@ export default class MGNERoll {
total: roll.total,
outcome,
specialText,
omenNeutralizeReminder,
omenRerollReminder,
showDamageButton,
damageActorId: showDamageButton ? actor.id : null,
damageItemId: showDamageButton ? item.id : null,
@@ -183,7 +197,30 @@ export default class MGNERoll {
const multiplier = damageBonus?.multiplier ?? 1
const baseFormula = item.system.damage || "1"
const formula = multiplier > 1 ? `${multiplier} * (${baseFormula})` : baseFormula
const roll = await (new Roll(formula)).evaluate()
const actorOmens = actor.system.omens?.current ?? 0
let maximize = false
if (actorOmens > 0) {
const choice = await foundry.applications.api.DialogV2.wait({
window: { title: f("MGNE.Roll.ItemDamageLabel", { item: item.name }) },
classes: ["mgne", "roll-dialog"],
content: `<section class="mgne-roll-dialog"><p>${f("MGNE.RollDialog.OmenMaximizePrompt", { omens: actorOmens })}</p></section>`,
buttons: [
{ action: "roll", label: t("MGNE.Common.Roll"), icon: "fa-solid fa-dice", callback: () => "roll" },
{ action: "maximize", label: t("MGNE.RollDialog.SpendOmenMaximize"), icon: "fa-solid fa-star", callback: () => "maximize" },
],
rejectClose: false,
})
if (choice === null) return null
maximize = choice === "maximize"
}
const roll = await (new Roll(formula)).evaluate(maximize ? { maximize: true } : {})
if (maximize) {
// Re-read omens after dialog to avoid overwriting concurrent changes
const currentOmens = actor.system.omens?.current ?? 0
await actor.update({ "system.omens.current": Math.max(0, currentOmens - 1) })
}
const isCritical = multiplier > 1
const contentHtml = await renderCard({
mode: "damage",
@@ -195,6 +232,7 @@ export default class MGNERoll {
total: roll.total,
outcome: t("MGNE.Roll.OutcomeRolled"),
specialText: isCritical ? t("MGNE.Roll.CriticalDamageApplied") : "",
omenMaximized: maximize,
showApplyButton: true,
damageTotal: roll.total,
damageCritical: isCritical,