From c6f7a9e966bdb327567a0832e56bb96df68be4f5 Mon Sep 17 00:00:00 2001 From: LeRatierBretonnier Date: Sat, 7 Mar 2026 19:18:03 +0100 Subject: [PATCH] Iteam cleanup + less migration --- assets/icons/conditions/blinded.svg | 9 + assets/icons/conditions/confused.svg | 9 + assets/icons/conditions/dazed.svg | 14 + assets/icons/conditions/deafened.svg | 7 + assets/icons/conditions/demoralized.svg | 10 + assets/icons/conditions/diseased.svg | 17 + assets/icons/conditions/enfeebled.svg | 13 + assets/icons/conditions/fatigued.svg | 9 + assets/icons/conditions/frightened.svg | 11 + assets/icons/conditions/ignited.svg | 7 + assets/icons/conditions/inspired.svg | 11 + assets/icons/conditions/invisible.svg | 9 + assets/icons/conditions/poisoned.svg | 11 + assets/icons/conditions/restrained.svg | 14 + assets/icons/conditions/stunned.svg | 11 + css/fvtt-oath-hammer.css | 428 +++++++--------- gulpfile.js | 22 + lang/en.json | 463 ++++++++++-------- less/actor-sheet.less | 135 +++++ less/base.less | 100 ++++ less/fvtt-oath-hammer.less | 10 + less/item-list.less | 67 +++ less/item-sheets.less | 74 +++ less/npc-sheet.less | 22 + less/variables.less | 51 ++ module/applications/_module.mjs | 2 - .../applications/sheets/character-sheet.mjs | 2 - .../applications/sheets/condition-sheet.mjs | 27 - module/applications/sheets/oath-sheet.mjs | 5 +- module/applications/sheets/shield-sheet.mjs | 27 - module/config/system.mjs | 318 ++++++++---- module/documents/actor.mjs | 10 +- module/documents/item.mjs | 18 +- module/models/_module.mjs | 2 - module/models/ability.mjs | 21 +- module/models/ammunition.mjs | 20 +- module/models/armor.mjs | 37 +- module/models/character.mjs | 2 +- module/models/condition.mjs | 17 - module/models/equipment.mjs | 16 +- module/models/magic-item.mjs | 43 +- module/models/miracle.mjs | 31 +- module/models/oath.mjs | 5 +- module/models/shield.mjs | 20 - module/models/spell.mjs | 48 +- module/models/weapon.mjs | 71 ++- oath-hammer.mjs | 10 +- package.json | 21 + system.json | 10 +- templates/actor/character-combat.hbs | 26 +- templates/actor/npc-combat.hbs | 4 +- templates/item/ability-sheet.hbs | 6 +- templates/item/ammunition-sheet.hbs | 2 +- templates/item/armor-sheet.hbs | 20 +- templates/item/condition-sheet.hbs | 17 - templates/item/equipment-sheet.hbs | 6 +- templates/item/magic-item-sheet.hbs | 23 +- templates/item/oath-sheet.hbs | 12 +- templates/item/shield-sheet.hbs | 21 - templates/item/weapon-sheet.hbs | 30 +- 60 files changed, 1633 insertions(+), 851 deletions(-) create mode 100644 assets/icons/conditions/blinded.svg create mode 100644 assets/icons/conditions/confused.svg create mode 100644 assets/icons/conditions/dazed.svg create mode 100644 assets/icons/conditions/deafened.svg create mode 100644 assets/icons/conditions/demoralized.svg create mode 100644 assets/icons/conditions/diseased.svg create mode 100644 assets/icons/conditions/enfeebled.svg create mode 100644 assets/icons/conditions/fatigued.svg create mode 100644 assets/icons/conditions/frightened.svg create mode 100644 assets/icons/conditions/ignited.svg create mode 100644 assets/icons/conditions/inspired.svg create mode 100644 assets/icons/conditions/invisible.svg create mode 100644 assets/icons/conditions/poisoned.svg create mode 100644 assets/icons/conditions/restrained.svg create mode 100644 assets/icons/conditions/stunned.svg create mode 100644 gulpfile.js create mode 100644 less/actor-sheet.less create mode 100644 less/base.less create mode 100644 less/fvtt-oath-hammer.less create mode 100644 less/item-list.less create mode 100644 less/item-sheets.less create mode 100644 less/npc-sheet.less create mode 100644 less/variables.less delete mode 100644 module/applications/sheets/condition-sheet.mjs delete mode 100644 module/applications/sheets/shield-sheet.mjs delete mode 100644 module/models/condition.mjs delete mode 100644 module/models/shield.mjs create mode 100644 package.json delete mode 100644 templates/item/condition-sheet.hbs delete mode 100644 templates/item/shield-sheet.hbs diff --git a/assets/icons/conditions/blinded.svg b/assets/icons/conditions/blinded.svg new file mode 100644 index 0000000..b5277de --- /dev/null +++ b/assets/icons/conditions/blinded.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/assets/icons/conditions/confused.svg b/assets/icons/conditions/confused.svg new file mode 100644 index 0000000..c270c1c --- /dev/null +++ b/assets/icons/conditions/confused.svg @@ -0,0 +1,9 @@ + + + + ? + + + + + diff --git a/assets/icons/conditions/dazed.svg b/assets/icons/conditions/dazed.svg new file mode 100644 index 0000000..b1823a6 --- /dev/null +++ b/assets/icons/conditions/dazed.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/assets/icons/conditions/deafened.svg b/assets/icons/conditions/deafened.svg new file mode 100644 index 0000000..cffa31f --- /dev/null +++ b/assets/icons/conditions/deafened.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/assets/icons/conditions/demoralized.svg b/assets/icons/conditions/demoralized.svg new file mode 100644 index 0000000..906a492 --- /dev/null +++ b/assets/icons/conditions/demoralized.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/assets/icons/conditions/diseased.svg b/assets/icons/conditions/diseased.svg new file mode 100644 index 0000000..3e33ac5 --- /dev/null +++ b/assets/icons/conditions/diseased.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/assets/icons/conditions/enfeebled.svg b/assets/icons/conditions/enfeebled.svg new file mode 100644 index 0000000..6932e2b --- /dev/null +++ b/assets/icons/conditions/enfeebled.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/assets/icons/conditions/fatigued.svg b/assets/icons/conditions/fatigued.svg new file mode 100644 index 0000000..4133013 --- /dev/null +++ b/assets/icons/conditions/fatigued.svg @@ -0,0 +1,9 @@ + + + + Z + Z + Z + + + diff --git a/assets/icons/conditions/frightened.svg b/assets/icons/conditions/frightened.svg new file mode 100644 index 0000000..c715be1 --- /dev/null +++ b/assets/icons/conditions/frightened.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/assets/icons/conditions/ignited.svg b/assets/icons/conditions/ignited.svg new file mode 100644 index 0000000..33682a4 --- /dev/null +++ b/assets/icons/conditions/ignited.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/assets/icons/conditions/inspired.svg b/assets/icons/conditions/inspired.svg new file mode 100644 index 0000000..4749324 --- /dev/null +++ b/assets/icons/conditions/inspired.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/assets/icons/conditions/invisible.svg b/assets/icons/conditions/invisible.svg new file mode 100644 index 0000000..68df1de --- /dev/null +++ b/assets/icons/conditions/invisible.svg @@ -0,0 +1,9 @@ + + + + + + + + diff --git a/assets/icons/conditions/poisoned.svg b/assets/icons/conditions/poisoned.svg new file mode 100644 index 0000000..469a9cf --- /dev/null +++ b/assets/icons/conditions/poisoned.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/assets/icons/conditions/restrained.svg b/assets/icons/conditions/restrained.svg new file mode 100644 index 0000000..6e22a13 --- /dev/null +++ b/assets/icons/conditions/restrained.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/assets/icons/conditions/stunned.svg b/assets/icons/conditions/stunned.svg new file mode 100644 index 0000000..263edbd --- /dev/null +++ b/assets/icons/conditions/stunned.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/css/fvtt-oath-hammer.css b/css/fvtt-oath-hammer.css index 0832a64..37bccd6 100644 --- a/css/fvtt-oath-hammer.css +++ b/css/fvtt-oath-hammer.css @@ -1,14 +1,11 @@ @font-face { font-family: "Sherwood"; - src: url("../assets/fonts/Sherwood.otf") format("opentype"), - url("../assets/fonts/SHERWOOD.TTF") format("truetype"), - url("../assets/fonts/Sherwood.woff") format("woff"); + src: url("../assets/fonts/Sherwood.otf") format("opentype"), url("../assets/fonts/SHERWOOD.TTF") format("truetype"), url("../assets/fonts/Sherwood.woff") format("woff"); } @font-face { font-family: "BlueDragon"; src: url("../assets/fonts/Blue Dragon.ttf") format("truetype"); } - :root { --oh-font-primary: "Sherwood", "Palatino Linotype", serif; --oh-font-secondary: "BlueDragon", "Palatino Linotype", serif; @@ -22,108 +19,100 @@ --oh-background-image: url("../assets/ui/oath_hammer_paper.webp"); --oh-logo: url("../assets/logos/official_logo_01.webp"); } - -/* ======================== */ -/* GLOBAL DIALOG STYLING */ -/* ======================== */ .application.dialog.oathhammer { - font-family: var(--oh-font-primary); - font-size: var(--oh-font-size); + font-family: "Sherwood", "Palatino Linotype", serif; + font-size: 0.82rem; background-image: var(--oh-background-image); + background-repeat: no-repeat; + background-size: 100% 100%; } - -/* ======================== */ -/* ACTOR SHEET CONTENT */ -/* ======================== */ .oathhammer .character-content, .oathhammer .npc-content { - font-family: var(--oh-font-primary); - font-size: var(--oh-font-size); + font-family: "Sherwood", "Palatino Linotype", serif; + font-size: 0.82rem; color: var(--color-dark-1); background-image: var(--oh-background-image); background-repeat: no-repeat; background-size: 100% 100%; overflow: auto; } - .oathhammer .character-content nav.tabs [data-tab], .oathhammer .npc-content nav.tabs [data-tab] { - color: var(--oh-color-olive); + color: #5a5a2a; } - .oathhammer .character-content nav.tabs [data-tab].active, .oathhammer .npc-content nav.tabs [data-tab].active { - color: var(--oh-color-blue); + color: #1a4a7a; } - .oathhammer .character-content input:disabled, -.oathhammer .character-content select:disabled, .oathhammer .npc-content input:disabled, +.oathhammer .character-content select:disabled, .oathhammer .npc-content select:disabled { background-color: rgba(0, 0, 0, 0.08); border-color: transparent; color: var(--color-dark-3); } - .oathhammer .character-content input, -.oathhammer .character-content select, .oathhammer .npc-content input, +.oathhammer .character-content select, .oathhammer .npc-content select { height: 1.5rem; background-color: rgba(255, 255, 255, 0.3); - border-color: var(--oh-color-blue); - color: var(--oh-color-dark); + border-color: #1a4a7a; + color: #2a1a0a; } - .oathhammer .character-content input[name="name"], .oathhammer .npc-content input[name="name"] { height: 2.5rem; - font-family: var(--oh-font-secondary); - font-size: 1.2rem; + font-family: "BlueDragon", "Palatino Linotype", serif; + font-size: calc(0.82rem * 1.2); font-weight: bold; border: none; - border-bottom: 2px solid var(--oh-color-blue); + border-bottom: 2px solid #1a4a7a; background: transparent; } - .oathhammer .character-content fieldset, .oathhammer .npc-content fieldset { margin-bottom: 4px; border-radius: 4px; - border-color: var(--oh-color-olive); + border-color: #5a5a2a; } - .oathhammer .character-content legend, .oathhammer .npc-content legend { - font-family: var(--oh-font-secondary); - font-size: calc(var(--oh-font-size) * 1.2); + font-family: "BlueDragon", "Palatino Linotype", serif; + font-size: calc(0.82rem * 1.1); font-weight: bold; letter-spacing: 1px; - color: var(--oh-color-blue); + color: #1a4a7a; } - .oathhammer .character-content label, .oathhammer .npc-content label { - font-family: var(--oh-font-secondary); - font-size: var(--oh-font-size); - color: var(--oh-color-dark); + font-family: "BlueDragon", "Palatino Linotype", serif; + font-size: 0.82rem; + color: #2a1a0a; +} +.oathhammer .tab { + padding: 4px; +} +.oathhammer .actor-img { + height: 150px; + width: auto; + border: 2px solid #1a4a7a; + border-radius: 4px; + cursor: pointer; + -o-object-fit: cover; + object-fit: cover; } - -/* ======================== */ -/* CHARACTER MAIN SECTION */ -/* ======================== */ .oathhammer .character-main { display: flex; flex-direction: column; gap: 4px; } - .oathhammer .character-main .character-pc { display: flex; gap: 10px; flex: 1; } - .oathhammer .character-main .character-left { min-width: 180px; max-width: 180px; @@ -132,92 +121,64 @@ align-items: center; gap: 4px; } - .oathhammer .character-main .character-portrait { display: flex; justify-content: center; } - -.oathhammer .actor-img { - height: 150px; - width: auto; - border: 2px solid var(--oh-color-blue); - border-radius: 4px; - cursor: pointer; - object-fit: cover; -} - .oathhammer .character-main .character-resource { display: flex; align-items: center; gap: 4px; margin-bottom: 2px; } - -.oathhammer .character-main .resource-label { - min-width: 3.5rem; - font-family: var(--oh-font-secondary); - font-size: calc(var(--oh-font-size) * 0.9); -} - .oathhammer .character-main .character-resource input { min-width: 2.5rem; max-width: 2.5rem; text-align: center; } - +.oathhammer .character-main .resource-label { + min-width: 3.5rem; + font-family: "BlueDragon", "Palatino Linotype", serif; + font-size: calc(0.82rem * 0.9); +} .oathhammer .character-main .character-right { flex: 1; display: flex; flex-direction: column; gap: 4px; } - .oathhammer .character-main .character-name { display: flex; align-items: center; gap: 4px; } - .oathhammer .character-main .character-name input { flex: 1; } - -/* ======================== */ -/* ATTRIBUTES GRID */ -/* ======================== */ .oathhammer .attributes-grid { display: grid; grid-template-columns: repeat(6, 1fr); gap: 4px; } - .oathhammer .attribute-box { display: flex; flex-direction: column; align-items: center; gap: 2px; } - .oathhammer .attribute-box label { - font-family: var(--oh-font-secondary); - font-size: calc(var(--oh-font-size) * 0.85); + font-family: "BlueDragon", "Palatino Linotype", serif; + font-size: calc(0.82rem * 0.85); text-align: center; } - .oathhammer .attribute-box input { width: 2.5rem; text-align: center; - font-size: calc(var(--oh-font-size) * 1.1); + font-size: calc(0.82rem * 1.1); } - -/* ======================== */ -/* CURRENCY BAR */ -/* ======================== */ .oathhammer .currency-bar { margin-top: 4px; } - .oathhammer .currency-bar .currency-item { display: flex; flex-direction: column; @@ -225,177 +186,22 @@ gap: 2px; flex: 1; } - .oathhammer .currency-bar .currency-item input { width: 4rem; text-align: center; } - -/* ======================== */ -/* ITEM LISTS */ -/* ======================== */ -.oathhammer .item-list { - list-style: none; - margin: 0; - padding: 0; -} - -.oathhammer .item-entry { - display: flex; - align-items: center; - gap: 6px; - padding: 3px 4px; - border-bottom: 1px solid rgba(90, 90, 42, 0.2); -} - -.oathhammer .item-entry:hover { - background-color: rgba(26, 74, 122, 0.08); -} - -.oathhammer .item-entry .item-img { - height: 24px; - width: 24px; - border: 1px solid var(--oh-color-olive); - border-radius: 2px; - object-fit: cover; -} - -.oathhammer .item-entry .item-name { - flex: 1; - font-family: var(--oh-font-body); - font-size: var(--oh-font-size); -} - -.oathhammer .item-entry .item-detail { - font-size: calc(var(--oh-font-size) * 0.9); - color: var(--oh-color-olive); - min-width: 4rem; - text-align: center; -} - -.oathhammer .item-entry .item-type { - font-size: calc(var(--oh-font-size) * 0.85); - color: var(--oh-color-blue); - min-width: 6rem; -} - -.oathhammer .item-entry a { - opacity: 0.6; - transition: opacity 0.2s; -} - -.oathhammer .item-entry a:hover { - opacity: 1; - color: var(--oh-color-blue); -} - -.oathhammer .no-items { - color: var(--color-dark-5); - font-style: italic; - font-size: calc(var(--oh-font-size) * 0.9); - padding: 4px; -} - -.oathhammer .create-btn { - margin-left: 6px; - color: var(--oh-color-blue); - opacity: 0.7; - transition: opacity 0.2s; -} - -.oathhammer .create-btn:hover { - opacity: 1; -} - -/* ======================== */ -/* BIODATA */ -/* ======================== */ .oathhammer .biodata-col { flex: 1; display: flex; flex-direction: column; gap: 2px; } - -/* ======================== */ -/* ITEM SHEET COMMON */ -/* ======================== */ -.oathhammer .item-sheet-common { - overflow: auto; - font-family: var(--oh-font-primary); - font-size: var(--oh-font-size); - background-image: var(--oh-background-image); - background-repeat: no-repeat; - background-size: 100% 100%; -} - -.oathhammer .item-sheet-common .header { - display: flex; - align-items: center; - gap: 8px; - margin-bottom: 8px; - padding-bottom: 4px; - border-bottom: 2px solid var(--oh-color-blue); -} - -.oathhammer .item-sheet-common .item-img { - height: 52px; - width: 52px; - border: 2px solid var(--oh-color-olive); - border-radius: 4px; - cursor: pointer; - object-fit: cover; -} - -.oathhammer .item-sheet-common .form-group { - display: flex; - flex: 1; - flex-direction: row; - align-items: center; - gap: 4px; - margin-bottom: 2px; -} - -.oathhammer .item-sheet-common .form-group label { - font-family: var(--oh-font-secondary); - font-size: var(--oh-font-size); - min-width: 9rem; - max-width: 9rem; -} - -.oathhammer .item-sheet-common .form-group select, -.oathhammer .item-sheet-common .form-group input { - text-align: left; - min-width: 10rem; - max-width: 12rem; -} - -.oathhammer .item-sheet-common .align-top { - align-self: flex-start; - padding: 0.2rem; - min-width: 260px; -} - -.oathhammer .item-sheet-common .shift-right { - margin-left: 2rem; -} - -.oathhammer .item-sheet-common fieldset { - margin-top: 6px; - border-color: var(--oh-color-olive); - border-radius: 4px; -} - -.oathhammer .item-sheet-common legend { - font-family: var(--oh-font-secondary); - font-size: calc(var(--oh-font-size) * 1.1); +.oathhammer .defense-display { + min-width: 3rem; + max-width: 3rem; + text-align: center; font-weight: bold; - color: var(--oh-color-blue); } - -/* ======================== */ -/* NPC SHEET */ -/* ======================== */ .oathhammer .npc-main .npc-left { min-width: 160px; max-width: 160px; @@ -404,27 +210,137 @@ align-items: center; gap: 4px; } - .oathhammer .npc-main .npc-right { flex: 1; display: flex; flex-direction: column; gap: 4px; } - -/* ======================== */ -/* DEFENSE DISPLAY */ -/* ======================== */ -.oathhammer .defense-display { - min-width: 3rem; - max-width: 3rem; - text-align: center; - font-weight: bold; +.oathhammer .item-list { + list-style: none; + margin: 0; + padding: 0; } - -/* ======================== */ -/* TABS */ -/* ======================== */ -.oathhammer .tab { +.oathhammer .item-entry { + display: flex; + align-items: center; + gap: 6px; + padding: 3px 4px; + border-bottom: 1px solid rgba(90, 90, 42, 0.2); +} +.oathhammer .item-entry:hover { + background-color: rgba(26, 74, 122, 0.08); +} +.oathhammer .item-entry .item-img { + height: 24px; + width: 24px; + border: 1px solid #5a5a2a; + border-radius: 2px; + -o-object-fit: cover; + object-fit: cover; +} +.oathhammer .item-entry .item-name { + flex: 1; + font-family: "Calibri", "Segoe UI", sans-serif; + font-size: 0.82rem; +} +.oathhammer .item-entry .item-detail { + font-size: calc(0.82rem * 0.9); + color: #5a5a2a; + min-width: 4rem; + text-align: center; +} +.oathhammer .item-entry .item-type { + font-size: calc(0.82rem * 0.85); + color: #1a4a7a; + min-width: 6rem; +} +.oathhammer .item-entry a { + opacity: 0.6; + transition: opacity 0.2s; +} +.oathhammer .item-entry a:hover { + opacity: 1; +} +.oathhammer .item-entry a:hover { + color: #1a4a7a; +} +.oathhammer .no-items { + color: var(--color-dark-5); + font-style: italic; + font-size: calc(0.82rem * 0.9); padding: 4px; } +.oathhammer .create-btn { + margin-left: 6px; + color: #1a4a7a; + opacity: 0.6; + transition: opacity 0.2s; +} +.oathhammer .create-btn:hover { + opacity: 1; +} +.oathhammer .item-sheet-common { + overflow: auto; + font-family: "Sherwood", "Palatino Linotype", serif; + font-size: 0.82rem; + background-image: var(--oh-background-image); + background-repeat: no-repeat; + background-size: 100% 100%; +} +.oathhammer .item-sheet-common .header { + display: flex; + align-items: center; + gap: 8px; + margin-bottom: 8px; + padding-bottom: 4px; + border-bottom: 2px solid #1a4a7a; +} +.oathhammer .item-sheet-common .item-img { + height: 52px; + width: 52px; + border: 2px solid #5a5a2a; + border-radius: 4px; + cursor: pointer; + -o-object-fit: cover; + object-fit: cover; +} +.oathhammer .item-sheet-common .form-group { + display: flex; + flex: 1; + flex-direction: row; + align-items: center; + gap: 4px; + margin-bottom: 2px; +} +.oathhammer .item-sheet-common .form-group label { + font-family: "BlueDragon", "Palatino Linotype", serif; + font-size: 0.82rem; + min-width: 9rem; + max-width: 9rem; +} +.oathhammer .item-sheet-common .form-group select, +.oathhammer .item-sheet-common .form-group input { + text-align: left; + min-width: 10rem; + max-width: 12rem; +} +.oathhammer .item-sheet-common .align-top { + align-self: flex-start; + padding: 0.2rem; + min-width: 260px; +} +.oathhammer .item-sheet-common .shift-right { + margin-left: 2rem; +} +.oathhammer .item-sheet-common fieldset { + margin-top: 6px; + border-color: #5a5a2a; + border-radius: 4px; +} +.oathhammer .item-sheet-common legend { + font-family: "BlueDragon", "Palatino Linotype", serif; + font-size: calc(0.82rem * 1.1); + font-weight: bold; + color: #1a4a7a; +} diff --git a/gulpfile.js b/gulpfile.js new file mode 100644 index 0000000..f5f66e8 --- /dev/null +++ b/gulpfile.js @@ -0,0 +1,22 @@ +const gulp = require('gulp') +const less = require('gulp-less') +const postcss = require('gulp-postcss') +const autoprefixer = require('autoprefixer') + +const LESS_SRC = './less/fvtt-oath-hammer.less' +const CSS_DEST = './css' + +function buildCss() { + return gulp.src(LESS_SRC) + .pipe(less()) + .pipe(postcss([autoprefixer])) + .pipe(gulp.dest(CSS_DEST)) +} + +function watchCss() { + gulp.watch('./less/**/*.less', buildCss) +} + +gulp.task('css', buildCss) +gulp.task('watch', watchCss) +gulp.task('default', buildCss) diff --git a/lang/en.json b/lang/en.json index 08ec4c0..857ce6f 100644 --- a/lang/en.json +++ b/lang/en.json @@ -5,7 +5,6 @@ "NPC": "Oath Hammer NPC Sheet", "Weapon": "Oath Hammer Weapon Sheet", "Armor": "Oath Hammer Armor Sheet", - "Shield": "Oath Hammer Shield Sheet", "Ammunition": "Oath Hammer Ammunition Sheet", "Equipment": "Oath Hammer Equipment Sheet", "Spell": "Oath Hammer Spell Sheet", @@ -32,32 +31,23 @@ }, "Lineage": { "Dwarf": "Dwarf", + "Firbolg": "Firbolg", + "Halfling": "Halfling", + "HighElf": "High Elf", "Human": "Human", - "Elf": "Elf", - "HalfElf": "Half-Elf", - "Halfling": "Halfling" + "WoodElf": "Wood Elf" }, "Class": { - "Fighter": "Fighter", - "Ranger": "Ranger", - "Wizard": "Wizard", - "Cleric": "Cleric", - "Rogue": "Rogue", - "Paladin": "Paladin" - }, - "Oath": { - "Justice": "Oath of Justice", - "Courage": "Oath of Courage", - "Honor": "Oath of Honor", - "Mercy": "Oath of Mercy", - "Truth": "Oath of Truth", - "Valor": "Oath of Valor", - "Protection": "Oath of Protection", - "Vengeance": "Oath of Vengeance", - "Sacrifice": "Oath of Sacrifice", - "Faith": "Oath of Faith", - "Service": "Oath of Service", - "Brotherhood": "Oath of Brotherhood" + "Berserker": "Berserker", + "Champion": "Champion", + "Delver": "Delver", + "Knight": "Knight", + "Mage": "Mage", + "Priest": "Priest", + "Scout": "Scout", + "Soldier": "Soldier", + "Spellblade": "Spellblade", + "Troubadour": "Troubadour" }, "Tradition": { "Elemental": "Elemental", @@ -67,70 +57,38 @@ "Runic": "Runic", "Stygian": "Stygian" }, - "WeaponType": { - "Melee": "Melee", - "Ranged": "Ranged" - }, - "DamageType": { - "Slashing": "Slashing", - "Piercing": "Piercing", - "Bludgeoning": "Bludgeoning", - "Fire": "Fire", - "Cold": "Cold", - "Lightning": "Lightning", - "Acid": "Acid", - "Poison": "Poison", - "Necrotic": "Necrotic", - "Radiant": "Radiant" - }, "ArmorType": { "Light": "Light", "Medium": "Medium", "Heavy": "Heavy" }, - "Hands": { - "OneHanded": "One-Handed", - "TwoHanded": "Two-Handed" - }, - "Range": { - "Short": "Short", - "Medium": "Medium", - "Long": "Long" - }, "Currency": { "GP": "Gold Pieces", "SP": "Silver Pieces", "CP": "Copper Pieces" }, "AmmoType": { - "Arrow": "Arrow", - "Bolt": "Bolt", - "Stone": "Stone", - "Javelin": "Javelin", - "ThrowingKnife": "Throwing Knife" + "Standard": "Arrow / Bolt", + "Bodkin": "Bodkin (−1 armor)", + "Envenomed": "Envenomed (poison)", + "Incendiary": "Incendiary (flaming)" }, "EquipmentType": { "Potion": "Potion", "Container": "Container", - "Tool": "Tool", - "Consumable": "Consumable", - "Misc": "Miscellaneous", - "HealingSupply": "Healing Supply", - "Food": "Food", "Mount": "Mount", + "HealingSupply": "Healing Supply", + "Food": "Food & Drink", + "LightSource": "Light Source", + "Misc": "Miscellaneous", "Vehicle": "Vehicle", + "Animal": "Animal", "WarMachine": "War Machine" }, "MagicItemType": { - "Weapon": "Weapon", - "Armor": "Armor", - "Wondrous": "Wondrous Item", - "Potion": "Potion", - "Ring": "Ring", - "Staff": "Staff", - "Wand": "Wand", - "Scroll": "Scroll", - "Rod": "Rod" + "Focus": "Focus", + "Talisman": "Talisman", + "Trinket": "Trinket" }, "Rarity": { "Common": "Common", @@ -141,19 +99,39 @@ }, "AbilityType": { "ClassAbility": "Class Ability", - "LineageTrait": "Lineage Trait", - "Feat": "Feat" + "LineageTrait": "Lineage Trait" }, "Condition": { "Blinded": "Blinded", + "BlindedDesc": "Cannot see. -3 penalty to defense and melee attack rolls. Cannot perform ranged attacks or cast spells.", + "Confused": "Confused", + "ConfusedDesc": "Must make a DV2 Discipline check at the start of each turn or may not move or perform actions.", + "Dazed": "Dazed", + "DazedDesc": "Cannot perform Magic checks or move more than 10 ft. -1 penalty to attack and defense rolls.", "Deafened": "Deafened", - "Prone": "Prone", - "Stunned": "Stunned", + "DeafenedDesc": "Cannot hear. -3 penalty to Magic checks due to verbal components required for miracles and spells.", + "Demoralized": "Demoralized", + "DemoralizedDesc": "−1 penalty to Discipline checks. Cannot perform Leadership checks.", + "Diseased": "Diseased", + "DiseasedDesc": "Lose 1 rank in all attributes. Can be treated with a DV5 Heal check once per day.", + "Enfeebled": "Enfeebled", + "EnfeebledDesc": "-2 penalty to attack and defense rolls. Cannot move more than 10 ft or perform the Run action.", + "Fatigued": "Fatigued", + "FatiguedDesc": "-1 penalty to attack and defense rolls, and to Resilience checks. Removed by a full night's rest.", "Frightened": "Frightened", + "FrightenedDesc": "Cannot approach enemies. May attempt a DV2 Discipline check as an action to end this condition. Cannot perform Leadership checks.", + "Ignited": "Ignited", + "IgnitedDesc": "Suffers 1DD flaming damage at the end of each round, ignoring armor. Can remove condition as an action (ends turn prone).", + "Inspired": "Inspired", + "InspiredDesc": "+1 bonus to Discipline and Leadership checks.", + "Invisible": "Invisible", + "InvisibleDesc": "Cannot be seen by ordinary means. Cannot be targeted by magic or ranged attacks. -3 penalty to melee attacks against invisible targets.", "Poisoned": "Poisoned", + "PoisonedDesc": "Suffers 1DD poison damage (black die) at the end of each round, ignoring armor.", "Restrained": "Restrained", - "Wounded": "Wounded", - "Other": "Other" + "RestrainedDesc": "Cannot move.", + "Stunned": "Stunned", + "StunnedDesc": "Cannot speak, move, or perform actions. -3 penalty to defense rolls." }, "Label": { "Character": "Character", @@ -194,13 +172,15 @@ "Effect": "Effect", "Components": "Components", "Charges": "Charges", - "Benefit": "Benefit", - "Violation": "Violation", "NoWeapons": "No weapons equipped.", "NoArmor": "No armor or shields.", "NoSpells": "No spells known.", "NoMiracles": "No miracles known.", - "NoEquipment": "No equipment." + "NoEquipment": "No equipment.", + "Enchantment": "Enchantment", + "Tenet": "Tenet", + "Boon": "Boon", + "Bane": "Bane" }, "NewItem": { "Weapon": "New Weapon", @@ -298,89 +278,89 @@ } }, "Weapon": { - "weaponType": { - "label": "Weapon Type" + "proficiencyGroup": "Proficiency Group", + "usesMight": "Uses Might", + "damageMod": "Damage Modifier", + "ap": "Armor Penetration (AP)", + "reach": "Reach (ft)", + "shortRange": "Short Range (ft)", + "longRange": "Long Range (ft)", + "traits": "Traits", + "slots": "Item Slots", + "rarity": "Rarity", + "equipped": "Equipped", + "cost": "Cost", + "currency": "Currency", + "description": "Description", + "isMagic": { + "label": "Magic Item" }, - "damageFormula": { - "label": "Damage" + "magicQuality": { + "label": "Quality" }, - "damageType": { - "label": "Damage Type" + "isCursed": { + "label": "Cursed" }, - "attributeBonus": { - "label": "Attribute Bonus" + "magicEffect": { + "label": "Enchantment" }, - "range": { - "label": "Range" - }, - "hands": { - "label": "Hands" - }, - "properties": { - "label": "Properties" - }, - "equipped": { - "label": "Equipped" - }, - "encumbrance": { - "label": "Enc." - }, - "cost": { - "label": "Cost" - }, - "currency": { - "label": "Currency" + "classRestriction": { + "label": "Restriction" } }, "Armor": { "armorType": { "label": "Armor Type" }, - "armorRating": { - "label": "Armor Rating" + "armorValue": { + "label": "Armor Value (AV)" }, - "movementPenalty": { - "label": "Movement Penalty" + "penalty": { + "label": "Penalty" + }, + "slots": { + "label": "Slots" + }, + "traits": { + "label": "Traits" + }, + "rarity": { + "label": "Rarity" }, "equipped": { "label": "Equipped" }, - "encumbrance": { - "label": "Enc." - }, "cost": { "label": "Cost" }, "currency": { "label": "Currency" - } - }, - "Shield": { - "shieldBonus": { - "label": "Shield Bonus" }, - "equipped": { - "label": "Equipped" + "isMagic": { + "label": "Magic Item" }, - "encumbrance": { - "label": "Enc." + "magicQuality": { + "label": "Quality" }, - "cost": { - "label": "Cost" + "isCursed": { + "label": "Cursed" }, - "currency": { - "label": "Currency" + "magicEffect": { + "label": "Enchantment" + }, + "classRestriction": { + "label": "Restriction" } }, "Ammunition": { "ammoType": { - "label": "Ammo Type" + "label": "Ammunition Type" }, "quantity": { "label": "Quantity" }, - "properties": { - "label": "Properties" + "rarity": { + "label": "Rarity" }, "cost": { "label": "Cost" @@ -391,13 +371,19 @@ }, "Equipment": { "itemType": { - "label": "Type" + "label": "Category" }, "quantity": { "label": "Quantity" }, - "weight": { - "label": "Weight" + "slots": { + "label": "Slots" + }, + "rarity": { + "label": "Rarity" + }, + "lightRadius": { + "label": "Light Radius (ft)" }, "cost": { "label": "Cost" @@ -410,11 +396,14 @@ "tradition": { "label": "Tradition" }, - "level": { - "label": "Level" + "difficultyValue": { + "label": "Difficulty Value (DV)" }, - "castingTime": { - "label": "Casting Time" + "isRitual": { + "label": "Ritual" + }, + "isMagicMissile": { + "label": "Magic Missile" }, "range": { "label": "Range" @@ -422,37 +411,28 @@ "duration": { "label": "Duration" }, - "arcaneStress": { - "label": "Arcane Stress" + "spellSave": { + "label": "Spell Save" }, - "components": { - "label": "Components", - "verbal": { - "label": "Verbal" - }, - "somatic": { - "label": "Somatic" - }, - "material": { - "label": "Material" - } + "element": { + "label": "Element" }, - "materialComponent": { - "label": "Material Component" + "runeType": { + "label": "Rune Type" }, - "savingThrow": { - "label": "Saving Throw" - }, - "enhancement": { - "label": "Enhancement" + "isExalted": { + "label": "Exalted" } }, "Miracle": { - "piety": { - "label": "Piety Cost" + "divineTradition": { + "label": "Divine Tradition" }, - "castingTime": { - "label": "Casting Time" + "difficultyValue": { + "label": "Difficulty Value (DV)" + }, + "isRitual": { + "label": "Ritual" }, "range": { "label": "Range" @@ -460,89 +440,140 @@ "duration": { "label": "Duration" }, - "components": { - "label": "Components", - "verbal": { - "label": "Verbal" - }, - "somatic": { - "label": "Somatic" - }, - "material": { - "label": "Material" - } - }, - "materialComponent": { - "label": "Material Component" - }, - "savingThrow": { - "label": "Saving Throw" + "spellSave": { + "label": "Spell Save" } }, "MagicItem": { "itemType": { - "label": "Item Type" + "label": "Type" }, - "rarity": { - "label": "Rarity" + "quality": { + "label": "Quality" }, - "attunement": { - "label": "Requires Attunement" + "isCursed": { + "label": "Cursed" }, - "charges": { - "label": "Charges", - "value": { - "label": "Current" - }, - "max": { - "label": "Maximum" - } + "isBonded": { + "label": "Bonded" }, - "recharge": { - "label": "Recharge" + "classRestriction": { + "label": "Restriction" + }, + "usagePeriod": { + "label": "Usage Period" + }, + "maxUses": { + "label": "Max Uses" + }, + "slots": { + "label": "Slots" }, "equipped": { "label": "Equipped" - }, - "cost": { - "label": "Cost" - }, - "currency": { - "label": "Currency" } }, "Ability": { "abilityType": { - "label": "Ability Type" + "label": "Type" }, "source": { - "label": "Source" + "label": "Source (Class / Lineage)" }, - "prerequisite": { - "label": "Prerequisite" + "usagePeriod": { + "label": "Usage Period" }, - "passiveBonus": { - "label": "Passive Bonus" + "maxUses": { + "label": "Max Uses" } }, - "Oath": { + "WeaponGroup": { + "Common": "Common", + "Dueling": "Dueling", + "Heavy": "Heavy", + "Polearms": "Polearms", + "Bows": "Bows", + "Throwing": "Throwing" + }, + "WeaponTrait": { + "Block": "Block", + "Brutal": "Brutal", + "Clumsy": "Clumsy", + "Couched": "Couched", + "Deadly": "Deadly", + "Fast": "Fast", + "Flaming": "Flaming", + "Nimble": "Nimble", + "Parry": "Parry", + "Reload": "Reload", + "Repel": "Repel", + "Stunning": "Stunning", + "Sweep": "Sweep", + "TwoHanded": "Two-handed", + "Versatile": "Versatile" + }, + "DivineTradition": { + "Druidic": "Druidic", + "Profane": "Profane", + "Sanctified": "Sanctified" + }, + "Element": { + "Air": "Air", + "Earth": "Earth", + "Fire": "Fire", + "Water": "Water", + "Varies": "Varies" + }, + "RuneType": { + "Armor": "Armor", + "Talisman": "Talisman", + "Warding": "Warding", + "Weapon": "Weapon" + }, + "ArmorTrait": { + "Clanging": "Clanging", + "Reinforced": "Reinforced" + }, + "UsagePeriod": { + "None": "Passive (always on)", + "Encounter": "Per Encounter", + "Day": "Per Day" + }, + "MagicQuality": { + "Lesser": "Lesser", + "Greater": "Greater", + "Legendary": "Legendary" + }, + "OathType": { + "Compassion": "Oath of Compassion", + "Courage": "Oath of Courage", + "Diligence": "Oath of Diligence", + "Faith": "Oath of Faith", + "Humility": "Oath of Humility", + "Justice": "Oath of Justice", + "Loyalty": "Oath of Loyalty", + "Peace": "Oath of Peace", + "Perseverance": "Oath of Perseverance", + "Purity": "Oath of Purity", + "Truth": "Oath of Truth", + "Wisdom": "Oath of Wisdom" + }, + "OathFields": { "oathType": { - "label": "Oath Type" + "label": "Oath" + }, + "tenet": { + "label": "Tenet" + }, + "boon": { + "label": "Boon" + }, + "bane": { + "label": "Bane" }, "violated": { "label": "Violated" } - }, - "Condition": { - "conditionType": { - "label": "Condition Type" - }, - "duration": { - "label": "Duration" - }, - "source": { - "label": "Source" - } } } } diff --git a/less/actor-sheet.less b/less/actor-sheet.less new file mode 100644 index 0000000..89c9a52 --- /dev/null +++ b/less/actor-sheet.less @@ -0,0 +1,135 @@ +// ============================================================ +// ACTOR SHEET — Character layout, attributes, resources, etc. +// ============================================================ + +.oathhammer { + + .actor-img { + height: @portrait-height; + width: auto; + border: 2px solid @color-blue; + border-radius: 4px; + cursor: pointer; + object-fit: cover; + } + + .character-main { + display: flex; + flex-direction: column; + gap: 4px; + + .character-pc { + display: flex; + gap: 10px; + flex: 1; + } + + .character-left { + min-width: @left-panel-width; + max-width: @left-panel-width; + display: flex; + flex-direction: column; + align-items: center; + gap: 4px; + } + + .character-portrait { + display: flex; + justify-content: center; + } + + .character-resource { + display: flex; + align-items: center; + gap: 4px; + margin-bottom: 2px; + + input { + min-width: 2.5rem; + max-width: 2.5rem; + text-align: center; + } + } + + .resource-label { + min-width: 3.5rem; + font-family: @font-secondary; + font-size: @font-size-xs; + } + + .character-right { + flex: 1; + display: flex; + flex-direction: column; + gap: 4px; + } + + .character-name { + display: flex; + align-items: center; + gap: 4px; + + input { flex: 1; } + } + } + + // Attributes grid + .attributes-grid { + display: grid; + grid-template-columns: repeat(6, 1fr); + gap: 4px; + } + + .attribute-box { + display: flex; + flex-direction: column; + align-items: center; + gap: 2px; + + label { + font-family: @font-secondary; + font-size: @font-size-sm; + text-align: center; + } + + input { + width: 2.5rem; + text-align: center; + font-size: @font-size-lg; + } + } + + // Currency bar + .currency-bar { + margin-top: 4px; + + .currency-item { + display: flex; + flex-direction: column; + align-items: center; + gap: 2px; + flex: 1; + + input { + width: 4rem; + text-align: center; + } + } + } + + // Biodata + .biodata-col { + flex: 1; + display: flex; + flex-direction: column; + gap: 2px; + } + + // Defense display + .defense-display { + min-width: 3rem; + max-width: 3rem; + text-align: center; + font-weight: bold; + } +} diff --git a/less/base.less b/less/base.less new file mode 100644 index 0000000..4ac0c63 --- /dev/null +++ b/less/base.less @@ -0,0 +1,100 @@ +// ============================================================ +// BASE — Fonts, :root CSS vars, global form elements +// ============================================================ + +@font-face { + font-family: "Sherwood"; + src: url("../assets/fonts/Sherwood.otf") format("opentype"), + url("../assets/fonts/SHERWOOD.TTF") format("truetype"), + url("../assets/fonts/Sherwood.woff") format("woff"); +} +@font-face { + font-family: "BlueDragon"; + src: url("../assets/fonts/Blue Dragon.ttf") format("truetype"); +} + +:root { + --oh-font-primary: @font-primary; + --oh-font-secondary: @font-secondary; + --oh-font-body: @font-body; + --oh-font-size: @font-size-base; + --oh-color-blue: @color-blue; + --oh-color-olive: @color-olive; + --oh-color-gold: @color-gold; + --oh-color-dark: @color-dark; + --oh-color-paper: @color-paper; + --oh-background-image: url("../assets/ui/oath_hammer_paper.webp"); + --oh-logo: url("../assets/logos/official_logo_01.webp"); +} + +// Global dialog styling +.application.dialog.oathhammer { + font-family: @font-primary; + font-size: @font-size-base; + .sheet-background(); +} + +// Shared actor content base +.oathhammer .character-content, +.oathhammer .npc-content { + font-family: @font-primary; + font-size: @font-size-base; + color: var(--color-dark-1); + .sheet-background(); + overflow: auto; + + nav.tabs [data-tab] { + color: @color-olive; + &.active { color: @color-blue; } + } + + input:disabled, + select:disabled { + background-color: @color-disabled-bg; + border-color: transparent; + color: var(--color-dark-3); + } + + input, + select { + height: 1.5rem; + background-color: @color-input-bg; + border-color: @color-blue; + color: @color-dark; + } + + input[name="name"] { + height: 2.5rem; + font-family: @font-secondary; + font-size: @font-size-xl; + font-weight: bold; + border: none; + border-bottom: 2px solid @color-blue; + background: transparent; + } + + fieldset { + margin-bottom: 4px; + border-radius: 4px; + border-color: @color-olive; + } + + legend { + font-family: @font-secondary; + font-size: @font-size-lg; + font-weight: bold; + letter-spacing: 1px; + color: @color-blue; + } + + label { + font-family: @font-secondary; + font-size: @font-size-base; + color: @color-dark; + } +} + +// Shared tab padding +.oathhammer .tab { + padding: 4px; +} diff --git a/less/fvtt-oath-hammer.less b/less/fvtt-oath-hammer.less new file mode 100644 index 0000000..1d4f2f9 --- /dev/null +++ b/less/fvtt-oath-hammer.less @@ -0,0 +1,10 @@ +// ============================================================ +// OATH HAMMER — Main LESS entry point +// ============================================================ + +@import "variables"; +@import "base"; +@import "actor-sheet"; +@import "npc-sheet"; +@import "item-list"; +@import "item-sheets"; diff --git a/less/item-list.less b/less/item-list.less new file mode 100644 index 0000000..648b680 --- /dev/null +++ b/less/item-list.less @@ -0,0 +1,67 @@ +// ============================================================ +// ITEM LIST — Used in actor sheet tabs (weapons, spells, etc.) +// ============================================================ + +.oathhammer { + + .item-list { + list-style: none; + margin: 0; + padding: 0; + } + + .item-entry { + display: flex; + align-items: center; + gap: 6px; + padding: 3px 4px; + border-bottom: 1px solid @color-olive-faint; + + &:hover { background-color: @color-blue-hover; } + + .item-img { + height: @item-img-size; + width: @item-img-size; + border: 1px solid @color-olive; + border-radius: 2px; + object-fit: cover; + } + + .item-name { + flex: 1; + font-family: @font-body; + font-size: @font-size-base; + } + + .item-detail { + font-size: @font-size-xs; + color: @color-olive; + min-width: 4rem; + text-align: center; + } + + .item-type { + font-size: @font-size-sm; + color: @color-blue; + min-width: 6rem; + } + + a { + .transition-opacity(); + &:hover { color: @color-blue; } + } + } + + .no-items { + color: var(--color-dark-5); + font-style: italic; + font-size: @font-size-xs; + padding: 4px; + } + + .create-btn { + margin-left: 6px; + color: @color-blue; + .transition-opacity(); + } +} diff --git a/less/item-sheets.less b/less/item-sheets.less new file mode 100644 index 0000000..3c0d543 --- /dev/null +++ b/less/item-sheets.less @@ -0,0 +1,74 @@ +// ============================================================ +// ITEM SHEETS — Shared item sheet layout (all item types) +// ============================================================ + +.oathhammer .item-sheet-common { + overflow: auto; + font-family: @font-primary; + font-size: @font-size-base; + .sheet-background(); + + .header { + display: flex; + align-items: center; + gap: 8px; + margin-bottom: 8px; + padding-bottom: 4px; + border-bottom: 2px solid @color-blue; + } + + .item-img { + height: @item-sheet-img; + width: @item-sheet-img; + border: 2px solid @color-olive; + border-radius: 4px; + cursor: pointer; + object-fit: cover; + } + + .form-group { + display: flex; + flex: 1; + flex-direction: row; + align-items: center; + gap: 4px; + margin-bottom: 2px; + + label { + font-family: @font-secondary; + font-size: @font-size-base; + min-width: @label-min-width; + max-width: @label-min-width; + } + + select, + input { + text-align: left; + min-width: @input-min-width; + max-width: @input-max-width; + } + } + + .align-top { + align-self: flex-start; + padding: 0.2rem; + min-width: 260px; + } + + .shift-right { + margin-left: 2rem; + } + + fieldset { + margin-top: 6px; + border-color: @color-olive; + border-radius: 4px; + } + + legend { + font-family: @font-secondary; + font-size: @font-size-lg; + font-weight: bold; + color: @color-blue; + } +} diff --git a/less/npc-sheet.less b/less/npc-sheet.less new file mode 100644 index 0000000..460e1e0 --- /dev/null +++ b/less/npc-sheet.less @@ -0,0 +1,22 @@ +// ============================================================ +// NPC SHEET — NPC-specific layout +// ============================================================ + +.oathhammer .npc-main { + + .npc-left { + min-width: @npc-left-width; + max-width: @npc-left-width; + display: flex; + flex-direction: column; + align-items: center; + gap: 4px; + } + + .npc-right { + flex: 1; + display: flex; + flex-direction: column; + gap: 4px; + } +} diff --git a/less/variables.less b/less/variables.less new file mode 100644 index 0000000..8232027 --- /dev/null +++ b/less/variables.less @@ -0,0 +1,51 @@ +// ============================================================ +// VARIABLES — Oath Hammer LESS +// ============================================================ + +// Fonts +@font-primary: "Sherwood", "Palatino Linotype", serif; +@font-secondary: "BlueDragon", "Palatino Linotype", serif; +@font-body: "Calibri", "Segoe UI", sans-serif; +@font-size-base: 0.82rem; + +// Colors +@color-blue: #1a4a7a; +@color-olive: #5a5a2a; +@color-gold: #c8a84b; +@color-dark: #2a1a0a; +@color-paper: #f5ead0; + +// Derived +@color-olive-faint: rgba(90, 90, 42, 0.2); +@color-blue-hover: rgba(26, 74, 122, 0.08); +@color-input-bg: rgba(255, 255, 255, 0.3); +@color-disabled-bg: rgba(0, 0, 0, 0.08); + +// Layout +@portrait-height: 150px; +@left-panel-width: 180px; +@npc-left-width: 160px; +@item-img-size: 24px; +@item-sheet-img: 52px; +@label-min-width: 9rem; +@input-min-width: 10rem; +@input-max-width: 12rem; + +// Fonts sizes +@font-size-sm: calc(@font-size-base * 0.85); +@font-size-xs: calc(@font-size-base * 0.9); +@font-size-lg: calc(@font-size-base * 1.1); +@font-size-xl: calc(@font-size-base * 1.2); + +// Mixins +.sheet-background() { + background-image: var(--oh-background-image); + background-repeat: no-repeat; + background-size: 100% 100%; +} + +.transition-opacity() { + opacity: 0.6; + transition: opacity 0.2s; + &:hover { opacity: 1; } +} diff --git a/module/applications/_module.mjs b/module/applications/_module.mjs index 6308201..b3d44c4 100644 --- a/module/applications/_module.mjs +++ b/module/applications/_module.mjs @@ -2,7 +2,6 @@ export { default as OathHammerCharacterSheet } from "./sheets/character-sheet.mj export { default as OathHammerNPCSheet } from "./sheets/npc-sheet.mjs" export { default as OathHammerWeaponSheet } from "./sheets/weapon-sheet.mjs" export { default as OathHammerArmorSheet } from "./sheets/armor-sheet.mjs" -export { default as OathHammerShieldSheet } from "./sheets/shield-sheet.mjs" export { default as OathHammerAmmunitionSheet } from "./sheets/ammunition-sheet.mjs" export { default as OathHammerEquipmentSheet } from "./sheets/equipment-sheet.mjs" export { default as OathHammerSpellSheet } from "./sheets/spell-sheet.mjs" @@ -10,4 +9,3 @@ export { default as OathHammerMiracleSheet } from "./sheets/miracle-sheet.mjs" export { default as OathHammerMagicItemSheet } from "./sheets/magic-item-sheet.mjs" export { default as OathHammerAbilitySheet } from "./sheets/ability-sheet.mjs" export { default as OathHammerOathSheet } from "./sheets/oath-sheet.mjs" -export { default as OathHammerConditionSheet } from "./sheets/condition-sheet.mjs" diff --git a/module/applications/sheets/character-sheet.mjs b/module/applications/sheets/character-sheet.mjs index 813ca98..663bcb1 100644 --- a/module/applications/sheets/character-sheet.mjs +++ b/module/applications/sheets/character-sheet.mjs @@ -86,7 +86,6 @@ export default class OathHammerCharacterSheet extends OathHammerActorSheet { context.tab = context.tabs.combat context.weapons = doc.itemTypes.weapon context.armors = doc.itemTypes.armor - context.shields = doc.itemTypes.shield context.ammunition = doc.itemTypes.ammunition break case "magic": @@ -98,7 +97,6 @@ export default class OathHammerCharacterSheet extends OathHammerActorSheet { context.tab = context.tabs.equipment context.equipment = doc.itemTypes.equipment context.magicItems = doc.itemTypes["magic-item"] - context.conditions = doc.itemTypes.condition break case "notes": context.tab = context.tabs.notes diff --git a/module/applications/sheets/condition-sheet.mjs b/module/applications/sheets/condition-sheet.mjs deleted file mode 100644 index 2a296f0..0000000 --- a/module/applications/sheets/condition-sheet.mjs +++ /dev/null @@ -1,27 +0,0 @@ -import OathHammerItemSheet from "./base-item-sheet.mjs" - -export default class OathHammerConditionSheet extends OathHammerItemSheet { - /** @override */ - static DEFAULT_OPTIONS = { - classes: ["condition"], - position: { - width: 620, - }, - window: { - contentClasses: ["condition-content"], - }, - } - - /** @override */ - static PARTS = { - main: { - template: "systems/fvtt-oath-hammer/templates/item/condition-sheet.hbs", - }, - } - - /** @override */ - async _prepareContext() { - const context = await super._prepareContext() - return context - } -} diff --git a/module/applications/sheets/oath-sheet.mjs b/module/applications/sheets/oath-sheet.mjs index 38776a0..c2116cc 100644 --- a/module/applications/sheets/oath-sheet.mjs +++ b/module/applications/sheets/oath-sheet.mjs @@ -22,8 +22,9 @@ export default class OathHammerOathSheet extends OathHammerItemSheet { /** @override */ async _prepareContext() { const context = await super._prepareContext() - context.enrichedBenefit = await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.document.system.benefit, { async: true }) - context.enrichedViolation = await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.document.system.violation, { async: true }) + context.enrichedTenet = await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.document.system.tenet, { async: true }) + context.enrichedBoon = await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.document.system.boon, { async: true }) + context.enrichedBane = await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.document.system.bane, { async: true }) return context } } diff --git a/module/applications/sheets/shield-sheet.mjs b/module/applications/sheets/shield-sheet.mjs deleted file mode 100644 index 92b1279..0000000 --- a/module/applications/sheets/shield-sheet.mjs +++ /dev/null @@ -1,27 +0,0 @@ -import OathHammerItemSheet from "./base-item-sheet.mjs" - -export default class OathHammerShieldSheet extends OathHammerItemSheet { - /** @override */ - static DEFAULT_OPTIONS = { - classes: ["shield"], - position: { - width: 620, - }, - window: { - contentClasses: ["shield-content"], - }, - } - - /** @override */ - static PARTS = { - main: { - template: "systems/fvtt-oath-hammer/templates/item/shield-sheet.hbs", - }, - } - - /** @override */ - async _prepareContext() { - const context = await super._prepareContext() - return context - } -} diff --git a/module/config/system.mjs b/module/config/system.mjs index 1817e8e..1b2da71 100644 --- a/module/config/system.mjs +++ b/module/config/system.mjs @@ -10,79 +10,102 @@ export const ATTRIBUTES = { } export const LINEAGE_CHOICES = { - dwarf: { id: "dwarf", label: "OATHHAMMER.Lineage.Dwarf" }, - human: { id: "human", label: "OATHHAMMER.Lineage.Human" }, - elf: { id: "elf", label: "OATHHAMMER.Lineage.Elf" }, - halfelf: { id: "halfelf", label: "OATHHAMMER.Lineage.HalfElf" }, - halfling: { id: "halfling", label: "OATHHAMMER.Lineage.Halfling" } + dwarf: { id: "dwarf", label: "OATHHAMMER.Lineage.Dwarf" }, + firbolg: { id: "firbolg", label: "OATHHAMMER.Lineage.Firbolg" }, + halfling: { id: "halfling", label: "OATHHAMMER.Lineage.Halfling" }, + "high-elf": { id: "high-elf", label: "OATHHAMMER.Lineage.HighElf" }, + human: { id: "human", label: "OATHHAMMER.Lineage.Human" }, + "wood-elf": { id: "wood-elf", label: "OATHHAMMER.Lineage.WoodElf" } } export const CLASS_CHOICES = { - fighter: { id: "fighter", label: "OATHHAMMER.Class.Fighter" }, - ranger: { id: "ranger", label: "OATHHAMMER.Class.Ranger" }, - wizard: { id: "wizard", label: "OATHHAMMER.Class.Wizard" }, - cleric: { id: "cleric", label: "OATHHAMMER.Class.Cleric" }, - rogue: { id: "rogue", label: "OATHHAMMER.Class.Rogue" }, - paladin: { id: "paladin", label: "OATHHAMMER.Class.Paladin" } + berserker: { id: "berserker", label: "OATHHAMMER.Class.Berserker" }, + champion: { id: "champion", label: "OATHHAMMER.Class.Champion" }, + delver: { id: "delver", label: "OATHHAMMER.Class.Delver" }, + knight: { id: "knight", label: "OATHHAMMER.Class.Knight" }, + mage: { id: "mage", label: "OATHHAMMER.Class.Mage" }, + priest: { id: "priest", label: "OATHHAMMER.Class.Priest" }, + scout: { id: "scout", label: "OATHHAMMER.Class.Scout" }, + soldier: { id: "soldier", label: "OATHHAMMER.Class.Soldier" }, + spellblade: { id: "spellblade", label: "OATHHAMMER.Class.Spellblade" }, + troubadour: { id: "troubadour", label: "OATHHAMMER.Class.Troubadour" } } export const OATH_TYPES = { - "oath-of-justice": { id: "oath-of-justice", label: "OATHHAMMER.Oath.Justice" }, - "oath-of-courage": { id: "oath-of-courage", label: "OATHHAMMER.Oath.Courage" }, - "oath-of-honor": { id: "oath-of-honor", label: "OATHHAMMER.Oath.Honor" }, - "oath-of-mercy": { id: "oath-of-mercy", label: "OATHHAMMER.Oath.Mercy" }, - "oath-of-truth": { id: "oath-of-truth", label: "OATHHAMMER.Oath.Truth" }, - "oath-of-valor": { id: "oath-of-valor", label: "OATHHAMMER.Oath.Valor" }, - "oath-of-protection": { id: "oath-of-protection", label: "OATHHAMMER.Oath.Protection" }, - "oath-of-vengeance": { id: "oath-of-vengeance", label: "OATHHAMMER.Oath.Vengeance" }, - "oath-of-sacrifice": { id: "oath-of-sacrifice", label: "OATHHAMMER.Oath.Sacrifice" }, - "oath-of-faith": { id: "oath-of-faith", label: "OATHHAMMER.Oath.Faith" }, - "oath-of-service": { id: "oath-of-service", label: "OATHHAMMER.Oath.Service" }, - "oath-of-brotherhood": { id: "oath-of-brotherhood", label: "OATHHAMMER.Oath.Brotherhood" } + "oath-of-compassion": { id: "oath-of-compassion", label: "OATHHAMMER.OathType.Compassion" }, + "oath-of-courage": { id: "oath-of-courage", label: "OATHHAMMER.OathType.Courage" }, + "oath-of-diligence": { id: "oath-of-diligence", label: "OATHHAMMER.OathType.Diligence" }, + "oath-of-faith": { id: "oath-of-faith", label: "OATHHAMMER.OathType.Faith" }, + "oath-of-humility": { id: "oath-of-humility", label: "OATHHAMMER.OathType.Humility" }, + "oath-of-justice": { id: "oath-of-justice", label: "OATHHAMMER.OathType.Justice" }, + "oath-of-loyalty": { id: "oath-of-loyalty", label: "OATHHAMMER.OathType.Loyalty" }, + "oath-of-peace": { id: "oath-of-peace", label: "OATHHAMMER.OathType.Peace" }, + "oath-of-perseverance": { id: "oath-of-perseverance", label: "OATHHAMMER.OathType.Perseverance" }, + "oath-of-purity": { id: "oath-of-purity", label: "OATHHAMMER.OathType.Purity" }, + "oath-of-truth": { id: "oath-of-truth", label: "OATHHAMMER.OathType.Truth" }, + "oath-of-wisdom": { id: "oath-of-wisdom", label: "OATHHAMMER.OathType.Wisdom" } } export const SORCEROUS_TRADITIONS = { - elemental: { id: "elemental", label: "OATHHAMMER.Tradition.Elemental" }, + elemental: { id: "elemental", label: "OATHHAMMER.Tradition.Elemental" }, illusionist: { id: "illusionist", label: "OATHHAMMER.Tradition.Illusionist" }, - imperial: { id: "imperial", label: "OATHHAMMER.Tradition.Imperial" }, - infernal: { id: "infernal", label: "OATHHAMMER.Tradition.Infernal" }, - runic: { id: "runic", label: "OATHHAMMER.Tradition.Runic" }, - stygian: { id: "stygian", label: "OATHHAMMER.Tradition.Stygian" } + imperial: { id: "imperial", label: "OATHHAMMER.Tradition.Imperial" }, + infernal: { id: "infernal", label: "OATHHAMMER.Tradition.Infernal" }, + runic: { id: "runic", label: "OATHHAMMER.Tradition.Runic" }, + stygian: { id: "stygian", label: "OATHHAMMER.Tradition.Stygian" } } -export const WEAPON_TYPE_CHOICES = { - melee: "OATHHAMMER.WeaponType.Melee", - ranged: "OATHHAMMER.WeaponType.Ranged" +// Three divine traditions for miracles (p.130) +export const DIVINE_TRADITIONS = { + druidic: "OATHHAMMER.DivineTradition.Druidic", + profane: "OATHHAMMER.DivineTradition.Profane", + sanctified: "OATHHAMMER.DivineTradition.Sanctified" } -export const DAMAGE_TYPE_CHOICES = { - slashing: "OATHHAMMER.DamageType.Slashing", - piercing: "OATHHAMMER.DamageType.Piercing", - bludgeoning: "OATHHAMMER.DamageType.Bludgeoning", - fire: "OATHHAMMER.DamageType.Fire", - cold: "OATHHAMMER.DamageType.Cold", - lightning: "OATHHAMMER.DamageType.Lightning", - acid: "OATHHAMMER.DamageType.Acid", - poison: "OATHHAMMER.DamageType.Poison", - necrotic: "OATHHAMMER.DamageType.Necrotic", - radiant: "OATHHAMMER.DamageType.Radiant" +// Elemental sub-types for Elemental tradition spells (p.103) +export const ELEMENTAL_CHOICES = { + air: "OATHHAMMER.Element.Air", + earth: "OATHHAMMER.Element.Earth", + fire: "OATHHAMMER.Element.Fire", + water: "OATHHAMMER.Element.Water", + varies: "OATHHAMMER.Element.Varies" } -export const ATTRIBUTE_BONUS_CHOICES = { - might: "OATHHAMMER.Attribute.Might", - agility: "OATHHAMMER.Attribute.Agility", - none: "OATHHAMMER.Label.None" +// Rune types for Runic tradition spells (p.120) +export const RUNE_TYPE_CHOICES = { + armor: "OATHHAMMER.RuneType.Armor", + talisman: "OATHHAMMER.RuneType.Talisman", + warding: "OATHHAMMER.RuneType.Warding", + weapon: "OATHHAMMER.RuneType.Weapon" } -export const RANGE_CHOICES = { - short: "OATHHAMMER.Range.Short", - medium: "OATHHAMMER.Range.Medium", - long: "OATHHAMMER.Range.Long" +// Weapon proficiency groups (from book pp.82-84) +export const WEAPON_PROFICIENCY_GROUPS = { + common: "OATHHAMMER.WeaponGroup.Common", + dueling: "OATHHAMMER.WeaponGroup.Dueling", + heavy: "OATHHAMMER.WeaponGroup.Heavy", + polearms: "OATHHAMMER.WeaponGroup.Polearms", + bows: "OATHHAMMER.WeaponGroup.Bows", + throwing: "OATHHAMMER.WeaponGroup.Throwing" } -export const HANDS_CHOICES = { - "one-handed": "OATHHAMMER.Hands.OneHanded", - "two-handed": "OATHHAMMER.Hands.TwoHanded" +// Weapon traits (from book p.82) +export const WEAPON_TRAITS = { + block: "OATHHAMMER.WeaponTrait.Block", + brutal: "OATHHAMMER.WeaponTrait.Brutal", + clumsy: "OATHHAMMER.WeaponTrait.Clumsy", + couched: "OATHHAMMER.WeaponTrait.Couched", + deadly: "OATHHAMMER.WeaponTrait.Deadly", + fast: "OATHHAMMER.WeaponTrait.Fast", + flaming: "OATHHAMMER.WeaponTrait.Flaming", + nimble: "OATHHAMMER.WeaponTrait.Nimble", + parry: "OATHHAMMER.WeaponTrait.Parry", + reload: "OATHHAMMER.WeaponTrait.Reload", + repel: "OATHHAMMER.WeaponTrait.Repel", + stunning: "OATHHAMMER.WeaponTrait.Stunning", + sweep: "OATHHAMMER.WeaponTrait.Sweep", + "two-handed": "OATHHAMMER.WeaponTrait.TwoHanded", + versatile: "OATHHAMMER.WeaponTrait.Versatile" } export const CURRENCY_CHOICES = { @@ -92,42 +115,52 @@ export const CURRENCY_CHOICES = { } export const ARMOR_TYPE_CHOICES = { - light: "OATHHAMMER.ArmorType.Light", + light: "OATHHAMMER.ArmorType.Light", medium: "OATHHAMMER.ArmorType.Medium", - heavy: "OATHHAMMER.ArmorType.Heavy" + heavy: "OATHHAMMER.ArmorType.Heavy" } +// Armor traits (p.88): Clanging = -1 Stealth; Reinforced = red dice for armor rolls +export const ARMOR_TRAITS = { + clanging: "OATHHAMMER.ArmorTrait.Clanging", + reinforced: "OATHHAMMER.ArmorTrait.Reinforced" +} + +// Ammunition types (p.88): standard = arrow/bolt; bodkin = -1 armor; envenomed = poison; incendiary = flaming export const AMMO_TYPE_CHOICES = { - arrow: "OATHHAMMER.AmmoType.Arrow", - bolt: "OATHHAMMER.AmmoType.Bolt", - stone: "OATHHAMMER.AmmoType.Stone", - javelin: "OATHHAMMER.AmmoType.Javelin", - "throwing-knife": "OATHHAMMER.AmmoType.ThrowingKnife" + standard: "OATHHAMMER.AmmoType.Standard", + bodkin: "OATHHAMMER.AmmoType.Bodkin", + envenomed: "OATHHAMMER.AmmoType.Envenomed", + incendiary: "OATHHAMMER.AmmoType.Incendiary" } +// Equipment sub-categories matching the rulebook sections (pp.90-96) export const EQUIPMENT_TYPE_CHOICES = { - potion: "OATHHAMMER.EquipmentType.Potion", - container: "OATHHAMMER.EquipmentType.Container", - tool: "OATHHAMMER.EquipmentType.Tool", - consumable: "OATHHAMMER.EquipmentType.Consumable", - misc: "OATHHAMMER.EquipmentType.Misc", + potion: "OATHHAMMER.EquipmentType.Potion", + container: "OATHHAMMER.EquipmentType.Container", + mount: "OATHHAMMER.EquipmentType.Mount", "healing-supply": "OATHHAMMER.EquipmentType.HealingSupply", - food: "OATHHAMMER.EquipmentType.Food", - mount: "OATHHAMMER.EquipmentType.Mount", - vehicle: "OATHHAMMER.EquipmentType.Vehicle", - "war-machine": "OATHHAMMER.EquipmentType.WarMachine" + food: "OATHHAMMER.EquipmentType.Food", + "light-source": "OATHHAMMER.EquipmentType.LightSource", + misc: "OATHHAMMER.EquipmentType.Misc", + vehicle: "OATHHAMMER.EquipmentType.Vehicle", + animal: "OATHHAMMER.EquipmentType.Animal", + "war-machine": "OATHHAMMER.EquipmentType.WarMachine" } +// Magic item sub-types for Focus/Talisman/Trinket (p.136) +// Weapon and Armor magic items use their respective item types with isMagic=true. export const MAGIC_ITEM_TYPE_CHOICES = { - weapon: "OATHHAMMER.MagicItemType.Weapon", - armor: "OATHHAMMER.MagicItemType.Armor", - wondrous: "OATHHAMMER.MagicItemType.Wondrous", - potion: "OATHHAMMER.MagicItemType.Potion", - ring: "OATHHAMMER.MagicItemType.Ring", - staff: "OATHHAMMER.MagicItemType.Staff", - wand: "OATHHAMMER.MagicItemType.Wand", - scroll: "OATHHAMMER.MagicItemType.Scroll", - rod: "OATHHAMMER.MagicItemType.Rod" + focus: "OATHHAMMER.MagicItemType.Focus", + talisman: "OATHHAMMER.MagicItemType.Talisman", + trinket: "OATHHAMMER.MagicItemType.Trinket" +} + +// Magic item quality (p.136): determines power and how they are found +export const MAGIC_QUALITY_CHOICES = { + lesser: "OATHHAMMER.MagicQuality.Lesser", + greater: "OATHHAMMER.MagicQuality.Greater", + legendary: "OATHHAMMER.MagicQuality.Legendary" } export const RARITY_CHOICES = { @@ -138,24 +171,112 @@ export const RARITY_CHOICES = { legendary: "OATHHAMMER.Rarity.Legendary" } +// Two types of ability: class traits and lineage traits. No feats in Oath Hammer. export const ABILITY_TYPE_CHOICES = { "class-ability": "OATHHAMMER.AbilityType.ClassAbility", - "lineage-trait": "OATHHAMMER.AbilityType.LineageTrait", - feat: "OATHHAMMER.AbilityType.Feat" + "lineage-trait": "OATHHAMMER.AbilityType.LineageTrait" } -export const CONDITION_TYPE_CHOICES = { - blinded: "OATHHAMMER.Condition.Blinded", - deafened: "OATHHAMMER.Condition.Deafened", - prone: "OATHHAMMER.Condition.Prone", - stunned: "OATHHAMMER.Condition.Stunned", - frightened: "OATHHAMMER.Condition.Frightened", - poisoned: "OATHHAMMER.Condition.Poisoned", - restrained: "OATHHAMMER.Condition.Restrained", - wounded: "OATHHAMMER.Condition.Wounded", - other: "OATHHAMMER.Condition.Other" +// When an ability's uses reset (none = passive/always on) +export const ABILITY_USAGE_PERIOD = { + none: "OATHHAMMER.UsagePeriod.None", + encounter: "OATHHAMMER.UsagePeriod.Encounter", + day: "OATHHAMMER.UsagePeriod.Day" } +export const STATUS_EFFECTS = [ + { + id: "blinded", + name: "OATHHAMMER.Condition.Blinded", + img: "systems/fvtt-oath-hammer/assets/icons/conditions/blinded.svg", + description: "OATHHAMMER.Condition.BlindedDesc" + }, + { + id: "confused", + name: "OATHHAMMER.Condition.Confused", + img: "systems/fvtt-oath-hammer/assets/icons/conditions/confused.svg", + description: "OATHHAMMER.Condition.ConfusedDesc" + }, + { + id: "dazed", + name: "OATHHAMMER.Condition.Dazed", + img: "systems/fvtt-oath-hammer/assets/icons/conditions/dazed.svg", + description: "OATHHAMMER.Condition.DazedDesc" + }, + { + id: "deafened", + name: "OATHHAMMER.Condition.Deafened", + img: "systems/fvtt-oath-hammer/assets/icons/conditions/deafened.svg", + description: "OATHHAMMER.Condition.DeafenedDesc" + }, + { + id: "demoralized", + name: "OATHHAMMER.Condition.Demoralized", + img: "systems/fvtt-oath-hammer/assets/icons/conditions/demoralized.svg", + description: "OATHHAMMER.Condition.DemoralizedDesc" + }, + { + id: "diseased", + name: "OATHHAMMER.Condition.Diseased", + img: "systems/fvtt-oath-hammer/assets/icons/conditions/diseased.svg", + description: "OATHHAMMER.Condition.DiseasedDesc" + }, + { + id: "enfeebled", + name: "OATHHAMMER.Condition.Enfeebled", + img: "systems/fvtt-oath-hammer/assets/icons/conditions/enfeebled.svg", + description: "OATHHAMMER.Condition.EnfeebledDesc" + }, + { + id: "fatigued", + name: "OATHHAMMER.Condition.Fatigued", + img: "systems/fvtt-oath-hammer/assets/icons/conditions/fatigued.svg", + description: "OATHHAMMER.Condition.FatiguedDesc" + }, + { + id: "frightened", + name: "OATHHAMMER.Condition.Frightened", + img: "systems/fvtt-oath-hammer/assets/icons/conditions/frightened.svg", + description: "OATHHAMMER.Condition.FrightenedDesc" + }, + { + id: "ignited", + name: "OATHHAMMER.Condition.Ignited", + img: "systems/fvtt-oath-hammer/assets/icons/conditions/ignited.svg", + description: "OATHHAMMER.Condition.IgnitedDesc" + }, + { + id: "inspired", + name: "OATHHAMMER.Condition.Inspired", + img: "systems/fvtt-oath-hammer/assets/icons/conditions/inspired.svg", + description: "OATHHAMMER.Condition.InspiredDesc" + }, + { + id: "invisible", + name: "OATHHAMMER.Condition.Invisible", + img: "systems/fvtt-oath-hammer/assets/icons/conditions/invisible.svg", + description: "OATHHAMMER.Condition.InvisibleDesc" + }, + { + id: "poisoned", + name: "OATHHAMMER.Condition.Poisoned", + img: "systems/fvtt-oath-hammer/assets/icons/conditions/poisoned.svg", + description: "OATHHAMMER.Condition.PoisonedDesc" + }, + { + id: "restrained", + name: "OATHHAMMER.Condition.Restrained", + img: "systems/fvtt-oath-hammer/assets/icons/conditions/restrained.svg", + description: "OATHHAMMER.Condition.RestrainedDesc" + }, + { + id: "stunned", + name: "OATHHAMMER.Condition.Stunned", + img: "systems/fvtt-oath-hammer/assets/icons/conditions/stunned.svg", + description: "OATHHAMMER.Condition.StunnedDesc" + } +] + export const ATTRIBUTE_RANK_CHOICES = { 1: "1", 2: "2", 3: "3", 4: "4" } export const ASCII = ` @@ -176,19 +297,22 @@ export const SYSTEM = { CLASS_CHOICES, OATH_TYPES, SORCEROUS_TRADITIONS, - WEAPON_TYPE_CHOICES, - DAMAGE_TYPE_CHOICES, - ATTRIBUTE_BONUS_CHOICES, - RANGE_CHOICES, - HANDS_CHOICES, - CURRENCY_CHOICES, + DIVINE_TRADITIONS, + ELEMENTAL_CHOICES, + RUNE_TYPE_CHOICES, + WEAPON_PROFICIENCY_GROUPS, + WEAPON_TRAITS, ARMOR_TYPE_CHOICES, + ARMOR_TRAITS, + CURRENCY_CHOICES, AMMO_TYPE_CHOICES, EQUIPMENT_TYPE_CHOICES, MAGIC_ITEM_TYPE_CHOICES, + MAGIC_QUALITY_CHOICES, RARITY_CHOICES, ABILITY_TYPE_CHOICES, - CONDITION_TYPE_CHOICES, + ABILITY_USAGE_PERIOD, + STATUS_EFFECTS, ATTRIBUTE_RANK_CHOICES, ASCII } diff --git a/module/documents/actor.mjs b/module/documents/actor.mjs index 511c215..abd417b 100644 --- a/module/documents/actor.mjs +++ b/module/documents/actor.mjs @@ -20,16 +20,12 @@ export default class OathHammerActor extends Actor { } } - getArmorRating() { - let rating = 0 + getArmorValue() { for (const item of this.items) { if (item.type === "armor" && item.system.equipped) { - rating += Number(item.system.armorRating) || 0 - } - if (item.type === "shield" && item.system.equipped) { - rating += Number(item.system.shieldBonus) || 0 + return Number(item.system.armorValue) || 0 } } - return rating + return 0 } } diff --git a/module/documents/item.mjs b/module/documents/item.mjs index b64a748..5055716 100644 --- a/module/documents/item.mjs +++ b/module/documents/item.mjs @@ -1,15 +1,13 @@ const defaultItemImg = { - weapon: "systems/fvtt-oath-hammer/assets/icons/icon_weapon.webp", - armor: "systems/fvtt-oath-hammer/assets/icons/icon_armor.webp", - shield: "systems/fvtt-oath-hammer/assets/icons/icon_shield.webp", - ammunition: "systems/fvtt-oath-hammer/assets/icons/icon_ammunition.webp", - equipment: "systems/fvtt-oath-hammer/assets/icons/icon_equipment.webp", - spell: "systems/fvtt-oath-hammer/assets/icons/icon_spell.webp", - miracle: "systems/fvtt-oath-hammer/assets/icons/icon_miracle.webp", + weapon: "systems/fvtt-oath-hammer/assets/icons/icon_weapon.webp", + armor: "systems/fvtt-oath-hammer/assets/icons/icon_armor.webp", + ammunition: "systems/fvtt-oath-hammer/assets/icons/icon_ammunition.webp", + equipment: "systems/fvtt-oath-hammer/assets/icons/icon_equipment.webp", + spell: "systems/fvtt-oath-hammer/assets/icons/icon_spell.webp", + miracle: "systems/fvtt-oath-hammer/assets/icons/icon_miracle.webp", "magic-item": "systems/fvtt-oath-hammer/assets/icons/icon_magic_item.webp", - ability: "systems/fvtt-oath-hammer/assets/icons/icon_ability.webp", - oath: "systems/fvtt-oath-hammer/assets/icons/icon_oath.webp", - condition: "systems/fvtt-oath-hammer/assets/icons/icon_condition.webp" + ability: "systems/fvtt-oath-hammer/assets/icons/icon_ability.webp", + oath: "systems/fvtt-oath-hammer/assets/icons/icon_oath.webp" } export default class OathHammerItem extends Item { diff --git a/module/models/_module.mjs b/module/models/_module.mjs index e5e2160..f18dab7 100644 --- a/module/models/_module.mjs +++ b/module/models/_module.mjs @@ -2,7 +2,6 @@ export { default as OathHammerCharacter } from "./character.mjs" export { default as OathHammerNPC } from "./npc.mjs" export { default as OathHammerWeapon } from "./weapon.mjs" export { default as OathHammerArmor } from "./armor.mjs" -export { default as OathHammerShield } from "./shield.mjs" export { default as OathHammerAmmunition } from "./ammunition.mjs" export { default as OathHammerEquipment } from "./equipment.mjs" export { default as OathHammerSpell } from "./spell.mjs" @@ -10,4 +9,3 @@ export { default as OathHammerMiracle } from "./miracle.mjs" export { default as OathHammerMagicItem } from "./magic-item.mjs" export { default as OathHammerAbility } from "./ability.mjs" export { default as OathHammerOath } from "./oath.mjs" -export { default as OathHammerCondition } from "./condition.mjs" diff --git a/module/models/ability.mjs b/module/models/ability.mjs index 7f87f16..1903a06 100644 --- a/module/models/ability.mjs +++ b/module/models/ability.mjs @@ -3,13 +3,28 @@ import { SYSTEM } from "../config/system.mjs" export default class OathHammerAbility extends foundry.abstract.TypeDataModel { static defineSchema() { const fields = foundry.data.fields + const requiredInteger = { required: true, nullable: false, integer: true } const schema = {} schema.description = new fields.HTMLField({ required: true, textSearch: true }) - schema.abilityType = new fields.StringField({ required: true, initial: "class-ability", choices: SYSTEM.ABILITY_TYPE_CHOICES }) + + // lineage-trait (racial) or class-ability (starting or advancement trait) + schema.abilityType = new fields.StringField({ + required: true, initial: "class-ability", choices: SYSTEM.ABILITY_TYPE_CHOICES + }) + + // Which class or lineage this trait belongs to (e.g. "Berserker", "Wood Elf") schema.source = new fields.StringField({ required: true, nullable: false, initial: "" }) - schema.prerequisite = new fields.StringField({ required: true, nullable: false, initial: "" }) - schema.passiveBonus = new fields.StringField({ required: true, nullable: false, initial: "" }) + + // When uses reset: none = passive (always on), encounter, day + schema.usagePeriod = new fields.StringField({ + required: true, initial: "none", choices: SYSTEM.ABILITY_USAGE_PERIOD + }) + + // Maximum uses per period. 0 = passive / unlimited. + // Use a descriptive string when the limit is formula-based + // (e.g. "equal to Fate ranks") — store the note in the description. + schema.maxUses = new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }) return schema } diff --git a/module/models/ammunition.mjs b/module/models/ammunition.mjs index 256cf18..f4cb588 100644 --- a/module/models/ammunition.mjs +++ b/module/models/ammunition.mjs @@ -7,11 +7,21 @@ export default class OathHammerAmmunition extends foundry.abstract.TypeDataModel const schema = {} schema.description = new fields.HTMLField({ required: true, textSearch: true }) - schema.ammoType = new fields.StringField({ required: true, initial: "arrow", choices: SYSTEM.AMMO_TYPE_CHOICES }) - schema.quantity = new fields.NumberField({ ...requiredInteger, initial: 1, min: 0 }) - schema.properties = new fields.StringField({ required: true, nullable: false, initial: "" }) - schema.cost = new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }) - schema.currency = new fields.StringField({ required: true, initial: "gp", choices: SYSTEM.CURRENCY_CHOICES }) + + // Ammo type determines special effect (p.88): + // bodkin → −1 to target's armor roll + // envenomed → poison damage + // incendiary → flaming damage + schema.ammoType = new fields.StringField({ required: true, initial: "standard", choices: SYSTEM.AMMO_TYPE_CHOICES }) + + // Quantity of individual arrows/bolts in this stack + schema.quantity = new fields.NumberField({ ...requiredInteger, initial: 20, min: 0 }) + + // Rarity: DV for Fortune check when purchasing; 0 = always available + schema.rarity = new fields.NumberField({ ...requiredInteger, initial: 0, min: 0, max: 6 }) + + schema.cost = new fields.NumberField({ required: true, nullable: false, initial: 0, min: 0 }) + schema.currency = new fields.StringField({ required: true, initial: "sp", choices: SYSTEM.CURRENCY_CHOICES }) return schema } diff --git a/module/models/armor.mjs b/module/models/armor.mjs index 9b5660f..24fdfa8 100644 --- a/module/models/armor.mjs +++ b/module/models/armor.mjs @@ -7,14 +7,41 @@ export default class OathHammerArmor extends foundry.abstract.TypeDataModel { const schema = {} schema.description = new fields.HTMLField({ required: true, textSearch: true }) + + // Proficiency group: light / medium / heavy (p.88) schema.armorType = new fields.StringField({ required: true, initial: "light", choices: SYSTEM.ARMOR_TYPE_CHOICES }) - schema.armorRating = new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }) - schema.movementPenalty = new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }) - schema.equipped = new fields.BooleanField({ required: true, initial: false }) - schema.encumbrance = new fields.NumberField({ required: true, initial: 1, min: 0 }) - schema.cost = new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }) + + // Armor Value (AV): number of armor dice rolled when receiving damage + schema.armorValue = new fields.NumberField({ ...requiredInteger, initial: 0, min: 0, max: 12 }) + + // Penalty: modifier to Acrobatics checks AND defense rolls (0, -1, -2, -3…) + schema.penalty = new fields.NumberField({ ...requiredInteger, initial: 0, min: -5, max: 0 }) + + // Item slots occupied while worn or stowed + schema.slots = new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }) + + // Armor traits: Clanging (−1 Stealth) / Reinforced (red dice for armor rolls) + schema.traits = new fields.SetField( + new fields.StringField({ choices: SYSTEM.ARMOR_TRAITS }) + ) + + // Rarity: DV for Fortune check when purchasing; 0 = always available + schema.rarity = new fields.NumberField({ ...requiredInteger, initial: 0, min: 0, max: 6 }) + + schema.equipped = new fields.BooleanField({ initial: false }) + schema.cost = new fields.NumberField({ required: true, nullable: false, initial: 0, min: 0 }) schema.currency = new fields.StringField({ required: true, initial: "gp", choices: SYSTEM.CURRENCY_CHOICES }) + // --- Magic properties (only relevant when isMagic = true) --- + schema.isMagic = new fields.BooleanField({ initial: false }) + schema.magicQuality = new fields.StringField({ + required: false, nullable: true, initial: null, choices: SYSTEM.MAGIC_QUALITY_CHOICES + }) + 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: "" }) + return schema } diff --git a/module/models/character.mjs b/module/models/character.mjs index b9dcdfc..6fef1e1 100644 --- a/module/models/character.mjs +++ b/module/models/character.mjs @@ -54,7 +54,7 @@ export default class OathHammerCharacter extends foundry.abstract.TypeDataModel schema.biodata = new fields.SchemaField({ lineage: new fields.StringField({ required: true, initial: "dwarf", choices: SYSTEM.LINEAGE_CHOICES }), - class: new fields.StringField({ required: true, initial: "fighter", choices: SYSTEM.CLASS_CHOICES }), + class: new fields.StringField({ required: true, initial: "soldier", choices: SYSTEM.CLASS_CHOICES }), age: new fields.StringField({ required: true, nullable: false, initial: "" }), gender: new fields.StringField({ required: true, nullable: false, initial: "" }), height: new fields.StringField({ required: true, nullable: false, initial: "" }), diff --git a/module/models/condition.mjs b/module/models/condition.mjs deleted file mode 100644 index f244a3c..0000000 --- a/module/models/condition.mjs +++ /dev/null @@ -1,17 +0,0 @@ -import { SYSTEM } from "../config/system.mjs" - -export default class OathHammerCondition extends foundry.abstract.TypeDataModel { - static defineSchema() { - const fields = foundry.data.fields - const schema = {} - - schema.description = new fields.HTMLField({ required: true, textSearch: true }) - schema.conditionType = new fields.StringField({ required: true, initial: "stunned", choices: SYSTEM.CONDITION_TYPE_CHOICES }) - schema.duration = new fields.StringField({ required: true, nullable: false, initial: "" }) - schema.source = new fields.StringField({ required: true, nullable: false, initial: "" }) - - return schema - } - - static LOCALIZATION_PREFIXES = ["OATHHAMMER.Condition"] -} diff --git a/module/models/equipment.mjs b/module/models/equipment.mjs index ef16829..9f74f2c 100644 --- a/module/models/equipment.mjs +++ b/module/models/equipment.mjs @@ -7,10 +7,22 @@ export default class OathHammerEquipment extends foundry.abstract.TypeDataModel const schema = {} schema.description = new fields.HTMLField({ required: true, textSearch: true }) + + // Sub-category matching the rulebook sections (pp.90-96) schema.itemType = new fields.StringField({ required: true, initial: "misc", choices: SYSTEM.EQUIPMENT_TYPE_CHOICES }) + schema.quantity = new fields.NumberField({ ...requiredInteger, initial: 1, min: 0 }) - schema.weight = new fields.NumberField({ required: true, initial: 0, min: 0 }) - schema.cost = new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }) + + // Item slots occupied when carried. 0 = small item (no slots). + schema.slots = new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }) + + // Rarity: DV for Fortune check when purchasing; 0 = always available + schema.rarity = new fields.NumberField({ ...requiredInteger, initial: 0, min: 0, max: 6 }) + + // Light radius in feet — only relevant for light-source items (Candle/Lamp/Lantern/Torch) + schema.lightRadius = new fields.NumberField({ required: false, nullable: true, initial: null, min: 0 }) + + schema.cost = new fields.NumberField({ required: true, nullable: false, initial: 0, min: 0 }) schema.currency = new fields.StringField({ required: true, initial: "gp", choices: SYSTEM.CURRENCY_CHOICES }) return schema diff --git a/module/models/magic-item.mjs b/module/models/magic-item.mjs index e83c3ba..adf7b0b 100644 --- a/module/models/magic-item.mjs +++ b/module/models/magic-item.mjs @@ -6,18 +6,39 @@ export default class OathHammerMagicItem extends foundry.abstract.TypeDataModel const requiredInteger = { required: true, nullable: false, integer: true } const schema = {} - schema.description = new fields.HTMLField({ required: true, textSearch: true }) - schema.itemType = new fields.StringField({ required: true, initial: "wondrous", choices: SYSTEM.MAGIC_ITEM_TYPE_CHOICES }) - schema.rarity = new fields.StringField({ required: true, initial: "common", choices: SYSTEM.RARITY_CHOICES }) - schema.attunement = new fields.BooleanField({ required: true, initial: false }) - schema.charges = new fields.SchemaField({ - value: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }), - max: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }) + // The magical effect / description of the item + schema.effect = new fields.HTMLField({ required: true, textSearch: true }) + + // Sub-type: Focus (arcane/divine instrument), Talisman (worn item), Trinket (misc object) + // Note: magic weapons and armor use the weapon/armor item types with isMagic=true instead. + schema.itemType = new fields.StringField({ + required: true, initial: "talisman", choices: SYSTEM.MAGIC_ITEM_TYPE_CHOICES }) - schema.recharge = new fields.StringField({ required: true, nullable: false, initial: "" }) - schema.equipped = new fields.BooleanField({ required: true, initial: false }) - schema.cost = new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }) - schema.currency = new fields.StringField({ required: true, initial: "gp", choices: SYSTEM.CURRENCY_CHOICES }) + + // Quality: lesser / greater / legendary (determines power and how found, p.136) + schema.quality = new fields.StringField({ + required: true, initial: "lesser", choices: SYSTEM.MAGIC_QUALITY_CHOICES + }) + + // Cursed items impose a bane that cannot be removed until the curse is broken + schema.isCursed = new fields.BooleanField({ initial: false }) + + // Legendary items bond to a single character (cannot be shared, p.136) + 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: "" }) + + // Limited-use items (e.g. "once per day"); none = always active + schema.usagePeriod = new fields.StringField({ + required: true, initial: "none", choices: SYSTEM.ABILITY_USAGE_PERIOD + }) + schema.maxUses = new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }) + + // Item slots occupied when carried; 0 = small item (no slots) + schema.slots = new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }) + + schema.equipped = new fields.BooleanField({ initial: false }) return schema } diff --git a/module/models/miracle.mjs b/module/models/miracle.mjs index f68429e..d7ce5ab 100644 --- a/module/models/miracle.mjs +++ b/module/models/miracle.mjs @@ -7,17 +7,28 @@ export default class OathHammerMiracle extends foundry.abstract.TypeDataModel { const schema = {} schema.effect = new fields.HTMLField({ required: true, textSearch: true }) - schema.piety = new fields.NumberField({ ...requiredInteger, initial: 1, min: 0 }) - schema.castingTime = new fields.StringField({ required: true, nullable: false, initial: "" }) - schema.range = new fields.StringField({ required: true, nullable: false, initial: "" }) - schema.duration = new fields.StringField({ required: true, nullable: false, initial: "" }) - schema.components = new fields.SchemaField({ - verbal: new fields.BooleanField(), - somatic: new fields.BooleanField(), - material: new fields.BooleanField() + + // Divine tradition (Druidic / Profane / Sanctified) + schema.divineTradition = new fields.StringField({ + required: true, initial: "sanctified", choices: SYSTEM.DIVINE_TRADITIONS }) - schema.materialComponent = new fields.StringField({ required: true, nullable: false, initial: "" }) - schema.savingThrow = new fields.StringField({ required: true, nullable: false, initial: "" }) + + // Difficulty Value: 0 = scales dynamically (1st = DV1, 2nd = DV2…). + // Non-zero only for Ritual miracles which have a fixed DV (p.129). + schema.difficultyValue = new fields.NumberField({ ...requiredInteger, initial: 0, min: 0, max: 10 }) + + // Ritual miracles require 1 hour; need a holy book; fixed DV; don't + // increment the daily miracle counter (p.129). + schema.isRitual = new fields.BooleanField({ initial: false }) + + // Range: "Touch", "Self", "20", "100", "1 mile", etc. + schema.range = new fields.StringField({ required: true, nullable: false, initial: "" }) + + // Duration: "1 hour", "Encounter", "1 day", etc. Empty = instantaneous. + schema.duration = new fields.StringField({ required: true, nullable: false, initial: "" }) + + // Spell Save: e.g. "DV4 Athletics", "DV5 Fortune". Empty = no save. + schema.spellSave = new fields.StringField({ required: true, nullable: false, initial: "" }) return schema } diff --git a/module/models/oath.mjs b/module/models/oath.mjs index 4033a5d..55d2a7b 100644 --- a/module/models/oath.mjs +++ b/module/models/oath.mjs @@ -5,9 +5,10 @@ export default class OathHammerOath extends foundry.abstract.TypeDataModel { const fields = foundry.data.fields const schema = {} - schema.benefit = new fields.HTMLField({ required: true, textSearch: true }) - schema.violation = new fields.HTMLField({ required: true, textSearch: true }) schema.oathType = new fields.StringField({ required: true, initial: "oath-of-justice", choices: SYSTEM.OATH_TYPES }) + schema.tenet = new fields.HTMLField({ required: false, textSearch: true }) + schema.boon = new fields.HTMLField({ required: true, textSearch: true }) + schema.bane = new fields.HTMLField({ required: true, textSearch: true }) schema.violated = new fields.BooleanField({ required: true, initial: false }) return schema diff --git a/module/models/shield.mjs b/module/models/shield.mjs deleted file mode 100644 index b4f6688..0000000 --- a/module/models/shield.mjs +++ /dev/null @@ -1,20 +0,0 @@ -import { SYSTEM } from "../config/system.mjs" - -export default class OathHammerShield extends foundry.abstract.TypeDataModel { - static defineSchema() { - const fields = foundry.data.fields - const requiredInteger = { required: true, nullable: false, integer: true } - const schema = {} - - schema.description = new fields.HTMLField({ required: true, textSearch: true }) - schema.shieldBonus = new fields.NumberField({ ...requiredInteger, initial: 1, min: 0 }) - schema.equipped = new fields.BooleanField({ required: true, initial: false }) - schema.encumbrance = new fields.NumberField({ required: true, initial: 1, min: 0 }) - schema.cost = new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }) - schema.currency = new fields.StringField({ required: true, initial: "gp", choices: SYSTEM.CURRENCY_CHOICES }) - - return schema - } - - static LOCALIZATION_PREFIXES = ["OATHHAMMER.Shield"] -} diff --git a/module/models/spell.mjs b/module/models/spell.mjs index 5be17f4..66030ba 100644 --- a/module/models/spell.mjs +++ b/module/models/spell.mjs @@ -7,20 +7,42 @@ export default class OathHammerSpell extends foundry.abstract.TypeDataModel { const schema = {} schema.effect = new fields.HTMLField({ required: true, textSearch: true }) - schema.tradition = new fields.StringField({ required: true, initial: "elemental", choices: SYSTEM.SORCEROUS_TRADITIONS }) - schema.level = new fields.NumberField({ ...requiredInteger, initial: 1, min: 1, max: 6 }) - schema.castingTime = new fields.StringField({ required: true, nullable: false, initial: "" }) - schema.range = new fields.StringField({ required: true, nullable: false, initial: "" }) - schema.duration = new fields.StringField({ required: true, nullable: false, initial: "" }) - schema.arcaneStress = new fields.NumberField({ ...requiredInteger, initial: 1, min: 0 }) - schema.components = new fields.SchemaField({ - verbal: new fields.BooleanField(), - somatic: new fields.BooleanField(), - material: new fields.BooleanField() + + // Arcane tradition (Elemental / Illusionist / Imperial / Infernal / Runic / Stygian) + schema.tradition = new fields.StringField({ + required: true, initial: "elemental", choices: SYSTEM.SORCEROUS_TRADITIONS }) - schema.materialComponent = new fields.StringField({ required: true, nullable: false, initial: "" }) - schema.savingThrow = new fields.StringField({ required: true, nullable: false, initial: "" }) - schema.enhancement = new fields.StringField({ required: true, nullable: false, initial: "" }) + + // Difficulty Value: the Magic check DV needed to cast this spell + schema.difficultyValue = new fields.NumberField({ ...requiredInteger, initial: 1, min: 1, max: 10 }) + + // Ritual spells take 1 hour to cast; DV is listed with "(Ritual)" in the book + schema.isRitual = new fields.BooleanField({ initial: false }) + + // Magic Missile spells can be intercepted like ranged attacks (p.101) + schema.isMagicMissile = new fields.BooleanField({ initial: false }) + + // Range: "Touch", "Self", "40", "200", "Cone AoE", "Large AoE", etc. + schema.range = new fields.StringField({ required: true, nullable: false, initial: "" }) + + // Duration: "1 hour", "Encounter", "2d3 rounds", etc. Empty = instantaneous. + schema.duration = new fields.StringField({ required: true, nullable: false, initial: "" }) + + // Spell Save: e.g. "DV5 Resilience", "DV3 Acrobatics". Empty = no save. + schema.spellSave = new fields.StringField({ required: true, nullable: false, initial: "" }) + + // Elemental tradition only — sub-element (Air / Earth / Fire / Water / Varies) + schema.element = new fields.StringField({ + required: false, nullable: true, initial: null, choices: SYSTEM.ELEMENTAL_CHOICES + }) + + // Runic tradition only — what surface/object the rune is inscribed upon + schema.runeType = new fields.StringField({ + required: false, nullable: true, initial: null, choices: SYSTEM.RUNE_TYPE_CHOICES + }) + + // Runic tradition only — exalted runes activate once then vanish (p.120) + schema.isExalted = new fields.BooleanField({ initial: false }) return schema } diff --git a/module/models/weapon.mjs b/module/models/weapon.mjs index 47b955a..cb742b8 100644 --- a/module/models/weapon.mjs +++ b/module/models/weapon.mjs @@ -7,20 +7,69 @@ export default class OathHammerWeapon extends foundry.abstract.TypeDataModel { const schema = {} schema.description = new fields.HTMLField({ required: true, textSearch: true }) - schema.weaponType = new fields.StringField({ required: true, initial: "melee", choices: SYSTEM.WEAPON_TYPE_CHOICES }) - schema.damageFormula = new fields.StringField({ required: true, nullable: false, initial: "1d6" }) - schema.damageType = new fields.StringField({ required: true, initial: "slashing", choices: SYSTEM.DAMAGE_TYPE_CHOICES }) - schema.attributeBonus = new fields.StringField({ required: true, initial: "might", choices: SYSTEM.ATTRIBUTE_BONUS_CHOICES }) - schema.range = new fields.StringField({ required: true, initial: "short", choices: SYSTEM.RANGE_CHOICES }) - schema.hands = new fields.StringField({ required: true, initial: "one-handed", choices: SYSTEM.HANDS_CHOICES }) - schema.properties = new fields.StringField({ required: true, nullable: false, initial: "" }) - schema.equipped = new fields.BooleanField({ required: true, initial: false }) - schema.encumbrance = new fields.NumberField({ required: true, initial: 1, min: 0 }) - schema.cost = new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }) - schema.currency = new fields.StringField({ required: true, initial: "gp", choices: SYSTEM.CURRENCY_CHOICES }) + + // Proficiency group (Common, Dueling, Heavy, Polearms, Bows, Throwing) + schema.proficiencyGroup = new fields.StringField({ + required: true, initial: "common", choices: SYSTEM.WEAPON_PROFICIENCY_GROUPS + }) + + // Damage: melee = Might rank + damageMod dice; bows = baseDice (fixed, no Might) + // usesMight=true → formula displayed as "M+2", "M-1", etc. + // usesMight=false → formula displayed as e.g. "6" (fixed dice for bows) + schema.usesMight = new fields.BooleanField({ required: true, initial: true }) + schema.damageMod = new fields.NumberField({ ...requiredInteger, initial: 0, min: -4, max: 5 }) + + // AP (Armor Penetration): penalty imposed on armor/defense rolls + schema.ap = new fields.NumberField({ ...requiredInteger, initial: 0, min: 0, max: 6 }) + + // Reach (melee, in ft: 5 / 10 / 15) — ignored for ranged/throwing + schema.reach = new fields.NumberField({ ...requiredInteger, initial: 5, min: 5 }) + + // Range (ranged & throwing, in ft): short and long + schema.shortRange = new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }) + schema.longRange = new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }) + + // Traits — stored as a Set of trait keys (Block, Brutal, Nimble, Parry, etc.) + schema.traits = new fields.SetField( + new fields.StringField({ choices: SYSTEM.WEAPON_TRAITS }), + { required: true, initial: [] } + ) + + // Item slots (when stowed; 0 = does not occupy slots) + schema.slots = new fields.NumberField({ ...requiredInteger, initial: 1, min: 0 }) + + // Rarity (DV for Fortune check to find item for sale) + schema.rarity = new fields.NumberField({ ...requiredInteger, initial: 0, min: 0, max: 5 }) + + schema.equipped = new fields.BooleanField({ required: true, initial: false }) + schema.cost = new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }) + schema.currency = new fields.StringField({ required: true, initial: "gp", choices: SYSTEM.CURRENCY_CHOICES }) + + // --- Magic properties (only relevant when isMagic = true) --- + schema.isMagic = new fields.BooleanField({ initial: false }) + schema.magicQuality = new fields.StringField({ + required: false, nullable: true, initial: null, choices: SYSTEM.MAGIC_QUALITY_CHOICES + }) + schema.isCursed = new fields.BooleanField({ initial: false }) + // Enchantment description (displayed when isMagic is true) + 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: "" }) return schema } static LOCALIZATION_PREFIXES = ["OATHHAMMER.Weapon"] + + /** + * Human-readable damage formula for display, e.g. "M+2", "M-1", "6" + */ + get damageLabel() { + if (this.usesMight) { + const mod = this.damageMod + if (mod === 0) return "M+0" + return mod > 0 ? `M+${mod}` : `M${mod}` + } + return String(this.damageMod) // bows: store base dice count in damageMod + } } diff --git a/oath-hammer.mjs b/oath-hammer.mjs index c501f0c..b16ae4d 100644 --- a/oath-hammer.mjs +++ b/oath-hammer.mjs @@ -1,4 +1,4 @@ -import { SYSTEM } from "./module/config/system.mjs" +import { SYSTEM, STATUS_EFFECTS } from "./module/config/system.mjs" globalThis.SYSTEM = SYSTEM import * as models from "./module/models/_module.mjs" @@ -25,15 +25,13 @@ Hooks.once("init", function () { CONFIG.Item.dataModels = { weapon: models.OathHammerWeapon, armor: models.OathHammerArmor, - shield: models.OathHammerShield, ammunition: models.OathHammerAmmunition, equipment: models.OathHammerEquipment, spell: models.OathHammerSpell, miracle: models.OathHammerMiracle, "magic-item": models.OathHammerMagicItem, ability: models.OathHammerAbility, - oath: models.OathHammerOath, - condition: models.OathHammerCondition + oath: models.OathHammerOath } foundry.documents.collections.Actors.unregisterSheet("core", foundry.appv1.sheets.ActorSheet) @@ -51,7 +49,6 @@ Hooks.once("init", function () { foundry.documents.collections.Items.unregisterSheet("core", foundry.appv1.sheets.ItemSheet) foundry.documents.collections.Items.registerSheet("fvtt-oath-hammer", applications.OathHammerWeaponSheet, { types: ["weapon"], makeDefault: true, label: "OATHHAMMER.Sheet.Weapon" }) foundry.documents.collections.Items.registerSheet("fvtt-oath-hammer", applications.OathHammerArmorSheet, { types: ["armor"], makeDefault: true, label: "OATHHAMMER.Sheet.Armor" }) - foundry.documents.collections.Items.registerSheet("fvtt-oath-hammer", applications.OathHammerShieldSheet, { types: ["shield"], makeDefault: true, label: "OATHHAMMER.Sheet.Shield" }) foundry.documents.collections.Items.registerSheet("fvtt-oath-hammer", applications.OathHammerAmmunitionSheet, { types: ["ammunition"], makeDefault: true, label: "OATHHAMMER.Sheet.Ammunition" }) foundry.documents.collections.Items.registerSheet("fvtt-oath-hammer", applications.OathHammerEquipmentSheet, { types: ["equipment"], makeDefault: true, label: "OATHHAMMER.Sheet.Equipment" }) foundry.documents.collections.Items.registerSheet("fvtt-oath-hammer", applications.OathHammerSpellSheet, { types: ["spell"], makeDefault: true, label: "OATHHAMMER.Sheet.Spell" }) @@ -59,7 +56,8 @@ Hooks.once("init", function () { foundry.documents.collections.Items.registerSheet("fvtt-oath-hammer", applications.OathHammerMagicItemSheet, { types: ["magic-item"], makeDefault: true, label: "OATHHAMMER.Sheet.MagicItem" }) foundry.documents.collections.Items.registerSheet("fvtt-oath-hammer", applications.OathHammerAbilitySheet, { types: ["ability"], makeDefault: true, label: "OATHHAMMER.Sheet.Ability" }) foundry.documents.collections.Items.registerSheet("fvtt-oath-hammer", applications.OathHammerOathSheet, { types: ["oath"], makeDefault: true, label: "OATHHAMMER.Sheet.Oath" }) - foundry.documents.collections.Items.registerSheet("fvtt-oath-hammer", applications.OathHammerConditionSheet, { types: ["condition"], makeDefault: true, label: "OATHHAMMER.Sheet.Condition" }) + + CONFIG.statusEffects = STATUS_EFFECTS OathHammerUtils.registerHandlebarsHelpers() diff --git a/package.json b/package.json new file mode 100644 index 0000000..a332e58 --- /dev/null +++ b/package.json @@ -0,0 +1,21 @@ +{ + "name": "fvtt-oath-hammer", + "version": "13.0.0", + "description": "Oath Hammer RPG System for FoundryVTT", + "private": true, + "scripts": { + "build": "gulp css", + "watch": "gulp watch" + }, + "devDependencies": { + "gulp": "^4.0.2", + "gulp-less": "^5.0.0", + "less": "^4.2.0", + "autoprefixer": "^10.4.20", + "gulp-postcss": "^9.0.1", + "postcss": "^8.4.49" + }, + "keywords": ["foundry-vtt", "oath-hammer"], + "author": "", + "license": "ISC" +} diff --git a/system.json b/system.json index d7a8549..9e7cbbc 100644 --- a/system.json +++ b/system.json @@ -13,17 +13,15 @@ "npc": { "htmlFields": ["description", "notes"] } }, "Item": { - "weapon": { "htmlFields": ["description"] }, - "armor": { "htmlFields": ["description"] }, - "shield": { "htmlFields": ["description"] }, + "weapon": { "htmlFields": ["description", "magicEffect"] }, + "armor": { "htmlFields": ["description", "magicEffect"] }, "ammunition": { "htmlFields": ["description"] }, "equipment": { "htmlFields": ["description"] }, "spell": { "htmlFields": ["effect"] }, "miracle": { "htmlFields": ["effect"] }, - "magic-item": { "htmlFields": ["description"] }, + "magic-item": { "htmlFields": ["effect"] }, "ability": { "htmlFields": ["description"] }, - "oath": { "htmlFields": ["benefit", "violation"] }, - "condition": { "htmlFields": ["description"] } + "oath": { "htmlFields": ["tenet", "boon", "bane"] } } }, "grid": { "distance": 5, "units": "ft" }, diff --git a/templates/actor/character-combat.hbs b/templates/actor/character-combat.hbs index 8c266b8..d4a5bef 100644 --- a/templates/actor/character-combat.hbs +++ b/templates/actor/character-combat.hbs @@ -26,8 +26,8 @@
  • {{weapon.name}} - {{weapon.system.damageFormula}} - {{localize weapon.system.damageType}} + {{weapon.system.damageLabel}} + AP: {{weapon.system.ap}} {{formField weapon.system.schema.fields.equipped value=weapon.system.equipped name="system.equipped"}} {{#unless ../isPlayMode}} @@ -49,8 +49,9 @@
  • {{armor.name}} - {{localize armor.system.armorType}} - AR: {{armor.system.armorRating}} + AV: {{armor.system.armorValue}} + {{#if armor.system.penalty}}{{armor.system.penalty}}{{/if}} + {{formField armor.system.schema.fields.equipped value=armor.system.equipped name="system.equipped"}} {{#unless ../isPlayMode}} @@ -59,22 +60,7 @@ {{/each}} {{/if}} - {{#if shields.length}} - - {{/if}} - {{#unless (or armors.length shields.length)}} + {{#unless armors.length}}

    {{localize "OATHHAMMER.Label.NoArmor"}}

    {{/unless}} diff --git a/templates/actor/npc-combat.hbs b/templates/actor/npc-combat.hbs index cc25bff..f5591b2 100644 --- a/templates/actor/npc-combat.hbs +++ b/templates/actor/npc-combat.hbs @@ -7,8 +7,8 @@
  • {{weapon.name}} - {{weapon.system.damageFormula}} - {{localize weapon.system.damageType}} + {{weapon.system.damageLabel}} + AP: {{weapon.system.ap}} {{#unless ../isPlayMode}} diff --git a/templates/item/ability-sheet.hbs b/templates/item/ability-sheet.hbs index 076c15f..d200a4d 100644 --- a/templates/item/ability-sheet.hbs +++ b/templates/item/ability-sheet.hbs @@ -7,8 +7,10 @@
    {{formField systemFields.abilityType value=system.abilityType name="system.abilityType" localize=true}} {{formField systemFields.source value=system.source name="system.source"}} - {{formField systemFields.prerequisite value=system.prerequisite name="system.prerequisite"}} - {{formField systemFields.passiveBonus value=system.passiveBonus name="system.passiveBonus"}} + {{formField systemFields.usagePeriod value=system.usagePeriod name="system.usagePeriod" localize=true}} + {{#unless (eq system.usagePeriod "none")}} + {{formField systemFields.maxUses value=system.maxUses name="system.maxUses"}} + {{/unless}}
    diff --git a/templates/item/ammunition-sheet.hbs b/templates/item/ammunition-sheet.hbs index 96302f5..b3b67dc 100644 --- a/templates/item/ammunition-sheet.hbs +++ b/templates/item/ammunition-sheet.hbs @@ -7,7 +7,7 @@
    {{formField systemFields.ammoType value=system.ammoType name="system.ammoType" localize=true}} {{formField systemFields.quantity value=system.quantity name="system.quantity"}} - {{formField systemFields.properties value=system.properties name="system.properties"}} + {{formField systemFields.rarity value=system.rarity name="system.rarity"}}
    {{formField systemFields.cost value=system.cost name="system.cost"}} diff --git a/templates/item/armor-sheet.hbs b/templates/item/armor-sheet.hbs index 0aa054f..c4321a8 100644 --- a/templates/item/armor-sheet.hbs +++ b/templates/item/armor-sheet.hbs @@ -6,12 +6,15 @@
    {{formField systemFields.armorType value=system.armorType name="system.armorType" localize=true}} - {{formField systemFields.armorRating value=system.armorRating name="system.armorRating"}} - {{formField systemFields.movementPenalty value=system.movementPenalty name="system.movementPenalty"}} + {{formField systemFields.armorValue value=system.armorValue name="system.armorValue"}} + {{formField systemFields.penalty value=system.penalty name="system.penalty"}} + {{formField systemFields.slots value=system.slots name="system.slots"}} + {{formField systemFields.traits value=system.traits name="system.traits" localize=true}}
    + {{formField systemFields.rarity value=system.rarity name="system.rarity"}} + {{formField systemFields.isMagic value=system.isMagic name="system.isMagic"}} {{formField systemFields.equipped value=system.equipped name="system.equipped"}} - {{formField systemFields.encumbrance value=system.encumbrance name="system.encumbrance"}} {{formField systemFields.cost value=system.cost name="system.cost"}} {{formField systemFields.currency value=system.currency name="system.currency" localize=true}}
    @@ -20,4 +23,15 @@ {{localize "OATHHAMMER.Label.Description"}} {{formInput systemFields.description enriched=enrichedDescription value=system.description name="system.description" toggled=true}}
    + {{#if system.isMagic}} +
    + {{localize "OATHHAMMER.Label.Enchantment"}} +
    + {{formField systemFields.magicQuality value=system.magicQuality name="system.magicQuality" localize=true}} + {{formField systemFields.isCursed value=system.isCursed name="system.isCursed"}} + {{formField systemFields.classRestriction value=system.classRestriction name="system.classRestriction"}} +
    + {{formInput systemFields.magicEffect enriched=enrichedMagicEffect value=system.magicEffect name="system.magicEffect" toggled=true}} +
    + {{/if}} diff --git a/templates/item/condition-sheet.hbs b/templates/item/condition-sheet.hbs deleted file mode 100644 index 5bf563e..0000000 --- a/templates/item/condition-sheet.hbs +++ /dev/null @@ -1,17 +0,0 @@ -
    -
    - - {{formInput fields.name value=source.name}} -
    -
    -
    - {{formField systemFields.conditionType value=system.conditionType name="system.conditionType" localize=true}} - {{formField systemFields.duration value=system.duration name="system.duration"}} - {{formField systemFields.source value=system.source name="system.source"}} -
    -
    -
    - {{localize "OATHHAMMER.Label.Description"}} - {{formInput systemFields.description enriched=enrichedDescription value=system.description name="system.description" toggled=true}} -
    -
    diff --git a/templates/item/equipment-sheet.hbs b/templates/item/equipment-sheet.hbs index cc53574..8688704 100644 --- a/templates/item/equipment-sheet.hbs +++ b/templates/item/equipment-sheet.hbs @@ -7,7 +7,11 @@
    {{formField systemFields.itemType value=system.itemType name="system.itemType" localize=true}} {{formField systemFields.quantity value=system.quantity name="system.quantity"}} - {{formField systemFields.weight value=system.weight name="system.weight"}} + {{formField systemFields.slots value=system.slots name="system.slots"}} + {{formField systemFields.rarity value=system.rarity name="system.rarity"}} + {{#if system.lightRadius}} + {{formField systemFields.lightRadius value=system.lightRadius name="system.lightRadius"}} + {{/if}}
    {{formField systemFields.cost value=system.cost name="system.cost"}} diff --git a/templates/item/magic-item-sheet.hbs b/templates/item/magic-item-sheet.hbs index ddbb563..55afbbc 100644 --- a/templates/item/magic-item-sheet.hbs +++ b/templates/item/magic-item-sheet.hbs @@ -6,23 +6,22 @@
    {{formField systemFields.itemType value=system.itemType name="system.itemType" localize=true}} - {{formField systemFields.rarity value=system.rarity name="system.rarity" localize=true}} - {{formField systemFields.attunement value=system.attunement name="system.attunement"}} - {{formField systemFields.recharge value=system.recharge name="system.recharge"}} + {{formField systemFields.quality value=system.quality name="system.quality" localize=true}} + {{formField systemFields.isCursed value=system.isCursed name="system.isCursed"}} + {{formField systemFields.isBonded value=system.isBonded name="system.isBonded"}} + {{formField systemFields.classRestriction value=system.classRestriction name="system.classRestriction"}}
    - -
    - {{formField systemFields.charges.fields.value value=system.charges.value name="system.charges.value"}} - {{formField systemFields.charges.fields.max value=system.charges.max name="system.charges.max"}} -
    + {{formField systemFields.usagePeriod value=system.usagePeriod name="system.usagePeriod" localize=true}} + {{#unless (eq system.usagePeriod "none")}} + {{formField systemFields.maxUses value=system.maxUses name="system.maxUses"}} + {{/unless}} + {{formField systemFields.slots value=system.slots name="system.slots"}} {{formField systemFields.equipped value=system.equipped name="system.equipped"}} - {{formField systemFields.cost value=system.cost name="system.cost"}} - {{formField systemFields.currency value=system.currency name="system.currency" localize=true}}
    - {{localize "OATHHAMMER.Label.Description"}} - {{formInput systemFields.description enriched=enrichedDescription value=system.description name="system.description" toggled=true}} + {{localize "OATHHAMMER.Label.Effect"}} + {{formInput systemFields.effect enriched=enrichedEffect value=system.effect name="system.effect" toggled=true}}
    diff --git a/templates/item/oath-sheet.hbs b/templates/item/oath-sheet.hbs index 264f5fb..8ae46c1 100644 --- a/templates/item/oath-sheet.hbs +++ b/templates/item/oath-sheet.hbs @@ -10,11 +10,15 @@
    - {{localize "OATHHAMMER.Label.Benefit"}} - {{formInput systemFields.benefit enriched=enrichedBenefit value=system.benefit name="system.benefit" toggled=true}} + {{localize "OATHHAMMER.Label.Tenet"}} + {{formInput systemFields.tenet enriched=enrichedTenet value=system.tenet name="system.tenet" toggled=true}}
    - {{localize "OATHHAMMER.Label.Violation"}} - {{formInput systemFields.violation enriched=enrichedViolation value=system.violation name="system.violation" toggled=true}} + {{localize "OATHHAMMER.Label.Boon"}} + {{formInput systemFields.boon enriched=enrichedBoon value=system.boon name="system.boon" toggled=true}} +
    +
    + {{localize "OATHHAMMER.Label.Bane"}} + {{formInput systemFields.bane enriched=enrichedBane value=system.bane name="system.bane" toggled=true}}
    diff --git a/templates/item/shield-sheet.hbs b/templates/item/shield-sheet.hbs deleted file mode 100644 index ccd8e22..0000000 --- a/templates/item/shield-sheet.hbs +++ /dev/null @@ -1,21 +0,0 @@ -
    -
    - - {{formInput fields.name value=source.name}} -
    -
    -
    - {{formField systemFields.shieldBonus value=system.shieldBonus name="system.shieldBonus"}} - {{formField systemFields.equipped value=system.equipped name="system.equipped"}} -
    -
    - {{formField systemFields.encumbrance value=system.encumbrance name="system.encumbrance"}} - {{formField systemFields.cost value=system.cost name="system.cost"}} - {{formField systemFields.currency value=system.currency name="system.currency" localize=true}} -
    -
    -
    - {{localize "OATHHAMMER.Label.Description"}} - {{formInput systemFields.description enriched=enrichedDescription value=system.description name="system.description" toggled=true}} -
    -
    diff --git a/templates/item/weapon-sheet.hbs b/templates/item/weapon-sheet.hbs index 05c2c18..76f3212 100644 --- a/templates/item/weapon-sheet.hbs +++ b/templates/item/weapon-sheet.hbs @@ -5,17 +5,20 @@
    - {{formField systemFields.weaponType value=system.weaponType name="system.weaponType" localize=true}} - {{formField systemFields.damageFormula value=system.damageFormula name="system.damageFormula"}} - {{formField systemFields.damageType value=system.damageType name="system.damageType" localize=true}} - {{formField systemFields.attributeBonus value=system.attributeBonus name="system.attributeBonus" localize=true}} - {{formField systemFields.hands value=system.hands name="system.hands" localize=true}} - {{formField systemFields.range value=system.range name="system.range" localize=true}} + {{formField systemFields.proficiencyGroup value=system.proficiencyGroup name="system.proficiencyGroup" localize=true}} + {{formField systemFields.usesMight value=system.usesMight name="system.usesMight"}} + {{formField systemFields.damageMod value=system.damageMod name="system.damageMod"}} + {{formField systemFields.ap value=system.ap name="system.ap"}} + {{formField systemFields.reach value=system.reach name="system.reach"}} + {{formField systemFields.shortRange value=system.shortRange name="system.shortRange"}} + {{formField systemFields.longRange value=system.longRange name="system.longRange"}}
    - {{formField systemFields.properties value=system.properties name="system.properties"}} + {{formField systemFields.traits value=system.traits name="system.traits" localize=true}} + {{formField systemFields.slots value=system.slots name="system.slots"}} + {{formField systemFields.rarity value=system.rarity name="system.rarity"}} + {{formField systemFields.isMagic value=system.isMagic name="system.isMagic"}} {{formField systemFields.equipped value=system.equipped name="system.equipped"}} - {{formField systemFields.encumbrance value=system.encumbrance name="system.encumbrance"}} {{formField systemFields.cost value=system.cost name="system.cost"}} {{formField systemFields.currency value=system.currency name="system.currency" localize=true}}
    @@ -24,4 +27,15 @@ {{localize "OATHHAMMER.Label.Description"}} {{formInput systemFields.description enriched=enrichedDescription value=system.description name="system.description" toggled=true}} + {{#if system.isMagic}} +
    + {{localize "OATHHAMMER.Label.Enchantment"}} +
    + {{formField systemFields.magicQuality value=system.magicQuality name="system.magicQuality" localize=true}} + {{formField systemFields.isCursed value=system.isCursed name="system.isCursed"}} + {{formField systemFields.classRestriction value=system.classRestriction name="system.classRestriction"}} +
    + {{formInput systemFields.magicEffect enriched=enrichedMagicEffect value=system.magicEffect name="system.magicEffect" toggled=true}} +
    + {{/if}}