Various fixes and add rune support
This commit is contained in:
@@ -30,10 +30,19 @@ export default class OathHammerItemSheet extends HandlebarsApplicationMixin(foun
|
||||
toggleSheet: OathHammerItemSheet.#onToggleSheet,
|
||||
editImage: OathHammerItemSheet.#onEditImage,
|
||||
rollRarity: OathHammerItemSheet.#onRollRarity,
|
||||
removeRune: OathHammerItemSheet.#onRemoveRune,
|
||||
openRune: OathHammerItemSheet.#onOpenRune,
|
||||
},
|
||||
}
|
||||
|
||||
_sheetMode = this.constructor.SHEET_MODES.PLAY
|
||||
_lockedReadOnly = false
|
||||
|
||||
/** @override — prevent form submissions when this sheet is opened in read-only mode */
|
||||
get isEditable() {
|
||||
if (this._lockedReadOnly) return false
|
||||
return super.isEditable
|
||||
}
|
||||
|
||||
get isPlayMode() {
|
||||
return this._sheetMode === this.constructor.SHEET_MODES.PLAY
|
||||
@@ -85,13 +94,49 @@ export default class OathHammerItemSheet extends HandlebarsApplicationMixin(foun
|
||||
// Class proficiency choices (for class-sheet checkboxes)
|
||||
context.armorTypeChoices = ARMOR_TYPE_CHOICES
|
||||
context.weaponGroupChoices = WEAPON_PROFICIENCY_GROUPS
|
||||
|
||||
// Rune context — enrich effect HTML for each attached rune
|
||||
if (Array.isArray(this.document.system.runes)) {
|
||||
context.enrichedRunes = await Promise.all(
|
||||
this.document.system.runes.map(async (rune, idx) => ({
|
||||
...rune,
|
||||
idx,
|
||||
enrichedEffect: await foundry.applications.ux.TextEditor.implementation.enrichHTML(rune.effect ?? "", { async: true }),
|
||||
}))
|
||||
)
|
||||
}
|
||||
context.acceptsRunes = this.#acceptsRunes()
|
||||
|
||||
return context
|
||||
}
|
||||
|
||||
/** Returns true if this item type can have runic spells attached. */
|
||||
#acceptsRunes() {
|
||||
const type = this.document.type
|
||||
if (type === "armor" || type === "weapon") return true
|
||||
if (type === "magic-item" && this.document.system.itemType === "talisman") return true
|
||||
return false
|
||||
}
|
||||
|
||||
/** Map runeType expected for each item type. */
|
||||
static #EXPECTED_RUNE_TYPE = {
|
||||
armor: "armor",
|
||||
weapon: "weapon",
|
||||
"magic-item": "talisman",
|
||||
}
|
||||
|
||||
/** @override */
|
||||
_onRender(context, options) {
|
||||
super._onRender(context, options)
|
||||
this.#dragDrop.forEach((d) => d.bind(this.element))
|
||||
if (this._lockedReadOnly) {
|
||||
this.element.querySelector('[data-action="toggleSheet"]')?.remove()
|
||||
for (const el of this.element.querySelectorAll(
|
||||
'.window-content input, .window-content select, .window-content textarea, .window-content button'
|
||||
)) {
|
||||
el.disabled = true
|
||||
}
|
||||
}
|
||||
for (const pm of this.element.querySelectorAll("prose-mirror[name]")) {
|
||||
pm.addEventListener("change", async (event) => {
|
||||
event.stopPropagation()
|
||||
@@ -129,9 +174,93 @@ export default class OathHammerItemSheet extends HandlebarsApplicationMixin(foun
|
||||
|
||||
_onDragOver(event) {}
|
||||
|
||||
async _onDrop(event) {}
|
||||
async _onDrop(event) {
|
||||
if (!this.#acceptsRunes()) return
|
||||
let dragData
|
||||
try { dragData = JSON.parse(event.dataTransfer.getData("text/plain")) } catch { return }
|
||||
if (dragData?.type !== "Item") return
|
||||
|
||||
let spell
|
||||
try { spell = await fromUuid(dragData.uuid) } catch { return }
|
||||
if (!spell || spell.type !== "spell") return
|
||||
|
||||
const sys = spell.system
|
||||
if (sys.tradition !== "runic") {
|
||||
ui.notifications.warn(game.i18n.localize("OATHHAMMER.Rune.NotRunic"))
|
||||
return
|
||||
}
|
||||
|
||||
const expectedType = OathHammerItemSheet.#EXPECTED_RUNE_TYPE[this.document.type]
|
||||
if (sys.runeType !== expectedType) {
|
||||
ui.notifications.warn(game.i18n.format("OATHHAMMER.Rune.WrongType", {
|
||||
expected: game.i18n.localize(`OATHHAMMER.RuneType.${expectedType.charAt(0).toUpperCase() + expectedType.slice(1)}`),
|
||||
}))
|
||||
return
|
||||
}
|
||||
|
||||
const current = this.document.system.runes ?? []
|
||||
if (current.length >= 2) {
|
||||
ui.notifications.warn(game.i18n.localize("OATHHAMMER.Rune.MaxRunes"))
|
||||
return
|
||||
}
|
||||
if (current.some(r => r.name === spell.name)) {
|
||||
ui.notifications.warn(game.i18n.localize("OATHHAMMER.Rune.Duplicate"))
|
||||
return
|
||||
}
|
||||
if (sys.isExalted && current.some(r => r.isExalted)) {
|
||||
ui.notifications.warn(game.i18n.localize("OATHHAMMER.Rune.MaxExalted"))
|
||||
return
|
||||
}
|
||||
|
||||
const snapshot = {
|
||||
sourceUuid: spell.uuid,
|
||||
name: spell.name,
|
||||
img: spell.img,
|
||||
runeType: sys.runeType,
|
||||
isExalted: sys.isExalted,
|
||||
difficultyValue: sys.difficultyValue,
|
||||
effect: sys.effect ?? "",
|
||||
duration: sys.duration ?? "",
|
||||
range: sys.range ?? "",
|
||||
spellSave: sys.spellSave ?? "",
|
||||
}
|
||||
await this.document.update({ "system.runes": [...current, snapshot] })
|
||||
ui.notifications.info(game.i18n.format("OATHHAMMER.Rune.Attached", { name: spell.name }))
|
||||
}
|
||||
|
||||
static async #onRemoveRune(event, target) {
|
||||
const idx = parseInt(target.dataset.runeIndex, 10)
|
||||
if (isNaN(idx)) return
|
||||
const runes = [...(this.document.system.runes ?? [])]
|
||||
runes.splice(idx, 1)
|
||||
await this.document.update({ "system.runes": runes })
|
||||
}
|
||||
|
||||
static async #onOpenRune(event, target) {
|
||||
const uuid = target.dataset.sourceUuid
|
||||
if (!uuid) return
|
||||
try {
|
||||
const spell = await fromUuid(uuid)
|
||||
if (!spell) {
|
||||
ui.notifications.warn(game.i18n.localize("OATHHAMMER.Rune.SourceNotFound"))
|
||||
return
|
||||
}
|
||||
// Use a unique ID so this read-only instance never conflicts with the
|
||||
// document's normal sheet (which uses {ClassName}-{documentId} as its ID).
|
||||
const SheetClass = spell.sheet.constructor
|
||||
const sheet = new SheetClass({
|
||||
document: spell,
|
||||
id: `${SheetClass.name}-rune-view-${spell.id}`,
|
||||
})
|
||||
sheet._lockedReadOnly = true
|
||||
sheet.render(true)
|
||||
} catch {
|
||||
ui.notifications.warn(game.i18n.localize("OATHHAMMER.Rune.SourceNotFound"))
|
||||
}
|
||||
}
|
||||
|
||||
static #onToggleSheet(event, target) {
|
||||
if (this._lockedReadOnly) return
|
||||
const modes = this.constructor.SHEET_MODES
|
||||
this._sheetMode = this.isEditMode ? modes.PLAY : modes.EDIT
|
||||
this.render()
|
||||
|
||||
@@ -38,6 +38,7 @@ export default class OathHammerCharacterSheet extends OathHammerActorSheet {
|
||||
adjustQty: OathHammerCharacterSheet.#onAdjustQty,
|
||||
adjustCurrency: OathHammerCharacterSheet.#onAdjustCurrency,
|
||||
adjustLuck: OathHammerCharacterSheet.#onAdjustLuck,
|
||||
adjustGrit: OathHammerCharacterSheet.#onAdjustGrit,
|
||||
clearStress: OathHammerCharacterSheet.#onClearStress,
|
||||
},
|
||||
}
|
||||
@@ -417,6 +418,13 @@ export default class OathHammerCharacterSheet extends OathHammerActorSheet {
|
||||
await this.document.update({ "system.luck.value": Math.max(0, current + delta) })
|
||||
}
|
||||
|
||||
static async #onAdjustGrit(event, target) {
|
||||
const delta = parseInt(target.dataset.delta, 10)
|
||||
const current = this.document.system.grit.value ?? 0
|
||||
const max = this.document.system.grit.max ?? 0
|
||||
await this.document.update({ "system.grit.value": Math.max(0, Math.min(max, current + delta)) })
|
||||
}
|
||||
|
||||
static async #onAdjustStress(event, target) {
|
||||
const delta = parseInt(target.dataset.delta, 10)
|
||||
const current = this.document.system.arcaneStress.value ?? 0
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { SYSTEM } from "../config/system.mjs"
|
||||
import { CLASS_RESTRICTION_CHOICES, SYSTEM } from "../config/system.mjs"
|
||||
|
||||
export default class OathHammerArmor extends foundry.abstract.TypeDataModel {
|
||||
static defineSchema() {
|
||||
@@ -39,7 +39,10 @@ export default class OathHammerArmor extends foundry.abstract.TypeDataModel {
|
||||
schema.isCursed = new fields.BooleanField({ initial: false })
|
||||
schema.magicEffect = new fields.HTMLField({ required: false, textSearch: true })
|
||||
// Class/lineage restriction, e.g. "Dwarves only" (empty = no restriction)
|
||||
schema.classRestriction = new fields.StringField({ required: true, nullable: false, initial: "" })
|
||||
schema.classRestriction = new fields.StringField({ required: false, nullable: true, initial: null, choices: CLASS_RESTRICTION_CHOICES })
|
||||
|
||||
// Attached runic spells (max 2; each is a snapshot of the spell item)
|
||||
schema.runes = new fields.ArrayField(new fields.ObjectField(), { required: true, initial: [] })
|
||||
|
||||
return schema
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { SYSTEM } from "../config/system.mjs"
|
||||
import { CLASS_RESTRICTION_CHOICES, SYSTEM } from "../config/system.mjs"
|
||||
|
||||
export default class OathHammerMagicItem extends foundry.abstract.TypeDataModel {
|
||||
static defineSchema() {
|
||||
@@ -27,7 +27,7 @@ export default class OathHammerMagicItem extends foundry.abstract.TypeDataModel
|
||||
schema.isBonded = new fields.BooleanField({ initial: false })
|
||||
|
||||
// Class/lineage restriction printed in the item's type line, e.g. "Troubadour Only"
|
||||
schema.classRestriction = new fields.StringField({ required: true, nullable: false, initial: "" })
|
||||
schema.classRestriction = new fields.StringField({ required: false, nullable: true, initial: null, choices: CLASS_RESTRICTION_CHOICES })
|
||||
|
||||
// Limited-use items (e.g. "once per day"); none = always active
|
||||
schema.usagePeriod = new fields.StringField({
|
||||
@@ -39,6 +39,9 @@ export default class OathHammerMagicItem extends foundry.abstract.TypeDataModel
|
||||
|
||||
schema.equipped = new fields.BooleanField({ initial: false })
|
||||
|
||||
// Attached runic spells — only for talisman subtype (max 2; snapshot of spell item)
|
||||
schema.runes = new fields.ArrayField(new fields.ObjectField(), { required: true, initial: [] })
|
||||
|
||||
return schema
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ export default class OathHammerParty extends foundry.abstract.TypeDataModel {
|
||||
})
|
||||
|
||||
schema.maxSlots = new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 })
|
||||
schema.renown = new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 })
|
||||
|
||||
return schema
|
||||
}
|
||||
|
||||
@@ -64,6 +64,9 @@ export default class OathHammerWeapon extends foundry.abstract.TypeDataModel {
|
||||
// Use this for abilities like Magic Bolt that roll Magic+Willpower instead.
|
||||
schema.skillOverride = new fields.StringField({ required: false, nullable: true, initial: null })
|
||||
|
||||
// Attached runic spells (max 2; each is a snapshot of the spell item)
|
||||
schema.runes = new fields.ArrayField(new fields.ObjectField(), { required: true, initial: [] })
|
||||
|
||||
return schema
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user