Various fixes and add rune support
This commit is contained in:
@@ -570,6 +570,26 @@
|
||||
.oathhammer .character-main .character-stats-band .character-resources .character-resource.character-resource--luck .luck-btn:hover {
|
||||
color: #2a1a0a;
|
||||
}
|
||||
.oathhammer .character-main .character-stats-band .character-resources .character-resource .grit-stepper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1px;
|
||||
}
|
||||
.oathhammer .character-main .character-stats-band .character-resources .character-resource .grit-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 1.2rem;
|
||||
height: 1.4rem;
|
||||
font-size: calc(0.86rem * 0.85);
|
||||
font-weight: bold;
|
||||
color: #535128;
|
||||
cursor: pointer;
|
||||
line-height: 1;
|
||||
}
|
||||
.oathhammer .character-main .character-stats-band .character-resources .character-resource .grit-btn:hover {
|
||||
color: #2a1a0a;
|
||||
}
|
||||
.oathhammer .character-main .character-stats-band .character-resources .resource-label {
|
||||
min-width: 4.2rem;
|
||||
font-family: "BlueDragon", "Palatino Linotype", serif;
|
||||
@@ -1419,6 +1439,110 @@
|
||||
.oathhammer .item-sheet-common .enchantment-fieldset .enchant-cursed-label input[type="checkbox"] {
|
||||
margin: 0;
|
||||
}
|
||||
.oathhammer .item-sheet-common .rune-zone {
|
||||
margin-top: 6px;
|
||||
border-color: #084a74;
|
||||
}
|
||||
.oathhammer .item-sheet-common .rune-zone legend {
|
||||
color: #084a74;
|
||||
}
|
||||
.oathhammer .item-sheet-common .rune-zone legend i {
|
||||
margin-right: 4px;
|
||||
}
|
||||
.oathhammer .item-sheet-common .rune-zone .rune-list {
|
||||
list-style: none;
|
||||
margin: 0 0 6px;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
.oathhammer .item-sheet-common .rune-zone .rune-entry {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 3px 6px;
|
||||
border-radius: 3px;
|
||||
background: rgba(0, 0, 0, 0.06);
|
||||
border: 1px solid #535128;
|
||||
}
|
||||
.oathhammer .item-sheet-common .rune-zone .rune-entry.rune-exalted {
|
||||
border-color: #084a74;
|
||||
background: rgba(8, 74, 116, 0.08);
|
||||
}
|
||||
.oathhammer .item-sheet-common .rune-zone .rune-entry .rune-img {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 3px;
|
||||
border: 1px solid #535128;
|
||||
-o-object-fit: cover;
|
||||
object-fit: cover;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.oathhammer .item-sheet-common .rune-zone .rune-entry .rune-name {
|
||||
flex: 1;
|
||||
font-family: "BlueDragon", "Palatino Linotype", serif;
|
||||
font-size: 0.86rem;
|
||||
font-weight: bold;
|
||||
color: #2a1a0a;
|
||||
min-width: 0;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.oathhammer .item-sheet-common .rune-zone .rune-entry .rune-badge-exalted {
|
||||
color: #084a74;
|
||||
font-size: calc(0.86rem * 0.85);
|
||||
font-weight: bold;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.oathhammer .item-sheet-common .rune-zone .rune-entry .rune-dv {
|
||||
font-family: "BlueDragon", "Palatino Linotype", serif;
|
||||
font-size: calc(0.86rem * 0.9);
|
||||
color: #535128;
|
||||
flex-shrink: 0;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.oathhammer .item-sheet-common .rune-zone .rune-entry .rune-duration {
|
||||
font-family: "Calibri", "Segoe UI", sans-serif;
|
||||
font-size: calc(0.86rem * 0.9);
|
||||
color: #2a1a0a;
|
||||
opacity: 0.7;
|
||||
flex-shrink: 0;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.oathhammer .item-sheet-common .rune-zone .rune-entry .rune-effect-toggle {
|
||||
color: #535128;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.oathhammer .item-sheet-common .rune-zone .rune-entry .rune-effect-toggle:hover {
|
||||
color: #084a74;
|
||||
}
|
||||
.oathhammer .item-sheet-common .rune-zone .rune-entry .rune-remove {
|
||||
color: #2a1a0a;
|
||||
opacity: 0.4;
|
||||
flex-shrink: 0;
|
||||
margin-left: auto;
|
||||
}
|
||||
.oathhammer .item-sheet-common .rune-zone .rune-entry .rune-remove:hover {
|
||||
color: #c0392b;
|
||||
opacity: 1;
|
||||
}
|
||||
.oathhammer .item-sheet-common .rune-zone .rune-drop-zone {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 6px;
|
||||
padding: 6px 8px;
|
||||
border: 1px dashed #535128;
|
||||
border-radius: 3px;
|
||||
color: #535128;
|
||||
font-family: "BlueDragon", "Palatino Linotype", serif;
|
||||
font-size: calc(0.86rem * 0.85);
|
||||
font-style: italic;
|
||||
opacity: 0.7;
|
||||
pointer-events: none;
|
||||
}
|
||||
.oathhammer .item-sheet-common .proficiency-section {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
@@ -2624,6 +2748,21 @@
|
||||
font-family: "BlueDragon", "Palatino Linotype", serif;
|
||||
font-size: calc(0.86rem * 0.85);
|
||||
}
|
||||
.oathhammer .party-main .party-slots .party-renown-label {
|
||||
font-weight: bold;
|
||||
color: #535128;
|
||||
margin-left: 0.8rem;
|
||||
margin-right: 0.3rem;
|
||||
text-transform: uppercase;
|
||||
font-size: calc(0.86rem * 0.9);
|
||||
letter-spacing: 0.04em;
|
||||
}
|
||||
.oathhammer .party-main .party-slots .party-renown-value {
|
||||
width: 3.5rem;
|
||||
text-align: center;
|
||||
font-family: "BlueDragon", "Palatino Linotype", serif;
|
||||
font-size: calc(0.86rem * 0.85);
|
||||
}
|
||||
.oathhammer .item-list--party-member .item-list-header,
|
||||
.oathhammer .item-list--party-member .item-entry {
|
||||
grid-template-columns: 1.8rem 24px 1fr 7rem 5rem 3rem 5.5rem;
|
||||
|
||||
17
lang/en.json
17
lang/en.json
@@ -373,7 +373,13 @@
|
||||
"Mercenary": "Mercenary",
|
||||
"CurrentXP": "XP",
|
||||
"CarriesLight": "Carries Light",
|
||||
"Slots": "Slots"
|
||||
"Slots": "Slots",
|
||||
"Runes": "Runes",
|
||||
"DropRuneHint": "Drop a runic spell here to attach it…",
|
||||
"RemoveRune": "Remove rune",
|
||||
"OpenRune": "Open spell sheet",
|
||||
"DifficultyValue": "Difficulty Value",
|
||||
"Exalted": "Exalted"
|
||||
},
|
||||
"ColorDice": {
|
||||
"White": "White (4+)",
|
||||
@@ -540,6 +546,15 @@
|
||||
"MagicSpells": "spells"
|
||||
}
|
||||
},
|
||||
"Rune": {
|
||||
"Attached": "Rune \"{name}\" attached.",
|
||||
"NotRunic": "Only runic tradition spells can be attached as runes.",
|
||||
"WrongType": "This item only accepts {expected} runes.",
|
||||
"MaxRunes": "This item already has 2 runes (maximum).",
|
||||
"Duplicate": "This rune is already attached to this item.",
|
||||
"MaxExalted": "This item already bears an exalted rune (maximum 1).",
|
||||
"SourceNotFound": "Source spell not found — it may have been deleted."
|
||||
},
|
||||
"FreeRoll": {
|
||||
"Label": "Free Roll",
|
||||
"PoolTitle": "Number of dice (1–20)",
|
||||
|
||||
@@ -189,6 +189,25 @@
|
||||
&:hover { color: @color-dark; }
|
||||
}
|
||||
}
|
||||
|
||||
.grit-stepper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1px;
|
||||
}
|
||||
.grit-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 1.2rem;
|
||||
height: 1.4rem;
|
||||
font-size: @font-size-sm;
|
||||
font-weight: bold;
|
||||
color: @color-olive;
|
||||
cursor: pointer;
|
||||
line-height: 1;
|
||||
&:hover { color: @color-dark; }
|
||||
}
|
||||
}
|
||||
|
||||
.resource-label {
|
||||
|
||||
@@ -134,7 +134,115 @@
|
||||
}
|
||||
}
|
||||
|
||||
// ── Class proficiency checkboxes ────────────────────────────
|
||||
// ── Rune zone ──────────────────────────────────────────────────
|
||||
.rune-zone {
|
||||
margin-top: 6px;
|
||||
border-color: @color-blue;
|
||||
|
||||
legend {
|
||||
color: @color-blue;
|
||||
i { margin-right: 4px; }
|
||||
}
|
||||
|
||||
.rune-list {
|
||||
list-style: none;
|
||||
margin: 0 0 6px;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.rune-entry {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 3px 6px;
|
||||
border-radius: 3px;
|
||||
background: rgba(0,0,0,0.06);
|
||||
border: 1px solid @color-olive;
|
||||
|
||||
&.rune-exalted {
|
||||
border-color: @color-blue;
|
||||
background: fade(@color-blue, 8%);
|
||||
}
|
||||
|
||||
.rune-img {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 3px;
|
||||
border: 1px solid @color-olive;
|
||||
object-fit: cover;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.rune-name {
|
||||
flex: 1;
|
||||
font-family: @font-secondary;
|
||||
font-size: @font-size-base;
|
||||
font-weight: bold;
|
||||
color: @color-dark;
|
||||
min-width: 0;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.rune-badge-exalted {
|
||||
color: @color-blue;
|
||||
font-size: @font-size-sm;
|
||||
font-weight: bold;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.rune-dv {
|
||||
font-family: @font-secondary;
|
||||
font-size: @font-size-xs;
|
||||
color: @color-olive;
|
||||
flex-shrink: 0;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.rune-duration {
|
||||
font-family: @font-body;
|
||||
font-size: @font-size-xs;
|
||||
color: @color-dark;
|
||||
opacity: 0.7;
|
||||
flex-shrink: 0;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.rune-effect-toggle {
|
||||
color: @color-olive;
|
||||
flex-shrink: 0;
|
||||
&:hover { color: @color-blue; }
|
||||
}
|
||||
|
||||
.rune-remove {
|
||||
color: @color-dark;
|
||||
opacity: 0.4;
|
||||
flex-shrink: 0;
|
||||
margin-left: auto;
|
||||
&:hover { color: #c0392b; opacity: 1; }
|
||||
}
|
||||
}
|
||||
|
||||
.rune-drop-zone {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 6px;
|
||||
padding: 6px 8px;
|
||||
border: 1px dashed @color-olive;
|
||||
border-radius: 3px;
|
||||
color: @color-olive;
|
||||
font-family: @font-secondary;
|
||||
font-size: @font-size-sm;
|
||||
font-style: italic;
|
||||
opacity: 0.7;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
.proficiency-section {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
|
||||
@@ -161,6 +161,21 @@
|
||||
font-family: @font-secondary;
|
||||
font-size: @font-size-sm;
|
||||
}
|
||||
.party-renown-label {
|
||||
font-weight: bold;
|
||||
color: @color-olive;
|
||||
margin-left: 0.8rem;
|
||||
margin-right: 0.3rem;
|
||||
text-transform: uppercase;
|
||||
font-size: @font-size-xs;
|
||||
letter-spacing: 0.04em;
|
||||
}
|
||||
.party-renown-value {
|
||||
width: 3.5rem;
|
||||
text-align: center;
|
||||
font-family: @font-secondary;
|
||||
font-size: @font-size-sm;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -98,6 +98,11 @@ Hooks.once("init", function () {
|
||||
|
||||
OathHammerUtils.registerHandlebarsHelpers()
|
||||
|
||||
// Pre-register Handlebars partials so {{> "path"}} works in item templates
|
||||
foundry.applications.handlebars.loadTemplates([
|
||||
"systems/fvtt-oath-hammer/templates/item/rune-zone.hbs",
|
||||
])
|
||||
|
||||
console.info("Oath Hammer | System Initialized")
|
||||
})
|
||||
|
||||
|
||||
@@ -50,7 +50,11 @@
|
||||
<fieldset class="character-resources">
|
||||
<div class="character-resource">
|
||||
<span class="resource-label">{{localize "OATHHAMMER.Label.Grit"}}</span>
|
||||
{{formInput systemFields.grit.fields.value value=system.grit.value name="system.grit.value" disabled=isPlayMode}}
|
||||
<div class="grit-stepper">
|
||||
<a data-action="adjustGrit" data-delta="-1" class="grit-btn">−</a>
|
||||
{{formInput systemFields.grit.fields.value value=system.grit.value name="system.grit.value"}}
|
||||
<a data-action="adjustGrit" data-delta="1" class="grit-btn">+</a>
|
||||
</div>
|
||||
<span class="res-sep">/</span>
|
||||
{{formInput systemFields.grit.fields.max value=system.grit.max name="system.grit.max" disabled=isPlayMode}}
|
||||
</div>
|
||||
|
||||
@@ -49,12 +49,14 @@
|
||||
|
||||
</div><!-- /party-treasury -->
|
||||
|
||||
<!-- Slots -->
|
||||
<!-- Slots + Renown -->
|
||||
<div class="party-slots">
|
||||
<span class="party-slots-label">{{localize "OATHHAMMER.Label.Slots"}}</span>
|
||||
<span class="party-slots-current">{{currentSlots}}</span>
|
||||
<span class="party-slots-sep">/</span>
|
||||
<input class="party-slots-max" type="number" name="system.maxSlots" value="{{system.maxSlots}}" min="0" {{#if isPlayMode}}disabled{{/if}} />
|
||||
<span class="party-renown-label">{{localize "OATHHAMMER.Label.Renown"}}</span>
|
||||
<input class="party-renown-value" type="number" name="system.renown" value="{{system.renown}}" min="0" />
|
||||
</div>
|
||||
</div><!-- /party-header-body -->
|
||||
</div>
|
||||
|
||||
@@ -108,4 +108,5 @@
|
||||
}}
|
||||
</fieldset>
|
||||
{{/if}}
|
||||
{{> "systems/fvtt-oath-hammer/templates/item/rune-zone.hbs"}}
|
||||
</section>
|
||||
@@ -24,4 +24,7 @@
|
||||
<legend>{{localize "OATHHAMMER.Label.Effect"}}</legend>
|
||||
{{formInput systemFields.effect enriched=enrichedEffect value=system.effect name="system.effect" toggled=true}}
|
||||
</fieldset>
|
||||
{{#if (eq system.itemType "talisman")}}
|
||||
{{> "systems/fvtt-oath-hammer/templates/item/rune-zone.hbs"}}
|
||||
{{/if}}
|
||||
</section>
|
||||
|
||||
38
templates/item/rune-zone.hbs
Normal file
38
templates/item/rune-zone.hbs
Normal file
@@ -0,0 +1,38 @@
|
||||
{{!-- Runic attachment zone — included in armor, weapon, magic-item (talisman) sheets --}}
|
||||
<fieldset class="rune-zone">
|
||||
<legend><i class="fa-solid fa-star-of-david"></i> {{localize "OATHHAMMER.Label.Runes"}}</legend>
|
||||
|
||||
{{#if enrichedRunes.length}}
|
||||
<ul class="rune-list">
|
||||
{{#each enrichedRunes as |rune|}}
|
||||
<li class="rune-entry {{#if rune.isExalted}}rune-exalted{{/if}}">
|
||||
<img class="rune-img" src="{{rune.img}}" alt="{{rune.name}}" />
|
||||
<span class="rune-name">{{rune.name}}</span>
|
||||
{{#if rune.isExalted}}
|
||||
<span class="rune-badge-exalted" data-tooltip="{{localize 'OATHHAMMER.Label.Exalted'}}">✦</span>
|
||||
{{/if}}
|
||||
<span class="rune-dv" data-tooltip="{{localize 'OATHHAMMER.Label.DifficultyValue'}}">DV{{rune.difficultyValue}}</span>
|
||||
{{#if rune.duration}}
|
||||
<span class="rune-duration">{{rune.duration}}</span>
|
||||
{{/if}}
|
||||
<a class="rune-effect-toggle"
|
||||
data-action="openRune"
|
||||
data-source-uuid="{{rune.sourceUuid}}"
|
||||
data-tooltip="{{localize 'OATHHAMMER.Label.OpenRune'}}"
|
||||
data-tooltip-direction="UP">
|
||||
<i class="fa-solid fa-circle-info"></i>
|
||||
</a>
|
||||
<a class="rune-remove" data-action="removeRune" data-rune-index="{{rune.idx}}"
|
||||
data-tooltip="{{localize 'OATHHAMMER.Label.RemoveRune'}}">
|
||||
<i class="fa-solid fa-xmark"></i>
|
||||
</a>
|
||||
</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
{{/if}}
|
||||
|
||||
<div class="rune-drop-zone">
|
||||
<i class="fa-solid fa-wand-sparkles"></i>
|
||||
<span>{{localize "OATHHAMMER.Label.DropRuneHint"}}</span>
|
||||
</div>
|
||||
</fieldset>
|
||||
@@ -78,4 +78,5 @@
|
||||
{{formInput systemFields.magicEffect enriched=enrichedMagicEffect value=system.magicEffect name="system.magicEffect" toggled=true}}
|
||||
</fieldset>
|
||||
{{/if}}
|
||||
{{> "systems/fvtt-oath-hammer/templates/item/rune-zone.hbs"}}
|
||||
</section>
|
||||
|
||||
Reference in New Issue
Block a user