From 49347370c709d0318017944172c2eaf6c3ee8f20 Mon Sep 17 00:00:00 2001 From: LeRatierBretonnier Date: Sun, 15 Mar 2026 23:20:32 +0100 Subject: [PATCH] Add roll windows from actor sheet --- .gitignore | 2 + .history/lang/en_20260308133951.json | 788 ++++++++++++++++++ .history/lang/en_20260308154927.json | 788 ++++++++++++++++++ .history/lang/en_20260308154930.json | 788 ++++++++++++++++++ .history/system_20260308195653.json | 130 +++ .history/system_20260308204838.json | 130 +++ .history/system_20260308204842.json | 130 +++ .history/system_20260308204843.json | 130 +++ .../item/armor-sheet_20260308134234.hbs | 51 ++ .../item/armor-sheet_20260308155000.hbs | 109 +++ .../item/armor-sheet_20260308155017.hbs | 51 ++ .../item/armor-sheet_20260308155019.hbs | 109 +++ .../item/armor-sheet_20260308155021.hbs | 110 +++ css/fvtt-oath-hammer.css | 613 +++++++++++++- lang/en.json | 160 +++- less/fvtt-oath-hammer.less | 2 + less/item-list.less | 6 +- less/roll-dialog.less | 552 ++++++++++++ less/rolls.less | 100 +++ module/applications/_module.mjs | 5 +- module/applications/miracle-dialog.mjs | 89 ++ module/applications/roll-dialog.mjs | 154 ++++ .../applications/sheets/base-actor-sheet.mjs | 4 +- .../applications/sheets/base-item-sheet.mjs | 18 + .../applications/sheets/character-sheet.mjs | 67 +- module/applications/sheets/lineage-sheet.mjs | 30 - module/applications/spell-dialog.mjs | 118 +++ module/applications/weapon-dialog.mjs | 215 +++++ module/config/system.mjs | 26 + module/models/_module.mjs | 1 - module/models/armor.mjs | 2 +- module/models/building.mjs | 5 +- module/models/character.mjs | 6 + module/models/lineage.mjs | 21 - module/models/magic-item.mjs | 3 +- module/models/weapon.mjs | 10 +- module/rolls.mjs | 555 ++++++++++++ oath-hammer.mjs | 39 +- system.json | 9 +- templates/actor/character-combat.hbs | 8 +- templates/actor/character-equipment.hbs | 6 - templates/actor/character-identity.hbs | 4 - templates/actor/character-magic.hbs | 16 +- templates/actor/character-sheet.hbs | 13 +- templates/actor/character-skills.hbs | 4 +- templates/item/ammunition-sheet.hbs | 4 +- templates/item/armor-sheet.hbs | 1 + templates/item/building-sheet.hbs | 2 +- templates/item/equipment-sheet.hbs | 1 + templates/item/lineage-sheet.hbs | 22 - templates/item/magic-item-sheet.hbs | 2 +- templates/item/weapon-sheet.hbs | 2 + templates/miracle-cast-dialog.hbs | 66 ++ templates/roll-dialog.hbs | 83 ++ templates/spell-cast-dialog.hbs | 80 ++ templates/weapon-attack-dialog.hbs | 69 ++ templates/weapon-damage-dialog.hbs | 47 ++ 57 files changed, 6372 insertions(+), 184 deletions(-) create mode 100644 .history/lang/en_20260308133951.json create mode 100644 .history/lang/en_20260308154927.json create mode 100644 .history/lang/en_20260308154930.json create mode 100644 .history/system_20260308195653.json create mode 100644 .history/system_20260308204838.json create mode 100644 .history/system_20260308204842.json create mode 100644 .history/system_20260308204843.json create mode 100644 .history/templates/item/armor-sheet_20260308134234.hbs create mode 100644 .history/templates/item/armor-sheet_20260308155000.hbs create mode 100644 .history/templates/item/armor-sheet_20260308155017.hbs create mode 100644 .history/templates/item/armor-sheet_20260308155019.hbs create mode 100644 .history/templates/item/armor-sheet_20260308155021.hbs create mode 100644 less/roll-dialog.less create mode 100644 less/rolls.less create mode 100644 module/applications/miracle-dialog.mjs create mode 100644 module/applications/roll-dialog.mjs delete mode 100644 module/applications/sheets/lineage-sheet.mjs create mode 100644 module/applications/spell-dialog.mjs create mode 100644 module/applications/weapon-dialog.mjs delete mode 100644 module/models/lineage.mjs create mode 100644 module/rolls.mjs delete mode 100644 templates/item/lineage-sheet.hbs create mode 100644 templates/miracle-cast-dialog.hbs create mode 100644 templates/roll-dialog.hbs create mode 100644 templates/spell-cast-dialog.hbs create mode 100644 templates/weapon-attack-dialog.hbs create mode 100644 templates/weapon-damage-dialog.hbs diff --git a/.gitignore b/.gitignore index f0eb598..33bf50f 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ _docs_private/ +node_modules/ +package-lock.json diff --git a/.history/lang/en_20260308133951.json b/.history/lang/en_20260308133951.json new file mode 100644 index 0000000..1711313 --- /dev/null +++ b/.history/lang/en_20260308133951.json @@ -0,0 +1,788 @@ +{ + "OATHHAMMER": { + "Sheet": { + "Character": "Oath Hammer Character Sheet", + "NPC": "Oath Hammer NPC Sheet", + "Weapon": "Oath Hammer Weapon Sheet", + "Armor": "Oath Hammer Armor 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", + "Lineage": "Oath Hammer Lineage Sheet", + "Class": "Oath Hammer Class Sheet" + }, + "Tab": { + "Identity": "Identity", + "Skills": "Skills", + "Combat": "Combat", + "Magic": "Magic", + "Equipment": "Equipment", + "Notes": "Notes" + }, + "Attribute": { + "Might": "Might", + "Toughness": "Toughness", + "Agility": "Agility", + "Willpower": "Willpower", + "Intelligence": "Intelligence", + "Fate": "Fate" + }, + "Skill": { + "Academics": "Academics", + "Acrobatics": "Acrobatics", + "AnimalHandling": "Animal Handling", + "Athletics": "Athletics", + "Brewing": "Brewing", + "Carpentry": "Carpentry", + "Defense": "Defense", + "Dexterity": "Dexterity", + "Diplomacy": "Diplomacy", + "Discipline": "Discipline", + "Fighting": "Fighting", + "Folklore": "Folklore", + "Fortune": "Fortune", + "Heal": "Heal", + "Leadership": "Leadership", + "Magic": "Magic", + "Masonry": "Masonry", + "Orientation": "Orientation", + "Perception": "Perception", + "Resilience": "Resilience", + "Ride": "Ride", + "Shooting": "Shooting", + "Smithing": "Smithing", + "Stealth": "Stealth", + "Survival": "Survival", + "Tracking": "Tracking" + }, + "Lineage": { + "Dwarf": "Dwarf", + "Firbolg": "Firbolg", + "Halfling": "Halfling", + "HighElf": "High Elf", + "Human": "Human", + "WoodElf": "Wood Elf", + "FIELDS": { + "description": { + "label": "Description" + }, + "traits": { + "label": "Traits" + }, + "movement": { + "label": "Movement (ft)" + }, + "gritModifier": { + "label": "Grit Modifier" + } + } + }, + "Class": { + "Berserker": "Berserker", + "Champion": "Champion", + "Delver": "Delver", + "Knight": "Knight", + "Mage": "Mage", + "Priest": "Priest", + "Scout": "Scout", + "Soldier": "Soldier", + "Spellblade": "Spellblade", + "Troubadour": "Troubadour", + "FIELDS": { + "description": { + "label": "Description" + }, + "features": { + "label": "Features" + }, + "armorProficiency": { + "label": "Armor Proficiency" + }, + "weaponProficiency": { + "label": "Weapon Proficiency" + } + } + }, + "Tradition": { + "Elemental": "Elemental", + "Illusionist": "Illusionist", + "Imperial": "Imperial", + "Infernal": "Infernal", + "Runic": "Runic", + "Stygian": "Stygian" + }, + "ArmorType": { + "Light": "Light", + "Medium": "Medium", + "Heavy": "Heavy" + }, + "Currency": { + "GP": "Gold Pieces", + "SP": "Silver Pieces", + "CP": "Copper Pieces" + }, + "AmmoType": { + "Standard": "Arrow / Bolt", + "Bodkin": "Bodkin (−1 armor)", + "Envenomed": "Envenomed (poison)", + "Incendiary": "Incendiary (flaming)" + }, + "EquipmentType": { + "Potion": "Potion", + "Container": "Container", + "Mount": "Mount", + "HealingSupply": "Healing Supply", + "Food": "Food & Drink", + "LightSource": "Light Source", + "Misc": "Miscellaneous", + "Vehicle": "Vehicle", + "Animal": "Animal", + "WarMachine": "War Machine" + }, + "MagicItemType": { + "Focus": "Focus", + "Talisman": "Talisman", + "Trinket": "Trinket" + }, + "Rarity": { + "Always": "Always", + "Common": "1 – Common", + "Uncommon": "2 – Uncommon", + "Rare": "3 – Rare", + "VeryRare": "4 – Very Rare", + "Legendary": "5 – Legendary" + }, + "AbilityType": { + "ClassAbility": "Class Ability", + "LineageTrait": "Lineage Trait" + }, + "Condition": { + "Blinded": "Blinded", + "BlindedDesc": "Cannot see. -3 penalty to defense and melee attack rolls. Cannot perform ranged attacks or cast spells.", + "Confused": "Confused", + "ConfusedDesc": "Must make a DV2 Discipline check at the start of each turn or may not move or perform actions.", + "Dazed": "Dazed", + "DazedDesc": "Cannot perform Magic checks or move more than 10 ft. -1 penalty to attack and defense rolls.", + "Deafened": "Deafened", + "DeafenedDesc": "Cannot hear. -3 penalty to Magic checks due to verbal components required for miracles and spells.", + "Demoralized": "Demoralized", + "DemoralizedDesc": "−1 penalty to Discipline checks. Cannot perform Leadership checks.", + "Diseased": "Diseased", + "DiseasedDesc": "Lose 1 rank in all attributes. Can be treated with a DV5 Heal check once per day.", + "Enfeebled": "Enfeebled", + "EnfeebledDesc": "-2 penalty to attack and defense rolls. Cannot move more than 10 ft or perform the Run action.", + "Fatigued": "Fatigued", + "FatiguedDesc": "-1 penalty to attack and defense rolls, and to Resilience checks. Removed by a full night's rest.", + "Frightened": "Frightened", + "FrightenedDesc": "Cannot approach enemies. May attempt a DV2 Discipline check as an action to end this condition. Cannot perform Leadership checks.", + "Ignited": "Ignited", + "IgnitedDesc": "Suffers 1DD flaming damage at the end of each round, ignoring armor. Can remove condition as an action (ends turn prone).", + "Inspired": "Inspired", + "InspiredDesc": "+1 bonus to Discipline and Leadership checks.", + "Invisible": "Invisible", + "InvisibleDesc": "Cannot be seen by ordinary means. Cannot be targeted by magic or ranged attacks. -3 penalty to melee attacks against invisible targets.", + "Poisoned": "Poisoned", + "PoisonedDesc": "Suffers 1DD poison damage (black die) at the end of each round, ignoring armor.", + "Restrained": "Restrained", + "RestrainedDesc": "Cannot move.", + "Stunned": "Stunned", + "StunnedDesc": "Cannot speak, move, or perform actions. -3 penalty to defense rolls." + }, + "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", + "Background": "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", + "NoWeapons": "No weapons equipped.", + "NoArmor": "No armor or shields.", + "NoSpells": "No spells known.", + "NoMiracles": "No miracles known.", + "NoEquipment": "No equipment.", + "Enchantment": "Enchantment", + "Tenet": "Tenet", + "Boon": "Boon", + "Bane": "Bane", + "Skill": "Skill", + "SkillRank": "Rank", + "SkillModifier": "Mod", + "TotalDice": "Total", + "ColorDice": "Color", + "DropLineage": "Drop Lineage Here", + "DropClass": "Drop Class Here", + "Traits": "Traits", + "Features": "Features", + "Name": "Name", + "Type": "Type", + "Damage": "Damage", + "Tradition": "Tradition", + "Piety": "Piety", + "Quantity": "Qty", + "Rarity": "Rarity", + "Penalty": "Penalty", + "Equipped": "Eq.", + "XPCurrent": "Current XP" + }, + "ColorDice": { + "White": "White (4+)", + "Red": "Red (3+)", + "Black": "Black (2+)" + }, + "NewItem": { + "Weapon": "New Weapon", + "Spell": "New Spell", + "Miracle": "New Miracle", + "Equipment": "New Equipment" + }, + "ToggleSheet": "Toggle Edit/Play Mode", + "Character": { + "FIELDS": { + "attributes": { + "label": "Attributes" + }, + "grit": { + "label": "Grit" + }, + "luck": { + "label": "Luck", + "fields": { + "value": { + "label": "Luck" + }, + "max": { + "label": "Luck Max" + } + } + }, + "arcaneStress": { + "label": "Arcane Stress" + }, + "movement": { + "label": "Movement" + }, + "defense": { + "label": "Defense" + }, + "experience": { + "label": "Experience" + }, + "biodata": { + "label": "Background", + "fields": { + "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" + } + }, + "description": { + "label": "Description" + }, + "notes": { + "label": "Notes" + }, + "skills": { + "label": "Skills" + } + } + }, + "NPC": { + "FIELDS": { + "attributes": { + "label": "Attributes" + }, + "grit": { + "label": "Grit" + }, + "defense": { + "label": "Defense" + }, + "movement": { + "label": "Movement" + }, + "attackBonus": { + "label": "Attack Bonus" + }, + "damageBonus": { + "label": "Damage Bonus" + }, + "challengeRating": { + "label": "Challenge Rating" + }, + "description": { + "label": "Description" + }, + "notes": { + "label": "Notes" + } + } + }, + "Weapon": { + "FIELDS": { + "proficiencyGroup": { + "label": "Proficiency Group" + }, + "usesMight": { + "label": "Uses Might" + }, + "damageMod": { + "label": "Damage Modifier" + }, + "ap": { + "label": "Armor Penetration (AP)" + }, + "reach": { + "label": "Reach (ft)" + }, + "shortRange": { + "label": "Short Range (ft)" + }, + "longRange": { + "label": "Long Range (ft)" + }, + "traits": { + "label": "Traits" + }, + "slots": { + "label": "Item Slots" + }, + "rarity": { + "label": "Rarity" + }, + "equipped": { + "label": "Equipped" + }, + "cost": { + "label": "Cost" + }, + "currency": { + "label": "Currency" + }, + "description": { + "label": "Description" + }, + "isMagic": { + "label": "Magic Item" + }, + "magicQuality": { + "label": "Quality" + }, + "isCursed": { + "label": "Cursed" + }, + "magicEffect": { + "label": "Enchantment" + }, + "classRestriction": { + "label": "Restriction" + } + } + }, + "Armor": { + "FIELDS": { + "armorType": { + "label": "Armor Type" + }, + "armorValue": { + "label": "Armor Value (AV)" + }, + "penalty": { + "label": "Penalty" + }, + "slots": { + "label": "Slots" + }, + "traits": { + "label": "Traits" + }, + "rarity": { + "label": "Rarity" + }, + "equipped": { + "label": "Equipped" + }, + "cost": { + "label": "Cost" + }, + "currency": { + "label": "Currency" + }, + "isMagic": { + "label": "Magic Item" + }, + "magicQuality": { + "label": "Quality" + }, + "isCursed": { + "label": "Cursed" + }, + "magicEffect": { + "label": "Enchantment" + }, + "classRestriction": { + "label": "Restriction" + } + } + }, + "Ammunition": { + "FIELDS": { + "ammoType": { + "label": "Ammunition Type" + }, + "quantity": { + "label": "Quantity" + }, + "rarity": { + "label": "Rarity" + }, + "cost": { + "label": "Cost" + }, + "currency": { + "label": "Currency" + } + } + }, + "Equipment": { + "FIELDS": { + "itemType": { + "label": "Category" + }, + "quantity": { + "label": "Quantity" + }, + "slots": { + "label": "Slots" + }, + "rarity": { + "label": "Rarity" + }, + "lightRadius": { + "label": "Light Radius (ft)" + }, + "cost": { + "label": "Cost" + }, + "currency": { + "label": "Currency" + } + } + }, + "Spell": { + "FIELDS": { + "tradition": { + "label": "Tradition" + }, + "difficultyValue": { + "label": "Difficulty Value (DV)" + }, + "isRitual": { + "label": "Ritual" + }, + "isMagicMissile": { + "label": "Magic Missile" + }, + "range": { + "label": "Range" + }, + "duration": { + "label": "Duration" + }, + "spellSave": { + "label": "Spell Save" + }, + "element": { + "label": "Element" + }, + "runeType": { + "label": "Rune Type" + }, + "isExalted": { + "label": "Exalted" + }, + "effect": { + "label": "Effect" + } + } + }, + "Miracle": { + "FIELDS": { + "divineTradition": { + "label": "Divine Tradition" + }, + "difficultyValue": { + "label": "Difficulty Value (DV)" + }, + "isRitual": { + "label": "Ritual" + }, + "range": { + "label": "Range" + }, + "duration": { + "label": "Duration" + }, + "spellSave": { + "label": "Spell Save" + }, + "effect": { + "label": "Effect" + } + } + }, + "MagicItem": { + "FIELDS": { + "itemType": { + "label": "Type" + }, + "quality": { + "label": "Quality" + }, + "isCursed": { + "label": "Cursed" + }, + "isBonded": { + "label": "Bonded" + }, + "classRestriction": { + "label": "Restriction" + }, + "usagePeriod": { + "label": "Usage Period" + }, + "maxUses": { + "label": "Max Uses" + }, + "slots": { + "label": "Slots" + }, + "equipped": { + "label": "Equipped" + }, + "effect": { + "label": "Effect" + } + } + }, + "Ability": { + "FIELDS": { + "abilityType": { + "label": "Type" + }, + "source": { + "label": "Source (Class / Lineage)" + }, + "usagePeriod": { + "label": "Usage Period" + }, + "maxUses": { + "label": "Max Uses" + }, + "description": { + "label": "Description" + } + } + }, + "WeaponGroup": { + "Common": "Common", + "Dueling": "Dueling", + "Heavy": "Heavy", + "Polearms": "Polearms", + "Bows": "Bows", + "Throwing": "Throwing" + }, + "WeaponTrait": { + "Block": "Block", + "Brutal": "Brutal", + "Clumsy": "Clumsy", + "Couched": "Couched", + "Deadly": "Deadly", + "Fast": "Fast", + "Flaming": "Flaming", + "Nimble": "Nimble", + "Parry": "Parry", + "Reload": "Reload", + "Repel": "Repel", + "Stunning": "Stunning", + "Sweep": "Sweep", + "TwoHanded": "Two-handed", + "Versatile": "Versatile" + }, + "DivineTradition": { + "Druidic": "Druidic", + "Profane": "Profane", + "Sanctified": "Sanctified" + }, + "Element": { + "Air": "Air", + "Earth": "Earth", + "Fire": "Fire", + "Water": "Water", + "Varies": "Varies" + }, + "RuneType": { + "Armor": "Armor", + "Talisman": "Talisman", + "Warding": "Warding", + "Weapon": "Weapon" + }, + "ArmorTrait": { + "Clanging": "Clanging", + "Reinforced": "Reinforced" + }, + "UsagePeriod": { + "None": "Passive (always on)", + "Encounter": "Per Encounter", + "Day": "Per Day" + }, + "MagicQuality": { + "Lesser": "Lesser", + "Greater": "Greater", + "Legendary": "Legendary" + }, + "OathType": { + "Compassion": "Oath of Compassion", + "Courage": "Oath of Courage", + "Diligence": "Oath of Diligence", + "Faith": "Oath of Faith", + "Humility": "Oath of Humility", + "Justice": "Oath of Justice", + "Loyalty": "Oath of Loyalty", + "Peace": "Oath of Peace", + "Perseverance": "Oath of Perseverance", + "Purity": "Oath of Purity", + "Truth": "Oath of Truth", + "Wisdom": "Oath of Wisdom" + }, + "OathFields": { + "oathType": { + "label": "Oath" + }, + "tenet": { + "label": "Tenet" + }, + "boon": { + "label": "Boon" + }, + "bane": { + "label": "Bane" + }, + "violated": { + "label": "Violated" + } + }, + "Oath": { + "FIELDS": { + "oathType": { + "label": "Oath" + }, + "tenet": { + "label": "Tenet" + }, + "boon": { + "label": "Boon" + }, + "bane": { + "label": "Bane" + }, + "violated": { + "label": "Violated" + } + } + } + }, + "TYPES": { + "Item": { + "weapon": "Weapon", + "armor": "Armor", + "ammunition": "Ammunition", + "equipment": "Equipment", + "spell": "Spell", + "miracle": "Miracle", + "magic_item": "Magic Item", + "magic-item": "Magic Item", + "ability": "Ability", + "oath": "Oath", + "lineage": "Lineage", + "class": "Class" + }, + "Actor": { + "character": "Character", + "npc": "NPC" + } + } +} \ No newline at end of file diff --git a/.history/lang/en_20260308154927.json b/.history/lang/en_20260308154927.json new file mode 100644 index 0000000..1711313 --- /dev/null +++ b/.history/lang/en_20260308154927.json @@ -0,0 +1,788 @@ +{ + "OATHHAMMER": { + "Sheet": { + "Character": "Oath Hammer Character Sheet", + "NPC": "Oath Hammer NPC Sheet", + "Weapon": "Oath Hammer Weapon Sheet", + "Armor": "Oath Hammer Armor 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", + "Lineage": "Oath Hammer Lineage Sheet", + "Class": "Oath Hammer Class Sheet" + }, + "Tab": { + "Identity": "Identity", + "Skills": "Skills", + "Combat": "Combat", + "Magic": "Magic", + "Equipment": "Equipment", + "Notes": "Notes" + }, + "Attribute": { + "Might": "Might", + "Toughness": "Toughness", + "Agility": "Agility", + "Willpower": "Willpower", + "Intelligence": "Intelligence", + "Fate": "Fate" + }, + "Skill": { + "Academics": "Academics", + "Acrobatics": "Acrobatics", + "AnimalHandling": "Animal Handling", + "Athletics": "Athletics", + "Brewing": "Brewing", + "Carpentry": "Carpentry", + "Defense": "Defense", + "Dexterity": "Dexterity", + "Diplomacy": "Diplomacy", + "Discipline": "Discipline", + "Fighting": "Fighting", + "Folklore": "Folklore", + "Fortune": "Fortune", + "Heal": "Heal", + "Leadership": "Leadership", + "Magic": "Magic", + "Masonry": "Masonry", + "Orientation": "Orientation", + "Perception": "Perception", + "Resilience": "Resilience", + "Ride": "Ride", + "Shooting": "Shooting", + "Smithing": "Smithing", + "Stealth": "Stealth", + "Survival": "Survival", + "Tracking": "Tracking" + }, + "Lineage": { + "Dwarf": "Dwarf", + "Firbolg": "Firbolg", + "Halfling": "Halfling", + "HighElf": "High Elf", + "Human": "Human", + "WoodElf": "Wood Elf", + "FIELDS": { + "description": { + "label": "Description" + }, + "traits": { + "label": "Traits" + }, + "movement": { + "label": "Movement (ft)" + }, + "gritModifier": { + "label": "Grit Modifier" + } + } + }, + "Class": { + "Berserker": "Berserker", + "Champion": "Champion", + "Delver": "Delver", + "Knight": "Knight", + "Mage": "Mage", + "Priest": "Priest", + "Scout": "Scout", + "Soldier": "Soldier", + "Spellblade": "Spellblade", + "Troubadour": "Troubadour", + "FIELDS": { + "description": { + "label": "Description" + }, + "features": { + "label": "Features" + }, + "armorProficiency": { + "label": "Armor Proficiency" + }, + "weaponProficiency": { + "label": "Weapon Proficiency" + } + } + }, + "Tradition": { + "Elemental": "Elemental", + "Illusionist": "Illusionist", + "Imperial": "Imperial", + "Infernal": "Infernal", + "Runic": "Runic", + "Stygian": "Stygian" + }, + "ArmorType": { + "Light": "Light", + "Medium": "Medium", + "Heavy": "Heavy" + }, + "Currency": { + "GP": "Gold Pieces", + "SP": "Silver Pieces", + "CP": "Copper Pieces" + }, + "AmmoType": { + "Standard": "Arrow / Bolt", + "Bodkin": "Bodkin (−1 armor)", + "Envenomed": "Envenomed (poison)", + "Incendiary": "Incendiary (flaming)" + }, + "EquipmentType": { + "Potion": "Potion", + "Container": "Container", + "Mount": "Mount", + "HealingSupply": "Healing Supply", + "Food": "Food & Drink", + "LightSource": "Light Source", + "Misc": "Miscellaneous", + "Vehicle": "Vehicle", + "Animal": "Animal", + "WarMachine": "War Machine" + }, + "MagicItemType": { + "Focus": "Focus", + "Talisman": "Talisman", + "Trinket": "Trinket" + }, + "Rarity": { + "Always": "Always", + "Common": "1 – Common", + "Uncommon": "2 – Uncommon", + "Rare": "3 – Rare", + "VeryRare": "4 – Very Rare", + "Legendary": "5 – Legendary" + }, + "AbilityType": { + "ClassAbility": "Class Ability", + "LineageTrait": "Lineage Trait" + }, + "Condition": { + "Blinded": "Blinded", + "BlindedDesc": "Cannot see. -3 penalty to defense and melee attack rolls. Cannot perform ranged attacks or cast spells.", + "Confused": "Confused", + "ConfusedDesc": "Must make a DV2 Discipline check at the start of each turn or may not move or perform actions.", + "Dazed": "Dazed", + "DazedDesc": "Cannot perform Magic checks or move more than 10 ft. -1 penalty to attack and defense rolls.", + "Deafened": "Deafened", + "DeafenedDesc": "Cannot hear. -3 penalty to Magic checks due to verbal components required for miracles and spells.", + "Demoralized": "Demoralized", + "DemoralizedDesc": "−1 penalty to Discipline checks. Cannot perform Leadership checks.", + "Diseased": "Diseased", + "DiseasedDesc": "Lose 1 rank in all attributes. Can be treated with a DV5 Heal check once per day.", + "Enfeebled": "Enfeebled", + "EnfeebledDesc": "-2 penalty to attack and defense rolls. Cannot move more than 10 ft or perform the Run action.", + "Fatigued": "Fatigued", + "FatiguedDesc": "-1 penalty to attack and defense rolls, and to Resilience checks. Removed by a full night's rest.", + "Frightened": "Frightened", + "FrightenedDesc": "Cannot approach enemies. May attempt a DV2 Discipline check as an action to end this condition. Cannot perform Leadership checks.", + "Ignited": "Ignited", + "IgnitedDesc": "Suffers 1DD flaming damage at the end of each round, ignoring armor. Can remove condition as an action (ends turn prone).", + "Inspired": "Inspired", + "InspiredDesc": "+1 bonus to Discipline and Leadership checks.", + "Invisible": "Invisible", + "InvisibleDesc": "Cannot be seen by ordinary means. Cannot be targeted by magic or ranged attacks. -3 penalty to melee attacks against invisible targets.", + "Poisoned": "Poisoned", + "PoisonedDesc": "Suffers 1DD poison damage (black die) at the end of each round, ignoring armor.", + "Restrained": "Restrained", + "RestrainedDesc": "Cannot move.", + "Stunned": "Stunned", + "StunnedDesc": "Cannot speak, move, or perform actions. -3 penalty to defense rolls." + }, + "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", + "Background": "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", + "NoWeapons": "No weapons equipped.", + "NoArmor": "No armor or shields.", + "NoSpells": "No spells known.", + "NoMiracles": "No miracles known.", + "NoEquipment": "No equipment.", + "Enchantment": "Enchantment", + "Tenet": "Tenet", + "Boon": "Boon", + "Bane": "Bane", + "Skill": "Skill", + "SkillRank": "Rank", + "SkillModifier": "Mod", + "TotalDice": "Total", + "ColorDice": "Color", + "DropLineage": "Drop Lineage Here", + "DropClass": "Drop Class Here", + "Traits": "Traits", + "Features": "Features", + "Name": "Name", + "Type": "Type", + "Damage": "Damage", + "Tradition": "Tradition", + "Piety": "Piety", + "Quantity": "Qty", + "Rarity": "Rarity", + "Penalty": "Penalty", + "Equipped": "Eq.", + "XPCurrent": "Current XP" + }, + "ColorDice": { + "White": "White (4+)", + "Red": "Red (3+)", + "Black": "Black (2+)" + }, + "NewItem": { + "Weapon": "New Weapon", + "Spell": "New Spell", + "Miracle": "New Miracle", + "Equipment": "New Equipment" + }, + "ToggleSheet": "Toggle Edit/Play Mode", + "Character": { + "FIELDS": { + "attributes": { + "label": "Attributes" + }, + "grit": { + "label": "Grit" + }, + "luck": { + "label": "Luck", + "fields": { + "value": { + "label": "Luck" + }, + "max": { + "label": "Luck Max" + } + } + }, + "arcaneStress": { + "label": "Arcane Stress" + }, + "movement": { + "label": "Movement" + }, + "defense": { + "label": "Defense" + }, + "experience": { + "label": "Experience" + }, + "biodata": { + "label": "Background", + "fields": { + "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" + } + }, + "description": { + "label": "Description" + }, + "notes": { + "label": "Notes" + }, + "skills": { + "label": "Skills" + } + } + }, + "NPC": { + "FIELDS": { + "attributes": { + "label": "Attributes" + }, + "grit": { + "label": "Grit" + }, + "defense": { + "label": "Defense" + }, + "movement": { + "label": "Movement" + }, + "attackBonus": { + "label": "Attack Bonus" + }, + "damageBonus": { + "label": "Damage Bonus" + }, + "challengeRating": { + "label": "Challenge Rating" + }, + "description": { + "label": "Description" + }, + "notes": { + "label": "Notes" + } + } + }, + "Weapon": { + "FIELDS": { + "proficiencyGroup": { + "label": "Proficiency Group" + }, + "usesMight": { + "label": "Uses Might" + }, + "damageMod": { + "label": "Damage Modifier" + }, + "ap": { + "label": "Armor Penetration (AP)" + }, + "reach": { + "label": "Reach (ft)" + }, + "shortRange": { + "label": "Short Range (ft)" + }, + "longRange": { + "label": "Long Range (ft)" + }, + "traits": { + "label": "Traits" + }, + "slots": { + "label": "Item Slots" + }, + "rarity": { + "label": "Rarity" + }, + "equipped": { + "label": "Equipped" + }, + "cost": { + "label": "Cost" + }, + "currency": { + "label": "Currency" + }, + "description": { + "label": "Description" + }, + "isMagic": { + "label": "Magic Item" + }, + "magicQuality": { + "label": "Quality" + }, + "isCursed": { + "label": "Cursed" + }, + "magicEffect": { + "label": "Enchantment" + }, + "classRestriction": { + "label": "Restriction" + } + } + }, + "Armor": { + "FIELDS": { + "armorType": { + "label": "Armor Type" + }, + "armorValue": { + "label": "Armor Value (AV)" + }, + "penalty": { + "label": "Penalty" + }, + "slots": { + "label": "Slots" + }, + "traits": { + "label": "Traits" + }, + "rarity": { + "label": "Rarity" + }, + "equipped": { + "label": "Equipped" + }, + "cost": { + "label": "Cost" + }, + "currency": { + "label": "Currency" + }, + "isMagic": { + "label": "Magic Item" + }, + "magicQuality": { + "label": "Quality" + }, + "isCursed": { + "label": "Cursed" + }, + "magicEffect": { + "label": "Enchantment" + }, + "classRestriction": { + "label": "Restriction" + } + } + }, + "Ammunition": { + "FIELDS": { + "ammoType": { + "label": "Ammunition Type" + }, + "quantity": { + "label": "Quantity" + }, + "rarity": { + "label": "Rarity" + }, + "cost": { + "label": "Cost" + }, + "currency": { + "label": "Currency" + } + } + }, + "Equipment": { + "FIELDS": { + "itemType": { + "label": "Category" + }, + "quantity": { + "label": "Quantity" + }, + "slots": { + "label": "Slots" + }, + "rarity": { + "label": "Rarity" + }, + "lightRadius": { + "label": "Light Radius (ft)" + }, + "cost": { + "label": "Cost" + }, + "currency": { + "label": "Currency" + } + } + }, + "Spell": { + "FIELDS": { + "tradition": { + "label": "Tradition" + }, + "difficultyValue": { + "label": "Difficulty Value (DV)" + }, + "isRitual": { + "label": "Ritual" + }, + "isMagicMissile": { + "label": "Magic Missile" + }, + "range": { + "label": "Range" + }, + "duration": { + "label": "Duration" + }, + "spellSave": { + "label": "Spell Save" + }, + "element": { + "label": "Element" + }, + "runeType": { + "label": "Rune Type" + }, + "isExalted": { + "label": "Exalted" + }, + "effect": { + "label": "Effect" + } + } + }, + "Miracle": { + "FIELDS": { + "divineTradition": { + "label": "Divine Tradition" + }, + "difficultyValue": { + "label": "Difficulty Value (DV)" + }, + "isRitual": { + "label": "Ritual" + }, + "range": { + "label": "Range" + }, + "duration": { + "label": "Duration" + }, + "spellSave": { + "label": "Spell Save" + }, + "effect": { + "label": "Effect" + } + } + }, + "MagicItem": { + "FIELDS": { + "itemType": { + "label": "Type" + }, + "quality": { + "label": "Quality" + }, + "isCursed": { + "label": "Cursed" + }, + "isBonded": { + "label": "Bonded" + }, + "classRestriction": { + "label": "Restriction" + }, + "usagePeriod": { + "label": "Usage Period" + }, + "maxUses": { + "label": "Max Uses" + }, + "slots": { + "label": "Slots" + }, + "equipped": { + "label": "Equipped" + }, + "effect": { + "label": "Effect" + } + } + }, + "Ability": { + "FIELDS": { + "abilityType": { + "label": "Type" + }, + "source": { + "label": "Source (Class / Lineage)" + }, + "usagePeriod": { + "label": "Usage Period" + }, + "maxUses": { + "label": "Max Uses" + }, + "description": { + "label": "Description" + } + } + }, + "WeaponGroup": { + "Common": "Common", + "Dueling": "Dueling", + "Heavy": "Heavy", + "Polearms": "Polearms", + "Bows": "Bows", + "Throwing": "Throwing" + }, + "WeaponTrait": { + "Block": "Block", + "Brutal": "Brutal", + "Clumsy": "Clumsy", + "Couched": "Couched", + "Deadly": "Deadly", + "Fast": "Fast", + "Flaming": "Flaming", + "Nimble": "Nimble", + "Parry": "Parry", + "Reload": "Reload", + "Repel": "Repel", + "Stunning": "Stunning", + "Sweep": "Sweep", + "TwoHanded": "Two-handed", + "Versatile": "Versatile" + }, + "DivineTradition": { + "Druidic": "Druidic", + "Profane": "Profane", + "Sanctified": "Sanctified" + }, + "Element": { + "Air": "Air", + "Earth": "Earth", + "Fire": "Fire", + "Water": "Water", + "Varies": "Varies" + }, + "RuneType": { + "Armor": "Armor", + "Talisman": "Talisman", + "Warding": "Warding", + "Weapon": "Weapon" + }, + "ArmorTrait": { + "Clanging": "Clanging", + "Reinforced": "Reinforced" + }, + "UsagePeriod": { + "None": "Passive (always on)", + "Encounter": "Per Encounter", + "Day": "Per Day" + }, + "MagicQuality": { + "Lesser": "Lesser", + "Greater": "Greater", + "Legendary": "Legendary" + }, + "OathType": { + "Compassion": "Oath of Compassion", + "Courage": "Oath of Courage", + "Diligence": "Oath of Diligence", + "Faith": "Oath of Faith", + "Humility": "Oath of Humility", + "Justice": "Oath of Justice", + "Loyalty": "Oath of Loyalty", + "Peace": "Oath of Peace", + "Perseverance": "Oath of Perseverance", + "Purity": "Oath of Purity", + "Truth": "Oath of Truth", + "Wisdom": "Oath of Wisdom" + }, + "OathFields": { + "oathType": { + "label": "Oath" + }, + "tenet": { + "label": "Tenet" + }, + "boon": { + "label": "Boon" + }, + "bane": { + "label": "Bane" + }, + "violated": { + "label": "Violated" + } + }, + "Oath": { + "FIELDS": { + "oathType": { + "label": "Oath" + }, + "tenet": { + "label": "Tenet" + }, + "boon": { + "label": "Boon" + }, + "bane": { + "label": "Bane" + }, + "violated": { + "label": "Violated" + } + } + } + }, + "TYPES": { + "Item": { + "weapon": "Weapon", + "armor": "Armor", + "ammunition": "Ammunition", + "equipment": "Equipment", + "spell": "Spell", + "miracle": "Miracle", + "magic_item": "Magic Item", + "magic-item": "Magic Item", + "ability": "Ability", + "oath": "Oath", + "lineage": "Lineage", + "class": "Class" + }, + "Actor": { + "character": "Character", + "npc": "NPC" + } + } +} \ No newline at end of file diff --git a/.history/lang/en_20260308154930.json b/.history/lang/en_20260308154930.json new file mode 100644 index 0000000..1711313 --- /dev/null +++ b/.history/lang/en_20260308154930.json @@ -0,0 +1,788 @@ +{ + "OATHHAMMER": { + "Sheet": { + "Character": "Oath Hammer Character Sheet", + "NPC": "Oath Hammer NPC Sheet", + "Weapon": "Oath Hammer Weapon Sheet", + "Armor": "Oath Hammer Armor 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", + "Lineage": "Oath Hammer Lineage Sheet", + "Class": "Oath Hammer Class Sheet" + }, + "Tab": { + "Identity": "Identity", + "Skills": "Skills", + "Combat": "Combat", + "Magic": "Magic", + "Equipment": "Equipment", + "Notes": "Notes" + }, + "Attribute": { + "Might": "Might", + "Toughness": "Toughness", + "Agility": "Agility", + "Willpower": "Willpower", + "Intelligence": "Intelligence", + "Fate": "Fate" + }, + "Skill": { + "Academics": "Academics", + "Acrobatics": "Acrobatics", + "AnimalHandling": "Animal Handling", + "Athletics": "Athletics", + "Brewing": "Brewing", + "Carpentry": "Carpentry", + "Defense": "Defense", + "Dexterity": "Dexterity", + "Diplomacy": "Diplomacy", + "Discipline": "Discipline", + "Fighting": "Fighting", + "Folklore": "Folklore", + "Fortune": "Fortune", + "Heal": "Heal", + "Leadership": "Leadership", + "Magic": "Magic", + "Masonry": "Masonry", + "Orientation": "Orientation", + "Perception": "Perception", + "Resilience": "Resilience", + "Ride": "Ride", + "Shooting": "Shooting", + "Smithing": "Smithing", + "Stealth": "Stealth", + "Survival": "Survival", + "Tracking": "Tracking" + }, + "Lineage": { + "Dwarf": "Dwarf", + "Firbolg": "Firbolg", + "Halfling": "Halfling", + "HighElf": "High Elf", + "Human": "Human", + "WoodElf": "Wood Elf", + "FIELDS": { + "description": { + "label": "Description" + }, + "traits": { + "label": "Traits" + }, + "movement": { + "label": "Movement (ft)" + }, + "gritModifier": { + "label": "Grit Modifier" + } + } + }, + "Class": { + "Berserker": "Berserker", + "Champion": "Champion", + "Delver": "Delver", + "Knight": "Knight", + "Mage": "Mage", + "Priest": "Priest", + "Scout": "Scout", + "Soldier": "Soldier", + "Spellblade": "Spellblade", + "Troubadour": "Troubadour", + "FIELDS": { + "description": { + "label": "Description" + }, + "features": { + "label": "Features" + }, + "armorProficiency": { + "label": "Armor Proficiency" + }, + "weaponProficiency": { + "label": "Weapon Proficiency" + } + } + }, + "Tradition": { + "Elemental": "Elemental", + "Illusionist": "Illusionist", + "Imperial": "Imperial", + "Infernal": "Infernal", + "Runic": "Runic", + "Stygian": "Stygian" + }, + "ArmorType": { + "Light": "Light", + "Medium": "Medium", + "Heavy": "Heavy" + }, + "Currency": { + "GP": "Gold Pieces", + "SP": "Silver Pieces", + "CP": "Copper Pieces" + }, + "AmmoType": { + "Standard": "Arrow / Bolt", + "Bodkin": "Bodkin (−1 armor)", + "Envenomed": "Envenomed (poison)", + "Incendiary": "Incendiary (flaming)" + }, + "EquipmentType": { + "Potion": "Potion", + "Container": "Container", + "Mount": "Mount", + "HealingSupply": "Healing Supply", + "Food": "Food & Drink", + "LightSource": "Light Source", + "Misc": "Miscellaneous", + "Vehicle": "Vehicle", + "Animal": "Animal", + "WarMachine": "War Machine" + }, + "MagicItemType": { + "Focus": "Focus", + "Talisman": "Talisman", + "Trinket": "Trinket" + }, + "Rarity": { + "Always": "Always", + "Common": "1 – Common", + "Uncommon": "2 – Uncommon", + "Rare": "3 – Rare", + "VeryRare": "4 – Very Rare", + "Legendary": "5 – Legendary" + }, + "AbilityType": { + "ClassAbility": "Class Ability", + "LineageTrait": "Lineage Trait" + }, + "Condition": { + "Blinded": "Blinded", + "BlindedDesc": "Cannot see. -3 penalty to defense and melee attack rolls. Cannot perform ranged attacks or cast spells.", + "Confused": "Confused", + "ConfusedDesc": "Must make a DV2 Discipline check at the start of each turn or may not move or perform actions.", + "Dazed": "Dazed", + "DazedDesc": "Cannot perform Magic checks or move more than 10 ft. -1 penalty to attack and defense rolls.", + "Deafened": "Deafened", + "DeafenedDesc": "Cannot hear. -3 penalty to Magic checks due to verbal components required for miracles and spells.", + "Demoralized": "Demoralized", + "DemoralizedDesc": "−1 penalty to Discipline checks. Cannot perform Leadership checks.", + "Diseased": "Diseased", + "DiseasedDesc": "Lose 1 rank in all attributes. Can be treated with a DV5 Heal check once per day.", + "Enfeebled": "Enfeebled", + "EnfeebledDesc": "-2 penalty to attack and defense rolls. Cannot move more than 10 ft or perform the Run action.", + "Fatigued": "Fatigued", + "FatiguedDesc": "-1 penalty to attack and defense rolls, and to Resilience checks. Removed by a full night's rest.", + "Frightened": "Frightened", + "FrightenedDesc": "Cannot approach enemies. May attempt a DV2 Discipline check as an action to end this condition. Cannot perform Leadership checks.", + "Ignited": "Ignited", + "IgnitedDesc": "Suffers 1DD flaming damage at the end of each round, ignoring armor. Can remove condition as an action (ends turn prone).", + "Inspired": "Inspired", + "InspiredDesc": "+1 bonus to Discipline and Leadership checks.", + "Invisible": "Invisible", + "InvisibleDesc": "Cannot be seen by ordinary means. Cannot be targeted by magic or ranged attacks. -3 penalty to melee attacks against invisible targets.", + "Poisoned": "Poisoned", + "PoisonedDesc": "Suffers 1DD poison damage (black die) at the end of each round, ignoring armor.", + "Restrained": "Restrained", + "RestrainedDesc": "Cannot move.", + "Stunned": "Stunned", + "StunnedDesc": "Cannot speak, move, or perform actions. -3 penalty to defense rolls." + }, + "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", + "Background": "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", + "NoWeapons": "No weapons equipped.", + "NoArmor": "No armor or shields.", + "NoSpells": "No spells known.", + "NoMiracles": "No miracles known.", + "NoEquipment": "No equipment.", + "Enchantment": "Enchantment", + "Tenet": "Tenet", + "Boon": "Boon", + "Bane": "Bane", + "Skill": "Skill", + "SkillRank": "Rank", + "SkillModifier": "Mod", + "TotalDice": "Total", + "ColorDice": "Color", + "DropLineage": "Drop Lineage Here", + "DropClass": "Drop Class Here", + "Traits": "Traits", + "Features": "Features", + "Name": "Name", + "Type": "Type", + "Damage": "Damage", + "Tradition": "Tradition", + "Piety": "Piety", + "Quantity": "Qty", + "Rarity": "Rarity", + "Penalty": "Penalty", + "Equipped": "Eq.", + "XPCurrent": "Current XP" + }, + "ColorDice": { + "White": "White (4+)", + "Red": "Red (3+)", + "Black": "Black (2+)" + }, + "NewItem": { + "Weapon": "New Weapon", + "Spell": "New Spell", + "Miracle": "New Miracle", + "Equipment": "New Equipment" + }, + "ToggleSheet": "Toggle Edit/Play Mode", + "Character": { + "FIELDS": { + "attributes": { + "label": "Attributes" + }, + "grit": { + "label": "Grit" + }, + "luck": { + "label": "Luck", + "fields": { + "value": { + "label": "Luck" + }, + "max": { + "label": "Luck Max" + } + } + }, + "arcaneStress": { + "label": "Arcane Stress" + }, + "movement": { + "label": "Movement" + }, + "defense": { + "label": "Defense" + }, + "experience": { + "label": "Experience" + }, + "biodata": { + "label": "Background", + "fields": { + "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" + } + }, + "description": { + "label": "Description" + }, + "notes": { + "label": "Notes" + }, + "skills": { + "label": "Skills" + } + } + }, + "NPC": { + "FIELDS": { + "attributes": { + "label": "Attributes" + }, + "grit": { + "label": "Grit" + }, + "defense": { + "label": "Defense" + }, + "movement": { + "label": "Movement" + }, + "attackBonus": { + "label": "Attack Bonus" + }, + "damageBonus": { + "label": "Damage Bonus" + }, + "challengeRating": { + "label": "Challenge Rating" + }, + "description": { + "label": "Description" + }, + "notes": { + "label": "Notes" + } + } + }, + "Weapon": { + "FIELDS": { + "proficiencyGroup": { + "label": "Proficiency Group" + }, + "usesMight": { + "label": "Uses Might" + }, + "damageMod": { + "label": "Damage Modifier" + }, + "ap": { + "label": "Armor Penetration (AP)" + }, + "reach": { + "label": "Reach (ft)" + }, + "shortRange": { + "label": "Short Range (ft)" + }, + "longRange": { + "label": "Long Range (ft)" + }, + "traits": { + "label": "Traits" + }, + "slots": { + "label": "Item Slots" + }, + "rarity": { + "label": "Rarity" + }, + "equipped": { + "label": "Equipped" + }, + "cost": { + "label": "Cost" + }, + "currency": { + "label": "Currency" + }, + "description": { + "label": "Description" + }, + "isMagic": { + "label": "Magic Item" + }, + "magicQuality": { + "label": "Quality" + }, + "isCursed": { + "label": "Cursed" + }, + "magicEffect": { + "label": "Enchantment" + }, + "classRestriction": { + "label": "Restriction" + } + } + }, + "Armor": { + "FIELDS": { + "armorType": { + "label": "Armor Type" + }, + "armorValue": { + "label": "Armor Value (AV)" + }, + "penalty": { + "label": "Penalty" + }, + "slots": { + "label": "Slots" + }, + "traits": { + "label": "Traits" + }, + "rarity": { + "label": "Rarity" + }, + "equipped": { + "label": "Equipped" + }, + "cost": { + "label": "Cost" + }, + "currency": { + "label": "Currency" + }, + "isMagic": { + "label": "Magic Item" + }, + "magicQuality": { + "label": "Quality" + }, + "isCursed": { + "label": "Cursed" + }, + "magicEffect": { + "label": "Enchantment" + }, + "classRestriction": { + "label": "Restriction" + } + } + }, + "Ammunition": { + "FIELDS": { + "ammoType": { + "label": "Ammunition Type" + }, + "quantity": { + "label": "Quantity" + }, + "rarity": { + "label": "Rarity" + }, + "cost": { + "label": "Cost" + }, + "currency": { + "label": "Currency" + } + } + }, + "Equipment": { + "FIELDS": { + "itemType": { + "label": "Category" + }, + "quantity": { + "label": "Quantity" + }, + "slots": { + "label": "Slots" + }, + "rarity": { + "label": "Rarity" + }, + "lightRadius": { + "label": "Light Radius (ft)" + }, + "cost": { + "label": "Cost" + }, + "currency": { + "label": "Currency" + } + } + }, + "Spell": { + "FIELDS": { + "tradition": { + "label": "Tradition" + }, + "difficultyValue": { + "label": "Difficulty Value (DV)" + }, + "isRitual": { + "label": "Ritual" + }, + "isMagicMissile": { + "label": "Magic Missile" + }, + "range": { + "label": "Range" + }, + "duration": { + "label": "Duration" + }, + "spellSave": { + "label": "Spell Save" + }, + "element": { + "label": "Element" + }, + "runeType": { + "label": "Rune Type" + }, + "isExalted": { + "label": "Exalted" + }, + "effect": { + "label": "Effect" + } + } + }, + "Miracle": { + "FIELDS": { + "divineTradition": { + "label": "Divine Tradition" + }, + "difficultyValue": { + "label": "Difficulty Value (DV)" + }, + "isRitual": { + "label": "Ritual" + }, + "range": { + "label": "Range" + }, + "duration": { + "label": "Duration" + }, + "spellSave": { + "label": "Spell Save" + }, + "effect": { + "label": "Effect" + } + } + }, + "MagicItem": { + "FIELDS": { + "itemType": { + "label": "Type" + }, + "quality": { + "label": "Quality" + }, + "isCursed": { + "label": "Cursed" + }, + "isBonded": { + "label": "Bonded" + }, + "classRestriction": { + "label": "Restriction" + }, + "usagePeriod": { + "label": "Usage Period" + }, + "maxUses": { + "label": "Max Uses" + }, + "slots": { + "label": "Slots" + }, + "equipped": { + "label": "Equipped" + }, + "effect": { + "label": "Effect" + } + } + }, + "Ability": { + "FIELDS": { + "abilityType": { + "label": "Type" + }, + "source": { + "label": "Source (Class / Lineage)" + }, + "usagePeriod": { + "label": "Usage Period" + }, + "maxUses": { + "label": "Max Uses" + }, + "description": { + "label": "Description" + } + } + }, + "WeaponGroup": { + "Common": "Common", + "Dueling": "Dueling", + "Heavy": "Heavy", + "Polearms": "Polearms", + "Bows": "Bows", + "Throwing": "Throwing" + }, + "WeaponTrait": { + "Block": "Block", + "Brutal": "Brutal", + "Clumsy": "Clumsy", + "Couched": "Couched", + "Deadly": "Deadly", + "Fast": "Fast", + "Flaming": "Flaming", + "Nimble": "Nimble", + "Parry": "Parry", + "Reload": "Reload", + "Repel": "Repel", + "Stunning": "Stunning", + "Sweep": "Sweep", + "TwoHanded": "Two-handed", + "Versatile": "Versatile" + }, + "DivineTradition": { + "Druidic": "Druidic", + "Profane": "Profane", + "Sanctified": "Sanctified" + }, + "Element": { + "Air": "Air", + "Earth": "Earth", + "Fire": "Fire", + "Water": "Water", + "Varies": "Varies" + }, + "RuneType": { + "Armor": "Armor", + "Talisman": "Talisman", + "Warding": "Warding", + "Weapon": "Weapon" + }, + "ArmorTrait": { + "Clanging": "Clanging", + "Reinforced": "Reinforced" + }, + "UsagePeriod": { + "None": "Passive (always on)", + "Encounter": "Per Encounter", + "Day": "Per Day" + }, + "MagicQuality": { + "Lesser": "Lesser", + "Greater": "Greater", + "Legendary": "Legendary" + }, + "OathType": { + "Compassion": "Oath of Compassion", + "Courage": "Oath of Courage", + "Diligence": "Oath of Diligence", + "Faith": "Oath of Faith", + "Humility": "Oath of Humility", + "Justice": "Oath of Justice", + "Loyalty": "Oath of Loyalty", + "Peace": "Oath of Peace", + "Perseverance": "Oath of Perseverance", + "Purity": "Oath of Purity", + "Truth": "Oath of Truth", + "Wisdom": "Oath of Wisdom" + }, + "OathFields": { + "oathType": { + "label": "Oath" + }, + "tenet": { + "label": "Tenet" + }, + "boon": { + "label": "Boon" + }, + "bane": { + "label": "Bane" + }, + "violated": { + "label": "Violated" + } + }, + "Oath": { + "FIELDS": { + "oathType": { + "label": "Oath" + }, + "tenet": { + "label": "Tenet" + }, + "boon": { + "label": "Boon" + }, + "bane": { + "label": "Bane" + }, + "violated": { + "label": "Violated" + } + } + } + }, + "TYPES": { + "Item": { + "weapon": "Weapon", + "armor": "Armor", + "ammunition": "Ammunition", + "equipment": "Equipment", + "spell": "Spell", + "miracle": "Miracle", + "magic_item": "Magic Item", + "magic-item": "Magic Item", + "ability": "Ability", + "oath": "Oath", + "lineage": "Lineage", + "class": "Class" + }, + "Actor": { + "character": "Character", + "npc": "NPC" + } + } +} \ No newline at end of file diff --git a/.history/system_20260308195653.json b/.history/system_20260308195653.json new file mode 100644 index 0000000..f56deb1 --- /dev/null +++ b/.history/system_20260308195653.json @@ -0,0 +1,130 @@ +{ + "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", + "magicEffect" + ] + }, + "armor": { + "htmlFields": [ + "description", + "magicEffect" + ] + }, + "ammunition": { + "htmlFields": [ + "description" + ] + }, + "equipment": { + "htmlFields": [ + "description" + ] + }, + "spell": { + "htmlFields": [ + "effect" + ] + }, + "miracle": { + "htmlFields": [ + "effect" + ] + }, + "magic-item": { + "htmlFields": [ + "effect" + ] + }, + "trait": { + "htmlFields": [ + "description" + ] + }, + "oath": { + "htmlFields": [ + "tenet", + "boon", + "bane" + ] + }, + "lineage": { + "htmlFields": [ + "description", + "traits" + ] + }, + "class": { + "htmlFields": [ + "description", + "features" + ] + }, + "building": { + "htmlFields": [ + "description", + "notes" + ] + } + } + }, + "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/" + ] + } + } +} \ No newline at end of file diff --git a/.history/system_20260308204838.json b/.history/system_20260308204838.json new file mode 100644 index 0000000..7ba25bb --- /dev/null +++ b/.history/system_20260308204838.json @@ -0,0 +1,130 @@ +{ + "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", + "magicEffect" + ] + }, + "armor": { + "htmlFields": [ + "description", + "magicEffect" + ] + }, + "ammunition": { + "htmlFields": [ + "description" + ] + }, + "equipment": { + "htmlFields": [ + "description" + ] + }, + "spell": { + "htmlFields": [ + "effect" + ] + }, + "miracle": { + "htmlFields": [ + "effect" + ] + }, + "magic-item": { + "htmlFields": [ + "effect" + ] + }, + "trait": { + "htmlFields": [ + "description" + ] + }, + "oath": { + "htmlFields": [ + "tenet", + "boon", + "bane" + ] + }, + "lineage": { + "htmlFields": [ + "description", + "traits" + ] + }, + "class": { + "htmlFields": [ + "description", + "features" + ] + }, + "building": { + "htmlFields": [ + "description", + "notes" + ] + } + } + }, + "grid": { + "distance": 5, + "units": "ft" + }, + "primaryTokenAttribute": "grit", + "socket": true, + "background": "assets/images/cover_art.webp", + "flags": { + "hotReload": { + "extensions": [ + "css", + "hbs", + "json" + ], + "paths": [ + "css/", + "lang/", + "assets/", + "templates/" + ] + } + } +} \ No newline at end of file diff --git a/.history/system_20260308204842.json b/.history/system_20260308204842.json new file mode 100644 index 0000000..560fff4 --- /dev/null +++ b/.history/system_20260308204842.json @@ -0,0 +1,130 @@ +{ + "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", + "magicEffect" + ] + }, + "armor": { + "htmlFields": [ + "description", + "magicEffect" + ] + }, + "ammunition": { + "htmlFields": [ + "description" + ] + }, + "equipment": { + "htmlFields": [ + "description" + ] + }, + "spell": { + "htmlFields": [ + "effect" + ] + }, + "miracle": { + "htmlFields": [ + "effect" + ] + }, + "magic-item": { + "htmlFields": [ + "effect" + ] + }, + "trait": { + "htmlFields": [ + "description" + ] + }, + "oath": { + "htmlFields": [ + "tenet", + "boon", + "bane" + ] + }, + "lineage": { + "htmlFields": [ + "description", + "traits" + ] + }, + "class": { + "htmlFields": [ + "description", + "features" + ] + }, + "building": { + "htmlFields": [ + "description", + "notes" + ] + } + } + }, + "grid": { + "distance": 5, + "units": "ft" + }, + "primaryTokenAttribute": "grit", + "socket": true, + "background": "systems/fvtt-oath-hammer/assets/images/cover_art.webp", + "flags": { + "hotReload": { + "extensions": [ + "css", + "hbs", + "json" + ], + "paths": [ + "css/", + "lang/", + "assets/", + "templates/" + ] + } + } +} \ No newline at end of file diff --git a/.history/system_20260308204843.json b/.history/system_20260308204843.json new file mode 100644 index 0000000..560fff4 --- /dev/null +++ b/.history/system_20260308204843.json @@ -0,0 +1,130 @@ +{ + "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", + "magicEffect" + ] + }, + "armor": { + "htmlFields": [ + "description", + "magicEffect" + ] + }, + "ammunition": { + "htmlFields": [ + "description" + ] + }, + "equipment": { + "htmlFields": [ + "description" + ] + }, + "spell": { + "htmlFields": [ + "effect" + ] + }, + "miracle": { + "htmlFields": [ + "effect" + ] + }, + "magic-item": { + "htmlFields": [ + "effect" + ] + }, + "trait": { + "htmlFields": [ + "description" + ] + }, + "oath": { + "htmlFields": [ + "tenet", + "boon", + "bane" + ] + }, + "lineage": { + "htmlFields": [ + "description", + "traits" + ] + }, + "class": { + "htmlFields": [ + "description", + "features" + ] + }, + "building": { + "htmlFields": [ + "description", + "notes" + ] + } + } + }, + "grid": { + "distance": 5, + "units": "ft" + }, + "primaryTokenAttribute": "grit", + "socket": true, + "background": "systems/fvtt-oath-hammer/assets/images/cover_art.webp", + "flags": { + "hotReload": { + "extensions": [ + "css", + "hbs", + "json" + ], + "paths": [ + "css/", + "lang/", + "assets/", + "templates/" + ] + } + } +} \ No newline at end of file diff --git a/.history/templates/item/armor-sheet_20260308134234.hbs b/.history/templates/item/armor-sheet_20260308134234.hbs new file mode 100644 index 0000000..3dc5c61 --- /dev/null +++ b/.history/templates/item/armor-sheet_20260308134234.hbs @@ -0,0 +1,51 @@ +
+
+ + {{formInput fields.name value=source.name}} +
+
+
+ {{formField systemFields.armorType value=system.armorType name="system.armorType" localize=true}} +
+ +
+ +
+
+
+ +
+ +
+
+ {{formField systemFields.slots value=system.slots name="system.slots"}} + {{formField systemFields.traits value=system.traits name="system.traits" localize=true}} +
+
+ {{formField systemFields.rarity value=system.rarity name="system.rarity" localize=true}} + {{formField systemFields.isMagic value=system.isMagic name="system.isMagic"}} + {{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}} +
+ {{#if system.isMagic}} +
+ {{localize "OATHHAMMER.Label.Enchantment"}} +
+ {{formField systemFields.magicQuality value=system.magicQuality name="system.magicQuality" localize=true}} + {{formField systemFields.isCursed value=system.isCursed name="system.isCursed"}} + {{formField systemFields.classRestriction value=system.classRestriction name="system.classRestriction"}} +
+ {{formInput systemFields.magicEffect enriched=enrichedMagicEffect value=system.magicEffect name="system.magicEffect" toggled=true}} +
+ {{/if}} +
diff --git a/.history/templates/item/armor-sheet_20260308155000.hbs b/.history/templates/item/armor-sheet_20260308155000.hbs new file mode 100644 index 0000000..ec53173 --- /dev/null +++ b/.history/templates/item/armor-sheet_20260308155000.hbs @@ -0,0 +1,109 @@ +
+
+ + {{formInput fields.name value=source.name}} +
+
+
+ {{formField + systemFields.armorType + value=system.armorType + name="system.armorType" + }} +
+ +
+ +
+
+
+ +
+ +
+
+ {{formField systemFields.slots value=system.slots name="system.slots"}} + {{formField + systemFields.traits + value=system.traits + name="system.traits" + localize=true + }} +
+
+ {{formField + systemFields.rarity + value=system.rarity + name="system.rarity" + localize=true + }} + {{formField + systemFields.isMagic + value=system.isMagic + name="system.isMagic" + }} + {{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 + }} +
+ {{#if system.isMagic}} +
+ {{localize "OATHHAMMER.Label.Enchantment"}} +
+ {{formField + systemFields.magicQuality + value=system.magicQuality + name="system.magicQuality" + localize=true + }} + {{formField + systemFields.isCursed + value=system.isCursed + name="system.isCursed" + }} + {{formField + systemFields.classRestriction + value=system.classRestriction + name="system.classRestriction" + }} +
+ {{formInput + systemFields.magicEffect + enriched=enrichedMagicEffect + value=system.magicEffect + name="system.magicEffect" + toggled=true + }} +
+ {{/if}} +
\ No newline at end of file diff --git a/.history/templates/item/armor-sheet_20260308155017.hbs b/.history/templates/item/armor-sheet_20260308155017.hbs new file mode 100644 index 0000000..2bdd08e --- /dev/null +++ b/.history/templates/item/armor-sheet_20260308155017.hbs @@ -0,0 +1,51 @@ +
+
+ + {{formInput fields.name value=source.name}} +
+
+
+ {{formField systemFields.armorType value=system.armorType name="system.armorType"}} +
+ +
+ +
+
+
+ +
+ +
+
+ {{formField systemFields.slots value=system.slots name="system.slots"}} + {{formField systemFields.traits value=system.traits name="system.traits" localize=true}} +
+
+ {{formField systemFields.rarity value=system.rarity name="system.rarity" localize=true}} + {{formField systemFields.isMagic value=system.isMagic name="system.isMagic"}} + {{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}} +
+ {{#if system.isMagic}} +
+ {{localize "OATHHAMMER.Label.Enchantment"}} +
+ {{formField systemFields.magicQuality value=system.magicQuality name="system.magicQuality" localize=true}} + {{formField systemFields.isCursed value=system.isCursed name="system.isCursed"}} + {{formField systemFields.classRestriction value=system.classRestriction name="system.classRestriction"}} +
+ {{formInput systemFields.magicEffect enriched=enrichedMagicEffect value=system.magicEffect name="system.magicEffect" toggled=true}} +
+ {{/if}} +
diff --git a/.history/templates/item/armor-sheet_20260308155019.hbs b/.history/templates/item/armor-sheet_20260308155019.hbs new file mode 100644 index 0000000..ec53173 --- /dev/null +++ b/.history/templates/item/armor-sheet_20260308155019.hbs @@ -0,0 +1,109 @@ +
+
+ + {{formInput fields.name value=source.name}} +
+
+
+ {{formField + systemFields.armorType + value=system.armorType + name="system.armorType" + }} +
+ +
+ +
+
+
+ +
+ +
+
+ {{formField systemFields.slots value=system.slots name="system.slots"}} + {{formField + systemFields.traits + value=system.traits + name="system.traits" + localize=true + }} +
+
+ {{formField + systemFields.rarity + value=system.rarity + name="system.rarity" + localize=true + }} + {{formField + systemFields.isMagic + value=system.isMagic + name="system.isMagic" + }} + {{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 + }} +
+ {{#if system.isMagic}} +
+ {{localize "OATHHAMMER.Label.Enchantment"}} +
+ {{formField + systemFields.magicQuality + value=system.magicQuality + name="system.magicQuality" + localize=true + }} + {{formField + systemFields.isCursed + value=system.isCursed + name="system.isCursed" + }} + {{formField + systemFields.classRestriction + value=system.classRestriction + name="system.classRestriction" + }} +
+ {{formInput + systemFields.magicEffect + enriched=enrichedMagicEffect + value=system.magicEffect + name="system.magicEffect" + toggled=true + }} +
+ {{/if}} +
\ No newline at end of file diff --git a/.history/templates/item/armor-sheet_20260308155021.hbs b/.history/templates/item/armor-sheet_20260308155021.hbs new file mode 100644 index 0000000..d8d62c4 --- /dev/null +++ b/.history/templates/item/armor-sheet_20260308155021.hbs @@ -0,0 +1,110 @@ +
+
+ + {{formInput fields.name value=source.name}} +
+
+
+ {{formField + systemFields.armorType + value=system.armorType + name="system.armorType" + localize=true + }} +
+ +
+ +
+
+
+ +
+ +
+
+ {{formField systemFields.slots value=system.slots name="system.slots"}} + {{formField + systemFields.traits + value=system.traits + name="system.traits" + localize=true + }} +
+
+ {{formField + systemFields.rarity + value=system.rarity + name="system.rarity" + localize=true + }} + {{formField + systemFields.isMagic + value=system.isMagic + name="system.isMagic" + }} + {{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 + }} +
+ {{#if system.isMagic}} +
+ {{localize "OATHHAMMER.Label.Enchantment"}} +
+ {{formField + systemFields.magicQuality + value=system.magicQuality + name="system.magicQuality" + localize=true + }} + {{formField + systemFields.isCursed + value=system.isCursed + name="system.isCursed" + }} + {{formField + systemFields.classRestriction + value=system.classRestriction + name="system.classRestriction" + }} +
+ {{formInput + systemFields.magicEffect + enriched=enrichedMagicEffect + value=system.magicEffect + name="system.magicEffect" + toggled=true + }} +
+ {{/if}} +
\ No newline at end of file diff --git a/css/fvtt-oath-hammer.css b/css/fvtt-oath-hammer.css index 45a20fd..aadef32 100644 --- a/css/fvtt-oath-hammer.css +++ b/css/fvtt-oath-hammer.css @@ -754,7 +754,7 @@ } .oathhammer .item-list--weapon .item-list-header, .oathhammer .item-list--weapon .item-entry { - grid-template-columns: 24px 1fr 4.5rem 3.5rem 2rem 1.5rem 1.8rem 3.5rem; + grid-template-columns: 24px 1fr 4.5rem 3.5rem 2rem 1.5rem 1.8rem 5.5rem; } .oathhammer .item-list--armor .item-list-header, .oathhammer .item-list--armor .item-entry { @@ -766,11 +766,11 @@ } .oathhammer .item-list--spell .item-list-header, .oathhammer .item-list--spell .item-entry { - grid-template-columns: 24px 1fr 3rem 6rem 3rem 3.5rem; + grid-template-columns: 24px 1fr 3rem 6rem 3rem 5.5rem; } .oathhammer .item-list--miracle .item-list-header, .oathhammer .item-list--miracle .item-entry { - grid-template-columns: 24px 1fr 4.5rem 3.5rem; + grid-template-columns: 24px 1fr 4.5rem 5.5rem; } .oathhammer .item-list--equipment .item-list-header, .oathhammer .item-list--equipment .item-entry { @@ -958,3 +958,610 @@ height: auto; accent-color: #084a74; } +.oh-roll-card { + font-family: "Calibri", "Segoe UI", sans-serif; + border: 1px solid #535128; + border-radius: 4px; + padding: 6px 8px; + background: rgba(245, 234, 208, 0.4); +} +.oh-roll-card .oh-roll-header { + font-family: "BlueDragon", "Palatino Linotype", serif; + font-size: 0.86rem; + font-weight: bold; + color: #2a1a0a; + margin-bottom: 4px; + border-bottom: 1px solid rgba(83, 81, 40, 0.2); + padding-bottom: 3px; +} +.oh-roll-card .oh-roll-info { + display: flex; + justify-content: space-between; + font-size: calc(0.86rem * 0.9); + color: #535128; + margin-bottom: 6px; +} +.oh-roll-card .oh-roll-dice { + display: flex; + flex-wrap: wrap; + gap: 3px; + margin-bottom: 6px; +} +.oh-roll-card .oh-roll-dice .oh-die { + display: inline-flex; + align-items: center; + justify-content: center; + width: 24px; + height: 24px; + border-radius: 3px; + font-weight: bold; + font-size: 13px; + border: 1px solid #535128; +} +.oh-roll-card .oh-roll-dice .die-success { + background: #2ecc71; + color: #fff; + border-color: #27ae60; +} +.oh-roll-card .oh-roll-dice .die-fail { + background: #ecf0f1; + color: #7f8c8d; + border-color: #bdc3c7; +} +.oh-roll-card .oh-roll-result { + display: flex; + justify-content: space-between; + align-items: center; + padding: 4px 6px; + border-radius: 3px; + font-weight: bold; + font-size: calc(0.86rem * 0.85); +} +.oh-roll-card .roll-success { + background: rgba(46, 204, 113, 0.2); + color: #1e8449; +} +.oh-roll-card .roll-failure { + background: rgba(231, 76, 60, 0.15); + color: #c0392b; +} +.oathhammer .rarity-roll-btn { + display: inline-flex; + align-items: center; + gap: 3px; + cursor: pointer; + font-size: calc(0.86rem * 0.9); + color: #084a74; + opacity: 0.6; + transition: opacity 0.2s; +} +.oathhammer .rarity-roll-btn:hover { + opacity: 1; +} +.oathhammer .rarity-roll-btn:hover { + color: #2a1a0a; + text-decoration: underline; +} +.oathhammer .rarity-roll-btn i { + font-size: 0.85em; +} +.fvtt-oath-hammer .window-content { + background: #f5ead0; + padding: 6px 8px; +} +.fvtt-oath-hammer .oh-roll-dialog { + font-family: "Calibri", "Segoe UI", sans-serif; + min-width: 320px; + padding: 4px 2px; + background: #f5ead0; + color: #2a1a0a; +} +.fvtt-oath-hammer .oh-roll-dialog .roll-actor-name { + text-align: center; + font-family: "BlueDragon", "Palatino Linotype", serif; + font-size: calc(0.86rem * 1.2); + color: #084a74; + margin-bottom: 8px; + padding-bottom: 4px; + border-bottom: 1px solid rgba(83, 81, 40, 0.2); +} +.fvtt-oath-hammer .oh-roll-dialog fieldset { + border: 1px solid rgba(83, 81, 40, 0.2); + border-radius: 4px; + padding: 8px 10px; + margin-bottom: 8px; +} +.fvtt-oath-hammer .oh-roll-dialog fieldset legend { + font-family: "BlueDragon", "Palatino Linotype", serif; + color: #535128; + font-size: calc(0.86rem * 0.9); + padding: 0 6px; + letter-spacing: 0.04em; +} +.fvtt-oath-hammer .oh-roll-dialog .roll-info-block { + background: rgba(83, 81, 40, 0.06); +} +.fvtt-oath-hammer .oh-roll-dialog .roll-info-block .roll-skill-line { + display: flex; + flex-direction: column; + gap: 2px; + margin-bottom: 8px; +} +.fvtt-oath-hammer .oh-roll-dialog .roll-info-block .roll-skill-name { + font-weight: bold; + font-size: calc(0.86rem * 1.1); + color: #2a1a0a; +} +.fvtt-oath-hammer .oh-roll-dialog .roll-info-block .roll-attr-info { + font-size: calc(0.86rem * 0.9); + color: #535128; +} +.fvtt-oath-hammer .oh-roll-dialog .roll-info-block .roll-dice-preview { + display: flex; + align-items: center; + gap: 8px; +} +.fvtt-oath-hammer .oh-roll-dialog .roll-info-block .roll-pool { + font-size: 1.6rem; + font-weight: bold; + color: #084a74; + line-height: 1; +} +.fvtt-oath-hammer .oh-roll-dialog .roll-info-block .roll-color-badge { + display: inline-flex; + align-items: center; + gap: 4px; + padding: 3px 8px; + border-radius: 4px; + font-size: calc(0.86rem * 0.9); + font-weight: bold; + border: 1px solid; +} +.fvtt-oath-hammer .oh-roll-dialog .roll-info-block .color-badge-white { + background: #f0f0f0; + color: #555; + border-color: #ccc; +} +.fvtt-oath-hammer .oh-roll-dialog .roll-info-block .color-badge-red { + background: rgba(231, 76, 60, 0.12); + color: #c0392b; + border-color: rgba(231, 76, 60, 0.35); +} +.fvtt-oath-hammer .oh-roll-dialog .roll-info-block .color-badge-black { + background: rgba(44, 62, 80, 0.1); + color: #2c3e50; + border-color: rgba(44, 62, 80, 0.35); +} +.fvtt-oath-hammer .oh-roll-dialog .roll-info-block .roll-dual-attr { + display: flex; + align-items: center; + gap: 6px; + margin-top: 8px; + padding-top: 6px; + border-top: 1px dashed rgba(83, 81, 40, 0.2); +} +.fvtt-oath-hammer .oh-roll-dialog .roll-info-block .roll-dual-attr label { + font-size: calc(0.86rem * 0.9); + color: #535128; + white-space: nowrap; +} +.fvtt-oath-hammer .oh-roll-dialog .roll-info-block .roll-dual-attr select { + flex: 1; + font-size: calc(0.86rem * 0.9); + font-family: "Calibri", "Segoe UI", sans-serif; + padding: 2px 4px; + border: 1px solid rgba(83, 81, 40, 0.2); + border-radius: 3px; + background: rgba(255, 255, 255, 0.85); + color: #2a1a0a; +} +.fvtt-oath-hammer .oh-roll-dialog .roll-options-block .roll-option-row { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 4px 8px; + padding: 5px 0; +} +.fvtt-oath-hammer .oh-roll-dialog .roll-options-block .roll-option-row:not(:last-child) { + border-bottom: 1px solid rgba(83, 81, 40, 0.1); +} +.fvtt-oath-hammer .oh-roll-dialog .roll-options-block .roll-option-row label { + flex: 1 1 120px; + font-size: calc(0.86rem * 0.9); + color: #2a1a0a; + white-space: nowrap; + font-family: "Calibri", "Segoe UI", sans-serif; +} +.fvtt-oath-hammer .oh-roll-dialog .roll-options-block .roll-option-row select { + flex: 0 0 90px; + padding: 3px 6px; + border: 1px solid rgba(49, 47, 23, 0.2); + border-radius: 3px; + background: rgba(255, 255, 255, 0.85); + color: #2a1a0a; + font-family: "Calibri", "Segoe UI", sans-serif; + font-size: calc(0.86rem * 0.9); +} +.fvtt-oath-hammer .oh-roll-dialog .roll-options-block .roll-option-row .roll-option-hint { + flex: 1 1 100%; + font-size: calc(calc(0.86rem * 0.9) * 0.85); + color: #535128; + font-style: italic; + padding-left: 4px; + white-space: normal; + overflow: hidden; + text-overflow: ellipsis; +} +.fvtt-oath-hammer .oh-roll-dialog .roll-options-block .roll-option-luck label { + color: #c8a84b; + font-weight: bold; +} +.fvtt-oath-hammer .oh-roll-dialog .roll-options-block .roll-option-luck select { + border-color: #c8a84b; +} +.fvtt-oath-hammer .oh-roll-dialog .roll-options-block .roll-option-luck .luck-icon { + color: #c8a84b; + font-size: 0.8em; +} +.fvtt-oath-hammer .oh-roll-dialog .roll-visibility-block select { + width: 100%; + padding: 4px 6px; + border: 1px solid rgba(83, 81, 40, 0.2); + border-radius: 3px; + background: rgba(255, 255, 255, 0.85); + color: #2a1a0a; + font-family: "Calibri", "Segoe UI", sans-serif; + font-size: calc(0.86rem * 0.9); +} +.fvtt-oath-hammer .skills-list a.skill-name-col { + cursor: pointer; + color: #2a1a0a; + text-decoration: none; + display: inline-flex; + align-items: center; + gap: 5px; + opacity: 0.6; + transition: opacity 0.2s; +} +.fvtt-oath-hammer .skills-list a.skill-name-col:hover { + opacity: 1; +} +.fvtt-oath-hammer .skills-list a.skill-name-col .skill-roll-icon { + font-size: 0.75em; + color: #535128; + flex-shrink: 0; +} +.fvtt-oath-hammer .skills-list a.skill-name-col:hover { + color: #084a74; + text-decoration: underline; +} +.fvtt-oath-hammer .skills-list a.skill-name-col:hover .skill-roll-icon { + color: #084a74; +} +.oh-weapon-dialog .weapon-header { + display: flex; + align-items: flex-start; + gap: 10px; + padding: 6px 0 8px; + border-bottom: 1px solid rgba(83, 81, 40, 0.2); + margin-bottom: 8px; +} +.oh-weapon-dialog .weapon-header .weapon-img-sm { + width: 40px; + height: 40px; + -o-object-fit: contain; + object-fit: contain; + border: 1px solid rgba(83, 81, 40, 0.2); + border-radius: 4px; + flex-shrink: 0; +} +.oh-weapon-dialog .weapon-header .weapon-header-info { + flex: 1; + display: flex; + flex-direction: column; + gap: 4px; +} +.oh-weapon-dialog .weapon-header .weapon-name-lg { + font-family: "BlueDragon", "Palatino Linotype", serif; + font-size: calc(0.86rem * 1.1); + color: #084a74; + font-weight: bold; +} +.oh-weapon-dialog .weapon-header .weapon-badges { + display: flex; + flex-wrap: wrap; + gap: 4px; + align-items: center; +} +.oh-weapon-dialog .weapon-header .damage-formula-badge { + font-family: "Calibri", "Segoe UI", sans-serif; + font-size: calc(0.86rem * 0.9); + font-weight: bold; + color: #2a1a0a; + background: rgba(83, 81, 40, 0.1); + border: 1px solid rgba(83, 81, 40, 0.2); + border-radius: 3px; + padding: 2px 6px; +} +.oh-weapon-dialog .weapon-header .ap-badge { + font-size: calc(0.86rem * 0.9); + font-weight: bold; + color: #8b0000; + background: rgba(139, 0, 0, 0.08); + border: 1px solid rgba(139, 0, 0, 0.25); + border-radius: 3px; + padding: 2px 6px; +} +.oh-weapon-dialog .weapon-header .range-badge { + font-size: calc(0.86rem * 0.9); + color: #535128; + background: rgba(83, 81, 40, 0.08); + border: 1px solid rgba(83, 81, 40, 0.2); + border-radius: 3px; + padding: 2px 6px; +} +.oh-weapon-dialog .weapon-header .auto-bonus-badge { + font-size: calc(0.86rem * 0.9); + font-weight: bold; + color: #987d2e; + background: rgba(200, 168, 75, 0.12); + border: 1px solid rgba(200, 168, 75, 0.4); + border-radius: 3px; + padding: 2px 6px; +} +.oh-weapon-dialog .weapon-header .weapon-traits-row { + display: flex; + flex-wrap: wrap; + gap: 3px; + margin-top: 2px; +} +.oh-weapon-dialog .weapon-header .trait-tag-sm { + font-size: calc(calc(0.86rem * 0.9) * 0.85); + color: #535128; + background: rgba(83, 81, 40, 0.08); + border: 1px solid rgba(83, 81, 40, 0.2); + border-radius: 3px; + padding: 1px 5px; +} +.oh-weapon-dialog .pool-info-line { + font-size: calc(0.86rem * 0.9); + color: #2a1a0a; + padding: 4px 0 6px; + font-family: "Calibri", "Segoe UI", sans-serif; +} +.oh-weapon-dialog .pool-info-line strong { + color: #084a74; + font-size: calc(0.86rem * 1.1); +} +.oh-weapon-dialog .pool-info-line .auto-bonus { + color: #ac8d34; + font-weight: bold; + font-size: calc(0.86rem * 0.9); +} +.oh-weapon-card .oh-roll-header, +.oh-spell-card .oh-roll-header, +.oh-miracle-card .oh-roll-header { + display: flex; + align-items: center; + gap: 8px; +} +.oh-weapon-card .oh-card-weapon-img, +.oh-spell-card .oh-card-weapon-img, +.oh-miracle-card .oh-card-weapon-img { + width: 28px; + height: 28px; + -o-object-fit: contain; + object-fit: contain; + border-radius: 3px; + border: 1px solid rgba(83, 81, 40, 0.2); + flex-shrink: 0; +} +.oh-weapon-card .oh-weapon-damage-btn-row, +.oh-spell-card .oh-weapon-damage-btn-row, +.oh-miracle-card .oh-weapon-damage-btn-row { + margin-top: 8px; + text-align: center; +} +.oh-weapon-card .oh-roll-damage-btn, +.oh-spell-card .oh-roll-damage-btn, +.oh-miracle-card .oh-roll-damage-btn { + background: #084a74; + color: #fff; + border: none; + border-radius: 4px; + padding: 5px 14px; + font-family: "Calibri", "Segoe UI", sans-serif; + font-size: calc(0.86rem * 0.9); + cursor: pointer; + opacity: 0.6; + transition: opacity 0.2s; +} +.oh-weapon-card .oh-roll-damage-btn:hover, +.oh-spell-card .oh-roll-damage-btn:hover, +.oh-miracle-card .oh-roll-damage-btn:hover { + opacity: 1; +} +.oh-weapon-card .oh-roll-damage-btn:hover, +.oh-spell-card .oh-roll-damage-btn:hover, +.oh-miracle-card .oh-roll-damage-btn:hover { + background: #0b68a4; + opacity: 1; +} +.oh-weapon-card .oh-ap-note, +.oh-spell-card .oh-ap-note, +.oh-miracle-card .oh-ap-note { + font-size: calc(0.86rem * 0.9); + color: #8b0000; + font-weight: bold; + margin-left: 8px; +} +.item-list--weapon .item-actions { + gap: 8px; +} +.item-list--weapon .item-actions a[data-action="attackWeapon"] { + color: #084a74; + font-size: 1.1em; + opacity: 0.6; + transition: opacity 0.2s; +} +.item-list--weapon .item-actions a[data-action="attackWeapon"]:hover { + opacity: 1; +} +.item-list--weapon .item-actions a[data-action="damageWeapon"] { + color: #8b0000; + font-size: 1.1em; + opacity: 0.6; + transition: opacity 0.2s; +} +.item-list--weapon .item-actions a[data-action="damageWeapon"]:hover { + opacity: 1; +} +.item-list--weapon .item-actions a[data-action="edit"] { + margin-left: 6px; +} +.oh-spell-dialog .dv-badge, +.oh-miracle-dialog .dv-badge { + background: #3a0e6b; + color: #e8d9ff; + border-radius: 3px; + padding: 1px 6px; + font-size: 0.78em; + font-weight: bold; +} +.oh-spell-dialog .tradition-badge, +.oh-miracle-dialog .tradition-badge { + background: #0e3a6b; + color: #d9eeff; + border-radius: 3px; + padding: 1px 6px; + font-size: 0.78em; +} +.oh-spell-dialog .ritual-badge, +.oh-miracle-dialog .ritual-badge { + background: #4a3000; + color: #ffe8a0; + border-radius: 3px; + padding: 1px 6px; + font-size: 0.78em; +} +.oh-spell-dialog .missile-badge, +.oh-miracle-dialog .missile-badge { + background: #1a4a1a; + color: #b8ffb8; + border-radius: 3px; + padding: 1px 6px; + font-size: 0.78em; +} +.oh-spell-dialog .range-badge, +.oh-miracle-dialog .range-badge, +.oh-spell-dialog .duration-badge, +.oh-miracle-dialog .duration-badge { + background: rgba(42, 26, 10, 0.12); + color: #2a1a0a; + border-radius: 3px; + padding: 1px 6px; + font-size: 0.75em; +} +.oh-spell-dialog .save-info, +.oh-miracle-dialog .save-info { + font-size: 0.8em; + color: #2a1a0a; + font-style: italic; + margin-top: 3px; +} +.oh-spell-dialog .stress-tracker, +.oh-miracle-dialog .stress-tracker { + display: flex; + align-items: center; + gap: 8px; + padding: 6px 10px; + margin: 8px 0 4px; + background: rgba(83, 81, 40, 0.1); + border: 1px solid rgba(83, 81, 40, 0.3); + border-radius: 4px; + font-size: 0.88em; +} +.oh-spell-dialog .stress-tracker i, +.oh-miracle-dialog .stress-tracker i { + color: #2a1a0a; +} +.oh-spell-dialog .stress-tracker .stress-warning, +.oh-miracle-dialog .stress-tracker .stress-warning { + margin-left: auto; + color: #c00; + font-weight: bold; + font-size: 0.9em; +} +.oh-spell-dialog .stress-tracker.stress-danger, +.oh-miracle-dialog .stress-tracker.stress-danger { + background: rgba(204, 0, 0, 0.08); + border-color: rgba(204, 0, 0, 0.4); +} +.oh-spell-dialog .miracle-warning, +.oh-miracle-dialog .miracle-warning { + display: flex; + align-items: center; + gap: 8px; + padding: 6px 10px; + margin: 8px 0 4px; + background: rgba(139, 0, 0, 0.08); + border: 1px solid rgba(139, 0, 0, 0.3); + border-radius: 4px; + font-size: 0.83em; + color: #8b0000; + font-style: italic; +} +.oh-spell-dialog .miracle-warning i, +.oh-miracle-dialog .miracle-warning i { + flex-shrink: 0; +} +.oh-spell-dialog select.enhancement-select, +.oh-miracle-dialog select.enhancement-select { + min-width: 220px; +} +.oh-spell-card .oh-stress-line, +.oh-miracle-card .oh-stress-line { + margin-top: 6px; + padding: 4px 8px; + background: rgba(83, 81, 40, 0.1); + border: 1px solid rgba(83, 81, 40, 0.3); + border-radius: 3px; + font-size: 0.82em; + color: #2a1a0a; +} +.oh-spell-card .oh-stress-line.stress-blocked, +.oh-miracle-card .oh-stress-line.stress-blocked { + background: rgba(204, 0, 0, 0.08); + border-color: rgba(204, 0, 0, 0.4); + color: #c00; +} +.oh-spell-card .oh-miracle-blocked, +.oh-miracle-card .oh-miracle-blocked { + margin-top: 6px; + padding: 5px 8px; + background: rgba(139, 0, 0, 0.08); + border: 1px solid rgba(139, 0, 0, 0.35); + border-radius: 3px; + font-size: 0.85em; + color: #8b0000; + font-weight: bold; + text-align: center; +} +.item-list--spell .item-actions, +.item-list--miracle .item-actions { + gap: 8px; +} +.item-list--spell .item-actions a[data-action="castSpell"] .spell-cast-icon, +.item-list--miracle .item-actions a[data-action="castSpell"] .spell-cast-icon { + color: #3a0e6b; + font-size: 1.05em; +} +.item-list--spell .item-actions a[data-action="castMiracle"] .miracle-cast-icon, +.item-list--miracle .item-actions a[data-action="castMiracle"] .miracle-cast-icon { + color: #5a3000; + font-size: 1.05em; +} +.item-list--spell .item-actions a[data-action="edit"], +.item-list--miracle .item-actions a[data-action="edit"] { + margin-left: 6px; +} diff --git a/lang/en.json b/lang/en.json index f18ed6f..8476064 100644 --- a/lang/en.json +++ b/lang/en.json @@ -13,7 +13,6 @@ "Trait": "Oath Hammer Trait Sheet", "Oath": "Oath Hammer Oath Sheet", "Condition": "Oath Hammer Condition Sheet", - "Lineage": "Oath Hammer Lineage Sheet", "Class": "Oath Hammer Class Sheet", "Building": "Oath Hammer Building Sheet" }, @@ -61,28 +60,6 @@ "Survival": "Survival", "Tracking": "Tracking" }, - "Lineage": { - "Dwarf": "Dwarf", - "Firbolg": "Firbolg", - "Halfling": "Halfling", - "HighElf": "High Elf", - "Human": "Human", - "WoodElf": "Wood Elf", - "FIELDS": { - "description": { - "label": "Description" - }, - "traits": { - "label": "Traits" - }, - "movement": { - "label": "Movement (ft)" - }, - "gritModifier": { - "label": "Grit Modifier" - } - } - }, "Class": { "Berserker": "Berserker", "Champion": "Champion", @@ -128,7 +105,7 @@ "buildTime": { "label": "Build Time" }, "taxRevenue": { "label": "Tax Revenue / month" }, "constructed":{ "label": "Constructed" }, - "settlement": { "label": "Settlement" }, + "description":{ "label": "Description" }, "notes": { "label": "Notes" } } @@ -264,7 +241,7 @@ "SkillModifier": "Mod", "TotalDice": "Total", "ColorDice": "Color", - "DropLineage": "Drop Lineage Here", + "Lineage": "Lineage", "DropClass": "Drop Class Here", "Traits": "Traits", "Features": "Features", @@ -276,12 +253,18 @@ "Magic": "Magic", "Damage": "Damage", "Tradition": "Tradition", + "DivineTradition": "Divine Tradition", "Piety": "Piety", "Quantity": "Qty", "Rarity": "Rarity", "Penalty": "Penalty", "Equipped": "Eq.", - "XPCurrent": "Current XP" + "XPCurrent": "Current XP", + "Ritual": "Ritual", + "MagicMissile": "Magic Missile", + "SpellSave": "Save", + "StressBlocked": "BLOCKED — over stress threshold!", + "ArcaneStressShort": "AS" }, "ColorDice": { "White": "White (4+)", @@ -296,8 +279,108 @@ "Building": "New Building" }, "ToggleSheet": "Toggle Edit/Play Mode", + "Action": { + "CastSpell": "Cast Spell", + "InvokeMiracle": "Invoke Miracle" + }, + "Dialog": { + "SkillCheckTitle": "Skill Check: {skill}", + "SkillCheck": "Skill Check", + "Options": "Options", + "Roll": "Roll", + "DV": "Difficulty (DV)", + "DVHint": "successes needed", + "Modifier": "Bonus / Penalty", + "ModifierHint": "extra or fewer dice", + "Supporters": "Supporters", + "SupportersHint": "+1 die each", + "LuckSpend": "Luck Points", + "LuckHint": "+2 dice each", + "Available": "available", + "Visibility": "Visibility", + "Attribute": "Attribute", + "RollSkill": "Click to roll skill check", + "AttackTitle": "Attack: {weapon}", + "DamageTitle": "Damage: {weapon}", + "Attack": "Attack", + "Damage": "Damage", + "AttackModifier": "Modifier", + "AttackModifierHint": "situational bonus or penalty", + "DamageModifier": "Damage Modifier", + "DamageModifierHint": "extra or fewer damage dice", + "RangeCondition": "Range Condition", + "RangeNormal": "Normal", + "RangeLong": "Long Range", + "RangeMoving": "Moving Before Shot", + "RangeConcealment": "Concealment", + "RangeCover": "Cover", + "RollAttack": "Roll Attack", + "RollDamage": "Roll Damage", + "SV": "Net Successes (SV)", + "SVHint": "attack successes − defense successes", + "NimbleHint": "nimble — use Agility", + "SpellCastTitle": "Cast Spell: {spell}", + "CastSpell": "Cast Spell", + "CastOptions": "Cast Options", + "Enhancement": "Enhancement", + "ElementCondition": "Elemental Condition", + "ElementNone": "Not met", + "ElementMet": "Element met", + "Grimoire": "Grimoire", + "GrimoireHas": "Has Grimoire", + "GrimoireNo": "No Grimoire (−2)", + "MiracleCastTitle": "Invoke Miracle: {miracle}", + "InvokeMiracle": "Invoke Miracle", + "InvokeOptions": "Invoke Options", + "MiracleDVNote": "miracle # today", + "MiracleCount": "Miracle # Today", + "MiracleCountHint": "1st = DV 1, 2nd = DV 2...", + "MiracleFailWarning": "Failure blocks ALL miracles for the rest of the day." + }, + "Enhancement": { + "None": "None", + "Focused": "Focused (red dice, +1 stress)", + "Controlled": "Controlled (+1 stress)", + "Empowered": "Empowered (+2 stress)", + "Extended": "Extended (−1 die, +1 stress)", + "Penetrating": "Penetrating (−1 die, +1 stress)", + "Lethal": "Lethal (−2 dice, +2 stress)", + "Hastened": "Hastened (−3 dice, +3 stress)", + "Safe": "Safe Spell (−3 dice, no stress from 1s)" + }, + "Roll": { + "Check": "Check", + "Success": "Success!", + "Failure": "Failure", + "AutoSuccess": "Automatically Available", + "RarityCheck": "Rarity Check", + "NoActor": "No character selected — assign a character to your user first.", + "Successes": "successes", + "Damage": "damage", + "RollDamage": "Roll Damage", + "SpellCast": "Spell Cast", + "MiracleCast": "Miracle Invocation", + "StressGained": "Arcane Stress Gained", + "MiracleBlocked": "You are now blocked from casting miracles for the rest of the day!", + "DualAttr": { + "DefenseMelee": "melee defense", + "FightingNimble": "nimble weapon", + "MagicSpells": "spells" + } + }, "Character": { "FIELDS": { + "lineage": { + "label": "Lineage", + "fields": { + "name": { + "label": "Lineage" + }, + "traits": { + "label": "Lineage Traits" + } + } + }, "attributes": { "label": "Attributes" }, @@ -330,12 +413,6 @@ "biodata": { "label": "Background", "fields": { - "lineage": { - "label": "Lineage" - }, - "class": { - "label": "Class" - }, "age": { "label": "Age" }, @@ -439,9 +516,6 @@ "traits": { "label": "Traits" }, - "slots": { - "label": "Item Slots" - }, "rarity": { "label": "Rarity" }, @@ -491,6 +565,9 @@ "traits": { "label": "Traits" }, + "specialProperties": { + "label": "Special Properties" + }, "rarity": { "label": "Rarity" }, @@ -704,6 +781,18 @@ "TwoHanded": "Two-handed", "Versatile": "Versatile" }, + "WeaponProperty": { + "Accurate": "Accurate", + "AdvMechanism": "Adv. Mechanism", + "ArmorBane": "Armor Bane", + "Balanced": "Balanced", + "HeavyDraw": "Heavy Draw", + "MasterCrafted": "Master-Crafted", + "Ornate": "Ornate", + "Refined": "Refined", + "RuneEtched": "Rune-Etched", + "Tempered": "Tempered" + }, "DivineTradition": { "Druidic": "Druidic", "Profane": "Profane", @@ -799,7 +888,6 @@ "magic-item": "Magic Item", "trait": "Trait", "oath": "Oath", - "lineage": "Lineage", "class": "Class" }, "Actor": { diff --git a/less/fvtt-oath-hammer.less b/less/fvtt-oath-hammer.less index 1417909..f5285b2 100644 --- a/less/fvtt-oath-hammer.less +++ b/less/fvtt-oath-hammer.less @@ -9,3 +9,5 @@ @import "npc-sheet"; @import "item-list"; @import "item-sheets"; +@import "rolls"; +@import "roll-dialog"; diff --git a/less/item-list.less b/less/item-list.less index 762c706..6ddf986 100644 --- a/less/item-list.less +++ b/less/item-list.less @@ -117,7 +117,7 @@ .item-list--weapon { .item-list-header, .item-entry { - grid-template-columns: @item-img-size 1fr 4.5rem 3.5rem 2rem 1.5rem 1.8rem 3.5rem; + grid-template-columns: @item-img-size 1fr 4.5rem 3.5rem 2rem 1.5rem 1.8rem 5.5rem; } } @@ -135,13 +135,13 @@ .item-list--spell { .item-list-header, .item-entry { - grid-template-columns: @item-img-size 1fr 3rem 6rem 3rem 3.5rem; + grid-template-columns: @item-img-size 1fr 3rem 6rem 3rem 5.5rem; } } .item-list--miracle { .item-list-header, .item-entry { - grid-template-columns: @item-img-size 1fr 4.5rem 3.5rem; + grid-template-columns: @item-img-size 1fr 4.5rem 5.5rem; } } diff --git a/less/roll-dialog.less b/less/roll-dialog.less new file mode 100644 index 0000000..2560635 --- /dev/null +++ b/less/roll-dialog.less @@ -0,0 +1,552 @@ +// ============================================================ +// ROLL DIALOG — Oath Hammer pre-roll configuration dialog +// ============================================================ + +// Target both the window chrome and the inner content to ensure background coverage +.fvtt-oath-hammer { + .window-content { + background: @color-paper; + padding: 6px 8px; + } +} + +.fvtt-oath-hammer .oh-roll-dialog { + font-family: @font-body; + min-width: 320px; + padding: 4px 2px; + background: @color-paper; + color: @color-dark; + + .roll-actor-name { + text-align: center; + font-family: @font-secondary; + font-size: @font-size-xl; + color: @color-blue; + margin-bottom: 8px; + padding-bottom: 4px; + border-bottom: 1px solid @color-olive-faint; + } + + fieldset { + border: 1px solid @color-olive-faint; + border-radius: 4px; + padding: 8px 10px; + margin-bottom: 8px; + + legend { + font-family: @font-secondary; + color: @color-olive; + font-size: @font-size-xs; + padding: 0 6px; + letter-spacing: 0.04em; + } + } + + // ——— Skill info block ——— + .roll-info-block { + background: fade(@color-olive, 6%); + + .roll-skill-line { + display: flex; + flex-direction: column; + gap: 2px; + margin-bottom: 8px; + } + + .roll-skill-name { + font-weight: bold; + font-size: @font-size-lg; + color: @color-dark; + } + + .roll-attr-info { + font-size: @font-size-xs; + color: @color-olive; + } + + .roll-dice-preview { + display: flex; + align-items: center; + gap: 8px; + } + + .roll-pool { + font-size: 1.6rem; + font-weight: bold; + color: @color-blue; + line-height: 1; + } + + .roll-color-badge { + display: inline-flex; + align-items: center; + gap: 4px; + padding: 3px 8px; + border-radius: 4px; + font-size: @font-size-xs; + font-weight: bold; + border: 1px solid; + } + + .color-badge-white { + background: #f0f0f0; + color: #555; + border-color: #ccc; + } + + .color-badge-red { + background: fade(#e74c3c, 12%); + color: #c0392b; + border-color: fade(#e74c3c, 35%); + } + + .color-badge-black { + background: fade(#2c3e50, 10%); + color: #2c3e50; + border-color: fade(#2c3e50, 35%); + } + + .roll-dual-attr { + display: flex; + align-items: center; + gap: 6px; + margin-top: 8px; + padding-top: 6px; + border-top: 1px dashed @color-olive-faint; + + label { + font-size: @font-size-xs; + color: @color-olive; + white-space: nowrap; + } + + select { + flex: 1; + font-size: @font-size-xs; + font-family: @font-body; + padding: 2px 4px; + border: 1px solid @color-olive-faint; + border-radius: 3px; + background: rgba(255, 255, 255, 0.85); + color: @color-dark; + } + } + } + + // ——— Options block ——— + .roll-options-block { + .roll-option-row { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 4px 8px; + padding: 5px 0; + + &:not(:last-child) { + border-bottom: 1px solid fade(@color-olive, 10%); + } + + label { + flex: 1 1 120px; + font-size: @font-size-xs; + color: @color-dark; + white-space: nowrap; + font-family: @font-body; + } + + select { + flex: 0 0 90px; + padding: 3px 6px; + border: 1px solid darken(@color-olive-faint, 10%); + border-radius: 3px; + background: rgba(255, 255, 255, 0.85); + color: @color-dark; + font-family: @font-body; + font-size: @font-size-xs; + } + + .roll-option-hint { + flex: 1 1 100%; + font-size: calc(@font-size-xs * 0.85); + color: @color-olive; + font-style: italic; + padding-left: 4px; + white-space: normal; + overflow: hidden; + text-overflow: ellipsis; + } + } + + .roll-option-luck { + label { color: @color-gold; font-weight: bold; } + select { border-color: @color-gold; } + .luck-icon { color: @color-gold; font-size: 0.8em; } + } + } + + // ——— Visibility block ——— + .roll-visibility-block { + select { + width: 100%; + padding: 4px 6px; + border: 1px solid @color-olive-faint; + border-radius: 3px; + background: rgba(255, 255, 255, 0.85); + color: @color-dark; + font-family: @font-body; + font-size: @font-size-xs; + } + } +} + +// Rollable skill name in the skills tab +.fvtt-oath-hammer .skills-list { + a.skill-name-col { + cursor: pointer; + color: @color-dark; + text-decoration: none; + display: inline-flex; + align-items: center; + gap: 5px; + .transition-opacity(); + + .skill-roll-icon { + font-size: 0.75em; + color: @color-olive; + flex-shrink: 0; + } + + &:hover { + color: @color-blue; + text-decoration: underline; + .skill-roll-icon { color: @color-blue; } + } + } +} + +// ============================================================ +// WEAPON DIALOG +// ============================================================ + +.oh-weapon-dialog { + + // ——— Weapon header bar ——— + .weapon-header { + display: flex; + align-items: flex-start; + gap: 10px; + padding: 6px 0 8px; + border-bottom: 1px solid @color-olive-faint; + margin-bottom: 8px; + + .weapon-img-sm { + width: 40px; + height: 40px; + object-fit: contain; + border: 1px solid @color-olive-faint; + border-radius: 4px; + flex-shrink: 0; + } + + .weapon-header-info { + flex: 1; + display: flex; + flex-direction: column; + gap: 4px; + } + + .weapon-name-lg { + font-family: @font-secondary; + font-size: @font-size-lg; + color: @color-blue; + font-weight: bold; + } + + .weapon-badges { + display: flex; + flex-wrap: wrap; + gap: 4px; + align-items: center; + } + + .damage-formula-badge { + font-family: @font-body; + font-size: @font-size-xs; + font-weight: bold; + color: @color-dark; + background: fade(@color-olive, 10%); + border: 1px solid @color-olive-faint; + border-radius: 3px; + padding: 2px 6px; + } + + .ap-badge { + font-size: @font-size-xs; + font-weight: bold; + color: #8b0000; + background: fade(#8b0000, 8%); + border: 1px solid fade(#8b0000, 25%); + border-radius: 3px; + padding: 2px 6px; + } + + .range-badge { + font-size: @font-size-xs; + color: @color-olive; + background: fade(@color-olive, 8%); + border: 1px solid @color-olive-faint; + border-radius: 3px; + padding: 2px 6px; + } + + .auto-bonus-badge { + font-size: @font-size-xs; + font-weight: bold; + color: darken(@color-gold, 15%); + background: fade(@color-gold, 12%); + border: 1px solid fade(@color-gold, 40%); + border-radius: 3px; + padding: 2px 6px; + } + + .weapon-traits-row { + display: flex; + flex-wrap: wrap; + gap: 3px; + margin-top: 2px; + } + + .trait-tag-sm { + font-size: calc(@font-size-xs * 0.85); + color: @color-olive; + background: fade(@color-olive, 8%); + border: 1px solid @color-olive-faint; + border-radius: 3px; + padding: 1px 5px; + } + } + + // ——— Pool info line ——— + .pool-info-line { + font-size: @font-size-xs; + color: @color-dark; + padding: 4px 0 6px; + font-family: @font-body; + + strong { color: @color-blue; font-size: @font-size-lg; } + + .auto-bonus { + color: darken(@color-gold, 10%); + font-weight: bold; + font-size: @font-size-xs; + } + } +} + +// ——— Weapon / Spell / Miracle cards in chat ——— +.oh-weapon-card, +.oh-spell-card, +.oh-miracle-card { + .oh-roll-header { + display: flex; + align-items: center; + gap: 8px; + } + + .oh-card-weapon-img { + width: 28px; + height: 28px; + object-fit: contain; + border-radius: 3px; + border: 1px solid @color-olive-faint; + flex-shrink: 0; + } + + .oh-weapon-damage-btn-row { + margin-top: 8px; + text-align: center; + } + + .oh-roll-damage-btn { + background: @color-blue; + color: #fff; + border: none; + border-radius: 4px; + padding: 5px 14px; + font-family: @font-body; + font-size: @font-size-xs; + cursor: pointer; + .transition-opacity(); + + &:hover { background: lighten(@color-blue, 10%); opacity: 1; } + } + + .oh-ap-note { + font-size: @font-size-xs; + color: #8b0000; + font-weight: bold; + margin-left: 8px; + } +} + +// Attack/damage buttons in weapon list +.item-list--weapon .item-actions { + gap: 8px; + a[data-action="attackWeapon"] { + color: @color-blue; + font-size: 1.1em; + .transition-opacity(); + } + a[data-action="damageWeapon"] { + color: #8b0000; + font-size: 1.1em; + .transition-opacity(); + } + a[data-action="edit"] { margin-left: 6px; } +} + +// ============================================================ +// SPELL / MIRACLE DIALOG STYLES +// ============================================================ + +.oh-spell-dialog, +.oh-miracle-dialog { + + // Spell/miracle header (reuses .spell-header, .weapon-img-sm, .weapon-name-lg, .weapon-badges) + .dv-badge { + background: #3a0e6b; + color: #e8d9ff; + border-radius: 3px; + padding: 1px 6px; + font-size: 0.78em; + font-weight: bold; + } + + .tradition-badge { + background: #0e3a6b; + color: #d9eeff; + border-radius: 3px; + padding: 1px 6px; + font-size: 0.78em; + } + + .ritual-badge { + background: #4a3000; + color: #ffe8a0; + border-radius: 3px; + padding: 1px 6px; + font-size: 0.78em; + } + + .missile-badge { + background: #1a4a1a; + color: #b8ffb8; + border-radius: 3px; + padding: 1px 6px; + font-size: 0.78em; + } + + .range-badge, .duration-badge { + background: fade(@color-dark, 12%); + color: @color-dark; + border-radius: 3px; + padding: 1px 6px; + font-size: 0.75em; + } + + .save-info { + font-size: 0.8em; + color: @color-dark; + font-style: italic; + margin-top: 3px; + } + + // Arcane stress tracker + .stress-tracker { + display: flex; + align-items: center; + gap: 8px; + padding: 6px 10px; + margin: 8px 0 4px; + background: fade(@color-olive, 10%); + border: 1px solid fade(@color-olive, 30%); + border-radius: 4px; + font-size: 0.88em; + + i { color: @color-dark; } + + .stress-warning { + margin-left: auto; + color: #c00; + font-weight: bold; + font-size: 0.9em; + } + + &.stress-danger { + background: fade(#c00, 8%); + border-color: fade(#c00, 40%); + } + } + + // Miracle failure warning banner + .miracle-warning { + display: flex; + align-items: center; + gap: 8px; + padding: 6px 10px; + margin: 8px 0 4px; + background: fade(#8b0000, 8%); + border: 1px solid fade(#8b0000, 30%); + border-radius: 4px; + font-size: 0.83em; + color: #8b0000; + font-style: italic; + + i { flex-shrink: 0; } + } + + // Wide select for enhancement list + select.enhancement-select { + min-width: 220px; + } +} + +// Chat card additions for spell/miracle +.oh-spell-card, .oh-miracle-card { + .oh-stress-line { + margin-top: 6px; + padding: 4px 8px; + background: fade(@color-olive, 10%); + border: 1px solid fade(@color-olive, 30%); + border-radius: 3px; + font-size: 0.82em; + color: @color-dark; + + &.stress-blocked { + background: fade(#c00, 8%); + border-color: fade(#c00, 40%); + color: #c00; + } + } + + .oh-miracle-blocked { + margin-top: 6px; + padding: 5px 8px; + background: fade(#8b0000, 8%); + border: 1px solid fade(#8b0000, 35%); + border-radius: 3px; + font-size: 0.85em; + color: #8b0000; + font-weight: bold; + text-align: center; + } +} + +// Cast icons in magic item lists +.item-list--spell .item-actions, +.item-list--miracle .item-actions { + gap: 8px; + a[data-action="castSpell"] .spell-cast-icon { color: #3a0e6b; font-size: 1.05em; } + a[data-action="castMiracle"] .miracle-cast-icon { color: #5a3000; font-size: 1.05em; } + a[data-action="edit"] { margin-left: 6px; } +} diff --git a/less/rolls.less b/less/rolls.less new file mode 100644 index 0000000..8e37d29 --- /dev/null +++ b/less/rolls.less @@ -0,0 +1,100 @@ +// ============================================================ +// ROLL CARDS — Chat message styling for dice rolls +// ============================================================ + +.oh-roll-card { + font-family: @font-body; + border: 1px solid @color-olive; + border-radius: 4px; + padding: 6px 8px; + background: fade(#f5ead0, 40%); + + .oh-roll-header { + font-family: @font-secondary; + font-size: @font-size-base; + font-weight: bold; + color: @color-dark; + margin-bottom: 4px; + border-bottom: 1px solid @color-olive-faint; + padding-bottom: 3px; + } + + .oh-roll-info { + display: flex; + justify-content: space-between; + font-size: @font-size-xs; + color: @color-olive; + margin-bottom: 6px; + } + + .oh-roll-dice { + display: flex; + flex-wrap: wrap; + gap: 3px; + margin-bottom: 6px; + + .oh-die { + display: inline-flex; + align-items: center; + justify-content: center; + width: 24px; + height: 24px; + border-radius: 3px; + font-weight: bold; + font-size: 13px; + border: 1px solid @color-olive; + } + + .die-success { + background: #2ecc71; + color: #fff; + border-color: #27ae60; + } + + .die-fail { + background: #ecf0f1; + color: #7f8c8d; + border-color: #bdc3c7; + } + } + + .oh-roll-result { + display: flex; + justify-content: space-between; + align-items: center; + padding: 4px 6px; + border-radius: 3px; + font-weight: bold; + font-size: @font-size-sm; + } + + .roll-success { + background: fade(#2ecc71, 20%); + color: #1e8449; + } + + .roll-failure { + background: fade(#e74c3c, 15%); + color: #c0392b; + } +} + +// Rollable rarity button on item sheets +.oathhammer { + .rarity-roll-btn { + display: inline-flex; + align-items: center; + gap: 3px; + cursor: pointer; + font-size: @font-size-xs; + color: @color-blue; + .transition-opacity(); + + &:hover { + color: @color-dark; + text-decoration: underline; + } + + i { font-size: 0.85em; } + } +} diff --git a/module/applications/_module.mjs b/module/applications/_module.mjs index 7a9c397..c644d01 100644 --- a/module/applications/_module.mjs +++ b/module/applications/_module.mjs @@ -9,6 +9,9 @@ export { default as OathHammerMiracleSheet } from "./sheets/miracle-sheet.mjs" export { default as OathHammerMagicItemSheet } from "./sheets/magic-item-sheet.mjs" export { default as OathHammerTraitSheet } from "./sheets/trait-sheet.mjs" export { default as OathHammerOathSheet } from "./sheets/oath-sheet.mjs" -export { default as OathHammerLineageSheet } from "./sheets/lineage-sheet.mjs" export { default as OathHammerClassSheet } from "./sheets/class-sheet.mjs" export { default as OathHammerBuildingSheet } from "./sheets/building-sheet.mjs" +export { default as OathHammerRollDialog } from "./roll-dialog.mjs" +export { default as OathHammerWeaponDialog } from "./weapon-dialog.mjs" +export { default as OathHammerSpellDialog } from "./spell-dialog.mjs" +export { default as OathHammerMiracleDialog } from "./miracle-dialog.mjs" diff --git a/module/applications/miracle-dialog.mjs b/module/applications/miracle-dialog.mjs new file mode 100644 index 0000000..a7232b2 --- /dev/null +++ b/module/applications/miracle-dialog.mjs @@ -0,0 +1,89 @@ +import { SYSTEM } from "../config/system.mjs" + +export default class OathHammerMiracleDialog { + + static async prompt(actor, miracle) { + const sys = miracle.system + const actorSys = actor.system + + const wpRank = actorSys.attributes.willpower.rank + const magicRank = actorSys.skills.magic.rank + const basePool = wpRank + magicRank + + const isRitual = sys.isRitual + const dv = isRitual ? (sys.difficultyValue || 1) : null + + const traditionLabel = (() => { + const key = SYSTEM.DIVINE_TRADITIONS?.[sys.divineTradition] + return key ? game.i18n.localize(key) : sys.divineTradition + })() + + // Miracle count options — DV = miracle number today + const miracleCountOptions = Array.from({ length: 10 }, (_, i) => ({ + value: i + 1, + label: `${i + 1}${_ordinal(i + 1)} — DV${i + 1}`, + selected: i === 0, + })) + + const bonusOptions = Array.from({ length: 13 }, (_, i) => { + const v = i - 6 + return { value: v, label: v > 0 ? `+${v}` : String(v), selected: v === 0 } + }) + + const rollModes = foundry.utils.duplicate(CONFIG.Dice.rollModes) + + const context = { + actorName: actor.name, + miracleName: miracle.name, + miracleImg: miracle.img, + dv, + traditionLabel, + isRitual, + range: sys.range, + duration: sys.duration, + spellSave: sys.spellSave, + wpRank, + magicRank, + basePool, + miracleCountOptions, + bonusOptions, + rollModes, + visibility: game.settings.get("core", "rollMode"), + } + + const content = await foundry.applications.handlebars.renderTemplate( + "systems/fvtt-oath-hammer/templates/miracle-cast-dialog.hbs", + context + ) + + const result = await foundry.applications.api.DialogV2.wait({ + window: { title: game.i18n.format("OATHHAMMER.Dialog.MiracleCastTitle", { miracle: miracle.name }) }, + classes: ["fvtt-oath-hammer"], + content, + rejectClose: false, + buttons: [{ + label: game.i18n.localize("OATHHAMMER.Dialog.InvokeMiracle"), + callback: (_ev, btn) => Object.fromEntries( + [...btn.form.elements].filter(e => e.name).map(e => [e.name, e.value]) + ), + }], + }) + + if (!result) return null + + const computedDV = isRitual ? dv : (parseInt(result.miracleCount) || 1) + + return { + dv: computedDV, + isRitual, + bonus: parseInt(result.bonus) || 0, + visibility: result.visibility ?? game.settings.get("core", "rollMode"), + } + } +} + +function _ordinal(n) { + const s = ["th", "st", "nd", "rd"] + const v = n % 100 + return s[(v - 20) % 10] ?? s[v] ?? s[0] +} diff --git a/module/applications/roll-dialog.mjs b/module/applications/roll-dialog.mjs new file mode 100644 index 0000000..0d86640 --- /dev/null +++ b/module/applications/roll-dialog.mjs @@ -0,0 +1,154 @@ +import { SYSTEM } from "../config/system.mjs" + +/** + * Roll configuration dialog for Oath Hammer skill checks. + * Uses DialogV2.wait() — pattern from fvtt-hellborn / fvtt-lethal-fantasy. + * + * Dice rules (from rulebook): + * Pool = Attribute rank + Skill rank + per-skill modifier + bonus + (luckSpend × 2) + supporters + * White dice: succeed on 4+ | Red: 3+ | Black: 2+ + * All dice explode on 6 (roll extra die). + * Luck Points: spending 1 LP adds +2 dice; LP restored each session. + * Supporters: each ally with ranks in the skill adds +1 die. + */ +export default class OathHammerRollDialog { + /** + * Dual-attribute skills: show an alternate attribute option in the dialog. + * key → { primary, alt, altLabel i18n key } + */ + static DUAL_ATTRIBUTE_SKILLS = { + defense: { alt: "might", altLabelKey: "OATHHAMMER.Roll.DualAttr.DefenseMelee" }, + fighting: { alt: "agility", altLabelKey: "OATHHAMMER.Roll.DualAttr.FightingNimble" }, + magic: { alt: "intelligence", altLabelKey: "OATHHAMMER.Roll.DualAttr.MagicSpells" }, + } + + /** + * Show a skill check dialog and return the user's choices. + * + * @param {Actor} actor Actor performing the check + * @param {string} skillKey SYSTEM.SKILLS key (e.g. "fortune", "fighting") + * @returns {Promise<{dv, bonus, luckSpend, supporters, attrOverride, visibility}|null>} + * Resolved options, or null if the dialog was cancelled. + */ + static async prompt(actor, skillKey) { + const sys = actor.system + const skillDef = SYSTEM.SKILLS[skillKey] + if (!skillDef) throw new Error(`Unknown skill: ${skillKey}`) + + const defaultAttrKey = skillDef.attribute + const attrRank = sys.attributes[defaultAttrKey].rank + const skill = sys.skills[skillKey] + const skillRank = skill.rank + const skillMod = skill.modifier ?? 0 + const baseTotal = attrRank + skillRank + skillMod + const colorType = skill.colorDiceType ?? "white" + const threshold = colorType === "black" ? 2 : colorType === "red" ? 3 : 4 + const colorLabel = colorType === "black" ? "⬛" : colorType === "red" ? "🔴" : "⬜" + + const dualDef = this.DUAL_ATTRIBUTE_SKILLS[skillKey] + // Build attribute options for dual-attribute skills + let attrOptions = null + if (dualDef) { + const altRank = sys.attributes[dualDef.alt].rank + attrOptions = [ + { + value: defaultAttrKey, + label: `${game.i18n.localize(`OATHHAMMER.Attribute.${defaultAttrKey.charAt(0).toUpperCase()}${defaultAttrKey.slice(1)}`)} (${attrRank}) — default`, + selected: true, + }, + { + value: dualDef.alt, + label: `${game.i18n.localize(`OATHHAMMER.Attribute.${dualDef.alt.charAt(0).toUpperCase()}${dualDef.alt.slice(1)}`)} (${altRank}) — ${game.i18n.localize(dualDef.altLabelKey)}`, + selected: false, + }, + ] + } + + const availableLuck = sys.luck?.value ?? 0 + const rollModes = foundry.utils.duplicate(CONFIG.Dice.rollModes) + + // Build select option arrays + const dvOptions = Array.from({ length: 10 }, (_, i) => { + const v = i + 1 + return { value: v, label: String(v), selected: v === 2 } + }) + + const bonusOptions = Array.from({ length: 13 }, (_, i) => { + const v = i - 6 + return { value: v, label: v > 0 ? `+${v}` : String(v), selected: v === 0 } + }) + + const supportersOptions = Array.from({ length: 7 }, (_, i) => ({ + value: i, label: String(i), selected: i === 0, + })) + + const luckOptions = Array.from({ length: availableLuck + 1 }, (_, i) => ({ + value: i, + label: i === 0 ? `0` : `${i} (+${i * 2}d)`, + selected: i === 0, + })) + + const context = { + actorName: actor.name, + skillKey, + skillLabel: game.i18n.localize(skillDef.label), + attrKey: defaultAttrKey, + attrLabel: game.i18n.localize(`OATHHAMMER.Attribute.${defaultAttrKey.charAt(0).toUpperCase()}${defaultAttrKey.slice(1)}`), + attrRank, + skillRank, + skillMod, + baseTotal, + colorType, + colorLabel, + threshold, + availableLuck, + attrOptions, + isDualAttr: !!dualDef, + rollModes, + visibility: game.settings.get("core", "rollMode"), + dvOptions, + bonusOptions, + supportersOptions, + luckOptions, + } + + const content = await foundry.applications.handlebars.renderTemplate( + "systems/fvtt-oath-hammer/templates/roll-dialog.hbs", + context + ) + + const title = game.i18n.format("OATHHAMMER.Dialog.SkillCheckTitle", { skill: context.skillLabel }) + + const result = await foundry.applications.api.DialogV2.wait({ + window: { title }, + classes: ["fvtt-oath-hammer"], + content, + rejectClose: false, + buttons: [ + { + label: game.i18n.localize("OATHHAMMER.Dialog.Roll"), + callback: (_event, button) => { + const out = {} + for (const el of button.form.elements) { + if (el.name) out[el.name] = el.value + } + return out + }, + }, + ], + }) + + if (!result) return null + + const attrOverride = result.attrOverride || defaultAttrKey + + return { + dv: Math.max(1, parseInt(result.dv) || 2), + bonus: parseInt(result.bonus) || 0, + luckSpend: Math.min(Math.max(0, parseInt(result.luckSpend) || 0), availableLuck), + supporters: Math.max(0, parseInt(result.supporters) || 0), + attrOverride, + visibility: result.visibility ?? game.settings.get("core", "rollMode"), + } + } +} diff --git a/module/applications/sheets/base-actor-sheet.mjs b/module/applications/sheets/base-actor-sheet.mjs index d5187e6..2ffe989 100644 --- a/module/applications/sheets/base-actor-sheet.mjs +++ b/module/applications/sheets/base-actor-sheet.mjs @@ -95,8 +95,8 @@ export default class OathHammerActorSheet extends HandlebarsApplicationMixin(fou async _onDropItem(item) { const itemData = item.toObject() - // Lineage and class are unique: replace any existing item of the same type - if (item.type === "lineage" || item.type === "class") { + // Class is unique: replace any existing item of the same type + if (item.type === "class") { const existing = this.document.itemTypes[item.type] if (existing.length > 0) { await this.document.deleteEmbeddedDocuments("Item", existing.map(i => i.id)) diff --git a/module/applications/sheets/base-item-sheet.mjs b/module/applications/sheets/base-item-sheet.mjs index 603c4e0..0418567 100644 --- a/module/applications/sheets/base-item-sheet.mjs +++ b/module/applications/sheets/base-item-sheet.mjs @@ -1,5 +1,6 @@ const { HandlebarsApplicationMixin } = foundry.applications.api import { ARMOR_TYPE_CHOICES, WEAPON_PROFICIENCY_GROUPS } from "../../config/system.mjs" +import { rollRarityCheck } from "../../rolls.mjs" export default class OathHammerItemSheet extends HandlebarsApplicationMixin(foundry.applications.sheets.ItemSheetV2) { static SHEET_MODES = { EDIT: 0, PLAY: 1 } @@ -28,6 +29,7 @@ export default class OathHammerItemSheet extends HandlebarsApplicationMixin(foun actions: { toggleSheet: OathHammerItemSheet.#onToggleSheet, editImage: OathHammerItemSheet.#onEditImage, + rollRarity: OathHammerItemSheet.#onRollRarity, }, } @@ -134,4 +136,20 @@ export default class OathHammerItemSheet extends HandlebarsApplicationMixin(foun }) return fp.browse() } + + static async #onRollRarity(event, target) { + const rarity = this.document.system.rarity + if (!rarity) return + // Find the owning actor (embedded item) or prompt user to select a character + let actor = this.document.parent + if (!actor || actor.documentName !== "Actor") { + // Item not embedded — use the user's selected character + actor = game.user.character + if (!actor) { + ui.notifications.warn(game.i18n.localize("OATHHAMMER.Roll.NoActor")) + return + } + } + await rollRarityCheck(actor, rarity, this.document.name) + } } diff --git a/module/applications/sheets/character-sheet.mjs b/module/applications/sheets/character-sheet.mjs index ca258ec..38c07f5 100644 --- a/module/applications/sheets/character-sheet.mjs +++ b/module/applications/sheets/character-sheet.mjs @@ -1,5 +1,10 @@ import OathHammerActorSheet from "./base-actor-sheet.mjs" import { SYSTEM } from "../../config/system.mjs" +import OathHammerRollDialog from "../roll-dialog.mjs" +import OathHammerWeaponDialog from "../weapon-dialog.mjs" +import OathHammerSpellDialog from "../spell-dialog.mjs" +import OathHammerMiracleDialog from "../miracle-dialog.mjs" +import { rollSkillCheck, rollWeaponAttack, rollWeaponDamage, rollSpellCast, rollMiracleCast } from "../../rolls.mjs" export default class OathHammerCharacterSheet extends OathHammerActorSheet { /** @override */ @@ -13,10 +18,15 @@ export default class OathHammerCharacterSheet extends OathHammerActorSheet { contentClasses: ["character-content"], }, actions: { - createWeapon: OathHammerCharacterSheet.#onCreateWeapon, - createSpell: OathHammerCharacterSheet.#onCreateSpell, - createMiracle: OathHammerCharacterSheet.#onCreateMiracle, + createWeapon: OathHammerCharacterSheet.#onCreateWeapon, + createSpell: OathHammerCharacterSheet.#onCreateSpell, + createMiracle: OathHammerCharacterSheet.#onCreateMiracle, createEquipment: OathHammerCharacterSheet.#onCreateEquipment, + rollSkill: OathHammerCharacterSheet.#onRollSkill, + attackWeapon: OathHammerCharacterSheet.#onAttackWeapon, + damageWeapon: OathHammerCharacterSheet.#onDamageWeapon, + castSpell: OathHammerCharacterSheet.#onCastSpell, + castMiracle: OathHammerCharacterSheet.#onCastMiracle, }, } @@ -57,9 +67,8 @@ export default class OathHammerCharacterSheet extends OathHammerActorSheet { async _prepareContext() { const context = await super._prepareContext() context.tabs = this.#getTabs() - // lineage/class/experience available to all parts (header + identity tab) + // class/experience available to all parts (header + identity tab) const doc = this.document - context.lineage = doc.itemTypes.lineage?.[0] ?? null context.characterClass = doc.itemTypes["class"]?.[0] ?? null return context } @@ -236,4 +245,52 @@ export default class OathHammerCharacterSheet extends OathHammerActorSheet { static #onCreateEquipment(event, target) { this.document.createEmbeddedDocuments("Item", [{ name: game.i18n.localize("OATHHAMMER.NewItem.Equipment"), type: "equipment" }]) } + + static async #onRollSkill(event, target) { + const skillKey = target.dataset.skill + if (!skillKey) return + const result = await OathHammerRollDialog.prompt(this.document, skillKey) + if (!result) return + await rollSkillCheck(this.document, skillKey, result.dv, result) + } + + static async #onAttackWeapon(event, target) { + const weaponId = target.dataset.itemId + if (!weaponId) return + const weapon = this.document.items.get(weaponId) + if (!weapon) return + const opts = await OathHammerWeaponDialog.promptAttack(this.document, weapon) + if (!opts) return + await rollWeaponAttack(this.document, weapon, opts) + } + + static async #onDamageWeapon(event, target) { + const weaponId = target.dataset.itemId + if (!weaponId) return + const weapon = this.document.items.get(weaponId) + if (!weapon) return + const opts = await OathHammerWeaponDialog.promptDamage(this.document, weapon, 0) + if (!opts) return + await rollWeaponDamage(this.document, weapon, opts) + } + + static async #onCastSpell(event, target) { + const spellId = target.dataset.itemId + if (!spellId) return + const spell = this.document.items.get(spellId) + if (!spell) return + const opts = await OathHammerSpellDialog.prompt(this.document, spell) + if (!opts) return + await rollSpellCast(this.document, spell, opts) + } + + static async #onCastMiracle(event, target) { + const miracleId = target.dataset.itemId + if (!miracleId) return + const miracle = this.document.items.get(miracleId) + if (!miracle) return + const opts = await OathHammerMiracleDialog.prompt(this.document, miracle) + if (!opts) return + await rollMiracleCast(this.document, miracle, opts) + } } diff --git a/module/applications/sheets/lineage-sheet.mjs b/module/applications/sheets/lineage-sheet.mjs deleted file mode 100644 index a17043c..0000000 --- a/module/applications/sheets/lineage-sheet.mjs +++ /dev/null @@ -1,30 +0,0 @@ -import OathHammerItemSheet from "./base-item-sheet.mjs" - -export default class OathHammerLineageSheet extends OathHammerItemSheet { - /** @override */ - static DEFAULT_OPTIONS = { - classes: ["lineage"], - position: { - width: 640, - }, - window: { - contentClasses: ["lineage-content"], - }, - } - - /** @override */ - static PARTS = { - main: { - template: "systems/fvtt-oath-hammer/templates/item/lineage-sheet.hbs", - }, - } - - /** @override */ - async _prepareContext() { - const context = await super._prepareContext() - context.enrichedTraits = await foundry.applications.ux.TextEditor.implementation.enrichHTML( - this.document.system.traits ?? "", { async: true } - ) - return context - } -} diff --git a/module/applications/spell-dialog.mjs b/module/applications/spell-dialog.mjs new file mode 100644 index 0000000..de6ca3f --- /dev/null +++ b/module/applications/spell-dialog.mjs @@ -0,0 +1,118 @@ +import { SYSTEM } from "../config/system.mjs" + +/** + * Spell enhancements — applied before casting (p.96-97). + * stress: Arcane Stress cost regardless of roll result + * penalty: dice pool penalty + * redDice: true = roll red dice (3+ threshold) on the check + * noStress: true = 1s rolled do NOT add Arcane Stress (Safe Spell) + */ +export const SPELL_ENHANCEMENTS = { + none: { label: "OATHHAMMER.Enhancement.None", stress: 0, penalty: 0, redDice: false, noStress: false }, + focused: { label: "OATHHAMMER.Enhancement.Focused", stress: 1, penalty: 0, redDice: true, noStress: false }, + controlled: { label: "OATHHAMMER.Enhancement.Controlled", stress: 1, penalty: 0, redDice: false, noStress: false }, + empowered: { label: "OATHHAMMER.Enhancement.Empowered", stress: 2, penalty: 0, redDice: false, noStress: false }, + extended: { label: "OATHHAMMER.Enhancement.Extended", stress: 1, penalty: -1, redDice: false, noStress: false }, + penetrating: { label: "OATHHAMMER.Enhancement.Penetrating", stress: 1, penalty: -1, redDice: false, noStress: false }, + lethal: { label: "OATHHAMMER.Enhancement.Lethal", stress: 2, penalty: -2, redDice: false, noStress: false }, + hastened: { label: "OATHHAMMER.Enhancement.Hastened", stress: 3, penalty: -3, redDice: false, noStress: false }, + safe: { label: "OATHHAMMER.Enhancement.Safe", stress: 0, penalty: -3, redDice: false, noStress: true }, +} + +export default class OathHammerSpellDialog { + + static async prompt(actor, spell) { + const sys = spell.system + const actorSys = actor.system + + const intRank = actorSys.attributes.intelligence.rank + const magicRank = actorSys.skills.magic.rank + const basePool = intRank + magicRank + + const currentStress = actorSys.arcaneStress.value + const stressThreshold = actorSys.arcaneStress.threshold + const isOverThreshold = currentStress >= stressThreshold + + const isElemental = sys.tradition === "elemental" + const dv = sys.difficultyValue + + const traditionLabel = (() => { + const entry = SYSTEM.SORCEROUS_TRADITIONS?.[sys.tradition] + return entry ? game.i18n.localize(entry.label) : (sys.tradition ?? "") + })() + + const enhancementOptions = Object.entries(SPELL_ENHANCEMENTS).map(([key, def]) => ({ + value: key, + label: game.i18n.localize(def.label), + selected: key === "none", + })) + + const bonusOptions = Array.from({ length: 13 }, (_, i) => { + const v = i - 6 + return { value: v, label: v > 0 ? `+${v}` : String(v), selected: v === 0 } + }) + + const rollModes = foundry.utils.duplicate(CONFIG.Dice.rollModes) + + const context = { + actorName: actor.name, + spellName: spell.name, + spellImg: spell.img, + dv, + traditionLabel, + isRitual: sys.isRitual, + isMagicMissile: sys.isMagicMissile, + range: sys.range, + duration: sys.duration, + spellSave: sys.spellSave, + isElemental, + element: sys.element, + intRank, + magicRank, + basePool, + currentStress, + stressThreshold, + isOverThreshold, + enhancementOptions, + bonusOptions, + rollModes, + visibility: game.settings.get("core", "rollMode"), + } + + const content = await foundry.applications.handlebars.renderTemplate( + "systems/fvtt-oath-hammer/templates/spell-cast-dialog.hbs", + context + ) + + const result = await foundry.applications.api.DialogV2.wait({ + window: { title: game.i18n.format("OATHHAMMER.Dialog.SpellCastTitle", { spell: spell.name }) }, + classes: ["fvtt-oath-hammer"], + content, + rejectClose: false, + buttons: [{ + label: game.i18n.localize("OATHHAMMER.Dialog.CastSpell"), + callback: (_ev, btn) => Object.fromEntries( + [...btn.form.elements].filter(e => e.name).map(e => [e.name, e.value]) + ), + }], + }) + + if (!result) return null + + const enhKey = result.enhancement ?? "none" + const enh = SPELL_ENHANCEMENTS[enhKey] ?? SPELL_ENHANCEMENTS.none + + return { + dv, + enhancement: enhKey, + stressCost: enh.stress, + poolPenalty: enh.penalty, + redDice: enh.redDice, + noStress: enh.noStress, + elementalBonus: parseInt(result.elementalBonus) || 0, + bonus: parseInt(result.bonus) || 0, + grimPenalty: parseInt(result.noGrimoire) || 0, + visibility: result.visibility ?? game.settings.get("core", "rollMode"), + } + } +} diff --git a/module/applications/weapon-dialog.mjs b/module/applications/weapon-dialog.mjs new file mode 100644 index 0000000..cbac4e3 --- /dev/null +++ b/module/applications/weapon-dialog.mjs @@ -0,0 +1,215 @@ +import { SYSTEM } from "../config/system.mjs" + +/** + * Roll dialogs for weapon attacks and damage. + * + * Attack flow: + * 1. promptAttack(actor, weapon) → options + * 2. rollWeaponAttack posts a chat card with a "Roll Damage" button + * 3. Clicking the button calls promptDamage with attackSuccesses pre-filled + * 4. rollWeaponDamage posts the damage chat card + */ +export default class OathHammerWeaponDialog { + + // ------------------------------------------------------------------ // + // ATTACK DIALOG + // ------------------------------------------------------------------ // + + static async promptAttack(actor, weapon) { + const sys = weapon.system + const actorSys = actor.system + + const isRanged = !sys.usesMight && (sys.shortRange > 0 || sys.longRange > 0) + const skillKey = isRanged ? "shooting" : "fighting" + const skillDef = SYSTEM.SKILLS[skillKey] + const defaultAttr = skillDef.attribute + const attrRank = actorSys.attributes[defaultAttr].rank + const skillRank = actorSys.skills[skillKey].rank + const skillColor = actorSys.skills[skillKey].colorDiceType ?? "white" + const threshold = skillColor === "black" ? 2 : skillColor === "red" ? 3 : 4 + + const hasNimble = sys.traits.has("nimble") + + // Auto-bonuses from special properties + let autoAttackBonus = 0 + if (sys.specialProperties.has("master-crafted")) autoAttackBonus += 1 + if (sys.specialProperties.has("accurate")) autoAttackBonus += 1 // bows + if (sys.specialProperties.has("balanced")) autoAttackBonus += 1 // grants Fast + + // Damage info for reference + const hasBrutal = sys.traits.has("brutal") + const hasDeadly = sys.traits.has("deadly") + const damageColorType = hasDeadly ? "black" : hasBrutal ? "red" : "white" + const damageThreshold = hasDeadly ? 2 : hasBrutal ? 3 : 4 + const damageColorLabel = hasDeadly ? "⬛" : hasBrutal ? "🔴" : "⬜" + const mightRank = actorSys.attributes.might.rank + const baseDamageDice = sys.usesMight ? Math.max(mightRank + sys.damageMod, 1) : Math.max(sys.damageMod, 1) + + const traitLabels = [...sys.traits].map(t => { + const key = SYSTEM.WEAPON_TRAITS[t] + return key ? game.i18n.localize(key) : t + }) + + // Option arrays + const attackBonusOptions = Array.from({ length: 13 }, (_, i) => { + const v = i - 6 + return { value: v, label: v > 0 ? `+${v}` : String(v), selected: v === 0 } + }) + + const rangeOptions = [ + { value: 0, label: game.i18n.localize("OATHHAMMER.Dialog.RangeNormal") }, + { value: -1, label: game.i18n.localize("OATHHAMMER.Dialog.RangeLong") + " (−1)" }, + { value: -2, label: game.i18n.localize("OATHHAMMER.Dialog.RangeMoving") + " (−2)" }, + { value: -2, label: game.i18n.localize("OATHHAMMER.Dialog.RangeConcealment") + " (−2)" }, + { value: -3, label: game.i18n.localize("OATHHAMMER.Dialog.RangeCover") + " (−3)" }, + ] + + const rollModes = foundry.utils.duplicate(CONFIG.Dice.rollModes) + + const context = { + actorName: actor.name, + weaponName: weapon.name, + weaponImg: weapon.img, + skillKey, + skillLabel: game.i18n.localize(skillDef.label), + attrKey: defaultAttr, + attrLabel: game.i18n.localize(`OATHHAMMER.Attribute.${_cap(defaultAttr)}`), + attrRank, + skillRank, + colorType: skillColor, + threshold, + baseAttackPool: attrRank + skillRank, + autoAttackBonus, + hasNimble, + mightLabel: game.i18n.localize("OATHHAMMER.Attribute.Might"), + mightRank, + agilityLabel: game.i18n.localize("OATHHAMMER.Attribute.Agility"), + agilityRank: actorSys.attributes.agility.rank, + isRanged, + shortRange: sys.shortRange, + longRange: sys.longRange, + damageLabel: sys.damageLabel, + damageColorType, + damageThreshold, + damageColorLabel, + baseDamageDice, + apValue: sys.ap, + traits: traitLabels, + attackBonusOptions, + rangeOptions, + rollModes, + visibility: game.settings.get("core", "rollMode"), + } + + const content = await foundry.applications.handlebars.renderTemplate( + "systems/fvtt-oath-hammer/templates/weapon-attack-dialog.hbs", + context + ) + + const result = await foundry.applications.api.DialogV2.wait({ + window: { title: game.i18n.format("OATHHAMMER.Dialog.AttackTitle", { weapon: weapon.name }) }, + classes: ["fvtt-oath-hammer"], + content, + rejectClose: false, + buttons: [{ + label: game.i18n.localize("OATHHAMMER.Dialog.RollAttack"), + callback: (_ev, btn) => Object.fromEntries( + [...btn.form.elements].filter(e => e.name).map(e => [e.name, e.value]) + ), + }], + }) + + if (!result) return null + return { + attackBonus: parseInt(result.attackBonus) || 0, + rangeCondition: parseInt(result.rangeCondition) || 0, + attrOverride: result.attrOverride || defaultAttr, + visibility: result.visibility ?? game.settings.get("core", "rollMode"), + autoAttackBonus, + } + } + + // ------------------------------------------------------------------ // + // DAMAGE DIALOG + // ------------------------------------------------------------------ // + + static async promptDamage(actor, weapon, defaultSV = 0) { + const sys = weapon.system + const actorSys = actor.system + + const hasBrutal = sys.traits.has("brutal") + const hasDeadly = sys.traits.has("deadly") + const damageColorType = hasDeadly ? "black" : hasBrutal ? "red" : "white" + const damageThreshold = hasDeadly ? 2 : hasBrutal ? 3 : 4 + const damageColorLabel = hasDeadly ? "⬛" : hasBrutal ? "🔴" : "⬜" + const mightRank = actorSys.attributes.might.rank + const baseDamageDice = sys.usesMight ? Math.max(mightRank + sys.damageMod, 1) : Math.max(sys.damageMod, 1) + + // Auto-bonuses from special properties + let autoDamageBonus = 0 + if (sys.specialProperties.has("master-crafted")) autoDamageBonus += 1 + if (sys.specialProperties.has("tempered")) autoDamageBonus += 1 + if (sys.specialProperties.has("heavy-draw")) autoDamageBonus += 1 + + const svOptions = Array.from({ length: 11 }, (_, i) => ({ + value: i, + label: i === 0 ? "0" : `+${i}d`, + selected: i === defaultSV, + })) + + const damageBonusOptions = Array.from({ length: 9 }, (_, i) => { + const v = i - 4 + return { value: v, label: v > 0 ? `+${v}` : String(v), selected: v === 0 } + }) + + const rollModes = foundry.utils.duplicate(CONFIG.Dice.rollModes) + + const context = { + actorName: actor.name, + weaponName: weapon.name, + weaponImg: weapon.img, + damageLabel: sys.damageLabel, + damageColorType, + damageThreshold, + damageColorLabel, + baseDamageDice, + autoDamageBonus, + apValue: sys.ap, + defaultSV, + svOptions, + damageBonusOptions, + rollModes, + visibility: game.settings.get("core", "rollMode"), + } + + const content = await foundry.applications.handlebars.renderTemplate( + "systems/fvtt-oath-hammer/templates/weapon-damage-dialog.hbs", + context + ) + + const result = await foundry.applications.api.DialogV2.wait({ + window: { title: game.i18n.format("OATHHAMMER.Dialog.DamageTitle", { weapon: weapon.name }) }, + classes: ["fvtt-oath-hammer"], + content, + rejectClose: false, + buttons: [{ + label: game.i18n.localize("OATHHAMMER.Dialog.RollDamage"), + callback: (_ev, btn) => Object.fromEntries( + [...btn.form.elements].filter(e => e.name).map(e => [e.name, e.value]) + ), + }], + }) + + if (!result) return null + return { + sv: parseInt(result.sv) || 0, + damageBonus: parseInt(result.damageBonus) || 0, + visibility: result.visibility ?? game.settings.get("core", "rollMode"), + autoDamageBonus, + } + } +} + +function _cap(str) { + return str.charAt(0).toUpperCase() + str.slice(1) +} diff --git a/module/config/system.mjs b/module/config/system.mjs index ea6c625..18f95f8 100644 --- a/module/config/system.mjs +++ b/module/config/system.mjs @@ -108,6 +108,20 @@ export const WEAPON_TRAITS = { versatile: "OATHHAMMER.WeaponTrait.Versatile" } +// Special Properties that can be added to weapons via crafting (p.98) +export const WEAPON_SPECIAL_PROPERTIES = { + accurate: "OATHHAMMER.WeaponProperty.Accurate", + "adv-mechanism":"OATHHAMMER.WeaponProperty.AdvMechanism", + "armor-bane": "OATHHAMMER.WeaponProperty.ArmorBane", + balanced: "OATHHAMMER.WeaponProperty.Balanced", + "heavy-draw": "OATHHAMMER.WeaponProperty.HeavyDraw", + "master-crafted":"OATHHAMMER.WeaponProperty.MasterCrafted", + ornate: "OATHHAMMER.WeaponProperty.Ornate", + refined: "OATHHAMMER.WeaponProperty.Refined", + "rune-etched": "OATHHAMMER.WeaponProperty.RuneEtched", + tempered: "OATHHAMMER.WeaponProperty.Tempered", +} + export const CURRENCY_CHOICES = { gp: "OATHHAMMER.Currency.GP", sp: "OATHHAMMER.Currency.SP", @@ -172,6 +186,16 @@ export const RARITY_CHOICES = { legendary: "OATHHAMMER.Rarity.Legendary" } +// Rarity key → Difficulty Value for Fortune rolls +export const RARITY_DV = { + always: 0, + common: 1, + uncommon: 2, + rare: 3, + "very-rare": 4, + legendary: 5 +} + // Two types of trait per the rulebook terminology export const TRAIT_TYPE_CHOICES = { "special-trait": "OATHHAMMER.TraitType.SpecialTrait", @@ -350,6 +374,7 @@ export const SYSTEM = { RUNE_TYPE_CHOICES, WEAPON_PROFICIENCY_GROUPS, WEAPON_TRAITS, + WEAPON_SPECIAL_PROPERTIES, ARMOR_TYPE_CHOICES, ARMOR_TRAITS, CURRENCY_CHOICES, @@ -358,6 +383,7 @@ export const SYSTEM = { MAGIC_ITEM_TYPE_CHOICES, MAGIC_QUALITY_CHOICES, RARITY_CHOICES, + RARITY_DV, TRAIT_TYPE_CHOICES, TRAIT_USAGE_PERIOD, BUILDING_SKILL_CHOICES, diff --git a/module/models/_module.mjs b/module/models/_module.mjs index 8f8f91e..e5f1e1a 100644 --- a/module/models/_module.mjs +++ b/module/models/_module.mjs @@ -9,6 +9,5 @@ export { default as OathHammerMiracle } from "./miracle.mjs" export { default as OathHammerMagicItem } from "./magic-item.mjs" export { default as OathHammerTrait } from "./trait.mjs" export { default as OathHammerOath } from "./oath.mjs" -export { default as OathHammerLineage } from "./lineage.mjs" export { default as OathHammerClass } from "./class.mjs" export { default as OathHammerBuilding } from "./building.mjs" diff --git a/module/models/armor.mjs b/module/models/armor.mjs index b89c3bc..4799343 100644 --- a/module/models/armor.mjs +++ b/module/models/armor.mjs @@ -12,7 +12,7 @@ export default class OathHammerArmor extends foundry.abstract.TypeDataModel { schema.armorType = new fields.StringField({ required: true, initial: "light", choices: SYSTEM.ARMOR_TYPE_CHOICES }) // Armor Value (AV): number of armor dice rolled when receiving damage - schema.armorValue = new fields.NumberField({ ...requiredInteger, initial: 0, min: 0, max: 12 }) + schema.armorValue = new fields.NumberField({ ...requiredInteger, initial: 0, min: 0, max: 16 }) // Penalty: modifier to Acrobatics checks AND defense rolls (0, -1, -2, -3…) schema.penalty = new fields.NumberField({ ...requiredInteger, initial: 0, min: -5, max: 0 }) diff --git a/module/models/building.mjs b/module/models/building.mjs index 7de4104..84e6142 100644 --- a/module/models/building.mjs +++ b/module/models/building.mjs @@ -23,11 +23,10 @@ export default class OathHammerBuilding extends foundry.abstract.TypeDataModel { // Monthly tax revenue formula ("3d6", "2d6", "1d3", "" = none) schema.taxRevenue = new fields.StringField({ required: true, nullable: false, initial: "" }) - // Is this building currently constructed in the settlement? + // Is this building currently constructed? schema.constructed = new fields.BooleanField({ required: true, initial: false }) - // Which settlement this building belongs to (free text or settlement name) - schema.settlement = new fields.StringField({ required: true, nullable: false, initial: "" }) + // Additional GM notes (special conditions, upgrades, damage, etc.) schema.notes = new fields.HTMLField({ required: false, textSearch: true }) diff --git a/module/models/character.mjs b/module/models/character.mjs index 3ce6368..323c08f 100644 --- a/module/models/character.mjs +++ b/module/models/character.mjs @@ -9,6 +9,12 @@ export default class OathHammerCharacter extends foundry.abstract.TypeDataModel schema.description = new fields.HTMLField({ required: true, textSearch: true }) schema.notes = new fields.HTMLField({ required: true, textSearch: true }) + // Lineage (simple fields on the actor — not an Item) + schema.lineage = new fields.SchemaField({ + name: new fields.StringField({ required: true, nullable: false, initial: "" }), + traits: new fields.HTMLField({ required: true, textSearch: true }), + }) + const attributeField = () => new fields.SchemaField({ rank: new fields.NumberField({ ...requiredInteger, initial: 1, min: 1, max: 4 }) }) diff --git a/module/models/lineage.mjs b/module/models/lineage.mjs deleted file mode 100644 index ecc5002..0000000 --- a/module/models/lineage.mjs +++ /dev/null @@ -1,21 +0,0 @@ -export default class OathHammerLineage extends foundry.abstract.TypeDataModel { - static defineSchema() { - const fields = foundry.data.fields - const schema = {} - - schema.description = new fields.HTMLField({ required: true, textSearch: true }) - - // Racial traits and special abilities (rich text) - schema.traits = new fields.HTMLField({ required: true, textSearch: true }) - - // Base movement speed in feet - schema.movement = new fields.NumberField({ required: true, nullable: false, integer: true, initial: 30, min: 0 }) - - // Modifier to max Grit Points (e.g. -1 for High Elf, Wood Elf) - schema.gritModifier = new fields.NumberField({ required: true, nullable: false, integer: true, initial: 0 }) - - return schema - } - - static LOCALIZATION_PREFIXES = ["OATHHAMMER.Lineage"] -} diff --git a/module/models/magic-item.mjs b/module/models/magic-item.mjs index 2b82590..1696fdd 100644 --- a/module/models/magic-item.mjs +++ b/module/models/magic-item.mjs @@ -35,8 +35,7 @@ export default class OathHammerMagicItem extends foundry.abstract.TypeDataModel }) schema.maxUses = new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }) - // Item slots occupied when carried; 0 = small item (no slots) - schema.slots = new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }) + schema.equipped = new fields.BooleanField({ initial: false }) diff --git a/module/models/weapon.mjs b/module/models/weapon.mjs index 053dd58..ffed064 100644 --- a/module/models/weapon.mjs +++ b/module/models/weapon.mjs @@ -17,10 +17,10 @@ export default class OathHammerWeapon extends foundry.abstract.TypeDataModel { // usesMight=true → formula displayed as "M+2", "M-1", etc. // usesMight=false → formula displayed as e.g. "6" (fixed dice for bows) schema.usesMight = new fields.BooleanField({ required: true, initial: true }) - schema.damageMod = new fields.NumberField({ ...requiredInteger, initial: 0, min: -4, max: 5 }) + schema.damageMod = new fields.NumberField({ ...requiredInteger, initial: 0, min: -4, max: 16 }) // AP (Armor Penetration): penalty imposed on armor/defense rolls - schema.ap = new fields.NumberField({ ...requiredInteger, initial: 0, min: 0, max: 6 }) + schema.ap = new fields.NumberField({ ...requiredInteger, initial: 0, min: 0, max: 16 }) // Reach (melee, in ft: 5 / 10 / 15) — ignored for ranged/throwing schema.reach = new fields.NumberField({ ...requiredInteger, initial: 5, min: 5 }) @@ -35,6 +35,12 @@ export default class OathHammerWeapon extends foundry.abstract.TypeDataModel { { required: true, initial: [] } ) + // Special Properties — crafting enhancements (Accurate, Master-Crafted, etc. p.98) + schema.specialProperties = new fields.SetField( + new fields.StringField({ choices: SYSTEM.WEAPON_SPECIAL_PROPERTIES }), + { required: true, initial: [] } + ) + // Item slots (when stowed; 0 = does not occupy slots) schema.slots = new fields.NumberField({ ...requiredInteger, initial: 1, min: 0 }) diff --git a/module/rolls.mjs b/module/rolls.mjs new file mode 100644 index 0000000..37a9916 --- /dev/null +++ b/module/rolls.mjs @@ -0,0 +1,555 @@ +import { SYSTEM } from "./config/system.mjs" + +/** + * Perform an Oath Hammer skill check and post results to chat. + * + * Dice rules (p.38-40): + * - Pool = Attribute rank + Skill rank + per-skill modifier + bonus + (luckSpend × 2) + supporters + * - White dice succeed on 4+ | Red: 3+ | Black: 2+ + * - All dice explode on 6 (roll extra die, continues while rolling 6s) + * - Pool can never drop below 1 die (penalties rule) + * - Luck Points: 1 LP spent = +2 dice; LP are deducted from actor.system.luck.value + * - Supporters: each ally with ranks in the skill adds +1 die + * + * @param {Actor} actor The actor performing the check + * @param {string} skillKey Skill key (e.g. "fortune") + * @param {number} dv Difficulty Value (successes required) + * @param {object} [options] + * @param {number} [options.bonus] Extra dice from dialog modifier (can be negative) + * @param {number} [options.luckSpend] Luck Points to spend (each adds +2 dice) + * @param {number} [options.supporters] Allies supporting the check (each adds +1 die) + * @param {string} [options.attrOverride] Override governing attribute (for dual-attr skills) + * @param {string} [options.visibility] Roll mode (public/gmroll/blindroll/selfroll) + * @param {string} [options.flavor] Optional flavor text for the chat card + * @returns {Promise<{successes: number, dv: number, isSuccess: boolean}>} + */ +export async function rollSkillCheck(actor, skillKey, dv, options = {}) { + const { bonus = 0, luckSpend = 0, supporters = 0, attrOverride, visibility, flavor } = options + + const sys = actor.system + const skillDef = SYSTEM.SKILLS[skillKey] + if (!skillDef) throw new Error(`Unknown skill: ${skillKey}`) + + // Attribute — use override if provided (dual-attribute skills: Defense, Fighting, Magic) + const attrKey = attrOverride && sys.attributes[attrOverride] ? attrOverride : skillDef.attribute + const attrRank = sys.attributes[attrKey].rank + + const skill = sys.skills[skillKey] + const skillRank = skill.rank + const skillMod = skill.modifier ?? 0 + const colorType = skill.colorDiceType ?? "white" + const threshold = colorType === "black" ? 2 : colorType === "red" ? 3 : 4 + + // Total dice pool (never below 1) + const totalDice = Math.max(attrRank + skillRank + skillMod + bonus + (luckSpend * 2) + supporters, 1) + + // Deduct spent Luck Points from actor + if (luckSpend > 0) { + const currentLuck = sys.luck?.value ?? 0 + await actor.update({ "system.luck.value": Math.max(0, currentLuck - luckSpend) }) + } + + // Roll the dice pool + const roll = await new Roll(`${totalDice}d6`).evaluate() + + // Count successes — exploding 6s produce additional dice + let successes = 0 + const diceResults = [] + let extraDice = 0 + + for (const r of roll.dice[0].results) { + const val = r.result + if (val >= threshold) successes++ + if (val === 6) extraDice++ + diceResults.push({ val, exploded: false }) + } + + while (extraDice > 0) { + const xRoll = await new Roll(`${extraDice}d6`).evaluate() + extraDice = 0 + for (const r of xRoll.dice[0].results) { + const val = r.result + if (val >= threshold) successes++ + if (val === 6) extraDice++ + diceResults.push({ val, exploded: true }) + } + } + + const isSuccess = successes >= dv + const colorEmoji = colorType === "black" ? "⬛" : colorType === "red" ? "🔴" : "⬜" + const skillLabel = game.i18n.localize(skillDef.label) + const attrLabel = game.i18n.localize(`OATHHAMMER.Attribute.${attrKey.charAt(0).toUpperCase()}${attrKey.slice(1)}`) + + // Build dice display HTML + const diceHtml = diceResults.map(({ val, exploded }) => { + const success = val >= threshold + const cssClass = success ? "die-success" : "die-fail" + const explodedClass = exploded ? " die-exploded" : "" + return `${val}` + }).join(" ") + + // Build modifier summary + const modParts = [] + if (bonus !== 0) modParts.push(`${bonus > 0 ? "+" : ""}${bonus} ${game.i18n.localize("OATHHAMMER.Dialog.Modifier")}`) + if (luckSpend > 0) modParts.push(`+${luckSpend * 2} ${game.i18n.localize("OATHHAMMER.Dialog.LuckSpend")} (${luckSpend}LP)`) + if (supporters > 0) modParts.push(`+${supporters} ${game.i18n.localize("OATHHAMMER.Dialog.Supporters")}`) + const modLine = modParts.length ? `
${modParts.join(" · ")}
` : "" + + const resultClass = isSuccess ? "roll-success" : "roll-failure" + const resultLabel = isSuccess + ? game.i18n.localize("OATHHAMMER.Roll.Success") + : game.i18n.localize("OATHHAMMER.Roll.Failure") + + const cardFlavor = flavor ?? `${skillLabel} ${game.i18n.localize("OATHHAMMER.Roll.Check")} (DV ${dv})` + + const content = ` +
+
${cardFlavor}
+
+ ${attrLabel} ${attrRank} + ${skillLabel} ${skillRank} + ${colorEmoji} ${totalDice}d6 (${threshold}+) +
+ ${modLine} +
${diceHtml}
+
+ ${successes} / ${dv} + ${resultLabel} +
+
+ ` + + const rollMode = visibility ?? game.settings.get("core", "rollMode") + const msgData = { + speaker: ChatMessage.getSpeaker({ actor }), + content, + rolls: [roll], + sound: CONFIG.sounds.dice, + } + ChatMessage.applyRollMode(msgData, rollMode) + await ChatMessage.create(msgData) + + return { successes, dv, isSuccess } +} + +/** + * Roll a Fortune check to find an item of a given rarity. + * Used by the rollable rarity button on item sheets. + * @param {Actor} actor The actor making the check + * @param {string} rarityKey Rarity key (e.g. "rare", "very-rare") + * @param {string} [itemName] Optional item name for flavor text + */ +export async function rollRarityCheck(actor, rarityKey, itemName) { + const dv = SYSTEM.RARITY_DV[rarityKey] ?? 1 + if (rarityKey === "always") { + const rarityLabel = game.i18n.localize(SYSTEM.RARITY_CHOICES[rarityKey]) + const content = ` +
+
${itemName ?? game.i18n.localize("OATHHAMMER.Label.Rarity")}
+
+ ${rarityLabel} — ${game.i18n.localize("OATHHAMMER.Roll.AutoSuccess")} +
+
+ ` + await ChatMessage.create({ speaker: ChatMessage.getSpeaker({ actor }), content }) + return { successes: 0, dv: 0, isSuccess: true } + } + + const rarityLabel = game.i18n.localize(SYSTEM.RARITY_CHOICES[rarityKey]) + const flavor = `${game.i18n.localize("OATHHAMMER.Skill.Fortune")} — ${itemName ?? rarityLabel} (DV ${dv})` + return rollSkillCheck(actor, "fortune", dv, { flavor }) +} + +// ============================================================ +// SHARED DICE HELPER +// ============================================================ + +/** + * Roll a pool of dice, counting successes (including exploding 6s). + * @param {number} pool Number of dice to roll + * @param {number} threshold Minimum value to count as a success + * @returns {Promise<{roll: Roll, successes: number, diceResults: Array}>} + */ +async function _rollPool(pool, threshold) { + const roll = await new Roll(`${Math.max(pool, 1)}d6`).evaluate() + let successes = 0 + const diceResults = [] + let extraDice = 0 + + for (const r of roll.dice[0].results) { + const val = r.result + if (val >= threshold) successes++ + if (val === 6) extraDice++ + diceResults.push({ val, exploded: false }) + } + + while (extraDice > 0) { + const xRoll = await new Roll(`${extraDice}d6`).evaluate() + extraDice = 0 + for (const r of xRoll.dice[0].results) { + const val = r.result + if (val >= threshold) successes++ + if (val === 6) extraDice++ + diceResults.push({ val, exploded: true }) + } + } + + return { roll, successes, diceResults } +} + +/** + * Render dice results as HTML spans. + */ +function _diceHtml(diceResults, threshold) { + return diceResults.map(({ val, exploded }) => { + const cssClass = val >= threshold ? "die-success" : "die-fail" + return `${val}` + }).join(" ") +} + +// ============================================================ +// WEAPON ATTACK ROLL +// ============================================================ + +/** + * Roll a weapon attack and post the result to chat. + * The chat card includes a "Roll Damage" button that triggers rollWeaponDamage. + * + * @param {Actor} actor The attacking actor + * @param {Item} weapon The weapon item + * @param {object} options From OathHammerWeaponDialog.promptAttack() + */ +export async function rollWeaponAttack(actor, weapon, options = {}) { + const { attackBonus = 0, rangeCondition = 0, attrOverride, visibility, autoAttackBonus = 0 } = options + + const sys = weapon.system + const actorSys = actor.system + + const isRanged = !sys.usesMight && (sys.shortRange > 0 || sys.longRange > 0) + const skillKey = isRanged ? "shooting" : "fighting" + const skillDef = SYSTEM.SKILLS[skillKey] + const defaultAttr = skillDef.attribute + + const attrKey = attrOverride && actorSys.attributes[attrOverride] ? attrOverride : defaultAttr + const attrRank = actorSys.attributes[attrKey].rank + const skillRank = actorSys.skills[skillKey].rank + const colorType = actorSys.skills[skillKey].colorDiceType ?? "white" + const threshold = colorType === "black" ? 2 : colorType === "red" ? 3 : 4 + const colorEmoji = colorType === "black" ? "⬛" : colorType === "red" ? "🔴" : "⬜" + + const totalDice = Math.max(attrRank + skillRank + attackBonus + rangeCondition + autoAttackBonus, 1) + + const { roll, successes, diceResults } = await _rollPool(totalDice, threshold) + + const skillLabel = game.i18n.localize(skillDef.label) + const attrLabel = game.i18n.localize(`OATHHAMMER.Attribute.${attrKey.charAt(0).toUpperCase() + attrKey.slice(1)}`) + const diceHtml = _diceHtml(diceResults, threshold) + + // Modifier summary + const modParts = [] + if (attackBonus !== 0) modParts.push(`${attackBonus > 0 ? "+" : ""}${attackBonus} ${game.i18n.localize("OATHHAMMER.Dialog.AttackModifier")}`) + if (rangeCondition !== 0) modParts.push(`${rangeCondition} ${game.i18n.localize("OATHHAMMER.Dialog.RangeCondition")}`) + if (autoAttackBonus > 0) modParts.push(`+${autoAttackBonus} auto`) + const modLine = modParts.length ? `
${modParts.join(" · ")}
` : "" + + const content = ` +
+
+ ${weapon.name} + ${weapon.name} — ${game.i18n.localize("OATHHAMMER.Dialog.Attack")} +
+
+ ${skillLabel} (${attrLabel} ${attrRank}) + ${skillLabel} ${skillRank} + ${colorEmoji} ${totalDice}d6 (${threshold}+) +
+ ${modLine} +
${diceHtml}
+
+ ${successes} ${game.i18n.localize("OATHHAMMER.Roll.Successes")} +
+
+ +
+
+ ` + + const rollMode = visibility ?? game.settings.get("core", "rollMode") + const flagData = { actorUuid: actor.uuid, weaponUuid: weapon.uuid, attackSuccesses: successes } + const msgData = { + speaker: ChatMessage.getSpeaker({ actor }), + content, + rolls: [roll], + sound: CONFIG.sounds.dice, + flags: { "fvtt-oath-hammer": { weaponAttack: flagData } }, + } + ChatMessage.applyRollMode(msgData, rollMode) + await ChatMessage.create(msgData) + + return { successes } +} + +// ============================================================ +// WEAPON DAMAGE ROLL +// ============================================================ + +/** + * Roll weapon damage and post to chat. + * + * @param {Actor} actor The attacking actor + * @param {Item} weapon The weapon item + * @param {object} options From OathHammerWeaponDialog.promptDamage() + */ +export async function rollWeaponDamage(actor, weapon, options = {}) { + const { sv = 0, damageBonus = 0, visibility, autoDamageBonus = 0 } = options + + const sys = weapon.system + const actorSys = actor.system + + const hasBrutal = sys.traits.has("brutal") + const hasDeadly = sys.traits.has("deadly") + const colorType = hasDeadly ? "black" : hasBrutal ? "red" : "white" + const threshold = hasDeadly ? 2 : hasBrutal ? 3 : 4 + const colorEmoji = hasDeadly ? "⬛" : hasBrutal ? "🔴" : "⬜" + const colorLabel = hasDeadly ? "Black" : hasBrutal ? "Red" : "White" + + const mightRank = actorSys.attributes.might.rank + const baseDamageDice = sys.usesMight ? Math.max(mightRank + sys.damageMod, 1) : Math.max(sys.damageMod, 1) + const totalDice = Math.max(baseDamageDice + sv + damageBonus + autoDamageBonus, 1) + + const { roll, successes, diceResults } = await _rollPool(totalDice, threshold) + const diceHtml = _diceHtml(diceResults, threshold) + + const modParts = [] + if (sv > 0) modParts.push(`+${sv} SV`) + if (damageBonus !== 0) modParts.push(`${damageBonus > 0 ? "+" : ""}${damageBonus} ${game.i18n.localize("OATHHAMMER.Dialog.DamageModifier")}`) + if (autoDamageBonus > 0) modParts.push(`+${autoDamageBonus} auto`) + const modLine = modParts.length ? `
${modParts.join(" · ")}
` : "" + + const apNote = sys.ap > 0 ? `AP ${sys.ap}` : "" + + const content = ` +
+
+ ${weapon.name} + ${weapon.name} — ${game.i18n.localize("OATHHAMMER.Dialog.Damage")} +
+
+ ${sys.damageLabel} = ${baseDamageDice}d6 ${sv > 0 ? `+${sv} SV` : ""} + ${colorEmoji} ${totalDice}d6 (${threshold}+) ${colorLabel} +
+ ${modLine} +
${diceHtml}
+
+ ${successes} ${game.i18n.localize("OATHHAMMER.Roll.Damage")} + ${apNote} +
+
+ ` + + const rollMode = visibility ?? game.settings.get("core", "rollMode") + const msgData = { + speaker: ChatMessage.getSpeaker({ actor }), + content, + rolls: [roll], + sound: CONFIG.sounds.dice, + } + ChatMessage.applyRollMode(msgData, rollMode) + await ChatMessage.create(msgData) + + return { successes } +} + + +// ============================================================ +// SPELL CAST ROLL +// ============================================================ + +/** + * Roll a spell casting check (Magic / Intelligence) and post to chat. + * Counts dice showing 1 and adds Arcane Stress to the actor. + * + * @param {Actor} actor The caster + * @param {Item} spell The spell item + * @param {object} options From OathHammerSpellDialog.prompt() + */ +export async function rollSpellCast(actor, spell, options = {}) { + const { + dv = spell.system.difficultyValue, + enhancement = "none", + stressCost = 0, + poolPenalty = 0, + redDice = false, + noStress = false, + elementalBonus = 0, + bonus = 0, + grimPenalty = 0, + visibility, + } = options + + const sys = spell.system + const actorSys = actor.system + + const intRank = actorSys.attributes.intelligence.rank + const magicRank = actorSys.skills.magic.rank + const totalDice = Math.max(intRank + magicRank + bonus + poolPenalty + elementalBonus + grimPenalty, 1) + const threshold = redDice ? 3 : 4 + const colorEmoji = redDice ? "🔴" : "⬜" + + const { roll, successes, diceResults } = await _rollPool(totalDice, threshold) + const diceHtml = _diceHtml(diceResults, threshold) + + // Count 1s for Arcane Stress (unless Safe Spell enhancement) + const onesCount = noStress ? 0 : diceResults.filter(d => d.val === 1 && !d.exploded).length + const totalStressGain = stressCost + onesCount + const isSuccess = successes >= dv + + // Update arcane stress + if (totalStressGain > 0) { + const currentStress = actorSys.arcaneStress.value + await actor.update({ "system.arcaneStress.value": currentStress + totalStressGain }) + } + + const newStress = (actorSys.arcaneStress.value ?? 0) + totalStressGain + const stressMax = actorSys.arcaneStress.threshold + const isBlocked = newStress >= stressMax + + const skillLabel = game.i18n.localize("OATHHAMMER.Skill.Magic") + const attrLabel = game.i18n.localize("OATHHAMMER.Attribute.Intelligence") + const resultClass = isSuccess ? "roll-success" : "roll-failure" + const resultLabel = isSuccess + ? game.i18n.localize("OATHHAMMER.Roll.Success") + : game.i18n.localize("OATHHAMMER.Roll.Failure") + + const modParts = [] + if (bonus !== 0) modParts.push(`${bonus > 0 ? "+" : ""}${bonus} ${game.i18n.localize("OATHHAMMER.Dialog.Modifier")}`) + if (poolPenalty !== 0) modParts.push(`${poolPenalty} ${game.i18n.localize("OATHHAMMER.Enhancement." + _cap(enhancement))}`) + if (elementalBonus > 0) modParts.push(`+${elementalBonus} ${game.i18n.localize("OATHHAMMER.Dialog.ElementMet")}`) + if (grimPenalty < 0) modParts.push(`${grimPenalty} ${game.i18n.localize("OATHHAMMER.Dialog.GrimoireNo")}`) + const modLine = modParts.length ? `
${modParts.join(" · ")}
` : "" + + const stressLine = `
+ 🧠 ${game.i18n.localize("OATHHAMMER.Label.ArcaneStress")}: +${totalStressGain} + (${onesCount} × 1s + ${stressCost} enh.) → ${newStress}/${stressMax} + ${isBlocked ? ` ⚠ ${game.i18n.localize("OATHHAMMER.Label.StressBlocked")}` : ""} +
` + + const content = ` +
+
+ ${spell.name} + ${spell.name} (DV ${dv}) +
+
+ ${attrLabel} ${intRank} + ${skillLabel} ${magicRank} + ${colorEmoji} ${totalDice}d6 (${threshold}+) +
+ ${modLine} +
${diceHtml}
+
+ ${successes} / ${dv} + ${resultLabel} +
+ ${stressLine} +
+ ` + + const rollMode = visibility ?? game.settings.get("core", "rollMode") + const msgData = { + speaker: ChatMessage.getSpeaker({ actor }), + content, + rolls: [roll], + sound: CONFIG.sounds.dice, + } + ChatMessage.applyRollMode(msgData, rollMode) + await ChatMessage.create(msgData) + + return { successes, dv, isSuccess, totalStressGain, newStress } +} + +// ============================================================ +// MIRACLE CAST ROLL +// ============================================================ + +/** + * Roll a miracle invocation check (Magic / Willpower) and post to chat. + * On failure, warns the player they are blocked from miracles for the day. + * + * @param {Actor} actor The caster + * @param {Item} miracle The miracle item + * @param {object} options From OathHammerMiracleDialog.prompt() + */ +export async function rollMiracleCast(actor, miracle, options = {}) { + const { + dv = 1, + isRitual = false, + bonus = 0, + visibility, + } = options + + const sys = miracle.system + const actorSys = actor.system + + const wpRank = actorSys.attributes.willpower.rank + const magicRank = actorSys.skills.magic.rank + const totalDice = Math.max(wpRank + magicRank + bonus, 1) + const threshold = 4 + const colorEmoji = "⬜" + + const { roll, successes, diceResults } = await _rollPool(totalDice, threshold) + const diceHtml = _diceHtml(diceResults, threshold) + const isSuccess = successes >= dv + + const skillLabel = game.i18n.localize("OATHHAMMER.Skill.Magic") + const attrLabel = game.i18n.localize("OATHHAMMER.Attribute.Willpower") + const resultClass = isSuccess ? "roll-success" : "roll-failure" + const resultLabel = isSuccess + ? game.i18n.localize("OATHHAMMER.Roll.Success") + : game.i18n.localize("OATHHAMMER.Roll.Failure") + + const modLine = bonus !== 0 + ? `
${bonus > 0 ? "+" : ""}${bonus} ${game.i18n.localize("OATHHAMMER.Dialog.Modifier")}
` + : "" + + const blockedLine = !isSuccess + ? `
⚠ ${game.i18n.localize("OATHHAMMER.Roll.MiracleBlocked")}
` + : "" + + const dvNote = isRitual + ? `DV ${dv} (${game.i18n.localize("OATHHAMMER.Label.Ritual")})` + : `DV ${dv}` + + const content = ` +
+
+ ${miracle.name} + ${miracle.name} (${dvNote}) +
+
+ ${attrLabel} ${wpRank} + ${skillLabel} ${magicRank} + ${colorEmoji} ${totalDice}d6 (${threshold}+) +
+ ${modLine} +
${diceHtml}
+
+ ${successes} / ${dv} + ${resultLabel} +
+ ${blockedLine} +
+ ` + + const rollMode = visibility ?? game.settings.get("core", "rollMode") + const msgData = { + speaker: ChatMessage.getSpeaker({ actor }), + content, + rolls: [roll], + sound: CONFIG.sounds.dice, + } + ChatMessage.applyRollMode(msgData, rollMode) + await ChatMessage.create(msgData) + + return { successes, dv, isSuccess } +} + +function _cap(s) { return s ? s.charAt(0).toUpperCase() + s.slice(1) : "" } diff --git a/oath-hammer.mjs b/oath-hammer.mjs index 8e2150a..caf6ba4 100644 --- a/oath-hammer.mjs +++ b/oath-hammer.mjs @@ -5,6 +5,8 @@ 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" +import OathHammerWeaponDialog from "./module/applications/weapon-dialog.mjs" +import { rollWeaponDamage } from "./module/rolls.mjs" Hooks.once("init", function () { console.info(SYSTEM.ASCII) @@ -32,7 +34,6 @@ Hooks.once("init", function () { "magic-item": models.OathHammerMagicItem, trait: models.OathHammerTrait, oath: models.OathHammerOath, - lineage: models.OathHammerLineage, "class": models.OathHammerClass, building: models.OathHammerBuilding } @@ -59,7 +60,6 @@ Hooks.once("init", function () { foundry.documents.collections.Items.registerSheet("fvtt-oath-hammer", applications.OathHammerMagicItemSheet, { types: ["magic-item"], makeDefault: true, label: "OATHHAMMER.Sheet.MagicItem" }) foundry.documents.collections.Items.registerSheet("fvtt-oath-hammer", applications.OathHammerTraitSheet, { types: ["trait"], makeDefault: true, label: "OATHHAMMER.Sheet.Trait" }) 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.OathHammerLineageSheet, { types: ["lineage"], makeDefault: true, label: "OATHHAMMER.Sheet.Lineage" }) foundry.documents.collections.Items.registerSheet("fvtt-oath-hammer", applications.OathHammerClassSheet, { types: ["class"], makeDefault: true, label: "OATHHAMMER.Sheet.Class" }) foundry.documents.collections.Items.registerSheet("fvtt-oath-hammer", applications.OathHammerBuildingSheet, { types: ["building"], makeDefault: true, label: "OATHHAMMER.Sheet.Building" }) @@ -70,6 +70,39 @@ Hooks.once("init", function () { console.info("Oath Hammer | System Initialized") }) -Hooks.once("ready", function () { +Hooks.once("ready", async function () { console.info("Oath Hammer | System Ready") + + // Migration: remove orphaned items with removed types (lineage → actor field, ability → trait) + const removedTypes = new Set(["lineage", "ability"]) + for (const actor of game.actors) { + const invalidItems = actor._source.items?.filter(i => removedTypes.has(i.type)) ?? [] + if (invalidItems.length) { + console.info(`Oath Hammer | Migrating ${actor.name}: removing ${invalidItems.length} obsolete item(s)`) + await actor.deleteEmbeddedDocuments("Item", invalidItems.map(i => i._id)) + } + } + for (const id of game.items.invalidDocumentIds) { + const item = game.items.getInvalid(id) + if (item && removedTypes.has(item._source.type)) { + console.info(`Oath Hammer | Deleting world item: ${item._source.name} (${item._source.type})`) + await item.delete() + } + } +}) + +// Handle "Roll Damage" button in weapon attack chat cards +Hooks.on("renderChatMessageHTML", (message, html) => { + const btn = html.querySelector("[data-action=\"rollWeaponDamage\"]") + if (!btn) return + btn.addEventListener("click", async () => { + const flagData = message.getFlag("fvtt-oath-hammer", "weaponAttack") + if (!flagData) return + const { actorUuid, weaponUuid, attackSuccesses } = flagData + const actor = await fromUuid(actorUuid) + const weapon = await fromUuid(weaponUuid) + if (!actor || !weapon) return ui.notifications.warn(game.i18n.localize("OATHHAMMER.Roll.NoActor")) + const opts = await OathHammerWeaponDialog.promptDamage(actor, weapon, attackSuccesses ?? 0) + if (opts) await rollWeaponDamage(actor, weapon, opts) + }) }) diff --git a/system.json b/system.json index 560fff4..12c9dfd 100644 --- a/system.json +++ b/system.json @@ -25,7 +25,8 @@ "character": { "htmlFields": [ "description", - "notes" + "notes", + "lineage.traits" ] }, "npc": { @@ -85,12 +86,6 @@ "bane" ] }, - "lineage": { - "htmlFields": [ - "description", - "traits" - ] - }, "class": { "htmlFields": [ "description", diff --git a/templates/actor/character-combat.hbs b/templates/actor/character-combat.hbs index 41400c0..f146e6d 100644 --- a/templates/actor/character-combat.hbs +++ b/templates/actor/character-combat.hbs @@ -44,10 +44,10 @@
- {{#unless ../isPlayMode}} + + - {{/unless}}
{{/each}} @@ -83,10 +83,8 @@
- {{#unless ../isPlayMode}} - {{/unless}}
{{/each}} @@ -112,10 +110,8 @@ {{ammo.name}} ×{{ammo.system.quantity}}
- {{#unless ../isPlayMode}} - {{/unless}}
{{/each}} diff --git a/templates/actor/character-equipment.hbs b/templates/actor/character-equipment.hbs index a8a78f8..145c54f 100644 --- a/templates/actor/character-equipment.hbs +++ b/templates/actor/character-equipment.hbs @@ -36,10 +36,8 @@ {{localize equip.system.itemType}} {{equip.system.quantity}}
- {{#unless ../isPlayMode}} - {{/unless}}
{{/each}} @@ -65,10 +63,8 @@ {{mi.name}} {{localize mi.system.rarity}}
- {{#unless ../isPlayMode}} - {{/unless}}
{{/each}} @@ -91,10 +87,8 @@ {{cond.name}} {{localize cond.system.conditionType}}
- {{#unless ../isPlayMode}} - {{/unless}}
{{/each}} diff --git a/templates/actor/character-identity.hbs b/templates/actor/character-identity.hbs index f4c4345..c17ba9b 100644 --- a/templates/actor/character-identity.hbs +++ b/templates/actor/character-identity.hbs @@ -21,10 +21,8 @@ {{trait._typeLabel}} {{trait._usageLabel}}
- {{#unless ../isPlayMode}} - {{/unless}}
{{/each}} @@ -49,10 +47,8 @@ {{oath._typeLabel}} {{#if oath._violated}}{{else}}{{/if}}
- {{#unless ../isPlayMode}} - {{/unless}}
{{/each}} diff --git a/templates/actor/character-magic.hbs b/templates/actor/character-magic.hbs index 4699cf2..fcb2cb4 100644 --- a/templates/actor/character-magic.hbs +++ b/templates/actor/character-magic.hbs @@ -15,7 +15,7 @@
  • {{localize "OATHHAMMER.Label.Name"}} - Lv. + DV {{localize "OATHHAMMER.Label.Tradition"}} AS @@ -24,14 +24,13 @@
  • {{spell.name}} - {{spell.system.level}} + {{spell.system.difficultyValue}} {{localize spell.system.tradition}} - {{spell.system.arcaneStress}} +
    - {{#unless ../isPlayMode}} + - {{/unless}}
  • {{/each}} @@ -49,19 +48,18 @@
  • {{localize "OATHHAMMER.Label.Name"}} - {{localize "OATHHAMMER.Label.Piety"}} + {{localize "OATHHAMMER.Label.DivineTradition"}}
  • {{#each miracles as |miracle|}}
  • {{miracle.name}} - {{miracle.system.piety}} + {{miracle.system.divineTradition}}
    - {{#unless ../isPlayMode}} + - {{/unless}}
  • {{/each}} diff --git a/templates/actor/character-sheet.hbs b/templates/actor/character-sheet.hbs index 95fd07e..3e2258f 100644 --- a/templates/actor/character-sheet.hbs +++ b/templates/actor/character-sheet.hbs @@ -21,18 +21,9 @@ {{!-- Row 2: Identity bar (lineage + class + level/xp) --}}
    -
    - {{#if lineage}} - - {{lineage.name}} - {{#unless isPlayMode}} - - - {{/unless}} - {{else}} +
    - {{localize "OATHHAMMER.Label.DropLineage"}} - {{/if}} + {{formInput systemFields.lineage.fields.name value=system.lineage.name name="system.lineage.name" placeholder=(localize "OATHHAMMER.Label.Lineage") disabled=isPlayMode}}
    {{#if characterClass}} diff --git a/templates/actor/character-skills.hbs b/templates/actor/character-skills.hbs index 548f4fe..4a96904 100644 --- a/templates/actor/character-skills.hbs +++ b/templates/actor/character-skills.hbs @@ -16,7 +16,9 @@
    {{#each group.skillData as |skill|}}
    - + + {{localize skill.label}} +
    + {{#each miracleCountOptions}}{{/each}} + + {{localize "OATHHAMMER.Dialog.MiracleCountHint"}} +
    + {{/unless}} + +
    + + + {{localize "OATHHAMMER.Dialog.AttackModifierHint"}} +
    + + + + {{!-- Visibility --}} +
    + {{localize "OATHHAMMER.Dialog.Visibility"}} + +
    + +
    diff --git a/templates/roll-dialog.hbs b/templates/roll-dialog.hbs new file mode 100644 index 0000000..b626e7a --- /dev/null +++ b/templates/roll-dialog.hbs @@ -0,0 +1,83 @@ +
    + + {{!-- Actor name --}} +
    {{actorName}}
    + + {{!-- Skill / pool info --}} +
    + {{localize "OATHHAMMER.Dialog.SkillCheck"}} + +
    + {{skillLabel}} + + {{attrLabel}} ({{attrRank}}) + {{localize "OATHHAMMER.Label.SkillRank"}} ({{skillRank}}) + {{#if skillMod}}+ {{localize "OATHHAMMER.Label.SkillModifier"}} ({{skillMod}}){{/if}} + +
    + +
    + {{baseTotal}}d6 + {{colorLabel}}  {{threshold}}+ +
    + + {{!-- Attribute override for dual-attribute skills (Defense, Fighting, Magic) --}} + {{#if isDualAttr}} +
    + + +
    + {{/if}} +
    + + {{!-- Roll options --}} +
    + {{localize "OATHHAMMER.Dialog.Options"}} + +
    + + + {{localize "OATHHAMMER.Dialog.DVHint"}} +
    + +
    + + + {{localize "OATHHAMMER.Dialog.ModifierHint"}} +
    + +
    + + + {{localize "OATHHAMMER.Dialog.SupportersHint"}} +
    + + {{#if availableLuck}} +
    + + + {{localize "OATHHAMMER.Dialog.LuckHint"}} ({{availableLuck}} {{localize "OATHHAMMER.Dialog.Available"}}) +
    + {{/if}} +
    + + {{!-- Visibility --}} +
    + {{localize "OATHHAMMER.Dialog.Visibility"}} + +
    + +
    diff --git a/templates/spell-cast-dialog.hbs b/templates/spell-cast-dialog.hbs new file mode 100644 index 0000000..a53b31f --- /dev/null +++ b/templates/spell-cast-dialog.hbs @@ -0,0 +1,80 @@ +
    + + {{!-- Spell header --}} +
    + {{spellName}} +
    + {{spellName}} +
    + DV {{dv}} + {{traditionLabel}} + {{#if isRitual}}{{localize "OATHHAMMER.Label.Ritual"}}{{/if}} + {{#if isMagicMissile}}{{localize "OATHHAMMER.Label.MagicMissile"}}{{/if}} + {{#if range}}{{range}}{{/if}} + {{#if duration}}{{duration}}{{/if}} +
    + {{#if spellSave}}
    {{localize "OATHHAMMER.Label.SpellSave"}}: {{spellSave}}
    {{/if}} +
    +
    + + {{!-- Arcane stress tracker --}} +
    + + {{localize "OATHHAMMER.Label.ArcaneStress"}}: {{currentStress}} / {{stressThreshold}} + {{#if isOverThreshold}}⚠ {{localize "OATHHAMMER.Label.StressBlocked"}}{{/if}} +
    + + {{!-- Cast options --}} +
    + {{localize "OATHHAMMER.Dialog.CastOptions"}} + +
    + {{localize "OATHHAMMER.Skill.Magic"}} ({{localize "OATHHAMMER.Attribute.Intelligence"}} {{intRank}}) + + {{localize "OATHHAMMER.Label.SkillRank"}} {{magicRank}} + = {{basePool}}d6 +
    + +
    + + +
    + + {{#if isElemental}} +
    + + +
    + {{/if}} + +
    + + + {{localize "OATHHAMMER.Dialog.AttackModifierHint"}} +
    + +
    + + +
    + +
    + + {{!-- Visibility --}} +
    + {{localize "OATHHAMMER.Dialog.Visibility"}} + +
    + +
    diff --git a/templates/weapon-attack-dialog.hbs b/templates/weapon-attack-dialog.hbs new file mode 100644 index 0000000..37a8265 --- /dev/null +++ b/templates/weapon-attack-dialog.hbs @@ -0,0 +1,69 @@ +
    + + {{!-- Weapon header --}} +
    + {{weaponName}} +
    + {{weaponName}} +
    + {{damageLabel}} → {{baseDamageDice}}d6 + {{damageColorLabel}} {{damageThreshold}}+ + {{#if apValue}}AP{{apValue}}{{/if}} + {{#if isRanged}}{{shortRange}}/{{longRange}}ft{{/if}} +
    + {{#if traits}} +
    + {{#each traits}}{{this}}{{/each}} +
    + {{/if}} +
    +
    + + {{!-- Attack roll config --}} +
    + {{localize "OATHHAMMER.Dialog.Attack"}} + +
    + {{skillLabel}} ({{attrLabel}} {{attrRank}}) + {{localize "OATHHAMMER.Label.SkillRank"}} {{skillRank}} + {{#if autoAttackBonus}} + +{{autoAttackBonus}} auto{{/if}} + = {{baseAttackPool}}d6 +
    + + {{#if hasNimble}} +
    + + +
    + {{/if}} + +
    + + + {{localize "OATHHAMMER.Dialog.AttackModifierHint"}} +
    + + {{#if isRanged}} +
    + + +
    + {{/if}} + +
    + + {{!-- Visibility --}} +
    + {{localize "OATHHAMMER.Dialog.Visibility"}} + +
    + +
    diff --git a/templates/weapon-damage-dialog.hbs b/templates/weapon-damage-dialog.hbs new file mode 100644 index 0000000..1ae91db --- /dev/null +++ b/templates/weapon-damage-dialog.hbs @@ -0,0 +1,47 @@ +
    + + {{!-- Weapon header --}} +
    + {{weaponName}} +
    + {{weaponName}} +
    + {{damageLabel}} → {{baseDamageDice}}d6 base + {{damageColorLabel}} {{damageThreshold}}+ + {{#if apValue}}AP{{apValue}}{{/if}} + {{#if autoDamageBonus}}+{{autoDamageBonus}} auto{{/if}} +
    +
    +
    + + {{!-- Damage options --}} +
    + {{localize "OATHHAMMER.Dialog.Damage"}} + +
    + + + {{localize "OATHHAMMER.Dialog.SVHint"}} +
    + +
    + + + {{localize "OATHHAMMER.Dialog.DamageModifierHint"}} +
    + +
    + + {{!-- Visibility --}} +
    + {{localize "OATHHAMMER.Dialog.Visibility"}} + +
    + +