Iteam cleanup + less migration

This commit is contained in:
2026-03-07 19:18:03 +01:00
parent 97cd50ed12
commit c6f7a9e966
60 changed files with 1633 additions and 851 deletions

View File

@@ -0,0 +1,9 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
<rect width="64" height="64" rx="8" fill="#1a0a0a"/>
<!-- Eye outline -->
<ellipse cx="32" cy="32" rx="18" ry="11" fill="none" stroke="#c8a87a" stroke-width="3"/>
<!-- Pupil -->
<circle cx="32" cy="32" r="5" fill="#c8a87a"/>
<!-- Slash through eye -->
<line x1="12" y1="12" x2="52" y2="52" stroke="#cc2222" stroke-width="4" stroke-linecap="round"/>
</svg>

After

Width:  |  Height:  |  Size: 431 B

View File

@@ -0,0 +1,9 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
<rect width="64" height="64" rx="8" fill="#1a0a1a"/>
<!-- Swirling question mark path -->
<text x="18" y="46" font-family="serif" font-size="42" font-weight="bold" fill="#b07fd4">?</text>
<!-- Small spiral dots -->
<circle cx="50" cy="14" r="3" fill="#b07fd4" opacity="0.7"/>
<circle cx="44" cy="9" r="2" fill="#b07fd4" opacity="0.5"/>
<circle cx="56" cy="20" r="2" fill="#b07fd4" opacity="0.5"/>
</svg>

After

Width:  |  Height:  |  Size: 479 B

View File

@@ -0,0 +1,14 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
<rect width="64" height="64" rx="8" fill="#1a1500"/>
<!-- Head circle -->
<circle cx="32" cy="36" r="16" fill="none" stroke="#c8a87a" stroke-width="2.5"/>
<!-- Stars around head -->
<text x="6" y="18" font-family="sans-serif" font-size="14" fill="#f0c040"></text>
<text x="26" y="10" font-family="sans-serif" font-size="10" fill="#f0c040"></text>
<text x="46" y="16" font-family="sans-serif" font-size="12" fill="#f0c040"></text>
<!-- Dazed eyes (Xs) -->
<line x1="24" y1="32" x2="28" y2="36" stroke="#c8a87a" stroke-width="2" stroke-linecap="round"/>
<line x1="28" y1="32" x2="24" y2="36" stroke="#c8a87a" stroke-width="2" stroke-linecap="round"/>
<line x1="36" y1="32" x2="40" y2="36" stroke="#c8a87a" stroke-width="2" stroke-linecap="round"/>
<line x1="40" y1="32" x2="36" y2="36" stroke="#c8a87a" stroke-width="2" stroke-linecap="round"/>
</svg>

After

Width:  |  Height:  |  Size: 945 B

View File

@@ -0,0 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
<rect width="64" height="64" rx="8" fill="#0a0a1a"/>
<!-- Ear shape -->
<path d="M28 14 Q18 14 18 28 Q18 40 28 44 Q30 44 30 40 Q24 38 24 28 Q24 20 30 18 Q38 18 38 28 Q38 34 34 36 Q30 38 30 44 L34 44 Q40 40 40 28 Q40 14 28 14Z" fill="none" stroke="#c8a87a" stroke-width="2.5"/>
<!-- Slash -->
<line x1="14" y1="14" x2="50" y2="50" stroke="#cc2222" stroke-width="4" stroke-linecap="round"/>
</svg>

After

Width:  |  Height:  |  Size: 467 B

View File

@@ -0,0 +1,10 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
<rect width="64" height="64" rx="8" fill="#0f0a00"/>
<!-- Broken shield -->
<path d="M32 8 L52 16 L52 34 Q52 50 32 56 Q12 50 12 34 L12 16 Z" fill="none" stroke="#7a6040" stroke-width="3"/>
<!-- Crack down the middle -->
<path d="M32 8 L30 26 L34 34 L31 56" fill="none" stroke="#cc6622" stroke-width="2.5" stroke-linecap="round"/>
<!-- Down arrow -->
<line x1="32" y1="20" x2="32" y2="44" stroke="#cc2222" stroke-width="3" stroke-linecap="round"/>
<polyline points="24,36 32,44 40,36" fill="none" stroke="#cc2222" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 663 B

View File

@@ -0,0 +1,17 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
<rect width="64" height="64" rx="8" fill="#0a140a"/>
<!-- Skull -->
<circle cx="32" cy="26" r="14" fill="none" stroke="#6aaa44" stroke-width="2.5"/>
<rect x="24" y="36" width="16" height="8" rx="2" fill="none" stroke="#6aaa44" stroke-width="2.5"/>
<!-- Eye sockets -->
<circle cx="26" cy="24" r="3" fill="#6aaa44"/>
<circle cx="38" cy="24" r="3" fill="#6aaa44"/>
<!-- Jaw lines -->
<line x1="28" y1="40" x2="28" y2="44" stroke="#6aaa44" stroke-width="2"/>
<line x1="32" y1="40" x2="32" y2="44" stroke="#6aaa44" stroke-width="2"/>
<line x1="36" y1="40" x2="36" y2="44" stroke="#6aaa44" stroke-width="2"/>
<!-- Disease dots -->
<circle cx="14" cy="18" r="3" fill="#6aaa44" opacity="0.7"/>
<circle cx="50" cy="14" r="2" fill="#6aaa44" opacity="0.6"/>
<circle cx="52" cy="44" r="3" fill="#6aaa44" opacity="0.7"/>
</svg>

After

Width:  |  Height:  |  Size: 907 B

View File

@@ -0,0 +1,13 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
<rect width="64" height="64" rx="8" fill="#120a0a"/>
<!-- Broken arm / cracked fist -->
<!-- Arm -->
<path d="M14 48 Q18 36 28 32 L36 30 Q44 28 46 22 Q48 16 44 14" fill="none" stroke="#7a6040" stroke-width="3.5" stroke-linecap="round"/>
<!-- Fist/hand -->
<rect x="38" y="10" width="14" height="10" rx="4" fill="none" stroke="#7a6040" stroke-width="2.5"/>
<!-- Crack / break -->
<path d="M30 32 L34 28 L30 24" fill="none" stroke="#cc2222" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
<!-- Downward arrow indicating weakness -->
<line x1="52" y1="40" x2="52" y2="56" stroke="#cc2222" stroke-width="3" stroke-linecap="round"/>
<polyline points="46,50 52,56 58,50" fill="none" stroke="#cc2222" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 870 B

View File

@@ -0,0 +1,9 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
<rect width="64" height="64" rx="8" fill="#0a0a14"/>
<!-- Z letters (sleep/fatigue) -->
<text x="30" y="52" font-family="serif" font-size="28" font-weight="bold" fill="#8888cc" opacity="0.9">Z</text>
<text x="20" y="36" font-family="serif" font-size="20" font-weight="bold" fill="#8888cc" opacity="0.7">Z</text>
<text x="14" y="24" font-family="serif" font-size="13" font-weight="bold" fill="#8888cc" opacity="0.5">Z</text>
<!-- Drooping eyelid line -->
<path d="M8 54 Q32 46 56 54" fill="none" stroke="#6666aa" stroke-width="2" stroke-linecap="round"/>
</svg>

After

Width:  |  Height:  |  Size: 636 B

View File

@@ -0,0 +1,11 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
<rect width="64" height="64" rx="8" fill="#140a00"/>
<!-- Exclamation mark -->
<rect x="28" y="8" width="8" height="30" rx="4" fill="#dd8800"/>
<circle cx="32" cy="50" r="5" fill="#dd8800"/>
<!-- Fear lines radiating -->
<line x1="10" y1="8" x2="18" y2="18" stroke="#dd8800" stroke-width="2" stroke-linecap="round" opacity="0.6"/>
<line x1="54" y1="8" x2="46" y2="18" stroke="#dd8800" stroke-width="2" stroke-linecap="round" opacity="0.6"/>
<line x1="4" y1="24" x2="14" y2="26" stroke="#dd8800" stroke-width="2" stroke-linecap="round" opacity="0.4"/>
<line x1="60" y1="24" x2="50" y2="26" stroke="#dd8800" stroke-width="2" stroke-linecap="round" opacity="0.4"/>
</svg>

After

Width:  |  Height:  |  Size: 751 B

View File

@@ -0,0 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
<rect width="64" height="64" rx="8" fill="#140500"/>
<!-- Flame -->
<path d="M32 56 Q16 50 16 36 Q16 24 26 18 Q24 28 30 30 Q28 20 34 10 Q44 22 44 36 Q44 50 32 56Z" fill="#ee5500"/>
<path d="M32 56 Q22 50 22 38 Q22 30 28 26 Q27 32 31 34 Q30 26 34 20 Q40 28 40 38 Q40 50 32 56Z" fill="#ffaa00"/>
<path d="M32 52 Q26 48 26 40 Q26 35 30 32 Q29 36 32 38 Q35 36 34 32 Q38 35 38 40 Q38 48 32 52Z" fill="#ffee44"/>
</svg>

After

Width:  |  Height:  |  Size: 485 B

View File

@@ -0,0 +1,11 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
<rect width="64" height="64" rx="8" fill="#0a0e00"/>
<!-- Radiant star -->
<polygon points="32,8 35,24 50,20 38,30 46,44 32,36 18,44 26,30 14,20 29,24" fill="#f0d020" stroke="#c8a000" stroke-width="1.5" stroke-linejoin="round"/>
<!-- Rays -->
<line x1="32" y1="4" x2="32" y2="10" stroke="#f0d020" stroke-width="2" stroke-linecap="round" opacity="0.6"/>
<line x1="54" y1="12" x2="50" y2="17" stroke="#f0d020" stroke-width="2" stroke-linecap="round" opacity="0.6"/>
<line x1="60" y1="32" x2="54" y2="32" stroke="#f0d020" stroke-width="2" stroke-linecap="round" opacity="0.6"/>
<line x1="10" y1="12" x2="14" y2="17" stroke="#f0d020" stroke-width="2" stroke-linecap="round" opacity="0.6"/>
<line x1="4" y1="32" x2="10" y2="32" stroke="#f0d020" stroke-width="2" stroke-linecap="round" opacity="0.6"/>
</svg>

After

Width:  |  Height:  |  Size: 884 B

View File

@@ -0,0 +1,9 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
<rect width="64" height="64" rx="8" fill="#080818"/>
<!-- Ghostly figure outline (dotted) -->
<path d="M32 12 Q20 12 20 24 L20 50 Q20 52 22 50 Q24 48 26 50 Q28 52 30 50 Q32 48 34 50 Q36 52 38 50 Q40 48 42 50 Q44 52 44 50 L44 24 Q44 12 32 12Z"
fill="none" stroke="#8888cc" stroke-width="2.5" stroke-dasharray="4 3" opacity="0.8"/>
<!-- Ghost eyes (faint) -->
<circle cx="26" cy="28" r="3" fill="#8888cc" opacity="0.5"/>
<circle cx="38" cy="28" r="3" fill="#8888cc" opacity="0.5"/>
</svg>

After

Width:  |  Height:  |  Size: 568 B

View File

@@ -0,0 +1,11 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
<rect width="64" height="64" rx="8" fill="#080e08"/>
<!-- Potion drop shape -->
<path d="M32 8 Q32 8 32 8 L44 30 Q50 40 44 50 Q38 58 32 58 Q26 58 20 50 Q14 40 20 30 Z" fill="none" stroke="#44cc44" stroke-width="2.5"/>
<path d="M32 14 L42 32 Q46 40 42 48 Q38 54 32 54 Q26 54 22 48 Q18 40 22 32 Z" fill="#1a4a1a" opacity="0.6"/>
<!-- Skull inside drop -->
<circle cx="32" cy="36" r="8" fill="none" stroke="#44cc44" stroke-width="1.5"/>
<circle cx="29" cy="34" r="1.5" fill="#44cc44"/>
<circle cx="35" cy="34" r="1.5" fill="#44cc44"/>
<line x1="30" y1="40" x2="34" y2="40" stroke="#44cc44" stroke-width="1.5" stroke-linecap="round"/>
</svg>

After

Width:  |  Height:  |  Size: 717 B

View File

@@ -0,0 +1,14 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
<rect width="64" height="64" rx="8" fill="#0e0800"/>
<!-- Chain links -->
<!-- Left chain link -->
<ellipse cx="18" cy="26" rx="8" ry="5" fill="none" stroke="#aa8833" stroke-width="3" transform="rotate(-30 18 26)"/>
<!-- Right chain link -->
<ellipse cx="46" cy="26" rx="8" ry="5" fill="none" stroke="#aa8833" stroke-width="3" transform="rotate(30 46 26)"/>
<!-- Center link -->
<ellipse cx="32" cy="28" rx="8" ry="5" fill="none" stroke="#aa8833" stroke-width="3"/>
<!-- Shackle bar -->
<line x1="14" y1="42" x2="50" y2="42" stroke="#aa8833" stroke-width="3.5" stroke-linecap="round"/>
<circle cx="14" cy="42" r="5" fill="none" stroke="#aa8833" stroke-width="3"/>
<circle cx="50" cy="42" r="5" fill="none" stroke="#aa8833" stroke-width="3"/>
</svg>

After

Width:  |  Height:  |  Size: 834 B

View File

@@ -0,0 +1,11 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
<rect width="64" height="64" rx="8" fill="#0a0a14"/>
<!-- Lightning bolt striking head -->
<!-- Head -->
<circle cx="32" cy="42" r="14" fill="none" stroke="#9988aa" stroke-width="2.5"/>
<!-- Lightning bolt -->
<path d="M38 6 L26 28 L34 28 L22 56 L44 24 L36 24 Z" fill="#cc88ff" stroke="#aa44ee" stroke-width="1.5" stroke-linejoin="round"/>
<!-- Impact sparks -->
<line x1="42" y1="30" x2="48" y2="26" stroke="#cc88ff" stroke-width="2" stroke-linecap="round" opacity="0.7"/>
<line x1="44" y1="36" x2="52" y2="36" stroke="#cc88ff" stroke-width="2" stroke-linecap="round" opacity="0.5"/>
</svg>

After

Width:  |  Height:  |  Size: 671 B

View File

@@ -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;
}

22
gulpfile.js Normal file
View File

@@ -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)

View File

@@ -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"
}
}
}
}

135
less/actor-sheet.less Normal file
View File

@@ -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;
}
}

100
less/base.less Normal file
View File

@@ -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;
}

View File

@@ -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";

67
less/item-list.less Normal file
View File

@@ -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();
}
}

74
less/item-sheets.less Normal file
View File

@@ -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;
}
}

22
less/npc-sheet.less Normal file
View File

@@ -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;
}
}

51
less/variables.less Normal file
View File

@@ -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; }
}

View File

@@ -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"

View File

@@ -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

View File

@@ -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
}
}

View File

@@ -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
}
}

View File

@@ -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
}
}

View File

@@ -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
}

View File

@@ -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
}
}

View File

@@ -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 {

View File

@@ -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"

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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: "" }),

View File

@@ -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"]
}

View File

@@ -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

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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

View File

@@ -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"]
}

View File

@@ -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
}

View File

@@ -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
}
}

View File

@@ -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()

21
package.json Normal file
View File

@@ -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"
}

View File

@@ -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" },

View File

@@ -26,8 +26,8 @@
<li class="item-entry flexrow" data-item-id="{{weapon.id}}" data-item-uuid="{{weapon.uuid}}">
<img src="{{weapon.img}}" class="item-img" />
<span class="item-name">{{weapon.name}}</span>
<span class="item-detail">{{weapon.system.damageFormula}}</span>
<span class="item-detail">{{localize weapon.system.damageType}}</span>
<span class="item-detail">{{weapon.system.damageLabel}}</span>
<span class="item-detail">AP: {{weapon.system.ap}}</span>
{{formField weapon.system.schema.fields.equipped value=weapon.system.equipped name="system.equipped"}}
{{#unless ../isPlayMode}}
<a data-action="edit" data-item-id="{{weapon.id}}" data-item-uuid="{{weapon.uuid}}"><i class="fa-solid fa-edit"></i></a>
@@ -49,8 +49,9 @@
<li class="item-entry flexrow" data-item-id="{{armor.id}}" data-item-uuid="{{armor.uuid}}">
<img src="{{armor.img}}" class="item-img" />
<span class="item-name">{{armor.name}}</span>
<span class="item-detail">{{localize armor.system.armorType}}</span>
<span class="item-detail">AR: {{armor.system.armorRating}}</span>
<span class="item-detail">AV: {{armor.system.armorValue}}</span>
{{#if armor.system.penalty}}<span class="item-detail">{{armor.system.penalty}}</span>{{/if}}
{{formField armor.system.schema.fields.equipped value=armor.system.equipped name="system.equipped"}}
{{#unless ../isPlayMode}}
<a data-action="edit" data-item-id="{{armor.id}}" data-item-uuid="{{armor.uuid}}"><i class="fa-solid fa-edit"></i></a>
<a data-action="delete" data-item-id="{{armor.id}}" data-item-uuid="{{armor.uuid}}"><i class="fa-solid fa-trash"></i></a>
@@ -59,22 +60,7 @@
{{/each}}
</ul>
{{/if}}
{{#if shields.length}}
<ul class="item-list">
{{#each shields as |shield|}}
<li class="item-entry flexrow" data-item-id="{{shield.id}}" data-item-uuid="{{shield.uuid}}">
<img src="{{shield.img}}" class="item-img" />
<span class="item-name">{{shield.name}}</span>
<span class="item-detail">+{{shield.system.shieldBonus}}</span>
{{#unless ../isPlayMode}}
<a data-action="edit" data-item-id="{{shield.id}}" data-item-uuid="{{shield.uuid}}"><i class="fa-solid fa-edit"></i></a>
<a data-action="delete" data-item-id="{{shield.id}}" data-item-uuid="{{shield.uuid}}"><i class="fa-solid fa-trash"></i></a>
{{/unless}}
</li>
{{/each}}
</ul>
{{/if}}
{{#unless (or armors.length shields.length)}}
{{#unless armors.length}}
<p class="no-items">{{localize "OATHHAMMER.Label.NoArmor"}}</p>
{{/unless}}
</fieldset>

View File

@@ -7,8 +7,8 @@
<li class="item-entry flexrow" data-item-id="{{weapon.id}}" data-item-uuid="{{weapon.uuid}}">
<img src="{{weapon.img}}" class="item-img" />
<span class="item-name">{{weapon.name}}</span>
<span class="item-detail">{{weapon.system.damageFormula}}</span>
<span class="item-detail">{{localize weapon.system.damageType}}</span>
<span class="item-detail">{{weapon.system.damageLabel}}</span>
<span class="item-detail">AP: {{weapon.system.ap}}</span>
{{#unless ../isPlayMode}}
<a data-action="edit" data-item-id="{{weapon.id}}" data-item-uuid="{{weapon.uuid}}"><i class="fa-solid fa-edit"></i></a>
<a data-action="delete" data-item-id="{{weapon.id}}" data-item-uuid="{{weapon.uuid}}"><i class="fa-solid fa-trash"></i></a>

View File

@@ -7,8 +7,10 @@
<div class="align-top">
{{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}}
</div>
</div>
<fieldset>

View File

@@ -7,7 +7,7 @@
<div class="align-top">
{{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"}}
</div>
<div class="align-top">
{{formField systemFields.cost value=system.cost name="system.cost"}}

View File

@@ -6,12 +6,15 @@
<div class="flexrow">
<div class="align-top">
{{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}}
</div>
<div class="align-top">
{{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}}
</div>
@@ -20,4 +23,15 @@
<legend>{{localize "OATHHAMMER.Label.Description"}}</legend>
{{formInput systemFields.description enriched=enrichedDescription value=system.description name="system.description" toggled=true}}
</fieldset>
{{#if system.isMagic}}
<fieldset>
<legend>{{localize "OATHHAMMER.Label.Enchantment"}}</legend>
<div class="flexrow">
{{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"}}
</div>
{{formInput systemFields.magicEffect enriched=enrichedMagicEffect value=system.magicEffect name="system.magicEffect" toggled=true}}
</fieldset>
{{/if}}
</section>

View File

@@ -1,17 +0,0 @@
<section class="item-sheet-common">
<div class="header">
<img class="item-img" src="{{item.img}}" data-edit="img" data-action="editImage" data-tooltip="{{item.name}}" />
{{formInput fields.name value=source.name}}
</div>
<div class="flexrow">
<div class="align-top">
{{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"}}
</div>
</div>
<fieldset>
<legend>{{localize "OATHHAMMER.Label.Description"}}</legend>
{{formInput systemFields.description enriched=enrichedDescription value=system.description name="system.description" toggled=true}}
</fieldset>
</section>

View File

@@ -7,7 +7,11 @@
<div class="align-top">
{{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}}
</div>
<div class="align-top">
{{formField systemFields.cost value=system.cost name="system.cost"}}

View File

@@ -6,23 +6,22 @@
<div class="flexrow">
<div class="align-top">
{{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"}}
</div>
<div class="align-top">
<label>{{localize "OATHHAMMER.Label.Charges"}}</label>
<div class="shift-right">
{{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"}}
</div>
{{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}}
</div>
</div>
<fieldset>
<legend>{{localize "OATHHAMMER.Label.Description"}}</legend>
{{formInput systemFields.description enriched=enrichedDescription value=system.description name="system.description" toggled=true}}
<legend>{{localize "OATHHAMMER.Label.Effect"}}</legend>
{{formInput systemFields.effect enriched=enrichedEffect value=system.effect name="system.effect" toggled=true}}
</fieldset>
</section>

View File

@@ -10,11 +10,15 @@
</div>
</div>
<fieldset>
<legend>{{localize "OATHHAMMER.Label.Benefit"}}</legend>
{{formInput systemFields.benefit enriched=enrichedBenefit value=system.benefit name="system.benefit" toggled=true}}
<legend>{{localize "OATHHAMMER.Label.Tenet"}}</legend>
{{formInput systemFields.tenet enriched=enrichedTenet value=system.tenet name="system.tenet" toggled=true}}
</fieldset>
<fieldset>
<legend>{{localize "OATHHAMMER.Label.Violation"}}</legend>
{{formInput systemFields.violation enriched=enrichedViolation value=system.violation name="system.violation" toggled=true}}
<legend>{{localize "OATHHAMMER.Label.Boon"}}</legend>
{{formInput systemFields.boon enriched=enrichedBoon value=system.boon name="system.boon" toggled=true}}
</fieldset>
<fieldset>
<legend>{{localize "OATHHAMMER.Label.Bane"}}</legend>
{{formInput systemFields.bane enriched=enrichedBane value=system.bane name="system.bane" toggled=true}}
</fieldset>
</section>

View File

@@ -1,21 +0,0 @@
<section class="item-sheet-common">
<div class="header">
<img class="item-img" src="{{item.img}}" data-edit="img" data-action="editImage" data-tooltip="{{item.name}}" />
{{formInput fields.name value=source.name}}
</div>
<div class="flexrow">
<div class="align-top">
{{formField systemFields.shieldBonus value=system.shieldBonus name="system.shieldBonus"}}
{{formField systemFields.equipped value=system.equipped name="system.equipped"}}
</div>
<div class="align-top">
{{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}}
</div>
</div>
<fieldset>
<legend>{{localize "OATHHAMMER.Label.Description"}}</legend>
{{formInput systemFields.description enriched=enrichedDescription value=system.description name="system.description" toggled=true}}
</fieldset>
</section>

View File

@@ -5,17 +5,20 @@
</div>
<div class="flexrow">
<div class="align-top">
{{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"}}
</div>
<div class="align-top">
{{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}}
</div>
@@ -24,4 +27,15 @@
<legend>{{localize "OATHHAMMER.Label.Description"}}</legend>
{{formInput systemFields.description enriched=enrichedDescription value=system.description name="system.description" toggled=true}}
</fieldset>
{{#if system.isMagic}}
<fieldset>
<legend>{{localize "OATHHAMMER.Label.Enchantment"}}</legend>
<div class="flexrow">
{{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"}}
</div>
{{formInput systemFields.magicEffect enriched=enrichedMagicEffect value=system.magicEffect name="system.magicEffect" toggled=true}}
</fieldset>
{{/if}}
</section>