diff --git a/assets/icons/icon_ability.webp b/assets/icons/icon_ability.webp
new file mode 100644
index 0000000..52bf674
Binary files /dev/null and b/assets/icons/icon_ability.webp differ
diff --git a/assets/icons/icon_ammunition.webp b/assets/icons/icon_ammunition.webp
new file mode 100644
index 0000000..408df55
Binary files /dev/null and b/assets/icons/icon_ammunition.webp differ
diff --git a/assets/icons/icon_armor.webp b/assets/icons/icon_armor.webp
new file mode 100644
index 0000000..6b14549
Binary files /dev/null and b/assets/icons/icon_armor.webp differ
diff --git a/assets/icons/icon_equipment.webp b/assets/icons/icon_equipment.webp
new file mode 100644
index 0000000..7be2a2d
Binary files /dev/null and b/assets/icons/icon_equipment.webp differ
diff --git a/assets/icons/icon_magic_item.webp b/assets/icons/icon_magic_item.webp
new file mode 100644
index 0000000..7eae212
Binary files /dev/null and b/assets/icons/icon_magic_item.webp differ
diff --git a/assets/icons/icon_miracle.webp b/assets/icons/icon_miracle.webp
new file mode 100644
index 0000000..4f63da8
Binary files /dev/null and b/assets/icons/icon_miracle.webp differ
diff --git a/assets/icons/icon_oath.webp b/assets/icons/icon_oath.webp
new file mode 100644
index 0000000..40c6098
Binary files /dev/null and b/assets/icons/icon_oath.webp differ
diff --git a/assets/icons/icon_spell.webp b/assets/icons/icon_spell.webp
new file mode 100644
index 0000000..6c631b2
Binary files /dev/null and b/assets/icons/icon_spell.webp differ
diff --git a/assets/icons/icon_weapon.webp b/assets/icons/icon_weapon.webp
new file mode 100644
index 0000000..7b5a985
Binary files /dev/null and b/assets/icons/icon_weapon.webp differ
diff --git a/css/fvtt-oath-hammer.css b/css/fvtt-oath-hammer.css
index 37bccd6..991892a 100644
--- a/css/fvtt-oath-hammer.css
+++ b/css/fvtt-oath-hammer.css
@@ -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;
}
diff --git a/lang/en.json b/lang/en.json
index 857ce6f..9cb5d3b 100644
--- a/lang/en.json
+++ b/lang/en.json
@@ -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,301 +222,380 @@
},
"ToggleSheet": "Toggle Edit/Play Mode",
"Character": {
- "attributes": {
- "label": "Attributes"
- },
- "grit": {
- "label": "Grit"
- },
- "luck": {
- "label": "Luck"
- },
- "arcaneStress": {
- "label": "Arcane Stress"
- },
- "movement": {
- "label": "Movement"
- },
- "defense": {
- "label": "Defense"
- },
- "experience": {
- "label": "Experience"
- },
- "biodata": {
- "label": "Background",
- "lineage": {
- "label": "Lineage"
+ "FIELDS": {
+ "attributes": {
+ "label": "Attributes"
},
- "class": {
- "label": "Class"
+ "grit": {
+ "label": "Grit"
},
- "age": {
- "label": "Age"
+ "luck": {
+ "label": "Luck",
+ "fields": {
+ "value": { "label": "Luck" },
+ "max": { "label": "Luck Max" }
+ }
},
- "gender": {
- "label": "Gender"
+ "arcaneStress": {
+ "label": "Arcane Stress"
},
- "height": {
- "label": "Height"
+ "movement": {
+ "label": "Movement"
},
- "weight": {
- "label": "Weight"
+ "defense": {
+ "label": "Defense"
},
- "eyes": {
- "label": "Eye Color"
+ "experience": {
+ "label": "Experience"
},
- "hair": {
- "label": "Hair Color"
+ "biodata": {
+ "label": "Background",
+ "lineage": {
+ "label": "Lineage"
+ },
+ "class": {
+ "label": "Class"
+ },
+ "age": {
+ "label": "Age"
+ },
+ "gender": {
+ "label": "Gender"
+ },
+ "height": {
+ "label": "Height"
+ },
+ "weight": {
+ "label": "Weight"
+ },
+ "eyes": {
+ "label": "Eye Color"
+ },
+ "hair": {
+ "label": "Hair Color"
+ },
+ "alignment": {
+ "label": "Alignment"
+ }
},
- "alignment": {
- "label": "Alignment"
- }
- },
- "currency": {
- "label": "Currency",
- "gold": {
- "label": "Gold"
+ "currency": {
+ "label": "Currency",
+ "gold": {
+ "label": "Gold"
+ },
+ "silver": {
+ "label": "Silver"
+ },
+ "copper": {
+ "label": "Copper"
+ }
},
- "silver": {
- "label": "Silver"
+ "description": {
+ "label": "Description"
},
- "copper": {
- "label": "Copper"
+ "notes": {
+ "label": "Notes"
+ },
+ "skills": {
+ "label": "Skills"
}
}
},
"NPC": {
- "attributes": {
- "label": "Attributes"
- },
- "grit": {
- "label": "Grit"
- },
- "defense": {
- "label": "Defense"
- },
- "movement": {
- "label": "Movement"
- },
- "attackBonus": {
- "label": "Attack Bonus"
- },
- "damageBonus": {
- "label": "Damage Bonus"
- },
- "challengeRating": {
- "label": "Challenge Rating"
+ "FIELDS": {
+ "attributes": {
+ "label": "Attributes"
+ },
+ "grit": {
+ "label": "Grit"
+ },
+ "defense": {
+ "label": "Defense"
+ },
+ "movement": {
+ "label": "Movement"
+ },
+ "attackBonus": {
+ "label": "Attack Bonus"
+ },
+ "damageBonus": {
+ "label": "Damage Bonus"
+ },
+ "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",
- "isMagic": {
- "label": "Magic Item"
- },
- "magicQuality": {
- "label": "Quality"
- },
- "isCursed": {
- "label": "Cursed"
- },
- "magicEffect": {
- "label": "Enchantment"
- },
- "classRestriction": {
- "label": "Restriction"
+ "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"
+ },
+ "magicQuality": {
+ "label": "Quality"
+ },
+ "isCursed": {
+ "label": "Cursed"
+ },
+ "magicEffect": {
+ "label": "Enchantment"
+ },
+ "classRestriction": {
+ "label": "Restriction"
+ }
}
},
"Armor": {
- "armorType": {
- "label": "Armor Type"
- },
- "armorValue": {
- "label": "Armor Value (AV)"
- },
- "penalty": {
- "label": "Penalty"
- },
- "slots": {
- "label": "Slots"
- },
- "traits": {
- "label": "Traits"
- },
- "rarity": {
- "label": "Rarity"
- },
- "equipped": {
- "label": "Equipped"
- },
- "cost": {
- "label": "Cost"
- },
- "currency": {
- "label": "Currency"
- },
- "isMagic": {
- "label": "Magic Item"
- },
- "magicQuality": {
- "label": "Quality"
- },
- "isCursed": {
- "label": "Cursed"
- },
- "magicEffect": {
- "label": "Enchantment"
- },
- "classRestriction": {
- "label": "Restriction"
+ "FIELDS": {
+ "armorType": {
+ "label": "Armor Type"
+ },
+ "armorValue": {
+ "label": "Armor Value (AV)"
+ },
+ "penalty": {
+ "label": "Penalty"
+ },
+ "slots": {
+ "label": "Slots"
+ },
+ "traits": {
+ "label": "Traits"
+ },
+ "rarity": {
+ "label": "Rarity"
+ },
+ "equipped": {
+ "label": "Equipped"
+ },
+ "cost": {
+ "label": "Cost"
+ },
+ "currency": {
+ "label": "Currency"
+ },
+ "isMagic": {
+ "label": "Magic Item"
+ },
+ "magicQuality": {
+ "label": "Quality"
+ },
+ "isCursed": {
+ "label": "Cursed"
+ },
+ "magicEffect": {
+ "label": "Enchantment"
+ },
+ "classRestriction": {
+ "label": "Restriction"
+ }
}
},
"Ammunition": {
- "ammoType": {
- "label": "Ammunition Type"
- },
- "quantity": {
- "label": "Quantity"
- },
- "rarity": {
- "label": "Rarity"
- },
- "cost": {
- "label": "Cost"
- },
- "currency": {
- "label": "Currency"
+ "FIELDS": {
+ "ammoType": {
+ "label": "Ammunition Type"
+ },
+ "quantity": {
+ "label": "Quantity"
+ },
+ "rarity": {
+ "label": "Rarity"
+ },
+ "cost": {
+ "label": "Cost"
+ },
+ "currency": {
+ "label": "Currency"
+ }
}
},
"Equipment": {
- "itemType": {
- "label": "Category"
- },
- "quantity": {
- "label": "Quantity"
- },
- "slots": {
- "label": "Slots"
- },
- "rarity": {
- "label": "Rarity"
- },
- "lightRadius": {
- "label": "Light Radius (ft)"
- },
- "cost": {
- "label": "Cost"
- },
- "currency": {
- "label": "Currency"
+ "FIELDS": {
+ "itemType": {
+ "label": "Category"
+ },
+ "quantity": {
+ "label": "Quantity"
+ },
+ "slots": {
+ "label": "Slots"
+ },
+ "rarity": {
+ "label": "Rarity"
+ },
+ "lightRadius": {
+ "label": "Light Radius (ft)"
+ },
+ "cost": {
+ "label": "Cost"
+ },
+ "currency": {
+ "label": "Currency"
+ }
}
},
"Spell": {
- "tradition": {
- "label": "Tradition"
- },
- "difficultyValue": {
- "label": "Difficulty Value (DV)"
- },
- "isRitual": {
- "label": "Ritual"
- },
- "isMagicMissile": {
- "label": "Magic Missile"
- },
- "range": {
- "label": "Range"
- },
- "duration": {
- "label": "Duration"
- },
- "spellSave": {
- "label": "Spell Save"
- },
- "element": {
- "label": "Element"
- },
- "runeType": {
- "label": "Rune Type"
- },
- "isExalted": {
- "label": "Exalted"
+ "FIELDS": {
+ "tradition": {
+ "label": "Tradition"
+ },
+ "difficultyValue": {
+ "label": "Difficulty Value (DV)"
+ },
+ "isRitual": {
+ "label": "Ritual"
+ },
+ "isMagicMissile": {
+ "label": "Magic Missile"
+ },
+ "range": {
+ "label": "Range"
+ },
+ "duration": {
+ "label": "Duration"
+ },
+ "spellSave": {
+ "label": "Spell Save"
+ },
+ "element": {
+ "label": "Element"
+ },
+ "runeType": {
+ "label": "Rune Type"
+ },
+ "isExalted": {
+ "label": "Exalted"
+ },
+ "effect": {
+ "label": "Effect"
+ }
}
},
"Miracle": {
- "divineTradition": {
- "label": "Divine Tradition"
- },
- "difficultyValue": {
- "label": "Difficulty Value (DV)"
- },
- "isRitual": {
- "label": "Ritual"
- },
- "range": {
- "label": "Range"
- },
- "duration": {
- "label": "Duration"
- },
- "spellSave": {
- "label": "Spell Save"
+ "FIELDS": {
+ "divineTradition": {
+ "label": "Divine Tradition"
+ },
+ "difficultyValue": {
+ "label": "Difficulty Value (DV)"
+ },
+ "isRitual": {
+ "label": "Ritual"
+ },
+ "range": {
+ "label": "Range"
+ },
+ "duration": {
+ "label": "Duration"
+ },
+ "spellSave": {
+ "label": "Spell Save"
+ },
+ "effect": {
+ "label": "Effect"
+ }
}
},
"MagicItem": {
- "itemType": {
- "label": "Type"
- },
- "quality": {
- "label": "Quality"
- },
- "isCursed": {
- "label": "Cursed"
- },
- "isBonded": {
- "label": "Bonded"
- },
- "classRestriction": {
- "label": "Restriction"
- },
- "usagePeriod": {
- "label": "Usage Period"
- },
- "maxUses": {
- "label": "Max Uses"
- },
- "slots": {
- "label": "Slots"
- },
- "equipped": {
- "label": "Equipped"
+ "FIELDS": {
+ "itemType": {
+ "label": "Type"
+ },
+ "quality": {
+ "label": "Quality"
+ },
+ "isCursed": {
+ "label": "Cursed"
+ },
+ "isBonded": {
+ "label": "Bonded"
+ },
+ "classRestriction": {
+ "label": "Restriction"
+ },
+ "usagePeriod": {
+ "label": "Usage Period"
+ },
+ "maxUses": {
+ "label": "Max Uses"
+ },
+ "slots": {
+ "label": "Slots"
+ },
+ "equipped": {
+ "label": "Equipped"
+ },
+ "effect": {
+ "label": "Effect"
+ }
}
},
"Ability": {
- "abilityType": {
- "label": "Type"
- },
- "source": {
- "label": "Source (Class / Lineage)"
- },
- "usagePeriod": {
- "label": "Usage Period"
- },
- "maxUses": {
- "label": "Max Uses"
+ "FIELDS": {
+ "abilityType": {
+ "label": "Type"
+ },
+ "source": {
+ "label": "Source (Class / Lineage)"
+ },
+ "usagePeriod": {
+ "label": "Usage Period"
+ },
+ "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"
}
}
-}
+}
\ No newline at end of file
diff --git a/less/actor-sheet.less b/less/actor-sheet.less
index 89c9a52..5fcbe4d 100644
--- a/less/actor-sheet.less
+++ b/less/actor-sheet.less
@@ -41,9 +41,15 @@
.character-resource {
display: flex;
align-items: center;
+ flex-wrap: nowrap;
gap: 4px;
margin-bottom: 2px;
+ // formInput renders a
— flatten it so it doesn't break flex row
+ div.form-group {
+ display: contents;
+ }
+
input {
min-width: 2.5rem;
max-width: 2.5rem;
diff --git a/less/base.less b/less/base.less
index 4ac0c63..f17193d 100644
--- a/less/base.less
+++ b/less/base.less
@@ -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;
+ }
+ }
+}
diff --git a/less/item-sheets.less b/less/item-sheets.less
index 3c0d543..8538a89 100644
--- a/less/item-sheets.less
+++ b/less/item-sheets.less
@@ -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;
}
+ // 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;
+ color: @color-dark;
+ }
+
.form-group {
display: flex;
- flex: 1;
flex-direction: row;
align-items: center;
- gap: 4px;
- margin-bottom: 2px;
+ gap: 6px;
+ margin-bottom: 4px;
- label {
+ & > label {
+ flex: 0 0 8rem; // Fixed label width, no grow/shrink
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-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 {
diff --git a/less/variables.less b/less/variables.less
index 8232027..76670ec 100644
--- a/less/variables.less
+++ b/less/variables.less
@@ -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);
diff --git a/module/applications/sheets/character-sheet.mjs b/module/applications/sheets/character-sheet.mjs
index 663bcb1..287fa5b 100644
--- a/module/applications/sheets/character-sheet.mjs
+++ b/module/applications/sheets/character-sheet.mjs
@@ -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 */
@@ -51,11 +39,12 @@ export default class OathHammerCharacterSheet extends OathHammerActorSheet {
#getTabs() {
const tabs = {
- identity: { id: "identity", group: "sheet", icon: "fa-solid fa-person", label: "OATHHAMMER.Tab.Identity" },
- 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" },
- notes: { id: "notes", group: "sheet", icon: "fa-solid fa-book", label: "OATHHAMMER.Tab.Notes" },
+ 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" },
+ notes: { id: "notes", group: "sheet", icon: "fa-solid fa-book", label: "OATHHAMMER.Tab.Notes" },
}
for (const v of Object.values(tabs)) {
v.active = this.tabGroups[v.group] === v.id
@@ -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
diff --git a/module/config/system.mjs b/module/config/system.mjs
index 1b2da71..6bd51b8 100644
--- a/module/config/system.mjs
+++ b/module/config/system.mjs
@@ -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,
diff --git a/module/models/character.mjs b/module/models/character.mjs
index 6fef1e1..de1bc75 100644
--- a/module/models/character.mjs
+++ b/module/models/character.mjs
@@ -21,13 +21,48 @@ export default class OathHammerCharacter extends foundry.abstract.TypeDataModel
fate: attributeField()
})
+ // Skills: 26 skills each with a rank (0–4). 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
}
}
diff --git a/templates/actor/character-combat.hbs b/templates/actor/character-combat.hbs
index d4a5bef..8b1d674 100644
--- a/templates/actor/character-combat.hbs
+++ b/templates/actor/character-combat.hbs
@@ -1,4 +1,4 @@
-
+