Add roll windows from actor sheet

This commit is contained in:
2026-03-15 23:20:32 +01:00
parent 82fddb0cb3
commit 49347370c7
57 changed files with 6372 additions and 184 deletions

2
.gitignore vendored
View File

@@ -1 +1,3 @@
_docs_private/ _docs_private/
node_modules/
package-lock.json

View File

@@ -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"
}
}
}

View File

@@ -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"
}
}
}

View File

@@ -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"
}
}
}

View File

@@ -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/"
]
}
}
}

View File

@@ -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/"
]
}
}
}

View File

@@ -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/"
]
}
}
}

View File

@@ -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/"
]
}
}
}

View File

@@ -0,0 +1,51 @@
<section class="item-sheet-common">
<div class="header">
<img class="item-img" src="{{item.img}}" data-edit="img" data-action="editImage" data-tooltip="{{item.name}}" />
{{formInput fields.name value=source.name}}
</div>
<div class="flexrow">
<div class="align-top">
{{formField systemFields.armorType value=system.armorType name="system.armorType" localize=true}}
<div class="form-group">
<label>{{localize "OATHHAMMER.Armor.FIELDS.armorValue.label"}}</label>
<div class="form-fields">
<select name="system.armorValue">
{{selectOptions armorValueChoices selected=system.armorValue}}
</select>
</div>
</div>
<div class="form-group">
<label>{{localize "OATHHAMMER.Armor.FIELDS.penalty.label"}}</label>
<div class="form-fields">
<select name="system.penalty">
{{selectOptions penaltyChoices selected=system.penalty}}
</select>
</div>
</div>
{{formField systemFields.slots value=system.slots name="system.slots"}}
{{formField systemFields.traits value=system.traits name="system.traits" localize=true}}
</div>
<div class="align-top">
{{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}}
</div>
</div>
<fieldset>
<legend>{{localize "OATHHAMMER.Label.Description"}}</legend>
{{formInput systemFields.description enriched=enrichedDescription value=system.description name="system.description" toggled=true}}
</fieldset>
{{#if system.isMagic}}
<fieldset>
<legend>{{localize "OATHHAMMER.Label.Enchantment"}}</legend>
<div class="flexrow">
{{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"}}
</div>
{{formInput systemFields.magicEffect enriched=enrichedMagicEffect value=system.magicEffect name="system.magicEffect" toggled=true}}
</fieldset>
{{/if}}
</section>

View File

@@ -0,0 +1,109 @@
<section class="item-sheet-common">
<div class="header">
<img
class="item-img"
src="{{item.img}}"
data-edit="img"
data-action="editImage"
data-tooltip="{{item.name}}"
/>
{{formInput fields.name value=source.name}}
</div>
<div class="flexrow">
<div class="align-top">
{{formField
systemFields.armorType
value=system.armorType
name="system.armorType"
}}
<div class="form-group">
<label>{{localize "OATHHAMMER.Armor.FIELDS.armorValue.label"}}</label>
<div class="form-fields">
<select name="system.armorValue">
{{selectOptions armorValueChoices selected=system.armorValue}}
</select>
</div>
</div>
<div class="form-group">
<label>{{localize "OATHHAMMER.Armor.FIELDS.penalty.label"}}</label>
<div class="form-fields">
<select name="system.penalty">
{{selectOptions penaltyChoices selected=system.penalty}}
</select>
</div>
</div>
{{formField systemFields.slots value=system.slots name="system.slots"}}
{{formField
systemFields.traits
value=system.traits
name="system.traits"
localize=true
}}
</div>
<div class="align-top">
{{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
}}
</div>
</div>
<fieldset>
<legend>{{localize "OATHHAMMER.Label.Description"}}</legend>
{{formInput
systemFields.description
enriched=enrichedDescription
value=system.description
name="system.description"
toggled=true
}}
</fieldset>
{{#if system.isMagic}}
<fieldset>
<legend>{{localize "OATHHAMMER.Label.Enchantment"}}</legend>
<div class="flexrow">
{{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"
}}
</div>
{{formInput
systemFields.magicEffect
enriched=enrichedMagicEffect
value=system.magicEffect
name="system.magicEffect"
toggled=true
}}
</fieldset>
{{/if}}
</section>

View File

@@ -0,0 +1,51 @@
<section class="item-sheet-common">
<div class="header">
<img class="item-img" src="{{item.img}}" data-edit="img" data-action="editImage" data-tooltip="{{item.name}}" />
{{formInput fields.name value=source.name}}
</div>
<div class="flexrow">
<div class="align-top">
{{formField systemFields.armorType value=system.armorType name="system.armorType"}}
<div class="form-group">
<label>{{localize "OATHHAMMER.Armor.FIELDS.armorValue.label"}}</label>
<div class="form-fields">
<select name="system.armorValue">
{{selectOptions armorValueChoices selected=system.armorValue}}
</select>
</div>
</div>
<div class="form-group">
<label>{{localize "OATHHAMMER.Armor.FIELDS.penalty.label"}}</label>
<div class="form-fields">
<select name="system.penalty">
{{selectOptions penaltyChoices selected=system.penalty}}
</select>
</div>
</div>
{{formField systemFields.slots value=system.slots name="system.slots"}}
{{formField systemFields.traits value=system.traits name="system.traits" localize=true}}
</div>
<div class="align-top">
{{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}}
</div>
</div>
<fieldset>
<legend>{{localize "OATHHAMMER.Label.Description"}}</legend>
{{formInput systemFields.description enriched=enrichedDescription value=system.description name="system.description" toggled=true}}
</fieldset>
{{#if system.isMagic}}
<fieldset>
<legend>{{localize "OATHHAMMER.Label.Enchantment"}}</legend>
<div class="flexrow">
{{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"}}
</div>
{{formInput systemFields.magicEffect enriched=enrichedMagicEffect value=system.magicEffect name="system.magicEffect" toggled=true}}
</fieldset>
{{/if}}
</section>

View File

@@ -0,0 +1,109 @@
<section class="item-sheet-common">
<div class="header">
<img
class="item-img"
src="{{item.img}}"
data-edit="img"
data-action="editImage"
data-tooltip="{{item.name}}"
/>
{{formInput fields.name value=source.name}}
</div>
<div class="flexrow">
<div class="align-top">
{{formField
systemFields.armorType
value=system.armorType
name="system.armorType"
}}
<div class="form-group">
<label>{{localize "OATHHAMMER.Armor.FIELDS.armorValue.label"}}</label>
<div class="form-fields">
<select name="system.armorValue">
{{selectOptions armorValueChoices selected=system.armorValue}}
</select>
</div>
</div>
<div class="form-group">
<label>{{localize "OATHHAMMER.Armor.FIELDS.penalty.label"}}</label>
<div class="form-fields">
<select name="system.penalty">
{{selectOptions penaltyChoices selected=system.penalty}}
</select>
</div>
</div>
{{formField systemFields.slots value=system.slots name="system.slots"}}
{{formField
systemFields.traits
value=system.traits
name="system.traits"
localize=true
}}
</div>
<div class="align-top">
{{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
}}
</div>
</div>
<fieldset>
<legend>{{localize "OATHHAMMER.Label.Description"}}</legend>
{{formInput
systemFields.description
enriched=enrichedDescription
value=system.description
name="system.description"
toggled=true
}}
</fieldset>
{{#if system.isMagic}}
<fieldset>
<legend>{{localize "OATHHAMMER.Label.Enchantment"}}</legend>
<div class="flexrow">
{{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"
}}
</div>
{{formInput
systemFields.magicEffect
enriched=enrichedMagicEffect
value=system.magicEffect
name="system.magicEffect"
toggled=true
}}
</fieldset>
{{/if}}
</section>

View File

@@ -0,0 +1,110 @@
<section class="item-sheet-common">
<div class="header">
<img
class="item-img"
src="{{item.img}}"
data-edit="img"
data-action="editImage"
data-tooltip="{{item.name}}"
/>
{{formInput fields.name value=source.name}}
</div>
<div class="flexrow">
<div class="align-top">
{{formField
systemFields.armorType
value=system.armorType
name="system.armorType"
localize=true
}}
<div class="form-group">
<label>{{localize "OATHHAMMER.Armor.FIELDS.armorValue.label"}}</label>
<div class="form-fields">
<select name="system.armorValue">
{{selectOptions armorValueChoices selected=system.armorValue}}
</select>
</div>
</div>
<div class="form-group">
<label>{{localize "OATHHAMMER.Armor.FIELDS.penalty.label"}}</label>
<div class="form-fields">
<select name="system.penalty">
{{selectOptions penaltyChoices selected=system.penalty}}
</select>
</div>
</div>
{{formField systemFields.slots value=system.slots name="system.slots"}}
{{formField
systemFields.traits
value=system.traits
name="system.traits"
localize=true
}}
</div>
<div class="align-top">
{{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
}}
</div>
</div>
<fieldset>
<legend>{{localize "OATHHAMMER.Label.Description"}}</legend>
{{formInput
systemFields.description
enriched=enrichedDescription
value=system.description
name="system.description"
toggled=true
}}
</fieldset>
{{#if system.isMagic}}
<fieldset>
<legend>{{localize "OATHHAMMER.Label.Enchantment"}}</legend>
<div class="flexrow">
{{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"
}}
</div>
{{formInput
systemFields.magicEffect
enriched=enrichedMagicEffect
value=system.magicEffect
name="system.magicEffect"
toggled=true
}}
</fieldset>
{{/if}}
</section>

View File

@@ -754,7 +754,7 @@
} }
.oathhammer .item-list--weapon .item-list-header, .oathhammer .item-list--weapon .item-list-header,
.oathhammer .item-list--weapon .item-entry { .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-list-header,
.oathhammer .item-list--armor .item-entry { .oathhammer .item-list--armor .item-entry {
@@ -766,11 +766,11 @@
} }
.oathhammer .item-list--spell .item-list-header, .oathhammer .item-list--spell .item-list-header,
.oathhammer .item-list--spell .item-entry { .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-list-header,
.oathhammer .item-list--miracle .item-entry { .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-list-header,
.oathhammer .item-list--equipment .item-entry { .oathhammer .item-list--equipment .item-entry {
@@ -958,3 +958,610 @@
height: auto; height: auto;
accent-color: #084a74; 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;
}

View File

@@ -13,7 +13,6 @@
"Trait": "Oath Hammer Trait Sheet", "Trait": "Oath Hammer Trait Sheet",
"Oath": "Oath Hammer Oath Sheet", "Oath": "Oath Hammer Oath Sheet",
"Condition": "Oath Hammer Condition Sheet", "Condition": "Oath Hammer Condition Sheet",
"Lineage": "Oath Hammer Lineage Sheet",
"Class": "Oath Hammer Class Sheet", "Class": "Oath Hammer Class Sheet",
"Building": "Oath Hammer Building Sheet" "Building": "Oath Hammer Building Sheet"
}, },
@@ -61,28 +60,6 @@
"Survival": "Survival", "Survival": "Survival",
"Tracking": "Tracking" "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": { "Class": {
"Berserker": "Berserker", "Berserker": "Berserker",
"Champion": "Champion", "Champion": "Champion",
@@ -128,7 +105,7 @@
"buildTime": { "label": "Build Time" }, "buildTime": { "label": "Build Time" },
"taxRevenue": { "label": "Tax Revenue / month" }, "taxRevenue": { "label": "Tax Revenue / month" },
"constructed":{ "label": "Constructed" }, "constructed":{ "label": "Constructed" },
"settlement": { "label": "Settlement" },
"description":{ "label": "Description" }, "description":{ "label": "Description" },
"notes": { "label": "Notes" } "notes": { "label": "Notes" }
} }
@@ -264,7 +241,7 @@
"SkillModifier": "Mod", "SkillModifier": "Mod",
"TotalDice": "Total", "TotalDice": "Total",
"ColorDice": "Color", "ColorDice": "Color",
"DropLineage": "Drop Lineage Here", "Lineage": "Lineage",
"DropClass": "Drop Class Here", "DropClass": "Drop Class Here",
"Traits": "Traits", "Traits": "Traits",
"Features": "Features", "Features": "Features",
@@ -276,12 +253,18 @@
"Magic": "Magic", "Magic": "Magic",
"Damage": "Damage", "Damage": "Damage",
"Tradition": "Tradition", "Tradition": "Tradition",
"DivineTradition": "Divine Tradition",
"Piety": "Piety", "Piety": "Piety",
"Quantity": "Qty", "Quantity": "Qty",
"Rarity": "Rarity", "Rarity": "Rarity",
"Penalty": "Penalty", "Penalty": "Penalty",
"Equipped": "Eq.", "Equipped": "Eq.",
"XPCurrent": "Current XP" "XPCurrent": "Current XP",
"Ritual": "Ritual",
"MagicMissile": "Magic Missile",
"SpellSave": "Save",
"StressBlocked": "BLOCKED — over stress threshold!",
"ArcaneStressShort": "AS"
}, },
"ColorDice": { "ColorDice": {
"White": "White (4+)", "White": "White (4+)",
@@ -296,8 +279,108 @@
"Building": "New Building" "Building": "New Building"
}, },
"ToggleSheet": "Toggle Edit/Play Mode", "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": { "Character": {
"FIELDS": { "FIELDS": {
"lineage": {
"label": "Lineage",
"fields": {
"name": {
"label": "Lineage"
},
"traits": {
"label": "Lineage Traits"
}
}
},
"attributes": { "attributes": {
"label": "Attributes" "label": "Attributes"
}, },
@@ -330,12 +413,6 @@
"biodata": { "biodata": {
"label": "Background", "label": "Background",
"fields": { "fields": {
"lineage": {
"label": "Lineage"
},
"class": {
"label": "Class"
},
"age": { "age": {
"label": "Age" "label": "Age"
}, },
@@ -439,9 +516,6 @@
"traits": { "traits": {
"label": "Traits" "label": "Traits"
}, },
"slots": {
"label": "Item Slots"
},
"rarity": { "rarity": {
"label": "Rarity" "label": "Rarity"
}, },
@@ -491,6 +565,9 @@
"traits": { "traits": {
"label": "Traits" "label": "Traits"
}, },
"specialProperties": {
"label": "Special Properties"
},
"rarity": { "rarity": {
"label": "Rarity" "label": "Rarity"
}, },
@@ -704,6 +781,18 @@
"TwoHanded": "Two-handed", "TwoHanded": "Two-handed",
"Versatile": "Versatile" "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": { "DivineTradition": {
"Druidic": "Druidic", "Druidic": "Druidic",
"Profane": "Profane", "Profane": "Profane",
@@ -799,7 +888,6 @@
"magic-item": "Magic Item", "magic-item": "Magic Item",
"trait": "Trait", "trait": "Trait",
"oath": "Oath", "oath": "Oath",
"lineage": "Lineage",
"class": "Class" "class": "Class"
}, },
"Actor": { "Actor": {

View File

@@ -9,3 +9,5 @@
@import "npc-sheet"; @import "npc-sheet";
@import "item-list"; @import "item-list";
@import "item-sheets"; @import "item-sheets";
@import "rolls";
@import "roll-dialog";

View File

@@ -117,7 +117,7 @@
.item-list--weapon { .item-list--weapon {
.item-list-header, .item-entry { .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--spell {
.item-list-header, .item-entry { .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--miracle {
.item-list-header, .item-entry { .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;
} }
} }

552
less/roll-dialog.less Normal file
View File

@@ -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; }
}

100
less/rolls.less Normal file
View File

@@ -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; }
}
}

View File

@@ -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 OathHammerMagicItemSheet } from "./sheets/magic-item-sheet.mjs"
export { default as OathHammerTraitSheet } from "./sheets/trait-sheet.mjs" export { default as OathHammerTraitSheet } from "./sheets/trait-sheet.mjs"
export { default as OathHammerOathSheet } from "./sheets/oath-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 OathHammerClassSheet } from "./sheets/class-sheet.mjs"
export { default as OathHammerBuildingSheet } from "./sheets/building-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"

View File

@@ -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]
}

View File

@@ -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"),
}
}
}

View File

@@ -95,8 +95,8 @@ export default class OathHammerActorSheet extends HandlebarsApplicationMixin(fou
async _onDropItem(item) { async _onDropItem(item) {
const itemData = item.toObject() const itemData = item.toObject()
// Lineage and class are unique: replace any existing item of the same type // Class is unique: replace any existing item of the same type
if (item.type === "lineage" || item.type === "class") { if (item.type === "class") {
const existing = this.document.itemTypes[item.type] const existing = this.document.itemTypes[item.type]
if (existing.length > 0) { if (existing.length > 0) {
await this.document.deleteEmbeddedDocuments("Item", existing.map(i => i.id)) await this.document.deleteEmbeddedDocuments("Item", existing.map(i => i.id))

View File

@@ -1,5 +1,6 @@
const { HandlebarsApplicationMixin } = foundry.applications.api const { HandlebarsApplicationMixin } = foundry.applications.api
import { ARMOR_TYPE_CHOICES, WEAPON_PROFICIENCY_GROUPS } from "../../config/system.mjs" 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) { export default class OathHammerItemSheet extends HandlebarsApplicationMixin(foundry.applications.sheets.ItemSheetV2) {
static SHEET_MODES = { EDIT: 0, PLAY: 1 } static SHEET_MODES = { EDIT: 0, PLAY: 1 }
@@ -28,6 +29,7 @@ export default class OathHammerItemSheet extends HandlebarsApplicationMixin(foun
actions: { actions: {
toggleSheet: OathHammerItemSheet.#onToggleSheet, toggleSheet: OathHammerItemSheet.#onToggleSheet,
editImage: OathHammerItemSheet.#onEditImage, editImage: OathHammerItemSheet.#onEditImage,
rollRarity: OathHammerItemSheet.#onRollRarity,
}, },
} }
@@ -134,4 +136,20 @@ export default class OathHammerItemSheet extends HandlebarsApplicationMixin(foun
}) })
return fp.browse() 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)
}
} }

View File

@@ -1,5 +1,10 @@
import OathHammerActorSheet from "./base-actor-sheet.mjs" import OathHammerActorSheet from "./base-actor-sheet.mjs"
import { SYSTEM } from "../../config/system.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 { export default class OathHammerCharacterSheet extends OathHammerActorSheet {
/** @override */ /** @override */
@@ -17,6 +22,11 @@ export default class OathHammerCharacterSheet extends OathHammerActorSheet {
createSpell: OathHammerCharacterSheet.#onCreateSpell, createSpell: OathHammerCharacterSheet.#onCreateSpell,
createMiracle: OathHammerCharacterSheet.#onCreateMiracle, createMiracle: OathHammerCharacterSheet.#onCreateMiracle,
createEquipment: OathHammerCharacterSheet.#onCreateEquipment, 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() { async _prepareContext() {
const context = await super._prepareContext() const context = await super._prepareContext()
context.tabs = this.#getTabs() 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 const doc = this.document
context.lineage = doc.itemTypes.lineage?.[0] ?? null
context.characterClass = doc.itemTypes["class"]?.[0] ?? null context.characterClass = doc.itemTypes["class"]?.[0] ?? null
return context return context
} }
@@ -236,4 +245,52 @@ export default class OathHammerCharacterSheet extends OathHammerActorSheet {
static #onCreateEquipment(event, target) { static #onCreateEquipment(event, target) {
this.document.createEmbeddedDocuments("Item", [{ name: game.i18n.localize("OATHHAMMER.NewItem.Equipment"), type: "equipment" }]) 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)
}
} }

View File

@@ -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
}
}

View File

@@ -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"),
}
}
}

View File

@@ -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)
}

View File

@@ -108,6 +108,20 @@ export const WEAPON_TRAITS = {
versatile: "OATHHAMMER.WeaponTrait.Versatile" 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 = { export const CURRENCY_CHOICES = {
gp: "OATHHAMMER.Currency.GP", gp: "OATHHAMMER.Currency.GP",
sp: "OATHHAMMER.Currency.SP", sp: "OATHHAMMER.Currency.SP",
@@ -172,6 +186,16 @@ export const RARITY_CHOICES = {
legendary: "OATHHAMMER.Rarity.Legendary" 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 // Two types of trait per the rulebook terminology
export const TRAIT_TYPE_CHOICES = { export const TRAIT_TYPE_CHOICES = {
"special-trait": "OATHHAMMER.TraitType.SpecialTrait", "special-trait": "OATHHAMMER.TraitType.SpecialTrait",
@@ -350,6 +374,7 @@ export const SYSTEM = {
RUNE_TYPE_CHOICES, RUNE_TYPE_CHOICES,
WEAPON_PROFICIENCY_GROUPS, WEAPON_PROFICIENCY_GROUPS,
WEAPON_TRAITS, WEAPON_TRAITS,
WEAPON_SPECIAL_PROPERTIES,
ARMOR_TYPE_CHOICES, ARMOR_TYPE_CHOICES,
ARMOR_TRAITS, ARMOR_TRAITS,
CURRENCY_CHOICES, CURRENCY_CHOICES,
@@ -358,6 +383,7 @@ export const SYSTEM = {
MAGIC_ITEM_TYPE_CHOICES, MAGIC_ITEM_TYPE_CHOICES,
MAGIC_QUALITY_CHOICES, MAGIC_QUALITY_CHOICES,
RARITY_CHOICES, RARITY_CHOICES,
RARITY_DV,
TRAIT_TYPE_CHOICES, TRAIT_TYPE_CHOICES,
TRAIT_USAGE_PERIOD, TRAIT_USAGE_PERIOD,
BUILDING_SKILL_CHOICES, BUILDING_SKILL_CHOICES,

View File

@@ -9,6 +9,5 @@ export { default as OathHammerMiracle } from "./miracle.mjs"
export { default as OathHammerMagicItem } from "./magic-item.mjs" export { default as OathHammerMagicItem } from "./magic-item.mjs"
export { default as OathHammerTrait } from "./trait.mjs" export { default as OathHammerTrait } from "./trait.mjs"
export { default as OathHammerOath } from "./oath.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 OathHammerClass } from "./class.mjs"
export { default as OathHammerBuilding } from "./building.mjs" export { default as OathHammerBuilding } from "./building.mjs"

View File

@@ -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 }) 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 // 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…) // Penalty: modifier to Acrobatics checks AND defense rolls (0, -1, -2, -3…)
schema.penalty = new fields.NumberField({ ...requiredInteger, initial: 0, min: -5, max: 0 }) schema.penalty = new fields.NumberField({ ...requiredInteger, initial: 0, min: -5, max: 0 })

View File

@@ -23,11 +23,10 @@ export default class OathHammerBuilding extends foundry.abstract.TypeDataModel {
// Monthly tax revenue formula ("3d6", "2d6", "1d3", "" = none) // Monthly tax revenue formula ("3d6", "2d6", "1d3", "" = none)
schema.taxRevenue = new fields.StringField({ required: true, nullable: false, initial: "" }) 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 }) 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.) // Additional GM notes (special conditions, upgrades, damage, etc.)
schema.notes = new fields.HTMLField({ required: false, textSearch: true }) schema.notes = new fields.HTMLField({ required: false, textSearch: true })

View File

@@ -9,6 +9,12 @@ export default class OathHammerCharacter extends foundry.abstract.TypeDataModel
schema.description = new fields.HTMLField({ required: true, textSearch: true }) schema.description = new fields.HTMLField({ required: true, textSearch: true })
schema.notes = 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({ const attributeField = () => new fields.SchemaField({
rank: new fields.NumberField({ ...requiredInteger, initial: 1, min: 1, max: 4 }) rank: new fields.NumberField({ ...requiredInteger, initial: 1, min: 1, max: 4 })
}) })

View File

@@ -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"]
}

View File

@@ -35,8 +35,7 @@ export default class OathHammerMagicItem extends foundry.abstract.TypeDataModel
}) })
schema.maxUses = new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }) 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 }) schema.equipped = new fields.BooleanField({ initial: false })

View File

@@ -17,10 +17,10 @@ export default class OathHammerWeapon extends foundry.abstract.TypeDataModel {
// usesMight=true → formula displayed as "M+2", "M-1", etc. // usesMight=true → formula displayed as "M+2", "M-1", etc.
// usesMight=false → formula displayed as e.g. "6" (fixed dice for bows) // usesMight=false → formula displayed as e.g. "6" (fixed dice for bows)
schema.usesMight = new fields.BooleanField({ required: true, initial: true }) 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 // 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 // Reach (melee, in ft: 5 / 10 / 15) — ignored for ranged/throwing
schema.reach = new fields.NumberField({ ...requiredInteger, initial: 5, min: 5 }) 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: [] } { 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) // Item slots (when stowed; 0 = does not occupy slots)
schema.slots = new fields.NumberField({ ...requiredInteger, initial: 1, min: 0 }) schema.slots = new fields.NumberField({ ...requiredInteger, initial: 1, min: 0 })

555
module/rolls.mjs Normal file
View File

@@ -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 `<span class="oh-die ${cssClass}${explodedClass}" title="${exploded ? "💥 Exploded" : ""}">${val}</span>`
}).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 ? `<div class="oh-roll-mods">${modParts.join(" · ")}</div>` : ""
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 = `
<div class="oh-roll-card">
<div class="oh-roll-header">${cardFlavor}</div>
<div class="oh-roll-info">
<span>${attrLabel} ${attrRank} + ${skillLabel} ${skillRank}</span>
<span>${colorEmoji} ${totalDice}d6 (${threshold}+)</span>
</div>
${modLine}
<div class="oh-roll-dice">${diceHtml}</div>
<div class="oh-roll-result ${resultClass}">
<span class="oh-roll-successes">${successes} / ${dv}</span>
<span class="oh-roll-verdict">${resultLabel}</span>
</div>
</div>
`
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 = `
<div class="oh-roll-card">
<div class="oh-roll-header">${itemName ?? game.i18n.localize("OATHHAMMER.Label.Rarity")}</div>
<div class="oh-roll-result roll-success">
<span class="oh-roll-verdict">${rarityLabel}${game.i18n.localize("OATHHAMMER.Roll.AutoSuccess")}</span>
</div>
</div>
`
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 `<span class="oh-die ${cssClass}${exploded ? " die-exploded" : ""}" title="${exploded ? "💥" : ""}">${val}</span>`
}).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 ? `<div class="oh-roll-mods">${modParts.join(" · ")}</div>` : ""
const content = `
<div class="oh-roll-card oh-weapon-card">
<div class="oh-roll-header">
<img src="${weapon.img}" class="oh-card-weapon-img" alt="${weapon.name}" />
<span>${weapon.name}${game.i18n.localize("OATHHAMMER.Dialog.Attack")}</span>
</div>
<div class="oh-roll-info">
<span>${skillLabel} (${attrLabel} ${attrRank}) + ${skillLabel} ${skillRank}</span>
<span>${colorEmoji} ${totalDice}d6 (${threshold}+)</span>
</div>
${modLine}
<div class="oh-roll-dice">${diceHtml}</div>
<div class="oh-roll-result">
<span class="oh-roll-successes">${successes} ${game.i18n.localize("OATHHAMMER.Roll.Successes")}</span>
</div>
<div class="oh-weapon-damage-btn-row">
<button type="button" class="oh-roll-damage-btn" data-action="rollWeaponDamage">
${game.i18n.localize("OATHHAMMER.Roll.RollDamage")} (SV ${successes})
</button>
</div>
</div>
`
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 ? `<div class="oh-roll-mods">${modParts.join(" · ")}</div>` : ""
const apNote = sys.ap > 0 ? `<span class="oh-ap-note">AP ${sys.ap}</span>` : ""
const content = `
<div class="oh-roll-card oh-weapon-card">
<div class="oh-roll-header">
<img src="${weapon.img}" class="oh-card-weapon-img" alt="${weapon.name}" />
<span>${weapon.name}${game.i18n.localize("OATHHAMMER.Dialog.Damage")}</span>
</div>
<div class="oh-roll-info">
<span>${sys.damageLabel} = ${baseDamageDice}d6 ${sv > 0 ? `+${sv} SV` : ""}</span>
<span>${colorEmoji} ${totalDice}d6 (${threshold}+) ${colorLabel}</span>
</div>
${modLine}
<div class="oh-roll-dice">${diceHtml}</div>
<div class="oh-roll-result">
<span class="oh-roll-successes">${successes} ${game.i18n.localize("OATHHAMMER.Roll.Damage")}</span>
${apNote}
</div>
</div>
`
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 ? `<div class="oh-roll-mods">${modParts.join(" · ")}</div>` : ""
const stressLine = `<div class="oh-stress-line${isBlocked ? " stress-blocked" : ""}">
🧠 ${game.i18n.localize("OATHHAMMER.Label.ArcaneStress")}: +${totalStressGain}
(${onesCount} × 1s + ${stressCost} enh.) → <strong>${newStress}/${stressMax}</strong>
${isBlocked ? `${game.i18n.localize("OATHHAMMER.Label.StressBlocked")}` : ""}
</div>`
const content = `
<div class="oh-roll-card oh-spell-card">
<div class="oh-roll-header">
<img src="${spell.img}" class="oh-card-weapon-img" alt="${spell.name}" />
<span>${spell.name} (DV ${dv})</span>
</div>
<div class="oh-roll-info">
<span>${attrLabel} ${intRank} + ${skillLabel} ${magicRank}</span>
<span>${colorEmoji} ${totalDice}d6 (${threshold}+)</span>
</div>
${modLine}
<div class="oh-roll-dice">${diceHtml}</div>
<div class="oh-roll-result ${resultClass}">
<span class="oh-roll-successes">${successes} / ${dv}</span>
<span class="oh-roll-verdict">${resultLabel}</span>
</div>
${stressLine}
</div>
`
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
? `<div class="oh-roll-mods">${bonus > 0 ? "+" : ""}${bonus} ${game.i18n.localize("OATHHAMMER.Dialog.Modifier")}</div>`
: ""
const blockedLine = !isSuccess
? `<div class="oh-miracle-blocked">⚠ ${game.i18n.localize("OATHHAMMER.Roll.MiracleBlocked")}</div>`
: ""
const dvNote = isRitual
? `DV ${dv} (${game.i18n.localize("OATHHAMMER.Label.Ritual")})`
: `DV ${dv}`
const content = `
<div class="oh-roll-card oh-miracle-card">
<div class="oh-roll-header">
<img src="${miracle.img}" class="oh-card-weapon-img" alt="${miracle.name}" />
<span>${miracle.name} (${dvNote})</span>
</div>
<div class="oh-roll-info">
<span>${attrLabel} ${wpRank} + ${skillLabel} ${magicRank}</span>
<span>${colorEmoji} ${totalDice}d6 (${threshold}+)</span>
</div>
${modLine}
<div class="oh-roll-dice">${diceHtml}</div>
<div class="oh-roll-result ${resultClass}">
<span class="oh-roll-successes">${successes} / ${dv}</span>
<span class="oh-roll-verdict">${resultLabel}</span>
</div>
${blockedLine}
</div>
`
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) : "" }

View File

@@ -5,6 +5,8 @@ import * as models from "./module/models/_module.mjs"
import * as documents from "./module/documents/_module.mjs" import * as documents from "./module/documents/_module.mjs"
import * as applications from "./module/applications/_module.mjs" import * as applications from "./module/applications/_module.mjs"
import OathHammerUtils from "./module/utils.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 () { Hooks.once("init", function () {
console.info(SYSTEM.ASCII) console.info(SYSTEM.ASCII)
@@ -32,7 +34,6 @@ Hooks.once("init", function () {
"magic-item": models.OathHammerMagicItem, "magic-item": models.OathHammerMagicItem,
trait: models.OathHammerTrait, trait: models.OathHammerTrait,
oath: models.OathHammerOath, oath: models.OathHammerOath,
lineage: models.OathHammerLineage,
"class": models.OathHammerClass, "class": models.OathHammerClass,
building: models.OathHammerBuilding 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.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.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.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.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" }) 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") console.info("Oath Hammer | System Initialized")
}) })
Hooks.once("ready", function () { Hooks.once("ready", async function () {
console.info("Oath Hammer | System Ready") 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)
})
}) })

View File

@@ -25,7 +25,8 @@
"character": { "character": {
"htmlFields": [ "htmlFields": [
"description", "description",
"notes" "notes",
"lineage.traits"
] ]
}, },
"npc": { "npc": {
@@ -85,12 +86,6 @@
"bane" "bane"
] ]
}, },
"lineage": {
"htmlFields": [
"description",
"traits"
]
},
"class": { "class": {
"htmlFields": [ "htmlFields": [
"description", "description",

View File

@@ -44,10 +44,10 @@
<input type="checkbox" class="item-equipped-cb" data-item-id="{{weapon.id}}" {{checked weapon.system.equipped}} {{#if ../isPlayMode}}disabled{{/if}}> <input type="checkbox" class="item-equipped-cb" data-item-id="{{weapon.id}}" {{checked weapon.system.equipped}} {{#if ../isPlayMode}}disabled{{/if}}>
</div> </div>
<div class="item-actions"> <div class="item-actions">
{{#unless ../isPlayMode}} <a data-action="attackWeapon" data-item-id="{{weapon.id}}" title="{{localize "OATHHAMMER.Dialog.Attack"}}"><i class="fa-solid fa-khanda"></i></a>
<a data-action="damageWeapon" data-item-id="{{weapon.id}}" title="{{localize "OATHHAMMER.Dialog.Damage"}}"><i class="fa-solid fa-burst"></i></a>
<a data-action="edit" data-item-id="{{weapon.id}}" data-item-uuid="{{weapon.uuid}}"><i class="fa-solid fa-edit"></i></a> <a data-action="edit" data-item-id="{{weapon.id}}" data-item-uuid="{{weapon.uuid}}"><i class="fa-solid fa-edit"></i></a>
<a data-action="delete" data-item-id="{{weapon.id}}" data-item-uuid="{{weapon.uuid}}"><i class="fa-solid fa-trash"></i></a> <a data-action="delete" data-item-id="{{weapon.id}}" data-item-uuid="{{weapon.uuid}}"><i class="fa-solid fa-trash"></i></a>
{{/unless}}
</div> </div>
</li> </li>
{{/each}} {{/each}}
@@ -83,10 +83,8 @@
<input type="checkbox" class="item-equipped-cb" data-item-id="{{armor.id}}" {{checked armor.system.equipped}} {{#if ../isPlayMode}}disabled{{/if}}> <input type="checkbox" class="item-equipped-cb" data-item-id="{{armor.id}}" {{checked armor.system.equipped}} {{#if ../isPlayMode}}disabled{{/if}}>
</div> </div>
<div class="item-actions"> <div class="item-actions">
{{#unless ../isPlayMode}}
<a data-action="edit" data-item-id="{{armor.id}}" data-item-uuid="{{armor.uuid}}"><i class="fa-solid fa-edit"></i></a> <a data-action="edit" data-item-id="{{armor.id}}" data-item-uuid="{{armor.uuid}}"><i class="fa-solid fa-edit"></i></a>
<a data-action="delete" data-item-id="{{armor.id}}" data-item-uuid="{{armor.uuid}}"><i class="fa-solid fa-trash"></i></a> <a data-action="delete" data-item-id="{{armor.id}}" data-item-uuid="{{armor.uuid}}"><i class="fa-solid fa-trash"></i></a>
{{/unless}}
</div> </div>
</li> </li>
{{/each}} {{/each}}
@@ -112,10 +110,8 @@
<span class="item-name">{{ammo.name}}</span> <span class="item-name">{{ammo.name}}</span>
<span class="item-detail">×{{ammo.system.quantity}}</span> <span class="item-detail">×{{ammo.system.quantity}}</span>
<div class="item-actions"> <div class="item-actions">
{{#unless ../isPlayMode}}
<a data-action="edit" data-item-id="{{ammo.id}}" data-item-uuid="{{ammo.uuid}}"><i class="fa-solid fa-edit"></i></a> <a data-action="edit" data-item-id="{{ammo.id}}" data-item-uuid="{{ammo.uuid}}"><i class="fa-solid fa-edit"></i></a>
<a data-action="delete" data-item-id="{{ammo.id}}" data-item-uuid="{{ammo.uuid}}"><i class="fa-solid fa-trash"></i></a> <a data-action="delete" data-item-id="{{ammo.id}}" data-item-uuid="{{ammo.uuid}}"><i class="fa-solid fa-trash"></i></a>
{{/unless}}
</div> </div>
</li> </li>
{{/each}} {{/each}}

View File

@@ -36,10 +36,8 @@
<span class="item-type">{{localize equip.system.itemType}}</span> <span class="item-type">{{localize equip.system.itemType}}</span>
<span class="item-detail">{{equip.system.quantity}}</span> <span class="item-detail">{{equip.system.quantity}}</span>
<div class="item-actions"> <div class="item-actions">
{{#unless ../isPlayMode}}
<a data-action="edit" data-item-id="{{equip.id}}" data-item-uuid="{{equip.uuid}}"><i class="fa-solid fa-edit"></i></a> <a data-action="edit" data-item-id="{{equip.id}}" data-item-uuid="{{equip.uuid}}"><i class="fa-solid fa-edit"></i></a>
<a data-action="delete" data-item-id="{{equip.id}}" data-item-uuid="{{equip.uuid}}"><i class="fa-solid fa-trash"></i></a> <a data-action="delete" data-item-id="{{equip.id}}" data-item-uuid="{{equip.uuid}}"><i class="fa-solid fa-trash"></i></a>
{{/unless}}
</div> </div>
</li> </li>
{{/each}} {{/each}}
@@ -65,10 +63,8 @@
<span class="item-name">{{mi.name}}</span> <span class="item-name">{{mi.name}}</span>
<span class="item-type">{{localize mi.system.rarity}}</span> <span class="item-type">{{localize mi.system.rarity}}</span>
<div class="item-actions"> <div class="item-actions">
{{#unless ../isPlayMode}}
<a data-action="edit" data-item-id="{{mi.id}}" data-item-uuid="{{mi.uuid}}"><i class="fa-solid fa-edit"></i></a> <a data-action="edit" data-item-id="{{mi.id}}" data-item-uuid="{{mi.uuid}}"><i class="fa-solid fa-edit"></i></a>
<a data-action="delete" data-item-id="{{mi.id}}" data-item-uuid="{{mi.uuid}}"><i class="fa-solid fa-trash"></i></a> <a data-action="delete" data-item-id="{{mi.id}}" data-item-uuid="{{mi.uuid}}"><i class="fa-solid fa-trash"></i></a>
{{/unless}}
</div> </div>
</li> </li>
{{/each}} {{/each}}
@@ -91,10 +87,8 @@
<span class="item-name">{{cond.name}}</span> <span class="item-name">{{cond.name}}</span>
<span class="item-type">{{localize cond.system.conditionType}}</span> <span class="item-type">{{localize cond.system.conditionType}}</span>
<div class="item-actions"> <div class="item-actions">
{{#unless ../isPlayMode}}
<a data-action="edit" data-item-id="{{cond.id}}" data-item-uuid="{{cond.uuid}}"><i class="fa-solid fa-edit"></i></a> <a data-action="edit" data-item-id="{{cond.id}}" data-item-uuid="{{cond.uuid}}"><i class="fa-solid fa-edit"></i></a>
<a data-action="delete" data-item-id="{{cond.id}}" data-item-uuid="{{cond.uuid}}"><i class="fa-solid fa-trash"></i></a> <a data-action="delete" data-item-id="{{cond.id}}" data-item-uuid="{{cond.uuid}}"><i class="fa-solid fa-trash"></i></a>
{{/unless}}
</div> </div>
</li> </li>
{{/each}} {{/each}}

View File

@@ -21,10 +21,8 @@
<span class="item-type">{{trait._typeLabel}}</span> <span class="item-type">{{trait._typeLabel}}</span>
<span class="item-usage">{{trait._usageLabel}}</span> <span class="item-usage">{{trait._usageLabel}}</span>
<div class="item-actions"> <div class="item-actions">
{{#unless ../isPlayMode}}
<a data-action="edit" data-item-id="{{trait.id}}" data-item-uuid="{{trait.uuid}}"><i class="fa-solid fa-edit"></i></a> <a data-action="edit" data-item-id="{{trait.id}}" data-item-uuid="{{trait.uuid}}"><i class="fa-solid fa-edit"></i></a>
<a data-action="delete" data-item-id="{{trait.id}}" data-item-uuid="{{trait.uuid}}"><i class="fa-solid fa-trash"></i></a> <a data-action="delete" data-item-id="{{trait.id}}" data-item-uuid="{{trait.uuid}}"><i class="fa-solid fa-trash"></i></a>
{{/unless}}
</div> </div>
</li> </li>
{{/each}} {{/each}}
@@ -49,10 +47,8 @@
<span class="item-type">{{oath._typeLabel}}</span> <span class="item-type">{{oath._typeLabel}}</span>
<span class="item-violated">{{#if oath._violated}}<i class="fa-solid fa-circle-xmark"></i>{{else}}<i class="fa-regular fa-circle-check"></i>{{/if}}</span> <span class="item-violated">{{#if oath._violated}}<i class="fa-solid fa-circle-xmark"></i>{{else}}<i class="fa-regular fa-circle-check"></i>{{/if}}</span>
<div class="item-actions"> <div class="item-actions">
{{#unless ../isPlayMode}}
<a data-action="edit" data-item-id="{{oath.id}}" data-item-uuid="{{oath.uuid}}"><i class="fa-solid fa-edit"></i></a> <a data-action="edit" data-item-id="{{oath.id}}" data-item-uuid="{{oath.uuid}}"><i class="fa-solid fa-edit"></i></a>
<a data-action="delete" data-item-id="{{oath.id}}" data-item-uuid="{{oath.uuid}}"><i class="fa-solid fa-trash"></i></a> <a data-action="delete" data-item-id="{{oath.id}}" data-item-uuid="{{oath.uuid}}"><i class="fa-solid fa-trash"></i></a>
{{/unless}}
</div> </div>
</li> </li>
{{/each}} {{/each}}

View File

@@ -15,7 +15,7 @@
<li class="item-list-header"> <li class="item-list-header">
<span></span> <span></span>
<span class="col-name">{{localize "OATHHAMMER.Label.Name"}}</span> <span class="col-name">{{localize "OATHHAMMER.Label.Name"}}</span>
<span>Lv.</span> <span>DV</span>
<span>{{localize "OATHHAMMER.Label.Tradition"}}</span> <span>{{localize "OATHHAMMER.Label.Tradition"}}</span>
<span>AS</span> <span>AS</span>
<span></span> <span></span>
@@ -24,14 +24,13 @@
<li class="item-entry" data-item-id="{{spell.id}}" data-item-uuid="{{spell.uuid}}"> <li class="item-entry" data-item-id="{{spell.id}}" data-item-uuid="{{spell.uuid}}">
<img src="{{spell.img}}" class="item-img" /> <img src="{{spell.img}}" class="item-img" />
<span class="item-name">{{spell.name}}</span> <span class="item-name">{{spell.name}}</span>
<span class="item-detail">{{spell.system.level}}</span> <span class="item-detail">{{spell.system.difficultyValue}}</span>
<span class="item-type">{{localize spell.system.tradition}}</span> <span class="item-type">{{localize spell.system.tradition}}</span>
<span class="item-detail">{{spell.system.arcaneStress}}</span> <span class="item-detail"></span>
<div class="item-actions"> <div class="item-actions">
{{#unless ../isPlayMode}} <a data-action="castSpell" data-item-id="{{spell.id}}" title="{{localize 'OATHHAMMER.Action.CastSpell'}}"><i class="fa-solid fa-wand-sparkles spell-cast-icon"></i></a>
<a data-action="edit" data-item-id="{{spell.id}}" data-item-uuid="{{spell.uuid}}"><i class="fa-solid fa-edit"></i></a> <a data-action="edit" data-item-id="{{spell.id}}" data-item-uuid="{{spell.uuid}}"><i class="fa-solid fa-edit"></i></a>
<a data-action="delete" data-item-id="{{spell.id}}" data-item-uuid="{{spell.uuid}}"><i class="fa-solid fa-trash"></i></a> <a data-action="delete" data-item-id="{{spell.id}}" data-item-uuid="{{spell.uuid}}"><i class="fa-solid fa-trash"></i></a>
{{/unless}}
</div> </div>
</li> </li>
{{/each}} {{/each}}
@@ -49,19 +48,18 @@
<li class="item-list-header"> <li class="item-list-header">
<span></span> <span></span>
<span class="col-name">{{localize "OATHHAMMER.Label.Name"}}</span> <span class="col-name">{{localize "OATHHAMMER.Label.Name"}}</span>
<span>{{localize "OATHHAMMER.Label.Piety"}}</span> <span>{{localize "OATHHAMMER.Label.DivineTradition"}}</span>
<span></span> <span></span>
</li> </li>
{{#each miracles as |miracle|}} {{#each miracles as |miracle|}}
<li class="item-entry" data-item-id="{{miracle.id}}" data-item-uuid="{{miracle.uuid}}"> <li class="item-entry" data-item-id="{{miracle.id}}" data-item-uuid="{{miracle.uuid}}">
<img src="{{miracle.img}}" class="item-img" /> <img src="{{miracle.img}}" class="item-img" />
<span class="item-name">{{miracle.name}}</span> <span class="item-name">{{miracle.name}}</span>
<span class="item-detail">{{miracle.system.piety}}</span> <span class="item-detail">{{miracle.system.divineTradition}}</span>
<div class="item-actions"> <div class="item-actions">
{{#unless ../isPlayMode}} <a data-action="castMiracle" data-item-id="{{miracle.id}}" title="{{localize 'OATHHAMMER.Action.InvokeMiracle'}}"><i class="fa-solid fa-hands-praying miracle-cast-icon"></i></a>
<a data-action="edit" data-item-id="{{miracle.id}}" data-item-uuid="{{miracle.uuid}}"><i class="fa-solid fa-edit"></i></a> <a data-action="edit" data-item-id="{{miracle.id}}" data-item-uuid="{{miracle.uuid}}"><i class="fa-solid fa-edit"></i></a>
<a data-action="delete" data-item-id="{{miracle.id}}" data-item-uuid="{{miracle.uuid}}"><i class="fa-solid fa-trash"></i></a> <a data-action="delete" data-item-id="{{miracle.id}}" data-item-uuid="{{miracle.uuid}}"><i class="fa-solid fa-trash"></i></a>
{{/unless}}
</div> </div>
</li> </li>
{{/each}} {{/each}}

View File

@@ -21,18 +21,9 @@
{{!-- Row 2: Identity bar (lineage + class + level/xp) --}} {{!-- Row 2: Identity bar (lineage + class + level/xp) --}}
<div class="character-identity-bar"> <div class="character-identity-bar">
<div class="identity-slot lineage-slot {{#unless lineage}}empty{{/unless}}" data-drop-type="lineage"> <div class="identity-slot lineage-slot">
{{#if lineage}}
<img src="{{lineage.img}}" class="identity-img" data-item-id="{{lineage.id}}" data-item-uuid="{{lineage.uuid}}" />
<span class="identity-name">{{lineage.name}}</span>
{{#unless isPlayMode}}
<a data-action="edit" data-item-id="{{lineage.id}}" data-item-uuid="{{lineage.uuid}}"><i class="fa-solid fa-edit"></i></a>
<a data-action="delete" data-item-id="{{lineage.id}}" data-item-uuid="{{lineage.uuid}}"><i class="fa-solid fa-trash"></i></a>
{{/unless}}
{{else}}
<i class="fa-solid fa-person slot-icon"></i> <i class="fa-solid fa-person slot-icon"></i>
<span class="slot-placeholder">{{localize "OATHHAMMER.Label.DropLineage"}}</span> {{formInput systemFields.lineage.fields.name value=system.lineage.name name="system.lineage.name" placeholder=(localize "OATHHAMMER.Label.Lineage") disabled=isPlayMode}}
{{/if}}
</div> </div>
<div class="identity-slot class-slot {{#unless characterClass}}empty{{/unless}}" data-drop-type="class"> <div class="identity-slot class-slot {{#unless characterClass}}empty{{/unless}}" data-drop-type="class">
{{#if characterClass}} {{#if characterClass}}

View File

@@ -16,7 +16,9 @@
</div> </div>
{{#each group.skillData as |skill|}} {{#each group.skillData as |skill|}}
<div class="skill-row"> <div class="skill-row">
<label class="skill-name-col">{{localize skill.label}}</label> <a class="skill-name-col rollable" data-action="rollSkill" data-skill="{{skill.key}}" data-tooltip="{{localize 'OATHHAMMER.Dialog.RollSkill'}}">
<i class="fa-solid fa-dice skill-roll-icon"></i>{{localize skill.label}}
</a>
<div class="skill-rank-col"> <div class="skill-rank-col">
<select name="{{skill.rankName}}" {{#if ../../isPlayMode}}disabled{{/if}}> <select name="{{skill.rankName}}" {{#if ../../isPlayMode}}disabled{{/if}}>
{{#each skill.rankOptions as |opt|}} {{#each skill.rankOptions as |opt|}}

View File

@@ -8,9 +8,7 @@
{{formField systemFields.ammoType value=system.ammoType name="system.ammoType" localize=true}} {{formField systemFields.ammoType value=system.ammoType name="system.ammoType" localize=true}}
{{formField systemFields.quantity value=system.quantity name="system.quantity"}} {{formField systemFields.quantity value=system.quantity name="system.quantity"}}
{{formField systemFields.rarity value=system.rarity name="system.rarity" localize=true}} {{formField systemFields.rarity value=system.rarity name="system.rarity" localize=true}}
</div> <a data-action="rollRarity" class="rarity-roll-btn" data-tooltip="{{localize 'OATHHAMMER.Roll.RarityCheck'}}"><i class="fa-solid fa-dice"></i> {{localize "OATHHAMMER.Roll.RarityCheck"}}</a>
<div class="align-top">
{{formField systemFields.cost value=system.cost name="system.cost"}}
{{formField systemFields.currency value=system.currency name="system.currency" localize=true}} {{formField systemFields.currency value=system.currency name="system.currency" localize=true}}
</div> </div>
</div> </div>

View File

@@ -48,6 +48,7 @@
name="system.rarity" name="system.rarity"
localize=true localize=true
}} }}
<a data-action="rollRarity" class="rarity-roll-btn" data-tooltip="{{localize 'OATHHAMMER.Roll.RarityCheck'}}"><i class="fa-solid fa-dice"></i> {{localize "OATHHAMMER.Roll.RarityCheck"}}</a>
{{formField {{formField
systemFields.isMagic systemFields.isMagic
value=system.isMagic value=system.isMagic

View File

@@ -9,7 +9,7 @@
{{formField systemFields.skillCheck value=system.skillCheck name="system.skillCheck" localize=true}} {{formField systemFields.skillCheck value=system.skillCheck name="system.skillCheck" localize=true}}
{{formField systemFields.cost value=system.cost name="system.cost"}} {{formField systemFields.cost value=system.cost name="system.cost"}}
{{formField systemFields.buildTime value=system.buildTime name="system.buildTime"}} {{formField systemFields.buildTime value=system.buildTime name="system.buildTime"}}
{{formField systemFields.settlement value=system.settlement name="system.settlement"}}
</div> </div>
<div class="building-col"> <div class="building-col">
{{formField systemFields.taxRevenue value=system.taxRevenue name="system.taxRevenue"}} {{formField systemFields.taxRevenue value=system.taxRevenue name="system.taxRevenue"}}

View File

@@ -9,6 +9,7 @@
{{formField systemFields.quantity value=system.quantity name="system.quantity"}} {{formField systemFields.quantity value=system.quantity name="system.quantity"}}
{{formField systemFields.slots value=system.slots name="system.slots"}} {{formField systemFields.slots value=system.slots name="system.slots"}}
{{formField systemFields.rarity value=system.rarity name="system.rarity" localize=true}} {{formField systemFields.rarity value=system.rarity name="system.rarity" localize=true}}
<a data-action="rollRarity" class="rarity-roll-btn" data-tooltip="{{localize 'OATHHAMMER.Roll.RarityCheck'}}"><i class="fa-solid fa-dice"></i> {{localize "OATHHAMMER.Roll.RarityCheck"}}</a>
{{#if system.lightRadius}} {{#if system.lightRadius}}
{{formField systemFields.lightRadius value=system.lightRadius name="system.lightRadius"}} {{formField systemFields.lightRadius value=system.lightRadius name="system.lightRadius"}}
{{/if}} {{/if}}

View File

@@ -1,22 +0,0 @@
<section class="item-sheet-common">
<div class="header">
<img class="item-img" src="{{item.img}}" data-edit="img" data-action="editImage" data-tooltip="{{item.name}}" />
{{formInput fields.name value=source.name}}
</div>
<div class="flexrow stats-row">
<div class="form-group">
{{formField systemFields.movement value=system.movement name="system.movement"}}
</div>
<div class="form-group">
{{formField systemFields.gritModifier value=system.gritModifier name="system.gritModifier"}}
</div>
</div>
<fieldset>
<legend>{{localize "OATHHAMMER.Label.Description"}}</legend>
{{formInput systemFields.description enriched=enrichedDescription value=system.description name="system.description" toggled=true}}
</fieldset>
<fieldset>
<legend>{{localize "OATHHAMMER.Label.Traits"}}</legend>
{{formInput systemFields.traits enriched=enrichedTraits value=system.traits name="system.traits" toggled=true}}
</fieldset>
</section>

View File

@@ -16,7 +16,7 @@
{{#unless (eq system.usagePeriod "none")}} {{#unless (eq system.usagePeriod "none")}}
{{formField systemFields.maxUses value=system.maxUses name="system.maxUses"}} {{formField systemFields.maxUses value=system.maxUses name="system.maxUses"}}
{{/unless}} {{/unless}}
{{formField systemFields.slots value=system.slots name="system.slots"}}
{{formField systemFields.equipped value=system.equipped name="system.equipped"}} {{formField systemFields.equipped value=system.equipped name="system.equipped"}}
</div> </div>
</div> </div>

View File

@@ -29,8 +29,10 @@
</div> </div>
<div class="align-top"> <div class="align-top">
{{formField systemFields.traits value=system.traits name="system.traits" localize=true}} {{formField systemFields.traits value=system.traits name="system.traits" localize=true}}
{{formField systemFields.specialProperties value=system.specialProperties name="system.specialProperties" localize=true}}
{{formField systemFields.slots value=system.slots name="system.slots"}} {{formField systemFields.slots value=system.slots name="system.slots"}}
{{formField systemFields.rarity value=system.rarity name="system.rarity" localize=true}} {{formField systemFields.rarity value=system.rarity name="system.rarity" localize=true}}
<a data-action="rollRarity" class="rarity-roll-btn" data-tooltip="{{localize 'OATHHAMMER.Roll.RarityCheck'}}"><i class="fa-solid fa-dice"></i> {{localize "OATHHAMMER.Roll.RarityCheck"}}</a>
{{formField systemFields.isMagic value=system.isMagic name="system.isMagic"}} {{formField systemFields.isMagic value=system.isMagic name="system.isMagic"}}
{{formField systemFields.equipped value=system.equipped name="system.equipped"}} {{formField systemFields.equipped value=system.equipped name="system.equipped"}}
{{formField systemFields.cost value=system.cost name="system.cost"}} {{formField systemFields.cost value=system.cost name="system.cost"}}

View File

@@ -0,0 +1,66 @@
<div class="oh-roll-dialog oh-miracle-dialog">
{{!-- Miracle header --}}
<div class="spell-header">
<img src="{{miracleImg}}" class="weapon-img-sm" alt="{{miracleName}}" />
<div class="spell-header-info">
<span class="weapon-name-lg">{{miracleName}}</span>
<div class="weapon-badges">
{{#if isRitual}}
<span class="dv-badge">DV {{dv}}{{localize "OATHHAMMER.Label.Ritual"}}</span>
{{else}}
<span class="dv-badge">DV = {{localize "OATHHAMMER.Dialog.MiracleDVNote"}}</span>
{{/if}}
<span class="tradition-badge">{{traditionLabel}}</span>
{{#if range}}<span class="range-badge">{{range}}</span>{{/if}}
{{#if duration}}<span class="duration-badge">{{duration}}</span>{{/if}}
</div>
{{#if spellSave}}<div class="save-info">{{localize "OATHHAMMER.Label.SpellSave"}}: {{spellSave}}</div>{{/if}}
</div>
</div>
{{!-- Failure warning --}}
<div class="miracle-warning">
<i class="fa-solid fa-triangle-exclamation"></i>
{{localize "OATHHAMMER.Dialog.MiracleFailWarning"}}
</div>
{{!-- Cast options --}}
<fieldset class="attack-options-block">
<legend>{{localize "OATHHAMMER.Dialog.InvokeOptions"}}</legend>
<div class="pool-info-line">
{{localize "OATHHAMMER.Skill.Magic"}} ({{localize "OATHHAMMER.Attribute.Willpower"}} {{wpRank}})
+ {{localize "OATHHAMMER.Label.SkillRank"}} {{magicRank}}
= <strong>{{basePool}}d6</strong>
</div>
{{#unless isRitual}}
<div class="roll-option-row">
<label>{{localize "OATHHAMMER.Dialog.MiracleCount"}}</label>
<select name="miracleCount">
{{#each miracleCountOptions}}<option value="{{value}}"{{#if selected}} selected{{/if}}>{{label}}</option>{{/each}}
</select>
<span class="roll-option-hint">{{localize "OATHHAMMER.Dialog.MiracleCountHint"}}</span>
</div>
{{/unless}}
<div class="roll-option-row">
<label>{{localize "OATHHAMMER.Dialog.Modifier"}}</label>
<select name="bonus">
{{#each bonusOptions}}<option value="{{value}}"{{#if selected}} selected{{/if}}>{{label}}</option>{{/each}}
</select>
<span class="roll-option-hint">{{localize "OATHHAMMER.Dialog.AttackModifierHint"}}</span>
</div>
</fieldset>
{{!-- Visibility --}}
<fieldset class="roll-visibility-block">
<legend>{{localize "OATHHAMMER.Dialog.Visibility"}}</legend>
<select name="visibility">
{{selectOptions rollModes selected=visibility localize=true}}
</select>
</fieldset>
</div>

83
templates/roll-dialog.hbs Normal file
View File

@@ -0,0 +1,83 @@
<div class="oh-roll-dialog">
{{!-- Actor name --}}
<div class="roll-actor-name">{{actorName}}</div>
{{!-- Skill / pool info --}}
<fieldset class="roll-info-block">
<legend>{{localize "OATHHAMMER.Dialog.SkillCheck"}}</legend>
<div class="roll-skill-line">
<span class="roll-skill-name">{{skillLabel}}</span>
<span class="roll-attr-info">
{{attrLabel}} ({{attrRank}}) + {{localize "OATHHAMMER.Label.SkillRank"}} ({{skillRank}})
{{#if skillMod}}+ {{localize "OATHHAMMER.Label.SkillModifier"}} ({{skillMod}}){{/if}}
</span>
</div>
<div class="roll-dice-preview">
<span class="roll-pool">{{baseTotal}}d6</span>
<span class="roll-color-badge color-badge-{{colorType}}">{{colorLabel}} &nbsp;{{threshold}}+</span>
</div>
{{!-- Attribute override for dual-attribute skills (Defense, Fighting, Magic) --}}
{{#if isDualAttr}}
<div class="roll-dual-attr">
<label>{{localize "OATHHAMMER.Dialog.Attribute"}}</label>
<select name="attrOverride">
{{#each attrOptions as |opt|}}
<option value="{{opt.value}}" {{#if opt.selected}}selected{{/if}}>{{opt.label}}</option>
{{/each}}
</select>
</div>
{{/if}}
</fieldset>
{{!-- Roll options --}}
<fieldset class="roll-options-block">
<legend>{{localize "OATHHAMMER.Dialog.Options"}}</legend>
<div class="roll-option-row">
<label>{{localize "OATHHAMMER.Dialog.DV"}}</label>
<select name="dv">
{{#each dvOptions}}<option value="{{value}}"{{#if selected}} selected{{/if}}>{{label}}</option>{{/each}}
</select>
<span class="roll-option-hint">{{localize "OATHHAMMER.Dialog.DVHint"}}</span>
</div>
<div class="roll-option-row">
<label>{{localize "OATHHAMMER.Dialog.Modifier"}}</label>
<select name="bonus">
{{#each bonusOptions}}<option value="{{value}}"{{#if selected}} selected{{/if}}>{{label}}</option>{{/each}}
</select>
<span class="roll-option-hint">{{localize "OATHHAMMER.Dialog.ModifierHint"}}</span>
</div>
<div class="roll-option-row">
<label>{{localize "OATHHAMMER.Dialog.Supporters"}}</label>
<select name="supporters">
{{#each supportersOptions}}<option value="{{value}}"{{#if selected}} selected{{/if}}>{{label}}</option>{{/each}}
</select>
<span class="roll-option-hint">{{localize "OATHHAMMER.Dialog.SupportersHint"}}</span>
</div>
{{#if availableLuck}}
<div class="roll-option-row roll-option-luck">
<label>{{localize "OATHHAMMER.Dialog.LuckSpend"}} <i class="fa-solid fa-clover luck-icon"></i></label>
<select name="luckSpend">
{{#each luckOptions}}<option value="{{value}}"{{#if selected}} selected{{/if}}>{{label}}</option>{{/each}}
</select>
<span class="roll-option-hint">{{localize "OATHHAMMER.Dialog.LuckHint"}} ({{availableLuck}} {{localize "OATHHAMMER.Dialog.Available"}})</span>
</div>
{{/if}}
</fieldset>
{{!-- Visibility --}}
<fieldset class="roll-visibility-block">
<legend>{{localize "OATHHAMMER.Dialog.Visibility"}}</legend>
<select name="visibility">
{{selectOptions rollModes selected=visibility localize=true}}
</select>
</fieldset>
</div>

View File

@@ -0,0 +1,80 @@
<div class="oh-roll-dialog oh-spell-dialog">
{{!-- Spell header --}}
<div class="spell-header">
<img src="{{spellImg}}" class="weapon-img-sm" alt="{{spellName}}" />
<div class="spell-header-info">
<span class="weapon-name-lg">{{spellName}}</span>
<div class="weapon-badges">
<span class="dv-badge">DV {{dv}}</span>
<span class="tradition-badge">{{traditionLabel}}</span>
{{#if isRitual}}<span class="ritual-badge">{{localize "OATHHAMMER.Label.Ritual"}}</span>{{/if}}
{{#if isMagicMissile}}<span class="missile-badge">{{localize "OATHHAMMER.Label.MagicMissile"}}</span>{{/if}}
{{#if range}}<span class="range-badge">{{range}}</span>{{/if}}
{{#if duration}}<span class="duration-badge">{{duration}}</span>{{/if}}
</div>
{{#if spellSave}}<div class="save-info">{{localize "OATHHAMMER.Label.SpellSave"}}: {{spellSave}}</div>{{/if}}
</div>
</div>
{{!-- Arcane stress tracker --}}
<div class="stress-tracker {{#if isOverThreshold}}stress-danger{{/if}}">
<i class="fa-solid fa-brain"></i>
<span>{{localize "OATHHAMMER.Label.ArcaneStress"}}: <strong>{{currentStress}} / {{stressThreshold}}</strong></span>
{{#if isOverThreshold}}<span class="stress-warning">⚠ {{localize "OATHHAMMER.Label.StressBlocked"}}</span>{{/if}}
</div>
{{!-- Cast options --}}
<fieldset class="attack-options-block">
<legend>{{localize "OATHHAMMER.Dialog.CastOptions"}}</legend>
<div class="pool-info-line">
{{localize "OATHHAMMER.Skill.Magic"}} ({{localize "OATHHAMMER.Attribute.Intelligence"}} {{intRank}})
+ {{localize "OATHHAMMER.Label.SkillRank"}} {{magicRank}}
= <strong>{{basePool}}d6</strong>
</div>
<div class="roll-option-row">
<label>{{localize "OATHHAMMER.Dialog.Enhancement"}}</label>
<select name="enhancement" class="enhancement-select">
{{#each enhancementOptions}}<option value="{{value}}"{{#if selected}} selected{{/if}}>{{label}}</option>{{/each}}
</select>
</div>
{{#if isElemental}}
<div class="roll-option-row">
<label>{{localize "OATHHAMMER.Dialog.ElementCondition"}}</label>
<select name="elementalBonus">
<option value="0">{{localize "OATHHAMMER.Dialog.ElementNone"}}</option>
<option value="1">{{localize "OATHHAMMER.Dialog.ElementMet"}} (+1)</option>
</select>
</div>
{{/if}}
<div class="roll-option-row">
<label>{{localize "OATHHAMMER.Dialog.Modifier"}}</label>
<select name="bonus">
{{#each bonusOptions}}<option value="{{value}}"{{#if selected}} selected{{/if}}>{{label}}</option>{{/each}}
</select>
<span class="roll-option-hint">{{localize "OATHHAMMER.Dialog.AttackModifierHint"}}</span>
</div>
<div class="roll-option-row">
<label>{{localize "OATHHAMMER.Dialog.Grimoire"}}</label>
<select name="noGrimoire">
<option value="0">{{localize "OATHHAMMER.Dialog.GrimoireHas"}}</option>
<option value="-2">{{localize "OATHHAMMER.Dialog.GrimoireNo"}} (2)</option>
</select>
</div>
</fieldset>
{{!-- Visibility --}}
<fieldset class="roll-visibility-block">
<legend>{{localize "OATHHAMMER.Dialog.Visibility"}}</legend>
<select name="visibility">
{{selectOptions rollModes selected=visibility localize=true}}
</select>
</fieldset>
</div>

View File

@@ -0,0 +1,69 @@
<div class="oh-roll-dialog oh-weapon-dialog">
{{!-- Weapon header --}}
<div class="weapon-header">
<img src="{{weaponImg}}" class="weapon-img-sm" alt="{{weaponName}}" />
<div class="weapon-header-info">
<span class="weapon-name-lg">{{weaponName}}</span>
<div class="weapon-badges">
<span class="damage-formula-badge">{{damageLabel}}{{baseDamageDice}}d6</span>
<span class="roll-color-badge color-badge-{{damageColorType}}">{{damageColorLabel}} {{damageThreshold}}+</span>
{{#if apValue}}<span class="ap-badge">AP{{apValue}}</span>{{/if}}
{{#if isRanged}}<span class="range-badge">{{shortRange}}/{{longRange}}ft</span>{{/if}}
</div>
{{#if traits}}
<div class="weapon-traits-row">
{{#each traits}}<span class="trait-tag-sm">{{this}}</span>{{/each}}
</div>
{{/if}}
</div>
</div>
{{!-- Attack roll config --}}
<fieldset class="attack-options-block">
<legend>{{localize "OATHHAMMER.Dialog.Attack"}}</legend>
<div class="pool-info-line">
{{skillLabel}} ({{attrLabel}} {{attrRank}}) + {{localize "OATHHAMMER.Label.SkillRank"}} {{skillRank}}
{{#if autoAttackBonus}} + <span class="auto-bonus">+{{autoAttackBonus}} auto</span>{{/if}}
= <strong>{{baseAttackPool}}d6</strong>
</div>
{{#if hasNimble}}
<div class="roll-option-row">
<label>{{localize "OATHHAMMER.Dialog.Attribute"}}</label>
<select name="attrOverride">
<option value="might">{{mightLabel}} ({{mightRank}})</option>
<option value="agility">{{agilityLabel}} ({{agilityRank}}) — {{localize "OATHHAMMER.Dialog.NimbleHint"}}</option>
</select>
</div>
{{/if}}
<div class="roll-option-row">
<label>{{localize "OATHHAMMER.Dialog.AttackModifier"}}</label>
<select name="attackBonus">
{{#each attackBonusOptions}}<option value="{{value}}"{{#if selected}} selected{{/if}}>{{label}}</option>{{/each}}
</select>
<span class="roll-option-hint">{{localize "OATHHAMMER.Dialog.AttackModifierHint"}}</span>
</div>
{{#if isRanged}}
<div class="roll-option-row">
<label>{{localize "OATHHAMMER.Dialog.RangeCondition"}}</label>
<select name="rangeCondition">
{{#each rangeOptions}}<option value="{{value}}">{{label}}</option>{{/each}}
</select>
</div>
{{/if}}
</fieldset>
{{!-- Visibility --}}
<fieldset class="roll-visibility-block">
<legend>{{localize "OATHHAMMER.Dialog.Visibility"}}</legend>
<select name="visibility">
{{selectOptions rollModes selected=visibility localize=true}}
</select>
</fieldset>
</div>

View File

@@ -0,0 +1,47 @@
<div class="oh-roll-dialog oh-weapon-dialog">
{{!-- Weapon header --}}
<div class="weapon-header">
<img src="{{weaponImg}}" class="weapon-img-sm" alt="{{weaponName}}" />
<div class="weapon-header-info">
<span class="weapon-name-lg">{{weaponName}}</span>
<div class="weapon-badges">
<span class="damage-formula-badge">{{damageLabel}}{{baseDamageDice}}d6 base</span>
<span class="roll-color-badge color-badge-{{damageColorType}}">{{damageColorLabel}} {{damageThreshold}}+</span>
{{#if apValue}}<span class="ap-badge">AP{{apValue}}</span>{{/if}}
{{#if autoDamageBonus}}<span class="auto-bonus-badge">+{{autoDamageBonus}} auto</span>{{/if}}
</div>
</div>
</div>
{{!-- Damage options --}}
<fieldset class="attack-options-block">
<legend>{{localize "OATHHAMMER.Dialog.Damage"}}</legend>
<div class="roll-option-row">
<label>{{localize "OATHHAMMER.Dialog.SV"}}</label>
<select name="sv">
{{#each svOptions}}<option value="{{value}}"{{#if selected}} selected{{/if}}>{{label}}</option>{{/each}}
</select>
<span class="roll-option-hint">{{localize "OATHHAMMER.Dialog.SVHint"}}</span>
</div>
<div class="roll-option-row">
<label>{{localize "OATHHAMMER.Dialog.DamageModifier"}}</label>
<select name="damageBonus">
{{#each damageBonusOptions}}<option value="{{value}}"{{#if selected}} selected{{/if}}>{{label}}</option>{{/each}}
</select>
<span class="roll-option-hint">{{localize "OATHHAMMER.Dialog.DamageModifierHint"}}</span>
</div>
</fieldset>
{{!-- Visibility --}}
<fieldset class="roll-visibility-block">
<legend>{{localize "OATHHAMMER.Dialog.Visibility"}}</legend>
<select name="visibility">
{{selectOptions rollModes selected=visibility localize=true}}
</select>
</fieldset>
</div>