commit 03bc0b7043a55908aed7e9301cb64f815617c103 Author: LeRatierBretonnier Date: Sat Mar 7 17:19:40 2026 +0100 Initial import diff --git a/assets/fonts/Blue Dragon.ttf b/assets/fonts/Blue Dragon.ttf new file mode 100644 index 0000000..6cce47b Binary files /dev/null and b/assets/fonts/Blue Dragon.ttf differ diff --git a/assets/fonts/SHERWOOD.TTF b/assets/fonts/SHERWOOD.TTF new file mode 100644 index 0000000..0e7d5f4 Binary files /dev/null and b/assets/fonts/SHERWOOD.TTF differ diff --git a/assets/fonts/Sherwood.otf b/assets/fonts/Sherwood.otf new file mode 100644 index 0000000..ff90f84 Binary files /dev/null and b/assets/fonts/Sherwood.otf differ diff --git a/assets/fonts/Sherwood.woff b/assets/fonts/Sherwood.woff new file mode 100644 index 0000000..60b1f2c Binary files /dev/null and b/assets/fonts/Sherwood.woff differ diff --git a/assets/images/alternate_art.webp b/assets/images/alternate_art.webp new file mode 100644 index 0000000..e98ef88 Binary files /dev/null and b/assets/images/alternate_art.webp differ diff --git a/assets/images/cover_art.webp b/assets/images/cover_art.webp new file mode 100644 index 0000000..138200d Binary files /dev/null and b/assets/images/cover_art.webp differ diff --git a/assets/images/oathhammer_map.webp b/assets/images/oathhammer_map.webp new file mode 100644 index 0000000..f470512 Binary files /dev/null and b/assets/images/oathhammer_map.webp differ diff --git a/assets/logos/official_logo_01.webp b/assets/logos/official_logo_01.webp new file mode 100644 index 0000000..fa21eef Binary files /dev/null and b/assets/logos/official_logo_01.webp differ diff --git a/assets/logos/third_party_logo_01.webp b/assets/logos/third_party_logo_01.webp new file mode 100644 index 0000000..75593cc Binary files /dev/null and b/assets/logos/third_party_logo_01.webp differ diff --git a/assets/ui/oath_hammer_paper.webp b/assets/ui/oath_hammer_paper.webp new file mode 100644 index 0000000..9286b94 Binary files /dev/null and b/assets/ui/oath_hammer_paper.webp differ diff --git a/css/fvtt-oath-hammer.css b/css/fvtt-oath-hammer.css new file mode 100644 index 0000000..0832a64 --- /dev/null +++ b/css/fvtt-oath-hammer.css @@ -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; +} diff --git a/lang/en.json b/lang/en.json new file mode 100644 index 0000000..08ec4c0 --- /dev/null +++ b/lang/en.json @@ -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" + } + } + } +} diff --git a/module/applications/_module.mjs b/module/applications/_module.mjs new file mode 100644 index 0000000..6308201 --- /dev/null +++ b/module/applications/_module.mjs @@ -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" diff --git a/module/applications/sheets/ability-sheet.mjs b/module/applications/sheets/ability-sheet.mjs new file mode 100644 index 0000000..d67d443 --- /dev/null +++ b/module/applications/sheets/ability-sheet.mjs @@ -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 + } +} diff --git a/module/applications/sheets/ammunition-sheet.mjs b/module/applications/sheets/ammunition-sheet.mjs new file mode 100644 index 0000000..0fcbaec --- /dev/null +++ b/module/applications/sheets/ammunition-sheet.mjs @@ -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 + } +} diff --git a/module/applications/sheets/armor-sheet.mjs b/module/applications/sheets/armor-sheet.mjs new file mode 100644 index 0000000..241a74d --- /dev/null +++ b/module/applications/sheets/armor-sheet.mjs @@ -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 + } +} diff --git a/module/applications/sheets/base-actor-sheet.mjs b/module/applications/sheets/base-actor-sheet.mjs new file mode 100644 index 0000000..6146980 --- /dev/null +++ b/module/applications/sheets/base-actor-sheet.mjs @@ -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() + } +} diff --git a/module/applications/sheets/base-item-sheet.mjs b/module/applications/sheets/base-item-sheet.mjs new file mode 100644 index 0000000..d6f5674 --- /dev/null +++ b/module/applications/sheets/base-item-sheet.mjs @@ -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() + } +} diff --git a/module/applications/sheets/character-sheet.mjs b/module/applications/sheets/character-sheet.mjs new file mode 100644 index 0000000..813ca98 --- /dev/null +++ b/module/applications/sheets/character-sheet.mjs @@ -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" }]) + } +} diff --git a/module/applications/sheets/condition-sheet.mjs b/module/applications/sheets/condition-sheet.mjs new file mode 100644 index 0000000..2a296f0 --- /dev/null +++ b/module/applications/sheets/condition-sheet.mjs @@ -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 + } +} diff --git a/module/applications/sheets/equipment-sheet.mjs b/module/applications/sheets/equipment-sheet.mjs new file mode 100644 index 0000000..634c97c --- /dev/null +++ b/module/applications/sheets/equipment-sheet.mjs @@ -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 + } +} diff --git a/module/applications/sheets/magic-item-sheet.mjs b/module/applications/sheets/magic-item-sheet.mjs new file mode 100644 index 0000000..64440cb --- /dev/null +++ b/module/applications/sheets/magic-item-sheet.mjs @@ -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 + } +} diff --git a/module/applications/sheets/miracle-sheet.mjs b/module/applications/sheets/miracle-sheet.mjs new file mode 100644 index 0000000..fc06e7e --- /dev/null +++ b/module/applications/sheets/miracle-sheet.mjs @@ -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 + } +} diff --git a/module/applications/sheets/npc-sheet.mjs b/module/applications/sheets/npc-sheet.mjs new file mode 100644 index 0000000..2eac717 --- /dev/null +++ b/module/applications/sheets/npc-sheet.mjs @@ -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) + } + } +} diff --git a/module/applications/sheets/oath-sheet.mjs b/module/applications/sheets/oath-sheet.mjs new file mode 100644 index 0000000..38776a0 --- /dev/null +++ b/module/applications/sheets/oath-sheet.mjs @@ -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 + } +} diff --git a/module/applications/sheets/shield-sheet.mjs b/module/applications/sheets/shield-sheet.mjs new file mode 100644 index 0000000..92b1279 --- /dev/null +++ b/module/applications/sheets/shield-sheet.mjs @@ -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 + } +} diff --git a/module/applications/sheets/spell-sheet.mjs b/module/applications/sheets/spell-sheet.mjs new file mode 100644 index 0000000..98d6e9c --- /dev/null +++ b/module/applications/sheets/spell-sheet.mjs @@ -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 + } +} diff --git a/module/applications/sheets/weapon-sheet.mjs b/module/applications/sheets/weapon-sheet.mjs new file mode 100644 index 0000000..011bf52 --- /dev/null +++ b/module/applications/sheets/weapon-sheet.mjs @@ -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 + } +} diff --git a/module/config/system.mjs b/module/config/system.mjs new file mode 100644 index 0000000..1817e8e --- /dev/null +++ b/module/config/system.mjs @@ -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 +} diff --git a/module/documents/_module.mjs b/module/documents/_module.mjs new file mode 100644 index 0000000..7b41ef9 --- /dev/null +++ b/module/documents/_module.mjs @@ -0,0 +1,2 @@ +export { default as OathHammerActor } from "./actor.mjs" +export { default as OathHammerItem } from "./item.mjs" diff --git a/module/documents/actor.mjs b/module/documents/actor.mjs new file mode 100644 index 0000000..511c215 --- /dev/null +++ b/module/documents/actor.mjs @@ -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 + } +} diff --git a/module/documents/item.mjs b/module/documents/item.mjs new file mode 100644 index 0000000..b64a748 --- /dev/null +++ b/module/documents/item.mjs @@ -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) + } +} diff --git a/module/models/_module.mjs b/module/models/_module.mjs new file mode 100644 index 0000000..e5e2160 --- /dev/null +++ b/module/models/_module.mjs @@ -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" diff --git a/module/models/ability.mjs b/module/models/ability.mjs new file mode 100644 index 0000000..7f87f16 --- /dev/null +++ b/module/models/ability.mjs @@ -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"] +} diff --git a/module/models/ammunition.mjs b/module/models/ammunition.mjs new file mode 100644 index 0000000..256cf18 --- /dev/null +++ b/module/models/ammunition.mjs @@ -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"] +} diff --git a/module/models/armor.mjs b/module/models/armor.mjs new file mode 100644 index 0000000..9b5660f --- /dev/null +++ b/module/models/armor.mjs @@ -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"] +} diff --git a/module/models/character.mjs b/module/models/character.mjs new file mode 100644 index 0000000..b9dcdfc --- /dev/null +++ b/module/models/character.mjs @@ -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 + } +} diff --git a/module/models/condition.mjs b/module/models/condition.mjs new file mode 100644 index 0000000..f244a3c --- /dev/null +++ b/module/models/condition.mjs @@ -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"] +} diff --git a/module/models/equipment.mjs b/module/models/equipment.mjs new file mode 100644 index 0000000..ef16829 --- /dev/null +++ b/module/models/equipment.mjs @@ -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"] +} diff --git a/module/models/magic-item.mjs b/module/models/magic-item.mjs new file mode 100644 index 0000000..e83c3ba --- /dev/null +++ b/module/models/magic-item.mjs @@ -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"] +} diff --git a/module/models/miracle.mjs b/module/models/miracle.mjs new file mode 100644 index 0000000..f68429e --- /dev/null +++ b/module/models/miracle.mjs @@ -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"] +} diff --git a/module/models/npc.mjs b/module/models/npc.mjs new file mode 100644 index 0000000..8b477bc --- /dev/null +++ b/module/models/npc.mjs @@ -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 + } +} diff --git a/module/models/oath.mjs b/module/models/oath.mjs new file mode 100644 index 0000000..4033a5d --- /dev/null +++ b/module/models/oath.mjs @@ -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"] +} diff --git a/module/models/shield.mjs b/module/models/shield.mjs new file mode 100644 index 0000000..b4f6688 --- /dev/null +++ b/module/models/shield.mjs @@ -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"] +} diff --git a/module/models/spell.mjs b/module/models/spell.mjs new file mode 100644 index 0000000..5be17f4 --- /dev/null +++ b/module/models/spell.mjs @@ -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"] +} diff --git a/module/models/weapon.mjs b/module/models/weapon.mjs new file mode 100644 index 0000000..47b955a --- /dev/null +++ b/module/models/weapon.mjs @@ -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"] +} diff --git a/module/utils.mjs b/module/utils.mjs new file mode 100644 index 0000000..cee73ec --- /dev/null +++ b/module/utils.mjs @@ -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() + } +} diff --git a/oath-hammer.mjs b/oath-hammer.mjs new file mode 100644 index 0000000..c501f0c --- /dev/null +++ b/oath-hammer.mjs @@ -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") +}) diff --git a/system.json b/system.json new file mode 100644 index 0000000..d7a8549 --- /dev/null +++ b/system.json @@ -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/"] + } + } +} diff --git a/templates/actor/character-combat.hbs b/templates/actor/character-combat.hbs new file mode 100644 index 0000000..8c266b8 --- /dev/null +++ b/templates/actor/character-combat.hbs @@ -0,0 +1,99 @@ +
+
+ {{localize "OATHHAMMER.Label.Defense"}} +
+
+ + +
+
+ + {{formInput systemFields.defense.fields.armorRating value=system.defense.armorRating name="system.defense.armorRating" disabled=isPlayMode}} +
+
+ + {{formInput systemFields.defense.fields.bonus value=system.defense.bonus name="system.defense.bonus" disabled=isPlayMode}} +
+
+
+
+ {{localize "OATHHAMMER.Label.Weapons"}} + {{#unless isPlayMode}}{{/unless}} + + {{#if weapons.length}} +
    + {{#each weapons as |weapon|}} +
  • + + {{weapon.name}} + {{weapon.system.damageFormula}} + {{localize weapon.system.damageType}} + {{formField weapon.system.schema.fields.equipped value=weapon.system.equipped name="system.equipped"}} + {{#unless ../isPlayMode}} + + + {{/unless}} +
  • + {{/each}} +
+ {{else}} +

{{localize "OATHHAMMER.Label.NoWeapons"}}

+ {{/if}} +
+
+ {{localize "OATHHAMMER.Label.Armor"}} + + {{#if armors.length}} +
    + {{#each armors as |armor|}} +
  • + + {{armor.name}} + {{localize armor.system.armorType}} + AR: {{armor.system.armorRating}} + {{#unless ../isPlayMode}} + + + {{/unless}} +
  • + {{/each}} +
+ {{/if}} + {{#if shields.length}} +
    + {{#each shields as |shield|}} +
  • + + {{shield.name}} + +{{shield.system.shieldBonus}} + {{#unless ../isPlayMode}} + + + {{/unless}} +
  • + {{/each}} +
+ {{/if}} + {{#unless (or armors.length shields.length)}} +

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

+ {{/unless}} +
+ {{#if ammunition.length}} +
+ {{localize "OATHHAMMER.Label.Ammunition"}} +
    + {{#each ammunition as |ammo|}} +
  • + + {{ammo.name}} + {{ammo.system.quantity}} + {{#unless ../isPlayMode}} + + + {{/unless}} +
  • + {{/each}} +
+
+ {{/if}} +
diff --git a/templates/actor/character-equipment.hbs b/templates/actor/character-equipment.hbs new file mode 100644 index 0000000..8ede981 --- /dev/null +++ b/templates/actor/character-equipment.hbs @@ -0,0 +1,61 @@ +
+
+ {{localize "OATHHAMMER.Label.Equipment"}} + {{#unless isPlayMode}}{{/unless}} + + {{#if equipment.length}} +
    + {{#each equipment as |equip|}} +
  • + + {{equip.name}} + {{localize equip.system.itemType}} + ×{{equip.system.quantity}} + {{#unless ../isPlayMode}} + + + {{/unless}} +
  • + {{/each}} +
+ {{else}} +

{{localize "OATHHAMMER.Label.NoEquipment"}}

+ {{/if}} +
+ {{#if magicItems.length}} +
+ {{localize "OATHHAMMER.Label.MagicItems"}} +
    + {{#each magicItems as |mi|}} +
  • + + {{mi.name}} + {{localize mi.system.rarity}} + {{#unless ../isPlayMode}} + + + {{/unless}} +
  • + {{/each}} +
+
+ {{/if}} + {{#if conditions.length}} +
+ {{localize "OATHHAMMER.Label.Conditions"}} +
    + {{#each conditions as |cond|}} +
  • + + {{cond.name}} + {{localize cond.system.conditionType}} + {{#unless ../isPlayMode}} + + + {{/unless}} +
  • + {{/each}} +
+
+ {{/if}} +
diff --git a/templates/actor/character-identity.hbs b/templates/actor/character-identity.hbs new file mode 100644 index 0000000..96b5580 --- /dev/null +++ b/templates/actor/character-identity.hbs @@ -0,0 +1,73 @@ +
+
+ {{localize "OATHHAMMER.Label.Biodata"}} +
+
+ {{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}} +
+
+ {{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}} +
+
+
+
+ {{localize "OATHHAMMER.Label.Experience"}} +
+
+ + {{formInput systemFields.experience.fields.level value=system.experience.level name="system.experience.level" disabled=isPlayMode}} +
+
+ + {{formInput systemFields.experience.fields.current value=system.experience.current name="system.experience.current" disabled=isPlayMode}} +
+
+ + {{formInput systemFields.experience.fields.total value=system.experience.total name="system.experience.total" disabled=isPlayMode}} +
+
+
+ {{#if abilities.length}} +
+ {{localize "OATHHAMMER.Label.Abilities"}} +
    + {{#each abilities as |ability|}} +
  • + + {{ability.name}} + {{localize ability.system.abilityType}} + {{#unless ../isPlayMode}} + + + {{/unless}} +
  • + {{/each}} +
+
+ {{/if}} + {{#if oaths.length}} +
+ {{localize "OATHHAMMER.Label.Oaths"}} +
    + {{#each oaths as |oath|}} +
  • + + {{oath.name}} + {{localize oath.system.oathType}} + {{#unless ../isPlayMode}} + + + {{/unless}} +
  • + {{/each}} +
+
+ {{/if}} +
diff --git a/templates/actor/character-magic.hbs b/templates/actor/character-magic.hbs new file mode 100644 index 0000000..546e5d3 --- /dev/null +++ b/templates/actor/character-magic.hbs @@ -0,0 +1,57 @@ +
+
+ {{localize "OATHHAMMER.Label.ArcaneStress"}} +
+ + {{formInput systemFields.arcaneStress.fields.value value=system.arcaneStress.value name="system.arcaneStress.value" disabled=isPlayMode}} + / + {{formInput systemFields.arcaneStress.fields.threshold value=system.arcaneStress.threshold name="system.arcaneStress.threshold" disabled=isPlayMode}} +
+
+
+ {{localize "OATHHAMMER.Label.Spells"}} + {{#unless isPlayMode}}{{/unless}} + + {{#if spells.length}} +
    + {{#each spells as |spell|}} +
  • + + {{spell.name}} + Lv.{{spell.system.level}} + {{localize spell.system.tradition}} + AS: {{spell.system.arcaneStress}} + {{#unless ../isPlayMode}} + + + {{/unless}} +
  • + {{/each}} +
+ {{else}} +

{{localize "OATHHAMMER.Label.NoSpells"}}

+ {{/if}} +
+
+ {{localize "OATHHAMMER.Label.Miracles"}} + {{#unless isPlayMode}}{{/unless}} + + {{#if miracles.length}} +
    + {{#each miracles as |miracle|}} +
  • + + {{miracle.name}} + Piety: {{miracle.system.piety}} + {{#unless ../isPlayMode}} + + + {{/unless}} +
  • + {{/each}} +
+ {{else}} +

{{localize "OATHHAMMER.Label.NoMiracles"}}

+ {{/if}} +
+
diff --git a/templates/actor/character-notes.hbs b/templates/actor/character-notes.hbs new file mode 100644 index 0000000..4852ca4 --- /dev/null +++ b/templates/actor/character-notes.hbs @@ -0,0 +1,10 @@ +
+
+ {{localize "OATHHAMMER.Label.Description"}} + {{formInput systemFields.description enriched=enrichedDescription value=system.description name="system.description" toggled=true}} +
+
+ {{localize "OATHHAMMER.Label.Notes"}} + {{formInput systemFields.notes enriched=enrichedNotes value=system.notes name="system.notes" toggled=true}} +
+
diff --git a/templates/actor/character-sheet.hbs b/templates/actor/character-sheet.hbs new file mode 100644 index 0000000..393178d --- /dev/null +++ b/templates/actor/character-sheet.hbs @@ -0,0 +1,76 @@ +
+
+ {{localize "OATHHAMMER.Label.Character"}} +
+
+
+ +
+
+
+ {{localize "OATHHAMMER.Label.Grit"}} + {{formInput systemFields.grit.fields.value value=system.grit.value name="system.grit.value" disabled=isPlayMode}} + / + {{formInput systemFields.grit.fields.max value=system.grit.max name="system.grit.max" disabled=isPlayMode}} +
+
+ {{localize "OATHHAMMER.Label.Luck"}} + {{formInput systemFields.luck.fields.value value=system.luck.value name="system.luck.value" disabled=isPlayMode}} +
+
+ {{localize "OATHHAMMER.Label.Defense"}} + +
+
+ {{localize "OATHHAMMER.Label.Movement"}} + {{formInput systemFields.movement.fields.base value=system.movement.base name="system.movement.base" disabled=isPlayMode}} +
+
+
+
+
+ {{formInput fields.name value=source.name rootId=partId disabled=isPlayMode}} + + + +
+
+ {{localize "OATHHAMMER.Label.Attributes"}} +
+ {{#each system.attributes as |attr key|}} +
+ + {{formInput (lookup ../systemFields.attributes.fields key).fields.rank value=attr.rank name=(concat "system.attributes." key ".rank") disabled=../isPlayMode}} +
+ {{/each}} +
+
+
+ {{localize "OATHHAMMER.Label.ArcaneStress"}} +
+ {{formInput systemFields.arcaneStress.fields.value value=system.arcaneStress.value name="system.arcaneStress.value" disabled=isPlayMode}} + / + {{formInput systemFields.arcaneStress.fields.threshold value=system.arcaneStress.threshold name="system.arcaneStress.threshold" disabled=isPlayMode}} +
+
+
+
+
+
+ {{localize "OATHHAMMER.Label.Currency"}} +
+
+ + {{formInput systemFields.currency.fields.gold value=system.currency.gold name="system.currency.gold" disabled=isPlayMode}} +
+
+ + {{formInput systemFields.currency.fields.silver value=system.currency.silver name="system.currency.silver" disabled=isPlayMode}} +
+
+ + {{formInput systemFields.currency.fields.copper value=system.currency.copper name="system.currency.copper" disabled=isPlayMode}} +
+
+
+
diff --git a/templates/actor/npc-combat.hbs b/templates/actor/npc-combat.hbs new file mode 100644 index 0000000..cc25bff --- /dev/null +++ b/templates/actor/npc-combat.hbs @@ -0,0 +1,23 @@ +
+
+ {{localize "OATHHAMMER.Label.Weapons"}} + {{#if weapons.length}} +
    + {{#each weapons as |weapon|}} +
  • + + {{weapon.name}} + {{weapon.system.damageFormula}} + {{localize weapon.system.damageType}} + {{#unless ../isPlayMode}} + + + {{/unless}} +
  • + {{/each}} +
+ {{else}} +

{{localize "OATHHAMMER.Label.NoWeapons"}}

+ {{/if}} +
+
diff --git a/templates/actor/npc-notes.hbs b/templates/actor/npc-notes.hbs new file mode 100644 index 0000000..4852ca4 --- /dev/null +++ b/templates/actor/npc-notes.hbs @@ -0,0 +1,10 @@ +
+
+ {{localize "OATHHAMMER.Label.Description"}} + {{formInput systemFields.description enriched=enrichedDescription value=system.description name="system.description" toggled=true}} +
+
+ {{localize "OATHHAMMER.Label.Notes"}} + {{formInput systemFields.notes enriched=enrichedNotes value=system.notes name="system.notes" toggled=true}} +
+
diff --git a/templates/actor/npc-sheet.hbs b/templates/actor/npc-sheet.hbs new file mode 100644 index 0000000..33af4c4 --- /dev/null +++ b/templates/actor/npc-sheet.hbs @@ -0,0 +1,62 @@ +
+
+ {{localize "OATHHAMMER.Label.NPC"}} +
+
+ +
+
+ {{localize "OATHHAMMER.Label.Grit"}} + {{formInput systemFields.grit.fields.value value=system.grit.value name="system.grit.value" disabled=isPlayMode}} + / + +
+
+ {{localize "OATHHAMMER.Label.Defense"}} + {{formInput systemFields.defense.fields.value value=system.defense.value name="system.defense.value" disabled=isPlayMode}} +
+
+ {{localize "OATHHAMMER.Label.Movement"}} + {{formInput systemFields.movement.fields.base value=system.movement.base name="system.movement.base" disabled=isPlayMode}} +
+
+
+
+
+ {{formInput fields.name value=source.name rootId=partId disabled=isPlayMode}} + + + +
+
+ {{localize "OATHHAMMER.Label.Stats"}} +
+
+ + {{formInput systemFields.challengeRating value=system.challengeRating name="system.challengeRating" disabled=isPlayMode}} +
+
+ + {{formInput systemFields.attackBonus value=system.attackBonus name="system.attackBonus" disabled=isPlayMode}} +
+
+ + {{formInput systemFields.damageBonus value=system.damageBonus name="system.damageBonus" disabled=isPlayMode}} +
+
+
+
+ {{localize "OATHHAMMER.Label.Attributes"}} +
+ {{#each system.attributes as |attr key|}} +
+ + {{formInput (lookup ../systemFields.attributes.fields key).fields.rank value=attr.rank name=(concat "system.attributes." key ".rank") disabled=../isPlayMode}} +
+ {{/each}} +
+
+
+
+
+
diff --git a/templates/item/ability-sheet.hbs b/templates/item/ability-sheet.hbs new file mode 100644 index 0000000..076c15f --- /dev/null +++ b/templates/item/ability-sheet.hbs @@ -0,0 +1,18 @@ +
+
+ + {{formInput fields.name value=source.name}} +
+
+
+ {{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"}} +
+
+
+ {{localize "OATHHAMMER.Label.Description"}} + {{formInput systemFields.description enriched=enrichedDescription value=system.description name="system.description" toggled=true}} +
+
diff --git a/templates/item/ammunition-sheet.hbs b/templates/item/ammunition-sheet.hbs new file mode 100644 index 0000000..96302f5 --- /dev/null +++ b/templates/item/ammunition-sheet.hbs @@ -0,0 +1,21 @@ +
+
+ + {{formInput fields.name value=source.name}} +
+
+
+ {{formField systemFields.ammoType value=system.ammoType name="system.ammoType" localize=true}} + {{formField systemFields.quantity value=system.quantity name="system.quantity"}} + {{formField systemFields.properties value=system.properties name="system.properties"}} +
+
+ {{formField systemFields.cost value=system.cost name="system.cost"}} + {{formField systemFields.currency value=system.currency name="system.currency" localize=true}} +
+
+
+ {{localize "OATHHAMMER.Label.Description"}} + {{formInput systemFields.description enriched=enrichedDescription value=system.description name="system.description" toggled=true}} +
+
diff --git a/templates/item/armor-sheet.hbs b/templates/item/armor-sheet.hbs new file mode 100644 index 0000000..0aa054f --- /dev/null +++ b/templates/item/armor-sheet.hbs @@ -0,0 +1,23 @@ +
+
+ + {{formInput fields.name value=source.name}} +
+
+
+ {{formField systemFields.armorType value=system.armorType name="system.armorType" localize=true}} + {{formField systemFields.armorRating value=system.armorRating name="system.armorRating"}} + {{formField systemFields.movementPenalty value=system.movementPenalty name="system.movementPenalty"}} +
+
+ {{formField systemFields.equipped value=system.equipped name="system.equipped"}} + {{formField systemFields.encumbrance value=system.encumbrance name="system.encumbrance"}} + {{formField systemFields.cost value=system.cost name="system.cost"}} + {{formField systemFields.currency value=system.currency name="system.currency" localize=true}} +
+
+
+ {{localize "OATHHAMMER.Label.Description"}} + {{formInput systemFields.description enriched=enrichedDescription value=system.description name="system.description" toggled=true}} +
+
diff --git a/templates/item/condition-sheet.hbs b/templates/item/condition-sheet.hbs new file mode 100644 index 0000000..5bf563e --- /dev/null +++ b/templates/item/condition-sheet.hbs @@ -0,0 +1,17 @@ +
+
+ + {{formInput fields.name value=source.name}} +
+
+
+ {{formField systemFields.conditionType value=system.conditionType name="system.conditionType" localize=true}} + {{formField systemFields.duration value=system.duration name="system.duration"}} + {{formField systemFields.source value=system.source name="system.source"}} +
+
+
+ {{localize "OATHHAMMER.Label.Description"}} + {{formInput systemFields.description enriched=enrichedDescription value=system.description name="system.description" toggled=true}} +
+
diff --git a/templates/item/equipment-sheet.hbs b/templates/item/equipment-sheet.hbs new file mode 100644 index 0000000..cc53574 --- /dev/null +++ b/templates/item/equipment-sheet.hbs @@ -0,0 +1,21 @@ +
+
+ + {{formInput fields.name value=source.name}} +
+
+
+ {{formField systemFields.itemType value=system.itemType name="system.itemType" localize=true}} + {{formField systemFields.quantity value=system.quantity name="system.quantity"}} + {{formField systemFields.weight value=system.weight name="system.weight"}} +
+
+ {{formField systemFields.cost value=system.cost name="system.cost"}} + {{formField systemFields.currency value=system.currency name="system.currency" localize=true}} +
+
+
+ {{localize "OATHHAMMER.Label.Description"}} + {{formInput systemFields.description enriched=enrichedDescription value=system.description name="system.description" toggled=true}} +
+
diff --git a/templates/item/magic-item-sheet.hbs b/templates/item/magic-item-sheet.hbs new file mode 100644 index 0000000..ddbb563 --- /dev/null +++ b/templates/item/magic-item-sheet.hbs @@ -0,0 +1,28 @@ +
+
+ + {{formInput fields.name value=source.name}} +
+
+
+ {{formField systemFields.itemType value=system.itemType name="system.itemType" localize=true}} + {{formField systemFields.rarity value=system.rarity name="system.rarity" localize=true}} + {{formField systemFields.attunement value=system.attunement name="system.attunement"}} + {{formField systemFields.recharge value=system.recharge name="system.recharge"}} +
+
+ +
+ {{formField systemFields.charges.fields.value value=system.charges.value name="system.charges.value"}} + {{formField systemFields.charges.fields.max value=system.charges.max name="system.charges.max"}} +
+ {{formField systemFields.equipped value=system.equipped name="system.equipped"}} + {{formField systemFields.cost value=system.cost name="system.cost"}} + {{formField systemFields.currency value=system.currency name="system.currency" localize=true}} +
+
+
+ {{localize "OATHHAMMER.Label.Description"}} + {{formInput systemFields.description enriched=enrichedDescription value=system.description name="system.description" toggled=true}} +
+
diff --git a/templates/item/miracle-sheet.hbs b/templates/item/miracle-sheet.hbs new file mode 100644 index 0000000..933d541 --- /dev/null +++ b/templates/item/miracle-sheet.hbs @@ -0,0 +1,28 @@ +
+
+ + {{formInput fields.name value=source.name}} +
+
+
+ {{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"}} +
+
+ +
+ {{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"}} +
+ {{formField systemFields.materialComponent value=system.materialComponent name="system.materialComponent"}} + {{formField systemFields.savingThrow value=system.savingThrow name="system.savingThrow"}} +
+
+
+ {{localize "OATHHAMMER.Label.Effect"}} + {{formInput systemFields.effect enriched=enrichedEffect value=system.effect name="system.effect" toggled=true}} +
+
diff --git a/templates/item/oath-sheet.hbs b/templates/item/oath-sheet.hbs new file mode 100644 index 0000000..264f5fb --- /dev/null +++ b/templates/item/oath-sheet.hbs @@ -0,0 +1,20 @@ +
+
+ + {{formInput fields.name value=source.name}} +
+
+
+ {{formField systemFields.oathType value=system.oathType name="system.oathType" localize=true}} + {{formField systemFields.violated value=system.violated name="system.violated"}} +
+
+
+ {{localize "OATHHAMMER.Label.Benefit"}} + {{formInput systemFields.benefit enriched=enrichedBenefit value=system.benefit name="system.benefit" toggled=true}} +
+
+ {{localize "OATHHAMMER.Label.Violation"}} + {{formInput systemFields.violation enriched=enrichedViolation value=system.violation name="system.violation" toggled=true}} +
+
diff --git a/templates/item/shield-sheet.hbs b/templates/item/shield-sheet.hbs new file mode 100644 index 0000000..ccd8e22 --- /dev/null +++ b/templates/item/shield-sheet.hbs @@ -0,0 +1,21 @@ +
+
+ + {{formInput fields.name value=source.name}} +
+
+
+ {{formField systemFields.shieldBonus value=system.shieldBonus name="system.shieldBonus"}} + {{formField systemFields.equipped value=system.equipped name="system.equipped"}} +
+
+ {{formField systemFields.encumbrance value=system.encumbrance name="system.encumbrance"}} + {{formField systemFields.cost value=system.cost name="system.cost"}} + {{formField systemFields.currency value=system.currency name="system.currency" localize=true}} +
+
+
+ {{localize "OATHHAMMER.Label.Description"}} + {{formInput systemFields.description enriched=enrichedDescription value=system.description name="system.description" toggled=true}} +
+
diff --git a/templates/item/spell-sheet.hbs b/templates/item/spell-sheet.hbs new file mode 100644 index 0000000..38074fe --- /dev/null +++ b/templates/item/spell-sheet.hbs @@ -0,0 +1,31 @@ +
+
+ + {{formInput fields.name value=source.name}} +
+
+
+ {{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"}} +
+
+ +
+ {{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"}} +
+ {{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"}} +
+
+
+ {{localize "OATHHAMMER.Label.Effect"}} + {{formInput systemFields.effect enriched=enrichedEffect value=system.effect name="system.effect" toggled=true}} +
+
diff --git a/templates/item/weapon-sheet.hbs b/templates/item/weapon-sheet.hbs new file mode 100644 index 0000000..05c2c18 --- /dev/null +++ b/templates/item/weapon-sheet.hbs @@ -0,0 +1,27 @@ +
+
+ + {{formInput fields.name value=source.name}} +
+
+
+ {{formField systemFields.weaponType value=system.weaponType name="system.weaponType" localize=true}} + {{formField systemFields.damageFormula value=system.damageFormula name="system.damageFormula"}} + {{formField systemFields.damageType value=system.damageType name="system.damageType" localize=true}} + {{formField systemFields.attributeBonus value=system.attributeBonus name="system.attributeBonus" localize=true}} + {{formField systemFields.hands value=system.hands name="system.hands" localize=true}} + {{formField systemFields.range value=system.range name="system.range" localize=true}} +
+
+ {{formField systemFields.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}} +
+
+
+ {{localize "OATHHAMMER.Label.Description"}} + {{formInput systemFields.description enriched=enrichedDescription value=system.description name="system.description" toggled=true}} +
+