Actor sheet, WIP

This commit is contained in:
2026-03-07 22:47:29 +01:00
parent c6f7a9e966
commit 8f7f0169e4
27 changed files with 835 additions and 381 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 492 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 514 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 526 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 468 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 562 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 538 B

BIN
assets/icons/icon_oath.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 526 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 492 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 564 B

View File

@@ -11,8 +11,8 @@
--oh-font-secondary: "BlueDragon", "Palatino Linotype", serif;
--oh-font-body: "Calibri", "Segoe UI", sans-serif;
--oh-font-size: 0.82rem;
--oh-color-blue: #1a4a7a;
--oh-color-olive: #5a5a2a;
--oh-color-blue: #084a74;
--oh-color-olive: #535128;
--oh-color-gold: #c8a84b;
--oh-color-dark: #2a1a0a;
--oh-color-paper: #f5ead0;
@@ -20,7 +20,7 @@
--oh-logo: url("../assets/logos/official_logo_01.webp");
}
.application.dialog.oathhammer {
font-family: "Sherwood", "Palatino Linotype", serif;
font-family: "Calibri", "Segoe UI", sans-serif;
font-size: 0.82rem;
background-image: var(--oh-background-image);
background-repeat: no-repeat;
@@ -28,21 +28,22 @@
}
.oathhammer .character-content,
.oathhammer .npc-content {
font-family: "Sherwood", "Palatino Linotype", serif;
font-family: "Calibri", "Segoe UI", sans-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;
padding: 10px 20px;
}
.oathhammer .character-content nav.tabs [data-tab],
.oathhammer .npc-content nav.tabs [data-tab] {
color: #5a5a2a;
color: #535128;
}
.oathhammer .character-content nav.tabs [data-tab].active,
.oathhammer .npc-content nav.tabs [data-tab].active {
color: #1a4a7a;
color: #084a74;
}
.oathhammer .character-content input:disabled,
.oathhammer .npc-content input:disabled,
@@ -58,24 +59,24 @@
.oathhammer .npc-content select {
height: 1.5rem;
background-color: rgba(255, 255, 255, 0.3);
border-color: #1a4a7a;
border-color: #084a74;
color: #2a1a0a;
}
.oathhammer .character-content input[name="name"],
.oathhammer .npc-content input[name="name"] {
height: 2.5rem;
font-family: "BlueDragon", "Palatino Linotype", serif;
font-family: "Sherwood", "Palatino Linotype", serif;
font-size: calc(0.82rem * 1.2);
font-weight: bold;
border: none;
border-bottom: 2px solid #1a4a7a;
border-bottom: 2px solid #084a74;
background: transparent;
}
.oathhammer .character-content fieldset,
.oathhammer .npc-content fieldset {
margin-bottom: 4px;
border-radius: 4px;
border-color: #5a5a2a;
border-color: #535128;
}
.oathhammer .character-content legend,
.oathhammer .npc-content legend {
@@ -83,7 +84,7 @@
font-size: calc(0.82rem * 1.1);
font-weight: bold;
letter-spacing: 1px;
color: #1a4a7a;
color: #084a74;
}
.oathhammer .character-content label,
.oathhammer .npc-content label {
@@ -94,10 +95,53 @@
.oathhammer .tab {
padding: 4px;
}
.oathhammer .skills-container {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 8px;
}
.oathhammer .skills-container .skills-group {
margin: 0;
}
.oathhammer .skills-container .skills-group legend .attr-rank {
font-family: "Calibri", "Segoe UI", sans-serif;
font-size: calc(0.82rem * 0.85);
color: #535128;
}
.oathhammer .skills-container .skills-header,
.oathhammer .skills-container .skill-row {
display: grid;
grid-template-columns: 1fr 3rem 3rem;
align-items: center;
gap: 4px;
}
.oathhammer .skills-container .skills-header {
font-family: "BlueDragon", "Palatino Linotype", serif;
font-size: calc(0.82rem * 0.85);
font-weight: bold;
color: #084a74;
border-bottom: 1px solid #535128;
margin-bottom: 2px;
padding-bottom: 2px;
}
.oathhammer .skills-container .skill-row {
margin-bottom: 2px;
}
.oathhammer .skills-container .skill-row label.skill-name-col {
font-size: calc(0.82rem * 0.85);
}
.oathhammer .skills-container .skill-row .skill-rank-col input {
text-align: center;
}
.oathhammer .skills-container .skill-row .skill-total {
text-align: center;
font-weight: bold;
color: #084a74;
}
.oathhammer .actor-img {
height: 150px;
width: auto;
border: 2px solid #1a4a7a;
border: 2px solid #084a74;
border-radius: 4px;
cursor: pointer;
-o-object-fit: cover;
@@ -128,9 +172,13 @@
.oathhammer .character-main .character-resource {
display: flex;
align-items: center;
flex-wrap: nowrap;
gap: 4px;
margin-bottom: 2px;
}
.oathhammer .character-main .character-resource div.form-group {
display: contents;
}
.oathhammer .character-main .character-resource input {
min-width: 2.5rem;
max-width: 2.5rem;
@@ -226,15 +274,15 @@
align-items: center;
gap: 6px;
padding: 3px 4px;
border-bottom: 1px solid rgba(90, 90, 42, 0.2);
border-bottom: 1px solid rgba(83, 81, 40, 0.2);
}
.oathhammer .item-entry:hover {
background-color: rgba(26, 74, 122, 0.08);
background-color: rgba(8, 74, 116, 0.08);
}
.oathhammer .item-entry .item-img {
height: 24px;
width: 24px;
border: 1px solid #5a5a2a;
border: 1px solid #535128;
border-radius: 2px;
-o-object-fit: cover;
object-fit: cover;
@@ -246,13 +294,13 @@
}
.oathhammer .item-entry .item-detail {
font-size: calc(0.82rem * 0.9);
color: #5a5a2a;
color: #535128;
min-width: 4rem;
text-align: center;
}
.oathhammer .item-entry .item-type {
font-size: calc(0.82rem * 0.85);
color: #1a4a7a;
color: #084a74;
min-width: 6rem;
}
.oathhammer .item-entry a {
@@ -263,7 +311,7 @@
opacity: 1;
}
.oathhammer .item-entry a:hover {
color: #1a4a7a;
color: #084a74;
}
.oathhammer .no-items {
color: var(--color-dark-5);
@@ -273,7 +321,7 @@
}
.oathhammer .create-btn {
margin-left: 6px;
color: #1a4a7a;
color: #084a74;
opacity: 0.6;
transition: opacity 0.2s;
}
@@ -282,7 +330,8 @@
}
.oathhammer .item-sheet-common {
overflow: auto;
font-family: "Sherwood", "Palatino Linotype", serif;
padding: 10px 20px;
font-family: "Calibri", "Segoe UI", sans-serif;
font-size: 0.82rem;
background-image: var(--oh-background-image);
background-repeat: no-repeat;
@@ -294,53 +343,86 @@
gap: 8px;
margin-bottom: 8px;
padding-bottom: 4px;
border-bottom: 2px solid #1a4a7a;
border-bottom: 2px solid #084a74;
}
.oathhammer .item-sheet-common .header input[name="name"] {
font-family: "Sherwood", "Palatino Linotype", serif;
}
.oathhammer .item-sheet-common .item-img {
height: 52px;
width: 52px;
border: 2px solid #5a5a2a;
border: 2px solid #535128;
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 input:not([type="checkbox"]),
.oathhammer .item-sheet-common select {
height: 1.5rem;
background-color: rgba(255, 255, 255, 0.3);
border-color: #084a74;
color: #2a1a0a;
}
.oathhammer .item-sheet-common .form-group label {
.oathhammer .item-sheet-common input[type="checkbox"] {
width: auto;
height: auto;
background-color: transparent;
border-color: #084a74;
}
.oathhammer .item-sheet-common input[name="name"] {
height: 2.5rem;
font-family: "Sherwood", "Palatino Linotype", serif;
font-size: calc(0.82rem * 1.2);
font-weight: bold;
border: none;
border-bottom: 2px solid #084a74;
background: transparent;
}
.oathhammer .item-sheet-common label {
font-family: "BlueDragon", "Palatino Linotype", serif;
font-size: 0.82rem;
min-width: 9rem;
max-width: 9rem;
color: #2a1a0a;
}
.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 .form-group {
display: flex;
flex-direction: row;
align-items: center;
gap: 6px;
margin-bottom: 4px;
}
.oathhammer .item-sheet-common .form-group > label {
flex: 0 0 8rem;
font-family: "BlueDragon", "Palatino Linotype", serif;
font-size: 0.82rem;
color: #2a1a0a;
}
.oathhammer .item-sheet-common .form-group .form-fields {
flex: 1;
min-width: 0;
}
.oathhammer .item-sheet-common .form-group .form-fields input:not([type="checkbox"]),
.oathhammer .item-sheet-common .form-group .form-fields select {
width: 100%;
box-sizing: border-box;
}
.oathhammer .item-sheet-common .align-top {
flex: 1;
min-width: 0;
align-self: flex-start;
padding: 0.2rem;
min-width: 260px;
padding: 0 0.3rem;
}
.oathhammer .item-sheet-common .shift-right {
margin-left: 2rem;
}
.oathhammer .item-sheet-common fieldset {
margin-top: 6px;
border-color: #5a5a2a;
border-color: #535128;
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;
color: #084a74;
}

View File

@@ -16,6 +16,7 @@
},
"Tab": {
"Identity": "Identity",
"Skills": "Skills",
"Combat": "Combat",
"Magic": "Magic",
"Equipment": "Equipment",
@@ -29,6 +30,34 @@
"Intelligence": "Intelligence",
"Fate": "Fate"
},
"Skill": {
"Academics": "Academics",
"Acrobatics": "Acrobatics",
"AnimalHandling": "Animal Handling",
"Athletics": "Athletics",
"Brewing": "Brewing",
"Carpentry": "Carpentry",
"Defense": "Defense",
"Dexterity": "Dexterity",
"Diplomacy": "Diplomacy",
"Discipline": "Discipline",
"Fighting": "Fighting",
"Folklore": "Folklore",
"Fortune": "Fortune",
"Heal": "Heal",
"Leadership": "Leadership",
"Magic": "Magic",
"Masonry": "Masonry",
"Orientation": "Orientation",
"Perception": "Perception",
"Resilience": "Resilience",
"Ride": "Ride",
"Shooting": "Shooting",
"Smithing": "Smithing",
"Stealth": "Stealth",
"Survival": "Survival",
"Tracking": "Tracking"
},
"Lineage": {
"Dwarf": "Dwarf",
"Firbolg": "Firbolg",
@@ -180,7 +209,10 @@
"Enchantment": "Enchantment",
"Tenet": "Tenet",
"Boon": "Boon",
"Bane": "Bane"
"Bane": "Bane",
"Skill": "Skill",
"SkillRank": "Rank",
"TotalDice": "Total Dice"
},
"NewItem": {
"Weapon": "New Weapon",
@@ -190,6 +222,7 @@
},
"ToggleSheet": "Toggle Edit/Play Mode",
"Character": {
"FIELDS": {
"attributes": {
"label": "Attributes"
},
@@ -197,7 +230,11 @@
"label": "Grit"
},
"luck": {
"label": "Luck"
"label": "Luck",
"fields": {
"value": { "label": "Luck" },
"max": { "label": "Luck Max" }
}
},
"arcaneStress": {
"label": "Arcane Stress"
@@ -252,9 +289,20 @@
"copper": {
"label": "Copper"
}
},
"description": {
"label": "Description"
},
"notes": {
"label": "Notes"
},
"skills": {
"label": "Skills"
}
}
},
"NPC": {
"FIELDS": {
"attributes": {
"label": "Attributes"
},
@@ -275,23 +323,59 @@
},
"challengeRating": {
"label": "Challenge Rating"
},
"description": {
"label": "Description"
},
"notes": {
"label": "Notes"
}
}
},
"Weapon": {
"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",
"FIELDS": {
"proficiencyGroup": {
"label": "Proficiency Group"
},
"usesMight": {
"label": "Uses Might"
},
"damageMod": {
"label": "Damage Modifier"
},
"ap": {
"label": "Armor Penetration (AP)"
},
"reach": {
"label": "Reach (ft)"
},
"shortRange": {
"label": "Short Range (ft)"
},
"longRange": {
"label": "Long Range (ft)"
},
"traits": {
"label": "Traits"
},
"slots": {
"label": "Item Slots"
},
"rarity": {
"label": "Rarity"
},
"equipped": {
"label": "Equipped"
},
"cost": {
"label": "Cost"
},
"currency": {
"label": "Currency"
},
"description": {
"label": "Description"
},
"isMagic": {
"label": "Magic Item"
},
@@ -307,8 +391,10 @@
"classRestriction": {
"label": "Restriction"
}
}
},
"Armor": {
"FIELDS": {
"armorType": {
"label": "Armor Type"
},
@@ -351,8 +437,10 @@
"classRestriction": {
"label": "Restriction"
}
}
},
"Ammunition": {
"FIELDS": {
"ammoType": {
"label": "Ammunition Type"
},
@@ -368,8 +456,10 @@
"currency": {
"label": "Currency"
}
}
},
"Equipment": {
"FIELDS": {
"itemType": {
"label": "Category"
},
@@ -391,8 +481,10 @@
"currency": {
"label": "Currency"
}
}
},
"Spell": {
"FIELDS": {
"tradition": {
"label": "Tradition"
},
@@ -422,9 +514,14 @@
},
"isExalted": {
"label": "Exalted"
},
"effect": {
"label": "Effect"
}
}
},
"Miracle": {
"FIELDS": {
"divineTradition": {
"label": "Divine Tradition"
},
@@ -442,9 +539,14 @@
},
"spellSave": {
"label": "Spell Save"
},
"effect": {
"label": "Effect"
}
}
},
"MagicItem": {
"FIELDS": {
"itemType": {
"label": "Type"
},
@@ -471,9 +573,14 @@
},
"equipped": {
"label": "Equipped"
},
"effect": {
"label": "Effect"
}
}
},
"Ability": {
"FIELDS": {
"abilityType": {
"label": "Type"
},
@@ -485,6 +592,10 @@
},
"maxUses": {
"label": "Max Uses"
},
"description": {
"label": "Description"
}
}
},
"WeaponGroup": {
@@ -574,6 +685,43 @@
"violated": {
"label": "Violated"
}
},
"Oath": {
"FIELDS": {
"oathType": {
"label": "Oath"
},
"tenet": {
"label": "Tenet"
},
"boon": {
"label": "Boon"
},
"bane": {
"label": "Bane"
},
"violated": {
"label": "Violated"
}
}
}
},
"TYPES": {
"Item": {
"weapon": "Weapon",
"armor": "Armor",
"ammunition": "Ammunition",
"equipment": "Equipment",
"spell": "Spell",
"miracle": "Miracle",
"magic_item": "Magic Item",
"magic-item": "Magic Item",
"ability": "Ability",
"oath": "Oath"
},
"Actor": {
"character": "Character",
"npc": "NPC"
}
}
}

View File

@@ -41,9 +41,15 @@
.character-resource {
display: flex;
align-items: center;
flex-wrap: nowrap;
gap: 4px;
margin-bottom: 2px;
// formInput renders a <div class="form-group"> — flatten it so it doesn't break flex row
div.form-group {
display: contents;
}
input {
min-width: 2.5rem;
max-width: 2.5rem;

View File

@@ -29,7 +29,7 @@
// Global dialog styling
.application.dialog.oathhammer {
font-family: @font-primary;
font-family: @font-body;
font-size: @font-size-base;
.sheet-background();
}
@@ -37,11 +37,12 @@
// Shared actor content base
.oathhammer .character-content,
.oathhammer .npc-content {
font-family: @font-primary;
font-family: @font-body; // Calibri — standard text per design_rules.md
font-size: @font-size-base;
color: var(--color-dark-1);
.sheet-background();
overflow: auto;
padding: 10px 20px; // Inner margin so content clears the parchment border
nav.tabs [data-tab] {
color: @color-olive;
@@ -63,9 +64,10 @@
color: @color-dark;
}
// Character/NPC name — decorative title font
input[name="name"] {
height: 2.5rem;
font-family: @font-secondary;
font-family: @font-primary; // Sherwood — decorative title
font-size: @font-size-xl;
font-weight: bold;
border: none;
@@ -80,7 +82,7 @@
}
legend {
font-family: @font-secondary;
font-family: @font-secondary; // BlueDragon — section headers
font-size: @font-size-lg;
font-weight: bold;
letter-spacing: 1px;
@@ -88,7 +90,7 @@
}
label {
font-family: @font-secondary;
font-family: @font-secondary; // BlueDragon — UI labels
font-size: @font-size-base;
color: @color-dark;
}
@@ -98,3 +100,58 @@
.oathhammer .tab {
padding: 4px;
}
// ============================================================
// SKILLS TAB
// ============================================================
.oathhammer .skills-container {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 8px;
.skills-group {
margin: 0;
legend .attr-rank {
font-family: @font-body;
font-size: @font-size-sm;
color: @color-olive;
}
}
.skills-header,
.skill-row {
display: grid;
grid-template-columns: 1fr 3rem 3rem;
align-items: center;
gap: 4px;
}
.skills-header {
font-family: @font-secondary;
font-size: @font-size-sm;
font-weight: bold;
color: @color-blue;
border-bottom: 1px solid @color-olive;
margin-bottom: 2px;
padding-bottom: 2px;
}
.skill-row {
margin-bottom: 2px;
label.skill-name-col {
font-size: @font-size-sm;
}
.skill-rank-col input {
text-align: center;
}
.skill-total {
text-align: center;
font-weight: bold;
color: @color-blue;
}
}
}

View File

@@ -4,7 +4,8 @@
.oathhammer .item-sheet-common {
overflow: auto;
font-family: @font-primary;
padding: 10px 20px; // Inner margin so content clears the parchment border
font-family: @font-body; // Calibri — standard text per design_rules.md
font-size: @font-size-base;
.sheet-background();
@@ -15,6 +16,11 @@
margin-bottom: 8px;
padding-bottom: 4px;
border-bottom: 2px solid @color-blue;
// Item name input — decorative title font
input[name="name"] {
font-family: @font-primary; // Sherwood — decorative title
}
}
.item-img {
@@ -26,33 +32,70 @@
object-fit: cover;
}
.form-group {
display: flex;
flex: 1;
flex-direction: row;
align-items: center;
gap: 4px;
margin-bottom: 2px;
// Override Foundry dark-theme input/select backgrounds for parchment look
input:not([type="checkbox"]),
select {
height: 1.5rem;
background-color: @color-input-bg;
border-color: @color-blue;
color: @color-dark;
}
// Checkboxes: natural size, no custom background
input[type="checkbox"] {
width: auto;
height: auto;
background-color: transparent;
border-color: @color-blue;
}
input[name="name"] {
height: 2.5rem;
font-family: @font-primary;
font-size: @font-size-xl;
font-weight: bold;
border: none;
border-bottom: 2px solid @color-blue;
background: transparent;
}
label {
font-family: @font-secondary;
font-size: @font-size-base;
min-width: @label-min-width;
max-width: @label-min-width;
color: @color-dark;
}
select,
input {
text-align: left;
min-width: @input-min-width;
max-width: @input-max-width;
.form-group {
display: flex;
flex-direction: row;
align-items: center;
gap: 6px;
margin-bottom: 4px;
& > label {
flex: 0 0 8rem; // Fixed label width, no grow/shrink
font-family: @font-secondary;
font-size: @font-size-base;
color: @color-dark;
}
.form-fields {
flex: 1;
min-width: 0;
input:not([type="checkbox"]),
select {
width: 100%;
box-sizing: border-box;
}
}
}
.align-top {
flex: 1;
min-width: 0;
align-self: flex-start;
padding: 0.2rem;
min-width: 260px;
padding: 0 0.3rem;
}
.shift-right {

View File

@@ -8,16 +8,18 @@
@font-body: "Calibri", "Segoe UI", sans-serif;
@font-size-base: 0.82rem;
// Colors
@color-blue: #1a4a7a;
@color-olive: #5a5a2a;
// Colors — from design_rules.md (CMYK converted to RGB)
// Blue: CMYK(94, 42, 9, 50) → rgb(8, 74, 116)
// Olive: CMYK(26, 28, 64, 56) → rgb(83, 81, 40)
@color-blue: #084a74;
@color-olive: #535128;
@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);
// Derived — using LESS fade() to keep colors in sync with variables
@color-olive-faint: fade(@color-olive, 20%);
@color-blue-hover: fade(@color-blue, 8%);
@color-input-bg: rgba(255, 255, 255, 0.3);
@color-disabled-bg: rgba(0, 0, 0, 0.08);

View File

@@ -1,4 +1,5 @@
import OathHammerActorSheet from "./base-actor-sheet.mjs"
import { SYSTEM } from "../../config/system.mjs"
export default class OathHammerCharacterSheet extends OathHammerActorSheet {
/** @override */
@@ -21,27 +22,14 @@ export default class OathHammerCharacterSheet extends OathHammerActorSheet {
/** @override */
static PARTS = {
main: {
template: "systems/fvtt-oath-hammer/templates/actor/character-sheet.hbs",
},
tabs: {
template: "templates/generic/tab-navigation.hbs",
},
identity: {
template: "systems/fvtt-oath-hammer/templates/actor/character-identity.hbs",
},
combat: {
template: "systems/fvtt-oath-hammer/templates/actor/character-combat.hbs",
},
magic: {
template: "systems/fvtt-oath-hammer/templates/actor/character-magic.hbs",
},
equipment: {
template: "systems/fvtt-oath-hammer/templates/actor/character-equipment.hbs",
},
notes: {
template: "systems/fvtt-oath-hammer/templates/actor/character-notes.hbs",
},
main: { template: "systems/fvtt-oath-hammer/templates/actor/character-sheet.hbs" },
tabs: { template: "templates/generic/tab-navigation.hbs" },
identity: { template: "systems/fvtt-oath-hammer/templates/actor/character-identity.hbs" },
skills: { template: "systems/fvtt-oath-hammer/templates/actor/character-skills.hbs" },
combat: { template: "systems/fvtt-oath-hammer/templates/actor/character-combat.hbs" },
magic: { template: "systems/fvtt-oath-hammer/templates/actor/character-magic.hbs" },
equipment: { template: "systems/fvtt-oath-hammer/templates/actor/character-equipment.hbs" },
notes: { template: "systems/fvtt-oath-hammer/templates/actor/character-notes.hbs" },
}
/** @override */
@@ -52,6 +40,7 @@ export default class OathHammerCharacterSheet extends OathHammerActorSheet {
#getTabs() {
const tabs = {
identity: { id: "identity", group: "sheet", icon: "fa-solid fa-person", label: "OATHHAMMER.Tab.Identity" },
skills: { id: "skills", group: "sheet", icon: "fa-solid fa-scroll", label: "OATHHAMMER.Tab.Skills" },
combat: { id: "combat", group: "sheet", icon: "fa-solid fa-swords", label: "OATHHAMMER.Tab.Combat" },
magic: { id: "magic", group: "sheet", icon: "fa-solid fa-wand-magic-sparkles", label: "OATHHAMMER.Tab.Magic" },
equipment: { id: "equipment", group: "sheet", icon: "fa-solid fa-backpack", label: "OATHHAMMER.Tab.Equipment" },
@@ -82,6 +71,33 @@ export default class OathHammerCharacterSheet extends OathHammerActorSheet {
context.abilities = doc.itemTypes.ability
context.oaths = doc.itemTypes.oath
break
case "skills": {
context.tab = context.tabs.skills
const sys = doc.system
const skillSchemaFields = doc.system.schema.fields.skills.fields
const attrRanks = {
might: sys.attributes.might.rank,
toughness: sys.attributes.toughness.rank,
agility: sys.attributes.agility.rank,
willpower: sys.attributes.willpower.rank,
intelligence: sys.attributes.intelligence.rank,
fate: sys.attributes.fate.rank,
}
context.skillGroups = Object.entries(SYSTEM.SKILLS_BY_ATTRIBUTE).map(([attr, skillKeys]) => ({
attribute: attr,
label: `OATHHAMMER.Attribute.${attr.charAt(0).toUpperCase()}${attr.slice(1)}`,
attrRank: attrRanks[attr],
skillData: skillKeys.map(skillKey => ({
key: skillKey,
label: SYSTEM.SKILLS[skillKey].label,
rank: sys.skills[skillKey].rank,
name: `system.skills.${skillKey}.rank`,
total: attrRanks[attr] + sys.skills[skillKey].rank,
field: skillSchemaFields[skillKey].fields.rank,
}))
}))
break
}
case "combat":
context.tab = context.tabs.combat
context.weapons = doc.itemTypes.weapon

View File

@@ -279,6 +279,46 @@ export const STATUS_EFFECTS = [
export const ATTRIBUTE_RANK_CHOICES = { 1: "1", 2: "2", 3: "3", 4: "4" }
// 26 skills: each mapped to its primary attribute. "magic" is dual (WP=miracles, INT=spells).
export const SKILLS = {
academics: { id: "academics", attribute: "intelligence", label: "OATHHAMMER.Skill.Academics" },
acrobatics: { id: "acrobatics", attribute: "agility", label: "OATHHAMMER.Skill.Acrobatics" },
animalHandling:{ id: "animalHandling", attribute: "willpower", label: "OATHHAMMER.Skill.AnimalHandling" },
athletics: { id: "athletics", attribute: "might", label: "OATHHAMMER.Skill.Athletics" },
brewing: { id: "brewing", attribute: "intelligence", label: "OATHHAMMER.Skill.Brewing" },
carpentry: { id: "carpentry", attribute: "agility", label: "OATHHAMMER.Skill.Carpentry" },
defense: { id: "defense", attribute: "agility", label: "OATHHAMMER.Skill.Defense" },
dexterity: { id: "dexterity", attribute: "agility", label: "OATHHAMMER.Skill.Dexterity" },
diplomacy: { id: "diplomacy", attribute: "willpower", label: "OATHHAMMER.Skill.Diplomacy" },
discipline: { id: "discipline", attribute: "willpower", label: "OATHHAMMER.Skill.Discipline" },
fighting: { id: "fighting", attribute: "might", label: "OATHHAMMER.Skill.Fighting" },
folklore: { id: "folklore", attribute: "fate", label: "OATHHAMMER.Skill.Folklore" },
fortune: { id: "fortune", attribute: "fate", label: "OATHHAMMER.Skill.Fortune" },
heal: { id: "heal", attribute: "intelligence", label: "OATHHAMMER.Skill.Heal" },
leadership: { id: "leadership", attribute: "willpower", label: "OATHHAMMER.Skill.Leadership" },
magic: { id: "magic", attribute: "willpower", label: "OATHHAMMER.Skill.Magic" },
masonry: { id: "masonry", attribute: "might", label: "OATHHAMMER.Skill.Masonry" },
orientation: { id: "orientation", attribute: "intelligence", label: "OATHHAMMER.Skill.Orientation" },
perception: { id: "perception", attribute: "willpower", label: "OATHHAMMER.Skill.Perception" },
resilience: { id: "resilience", attribute: "toughness", label: "OATHHAMMER.Skill.Resilience" },
ride: { id: "ride", attribute: "agility", label: "OATHHAMMER.Skill.Ride" },
shooting: { id: "shooting", attribute: "agility", label: "OATHHAMMER.Skill.Shooting" },
smithing: { id: "smithing", attribute: "might", label: "OATHHAMMER.Skill.Smithing" },
stealth: { id: "stealth", attribute: "agility", label: "OATHHAMMER.Skill.Stealth" },
survival: { id: "survival", attribute: "willpower", label: "OATHHAMMER.Skill.Survival" },
tracking: { id: "tracking", attribute: "intelligence", label: "OATHHAMMER.Skill.Tracking" },
}
// Skills grouped by primary attribute (for sheet display)
export const SKILLS_BY_ATTRIBUTE = {
might: ["athletics", "fighting", "masonry", "smithing"],
toughness: ["resilience"],
agility: ["acrobatics", "carpentry", "defense", "dexterity", "ride", "shooting", "stealth"],
willpower: ["animalHandling", "diplomacy", "discipline", "leadership", "magic", "perception", "survival"],
intelligence: ["academics", "brewing", "heal", "orientation", "tracking"],
fate: ["folklore", "fortune"],
}
export const ASCII = `
·················································
: ___ _ _ _ _ :
@@ -293,6 +333,8 @@ export const ASCII = `
export const SYSTEM = {
id: SYSTEM_ID,
ATTRIBUTES,
SKILLS,
SKILLS_BY_ATTRIBUTE,
LINEAGE_CHOICES,
CLASS_CHOICES,
OATH_TYPES,

View File

@@ -21,13 +21,48 @@ export default class OathHammerCharacter extends foundry.abstract.TypeDataModel
fate: attributeField()
})
// Skills: 26 skills each with a rank (04). Grit.max = resilience.rank + toughness.rank.
const skillField = () => new fields.SchemaField({
rank: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0, max: 4 })
})
schema.skills = new fields.SchemaField({
academics: skillField(),
acrobatics: skillField(),
animalHandling:skillField(),
athletics: skillField(),
brewing: skillField(),
carpentry: skillField(),
defense: skillField(),
dexterity: skillField(),
diplomacy: skillField(),
discipline: skillField(),
fighting: skillField(),
folklore: skillField(),
fortune: skillField(),
heal: skillField(),
leadership: skillField(),
magic: skillField(),
masonry: skillField(),
orientation: skillField(),
perception: skillField(),
resilience: skillField(),
ride: skillField(),
shooting: skillField(),
smithing: skillField(),
stealth: skillField(),
survival: skillField(),
tracking: skillField(),
})
schema.grit = new fields.SchemaField({
value: new fields.NumberField({ ...requiredInteger, initial: 2, min: 0 }),
max: new fields.NumberField({ ...requiredInteger, initial: 2, min: 0 })
})
// Luck.max is derived from fate.rank; resets at session start.
schema.luck = new fields.SchemaField({
value: new fields.NumberField({ ...requiredInteger, initial: 1, min: 0 })
value: new fields.NumberField({ ...requiredInteger, initial: 1, min: 0 }),
max: new fields.NumberField({ ...requiredInteger, initial: 1, min: 0 })
})
schema.arcaneStress = new fields.SchemaField({
@@ -77,7 +112,11 @@ export default class OathHammerCharacter extends foundry.abstract.TypeDataModel
prepareDerivedData() {
super.prepareDerivedData()
this.grit.max = this.attributes.might.rank + this.attributes.toughness.rank
// Grit max = Resilience skill rank + Toughness attribute rank (rulebook p.5)
this.grit.max = this.skills.resilience.rank + this.attributes.toughness.rank
// Luck max = Fate rank; restores at session start
this.luck.max = this.attributes.fate.rank
// Defense score = 10 + Agility + Armor Rating + bonus
this.defense.value = 10 + this.attributes.agility.rank + this.defense.armorRating + this.defense.bonus
}
}

View File

@@ -1,4 +1,4 @@
<section data-tab="combat" class="tab">
<section data-tab="combat" class="tab {{tab.cssClass}}">
<fieldset>
<legend>{{localize "OATHHAMMER.Label.Defense"}}</legend>
<div class="flexrow">

View File

@@ -1,4 +1,4 @@
<section data-tab="equipment" class="tab">
<section data-tab="equipment" class="tab {{tab.cssClass}}">
<fieldset>
<legend>{{localize "OATHHAMMER.Label.Equipment"}}
{{#unless isPlayMode}}<a data-action="createEquipment" class="create-btn"><i class="fa-solid fa-plus"></i></a>{{/unless}}

View File

@@ -1,4 +1,4 @@
<section data-tab="identity" class="tab">
<section data-tab="identity" class="tab {{tab.cssClass}}">
<fieldset>
<legend>{{localize "OATHHAMMER.Label.Biodata"}}</legend>
<div class="flexrow">

View File

@@ -1,4 +1,4 @@
<section data-tab="magic" class="tab">
<section data-tab="magic" class="tab {{tab.cssClass}}">
<fieldset>
<legend>{{localize "OATHHAMMER.Label.ArcaneStress"}}</legend>
<div class="flexrow">

View File

@@ -1,4 +1,4 @@
<section data-tab="notes" class="tab">
<section data-tab="notes" class="tab {{tab.cssClass}}">
<fieldset>
<legend>{{localize "OATHHAMMER.Label.Description"}}</legend>
{{formInput systemFields.description enriched=enrichedDescription value=system.description name="system.description" toggled=true}}

View File

@@ -16,6 +16,8 @@
<div class="flexrow character-resource">
<span class="resource-label">{{localize "OATHHAMMER.Label.Luck"}}</span>
{{formInput systemFields.luck.fields.value value=system.luck.value name="system.luck.value" disabled=isPlayMode}}
<span>/</span>
{{formInput systemFields.luck.fields.max value=system.luck.max name="system.luck.max" disabled=isPlayMode}}
</div>
<div class="flexrow character-resource">
<span class="resource-label">{{localize "OATHHAMMER.Label.Defense"}}</span>
@@ -40,7 +42,7 @@
{{#each system.attributes as |attr key|}}
<div class="attribute-box">
<label>{{localize (concat "OATHHAMMER.Attribute." (capitalize key))}}</label>
{{formInput (lookup ../systemFields.attributes.fields key).fields.rank value=attr.rank name=(concat "system.attributes." key ".rank") disabled=../isPlayMode}}
<input type="number" name="system.attributes.{{key}}.rank" value="{{attr.rank}}" min="1" max="4" {{#if ../isPlayMode}}disabled{{/if}}>
</div>
{{/each}}
</div>

View File

@@ -0,0 +1,28 @@
<section data-tab="skills" class="tab {{tab.cssClass}}">
<div class="skills-container">
{{#each skillGroups as |group|}}
<fieldset class="skills-group">
<legend>
{{localize group.label}}
<span class="attr-rank">({{group.attrRank}})</span>
</legend>
<div class="skills-list">
<div class="skills-header">
<span class="skill-name-col">{{localize "OATHHAMMER.Label.Skill"}}</span>
<span class="skill-rank-col">{{localize "OATHHAMMER.Label.SkillRank"}}</span>
<span class="skill-total-col">{{localize "OATHHAMMER.Label.TotalDice"}}</span>
</div>
{{#each group.skillData as |skill|}}
<div class="skill-row">
<label class="skill-name-col">{{localize skill.label}}</label>
<div class="skill-rank-col">
{{formInput skill.field value=skill.rank name=skill.name disabled=../../isPlayMode}}
</div>
<span class="skill-total-col skill-total">{{skill.total}}</span>
</div>
{{/each}}
</div>
</fieldset>
{{/each}}
</div>
</section>

View File

@@ -5,20 +5,14 @@
</div>
<div class="flexrow">
<div class="align-top">
{{formField systemFields.piety value=system.piety name="system.piety"}}
{{formField systemFields.castingTime value=system.castingTime name="system.castingTime"}}
{{formField systemFields.divineTradition value=system.divineTradition name="system.divineTradition" localize=true}}
{{formField systemFields.difficultyValue value=system.difficultyValue name="system.difficultyValue"}}
{{formField systemFields.range value=system.range name="system.range"}}
{{formField systemFields.duration value=system.duration name="system.duration"}}
{{formField systemFields.spellSave value=system.spellSave name="system.spellSave"}}
</div>
<div class="align-top">
<label>{{localize "OATHHAMMER.Label.Components"}}</label>
<div class="shift-right">
{{formField systemFields.components.fields.verbal value=system.components.verbal name="system.components.verbal"}}
{{formField systemFields.components.fields.somatic value=system.components.somatic name="system.components.somatic"}}
{{formField systemFields.components.fields.material value=system.components.material name="system.components.material"}}
</div>
{{formField systemFields.materialComponent value=system.materialComponent name="system.materialComponent"}}
{{formField systemFields.savingThrow value=system.savingThrow name="system.savingThrow"}}
{{formField systemFields.isRitual value=system.isRitual name="system.isRitual"}}
</div>
</div>
<fieldset>

View File

@@ -6,22 +6,17 @@
<div class="flexrow">
<div class="align-top">
{{formField systemFields.tradition value=system.tradition name="system.tradition" localize=true}}
{{formField systemFields.level value=system.level name="system.level"}}
{{formField systemFields.arcaneStress value=system.arcaneStress name="system.arcaneStress"}}
{{formField systemFields.castingTime value=system.castingTime name="system.castingTime"}}
{{formField systemFields.difficultyValue value=system.difficultyValue name="system.difficultyValue"}}
{{formField systemFields.range value=system.range name="system.range"}}
{{formField systemFields.duration value=system.duration name="system.duration"}}
{{formField systemFields.spellSave value=system.spellSave name="system.spellSave"}}
</div>
<div class="align-top">
<label>{{localize "OATHHAMMER.Label.Components"}}</label>
<div class="shift-right">
{{formField systemFields.components.fields.verbal value=system.components.verbal name="system.components.verbal"}}
{{formField systemFields.components.fields.somatic value=system.components.somatic name="system.components.somatic"}}
{{formField systemFields.components.fields.material value=system.components.material name="system.components.material"}}
</div>
{{formField systemFields.materialComponent value=system.materialComponent name="system.materialComponent"}}
{{formField systemFields.savingThrow value=system.savingThrow name="system.savingThrow"}}
{{formField systemFields.enhancement value=system.enhancement name="system.enhancement"}}
{{formField systemFields.isRitual value=system.isRitual name="system.isRitual"}}
{{formField systemFields.isMagicMissile value=system.isMagicMissile name="system.isMagicMissile"}}
{{formField systemFields.isExalted value=system.isExalted name="system.isExalted"}}
{{formField systemFields.element value=system.element name="system.element" localize=true}}
{{formField systemFields.runeType value=system.runeType name="system.runeType" localize=true}}
</div>
</div>
<fieldset>