Initial import

This commit is contained in:
2026-03-07 17:19:40 +01:00
commit 03bc0b7043
69 changed files with 3263 additions and 0 deletions

Binary file not shown.

BIN
assets/fonts/SHERWOOD.TTF Normal file

Binary file not shown.

BIN
assets/fonts/Sherwood.otf Normal file

Binary file not shown.

BIN
assets/fonts/Sherwood.woff Normal file

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 168 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

430
css/fvtt-oath-hammer.css Normal file
View File

@@ -0,0 +1,430 @@
@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: "Sherwood", "Palatino Linotype", serif;
--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-gold: #c8a84b;
--oh-color-dark: #2a1a0a;
--oh-color-paper: #f5ead0;
--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);
background-image: var(--oh-background-image);
}
/* ======================== */
/* ACTOR SHEET CONTENT */
/* ======================== */
.oathhammer .character-content,
.oathhammer .npc-content {
font-family: var(--oh-font-primary);
font-size: var(--oh-font-size);
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);
}
.oathhammer .character-content nav.tabs [data-tab].active,
.oathhammer .npc-content nav.tabs [data-tab].active {
color: var(--oh-color-blue);
}
.oathhammer .character-content input:disabled,
.oathhammer .character-content select:disabled,
.oathhammer .npc-content input: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 .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);
}
.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-weight: bold;
border: none;
border-bottom: 2px solid var(--oh-color-blue);
background: transparent;
}
.oathhammer .character-content fieldset,
.oathhammer .npc-content fieldset {
margin-bottom: 4px;
border-radius: 4px;
border-color: var(--oh-color-olive);
}
.oathhammer .character-content legend,
.oathhammer .npc-content legend {
font-family: var(--oh-font-secondary);
font-size: calc(var(--oh-font-size) * 1.2);
font-weight: bold;
letter-spacing: 1px;
color: var(--oh-color-blue);
}
.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);
}
/* ======================== */
/* 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;
display: flex;
flex-direction: column;
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 .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);
text-align: center;
}
.oathhammer .attribute-box input {
width: 2.5rem;
text-align: center;
font-size: calc(var(--oh-font-size) * 1.1);
}
/* ======================== */
/* CURRENCY BAR */
/* ======================== */
.oathhammer .currency-bar {
margin-top: 4px;
}
.oathhammer .currency-bar .currency-item {
display: flex;
flex-direction: column;
align-items: center;
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);
font-weight: bold;
color: var(--oh-color-blue);
}
/* ======================== */
/* NPC SHEET */
/* ======================== */
.oathhammer .npc-main .npc-left {
min-width: 160px;
max-width: 160px;
display: flex;
flex-direction: column;
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;
}
/* ======================== */
/* TABS */
/* ======================== */
.oathhammer .tab {
padding: 4px;
}

548
lang/en.json Normal file
View File

@@ -0,0 +1,548 @@
{
"OATHHAMMER": {
"Sheet": {
"Character": "Oath Hammer Character Sheet",
"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",
"Miracle": "Oath Hammer Miracle Sheet",
"MagicItem": "Oath Hammer Magic Item Sheet",
"Ability": "Oath Hammer Ability Sheet",
"Oath": "Oath Hammer Oath Sheet",
"Condition": "Oath Hammer Condition Sheet"
},
"Tab": {
"Identity": "Identity",
"Combat": "Combat",
"Magic": "Magic",
"Equipment": "Equipment",
"Notes": "Notes"
},
"Attribute": {
"Might": "Might",
"Toughness": "Toughness",
"Agility": "Agility",
"Willpower": "Willpower",
"Intelligence": "Intelligence",
"Fate": "Fate"
},
"Lineage": {
"Dwarf": "Dwarf",
"Human": "Human",
"Elf": "Elf",
"HalfElf": "Half-Elf",
"Halfling": "Halfling"
},
"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"
},
"Tradition": {
"Elemental": "Elemental",
"Illusionist": "Illusionist",
"Imperial": "Imperial",
"Infernal": "Infernal",
"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"
},
"EquipmentType": {
"Potion": "Potion",
"Container": "Container",
"Tool": "Tool",
"Consumable": "Consumable",
"Misc": "Miscellaneous",
"HealingSupply": "Healing Supply",
"Food": "Food",
"Mount": "Mount",
"Vehicle": "Vehicle",
"WarMachine": "War Machine"
},
"MagicItemType": {
"Weapon": "Weapon",
"Armor": "Armor",
"Wondrous": "Wondrous Item",
"Potion": "Potion",
"Ring": "Ring",
"Staff": "Staff",
"Wand": "Wand",
"Scroll": "Scroll",
"Rod": "Rod"
},
"Rarity": {
"Common": "Common",
"Uncommon": "Uncommon",
"Rare": "Rare",
"VeryRare": "Very Rare",
"Legendary": "Legendary"
},
"AbilityType": {
"ClassAbility": "Class Ability",
"LineageTrait": "Lineage Trait",
"Feat": "Feat"
},
"Condition": {
"Blinded": "Blinded",
"Deafened": "Deafened",
"Prone": "Prone",
"Stunned": "Stunned",
"Frightened": "Frightened",
"Poisoned": "Poisoned",
"Restrained": "Restrained",
"Wounded": "Wounded",
"Other": "Other"
},
"Label": {
"Character": "Character",
"NPC": "NPC",
"Grit": "Grit",
"Luck": "Luck",
"Defense": "Defense",
"DefenseValue": "Defense Value",
"ArmorRating": "Armor Rating",
"DefenseBonus": "Defense Bonus",
"Movement": "Movement",
"ArcaneStress": "Arcane Stress",
"StressValue": "Stress",
"Attributes": "Attributes",
"Biodata": "Background",
"Experience": "Experience",
"Level": "Level",
"XP": "Current XP",
"TotalXP": "Total XP",
"Abilities": "Abilities & Traits",
"Oaths": "Oaths",
"Weapons": "Weapons",
"Armor": "Armor & Shields",
"Ammunition": "Ammunition",
"Spells": "Spells",
"Miracles": "Miracles",
"Equipment": "Equipment",
"MagicItems": "Magic Items",
"Conditions": "Conditions",
"Description": "Description",
"Notes": "Notes",
"Stats": "Statistics",
"CR": "Challenge Rating",
"AttackBonus": "Attack Bonus",
"DamageBonus": "Damage Bonus",
"Currency": "Currency",
"None": "None",
"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."
},
"NewItem": {
"Weapon": "New Weapon",
"Spell": "New Spell",
"Miracle": "New Miracle",
"Equipment": "New Equipment"
},
"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"
},
"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"
}
},
"currency": {
"label": "Currency",
"gold": {
"label": "Gold"
},
"silver": {
"label": "Silver"
},
"copper": {
"label": "Copper"
}
}
},
"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"
}
},
"Weapon": {
"weaponType": {
"label": "Weapon Type"
},
"damageFormula": {
"label": "Damage"
},
"damageType": {
"label": "Damage Type"
},
"attributeBonus": {
"label": "Attribute Bonus"
},
"range": {
"label": "Range"
},
"hands": {
"label": "Hands"
},
"properties": {
"label": "Properties"
},
"equipped": {
"label": "Equipped"
},
"encumbrance": {
"label": "Enc."
},
"cost": {
"label": "Cost"
},
"currency": {
"label": "Currency"
}
},
"Armor": {
"armorType": {
"label": "Armor Type"
},
"armorRating": {
"label": "Armor Rating"
},
"movementPenalty": {
"label": "Movement Penalty"
},
"equipped": {
"label": "Equipped"
},
"encumbrance": {
"label": "Enc."
},
"cost": {
"label": "Cost"
},
"currency": {
"label": "Currency"
}
},
"Shield": {
"shieldBonus": {
"label": "Shield Bonus"
},
"equipped": {
"label": "Equipped"
},
"encumbrance": {
"label": "Enc."
},
"cost": {
"label": "Cost"
},
"currency": {
"label": "Currency"
}
},
"Ammunition": {
"ammoType": {
"label": "Ammo Type"
},
"quantity": {
"label": "Quantity"
},
"properties": {
"label": "Properties"
},
"cost": {
"label": "Cost"
},
"currency": {
"label": "Currency"
}
},
"Equipment": {
"itemType": {
"label": "Type"
},
"quantity": {
"label": "Quantity"
},
"weight": {
"label": "Weight"
},
"cost": {
"label": "Cost"
},
"currency": {
"label": "Currency"
}
},
"Spell": {
"tradition": {
"label": "Tradition"
},
"level": {
"label": "Level"
},
"castingTime": {
"label": "Casting Time"
},
"range": {
"label": "Range"
},
"duration": {
"label": "Duration"
},
"arcaneStress": {
"label": "Arcane Stress"
},
"components": {
"label": "Components",
"verbal": {
"label": "Verbal"
},
"somatic": {
"label": "Somatic"
},
"material": {
"label": "Material"
}
},
"materialComponent": {
"label": "Material Component"
},
"savingThrow": {
"label": "Saving Throw"
},
"enhancement": {
"label": "Enhancement"
}
},
"Miracle": {
"piety": {
"label": "Piety Cost"
},
"castingTime": {
"label": "Casting Time"
},
"range": {
"label": "Range"
},
"duration": {
"label": "Duration"
},
"components": {
"label": "Components",
"verbal": {
"label": "Verbal"
},
"somatic": {
"label": "Somatic"
},
"material": {
"label": "Material"
}
},
"materialComponent": {
"label": "Material Component"
},
"savingThrow": {
"label": "Saving Throw"
}
},
"MagicItem": {
"itemType": {
"label": "Item Type"
},
"rarity": {
"label": "Rarity"
},
"attunement": {
"label": "Requires Attunement"
},
"charges": {
"label": "Charges",
"value": {
"label": "Current"
},
"max": {
"label": "Maximum"
}
},
"recharge": {
"label": "Recharge"
},
"equipped": {
"label": "Equipped"
},
"cost": {
"label": "Cost"
},
"currency": {
"label": "Currency"
}
},
"Ability": {
"abilityType": {
"label": "Ability Type"
},
"source": {
"label": "Source"
},
"prerequisite": {
"label": "Prerequisite"
},
"passiveBonus": {
"label": "Passive Bonus"
}
},
"Oath": {
"oathType": {
"label": "Oath Type"
},
"violated": {
"label": "Violated"
}
},
"Condition": {
"conditionType": {
"label": "Condition Type"
},
"duration": {
"label": "Duration"
},
"source": {
"label": "Source"
}
}
}
}

View File

@@ -0,0 +1,13 @@
export { default as OathHammerCharacterSheet } from "./sheets/character-sheet.mjs"
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"
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

@@ -0,0 +1,27 @@
import OathHammerItemSheet from "./base-item-sheet.mjs"
export default class OathHammerAbilitySheet extends OathHammerItemSheet {
/** @override */
static DEFAULT_OPTIONS = {
classes: ["ability"],
position: {
width: 620,
},
window: {
contentClasses: ["ability-content"],
},
}
/** @override */
static PARTS = {
main: {
template: "systems/fvtt-oath-hammer/templates/item/ability-sheet.hbs",
},
}
/** @override */
async _prepareContext() {
const context = await super._prepareContext()
return context
}
}

View File

@@ -0,0 +1,27 @@
import OathHammerItemSheet from "./base-item-sheet.mjs"
export default class OathHammerAmmunitionSheet extends OathHammerItemSheet {
/** @override */
static DEFAULT_OPTIONS = {
classes: ["ammunition"],
position: {
width: 620,
},
window: {
contentClasses: ["ammunition-content"],
},
}
/** @override */
static PARTS = {
main: {
template: "systems/fvtt-oath-hammer/templates/item/ammunition-sheet.hbs",
},
}
/** @override */
async _prepareContext() {
const context = await super._prepareContext()
return context
}
}

View File

@@ -0,0 +1,27 @@
import OathHammerItemSheet from "./base-item-sheet.mjs"
export default class OathHammerArmorSheet extends OathHammerItemSheet {
/** @override */
static DEFAULT_OPTIONS = {
classes: ["armor"],
position: {
width: 620,
},
window: {
contentClasses: ["armor-content"],
},
}
/** @override */
static PARTS = {
main: {
template: "systems/fvtt-oath-hammer/templates/item/armor-sheet.hbs",
},
}
/** @override */
async _prepareContext() {
const context = await super._prepareContext()
return context
}
}

View File

@@ -0,0 +1,138 @@
const { HandlebarsApplicationMixin } = foundry.applications.api
export default class OathHammerActorSheet extends HandlebarsApplicationMixin(foundry.applications.sheets.ActorSheetV2) {
static SHEET_MODES = { EDIT: 0, PLAY: 1 }
constructor(options = {}) {
super(options)
this.#dragDrop = this.#createDragDropHandlers()
}
#dragDrop
/** @override */
static DEFAULT_OPTIONS = {
classes: ["oathhammer", "actor"],
position: {
width: 900,
height: "auto",
},
form: {
submitOnChange: true,
},
window: {
resizable: true,
},
dragDrop: [{ dragSelector: '[data-drag="true"], .rollable', dropSelector: null }],
actions: {
editImage: OathHammerActorSheet.#onEditImage,
toggleSheet: OathHammerActorSheet.#onToggleSheet,
edit: OathHammerActorSheet.#onItemEdit,
delete: OathHammerActorSheet.#onItemDelete,
},
}
_sheetMode = this.constructor.SHEET_MODES.PLAY
get isPlayMode() {
return this._sheetMode === this.constructor.SHEET_MODES.PLAY
}
get isEditMode() {
return this._sheetMode === this.constructor.SHEET_MODES.EDIT
}
/** @override */
async _prepareContext() {
const context = {
fields: this.document.schema.fields,
systemFields: this.document.system.schema.fields,
actor: this.document,
system: this.document.system,
source: this.document.toObject(),
isEditMode: this.isEditMode,
isPlayMode: this.isPlayMode,
isEditable: this.isEditable,
}
return context
}
/** @override */
_onRender(context, options) {
this.#dragDrop.forEach((d) => d.bind(this.element))
}
#createDragDropHandlers() {
return this.options.dragDrop.map((d) => {
d.permissions = {
dragstart: this._canDragStart.bind(this),
drop: this._canDragDrop.bind(this),
}
d.callbacks = {
dragstart: this._onDragStart.bind(this),
dragover: this._onDragOver.bind(this),
drop: this._onDrop.bind(this),
}
return new foundry.applications.ux.DragDrop.implementation(d)
})
}
async _onDrop(event) {}
_canDragStart(selector) {
return this.isEditable
}
_canDragDrop(selector) {
return this.isEditable && this.document.isOwner
}
_onDragStart(event) {
if ("link" in event.target.dataset) return
}
_onDragOver(event) {}
async _onDropItem(item) {
const itemData = item.toObject()
await this.document.createEmbeddedDocuments("Item", [itemData], { renderSheet: false })
}
static #onToggleSheet(event, target) {
const modes = this.constructor.SHEET_MODES
this._sheetMode = this.isEditMode ? modes.PLAY : modes.EDIT
this.render()
}
static async #onEditImage(event, target) {
const attr = target.dataset.edit
const current = foundry.utils.getProperty(this.document, attr)
const { img } = this.document.constructor.getDefaultArtwork?.(this.document.toObject()) ?? {}
const fp = new FilePicker({
current,
type: "image",
redirectToRoot: img ? [img] : [],
callback: (path) => {
this.document.update({ [attr]: path })
},
top: this.position.top + 40,
left: this.position.left + 10,
})
return fp.browse()
}
static async #onItemEdit(event, target) {
const id = target.getAttribute("data-item-id")
const uuid = target.getAttribute("data-item-uuid")
let item = await fromUuid(uuid)
if (!item) item = this.document.items.get(id)
if (!item) return
item.sheet.render(true)
}
static async #onItemDelete(event, target) {
const itemUuid = target.getAttribute("data-item-uuid")
const item = await fromUuid(itemUuid)
await item.deleteDialog()
}
}

View File

@@ -0,0 +1,119 @@
const { HandlebarsApplicationMixin } = foundry.applications.api
export default class OathHammerItemSheet extends HandlebarsApplicationMixin(foundry.applications.sheets.ItemSheetV2) {
static SHEET_MODES = { EDIT: 0, PLAY: 1 }
constructor(options = {}) {
super(options)
this.#dragDrop = this.#createDragDropHandlers()
}
#dragDrop
/** @override */
static DEFAULT_OPTIONS = {
classes: ["oathhammer", "item"],
position: {
width: 600,
height: "auto",
},
form: {
submitOnChange: true,
},
window: {
resizable: true,
},
dragDrop: [{ dragSelector: "[data-drag]", dropSelector: null }],
actions: {
toggleSheet: OathHammerItemSheet.#onToggleSheet,
editImage: OathHammerItemSheet.#onEditImage,
},
}
_sheetMode = this.constructor.SHEET_MODES.PLAY
get isPlayMode() {
return this._sheetMode === this.constructor.SHEET_MODES.PLAY
}
get isEditMode() {
return this._sheetMode === this.constructor.SHEET_MODES.EDIT
}
/** @override */
async _prepareContext() {
const context = await super._prepareContext()
context.fields = this.document.schema.fields
context.systemFields = this.document.system.schema.fields
context.item = this.document
context.system = this.document.system
context.source = this.document.toObject()
context.isEditMode = this.isEditMode
context.isPlayMode = this.isPlayMode
context.isEditable = this.isEditable
if (this.document.system.description !== undefined) {
context.enrichedDescription = await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.document.system.description ?? "", { async: true })
}
return context
}
/** @override */
_onRender(context, options) {
super._onRender(context, options)
this.#dragDrop.forEach((d) => d.bind(this.element))
}
#createDragDropHandlers() {
return this.options.dragDrop.map((d) => {
d.permissions = {
dragstart: this._canDragStart.bind(this),
drop: this._canDragDrop.bind(this),
}
d.callbacks = {
dragstart: this._onDragStart.bind(this),
dragover: this._onDragOver.bind(this),
drop: this._onDrop.bind(this),
}
return new foundry.applications.ux.DragDrop.implementation(d)
})
}
_canDragStart(selector) {
return this.isEditable
}
_canDragDrop(selector) {
return this.isEditable && this.document.isOwner
}
_onDragStart(event) {
if ("link" in event.target.dataset) return
}
_onDragOver(event) {}
async _onDrop(event) {}
static #onToggleSheet(event, target) {
const modes = this.constructor.SHEET_MODES
this._sheetMode = this.isEditMode ? modes.PLAY : modes.EDIT
this.render()
}
static async #onEditImage(event, target) {
const attr = target.dataset.edit
const current = foundry.utils.getProperty(this.document, attr)
const { img } = this.document.constructor.getDefaultArtwork?.(this.document.toObject()) ?? {}
const fp = new FilePicker({
current,
type: "image",
redirectToRoot: img ? [img] : [],
callback: (path) => {
this.document.update({ [attr]: path })
},
top: this.position.top + 40,
left: this.position.left + 10,
})
return fp.browse()
}
}

View File

@@ -0,0 +1,136 @@
import OathHammerActorSheet from "./base-actor-sheet.mjs"
export default class OathHammerCharacterSheet extends OathHammerActorSheet {
/** @override */
static DEFAULT_OPTIONS = {
classes: ["character"],
position: {
width: 972,
height: 780,
},
window: {
contentClasses: ["character-content"],
},
actions: {
createWeapon: OathHammerCharacterSheet.#onCreateWeapon,
createSpell: OathHammerCharacterSheet.#onCreateSpell,
createMiracle: OathHammerCharacterSheet.#onCreateMiracle,
createEquipment: OathHammerCharacterSheet.#onCreateEquipment,
},
}
/** @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",
},
}
/** @override */
tabGroups = {
sheet: "identity",
}
#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" },
}
for (const v of Object.values(tabs)) {
v.active = this.tabGroups[v.group] === v.id
v.cssClass = v.active ? "active" : ""
}
return tabs
}
/** @override */
async _prepareContext() {
const context = await super._prepareContext()
context.tabs = this.#getTabs()
return context
}
/** @override */
async _preparePartContext(partId, context) {
const doc = this.document
switch (partId) {
case "main":
break
case "identity":
context.tab = context.tabs.identity
context.abilities = doc.itemTypes.ability
context.oaths = doc.itemTypes.oath
break
case "combat":
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":
context.tab = context.tabs.magic
context.spells = doc.itemTypes.spell
context.miracles = doc.itemTypes.miracle
break
case "equipment":
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
context.enrichedDescription = await foundry.applications.ux.TextEditor.implementation.enrichHTML(doc.system.description, { async: true })
context.enrichedNotes = await foundry.applications.ux.TextEditor.implementation.enrichHTML(doc.system.notes, { async: true })
break
}
return context
}
async _onDrop(event) {
if (!this.isEditable || !this.isEditMode) return
const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event)
if (data.type === "Item") {
const item = await fromUuid(data.uuid)
return this._onDropItem(item)
}
}
static #onCreateWeapon(event, target) {
this.document.createEmbeddedDocuments("Item", [{ name: game.i18n.localize("OATHHAMMER.NewItem.Weapon"), type: "weapon" }])
}
static #onCreateSpell(event, target) {
this.document.createEmbeddedDocuments("Item", [{ name: game.i18n.localize("OATHHAMMER.NewItem.Spell"), type: "spell" }])
}
static #onCreateMiracle(event, target) {
this.document.createEmbeddedDocuments("Item", [{ name: game.i18n.localize("OATHHAMMER.NewItem.Miracle"), type: "miracle" }])
}
static #onCreateEquipment(event, target) {
this.document.createEmbeddedDocuments("Item", [{ name: game.i18n.localize("OATHHAMMER.NewItem.Equipment"), type: "equipment" }])
}
}

View File

@@ -0,0 +1,27 @@
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

@@ -0,0 +1,27 @@
import OathHammerItemSheet from "./base-item-sheet.mjs"
export default class OathHammerEquipmentSheet extends OathHammerItemSheet {
/** @override */
static DEFAULT_OPTIONS = {
classes: ["equipment"],
position: {
width: 620,
},
window: {
contentClasses: ["equipment-content"],
},
}
/** @override */
static PARTS = {
main: {
template: "systems/fvtt-oath-hammer/templates/item/equipment-sheet.hbs",
},
}
/** @override */
async _prepareContext() {
const context = await super._prepareContext()
return context
}
}

View File

@@ -0,0 +1,27 @@
import OathHammerItemSheet from "./base-item-sheet.mjs"
export default class OathHammerMagicItemSheet extends OathHammerItemSheet {
/** @override */
static DEFAULT_OPTIONS = {
classes: ["magic-item"],
position: {
width: 620,
},
window: {
contentClasses: ["magic-item-content"],
},
}
/** @override */
static PARTS = {
main: {
template: "systems/fvtt-oath-hammer/templates/item/magic-item-sheet.hbs",
},
}
/** @override */
async _prepareContext() {
const context = await super._prepareContext()
return context
}
}

View File

@@ -0,0 +1,28 @@
import OathHammerItemSheet from "./base-item-sheet.mjs"
export default class OathHammerMiracleSheet extends OathHammerItemSheet {
/** @override */
static DEFAULT_OPTIONS = {
classes: ["miracle"],
position: {
width: 620,
},
window: {
contentClasses: ["miracle-content"],
},
}
/** @override */
static PARTS = {
main: {
template: "systems/fvtt-oath-hammer/templates/item/miracle-sheet.hbs",
},
}
/** @override */
async _prepareContext() {
const context = await super._prepareContext()
context.enrichedEffect = await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.document.system.effect, { async: true })
return context
}
}

View File

@@ -0,0 +1,83 @@
import OathHammerActorSheet from "./base-actor-sheet.mjs"
export default class OathHammerNPCSheet extends OathHammerActorSheet {
/** @override */
static DEFAULT_OPTIONS = {
classes: ["npc"],
position: {
width: 720,
height: "auto",
},
window: {
contentClasses: ["npc-content"],
},
}
/** @override */
static PARTS = {
main: {
template: "systems/fvtt-oath-hammer/templates/actor/npc-sheet.hbs",
},
tabs: {
template: "templates/generic/tab-navigation.hbs",
},
combat: {
template: "systems/fvtt-oath-hammer/templates/actor/npc-combat.hbs",
},
notes: {
template: "systems/fvtt-oath-hammer/templates/actor/npc-notes.hbs",
},
}
/** @override */
tabGroups = {
sheet: "combat",
}
#getTabs() {
const tabs = {
combat: { id: "combat", group: "sheet", icon: "fa-solid fa-swords", label: "OATHHAMMER.Tab.Combat" },
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
v.cssClass = v.active ? "active" : ""
}
return tabs
}
/** @override */
async _prepareContext() {
const context = await super._prepareContext()
context.tabs = this.#getTabs()
return context
}
/** @override */
async _preparePartContext(partId, context) {
const doc = this.document
switch (partId) {
case "main":
break
case "combat":
context.tab = context.tabs.combat
context.weapons = doc.itemTypes.weapon
break
case "notes":
context.tab = context.tabs.notes
context.enrichedDescription = await foundry.applications.ux.TextEditor.implementation.enrichHTML(doc.system.description, { async: true })
context.enrichedNotes = await foundry.applications.ux.TextEditor.implementation.enrichHTML(doc.system.notes, { async: true })
break
}
return context
}
async _onDrop(event) {
if (!this.isEditable || !this.isEditMode) return
const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event)
if (data.type === "Item") {
const item = await fromUuid(data.uuid)
return this._onDropItem(item)
}
}
}

View File

@@ -0,0 +1,29 @@
import OathHammerItemSheet from "./base-item-sheet.mjs"
export default class OathHammerOathSheet extends OathHammerItemSheet {
/** @override */
static DEFAULT_OPTIONS = {
classes: ["oath"],
position: {
width: 620,
},
window: {
contentClasses: ["oath-content"],
},
}
/** @override */
static PARTS = {
main: {
template: "systems/fvtt-oath-hammer/templates/item/oath-sheet.hbs",
},
}
/** @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 })
return context
}
}

View File

@@ -0,0 +1,27 @@
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

@@ -0,0 +1,28 @@
import OathHammerItemSheet from "./base-item-sheet.mjs"
export default class OathHammerSpellSheet extends OathHammerItemSheet {
/** @override */
static DEFAULT_OPTIONS = {
classes: ["spell"],
position: {
width: 620,
},
window: {
contentClasses: ["spell-content"],
},
}
/** @override */
static PARTS = {
main: {
template: "systems/fvtt-oath-hammer/templates/item/spell-sheet.hbs",
},
}
/** @override */
async _prepareContext() {
const context = await super._prepareContext()
context.enrichedEffect = await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.document.system.effect, { async: true })
return context
}
}

View File

@@ -0,0 +1,27 @@
import OathHammerItemSheet from "./base-item-sheet.mjs"
export default class OathHammerWeaponSheet extends OathHammerItemSheet {
/** @override */
static DEFAULT_OPTIONS = {
classes: ["weapon"],
position: {
width: 620,
},
window: {
contentClasses: ["weapon-content"],
},
}
/** @override */
static PARTS = {
main: {
template: "systems/fvtt-oath-hammer/templates/item/weapon-sheet.hbs",
},
}
/** @override */
async _prepareContext() {
const context = await super._prepareContext()
return context
}
}

194
module/config/system.mjs Normal file
View File

@@ -0,0 +1,194 @@
export const SYSTEM_ID = "fvtt-oath-hammer"
export const ATTRIBUTES = {
might: { id: "might", abbrev: "M", label: "OATHHAMMER.Attribute.Might" },
toughness: { id: "toughness", abbrev: "T", label: "OATHHAMMER.Attribute.Toughness" },
agility: { id: "agility", abbrev: "A", label: "OATHHAMMER.Attribute.Agility" },
willpower: { id: "willpower", abbrev: "WP", label: "OATHHAMMER.Attribute.Willpower" },
intelligence: { id: "intelligence", abbrev: "I", label: "OATHHAMMER.Attribute.Intelligence" },
fate: { id: "fate", abbrev: "F", label: "OATHHAMMER.Attribute.Fate" }
}
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" }
}
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" }
}
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" }
}
export const SORCEROUS_TRADITIONS = {
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" }
}
export const WEAPON_TYPE_CHOICES = {
melee: "OATHHAMMER.WeaponType.Melee",
ranged: "OATHHAMMER.WeaponType.Ranged"
}
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"
}
export const ATTRIBUTE_BONUS_CHOICES = {
might: "OATHHAMMER.Attribute.Might",
agility: "OATHHAMMER.Attribute.Agility",
none: "OATHHAMMER.Label.None"
}
export const RANGE_CHOICES = {
short: "OATHHAMMER.Range.Short",
medium: "OATHHAMMER.Range.Medium",
long: "OATHHAMMER.Range.Long"
}
export const HANDS_CHOICES = {
"one-handed": "OATHHAMMER.Hands.OneHanded",
"two-handed": "OATHHAMMER.Hands.TwoHanded"
}
export const CURRENCY_CHOICES = {
gp: "OATHHAMMER.Currency.GP",
sp: "OATHHAMMER.Currency.SP",
cp: "OATHHAMMER.Currency.CP"
}
export const ARMOR_TYPE_CHOICES = {
light: "OATHHAMMER.ArmorType.Light",
medium: "OATHHAMMER.ArmorType.Medium",
heavy: "OATHHAMMER.ArmorType.Heavy"
}
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"
}
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",
"healing-supply": "OATHHAMMER.EquipmentType.HealingSupply",
food: "OATHHAMMER.EquipmentType.Food",
mount: "OATHHAMMER.EquipmentType.Mount",
vehicle: "OATHHAMMER.EquipmentType.Vehicle",
"war-machine": "OATHHAMMER.EquipmentType.WarMachine"
}
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"
}
export const RARITY_CHOICES = {
common: "OATHHAMMER.Rarity.Common",
uncommon: "OATHHAMMER.Rarity.Uncommon",
rare: "OATHHAMMER.Rarity.Rare",
"very-rare": "OATHHAMMER.Rarity.VeryRare",
legendary: "OATHHAMMER.Rarity.Legendary"
}
export const ABILITY_TYPE_CHOICES = {
"class-ability": "OATHHAMMER.AbilityType.ClassAbility",
"lineage-trait": "OATHHAMMER.AbilityType.LineageTrait",
feat: "OATHHAMMER.AbilityType.Feat"
}
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"
}
export const ATTRIBUTE_RANK_CHOICES = { 1: "1", 2: "2", 3: "3", 4: "4" }
export const ASCII = `
·················································
: ___ _ _ _ _ :
: / _ \\ __ _| |_| |__ | | | | __ _ _ __ ___ :
: | | | / _\` | __| '_ \\ | |_| |/ _\` | '_ \` _ \\:
: | |_| | (_| | |_| | | | | _ | (_| | | | | | |
: \\___/ \\__,_|\\__|_| |_| |_| |_|\\__,_|_| |_| |_|
: :
·················································
`
export const SYSTEM = {
id: SYSTEM_ID,
ATTRIBUTES,
LINEAGE_CHOICES,
CLASS_CHOICES,
OATH_TYPES,
SORCEROUS_TRADITIONS,
WEAPON_TYPE_CHOICES,
DAMAGE_TYPE_CHOICES,
ATTRIBUTE_BONUS_CHOICES,
RANGE_CHOICES,
HANDS_CHOICES,
CURRENCY_CHOICES,
ARMOR_TYPE_CHOICES,
AMMO_TYPE_CHOICES,
EQUIPMENT_TYPE_CHOICES,
MAGIC_ITEM_TYPE_CHOICES,
RARITY_CHOICES,
ABILITY_TYPE_CHOICES,
CONDITION_TYPE_CHOICES,
ATTRIBUTE_RANK_CHOICES,
ASCII
}

View File

@@ -0,0 +1,2 @@
export { default as OathHammerActor } from "./actor.mjs"
export { default as OathHammerItem } from "./item.mjs"

View File

@@ -0,0 +1,35 @@
export default class OathHammerActor extends Actor {
async _preCreate(data, options, user) {
await super._preCreate(data, options, user)
const prototypeToken = {}
if (this.type === "character") {
Object.assign(prototypeToken, {
sight: { enabled: true },
actorLink: true,
disposition: CONST.TOKEN_DISPOSITIONS.FRIENDLY,
})
this.updateSource({ prototypeToken })
}
if (this.type === "npc") {
Object.assign(prototypeToken, {
sight: { enabled: false },
actorLink: false,
disposition: CONST.TOKEN_DISPOSITIONS.HOSTILE,
})
this.updateSource({ prototypeToken })
}
}
getArmorRating() {
let rating = 0
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 rating
}
}

22
module/documents/item.mjs Normal file
View File

@@ -0,0 +1,22 @@
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",
"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"
}
export default class OathHammerItem extends Item {
constructor(data, context) {
if (!data.img && defaultItemImg[data.type]) {
data.img = defaultItemImg[data.type]
}
super(data, context)
}
}

13
module/models/_module.mjs Normal file
View File

@@ -0,0 +1,13 @@
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"
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"

18
module/models/ability.mjs Normal file
View File

@@ -0,0 +1,18 @@
import { SYSTEM } from "../config/system.mjs"
export default class OathHammerAbility extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields
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 })
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: "" })
return schema
}
static LOCALIZATION_PREFIXES = ["OATHHAMMER.Ability"]
}

View File

@@ -0,0 +1,20 @@
import { SYSTEM } from "../config/system.mjs"
export default class OathHammerAmmunition 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.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 })
return schema
}
static LOCALIZATION_PREFIXES = ["OATHHAMMER.Ammunition"]
}

22
module/models/armor.mjs Normal file
View File

@@ -0,0 +1,22 @@
import { SYSTEM } from "../config/system.mjs"
export default class OathHammerArmor 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.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 })
schema.currency = new fields.StringField({ required: true, initial: "gp", choices: SYSTEM.CURRENCY_CHOICES })
return schema
}
static LOCALIZATION_PREFIXES = ["OATHHAMMER.Armor"]
}

View File

@@ -0,0 +1,83 @@
import { SYSTEM } from "../config/system.mjs"
export default class OathHammerCharacter 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.notes = new fields.HTMLField({ required: true, textSearch: true })
const attributeField = () => new fields.SchemaField({
rank: new fields.NumberField({ ...requiredInteger, initial: 1, min: 1, max: 4 })
})
schema.attributes = new fields.SchemaField({
might: attributeField(),
toughness: attributeField(),
agility: attributeField(),
willpower: attributeField(),
intelligence: attributeField(),
fate: attributeField()
})
schema.grit = new fields.SchemaField({
value: new fields.NumberField({ ...requiredInteger, initial: 2, min: 0 }),
max: new fields.NumberField({ ...requiredInteger, initial: 2, min: 0 })
})
schema.luck = new fields.SchemaField({
value: new fields.NumberField({ ...requiredInteger, initial: 1, min: 0 })
})
schema.arcaneStress = new fields.SchemaField({
value: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
threshold: new fields.NumberField({ ...requiredInteger, initial: 10, min: 0 })
})
schema.movement = new fields.SchemaField({
base: new fields.NumberField({ ...requiredInteger, initial: 30, min: 0 }),
adjusted: new fields.NumberField({ ...requiredInteger, initial: 30, min: 0 })
})
schema.defense = new fields.SchemaField({
value: new fields.NumberField({ ...requiredInteger, initial: 10, min: 0 }),
armorRating: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
bonus: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 })
})
schema.experience = new fields.SchemaField({
current: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
total: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
level: new fields.NumberField({ ...requiredInteger, initial: 1, min: 1 })
})
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 }),
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: "" }),
weight: new fields.StringField({ required: true, nullable: false, initial: "" }),
eyes: new fields.StringField({ required: true, nullable: false, initial: "" }),
hair: new fields.StringField({ required: true, nullable: false, initial: "" }),
alignment: new fields.StringField({ required: true, nullable: false, initial: "" })
})
schema.currency = new fields.SchemaField({
gold: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
silver: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
copper: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 })
})
return schema
}
static LOCALIZATION_PREFIXES = ["OATHHAMMER.Character"]
prepareDerivedData() {
super.prepareDerivedData()
this.grit.max = this.attributes.might.rank + this.attributes.toughness.rank
this.defense.value = 10 + this.attributes.agility.rank + this.defense.armorRating + this.defense.bonus
}
}

View File

@@ -0,0 +1,17 @@
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

@@ -0,0 +1,20 @@
import { SYSTEM } from "../config/system.mjs"
export default class OathHammerEquipment 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.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 })
schema.currency = new fields.StringField({ required: true, initial: "gp", choices: SYSTEM.CURRENCY_CHOICES })
return schema
}
static LOCALIZATION_PREFIXES = ["OATHHAMMER.Equipment"]
}

View File

@@ -0,0 +1,26 @@
import { SYSTEM } from "../config/system.mjs"
export default class OathHammerMagicItem 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.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 })
})
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 })
return schema
}
static LOCALIZATION_PREFIXES = ["OATHHAMMER.MagicItem"]
}

26
module/models/miracle.mjs Normal file
View File

@@ -0,0 +1,26 @@
import { SYSTEM } from "../config/system.mjs"
export default class OathHammerMiracle extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields
const requiredInteger = { required: true, nullable: false, integer: true }
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()
})
schema.materialComponent = new fields.StringField({ required: true, nullable: false, initial: "" })
schema.savingThrow = new fields.StringField({ required: true, nullable: false, initial: "" })
return schema
}
static LOCALIZATION_PREFIXES = ["OATHHAMMER.Miracle"]
}

50
module/models/npc.mjs Normal file
View File

@@ -0,0 +1,50 @@
import { SYSTEM } from "../config/system.mjs"
export default class OathHammerNPC 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.notes = new fields.HTMLField({ required: true, textSearch: true })
const attributeField = () => new fields.SchemaField({
rank: new fields.NumberField({ ...requiredInteger, initial: 1, min: 1, max: 4 })
})
schema.attributes = new fields.SchemaField({
might: attributeField(),
toughness: attributeField(),
agility: attributeField(),
willpower: attributeField(),
intelligence: attributeField(),
fate: attributeField()
})
schema.grit = new fields.SchemaField({
value: new fields.NumberField({ ...requiredInteger, initial: 2, min: 0 }),
max: new fields.NumberField({ ...requiredInteger, initial: 2, min: 0 })
})
schema.defense = new fields.SchemaField({
value: new fields.NumberField({ ...requiredInteger, initial: 10, min: 0 })
})
schema.movement = new fields.SchemaField({
base: new fields.NumberField({ ...requiredInteger, initial: 30, min: 0 })
})
schema.attackBonus = new fields.NumberField({ ...requiredInteger, initial: 0 })
schema.damageBonus = new fields.NumberField({ ...requiredInteger, initial: 0 })
schema.challengeRating = new fields.StringField({ required: true, nullable: false, initial: "1" })
return schema
}
static LOCALIZATION_PREFIXES = ["OATHHAMMER.NPC"]
prepareDerivedData() {
super.prepareDerivedData()
this.grit.max = this.attributes.might.rank + this.attributes.toughness.rank
}
}

17
module/models/oath.mjs Normal file
View File

@@ -0,0 +1,17 @@
import { SYSTEM } from "../config/system.mjs"
export default class OathHammerOath extends foundry.abstract.TypeDataModel {
static defineSchema() {
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.violated = new fields.BooleanField({ required: true, initial: false })
return schema
}
static LOCALIZATION_PREFIXES = ["OATHHAMMER.Oath"]
}

20
module/models/shield.mjs Normal file
View File

@@ -0,0 +1,20 @@
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"]
}

29
module/models/spell.mjs Normal file
View File

@@ -0,0 +1,29 @@
import { SYSTEM } from "../config/system.mjs"
export default class OathHammerSpell extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields
const requiredInteger = { required: true, nullable: false, integer: true }
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()
})
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: "" })
return schema
}
static LOCALIZATION_PREFIXES = ["OATHHAMMER.Spell"]
}

26
module/models/weapon.mjs Normal file
View File

@@ -0,0 +1,26 @@
import { SYSTEM } from "../config/system.mjs"
export default class OathHammerWeapon 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.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 })
return schema
}
static LOCALIZATION_PREFIXES = ["OATHHAMMER.Weapon"]
}

19
module/utils.mjs Normal file
View File

@@ -0,0 +1,19 @@
export default class OathHammerUtils {
static registerHandlebarsHelpers() {
Handlebars.registerHelper("ifThen", (condition, trueVal, falseVal) => condition ? trueVal : falseVal)
Handlebars.registerHelper("capitalize", (str) => {
if (typeof str !== "string") return str
return str.charAt(0).toUpperCase() + str.slice(1)
})
Handlebars.registerHelper("concat", (...args) => {
args.pop() // remove handlebars options object
return args.join("")
})
}
static async loadCompendium(packId) {
const pack = game.packs.get(packId)
if (!pack) return []
return await pack.getDocuments()
}
}

71
oath-hammer.mjs Normal file
View File

@@ -0,0 +1,71 @@
import { SYSTEM } from "./module/config/system.mjs"
globalThis.SYSTEM = SYSTEM
import * as models from "./module/models/_module.mjs"
import * as documents from "./module/documents/_module.mjs"
import * as applications from "./module/applications/_module.mjs"
import OathHammerUtils from "./module/utils.mjs"
Hooks.once("init", function () {
console.info(SYSTEM.ASCII)
console.info("Oath Hammer | Initializing System")
globalThis.oathHammer = game.system
game.system.CONST = SYSTEM
game.system.api = { applications, models, documents }
CONFIG.Actor.documentClass = documents.OathHammerActor
CONFIG.Actor.dataModels = {
character: models.OathHammerCharacter,
npc: models.OathHammerNPC
}
CONFIG.Item.documentClass = documents.OathHammerItem
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
}
foundry.documents.collections.Actors.unregisterSheet("core", foundry.appv1.sheets.ActorSheet)
foundry.documents.collections.Actors.registerSheet("fvtt-oath-hammer", applications.OathHammerCharacterSheet, {
types: ["character"],
makeDefault: true,
label: "OATHHAMMER.Sheet.Character"
})
foundry.documents.collections.Actors.registerSheet("fvtt-oath-hammer", applications.OathHammerNPCSheet, {
types: ["npc"],
makeDefault: true,
label: "OATHHAMMER.Sheet.NPC"
})
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" })
foundry.documents.collections.Items.registerSheet("fvtt-oath-hammer", applications.OathHammerMiracleSheet, { types: ["miracle"], makeDefault: true, label: "OATHHAMMER.Sheet.Miracle" })
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" })
OathHammerUtils.registerHandlebarsHelpers()
console.info("Oath Hammer | System Initialized")
})
Hooks.once("ready", function () {
console.info("Oath Hammer | System Ready")
})

39
system.json Normal file
View File

@@ -0,0 +1,39 @@
{
"id": "fvtt-oath-hammer",
"title": "Oath Hammer RPG",
"description": "Oath Hammer RPG System for FoundryVTT",
"version": "13.0.0",
"compatibility": { "minimum": "13", "verified": "13" },
"esmodules": ["oath-hammer.mjs"],
"styles": ["css/fvtt-oath-hammer.css"],
"languages": [{ "lang": "en", "name": "English", "path": "lang/en.json" }],
"documentTypes": {
"Actor": {
"character": { "htmlFields": ["description", "notes"] },
"npc": { "htmlFields": ["description", "notes"] }
},
"Item": {
"weapon": { "htmlFields": ["description"] },
"armor": { "htmlFields": ["description"] },
"shield": { "htmlFields": ["description"] },
"ammunition": { "htmlFields": ["description"] },
"equipment": { "htmlFields": ["description"] },
"spell": { "htmlFields": ["effect"] },
"miracle": { "htmlFields": ["effect"] },
"magic-item": { "htmlFields": ["description"] },
"ability": { "htmlFields": ["description"] },
"oath": { "htmlFields": ["benefit", "violation"] },
"condition": { "htmlFields": ["description"] }
}
},
"grid": { "distance": 5, "units": "ft" },
"primaryTokenAttribute": "grit",
"socket": true,
"background": "systems/fvtt-oath-hammer/assets/ui/oath_hammer_paper.webp",
"flags": {
"hotReload": {
"extensions": ["css", "hbs", "json"],
"paths": ["css/", "lang/", "assets/", "templates/"]
}
}
}

View File

@@ -0,0 +1,99 @@
<section data-tab="combat" class="tab">
<fieldset>
<legend>{{localize "OATHHAMMER.Label.Defense"}}</legend>
<div class="flexrow">
<div class="form-group">
<label>{{localize "OATHHAMMER.Label.DefenseValue"}}</label>
<input type="text" value="{{system.defense.value}}" disabled />
</div>
<div class="form-group">
<label>{{localize "OATHHAMMER.Label.ArmorRating"}}</label>
{{formInput systemFields.defense.fields.armorRating value=system.defense.armorRating name="system.defense.armorRating" disabled=isPlayMode}}
</div>
<div class="form-group">
<label>{{localize "OATHHAMMER.Label.DefenseBonus"}}</label>
{{formInput systemFields.defense.fields.bonus value=system.defense.bonus name="system.defense.bonus" disabled=isPlayMode}}
</div>
</div>
</fieldset>
<fieldset>
<legend>{{localize "OATHHAMMER.Label.Weapons"}}
{{#unless isPlayMode}}<a data-action="createWeapon" class="create-btn"><i class="fa-solid fa-plus"></i></a>{{/unless}}
</legend>
{{#if weapons.length}}
<ul class="item-list">
{{#each weapons as |weapon|}}
<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>
{{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>
<a data-action="delete" data-item-id="{{weapon.id}}" data-item-uuid="{{weapon.uuid}}"><i class="fa-solid fa-trash"></i></a>
{{/unless}}
</li>
{{/each}}
</ul>
{{else}}
<p class="no-items">{{localize "OATHHAMMER.Label.NoWeapons"}}</p>
{{/if}}
</fieldset>
<fieldset>
<legend>{{localize "OATHHAMMER.Label.Armor"}}
</legend>
{{#if armors.length}}
<ul class="item-list">
{{#each armors as |armor|}}
<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>
{{#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>
{{/unless}}
</li>
{{/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)}}
<p class="no-items">{{localize "OATHHAMMER.Label.NoArmor"}}</p>
{{/unless}}
</fieldset>
{{#if ammunition.length}}
<fieldset>
<legend>{{localize "OATHHAMMER.Label.Ammunition"}}</legend>
<ul class="item-list">
{{#each ammunition as |ammo|}}
<li class="item-entry flexrow" data-item-id="{{ammo.id}}" data-item-uuid="{{ammo.uuid}}">
<img src="{{ammo.img}}" class="item-img" />
<span class="item-name">{{ammo.name}}</span>
<span class="item-detail">{{ammo.system.quantity}}</span>
{{#unless ../isPlayMode}}
<a data-action="edit" data-item-id="{{ammo.id}}" data-item-uuid="{{ammo.uuid}}"><i class="fa-solid fa-edit"></i></a>
<a data-action="delete" data-item-id="{{ammo.id}}" data-item-uuid="{{ammo.uuid}}"><i class="fa-solid fa-trash"></i></a>
{{/unless}}
</li>
{{/each}}
</ul>
</fieldset>
{{/if}}
</section>

View File

@@ -0,0 +1,61 @@
<section data-tab="equipment" class="tab">
<fieldset>
<legend>{{localize "OATHHAMMER.Label.Equipment"}}
{{#unless isPlayMode}}<a data-action="createEquipment" class="create-btn"><i class="fa-solid fa-plus"></i></a>{{/unless}}
</legend>
{{#if equipment.length}}
<ul class="item-list">
{{#each equipment as |equip|}}
<li class="item-entry flexrow" data-item-id="{{equip.id}}" data-item-uuid="{{equip.uuid}}">
<img src="{{equip.img}}" class="item-img" />
<span class="item-name">{{equip.name}}</span>
<span class="item-detail">{{localize equip.system.itemType}}</span>
<span class="item-detail">×{{equip.system.quantity}}</span>
{{#unless ../isPlayMode}}
<a data-action="edit" data-item-id="{{equip.id}}" data-item-uuid="{{equip.uuid}}"><i class="fa-solid fa-edit"></i></a>
<a data-action="delete" data-item-id="{{equip.id}}" data-item-uuid="{{equip.uuid}}"><i class="fa-solid fa-trash"></i></a>
{{/unless}}
</li>
{{/each}}
</ul>
{{else}}
<p class="no-items">{{localize "OATHHAMMER.Label.NoEquipment"}}</p>
{{/if}}
</fieldset>
{{#if magicItems.length}}
<fieldset>
<legend>{{localize "OATHHAMMER.Label.MagicItems"}}</legend>
<ul class="item-list">
{{#each magicItems as |mi|}}
<li class="item-entry flexrow" data-item-id="{{mi.id}}" data-item-uuid="{{mi.uuid}}">
<img src="{{mi.img}}" class="item-img" />
<span class="item-name">{{mi.name}}</span>
<span class="item-detail">{{localize mi.system.rarity}}</span>
{{#unless ../isPlayMode}}
<a data-action="edit" data-item-id="{{mi.id}}" data-item-uuid="{{mi.uuid}}"><i class="fa-solid fa-edit"></i></a>
<a data-action="delete" data-item-id="{{mi.id}}" data-item-uuid="{{mi.uuid}}"><i class="fa-solid fa-trash"></i></a>
{{/unless}}
</li>
{{/each}}
</ul>
</fieldset>
{{/if}}
{{#if conditions.length}}
<fieldset>
<legend>{{localize "OATHHAMMER.Label.Conditions"}}</legend>
<ul class="item-list">
{{#each conditions as |cond|}}
<li class="item-entry flexrow" data-item-id="{{cond.id}}" data-item-uuid="{{cond.uuid}}">
<img src="{{cond.img}}" class="item-img" />
<span class="item-name">{{cond.name}}</span>
<span class="item-detail">{{localize cond.system.conditionType}}</span>
{{#unless ../isPlayMode}}
<a data-action="edit" data-item-id="{{cond.id}}" data-item-uuid="{{cond.uuid}}"><i class="fa-solid fa-edit"></i></a>
<a data-action="delete" data-item-id="{{cond.id}}" data-item-uuid="{{cond.uuid}}"><i class="fa-solid fa-trash"></i></a>
{{/unless}}
</li>
{{/each}}
</ul>
</fieldset>
{{/if}}
</section>

View File

@@ -0,0 +1,73 @@
<section data-tab="identity" class="tab">
<fieldset>
<legend>{{localize "OATHHAMMER.Label.Biodata"}}</legend>
<div class="flexrow">
<div class="biodata-col">
{{formField systemFields.biodata.fields.lineage value=system.biodata.lineage name="system.biodata.lineage" localize=true disabled=isPlayMode}}
{{formField systemFields.biodata.fields.class value=system.biodata.class name="system.biodata.class" localize=true disabled=isPlayMode}}
{{formField systemFields.biodata.fields.alignment value=system.biodata.alignment name="system.biodata.alignment" disabled=isPlayMode}}
{{formField systemFields.biodata.fields.age value=system.biodata.age name="system.biodata.age" disabled=isPlayMode}}
{{formField systemFields.biodata.fields.gender value=system.biodata.gender name="system.biodata.gender" disabled=isPlayMode}}
</div>
<div class="biodata-col">
{{formField systemFields.biodata.fields.height value=system.biodata.height name="system.biodata.height" disabled=isPlayMode}}
{{formField systemFields.biodata.fields.weight value=system.biodata.weight name="system.biodata.weight" disabled=isPlayMode}}
{{formField systemFields.biodata.fields.eyes value=system.biodata.eyes name="system.biodata.eyes" disabled=isPlayMode}}
{{formField systemFields.biodata.fields.hair value=system.biodata.hair name="system.biodata.hair" disabled=isPlayMode}}
</div>
</div>
</fieldset>
<fieldset>
<legend>{{localize "OATHHAMMER.Label.Experience"}}</legend>
<div class="flexrow">
<div class="form-group">
<label>{{localize "OATHHAMMER.Label.Level"}}</label>
{{formInput systemFields.experience.fields.level value=system.experience.level name="system.experience.level" disabled=isPlayMode}}
</div>
<div class="form-group">
<label>{{localize "OATHHAMMER.Label.XP"}}</label>
{{formInput systemFields.experience.fields.current value=system.experience.current name="system.experience.current" disabled=isPlayMode}}
</div>
<div class="form-group">
<label>{{localize "OATHHAMMER.Label.TotalXP"}}</label>
{{formInput systemFields.experience.fields.total value=system.experience.total name="system.experience.total" disabled=isPlayMode}}
</div>
</div>
</fieldset>
{{#if abilities.length}}
<fieldset>
<legend>{{localize "OATHHAMMER.Label.Abilities"}}</legend>
<ul class="item-list">
{{#each abilities as |ability|}}
<li class="item-entry flexrow" data-item-id="{{ability.id}}" data-item-uuid="{{ability.uuid}}">
<img src="{{ability.img}}" class="item-img" />
<span class="item-name">{{ability.name}}</span>
<span class="item-type">{{localize ability.system.abilityType}}</span>
{{#unless ../isPlayMode}}
<a data-action="edit" data-item-id="{{ability.id}}" data-item-uuid="{{ability.uuid}}"><i class="fa-solid fa-edit"></i></a>
<a data-action="delete" data-item-id="{{ability.id}}" data-item-uuid="{{ability.uuid}}"><i class="fa-solid fa-trash"></i></a>
{{/unless}}
</li>
{{/each}}
</ul>
</fieldset>
{{/if}}
{{#if oaths.length}}
<fieldset>
<legend>{{localize "OATHHAMMER.Label.Oaths"}}</legend>
<ul class="item-list">
{{#each oaths as |oath|}}
<li class="item-entry flexrow" data-item-id="{{oath.id}}" data-item-uuid="{{oath.uuid}}">
<img src="{{oath.img}}" class="item-img" />
<span class="item-name">{{oath.name}}</span>
<span class="item-type">{{localize oath.system.oathType}}</span>
{{#unless ../isPlayMode}}
<a data-action="edit" data-item-id="{{oath.id}}" data-item-uuid="{{oath.uuid}}"><i class="fa-solid fa-edit"></i></a>
<a data-action="delete" data-item-id="{{oath.id}}" data-item-uuid="{{oath.uuid}}"><i class="fa-solid fa-trash"></i></a>
{{/unless}}
</li>
{{/each}}
</ul>
</fieldset>
{{/if}}
</section>

View File

@@ -0,0 +1,57 @@
<section data-tab="magic" class="tab">
<fieldset>
<legend>{{localize "OATHHAMMER.Label.ArcaneStress"}}</legend>
<div class="flexrow">
<label>{{localize "OATHHAMMER.Label.StressValue"}}</label>
{{formInput systemFields.arcaneStress.fields.value value=system.arcaneStress.value name="system.arcaneStress.value" disabled=isPlayMode}}
<span>/</span>
{{formInput systemFields.arcaneStress.fields.threshold value=system.arcaneStress.threshold name="system.arcaneStress.threshold" disabled=isPlayMode}}
</div>
</fieldset>
<fieldset>
<legend>{{localize "OATHHAMMER.Label.Spells"}}
{{#unless isPlayMode}}<a data-action="createSpell" class="create-btn"><i class="fa-solid fa-plus"></i></a>{{/unless}}
</legend>
{{#if spells.length}}
<ul class="item-list">
{{#each spells as |spell|}}
<li class="item-entry flexrow" data-item-id="{{spell.id}}" data-item-uuid="{{spell.uuid}}">
<img src="{{spell.img}}" class="item-img" />
<span class="item-name">{{spell.name}}</span>
<span class="item-detail">Lv.{{spell.system.level}}</span>
<span class="item-detail">{{localize spell.system.tradition}}</span>
<span class="item-detail">AS: {{spell.system.arcaneStress}}</span>
{{#unless ../isPlayMode}}
<a data-action="edit" data-item-id="{{spell.id}}" data-item-uuid="{{spell.uuid}}"><i class="fa-solid fa-edit"></i></a>
<a data-action="delete" data-item-id="{{spell.id}}" data-item-uuid="{{spell.uuid}}"><i class="fa-solid fa-trash"></i></a>
{{/unless}}
</li>
{{/each}}
</ul>
{{else}}
<p class="no-items">{{localize "OATHHAMMER.Label.NoSpells"}}</p>
{{/if}}
</fieldset>
<fieldset>
<legend>{{localize "OATHHAMMER.Label.Miracles"}}
{{#unless isPlayMode}}<a data-action="createMiracle" class="create-btn"><i class="fa-solid fa-plus"></i></a>{{/unless}}
</legend>
{{#if miracles.length}}
<ul class="item-list">
{{#each miracles as |miracle|}}
<li class="item-entry flexrow" data-item-id="{{miracle.id}}" data-item-uuid="{{miracle.uuid}}">
<img src="{{miracle.img}}" class="item-img" />
<span class="item-name">{{miracle.name}}</span>
<span class="item-detail">Piety: {{miracle.system.piety}}</span>
{{#unless ../isPlayMode}}
<a data-action="edit" data-item-id="{{miracle.id}}" data-item-uuid="{{miracle.uuid}}"><i class="fa-solid fa-edit"></i></a>
<a data-action="delete" data-item-id="{{miracle.id}}" data-item-uuid="{{miracle.uuid}}"><i class="fa-solid fa-trash"></i></a>
{{/unless}}
</li>
{{/each}}
</ul>
{{else}}
<p class="no-items">{{localize "OATHHAMMER.Label.NoMiracles"}}</p>
{{/if}}
</fieldset>
</section>

View File

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

View File

@@ -0,0 +1,76 @@
<section class="character-main character-main-{{ifThen isPlayMode 'play' 'edit'}}">
<fieldset>
<legend>{{localize "OATHHAMMER.Label.Character"}}</legend>
<div class="character-pc character-pc-{{ifThen isPlayMode 'play' 'edit'}}">
<div class="character-left">
<div class="character-portrait">
<img class="actor-img" src="{{actor.img}}" data-edit="img" data-action="editImage" data-tooltip="{{actor.name}}" />
</div>
<fieldset>
<div class="flexrow character-resource">
<span class="resource-label">{{localize "OATHHAMMER.Label.Grit"}}</span>
{{formInput systemFields.grit.fields.value value=system.grit.value name="system.grit.value" disabled=isPlayMode}}
<span>/</span>
{{formInput systemFields.grit.fields.max value=system.grit.max name="system.grit.max" disabled=isPlayMode}}
</div>
<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}}
</div>
<div class="flexrow character-resource">
<span class="resource-label">{{localize "OATHHAMMER.Label.Defense"}}</span>
<input type="text" value="{{system.defense.value}}" disabled class="defense-display" />
</div>
<div class="flexrow character-resource">
<span class="resource-label">{{localize "OATHHAMMER.Label.Movement"}}</span>
{{formInput systemFields.movement.fields.base value=system.movement.base name="system.movement.base" disabled=isPlayMode}}
</div>
</fieldset>
</div>
<div class="character-right">
<div class="character-name">
{{formInput fields.name value=source.name rootId=partId disabled=isPlayMode}}
<a class="control" data-action="toggleSheet" data-tooltip="OATHHAMMER.ToggleSheet" data-tooltip-direction="UP">
<i class="fa-solid fa-user-{{ifThen isPlayMode 'lock' 'pen'}}"></i>
</a>
</div>
<fieldset class="character-attributes character-attributes-{{ifThen isPlayMode 'play' 'edit'}}">
<legend>{{localize "OATHHAMMER.Label.Attributes"}}</legend>
<div class="attributes-grid">
{{#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}}
</div>
{{/each}}
</div>
</fieldset>
<fieldset class="character-arcane-stress">
<legend>{{localize "OATHHAMMER.Label.ArcaneStress"}}</legend>
<div class="flexrow">
{{formInput systemFields.arcaneStress.fields.value value=system.arcaneStress.value name="system.arcaneStress.value" disabled=isPlayMode}}
<span>/</span>
{{formInput systemFields.arcaneStress.fields.threshold value=system.arcaneStress.threshold name="system.arcaneStress.threshold" disabled=isPlayMode}}
</div>
</fieldset>
</div>
</div>
</fieldset>
<fieldset class="currency-bar">
<legend>{{localize "OATHHAMMER.Label.Currency"}}</legend>
<div class="flexrow">
<div class="currency-item">
<label>{{localize "OATHHAMMER.Currency.GP"}}</label>
{{formInput systemFields.currency.fields.gold value=system.currency.gold name="system.currency.gold" disabled=isPlayMode}}
</div>
<div class="currency-item">
<label>{{localize "OATHHAMMER.Currency.SP"}}</label>
{{formInput systemFields.currency.fields.silver value=system.currency.silver name="system.currency.silver" disabled=isPlayMode}}
</div>
<div class="currency-item">
<label>{{localize "OATHHAMMER.Currency.CP"}}</label>
{{formInput systemFields.currency.fields.copper value=system.currency.copper name="system.currency.copper" disabled=isPlayMode}}
</div>
</div>
</fieldset>
</section>

View File

@@ -0,0 +1,23 @@
<section data-tab="combat" class="tab">
<fieldset>
<legend>{{localize "OATHHAMMER.Label.Weapons"}}</legend>
{{#if weapons.length}}
<ul class="item-list">
{{#each weapons as |weapon|}}
<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>
{{#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>
{{/unless}}
</li>
{{/each}}
</ul>
{{else}}
<p class="no-items">{{localize "OATHHAMMER.Label.NoWeapons"}}</p>
{{/if}}
</fieldset>
</section>

View File

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

View File

@@ -0,0 +1,62 @@
<section class="npc-main npc-main-{{ifThen isPlayMode 'play' 'edit'}}">
<fieldset>
<legend>{{localize "OATHHAMMER.Label.NPC"}}</legend>
<div class="npc-pc flexrow">
<div class="npc-left">
<img class="actor-img" src="{{actor.img}}" data-edit="img" data-action="editImage" data-tooltip="{{actor.name}}" />
<fieldset>
<div class="flexrow character-resource">
<span class="resource-label">{{localize "OATHHAMMER.Label.Grit"}}</span>
{{formInput systemFields.grit.fields.value value=system.grit.value name="system.grit.value" disabled=isPlayMode}}
<span>/</span>
<input type="text" value="{{system.grit.max}}" disabled />
</div>
<div class="flexrow character-resource">
<span class="resource-label">{{localize "OATHHAMMER.Label.Defense"}}</span>
{{formInput systemFields.defense.fields.value value=system.defense.value name="system.defense.value" disabled=isPlayMode}}
</div>
<div class="flexrow character-resource">
<span class="resource-label">{{localize "OATHHAMMER.Label.Movement"}}</span>
{{formInput systemFields.movement.fields.base value=system.movement.base name="system.movement.base" disabled=isPlayMode}}
</div>
</fieldset>
</div>
<div class="npc-right">
<div class="character-name">
{{formInput fields.name value=source.name rootId=partId disabled=isPlayMode}}
<a class="control" data-action="toggleSheet" data-tooltip="OATHHAMMER.ToggleSheet" data-tooltip-direction="UP">
<i class="fa-solid fa-user-{{ifThen isPlayMode 'lock' 'pen'}}"></i>
</a>
</div>
<fieldset>
<legend>{{localize "OATHHAMMER.Label.Stats"}}</legend>
<div class="flexrow">
<div class="form-group">
<label>{{localize "OATHHAMMER.Label.CR"}}</label>
{{formInput systemFields.challengeRating value=system.challengeRating name="system.challengeRating" disabled=isPlayMode}}
</div>
<div class="form-group">
<label>{{localize "OATHHAMMER.Label.AttackBonus"}}</label>
{{formInput systemFields.attackBonus value=system.attackBonus name="system.attackBonus" disabled=isPlayMode}}
</div>
<div class="form-group">
<label>{{localize "OATHHAMMER.Label.DamageBonus"}}</label>
{{formInput systemFields.damageBonus value=system.damageBonus name="system.damageBonus" disabled=isPlayMode}}
</div>
</div>
</fieldset>
<fieldset>
<legend>{{localize "OATHHAMMER.Label.Attributes"}}</legend>
<div class="attributes-grid">
{{#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}}
</div>
{{/each}}
</div>
</fieldset>
</div>
</div>
</fieldset>
</section>

View File

@@ -0,0 +1,18 @@
<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.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"}}
</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

@@ -0,0 +1,21 @@
<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.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"}}
</div>
<div class="align-top">
{{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

@@ -0,0 +1,23 @@
<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.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"}}
</div>
<div class="align-top">
{{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>
</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

@@ -0,0 +1,17 @@
<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

@@ -0,0 +1,21 @@
<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.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"}}
</div>
<div class="align-top">
{{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

@@ -0,0 +1,28 @@
<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.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"}}
</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.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}}
</fieldset>
</section>

View File

@@ -0,0 +1,28 @@
<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.piety value=system.piety name="system.piety"}}
{{formField systemFields.castingTime value=system.castingTime name="system.castingTime"}}
{{formField systemFields.range value=system.range name="system.range"}}
{{formField systemFields.duration value=system.duration name="system.duration"}}
</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"}}
</div>
</div>
<fieldset>
<legend>{{localize "OATHHAMMER.Label.Effect"}}</legend>
{{formInput systemFields.effect enriched=enrichedEffect value=system.effect name="system.effect" toggled=true}}
</fieldset>
</section>

View File

@@ -0,0 +1,20 @@
<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.oathType value=system.oathType name="system.oathType" localize=true}}
{{formField systemFields.violated value=system.violated name="system.violated"}}
</div>
</div>
<fieldset>
<legend>{{localize "OATHHAMMER.Label.Benefit"}}</legend>
{{formInput systemFields.benefit enriched=enrichedBenefit value=system.benefit name="system.benefit" toggled=true}}
</fieldset>
<fieldset>
<legend>{{localize "OATHHAMMER.Label.Violation"}}</legend>
{{formInput systemFields.violation enriched=enrichedViolation value=system.violation name="system.violation" toggled=true}}
</fieldset>
</section>

View File

@@ -0,0 +1,21 @@
<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

@@ -0,0 +1,31 @@
<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.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.range value=system.range name="system.range"}}
{{formField systemFields.duration value=system.duration name="system.duration"}}
</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"}}
</div>
</div>
<fieldset>
<legend>{{localize "OATHHAMMER.Label.Effect"}}</legend>
{{formInput systemFields.effect enriched=enrichedEffect value=system.effect name="system.effect" toggled=true}}
</fieldset>
</section>

View File

@@ -0,0 +1,27 @@
<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.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}}
</div>
<div class="align-top">
{{formField systemFields.properties value=system.properties name="system.properties"}}
{{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>
</div>
<fieldset>
<legend>{{localize "OATHHAMMER.Label.Description"}}</legend>
{{formInput systemFields.description enriched=enrichedDescription value=system.description name="system.description" toggled=true}}
</fieldset>
</section>