playlist-data-engine 1.7.0 → 1.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const y=require("./unifyBeatMap-DIsNlck4.cjs"),z=require("./arweaveGatewayManager-C9SIMDTd.cjs"),O=require("./gateway-CtmUPK4J.cjs"),ds={0:"",1:"I",2:"II",3:"III",4:"IV",5:"V",6:"VI",7:"VII",8:"VIII",9:"IX",10:"X"},st=10,hs=10,ps=1e3,ia=1.5;function tr(s){return typeof s=="number"&&Number.isInteger(s)&&s>=0&&s<=10}function ms(s){return Math.max(0,Math.min(10,Math.floor(s)))}function ar(s){return typeof s=="string"&&["normal","aggressive"].includes(s)}const Ze={Greataxe:{name:"Greataxe",type:"weapon",rarity:"common",weight:7,damage:{dice:"1d12",damageType:"slashing"},weaponProperties:["two-handed"],spawnWeight:1,tags:["martial","melee","two-handed"],description:"A massive two-handed axe favored by barbarians for its devastating damage potential. Requires great strength to wield effectively."},Longsword:{name:"Longsword",type:"weapon",rarity:"common",weight:3,damage:{dice:"1d8",damageType:"slashing",versatile:"1d10"},weaponProperties:["versatile"],spawnWeight:1,tags:["martial","melee","versatile"],description:"A versatile sword usable with one or two hands. The classic weapon of knights and warriors throughout history."},Shortsword:{name:"Shortsword",type:"weapon",rarity:"common",weight:2,damage:{dice:"1d6",damageType:"piercing",versatile:"1d8"},weaponProperties:["finesse","light","versatile"],spawnWeight:1,tags:["martial","melee","finesse","light"],description:"A light, agile sword effective in close quarters. Can be used with Dexterity instead of Strength when wielded alone."},Rapier:{name:"Rapier",type:"weapon",rarity:"common",weight:2,damage:{dice:"1d8",damageType:"piercing"},weaponProperties:["finesse"],spawnWeight:1,tags:["martial","melee","finesse"],description:"A slender, thrusting sword designed for speed and precision. Favored by duelists and rogues for its finesse property."},Quarterstaff:{name:"Quarterstaff",type:"weapon",rarity:"common",weight:4,damage:{dice:"1d6",damageType:"bludgeoning",versatile:"1d8"},weaponProperties:["versatile","two-handed"],spawnWeight:1,tags:["simple","melee","versatile"],description:"A simple wooden staff, often the first weapon of monks and spellcasters. Effective when used with two hands."},Mace:{name:"Mace",type:"weapon",rarity:"common",weight:4,damage:{dice:"1d6",damageType:"bludgeoning",versatile:"1d8"},weaponProperties:["versatile"],spawnWeight:1,tags:["simple","melee","versatile"],description:"A simple blunt weapon effective against armored foes. Common among clerics and those favoring divine righteousness."},Handaxe:{name:"Handaxe",type:"weapon",rarity:"common",weight:2,damage:{dice:"1d6",damageType:"slashing"},weaponProperties:["light","thrown","range_20_60"],spawnWeight:1,tags:["simple","melee","thrown","light"],description:"A small axe versatile in melee or as a thrown weapon. Popular among dwarves and rangers."},Dagger:{name:"Dagger",type:"weapon",rarity:"common",weight:1,damage:{dice:"1d4",damageType:"piercing",versatile:"1d6"},weaponProperties:["finesse","light","thrown","range_20_60"],spawnWeight:1,tags:["simple","melee","finesse","light","thrown"],description:"A lightweight weapon useful for melee and ranged attacks. Essential for rogues and as a backup weapon for all adventurers."},Dart:{name:"Dart",type:"weapon",rarity:"common",weight:.25,damage:{dice:"1d4",damageType:"piercing"},weaponProperties:["finesse","thrown","range_20_60"],spawnWeight:1,tags:["simple","ranged","thrown","finesse"],description:"A small throwing weapon, often carried in bulk. Lightweight but deals minimal damage."},Javelin:{name:"Javelin",type:"weapon",rarity:"common",weight:2,damage:{dice:"1d6",damageType:"piercing"},weaponProperties:["thrown","range_30_120"],spawnWeight:1,tags:["simple","melee","thrown"],description:"A light spear designed for throwing. Effective at both melee and range."},"Light Crossbow":{name:"Light Crossbow",type:"weapon",rarity:"uncommon",weight:5,damage:{dice:"1d8",damageType:"piercing"},weaponProperties:["ammunition","range_80_320","two-handed","loading"],spawnWeight:1,tags:["simple","ranged","two-handed","ammunition"],description:"A compact crossbow requiring minimal training to use. Devastating at range but requires a reload action between shots."},"Hand Crossbow":{name:"Hand Crossbow",type:"weapon",rarity:"uncommon",weight:3,damage:{dice:"1d6",damageType:"piercing"},weaponProperties:["ammunition","range_30_120","light","loading"],spawnWeight:1,tags:["martial","ranged","light","ammunition"],description:"A small crossbow usable in one hand. Popular among rogues and those who need a ranged sidearm."},Longbow:{name:"Longbow",type:"weapon",rarity:"uncommon",weight:2,damage:{dice:"1d8",damageType:"piercing"},weaponProperties:["ammunition","range_150_600","two-handed","heavy"],spawnWeight:1,tags:["martial","ranged","two-handed","ammunition","heavy"],description:"A tall bow with impressive range and power. Requires significant strength to draw and training to use effectively."},"Martial Melee Weapon":{name:"Martial Melee Weapon",type:"weapon",rarity:"common",weight:3,damage:{dice:"1d8",damageType:"slashing"},weaponProperties:["versatile"],spawnWeight:.5,tags:["martial","melee","versatile","generic"],description:"A generic martial melee weapon. Used as a fallback when no specific weapon is specified."},"No Armor":{name:"No Armor",type:"armor",rarity:"common",weight:0,acBonus:10,spawnWeight:1,tags:["armor","light","no_armor"],description:"No armor worn. Relies entirely on Dexterity for defense. Maximum mobility but minimal protection."},"Leather Armor":{name:"Leather Armor",type:"armor",rarity:"common",weight:10,acBonus:11,properties:[{type:"passive_modifier",target:"ac",value:"11 + DEX",description:"Base AC: 11 + DEX"}],spawnWeight:1,tags:["armor","light"],description:"Light armor made from cured animal hides. Offers basic protection without restricting movement. Adds full Dexterity bonus to AC."},"Scale Mail":{name:"Scale Mail",type:"armor",rarity:"common",weight:45,acBonus:14,properties:[{type:"passive_modifier",target:"ac",value:"14 + min(DEX, 2)",description:"Base AC: 14 + DEX (max 2)"},{type:"special_property",target:"stealth_disadvantage",value:!0,description:"Stealth disadvantage"}],spawnWeight:1,tags:["armor","medium"],description:"Armor consisting of many overlapping metal scales. Solid protection but imposes disadvantage on Stealth checks."},"Chain Mail":{name:"Chain Mail",type:"armor",rarity:"common",weight:55,acBonus:16,properties:[{type:"passive_modifier",target:"ac",value:"16",description:"Fixed AC: 16"},{type:"special_property",target:"stealth_disadvantage",value:!0,description:"Stealth disadvantage"},{type:"stat_requirement",target:"STR",value:13,description:"Requires STR 13"}],spawnWeight:1,tags:["armor","heavy"],description:"Interlocking metal rings provide excellent protection. Requires Strength 13 to wear and imposes disadvantage on Stealth."},"Plate Armor":{name:"Plate Armor",type:"armor",rarity:"rare",weight:65,acBonus:18,properties:[{type:"passive_modifier",target:"ac",value:"18",description:"Fixed AC: 18"},{type:"special_property",target:"stealth_disadvantage",value:!0,description:"Stealth disadvantage"},{type:"stat_requirement",target:"STR",value:15,description:"Requires STR 15"}],spawnWeight:1,tags:["armor","heavy","rare"],description:"The finest armor available, consisting of shaped and fitted metal plates. Provides maximum protection but is expensive, heavy, and requires Strength 15."},Shield:{name:"Shield",type:"armor",rarity:"common",weight:6,acBonus:2,properties:[{type:"passive_modifier",target:"ac",value:2,description:"+2 AC bonus"}],spawnWeight:1,tags:["armor","shield"],description:"A portable defensive barrier carried in one hand. Grants +2 to Armor Class. Cannot be used with two-handed weapons."},Spellbook:{name:"Spellbook",type:"item",rarity:"uncommon",weight:3,spawnWeight:1,tags:["gear","arcane","spellbook"],description:"A blank tome used by wizards to record their spells. Essential for preparing and casting wizard spells."},"Holy Symbol":{name:"Holy Symbol",type:"item",rarity:"common",weight:1,spawnWeight:1,tags:["gear","divine","focus"],description:"An emblem of a deity used by clerics and paladins as a divine focus for spellcasting."},"Arcane Focus":{name:"Arcane Focus",type:"item",rarity:"common",weight:1,spawnWeight:1,tags:["gear","arcane","focus"],description:"An object such as a crystal, orb, or wand used to channel arcane magic. Replaces material components for most spells."},"Druidic Focus":{name:"Druidic Focus",type:"item",rarity:"common",weight:1,spawnWeight:1,tags:["gear","druid","focus"],description:"A natural object such as a staff, wand, or totem used by druids to channel primal magic."},"Component Pouch":{name:"Component Pouch",type:"box",rarity:"common",weight:2,spawnWeight:1,tags:["gear","components","spellcasting"],boxContents:{drops:[],consumeOnOpen:!1},description:"A pouch containing small compartments for storing material components needed for spells. A practical alternative to an arcane focus. Assumes to contain all material components without specific gold costs."},Lute:{name:"Lute",type:"item",rarity:"common",weight:2,spawnWeight:1,tags:["gear","instrument","bard"],description:"A stringed musical instrument used by bards to perform and channel magical inspiration."},"Thieves' Tools":{name:"Thieves' Tools",type:"item",rarity:"uncommon",weight:1,grantsSkills:[{skillId:"thieves_tools",level:"proficient"}],spawnWeight:1,tags:["gear","tools","dexterity"],description:"A small collection of tools including picks, files, and crowbar. Essential for picking locks and disarming traps."},"Healer's Kit":{name:"Healer's Kit",type:"box",rarity:"common",weight:3,spawnWeight:1,tags:["gear","healing","consumable"],boxContents:{drops:[{pool:[{weight:100,itemName:"Medical Supply",quantity:10}]}],consumeOnOpen:!1},description:"A collection of bandages, salves, and tools for treating wounds. Contains 10 medical supplies. Essential for stabilizing dying creatures and providing medical aid."},Bedroll:{name:"Bedroll",type:"item",rarity:"common",weight:7,spawnWeight:1,tags:["gear","camp","comfort"],description:"A padded blanket roll used for sleeping outdoors. Essential comfort for adventurers on the road."},Rope:{name:"Rope",type:"item",rarity:"common",weight:1,spawnWeight:1,tags:["gear","utility","climbing"],description:"50 feet of hempen rope. Essential for climbing, securing prisoners, and countless other utility purposes."},Backpack:{name:"Backpack",type:"box",rarity:"common",weight:5,spawnWeight:1,tags:["gear","container","storage"],boxContents:{drops:[],consumeOnOpen:!1},description:"A leather backpack for carrying adventuring gear. Essential storage for any traveling adventurer."},Torch:{name:"Torch",type:"item",rarity:"common",weight:1,properties:[{type:"special_property",target:"light",value:"bright_light_20ft",description:"Sheds bright light 20ft, dim 20ft"}],spawnWeight:1,tags:["gear","light","consumable"],description:"A wooden rod topped with flammable material. Burns for about an hour, shedding light in a 20-foot radius."},Waterskin:{name:"Waterskin",type:"item",rarity:"common",weight:5,spawnWeight:1,tags:["gear","container","water"],description:"A leather container for holding water. Essential for surviving in wilderness and dungeon environments."},"Ink & Quill":{name:"Ink & Quill",type:"item",rarity:"common",weight:1,spawnWeight:1,tags:["gear","writing","consumable"],description:"Ink and a quill pen for writing. Useful for recording maps, copying spells, and documenting adventures."},"Ball Bearings":{name:"Ball Bearings",type:"item",rarity:"common",weight:2,spawnWeight:1,tags:["gear","utility","rogue"],description:"A bag containing 1,000 tiny metal balls. Scattered on the ground, they create a treacherous surface that can cause creatures to fall prone."},String:{name:"String",type:"item",rarity:"common",weight:0,spawnWeight:1,tags:["gear","utility"],description:"10 feet of sturdy string. Useful for tripwires, tying items together, or countless other creative purposes."},Bell:{name:"Bell",type:"item",rarity:"common",weight:0,spawnWeight:1,tags:["gear","utility","alarm"],description:"A small brass bell that rings when disturbed. Perfect for creating warning systems or signaling."},Crowbar:{name:"Crowbar",type:"item",rarity:"common",weight:5,spawnWeight:1,tags:["gear","tool","utility"],description:"A heavy iron bar used for prying open doors, chests, and other stuck objects. Grants advantage on Strength checks to force objects open."},Hammer:{name:"Hammer",type:"item",rarity:"common",weight:3,spawnWeight:1,tags:["gear","tool","utility"],description:"A sturdy hammer designed for driving pitons into walls and other heavy work. Essential for dungeon exploration."},Piton:{name:"Piton",type:"item",rarity:"common",weight:.25,spawnWeight:1,tags:["gear","climbing","utility"],description:"A metal spike with a ring at one end. Driven into cracks to create climbing anchors or secure rope lines."},"Small Knife":{name:"Small Knife",type:"item",rarity:"common",weight:1,spawnWeight:1,tags:["gear","tool","utility"],description:"A small utility knife useful for cutting rope, skinning game, and other everyday tasks. Not designed for combat."},"Iron Key":{name:"Iron Key",type:"item",rarity:"common",weight:0,spawnWeight:.5,tags:["key","utility","consumable"],description:"A simple iron key that can unlock certain containers. Consumed when used to open a locked box."},"Golden Key":{name:"Golden Key",type:"item",rarity:"uncommon",weight:0,spawnWeight:.2,tags:["key","utility","consumable","valuable"],description:"An ornate golden key for unlocking valuable chests. Consumed when used to open a locked box."},"Skeleton Key":{name:"Skeleton Key",type:"item",rarity:"rare",weight:0,spawnWeight:.1,tags:["key","utility","consumable","universal"],description:"A master key that can open many different locks. Consumed when used to open a locked box."},Lockpick:{name:"Lockpick",type:"item",rarity:"common",weight:0,spawnWeight:.8,tags:["gear","rogue","consumable","tool"],description:"A simple lockpick. Multiple may be needed for complex locks, as they often break during use."},"Gold Coin":{name:"Gold Coin",type:"item",rarity:"common",weight:0,spawnWeight:0,tags:["currency","gold","money"],description:"A standard gold coin used as currency throughout the realm. Can be consumed to open certain magical locks."},"Locked Chest":{name:"Locked Chest",type:"box",rarity:"uncommon",weight:10,spawnWeight:.5,tags:["loot","treasure","locked"],boxContents:{openRequirements:[{itemName:"Iron Key"}],drops:[{pool:[{weight:100,gold:50}]},{pool:[{weight:50,itemName:"Shortsword"},{weight:30,itemName:"Leather Armor"},{weight:20,itemName:"Medical Supply",quantity:3}]}]},description:"A sturdy locked chest. Requires an Iron Key to open."},"Gilded Strongbox":{name:"Gilded Strongbox",type:"box",rarity:"rare",weight:15,spawnWeight:.3,tags:["loot","treasure","gold-locked"],boxContents:{openRequirements:[{itemName:"Gold Coin",quantity:100}],drops:[{pool:[{weight:100,gold:250}]},{pool:[{weight:40,itemName:"Longsword"},{weight:30,itemName:"Chain Mail"},{weight:20,itemName:"Scale Mail"},{weight:10,itemName:"Medical Supply",quantity:5}]}]},description:"A gilded strongbox with a magical lock. Consumes 100 Gold Coins to unlock."},"Royal Treasury Box":{name:"Royal Treasury Box",type:"box",rarity:"very_rare",weight:20,spawnWeight:.1,tags:["loot","treasure","royal","locked"],boxContents:{openRequirements:[{itemName:"Golden Key"},{itemName:"Gold Coin",quantity:200}],drops:[{pool:[{weight:100,gold:1e3}]},{pool:[{weight:30,itemName:"Plate Armor"},{weight:25,itemName:"Chain Mail"},{weight:25,itemName:"Greataxe"},{weight:20,itemName:"Longsword"}]},{pool:[{weight:60,itemName:"Medical Supply",quantity:10},{weight:40,itemName:"Thieves' Tools"}]}]},description:"A royal treasury box sealed with powerful magic. Requires a Golden Key and 200 Gold Coins to open."},"Thieves' Cache":{name:"Thieves' Cache",type:"box",rarity:"uncommon",weight:5,spawnWeight:.4,tags:["loot","treasure","rogue","locked"],boxContents:{openRequirements:[{itemName:"Lockpick",quantity:3}],drops:[{pool:[{weight:100,gold:75}]},{pool:[{weight:50,itemName:"Thieves' Tools"},{weight:30,itemName:"Dagger"},{weight:20,itemName:"Disguise Kit"}]}]},description:"A hidden cache with a complex lock. Requires 3 lockpicks to crack (consumed in the process)."},Candle:{name:"Candle",type:"item",rarity:"common",weight:0,properties:[{type:"special_property",target:"light",value:"dim_light_5ft",description:"Sheds dim light 5ft, bright 2ft"}],spawnWeight:1,tags:["gear","light","consumable"],description:"A simple wax candle that burns for about an hour. Provides dim light in a 5-foot radius, bright light in a 2-foot radius."},"Hooded Lantern":{name:"Hooded Lantern",type:"item",rarity:"common",weight:2,properties:[{type:"special_property",target:"light",value:"bright_light_30ft",description:"Sheds bright light 30ft, dim 30ft when unhooded"}],spawnWeight:1,tags:["gear","light"],description:"A metal lantern with a hood that can be raised or lowered to control the light. Burns for 6 hours on a flask of oil. Sheds bright light in a 30-foot cone and dim light for an additional 30 feet when unhooded."},"Oil Flask":{name:"Oil Flask",type:"item",rarity:"common",weight:1,spawnWeight:1,tags:["gear","light","consumable"],description:"A flask of oil suitable for fueling lanterns. Can also be used as a weapon when thrown and ignited, dealing fire damage. Burns for 6 hours in a lantern."},Rations:{name:"Rations",type:"item",rarity:"common",weight:2,spawnWeight:1,tags:["gear","food","consumable"],description:"One day's worth of dried meat, hard bread, and other preserved food. Essential for surviving long journeys without access to fresh food."},Water:{name:"Water",type:"item",rarity:"common",weight:5,spawnWeight:1,tags:["gear","drink","consumable"],description:"Clean drinking water essential for survival. Typically stored in a waterskin and consumed during rest periods."},"Medical Supply":{name:"Medical Supply",type:"item",rarity:"common",weight:.3,spawnWeight:1,tags:["gear","healing","consumable"],description:"A single-use bandage, salve, or medical tool for treating wounds. Used to stabilize dying creatures or provide basic first aid."},Tinderbox:{name:"Tinderbox",type:"item",rarity:"common",weight:1,spawnWeight:1,tags:["gear","fire","utility"],description:"A small container with flint, steel, and tinder for starting fires. Essential for lighting torches, campfires, and lanterns."},"Mess Kit":{name:"Mess Kit",type:"item",rarity:"common",weight:1,spawnWeight:1,tags:["gear","camp","dining"],description:"A tin box containing a cup, bowl, and simple eating utensils. Collapses flat for easy packing and storage."},Blanket:{name:"Blanket",type:"item",rarity:"common",weight:3,spawnWeight:1,tags:["gear","camp","comfort"],description:"A warm woolen blanket for sleeping. Provides basic warmth and comfort during rest in cold environments."},Costume:{name:"Costume",type:"item",rarity:"common",weight:4,spawnWeight:1,tags:["gear","clothing","performance"],description:"A set of performer's clothing suitable for stage appearances and entertaining. Includes colorful fabrics and decorative elements."},"Traveler's Clothes":{name:"Traveler's Clothes",type:"item",rarity:"common",weight:4,spawnWeight:1,tags:["gear","clothing"],description:"Sturdy, practical clothing designed for long journeys. Includes boots, a cloak, and weather-appropriate garb for the road."},Vestments:{name:"Vestments",type:"item",rarity:"common",weight:2,spawnWeight:1,tags:["gear","clothing","religious"],description:"Ceremonial robes worn by priests and other religious figures. Often adorned with symbols of faith and made of fine materials."},"Alms Box":{name:"Alms Box",type:"item",rarity:"common",weight:0,spawnWeight:1,tags:["gear","religious","container"],description:"A small wooden box with a slot for collecting alms and donations. Used by priests and mendicants to gather charitable offerings."},Incense:{name:"Incense",type:"item",rarity:"common",weight:0,spawnWeight:1,tags:["gear","religious","consumable"],description:"A block of aromatic incense used in religious ceremonies and rituals. Burns slowly to produce fragrant smoke during prayers and worship."},Censer:{name:"Censer",type:"item",rarity:"common",weight:2,spawnWeight:1,tags:["gear","religious"],description:"A metal container suspended on chains, used for burning incense during religious ceremonies. Often made of silver or brass and adorned with religious symbols."},"Book of Lore":{name:"Book of Lore",type:"item",rarity:"common",weight:5,spawnWeight:1,tags:["gear","book","knowledge"],description:"A scholarly tome containing accumulated knowledge on various subjects. Scholars and wizards often carry these to reference arcane theories, historical accounts, or arcane formulas."},"Bottle of Ink":{name:"Bottle of Ink",type:"item",rarity:"common",weight:0,spawnWeight:1,tags:["gear","writing","consumable"],description:"A small glass bottle containing black ink, sealed with a stopper. Used with an ink pen for writing on parchment or paper."},"Ink Pen":{name:"Ink Pen",type:"item",rarity:"common",weight:0,spawnWeight:1,tags:["gear","writing"],description:"A wooden pen with a metal nib, designed to be dipped in ink for writing. Essential for scribes, scholars, and anyone needing to keep written records."},Parchment:{name:"Parchment",type:"item",rarity:"common",weight:0,spawnWeight:1,tags:["gear","writing","consumable"],description:"A single sheet of parchment made from animal skin, used for writing important documents, letters, or arcane inscriptions. More durable than paper but also more expensive."},"Bag of Sand":{name:"Bag of Sand",type:"item",rarity:"common",weight:0,spawnWeight:1,tags:["gear","writing","utility"],description:"A small cloth bag filled with fine sand. Sprinkled over freshly written text to absorb excess ink and speed drying, preventing smudges and blots."},"Disguise Kit":{name:"Disguise Kit",type:"item",rarity:"common",weight:3,spawnWeight:1,tags:["gear","deception","rogue"],description:"A kit containing cosmetics, hair dye, and small props to create disguises. Includes makeup, false facial hair, and other materials needed to change one's appearance. Essential for spies, actors, and rogues who need to assume different identities."},"Burglar's Pack":{name:"Burglar's Pack",type:"box",rarity:"common",weight:44,spawnWeight:1,tags:["gear","pack","rogue"],boxContents:{drops:[{pool:[{weight:100,itemName:"Backpack"}]},{pool:[{weight:100,itemName:"Ball Bearings",quantity:1e3}]},{pool:[{weight:100,itemName:"String"}]},{pool:[{weight:100,itemName:"Bell"}]},{pool:[{weight:100,itemName:"Candle",quantity:5}]},{pool:[{weight:100,itemName:"Crowbar"}]},{pool:[{weight:100,itemName:"Hammer"}]},{pool:[{weight:100,itemName:"Piton",quantity:10}]},{pool:[{weight:100,itemName:"Hooded Lantern"}]},{pool:[{weight:100,itemName:"Oil Flask",quantity:2}]},{pool:[{weight:100,itemName:"Rations",quantity:5}]},{pool:[{weight:100,itemName:"Tinderbox"}]},{pool:[{weight:100,itemName:"Waterskin"}]},{pool:[{weight:100,itemName:"Rope"}]}],consumeOnOpen:!0},description:"A backpack containing gear for a rogue: a backpack, a bag of 1,000 ball bearings, 10 feet of string, a bell, 5 candles, a crowbar, a hammer, 10 pitons, a hooded lantern, 2 flasks of oil, 5 days' rations, a tinderbox, and a waterskin. Includes a pack with 50 feet of hempen rope strapped to the side."},"Explorer's Pack":{name:"Explorer's Pack",type:"box",rarity:"common",weight:59,spawnWeight:1,tags:["gear","pack","general"],boxContents:{drops:[{pool:[{weight:100,itemName:"Backpack"}]},{pool:[{weight:100,itemName:"Bedroll"}]},{pool:[{weight:100,itemName:"Mess Kit"}]},{pool:[{weight:100,itemName:"Tinderbox"}]},{pool:[{weight:100,itemName:"Waterskin"}]},{pool:[{weight:100,itemName:"Rope"}]},{pool:[{weight:100,itemName:"Torch",quantity:10}]},{pool:[{weight:100,itemName:"Rations",quantity:10}]}],consumeOnOpen:!0},description:"A backpack containing gear for wilderness exploration: a backpack, a bedroll, mess kit, tinderbox, 10 torches, and 10 days' rations. Includes a waterskin and 50 feet of hempen rope."},"Entertainer's Pack":{name:"Entertainer's Pack",type:"box",rarity:"common",weight:58,spawnWeight:1,tags:["gear","pack","bard"],boxContents:{drops:[{pool:[{weight:100,itemName:"Backpack"}]},{pool:[{weight:100,itemName:"Bedroll"}]},{pool:[{weight:100,itemName:"Costume",quantity:2}]},{pool:[{weight:100,itemName:"Candle",quantity:5}]},{pool:[{weight:100,itemName:"Rations",quantity:5}]},{pool:[{weight:100,itemName:"Waterskin"}]},{pool:[{weight:100,itemName:"Disguise Kit"}]}],consumeOnOpen:!0},description:"A backpack containing a bard's performance gear: a backpack, a bedroll, 2 costumes, 5 candles, 5 days' rations, a waterskin, and a disguise kit."},"Priest's Pack":{name:"Priest's Pack",type:"box",rarity:"common",weight:33,spawnWeight:1,tags:["gear","pack","cleric"],boxContents:{drops:[{pool:[{weight:100,itemName:"Backpack"}]},{pool:[{weight:100,itemName:"Blanket"}]},{pool:[{weight:100,itemName:"Candle",quantity:10}]},{pool:[{weight:100,itemName:"Tinderbox"}]},{pool:[{weight:100,itemName:"Alms Box"}]},{pool:[{weight:100,itemName:"Incense",quantity:2}]},{pool:[{weight:100,itemName:"Censer"}]},{pool:[{weight:100,itemName:"Vestments"}]},{pool:[{weight:100,itemName:"Rations",quantity:2}]},{pool:[{weight:100,itemName:"Waterskin"}]}],consumeOnOpen:!0},description:"A backpack containing a priest's supplies: a backpack, a blanket, 10 candles, a tinderbox, an alms box, 2 blocks of incense, a censer, vestments, 2 days' rations, and a waterskin."},"Dungeon Delver's Pack":{name:"Dungeon Delver's Pack",type:"box",rarity:"common",weight:48,spawnWeight:1,tags:["gear","pack","dungeon"],boxContents:{drops:[{pool:[{weight:100,itemName:"Backpack"}]},{pool:[{weight:100,itemName:"Crowbar"}]},{pool:[{weight:100,itemName:"Hammer"}]},{pool:[{weight:100,itemName:"Piton",quantity:10}]},{pool:[{weight:100,itemName:"Torch",quantity:10}]},{pool:[{weight:100,itemName:"Tinderbox"}]},{pool:[{weight:100,itemName:"Rations",quantity:10}]},{pool:[{weight:100,itemName:"Waterskin"}]},{pool:[{weight:100,itemName:"Rope"}]}],consumeOnOpen:!0},description:"A backpack optimized for dungeon exploration. Contains a backpack, crowbar, hammer, 10 pitons, 10 torches, a tinderbox, 10 days' rations, and a waterskin. Includes 50 feet of hempen rope."},"Dungeoneer's Pack":{name:"Dungeoneer's Pack",type:"box",rarity:"common",weight:48,spawnWeight:1,tags:["gear","pack","dungeon"],boxContents:{drops:[{pool:[{weight:100,itemName:"Backpack"}]},{pool:[{weight:100,itemName:"Crowbar"}]},{pool:[{weight:100,itemName:"Hammer"}]},{pool:[{weight:100,itemName:"Piton",quantity:10}]},{pool:[{weight:100,itemName:"Torch",quantity:10}]},{pool:[{weight:100,itemName:"Tinderbox"}]},{pool:[{weight:100,itemName:"Rations",quantity:10}]},{pool:[{weight:100,itemName:"Waterskin"}]},{pool:[{weight:100,itemName:"Rope"}]}],consumeOnOpen:!0},description:"A backpack optimized for underground exploration. Contains a backpack, crowbar, hammer, 10 pitons, 10 torches, a tinderbox, 10 days' rations, and a waterskin. Includes 50 feet of hempen rope."},"Scholar's Pack":{name:"Scholar's Pack",type:"box",rarity:"common",weight:49,spawnWeight:1,tags:["gear","pack","wizard"],boxContents:{drops:[{pool:[{weight:100,itemName:"Backpack"}]},{pool:[{weight:100,itemName:"Book of Lore"}]},{pool:[{weight:100,itemName:"Bottle of Ink"}]},{pool:[{weight:100,itemName:"Ink Pen"}]},{pool:[{weight:100,itemName:"Parchment",quantity:10}]},{pool:[{weight:100,itemName:"Bag of Sand"}]}],consumeOnOpen:!0},description:"A backpack containing a scholar's supplies: a backpack, a book of lore, a bottle of ink, an ink pen, 10 sheets of parchment, and a little bag of sand."},"Traveler's Pack":{name:"Traveler's Pack",type:"box",rarity:"common",weight:43,spawnWeight:1,tags:["gear","pack","general"],boxContents:{drops:[{pool:[{weight:100,itemName:"Backpack"}]},{pool:[{weight:100,itemName:"Bedroll"}]},{pool:[{weight:100,itemName:"Mess Kit"}]},{pool:[{weight:100,itemName:"Rations",quantity:10}]},{pool:[{weight:100,itemName:"Waterskin"}]},{pool:[{weight:100,itemName:"Traveler's Clothes"}]},{pool:[{weight:100,itemName:"Rope"}]}],consumeOnOpen:!0},description:"A backpack for long-distance travel: a backpack, a bedroll, a mess kit, 10 days' rations, a waterskin, traveler's clothes, and 50 feet of hempen rope."},Arrow:{name:"Arrow",type:"item",rarity:"common",weight:.05,spawnWeight:1,tags:["ammunition","bow"],description:"Ammunition for a bow. Typically carried in quivers of 20 arrows. Essential for archers."},Bolt:{name:"Bolt",type:"item",rarity:"common",weight:.075,spawnWeight:1,tags:["ammunition","crossbow"],description:"Ammunition for a crossbow. Shorter and heavier than arrows, designed for crossbow use."},Insignia:{name:"Insignia",type:"item",rarity:"common",weight:0,spawnWeight:1,tags:["gear","roleplay","insignia"],description:"A badge, banner, or token representing rank, membership, or authority. Useful for roleplaying and establishing credentials."},"Martial Arts":{name:"Martial Arts",type:"weapon",rarity:"common",weight:0,damage:{dice:"1d4",damageType:"bludgeoning",versatile:"1d6"},weaponProperties:["finesse","unarmed"],spawnWeight:1,tags:["monk","unarmed","natural"],description:"The monk's unarmed strike. Uses Dexterity instead of Strength and deals increased damage at higher levels."}};class U{static rollDie(e){if(e<1)throw new Error("Die must have at least 1 side");return Math.floor(Math.random()*e)+1}static rollMultipleDice(e,t){const a=[];for(let i=0;i<e;i++)a.push(this.rollDie(t));return a}static parseDiceFormula(e){const t=e.match(/^(\d+)d(\d+)([+-]\d+)?$/);if(!t)throw new Error(`Invalid dice formula: ${e}`);const a=parseInt(t[1],10),i=parseInt(t[2],10),n=t[3]?parseInt(t[3],10):0,r=this.rollMultipleDice(a,i),o=r.reduce((l,c)=>l+c,0)+n;return{diceCount:a,diceSides:i,modifier:n,rolls:r,total:o}}static rollD20(){return this.rollDie(20)}static rollWithAdvantage(){const e=this.rollD20(),t=this.rollD20();return{roll1:e,roll2:t,result:Math.max(e,t)}}static rollWithDisadvantage(){const e=this.rollD20(),t=this.rollD20();return{roll1:e,roll2:t,result:Math.min(e,t)}}static rollInitiative(e){return this.rollD20()+e}static isCriticalHit(e){return e===20}static isCriticalMiss(e){return e===1}static doubleDamage(e){return[...e,...e]}static calculateDamage(e,t,a=!1){let n=this.parseDiceFormula(e).rolls;a&&(n=this.doubleDamage(n));const r=n.reduce((o,l)=>o+l,0)+t;return{diceFormula:e,rolls:n,modifier:t,total:r,isCritical:a}}static rollSavingThrow(e,t=0){return this.rollD20()+e+t}static rollAbilityCheck(e,t=0){return this.rollD20()+e+t}static seededRoll(e){const t=Math.sin(e)*1e4;return Math.floor((t-Math.floor(t))*20)+1}static rollPercentile(){return this.rollDie(100)}}const ir={1:[0,2,0,0,0,0,0,0,0,0],2:[0,3,0,0,0,0,0,0,0,0],3:[0,4,2,0,0,0,0,0,0,0],4:[0,4,3,0,0,0,0,0,0,0],5:[0,4,3,2,0,0,0,0,0,0],6:[0,4,3,3,0,0,0,0,0,0],7:[0,4,3,3,1,0,0,0,0,0],8:[0,4,3,3,2,0,0,0,0,0],9:[0,4,3,3,3,1,0,0,0,0],10:[0,4,3,3,3,2,0,0,0,0],11:[0,4,3,3,3,2,1,0,0,0],12:[0,4,3,3,3,2,1,0,0,0],13:[0,4,3,3,3,2,1,1,0,0],14:[0,4,3,3,3,2,1,1,0,0],15:[0,4,3,3,3,2,1,1,1,0],16:[0,4,3,3,3,2,1,1,1,0],17:[0,4,3,3,3,2,1,1,1,1],18:[0,4,3,3,3,3,1,1,1,1],19:[0,4,3,3,3,3,2,1,1,1],20:[0,4,3,3,3,3,2,2,1,1]};function sa(s){const e=ir[s];if(!e)return{};const t={};for(let a=1;a<e.length;a++)e[a]>0&&(t[a]=e[a]);return t}const di={charm:{name:"Charmed",requiresConcentration:!0,mechanicalEffects:{disadvantageOnAttackNonSource:!0}},frighten:{name:"Frightened",requiresConcentration:!0,mechanicalEffects:{disadvantageOnAttack:!0,disadvantageOnAbilityChecks:!0}},stun:{name:"Stunned",requiresConcentration:!1,mechanicalEffects:{disadvantageOnDexSaves:!0,speedZero:!0,skipTurn:!0}},paralyze:{name:"Paralyzed",requiresConcentration:!1,mechanicalEffects:{disadvantageOnDexSaves:!0,speedZero:!0,skipTurn:!0}},restrain:{name:"Restrained",requiresConcentration:!0,mechanicalEffects:{disadvantageOnDexSaves:!0,speedZero:!0}},poison:{name:"Poisoned",requiresConcentration:!1,mechanicalEffects:{disadvantageOnAttack:!0,disadvantageOnAbilityChecks:!0}},blind:{name:"Blinded",requiresConcentration:!1,mechanicalEffects:{disadvantageOnAttack:!0,disadvantageOnAbilityChecks:!0}},deafen:{name:"Deafened",requiresConcentration:!1},burn:{name:"Burning",requiresConcentration:!1}};class V{constructor(e){this.diceRoller=e}castSpell(e,t,a){const i=t.level??0;if(!this.hasSpellSlot(e,i))return{success:!1,spellName:t.name,caster:e,targets:a,effectsApplied:[],spellSlotUsed:i,description:`${e.character.name} tried to cast ${t.name} but has no spell slots available!`};this.consumeSpellSlot(e,i);const n=[];let r,o;const l=t.damage_dice??t.damage,c=t.damage_type??t.damageType,u=t.saving_throw??t.save;if(l&&c){if(t.attack_roll)r=this.diceRoller?this.diceRoller.calculateDamage(l,0,!1):U.calculateDamage(l,0,!1);else if(u){o=this.calculateSaveDC(e,u);for(const m of a){const v=u.toLowerCase()==="dexterity"&&m.statusEffects.some(E=>E.mechanicalEffects?.disadvantageOnDexSaves);this.makeSavingThrow(m,u,o,v)||(r=this.diceRoller?this.diceRoller.calculateDamage(l,0,!1):U.calculateDamage(l,0,!1),m.currentHP-=r.total,m.currentHP<0&&(m.currentHP=0,m.isDefeated=!0))}}}const d=t.tags??[],h=new Set;for(const m of d){const f=di[m];if(f){h.add(f.name);const v={name:f.name,description:`${f.name} by ${e.character.name}`,duration:1,source:e.id,hasConcentration:f.requiresConcentration,mechanicalEffects:f.mechanicalEffects};for(const w of a)n.push({...v})}}const p=[t.description,t.effect].filter(Boolean).join(" ").toLowerCase();if(!h.has("Charmed")&&p.includes("charm")){const m={name:"Charmed",description:`Charmed by ${e.character.name}`,duration:1,source:e.id,hasConcentration:!0};for(const f of a)n.push({...m})}if(!h.has("Frightened")&&p.includes("frighten")){const m={name:"Frightened",description:`Frightened of ${e.character.name}`,duration:1,source:e.id,hasConcentration:!0};for(const f of a)n.push({...m})}const g=`${e.character.name} casts ${t.name} (Level ${i}) at ${a.map(m=>m.character.name).join(", ")}`;return{success:!0,spellName:t.name,caster:e,targets:a,saveDC:o,damage:r,effectsApplied:n,spellSlotUsed:i,description:g}}hasSpellSlot(e,t){return t===0?!0:!e.spellSlots||!e.spellSlots[t]?!1:e.spellSlots[t]>0}consumeSpellSlot(e,t){t!==0&&(e.spellSlots||(e.spellSlots={}),e.spellSlots[t]||(e.spellSlots[t]=0),e.spellSlots[t]--)}restoreSpellSlots(e){e.spellSlots=sa(e.character.level)}calculateSaveDC(e,t){const a=t.toLowerCase(),i=e.character.ability_modifiers[a]||0,n=e.character.proficiency_bonus;return 8+i+n}makeSavingThrow(e,t,a,i=!1){const n=t.toLowerCase(),r=e.character.ability_modifiers[n]||0,c=e.character.saving_throws[n]?e.character.proficiency_bonus:0;let u;return i?u=(this.diceRoller?this.diceRoller.rollWithDisadvantage():U.rollWithDisadvantage()).result+r+c:u=this.diceRoller?this.diceRoller.rollSavingThrow(r,c):U.rollSavingThrow(r,c),u>=a}getSpellSlotInfo(e){if(!e.spellSlots)return"No spell slots available";const t=[];for(let a=1;a<=9;a++){const i=e.spellSlots[a]||0;i>0&&t.push(`Level ${a}: ${i} slots`)}return t.length>0?t.join(", "):"No spell slots available"}canUpcast(e,t,a){const i=t.level??0;return a<i?!1:this.hasSpellSlot(e,a)}upcastSpell(e,t,a,i){const n=t.level??0;if(i<n)return{success:!1,spellName:t.name,caster:e,targets:a,effectsApplied:[],spellSlotUsed:i,description:`Cannot cast ${t.name} with a lower-level slot`};const r=t.level;t.level=i;const o=this.castSpell(e,t,a);return t.level=r,o}static hasSpellTag(e,t){return Array.isArray(e.tags)&&e.tags.includes(t)}static getSpellTags(e){return Array.isArray(e.tags)?[...e.tags]:[]}static isDamageSpell(e){return V.hasSpellTag(e,"damage")||!!(e.damage_dice||e.damage)}static requiresConcentration(e){return e.concentration===!0}static isAOESpell(e){return V.hasSpellTag(e,"aoe")}static isMultiTargetSpell(e){return V.hasSpellTag(e,"multi-target")}static isBonusActionSpell(e){return V.hasSpellTag(e,"bonus-action")}static isAllySpell(e){return V.hasSpellTag(e,"ally")}static isSelfSpell(e){return V.hasSpellTag(e,"self")}static getStatusEffectTags(){return di}}class gs{constructor(e){this.config=e}getStyleForCombatant(e){return this.config.overrides?.has(e.id)?this.config.overrides.get(e.id):e.id.startsWith("player")?this.config.playerStyle:this.config.enemyStyle}getSide(e){return e.id.startsWith("player")?"player":"enemy"}getEnemies(e,t){const i=this.getSide(e)==="player"?"enemy":"player";return t.combatants.filter(n=>n.id.startsWith(i)&&!n.isDefeated)}getAllies(e,t){const i=this.getSide(e)==="player"?"player":"enemy";return t.combatants.filter(n=>n.id.startsWith(i)&&!n.isDefeated)}assessThreat(e,t){const a=e.character.hp.max,i=a>0?e.currentHP/a:0,n=this.getAllies(e,t),r=this.getEnemies(e,t),o=n.reduce((v,w)=>{if(w.id===e.id)return v;const E=w.character.hp.max,I=E>0?w.currentHP/E:0;return Math.min(v,I)},1),l=r.reduce((v,w)=>Math.min(v,w.currentHP),r.length>0?1/0:0),c=r.reduce((v,w)=>{const E=w.character.equipment?.weapons.filter(D=>D.equipped)||[];let I=5;for(const D of E){const x=Ze[D.name],N=D.damage_dice||(typeof D.damage!="string"?D.damage?.dice:void 0)||x?.damage?.dice||"1d6",$=this.averageDamageFromFormula(N);$>I&&(I=$)}return Math.max(v,I)},0),d=(e.character.equipment?.items||[]).some(v=>v.quantity>0&&!v.equipped&&v.tags?.some(w=>w==="healing"||w==="consumable")),h=e.spellSlots,p=h!==void 0&&Object.values(h).some(v=>v>0),g=(e.legendaryActionsRemaining??0)>0,m=(e.legendaryResistancesRemaining??0)>0,f=g||m;return{myHPPercent:i,myAC:e.character.armor_class,lowestAllyHPPercent:o,lowestEnemyHP:l,highestEnemyDamage:c,partySize:n.length,enemyCount:r.length,roundNumber:t.roundNumber,isLowHP:i<.25,isCriticalHP:i<.1,hasHealingItems:d,hasSpellSlots:p,hasRemainingLimitedAbilities:f}}decide(e,t){const a=this.getStyleForCombatant(e),i=this.assessThreat(e,t);if(this.getEnemies(e,t).length===0)return{action:"skip",reasoning:"No valid targets remaining"};const r=this.selectSpellAction(e,t,i,a);if(r)return r;const o=this.selectItemAction(e,i,a);if(o)return o;const l=this.selectDefensiveAction(e,t,i,a);return l||this.selectAttackAction(e,t,i,a)}selectSpellAction(e,t,a,i){const n=this.getAvailableSpells(e);if(n.length===0)return null;const r=n.map(d=>this.evaluateSpell(d,e,t,a)),o=r.filter(d=>d.expectedDamage>0&&!d.isHealing),l=r.filter(d=>d.isHealing),c=r.filter(d=>d.isControl&&!d.isHealing),u=r.filter(d=>d.isBuff&&!d.isHealing);if(l.length>0&&this.shouldHeal(e,a,i)){const d=this.pickHealingSpell(l,e,t,a,i);if(d){const h=this.pickHealingTarget(e,t,i);return{action:"castSpell",spellName:d.spell.name,target:h?.id,targetIds:d.isMultiTarget?this.getHealingTargets(e,t,i).map(p=>p.id):void 0,reasoning:`Cast ${d.spell.name} to heal ${h?.character.name??"self"} (${i} style)`}}}if(o.length>0&&this.shouldCastDamageSpell(o,a,i)){const d=this.pickDamageSpell(o,a,i),h=this.pickSpellTargets(d,e,t,i);if(h.length>0)return{action:"castSpell",spellName:d.spell.name,target:h.length===1?h[0].id:void 0,targetIds:h.length>1?h.map(p=>p.id):void 0,reasoning:`Cast ${d.spell.name} on ${h.map(p=>p.character.name).join(", ")} (${i} style, ~${d.expectedDamage.toFixed(1)} expected damage)`}}if(c.length>0&&i==="normal"&&this.shouldCastControlSpell(a)){const d=c[0],h=this.pickSpellTargets(d,e,t,i);if(h.length>0)return{action:"castSpell",spellName:d.spell.name,target:h[0].id,reasoning:`Cast ${d.spell.name} for crowd control (${i} style)`}}if(u.length>0&&i==="normal"&&!a.isLowHP&&!a.isCriticalHP){const d=u[0],h=this.pickBuffTarget(e,t);return{action:"castSpell",spellName:d.spell.name,target:h?.id,reasoning:`Cast ${d.spell.name} on ${h?.character.name??"self"} (buff, normal style)`}}return null}getAvailableSpells(e){const t=e.character.combat_spells;return!t||t.length===0?[]:t.filter(a=>{if(!a.level||a.level===0)return!0;const i=e.spellSlots;return i?this.hasSlotForLevel(i,a.level):!1})}hasSlotForLevel(e,t){if((e[t]??0)>0)return!0;for(let a=t+1;a<=9;a++)if((e[a]??0)>0)return!0;return!1}evaluateSpell(e,t,a,i){const n=!e.level||e.level===0,r=n?0:this.getBestAvailableSlot(t.spellSlots,e.level??0),o=e.damage_dice||e.damage;let l=o?this.averageDamageFromFormula(o):0;const c=V.isAOESpell(e),u=V.isMultiTargetSpell(e),d=V.isAllySpell(e)||V.isSelfSpell(e)||e.tags?.includes("healing"),h=V.hasSpellTag(e,"buff"),p=V.hasSpellTag(e,"control")||V.hasSpellTag(e,"debuff");if((c||u)&&l>0){const g=i.enemyCount;g>1&&(l*=Math.min(g,4))}return!n&&l>0&&(l*=1.5),{spell:e,expectedDamage:l,isHealing:!!d,isBuff:!!h,isControl:!!p,isAOE:c,isMultiTarget:u,isAllySpell:V.isAllySpell(e),isSelfSpell:V.isSelfSpell(e),isBonusAction:V.isBonusActionSpell(e),requiresConcentration:V.requiresConcentration(e),slotLevel:r,isCantrip:n}}getBestAvailableSlot(e,t){if(!e)return 0;if((e[t]??0)>0)return t;for(let a=t+1;a<=9;a++)if((e[a]??0)>0)return a;return 0}shouldHeal(e,t,a){return a==="aggressive"?t.myHPPercent<.75:t.isLowHP||t.isCriticalHP}pickHealingSpell(e,t,a,i,n){if(e.length===0)return null;const r=[...e].sort((o,l)=>l.expectedDamage-o.expectedDamage);if(n==="aggressive")return r[0];if(!i.isCriticalHP){const o=r.find(l=>l.isCantrip);if(o)return o}return r[0]}pickHealingTarget(e,t,a){const i=this.getAllies(e,t);if(i.length===0)return null;const n=[...i].sort((r,o)=>{const l=r.currentHP/r.character.hp.max,c=o.currentHP/o.character.hp.max;return l-c});return a==="normal"&&n[0].id!==e.id?n[0].currentHP/n[0].character.hp.max<.5?n[0]:e:n[0]}getHealingTargets(e,t,a){const i=this.getAllies(e,t),n=a==="aggressive"?1:.75;return i.filter(r=>r.currentHP/r.character.hp.max<n)}shouldCastDamageSpell(e,t,a){if(e.length===0)return!1;if(a==="aggressive")return!0;const i=e[0];return!!(e.some(r=>r.isAOE||r.isMultiTarget)&&t.enemyCount>=3||i.isCantrip||i.expectedDamage>15&&!i.isCantrip)}pickDamageSpell(e,t,a){const i=[...e].sort((o,l)=>l.expectedDamage-o.expectedDamage);if(a==="aggressive")return i[0];const n=i.filter(o=>o.isCantrip),r=i.filter(o=>!o.isCantrip);if(n.length>0){const o=n[0];return r.length>0&&r[0].expectedDamage>o.expectedDamage*1.5&&(t.roundNumber>1||this.totalSlotsRemaining(t)>3)?r[0]:o}return i[0]}totalSlotsRemaining(e){return e.hasSpellSlots?5:0}pickSpellTargets(e,t,a,i){const n=this.getEnemies(t,a),r=this.getAllies(t,a);return e.isAllySpell||e.isHealing?r.length>0?[this.pickHealingTarget(t,a,i)??r[0]]:[]:e.isSelfSpell?[t]:e.isMultiTarget||e.isAOE?n:[this.selectTarget(n,i)]}shouldCastControlSpell(e){return e.enemyCount>=2}pickBuffTarget(e,t){return this.getAllies(e,t).reduce((i,n)=>{const r=Math.max(i.character.ability_scores.STR,i.character.ability_scores.DEX);return Math.max(n.character.ability_scores.STR,n.character.ability_scores.DEX)>r?n:i})}selectItemAction(e,t,a){if(!t.hasHealingItems)return null;const n=(e.character.equipment?.items||[]).filter(r=>r.quantity>0&&!r.equipped);if(n.length===0)return null;if(a==="aggressive"){if(t.myHPPercent<.75&&!t.hasSpellSlots)return{action:"useItem",itemName:n[0].name,target:e.id,reasoning:`Use ${n[0].name} to heal (aggressive: maintain HP for damage output)`}}else if(t.isLowHP&&!t.hasSpellSlots)return{action:"useItem",itemName:n[0].name,target:e.id,reasoning:`Use ${n[0].name} to heal (no spell slots available)`};return null}selectDefensiveAction(e,t,a,i){return i==="aggressive"?null:a.isLowHP&&a.partySize<=1&&a.enemyCount>1?{action:"dodge",reasoning:"Dodge — isolated and low HP, improving survivability"}:null}selectAttackAction(e,t,a,i){const n=this.getEnemies(e,t),r=this.selectTarget(n,i),o=this.selectWeapon(e,i);return{action:"attack",target:r.id,weaponName:o.name,reasoning:`Attack ${r.character.name} with ${o.name} (${i} style, expected ~${o.expectedDamage.toFixed(1)} damage)`}}selectTarget(e,t){if(e.length===0)throw new Error("No valid targets to select from");return e.length===1?e[0]:t==="aggressive"?e.reduce((a,i)=>i.currentHP<a.currentHP?i:a):e.reduce((a,i)=>i.character.armor_class<a.character.armor_class?i:a)}selectWeapon(e,t){const a=this.evaluateWeapons(e);return a.length===0?{name:"Unarmed Strike",expectedDamage:1,attackBonus:0,type:"melee",properties:[]}:a.length===1?a[0]:t==="aggressive"?a.reduce((i,n)=>n.expectedDamage>i.expectedDamage?n:i):a.reduce((i,n)=>{const r=i.expectedDamage+i.attackBonus*.1;return n.expectedDamage+n.attackBonus*.1>r?n:i})}evaluateWeapons(e){const t=[],a=e.character.equipment?.weapons.filter(n=>n.equipped)||[];for(const n of a){const r=Ze[n.name],o=n.damage_dice||(typeof n.damage!="string"?n.damage?.dice:void 0)||r?.damage?.dice||"1d6",l=this.averageDamageFromFormula(o),c=n.weaponProperties?.includes("ranged")||r?.weaponProperties?.includes("ranged")||!1,u=c?"DEX":"STR",h=Math.floor((e.character.ability_scores[u]-10)/2)+e.character.proficiency_bonus;t.push({name:n.name,expectedDamage:l,attackBonus:h,type:c?"ranged":"melee",properties:n.weaponProperties||r?.weaponProperties||[]})}const i=Math.floor((e.character.ability_scores.STR-10)/2);return t.push({name:"Unarmed Strike",expectedDamage:1+Math.max(0,i),attackBonus:i+e.character.proficiency_bonus,type:"melee",properties:[]}),t}selectLegendaryAction(e,t){const a=e.character.legendary_config;if(!a)return null;const i=e.legendaryActionsRemaining??0;if(i<=0)return null;const n=this.getStyleForCombatant(e),r=a.actions.filter(p=>p.cost<=i);if(r.length===0)return null;const o=this.getEnemies(e,t);if(o.length===0)return null;const l=r.filter(p=>p.damage||p.tags?.includes("damage")),c=r.filter(p=>p.tags?.includes("heal")),u=r.filter(p=>p.tags?.includes("control")),d=this.assessThreat(e,t);if(n==="aggressive"&&l.length>0){const g=[...l].sort((f,v)=>v.cost-f.cost)[0],m=this.selectTarget(o,n);return{action:"legendaryAction",legendaryActionId:g.id,target:m.id,reasoning:`Legendary action: ${g.name} (cost: ${g.cost}, aggressive: max damage)`}}if(l.length>0){const g=[...l].sort((f,v)=>f.cost-v.cost)[0],m=this.selectTarget(o,n);return{action:"legendaryAction",legendaryActionId:g.id,target:m.id,reasoning:`Legendary action: ${g.name} (cost: ${g.cost}, normal: conserve points)`}}if(c.length>0&&d.isLowHP)return{action:"legendaryAction",legendaryActionId:c[0].id,reasoning:`Legendary action: ${c[0].name} (healing, low HP)`};if(u.length>0){const p=this.selectTarget(o,n);return{action:"legendaryAction",legendaryActionId:u[0].id,target:p.id,reasoning:`Legendary action: ${u[0].name} (control)`}}const h=this.selectTarget(o,n);return{action:"legendaryAction",legendaryActionId:r[0].id,target:h.id,reasoning:`Legendary action: ${r[0].name} (fallback)`}}averageDamageFromFormula(e){if(!e)return 0;const a=e.replace(/\s/g,"").match(/^(\d+)d(\d+)([+-]\d+)?$/);if(!a)return 0;const i=parseInt(a[1],10),n=parseInt(a[2],10),r=a[3]?parseInt(a[3],10):0,o=(n+1)/2;return i*o+r}isSupportArchetype(e){const t=e.character.combat_spells;if(!t||t.length===0)return!1;const a=t.some(n=>V.isAllySpell(n)||n.tags?.includes("healing")),i=t.some(n=>V.hasSpellTag(n,"buff"));return a||i}}class fs{constructor(e){this.diceRoller=e}rollInitiativeForCombatant(e){const t=e.character.ability_modifiers.dexterity??0,a=this.diceRoller?this.diceRoller.rollD20():U.rollD20(),i=a+t;return e.initiative=i,{combatant:e,d20Roll:a,dexModifier:t,initiativeTotal:i}}rollInitiativeForAll(e){const t=[];for(const i of e){const n=this.rollInitiativeForCombatant(i);t.push(n)}const a=[...e].sort((i,n)=>{if(i.initiative!==n.initiative)return n.initiative-i.initiative;const r=i.character.ability_modifiers.dexterity??0;return(n.character.ability_modifiers.dexterity??0)-r});return{results:t,sortedCombatants:a}}getNextCombatant(e,t){const a=(t+1)%e.length,i=a===0;return{combatant:e[a],index:a,isNewRound:i}}getInitiativeOrder(e){return e.map((t,a)=>{const i=t.character.ability_modifiers.dexterity??0;return`${a+1}. ${t.character.name} (Initiative: ${t.initiative}, DEX: ${i})`})}rerollInitiativeForCombatant(e){const t=e.character.ability_modifiers.dexterity??0,a=this.diceRoller?this.diceRoller.rollD20():U.rollD20();return e.initiative=a+t,e.initiative}delayTurn(e,t){const a=e.findIndex(r=>r.id===t);if(a===-1)return e;const i=[...e],[n]=i.splice(a,1);return i.splice(a+1,0,n),i}resortByInitiative(e){return[...e].sort((t,a)=>{if(t.initiative!==a.initiative)return a.initiative-t.initiative;const i=t.character.ability_modifiers.dexterity??0;return(a.character.ability_modifiers.dexterity??0)-i})}}class ue{constructor(e,t="scaled"){this.diceRoller=e,this.hitMode=t}static computeScaledDamage(e,t,a,i,n){const r=Math.max(1,Math.floor(e*2+(t-a)*.3)),o=ue.getWeaponBonus(i);return n?Math.max(1,Math.floor(r*1.5))+o:Math.max(1,r+o)}static computeDamageScale(e,t){if(e>=t)return 1;const a=t-e;return Math.max(.1,1-a*.1)}static computeAttackBonus(e,t,a){const i=t.includes("ranged"),n=t.includes("finesse");return Math.floor(((e[i||n?"DEX":"STR"]??10)-10)/2)+a}resolveAttack(e,t,a){const i=this.rollAttack(e,t,a);let n,r,o="";if(i.isMiss)o=`${e.character.name} uses ${a.name} against ${t.character.name} - CRITICAL MISS (natural 1)!`;else if(!i.hit)o=`${e.character.name} uses ${a.name} against ${t.character.name} - Miss (rolled ${i.d20Roll}, needed ${i.targetAC})`;else{if(n=this.rollDamage(e,t,a,i.isCritical),i.damageScale!==void 0&&i.damageScale<1){const l=Math.max(1,Math.floor(n.total*i.damageScale));n={...n,total:l}}r=t.currentHP-n.total,r<0?(r=0,t.currentHP=0,t.isDefeated=!0):t.currentHP=r,i.isCritical?o=`${e.character.name} uses ${a.name} against ${t.character.name} - CRITICAL HIT! (natural 20, doubled damage: ${n.total})`:o=`${e.character.name} uses ${a.name} against ${t.character.name} - Hit! (rolled ${i.d20Roll}, damage: ${n.total})`}return{attacker:e,target:t,attack:a,attackRoll:i,damageRoll:n,hpAfterDamage:r,description:o}}rollAttack(e,t,a){const i=this.diceRoller?this.diceRoller.rollD20():U.rollD20(),n=a.attack_bonus??0,r=i+n,o=t.character.armor_class,l=this.diceRoller?this.diceRoller.isCriticalHit(i):U.isCriticalHit(i),c=this.diceRoller?this.diceRoller.isCriticalMiss(i):U.isCriticalMiss(i);let u,d;return this.hitMode==="scaled"?(u=!c,!c&&!l?d=ue.computeDamageScale(r,o):d=1):u=!c&&(l||r>=o),{d20Roll:i,attackBonus:n,totalRoll:r,targetAC:o,hit:u,isCritical:l,isMiss:c,damageScale:d}}rollDamage(e,t,a,i){return this.hitMode==="dnd"?this.rollDamageDND(e,t,a,i):this.rollDamageScaled(e,t,a,i)}getDamageModifier(e,t){const a=e.character.ability_modifiers,i=a.STR??0,n=a.DEX??0;return t.type==="spell"?0:t.type==="ranged"?n:t.properties?.includes("finesse")?Math.max(i,n):i}rollDamageDND(e,t,a,i){const n=e.character.ability_scores.STR??10,r=e.character.ability_scores.DEX??10,o=a.properties?.includes("finesse")??!1,l=a.properties?.includes("ranged")??!1,c=Math.floor((n-10)/2),u=Math.floor((r-10)/2),d=o?Math.max(c,u):l?u:c,h=a.damage_dice??"",p=this.diceRoller?this.diceRoller.calculateDamage(h,d,i):U.calculateDamage(h,d,i),g=Math.max(1,p.total);return{diceFormula:h,rolls:p.rolls,modifier:d,total:g,isCritical:p.isCritical,baseDamage:0,weaponRoll:p.total-d}}rollDamageScaled(e,t,a,i){const n=e.character.level??1,r=e.character.ability_scores.STR??10,o=t.character.armor_class,l=Math.max(1,Math.floor(n*2+(r-o)*.3)),c=ue.getWeaponBonus(a.damage_dice),u=ue.computeScaledDamage(n,r,o,a.damage_dice??"",i);return{diceFormula:a.damage_dice??"",rolls:[],modifier:0,total:u,isCritical:i,baseDamage:l,weaponRoll:c}}static getWeaponBonus(e){if(!e)return 0;try{const t=U.parseDiceFormula(e),a=t.diceCount*(t.diceSides+1)/2;return Math.round(a/2.5)}catch{return 0}}static formatWeaponDamage(e,t,a){if(a==="scaled"){const i=ue.getWeaponBonus(e);return i>0?`+${i} ${t}`:t}return`${e} ${t}`}static estimateDamagePerHit(e){if(e.hitMode==="scaled")return ue.computeScaledDamage(e.level,e.abilityScore,e.targetAC,e.damageDice,!1);const t=Math.floor((e.abilityScore-10)/2);try{const a=U.parseDiceFormula(e.damageDice);return a.diceCount*(a.diceSides+1)/2+a.modifier+t}catch{return t+1}}static estimateDPR(e){const t=e.attackCount??1;if(e.hitMode==="scaled"){const l=ue.computeScaledDamage(e.level,e.abilityScore,e.targetAC,e.damageDice,!1),c=ue.computeScaledDamage(e.level,e.abilityScore,e.targetAC,e.damageDice,!0),u=e.proficiencyBonus??Math.ceil(1+((e.level||1)-1)/4),d=e.attackBonus??Math.floor((e.abilityScore-10)/2)+u;let h=0;for(let p=1;p<=20;p++){if(p===1)continue;if(p===20){h+=c;continue}const g=p+d,m=ue.computeDamageScale(g,e.targetAC);h+=Math.max(1,Math.floor(l*m))}return h/20*t}const a=ue.estimateDamagePerHit(e),i=e.proficiencyBonus??Math.ceil(1+((e.level||1)-1)/4),n=Math.floor((e.abilityScore-10)/2),r=e.attackBonus??n+i,o=Math.max(.05,Math.min(.95,(21-(e.targetAC-r))/20));return a*o*t}isInRange(e,t,a){if(!e.position||!t.position)return!0;const i=Math.sqrt(Math.pow(e.position.x-t.position.x,2)+Math.pow(e.position.y-t.position.y,2));return a.range?i<=a.range:i<=5}calculateAttackBonus(e,t,a,i=!1){let n=a;return i&&(n+=e.proficiency_bonus),n}attackWithAdvantage(e,t,a){const i=this.diceRoller?this.diceRoller.rollWithAdvantage():U.rollWithAdvantage(),n=i.roll1,r=i.roll2,o=i.result,l=a.attack_bonus??0,c=o+l,u=t.character.armor_class,d=(this.diceRoller?this.diceRoller.isCriticalHit(n):U.isCriticalHit(n))||(this.diceRoller?this.diceRoller.isCriticalHit(r):U.isCriticalHit(r)),h=this.diceRoller?this.diceRoller.isCriticalMiss(o):U.isCriticalMiss(o);let p,g;if(this.hitMode==="scaled")if(p=!h,!h&&!d&&c<u){const f=u-c;g=Math.max(.1,1-f*.1)}else g=1;else p=!h&&(d||c>=u);const m=`${e.character.name} attacks with advantage (rolled ${n} and ${r}, using ${o})`;if(p&&!h){let f=this.rollDamage(e,t,a,d);if(g!==void 0&&g<1){const w=Math.max(1,Math.floor(f.total*g));f={...f,total:w}}const v=t.currentHP-f.total;return v<0?(t.currentHP=0,t.isDefeated=!0):t.currentHP=v,{attacker:e,target:t,attack:a,attackRoll:{d20Roll:o,attackBonus:l,totalRoll:c,targetAC:u,hit:p,isCritical:d,isMiss:h,damageScale:g},damageRoll:f,hpAfterDamage:t.currentHP,description:m}}return{attacker:e,target:t,attack:a,attackRoll:{d20Roll:o,attackBonus:l,totalRoll:c,targetAC:u,hit:p,isCritical:d,isMiss:h,damageScale:g},description:m}}attackWithDisadvantage(e,t,a){const i=this.diceRoller?this.diceRoller.rollWithDisadvantage():U.rollWithDisadvantage(),n=i.roll1,r=i.roll2,o=i.result,l=a.attack_bonus??0,c=o+l,u=t.character.armor_class,d=this.diceRoller?this.diceRoller.isCriticalHit(o):U.isCriticalHit(o),h=(this.diceRoller?this.diceRoller.isCriticalMiss(n):U.isCriticalMiss(n))||(this.diceRoller?this.diceRoller.isCriticalMiss(r):U.isCriticalMiss(r));let p,g;if(this.hitMode==="scaled")if(p=!h,!h&&!d&&c<u){const f=u-c;g=Math.max(.1,1-f*.1)}else g=1;else p=!h&&(d||c>=u);const m=`${e.character.name} attacks with disadvantage (rolled ${n} and ${r}, using ${o})`;if(p&&!h){let f=this.rollDamage(e,t,a,d);if(g!==void 0&&g<1){const w=Math.max(1,Math.floor(f.total*g));f={...f,total:w}}const v=t.currentHP-f.total;return v<0?(t.currentHP=0,t.isDefeated=!0):t.currentHP=v,{attacker:e,target:t,attack:a,attackRoll:{d20Roll:o,attackBonus:l,totalRoll:c,targetAC:u,hit:p,isCritical:d,isMiss:h,damageScale:g},damageRoll:f,hpAfterDamage:t.currentHP,description:m}}return{attacker:e,target:t,attack:a,attackRoll:{d20Roll:o,attackBonus:l,totalRoll:c,targetAC:u,hit:p,isCritical:d,isMiss:h,damageScale:g},description:m}}static simulateAttacks(e,t,a,i=1e3,n,r){const o=new ue(n,r),l=new Map;let c=0,u=0,d=0;for(let g=0;g<i;g++){const m={id:`sim-atk-${g}`,character:e,initiative:0,currentHP:e.hp.max,statusEffects:[],isDefeated:!1,actionUsed:!1,bonusActionUsed:!1,reactionUsed:!1},f={id:`sim-tgt-${g}`,character:t,initiative:0,currentHP:t.hp.max,statusEffects:[],isDefeated:!1,actionUsed:!1,bonusActionUsed:!1,reactionUsed:!1},v=o.resolveAttack(m,f,a),w=v.damageRoll?.total??0;l.set(w,(l.get(w)??0)+1),v.attackRoll.isMiss||!v.attackRoll.hit?d++:(c++,v.attackRoll.isCritical&&u++)}const h=Array.from(l.entries()).map(([g,m])=>({damage:g,count:m,percentage:m/i*100})).sort((g,m)=>g.damage-m.damage),p=h.reduce((g,m)=>g+m.damage*m.count,0)/i;return{iterations:i,totalHits:c,totalMisses:d,totalCrits:u,hitRate:c/i*100,critRate:u/i*100,missRate:d/i*100,averageDamage:p,maxDamage:Math.max(...l.keys()),distribution:h}}}class Se{constructor(e){this.seed=e,this.counter=0}random(){const e=y.deriveSeed(this.seed,this.counter.toString());return this.counter++,y.hashSeedToFloat(e)}randomInt(e,t){return Math.floor(this.random()*(t-e))+e}randomChoice(e){const t=this.randomInt(0,e.length);return e[t]}weightedChoice(e){const t=e.reduce((i,[,n])=>i+n,0);let a=this.random()*t;for(const[i,n]of e)if(a-=n,a<=0)return i;return e[e.length-1][0]}shuffle(e){const t=[...e];for(let a=t.length-1;a>0;a--){const i=this.randomInt(0,a+1);[t[a],t[i]]=[t[i],t[a]]}return t}reset(){this.counter=0}}const ys={1:{easy:25,medium:50,hard:75,deadly:100},2:{easy:50,medium:100,hard:150,deadly:200},3:{easy:75,medium:150,hard:225,deadly:300},4:{easy:125,medium:250,hard:375,deadly:500},5:{easy:250,medium:500,hard:750,deadly:1e3},6:{easy:300,medium:600,hard:900,deadly:1200},7:{easy:350,medium:750,hard:1100,deadly:1700},8:{easy:400,medium:800,hard:1200,deadly:1900},9:{easy:500,medium:1e3,hard:1500,deadly:2300},10:{easy:600,medium:1200,hard:1800,deadly:2400},11:{easy:800,medium:1600,hard:2400,deadly:3600},12:{easy:1e3,medium:2e3,hard:3e3,deadly:4500},13:{easy:1200,medium:2400,hard:3600,deadly:5400},14:{easy:1400,medium:2800,hard:4200,deadly:5700},15:{easy:1600,medium:3200,hard:4800,deadly:6400},16:{easy:2e3,medium:4e3,hard:6e3,deadly:8e3},17:{easy:2400,medium:4800,hard:7200,deadly:9600},18:{easy:3e3,medium:6e3,hard:9e3,deadly:12e3},19:{easy:4e3,medium:8e3,hard:12e3,deadly:16e3},20:{easy:5e3,medium:1e4,hard:15e3,deadly:2e4}},Mt={0:10,.125:25,.25:50,.5:100,1:200,2:450,3:700,4:1100,5:1800,6:2300,7:2900,8:3900,9:5e3,10:5900,11:7200,12:8400,13:1e4,14:11500,15:13e3,16:15e3,17:18e3,18:2e4,19:22e3,20:25e3,21:33e3,22:41e3,23:5e4,24:62e3,26:9e4,27:105e3,28:12e4,29:135e3,30:155e3},na={1:1,2:1.5,3:2,4:2,5:2,6:2,7:1.5,8:1.5,9:1.5,10:1.5,11:1,12:1,13:1,14:1,15:1},sr={DEFAULT:1,EASIER:.9,MUCH_EASIER:.8,HARDER:1.1,MUCH_HARDER:1.2,BRUTAL:1.5};function vs(s,e){const t=ys[s];return t&&t[e]||0}function Wt(s,e){return s.reduce((t,a)=>t+vs(a,e),0)}function ka(s){return Mt[s]||0}function Ta(s){if(s<=0)return 0;const e=Object.keys(Mt).map(Number).sort((n,r)=>n-r);let t=0,a=e.length-1,i=0;for(;t<=a;){const n=Math.floor((t+a)/2),r=e[n];Mt[r]<=s?(i=r,t=n+1):a=n-1}return i}function nr(s,e=1){return Math.round(s*e)}function ot(s){return s<=0?1:s>=15?na[15]:na[s]||1}function rr(s,e){const t=s.reduce((i,n)=>i+ka(n),0),a=e!==void 0?e:ot(s.length);return Math.round(t*a)}function bs(s){if(s.length===0)return 1;const e=s.reduce((t,a)=>t+a,0);return Math.floor(e/s.length)}function or(s){return typeof s=="string"&&["easy","medium","hard","deadly"].includes(s)}class ws{constructor(e={},t){this.diceRoller=t,this.initiativeRoller=new fs(t),this.attackResolver=new ue(t,e.hitMode),this.spellCaster=new V(t),this.config={useEnvironment:!0,useMusic:!1,tacticalMode:!1,maxTurnsBeforeDraw:100,allowFleeing:!1,seed:e.seed||`combat_${Date.now()}`,...e},this.rng=new Se(this.config.seed)}startCombat(e,t,a){const i=[];let n=0;for(const l of e)i.push(this.createCombatant(l,`player_${n++}`));for(const l of t)i.push(this.createCombatant(l,`enemy_${n++}`));const r=this.initiativeRoller.rollInitiativeForAll(i);return{id:`combat_${Date.now()}`,combatants:r.sortedCombatants,currentTurnIndex:0,roundNumber:1,environment:a,history:[],isActive:!0,startTime:Date.now(),lastUpdated:Date.now()}}getCurrentCombatant(e){return e.combatants[e.currentTurnIndex]}executeAttack(e,t,a,i){const n=this.getAttackAdvantageDisadvantage(t,a,i);let r;n==="advantage"?r=this.attackResolver.attackWithAdvantage(t,a,i):n==="disadvantage"?r=this.attackResolver.attackWithDisadvantage(t,a,i):r=this.attackResolver.resolveAttack(t,a,i);const o={type:"attack",actor:t,target:a,attack:i,result:{success:r.attackRoll.hit,roll:r.attackRoll.d20Roll,isCritical:r.attackRoll.isCritical,damage:r.damageRoll?.total,damageType:i.damage_type,targetHP:r.hpAfterDamage,description:r.description}};return e.history.push(o),a.currentHP<=0?(a.isDefeated=!0,a.concentratingOn&&(a.concentratingOn=void 0)):r.attackRoll.hit&&r.damageRoll?.total&&a.concentratingOn&&this.checkConcentration(e,a,r.damageRoll.total),o}executeWeaponAttack(e,t,a,i){const n=t.character.equipment?.weapons.filter(l=>l.equipped)||[];if(i==="unarmed"||n.length===0){const l=this.buildUnarmedAttack(t.character);return this.executeAttack(e,t,a,l)}const r=i?n.find(l=>l.name===i):n[0];if(!r)throw new Error(`Weapon "${i}" is not equipped`);const o=this.buildAttackFromWeapon(r,t.character);return this.executeAttack(e,t,a,o)}buildAttackFromWeapon(e,t){const a=Ze[e.name];if(!a)throw new Error(`Weapon "${e.name}" not found in equipment database`);const i=a.weaponProperties||[],n=i.includes("ranged"),r=t.proficiency_bonus,o=ue.computeAttackBonus(t.ability_scores,i,r);return{name:e.name,damage_dice:e.damage_dice||a.damage?.dice||"1d6",damage_type:a.damage?.damageType||"bludgeoning",type:n?"ranged":"melee",attack_bonus:o,properties:i}}buildUnarmedAttack(e){const t=Math.floor((e.ability_scores.STR-10)/2),a=e.proficiency_bonus;return{name:"Unarmed Strike",damage_dice:"1d1",damage_type:"bludgeoning",type:"melee",attack_bonus:t+a,properties:[]}}executeCastSpell(e,t,a,i){const n=this.spellCaster.castSpell(t,a,i),r={type:"spell",actor:t,targets:i,spell:a,result:{success:n.success,damage:n.damage?.total,damageType:a.damage_type??a.damageType,description:n.description}};if(e.history.push(r),n.success)for(const o of i)for(const l of n.effectsApplied)this.applyStatusEffect(o,l);for(const o of i)o.currentHP<=0?(o.isDefeated=!0,o.concentratingOn&&(o.concentratingOn=void 0)):n.success&&n.damage?.total&&o.concentratingOn&&this.checkConcentration(e,o,n.damage.total);return r}executeLegendaryAction(e,t,a,i){const n=t.character.legendary_config;if(!n)throw new Error(`${t.character.name} has no legendary actions (not a boss)`);const r=n.actions.find(p=>p.id===a.id);if(!r)throw new Error(`Legendary action "${a.id}" not found on ${t.character.name}`);const o=r.cost,l=t.legendaryActionsRemaining??0;if(l<o)throw new Error(`${t.character.name} needs ${o} legendary action points but only has ${l} remaining`);t.legendaryActionsRemaining=l-o;let c,u;if(r.damage&&i){const p=r.damage.replace(/\s/g,""),g=this.diceRoller;g?c=g.parseDiceFormula(p).total:c=U.parseDiceFormula(p).total;const m=this.applyDamage(i,c);u=i.currentHP,i.currentHP<=0?(i.isDefeated=!0,i.concentratingOn&&(i.concentratingOn=void 0)):i.concentratingOn&&m>0&&this.checkConcentration(e,i,m)}const d=i?`${t.character.name} uses ${r.name} on ${i.character.name}${c?` for ${c} ${r.damage_type??""} damage`:""} (${o} action point${o!==1?"s":""} spent, ${t.legendaryActionsRemaining} remaining)`:`${t.character.name} uses ${r.name} (${o} action point${o!==1?"s":""} spent, ${t.legendaryActionsRemaining} remaining)`,h={type:"legendaryAction",actor:t,target:i,legendaryAction:{id:r.id,name:r.name,description:r.effect,cost:r.cost,effect:r.effect,damage:r.damage,damageType:r.damage_type,archetypes:[]},result:{success:!0,damage:c,damageType:r.damage_type,targetHP:u,description:d}};return e.history.push(h),i?.isDefeated&&this.checkCombatStatus(e),h}useLegendaryResistance(e,t){const a=t.legendaryResistancesRemaining??0;return a<=0?!1:(t.legendaryResistancesRemaining=a-1,e.history.push({type:"statusEffectTick",actor:t,result:{success:!0,description:`${t.character.name} used legendary resistance (${t.legendaryResistancesRemaining} remaining today)`}}),!0)}executeDodge(e,t){const a={type:"dodge",actor:t,result:{success:!0,description:`${t.character.name} takes the Dodge action (AC increased until next turn)`}};return e.history.push(a),a}executeDash(e,t){const a={type:"dash",actor:t,result:{success:!0,description:`${t.character.name} takes the Dash action (double movement)`}};return e.history.push(a),a}executeDisengage(e,t){const a={type:"disengage",actor:t,result:{success:!0,description:`${t.character.name} takes the Disengage action (no opportunity attacks provoked)`}};return e.history.push(a),a}canFlee(){return this.config.allowFleeing===!0}executeFlee(e,t){if(!this.canFlee())throw new Error("Fleeing is not allowed in this combat. Enable with allowFleeing: true in CombatConfig.");const a={type:"flee",actor:t,result:{success:!0,description:`${t.character.name} flees from combat!`}};e.history.push(a);const i=e.combatants.findIndex(n=>n.id===t.id);return i!==-1&&(e.combatants.splice(i,1),i<e.currentTurnIndex?e.currentTurnIndex--:i===e.currentTurnIndex&&(e.currentTurnIndex=Math.min(e.currentTurnIndex,e.combatants.length-1))),t.isDefeated=!0,e.lastUpdated=Date.now(),this.checkCombatStatus(e),a}nextTurn(e){const t=e.combatants[e.currentTurnIndex];t.actionUsed=!1,t.bonusActionUsed=!1,t.reactionUsed=!1;const a=(this.config.maxTurnsBeforeDraw||100)+e.combatants.length;for(let i=0;i<a;i++){const n=(e.currentTurnIndex+1)%e.combatants.length;if(n===0){e.roundNumber++;for(const l of e.combatants)l.character.legendary_config&&!l.isDefeated&&(l.legendaryActionsRemaining=3)}e.currentTurnIndex=n,e.lastUpdated=Date.now();const o=e.combatants[n];if(!o.isDefeated){if(this.processStartOfTurnDamage(e,o),o.isDefeated){if(this.checkCombatStatus(e),!e.isActive)return e;continue}if(this.shouldSkipTurn(o)){if(o.actionUsed=!0,o.bonusActionUsed=!0,o.reactionUsed=!0,o.concentratingOn){const c=this.dropConcentration(o,"Incapacitated");c&&e.history.push({type:"statusEffectTick",actor:o,result:{success:!0,description:`${o.character.name} lost concentration on ${c.name} (incapacitated)`}})}const l=o.statusEffects.filter(c=>c.mechanicalEffects?.skipTurn).map(c=>c.name);if(e.history.push({type:"statusEffectTick",actor:o,result:{success:!0,description:`${o.character.name}'s turn is skipped (${l.join(", ")})`}}),this.tickStatusEffects(e,o),this.checkCombatStatus(e),!e.isActive)return e;continue}this.tickStatusEffects(e,o);break}}return this.checkCombatStatus(e),e}processStartOfTurnDamage(e,t){for(const a of t.statusEffects)if(a.damage&&a.damage>0){const i=this.applyDamage(t,a.damage);e.history.push({type:"statusEffectTick",actor:t,result:{success:!0,damage:i,damageType:a.damageType,description:`${t.character.name} takes ${i} ${a.damageType??""} damage from ${a.name}`}}),t.concentratingOn&&(t.isDefeated?t.concentratingOn=void 0:this.checkConcentration(e,t,i))}}tickStatusEffects(e,t){if(t.statusEffects.length===0)return;for(const i of t.statusEffects)i.duration--;const a=this.removeExpiredStatusEffects(t);if(a.length>0){const i=a.map(n=>n.name).join(", ");e.history.push({type:"statusEffectTick",actor:t,result:{success:!0,description:`${t.character.name}: ${i} expired`}})}}getAttackAdvantageDisadvantage(e,t,a){let i=!1,n=!1;const r=a.type??"melee";for(const o of e.statusEffects)o.mechanicalEffects&&(o.mechanicalEffects.disadvantageOnAttack&&(n=!0),o.mechanicalEffects.disadvantageOnAttackNonSource&&o.source!==t.id&&(n=!0));for(const o of t.statusEffects)o.mechanicalEffects&&(o.mechanicalEffects.advantageOnMeleeAttackAgainst&&r==="melee"&&(i=!0),o.mechanicalEffects.advantageOnRangedAttackAgainst&&r==="ranged"&&(i=!0));return i&&n?"normal":i?"advantage":n?"disadvantage":"normal"}shouldSkipTurn(e){return e.statusEffects.some(t=>t.mechanicalEffects?.skipTurn)}checkCombatStatus(e){const t=e.combatants.filter(o=>o.id.startsWith("player")),a=e.combatants.filter(o=>o.id.startsWith("enemy")),i=a.every(o=>o.isDefeated),n=t.every(o=>o.isDefeated),r=e.history.length>=(this.config.maxTurnsBeforeDraw||100);(i||n||r)&&(e.isActive=!1,i&&!n?(e.winner=t.find(o=>!o.isDefeated),e.winnerSide="player"):n&&!i?(e.winner=a.find(o=>!o.isDefeated),e.winnerSide="enemy"):e.winnerSide="draw")}getCombatResult(e){if(e.isActive)return null;const t=e.combatants.filter(c=>c.id.startsWith("enemy")),a=e.combatants.filter(c=>c.isDefeated);let i=0;i=Math.ceil(e.history.length/Math.max(1,e.combatants.length));const n=t.filter(c=>c.isDefeated).reduce((c,u)=>{const d=u.character.cr??u.character.level;return c+ka(d)},0),r=e.winner?`${e.winner.character.name} won the combat!`:"Combat ended in a draw.";let o=0,l=[];if(this.config.treasure){if(typeof this.config.treasure.gold=="number")o=this.config.treasure.gold;else if(typeof this.config.treasure.gold=="object"&&this.config.treasure.gold!==null){const{min:c,max:u}=this.config.treasure.gold;o=c+Math.floor(this.rng.random()*(u-c+1))}l=this.config.treasure.items||[]}else o=Math.floor(this.rng.random()*100);return{winner:e.winner,winnerSide:e.winnerSide??"draw",defeated:a,roundsElapsed:e.roundNumber,totalTurns:i,xpAwarded:n,treasureAwarded:{gold:o,items:l},description:r}}createCombatant(e,t){const a={id:t,character:e,initiative:0,currentHP:e.hp.max,temporaryHP:0,statusEffects:[],isDefeated:!1,actionUsed:!1,bonusActionUsed:!1,reactionUsed:!1,spellSlots:this.initializeSpellSlots(e)};return e.legendary_config&&(a.legendaryActionsRemaining=3,a.legendaryResistancesRemaining=e.legendary_config.resistances_per_day),a}validateSpellSlots(e){const t=[],a=e.spells?.spell_slots;if(!a||Object.keys(a).length===0)return t;for(const[i,n]of Object.entries(a)){const r=Number(i);if(!Number.isInteger(r)||r<1||r>9){t.push({message:`Invalid spell level key "${i}" — must be an integer 1-9`,level:Number.isNaN(r)?void 0:r,severity:"error"});continue}if(typeof n!="object"||n===null){t.push({message:`Spell level ${r} has invalid slot data (expected { total, used }, got ${typeof n})`,level:r,severity:"error"});continue}const{total:o,used:l}=n;if(typeof o!="number"||typeof l!="number"){t.push({message:`Spell level ${r} has non-numeric total (${typeof o}) or used (${typeof l})`,level:r,severity:"error"});continue}o<0&&t.push({message:`Spell level ${r} has negative total (${o})`,level:r,severity:"error"}),l<0&&t.push({message:`Spell level ${r} has negative used (${l})`,level:r,severity:"error"}),o===0&&l!==0&&t.push({message:`Spell level ${r} has total=0 but used=${l} — no slots exist to consume`,level:r,severity:"warn"}),l>o&&t.push({message:`Spell level ${r} has used (${l}) exceeding total (${o})`,level:r,severity:"error"})}return t}validateCombatantSpellSlots(e){const t=[],a=e.character,i=a.spells?.spell_slots,n=e.spellSlots;if(!i||Object.keys(i).length===0){if(n&&Object.keys(n).length>0){const r=sa(a.level),o=Object.keys(r).sort(),l=Object.keys(n).sort();if(o.join(",")!==l.join(","))t.push({message:`Combatant has spell slots ${l.join(", ")} but fallback table for ${a.class} level ${a.level} expects ${o.join(", ")}`,severity:"warn"});else for(const c of o){const u=Number(c);n[u]!==r[u]&&t.push({message:`Spell level ${u}: combatant has ${n[u]} but fallback table expects ${r[u]}`,level:u,severity:"warn"})}}return t}if(!n)return Object.values(i).every(o=>o.total-o.used<=0)||t.push({message:"Character has spell slot data but combatant has no spell slots — expected some remaining",severity:"error"}),t;for(const[r,o]of Object.entries(i)){const l=Number(r),c=Math.max(0,o.total-o.used);if(c===0)n[l]!==void 0&&n[l]>0&&t.push({message:`Spell level ${l}: combatant has ${n[l]} slots but source indicates all used (total=${o.total}, used=${o.used})`,level:l,severity:"error"});else{const u=n[l]??0;u!==c&&t.push({message:`Spell level ${l}: combatant has ${u} but source expects ${c} (total=${o.total}, used=${o.used})`,level:l,severity:"error"})}}for(const r of Object.keys(n)){const o=Number(r);o in i||t.push({message:`Combatant has spell level ${o} with ${n[o]} slots but no source data for that level`,level:o,severity:"warn"})}return t}initializeSpellSlots(e){const t=e.spells?.spell_slots;if(t&&Object.keys(t).length>0){const n=this.validateSpellSlots(e).filter(r=>r.severity==="error");if(n.length>0)console.warn(`[CombatEngine] Spell slot validation failed for ${e.name} (${e.class}): `+n.map(r=>r.message).join("; ")+". Falling back to class table.");else{const r={};for(const[o,l]of Object.entries(t)){const c=l.total-l.used;c>0&&(r[Number(o)]=c)}return Object.keys(r).length>0?r:void 0}}if(["Wizard","Cleric","Sorcerer","Bard","Druid","Warlock","Paladin","Ranger"].includes(e.class))return sa(e.level)}getCombatSummary(e){const t=this.getCurrentCombatant(e);return`Round ${e.roundNumber}, Turn: ${t.character.name} (${t.currentHP}/${t.character.hp.max} HP)`}applyDamage(e,t){const a=Math.max(0,t);if(e.temporaryHP){const i=Math.min(e.temporaryHP,a);e.temporaryHP-=i;const n=a-i;e.currentHP-=n}else e.currentHP-=a;return e.currentHP<=0&&(e.currentHP=0,e.isDefeated=!0),a}healCombatant(e,t){const a=Math.min(t,e.character.hp.max-e.currentHP);return e.currentHP+=a,a}applyTemporaryHP(e,t){e.temporaryHP=Math.max(e.temporaryHP||0,t)}applyStatusEffect(e,t){t.hasConcentration&&e.concentratingOn&&this.dropConcentration(e,"New concentration spell cast");const a=e.statusEffects.findIndex(i=>i.name===t.name);if(a!==-1){const i=e.statusEffects[a];return i.duration=Math.max(i.duration,t.duration),t.damage!==void 0&&(i.damage=Math.max(i.damage??0,t.damage)),t.source!==void 0&&(i.source=t.source),t.mechanicalEffects&&(i.mechanicalEffects={...i.mechanicalEffects,...t.mechanicalEffects}),t.hasConcentration&&(i.hasConcentration=!0),t.damageType&&(i.damageType=t.damageType),t.hasConcentration&&(e.concentratingOn=t.name),i}return e.statusEffects.push(t),t.hasConcentration&&(e.concentratingOn=t.name),t}dropConcentration(e,t="Concentration ended"){if(!e.concentratingOn)return;const a=e.concentratingOn,i=e.statusEffects.findIndex(n=>n.name===a);if(i!==-1){const[n]=e.statusEffects.splice(i,1);return e.concentratingOn=void 0,n}e.concentratingOn=void 0}rollConcentrationSave(e,t){if(!e.concentratingOn)return!0;const a=Math.max(10,Math.floor(t/2)),i=e.character.ability_modifiers.CON??0,r=e.character.saving_throws.CON??!1?e.character.proficiency_bonus:0,o=this.diceRoller?this.diceRoller.rollSavingThrow(i,r):U.rollSavingThrow(i,r),l=o-i-r;return l===1?!1:l===20?!0:o>=a}checkConcentration(e,t,a){if(!t.concentratingOn)return!1;if(!this.rollConcentrationSave(t,a)){const n=this.dropConcentration(t,"Took damage and failed concentration save");return n&&e.history.push({type:"statusEffectTick",actor:t,result:{success:!0,description:`${t.character.name} lost concentration on ${n.name} (took ${a} damage)`}}),!0}return!1}removeExpiredStatusEffects(e){const t=e.statusEffects.filter(a=>a.duration<=0);return e.statusEffects=e.statusEffects.filter(a=>a.duration>0),e.concentratingOn&&(e.statusEffects.some(i=>i.name===e.concentratingOn)||(e.concentratingOn=void 0)),t}getLivingCombatants(e){return e.combatants.filter(t=>!t.isDefeated)}getDefeatedCombatants(e){return e.combatants.filter(t=>t.isDefeated)}}class Ss{computeMetrics(e){const t=new Map;for(const o of e.combatants){const l=o.id.startsWith("player")?"player":"enemy";t.set(o.id,{combatantId:o.id,name:o.character.name,side:l,totalDamageDealt:0,totalDamageTaken:0,totalHealingDone:0,spellsCast:0,itemsUsed:0,criticalHits:0,hits:0,misses:0,kills:0,roundsSurvived:0,survived:!o.isDefeated,actionsByType:{},damagePerRound:[],hpRemainingPercent:0})}const a=new Set(["attack","spell","dodge","dash","disengage","flee","help","hide","ready","useItem","skip"]),i=new Map;for(const o of e.history){const l=o.actor.id,c=t.get(l);switch(c&&(c.actionsByType[o.type]=(c.actionsByType[o.type]||0)+1),a.has(o.type)&&i.set(l,(i.get(l)??0)+1),o.type){case"attack":{const u=o.result?.damage??0,d=o.result?.isCritical===!0,h=o.result?.success===!0;if(c&&(h?c.hits+=1:c.misses+=1),c&&u>0&&(c.totalDamageDealt+=u,c.criticalHits+=d?1:0),o.target){const p=t.get(o.target.id);p&&u>0&&(p.totalDamageTaken+=u)}break}case"spell":{const u=o.result?.damage??0,d=o.result?.isCritical===!0,h=this.isHealingSpell(o);if(c)if(c.spellsCast+=1,h){const p=o.targets??(o.target?[o.target]:[]);for(const g of p){const m=o.result?.damage??0;m>0&&(c.totalHealingDone+=m)}}else u>0&&(c.totalDamageDealt+=u,c.criticalHits+=d?1:0,o.result?.success===!0?c.hits+=1:c.misses+=1);if(!h&&u>0){const p=o.targets??(o.target?[o.target]:[]);for(const g of p){const m=t.get(g.id);m&&(m.totalDamageTaken+=u)}}break}case"legendaryAction":{const u=o.result?.damage??0;if(c&&u>0&&(c.totalDamageDealt+=u),o.target&&u>0){const d=t.get(o.target.id);d&&(d.totalDamageTaken+=u)}break}case"useItem":{c&&(c.itemsUsed+=1);break}}}const n=new Map;for(const o of e.history)if((o.type==="attack"||o.type==="spell"||o.type==="legendaryAction")&&o.target){const l=o.result?.damage??0;if(l>0&&n.set(o.target.id,o.actor.id),o.targets)for(const c of o.targets)l>0&&n.set(c.id,o.actor.id)}for(const o of e.combatants)if(o.isDefeated){const l=n.get(o.id);if(l){const c=t.get(l);c&&(c.kills+=1)}}const r=e.roundNumber;for(const[o,l]of t){l.roundsSurvived=Math.max(1,r);const c=i.get(o)??0;c>0&&l.totalDamageDealt>0&&(l.damagePerRound=[l.totalDamageDealt/c]);const u=e.combatants.find(d=>d.id===o);u&&u.character.hp.max>0&&(l.hpRemainingPercent=l.survived?Math.round(u.currentHP/u.character.hp.max*100):0)}return e.metrics=t,t}isHealingSpell(e){if(!e.spell)return!1;const t=e.spell.tags??[];return t.includes("healing")||t.includes("heal")}}class _s{runFullCombat(e,t,a,i,n){const r=new ws(i??{},n),o=new gs(a),l=r.startCombat(e,t);if(l.combatants.length===0)return{combat:l,result:{winnerSide:"draw",defeated:[],roundsElapsed:0,totalTurns:0,xpAwarded:0,description:"No combatants — combat could not start."},metrics:new Map};for(;l.isActive;){const h=r.getCurrentCombatant(l);if(h.isDefeated){r.nextTurn(l);continue}if(this.hasSkipTurnEffect(h)){l.history.push({type:"statusEffectTick",actor:h,result:{success:!0,description:`${h.character.name}'s turn is skipped (${h.statusEffects.filter(g=>g.mechanicalEffects?.skipTurn).map(g=>g.name).join(", ")})`}}),r.nextTurn(l);continue}const p=o.decide(h,l);if(this.executeDecision(r,l,h,p),!l.isActive||(this.processLegendaryActions(r,o,l,h),!l.isActive))break;r.nextTurn(l)}const c=r.getCombatResult(l);if(!c)throw new Error("AICombatRunner: combat ended but getCombatResult returned null");const d=new Ss().computeMetrics(l);return{combat:l,result:c,metrics:d}}executeDecision(e,t,a,i){switch(i.action){case"attack":{const n=this.findCombatant(t,i.target);n&&this.executeWeaponAttack(e,t,a,n,i.weaponName);break}case"castSpell":{const n=this.findSpell(a,i.spellName);if(n){const r=this.resolveTargets(t,i.target,i.targetIds);r.length>0&&e.executeCastSpell(t,a,n,r)}break}case"dodge":e.executeDodge(t,a);break;case"dash":e.executeDash(t,a);break;case"disengage":e.executeDisengage(t,a);break;case"flee":{e.canFlee()?e.executeFlee(t,a):this.fallbackAttack(e,t,a);break}case"useItem":{const n=a.character.equipment?.items||[],r=i.itemName?n.find(o=>o.name===i.itemName&&o.quantity>0):void 0;if(r&&(r.quantity-=1,r.quantity<=0)){const o=n.indexOf(r);o!==-1&&n.splice(o,1)}t.history.push({type:"useItem",actor:a,result:{success:!!r,description:r?`${a.character.name} uses ${i.itemName}`:`${a.character.name} tries to use ${i.itemName??"an item"} — item not found`}});break}case"legendaryAction":{this.executeLegendaryDecision(e,t,a,i);break}case"skip":t.history.push({type:"statusEffectTick",actor:a,result:{success:!0,description:`${a.character.name} skips their turn (${i.reasoning??"no action"})`}});break}}processLegendaryActions(e,t,a,i){for(const n of a.combatants)if(n.character.legendary_config&&!n.isDefeated&&n.id!==i.id){for(;a.isActive&&(n.legendaryActionsRemaining??0)>0;){const r=t.selectLegendaryAction(n,a);if(!r||(this.executeLegendaryDecision(e,a,n,r),!a.isActive))break}if(!a.isActive)break}}executeLegendaryDecision(e,t,a,i){const n=a.character.legendary_config;if(!n||!i.legendaryActionId)return;const r=n.actions.find(l=>l.id===i.legendaryActionId);if(!r)return;const o=i.target?this.findCombatant(t,i.target):void 0;try{e.executeLegendaryAction(t,a,{id:r.id,name:r.name,cost:r.cost,effect:r.effect,damage:r.damage,damage_type:r.damage_type},o)}catch{}}executeWeaponAttack(e,t,a,i,n){const r=n==="Unarmed Strike"?"unarmed":n;try{e.executeWeaponAttack(t,a,i,r)}catch{try{e.executeWeaponAttack(t,a,i,"unarmed")}catch{}}}findCombatant(e,t){if(t)return e.combatants.find(a=>a.id===t)}findSpell(e,t){if(t)return e.character.combat_spells?.find(a=>a.name===t)}resolveTargets(e,t,a){if(a&&a.length>0)return a.map(i=>e.combatants.find(n=>n.id===i)).filter(i=>i!==void 0);if(t){const i=e.combatants.find(n=>n.id===t);return i?[i]:[]}return[]}fallbackAttack(e,t,a){const i=a.id.startsWith("player")?"enemy":"player",n=t.combatants.filter(r=>r.id.startsWith(i)&&!r.isDefeated);n.length>0&&this.executeWeaponAttack(e,t,a,n[0],void 0)}hasSkipTurnEffect(e){return e.statusEffects.some(t=>t.mechanicalEffects?.skipTurn)}}function As(s){return new ks(s)}class ks{constructor(e){this.rng=typeof e=="string"?new Se(e):e}rollDie(e){if(e<1)throw new Error("Die must have at least 1 side");return this.rng.randomInt(1,e+1)}rollMultipleDice(e,t){const a=[];for(let i=0;i<e;i++)a.push(this.rollDie(t));return a}parseDiceFormula(e){const t=e.match(/^(\d+)d(\d+)([+-]\d+)?$/);if(!t)throw new Error(`Invalid dice formula: ${e}`);const a=parseInt(t[1],10),i=parseInt(t[2],10),n=t[3]?parseInt(t[3],10):0,r=this.rollMultipleDice(a,i),o=r.reduce((l,c)=>l+c,0)+n;return{diceCount:a,diceSides:i,modifier:n,rolls:r,total:o}}rollD20(){return this.rollDie(20)}rollWithAdvantage(){const e=this.rollD20(),t=this.rollD20();return{roll1:e,roll2:t,result:Math.max(e,t)}}rollWithDisadvantage(){const e=this.rollD20(),t=this.rollD20();return{roll1:e,roll2:t,result:Math.min(e,t)}}rollInitiative(e){return this.rollD20()+e}isCriticalHit(e){return e===20}isCriticalMiss(e){return e===1}doubleDamage(e){return[...e,...e]}calculateDamage(e,t,a=!1){let n=this.parseDiceFormula(e).rolls;a&&(n=this.doubleDamage(n));const r=n.reduce((o,l)=>o+l,0)+t;return{diceFormula:e,rolls:n,modifier:t,total:r,isCritical:a}}rollSavingThrow(e,t=0){return this.rollD20()+e+t}rollAbilityCheck(e,t=0){return this.rollD20()+e+t}rollPercentile(){return this.rollDie(100)}}const lr=[{id:"skeleton",name:"Skeleton",category:"undead",archetype:"archer",signatureAbility:{id:"skeleton_bone_shot",name:"Bone Shot",description:"Fires a sharpened bone projectile that deals piercing damage. The hollow whistle of the bone strikes fear into the living.",damageDie:"d6",damageType:"piercing",attackType:"ranged",range:80,properties:["ammo","accuracy"]},baseStats:{STR:12,DEX:14,CON:12,INT:6,WIS:8,CHA:5},baseHP:13,baseAC:13,baseSpeed:30,audioPreference:{bass:.1,mid:.2,treble:.9},resistances:{resistances:["necrotic"],immunities:["poison"]}},{id:"zombie",name:"Zombie",category:"undead",archetype:"brute",signatureAbility:{id:"zombie_undead_grip",name:"Undead Grip",description:"A grapple followed by a bite that deals necrotic damage. The zombie's grip is supernaturally strong and unnaturally cold.",damageDie:"d6",damageType:"necrotic",attackType:"melee",properties:["grapple","persistent"]},baseStats:{STR:14,DEX:8,CON:14,INT:3,WIS:6,CHA:5},baseHP:22,baseAC:8,baseSpeed:20,audioPreference:{bass:.9,mid:.1,treble:.1},resistances:{resistances:["necrotic"],immunities:["poison"]}},{id:"wight",name:"Wight",category:"undead",archetype:"brute",signatureAbility:{id:"wight_life_drain",name:"Life Drain",description:"A melee attack that deals necrotic damage and heals the wight for half the damage dealt. The stolen life force sustains its unholy existence.",damageDie:"d6",damageType:"necrotic",attackType:"melee",properties:["lifesteal"]},baseStats:{STR:16,DEX:12,CON:14,INT:10,WIS:12,CHA:14},baseHP:26,baseAC:14,baseSpeed:30,audioPreference:{bass:.3,mid:.6,treble:.2},resistances:{resistances:["necrotic"],immunities:["poison"]}},{id:"ghost",name:"Ghost",category:"undead",archetype:"support",signatureAbility:{id:"ghost_horrifying_visage",name:"Horrifying Visage",description:"Projects terrifying spectral visage that frightens nearby enemies. Frightened creatures have disadvantage on ability checks and attack rolls.",damageDie:"d6",damageType:"psychic",attackType:"spell",range:30,properties:["debuff","fear","control"]},baseStats:{STR:8,DEX:14,CON:12,INT:12,WIS:14,CHA:16},baseHP:15,baseAC:11,baseSpeed:0,audioPreference:{bass:.2,mid:.7,treble:.3},resistances:{resistances:["acid","cold","fire","lightning","thunder"],immunities:["poison","necrotic"]}}],cr=[{id:"imp",name:"Imp",category:"fiend",archetype:"archer",signatureAbility:{id:"imp_sting",name:"Sting",description:"A poisonous tail strike that deals poison damage and may poison the target. The imp's venom causes necrosis and weakness.",damageDie:"d6",damageType:"poison",attackType:"ranged",range:30,properties:["poison","debuff"]},baseStats:{STR:6,DEX:15,CON:10,INT:11,WIS:10,CHA:12},baseHP:10,baseAC:13,baseSpeed:20,audioPreference:{bass:.1,mid:.2,treble:.9},resistances:{resistances:["fire","cold"],immunities:["poison"]}},{id:"quasit",name:"Quasit",category:"fiend",archetype:"support",signatureAbility:{id:"quasit_fear_aura",name:"Fear Aura",description:"Projects an aura of unnatural terror that frightens nearby enemies. Frightened creatures have disadvantage on ability checks.",damageDie:"d6",damageType:"psychic",attackType:"spell",range:20,properties:["debuff","fear","control"]},baseStats:{STR:8,DEX:12,CON:10,INT:10,WIS:11,CHA:12},baseHP:7,baseAC:13,baseSpeed:40,audioPreference:{bass:.2,mid:.7,treble:.3},resistances:{resistances:["fire","cold"],immunities:["poison"]}},{id:"lemure",name:"Lemure",category:"fiend",archetype:"brute",signatureAbility:{id:"lemure_hellish_resilience",name:"Hellish Resilience",description:"A slam attack that deals fire damage while simultaneously healing the lemure. Its corrupted flesh regenerates from hellfire.",damageDie:"d6",damageType:"fire",attackType:"melee",properties:["lifesteal","persistent"]},baseStats:{STR:10,DEX:6,CON:12,INT:3,WIS:8,CHA:5},baseHP:13,baseAC:7,baseSpeed:30,audioPreference:{bass:.9,mid:.1,treble:.1},resistances:{resistances:["fire","cold"],immunities:["poison"]}},{id:"demon",name:"Demon",category:"fiend",archetype:"brute",signatureAbility:{id:"demon_chaos_claw",name:"Chaos Claw",description:"A claw attack infused with chaotic energy that deals random damage types. Each strike channels the unpredictability of the abyss.",damageDie:"d6",damageType:"necrotic",attackType:"melee",properties:["chaotic","versatile"]},baseStats:{STR:16,DEX:12,CON:14,INT:8,WIS:10,CHA:10},baseHP:28,baseAC:13,baseSpeed:30,audioPreference:{bass:.8,mid:.3,treble:.2},resistances:{resistances:["fire","cold"],immunities:["poison"]}}],ur=[{id:"fire-elemental",name:"Fire Elemental",category:"elemental",archetype:"brute",signatureAbility:{id:"fire_elemental_burning_touch",name:"Burning Touch",description:"A searing touch that deals fire damage and applies burning damage over time. The flames continue to burn after impact.",damageDie:"d6",damageType:"fire",attackType:"melee",properties:["ongoing","burn"]},baseStats:{STR:14,DEX:12,CON:16,INT:6,WIS:10,CHA:6},baseHP:26,baseAC:13,baseSpeed:50,audioPreference:{bass:.8,mid:.2,treble:.1},resistances:{immunities:["fire"],resistances:[]}},{id:"earth-elemental",name:"Earth Elemental",category:"elemental",archetype:"brute",signatureAbility:{id:"earth_elemental_earth_slam",name:"Earth Slam",description:"A devastating slam that deals bludgeoning damage in an area and knocks targets prone. The ground cracks under its power.",damageDie:"d8",damageType:"bludgeoning",attackType:"melee",properties:["aoe","prone"]},baseStats:{STR:18,DEX:8,CON:16,INT:6,WIS:10,CHA:6},baseHP:30,baseAC:17,baseSpeed:20,audioPreference:{bass:.9,mid:.1,treble:.1},resistances:{immunities:["poison"],resistances:["necrotic"]}},{id:"water-elemental",name:"Water Elemental",category:"elemental",archetype:"support",signatureAbility:{id:"water_elemental_whirlpool",name:"Whirlpool",description:"Creates a swirling vortex that restrains targets and pulls them toward the center. The crushing waves hold enemies in place.",damageDie:"d6",damageType:"cold",attackType:"spell",range:30,properties:["control","restrain","pull"]},baseStats:{STR:14,DEX:14,CON:14,INT:8,WIS:12,CHA:8},baseHP:22,baseAC:14,baseSpeed:30,audioPreference:{bass:.2,mid:.7,treble:.2},resistances:{immunities:["cold"],resistances:["fire","necrotic"]}},{id:"air-elemental",name:"Air Elemental",category:"elemental",archetype:"archer",signatureAbility:{id:"air_elemental_wind_blast",name:"Wind Blast",description:"Projects a concentrated gust of wind that pushes targets back and deals force damage. The howling wind disrupts formation.",damageDie:"d6",damageType:"force",attackType:"ranged",range:60,properties:["push","control"]},baseStats:{STR:12,DEX:16,CON:14,INT:8,WIS:12,CHA:8},baseHP:18,baseAC:15,baseSpeed:90,audioPreference:{bass:.1,mid:.2,treble:.9},resistances:{immunities:["lightning"],resistances:["thunder"]}}],dr=[{id:"animated-armor",name:"Animated Armor",category:"construct",archetype:"brute",signatureAbility:{id:"animated_armor_slam",name:"Slam",description:"A powerful melee attack that deals force damage. The enchanted armor strikes with supernatural strength.",damageDie:"d6",damageType:"force",attackType:"melee",properties:["force"]},baseStats:{STR:14,DEX:10,CON:14,INT:3,WIS:6,CHA:1},baseHP:18,baseAC:18,baseSpeed:25,audioPreference:{bass:.9,mid:.1,treble:.1},resistances:{immunities:["poison","psychic"]}},{id:"golem",name:"Golem",category:"construct",archetype:"brute",signatureAbility:{id:"golem_immutable_form",name:"Immutable Form",description:"A devastating slam that deals bludgeoning damage. The golem is immune to most status conditions and non-magical physical attacks.",damageDie:"d8",damageType:"bludgeoning",attackType:"melee",properties:["status_immunity","nonmagical_resistance"]},baseStats:{STR:18,DEX:8,CON:16,INT:3,WIS:6,CHA:1},baseHP:32,baseAC:15,baseSpeed:20,audioPreference:{bass:.9,mid:.1,treble:0},resistances:{immunities:["poison","psychic"],resistances:["bludgeoning","piercing","slashing"]}},{id:"flying-sword",name:"Flying Sword",category:"construct",archetype:"archer",signatureAbility:{id:"flying_sword_diving_strike",name:"Diving Strike",description:"A diving attack that deals bonus damage when moving toward the target. The enchanted sword strikes with supernatural precision.",damageDie:"d6",damageType:"slashing",attackType:"melee",properties:["charge","accuracy"]},baseStats:{STR:12,DEX:16,CON:12,INT:1,WIS:4,CHA:1},baseHP:10,baseAC:17,baseSpeed:50,audioPreference:{bass:.1,mid:.1,treble:.9},resistances:{immunities:["poison","psychic"]}},{id:"shield-guardian",name:"Shield Guardian",category:"construct",archetype:"support",signatureAbility:{id:"shield_guardian_protection_aura",name:"Protection Aura",description:"Projects a protective field that increases armor class of nearby allies. The magical shield deflects attacks meant for others.",damageDie:"d6",damageType:"force",attackType:"spell",range:20,properties:["buff","ally","defense"]},baseStats:{STR:14,DEX:10,CON:16,INT:6,WIS:10,CHA:5},baseHP:22,baseAC:16,baseSpeed:30,audioPreference:{bass:.2,mid:.7,treble:.2},resistances:{immunities:["poison","psychic"]}}],hr=[{id:"young-red-dragon",name:"Young Red Dragon",category:"dragon",archetype:"brute",signatureAbility:{id:"young_red_dragon_fire_breath",name:"Fire Breath",description:"Exhales a cone of searing flame that deals fire damage to all creatures in the area. The intense heat can ignite flammable objects.",damageDie:"d8",damageType:"fire",attackType:"ranged",range:30,properties:["aoe","cone","burn"]},baseStats:{STR:19,DEX:12,CON:17,INT:12,WIS:13,CHA:15},baseHP:45,baseAC:18,baseSpeed:40,audioPreference:{bass:.9,mid:.2,treble:.1},resistances:{immunities:["fire"],resistances:[]}},{id:"dragon-wyrmling",name:"Dragon Wyrmling",category:"dragon",archetype:"brute",signatureAbility:{id:"dragon_wyrmling_multiattack",name:"Bite + Claw",description:"A combination attack with bite and claws that deals multiple physical attacks. The wyrmling attacks with ferocious hunger.",damageDie:"d6",damageType:"slashing",attackType:"melee",properties:["multiattack"]},baseStats:{STR:15,DEX:12,CON:14,INT:10,WIS:11,CHA:12},baseHP:26,baseAC:17,baseSpeed:30,audioPreference:{bass:.3,mid:.6,treble:.2},resistances:{immunities:[],resistances:["acid"]}},{id:"drake",name:"Drake",category:"dragon",archetype:"brute",signatureAbility:{id:"drake_tail_swipe",name:"Tail Swipe",description:"A powerful tail strike that deals bludgeoning damage and knocks targets back. The force can push enemies away from the drake.",damageDie:"d6",damageType:"bludgeoning",attackType:"melee",properties:["knockback","push"]},baseStats:{STR:16,DEX:12,CON:14,INT:8,WIS:10,CHA:10},baseHP:32,baseAC:15,baseSpeed:40,audioPreference:{bass:.8,mid:.3,treble:.1},resistances:{immunities:[],resistances:["cold"]}},{id:"young-blue-dragon",name:"Young Blue Dragon",category:"dragon",archetype:"archer",signatureAbility:{id:"young_blue_dragon_lightning_breath",name:"Lightning Breath",description:"Exhales a line of crackling lightning that deals lightning damage to all creatures in its path. The electrical discharge arcs between targets.",damageDie:"d8",damageType:"lightning",attackType:"ranged",range:60,properties:["aoe","line","chain"]},baseStats:{STR:17,DEX:14,CON:15,INT:14,WIS:13,CHA:15},baseHP:38,baseAC:19,baseSpeed:40,audioPreference:{bass:.1,mid:.2,treble:.9},resistances:{immunities:["lightning"],resistances:["thunder"]}}],pr=[{id:"owlbear",name:"Owlbear",category:"monstrosity",archetype:"brute",signatureAbility:{id:"owlbear_multiattack",name:"Multiattack",description:"A devastating combination of beak and claws attacks. The owlbear unleashes its primal fury in a flurry of strikes.",damageDie:"d6",damageType:"slashing",attackType:"melee",properties:["multiattack"]},baseStats:{STR:18,DEX:10,CON:14,INT:3,WIS:10,CHA:6},baseHP:37,baseAC:13,baseSpeed:40,audioPreference:{bass:.9,mid:.2,treble:.1},resistances:{resistances:[]}},{id:"griffin",name:"Griffin",category:"monstrosity",archetype:"archer",signatureAbility:{id:"griffin_dive_attack",name:"Dive Attack",description:"A high-speed diving attack from above that deals extra piercing damage. The griffin uses its powerful wings to strike with devastating force.",damageDie:"d6",damageType:"piercing",attackType:"ranged",range:80,properties:["charge","flying"]},baseStats:{STR:16,DEX:15,CON:14,INT:4,WIS:12,CHA:7},baseHP:30,baseAC:12,baseSpeed:50,audioPreference:{bass:.1,mid:.2,treble:.9},resistances:{resistances:[]}},{id:"mimic",name:"Mimic",category:"monstrosity",archetype:"brute",signatureAbility:{id:"mimic_adhesive",name:"Adhesive",description:"A pseudopod attack that deals bludgeoning damage and grapples the target. The mimic's adhesive surface traps prey.",damageDie:"d6",damageType:"bludgeoning",attackType:"melee",properties:["grapple","ambush"]},baseStats:{STR:14,DEX:10,CON:12,INT:6,WIS:8,CHA:4},baseHP:21,baseAC:12,baseSpeed:10,audioPreference:{bass:.2,mid:.7,treble:.3},resistances:{resistances:["acid"]}},{id:"basilisk",name:"Basilisk",category:"monstrosity",archetype:"support",signatureAbility:{id:"basilisk_petrifying_gaze",name:"Petrifying Gaze",description:"A gaze attack that can stun targets. Those who meet the basilisk's eyes feel their limbs turning to stone.",damageDie:"d6",damageType:"psychic",attackType:"spell",range:30,properties:["debuff","control","stun"]},baseStats:{STR:14,DEX:10,CON:12,INT:4,WIS:6,CHA:6},baseHP:26,baseAC:14,baseSpeed:20,audioPreference:{bass:.2,mid:.7,treble:.2},resistances:{resistances:["poison"]}}],mr=[{id:"orc",name:"Orc",category:"humanoid",archetype:"brute",signatureAbility:{id:"orc_savage_strike",name:"Savage Strike",description:"A powerful melee attack that deals extra damage. The ferocity of the attack embodies primal aggression.",damageDie:"d6",damageType:"slashing",attackType:"melee",properties:["versatile"]},baseStats:{STR:16,DEX:12,CON:14,INT:8,WIS:10,CHA:8},baseHP:15,baseAC:13,baseSpeed:30,audioPreference:{bass:.8,mid:.3,treble:.1},resistances:{resistances:["poison"]}},{id:"bandit",name:"Bandit",category:"humanoid",archetype:"brute",signatureAbility:{id:"bandit_cheap_shot",name:"Cheap Shot",description:"A dirty fighting technique that deals bonus damage against surprised or flat-footed opponents.",damageDie:"d6",damageType:"slashing",attackType:"melee",properties:["finesse"]},baseStats:{STR:13,DEX:14,CON:12,INT:10,WIS:10,CHA:11},baseHP:11,baseAC:12,baseSpeed:30,audioPreference:{bass:.4,mid:.6,treble:.3},resistances:{resistances:[]}},{id:"hunter",name:"Hunter",category:"humanoid",archetype:"archer",signatureAbility:{id:"hunter_precise_shot",name:"Precise Shot",description:"A carefully aimed shot that can ignore half cover. Exceptional accuracy allows targeting vulnerabilities.",damageDie:"d6",damageType:"piercing",attackType:"ranged",range:120,properties:["accuracy"]},baseStats:{STR:11,DEX:16,CON:12,INT:12,WIS:14,CHA:10},baseHP:10,baseAC:13,baseSpeed:30,audioPreference:{bass:.1,mid:.3,treble:.8},resistances:{resistances:[]}},{id:"goblin-archer",name:"Goblin Archer",category:"humanoid",archetype:"archer",signatureAbility:{id:"goblin_sneaky_shot",name:"Sneaky Shot",description:"A quick shot from hiding that deals bonus damage. Works best when the goblin is unseen.",damageDie:"d6",damageType:"piercing",attackType:"ranged",range:80,properties:["finesse","stealth"]},baseStats:{STR:8,DEX:15,CON:10,INT:10,WIS:8,CHA:8},baseHP:7,baseAC:13,baseSpeed:30,audioPreference:{bass:.1,mid:.2,treble:.9},resistances:{resistances:[]}},{id:"shaman",name:"Shaman",category:"humanoid",archetype:"support",signatureAbility:{id:"shaman_spirit_bond",name:"Spirit Bond",description:"Channels spiritual energy to grant nearby allies a damage bonus on their next attack.",damageDie:"d6",damageType:"force",attackType:"spell",range:30,properties:["buff","ally"]},baseStats:{STR:10,DEX:10,CON:12,INT:12,WIS:15,CHA:13},baseHP:9,baseAC:11,baseSpeed:30,audioPreference:{bass:.3,mid:.7,treble:.3},resistances:{resistances:["necrotic"]}},{id:"cultist",name:"Cultist",category:"humanoid",archetype:"support",signatureAbility:{id:"cultist_dark_blessing",name:"Dark Blessing",description:"Invokes dark powers to temporarily increase nearby allies' armor class.",damageDie:"d6",damageType:"necrotic",attackType:"spell",range:20,properties:["buff","ally","defense"]},baseStats:{STR:11,DEX:12,CON:12,INT:13,WIS:11,CHA:14},baseHP:9,baseAC:13,baseSpeed:30,audioPreference:{bass:.3,mid:.7,treble:.2},resistances:{resistances:["necrotic"]}},{id:"bear",name:"Bear",category:"beast",archetype:"brute",signatureAbility:{id:"bear_maul",name:"Maul",description:"A devastating multiattack with claws that can also grapple the target. Raw power embodied.",damageDie:"d6",damageType:"slashing",attackType:"melee",properties:["multiattack","grapple"]},baseStats:{STR:18,DEX:10,CON:16,INT:2,WIS:12,CHA:6},baseHP:34,baseAC:11,baseSpeed:40,audioPreference:{bass:.9,mid:.2,treble:.1},resistances:{resistances:["cold"]}},{id:"boar",name:"Boar",category:"beast",archetype:"brute",signatureAbility:{id:"boar_gore_charge",name:"Gore Charge",description:"A ferocious charge that deals bonus damage if the boar moves at least 20 feet before attacking.",damageDie:"d6",damageType:"piercing",attackType:"melee",properties:["charge"]},baseStats:{STR:13,DEX:11,CON:12,INT:2,WIS:8,CHA:4},baseHP:11,baseAC:11,baseSpeed:40,audioPreference:{bass:.8,mid:.3,treble:.1},resistances:{resistances:[]}},{id:"giant-spider",name:"Giant Spider",category:"beast",archetype:"archer",signatureAbility:{id:"spider_web_spray",name:"Web Spray",description:"Shoots sticky webbing at a target to restrain them. A restrained creature has speed 0 and can't use reactions.",damageDie:"d6",damageType:"poison",attackType:"ranged",range:60,properties:["control","restrain"]},baseStats:{STR:12,DEX:14,CON:12,INT:2,WIS:12,CHA:4},baseHP:26,baseAC:13,baseSpeed:30,audioPreference:{bass:.2,mid:.3,treble:.8},resistances:{resistances:["poison"]}},{id:"stirge",name:"Stirge",category:"beast",archetype:"archer",signatureAbility:{id:"stirge_blood_drain",name:"Blood Drain",description:"Attaches to target and drains blood, dealing damage and healing the stirge for half the damage dealt.",damageDie:"d6",damageType:"piercing",attackType:"melee",properties:["lifesteal","grapple"]},baseStats:{STR:4,DEX:14,CON:10,INT:1,WIS:6,CHA:2},baseHP:5,baseAC:12,baseSpeed:10,audioPreference:{bass:.1,mid:.2,treble:.9},resistances:{resistances:[]}}],Xe=[...mr,...lr,...cr,...ur,...dr,...hr,...pr];function gr(s){return Xe.find(e=>e.id===s)}function fr(s){return Xe.filter(e=>e.category===s)}function yr(s){return Xe.filter(e=>e.archetype===s)}const Ca={common:{statMultiplier:1,hpMultiplier:1,signatureDieSize:6,extraAbilityCount:0,hasResistances:!1},uncommon:{statMultiplier:1.08,hpMultiplier:1.3,signatureDieSize:8,extraAbilityCount:1,hasResistances:!1},elite:{statMultiplier:1.15,hpMultiplier:1.7,signatureDieSize:10,extraAbilityCount:2,hasResistances:!0},boss:{statMultiplier:1.25,hpMultiplier:2.2,signatureDieSize:12,extraAbilityCount:3,hasResistances:!0}};function Oe(s){return Ca[s]}function vr(s){return`1d${Ca[s].signatureDieSize}`}function Ts(){return["common","uncommon","elite","boss"]}function br(s){const e=Ts(),t=e.indexOf(s);if(!(t===-1||t===e.length-1))return e[t+1]}function wr(s){return s}const Sr=["Human","Elf","Dwarf","Halfling","Dragonborn","Gnome","Half-Elf","Half-Orc","Tiefling"],_r=Sr.map(wr);function Cs(s){return s}const Ar=["Barbarian","Bard","Cleric","Druid","Fighter","Monk","Paladin","Ranger","Rogue","Sorcerer","Warlock","Wizard"],Re=Ar.map(Cs);function ra(s){if(typeof s!="string")return!1;if(Re.includes(s))return!0;try{const t=require("../extensions/ExtensionManager.js").ExtensionManager.getInstance();if(t.get("classes")?.includes(s))return!0;const i=t.get("classes.data");if(i&&Array.isArray(i)&&i.map(r=>r.name).includes(s))return!0}catch{}return!1}const lt=["STR","DEX","CON","INT","WIS","CHA"];function Pe(s){return lt.includes(s)}const Is=["http://","https://","/","assets/"];function Es(s){return typeof s!="string"||s.trim()===""?!1:Is.some(e=>s.startsWith(e))}function hi(s,e){return s===void 0?{valid:!0,errors:[]}:typeof s!="string"?{valid:!1,errors:[`${e} must be a string`]}:s.trim()===""?{valid:!1,errors:[`${e} cannot be an empty string`]}:Es(s)?{valid:!0,errors:[]}:{valid:!1,errors:[`${e} must start with one of: ${Is.join(", ")} (got: "${s}")`]}}function Be(s){const e=[],t=hi(s.icon,"icon");t.valid||e.push(...t.errors);const a=hi(s.image,"image");return a.valid||e.push(...a.errors),e}const pi=["passive","active","resource","trigger"],mt=["default","custom"],mi=["stat_bonus","skill_proficiency","ability_unlock","passive_modifier","resource_grant","spell_slot_bonus"],gt=["Human","Elf","Dwarf","Halfling","Dragonborn","Gnome","Half-Elf","Half-Orc","Tiefling"],ft=["athletics","acrobatics","sleight_of_hand","stealth","arcana","history","investigation","nature","religion","animal_handling","insight","medicine","perception","survival","deception","intimidation","performance","persuasion"],gi=["none","proficient","expertise"];class Fe{static validateClassFeature(e,t){const a=[];if(!e||typeof e!="object"||Array.isArray(e))return{valid:!1,errors:["Class feature must be an object"]};const i=e;if(!i.id||typeof i.id!="string"?a.push("Feature must have a valid id (string)"):this.isValidFeatureId(i.id)||a.push(`Feature id must use lowercase_with_underscores format: "${i.id}"`),(!i.name||typeof i.name!="string")&&a.push("Feature must have a valid name (string)"),(!i.description||typeof i.description!="string")&&a.push("Feature must have a description (string)"),!i.type||typeof i.type!="string"?a.push("Feature must have a type (string)"):pi.includes(i.type)||a.push(`Invalid type: "${i.type}". Must be one of: ${pi.join(", ")}`),!i.class||typeof i.class!="string"?a.push("Feature must have a class (string)"):ra(i.class)||a.push(`Invalid class: "${i.class}". Must be one of: ${Re.join(", ")} or a custom class registered via ExtensionManager.`),typeof i.level!="number"?a.push("Feature must have a level (number)"):(i.level<1||i.level>20)&&a.push(`Feature level must be between 1 and 20 (got: ${i.level})`),!i.source||typeof i.source!="string"?a.push("Feature must have a source (string)"):mt.includes(i.source)||a.push(`Invalid source: "${i.source}". Must be one of: ${mt.join(", ")}`),i.tags!==void 0)if(!Array.isArray(i.tags))a.push("Feature tags must be an array");else for(const r of i.tags)typeof r!="string"&&a.push(`Feature tag must be a string (got: ${typeof r})`);i.lore!==void 0&&typeof i.lore!="string"&&a.push("Feature lore must be a string"),i.subrace!==void 0&&typeof i.subrace!="string"&&a.push("Feature subrace must be a string");const n=Be({icon:i.icon,image:i.image});if(a.push(...n.map(r=>`Feature ${r}`)),i.prerequisites!==void 0){const r=this.validatePrerequisites(i.prerequisites,t);r.valid||a.push(...r.errors.map(o=>`Prerequisites: ${o}`))}if(i.effects!==void 0)if(!Array.isArray(i.effects))a.push("Feature effects must be an array");else for(let r=0;r<i.effects.length;r++){const o=this.validateEffect(i.effects[r]);o.valid||a.push(...o.errors.map(l=>`Effect ${r}: ${l}`))}return{valid:a.length===0,errors:a}}static validateRacialTrait(e,t){const a=[];if(!e||typeof e!="object"||Array.isArray(e))return{valid:!1,errors:["Racial trait must be an object"]};const i=e;if(!i.id||typeof i.id!="string"?a.push("Trait must have a valid id (string)"):this.isValidFeatureId(i.id)||a.push(`Trait id must use lowercase_with_underscores format: "${i.id}"`),(!i.name||typeof i.name!="string")&&a.push("Trait must have a valid name (string)"),(!i.description||typeof i.description!="string")&&a.push("Trait must have a description (string)"),!i.race||typeof i.race!="string")a.push("Trait must have a race (string)");else{const r=gt.includes(i.race),o=t?.customRaces??[],l=o.includes(i.race);if(!r&&!l){const c=o.length>0?`, ${o.join(", ")}`:"";a.push(`Invalid race: "${i.race}". Must be one of: ${gt.join(",")}${c} or a custom race registered via ExtensionManager.`)}}if(!i.source||typeof i.source!="string"?a.push("Trait must have a source (string)"):mt.includes(i.source)||a.push(`Invalid source: "${i.source}". Must be one of: ${mt.join(", ")}`),i.tags!==void 0)if(!Array.isArray(i.tags))a.push("Trait tags must be an array");else for(const r of i.tags)typeof r!="string"&&a.push(`Trait tag must be a string (got: ${typeof r})`);i.lore!==void 0&&typeof i.lore!="string"&&a.push("Trait lore must be a string"),i.subrace!==void 0&&typeof i.subrace!="string"&&a.push("Trait subrace must be a string");const n=Be({icon:i.icon,image:i.image});if(a.push(...n.map(r=>`Trait ${r}`)),i.prerequisites!==void 0){const r=this.validatePrerequisites(i.prerequisites,t);r.valid||a.push(...r.errors.map(o=>`Prerequisites: ${o}`))}if(i.effects!==void 0)if(!Array.isArray(i.effects))a.push("Trait effects must be an array");else for(let r=0;r<i.effects.length;r++){const o=this.validateEffect(i.effects[r]);o.valid||a.push(...o.errors.map(l=>`Effect ${r}: ${l}`))}return{valid:a.length===0,errors:a}}static validateEffect(e){const t=[];if(!e||typeof e!="object"||Array.isArray(e))return{valid:!1,errors:["Effect must be an object"]};const a=e;if(!a.type||typeof a.type!="string"?t.push("Effect must have a type (string)"):mi.includes(a.type)||t.push(`Invalid effect type: "${a.type}". Must be one of: ${mi.join(", ")}`),!a.target||typeof a.target!="string")t.push("Effect must have a target (string)");else{const i=a.type,n=a.target;if(!(i==="stat_bonus"&&this.isAbility(n))){if(i==="skill_proficiency"&&!ft.includes(n))t.push(`Invalid skill target: "${n}". Must be one of: ${ft.join(", ")}`);else if(i==="skill_proficiency"){const r=a.value;typeof r=="string"&&!gi.includes(r)&&t.push(`Invalid proficiency level: "${r}". Must be one of: ${gi.join(", ")}`)}}}return a.value===void 0&&t.push("Effect must have a value"),a.condition!==void 0&&typeof a.condition!="string"&&t.push("Effect condition must be a string"),a.description!==void 0&&typeof a.description!="string"&&t.push("Effect description must be a string"),{valid:t.length===0,errors:t}}static validatePrerequisites(e,t){const a=[];if(!e||typeof e!="object"||Array.isArray(e))return{valid:!1,errors:["Prerequisites must be an object"]};const i=e;if(i.level!==void 0&&(typeof i.level!="number"?a.push("Prerequisite level must be a number"):(i.level<1||i.level>20)&&a.push(`Prerequisite level must be between 1 and 20 (got: ${i.level})`)),i.abilities!==void 0)if(typeof i.abilities!="object"||Array.isArray(i.abilities)||i.abilities===null)a.push("Prerequisite abilities must be a record");else for(const[n,r]of Object.entries(i.abilities))lt.includes(n)||a.push(`Invalid ability in prerequisites: "${n}". Must be one of: ${lt.join(", ")}`),(typeof r!="number"||r<1||r>20)&&a.push(`Ability score requirement for ${n} must be a number between 1 and 20`);if(i.class!==void 0){if(typeof i.class!="string")a.push("Prerequisite class must be a string");else if(!ra(i.class)){const n=t?.customClasses??[];if(!n.includes(i.class)){const o=n.length>0?`, ${n.join(", ")}`:"";a.push(`Invalid prerequisite class: "${i.class}". Must be one of: ${Re.join(",")}${o} or a custom class registered via ExtensionManager.`)}}}if(i.race!==void 0)if(typeof i.race!="string")a.push("Prerequisite race must be a string");else{const n=gt.includes(i.race),r=t?.customRaces??[],o=r.includes(i.race);if(!n&&!o){const l=r.length>0?`, ${r.join(", ")}`:"";a.push(`Invalid prerequisite race: "${i.race}". Must be one of: ${gt.join(",")}${l} or a custom race registered via ExtensionManager.`)}}if(i.features!==void 0)if(!Array.isArray(i.features))a.push("Prerequisite features must be an array");else for(const n of i.features)typeof n!="string"?a.push("Prerequisite feature IDs must be strings"):this.isValidFeatureId(n)||a.push(`Invalid prerequisite feature ID format: "${n}"`);if(i.subrace!==void 0&&(typeof i.subrace!="string"?a.push("Prerequisite subrace must be a string"):i.subrace.trim()===""&&a.push("Prerequisite subrace cannot be empty")),i.skills!==void 0)if(!Array.isArray(i.skills))a.push("Prerequisite skills must be an array");else for(const n of i.skills)typeof n!="string"?a.push("Prerequisite skill IDs must be strings"):ft.includes(n)||a.push(`Invalid prerequisite skill: "${n}". Must be one of: ${ft.join(", ")}`);if(i.spells!==void 0)if(!Array.isArray(i.spells))a.push("Prerequisite spells must be an array");else for(const n of i.spells)typeof n!="string"?a.push("Prerequisite spell names must be strings"):n.trim()===""&&a.push("Prerequisite spell name cannot be empty");return i.custom!==void 0&&typeof i.custom!="string"&&a.push("Prerequisite custom condition must be a string"),{valid:a.length===0,errors:a}}static validateClassFeatures(e){const t=[];return Array.isArray(e)?(e.forEach((a,i)=>{const n=this.validateClassFeature(a);n.valid||t.push(`Feature at index ${i}: ${n.errors.join("; ")}`)}),{valid:t.length===0,errors:t}):{valid:!1,errors:["Input must be an array of class features"]}}static validateRacialTraits(e){const t=[];return Array.isArray(e)?(e.forEach((a,i)=>{const n=this.validateRacialTrait(a);n.valid||t.push(`Trait at index ${i}: ${n.errors.join("; ")}`)}),{valid:t.length===0,errors:t}):{valid:!1,errors:["Input must be an array of racial traits"]}}static isAbility(e){return Pe(e)}static isValidFeatureId(e){return/^[a-z][a-z0-9_]*$/.test(e)}}function kr(s,e){return Fe.validateClassFeature(s,e)}function Tr(s,e){return Fe.validateRacialTrait(s,e)}function Cr(s){return Fe.validateClassFeatures(s)}function Ir(s){return Fe.validateRacialTraits(s)}function Er(s){const e=[];if(s==null)return{valid:!0,errors:[]};if(typeof s!="object"||Array.isArray(s))return{valid:!1,errors:["Prerequisites must be an object"]};const t=s;if(t.level!==void 0&&(typeof t.level!="number"?e.push("Prerequisite level must be a number"):t.level<1&&e.push(`Prerequisite level must be at least 1 (got: ${t.level})`)),t.casterLevel!==void 0&&(typeof t.casterLevel!="number"?e.push("Prerequisite casterLevel must be a number"):t.casterLevel<1&&e.push(`Prerequisite casterLevel must be at least 1 (got: ${t.casterLevel})`)),t.abilities!==void 0)if(typeof t.abilities!="object"||Array.isArray(t.abilities)||t.abilities===null)e.push("Prerequisite abilities must be a record");else for(const[a,i]of Object.entries(t.abilities))Pe(a)||e.push(`Invalid ability in prerequisites: "${a}". Must be one of: STR, DEX, CON, INT, WIS, CHA`),typeof i!="number"?e.push(`Ability score for ${a} must be a number (got: ${typeof i})`):(i<1||i>20)&&e.push(`Ability score for ${a} must be between 1 and 20 (got: ${i})`);if(t.class!==void 0&&typeof t.class!="string"&&e.push("Prerequisite class must be a string"),t.race!==void 0&&typeof t.race!="string"&&e.push("Prerequisite race must be a string"),t.subrace!==void 0&&(typeof t.subrace!="string"?e.push("Prerequisite subrace must be a string"):t.subrace.trim()===""&&e.push("Prerequisite subrace cannot be empty")),t.features!==void 0)if(!Array.isArray(t.features))e.push("Prerequisite features must be an array");else for(const a of t.features)typeof a!="string"&&e.push("Prerequisite feature IDs must be strings");if(t.skills!==void 0)if(!Array.isArray(t.skills))e.push("Prerequisite skills must be an array");else for(const a of t.skills)typeof a!="string"&&e.push("Prerequisite skill IDs must be strings");if(t.spells!==void 0)if(!Array.isArray(t.spells))e.push("Prerequisite spells must be an array");else for(const a of t.spells)typeof a!="string"?e.push("Prerequisite spell names must be strings"):a.trim()===""&&e.push("Prerequisite spell name cannot be empty");return t.custom!==void 0&&typeof t.custom!="string"&&e.push("Prerequisite custom condition must be a string"),{valid:e.length===0,errors:e}}function Ms(s,e){const t=[];if(!s)return{valid:!0,errors:[]};if(s.level!==void 0&&e.level<s.level&&t.push(`Requires level ${s.level} (current: ${e.level})`),s.casterLevel!==void 0&&e.level<s.casterLevel&&t.push(`Requires caster level ${s.casterLevel} (current: ${e.level})`),s.abilities)for(const[a,i]of Object.entries(s.abilities)){const n=e.ability_scores[a];(n===void 0||n<i)&&t.push(`Requires ${a} ${i}+ (current: ${n??0})`)}if(s.class!==void 0&&e.class!==s.class&&t.push(`Requires ${s.class} class (current: ${e.class})`),s.race!==void 0&&e.race!==s.race&&t.push(`Requires ${s.race} race (current: ${e.race})`),s.subrace!==void 0){const a=e.subrace||"";a!==s.subrace&&t.push(`Requires ${s.subrace} subrace (current: ${a||"none"})`)}if(s.skills&&s.skills.length>0)for(const a of s.skills){const i=e.skills[a];i!=="proficient"&&i!=="expertise"&&t.push(`Requires proficiency in ${a} (current: ${i??"none"})`)}if(s.features&&s.features.length>0){const a=e.class_features||[];for(const i of s.features)a.includes(i)||t.push(`Requires feature: ${i}`)}if(s.spells&&s.spells.length>0){const a=e.spells?.known_spells||[],i=e.spells?.cantrips||[],n=[...a,...i];for(const r of s.spells)n.includes(r)||t.push(`Requires spell: ${r}`)}return s.custom,{valid:t.length===0,errors:t}}const fi=["default","custom"],yi=["none","proficient","expertise"],vi=["class","background","feat","custom","racial","other"];class we{static validateSkill(e){const t=[];if(!e||typeof e!="object"||Array.isArray(e))return{valid:!1,errors:["Skill must be an object"]};const a=e;if(!a.id||typeof a.id!="string"?t.push("Skill must have a valid id (string)"):this.isValidSkillId(a.id)||t.push(`Skill id must use lowercase_with_underscores format: "${a.id}"`),(!a.name||typeof a.name!="string")&&t.push("Skill must have a valid name (string)"),!a.ability||typeof a.ability!="string"?t.push("Skill must have an ability (string)"):lt.includes(a.ability)||t.push(`Invalid ability: "${a.ability}". Must be one of: ${lt.join(", ")}`),!a.source||typeof a.source!="string"?t.push("Skill must have a source (string)"):fi.includes(a.source)||t.push(`Invalid source: "${a.source}". Must be one of: ${fi.join(", ")}`),a.description!==void 0&&typeof a.description!="string"&&t.push("Skill description must be a string"),a.armorPenalty!==void 0&&typeof a.armorPenalty!="boolean"&&t.push("Skill armorPenalty must be a boolean"),a.customProperties!==void 0)if(typeof a.customProperties!="object"||Array.isArray(a.customProperties)||a.customProperties===null)t.push("Skill customProperties must be a record");else for(const[n,r]of Object.entries(a.customProperties)){const o=typeof r;if(!["string","number","boolean"].includes(o)&&!Array.isArray(r)&&t.push(`customProperties.${n} has invalid type: ${o}. Must be string, number, boolean, or string array`),Array.isArray(r))for(let l=0;l<r.length;l++)typeof r[l]!="string"&&t.push(`customProperties.${n}[${l}] must be a string`)}if(a.categories!==void 0)if(!Array.isArray(a.categories))t.push("Skill categories must be an array");else for(const n of a.categories)typeof n!="string"&&t.push(`Skill category must be a string (got: ${typeof n})`);if(a.tags!==void 0)if(!Array.isArray(a.tags))t.push("Skill tags must be an array");else for(const n of a.tags)typeof n!="string"&&t.push(`Skill tag must be a string (got: ${typeof n})`);a.lore!==void 0&&typeof a.lore!="string"&&t.push("Skill lore must be a string");const i=Be({icon:a.icon,image:a.image});return t.push(...i.map(n=>`Skill ${n}`)),{valid:t.length===0,errors:t}}static validateSkills(e){const t=[];return Array.isArray(e)?(e.forEach((a,i)=>{const n=this.validateSkill(a);n.valid||t.push(`Skill at index ${i}: ${n.errors.join("; ")}`)}),{valid:t.length===0,errors:t}):{valid:!1,errors:["Input must be an array of skills"]}}static validateSkillProficiency(e){const t=[];if(!e||typeof e!="object"||Array.isArray(e))return{valid:!1,errors:["Skill proficiency must be an object"]};const a=e;return!a.skillId||typeof a.skillId!="string"?t.push("Skill proficiency must have a valid skillId (string)"):this.isValidSkillId(a.skillId)||t.push(`Skill proficiency skillId must use lowercase_with_underscores format: "${a.skillId}"`),!a.level||typeof a.level!="string"?t.push("Skill proficiency must have a level (string)"):yi.includes(a.level)||t.push(`Invalid proficiency level: "${a.level}". Must be one of: ${yi.join(", ")}`),!a.source||typeof a.source!="string"?t.push("Skill proficiency must have a source (string)"):vi.includes(a.source)||t.push(`Invalid proficiency source: "${a.source}". Must be one of: ${vi.join(", ")}`),a.grantedBy!==void 0&&typeof a.grantedBy!="string"&&t.push("Skill proficiency grantedBy must be a string"),{valid:t.length===0,errors:t}}static validateSkillProficiencies(e){const t=[];return Array.isArray(e)?(e.forEach((a,i)=>{const n=this.validateSkillProficiency(a);n.valid||t.push(`Proficiency at index ${i}: ${n.errors.join("; ")}`)}),{valid:t.length===0,errors:t}):{valid:!1,errors:["Input must be an array of skill proficiencies"]}}static validateSkillListDefinition(e){const t=[];if(!e||typeof e!="object"||Array.isArray(e))return{valid:!1,errors:["Skill list definition must be an object"]};const a=e;if((!a.class||typeof a.class!="string")&&t.push("Skill list definition must have a class (string)"),typeof a.skillCount!="number"?t.push("Skill list definition must have a skillCount (number)"):(a.skillCount<0||!Number.isInteger(a.skillCount))&&t.push(`Skill list skillCount must be a non-negative integer (got: ${a.skillCount})`),!Array.isArray(a.availableSkills))t.push("Skill list definition must have availableSkills (array)");else{for(const i of a.availableSkills)typeof i!="string"?t.push(`availableSkills must contain only strings (got: ${typeof i})`):this.isValidSkillId(i)||t.push(`availableSkills contains invalid skill ID format: "${i}"`);typeof a.skillCount=="number"&&a.skillCount>a.availableSkills.length&&t.push(`skillCount (${a.skillCount}) cannot exceed availableSkills length (${a.availableSkills.length})`)}return a.hasExpertise!==void 0&&typeof a.hasExpertise!="boolean"&&t.push("Skill list hasExpertise must be a boolean"),a.expertiseCount!==void 0&&(typeof a.expertiseCount!="number"?t.push("Skill list expertiseCount must be a number"):a.expertiseCount<0||!Number.isInteger(a.expertiseCount)?t.push(`expertiseCount must be a non-negative integer (got: ${a.expertiseCount})`):Array.isArray(a.availableSkills)&&a.expertiseCount>a.availableSkills.length&&t.push(`expertiseCount (${a.expertiseCount}) cannot exceed availableSkills length (${a.availableSkills.length})`)),{valid:t.length===0,errors:t}}static isValidSkillId(e){return/^[a-z][a-z0-9_]*$/.test(e)}static isValidAbility(e){return Pe(e)}static validateSkillPrerequisites(e,t){return Ms(e,t)}}function Mr(s){return we.validateSkill(s)}function Dr(s){return we.validateSkills(s)}function Rr(s){return we.validateSkillProficiency(s)}function Pr(s){return we.validateSkillProficiencies(s)}function xr(s){return we.validateSkillListDefinition(s)}function Nr(s,e){return we.validateSkillPrerequisites(s,e)}const Yt=["Abjuration","Conjuration","Divination","Enchantment","Evocation","Illusion","Necromancy","Transmutation"],Zt=[0,1,2,3,4,5,6,7,8,9],bi=["V","S","M"];class De{static validateSpell(e){const t=[];if(!e||typeof e!="object"||Array.isArray(e))return{valid:!1,errors:["Spell must be an object"]};const a=e;if((!a.name||typeof a.name!="string")&&t.push("Spell must have a valid name (string)"),typeof a.level!="number"?t.push("Spell must have a level (number)"):Zt.includes(a.level)||t.push(`Invalid spell level: "${a.level}". Must be one of: ${Zt.join(", ")}`),!a.school||typeof a.school!="string"?t.push("Spell must have a school (string)"):Yt.includes(a.school)||t.push(`Invalid school: "${a.school}". Must be one of: ${Yt.join(", ")}`),(!a.casting_time||typeof a.casting_time!="string")&&t.push("Spell must have a casting_time (string)"),(!a.range||typeof a.range!="string")&&t.push("Spell must have a range (string)"),!Array.isArray(a.components))t.push("Spell must have components (array)");else for(const n of a.components)typeof n!="string"?t.push(`Spell component must be a string (got: ${typeof n})`):bi.includes(n)||t.push(`Invalid spell component: "${n}". Must be one of: ${bi.join(", ")}`);(!a.duration||typeof a.duration!="string")&&t.push("Spell must have a duration (string)"),a.id!==void 0&&typeof a.id!="string"&&t.push("Spell id must be a string"),a.description!==void 0&&typeof a.description!="string"&&t.push("Spell description must be a string");const i=Be({icon:a.icon,image:a.image});if(t.push(...i.map(n=>`Spell ${n}`)),a.prerequisites!==void 0){const n=this.validatePrerequisites(a.prerequisites);n.valid||t.push(...n.errors.map(r=>`Prerequisites: ${r}`))}return{valid:t.length===0,errors:t}}static validateSpells(e){const t=[];return Array.isArray(e)?(e.forEach((a,i)=>{const n=this.validateSpell(a);n.valid||t.push(`Spell at index ${i}: ${n.errors.join("; ")}`)}),{valid:t.length===0,errors:t}):{valid:!1,errors:["Input must be an array of spells"]}}static validatePrerequisites(e){return Er(e)}static validateSpellPrerequisites(e,t){return Ms(e,t)}static isValidAbility(e){return Pe(e)}static isValidSchool(e){return Yt.includes(e)}static isValidSpellLevel(e){return Zt.includes(e)}}function $r(s){return De.validateSpell(s)}function Lr(s){return De.validateSpells(s)}function Or(s){return De.validatePrerequisites(s)}function Br(s,e){return De.validateSpellPrerequisites(s,e)}class pe{constructor(){this.allSkillsCache=null,this.abilityCache=null,this.categoryCache=null,this.manager=R.getInstance()}static getInstance(){return pe.instance||(pe.instance=new pe),pe.instance}invalidateCache(){this.allSkillsCache=null,this.abilityCache=null,this.categoryCache=null}getSkill(e){return this.getAllSkills().find(a=>a.id===e)}getAllSkills(){if(!this.allSkillsCache){const e=this.manager.get("skills");this.allSkillsCache=e}return this.allSkillsCache}getSkillsByAbility(e){if(!this.abilityCache){this.abilityCache=new Map;const t=this.getAllSkills();for(const a of t)this.abilityCache.has(a.ability)||this.abilityCache.set(a.ability,[]),this.abilityCache.get(a.ability).push(a)}return this.abilityCache.get(e)||[]}getSkillsByCategory(e){if(!this.categoryCache){this.categoryCache=new Map;const t=this.getAllSkills();for(const a of t)if(a.categories&&a.categories.length>0)for(const i of a.categories)this.categoryCache.has(i)||this.categoryCache.set(i,[]),this.categoryCache.get(i).push(a)}return this.categoryCache.get(e)||[]}getCategories(){const e=this.getAllSkills(),t=new Set;for(const a of e)if(a.categories)for(const i of a.categories)t.add(i);return Array.from(t)}getSkillsBySource(e){return this.getAllSkills().filter(t=>t.source===e)}isValidSkill(e){return this.getSkill(e)!==void 0}validateSkill(e){return we.validateSkill(e)}validatePrerequisites(e,t){return we.validateSkillPrerequisites(e.prerequisites,t)}getQueryStats(){const e=this.getAllSkills(),t=e.filter(r=>r.source==="default"),a=e.filter(r=>r.source==="custom"),i={STR:0,DEX:0,CON:0,INT:0,WIS:0,CHA:0};for(const r of e)i[r.ability]++;const n=this.getCategories();return{totalSkills:e.length,defaultSkills:t.length,customSkills:a.length,skillsByAbility:i,categories:n}}getSkillCount(){return this.getAllSkills().length}getAvailableSkills(e){return this.getAllSkills().filter(t=>t.prerequisites?this.validatePrerequisites(t,e).valid:!0)}}function Fr(){return pe.getInstance()}const it=["STR","DEX","CON","INT","WIS","CHA"],wi=["weapon","armor","item","box"],Si=["common","uncommon","rare","very_rare","legendary"],_i=["stat_bonus","skill_proficiency","ability_unlock","passive_modifier","special_property","damage_bonus","stat_requirement"],Ai=["vs_creature_type","at_time_of_day","wielder_race","wielder_class","while_equipped","on_hit","on_damage_taken","custom"],ki=["day","night","dawn","dusk"],Ti=["proficient","expertise"],Ci=["default","custom"],yt=/^\d+d\d+([+-]\d+)?$/,Hr=/^range_\d+_\d+$/;class Dt{static validateEquipment(e){const t=[];if((!e.name||typeof e.name!="string")&&t.push("Equipment must have a valid name (required)"),(!e.type||!wi.includes(e.type))&&t.push(`Equipment type must be one of: ${wi.join(", ")}`),(!e.rarity||!Si.includes(e.rarity))&&t.push(`Equipment rarity must be one of: ${Si.join(", ")}`),(typeof e.weight!="number"||e.weight<0)&&t.push("Equipment weight must be a non-negative number"),e.source!==void 0&&!Ci.includes(e.source)&&t.push(`Equipment source must be one of: ${Ci.join(", ")}`),e.properties)if(!Array.isArray(e.properties))t.push("Equipment properties must be an array");else for(let n=0;n<e.properties.length;n++){const r=this.validateProperty(e.properties[n]);r.valid||t.push(`Property at index ${n}: ${r.errors?.join(", ")}`)}if(e.grantsFeatures)if(!Array.isArray(e.grantsFeatures))t.push("grantsFeatures must be an array");else for(let n=0;n<e.grantsFeatures.length;n++){const r=e.grantsFeatures[n],o=this.validateFeatureReference(r,n);o.valid||t.push(...o.errors||[])}if(e.grantsSkills)if(!Array.isArray(e.grantsSkills))t.push("grantsSkills must be an array");else for(let n=0;n<e.grantsSkills.length;n++){const r=e.grantsSkills[n],o=this.validateSkillReference(r.skillId,n);o.valid||t.push(...o.errors||[]),r.level&&!Ti.includes(r.level)&&t.push(`grantsSkills[${n}]: proficiency level must be 'proficient' or 'expertise'`)}if(e.grantsSpells)if(!Array.isArray(e.grantsSpells))t.push("grantsSpells must be an array");else for(let n=0;n<e.grantsSpells.length;n++){const r=e.grantsSpells[n];(!r.spellId||typeof r.spellId!="string")&&t.push(`grantsSpells[${n}]: must have a valid spellId`),r.uses!==void 0&&(typeof r.uses!="number"||r.uses<0)&&t.push(`grantsSpells[${n}]: uses must be a non-negative number`),r.level!==void 0&&(typeof r.level!="number"||r.level<0)&&t.push(`grantsSpells[${n}]: level must be a non-negative number`)}if(e.damage){const n=this.validateDamageInfo(e.damage);n.valid||t.push(...n.errors||[])}const a=e.acBonus??e.armor_class;if(a!==void 0&&typeof a!="number"&&t.push("AC bonus must be a number"),e.weaponProperties&&(Array.isArray(e.weaponProperties)||t.push("weaponProperties must be an array")),e.spawnWeight!==void 0){const n=this.validateSpawnWeight(e.spawnWeight);n.valid||t.push(...n.errors||[])}if(e.templateId!==void 0&&typeof e.templateId!="string"&&t.push("templateId must be a string"),e.tags&&(Array.isArray(e.tags)||t.push("tags must be an array")),e.type==="box"&&!e.boxContents&&t.push('Equipment of type "box" must have a boxContents property'),e.boxContents!==void 0){const n=this.validateBoxContents(e.boxContents);n.valid||t.push(...n.errors||[])}const i=Be({icon:e.icon,image:e.image});return t.push(...i.map(n=>`Equipment ${n}`)),{valid:t.length===0,errors:t.length>0?t:void 0}}static validateProperty(e){const t=[];if((!e.type||!_i.includes(e.type))&&t.push(`Property type must be one of: ${_i.join(", ")}`),(!e.target||typeof e.target!="string")&&t.push("Property must have a valid target"),e.value===void 0)t.push("Property must have a value");else{const a=this.validatePropertyValue(e.type,e.target,e.value);a.valid||t.push(...a.errors||[])}if(e.condition){const a=this.validateCondition(e.condition);a.valid||t.push(...a.errors||[])}return e.stackable!==void 0&&typeof e.stackable!="boolean"&&t.push("stackable must be a boolean"),e.description!==void 0&&typeof e.description!="string"&&t.push("description must be a string"),{valid:t.length===0,errors:t.length>0?t:void 0}}static validatePropertyValue(e,t,a){const i=[];switch(e){case"stat_bonus":it.includes(t)||i.push(`stat_bonus target must be a valid ability: ${it.join(", ")}`),typeof a!="number"&&i.push("stat_bonus value must be a number");break;case"skill_proficiency":typeof t!="string"?i.push("skill_proficiency target must be a string"):pe.getInstance().isValidSkill(t)||i.push(`skill_proficiency target must be a valid skill ID: "${t}" not found in SkillQuery`),a!=="proficient"&&a!=="expertise"&&typeof a!="boolean"&&i.push('skill_proficiency value must be "proficient", "expertise", or a boolean');break;case"ability_unlock":(typeof t!="string"||t.length===0)&&i.push("ability_unlock must have a valid target"),typeof a!="boolean"&&typeof a!="number"&&i.push("ability_unlock value must be a boolean or number");break;case"passive_modifier":!["ac","armor_class","speed","max_hp","hp_max","initiative","saving_throws","attack_roll","damage_roll"].includes(t)&&!it.includes(t)&&pe.getInstance().isValidSkill(t),typeof a!="number"&&typeof a!="boolean"&&i.push("passive_modifier value must be a number or boolean");break;case"stat_requirement":it.includes(t)||i.push(`stat_requirement target must be a valid ability: ${it.join(", ")}`),typeof a!="number"&&i.push("stat_requirement value must be a number (minimum score)");break;case"special_property":(typeof t!="string"||t.length===0)&&i.push("special_property must have a valid target");break;case"damage_bonus":(typeof t!="string"||t.length===0)&&i.push("damage_bonus must have a valid target (damage type)"),typeof a=="string"&&!yt.test(a)?i.push('damage_bonus value must be a dice format (e.g., "1d6") or a number'):typeof a!="number"&&typeof a!="string"&&i.push("damage_bonus value must be a number or dice string");break;default:i.push(`Unknown property type: ${e}`)}return{valid:i.length===0,errors:i.length>0?i:void 0}}static validateCondition(e){const t=[];(!e.type||!Ai.includes(e.type))&&t.push(`Condition type must be one of: ${Ai.join(", ")}`);const a=e.type,i=e.value;switch(a){case"at_time_of_day":ki.includes(e.value)||t.push(`at_time_of_day value must be one of: ${ki.join(", ")}`);break;case"wielder_race":case"wielder_class":case"vs_creature_type":(typeof i!="string"||i.length===0)&&t.push(`${a} condition must have a valid string value`);break;case"while_equipped":case"on_hit":case"on_damage_taken":typeof i!="boolean"&&t.push(`${a} condition must have a boolean value`);break;case"custom":(typeof i!="string"||i.length===0)&&t.push("custom condition must have a valid value string"),(!e.description||typeof e.description!="string")&&t.push("custom condition must have a description");break;default:t.push(`Unknown condition type: ${a}`)}return{valid:t.length===0,errors:t.length>0?t:void 0}}static validateFeatureReference(e,t){const a=[];if(typeof e=="string"){const i=re.getInstance(),n=i.getClassFeatureById(e),r=n?void 0:i.getRacialTraitById(e);!n&&!r&&a.push(`grantsFeatures[${t}]: Feature "${e}" not found in FeatureQuery`)}else if(typeof e=="object"&&e!==null){if((!e.id||typeof e.id!="string")&&a.push(`grantsFeatures[${t}]: Mini-feature must have a valid id`),(!e.name||typeof e.name!="string")&&a.push(`grantsFeatures[${t}]: Mini-feature must have a valid name`),(!e.description||typeof e.description!="string")&&a.push(`grantsFeatures[${t}]: Mini-feature must have a valid description`),!e.effects||!Array.isArray(e.effects))a.push(`grantsFeatures[${t}]: Mini-feature must have an effects array`);else for(let i=0;i<e.effects.length;i++){const n=this.validateProperty(e.effects[i]);n.valid||a.push(`grantsFeatures[${t}].effects[${i}]: ${n.errors?.join(", ")}`)}e.source!=="equipment_inline"&&a.push(`grantsFeatures[${t}]: Mini-feature must have source: 'equipment_inline'`)}else a.push(`grantsFeatures[${t}]: Feature reference must be a string or object`);return{valid:a.length===0,errors:a.length>0?a:void 0}}static validateEquipmentFeatureReference(e){const t=re.getInstance(),a=t.getClassFeatureById(e),i=a?void 0:t.getRacialTraitById(e);return!!(a||i)}static validateSkillReference(e,t){const a=[],i=t!==void 0?`grantsSkills[${t}]`:"skill";return!e||typeof e!="string"?(a.push(`${i}: must have a valid skillId`),{valid:!1,errors:a}):(pe.getInstance().isValidSkill(e)||a.push(`${i}: Skill "${e}" not found in SkillQuery`),{valid:a.length===0,errors:a.length>0?a:void 0})}static validateEquipmentSkillReference(e){return pe.getInstance().isValidSkill(e)}static validateDamageInfo(e){const t=[];if(!e)return{valid:!0};if(typeof e=="string"){const a=e.trim().split(/\s+/);if(a.length<2)t.push(`Damage string must be in format "NdM type" (e.g., "1d8 slashing"), got: "${e}"`);else{const i=a[0];yt.test(i)||t.push(`Damage dice must be in format "NdM" (e.g., "1d8"), got: ${i}`)}return{valid:t.length===0,errors:t.length>0?t:void 0}}return!e.dice||typeof e.dice!="string"?t.push('Damage must have a valid dice string (e.g., "1d8")'):yt.test(e.dice)||t.push(`Damage dice must be in format "NdM" (e.g., "1d8", "2d6"), got: ${e.dice}`),(!e.damageType||typeof e.damageType!="string")&&t.push("Damage must have a valid damageType"),e.versatile!==void 0&&(typeof e.versatile!="string"?t.push("Damage versatile must be a string (dice format)"):yt.test(e.versatile)||t.push(`Damage versatile must be in dice format "NdM" (e.g., "1d10"), got: ${e.versatile}`)),{valid:t.length===0,errors:t.length>0?t:void 0}}static validateSpawnWeight(e){const t=[];return typeof e!="number"?t.push("Spawn weight must be a number"):e<0?t.push("Spawn weight must be non-negative (0 = never random, still usable by game logic)"):Number.isFinite(e)||t.push("Spawn weight must be a finite number"),{valid:t.length===0,errors:t.length>0?t:void 0}}static validateBoxContents(e){const t=[];if(!e||typeof e!="object")return t.push("boxContents must be an object"),{valid:!1,errors:t};if(!Array.isArray(e.drops))return t.push("boxContents.drops must be an array"),{valid:!1,errors:t};if(e.consumeOnOpen!==void 0&&typeof e.consumeOnOpen!="boolean"&&t.push("boxContents.consumeOnOpen must be a boolean"),e.openRequirements!==void 0)if(!Array.isArray(e.openRequirements))t.push("boxContents.openRequirements must be an array");else for(let a=0;a<e.openRequirements.length;a++){const i=this.validateBoxOpenRequirement(e.openRequirements[a]);i.valid||t.push(...(i.errors||[]).map(n=>`openRequirements[${a}]: ${n}`))}for(let a=0;a<e.drops.length;a++){const i=e.drops[a];if(!i||!Array.isArray(i.pool)){t.push(`boxContents.drops[${a}]: pool must be an array`);continue}if(i.pool.length===0){t.push(`boxContents.drops[${a}]: pool must not be empty`);continue}let n=0;for(let r=0;r<i.pool.length;r++){const o=i.pool[r],l=`boxContents.drops[${a}].pool[${r}]`;typeof o.weight!="number"||o.weight<0?t.push(`${l}: weight must be a non-negative number`):n+=o.weight,o.itemName!==void 0&&o.gold!==void 0&&t.push(`${l}: itemName and gold are mutually exclusive`),o.itemName===void 0&&o.gold===void 0&&t.push(`${l}: must have either itemName or gold`),o.itemName!==void 0&&(typeof o.itemName!="string"||o.itemName.length===0?t.push(`${l}: itemName must be a non-empty string`):R.getInstance().get("equipment").some(h=>h.name===o.itemName)||t.push(`${l}: itemName "${o.itemName}" not found in equipment registry`)),o.gold!==void 0&&(typeof o.gold!="number"||o.gold<0)&&t.push(`${l}: gold must be a non-negative number`),o.quantity!==void 0&&(typeof o.quantity!="number"||o.quantity<1||!Number.isInteger(o.quantity))&&t.push(`${l}: quantity must be a positive integer`)}i.pool.length>0&&Math.abs(n-100)>.001&&t.push(`boxContents.drops[${a}]: pool weights sum to ${n}, expected 100`)}return{valid:t.length===0,errors:t.length>0?t:void 0}}static validateBoxOpenRequirement(e){const t=[];return!e||typeof e!="object"?(t.push("BoxOpenRequirement must be an object"),{valid:!1,errors:t}):(!e.itemName||typeof e.itemName!="string"||e.itemName.length===0?t.push("BoxOpenRequirement.itemName must be a non-empty string"):R.getInstance().get("equipment").some(r=>r.name===e.itemName)||t.push(`BoxOpenRequirement.itemName "${e.itemName}" not found in equipment registry`),e.quantity!==void 0&&(typeof e.quantity!="number"||e.quantity<1||!Number.isInteger(e.quantity))&&t.push("BoxOpenRequirement.quantity must be a positive integer"),{valid:t.length===0,errors:t.length>0?t:void 0})}static validateModification(e){const t=[];if((!e.id||typeof e.id!="string")&&t.push("Modification must have a valid id"),(!e.name||typeof e.name!="string")&&t.push("Modification must have a valid name"),(!e.appliedAt||typeof e.appliedAt!="string")&&t.push("Modification must have a valid appliedAt timestamp"),(!e.source||typeof e.source!="string")&&t.push("Modification must have a valid source"),!e.properties||!Array.isArray(e.properties))t.push("Modification must have a properties array");else for(let a=0;a<e.properties.length;a++){const i=this.validateProperty(e.properties[a]);i.valid||t.push(`Modification properties[${a}]: ${i.errors?.join(", ")}`)}if(e.addsFeatures)if(!Array.isArray(e.addsFeatures))t.push("Modification addsFeatures must be an array");else for(let a=0;a<e.addsFeatures.length;a++){const i=this.validateFeatureReference(e.addsFeatures[a],a);i.valid||t.push(`Modification addsFeatures[${a}]: ${i.errors?.join(", ")}`)}if(e.addsSkills)if(!Array.isArray(e.addsSkills))t.push("Modification addsSkills must be an array");else for(let a=0;a<e.addsSkills.length;a++){const i=e.addsSkills[a],n=this.validateSkillReference(i.skillId,a);n.valid||t.push(...(n.errors||[]).map(r=>`Modification ${r}`)),i.level&&!Ti.includes(i.level)&&t.push(`Modification addsSkills[${a}]: proficiency level must be 'proficient' or 'expertise'`)}return e.addsSpells&&(Array.isArray(e.addsSpells)||t.push("Modification addsSpells must be an array")),{valid:t.length===0,errors:t.length>0?t:void 0}}static validateMiniFeature(e){const t=[];if((!e.id||typeof e.id!="string")&&t.push("Mini-feature must have a valid id"),(!e.name||typeof e.name!="string")&&t.push("Mini-feature must have a valid name"),(!e.description||typeof e.description!="string")&&t.push("Mini-feature must have a valid description"),!e.effects||!Array.isArray(e.effects))t.push("Mini-feature must have an effects array");else for(let a=0;a<e.effects.length;a++){const i=this.validateProperty(e.effects[a]);i.valid||t.push(`Mini-feature effects[${a}]: ${i.errors?.join(", ")}`)}return e.source!=="equipment_inline"&&t.push('Mini-feature must have source: "equipment_inline"'),{valid:t.length===0,errors:t.length>0?t:void 0}}static validateACBonus(e){const t=[];return typeof e!="number"?t.push("AC bonus must be a number"):e<0?t.push("AC bonus cannot be negative"):Number.isFinite(e)||t.push("AC bonus must be a finite number"),{valid:t.length===0,errors:t.length>0?t:void 0}}static validateWeaponProperties(e){const t=[];if(!Array.isArray(e))return t.push("Weapon properties must be an array"),{valid:!1,errors:t};for(let a=0;a<e.length;a++){const i=e[a];if(typeof i!="string"){t.push(`Weapon property at index ${a} must be a string`);continue}i.startsWith("range_")&&(Hr.test(i)||t.push(`Weapon property "${i}" at index ${a} must be in format "range_MIN_MAX" (e.g., "range_20_60")`))}return{valid:t.length===0,errors:t.length>0?t:void 0}}}const Ce={Barbarian:{primary_ability:"STR",hit_die:12,saving_throws:["STR","CON"],is_spellcaster:!1,skill_count:2,available_skills:["athletics","animal_handling","intimidation","nature","perception","survival"],has_expertise:!1,description:"A fierce warrior of primitive background who can enter a battle rage. Barbarians draw on primal fury to unleash devastating attacks, shrug off wounds that would fell others, and channel their totem spirits' power. They thrive on the front lines, where their rage makes them unstoppable forces of nature."},Bard:{primary_ability:"CHA",hit_die:8,saving_throws:["DEX","CHA"],is_spellcaster:!0,skill_count:3,available_skills:["athletics","acrobatics","sleight_of_hand","stealth","arcana","history","investigation","nature","religion","animal_handling","insight","medicine","perception","survival","deception","intimidation","performance","persuasion"],has_expertise:!0,expertise_count:2,description:"An inspiring magician whose power echoes the music of creation. Bards weave magic through music, poetry, and performance, inspiring allies, demoralizing enemies, and wielding versatile spells. They are jack-of-all-trades who can fill any role, from frontline support to devastating spellcaster."},Cleric:{primary_ability:"WIS",hit_die:8,saving_throws:["WIS","CHA"],is_spellcaster:!0,skill_count:2,available_skills:["history","insight","medicine","persuasion","religion"],has_expertise:!1,description:"A priestly champion who wields divine magic in service of a higher power. Clerics serve as intermediaries between the mortal world and the divine, wielding healing and destructive magic granted by their deities. They can wear heavy armor, wield powerful weapons, and specialize in different domains of divine influence."},Druid:{primary_ability:"WIS",hit_die:8,saving_throws:["INT","WIS"],is_spellcaster:!0,skill_count:2,available_skills:["arcana","animal_handling","insight","medicine","nature","perception","religion","survival"],has_expertise:!1,description:"A priest of the Old Faith, wielding the powers of nature and adopting animal forms. Druids revere nature and gain their magic from the natural world, commanding the elements, transforming into beasts, and calling upon the forces of life and growth. They serve as guardians of the wild places and mediators between civilization and nature."},Fighter:{primary_ability:"STR",hit_die:10,saving_throws:["STR","CON"],is_spellcaster:!1,skill_count:2,available_skills:["acrobatics","animal_handling","athletics","history","insight","intimidation","perception","survival"],has_expertise:!1,description:"A master of martial combat, skilled with a variety of weapons and armor. Fighters learn countless fighting techniques, specializing in certain styles of combat. They are the backbone of any adventuring party, capable of withstanding tremendous punishment while dishing out devastating damage through martial prowess alone."},Monk:{primary_ability:"DEX",hit_die:8,saving_throws:["STR","DEX"],is_spellcaster:!1,skill_count:2,available_skills:["acrobatics","athletics","history","insight","religion","stealth"],has_expertise:!1,description:"A master of martial arts, harnessing the power of the body in pursuit of physical and spiritual perfection. Monks use their bodies as weapons, channeling ki energy to perform extraordinary feats. They are agile warriors who can strike with deadly precision, evade attacks with supernatural grace, and harness mystical abilities."},Paladin:{primary_ability:"STR",hit_die:10,saving_throws:["WIS","CHA"],is_spellcaster:!0,skill_count:2,available_skills:["athletics","insight","intimidation","medicine","persuasion","religion"],has_expertise:!1,description:"A holy warrior bound to a sacred oath. Paladins combine martial prowess with divine magic, smiting foes with holy fire and healing allies with sacred power. They are paragons of their beliefs, radiating auras that protect allies and punish enemies. Their strength comes from their unwavering devotion to their oath."},Ranger:{primary_ability:"DEX",hit_die:10,saving_throws:["STR","DEX"],is_spellcaster:!0,skill_count:3,available_skills:["animal_handling","athletics","insight","investigation","nature","perception","stealth","survival"],has_expertise:!1,description:"A warrior who combats threats on the edges of civilization. Rangers are hunters and trackers who know the wilderness intimately, moving swiftly through natural terrain and striking from a distance. They develop supernatural connections to their chosen enemies and environments, gaining deadly precision against favored foes."},Rogue:{primary_ability:"DEX",hit_die:8,saving_throws:["DEX","INT"],is_spellcaster:!1,skill_count:4,available_skills:["acrobatics","athletics","deception","insight","intimidation","investigation","perception","performance","persuasion","sleight_of_hand","stealth"],has_expertise:!0,expertise_count:2,description:"A scoundrel who uses stealth and trickery to overcome obstacles and enemies. Rogues excel at skills, finding traps, and striking devastating blows from hiding. Whether as thieves, assassins, or spies, they are versatile problem-solvers who can disable threats before allies even know they exist."},Sorcerer:{primary_ability:"CHA",hit_die:6,saving_throws:["CON","CHA"],is_spellcaster:!0,skill_count:2,available_skills:["arcana","deception","insight","intimidation","persuasion","religion"],has_expertise:!1,description:"A spellcaster who draws on inherent magic from a supernatural bloodline or cosmic source. Sorcerers have no need for spellbooks—their power comes from within, born of draconic heritage, wild magic, or other supernatural origins. They can manipulate and metamagic their spells in ways other casters cannot."},Warlock:{primary_ability:"CHA",hit_die:8,saving_throws:["WIS","CHA"],is_spellcaster:!0,skill_count:2,available_skills:["arcana","deception","history","intimidation","investigation","nature","religion"],has_expertise:!1,description:"A wielder of magic that is derived from a bargain with an extraplanar entity. Warlocks gain their powers through pacts with powerful beings—fiends, fey, Great Old Ones, or other eldritch entities. In exchange for service, they receive mystical abilities and can cast eldritch invocations that blur the line between spell and supernatural ability."},Wizard:{primary_ability:"INT",hit_die:6,saving_throws:["INT","WIS"],is_spellcaster:!0,skill_count:2,available_skills:["arcana","history","insight","investigation","medicine","religion"],has_expertise:!1,description:"A scholarly magic-user capable of manipulating the structures of reality. Wizards are masters of arcane magic, studying spellbooks to unlock the secrets of the universe. They specialize in specific schools of magic, from evocation to illusion, and have access to the widest variety of spells of any class."}},Ds=["Barbarian","Bard","Cleric","Druid","Fighter","Monk","Paladin","Ranger","Rogue","Sorcerer","Warlock","Wizard"],Rs={Barbarian:{primary:"bass",secondary:"amplitude",bass:1,amplitude:.7},Fighter:{primary:"bass",secondary:"amplitude",bass:.9,amplitude:.8},Paladin:{primary:"bass",secondary:"mid",bass:.8,mid:.5},Rogue:{primary:"treble",treble:1},Ranger:{primary:"treble",secondary:"bass",treble:.8,bass:.5},Monk:{primary:"treble",secondary:"mid",treble:.7,mid:.6},Wizard:{primary:"mid",mid:1},Cleric:{primary:"mid",secondary:"amplitude",mid:.8,amplitude:.6},Druid:{primary:"mid",secondary:"bass",mid:.7,bass:.6},Bard:{primary:"amplitude",secondary:"mid",tertiary:"treble",amplitude:.8,mid:.6,treble:.3},Sorcerer:{primary:"amplitude",secondary:"chaos",amplitude:.9},Warlock:{primary:"amplitude",secondary:"treble",amplitude:.7,treble:.5}},Ia={"Acid Splash":{name:"Acid Splash",level:0,school:"Evocation",casting_time:"1 action",range:"60 feet",components:["V","S"],duration:"Instantaneous",description:"Hurls a bubble of acid at a creature or point. On hit, deals acid damage to creatures in a small splash radius. Dexterity save for half damage."},"Fire Bolt":{name:"Fire Bolt",level:0,school:"Evocation",casting_time:"1 action",range:"120 feet",components:["V","S"],duration:"Instantaneous",description:"Hurls a mote of fire at a creature or object. On hit, deals fire damage. One of the most reliable damaging cantrips for ranged combat."},Light:{name:"Light",level:0,school:"Evocation",casting_time:"1 action",range:"Touch",components:["V","M"],duration:"1 hour",description:"Touch one object to make it shed bright light in a 20-foot radius and dim light for another 20 feet. Completely illuminates dark areas and can be moved with the touched object."},"Mage Hand":{name:"Mage Hand",level:0,school:"Conjuration",casting_time:"1 action",range:"30 feet",components:["V","S"],duration:"Concentration, up to 1 minute",description:"Conjures a spectral, floating hand at a point you choose. The hand can manipulate objects, open unlocked doors, or stow items. It cannot attack or activate magical items."},Mending:{name:"Mending",level:0,school:"Transmutation",casting_time:"1 minute",range:"Touch",components:["V","S","M"],duration:"Instantaneous",description:"Repairs a single break or tear in an object you touch, such as a broken chain link, two halves of a broken key, or a torn cloak. Cannot restore magic items or creatures."},Message:{name:"Message",level:0,school:"Transmutation",casting_time:"1 action",range:"120 feet",components:["V","S","M"],duration:"1 round",description:"Points your finger toward a creature within range and whisper a message. The target hears the message and can reply in a whisper that only you can hear. Perfect for covert communication."},Prestidigitation:{name:"Prestidigitation",level:0,school:"Transmutation",casting_time:"1 action",range:"10 feet",components:["V","S"],duration:"Up to 1 hour",description:"Performs minor magical tricks for your amusement. Can chill, warm, flavor, clean, or soil small objects. Create harmless sensory effects like colored lights or faint sounds."},"Sacred Flame":{name:"Sacred Flame",level:0,school:"Evocation",casting_time:"1 action",range:"60 feet",components:["V","S"],duration:"Instantaneous",description:"Calls down flame-like radiance on a target. The target must succeed on a Dexterity save or take radiant damage. This spell can be cast from behind cover and ignores half and three-quarters cover."},"Shocking Grasp":{name:"Shocking Grasp",level:0,school:"Evocation",casting_time:"1 action",range:"Touch",components:["V","S"],duration:"Instantaneous",description:"Lightning springs from your hand to deliver a shock to a creature. You have advantage on the attack roll if the target is wearing metal armor. The shock cannot be used to make reactions of opportunity."},"Vicious Mockery":{name:"Vicious Mockery",level:0,school:"Enchantment",casting_time:"1 action",range:"60 feet",components:["V"],duration:"Instantaneous",description:"Utters a cruel insult to a creature within range. The target must make a Wisdom save or take psychic damage. A creative and subtle way to attack enemies in social situations."},"Burning Hands":{name:"Burning Hands",level:1,school:"Evocation",casting_time:"1 action",range:"Self (15-foot cone)",components:["V","S"],duration:"Instantaneous",description:"Ignites a cone of fire from your outstretched hands. Each creature in the area must make a Dexterity save or take fire damage. Devastating damage at close range but dangerous to allies in the cone."},"Charm Person":{name:"Charm Person",level:1,school:"Enchantment",casting_time:"1 action",range:"30 feet",components:["V","S"],duration:"1 hour",description:"Attempts to charm a humanoid you can see. The target must make a Wisdom save or be charmed by you. While charmed, it regards you as a friendly acquaintance. The effect ends if you or your allies do anything harmful to it."},"Cure Wounds":{name:"Cure Wounds",level:1,school:"Evocation",casting_time:"1 action",range:"Touch",components:["V","S"],duration:"Instantaneous",description:"A creature you touch regains hit points equal to 1d8 plus your spellcasting ability modifier. The amount increases by 1d8 for each slot level above 1st. This spell has no effect on undead or constructs."},"Detect Magic":{name:"Detect Magic",level:1,school:"Divination",casting_time:"1 action",range:"Self",components:["V","S"],duration:"Concentration, up to 10 minutes",description:"For the duration, you sense the presence of magic within 30 feet of you. You can identify the school of magic and locate the source, but not learn its exact properties. Essential for identifying magical auras."},"Disguise Self":{name:"Disguise Self",level:1,school:"Illusion",casting_time:"1 action",range:"Self",components:["V","S"],duration:"1 hour",description:"Makes you—including your clothing, armor, weapons, and other belongings—look different until the spell ends. You can change your height, weight, facial features, voice, and even appear as another creature of the same size."},"Expeditious Retreat":{name:"Expeditious Retreat",level:1,school:"Transmutation",casting_time:"1 bonus action",range:"Self",components:["V","S"],duration:"Concentration, up to 10 minutes",description:"Doubles your walking speed and grants the ability to take the Disengage or Hide action as a bonus action on each turn. Essential for escape or closing the distance safely."},"False Life":{name:"False Life",level:1,school:"Necromancy",casting_time:"1 action",range:"Self",components:["V","S","M"],duration:"1 hour",description:"Bolsters your vitality with necromantic magic, gaining temporary hit points equal to 1d4 plus your spellcasting ability modifier. The temporary hit points increase by 1d4 for each slot level above 1st."},"Feather Fall":{name:"Feather Fall",level:1,school:"Transmutation",casting_time:"1 reaction",range:"60 feet",components:["V","M"],duration:"1 minute",description:"Choose up to five falling creatures within range. A falling creature's rate of descent slows to 60 feet per round until the spell ends. If the creature lands before the spell ends, it takes no falling damage and lands on its feet."},Grease:{name:"Grease",level:1,school:"Conjuration",casting_time:"1 action",range:"60 feet",components:["V","S","M"],duration:"1 minute",description:"Covers a 10-foot square on the ground in slippery grease. Creatures in the area must succeed on a Dexterity save or fall prone. Creatures entering the area or ending their turn there must also save or fall."},"Healing Word":{name:"Healing Word",level:1,school:"Evocation",casting_time:"1 bonus action",range:"60 feet",components:["V","S"],duration:"Instantaneous",description:"A creature of your choice that you can see within range regains hit points equal to 1d4 plus your spellcasting ability modifier. This spell has no effect on undead or constructs. Essential for saving fallen allies from a distance."},Identify:{name:"Identify",level:1,school:"Divination",casting_time:"1 minute",range:"Touch",components:["V","S","M"],duration:"Instantaneous",description:"Choose one object that you must touch throughout the casting. Learn if it's magically cursed or has been affected by a spell. You learn the properties and how to use them if it's a magic item. Requires a 100gp pearl focus."},"Mage Armor":{name:"Mage Armor",level:1,school:"Abjuration",casting_time:"1 action",range:"Touch",components:["V","S","M"],duration:"8 hours",description:"Touch a willing creature to protect it with a magical force. The target's AC becomes 13 + its Dexterity modifier, minus any magical penalties. The spell ends if the target dons armor or if you dismiss it."},"Magic Missile":{name:"Magic Missile",level:1,school:"Evocation",casting_time:"1 action",range:"120 feet",components:["V","S"],duration:"Instantaneous",description:"Creates three glowing darts of magical force. Each dart hits a creature of your choice that you can see within range. A dart deals 1d4 + 1 force damage. For each slot level above 1st, the spell creates one more dart."},Shield:{name:"Shield",level:1,school:"Abjuration",casting_time:"1 reaction",range:"Self",components:["V","S"],duration:"1 round",description:"An invisible barrier of magical force appears and protects you. Until the start of your next turn, you have a +5 bonus to AC, including against the triggering attack, and you take no damage from magic missile."},Sleep:{name:"Sleep",level:1,school:"Enchantment",casting_time:"1 action",range:"90 feet",components:["V","S","M"],duration:"1 minute",description:"Rolls 5d8; the total is how many hit points of creatures this spell can affect. Creatures in a 20-foot cube within range are affected in ascending order of current hit points. Unconscious creatures fall prone and are incapacitated."},Thunderwave:{name:"Thunderwave",level:1,school:"Evocation",casting_time:"1 action",range:"Self (15-foot cube)",components:["V","S"],duration:"Instantaneous",description:"A wave of thunderous force sweeps out from you. Each creature in the area must make a Constitution save or take thunder damage and be pushed 10 feet away. Unattended objects are also pushed and take damage."},"Acid Arrow":{name:"Acid Arrow",level:2,school:"Evocation",casting_time:"1 action",range:"90 feet",components:["V","S","M"],duration:"Instantaneous",description:"Hurls a bubble of acid at a point you choose within range. On a hit, the target takes acid damage immediately and additional acid damage at the end of its next turn. This secondary damage ignores resistance but not immunity."},"Aganazzar's Scorcher":{name:"Aganazzar's Scorcher",level:2,school:"Evocation",casting_time:"1 action",range:"30 feet",components:["V","S","M"],duration:"Instantaneous",description:"A line of roaring flame erupts from you in a 30-foot line. Each creature in the line must make a Dexterity save or take fire damage. A flamethrower-like effect that excels at controlling narrow corridors."},Blur:{name:"Blur",level:2,school:"Illusion",casting_time:"1 action",range:"Self",components:["V"],duration:"Concentration, up to 1 minute",description:"Your body becomes blurred, shifting and wavering to all who can see you. Until the spell ends, attacks against you have disadvantage, and you have advantage on Dexterity saving throws. Essential defense for spellcasters."},"Detect Thoughts":{name:"Detect Thoughts",level:2,school:"Divination",casting_time:"1 action",range:"Self",components:["V","S","M"],duration:"Concentration, up to 1 minute",description:"For the duration, you can read the thoughts of certain creatures. You initially sense the presence of thinking creatures, then can focus on one to read its surface thoughts. Deeper thoughts require a contest of Intelligence."},Fireball:{name:"Fireball",level:3,school:"Evocation",casting_time:"1 action",range:"150 feet",components:["V","S","M"],duration:"Instantaneous",description:"A bright streak flashes from your pointing finger to a point you choose within range and then blossoms with a low roar into an explosion of flame. Each creature in a 20-foot-radius sphere must make a Dexterity save or take fire damage."},"Hold Person":{name:"Hold Person",level:2,school:"Enchantment",casting_time:"1 action",range:"60 feet",components:["V","S","M"],duration:"Concentration, up to 1 minute",description:"Choose a humanoid that you can see within range. The target must succeed on a Wisdom save or be paralyzed. The spell can target additional humanoids at higher slot levels. Paralyzed creatures are automatically critical hits."},Invisibility:{name:"Invisibility",level:2,school:"Illusion",casting_time:"1 action",range:"Touch",components:["V","S","M"],duration:"Concentration, up to 1 hour",description:"A creature you touch becomes invisible until the spell ends. Anything the target is wearing or carrying is invisible as long as it is on the target's person. The spell ends if the target attacks or casts a spell."},Knock:{name:"Knock",level:2,school:"Transmutation",casting_time:"1 action",range:"60 feet",components:["V"],duration:"Instantaneous",description:"Choose an object that you can see within range. The object can be a door, a box, a chest, a set of manacles, a padlock, or another object that contains a mundane or magical means of preventing access."},"Misty Step":{name:"Misty Step",level:2,school:"Conjuration",casting_time:"1 bonus action",range:"Self",components:["V"],duration:"Instantaneous",description:"Briefly surrounded by silvery mist, you teleport up to 30 feet to an unoccupied space you can see. You can bring along objects as long as their weight doesn't exceed what you can carry. A quick escape or repositioning tool."},"Mirror Image":{name:"Mirror Image",level:2,school:"Illusion",casting_time:"1 action",range:"Self",components:["V","S"],duration:"Concentration, up to 1 minute",description:"Three illusory duplicates of yourself appear in your space. Until the spell ends, the duplicates move with you and mimic your actions, shifting position so it's impossible to track which image is real."},"Scorching Ray":{name:"Scorching Ray",level:2,school:"Evocation",casting_time:"1 action",range:"120 feet",components:["V","S"],duration:"Instantaneous",description:"You create three rays of fire and hurl them at targets within range. You can aim them at one target or several. Make a ranged spell attack for each ray. On a hit, the target takes fire damage."},Shatter:{name:"Shatter",level:2,school:"Evocation",casting_time:"1 action",range:"60 feet",components:["V","S","M"],duration:"Instantaneous",description:"A sudden loud ringing noise, painfully intense, erupts from a point of your choice within range. Each creature in a 10-foot-radius sphere must make a Constitution save or take thunder damage."},Suggestion:{name:"Suggestion",level:2,school:"Enchantment",casting_time:"1 action",range:"30 feet",components:["V","M"],duration:"Concentration, up to 1 minute",description:"Suggest a course of activity to a creature you can see within range and can understand. The target must make a Wisdom save or pursue the course of action. The suggestion must be worded so as to sound reasonable."},"Animate Dead":{name:"Animate Dead",level:3,school:"Necromancy",casting_time:"1 minute",range:"10 feet",components:["V","S","M"],duration:"Instantaneous",description:"This spell creates an undead servant. Choose a pile of bones or a corpse of a Medium or Small humanoid within range. Your spell animates it as a skeleton or a zombie that obeys your commands until destroyed."},Blink:{name:"Blink",level:3,school:"Transmutation",casting_time:"1 action",range:"Self",components:["V","S"],duration:"1 minute",description:"Roll a d20 at the start of each of your turns. On an 11 or higher, you vanish from your current plane of existence and appear in the Ethereal Plane for the round, returning to where you were. Attacks against you have disadvantage while blinking."},Counterspell:{name:"Counterspell",level:3,school:"Abjuration",casting_time:"1 reaction",range:"60 feet",components:["S"],duration:"Instantaneous",description:"You attempt to interrupt a creature in the process of casting a spell. The creature must make a Constitution save. If the spell is 3rd level or lower, it fails and has no effect. Higher level spells require an ability check."},"Dispel Magic":{name:"Dispel Magic",level:3,school:"Abjuration",casting_time:"1 action",range:"120 feet",components:["V","S"],duration:"Instantaneous",description:"Choose one creature, object, or magical effect within range. Any spell of 3rd level or lower on the target ends. For each spell of 4th level or higher, make an ability check using your spellcasting ability."},"Lightning Bolt":{name:"Lightning Bolt",level:3,school:"Evocation",casting_time:"1 action",range:"Self (100-foot line)",components:["V","S","M"],duration:"Instantaneous",description:"A stroke of lightning forming a line 100 feet long and 5 feet wide blasts out from you in a direction you choose. Each creature in the line must make a Dexterity save or take lightning damage."},"Major Image":{name:"Major Image",level:3,school:"Illusion",casting_time:"1 action",range:"120 feet",components:["V","S","M"],duration:"Concentration, up to 10 minutes",description:"You create the image of an object, a creature, or some other visible phenomenon that is no larger than a 20-foot cube. The image appears at a spot within range and lasts for the duration. The image includes sounds, smells, and temperature."},"Sleet Storm":{name:"Sleet Storm",level:3,school:"Evocation",casting_time:"1 action",range:"150 feet",components:["V","S","M"],duration:"Concentration, up to 1 minute",description:"Until the spell ends, freezing rain and sleet fall in a 20-foot-tall cylinder with a 40-foot radius centered on a point you choose within range. The area is heavily obscured, and the ground is difficult terrain."},Telekinesis:{name:"Telekinesis",level:3,school:"Transmutation",casting_time:"1 action",range:"60 feet",components:["V","S"],duration:"Concentration, up to 1 minute",description:"You gain the ability to move or manipulate creatures or objects by thought. When you cast the spell, and as your action each round for the duration, you can exert your will on one creature or object."},"Dimension Door":{name:"Dimension Door",level:4,school:"Conjuration",casting_time:"1 action",range:"500 feet",components:["V"],duration:"Instantaneous",description:"You teleport yourself from your current location to any other spot within range. You arrive at exactly the spot desired. It can be a place you can see, one you can visualize, or one you can describe by stating distance and direction."},"Greater Invisibility":{name:"Greater Invisibility",level:4,school:"Illusion",casting_time:"1 action",range:"Touch",components:["V","S"],duration:"Concentration, up to 1 minute",description:"You or a creature you touch becomes invisible until the spell ends. Anything the target is wearing or carrying is invisible as long as it is on the target's person. Unlike regular invisibility, attacks do not end the spell."},Polymorph:{name:"Polymorph",level:4,school:"Transmutation",casting_time:"1 action",range:"60 feet",components:["V","S","M"],duration:"Concentration, up to 1 hour",description:"Transforms a creature into a different beast. The target's game statistics, including mental ability scores, are replaced by the statistics of the chosen beast. It retains its alignment and personality. The creature assumes the hit points of the new form."},Stoneskin:{name:"Stoneskin",level:4,school:"Abjuration",casting_time:"1 action",range:"Touch",components:["V","S","M"],duration:"Concentration, up to 1 hour",description:"The touched creature's skin becomes as hard as stone and covered in rocky growths. The target's AC becomes 15 + its Dexterity modifier (max 23), and it is resistant to nonmagical bludgeoning, piercing, and slashing damage."},"Cone of Cold":{name:"Cone of Cold",level:5,school:"Evocation",casting_time:"1 action",range:"Self (60-foot cone)",components:["V","S","M"],duration:"Instantaneous",description:"A blast of cold air erupts from your hands. Each creature in a 60-foot cone must make a Constitution saving throw. A creature takes 8d8 cold damage on a failed save, or half as much on a successful one."},"Teleportation Circle":{name:"Teleportation Circle",level:5,school:"Conjuration",casting_time:"1 minute",range:"10 feet",components:["V","M"],duration:"1 round",description:"As you cast the spell, you draw a circle on the ground with the chalk. When you complete the casting, a glowing portal opens within the circle. Any creature that enters the portal instantly appears within 5 feet of the destination circle."}},Rt={Wizard:{cantrips:["Acid Splash","Fire Bolt","Light","Mage Hand","Mending","Message","Prestidigitation","Shocking Grasp"],spells_by_level:{1:["Burning Hands","Charm Person","Cure Wounds","Detect Magic","Disguise Self","Expeditious Retreat","False Life","Feather Fall","Grease","Identify","Mage Armor","Magic Missile","Shield","Sleep","Thunderwave"],2:["Acid Arrow","Blur","Detect Thoughts","Hold Person","Invisibility","Knock","Misty Step","Mirror Image","Scorching Ray","Shatter","Suggestion"],3:["Animate Dead","Blink","Counterspell","Dispel Magic","Lightning Bolt","Major Image","Sleet Storm","Telekinesis"],4:["Dimension Door","Greater Invisibility","Polymorph","Stoneskin"],5:["Cone of Cold","Teleportation Circle"]}},Sorcerer:{cantrips:["Acid Splash","Fire Bolt","Light","Mage Hand","Message","Prestidigitation","Shocking Grasp"],spells_by_level:{1:["Burning Hands","Charm Person","Cure Wounds","Detect Magic","Disguise Self","Expeditious Retreat","False Life","Feather Fall","Grease","Mage Armor","Magic Missile","Shield","Sleep","Thunderwave"],2:["Acid Arrow","Blur","Detect Thoughts","Hold Person","Invisibility","Knock","Misty Step","Mirror Image","Scorching Ray","Shatter","Suggestion"],3:["Animate Dead","Blink","Counterspell","Dispel Magic","Lightning Bolt","Major Image","Sleet Storm"],4:["Dimension Door","Greater Invisibility","Polymorph","Stoneskin"],5:["Cone of Cold","Teleportation Circle"]}},Bard:{cantrips:["Light","Mage Hand","Mending","Message","Prestidigitation","Vicious Mockery"],spells_by_level:{1:["Charm Person","Detect Magic","Disguise Self","Expeditious Retreat","Feather Fall","Grease","Healing Word","Identify","Magic Missile","Sleep","Thunderwave"],2:["Acid Arrow","Blur","Detect Thoughts","Hold Person","Invisibility","Knock","Misty Step","Mirror Image","Scorching Ray","Suggestion"],3:["Animate Dead","Blink","Counterspell","Dispel Magic","Lightning Bolt","Major Image"],4:["Dimension Door","Greater Invisibility","Polymorph"],5:["Teleportation Circle"]}},Cleric:{cantrips:["Light","Mending","Message","Sacred Flame"],spells_by_level:{1:["Burning Hands","Charm Person","Cure Wounds","Detect Magic","Identify","Healing Word","Magic Missile","Shield","Sleep"],2:["Acid Arrow","Blur","Detect Thoughts","Hold Person","Invisibility","Scorching Ray"],3:["Animate Dead","Counterspell","Dispel Magic","Lightning Bolt"],4:["Dimension Door","Polymorph"],5:["Cone of Cold"]}},Druid:{cantrips:["Light","Mending","Prestidigitation"],spells_by_level:{1:["Burning Hands","Cure Wounds","Detect Magic","Expeditious Retreat","Feather Fall","Grease","Healing Word","Identify"],2:["Acid Arrow","Blur","Detect Thoughts","Hold Person","Invisibility","Scorching Ray","Shatter"],3:["Animate Dead","Counterspell","Dispel Magic","Lightning Bolt","Sleet Storm"],4:["Dimension Door","Polymorph"],5:["Cone of Cold","Teleportation Circle"]}},Paladin:{cantrips:[],spells_by_level:{1:["Burning Hands","Charm Person","Cure Wounds","Detect Magic","Feather Fall","Healing Word","Protection","Shield"],2:["Acid Arrow","Hold Person","Scorching Ray"],3:["Counterspell","Dispel Magic","Lightning Bolt"],4:["Dimension Door","Polymorph"],5:["Cone of Cold"]}},Ranger:{cantrips:[],spells_by_level:{1:["Detect Magic","Expeditious Retreat","Feather Fall","Grease","Identify"],2:["Blur","Hold Person","Invisibility","Misty Step"],3:["Blink","Counterspell","Dispel Magic"],4:["Dimension Door","Polymorph"],5:[]}},Warlock:{cantrips:["Acid Splash","Fire Bolt","Mage Hand","Message","Prestidigitation","Shocking Grasp"],spells_by_level:{1:["Burning Hands","Charm Person","Detect Magic","Disguise Self","Expeditious Retreat","False Life","Grease","Hex","Magic Missile"],2:["Acid Arrow","Darkness","Hold Person","Invisibility","Scorching Ray","Shatter"],3:["Counterspell","Dispel Magic","Fireball","Lightning Bolt"],4:["Dimension Door","Greater Invisibility"],5:["Cone of Cold"]}}},oa={Wizard:{1:{1:2},2:{1:3},3:{1:4,2:2},4:{1:4,2:3},5:{1:4,2:3,3:2},6:{1:4,2:3,3:3},7:{1:4,2:3,3:3,4:1},8:{1:4,2:3,3:3,4:2},9:{1:4,2:3,3:3,4:3,5:1},10:{1:4,2:3,3:3,4:3,5:2},11:{1:4,2:3,3:3,4:3,5:2,6:1},12:{1:4,2:3,3:3,4:3,5:2,6:1},13:{1:4,2:3,3:3,4:3,5:2,6:1,7:1},14:{1:4,2:3,3:3,4:3,5:2,6:1,7:1},15:{1:4,2:3,3:3,4:3,5:2,6:1,7:1,8:1},16:{1:4,2:3,3:3,4:3,5:2,6:1,7:1,8:1},17:{1:4,2:3,3:3,4:3,5:2,6:1,7:1,8:1,9:1},18:{1:4,2:3,3:3,4:3,5:3,6:1,7:1,8:1,9:1},19:{1:4,2:3,3:3,4:3,5:3,6:2,7:1,8:1,9:1},20:{1:4,2:3,3:3,4:3,5:3,6:2,7:2,8:1,9:1}},Sorcerer:{1:{1:2},2:{1:3},3:{1:4,2:2},4:{1:4,2:3},5:{1:4,2:3,3:2},6:{1:4,2:3,3:3},7:{1:4,2:3,3:3,4:1},8:{1:4,2:3,3:3,4:2},9:{1:4,2:3,3:3,4:3,5:1},10:{1:4,2:3,3:3,4:3,5:2},11:{1:4,2:3,3:3,4:3,5:2,6:1},12:{1:4,2:3,3:3,4:3,5:2,6:1},13:{1:4,2:3,3:3,4:3,5:2,6:1,7:1},14:{1:4,2:3,3:3,4:3,5:2,6:1,7:1},15:{1:4,2:3,3:3,4:3,5:2,6:1,7:1,8:1},16:{1:4,2:3,3:3,4:3,5:2,6:1,7:1,8:1},17:{1:4,2:3,3:3,4:3,5:2,6:1,7:1,8:1,9:1},18:{1:4,2:3,3:3,4:3,5:3,6:1,7:1,8:1,9:1},19:{1:4,2:3,3:3,4:3,5:3,6:2,7:1,8:1,9:1},20:{1:4,2:3,3:3,4:3,5:3,6:2,7:2,8:1,9:1}},Bard:{1:{1:2},2:{1:2,2:0},3:{1:3,2:0},4:{1:3,2:2},5:{1:4,2:2,3:0},6:{1:4,2:2,3:0},7:{1:4,2:2,3:2},8:{1:4,2:2,3:2,4:0},9:{1:4,2:3,3:2,4:0},10:{1:4,2:3,3:2,4:1},11:{1:4,2:3,3:3,4:1},12:{1:4,2:3,3:3,4:1},13:{1:4,2:3,3:3,4:2,5:0},14:{1:4,2:3,3:3,4:2,5:0},15:{1:4,2:3,3:3,4:2,5:1},16:{1:4,2:3,3:3,4:2,5:1},17:{1:4,2:3,3:3,4:2,5:1,6:0},18:{1:4,2:3,3:3,4:3,5:1,6:0},19:{1:4,2:3,3:3,4:3,5:2,6:1},20:{1:4,2:3,3:3,4:3,5:2,6:1}},Cleric:{1:{1:2},2:{1:3},3:{1:3,2:2},4:{1:4,2:2},5:{1:4,2:3,3:2},6:{1:4,2:3,3:3},7:{1:4,2:3,3:3,4:1},8:{1:4,2:3,3:3,4:2},9:{1:4,2:3,3:3,4:3,5:1},10:{1:4,2:3,3:3,4:3,5:2},11:{1:4,2:3,3:3,4:3,5:2,6:1},12:{1:4,2:3,3:3,4:3,5:2,6:1},13:{1:4,2:3,3:3,4:3,5:2,6:1,7:1},14:{1:4,2:3,3:3,4:3,5:2,6:1,7:1},15:{1:4,2:3,3:3,4:3,5:2,6:1,7:1,8:1},16:{1:4,2:3,3:3,4:3,5:2,6:1,7:1,8:1},17:{1:4,2:3,3:3,4:3,5:2,6:1,7:1,8:1,9:1},18:{1:4,2:3,3:3,4:3,5:3,6:1,7:1,8:1,9:1},19:{1:4,2:3,3:3,4:3,5:3,6:2,7:1,8:1,9:1},20:{1:4,2:3,3:3,4:3,5:3,6:2,7:2,8:1,9:1}},Druid:{1:{1:2},2:{1:3},3:{1:3,2:2},4:{1:4,2:2},5:{1:4,2:3,3:2},6:{1:4,2:3,3:3},7:{1:4,2:3,3:3,4:1},8:{1:4,2:3,3:3,4:2},9:{1:4,2:3,3:3,4:3,5:1},10:{1:4,2:3,3:3,4:3,5:2},11:{1:4,2:3,3:3,4:3,5:2,6:1},12:{1:4,2:3,3:3,4:3,5:2,6:1},13:{1:4,2:3,3:3,4:3,5:2,6:1,7:1},14:{1:4,2:3,3:3,4:3,5:2,6:1,7:1},15:{1:4,2:3,3:3,4:3,5:2,6:1,7:1,8:1},16:{1:4,2:3,3:3,4:3,5:2,6:1,7:1,8:1},17:{1:4,2:3,3:3,4:3,5:2,6:1,7:1,8:1,9:1},18:{1:4,2:3,3:3,4:3,5:3,6:1,7:1,8:1,9:1},19:{1:4,2:3,3:3,4:3,5:3,6:2,7:1,8:1,9:1},20:{1:4,2:3,3:3,4:3,5:3,6:2,7:2,8:1,9:1}},Paladin:{1:{1:0},2:{1:2},3:{1:3},4:{1:3},5:{1:4,2:2},6:{1:4,2:2},7:{1:4,2:3},8:{1:4,2:3},9:{1:4,2:3,3:2},10:{1:4,2:3,3:2},11:{1:4,2:3,3:3},12:{1:4,2:3,3:3},13:{1:4,2:3,3:3,4:1},14:{1:4,2:3,3:3,4:1},15:{1:4,2:3,3:3,4:2},16:{1:4,2:3,3:3,4:2},17:{1:4,2:3,3:3,4:3,5:1},18:{1:4,2:3,3:3,4:3,5:1},19:{1:4,2:3,3:3,4:3,5:2},20:{1:4,2:3,3:3,4:3,5:2}},Ranger:{1:{1:0},2:{1:2},3:{1:2,2:0},4:{1:3,2:0},5:{1:3,2:2},6:{1:3,2:2},7:{1:3,2:2,3:0},8:{1:3,2:2,3:0},9:{1:3,2:3,3:0},10:{1:4,2:3,3:0},11:{1:4,2:3,3:2},12:{1:4,2:3,3:2},13:{1:4,2:3,3:2,4:0},14:{1:4,2:3,3:2,4:0},15:{1:4,2:3,3:2,4:1},16:{1:4,2:3,3:2,4:1},17:{1:4,2:3,3:3,4:1},18:{1:4,2:3,3:3,4:1},19:{1:4,2:3,3:3,4:2},20:{1:4,2:3,3:3,4:2}},Warlock:{1:{1:1},2:{1:2},3:{1:2,2:2},4:{1:2,2:2},5:{1:2,2:2,3:2},6:{1:2,2:2,3:2},7:{1:2,2:2,3:2,4:1},8:{1:2,2:2,3:2,4:1},9:{1:2,2:2,3:2,4:2,5:1},10:{1:2,2:2,3:2,4:2,5:1},11:{1:3,2:3,3:3,4:3,5:1},12:{1:3,2:3,3:3,4:3,5:2},13:{1:3,2:3,3:3,4:3,5:2},14:{1:3,2:3,3:3,4:3,5:2},15:{1:3,2:3,3:3,4:3,5:3},16:{1:3,2:3,3:3,4:3,5:3},17:{1:4,2:4,3:4,4:4,5:4},18:{1:4,2:4,3:4,4:4,5:4},19:{1:4,2:4,3:4,4:4,5:4},20:{1:4,2:4,3:4,4:4,5:4}}};function Ea(s){if(s in Ce)return Ce[s];try{const t=R.getInstance().get("classes.data");if(t&&Array.isArray(t)){const a=t.find(i=>i.name===s);if(a){if(a.baseClass&&a.baseClass in Ce){const i=Ce[a.baseClass];return{...i,...a,available_skills:a.available_skills||i.available_skills}}return a}}}catch{}}const ct={1:0,2:300,3:900,4:2700,5:6500,6:14e3,7:23e3,8:34e3,9:48e3,10:64e3,11:85e3,12:1e5,13:12e4,14:14e4,15:165e3,16:195e3,17:225e3,18:265e3,19:305e3,20:355e3},Ma={1:2,2:2,3:2,4:2,5:3,6:3,7:3,8:3,9:4,10:4,11:4,12:4,13:5,14:5,15:5,16:5,17:6,18:6,19:6,20:6},Ps=["Human","Elf","Dwarf","Halfling","Dragonborn","Gnome","Half-Elf","Half-Orc","Tiefling"],Ae={adjectives:{techno:{bass:["Thumping","Pulsing","Driving","Hypnotic","Relentless","Mechanical","Deep"],treble:["Piercing","Synthetic","Digital","Sharp","Glittering","Laser"],mid:["Driving","Rhythmic","Flowing","Seamless","Kinetic"],quiet:["Minimal","Sparse","Subtle","Ambient","Whispered"],loud:["Pounding","Overwhelming","Massive","Explosive","Crushing"]},rock:{bass:["Heavy","Rumbling","Gritty","Raw","Crushing","Grinding"],treble:["Screaming","Wailing","Soaring","Piercing","Shrill"],mid:["Crunchy","Distorted","Overdriven","Gritty","Aggressive"],quiet:["Acoustic","Stripped","Bare","Intimate","Unplugged"],loud:["Thunderous","Explosive","Deafening","Roaring","Volcanic"]},metal:{bass:["Brutal","Crushing","Punishing","Merciless","Devastating","Obliterating"],treble:["Shredding","Screaming","Hellish","Demonic","Piercing"],mid:["Chugging","Grinding","Relentless","Aggressive","Furious"],quiet:["Doomed","Somber","Ominous","Foreboding","Dark"],loud:["Deafening","Apocalyptic","Cataclysmic","Earth-Shattering","Monolithic"]},ambient:{bass:["Deep","Vast","Profound","Oceanic","Cavernous","Abyssal"],treble:["Ethereal","Crystalline","Shimmering","Celestial","Gossamer"],mid:["Whispering","Drifting","Floating","Meandering","Breathing"],quiet:["Silent","Hushed","Meditative","Peaceful","Tranquil"],loud:["Swelling","Building","Crescendoing","Rising","Expanding"]},classical:{bass:["Grand","Stately","Majestic","Regal","Noble","Profound"],treble:["Soaring","Triumphant","Brilliant","Radiant","Gleaming"],mid:["Noble","Elegant","Refined","Graceful","Poised"],quiet:["Gentle","Delicate","Tender","Soft","Intimate"],loud:["Majestic","Powerful","Commanding","Heroic","Epic"]},jazz:{bass:["Smooth","Groovy","Walking","Swinging","Funky","Mellow"],treble:["Bright","Crisp","Brilliant","Clear","Sparkling"],mid:["Swinging","Syncopated","Bluesy","Soulful","Improvisational"],quiet:["Cool","Laid-back","Subtle","Understated","Intimate"],loud:["Big","Bold","Brassy","Energetic","Vibrant"]},"hip hop":{bass:["Bumping","Knocking","Booming","Heavy","Fat","Deep"],treble:["Sharp","Crisp","Cutting","Bright","Piercing"],mid:["Flowing","Smooth","Rhythmic","Funky","Grooving"],quiet:["Chill","Mellow","Lo-fi","Smooth","Laid-back"],loud:["Hype","Aggressive","Hard","Explosive","Banging"]},pop:{bass:["Bouncy","Punchy","Groovy","Catchy","Upbeat"],treble:["Sparkling","Bright","Shimmering","Glittering","Polished"],mid:["Catchy","Memorable","Infectious","Singable","Hooky"],quiet:["Soft","Gentle","Sweet","Tender","Intimate"],loud:["Anthemic","Soaring","Epic","Triumphant","Powerful"]},electronic:{bass:["Pulsing","Wobbling","Throbbing","Synthetic","Digital","Modulated"],treble:["Glitchy","Digital","Futuristic","Synthetic","Robotic"],mid:["Synthetic","Programmed","Sequenced","Processed","Modular"],quiet:["Atmospheric","Ambient","Minimal","Spacious","Ethereal"],loud:["Massive","Crushing","Drop-heavy","Explosive","Wall-of-sound"]},default:{bass:["Booming","Deep","Low","Resonant","Rumbling","Heavy"],treble:["Sharp","High","Piercing","Bright","Clear"],mid:["Resonant","Balanced","Full","Rich","Warm"],quiet:["Quiet","Soft","Gentle","Subtle","Hushed"],loud:["Loud","Powerful","Strong","Intense","Forceful"]}},descriptors:["Swift","Iron","Shadow","Mystic","Wild","Radiant","Grim","Noble","Ancient","Blazing"],classAspects:{Barbarian:["Warrior","Rage","Beast","Fury"],Bard:["Song","Voice","Muse","Melody"],Cleric:["Light","Faith","Devotion","Grace"],Druid:["Wild","Nature","Grove","Fang"],Fighter:["Blade","Shield","Defender","Champion"],Monk:["Fist","Spirit","Flow","Path"],Paladin:["Oath","Justice","Light","Defender"],Ranger:["Hunter","Arrow","Tracker","Scout"],Rogue:["Shadow","Blade","Whisper","Dagger"],Sorcerer:["Flame","Storm","Chaos","Power"],Warlock:["Pact","Shadow","Hex","Dark"],Wizard:["Arcane","Sage","Spellweaver","Mind"],Artificer:["Craft","Forge","Construct","Invention"]},prefixes:["Thunder","Shadow","Flame","Frost","Storm","Star","Blood","Soul","Moon","Sun"],suffixes:["Blessed","Forged","Wreathed","Touched","Born","Kissed","Bound","Marked","Sworn","Woven"],occupations:["smith","weaver","caller","keeper","herald","warden","seeker","walker","singer"],realms:["Eternal Stage","Forgotten Hall","Misty Vale","Iron Keep","Crystal Spire","Shadow Court","Golden Fields","Storm Peak","Ancient Grove","Silent Deep"],subtitlePrefixes:["of Stars","Eternal","Reborn","Unchained","Ascendant","the Fallen","Unbound","Rising","Triumphant","Immortal"]},Wr={athletics:"STR",acrobatics:"DEX",sleight_of_hand:"DEX",stealth:"DEX",arcana:"INT",history:"INT",investigation:"INT",nature:"INT",religion:"INT",animal_handling:"WIS",insight:"WIS",medicine:"WIS",perception:"WIS",survival:"WIS",deception:"CHA",intimidation:"CHA",performance:"CHA",persuasion:"CHA"};function la(s){if(s in Rt)return Rt[s];try{const e=R.getInstance(),t=`classSpellLists.${s}`,a=e.get(t);if(a&&a.length>0)return a[0]}catch{}}function Da(s,e){if(s in oa){const t=oa[s];if(t&&e in t)return t[e]}try{const i=R.getInstance().get("classSpellSlots");if(i&&Array.isArray(i)){const n=i.find(r=>r.class===s);if(n&&n.slots_by_level){const r=n.slots_by_level;if(e in r)return r[e]}}}catch{}}const zr=10,xs=50;class Ie{constructor(){this.allSpellsCache=null,this.levelCache=null,this.schoolCache=null,this.manager=R.getInstance()}static getInstance(){return Ie.instance||(Ie.instance=new Ie),Ie.instance}invalidateCache(){this.allSpellsCache=null,this.levelCache=null,this.schoolCache=null}getSpell(e){return this.getSpells().find(a=>a.id===e)}getSpells(){if(!this.allSpellsCache){const e=this.manager.get("spells");this.allSpellsCache=e}return this.allSpellsCache}getSpellsByLevel(e){if(!this.levelCache){this.levelCache=new Map;const t=this.getSpells();for(const a of t)this.levelCache.has(a.level)||this.levelCache.set(a.level,[]),this.levelCache.get(a.level).push(a)}return this.levelCache.get(e)||[]}getSpellsBySchool(e){if(!this.schoolCache){this.schoolCache=new Map;const t=this.getSpells();for(const a of t)this.schoolCache.has(a.school)||this.schoolCache.set(a.school,[]),this.schoolCache.get(a.school).push(a)}return this.schoolCache.get(e)||[]}getSpellsForClass(e){return this.getSpells().filter(a=>a.classes&&a.classes.includes(e))}getAvailableSpells(e){const t=this.getSpells(),a=[];for(const i of t)this.validatePrerequisites(i,e).valid&&a.push(i);return a}getClassSpellList(e){const t=`spells.${e}`;return this.manager.get(t)||[]}getSpellSlotsForClass(e,t){const a=Da(e,t);return a?Object.values(a).reduce((n,r)=>n+r,0):0}validatePrerequisites(e,t){return De.validateSpellPrerequisites(e.prerequisites,t)}validateSpell(e){return De.validateSpell(e)}hasSpell(e){return this.getSpell(e)!==void 0}getSpellCount(){return this.getSpells().length}getSpellsBySource(e){return this.getSpells().filter(t=>t.source===e)}getQueryStats(){const e=this.getSpells(),t=e.filter(o=>o.source==="default"),a=e.filter(o=>o.source==="custom"),i={};for(let o=0;o<=9;o++)i[o]=this.getSpellsByLevel(o).length;const n={Abjuration:this.getSpellsBySchool("Abjuration").length,Conjuration:this.getSpellsBySchool("Conjuration").length,Divination:this.getSpellsBySchool("Divination").length,Enchantment:this.getSpellsBySchool("Enchantment").length,Evocation:this.getSpellsBySchool("Evocation").length,Illusion:this.getSpellsBySchool("Illusion").length,Necromancy:this.getSpellsBySchool("Necromancy").length,Transmutation:this.getSpellsBySchool("Transmutation").length},r=new Set;for(const o of e)if(o.classes)for(const l of o.classes)r.add(l);return{totalSpells:e.length,defaultSpells:t.length,customSpells:a.length,spellsByLevel:i,spellsBySchool:n,classesWithSpells:r.size}}}function qr(){return Ie.getInstance()}class R{constructor(){this.defaultData=new Map,this.extensions=new Map,this.customWeights=new Map,this.imageOverrides=new Map}static getInstance(){return R.instance||(R.instance=new R),R.instance}initializeDefaults(e,t){this.defaultData.set(e,[...t]),this.invalidateRegistryCache(e)}initializeAllDefaults(e){for(const[t,a]of Object.entries(e))this.initializeDefaults(t,a)}register(e,t,a={}){const i=this.extensions.get(e),n=a.mode!==void 0?a.mode:i?.options.mode,{weights:r={},validate:o=!0}=a,l=["races","classes"];let c=t;if(l.includes(e)&&(c=t.map(d=>typeof d=="string"?d:d&&typeof d=="object"&&typeof d.name=="string"?d.name:d)),o){const d=this.validate(e,c);if(!d.valid)throw new Error(`Invalid items for category '${e}':
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const y=require("./unifyBeatMap-DIsNlck4.cjs"),z=require("./arweaveGatewayManager-C9SIMDTd.cjs"),O=require("./gateway-UffqbE1i.cjs"),ds={0:"",1:"I",2:"II",3:"III",4:"IV",5:"V",6:"VI",7:"VII",8:"VIII",9:"IX",10:"X"},st=10,hs=10,ps=1e3,ia=1.5;function tr(s){return typeof s=="number"&&Number.isInteger(s)&&s>=0&&s<=10}function ms(s){return Math.max(0,Math.min(10,Math.floor(s)))}function ar(s){return typeof s=="string"&&["normal","aggressive"].includes(s)}const Ze={Greataxe:{name:"Greataxe",type:"weapon",rarity:"common",weight:7,damage:{dice:"1d12",damageType:"slashing"},weaponProperties:["two-handed"],spawnWeight:1,tags:["martial","melee","two-handed"],description:"A massive two-handed axe favored by barbarians for its devastating damage potential. Requires great strength to wield effectively."},Longsword:{name:"Longsword",type:"weapon",rarity:"common",weight:3,damage:{dice:"1d8",damageType:"slashing",versatile:"1d10"},weaponProperties:["versatile"],spawnWeight:1,tags:["martial","melee","versatile"],description:"A versatile sword usable with one or two hands. The classic weapon of knights and warriors throughout history."},Shortsword:{name:"Shortsword",type:"weapon",rarity:"common",weight:2,damage:{dice:"1d6",damageType:"piercing",versatile:"1d8"},weaponProperties:["finesse","light","versatile"],spawnWeight:1,tags:["martial","melee","finesse","light"],description:"A light, agile sword effective in close quarters. Can be used with Dexterity instead of Strength when wielded alone."},Rapier:{name:"Rapier",type:"weapon",rarity:"common",weight:2,damage:{dice:"1d8",damageType:"piercing"},weaponProperties:["finesse"],spawnWeight:1,tags:["martial","melee","finesse"],description:"A slender, thrusting sword designed for speed and precision. Favored by duelists and rogues for its finesse property."},Quarterstaff:{name:"Quarterstaff",type:"weapon",rarity:"common",weight:4,damage:{dice:"1d6",damageType:"bludgeoning",versatile:"1d8"},weaponProperties:["versatile","two-handed"],spawnWeight:1,tags:["simple","melee","versatile"],description:"A simple wooden staff, often the first weapon of monks and spellcasters. Effective when used with two hands."},Mace:{name:"Mace",type:"weapon",rarity:"common",weight:4,damage:{dice:"1d6",damageType:"bludgeoning",versatile:"1d8"},weaponProperties:["versatile"],spawnWeight:1,tags:["simple","melee","versatile"],description:"A simple blunt weapon effective against armored foes. Common among clerics and those favoring divine righteousness."},Handaxe:{name:"Handaxe",type:"weapon",rarity:"common",weight:2,damage:{dice:"1d6",damageType:"slashing"},weaponProperties:["light","thrown","range_20_60"],spawnWeight:1,tags:["simple","melee","thrown","light"],description:"A small axe versatile in melee or as a thrown weapon. Popular among dwarves and rangers."},Dagger:{name:"Dagger",type:"weapon",rarity:"common",weight:1,damage:{dice:"1d4",damageType:"piercing",versatile:"1d6"},weaponProperties:["finesse","light","thrown","range_20_60"],spawnWeight:1,tags:["simple","melee","finesse","light","thrown"],description:"A lightweight weapon useful for melee and ranged attacks. Essential for rogues and as a backup weapon for all adventurers."},Dart:{name:"Dart",type:"weapon",rarity:"common",weight:.25,damage:{dice:"1d4",damageType:"piercing"},weaponProperties:["finesse","thrown","range_20_60"],spawnWeight:1,tags:["simple","ranged","thrown","finesse"],description:"A small throwing weapon, often carried in bulk. Lightweight but deals minimal damage."},Javelin:{name:"Javelin",type:"weapon",rarity:"common",weight:2,damage:{dice:"1d6",damageType:"piercing"},weaponProperties:["thrown","range_30_120"],spawnWeight:1,tags:["simple","melee","thrown"],description:"A light spear designed for throwing. Effective at both melee and range."},"Light Crossbow":{name:"Light Crossbow",type:"weapon",rarity:"uncommon",weight:5,damage:{dice:"1d8",damageType:"piercing"},weaponProperties:["ammunition","range_80_320","two-handed","loading"],spawnWeight:1,tags:["simple","ranged","two-handed","ammunition"],description:"A compact crossbow requiring minimal training to use. Devastating at range but requires a reload action between shots."},"Hand Crossbow":{name:"Hand Crossbow",type:"weapon",rarity:"uncommon",weight:3,damage:{dice:"1d6",damageType:"piercing"},weaponProperties:["ammunition","range_30_120","light","loading"],spawnWeight:1,tags:["martial","ranged","light","ammunition"],description:"A small crossbow usable in one hand. Popular among rogues and those who need a ranged sidearm."},Longbow:{name:"Longbow",type:"weapon",rarity:"uncommon",weight:2,damage:{dice:"1d8",damageType:"piercing"},weaponProperties:["ammunition","range_150_600","two-handed","heavy"],spawnWeight:1,tags:["martial","ranged","two-handed","ammunition","heavy"],description:"A tall bow with impressive range and power. Requires significant strength to draw and training to use effectively."},"Martial Melee Weapon":{name:"Martial Melee Weapon",type:"weapon",rarity:"common",weight:3,damage:{dice:"1d8",damageType:"slashing"},weaponProperties:["versatile"],spawnWeight:.5,tags:["martial","melee","versatile","generic"],description:"A generic martial melee weapon. Used as a fallback when no specific weapon is specified."},"No Armor":{name:"No Armor",type:"armor",rarity:"common",weight:0,acBonus:10,spawnWeight:1,tags:["armor","light","no_armor"],description:"No armor worn. Relies entirely on Dexterity for defense. Maximum mobility but minimal protection."},"Leather Armor":{name:"Leather Armor",type:"armor",rarity:"common",weight:10,acBonus:11,properties:[{type:"passive_modifier",target:"ac",value:"11 + DEX",description:"Base AC: 11 + DEX"}],spawnWeight:1,tags:["armor","light"],description:"Light armor made from cured animal hides. Offers basic protection without restricting movement. Adds full Dexterity bonus to AC."},"Scale Mail":{name:"Scale Mail",type:"armor",rarity:"common",weight:45,acBonus:14,properties:[{type:"passive_modifier",target:"ac",value:"14 + min(DEX, 2)",description:"Base AC: 14 + DEX (max 2)"},{type:"special_property",target:"stealth_disadvantage",value:!0,description:"Stealth disadvantage"}],spawnWeight:1,tags:["armor","medium"],description:"Armor consisting of many overlapping metal scales. Solid protection but imposes disadvantage on Stealth checks."},"Chain Mail":{name:"Chain Mail",type:"armor",rarity:"common",weight:55,acBonus:16,properties:[{type:"passive_modifier",target:"ac",value:"16",description:"Fixed AC: 16"},{type:"special_property",target:"stealth_disadvantage",value:!0,description:"Stealth disadvantage"},{type:"stat_requirement",target:"STR",value:13,description:"Requires STR 13"}],spawnWeight:1,tags:["armor","heavy"],description:"Interlocking metal rings provide excellent protection. Requires Strength 13 to wear and imposes disadvantage on Stealth."},"Plate Armor":{name:"Plate Armor",type:"armor",rarity:"rare",weight:65,acBonus:18,properties:[{type:"passive_modifier",target:"ac",value:"18",description:"Fixed AC: 18"},{type:"special_property",target:"stealth_disadvantage",value:!0,description:"Stealth disadvantage"},{type:"stat_requirement",target:"STR",value:15,description:"Requires STR 15"}],spawnWeight:1,tags:["armor","heavy","rare"],description:"The finest armor available, consisting of shaped and fitted metal plates. Provides maximum protection but is expensive, heavy, and requires Strength 15."},Shield:{name:"Shield",type:"armor",rarity:"common",weight:6,acBonus:2,properties:[{type:"passive_modifier",target:"ac",value:2,description:"+2 AC bonus"}],spawnWeight:1,tags:["armor","shield"],description:"A portable defensive barrier carried in one hand. Grants +2 to Armor Class. Cannot be used with two-handed weapons."},Spellbook:{name:"Spellbook",type:"item",rarity:"uncommon",weight:3,spawnWeight:1,tags:["gear","arcane","spellbook"],description:"A blank tome used by wizards to record their spells. Essential for preparing and casting wizard spells."},"Holy Symbol":{name:"Holy Symbol",type:"item",rarity:"common",weight:1,spawnWeight:1,tags:["gear","divine","focus"],description:"An emblem of a deity used by clerics and paladins as a divine focus for spellcasting."},"Arcane Focus":{name:"Arcane Focus",type:"item",rarity:"common",weight:1,spawnWeight:1,tags:["gear","arcane","focus"],description:"An object such as a crystal, orb, or wand used to channel arcane magic. Replaces material components for most spells."},"Druidic Focus":{name:"Druidic Focus",type:"item",rarity:"common",weight:1,spawnWeight:1,tags:["gear","druid","focus"],description:"A natural object such as a staff, wand, or totem used by druids to channel primal magic."},"Component Pouch":{name:"Component Pouch",type:"box",rarity:"common",weight:2,spawnWeight:1,tags:["gear","components","spellcasting"],boxContents:{drops:[],consumeOnOpen:!1},description:"A pouch containing small compartments for storing material components needed for spells. A practical alternative to an arcane focus. Assumes to contain all material components without specific gold costs."},Lute:{name:"Lute",type:"item",rarity:"common",weight:2,spawnWeight:1,tags:["gear","instrument","bard"],description:"A stringed musical instrument used by bards to perform and channel magical inspiration."},"Thieves' Tools":{name:"Thieves' Tools",type:"item",rarity:"uncommon",weight:1,grantsSkills:[{skillId:"thieves_tools",level:"proficient"}],spawnWeight:1,tags:["gear","tools","dexterity"],description:"A small collection of tools including picks, files, and crowbar. Essential for picking locks and disarming traps."},"Healer's Kit":{name:"Healer's Kit",type:"box",rarity:"common",weight:3,spawnWeight:1,tags:["gear","healing","consumable"],boxContents:{drops:[{pool:[{weight:100,itemName:"Medical Supply",quantity:10}]}],consumeOnOpen:!1},description:"A collection of bandages, salves, and tools for treating wounds. Contains 10 medical supplies. Essential for stabilizing dying creatures and providing medical aid."},Bedroll:{name:"Bedroll",type:"item",rarity:"common",weight:7,spawnWeight:1,tags:["gear","camp","comfort"],description:"A padded blanket roll used for sleeping outdoors. Essential comfort for adventurers on the road."},Rope:{name:"Rope",type:"item",rarity:"common",weight:1,spawnWeight:1,tags:["gear","utility","climbing"],description:"50 feet of hempen rope. Essential for climbing, securing prisoners, and countless other utility purposes."},Backpack:{name:"Backpack",type:"box",rarity:"common",weight:5,spawnWeight:1,tags:["gear","container","storage"],boxContents:{drops:[],consumeOnOpen:!1},description:"A leather backpack for carrying adventuring gear. Essential storage for any traveling adventurer."},Torch:{name:"Torch",type:"item",rarity:"common",weight:1,properties:[{type:"special_property",target:"light",value:"bright_light_20ft",description:"Sheds bright light 20ft, dim 20ft"}],spawnWeight:1,tags:["gear","light","consumable"],description:"A wooden rod topped with flammable material. Burns for about an hour, shedding light in a 20-foot radius."},Waterskin:{name:"Waterskin",type:"item",rarity:"common",weight:5,spawnWeight:1,tags:["gear","container","water"],description:"A leather container for holding water. Essential for surviving in wilderness and dungeon environments."},"Ink & Quill":{name:"Ink & Quill",type:"item",rarity:"common",weight:1,spawnWeight:1,tags:["gear","writing","consumable"],description:"Ink and a quill pen for writing. Useful for recording maps, copying spells, and documenting adventures."},"Ball Bearings":{name:"Ball Bearings",type:"item",rarity:"common",weight:2,spawnWeight:1,tags:["gear","utility","rogue"],description:"A bag containing 1,000 tiny metal balls. Scattered on the ground, they create a treacherous surface that can cause creatures to fall prone."},String:{name:"String",type:"item",rarity:"common",weight:0,spawnWeight:1,tags:["gear","utility"],description:"10 feet of sturdy string. Useful for tripwires, tying items together, or countless other creative purposes."},Bell:{name:"Bell",type:"item",rarity:"common",weight:0,spawnWeight:1,tags:["gear","utility","alarm"],description:"A small brass bell that rings when disturbed. Perfect for creating warning systems or signaling."},Crowbar:{name:"Crowbar",type:"item",rarity:"common",weight:5,spawnWeight:1,tags:["gear","tool","utility"],description:"A heavy iron bar used for prying open doors, chests, and other stuck objects. Grants advantage on Strength checks to force objects open."},Hammer:{name:"Hammer",type:"item",rarity:"common",weight:3,spawnWeight:1,tags:["gear","tool","utility"],description:"A sturdy hammer designed for driving pitons into walls and other heavy work. Essential for dungeon exploration."},Piton:{name:"Piton",type:"item",rarity:"common",weight:.25,spawnWeight:1,tags:["gear","climbing","utility"],description:"A metal spike with a ring at one end. Driven into cracks to create climbing anchors or secure rope lines."},"Small Knife":{name:"Small Knife",type:"item",rarity:"common",weight:1,spawnWeight:1,tags:["gear","tool","utility"],description:"A small utility knife useful for cutting rope, skinning game, and other everyday tasks. Not designed for combat."},"Iron Key":{name:"Iron Key",type:"item",rarity:"common",weight:0,spawnWeight:.5,tags:["key","utility","consumable"],description:"A simple iron key that can unlock certain containers. Consumed when used to open a locked box."},"Golden Key":{name:"Golden Key",type:"item",rarity:"uncommon",weight:0,spawnWeight:.2,tags:["key","utility","consumable","valuable"],description:"An ornate golden key for unlocking valuable chests. Consumed when used to open a locked box."},"Skeleton Key":{name:"Skeleton Key",type:"item",rarity:"rare",weight:0,spawnWeight:.1,tags:["key","utility","consumable","universal"],description:"A master key that can open many different locks. Consumed when used to open a locked box."},Lockpick:{name:"Lockpick",type:"item",rarity:"common",weight:0,spawnWeight:.8,tags:["gear","rogue","consumable","tool"],description:"A simple lockpick. Multiple may be needed for complex locks, as they often break during use."},"Gold Coin":{name:"Gold Coin",type:"item",rarity:"common",weight:0,spawnWeight:0,tags:["currency","gold","money"],description:"A standard gold coin used as currency throughout the realm. Can be consumed to open certain magical locks."},"Locked Chest":{name:"Locked Chest",type:"box",rarity:"uncommon",weight:10,spawnWeight:.5,tags:["loot","treasure","locked"],boxContents:{openRequirements:[{itemName:"Iron Key"}],drops:[{pool:[{weight:100,gold:50}]},{pool:[{weight:50,itemName:"Shortsword"},{weight:30,itemName:"Leather Armor"},{weight:20,itemName:"Medical Supply",quantity:3}]}]},description:"A sturdy locked chest. Requires an Iron Key to open."},"Gilded Strongbox":{name:"Gilded Strongbox",type:"box",rarity:"rare",weight:15,spawnWeight:.3,tags:["loot","treasure","gold-locked"],boxContents:{openRequirements:[{itemName:"Gold Coin",quantity:100}],drops:[{pool:[{weight:100,gold:250}]},{pool:[{weight:40,itemName:"Longsword"},{weight:30,itemName:"Chain Mail"},{weight:20,itemName:"Scale Mail"},{weight:10,itemName:"Medical Supply",quantity:5}]}]},description:"A gilded strongbox with a magical lock. Consumes 100 Gold Coins to unlock."},"Royal Treasury Box":{name:"Royal Treasury Box",type:"box",rarity:"very_rare",weight:20,spawnWeight:.1,tags:["loot","treasure","royal","locked"],boxContents:{openRequirements:[{itemName:"Golden Key"},{itemName:"Gold Coin",quantity:200}],drops:[{pool:[{weight:100,gold:1e3}]},{pool:[{weight:30,itemName:"Plate Armor"},{weight:25,itemName:"Chain Mail"},{weight:25,itemName:"Greataxe"},{weight:20,itemName:"Longsword"}]},{pool:[{weight:60,itemName:"Medical Supply",quantity:10},{weight:40,itemName:"Thieves' Tools"}]}]},description:"A royal treasury box sealed with powerful magic. Requires a Golden Key and 200 Gold Coins to open."},"Thieves' Cache":{name:"Thieves' Cache",type:"box",rarity:"uncommon",weight:5,spawnWeight:.4,tags:["loot","treasure","rogue","locked"],boxContents:{openRequirements:[{itemName:"Lockpick",quantity:3}],drops:[{pool:[{weight:100,gold:75}]},{pool:[{weight:50,itemName:"Thieves' Tools"},{weight:30,itemName:"Dagger"},{weight:20,itemName:"Disguise Kit"}]}]},description:"A hidden cache with a complex lock. Requires 3 lockpicks to crack (consumed in the process)."},Candle:{name:"Candle",type:"item",rarity:"common",weight:0,properties:[{type:"special_property",target:"light",value:"dim_light_5ft",description:"Sheds dim light 5ft, bright 2ft"}],spawnWeight:1,tags:["gear","light","consumable"],description:"A simple wax candle that burns for about an hour. Provides dim light in a 5-foot radius, bright light in a 2-foot radius."},"Hooded Lantern":{name:"Hooded Lantern",type:"item",rarity:"common",weight:2,properties:[{type:"special_property",target:"light",value:"bright_light_30ft",description:"Sheds bright light 30ft, dim 30ft when unhooded"}],spawnWeight:1,tags:["gear","light"],description:"A metal lantern with a hood that can be raised or lowered to control the light. Burns for 6 hours on a flask of oil. Sheds bright light in a 30-foot cone and dim light for an additional 30 feet when unhooded."},"Oil Flask":{name:"Oil Flask",type:"item",rarity:"common",weight:1,spawnWeight:1,tags:["gear","light","consumable"],description:"A flask of oil suitable for fueling lanterns. Can also be used as a weapon when thrown and ignited, dealing fire damage. Burns for 6 hours in a lantern."},Rations:{name:"Rations",type:"item",rarity:"common",weight:2,spawnWeight:1,tags:["gear","food","consumable"],description:"One day's worth of dried meat, hard bread, and other preserved food. Essential for surviving long journeys without access to fresh food."},Water:{name:"Water",type:"item",rarity:"common",weight:5,spawnWeight:1,tags:["gear","drink","consumable"],description:"Clean drinking water essential for survival. Typically stored in a waterskin and consumed during rest periods."},"Medical Supply":{name:"Medical Supply",type:"item",rarity:"common",weight:.3,spawnWeight:1,tags:["gear","healing","consumable"],description:"A single-use bandage, salve, or medical tool for treating wounds. Used to stabilize dying creatures or provide basic first aid."},Tinderbox:{name:"Tinderbox",type:"item",rarity:"common",weight:1,spawnWeight:1,tags:["gear","fire","utility"],description:"A small container with flint, steel, and tinder for starting fires. Essential for lighting torches, campfires, and lanterns."},"Mess Kit":{name:"Mess Kit",type:"item",rarity:"common",weight:1,spawnWeight:1,tags:["gear","camp","dining"],description:"A tin box containing a cup, bowl, and simple eating utensils. Collapses flat for easy packing and storage."},Blanket:{name:"Blanket",type:"item",rarity:"common",weight:3,spawnWeight:1,tags:["gear","camp","comfort"],description:"A warm woolen blanket for sleeping. Provides basic warmth and comfort during rest in cold environments."},Costume:{name:"Costume",type:"item",rarity:"common",weight:4,spawnWeight:1,tags:["gear","clothing","performance"],description:"A set of performer's clothing suitable for stage appearances and entertaining. Includes colorful fabrics and decorative elements."},"Traveler's Clothes":{name:"Traveler's Clothes",type:"item",rarity:"common",weight:4,spawnWeight:1,tags:["gear","clothing"],description:"Sturdy, practical clothing designed for long journeys. Includes boots, a cloak, and weather-appropriate garb for the road."},Vestments:{name:"Vestments",type:"item",rarity:"common",weight:2,spawnWeight:1,tags:["gear","clothing","religious"],description:"Ceremonial robes worn by priests and other religious figures. Often adorned with symbols of faith and made of fine materials."},"Alms Box":{name:"Alms Box",type:"item",rarity:"common",weight:0,spawnWeight:1,tags:["gear","religious","container"],description:"A small wooden box with a slot for collecting alms and donations. Used by priests and mendicants to gather charitable offerings."},Incense:{name:"Incense",type:"item",rarity:"common",weight:0,spawnWeight:1,tags:["gear","religious","consumable"],description:"A block of aromatic incense used in religious ceremonies and rituals. Burns slowly to produce fragrant smoke during prayers and worship."},Censer:{name:"Censer",type:"item",rarity:"common",weight:2,spawnWeight:1,tags:["gear","religious"],description:"A metal container suspended on chains, used for burning incense during religious ceremonies. Often made of silver or brass and adorned with religious symbols."},"Book of Lore":{name:"Book of Lore",type:"item",rarity:"common",weight:5,spawnWeight:1,tags:["gear","book","knowledge"],description:"A scholarly tome containing accumulated knowledge on various subjects. Scholars and wizards often carry these to reference arcane theories, historical accounts, or arcane formulas."},"Bottle of Ink":{name:"Bottle of Ink",type:"item",rarity:"common",weight:0,spawnWeight:1,tags:["gear","writing","consumable"],description:"A small glass bottle containing black ink, sealed with a stopper. Used with an ink pen for writing on parchment or paper."},"Ink Pen":{name:"Ink Pen",type:"item",rarity:"common",weight:0,spawnWeight:1,tags:["gear","writing"],description:"A wooden pen with a metal nib, designed to be dipped in ink for writing. Essential for scribes, scholars, and anyone needing to keep written records."},Parchment:{name:"Parchment",type:"item",rarity:"common",weight:0,spawnWeight:1,tags:["gear","writing","consumable"],description:"A single sheet of parchment made from animal skin, used for writing important documents, letters, or arcane inscriptions. More durable than paper but also more expensive."},"Bag of Sand":{name:"Bag of Sand",type:"item",rarity:"common",weight:0,spawnWeight:1,tags:["gear","writing","utility"],description:"A small cloth bag filled with fine sand. Sprinkled over freshly written text to absorb excess ink and speed drying, preventing smudges and blots."},"Disguise Kit":{name:"Disguise Kit",type:"item",rarity:"common",weight:3,spawnWeight:1,tags:["gear","deception","rogue"],description:"A kit containing cosmetics, hair dye, and small props to create disguises. Includes makeup, false facial hair, and other materials needed to change one's appearance. Essential for spies, actors, and rogues who need to assume different identities."},"Burglar's Pack":{name:"Burglar's Pack",type:"box",rarity:"common",weight:44,spawnWeight:1,tags:["gear","pack","rogue"],boxContents:{drops:[{pool:[{weight:100,itemName:"Backpack"}]},{pool:[{weight:100,itemName:"Ball Bearings",quantity:1e3}]},{pool:[{weight:100,itemName:"String"}]},{pool:[{weight:100,itemName:"Bell"}]},{pool:[{weight:100,itemName:"Candle",quantity:5}]},{pool:[{weight:100,itemName:"Crowbar"}]},{pool:[{weight:100,itemName:"Hammer"}]},{pool:[{weight:100,itemName:"Piton",quantity:10}]},{pool:[{weight:100,itemName:"Hooded Lantern"}]},{pool:[{weight:100,itemName:"Oil Flask",quantity:2}]},{pool:[{weight:100,itemName:"Rations",quantity:5}]},{pool:[{weight:100,itemName:"Tinderbox"}]},{pool:[{weight:100,itemName:"Waterskin"}]},{pool:[{weight:100,itemName:"Rope"}]}],consumeOnOpen:!0},description:"A backpack containing gear for a rogue: a backpack, a bag of 1,000 ball bearings, 10 feet of string, a bell, 5 candles, a crowbar, a hammer, 10 pitons, a hooded lantern, 2 flasks of oil, 5 days' rations, a tinderbox, and a waterskin. Includes a pack with 50 feet of hempen rope strapped to the side."},"Explorer's Pack":{name:"Explorer's Pack",type:"box",rarity:"common",weight:59,spawnWeight:1,tags:["gear","pack","general"],boxContents:{drops:[{pool:[{weight:100,itemName:"Backpack"}]},{pool:[{weight:100,itemName:"Bedroll"}]},{pool:[{weight:100,itemName:"Mess Kit"}]},{pool:[{weight:100,itemName:"Tinderbox"}]},{pool:[{weight:100,itemName:"Waterskin"}]},{pool:[{weight:100,itemName:"Rope"}]},{pool:[{weight:100,itemName:"Torch",quantity:10}]},{pool:[{weight:100,itemName:"Rations",quantity:10}]}],consumeOnOpen:!0},description:"A backpack containing gear for wilderness exploration: a backpack, a bedroll, mess kit, tinderbox, 10 torches, and 10 days' rations. Includes a waterskin and 50 feet of hempen rope."},"Entertainer's Pack":{name:"Entertainer's Pack",type:"box",rarity:"common",weight:58,spawnWeight:1,tags:["gear","pack","bard"],boxContents:{drops:[{pool:[{weight:100,itemName:"Backpack"}]},{pool:[{weight:100,itemName:"Bedroll"}]},{pool:[{weight:100,itemName:"Costume",quantity:2}]},{pool:[{weight:100,itemName:"Candle",quantity:5}]},{pool:[{weight:100,itemName:"Rations",quantity:5}]},{pool:[{weight:100,itemName:"Waterskin"}]},{pool:[{weight:100,itemName:"Disguise Kit"}]}],consumeOnOpen:!0},description:"A backpack containing a bard's performance gear: a backpack, a bedroll, 2 costumes, 5 candles, 5 days' rations, a waterskin, and a disguise kit."},"Priest's Pack":{name:"Priest's Pack",type:"box",rarity:"common",weight:33,spawnWeight:1,tags:["gear","pack","cleric"],boxContents:{drops:[{pool:[{weight:100,itemName:"Backpack"}]},{pool:[{weight:100,itemName:"Blanket"}]},{pool:[{weight:100,itemName:"Candle",quantity:10}]},{pool:[{weight:100,itemName:"Tinderbox"}]},{pool:[{weight:100,itemName:"Alms Box"}]},{pool:[{weight:100,itemName:"Incense",quantity:2}]},{pool:[{weight:100,itemName:"Censer"}]},{pool:[{weight:100,itemName:"Vestments"}]},{pool:[{weight:100,itemName:"Rations",quantity:2}]},{pool:[{weight:100,itemName:"Waterskin"}]}],consumeOnOpen:!0},description:"A backpack containing a priest's supplies: a backpack, a blanket, 10 candles, a tinderbox, an alms box, 2 blocks of incense, a censer, vestments, 2 days' rations, and a waterskin."},"Dungeon Delver's Pack":{name:"Dungeon Delver's Pack",type:"box",rarity:"common",weight:48,spawnWeight:1,tags:["gear","pack","dungeon"],boxContents:{drops:[{pool:[{weight:100,itemName:"Backpack"}]},{pool:[{weight:100,itemName:"Crowbar"}]},{pool:[{weight:100,itemName:"Hammer"}]},{pool:[{weight:100,itemName:"Piton",quantity:10}]},{pool:[{weight:100,itemName:"Torch",quantity:10}]},{pool:[{weight:100,itemName:"Tinderbox"}]},{pool:[{weight:100,itemName:"Rations",quantity:10}]},{pool:[{weight:100,itemName:"Waterskin"}]},{pool:[{weight:100,itemName:"Rope"}]}],consumeOnOpen:!0},description:"A backpack optimized for dungeon exploration. Contains a backpack, crowbar, hammer, 10 pitons, 10 torches, a tinderbox, 10 days' rations, and a waterskin. Includes 50 feet of hempen rope."},"Dungeoneer's Pack":{name:"Dungeoneer's Pack",type:"box",rarity:"common",weight:48,spawnWeight:1,tags:["gear","pack","dungeon"],boxContents:{drops:[{pool:[{weight:100,itemName:"Backpack"}]},{pool:[{weight:100,itemName:"Crowbar"}]},{pool:[{weight:100,itemName:"Hammer"}]},{pool:[{weight:100,itemName:"Piton",quantity:10}]},{pool:[{weight:100,itemName:"Torch",quantity:10}]},{pool:[{weight:100,itemName:"Tinderbox"}]},{pool:[{weight:100,itemName:"Rations",quantity:10}]},{pool:[{weight:100,itemName:"Waterskin"}]},{pool:[{weight:100,itemName:"Rope"}]}],consumeOnOpen:!0},description:"A backpack optimized for underground exploration. Contains a backpack, crowbar, hammer, 10 pitons, 10 torches, a tinderbox, 10 days' rations, and a waterskin. Includes 50 feet of hempen rope."},"Scholar's Pack":{name:"Scholar's Pack",type:"box",rarity:"common",weight:49,spawnWeight:1,tags:["gear","pack","wizard"],boxContents:{drops:[{pool:[{weight:100,itemName:"Backpack"}]},{pool:[{weight:100,itemName:"Book of Lore"}]},{pool:[{weight:100,itemName:"Bottle of Ink"}]},{pool:[{weight:100,itemName:"Ink Pen"}]},{pool:[{weight:100,itemName:"Parchment",quantity:10}]},{pool:[{weight:100,itemName:"Bag of Sand"}]}],consumeOnOpen:!0},description:"A backpack containing a scholar's supplies: a backpack, a book of lore, a bottle of ink, an ink pen, 10 sheets of parchment, and a little bag of sand."},"Traveler's Pack":{name:"Traveler's Pack",type:"box",rarity:"common",weight:43,spawnWeight:1,tags:["gear","pack","general"],boxContents:{drops:[{pool:[{weight:100,itemName:"Backpack"}]},{pool:[{weight:100,itemName:"Bedroll"}]},{pool:[{weight:100,itemName:"Mess Kit"}]},{pool:[{weight:100,itemName:"Rations",quantity:10}]},{pool:[{weight:100,itemName:"Waterskin"}]},{pool:[{weight:100,itemName:"Traveler's Clothes"}]},{pool:[{weight:100,itemName:"Rope"}]}],consumeOnOpen:!0},description:"A backpack for long-distance travel: a backpack, a bedroll, a mess kit, 10 days' rations, a waterskin, traveler's clothes, and 50 feet of hempen rope."},Arrow:{name:"Arrow",type:"item",rarity:"common",weight:.05,spawnWeight:1,tags:["ammunition","bow"],description:"Ammunition for a bow. Typically carried in quivers of 20 arrows. Essential for archers."},Bolt:{name:"Bolt",type:"item",rarity:"common",weight:.075,spawnWeight:1,tags:["ammunition","crossbow"],description:"Ammunition for a crossbow. Shorter and heavier than arrows, designed for crossbow use."},Insignia:{name:"Insignia",type:"item",rarity:"common",weight:0,spawnWeight:1,tags:["gear","roleplay","insignia"],description:"A badge, banner, or token representing rank, membership, or authority. Useful for roleplaying and establishing credentials."},"Martial Arts":{name:"Martial Arts",type:"weapon",rarity:"common",weight:0,damage:{dice:"1d4",damageType:"bludgeoning",versatile:"1d6"},weaponProperties:["finesse","unarmed"],spawnWeight:1,tags:["monk","unarmed","natural"],description:"The monk's unarmed strike. Uses Dexterity instead of Strength and deals increased damage at higher levels."}};class U{static rollDie(e){if(e<1)throw new Error("Die must have at least 1 side");return Math.floor(Math.random()*e)+1}static rollMultipleDice(e,t){const a=[];for(let i=0;i<e;i++)a.push(this.rollDie(t));return a}static parseDiceFormula(e){const t=e.match(/^(\d+)d(\d+)([+-]\d+)?$/);if(!t)throw new Error(`Invalid dice formula: ${e}`);const a=parseInt(t[1],10),i=parseInt(t[2],10),n=t[3]?parseInt(t[3],10):0,r=this.rollMultipleDice(a,i),o=r.reduce((l,c)=>l+c,0)+n;return{diceCount:a,diceSides:i,modifier:n,rolls:r,total:o}}static rollD20(){return this.rollDie(20)}static rollWithAdvantage(){const e=this.rollD20(),t=this.rollD20();return{roll1:e,roll2:t,result:Math.max(e,t)}}static rollWithDisadvantage(){const e=this.rollD20(),t=this.rollD20();return{roll1:e,roll2:t,result:Math.min(e,t)}}static rollInitiative(e){return this.rollD20()+e}static isCriticalHit(e){return e===20}static isCriticalMiss(e){return e===1}static doubleDamage(e){return[...e,...e]}static calculateDamage(e,t,a=!1){let n=this.parseDiceFormula(e).rolls;a&&(n=this.doubleDamage(n));const r=n.reduce((o,l)=>o+l,0)+t;return{diceFormula:e,rolls:n,modifier:t,total:r,isCritical:a}}static rollSavingThrow(e,t=0){return this.rollD20()+e+t}static rollAbilityCheck(e,t=0){return this.rollD20()+e+t}static seededRoll(e){const t=Math.sin(e)*1e4;return Math.floor((t-Math.floor(t))*20)+1}static rollPercentile(){return this.rollDie(100)}}const ir={1:[0,2,0,0,0,0,0,0,0,0],2:[0,3,0,0,0,0,0,0,0,0],3:[0,4,2,0,0,0,0,0,0,0],4:[0,4,3,0,0,0,0,0,0,0],5:[0,4,3,2,0,0,0,0,0,0],6:[0,4,3,3,0,0,0,0,0,0],7:[0,4,3,3,1,0,0,0,0,0],8:[0,4,3,3,2,0,0,0,0,0],9:[0,4,3,3,3,1,0,0,0,0],10:[0,4,3,3,3,2,0,0,0,0],11:[0,4,3,3,3,2,1,0,0,0],12:[0,4,3,3,3,2,1,0,0,0],13:[0,4,3,3,3,2,1,1,0,0],14:[0,4,3,3,3,2,1,1,0,0],15:[0,4,3,3,3,2,1,1,1,0],16:[0,4,3,3,3,2,1,1,1,0],17:[0,4,3,3,3,2,1,1,1,1],18:[0,4,3,3,3,3,1,1,1,1],19:[0,4,3,3,3,3,2,1,1,1],20:[0,4,3,3,3,3,2,2,1,1]};function sa(s){const e=ir[s];if(!e)return{};const t={};for(let a=1;a<e.length;a++)e[a]>0&&(t[a]=e[a]);return t}const di={charm:{name:"Charmed",requiresConcentration:!0,mechanicalEffects:{disadvantageOnAttackNonSource:!0}},frighten:{name:"Frightened",requiresConcentration:!0,mechanicalEffects:{disadvantageOnAttack:!0,disadvantageOnAbilityChecks:!0}},stun:{name:"Stunned",requiresConcentration:!1,mechanicalEffects:{disadvantageOnDexSaves:!0,speedZero:!0,skipTurn:!0}},paralyze:{name:"Paralyzed",requiresConcentration:!1,mechanicalEffects:{disadvantageOnDexSaves:!0,speedZero:!0,skipTurn:!0}},restrain:{name:"Restrained",requiresConcentration:!0,mechanicalEffects:{disadvantageOnDexSaves:!0,speedZero:!0}},poison:{name:"Poisoned",requiresConcentration:!1,mechanicalEffects:{disadvantageOnAttack:!0,disadvantageOnAbilityChecks:!0}},blind:{name:"Blinded",requiresConcentration:!1,mechanicalEffects:{disadvantageOnAttack:!0,disadvantageOnAbilityChecks:!0}},deafen:{name:"Deafened",requiresConcentration:!1},burn:{name:"Burning",requiresConcentration:!1}};class V{constructor(e){this.diceRoller=e}castSpell(e,t,a){const i=t.level??0;if(!this.hasSpellSlot(e,i))return{success:!1,spellName:t.name,caster:e,targets:a,effectsApplied:[],spellSlotUsed:i,description:`${e.character.name} tried to cast ${t.name} but has no spell slots available!`};this.consumeSpellSlot(e,i);const n=[];let r,o;const l=t.damage_dice??t.damage,c=t.damage_type??t.damageType,u=t.saving_throw??t.save;if(l&&c){if(t.attack_roll)r=this.diceRoller?this.diceRoller.calculateDamage(l,0,!1):U.calculateDamage(l,0,!1);else if(u){o=this.calculateSaveDC(e,u);for(const m of a){const v=u.toLowerCase()==="dexterity"&&m.statusEffects.some(E=>E.mechanicalEffects?.disadvantageOnDexSaves);this.makeSavingThrow(m,u,o,v)||(r=this.diceRoller?this.diceRoller.calculateDamage(l,0,!1):U.calculateDamage(l,0,!1),m.currentHP-=r.total,m.currentHP<0&&(m.currentHP=0,m.isDefeated=!0))}}}const d=t.tags??[],h=new Set;for(const m of d){const f=di[m];if(f){h.add(f.name);const v={name:f.name,description:`${f.name} by ${e.character.name}`,duration:1,source:e.id,hasConcentration:f.requiresConcentration,mechanicalEffects:f.mechanicalEffects};for(const w of a)n.push({...v})}}const p=[t.description,t.effect].filter(Boolean).join(" ").toLowerCase();if(!h.has("Charmed")&&p.includes("charm")){const m={name:"Charmed",description:`Charmed by ${e.character.name}`,duration:1,source:e.id,hasConcentration:!0};for(const f of a)n.push({...m})}if(!h.has("Frightened")&&p.includes("frighten")){const m={name:"Frightened",description:`Frightened of ${e.character.name}`,duration:1,source:e.id,hasConcentration:!0};for(const f of a)n.push({...m})}const g=`${e.character.name} casts ${t.name} (Level ${i}) at ${a.map(m=>m.character.name).join(", ")}`;return{success:!0,spellName:t.name,caster:e,targets:a,saveDC:o,damage:r,effectsApplied:n,spellSlotUsed:i,description:g}}hasSpellSlot(e,t){return t===0?!0:!e.spellSlots||!e.spellSlots[t]?!1:e.spellSlots[t]>0}consumeSpellSlot(e,t){t!==0&&(e.spellSlots||(e.spellSlots={}),e.spellSlots[t]||(e.spellSlots[t]=0),e.spellSlots[t]--)}restoreSpellSlots(e){e.spellSlots=sa(e.character.level)}calculateSaveDC(e,t){const a=t.toLowerCase(),i=e.character.ability_modifiers[a]||0,n=e.character.proficiency_bonus;return 8+i+n}makeSavingThrow(e,t,a,i=!1){const n=t.toLowerCase(),r=e.character.ability_modifiers[n]||0,c=e.character.saving_throws[n]?e.character.proficiency_bonus:0;let u;return i?u=(this.diceRoller?this.diceRoller.rollWithDisadvantage():U.rollWithDisadvantage()).result+r+c:u=this.diceRoller?this.diceRoller.rollSavingThrow(r,c):U.rollSavingThrow(r,c),u>=a}getSpellSlotInfo(e){if(!e.spellSlots)return"No spell slots available";const t=[];for(let a=1;a<=9;a++){const i=e.spellSlots[a]||0;i>0&&t.push(`Level ${a}: ${i} slots`)}return t.length>0?t.join(", "):"No spell slots available"}canUpcast(e,t,a){const i=t.level??0;return a<i?!1:this.hasSpellSlot(e,a)}upcastSpell(e,t,a,i){const n=t.level??0;if(i<n)return{success:!1,spellName:t.name,caster:e,targets:a,effectsApplied:[],spellSlotUsed:i,description:`Cannot cast ${t.name} with a lower-level slot`};const r=t.level;t.level=i;const o=this.castSpell(e,t,a);return t.level=r,o}static hasSpellTag(e,t){return Array.isArray(e.tags)&&e.tags.includes(t)}static getSpellTags(e){return Array.isArray(e.tags)?[...e.tags]:[]}static isDamageSpell(e){return V.hasSpellTag(e,"damage")||!!(e.damage_dice||e.damage)}static requiresConcentration(e){return e.concentration===!0}static isAOESpell(e){return V.hasSpellTag(e,"aoe")}static isMultiTargetSpell(e){return V.hasSpellTag(e,"multi-target")}static isBonusActionSpell(e){return V.hasSpellTag(e,"bonus-action")}static isAllySpell(e){return V.hasSpellTag(e,"ally")}static isSelfSpell(e){return V.hasSpellTag(e,"self")}static getStatusEffectTags(){return di}}class gs{constructor(e){this.config=e}getStyleForCombatant(e){return this.config.overrides?.has(e.id)?this.config.overrides.get(e.id):e.id.startsWith("player")?this.config.playerStyle:this.config.enemyStyle}getSide(e){return e.id.startsWith("player")?"player":"enemy"}getEnemies(e,t){const i=this.getSide(e)==="player"?"enemy":"player";return t.combatants.filter(n=>n.id.startsWith(i)&&!n.isDefeated)}getAllies(e,t){const i=this.getSide(e)==="player"?"player":"enemy";return t.combatants.filter(n=>n.id.startsWith(i)&&!n.isDefeated)}assessThreat(e,t){const a=e.character.hp.max,i=a>0?e.currentHP/a:0,n=this.getAllies(e,t),r=this.getEnemies(e,t),o=n.reduce((v,w)=>{if(w.id===e.id)return v;const E=w.character.hp.max,I=E>0?w.currentHP/E:0;return Math.min(v,I)},1),l=r.reduce((v,w)=>Math.min(v,w.currentHP),r.length>0?1/0:0),c=r.reduce((v,w)=>{const E=w.character.equipment?.weapons.filter(D=>D.equipped)||[];let I=5;for(const D of E){const x=Ze[D.name],N=D.damage_dice||(typeof D.damage!="string"?D.damage?.dice:void 0)||x?.damage?.dice||"1d6",$=this.averageDamageFromFormula(N);$>I&&(I=$)}return Math.max(v,I)},0),d=(e.character.equipment?.items||[]).some(v=>v.quantity>0&&!v.equipped&&v.tags?.some(w=>w==="healing"||w==="consumable")),h=e.spellSlots,p=h!==void 0&&Object.values(h).some(v=>v>0),g=(e.legendaryActionsRemaining??0)>0,m=(e.legendaryResistancesRemaining??0)>0,f=g||m;return{myHPPercent:i,myAC:e.character.armor_class,lowestAllyHPPercent:o,lowestEnemyHP:l,highestEnemyDamage:c,partySize:n.length,enemyCount:r.length,roundNumber:t.roundNumber,isLowHP:i<.25,isCriticalHP:i<.1,hasHealingItems:d,hasSpellSlots:p,hasRemainingLimitedAbilities:f}}decide(e,t){const a=this.getStyleForCombatant(e),i=this.assessThreat(e,t);if(this.getEnemies(e,t).length===0)return{action:"skip",reasoning:"No valid targets remaining"};const r=this.selectSpellAction(e,t,i,a);if(r)return r;const o=this.selectItemAction(e,i,a);if(o)return o;const l=this.selectDefensiveAction(e,t,i,a);return l||this.selectAttackAction(e,t,i,a)}selectSpellAction(e,t,a,i){const n=this.getAvailableSpells(e);if(n.length===0)return null;const r=n.map(d=>this.evaluateSpell(d,e,t,a)),o=r.filter(d=>d.expectedDamage>0&&!d.isHealing),l=r.filter(d=>d.isHealing),c=r.filter(d=>d.isControl&&!d.isHealing),u=r.filter(d=>d.isBuff&&!d.isHealing);if(l.length>0&&this.shouldHeal(e,a,i)){const d=this.pickHealingSpell(l,e,t,a,i);if(d){const h=this.pickHealingTarget(e,t,i);return{action:"castSpell",spellName:d.spell.name,target:h?.id,targetIds:d.isMultiTarget?this.getHealingTargets(e,t,i).map(p=>p.id):void 0,reasoning:`Cast ${d.spell.name} to heal ${h?.character.name??"self"} (${i} style)`}}}if(o.length>0&&this.shouldCastDamageSpell(o,a,i)){const d=this.pickDamageSpell(o,a,i),h=this.pickSpellTargets(d,e,t,i);if(h.length>0)return{action:"castSpell",spellName:d.spell.name,target:h.length===1?h[0].id:void 0,targetIds:h.length>1?h.map(p=>p.id):void 0,reasoning:`Cast ${d.spell.name} on ${h.map(p=>p.character.name).join(", ")} (${i} style, ~${d.expectedDamage.toFixed(1)} expected damage)`}}if(c.length>0&&i==="normal"&&this.shouldCastControlSpell(a)){const d=c[0],h=this.pickSpellTargets(d,e,t,i);if(h.length>0)return{action:"castSpell",spellName:d.spell.name,target:h[0].id,reasoning:`Cast ${d.spell.name} for crowd control (${i} style)`}}if(u.length>0&&i==="normal"&&!a.isLowHP&&!a.isCriticalHP){const d=u[0],h=this.pickBuffTarget(e,t);return{action:"castSpell",spellName:d.spell.name,target:h?.id,reasoning:`Cast ${d.spell.name} on ${h?.character.name??"self"} (buff, normal style)`}}return null}getAvailableSpells(e){const t=e.character.combat_spells;return!t||t.length===0?[]:t.filter(a=>{if(!a.level||a.level===0)return!0;const i=e.spellSlots;return i?this.hasSlotForLevel(i,a.level):!1})}hasSlotForLevel(e,t){if((e[t]??0)>0)return!0;for(let a=t+1;a<=9;a++)if((e[a]??0)>0)return!0;return!1}evaluateSpell(e,t,a,i){const n=!e.level||e.level===0,r=n?0:this.getBestAvailableSlot(t.spellSlots,e.level??0),o=e.damage_dice||e.damage;let l=o?this.averageDamageFromFormula(o):0;const c=V.isAOESpell(e),u=V.isMultiTargetSpell(e),d=V.isAllySpell(e)||V.isSelfSpell(e)||e.tags?.includes("healing"),h=V.hasSpellTag(e,"buff"),p=V.hasSpellTag(e,"control")||V.hasSpellTag(e,"debuff");if((c||u)&&l>0){const g=i.enemyCount;g>1&&(l*=Math.min(g,4))}return!n&&l>0&&(l*=1.5),{spell:e,expectedDamage:l,isHealing:!!d,isBuff:!!h,isControl:!!p,isAOE:c,isMultiTarget:u,isAllySpell:V.isAllySpell(e),isSelfSpell:V.isSelfSpell(e),isBonusAction:V.isBonusActionSpell(e),requiresConcentration:V.requiresConcentration(e),slotLevel:r,isCantrip:n}}getBestAvailableSlot(e,t){if(!e)return 0;if((e[t]??0)>0)return t;for(let a=t+1;a<=9;a++)if((e[a]??0)>0)return a;return 0}shouldHeal(e,t,a){return a==="aggressive"?t.myHPPercent<.75:t.isLowHP||t.isCriticalHP}pickHealingSpell(e,t,a,i,n){if(e.length===0)return null;const r=[...e].sort((o,l)=>l.expectedDamage-o.expectedDamage);if(n==="aggressive")return r[0];if(!i.isCriticalHP){const o=r.find(l=>l.isCantrip);if(o)return o}return r[0]}pickHealingTarget(e,t,a){const i=this.getAllies(e,t);if(i.length===0)return null;const n=[...i].sort((r,o)=>{const l=r.currentHP/r.character.hp.max,c=o.currentHP/o.character.hp.max;return l-c});return a==="normal"&&n[0].id!==e.id?n[0].currentHP/n[0].character.hp.max<.5?n[0]:e:n[0]}getHealingTargets(e,t,a){const i=this.getAllies(e,t),n=a==="aggressive"?1:.75;return i.filter(r=>r.currentHP/r.character.hp.max<n)}shouldCastDamageSpell(e,t,a){if(e.length===0)return!1;if(a==="aggressive")return!0;const i=e[0];return!!(e.some(r=>r.isAOE||r.isMultiTarget)&&t.enemyCount>=3||i.isCantrip||i.expectedDamage>15&&!i.isCantrip)}pickDamageSpell(e,t,a){const i=[...e].sort((o,l)=>l.expectedDamage-o.expectedDamage);if(a==="aggressive")return i[0];const n=i.filter(o=>o.isCantrip),r=i.filter(o=>!o.isCantrip);if(n.length>0){const o=n[0];return r.length>0&&r[0].expectedDamage>o.expectedDamage*1.5&&(t.roundNumber>1||this.totalSlotsRemaining(t)>3)?r[0]:o}return i[0]}totalSlotsRemaining(e){return e.hasSpellSlots?5:0}pickSpellTargets(e,t,a,i){const n=this.getEnemies(t,a),r=this.getAllies(t,a);return e.isAllySpell||e.isHealing?r.length>0?[this.pickHealingTarget(t,a,i)??r[0]]:[]:e.isSelfSpell?[t]:e.isMultiTarget||e.isAOE?n:[this.selectTarget(n,i)]}shouldCastControlSpell(e){return e.enemyCount>=2}pickBuffTarget(e,t){return this.getAllies(e,t).reduce((i,n)=>{const r=Math.max(i.character.ability_scores.STR,i.character.ability_scores.DEX);return Math.max(n.character.ability_scores.STR,n.character.ability_scores.DEX)>r?n:i})}selectItemAction(e,t,a){if(!t.hasHealingItems)return null;const n=(e.character.equipment?.items||[]).filter(r=>r.quantity>0&&!r.equipped);if(n.length===0)return null;if(a==="aggressive"){if(t.myHPPercent<.75&&!t.hasSpellSlots)return{action:"useItem",itemName:n[0].name,target:e.id,reasoning:`Use ${n[0].name} to heal (aggressive: maintain HP for damage output)`}}else if(t.isLowHP&&!t.hasSpellSlots)return{action:"useItem",itemName:n[0].name,target:e.id,reasoning:`Use ${n[0].name} to heal (no spell slots available)`};return null}selectDefensiveAction(e,t,a,i){return i==="aggressive"?null:a.isLowHP&&a.partySize<=1&&a.enemyCount>1?{action:"dodge",reasoning:"Dodge — isolated and low HP, improving survivability"}:null}selectAttackAction(e,t,a,i){const n=this.getEnemies(e,t),r=this.selectTarget(n,i),o=this.selectWeapon(e,i);return{action:"attack",target:r.id,weaponName:o.name,reasoning:`Attack ${r.character.name} with ${o.name} (${i} style, expected ~${o.expectedDamage.toFixed(1)} damage)`}}selectTarget(e,t){if(e.length===0)throw new Error("No valid targets to select from");return e.length===1?e[0]:t==="aggressive"?e.reduce((a,i)=>i.currentHP<a.currentHP?i:a):e.reduce((a,i)=>i.character.armor_class<a.character.armor_class?i:a)}selectWeapon(e,t){const a=this.evaluateWeapons(e);return a.length===0?{name:"Unarmed Strike",expectedDamage:1,attackBonus:0,type:"melee",properties:[]}:a.length===1?a[0]:t==="aggressive"?a.reduce((i,n)=>n.expectedDamage>i.expectedDamage?n:i):a.reduce((i,n)=>{const r=i.expectedDamage+i.attackBonus*.1;return n.expectedDamage+n.attackBonus*.1>r?n:i})}evaluateWeapons(e){const t=[],a=e.character.equipment?.weapons.filter(n=>n.equipped)||[];for(const n of a){const r=Ze[n.name],o=n.damage_dice||(typeof n.damage!="string"?n.damage?.dice:void 0)||r?.damage?.dice||"1d6",l=this.averageDamageFromFormula(o),c=n.weaponProperties?.includes("ranged")||r?.weaponProperties?.includes("ranged")||!1,u=c?"DEX":"STR",h=Math.floor((e.character.ability_scores[u]-10)/2)+e.character.proficiency_bonus;t.push({name:n.name,expectedDamage:l,attackBonus:h,type:c?"ranged":"melee",properties:n.weaponProperties||r?.weaponProperties||[]})}const i=Math.floor((e.character.ability_scores.STR-10)/2);return t.push({name:"Unarmed Strike",expectedDamage:1+Math.max(0,i),attackBonus:i+e.character.proficiency_bonus,type:"melee",properties:[]}),t}selectLegendaryAction(e,t){const a=e.character.legendary_config;if(!a)return null;const i=e.legendaryActionsRemaining??0;if(i<=0)return null;const n=this.getStyleForCombatant(e),r=a.actions.filter(p=>p.cost<=i);if(r.length===0)return null;const o=this.getEnemies(e,t);if(o.length===0)return null;const l=r.filter(p=>p.damage||p.tags?.includes("damage")),c=r.filter(p=>p.tags?.includes("heal")),u=r.filter(p=>p.tags?.includes("control")),d=this.assessThreat(e,t);if(n==="aggressive"&&l.length>0){const g=[...l].sort((f,v)=>v.cost-f.cost)[0],m=this.selectTarget(o,n);return{action:"legendaryAction",legendaryActionId:g.id,target:m.id,reasoning:`Legendary action: ${g.name} (cost: ${g.cost}, aggressive: max damage)`}}if(l.length>0){const g=[...l].sort((f,v)=>f.cost-v.cost)[0],m=this.selectTarget(o,n);return{action:"legendaryAction",legendaryActionId:g.id,target:m.id,reasoning:`Legendary action: ${g.name} (cost: ${g.cost}, normal: conserve points)`}}if(c.length>0&&d.isLowHP)return{action:"legendaryAction",legendaryActionId:c[0].id,reasoning:`Legendary action: ${c[0].name} (healing, low HP)`};if(u.length>0){const p=this.selectTarget(o,n);return{action:"legendaryAction",legendaryActionId:u[0].id,target:p.id,reasoning:`Legendary action: ${u[0].name} (control)`}}const h=this.selectTarget(o,n);return{action:"legendaryAction",legendaryActionId:r[0].id,target:h.id,reasoning:`Legendary action: ${r[0].name} (fallback)`}}averageDamageFromFormula(e){if(!e)return 0;const a=e.replace(/\s/g,"").match(/^(\d+)d(\d+)([+-]\d+)?$/);if(!a)return 0;const i=parseInt(a[1],10),n=parseInt(a[2],10),r=a[3]?parseInt(a[3],10):0,o=(n+1)/2;return i*o+r}isSupportArchetype(e){const t=e.character.combat_spells;if(!t||t.length===0)return!1;const a=t.some(n=>V.isAllySpell(n)||n.tags?.includes("healing")),i=t.some(n=>V.hasSpellTag(n,"buff"));return a||i}}class fs{constructor(e){this.diceRoller=e}rollInitiativeForCombatant(e){const t=e.character.ability_modifiers.dexterity??0,a=this.diceRoller?this.diceRoller.rollD20():U.rollD20(),i=a+t;return e.initiative=i,{combatant:e,d20Roll:a,dexModifier:t,initiativeTotal:i}}rollInitiativeForAll(e){const t=[];for(const i of e){const n=this.rollInitiativeForCombatant(i);t.push(n)}const a=[...e].sort((i,n)=>{if(i.initiative!==n.initiative)return n.initiative-i.initiative;const r=i.character.ability_modifiers.dexterity??0;return(n.character.ability_modifiers.dexterity??0)-r});return{results:t,sortedCombatants:a}}getNextCombatant(e,t){const a=(t+1)%e.length,i=a===0;return{combatant:e[a],index:a,isNewRound:i}}getInitiativeOrder(e){return e.map((t,a)=>{const i=t.character.ability_modifiers.dexterity??0;return`${a+1}. ${t.character.name} (Initiative: ${t.initiative}, DEX: ${i})`})}rerollInitiativeForCombatant(e){const t=e.character.ability_modifiers.dexterity??0,a=this.diceRoller?this.diceRoller.rollD20():U.rollD20();return e.initiative=a+t,e.initiative}delayTurn(e,t){const a=e.findIndex(r=>r.id===t);if(a===-1)return e;const i=[...e],[n]=i.splice(a,1);return i.splice(a+1,0,n),i}resortByInitiative(e){return[...e].sort((t,a)=>{if(t.initiative!==a.initiative)return a.initiative-t.initiative;const i=t.character.ability_modifiers.dexterity??0;return(a.character.ability_modifiers.dexterity??0)-i})}}class ue{constructor(e,t="scaled"){this.diceRoller=e,this.hitMode=t}static computeScaledDamage(e,t,a,i,n){const r=Math.max(1,Math.floor(e*2+(t-a)*.3)),o=ue.getWeaponBonus(i);return n?Math.max(1,Math.floor(r*1.5))+o:Math.max(1,r+o)}static computeDamageScale(e,t){if(e>=t)return 1;const a=t-e;return Math.max(.1,1-a*.1)}static computeAttackBonus(e,t,a){const i=t.includes("ranged"),n=t.includes("finesse");return Math.floor(((e[i||n?"DEX":"STR"]??10)-10)/2)+a}resolveAttack(e,t,a){const i=this.rollAttack(e,t,a);let n,r,o="";if(i.isMiss)o=`${e.character.name} uses ${a.name} against ${t.character.name} - CRITICAL MISS (natural 1)!`;else if(!i.hit)o=`${e.character.name} uses ${a.name} against ${t.character.name} - Miss (rolled ${i.d20Roll}, needed ${i.targetAC})`;else{if(n=this.rollDamage(e,t,a,i.isCritical),i.damageScale!==void 0&&i.damageScale<1){const l=Math.max(1,Math.floor(n.total*i.damageScale));n={...n,total:l}}r=t.currentHP-n.total,r<0?(r=0,t.currentHP=0,t.isDefeated=!0):t.currentHP=r,i.isCritical?o=`${e.character.name} uses ${a.name} against ${t.character.name} - CRITICAL HIT! (natural 20, doubled damage: ${n.total})`:o=`${e.character.name} uses ${a.name} against ${t.character.name} - Hit! (rolled ${i.d20Roll}, damage: ${n.total})`}return{attacker:e,target:t,attack:a,attackRoll:i,damageRoll:n,hpAfterDamage:r,description:o}}rollAttack(e,t,a){const i=this.diceRoller?this.diceRoller.rollD20():U.rollD20(),n=a.attack_bonus??0,r=i+n,o=t.character.armor_class,l=this.diceRoller?this.diceRoller.isCriticalHit(i):U.isCriticalHit(i),c=this.diceRoller?this.diceRoller.isCriticalMiss(i):U.isCriticalMiss(i);let u,d;return this.hitMode==="scaled"?(u=!c,!c&&!l?d=ue.computeDamageScale(r,o):d=1):u=!c&&(l||r>=o),{d20Roll:i,attackBonus:n,totalRoll:r,targetAC:o,hit:u,isCritical:l,isMiss:c,damageScale:d}}rollDamage(e,t,a,i){return this.hitMode==="dnd"?this.rollDamageDND(e,t,a,i):this.rollDamageScaled(e,t,a,i)}getDamageModifier(e,t){const a=e.character.ability_modifiers,i=a.STR??0,n=a.DEX??0;return t.type==="spell"?0:t.type==="ranged"?n:t.properties?.includes("finesse")?Math.max(i,n):i}rollDamageDND(e,t,a,i){const n=e.character.ability_scores.STR??10,r=e.character.ability_scores.DEX??10,o=a.properties?.includes("finesse")??!1,l=a.properties?.includes("ranged")??!1,c=Math.floor((n-10)/2),u=Math.floor((r-10)/2),d=o?Math.max(c,u):l?u:c,h=a.damage_dice??"",p=this.diceRoller?this.diceRoller.calculateDamage(h,d,i):U.calculateDamage(h,d,i),g=Math.max(1,p.total);return{diceFormula:h,rolls:p.rolls,modifier:d,total:g,isCritical:p.isCritical,baseDamage:0,weaponRoll:p.total-d}}rollDamageScaled(e,t,a,i){const n=e.character.level??1,r=e.character.ability_scores.STR??10,o=t.character.armor_class,l=Math.max(1,Math.floor(n*2+(r-o)*.3)),c=ue.getWeaponBonus(a.damage_dice),u=ue.computeScaledDamage(n,r,o,a.damage_dice??"",i);return{diceFormula:a.damage_dice??"",rolls:[],modifier:0,total:u,isCritical:i,baseDamage:l,weaponRoll:c}}static getWeaponBonus(e){if(!e)return 0;try{const t=U.parseDiceFormula(e),a=t.diceCount*(t.diceSides+1)/2;return Math.round(a/2.5)}catch{return 0}}static formatWeaponDamage(e,t,a){if(a==="scaled"){const i=ue.getWeaponBonus(e);return i>0?`+${i} ${t}`:t}return`${e} ${t}`}static estimateDamagePerHit(e){if(e.hitMode==="scaled")return ue.computeScaledDamage(e.level,e.abilityScore,e.targetAC,e.damageDice,!1);const t=Math.floor((e.abilityScore-10)/2);try{const a=U.parseDiceFormula(e.damageDice);return a.diceCount*(a.diceSides+1)/2+a.modifier+t}catch{return t+1}}static estimateDPR(e){const t=e.attackCount??1;if(e.hitMode==="scaled"){const l=ue.computeScaledDamage(e.level,e.abilityScore,e.targetAC,e.damageDice,!1),c=ue.computeScaledDamage(e.level,e.abilityScore,e.targetAC,e.damageDice,!0),u=e.proficiencyBonus??Math.ceil(1+((e.level||1)-1)/4),d=e.attackBonus??Math.floor((e.abilityScore-10)/2)+u;let h=0;for(let p=1;p<=20;p++){if(p===1)continue;if(p===20){h+=c;continue}const g=p+d,m=ue.computeDamageScale(g,e.targetAC);h+=Math.max(1,Math.floor(l*m))}return h/20*t}const a=ue.estimateDamagePerHit(e),i=e.proficiencyBonus??Math.ceil(1+((e.level||1)-1)/4),n=Math.floor((e.abilityScore-10)/2),r=e.attackBonus??n+i,o=Math.max(.05,Math.min(.95,(21-(e.targetAC-r))/20));return a*o*t}isInRange(e,t,a){if(!e.position||!t.position)return!0;const i=Math.sqrt(Math.pow(e.position.x-t.position.x,2)+Math.pow(e.position.y-t.position.y,2));return a.range?i<=a.range:i<=5}calculateAttackBonus(e,t,a,i=!1){let n=a;return i&&(n+=e.proficiency_bonus),n}attackWithAdvantage(e,t,a){const i=this.diceRoller?this.diceRoller.rollWithAdvantage():U.rollWithAdvantage(),n=i.roll1,r=i.roll2,o=i.result,l=a.attack_bonus??0,c=o+l,u=t.character.armor_class,d=(this.diceRoller?this.diceRoller.isCriticalHit(n):U.isCriticalHit(n))||(this.diceRoller?this.diceRoller.isCriticalHit(r):U.isCriticalHit(r)),h=this.diceRoller?this.diceRoller.isCriticalMiss(o):U.isCriticalMiss(o);let p,g;if(this.hitMode==="scaled")if(p=!h,!h&&!d&&c<u){const f=u-c;g=Math.max(.1,1-f*.1)}else g=1;else p=!h&&(d||c>=u);const m=`${e.character.name} attacks with advantage (rolled ${n} and ${r}, using ${o})`;if(p&&!h){let f=this.rollDamage(e,t,a,d);if(g!==void 0&&g<1){const w=Math.max(1,Math.floor(f.total*g));f={...f,total:w}}const v=t.currentHP-f.total;return v<0?(t.currentHP=0,t.isDefeated=!0):t.currentHP=v,{attacker:e,target:t,attack:a,attackRoll:{d20Roll:o,attackBonus:l,totalRoll:c,targetAC:u,hit:p,isCritical:d,isMiss:h,damageScale:g},damageRoll:f,hpAfterDamage:t.currentHP,description:m}}return{attacker:e,target:t,attack:a,attackRoll:{d20Roll:o,attackBonus:l,totalRoll:c,targetAC:u,hit:p,isCritical:d,isMiss:h,damageScale:g},description:m}}attackWithDisadvantage(e,t,a){const i=this.diceRoller?this.diceRoller.rollWithDisadvantage():U.rollWithDisadvantage(),n=i.roll1,r=i.roll2,o=i.result,l=a.attack_bonus??0,c=o+l,u=t.character.armor_class,d=this.diceRoller?this.diceRoller.isCriticalHit(o):U.isCriticalHit(o),h=(this.diceRoller?this.diceRoller.isCriticalMiss(n):U.isCriticalMiss(n))||(this.diceRoller?this.diceRoller.isCriticalMiss(r):U.isCriticalMiss(r));let p,g;if(this.hitMode==="scaled")if(p=!h,!h&&!d&&c<u){const f=u-c;g=Math.max(.1,1-f*.1)}else g=1;else p=!h&&(d||c>=u);const m=`${e.character.name} attacks with disadvantage (rolled ${n} and ${r}, using ${o})`;if(p&&!h){let f=this.rollDamage(e,t,a,d);if(g!==void 0&&g<1){const w=Math.max(1,Math.floor(f.total*g));f={...f,total:w}}const v=t.currentHP-f.total;return v<0?(t.currentHP=0,t.isDefeated=!0):t.currentHP=v,{attacker:e,target:t,attack:a,attackRoll:{d20Roll:o,attackBonus:l,totalRoll:c,targetAC:u,hit:p,isCritical:d,isMiss:h,damageScale:g},damageRoll:f,hpAfterDamage:t.currentHP,description:m}}return{attacker:e,target:t,attack:a,attackRoll:{d20Roll:o,attackBonus:l,totalRoll:c,targetAC:u,hit:p,isCritical:d,isMiss:h,damageScale:g},description:m}}static simulateAttacks(e,t,a,i=1e3,n,r){const o=new ue(n,r),l=new Map;let c=0,u=0,d=0;for(let g=0;g<i;g++){const m={id:`sim-atk-${g}`,character:e,initiative:0,currentHP:e.hp.max,statusEffects:[],isDefeated:!1,actionUsed:!1,bonusActionUsed:!1,reactionUsed:!1},f={id:`sim-tgt-${g}`,character:t,initiative:0,currentHP:t.hp.max,statusEffects:[],isDefeated:!1,actionUsed:!1,bonusActionUsed:!1,reactionUsed:!1},v=o.resolveAttack(m,f,a),w=v.damageRoll?.total??0;l.set(w,(l.get(w)??0)+1),v.attackRoll.isMiss||!v.attackRoll.hit?d++:(c++,v.attackRoll.isCritical&&u++)}const h=Array.from(l.entries()).map(([g,m])=>({damage:g,count:m,percentage:m/i*100})).sort((g,m)=>g.damage-m.damage),p=h.reduce((g,m)=>g+m.damage*m.count,0)/i;return{iterations:i,totalHits:c,totalMisses:d,totalCrits:u,hitRate:c/i*100,critRate:u/i*100,missRate:d/i*100,averageDamage:p,maxDamage:Math.max(...l.keys()),distribution:h}}}class Se{constructor(e){this.seed=e,this.counter=0}random(){const e=y.deriveSeed(this.seed,this.counter.toString());return this.counter++,y.hashSeedToFloat(e)}randomInt(e,t){return Math.floor(this.random()*(t-e))+e}randomChoice(e){const t=this.randomInt(0,e.length);return e[t]}weightedChoice(e){const t=e.reduce((i,[,n])=>i+n,0);let a=this.random()*t;for(const[i,n]of e)if(a-=n,a<=0)return i;return e[e.length-1][0]}shuffle(e){const t=[...e];for(let a=t.length-1;a>0;a--){const i=this.randomInt(0,a+1);[t[a],t[i]]=[t[i],t[a]]}return t}reset(){this.counter=0}}const ys={1:{easy:25,medium:50,hard:75,deadly:100},2:{easy:50,medium:100,hard:150,deadly:200},3:{easy:75,medium:150,hard:225,deadly:300},4:{easy:125,medium:250,hard:375,deadly:500},5:{easy:250,medium:500,hard:750,deadly:1e3},6:{easy:300,medium:600,hard:900,deadly:1200},7:{easy:350,medium:750,hard:1100,deadly:1700},8:{easy:400,medium:800,hard:1200,deadly:1900},9:{easy:500,medium:1e3,hard:1500,deadly:2300},10:{easy:600,medium:1200,hard:1800,deadly:2400},11:{easy:800,medium:1600,hard:2400,deadly:3600},12:{easy:1e3,medium:2e3,hard:3e3,deadly:4500},13:{easy:1200,medium:2400,hard:3600,deadly:5400},14:{easy:1400,medium:2800,hard:4200,deadly:5700},15:{easy:1600,medium:3200,hard:4800,deadly:6400},16:{easy:2e3,medium:4e3,hard:6e3,deadly:8e3},17:{easy:2400,medium:4800,hard:7200,deadly:9600},18:{easy:3e3,medium:6e3,hard:9e3,deadly:12e3},19:{easy:4e3,medium:8e3,hard:12e3,deadly:16e3},20:{easy:5e3,medium:1e4,hard:15e3,deadly:2e4}},Mt={0:10,.125:25,.25:50,.5:100,1:200,2:450,3:700,4:1100,5:1800,6:2300,7:2900,8:3900,9:5e3,10:5900,11:7200,12:8400,13:1e4,14:11500,15:13e3,16:15e3,17:18e3,18:2e4,19:22e3,20:25e3,21:33e3,22:41e3,23:5e4,24:62e3,26:9e4,27:105e3,28:12e4,29:135e3,30:155e3},na={1:1,2:1.5,3:2,4:2,5:2,6:2,7:1.5,8:1.5,9:1.5,10:1.5,11:1,12:1,13:1,14:1,15:1},sr={DEFAULT:1,EASIER:.9,MUCH_EASIER:.8,HARDER:1.1,MUCH_HARDER:1.2,BRUTAL:1.5};function vs(s,e){const t=ys[s];return t&&t[e]||0}function Wt(s,e){return s.reduce((t,a)=>t+vs(a,e),0)}function ka(s){return Mt[s]||0}function Ta(s){if(s<=0)return 0;const e=Object.keys(Mt).map(Number).sort((n,r)=>n-r);let t=0,a=e.length-1,i=0;for(;t<=a;){const n=Math.floor((t+a)/2),r=e[n];Mt[r]<=s?(i=r,t=n+1):a=n-1}return i}function nr(s,e=1){return Math.round(s*e)}function ot(s){return s<=0?1:s>=15?na[15]:na[s]||1}function rr(s,e){const t=s.reduce((i,n)=>i+ka(n),0),a=e!==void 0?e:ot(s.length);return Math.round(t*a)}function bs(s){if(s.length===0)return 1;const e=s.reduce((t,a)=>t+a,0);return Math.floor(e/s.length)}function or(s){return typeof s=="string"&&["easy","medium","hard","deadly"].includes(s)}class ws{constructor(e={},t){this.diceRoller=t,this.initiativeRoller=new fs(t),this.attackResolver=new ue(t,e.hitMode),this.spellCaster=new V(t),this.config={useEnvironment:!0,useMusic:!1,tacticalMode:!1,maxTurnsBeforeDraw:100,allowFleeing:!1,seed:e.seed||`combat_${Date.now()}`,...e},this.rng=new Se(this.config.seed)}startCombat(e,t,a){const i=[];let n=0;for(const l of e)i.push(this.createCombatant(l,`player_${n++}`));for(const l of t)i.push(this.createCombatant(l,`enemy_${n++}`));const r=this.initiativeRoller.rollInitiativeForAll(i);return{id:`combat_${Date.now()}`,combatants:r.sortedCombatants,currentTurnIndex:0,roundNumber:1,environment:a,history:[],isActive:!0,startTime:Date.now(),lastUpdated:Date.now()}}getCurrentCombatant(e){return e.combatants[e.currentTurnIndex]}executeAttack(e,t,a,i){const n=this.getAttackAdvantageDisadvantage(t,a,i);let r;n==="advantage"?r=this.attackResolver.attackWithAdvantage(t,a,i):n==="disadvantage"?r=this.attackResolver.attackWithDisadvantage(t,a,i):r=this.attackResolver.resolveAttack(t,a,i);const o={type:"attack",actor:t,target:a,attack:i,result:{success:r.attackRoll.hit,roll:r.attackRoll.d20Roll,isCritical:r.attackRoll.isCritical,damage:r.damageRoll?.total,damageType:i.damage_type,targetHP:r.hpAfterDamage,description:r.description}};return e.history.push(o),a.currentHP<=0?(a.isDefeated=!0,a.concentratingOn&&(a.concentratingOn=void 0)):r.attackRoll.hit&&r.damageRoll?.total&&a.concentratingOn&&this.checkConcentration(e,a,r.damageRoll.total),o}executeWeaponAttack(e,t,a,i){const n=t.character.equipment?.weapons.filter(l=>l.equipped)||[];if(i==="unarmed"||n.length===0){const l=this.buildUnarmedAttack(t.character);return this.executeAttack(e,t,a,l)}const r=i?n.find(l=>l.name===i):n[0];if(!r)throw new Error(`Weapon "${i}" is not equipped`);const o=this.buildAttackFromWeapon(r,t.character);return this.executeAttack(e,t,a,o)}buildAttackFromWeapon(e,t){const a=Ze[e.name];if(!a)throw new Error(`Weapon "${e.name}" not found in equipment database`);const i=a.weaponProperties||[],n=i.includes("ranged"),r=t.proficiency_bonus,o=ue.computeAttackBonus(t.ability_scores,i,r);return{name:e.name,damage_dice:e.damage_dice||a.damage?.dice||"1d6",damage_type:a.damage?.damageType||"bludgeoning",type:n?"ranged":"melee",attack_bonus:o,properties:i}}buildUnarmedAttack(e){const t=Math.floor((e.ability_scores.STR-10)/2),a=e.proficiency_bonus;return{name:"Unarmed Strike",damage_dice:"1d1",damage_type:"bludgeoning",type:"melee",attack_bonus:t+a,properties:[]}}executeCastSpell(e,t,a,i){const n=this.spellCaster.castSpell(t,a,i),r={type:"spell",actor:t,targets:i,spell:a,result:{success:n.success,damage:n.damage?.total,damageType:a.damage_type??a.damageType,description:n.description}};if(e.history.push(r),n.success)for(const o of i)for(const l of n.effectsApplied)this.applyStatusEffect(o,l);for(const o of i)o.currentHP<=0?(o.isDefeated=!0,o.concentratingOn&&(o.concentratingOn=void 0)):n.success&&n.damage?.total&&o.concentratingOn&&this.checkConcentration(e,o,n.damage.total);return r}executeLegendaryAction(e,t,a,i){const n=t.character.legendary_config;if(!n)throw new Error(`${t.character.name} has no legendary actions (not a boss)`);const r=n.actions.find(p=>p.id===a.id);if(!r)throw new Error(`Legendary action "${a.id}" not found on ${t.character.name}`);const o=r.cost,l=t.legendaryActionsRemaining??0;if(l<o)throw new Error(`${t.character.name} needs ${o} legendary action points but only has ${l} remaining`);t.legendaryActionsRemaining=l-o;let c,u;if(r.damage&&i){const p=r.damage.replace(/\s/g,""),g=this.diceRoller;g?c=g.parseDiceFormula(p).total:c=U.parseDiceFormula(p).total;const m=this.applyDamage(i,c);u=i.currentHP,i.currentHP<=0?(i.isDefeated=!0,i.concentratingOn&&(i.concentratingOn=void 0)):i.concentratingOn&&m>0&&this.checkConcentration(e,i,m)}const d=i?`${t.character.name} uses ${r.name} on ${i.character.name}${c?` for ${c} ${r.damage_type??""} damage`:""} (${o} action point${o!==1?"s":""} spent, ${t.legendaryActionsRemaining} remaining)`:`${t.character.name} uses ${r.name} (${o} action point${o!==1?"s":""} spent, ${t.legendaryActionsRemaining} remaining)`,h={type:"legendaryAction",actor:t,target:i,legendaryAction:{id:r.id,name:r.name,description:r.effect,cost:r.cost,effect:r.effect,damage:r.damage,damageType:r.damage_type,archetypes:[]},result:{success:!0,damage:c,damageType:r.damage_type,targetHP:u,description:d}};return e.history.push(h),i?.isDefeated&&this.checkCombatStatus(e),h}useLegendaryResistance(e,t){const a=t.legendaryResistancesRemaining??0;return a<=0?!1:(t.legendaryResistancesRemaining=a-1,e.history.push({type:"statusEffectTick",actor:t,result:{success:!0,description:`${t.character.name} used legendary resistance (${t.legendaryResistancesRemaining} remaining today)`}}),!0)}executeDodge(e,t){const a={type:"dodge",actor:t,result:{success:!0,description:`${t.character.name} takes the Dodge action (AC increased until next turn)`}};return e.history.push(a),a}executeDash(e,t){const a={type:"dash",actor:t,result:{success:!0,description:`${t.character.name} takes the Dash action (double movement)`}};return e.history.push(a),a}executeDisengage(e,t){const a={type:"disengage",actor:t,result:{success:!0,description:`${t.character.name} takes the Disengage action (no opportunity attacks provoked)`}};return e.history.push(a),a}canFlee(){return this.config.allowFleeing===!0}executeFlee(e,t){if(!this.canFlee())throw new Error("Fleeing is not allowed in this combat. Enable with allowFleeing: true in CombatConfig.");const a={type:"flee",actor:t,result:{success:!0,description:`${t.character.name} flees from combat!`}};e.history.push(a);const i=e.combatants.findIndex(n=>n.id===t.id);return i!==-1&&(e.combatants.splice(i,1),i<e.currentTurnIndex?e.currentTurnIndex--:i===e.currentTurnIndex&&(e.currentTurnIndex=Math.min(e.currentTurnIndex,e.combatants.length-1))),t.isDefeated=!0,e.lastUpdated=Date.now(),this.checkCombatStatus(e),a}nextTurn(e){const t=e.combatants[e.currentTurnIndex];t.actionUsed=!1,t.bonusActionUsed=!1,t.reactionUsed=!1;const a=(this.config.maxTurnsBeforeDraw||100)+e.combatants.length;for(let i=0;i<a;i++){const n=(e.currentTurnIndex+1)%e.combatants.length;if(n===0){e.roundNumber++;for(const l of e.combatants)l.character.legendary_config&&!l.isDefeated&&(l.legendaryActionsRemaining=3)}e.currentTurnIndex=n,e.lastUpdated=Date.now();const o=e.combatants[n];if(!o.isDefeated){if(this.processStartOfTurnDamage(e,o),o.isDefeated){if(this.checkCombatStatus(e),!e.isActive)return e;continue}if(this.shouldSkipTurn(o)){if(o.actionUsed=!0,o.bonusActionUsed=!0,o.reactionUsed=!0,o.concentratingOn){const c=this.dropConcentration(o,"Incapacitated");c&&e.history.push({type:"statusEffectTick",actor:o,result:{success:!0,description:`${o.character.name} lost concentration on ${c.name} (incapacitated)`}})}const l=o.statusEffects.filter(c=>c.mechanicalEffects?.skipTurn).map(c=>c.name);if(e.history.push({type:"statusEffectTick",actor:o,result:{success:!0,description:`${o.character.name}'s turn is skipped (${l.join(", ")})`}}),this.tickStatusEffects(e,o),this.checkCombatStatus(e),!e.isActive)return e;continue}this.tickStatusEffects(e,o);break}}return this.checkCombatStatus(e),e}processStartOfTurnDamage(e,t){for(const a of t.statusEffects)if(a.damage&&a.damage>0){const i=this.applyDamage(t,a.damage);e.history.push({type:"statusEffectTick",actor:t,result:{success:!0,damage:i,damageType:a.damageType,description:`${t.character.name} takes ${i} ${a.damageType??""} damage from ${a.name}`}}),t.concentratingOn&&(t.isDefeated?t.concentratingOn=void 0:this.checkConcentration(e,t,i))}}tickStatusEffects(e,t){if(t.statusEffects.length===0)return;for(const i of t.statusEffects)i.duration--;const a=this.removeExpiredStatusEffects(t);if(a.length>0){const i=a.map(n=>n.name).join(", ");e.history.push({type:"statusEffectTick",actor:t,result:{success:!0,description:`${t.character.name}: ${i} expired`}})}}getAttackAdvantageDisadvantage(e,t,a){let i=!1,n=!1;const r=a.type??"melee";for(const o of e.statusEffects)o.mechanicalEffects&&(o.mechanicalEffects.disadvantageOnAttack&&(n=!0),o.mechanicalEffects.disadvantageOnAttackNonSource&&o.source!==t.id&&(n=!0));for(const o of t.statusEffects)o.mechanicalEffects&&(o.mechanicalEffects.advantageOnMeleeAttackAgainst&&r==="melee"&&(i=!0),o.mechanicalEffects.advantageOnRangedAttackAgainst&&r==="ranged"&&(i=!0));return i&&n?"normal":i?"advantage":n?"disadvantage":"normal"}shouldSkipTurn(e){return e.statusEffects.some(t=>t.mechanicalEffects?.skipTurn)}checkCombatStatus(e){const t=e.combatants.filter(o=>o.id.startsWith("player")),a=e.combatants.filter(o=>o.id.startsWith("enemy")),i=a.every(o=>o.isDefeated),n=t.every(o=>o.isDefeated),r=e.history.length>=(this.config.maxTurnsBeforeDraw||100);(i||n||r)&&(e.isActive=!1,i&&!n?(e.winner=t.find(o=>!o.isDefeated),e.winnerSide="player"):n&&!i?(e.winner=a.find(o=>!o.isDefeated),e.winnerSide="enemy"):e.winnerSide="draw")}getCombatResult(e){if(e.isActive)return null;const t=e.combatants.filter(c=>c.id.startsWith("enemy")),a=e.combatants.filter(c=>c.isDefeated);let i=0;i=Math.ceil(e.history.length/Math.max(1,e.combatants.length));const n=t.filter(c=>c.isDefeated).reduce((c,u)=>{const d=u.character.cr??u.character.level;return c+ka(d)},0),r=e.winner?`${e.winner.character.name} won the combat!`:"Combat ended in a draw.";let o=0,l=[];if(this.config.treasure){if(typeof this.config.treasure.gold=="number")o=this.config.treasure.gold;else if(typeof this.config.treasure.gold=="object"&&this.config.treasure.gold!==null){const{min:c,max:u}=this.config.treasure.gold;o=c+Math.floor(this.rng.random()*(u-c+1))}l=this.config.treasure.items||[]}else o=Math.floor(this.rng.random()*100);return{winner:e.winner,winnerSide:e.winnerSide??"draw",defeated:a,roundsElapsed:e.roundNumber,totalTurns:i,xpAwarded:n,treasureAwarded:{gold:o,items:l},description:r}}createCombatant(e,t){const a={id:t,character:e,initiative:0,currentHP:e.hp.max,temporaryHP:0,statusEffects:[],isDefeated:!1,actionUsed:!1,bonusActionUsed:!1,reactionUsed:!1,spellSlots:this.initializeSpellSlots(e)};return e.legendary_config&&(a.legendaryActionsRemaining=3,a.legendaryResistancesRemaining=e.legendary_config.resistances_per_day),a}validateSpellSlots(e){const t=[],a=e.spells?.spell_slots;if(!a||Object.keys(a).length===0)return t;for(const[i,n]of Object.entries(a)){const r=Number(i);if(!Number.isInteger(r)||r<1||r>9){t.push({message:`Invalid spell level key "${i}" — must be an integer 1-9`,level:Number.isNaN(r)?void 0:r,severity:"error"});continue}if(typeof n!="object"||n===null){t.push({message:`Spell level ${r} has invalid slot data (expected { total, used }, got ${typeof n})`,level:r,severity:"error"});continue}const{total:o,used:l}=n;if(typeof o!="number"||typeof l!="number"){t.push({message:`Spell level ${r} has non-numeric total (${typeof o}) or used (${typeof l})`,level:r,severity:"error"});continue}o<0&&t.push({message:`Spell level ${r} has negative total (${o})`,level:r,severity:"error"}),l<0&&t.push({message:`Spell level ${r} has negative used (${l})`,level:r,severity:"error"}),o===0&&l!==0&&t.push({message:`Spell level ${r} has total=0 but used=${l} — no slots exist to consume`,level:r,severity:"warn"}),l>o&&t.push({message:`Spell level ${r} has used (${l}) exceeding total (${o})`,level:r,severity:"error"})}return t}validateCombatantSpellSlots(e){const t=[],a=e.character,i=a.spells?.spell_slots,n=e.spellSlots;if(!i||Object.keys(i).length===0){if(n&&Object.keys(n).length>0){const r=sa(a.level),o=Object.keys(r).sort(),l=Object.keys(n).sort();if(o.join(",")!==l.join(","))t.push({message:`Combatant has spell slots ${l.join(", ")} but fallback table for ${a.class} level ${a.level} expects ${o.join(", ")}`,severity:"warn"});else for(const c of o){const u=Number(c);n[u]!==r[u]&&t.push({message:`Spell level ${u}: combatant has ${n[u]} but fallback table expects ${r[u]}`,level:u,severity:"warn"})}}return t}if(!n)return Object.values(i).every(o=>o.total-o.used<=0)||t.push({message:"Character has spell slot data but combatant has no spell slots — expected some remaining",severity:"error"}),t;for(const[r,o]of Object.entries(i)){const l=Number(r),c=Math.max(0,o.total-o.used);if(c===0)n[l]!==void 0&&n[l]>0&&t.push({message:`Spell level ${l}: combatant has ${n[l]} slots but source indicates all used (total=${o.total}, used=${o.used})`,level:l,severity:"error"});else{const u=n[l]??0;u!==c&&t.push({message:`Spell level ${l}: combatant has ${u} but source expects ${c} (total=${o.total}, used=${o.used})`,level:l,severity:"error"})}}for(const r of Object.keys(n)){const o=Number(r);o in i||t.push({message:`Combatant has spell level ${o} with ${n[o]} slots but no source data for that level`,level:o,severity:"warn"})}return t}initializeSpellSlots(e){const t=e.spells?.spell_slots;if(t&&Object.keys(t).length>0){const n=this.validateSpellSlots(e).filter(r=>r.severity==="error");if(n.length>0)console.warn(`[CombatEngine] Spell slot validation failed for ${e.name} (${e.class}): `+n.map(r=>r.message).join("; ")+". Falling back to class table.");else{const r={};for(const[o,l]of Object.entries(t)){const c=l.total-l.used;c>0&&(r[Number(o)]=c)}return Object.keys(r).length>0?r:void 0}}if(["Wizard","Cleric","Sorcerer","Bard","Druid","Warlock","Paladin","Ranger"].includes(e.class))return sa(e.level)}getCombatSummary(e){const t=this.getCurrentCombatant(e);return`Round ${e.roundNumber}, Turn: ${t.character.name} (${t.currentHP}/${t.character.hp.max} HP)`}applyDamage(e,t){const a=Math.max(0,t);if(e.temporaryHP){const i=Math.min(e.temporaryHP,a);e.temporaryHP-=i;const n=a-i;e.currentHP-=n}else e.currentHP-=a;return e.currentHP<=0&&(e.currentHP=0,e.isDefeated=!0),a}healCombatant(e,t){const a=Math.min(t,e.character.hp.max-e.currentHP);return e.currentHP+=a,a}applyTemporaryHP(e,t){e.temporaryHP=Math.max(e.temporaryHP||0,t)}applyStatusEffect(e,t){t.hasConcentration&&e.concentratingOn&&this.dropConcentration(e,"New concentration spell cast");const a=e.statusEffects.findIndex(i=>i.name===t.name);if(a!==-1){const i=e.statusEffects[a];return i.duration=Math.max(i.duration,t.duration),t.damage!==void 0&&(i.damage=Math.max(i.damage??0,t.damage)),t.source!==void 0&&(i.source=t.source),t.mechanicalEffects&&(i.mechanicalEffects={...i.mechanicalEffects,...t.mechanicalEffects}),t.hasConcentration&&(i.hasConcentration=!0),t.damageType&&(i.damageType=t.damageType),t.hasConcentration&&(e.concentratingOn=t.name),i}return e.statusEffects.push(t),t.hasConcentration&&(e.concentratingOn=t.name),t}dropConcentration(e,t="Concentration ended"){if(!e.concentratingOn)return;const a=e.concentratingOn,i=e.statusEffects.findIndex(n=>n.name===a);if(i!==-1){const[n]=e.statusEffects.splice(i,1);return e.concentratingOn=void 0,n}e.concentratingOn=void 0}rollConcentrationSave(e,t){if(!e.concentratingOn)return!0;const a=Math.max(10,Math.floor(t/2)),i=e.character.ability_modifiers.CON??0,r=e.character.saving_throws.CON??!1?e.character.proficiency_bonus:0,o=this.diceRoller?this.diceRoller.rollSavingThrow(i,r):U.rollSavingThrow(i,r),l=o-i-r;return l===1?!1:l===20?!0:o>=a}checkConcentration(e,t,a){if(!t.concentratingOn)return!1;if(!this.rollConcentrationSave(t,a)){const n=this.dropConcentration(t,"Took damage and failed concentration save");return n&&e.history.push({type:"statusEffectTick",actor:t,result:{success:!0,description:`${t.character.name} lost concentration on ${n.name} (took ${a} damage)`}}),!0}return!1}removeExpiredStatusEffects(e){const t=e.statusEffects.filter(a=>a.duration<=0);return e.statusEffects=e.statusEffects.filter(a=>a.duration>0),e.concentratingOn&&(e.statusEffects.some(i=>i.name===e.concentratingOn)||(e.concentratingOn=void 0)),t}getLivingCombatants(e){return e.combatants.filter(t=>!t.isDefeated)}getDefeatedCombatants(e){return e.combatants.filter(t=>t.isDefeated)}}class Ss{computeMetrics(e){const t=new Map;for(const o of e.combatants){const l=o.id.startsWith("player")?"player":"enemy";t.set(o.id,{combatantId:o.id,name:o.character.name,side:l,totalDamageDealt:0,totalDamageTaken:0,totalHealingDone:0,spellsCast:0,itemsUsed:0,criticalHits:0,hits:0,misses:0,kills:0,roundsSurvived:0,survived:!o.isDefeated,actionsByType:{},damagePerRound:[],hpRemainingPercent:0})}const a=new Set(["attack","spell","dodge","dash","disengage","flee","help","hide","ready","useItem","skip"]),i=new Map;for(const o of e.history){const l=o.actor.id,c=t.get(l);switch(c&&(c.actionsByType[o.type]=(c.actionsByType[o.type]||0)+1),a.has(o.type)&&i.set(l,(i.get(l)??0)+1),o.type){case"attack":{const u=o.result?.damage??0,d=o.result?.isCritical===!0,h=o.result?.success===!0;if(c&&(h?c.hits+=1:c.misses+=1),c&&u>0&&(c.totalDamageDealt+=u,c.criticalHits+=d?1:0),o.target){const p=t.get(o.target.id);p&&u>0&&(p.totalDamageTaken+=u)}break}case"spell":{const u=o.result?.damage??0,d=o.result?.isCritical===!0,h=this.isHealingSpell(o);if(c)if(c.spellsCast+=1,h){const p=o.targets??(o.target?[o.target]:[]);for(const g of p){const m=o.result?.damage??0;m>0&&(c.totalHealingDone+=m)}}else u>0&&(c.totalDamageDealt+=u,c.criticalHits+=d?1:0,o.result?.success===!0?c.hits+=1:c.misses+=1);if(!h&&u>0){const p=o.targets??(o.target?[o.target]:[]);for(const g of p){const m=t.get(g.id);m&&(m.totalDamageTaken+=u)}}break}case"legendaryAction":{const u=o.result?.damage??0;if(c&&u>0&&(c.totalDamageDealt+=u),o.target&&u>0){const d=t.get(o.target.id);d&&(d.totalDamageTaken+=u)}break}case"useItem":{c&&(c.itemsUsed+=1);break}}}const n=new Map;for(const o of e.history)if((o.type==="attack"||o.type==="spell"||o.type==="legendaryAction")&&o.target){const l=o.result?.damage??0;if(l>0&&n.set(o.target.id,o.actor.id),o.targets)for(const c of o.targets)l>0&&n.set(c.id,o.actor.id)}for(const o of e.combatants)if(o.isDefeated){const l=n.get(o.id);if(l){const c=t.get(l);c&&(c.kills+=1)}}const r=e.roundNumber;for(const[o,l]of t){l.roundsSurvived=Math.max(1,r);const c=i.get(o)??0;c>0&&l.totalDamageDealt>0&&(l.damagePerRound=[l.totalDamageDealt/c]);const u=e.combatants.find(d=>d.id===o);u&&u.character.hp.max>0&&(l.hpRemainingPercent=l.survived?Math.round(u.currentHP/u.character.hp.max*100):0)}return e.metrics=t,t}isHealingSpell(e){if(!e.spell)return!1;const t=e.spell.tags??[];return t.includes("healing")||t.includes("heal")}}class _s{runFullCombat(e,t,a,i,n){const r=new ws(i??{},n),o=new gs(a),l=r.startCombat(e,t);if(l.combatants.length===0)return{combat:l,result:{winnerSide:"draw",defeated:[],roundsElapsed:0,totalTurns:0,xpAwarded:0,description:"No combatants — combat could not start."},metrics:new Map};for(;l.isActive;){const h=r.getCurrentCombatant(l);if(h.isDefeated){r.nextTurn(l);continue}if(this.hasSkipTurnEffect(h)){l.history.push({type:"statusEffectTick",actor:h,result:{success:!0,description:`${h.character.name}'s turn is skipped (${h.statusEffects.filter(g=>g.mechanicalEffects?.skipTurn).map(g=>g.name).join(", ")})`}}),r.nextTurn(l);continue}const p=o.decide(h,l);if(this.executeDecision(r,l,h,p),!l.isActive||(this.processLegendaryActions(r,o,l,h),!l.isActive))break;r.nextTurn(l)}const c=r.getCombatResult(l);if(!c)throw new Error("AICombatRunner: combat ended but getCombatResult returned null");const d=new Ss().computeMetrics(l);return{combat:l,result:c,metrics:d}}executeDecision(e,t,a,i){switch(i.action){case"attack":{const n=this.findCombatant(t,i.target);n&&this.executeWeaponAttack(e,t,a,n,i.weaponName);break}case"castSpell":{const n=this.findSpell(a,i.spellName);if(n){const r=this.resolveTargets(t,i.target,i.targetIds);r.length>0&&e.executeCastSpell(t,a,n,r)}break}case"dodge":e.executeDodge(t,a);break;case"dash":e.executeDash(t,a);break;case"disengage":e.executeDisengage(t,a);break;case"flee":{e.canFlee()?e.executeFlee(t,a):this.fallbackAttack(e,t,a);break}case"useItem":{const n=a.character.equipment?.items||[],r=i.itemName?n.find(o=>o.name===i.itemName&&o.quantity>0):void 0;if(r&&(r.quantity-=1,r.quantity<=0)){const o=n.indexOf(r);o!==-1&&n.splice(o,1)}t.history.push({type:"useItem",actor:a,result:{success:!!r,description:r?`${a.character.name} uses ${i.itemName}`:`${a.character.name} tries to use ${i.itemName??"an item"} — item not found`}});break}case"legendaryAction":{this.executeLegendaryDecision(e,t,a,i);break}case"skip":t.history.push({type:"statusEffectTick",actor:a,result:{success:!0,description:`${a.character.name} skips their turn (${i.reasoning??"no action"})`}});break}}processLegendaryActions(e,t,a,i){for(const n of a.combatants)if(n.character.legendary_config&&!n.isDefeated&&n.id!==i.id){for(;a.isActive&&(n.legendaryActionsRemaining??0)>0;){const r=t.selectLegendaryAction(n,a);if(!r||(this.executeLegendaryDecision(e,a,n,r),!a.isActive))break}if(!a.isActive)break}}executeLegendaryDecision(e,t,a,i){const n=a.character.legendary_config;if(!n||!i.legendaryActionId)return;const r=n.actions.find(l=>l.id===i.legendaryActionId);if(!r)return;const o=i.target?this.findCombatant(t,i.target):void 0;try{e.executeLegendaryAction(t,a,{id:r.id,name:r.name,cost:r.cost,effect:r.effect,damage:r.damage,damage_type:r.damage_type},o)}catch{}}executeWeaponAttack(e,t,a,i,n){const r=n==="Unarmed Strike"?"unarmed":n;try{e.executeWeaponAttack(t,a,i,r)}catch{try{e.executeWeaponAttack(t,a,i,"unarmed")}catch{}}}findCombatant(e,t){if(t)return e.combatants.find(a=>a.id===t)}findSpell(e,t){if(t)return e.character.combat_spells?.find(a=>a.name===t)}resolveTargets(e,t,a){if(a&&a.length>0)return a.map(i=>e.combatants.find(n=>n.id===i)).filter(i=>i!==void 0);if(t){const i=e.combatants.find(n=>n.id===t);return i?[i]:[]}return[]}fallbackAttack(e,t,a){const i=a.id.startsWith("player")?"enemy":"player",n=t.combatants.filter(r=>r.id.startsWith(i)&&!r.isDefeated);n.length>0&&this.executeWeaponAttack(e,t,a,n[0],void 0)}hasSkipTurnEffect(e){return e.statusEffects.some(t=>t.mechanicalEffects?.skipTurn)}}function As(s){return new ks(s)}class ks{constructor(e){this.rng=typeof e=="string"?new Se(e):e}rollDie(e){if(e<1)throw new Error("Die must have at least 1 side");return this.rng.randomInt(1,e+1)}rollMultipleDice(e,t){const a=[];for(let i=0;i<e;i++)a.push(this.rollDie(t));return a}parseDiceFormula(e){const t=e.match(/^(\d+)d(\d+)([+-]\d+)?$/);if(!t)throw new Error(`Invalid dice formula: ${e}`);const a=parseInt(t[1],10),i=parseInt(t[2],10),n=t[3]?parseInt(t[3],10):0,r=this.rollMultipleDice(a,i),o=r.reduce((l,c)=>l+c,0)+n;return{diceCount:a,diceSides:i,modifier:n,rolls:r,total:o}}rollD20(){return this.rollDie(20)}rollWithAdvantage(){const e=this.rollD20(),t=this.rollD20();return{roll1:e,roll2:t,result:Math.max(e,t)}}rollWithDisadvantage(){const e=this.rollD20(),t=this.rollD20();return{roll1:e,roll2:t,result:Math.min(e,t)}}rollInitiative(e){return this.rollD20()+e}isCriticalHit(e){return e===20}isCriticalMiss(e){return e===1}doubleDamage(e){return[...e,...e]}calculateDamage(e,t,a=!1){let n=this.parseDiceFormula(e).rolls;a&&(n=this.doubleDamage(n));const r=n.reduce((o,l)=>o+l,0)+t;return{diceFormula:e,rolls:n,modifier:t,total:r,isCritical:a}}rollSavingThrow(e,t=0){return this.rollD20()+e+t}rollAbilityCheck(e,t=0){return this.rollD20()+e+t}rollPercentile(){return this.rollDie(100)}}const lr=[{id:"skeleton",name:"Skeleton",category:"undead",archetype:"archer",signatureAbility:{id:"skeleton_bone_shot",name:"Bone Shot",description:"Fires a sharpened bone projectile that deals piercing damage. The hollow whistle of the bone strikes fear into the living.",damageDie:"d6",damageType:"piercing",attackType:"ranged",range:80,properties:["ammo","accuracy"]},baseStats:{STR:12,DEX:14,CON:12,INT:6,WIS:8,CHA:5},baseHP:13,baseAC:13,baseSpeed:30,audioPreference:{bass:.1,mid:.2,treble:.9},resistances:{resistances:["necrotic"],immunities:["poison"]}},{id:"zombie",name:"Zombie",category:"undead",archetype:"brute",signatureAbility:{id:"zombie_undead_grip",name:"Undead Grip",description:"A grapple followed by a bite that deals necrotic damage. The zombie's grip is supernaturally strong and unnaturally cold.",damageDie:"d6",damageType:"necrotic",attackType:"melee",properties:["grapple","persistent"]},baseStats:{STR:14,DEX:8,CON:14,INT:3,WIS:6,CHA:5},baseHP:22,baseAC:8,baseSpeed:20,audioPreference:{bass:.9,mid:.1,treble:.1},resistances:{resistances:["necrotic"],immunities:["poison"]}},{id:"wight",name:"Wight",category:"undead",archetype:"brute",signatureAbility:{id:"wight_life_drain",name:"Life Drain",description:"A melee attack that deals necrotic damage and heals the wight for half the damage dealt. The stolen life force sustains its unholy existence.",damageDie:"d6",damageType:"necrotic",attackType:"melee",properties:["lifesteal"]},baseStats:{STR:16,DEX:12,CON:14,INT:10,WIS:12,CHA:14},baseHP:26,baseAC:14,baseSpeed:30,audioPreference:{bass:.3,mid:.6,treble:.2},resistances:{resistances:["necrotic"],immunities:["poison"]}},{id:"ghost",name:"Ghost",category:"undead",archetype:"support",signatureAbility:{id:"ghost_horrifying_visage",name:"Horrifying Visage",description:"Projects terrifying spectral visage that frightens nearby enemies. Frightened creatures have disadvantage on ability checks and attack rolls.",damageDie:"d6",damageType:"psychic",attackType:"spell",range:30,properties:["debuff","fear","control"]},baseStats:{STR:8,DEX:14,CON:12,INT:12,WIS:14,CHA:16},baseHP:15,baseAC:11,baseSpeed:0,audioPreference:{bass:.2,mid:.7,treble:.3},resistances:{resistances:["acid","cold","fire","lightning","thunder"],immunities:["poison","necrotic"]}}],cr=[{id:"imp",name:"Imp",category:"fiend",archetype:"archer",signatureAbility:{id:"imp_sting",name:"Sting",description:"A poisonous tail strike that deals poison damage and may poison the target. The imp's venom causes necrosis and weakness.",damageDie:"d6",damageType:"poison",attackType:"ranged",range:30,properties:["poison","debuff"]},baseStats:{STR:6,DEX:15,CON:10,INT:11,WIS:10,CHA:12},baseHP:10,baseAC:13,baseSpeed:20,audioPreference:{bass:.1,mid:.2,treble:.9},resistances:{resistances:["fire","cold"],immunities:["poison"]}},{id:"quasit",name:"Quasit",category:"fiend",archetype:"support",signatureAbility:{id:"quasit_fear_aura",name:"Fear Aura",description:"Projects an aura of unnatural terror that frightens nearby enemies. Frightened creatures have disadvantage on ability checks.",damageDie:"d6",damageType:"psychic",attackType:"spell",range:20,properties:["debuff","fear","control"]},baseStats:{STR:8,DEX:12,CON:10,INT:10,WIS:11,CHA:12},baseHP:7,baseAC:13,baseSpeed:40,audioPreference:{bass:.2,mid:.7,treble:.3},resistances:{resistances:["fire","cold"],immunities:["poison"]}},{id:"lemure",name:"Lemure",category:"fiend",archetype:"brute",signatureAbility:{id:"lemure_hellish_resilience",name:"Hellish Resilience",description:"A slam attack that deals fire damage while simultaneously healing the lemure. Its corrupted flesh regenerates from hellfire.",damageDie:"d6",damageType:"fire",attackType:"melee",properties:["lifesteal","persistent"]},baseStats:{STR:10,DEX:6,CON:12,INT:3,WIS:8,CHA:5},baseHP:13,baseAC:7,baseSpeed:30,audioPreference:{bass:.9,mid:.1,treble:.1},resistances:{resistances:["fire","cold"],immunities:["poison"]}},{id:"demon",name:"Demon",category:"fiend",archetype:"brute",signatureAbility:{id:"demon_chaos_claw",name:"Chaos Claw",description:"A claw attack infused with chaotic energy that deals random damage types. Each strike channels the unpredictability of the abyss.",damageDie:"d6",damageType:"necrotic",attackType:"melee",properties:["chaotic","versatile"]},baseStats:{STR:16,DEX:12,CON:14,INT:8,WIS:10,CHA:10},baseHP:28,baseAC:13,baseSpeed:30,audioPreference:{bass:.8,mid:.3,treble:.2},resistances:{resistances:["fire","cold"],immunities:["poison"]}}],ur=[{id:"fire-elemental",name:"Fire Elemental",category:"elemental",archetype:"brute",signatureAbility:{id:"fire_elemental_burning_touch",name:"Burning Touch",description:"A searing touch that deals fire damage and applies burning damage over time. The flames continue to burn after impact.",damageDie:"d6",damageType:"fire",attackType:"melee",properties:["ongoing","burn"]},baseStats:{STR:14,DEX:12,CON:16,INT:6,WIS:10,CHA:6},baseHP:26,baseAC:13,baseSpeed:50,audioPreference:{bass:.8,mid:.2,treble:.1},resistances:{immunities:["fire"],resistances:[]}},{id:"earth-elemental",name:"Earth Elemental",category:"elemental",archetype:"brute",signatureAbility:{id:"earth_elemental_earth_slam",name:"Earth Slam",description:"A devastating slam that deals bludgeoning damage in an area and knocks targets prone. The ground cracks under its power.",damageDie:"d8",damageType:"bludgeoning",attackType:"melee",properties:["aoe","prone"]},baseStats:{STR:18,DEX:8,CON:16,INT:6,WIS:10,CHA:6},baseHP:30,baseAC:17,baseSpeed:20,audioPreference:{bass:.9,mid:.1,treble:.1},resistances:{immunities:["poison"],resistances:["necrotic"]}},{id:"water-elemental",name:"Water Elemental",category:"elemental",archetype:"support",signatureAbility:{id:"water_elemental_whirlpool",name:"Whirlpool",description:"Creates a swirling vortex that restrains targets and pulls them toward the center. The crushing waves hold enemies in place.",damageDie:"d6",damageType:"cold",attackType:"spell",range:30,properties:["control","restrain","pull"]},baseStats:{STR:14,DEX:14,CON:14,INT:8,WIS:12,CHA:8},baseHP:22,baseAC:14,baseSpeed:30,audioPreference:{bass:.2,mid:.7,treble:.2},resistances:{immunities:["cold"],resistances:["fire","necrotic"]}},{id:"air-elemental",name:"Air Elemental",category:"elemental",archetype:"archer",signatureAbility:{id:"air_elemental_wind_blast",name:"Wind Blast",description:"Projects a concentrated gust of wind that pushes targets back and deals force damage. The howling wind disrupts formation.",damageDie:"d6",damageType:"force",attackType:"ranged",range:60,properties:["push","control"]},baseStats:{STR:12,DEX:16,CON:14,INT:8,WIS:12,CHA:8},baseHP:18,baseAC:15,baseSpeed:90,audioPreference:{bass:.1,mid:.2,treble:.9},resistances:{immunities:["lightning"],resistances:["thunder"]}}],dr=[{id:"animated-armor",name:"Animated Armor",category:"construct",archetype:"brute",signatureAbility:{id:"animated_armor_slam",name:"Slam",description:"A powerful melee attack that deals force damage. The enchanted armor strikes with supernatural strength.",damageDie:"d6",damageType:"force",attackType:"melee",properties:["force"]},baseStats:{STR:14,DEX:10,CON:14,INT:3,WIS:6,CHA:1},baseHP:18,baseAC:18,baseSpeed:25,audioPreference:{bass:.9,mid:.1,treble:.1},resistances:{immunities:["poison","psychic"]}},{id:"golem",name:"Golem",category:"construct",archetype:"brute",signatureAbility:{id:"golem_immutable_form",name:"Immutable Form",description:"A devastating slam that deals bludgeoning damage. The golem is immune to most status conditions and non-magical physical attacks.",damageDie:"d8",damageType:"bludgeoning",attackType:"melee",properties:["status_immunity","nonmagical_resistance"]},baseStats:{STR:18,DEX:8,CON:16,INT:3,WIS:6,CHA:1},baseHP:32,baseAC:15,baseSpeed:20,audioPreference:{bass:.9,mid:.1,treble:0},resistances:{immunities:["poison","psychic"],resistances:["bludgeoning","piercing","slashing"]}},{id:"flying-sword",name:"Flying Sword",category:"construct",archetype:"archer",signatureAbility:{id:"flying_sword_diving_strike",name:"Diving Strike",description:"A diving attack that deals bonus damage when moving toward the target. The enchanted sword strikes with supernatural precision.",damageDie:"d6",damageType:"slashing",attackType:"melee",properties:["charge","accuracy"]},baseStats:{STR:12,DEX:16,CON:12,INT:1,WIS:4,CHA:1},baseHP:10,baseAC:17,baseSpeed:50,audioPreference:{bass:.1,mid:.1,treble:.9},resistances:{immunities:["poison","psychic"]}},{id:"shield-guardian",name:"Shield Guardian",category:"construct",archetype:"support",signatureAbility:{id:"shield_guardian_protection_aura",name:"Protection Aura",description:"Projects a protective field that increases armor class of nearby allies. The magical shield deflects attacks meant for others.",damageDie:"d6",damageType:"force",attackType:"spell",range:20,properties:["buff","ally","defense"]},baseStats:{STR:14,DEX:10,CON:16,INT:6,WIS:10,CHA:5},baseHP:22,baseAC:16,baseSpeed:30,audioPreference:{bass:.2,mid:.7,treble:.2},resistances:{immunities:["poison","psychic"]}}],hr=[{id:"young-red-dragon",name:"Young Red Dragon",category:"dragon",archetype:"brute",signatureAbility:{id:"young_red_dragon_fire_breath",name:"Fire Breath",description:"Exhales a cone of searing flame that deals fire damage to all creatures in the area. The intense heat can ignite flammable objects.",damageDie:"d8",damageType:"fire",attackType:"ranged",range:30,properties:["aoe","cone","burn"]},baseStats:{STR:19,DEX:12,CON:17,INT:12,WIS:13,CHA:15},baseHP:45,baseAC:18,baseSpeed:40,audioPreference:{bass:.9,mid:.2,treble:.1},resistances:{immunities:["fire"],resistances:[]}},{id:"dragon-wyrmling",name:"Dragon Wyrmling",category:"dragon",archetype:"brute",signatureAbility:{id:"dragon_wyrmling_multiattack",name:"Bite + Claw",description:"A combination attack with bite and claws that deals multiple physical attacks. The wyrmling attacks with ferocious hunger.",damageDie:"d6",damageType:"slashing",attackType:"melee",properties:["multiattack"]},baseStats:{STR:15,DEX:12,CON:14,INT:10,WIS:11,CHA:12},baseHP:26,baseAC:17,baseSpeed:30,audioPreference:{bass:.3,mid:.6,treble:.2},resistances:{immunities:[],resistances:["acid"]}},{id:"drake",name:"Drake",category:"dragon",archetype:"brute",signatureAbility:{id:"drake_tail_swipe",name:"Tail Swipe",description:"A powerful tail strike that deals bludgeoning damage and knocks targets back. The force can push enemies away from the drake.",damageDie:"d6",damageType:"bludgeoning",attackType:"melee",properties:["knockback","push"]},baseStats:{STR:16,DEX:12,CON:14,INT:8,WIS:10,CHA:10},baseHP:32,baseAC:15,baseSpeed:40,audioPreference:{bass:.8,mid:.3,treble:.1},resistances:{immunities:[],resistances:["cold"]}},{id:"young-blue-dragon",name:"Young Blue Dragon",category:"dragon",archetype:"archer",signatureAbility:{id:"young_blue_dragon_lightning_breath",name:"Lightning Breath",description:"Exhales a line of crackling lightning that deals lightning damage to all creatures in its path. The electrical discharge arcs between targets.",damageDie:"d8",damageType:"lightning",attackType:"ranged",range:60,properties:["aoe","line","chain"]},baseStats:{STR:17,DEX:14,CON:15,INT:14,WIS:13,CHA:15},baseHP:38,baseAC:19,baseSpeed:40,audioPreference:{bass:.1,mid:.2,treble:.9},resistances:{immunities:["lightning"],resistances:["thunder"]}}],pr=[{id:"owlbear",name:"Owlbear",category:"monstrosity",archetype:"brute",signatureAbility:{id:"owlbear_multiattack",name:"Multiattack",description:"A devastating combination of beak and claws attacks. The owlbear unleashes its primal fury in a flurry of strikes.",damageDie:"d6",damageType:"slashing",attackType:"melee",properties:["multiattack"]},baseStats:{STR:18,DEX:10,CON:14,INT:3,WIS:10,CHA:6},baseHP:37,baseAC:13,baseSpeed:40,audioPreference:{bass:.9,mid:.2,treble:.1},resistances:{resistances:[]}},{id:"griffin",name:"Griffin",category:"monstrosity",archetype:"archer",signatureAbility:{id:"griffin_dive_attack",name:"Dive Attack",description:"A high-speed diving attack from above that deals extra piercing damage. The griffin uses its powerful wings to strike with devastating force.",damageDie:"d6",damageType:"piercing",attackType:"ranged",range:80,properties:["charge","flying"]},baseStats:{STR:16,DEX:15,CON:14,INT:4,WIS:12,CHA:7},baseHP:30,baseAC:12,baseSpeed:50,audioPreference:{bass:.1,mid:.2,treble:.9},resistances:{resistances:[]}},{id:"mimic",name:"Mimic",category:"monstrosity",archetype:"brute",signatureAbility:{id:"mimic_adhesive",name:"Adhesive",description:"A pseudopod attack that deals bludgeoning damage and grapples the target. The mimic's adhesive surface traps prey.",damageDie:"d6",damageType:"bludgeoning",attackType:"melee",properties:["grapple","ambush"]},baseStats:{STR:14,DEX:10,CON:12,INT:6,WIS:8,CHA:4},baseHP:21,baseAC:12,baseSpeed:10,audioPreference:{bass:.2,mid:.7,treble:.3},resistances:{resistances:["acid"]}},{id:"basilisk",name:"Basilisk",category:"monstrosity",archetype:"support",signatureAbility:{id:"basilisk_petrifying_gaze",name:"Petrifying Gaze",description:"A gaze attack that can stun targets. Those who meet the basilisk's eyes feel their limbs turning to stone.",damageDie:"d6",damageType:"psychic",attackType:"spell",range:30,properties:["debuff","control","stun"]},baseStats:{STR:14,DEX:10,CON:12,INT:4,WIS:6,CHA:6},baseHP:26,baseAC:14,baseSpeed:20,audioPreference:{bass:.2,mid:.7,treble:.2},resistances:{resistances:["poison"]}}],mr=[{id:"orc",name:"Orc",category:"humanoid",archetype:"brute",signatureAbility:{id:"orc_savage_strike",name:"Savage Strike",description:"A powerful melee attack that deals extra damage. The ferocity of the attack embodies primal aggression.",damageDie:"d6",damageType:"slashing",attackType:"melee",properties:["versatile"]},baseStats:{STR:16,DEX:12,CON:14,INT:8,WIS:10,CHA:8},baseHP:15,baseAC:13,baseSpeed:30,audioPreference:{bass:.8,mid:.3,treble:.1},resistances:{resistances:["poison"]}},{id:"bandit",name:"Bandit",category:"humanoid",archetype:"brute",signatureAbility:{id:"bandit_cheap_shot",name:"Cheap Shot",description:"A dirty fighting technique that deals bonus damage against surprised or flat-footed opponents.",damageDie:"d6",damageType:"slashing",attackType:"melee",properties:["finesse"]},baseStats:{STR:13,DEX:14,CON:12,INT:10,WIS:10,CHA:11},baseHP:11,baseAC:12,baseSpeed:30,audioPreference:{bass:.4,mid:.6,treble:.3},resistances:{resistances:[]}},{id:"hunter",name:"Hunter",category:"humanoid",archetype:"archer",signatureAbility:{id:"hunter_precise_shot",name:"Precise Shot",description:"A carefully aimed shot that can ignore half cover. Exceptional accuracy allows targeting vulnerabilities.",damageDie:"d6",damageType:"piercing",attackType:"ranged",range:120,properties:["accuracy"]},baseStats:{STR:11,DEX:16,CON:12,INT:12,WIS:14,CHA:10},baseHP:10,baseAC:13,baseSpeed:30,audioPreference:{bass:.1,mid:.3,treble:.8},resistances:{resistances:[]}},{id:"goblin-archer",name:"Goblin Archer",category:"humanoid",archetype:"archer",signatureAbility:{id:"goblin_sneaky_shot",name:"Sneaky Shot",description:"A quick shot from hiding that deals bonus damage. Works best when the goblin is unseen.",damageDie:"d6",damageType:"piercing",attackType:"ranged",range:80,properties:["finesse","stealth"]},baseStats:{STR:8,DEX:15,CON:10,INT:10,WIS:8,CHA:8},baseHP:7,baseAC:13,baseSpeed:30,audioPreference:{bass:.1,mid:.2,treble:.9},resistances:{resistances:[]}},{id:"shaman",name:"Shaman",category:"humanoid",archetype:"support",signatureAbility:{id:"shaman_spirit_bond",name:"Spirit Bond",description:"Channels spiritual energy to grant nearby allies a damage bonus on their next attack.",damageDie:"d6",damageType:"force",attackType:"spell",range:30,properties:["buff","ally"]},baseStats:{STR:10,DEX:10,CON:12,INT:12,WIS:15,CHA:13},baseHP:9,baseAC:11,baseSpeed:30,audioPreference:{bass:.3,mid:.7,treble:.3},resistances:{resistances:["necrotic"]}},{id:"cultist",name:"Cultist",category:"humanoid",archetype:"support",signatureAbility:{id:"cultist_dark_blessing",name:"Dark Blessing",description:"Invokes dark powers to temporarily increase nearby allies' armor class.",damageDie:"d6",damageType:"necrotic",attackType:"spell",range:20,properties:["buff","ally","defense"]},baseStats:{STR:11,DEX:12,CON:12,INT:13,WIS:11,CHA:14},baseHP:9,baseAC:13,baseSpeed:30,audioPreference:{bass:.3,mid:.7,treble:.2},resistances:{resistances:["necrotic"]}},{id:"bear",name:"Bear",category:"beast",archetype:"brute",signatureAbility:{id:"bear_maul",name:"Maul",description:"A devastating multiattack with claws that can also grapple the target. Raw power embodied.",damageDie:"d6",damageType:"slashing",attackType:"melee",properties:["multiattack","grapple"]},baseStats:{STR:18,DEX:10,CON:16,INT:2,WIS:12,CHA:6},baseHP:34,baseAC:11,baseSpeed:40,audioPreference:{bass:.9,mid:.2,treble:.1},resistances:{resistances:["cold"]}},{id:"boar",name:"Boar",category:"beast",archetype:"brute",signatureAbility:{id:"boar_gore_charge",name:"Gore Charge",description:"A ferocious charge that deals bonus damage if the boar moves at least 20 feet before attacking.",damageDie:"d6",damageType:"piercing",attackType:"melee",properties:["charge"]},baseStats:{STR:13,DEX:11,CON:12,INT:2,WIS:8,CHA:4},baseHP:11,baseAC:11,baseSpeed:40,audioPreference:{bass:.8,mid:.3,treble:.1},resistances:{resistances:[]}},{id:"giant-spider",name:"Giant Spider",category:"beast",archetype:"archer",signatureAbility:{id:"spider_web_spray",name:"Web Spray",description:"Shoots sticky webbing at a target to restrain them. A restrained creature has speed 0 and can't use reactions.",damageDie:"d6",damageType:"poison",attackType:"ranged",range:60,properties:["control","restrain"]},baseStats:{STR:12,DEX:14,CON:12,INT:2,WIS:12,CHA:4},baseHP:26,baseAC:13,baseSpeed:30,audioPreference:{bass:.2,mid:.3,treble:.8},resistances:{resistances:["poison"]}},{id:"stirge",name:"Stirge",category:"beast",archetype:"archer",signatureAbility:{id:"stirge_blood_drain",name:"Blood Drain",description:"Attaches to target and drains blood, dealing damage and healing the stirge for half the damage dealt.",damageDie:"d6",damageType:"piercing",attackType:"melee",properties:["lifesteal","grapple"]},baseStats:{STR:4,DEX:14,CON:10,INT:1,WIS:6,CHA:2},baseHP:5,baseAC:12,baseSpeed:10,audioPreference:{bass:.1,mid:.2,treble:.9},resistances:{resistances:[]}}],Xe=[...mr,...lr,...cr,...ur,...dr,...hr,...pr];function gr(s){return Xe.find(e=>e.id===s)}function fr(s){return Xe.filter(e=>e.category===s)}function yr(s){return Xe.filter(e=>e.archetype===s)}const Ca={common:{statMultiplier:1,hpMultiplier:1,signatureDieSize:6,extraAbilityCount:0,hasResistances:!1},uncommon:{statMultiplier:1.08,hpMultiplier:1.3,signatureDieSize:8,extraAbilityCount:1,hasResistances:!1},elite:{statMultiplier:1.15,hpMultiplier:1.7,signatureDieSize:10,extraAbilityCount:2,hasResistances:!0},boss:{statMultiplier:1.25,hpMultiplier:2.2,signatureDieSize:12,extraAbilityCount:3,hasResistances:!0}};function Oe(s){return Ca[s]}function vr(s){return`1d${Ca[s].signatureDieSize}`}function Ts(){return["common","uncommon","elite","boss"]}function br(s){const e=Ts(),t=e.indexOf(s);if(!(t===-1||t===e.length-1))return e[t+1]}function wr(s){return s}const Sr=["Human","Elf","Dwarf","Halfling","Dragonborn","Gnome","Half-Elf","Half-Orc","Tiefling"],_r=Sr.map(wr);function Cs(s){return s}const Ar=["Barbarian","Bard","Cleric","Druid","Fighter","Monk","Paladin","Ranger","Rogue","Sorcerer","Warlock","Wizard"],Re=Ar.map(Cs);function ra(s){if(typeof s!="string")return!1;if(Re.includes(s))return!0;try{const t=require("../extensions/ExtensionManager.js").ExtensionManager.getInstance();if(t.get("classes")?.includes(s))return!0;const i=t.get("classes.data");if(i&&Array.isArray(i)&&i.map(r=>r.name).includes(s))return!0}catch{}return!1}const lt=["STR","DEX","CON","INT","WIS","CHA"];function Pe(s){return lt.includes(s)}const Is=["http://","https://","/","assets/"];function Es(s){return typeof s!="string"||s.trim()===""?!1:Is.some(e=>s.startsWith(e))}function hi(s,e){return s===void 0?{valid:!0,errors:[]}:typeof s!="string"?{valid:!1,errors:[`${e} must be a string`]}:s.trim()===""?{valid:!1,errors:[`${e} cannot be an empty string`]}:Es(s)?{valid:!0,errors:[]}:{valid:!1,errors:[`${e} must start with one of: ${Is.join(", ")} (got: "${s}")`]}}function Be(s){const e=[],t=hi(s.icon,"icon");t.valid||e.push(...t.errors);const a=hi(s.image,"image");return a.valid||e.push(...a.errors),e}const pi=["passive","active","resource","trigger"],mt=["default","custom"],mi=["stat_bonus","skill_proficiency","ability_unlock","passive_modifier","resource_grant","spell_slot_bonus"],gt=["Human","Elf","Dwarf","Halfling","Dragonborn","Gnome","Half-Elf","Half-Orc","Tiefling"],ft=["athletics","acrobatics","sleight_of_hand","stealth","arcana","history","investigation","nature","religion","animal_handling","insight","medicine","perception","survival","deception","intimidation","performance","persuasion"],gi=["none","proficient","expertise"];class Fe{static validateClassFeature(e,t){const a=[];if(!e||typeof e!="object"||Array.isArray(e))return{valid:!1,errors:["Class feature must be an object"]};const i=e;if(!i.id||typeof i.id!="string"?a.push("Feature must have a valid id (string)"):this.isValidFeatureId(i.id)||a.push(`Feature id must use lowercase_with_underscores format: "${i.id}"`),(!i.name||typeof i.name!="string")&&a.push("Feature must have a valid name (string)"),(!i.description||typeof i.description!="string")&&a.push("Feature must have a description (string)"),!i.type||typeof i.type!="string"?a.push("Feature must have a type (string)"):pi.includes(i.type)||a.push(`Invalid type: "${i.type}". Must be one of: ${pi.join(", ")}`),!i.class||typeof i.class!="string"?a.push("Feature must have a class (string)"):ra(i.class)||a.push(`Invalid class: "${i.class}". Must be one of: ${Re.join(", ")} or a custom class registered via ExtensionManager.`),typeof i.level!="number"?a.push("Feature must have a level (number)"):(i.level<1||i.level>20)&&a.push(`Feature level must be between 1 and 20 (got: ${i.level})`),!i.source||typeof i.source!="string"?a.push("Feature must have a source (string)"):mt.includes(i.source)||a.push(`Invalid source: "${i.source}". Must be one of: ${mt.join(", ")}`),i.tags!==void 0)if(!Array.isArray(i.tags))a.push("Feature tags must be an array");else for(const r of i.tags)typeof r!="string"&&a.push(`Feature tag must be a string (got: ${typeof r})`);i.lore!==void 0&&typeof i.lore!="string"&&a.push("Feature lore must be a string"),i.subrace!==void 0&&typeof i.subrace!="string"&&a.push("Feature subrace must be a string");const n=Be({icon:i.icon,image:i.image});if(a.push(...n.map(r=>`Feature ${r}`)),i.prerequisites!==void 0){const r=this.validatePrerequisites(i.prerequisites,t);r.valid||a.push(...r.errors.map(o=>`Prerequisites: ${o}`))}if(i.effects!==void 0)if(!Array.isArray(i.effects))a.push("Feature effects must be an array");else for(let r=0;r<i.effects.length;r++){const o=this.validateEffect(i.effects[r]);o.valid||a.push(...o.errors.map(l=>`Effect ${r}: ${l}`))}return{valid:a.length===0,errors:a}}static validateRacialTrait(e,t){const a=[];if(!e||typeof e!="object"||Array.isArray(e))return{valid:!1,errors:["Racial trait must be an object"]};const i=e;if(!i.id||typeof i.id!="string"?a.push("Trait must have a valid id (string)"):this.isValidFeatureId(i.id)||a.push(`Trait id must use lowercase_with_underscores format: "${i.id}"`),(!i.name||typeof i.name!="string")&&a.push("Trait must have a valid name (string)"),(!i.description||typeof i.description!="string")&&a.push("Trait must have a description (string)"),!i.race||typeof i.race!="string")a.push("Trait must have a race (string)");else{const r=gt.includes(i.race),o=t?.customRaces??[],l=o.includes(i.race);if(!r&&!l){const c=o.length>0?`, ${o.join(", ")}`:"";a.push(`Invalid race: "${i.race}". Must be one of: ${gt.join(",")}${c} or a custom race registered via ExtensionManager.`)}}if(!i.source||typeof i.source!="string"?a.push("Trait must have a source (string)"):mt.includes(i.source)||a.push(`Invalid source: "${i.source}". Must be one of: ${mt.join(", ")}`),i.tags!==void 0)if(!Array.isArray(i.tags))a.push("Trait tags must be an array");else for(const r of i.tags)typeof r!="string"&&a.push(`Trait tag must be a string (got: ${typeof r})`);i.lore!==void 0&&typeof i.lore!="string"&&a.push("Trait lore must be a string"),i.subrace!==void 0&&typeof i.subrace!="string"&&a.push("Trait subrace must be a string");const n=Be({icon:i.icon,image:i.image});if(a.push(...n.map(r=>`Trait ${r}`)),i.prerequisites!==void 0){const r=this.validatePrerequisites(i.prerequisites,t);r.valid||a.push(...r.errors.map(o=>`Prerequisites: ${o}`))}if(i.effects!==void 0)if(!Array.isArray(i.effects))a.push("Trait effects must be an array");else for(let r=0;r<i.effects.length;r++){const o=this.validateEffect(i.effects[r]);o.valid||a.push(...o.errors.map(l=>`Effect ${r}: ${l}`))}return{valid:a.length===0,errors:a}}static validateEffect(e){const t=[];if(!e||typeof e!="object"||Array.isArray(e))return{valid:!1,errors:["Effect must be an object"]};const a=e;if(!a.type||typeof a.type!="string"?t.push("Effect must have a type (string)"):mi.includes(a.type)||t.push(`Invalid effect type: "${a.type}". Must be one of: ${mi.join(", ")}`),!a.target||typeof a.target!="string")t.push("Effect must have a target (string)");else{const i=a.type,n=a.target;if(!(i==="stat_bonus"&&this.isAbility(n))){if(i==="skill_proficiency"&&!ft.includes(n))t.push(`Invalid skill target: "${n}". Must be one of: ${ft.join(", ")}`);else if(i==="skill_proficiency"){const r=a.value;typeof r=="string"&&!gi.includes(r)&&t.push(`Invalid proficiency level: "${r}". Must be one of: ${gi.join(", ")}`)}}}return a.value===void 0&&t.push("Effect must have a value"),a.condition!==void 0&&typeof a.condition!="string"&&t.push("Effect condition must be a string"),a.description!==void 0&&typeof a.description!="string"&&t.push("Effect description must be a string"),{valid:t.length===0,errors:t}}static validatePrerequisites(e,t){const a=[];if(!e||typeof e!="object"||Array.isArray(e))return{valid:!1,errors:["Prerequisites must be an object"]};const i=e;if(i.level!==void 0&&(typeof i.level!="number"?a.push("Prerequisite level must be a number"):(i.level<1||i.level>20)&&a.push(`Prerequisite level must be between 1 and 20 (got: ${i.level})`)),i.abilities!==void 0)if(typeof i.abilities!="object"||Array.isArray(i.abilities)||i.abilities===null)a.push("Prerequisite abilities must be a record");else for(const[n,r]of Object.entries(i.abilities))lt.includes(n)||a.push(`Invalid ability in prerequisites: "${n}". Must be one of: ${lt.join(", ")}`),(typeof r!="number"||r<1||r>20)&&a.push(`Ability score requirement for ${n} must be a number between 1 and 20`);if(i.class!==void 0){if(typeof i.class!="string")a.push("Prerequisite class must be a string");else if(!ra(i.class)){const n=t?.customClasses??[];if(!n.includes(i.class)){const o=n.length>0?`, ${n.join(", ")}`:"";a.push(`Invalid prerequisite class: "${i.class}". Must be one of: ${Re.join(",")}${o} or a custom class registered via ExtensionManager.`)}}}if(i.race!==void 0)if(typeof i.race!="string")a.push("Prerequisite race must be a string");else{const n=gt.includes(i.race),r=t?.customRaces??[],o=r.includes(i.race);if(!n&&!o){const l=r.length>0?`, ${r.join(", ")}`:"";a.push(`Invalid prerequisite race: "${i.race}". Must be one of: ${gt.join(",")}${l} or a custom race registered via ExtensionManager.`)}}if(i.features!==void 0)if(!Array.isArray(i.features))a.push("Prerequisite features must be an array");else for(const n of i.features)typeof n!="string"?a.push("Prerequisite feature IDs must be strings"):this.isValidFeatureId(n)||a.push(`Invalid prerequisite feature ID format: "${n}"`);if(i.subrace!==void 0&&(typeof i.subrace!="string"?a.push("Prerequisite subrace must be a string"):i.subrace.trim()===""&&a.push("Prerequisite subrace cannot be empty")),i.skills!==void 0)if(!Array.isArray(i.skills))a.push("Prerequisite skills must be an array");else for(const n of i.skills)typeof n!="string"?a.push("Prerequisite skill IDs must be strings"):ft.includes(n)||a.push(`Invalid prerequisite skill: "${n}". Must be one of: ${ft.join(", ")}`);if(i.spells!==void 0)if(!Array.isArray(i.spells))a.push("Prerequisite spells must be an array");else for(const n of i.spells)typeof n!="string"?a.push("Prerequisite spell names must be strings"):n.trim()===""&&a.push("Prerequisite spell name cannot be empty");return i.custom!==void 0&&typeof i.custom!="string"&&a.push("Prerequisite custom condition must be a string"),{valid:a.length===0,errors:a}}static validateClassFeatures(e){const t=[];return Array.isArray(e)?(e.forEach((a,i)=>{const n=this.validateClassFeature(a);n.valid||t.push(`Feature at index ${i}: ${n.errors.join("; ")}`)}),{valid:t.length===0,errors:t}):{valid:!1,errors:["Input must be an array of class features"]}}static validateRacialTraits(e){const t=[];return Array.isArray(e)?(e.forEach((a,i)=>{const n=this.validateRacialTrait(a);n.valid||t.push(`Trait at index ${i}: ${n.errors.join("; ")}`)}),{valid:t.length===0,errors:t}):{valid:!1,errors:["Input must be an array of racial traits"]}}static isAbility(e){return Pe(e)}static isValidFeatureId(e){return/^[a-z][a-z0-9_]*$/.test(e)}}function kr(s,e){return Fe.validateClassFeature(s,e)}function Tr(s,e){return Fe.validateRacialTrait(s,e)}function Cr(s){return Fe.validateClassFeatures(s)}function Ir(s){return Fe.validateRacialTraits(s)}function Er(s){const e=[];if(s==null)return{valid:!0,errors:[]};if(typeof s!="object"||Array.isArray(s))return{valid:!1,errors:["Prerequisites must be an object"]};const t=s;if(t.level!==void 0&&(typeof t.level!="number"?e.push("Prerequisite level must be a number"):t.level<1&&e.push(`Prerequisite level must be at least 1 (got: ${t.level})`)),t.casterLevel!==void 0&&(typeof t.casterLevel!="number"?e.push("Prerequisite casterLevel must be a number"):t.casterLevel<1&&e.push(`Prerequisite casterLevel must be at least 1 (got: ${t.casterLevel})`)),t.abilities!==void 0)if(typeof t.abilities!="object"||Array.isArray(t.abilities)||t.abilities===null)e.push("Prerequisite abilities must be a record");else for(const[a,i]of Object.entries(t.abilities))Pe(a)||e.push(`Invalid ability in prerequisites: "${a}". Must be one of: STR, DEX, CON, INT, WIS, CHA`),typeof i!="number"?e.push(`Ability score for ${a} must be a number (got: ${typeof i})`):(i<1||i>20)&&e.push(`Ability score for ${a} must be between 1 and 20 (got: ${i})`);if(t.class!==void 0&&typeof t.class!="string"&&e.push("Prerequisite class must be a string"),t.race!==void 0&&typeof t.race!="string"&&e.push("Prerequisite race must be a string"),t.subrace!==void 0&&(typeof t.subrace!="string"?e.push("Prerequisite subrace must be a string"):t.subrace.trim()===""&&e.push("Prerequisite subrace cannot be empty")),t.features!==void 0)if(!Array.isArray(t.features))e.push("Prerequisite features must be an array");else for(const a of t.features)typeof a!="string"&&e.push("Prerequisite feature IDs must be strings");if(t.skills!==void 0)if(!Array.isArray(t.skills))e.push("Prerequisite skills must be an array");else for(const a of t.skills)typeof a!="string"&&e.push("Prerequisite skill IDs must be strings");if(t.spells!==void 0)if(!Array.isArray(t.spells))e.push("Prerequisite spells must be an array");else for(const a of t.spells)typeof a!="string"?e.push("Prerequisite spell names must be strings"):a.trim()===""&&e.push("Prerequisite spell name cannot be empty");return t.custom!==void 0&&typeof t.custom!="string"&&e.push("Prerequisite custom condition must be a string"),{valid:e.length===0,errors:e}}function Ms(s,e){const t=[];if(!s)return{valid:!0,errors:[]};if(s.level!==void 0&&e.level<s.level&&t.push(`Requires level ${s.level} (current: ${e.level})`),s.casterLevel!==void 0&&e.level<s.casterLevel&&t.push(`Requires caster level ${s.casterLevel} (current: ${e.level})`),s.abilities)for(const[a,i]of Object.entries(s.abilities)){const n=e.ability_scores[a];(n===void 0||n<i)&&t.push(`Requires ${a} ${i}+ (current: ${n??0})`)}if(s.class!==void 0&&e.class!==s.class&&t.push(`Requires ${s.class} class (current: ${e.class})`),s.race!==void 0&&e.race!==s.race&&t.push(`Requires ${s.race} race (current: ${e.race})`),s.subrace!==void 0){const a=e.subrace||"";a!==s.subrace&&t.push(`Requires ${s.subrace} subrace (current: ${a||"none"})`)}if(s.skills&&s.skills.length>0)for(const a of s.skills){const i=e.skills[a];i!=="proficient"&&i!=="expertise"&&t.push(`Requires proficiency in ${a} (current: ${i??"none"})`)}if(s.features&&s.features.length>0){const a=e.class_features||[];for(const i of s.features)a.includes(i)||t.push(`Requires feature: ${i}`)}if(s.spells&&s.spells.length>0){const a=e.spells?.known_spells||[],i=e.spells?.cantrips||[],n=[...a,...i];for(const r of s.spells)n.includes(r)||t.push(`Requires spell: ${r}`)}return s.custom,{valid:t.length===0,errors:t}}const fi=["default","custom"],yi=["none","proficient","expertise"],vi=["class","background","feat","custom","racial","other"];class we{static validateSkill(e){const t=[];if(!e||typeof e!="object"||Array.isArray(e))return{valid:!1,errors:["Skill must be an object"]};const a=e;if(!a.id||typeof a.id!="string"?t.push("Skill must have a valid id (string)"):this.isValidSkillId(a.id)||t.push(`Skill id must use lowercase_with_underscores format: "${a.id}"`),(!a.name||typeof a.name!="string")&&t.push("Skill must have a valid name (string)"),!a.ability||typeof a.ability!="string"?t.push("Skill must have an ability (string)"):lt.includes(a.ability)||t.push(`Invalid ability: "${a.ability}". Must be one of: ${lt.join(", ")}`),!a.source||typeof a.source!="string"?t.push("Skill must have a source (string)"):fi.includes(a.source)||t.push(`Invalid source: "${a.source}". Must be one of: ${fi.join(", ")}`),a.description!==void 0&&typeof a.description!="string"&&t.push("Skill description must be a string"),a.armorPenalty!==void 0&&typeof a.armorPenalty!="boolean"&&t.push("Skill armorPenalty must be a boolean"),a.customProperties!==void 0)if(typeof a.customProperties!="object"||Array.isArray(a.customProperties)||a.customProperties===null)t.push("Skill customProperties must be a record");else for(const[n,r]of Object.entries(a.customProperties)){const o=typeof r;if(!["string","number","boolean"].includes(o)&&!Array.isArray(r)&&t.push(`customProperties.${n} has invalid type: ${o}. Must be string, number, boolean, or string array`),Array.isArray(r))for(let l=0;l<r.length;l++)typeof r[l]!="string"&&t.push(`customProperties.${n}[${l}] must be a string`)}if(a.categories!==void 0)if(!Array.isArray(a.categories))t.push("Skill categories must be an array");else for(const n of a.categories)typeof n!="string"&&t.push(`Skill category must be a string (got: ${typeof n})`);if(a.tags!==void 0)if(!Array.isArray(a.tags))t.push("Skill tags must be an array");else for(const n of a.tags)typeof n!="string"&&t.push(`Skill tag must be a string (got: ${typeof n})`);a.lore!==void 0&&typeof a.lore!="string"&&t.push("Skill lore must be a string");const i=Be({icon:a.icon,image:a.image});return t.push(...i.map(n=>`Skill ${n}`)),{valid:t.length===0,errors:t}}static validateSkills(e){const t=[];return Array.isArray(e)?(e.forEach((a,i)=>{const n=this.validateSkill(a);n.valid||t.push(`Skill at index ${i}: ${n.errors.join("; ")}`)}),{valid:t.length===0,errors:t}):{valid:!1,errors:["Input must be an array of skills"]}}static validateSkillProficiency(e){const t=[];if(!e||typeof e!="object"||Array.isArray(e))return{valid:!1,errors:["Skill proficiency must be an object"]};const a=e;return!a.skillId||typeof a.skillId!="string"?t.push("Skill proficiency must have a valid skillId (string)"):this.isValidSkillId(a.skillId)||t.push(`Skill proficiency skillId must use lowercase_with_underscores format: "${a.skillId}"`),!a.level||typeof a.level!="string"?t.push("Skill proficiency must have a level (string)"):yi.includes(a.level)||t.push(`Invalid proficiency level: "${a.level}". Must be one of: ${yi.join(", ")}`),!a.source||typeof a.source!="string"?t.push("Skill proficiency must have a source (string)"):vi.includes(a.source)||t.push(`Invalid proficiency source: "${a.source}". Must be one of: ${vi.join(", ")}`),a.grantedBy!==void 0&&typeof a.grantedBy!="string"&&t.push("Skill proficiency grantedBy must be a string"),{valid:t.length===0,errors:t}}static validateSkillProficiencies(e){const t=[];return Array.isArray(e)?(e.forEach((a,i)=>{const n=this.validateSkillProficiency(a);n.valid||t.push(`Proficiency at index ${i}: ${n.errors.join("; ")}`)}),{valid:t.length===0,errors:t}):{valid:!1,errors:["Input must be an array of skill proficiencies"]}}static validateSkillListDefinition(e){const t=[];if(!e||typeof e!="object"||Array.isArray(e))return{valid:!1,errors:["Skill list definition must be an object"]};const a=e;if((!a.class||typeof a.class!="string")&&t.push("Skill list definition must have a class (string)"),typeof a.skillCount!="number"?t.push("Skill list definition must have a skillCount (number)"):(a.skillCount<0||!Number.isInteger(a.skillCount))&&t.push(`Skill list skillCount must be a non-negative integer (got: ${a.skillCount})`),!Array.isArray(a.availableSkills))t.push("Skill list definition must have availableSkills (array)");else{for(const i of a.availableSkills)typeof i!="string"?t.push(`availableSkills must contain only strings (got: ${typeof i})`):this.isValidSkillId(i)||t.push(`availableSkills contains invalid skill ID format: "${i}"`);typeof a.skillCount=="number"&&a.skillCount>a.availableSkills.length&&t.push(`skillCount (${a.skillCount}) cannot exceed availableSkills length (${a.availableSkills.length})`)}return a.hasExpertise!==void 0&&typeof a.hasExpertise!="boolean"&&t.push("Skill list hasExpertise must be a boolean"),a.expertiseCount!==void 0&&(typeof a.expertiseCount!="number"?t.push("Skill list expertiseCount must be a number"):a.expertiseCount<0||!Number.isInteger(a.expertiseCount)?t.push(`expertiseCount must be a non-negative integer (got: ${a.expertiseCount})`):Array.isArray(a.availableSkills)&&a.expertiseCount>a.availableSkills.length&&t.push(`expertiseCount (${a.expertiseCount}) cannot exceed availableSkills length (${a.availableSkills.length})`)),{valid:t.length===0,errors:t}}static isValidSkillId(e){return/^[a-z][a-z0-9_]*$/.test(e)}static isValidAbility(e){return Pe(e)}static validateSkillPrerequisites(e,t){return Ms(e,t)}}function Mr(s){return we.validateSkill(s)}function Dr(s){return we.validateSkills(s)}function Rr(s){return we.validateSkillProficiency(s)}function Pr(s){return we.validateSkillProficiencies(s)}function xr(s){return we.validateSkillListDefinition(s)}function Nr(s,e){return we.validateSkillPrerequisites(s,e)}const Yt=["Abjuration","Conjuration","Divination","Enchantment","Evocation","Illusion","Necromancy","Transmutation"],Zt=[0,1,2,3,4,5,6,7,8,9],bi=["V","S","M"];class De{static validateSpell(e){const t=[];if(!e||typeof e!="object"||Array.isArray(e))return{valid:!1,errors:["Spell must be an object"]};const a=e;if((!a.name||typeof a.name!="string")&&t.push("Spell must have a valid name (string)"),typeof a.level!="number"?t.push("Spell must have a level (number)"):Zt.includes(a.level)||t.push(`Invalid spell level: "${a.level}". Must be one of: ${Zt.join(", ")}`),!a.school||typeof a.school!="string"?t.push("Spell must have a school (string)"):Yt.includes(a.school)||t.push(`Invalid school: "${a.school}". Must be one of: ${Yt.join(", ")}`),(!a.casting_time||typeof a.casting_time!="string")&&t.push("Spell must have a casting_time (string)"),(!a.range||typeof a.range!="string")&&t.push("Spell must have a range (string)"),!Array.isArray(a.components))t.push("Spell must have components (array)");else for(const n of a.components)typeof n!="string"?t.push(`Spell component must be a string (got: ${typeof n})`):bi.includes(n)||t.push(`Invalid spell component: "${n}". Must be one of: ${bi.join(", ")}`);(!a.duration||typeof a.duration!="string")&&t.push("Spell must have a duration (string)"),a.id!==void 0&&typeof a.id!="string"&&t.push("Spell id must be a string"),a.description!==void 0&&typeof a.description!="string"&&t.push("Spell description must be a string");const i=Be({icon:a.icon,image:a.image});if(t.push(...i.map(n=>`Spell ${n}`)),a.prerequisites!==void 0){const n=this.validatePrerequisites(a.prerequisites);n.valid||t.push(...n.errors.map(r=>`Prerequisites: ${r}`))}return{valid:t.length===0,errors:t}}static validateSpells(e){const t=[];return Array.isArray(e)?(e.forEach((a,i)=>{const n=this.validateSpell(a);n.valid||t.push(`Spell at index ${i}: ${n.errors.join("; ")}`)}),{valid:t.length===0,errors:t}):{valid:!1,errors:["Input must be an array of spells"]}}static validatePrerequisites(e){return Er(e)}static validateSpellPrerequisites(e,t){return Ms(e,t)}static isValidAbility(e){return Pe(e)}static isValidSchool(e){return Yt.includes(e)}static isValidSpellLevel(e){return Zt.includes(e)}}function $r(s){return De.validateSpell(s)}function Lr(s){return De.validateSpells(s)}function Or(s){return De.validatePrerequisites(s)}function Br(s,e){return De.validateSpellPrerequisites(s,e)}class pe{constructor(){this.allSkillsCache=null,this.abilityCache=null,this.categoryCache=null,this.manager=R.getInstance()}static getInstance(){return pe.instance||(pe.instance=new pe),pe.instance}invalidateCache(){this.allSkillsCache=null,this.abilityCache=null,this.categoryCache=null}getSkill(e){return this.getAllSkills().find(a=>a.id===e)}getAllSkills(){if(!this.allSkillsCache){const e=this.manager.get("skills");this.allSkillsCache=e}return this.allSkillsCache}getSkillsByAbility(e){if(!this.abilityCache){this.abilityCache=new Map;const t=this.getAllSkills();for(const a of t)this.abilityCache.has(a.ability)||this.abilityCache.set(a.ability,[]),this.abilityCache.get(a.ability).push(a)}return this.abilityCache.get(e)||[]}getSkillsByCategory(e){if(!this.categoryCache){this.categoryCache=new Map;const t=this.getAllSkills();for(const a of t)if(a.categories&&a.categories.length>0)for(const i of a.categories)this.categoryCache.has(i)||this.categoryCache.set(i,[]),this.categoryCache.get(i).push(a)}return this.categoryCache.get(e)||[]}getCategories(){const e=this.getAllSkills(),t=new Set;for(const a of e)if(a.categories)for(const i of a.categories)t.add(i);return Array.from(t)}getSkillsBySource(e){return this.getAllSkills().filter(t=>t.source===e)}isValidSkill(e){return this.getSkill(e)!==void 0}validateSkill(e){return we.validateSkill(e)}validatePrerequisites(e,t){return we.validateSkillPrerequisites(e.prerequisites,t)}getQueryStats(){const e=this.getAllSkills(),t=e.filter(r=>r.source==="default"),a=e.filter(r=>r.source==="custom"),i={STR:0,DEX:0,CON:0,INT:0,WIS:0,CHA:0};for(const r of e)i[r.ability]++;const n=this.getCategories();return{totalSkills:e.length,defaultSkills:t.length,customSkills:a.length,skillsByAbility:i,categories:n}}getSkillCount(){return this.getAllSkills().length}getAvailableSkills(e){return this.getAllSkills().filter(t=>t.prerequisites?this.validatePrerequisites(t,e).valid:!0)}}function Fr(){return pe.getInstance()}const it=["STR","DEX","CON","INT","WIS","CHA"],wi=["weapon","armor","item","box"],Si=["common","uncommon","rare","very_rare","legendary"],_i=["stat_bonus","skill_proficiency","ability_unlock","passive_modifier","special_property","damage_bonus","stat_requirement"],Ai=["vs_creature_type","at_time_of_day","wielder_race","wielder_class","while_equipped","on_hit","on_damage_taken","custom"],ki=["day","night","dawn","dusk"],Ti=["proficient","expertise"],Ci=["default","custom"],yt=/^\d+d\d+([+-]\d+)?$/,Hr=/^range_\d+_\d+$/;class Dt{static validateEquipment(e){const t=[];if((!e.name||typeof e.name!="string")&&t.push("Equipment must have a valid name (required)"),(!e.type||!wi.includes(e.type))&&t.push(`Equipment type must be one of: ${wi.join(", ")}`),(!e.rarity||!Si.includes(e.rarity))&&t.push(`Equipment rarity must be one of: ${Si.join(", ")}`),(typeof e.weight!="number"||e.weight<0)&&t.push("Equipment weight must be a non-negative number"),e.source!==void 0&&!Ci.includes(e.source)&&t.push(`Equipment source must be one of: ${Ci.join(", ")}`),e.properties)if(!Array.isArray(e.properties))t.push("Equipment properties must be an array");else for(let n=0;n<e.properties.length;n++){const r=this.validateProperty(e.properties[n]);r.valid||t.push(`Property at index ${n}: ${r.errors?.join(", ")}`)}if(e.grantsFeatures)if(!Array.isArray(e.grantsFeatures))t.push("grantsFeatures must be an array");else for(let n=0;n<e.grantsFeatures.length;n++){const r=e.grantsFeatures[n],o=this.validateFeatureReference(r,n);o.valid||t.push(...o.errors||[])}if(e.grantsSkills)if(!Array.isArray(e.grantsSkills))t.push("grantsSkills must be an array");else for(let n=0;n<e.grantsSkills.length;n++){const r=e.grantsSkills[n],o=this.validateSkillReference(r.skillId,n);o.valid||t.push(...o.errors||[]),r.level&&!Ti.includes(r.level)&&t.push(`grantsSkills[${n}]: proficiency level must be 'proficient' or 'expertise'`)}if(e.grantsSpells)if(!Array.isArray(e.grantsSpells))t.push("grantsSpells must be an array");else for(let n=0;n<e.grantsSpells.length;n++){const r=e.grantsSpells[n];(!r.spellId||typeof r.spellId!="string")&&t.push(`grantsSpells[${n}]: must have a valid spellId`),r.uses!==void 0&&(typeof r.uses!="number"||r.uses<0)&&t.push(`grantsSpells[${n}]: uses must be a non-negative number`),r.level!==void 0&&(typeof r.level!="number"||r.level<0)&&t.push(`grantsSpells[${n}]: level must be a non-negative number`)}if(e.damage){const n=this.validateDamageInfo(e.damage);n.valid||t.push(...n.errors||[])}const a=e.acBonus??e.armor_class;if(a!==void 0&&typeof a!="number"&&t.push("AC bonus must be a number"),e.weaponProperties&&(Array.isArray(e.weaponProperties)||t.push("weaponProperties must be an array")),e.spawnWeight!==void 0){const n=this.validateSpawnWeight(e.spawnWeight);n.valid||t.push(...n.errors||[])}if(e.templateId!==void 0&&typeof e.templateId!="string"&&t.push("templateId must be a string"),e.tags&&(Array.isArray(e.tags)||t.push("tags must be an array")),e.type==="box"&&!e.boxContents&&t.push('Equipment of type "box" must have a boxContents property'),e.boxContents!==void 0){const n=this.validateBoxContents(e.boxContents);n.valid||t.push(...n.errors||[])}const i=Be({icon:e.icon,image:e.image});return t.push(...i.map(n=>`Equipment ${n}`)),{valid:t.length===0,errors:t.length>0?t:void 0}}static validateProperty(e){const t=[];if((!e.type||!_i.includes(e.type))&&t.push(`Property type must be one of: ${_i.join(", ")}`),(!e.target||typeof e.target!="string")&&t.push("Property must have a valid target"),e.value===void 0)t.push("Property must have a value");else{const a=this.validatePropertyValue(e.type,e.target,e.value);a.valid||t.push(...a.errors||[])}if(e.condition){const a=this.validateCondition(e.condition);a.valid||t.push(...a.errors||[])}return e.stackable!==void 0&&typeof e.stackable!="boolean"&&t.push("stackable must be a boolean"),e.description!==void 0&&typeof e.description!="string"&&t.push("description must be a string"),{valid:t.length===0,errors:t.length>0?t:void 0}}static validatePropertyValue(e,t,a){const i=[];switch(e){case"stat_bonus":it.includes(t)||i.push(`stat_bonus target must be a valid ability: ${it.join(", ")}`),typeof a!="number"&&i.push("stat_bonus value must be a number");break;case"skill_proficiency":typeof t!="string"?i.push("skill_proficiency target must be a string"):pe.getInstance().isValidSkill(t)||i.push(`skill_proficiency target must be a valid skill ID: "${t}" not found in SkillQuery`),a!=="proficient"&&a!=="expertise"&&typeof a!="boolean"&&i.push('skill_proficiency value must be "proficient", "expertise", or a boolean');break;case"ability_unlock":(typeof t!="string"||t.length===0)&&i.push("ability_unlock must have a valid target"),typeof a!="boolean"&&typeof a!="number"&&i.push("ability_unlock value must be a boolean or number");break;case"passive_modifier":!["ac","armor_class","speed","max_hp","hp_max","initiative","saving_throws","attack_roll","damage_roll"].includes(t)&&!it.includes(t)&&pe.getInstance().isValidSkill(t),typeof a!="number"&&typeof a!="boolean"&&i.push("passive_modifier value must be a number or boolean");break;case"stat_requirement":it.includes(t)||i.push(`stat_requirement target must be a valid ability: ${it.join(", ")}`),typeof a!="number"&&i.push("stat_requirement value must be a number (minimum score)");break;case"special_property":(typeof t!="string"||t.length===0)&&i.push("special_property must have a valid target");break;case"damage_bonus":(typeof t!="string"||t.length===0)&&i.push("damage_bonus must have a valid target (damage type)"),typeof a=="string"&&!yt.test(a)?i.push('damage_bonus value must be a dice format (e.g., "1d6") or a number'):typeof a!="number"&&typeof a!="string"&&i.push("damage_bonus value must be a number or dice string");break;default:i.push(`Unknown property type: ${e}`)}return{valid:i.length===0,errors:i.length>0?i:void 0}}static validateCondition(e){const t=[];(!e.type||!Ai.includes(e.type))&&t.push(`Condition type must be one of: ${Ai.join(", ")}`);const a=e.type,i=e.value;switch(a){case"at_time_of_day":ki.includes(e.value)||t.push(`at_time_of_day value must be one of: ${ki.join(", ")}`);break;case"wielder_race":case"wielder_class":case"vs_creature_type":(typeof i!="string"||i.length===0)&&t.push(`${a} condition must have a valid string value`);break;case"while_equipped":case"on_hit":case"on_damage_taken":typeof i!="boolean"&&t.push(`${a} condition must have a boolean value`);break;case"custom":(typeof i!="string"||i.length===0)&&t.push("custom condition must have a valid value string"),(!e.description||typeof e.description!="string")&&t.push("custom condition must have a description");break;default:t.push(`Unknown condition type: ${a}`)}return{valid:t.length===0,errors:t.length>0?t:void 0}}static validateFeatureReference(e,t){const a=[];if(typeof e=="string"){const i=re.getInstance(),n=i.getClassFeatureById(e),r=n?void 0:i.getRacialTraitById(e);!n&&!r&&a.push(`grantsFeatures[${t}]: Feature "${e}" not found in FeatureQuery`)}else if(typeof e=="object"&&e!==null){if((!e.id||typeof e.id!="string")&&a.push(`grantsFeatures[${t}]: Mini-feature must have a valid id`),(!e.name||typeof e.name!="string")&&a.push(`grantsFeatures[${t}]: Mini-feature must have a valid name`),(!e.description||typeof e.description!="string")&&a.push(`grantsFeatures[${t}]: Mini-feature must have a valid description`),!e.effects||!Array.isArray(e.effects))a.push(`grantsFeatures[${t}]: Mini-feature must have an effects array`);else for(let i=0;i<e.effects.length;i++){const n=this.validateProperty(e.effects[i]);n.valid||a.push(`grantsFeatures[${t}].effects[${i}]: ${n.errors?.join(", ")}`)}e.source!=="equipment_inline"&&a.push(`grantsFeatures[${t}]: Mini-feature must have source: 'equipment_inline'`)}else a.push(`grantsFeatures[${t}]: Feature reference must be a string or object`);return{valid:a.length===0,errors:a.length>0?a:void 0}}static validateEquipmentFeatureReference(e){const t=re.getInstance(),a=t.getClassFeatureById(e),i=a?void 0:t.getRacialTraitById(e);return!!(a||i)}static validateSkillReference(e,t){const a=[],i=t!==void 0?`grantsSkills[${t}]`:"skill";return!e||typeof e!="string"?(a.push(`${i}: must have a valid skillId`),{valid:!1,errors:a}):(pe.getInstance().isValidSkill(e)||a.push(`${i}: Skill "${e}" not found in SkillQuery`),{valid:a.length===0,errors:a.length>0?a:void 0})}static validateEquipmentSkillReference(e){return pe.getInstance().isValidSkill(e)}static validateDamageInfo(e){const t=[];if(!e)return{valid:!0};if(typeof e=="string"){const a=e.trim().split(/\s+/);if(a.length<2)t.push(`Damage string must be in format "NdM type" (e.g., "1d8 slashing"), got: "${e}"`);else{const i=a[0];yt.test(i)||t.push(`Damage dice must be in format "NdM" (e.g., "1d8"), got: ${i}`)}return{valid:t.length===0,errors:t.length>0?t:void 0}}return!e.dice||typeof e.dice!="string"?t.push('Damage must have a valid dice string (e.g., "1d8")'):yt.test(e.dice)||t.push(`Damage dice must be in format "NdM" (e.g., "1d8", "2d6"), got: ${e.dice}`),(!e.damageType||typeof e.damageType!="string")&&t.push("Damage must have a valid damageType"),e.versatile!==void 0&&(typeof e.versatile!="string"?t.push("Damage versatile must be a string (dice format)"):yt.test(e.versatile)||t.push(`Damage versatile must be in dice format "NdM" (e.g., "1d10"), got: ${e.versatile}`)),{valid:t.length===0,errors:t.length>0?t:void 0}}static validateSpawnWeight(e){const t=[];return typeof e!="number"?t.push("Spawn weight must be a number"):e<0?t.push("Spawn weight must be non-negative (0 = never random, still usable by game logic)"):Number.isFinite(e)||t.push("Spawn weight must be a finite number"),{valid:t.length===0,errors:t.length>0?t:void 0}}static validateBoxContents(e){const t=[];if(!e||typeof e!="object")return t.push("boxContents must be an object"),{valid:!1,errors:t};if(!Array.isArray(e.drops))return t.push("boxContents.drops must be an array"),{valid:!1,errors:t};if(e.consumeOnOpen!==void 0&&typeof e.consumeOnOpen!="boolean"&&t.push("boxContents.consumeOnOpen must be a boolean"),e.openRequirements!==void 0)if(!Array.isArray(e.openRequirements))t.push("boxContents.openRequirements must be an array");else for(let a=0;a<e.openRequirements.length;a++){const i=this.validateBoxOpenRequirement(e.openRequirements[a]);i.valid||t.push(...(i.errors||[]).map(n=>`openRequirements[${a}]: ${n}`))}for(let a=0;a<e.drops.length;a++){const i=e.drops[a];if(!i||!Array.isArray(i.pool)){t.push(`boxContents.drops[${a}]: pool must be an array`);continue}if(i.pool.length===0){t.push(`boxContents.drops[${a}]: pool must not be empty`);continue}let n=0;for(let r=0;r<i.pool.length;r++){const o=i.pool[r],l=`boxContents.drops[${a}].pool[${r}]`;typeof o.weight!="number"||o.weight<0?t.push(`${l}: weight must be a non-negative number`):n+=o.weight,o.itemName!==void 0&&o.gold!==void 0&&t.push(`${l}: itemName and gold are mutually exclusive`),o.itemName===void 0&&o.gold===void 0&&t.push(`${l}: must have either itemName or gold`),o.itemName!==void 0&&(typeof o.itemName!="string"||o.itemName.length===0?t.push(`${l}: itemName must be a non-empty string`):R.getInstance().get("equipment").some(h=>h.name===o.itemName)||t.push(`${l}: itemName "${o.itemName}" not found in equipment registry`)),o.gold!==void 0&&(typeof o.gold!="number"||o.gold<0)&&t.push(`${l}: gold must be a non-negative number`),o.quantity!==void 0&&(typeof o.quantity!="number"||o.quantity<1||!Number.isInteger(o.quantity))&&t.push(`${l}: quantity must be a positive integer`)}i.pool.length>0&&Math.abs(n-100)>.001&&t.push(`boxContents.drops[${a}]: pool weights sum to ${n}, expected 100`)}return{valid:t.length===0,errors:t.length>0?t:void 0}}static validateBoxOpenRequirement(e){const t=[];return!e||typeof e!="object"?(t.push("BoxOpenRequirement must be an object"),{valid:!1,errors:t}):(!e.itemName||typeof e.itemName!="string"||e.itemName.length===0?t.push("BoxOpenRequirement.itemName must be a non-empty string"):R.getInstance().get("equipment").some(r=>r.name===e.itemName)||t.push(`BoxOpenRequirement.itemName "${e.itemName}" not found in equipment registry`),e.quantity!==void 0&&(typeof e.quantity!="number"||e.quantity<1||!Number.isInteger(e.quantity))&&t.push("BoxOpenRequirement.quantity must be a positive integer"),{valid:t.length===0,errors:t.length>0?t:void 0})}static validateModification(e){const t=[];if((!e.id||typeof e.id!="string")&&t.push("Modification must have a valid id"),(!e.name||typeof e.name!="string")&&t.push("Modification must have a valid name"),(!e.appliedAt||typeof e.appliedAt!="string")&&t.push("Modification must have a valid appliedAt timestamp"),(!e.source||typeof e.source!="string")&&t.push("Modification must have a valid source"),!e.properties||!Array.isArray(e.properties))t.push("Modification must have a properties array");else for(let a=0;a<e.properties.length;a++){const i=this.validateProperty(e.properties[a]);i.valid||t.push(`Modification properties[${a}]: ${i.errors?.join(", ")}`)}if(e.addsFeatures)if(!Array.isArray(e.addsFeatures))t.push("Modification addsFeatures must be an array");else for(let a=0;a<e.addsFeatures.length;a++){const i=this.validateFeatureReference(e.addsFeatures[a],a);i.valid||t.push(`Modification addsFeatures[${a}]: ${i.errors?.join(", ")}`)}if(e.addsSkills)if(!Array.isArray(e.addsSkills))t.push("Modification addsSkills must be an array");else for(let a=0;a<e.addsSkills.length;a++){const i=e.addsSkills[a],n=this.validateSkillReference(i.skillId,a);n.valid||t.push(...(n.errors||[]).map(r=>`Modification ${r}`)),i.level&&!Ti.includes(i.level)&&t.push(`Modification addsSkills[${a}]: proficiency level must be 'proficient' or 'expertise'`)}return e.addsSpells&&(Array.isArray(e.addsSpells)||t.push("Modification addsSpells must be an array")),{valid:t.length===0,errors:t.length>0?t:void 0}}static validateMiniFeature(e){const t=[];if((!e.id||typeof e.id!="string")&&t.push("Mini-feature must have a valid id"),(!e.name||typeof e.name!="string")&&t.push("Mini-feature must have a valid name"),(!e.description||typeof e.description!="string")&&t.push("Mini-feature must have a valid description"),!e.effects||!Array.isArray(e.effects))t.push("Mini-feature must have an effects array");else for(let a=0;a<e.effects.length;a++){const i=this.validateProperty(e.effects[a]);i.valid||t.push(`Mini-feature effects[${a}]: ${i.errors?.join(", ")}`)}return e.source!=="equipment_inline"&&t.push('Mini-feature must have source: "equipment_inline"'),{valid:t.length===0,errors:t.length>0?t:void 0}}static validateACBonus(e){const t=[];return typeof e!="number"?t.push("AC bonus must be a number"):e<0?t.push("AC bonus cannot be negative"):Number.isFinite(e)||t.push("AC bonus must be a finite number"),{valid:t.length===0,errors:t.length>0?t:void 0}}static validateWeaponProperties(e){const t=[];if(!Array.isArray(e))return t.push("Weapon properties must be an array"),{valid:!1,errors:t};for(let a=0;a<e.length;a++){const i=e[a];if(typeof i!="string"){t.push(`Weapon property at index ${a} must be a string`);continue}i.startsWith("range_")&&(Hr.test(i)||t.push(`Weapon property "${i}" at index ${a} must be in format "range_MIN_MAX" (e.g., "range_20_60")`))}return{valid:t.length===0,errors:t.length>0?t:void 0}}}const Ce={Barbarian:{primary_ability:"STR",hit_die:12,saving_throws:["STR","CON"],is_spellcaster:!1,skill_count:2,available_skills:["athletics","animal_handling","intimidation","nature","perception","survival"],has_expertise:!1,description:"A fierce warrior of primitive background who can enter a battle rage. Barbarians draw on primal fury to unleash devastating attacks, shrug off wounds that would fell others, and channel their totem spirits' power. They thrive on the front lines, where their rage makes them unstoppable forces of nature."},Bard:{primary_ability:"CHA",hit_die:8,saving_throws:["DEX","CHA"],is_spellcaster:!0,skill_count:3,available_skills:["athletics","acrobatics","sleight_of_hand","stealth","arcana","history","investigation","nature","religion","animal_handling","insight","medicine","perception","survival","deception","intimidation","performance","persuasion"],has_expertise:!0,expertise_count:2,description:"An inspiring magician whose power echoes the music of creation. Bards weave magic through music, poetry, and performance, inspiring allies, demoralizing enemies, and wielding versatile spells. They are jack-of-all-trades who can fill any role, from frontline support to devastating spellcaster."},Cleric:{primary_ability:"WIS",hit_die:8,saving_throws:["WIS","CHA"],is_spellcaster:!0,skill_count:2,available_skills:["history","insight","medicine","persuasion","religion"],has_expertise:!1,description:"A priestly champion who wields divine magic in service of a higher power. Clerics serve as intermediaries between the mortal world and the divine, wielding healing and destructive magic granted by their deities. They can wear heavy armor, wield powerful weapons, and specialize in different domains of divine influence."},Druid:{primary_ability:"WIS",hit_die:8,saving_throws:["INT","WIS"],is_spellcaster:!0,skill_count:2,available_skills:["arcana","animal_handling","insight","medicine","nature","perception","religion","survival"],has_expertise:!1,description:"A priest of the Old Faith, wielding the powers of nature and adopting animal forms. Druids revere nature and gain their magic from the natural world, commanding the elements, transforming into beasts, and calling upon the forces of life and growth. They serve as guardians of the wild places and mediators between civilization and nature."},Fighter:{primary_ability:"STR",hit_die:10,saving_throws:["STR","CON"],is_spellcaster:!1,skill_count:2,available_skills:["acrobatics","animal_handling","athletics","history","insight","intimidation","perception","survival"],has_expertise:!1,description:"A master of martial combat, skilled with a variety of weapons and armor. Fighters learn countless fighting techniques, specializing in certain styles of combat. They are the backbone of any adventuring party, capable of withstanding tremendous punishment while dishing out devastating damage through martial prowess alone."},Monk:{primary_ability:"DEX",hit_die:8,saving_throws:["STR","DEX"],is_spellcaster:!1,skill_count:2,available_skills:["acrobatics","athletics","history","insight","religion","stealth"],has_expertise:!1,description:"A master of martial arts, harnessing the power of the body in pursuit of physical and spiritual perfection. Monks use their bodies as weapons, channeling ki energy to perform extraordinary feats. They are agile warriors who can strike with deadly precision, evade attacks with supernatural grace, and harness mystical abilities."},Paladin:{primary_ability:"STR",hit_die:10,saving_throws:["WIS","CHA"],is_spellcaster:!0,skill_count:2,available_skills:["athletics","insight","intimidation","medicine","persuasion","religion"],has_expertise:!1,description:"A holy warrior bound to a sacred oath. Paladins combine martial prowess with divine magic, smiting foes with holy fire and healing allies with sacred power. They are paragons of their beliefs, radiating auras that protect allies and punish enemies. Their strength comes from their unwavering devotion to their oath."},Ranger:{primary_ability:"DEX",hit_die:10,saving_throws:["STR","DEX"],is_spellcaster:!0,skill_count:3,available_skills:["animal_handling","athletics","insight","investigation","nature","perception","stealth","survival"],has_expertise:!1,description:"A warrior who combats threats on the edges of civilization. Rangers are hunters and trackers who know the wilderness intimately, moving swiftly through natural terrain and striking from a distance. They develop supernatural connections to their chosen enemies and environments, gaining deadly precision against favored foes."},Rogue:{primary_ability:"DEX",hit_die:8,saving_throws:["DEX","INT"],is_spellcaster:!1,skill_count:4,available_skills:["acrobatics","athletics","deception","insight","intimidation","investigation","perception","performance","persuasion","sleight_of_hand","stealth"],has_expertise:!0,expertise_count:2,description:"A scoundrel who uses stealth and trickery to overcome obstacles and enemies. Rogues excel at skills, finding traps, and striking devastating blows from hiding. Whether as thieves, assassins, or spies, they are versatile problem-solvers who can disable threats before allies even know they exist."},Sorcerer:{primary_ability:"CHA",hit_die:6,saving_throws:["CON","CHA"],is_spellcaster:!0,skill_count:2,available_skills:["arcana","deception","insight","intimidation","persuasion","religion"],has_expertise:!1,description:"A spellcaster who draws on inherent magic from a supernatural bloodline or cosmic source. Sorcerers have no need for spellbooks—their power comes from within, born of draconic heritage, wild magic, or other supernatural origins. They can manipulate and metamagic their spells in ways other casters cannot."},Warlock:{primary_ability:"CHA",hit_die:8,saving_throws:["WIS","CHA"],is_spellcaster:!0,skill_count:2,available_skills:["arcana","deception","history","intimidation","investigation","nature","religion"],has_expertise:!1,description:"A wielder of magic that is derived from a bargain with an extraplanar entity. Warlocks gain their powers through pacts with powerful beings—fiends, fey, Great Old Ones, or other eldritch entities. In exchange for service, they receive mystical abilities and can cast eldritch invocations that blur the line between spell and supernatural ability."},Wizard:{primary_ability:"INT",hit_die:6,saving_throws:["INT","WIS"],is_spellcaster:!0,skill_count:2,available_skills:["arcana","history","insight","investigation","medicine","religion"],has_expertise:!1,description:"A scholarly magic-user capable of manipulating the structures of reality. Wizards are masters of arcane magic, studying spellbooks to unlock the secrets of the universe. They specialize in specific schools of magic, from evocation to illusion, and have access to the widest variety of spells of any class."}},Ds=["Barbarian","Bard","Cleric","Druid","Fighter","Monk","Paladin","Ranger","Rogue","Sorcerer","Warlock","Wizard"],Rs={Barbarian:{primary:"bass",secondary:"amplitude",bass:1,amplitude:.7},Fighter:{primary:"bass",secondary:"amplitude",bass:.9,amplitude:.8},Paladin:{primary:"bass",secondary:"mid",bass:.8,mid:.5},Rogue:{primary:"treble",treble:1},Ranger:{primary:"treble",secondary:"bass",treble:.8,bass:.5},Monk:{primary:"treble",secondary:"mid",treble:.7,mid:.6},Wizard:{primary:"mid",mid:1},Cleric:{primary:"mid",secondary:"amplitude",mid:.8,amplitude:.6},Druid:{primary:"mid",secondary:"bass",mid:.7,bass:.6},Bard:{primary:"amplitude",secondary:"mid",tertiary:"treble",amplitude:.8,mid:.6,treble:.3},Sorcerer:{primary:"amplitude",secondary:"chaos",amplitude:.9},Warlock:{primary:"amplitude",secondary:"treble",amplitude:.7,treble:.5}},Ia={"Acid Splash":{name:"Acid Splash",level:0,school:"Evocation",casting_time:"1 action",range:"60 feet",components:["V","S"],duration:"Instantaneous",description:"Hurls a bubble of acid at a creature or point. On hit, deals acid damage to creatures in a small splash radius. Dexterity save for half damage."},"Fire Bolt":{name:"Fire Bolt",level:0,school:"Evocation",casting_time:"1 action",range:"120 feet",components:["V","S"],duration:"Instantaneous",description:"Hurls a mote of fire at a creature or object. On hit, deals fire damage. One of the most reliable damaging cantrips for ranged combat."},Light:{name:"Light",level:0,school:"Evocation",casting_time:"1 action",range:"Touch",components:["V","M"],duration:"1 hour",description:"Touch one object to make it shed bright light in a 20-foot radius and dim light for another 20 feet. Completely illuminates dark areas and can be moved with the touched object."},"Mage Hand":{name:"Mage Hand",level:0,school:"Conjuration",casting_time:"1 action",range:"30 feet",components:["V","S"],duration:"Concentration, up to 1 minute",description:"Conjures a spectral, floating hand at a point you choose. The hand can manipulate objects, open unlocked doors, or stow items. It cannot attack or activate magical items."},Mending:{name:"Mending",level:0,school:"Transmutation",casting_time:"1 minute",range:"Touch",components:["V","S","M"],duration:"Instantaneous",description:"Repairs a single break or tear in an object you touch, such as a broken chain link, two halves of a broken key, or a torn cloak. Cannot restore magic items or creatures."},Message:{name:"Message",level:0,school:"Transmutation",casting_time:"1 action",range:"120 feet",components:["V","S","M"],duration:"1 round",description:"Points your finger toward a creature within range and whisper a message. The target hears the message and can reply in a whisper that only you can hear. Perfect for covert communication."},Prestidigitation:{name:"Prestidigitation",level:0,school:"Transmutation",casting_time:"1 action",range:"10 feet",components:["V","S"],duration:"Up to 1 hour",description:"Performs minor magical tricks for your amusement. Can chill, warm, flavor, clean, or soil small objects. Create harmless sensory effects like colored lights or faint sounds."},"Sacred Flame":{name:"Sacred Flame",level:0,school:"Evocation",casting_time:"1 action",range:"60 feet",components:["V","S"],duration:"Instantaneous",description:"Calls down flame-like radiance on a target. The target must succeed on a Dexterity save or take radiant damage. This spell can be cast from behind cover and ignores half and three-quarters cover."},"Shocking Grasp":{name:"Shocking Grasp",level:0,school:"Evocation",casting_time:"1 action",range:"Touch",components:["V","S"],duration:"Instantaneous",description:"Lightning springs from your hand to deliver a shock to a creature. You have advantage on the attack roll if the target is wearing metal armor. The shock cannot be used to make reactions of opportunity."},"Vicious Mockery":{name:"Vicious Mockery",level:0,school:"Enchantment",casting_time:"1 action",range:"60 feet",components:["V"],duration:"Instantaneous",description:"Utters a cruel insult to a creature within range. The target must make a Wisdom save or take psychic damage. A creative and subtle way to attack enemies in social situations."},"Burning Hands":{name:"Burning Hands",level:1,school:"Evocation",casting_time:"1 action",range:"Self (15-foot cone)",components:["V","S"],duration:"Instantaneous",description:"Ignites a cone of fire from your outstretched hands. Each creature in the area must make a Dexterity save or take fire damage. Devastating damage at close range but dangerous to allies in the cone."},"Charm Person":{name:"Charm Person",level:1,school:"Enchantment",casting_time:"1 action",range:"30 feet",components:["V","S"],duration:"1 hour",description:"Attempts to charm a humanoid you can see. The target must make a Wisdom save or be charmed by you. While charmed, it regards you as a friendly acquaintance. The effect ends if you or your allies do anything harmful to it."},"Cure Wounds":{name:"Cure Wounds",level:1,school:"Evocation",casting_time:"1 action",range:"Touch",components:["V","S"],duration:"Instantaneous",description:"A creature you touch regains hit points equal to 1d8 plus your spellcasting ability modifier. The amount increases by 1d8 for each slot level above 1st. This spell has no effect on undead or constructs."},"Detect Magic":{name:"Detect Magic",level:1,school:"Divination",casting_time:"1 action",range:"Self",components:["V","S"],duration:"Concentration, up to 10 minutes",description:"For the duration, you sense the presence of magic within 30 feet of you. You can identify the school of magic and locate the source, but not learn its exact properties. Essential for identifying magical auras."},"Disguise Self":{name:"Disguise Self",level:1,school:"Illusion",casting_time:"1 action",range:"Self",components:["V","S"],duration:"1 hour",description:"Makes you—including your clothing, armor, weapons, and other belongings—look different until the spell ends. You can change your height, weight, facial features, voice, and even appear as another creature of the same size."},"Expeditious Retreat":{name:"Expeditious Retreat",level:1,school:"Transmutation",casting_time:"1 bonus action",range:"Self",components:["V","S"],duration:"Concentration, up to 10 minutes",description:"Doubles your walking speed and grants the ability to take the Disengage or Hide action as a bonus action on each turn. Essential for escape or closing the distance safely."},"False Life":{name:"False Life",level:1,school:"Necromancy",casting_time:"1 action",range:"Self",components:["V","S","M"],duration:"1 hour",description:"Bolsters your vitality with necromantic magic, gaining temporary hit points equal to 1d4 plus your spellcasting ability modifier. The temporary hit points increase by 1d4 for each slot level above 1st."},"Feather Fall":{name:"Feather Fall",level:1,school:"Transmutation",casting_time:"1 reaction",range:"60 feet",components:["V","M"],duration:"1 minute",description:"Choose up to five falling creatures within range. A falling creature's rate of descent slows to 60 feet per round until the spell ends. If the creature lands before the spell ends, it takes no falling damage and lands on its feet."},Grease:{name:"Grease",level:1,school:"Conjuration",casting_time:"1 action",range:"60 feet",components:["V","S","M"],duration:"1 minute",description:"Covers a 10-foot square on the ground in slippery grease. Creatures in the area must succeed on a Dexterity save or fall prone. Creatures entering the area or ending their turn there must also save or fall."},"Healing Word":{name:"Healing Word",level:1,school:"Evocation",casting_time:"1 bonus action",range:"60 feet",components:["V","S"],duration:"Instantaneous",description:"A creature of your choice that you can see within range regains hit points equal to 1d4 plus your spellcasting ability modifier. This spell has no effect on undead or constructs. Essential for saving fallen allies from a distance."},Identify:{name:"Identify",level:1,school:"Divination",casting_time:"1 minute",range:"Touch",components:["V","S","M"],duration:"Instantaneous",description:"Choose one object that you must touch throughout the casting. Learn if it's magically cursed or has been affected by a spell. You learn the properties and how to use them if it's a magic item. Requires a 100gp pearl focus."},"Mage Armor":{name:"Mage Armor",level:1,school:"Abjuration",casting_time:"1 action",range:"Touch",components:["V","S","M"],duration:"8 hours",description:"Touch a willing creature to protect it with a magical force. The target's AC becomes 13 + its Dexterity modifier, minus any magical penalties. The spell ends if the target dons armor or if you dismiss it."},"Magic Missile":{name:"Magic Missile",level:1,school:"Evocation",casting_time:"1 action",range:"120 feet",components:["V","S"],duration:"Instantaneous",description:"Creates three glowing darts of magical force. Each dart hits a creature of your choice that you can see within range. A dart deals 1d4 + 1 force damage. For each slot level above 1st, the spell creates one more dart."},Shield:{name:"Shield",level:1,school:"Abjuration",casting_time:"1 reaction",range:"Self",components:["V","S"],duration:"1 round",description:"An invisible barrier of magical force appears and protects you. Until the start of your next turn, you have a +5 bonus to AC, including against the triggering attack, and you take no damage from magic missile."},Sleep:{name:"Sleep",level:1,school:"Enchantment",casting_time:"1 action",range:"90 feet",components:["V","S","M"],duration:"1 minute",description:"Rolls 5d8; the total is how many hit points of creatures this spell can affect. Creatures in a 20-foot cube within range are affected in ascending order of current hit points. Unconscious creatures fall prone and are incapacitated."},Thunderwave:{name:"Thunderwave",level:1,school:"Evocation",casting_time:"1 action",range:"Self (15-foot cube)",components:["V","S"],duration:"Instantaneous",description:"A wave of thunderous force sweeps out from you. Each creature in the area must make a Constitution save or take thunder damage and be pushed 10 feet away. Unattended objects are also pushed and take damage."},"Acid Arrow":{name:"Acid Arrow",level:2,school:"Evocation",casting_time:"1 action",range:"90 feet",components:["V","S","M"],duration:"Instantaneous",description:"Hurls a bubble of acid at a point you choose within range. On a hit, the target takes acid damage immediately and additional acid damage at the end of its next turn. This secondary damage ignores resistance but not immunity."},"Aganazzar's Scorcher":{name:"Aganazzar's Scorcher",level:2,school:"Evocation",casting_time:"1 action",range:"30 feet",components:["V","S","M"],duration:"Instantaneous",description:"A line of roaring flame erupts from you in a 30-foot line. Each creature in the line must make a Dexterity save or take fire damage. A flamethrower-like effect that excels at controlling narrow corridors."},Blur:{name:"Blur",level:2,school:"Illusion",casting_time:"1 action",range:"Self",components:["V"],duration:"Concentration, up to 1 minute",description:"Your body becomes blurred, shifting and wavering to all who can see you. Until the spell ends, attacks against you have disadvantage, and you have advantage on Dexterity saving throws. Essential defense for spellcasters."},"Detect Thoughts":{name:"Detect Thoughts",level:2,school:"Divination",casting_time:"1 action",range:"Self",components:["V","S","M"],duration:"Concentration, up to 1 minute",description:"For the duration, you can read the thoughts of certain creatures. You initially sense the presence of thinking creatures, then can focus on one to read its surface thoughts. Deeper thoughts require a contest of Intelligence."},Fireball:{name:"Fireball",level:3,school:"Evocation",casting_time:"1 action",range:"150 feet",components:["V","S","M"],duration:"Instantaneous",description:"A bright streak flashes from your pointing finger to a point you choose within range and then blossoms with a low roar into an explosion of flame. Each creature in a 20-foot-radius sphere must make a Dexterity save or take fire damage."},"Hold Person":{name:"Hold Person",level:2,school:"Enchantment",casting_time:"1 action",range:"60 feet",components:["V","S","M"],duration:"Concentration, up to 1 minute",description:"Choose a humanoid that you can see within range. The target must succeed on a Wisdom save or be paralyzed. The spell can target additional humanoids at higher slot levels. Paralyzed creatures are automatically critical hits."},Invisibility:{name:"Invisibility",level:2,school:"Illusion",casting_time:"1 action",range:"Touch",components:["V","S","M"],duration:"Concentration, up to 1 hour",description:"A creature you touch becomes invisible until the spell ends. Anything the target is wearing or carrying is invisible as long as it is on the target's person. The spell ends if the target attacks or casts a spell."},Knock:{name:"Knock",level:2,school:"Transmutation",casting_time:"1 action",range:"60 feet",components:["V"],duration:"Instantaneous",description:"Choose an object that you can see within range. The object can be a door, a box, a chest, a set of manacles, a padlock, or another object that contains a mundane or magical means of preventing access."},"Misty Step":{name:"Misty Step",level:2,school:"Conjuration",casting_time:"1 bonus action",range:"Self",components:["V"],duration:"Instantaneous",description:"Briefly surrounded by silvery mist, you teleport up to 30 feet to an unoccupied space you can see. You can bring along objects as long as their weight doesn't exceed what you can carry. A quick escape or repositioning tool."},"Mirror Image":{name:"Mirror Image",level:2,school:"Illusion",casting_time:"1 action",range:"Self",components:["V","S"],duration:"Concentration, up to 1 minute",description:"Three illusory duplicates of yourself appear in your space. Until the spell ends, the duplicates move with you and mimic your actions, shifting position so it's impossible to track which image is real."},"Scorching Ray":{name:"Scorching Ray",level:2,school:"Evocation",casting_time:"1 action",range:"120 feet",components:["V","S"],duration:"Instantaneous",description:"You create three rays of fire and hurl them at targets within range. You can aim them at one target or several. Make a ranged spell attack for each ray. On a hit, the target takes fire damage."},Shatter:{name:"Shatter",level:2,school:"Evocation",casting_time:"1 action",range:"60 feet",components:["V","S","M"],duration:"Instantaneous",description:"A sudden loud ringing noise, painfully intense, erupts from a point of your choice within range. Each creature in a 10-foot-radius sphere must make a Constitution save or take thunder damage."},Suggestion:{name:"Suggestion",level:2,school:"Enchantment",casting_time:"1 action",range:"30 feet",components:["V","M"],duration:"Concentration, up to 1 minute",description:"Suggest a course of activity to a creature you can see within range and can understand. The target must make a Wisdom save or pursue the course of action. The suggestion must be worded so as to sound reasonable."},"Animate Dead":{name:"Animate Dead",level:3,school:"Necromancy",casting_time:"1 minute",range:"10 feet",components:["V","S","M"],duration:"Instantaneous",description:"This spell creates an undead servant. Choose a pile of bones or a corpse of a Medium or Small humanoid within range. Your spell animates it as a skeleton or a zombie that obeys your commands until destroyed."},Blink:{name:"Blink",level:3,school:"Transmutation",casting_time:"1 action",range:"Self",components:["V","S"],duration:"1 minute",description:"Roll a d20 at the start of each of your turns. On an 11 or higher, you vanish from your current plane of existence and appear in the Ethereal Plane for the round, returning to where you were. Attacks against you have disadvantage while blinking."},Counterspell:{name:"Counterspell",level:3,school:"Abjuration",casting_time:"1 reaction",range:"60 feet",components:["S"],duration:"Instantaneous",description:"You attempt to interrupt a creature in the process of casting a spell. The creature must make a Constitution save. If the spell is 3rd level or lower, it fails and has no effect. Higher level spells require an ability check."},"Dispel Magic":{name:"Dispel Magic",level:3,school:"Abjuration",casting_time:"1 action",range:"120 feet",components:["V","S"],duration:"Instantaneous",description:"Choose one creature, object, or magical effect within range. Any spell of 3rd level or lower on the target ends. For each spell of 4th level or higher, make an ability check using your spellcasting ability."},"Lightning Bolt":{name:"Lightning Bolt",level:3,school:"Evocation",casting_time:"1 action",range:"Self (100-foot line)",components:["V","S","M"],duration:"Instantaneous",description:"A stroke of lightning forming a line 100 feet long and 5 feet wide blasts out from you in a direction you choose. Each creature in the line must make a Dexterity save or take lightning damage."},"Major Image":{name:"Major Image",level:3,school:"Illusion",casting_time:"1 action",range:"120 feet",components:["V","S","M"],duration:"Concentration, up to 10 minutes",description:"You create the image of an object, a creature, or some other visible phenomenon that is no larger than a 20-foot cube. The image appears at a spot within range and lasts for the duration. The image includes sounds, smells, and temperature."},"Sleet Storm":{name:"Sleet Storm",level:3,school:"Evocation",casting_time:"1 action",range:"150 feet",components:["V","S","M"],duration:"Concentration, up to 1 minute",description:"Until the spell ends, freezing rain and sleet fall in a 20-foot-tall cylinder with a 40-foot radius centered on a point you choose within range. The area is heavily obscured, and the ground is difficult terrain."},Telekinesis:{name:"Telekinesis",level:3,school:"Transmutation",casting_time:"1 action",range:"60 feet",components:["V","S"],duration:"Concentration, up to 1 minute",description:"You gain the ability to move or manipulate creatures or objects by thought. When you cast the spell, and as your action each round for the duration, you can exert your will on one creature or object."},"Dimension Door":{name:"Dimension Door",level:4,school:"Conjuration",casting_time:"1 action",range:"500 feet",components:["V"],duration:"Instantaneous",description:"You teleport yourself from your current location to any other spot within range. You arrive at exactly the spot desired. It can be a place you can see, one you can visualize, or one you can describe by stating distance and direction."},"Greater Invisibility":{name:"Greater Invisibility",level:4,school:"Illusion",casting_time:"1 action",range:"Touch",components:["V","S"],duration:"Concentration, up to 1 minute",description:"You or a creature you touch becomes invisible until the spell ends. Anything the target is wearing or carrying is invisible as long as it is on the target's person. Unlike regular invisibility, attacks do not end the spell."},Polymorph:{name:"Polymorph",level:4,school:"Transmutation",casting_time:"1 action",range:"60 feet",components:["V","S","M"],duration:"Concentration, up to 1 hour",description:"Transforms a creature into a different beast. The target's game statistics, including mental ability scores, are replaced by the statistics of the chosen beast. It retains its alignment and personality. The creature assumes the hit points of the new form."},Stoneskin:{name:"Stoneskin",level:4,school:"Abjuration",casting_time:"1 action",range:"Touch",components:["V","S","M"],duration:"Concentration, up to 1 hour",description:"The touched creature's skin becomes as hard as stone and covered in rocky growths. The target's AC becomes 15 + its Dexterity modifier (max 23), and it is resistant to nonmagical bludgeoning, piercing, and slashing damage."},"Cone of Cold":{name:"Cone of Cold",level:5,school:"Evocation",casting_time:"1 action",range:"Self (60-foot cone)",components:["V","S","M"],duration:"Instantaneous",description:"A blast of cold air erupts from your hands. Each creature in a 60-foot cone must make a Constitution saving throw. A creature takes 8d8 cold damage on a failed save, or half as much on a successful one."},"Teleportation Circle":{name:"Teleportation Circle",level:5,school:"Conjuration",casting_time:"1 minute",range:"10 feet",components:["V","M"],duration:"1 round",description:"As you cast the spell, you draw a circle on the ground with the chalk. When you complete the casting, a glowing portal opens within the circle. Any creature that enters the portal instantly appears within 5 feet of the destination circle."}},Rt={Wizard:{cantrips:["Acid Splash","Fire Bolt","Light","Mage Hand","Mending","Message","Prestidigitation","Shocking Grasp"],spells_by_level:{1:["Burning Hands","Charm Person","Cure Wounds","Detect Magic","Disguise Self","Expeditious Retreat","False Life","Feather Fall","Grease","Identify","Mage Armor","Magic Missile","Shield","Sleep","Thunderwave"],2:["Acid Arrow","Blur","Detect Thoughts","Hold Person","Invisibility","Knock","Misty Step","Mirror Image","Scorching Ray","Shatter","Suggestion"],3:["Animate Dead","Blink","Counterspell","Dispel Magic","Lightning Bolt","Major Image","Sleet Storm","Telekinesis"],4:["Dimension Door","Greater Invisibility","Polymorph","Stoneskin"],5:["Cone of Cold","Teleportation Circle"]}},Sorcerer:{cantrips:["Acid Splash","Fire Bolt","Light","Mage Hand","Message","Prestidigitation","Shocking Grasp"],spells_by_level:{1:["Burning Hands","Charm Person","Cure Wounds","Detect Magic","Disguise Self","Expeditious Retreat","False Life","Feather Fall","Grease","Mage Armor","Magic Missile","Shield","Sleep","Thunderwave"],2:["Acid Arrow","Blur","Detect Thoughts","Hold Person","Invisibility","Knock","Misty Step","Mirror Image","Scorching Ray","Shatter","Suggestion"],3:["Animate Dead","Blink","Counterspell","Dispel Magic","Lightning Bolt","Major Image","Sleet Storm"],4:["Dimension Door","Greater Invisibility","Polymorph","Stoneskin"],5:["Cone of Cold","Teleportation Circle"]}},Bard:{cantrips:["Light","Mage Hand","Mending","Message","Prestidigitation","Vicious Mockery"],spells_by_level:{1:["Charm Person","Detect Magic","Disguise Self","Expeditious Retreat","Feather Fall","Grease","Healing Word","Identify","Magic Missile","Sleep","Thunderwave"],2:["Acid Arrow","Blur","Detect Thoughts","Hold Person","Invisibility","Knock","Misty Step","Mirror Image","Scorching Ray","Suggestion"],3:["Animate Dead","Blink","Counterspell","Dispel Magic","Lightning Bolt","Major Image"],4:["Dimension Door","Greater Invisibility","Polymorph"],5:["Teleportation Circle"]}},Cleric:{cantrips:["Light","Mending","Message","Sacred Flame"],spells_by_level:{1:["Burning Hands","Charm Person","Cure Wounds","Detect Magic","Identify","Healing Word","Magic Missile","Shield","Sleep"],2:["Acid Arrow","Blur","Detect Thoughts","Hold Person","Invisibility","Scorching Ray"],3:["Animate Dead","Counterspell","Dispel Magic","Lightning Bolt"],4:["Dimension Door","Polymorph"],5:["Cone of Cold"]}},Druid:{cantrips:["Light","Mending","Prestidigitation"],spells_by_level:{1:["Burning Hands","Cure Wounds","Detect Magic","Expeditious Retreat","Feather Fall","Grease","Healing Word","Identify"],2:["Acid Arrow","Blur","Detect Thoughts","Hold Person","Invisibility","Scorching Ray","Shatter"],3:["Animate Dead","Counterspell","Dispel Magic","Lightning Bolt","Sleet Storm"],4:["Dimension Door","Polymorph"],5:["Cone of Cold","Teleportation Circle"]}},Paladin:{cantrips:[],spells_by_level:{1:["Burning Hands","Charm Person","Cure Wounds","Detect Magic","Feather Fall","Healing Word","Protection","Shield"],2:["Acid Arrow","Hold Person","Scorching Ray"],3:["Counterspell","Dispel Magic","Lightning Bolt"],4:["Dimension Door","Polymorph"],5:["Cone of Cold"]}},Ranger:{cantrips:[],spells_by_level:{1:["Detect Magic","Expeditious Retreat","Feather Fall","Grease","Identify"],2:["Blur","Hold Person","Invisibility","Misty Step"],3:["Blink","Counterspell","Dispel Magic"],4:["Dimension Door","Polymorph"],5:[]}},Warlock:{cantrips:["Acid Splash","Fire Bolt","Mage Hand","Message","Prestidigitation","Shocking Grasp"],spells_by_level:{1:["Burning Hands","Charm Person","Detect Magic","Disguise Self","Expeditious Retreat","False Life","Grease","Hex","Magic Missile"],2:["Acid Arrow","Darkness","Hold Person","Invisibility","Scorching Ray","Shatter"],3:["Counterspell","Dispel Magic","Fireball","Lightning Bolt"],4:["Dimension Door","Greater Invisibility"],5:["Cone of Cold"]}}},oa={Wizard:{1:{1:2},2:{1:3},3:{1:4,2:2},4:{1:4,2:3},5:{1:4,2:3,3:2},6:{1:4,2:3,3:3},7:{1:4,2:3,3:3,4:1},8:{1:4,2:3,3:3,4:2},9:{1:4,2:3,3:3,4:3,5:1},10:{1:4,2:3,3:3,4:3,5:2},11:{1:4,2:3,3:3,4:3,5:2,6:1},12:{1:4,2:3,3:3,4:3,5:2,6:1},13:{1:4,2:3,3:3,4:3,5:2,6:1,7:1},14:{1:4,2:3,3:3,4:3,5:2,6:1,7:1},15:{1:4,2:3,3:3,4:3,5:2,6:1,7:1,8:1},16:{1:4,2:3,3:3,4:3,5:2,6:1,7:1,8:1},17:{1:4,2:3,3:3,4:3,5:2,6:1,7:1,8:1,9:1},18:{1:4,2:3,3:3,4:3,5:3,6:1,7:1,8:1,9:1},19:{1:4,2:3,3:3,4:3,5:3,6:2,7:1,8:1,9:1},20:{1:4,2:3,3:3,4:3,5:3,6:2,7:2,8:1,9:1}},Sorcerer:{1:{1:2},2:{1:3},3:{1:4,2:2},4:{1:4,2:3},5:{1:4,2:3,3:2},6:{1:4,2:3,3:3},7:{1:4,2:3,3:3,4:1},8:{1:4,2:3,3:3,4:2},9:{1:4,2:3,3:3,4:3,5:1},10:{1:4,2:3,3:3,4:3,5:2},11:{1:4,2:3,3:3,4:3,5:2,6:1},12:{1:4,2:3,3:3,4:3,5:2,6:1},13:{1:4,2:3,3:3,4:3,5:2,6:1,7:1},14:{1:4,2:3,3:3,4:3,5:2,6:1,7:1},15:{1:4,2:3,3:3,4:3,5:2,6:1,7:1,8:1},16:{1:4,2:3,3:3,4:3,5:2,6:1,7:1,8:1},17:{1:4,2:3,3:3,4:3,5:2,6:1,7:1,8:1,9:1},18:{1:4,2:3,3:3,4:3,5:3,6:1,7:1,8:1,9:1},19:{1:4,2:3,3:3,4:3,5:3,6:2,7:1,8:1,9:1},20:{1:4,2:3,3:3,4:3,5:3,6:2,7:2,8:1,9:1}},Bard:{1:{1:2},2:{1:2,2:0},3:{1:3,2:0},4:{1:3,2:2},5:{1:4,2:2,3:0},6:{1:4,2:2,3:0},7:{1:4,2:2,3:2},8:{1:4,2:2,3:2,4:0},9:{1:4,2:3,3:2,4:0},10:{1:4,2:3,3:2,4:1},11:{1:4,2:3,3:3,4:1},12:{1:4,2:3,3:3,4:1},13:{1:4,2:3,3:3,4:2,5:0},14:{1:4,2:3,3:3,4:2,5:0},15:{1:4,2:3,3:3,4:2,5:1},16:{1:4,2:3,3:3,4:2,5:1},17:{1:4,2:3,3:3,4:2,5:1,6:0},18:{1:4,2:3,3:3,4:3,5:1,6:0},19:{1:4,2:3,3:3,4:3,5:2,6:1},20:{1:4,2:3,3:3,4:3,5:2,6:1}},Cleric:{1:{1:2},2:{1:3},3:{1:3,2:2},4:{1:4,2:2},5:{1:4,2:3,3:2},6:{1:4,2:3,3:3},7:{1:4,2:3,3:3,4:1},8:{1:4,2:3,3:3,4:2},9:{1:4,2:3,3:3,4:3,5:1},10:{1:4,2:3,3:3,4:3,5:2},11:{1:4,2:3,3:3,4:3,5:2,6:1},12:{1:4,2:3,3:3,4:3,5:2,6:1},13:{1:4,2:3,3:3,4:3,5:2,6:1,7:1},14:{1:4,2:3,3:3,4:3,5:2,6:1,7:1},15:{1:4,2:3,3:3,4:3,5:2,6:1,7:1,8:1},16:{1:4,2:3,3:3,4:3,5:2,6:1,7:1,8:1},17:{1:4,2:3,3:3,4:3,5:2,6:1,7:1,8:1,9:1},18:{1:4,2:3,3:3,4:3,5:3,6:1,7:1,8:1,9:1},19:{1:4,2:3,3:3,4:3,5:3,6:2,7:1,8:1,9:1},20:{1:4,2:3,3:3,4:3,5:3,6:2,7:2,8:1,9:1}},Druid:{1:{1:2},2:{1:3},3:{1:3,2:2},4:{1:4,2:2},5:{1:4,2:3,3:2},6:{1:4,2:3,3:3},7:{1:4,2:3,3:3,4:1},8:{1:4,2:3,3:3,4:2},9:{1:4,2:3,3:3,4:3,5:1},10:{1:4,2:3,3:3,4:3,5:2},11:{1:4,2:3,3:3,4:3,5:2,6:1},12:{1:4,2:3,3:3,4:3,5:2,6:1},13:{1:4,2:3,3:3,4:3,5:2,6:1,7:1},14:{1:4,2:3,3:3,4:3,5:2,6:1,7:1},15:{1:4,2:3,3:3,4:3,5:2,6:1,7:1,8:1},16:{1:4,2:3,3:3,4:3,5:2,6:1,7:1,8:1},17:{1:4,2:3,3:3,4:3,5:2,6:1,7:1,8:1,9:1},18:{1:4,2:3,3:3,4:3,5:3,6:1,7:1,8:1,9:1},19:{1:4,2:3,3:3,4:3,5:3,6:2,7:1,8:1,9:1},20:{1:4,2:3,3:3,4:3,5:3,6:2,7:2,8:1,9:1}},Paladin:{1:{1:0},2:{1:2},3:{1:3},4:{1:3},5:{1:4,2:2},6:{1:4,2:2},7:{1:4,2:3},8:{1:4,2:3},9:{1:4,2:3,3:2},10:{1:4,2:3,3:2},11:{1:4,2:3,3:3},12:{1:4,2:3,3:3},13:{1:4,2:3,3:3,4:1},14:{1:4,2:3,3:3,4:1},15:{1:4,2:3,3:3,4:2},16:{1:4,2:3,3:3,4:2},17:{1:4,2:3,3:3,4:3,5:1},18:{1:4,2:3,3:3,4:3,5:1},19:{1:4,2:3,3:3,4:3,5:2},20:{1:4,2:3,3:3,4:3,5:2}},Ranger:{1:{1:0},2:{1:2},3:{1:2,2:0},4:{1:3,2:0},5:{1:3,2:2},6:{1:3,2:2},7:{1:3,2:2,3:0},8:{1:3,2:2,3:0},9:{1:3,2:3,3:0},10:{1:4,2:3,3:0},11:{1:4,2:3,3:2},12:{1:4,2:3,3:2},13:{1:4,2:3,3:2,4:0},14:{1:4,2:3,3:2,4:0},15:{1:4,2:3,3:2,4:1},16:{1:4,2:3,3:2,4:1},17:{1:4,2:3,3:3,4:1},18:{1:4,2:3,3:3,4:1},19:{1:4,2:3,3:3,4:2},20:{1:4,2:3,3:3,4:2}},Warlock:{1:{1:1},2:{1:2},3:{1:2,2:2},4:{1:2,2:2},5:{1:2,2:2,3:2},6:{1:2,2:2,3:2},7:{1:2,2:2,3:2,4:1},8:{1:2,2:2,3:2,4:1},9:{1:2,2:2,3:2,4:2,5:1},10:{1:2,2:2,3:2,4:2,5:1},11:{1:3,2:3,3:3,4:3,5:1},12:{1:3,2:3,3:3,4:3,5:2},13:{1:3,2:3,3:3,4:3,5:2},14:{1:3,2:3,3:3,4:3,5:2},15:{1:3,2:3,3:3,4:3,5:3},16:{1:3,2:3,3:3,4:3,5:3},17:{1:4,2:4,3:4,4:4,5:4},18:{1:4,2:4,3:4,4:4,5:4},19:{1:4,2:4,3:4,4:4,5:4},20:{1:4,2:4,3:4,4:4,5:4}}};function Ea(s){if(s in Ce)return Ce[s];try{const t=R.getInstance().get("classes.data");if(t&&Array.isArray(t)){const a=t.find(i=>i.name===s);if(a){if(a.baseClass&&a.baseClass in Ce){const i=Ce[a.baseClass];return{...i,...a,available_skills:a.available_skills||i.available_skills}}return a}}}catch{}}const ct={1:0,2:300,3:900,4:2700,5:6500,6:14e3,7:23e3,8:34e3,9:48e3,10:64e3,11:85e3,12:1e5,13:12e4,14:14e4,15:165e3,16:195e3,17:225e3,18:265e3,19:305e3,20:355e3},Ma={1:2,2:2,3:2,4:2,5:3,6:3,7:3,8:3,9:4,10:4,11:4,12:4,13:5,14:5,15:5,16:5,17:6,18:6,19:6,20:6},Ps=["Human","Elf","Dwarf","Halfling","Dragonborn","Gnome","Half-Elf","Half-Orc","Tiefling"],Ae={adjectives:{techno:{bass:["Thumping","Pulsing","Driving","Hypnotic","Relentless","Mechanical","Deep"],treble:["Piercing","Synthetic","Digital","Sharp","Glittering","Laser"],mid:["Driving","Rhythmic","Flowing","Seamless","Kinetic"],quiet:["Minimal","Sparse","Subtle","Ambient","Whispered"],loud:["Pounding","Overwhelming","Massive","Explosive","Crushing"]},rock:{bass:["Heavy","Rumbling","Gritty","Raw","Crushing","Grinding"],treble:["Screaming","Wailing","Soaring","Piercing","Shrill"],mid:["Crunchy","Distorted","Overdriven","Gritty","Aggressive"],quiet:["Acoustic","Stripped","Bare","Intimate","Unplugged"],loud:["Thunderous","Explosive","Deafening","Roaring","Volcanic"]},metal:{bass:["Brutal","Crushing","Punishing","Merciless","Devastating","Obliterating"],treble:["Shredding","Screaming","Hellish","Demonic","Piercing"],mid:["Chugging","Grinding","Relentless","Aggressive","Furious"],quiet:["Doomed","Somber","Ominous","Foreboding","Dark"],loud:["Deafening","Apocalyptic","Cataclysmic","Earth-Shattering","Monolithic"]},ambient:{bass:["Deep","Vast","Profound","Oceanic","Cavernous","Abyssal"],treble:["Ethereal","Crystalline","Shimmering","Celestial","Gossamer"],mid:["Whispering","Drifting","Floating","Meandering","Breathing"],quiet:["Silent","Hushed","Meditative","Peaceful","Tranquil"],loud:["Swelling","Building","Crescendoing","Rising","Expanding"]},classical:{bass:["Grand","Stately","Majestic","Regal","Noble","Profound"],treble:["Soaring","Triumphant","Brilliant","Radiant","Gleaming"],mid:["Noble","Elegant","Refined","Graceful","Poised"],quiet:["Gentle","Delicate","Tender","Soft","Intimate"],loud:["Majestic","Powerful","Commanding","Heroic","Epic"]},jazz:{bass:["Smooth","Groovy","Walking","Swinging","Funky","Mellow"],treble:["Bright","Crisp","Brilliant","Clear","Sparkling"],mid:["Swinging","Syncopated","Bluesy","Soulful","Improvisational"],quiet:["Cool","Laid-back","Subtle","Understated","Intimate"],loud:["Big","Bold","Brassy","Energetic","Vibrant"]},"hip hop":{bass:["Bumping","Knocking","Booming","Heavy","Fat","Deep"],treble:["Sharp","Crisp","Cutting","Bright","Piercing"],mid:["Flowing","Smooth","Rhythmic","Funky","Grooving"],quiet:["Chill","Mellow","Lo-fi","Smooth","Laid-back"],loud:["Hype","Aggressive","Hard","Explosive","Banging"]},pop:{bass:["Bouncy","Punchy","Groovy","Catchy","Upbeat"],treble:["Sparkling","Bright","Shimmering","Glittering","Polished"],mid:["Catchy","Memorable","Infectious","Singable","Hooky"],quiet:["Soft","Gentle","Sweet","Tender","Intimate"],loud:["Anthemic","Soaring","Epic","Triumphant","Powerful"]},electronic:{bass:["Pulsing","Wobbling","Throbbing","Synthetic","Digital","Modulated"],treble:["Glitchy","Digital","Futuristic","Synthetic","Robotic"],mid:["Synthetic","Programmed","Sequenced","Processed","Modular"],quiet:["Atmospheric","Ambient","Minimal","Spacious","Ethereal"],loud:["Massive","Crushing","Drop-heavy","Explosive","Wall-of-sound"]},default:{bass:["Booming","Deep","Low","Resonant","Rumbling","Heavy"],treble:["Sharp","High","Piercing","Bright","Clear"],mid:["Resonant","Balanced","Full","Rich","Warm"],quiet:["Quiet","Soft","Gentle","Subtle","Hushed"],loud:["Loud","Powerful","Strong","Intense","Forceful"]}},descriptors:["Swift","Iron","Shadow","Mystic","Wild","Radiant","Grim","Noble","Ancient","Blazing"],classAspects:{Barbarian:["Warrior","Rage","Beast","Fury"],Bard:["Song","Voice","Muse","Melody"],Cleric:["Light","Faith","Devotion","Grace"],Druid:["Wild","Nature","Grove","Fang"],Fighter:["Blade","Shield","Defender","Champion"],Monk:["Fist","Spirit","Flow","Path"],Paladin:["Oath","Justice","Light","Defender"],Ranger:["Hunter","Arrow","Tracker","Scout"],Rogue:["Shadow","Blade","Whisper","Dagger"],Sorcerer:["Flame","Storm","Chaos","Power"],Warlock:["Pact","Shadow","Hex","Dark"],Wizard:["Arcane","Sage","Spellweaver","Mind"],Artificer:["Craft","Forge","Construct","Invention"]},prefixes:["Thunder","Shadow","Flame","Frost","Storm","Star","Blood","Soul","Moon","Sun"],suffixes:["Blessed","Forged","Wreathed","Touched","Born","Kissed","Bound","Marked","Sworn","Woven"],occupations:["smith","weaver","caller","keeper","herald","warden","seeker","walker","singer"],realms:["Eternal Stage","Forgotten Hall","Misty Vale","Iron Keep","Crystal Spire","Shadow Court","Golden Fields","Storm Peak","Ancient Grove","Silent Deep"],subtitlePrefixes:["of Stars","Eternal","Reborn","Unchained","Ascendant","the Fallen","Unbound","Rising","Triumphant","Immortal"]},Wr={athletics:"STR",acrobatics:"DEX",sleight_of_hand:"DEX",stealth:"DEX",arcana:"INT",history:"INT",investigation:"INT",nature:"INT",religion:"INT",animal_handling:"WIS",insight:"WIS",medicine:"WIS",perception:"WIS",survival:"WIS",deception:"CHA",intimidation:"CHA",performance:"CHA",persuasion:"CHA"};function la(s){if(s in Rt)return Rt[s];try{const e=R.getInstance(),t=`classSpellLists.${s}`,a=e.get(t);if(a&&a.length>0)return a[0]}catch{}}function Da(s,e){if(s in oa){const t=oa[s];if(t&&e in t)return t[e]}try{const i=R.getInstance().get("classSpellSlots");if(i&&Array.isArray(i)){const n=i.find(r=>r.class===s);if(n&&n.slots_by_level){const r=n.slots_by_level;if(e in r)return r[e]}}}catch{}}const zr=10,xs=50;class Ie{constructor(){this.allSpellsCache=null,this.levelCache=null,this.schoolCache=null,this.manager=R.getInstance()}static getInstance(){return Ie.instance||(Ie.instance=new Ie),Ie.instance}invalidateCache(){this.allSpellsCache=null,this.levelCache=null,this.schoolCache=null}getSpell(e){return this.getSpells().find(a=>a.id===e)}getSpells(){if(!this.allSpellsCache){const e=this.manager.get("spells");this.allSpellsCache=e}return this.allSpellsCache}getSpellsByLevel(e){if(!this.levelCache){this.levelCache=new Map;const t=this.getSpells();for(const a of t)this.levelCache.has(a.level)||this.levelCache.set(a.level,[]),this.levelCache.get(a.level).push(a)}return this.levelCache.get(e)||[]}getSpellsBySchool(e){if(!this.schoolCache){this.schoolCache=new Map;const t=this.getSpells();for(const a of t)this.schoolCache.has(a.school)||this.schoolCache.set(a.school,[]),this.schoolCache.get(a.school).push(a)}return this.schoolCache.get(e)||[]}getSpellsForClass(e){return this.getSpells().filter(a=>a.classes&&a.classes.includes(e))}getAvailableSpells(e){const t=this.getSpells(),a=[];for(const i of t)this.validatePrerequisites(i,e).valid&&a.push(i);return a}getClassSpellList(e){const t=`spells.${e}`;return this.manager.get(t)||[]}getSpellSlotsForClass(e,t){const a=Da(e,t);return a?Object.values(a).reduce((n,r)=>n+r,0):0}validatePrerequisites(e,t){return De.validateSpellPrerequisites(e.prerequisites,t)}validateSpell(e){return De.validateSpell(e)}hasSpell(e){return this.getSpell(e)!==void 0}getSpellCount(){return this.getSpells().length}getSpellsBySource(e){return this.getSpells().filter(t=>t.source===e)}getQueryStats(){const e=this.getSpells(),t=e.filter(o=>o.source==="default"),a=e.filter(o=>o.source==="custom"),i={};for(let o=0;o<=9;o++)i[o]=this.getSpellsByLevel(o).length;const n={Abjuration:this.getSpellsBySchool("Abjuration").length,Conjuration:this.getSpellsBySchool("Conjuration").length,Divination:this.getSpellsBySchool("Divination").length,Enchantment:this.getSpellsBySchool("Enchantment").length,Evocation:this.getSpellsBySchool("Evocation").length,Illusion:this.getSpellsBySchool("Illusion").length,Necromancy:this.getSpellsBySchool("Necromancy").length,Transmutation:this.getSpellsBySchool("Transmutation").length},r=new Set;for(const o of e)if(o.classes)for(const l of o.classes)r.add(l);return{totalSpells:e.length,defaultSpells:t.length,customSpells:a.length,spellsByLevel:i,spellsBySchool:n,classesWithSpells:r.size}}}function qr(){return Ie.getInstance()}class R{constructor(){this.defaultData=new Map,this.extensions=new Map,this.customWeights=new Map,this.imageOverrides=new Map}static getInstance(){return R.instance||(R.instance=new R),R.instance}initializeDefaults(e,t){this.defaultData.set(e,[...t]),this.invalidateRegistryCache(e)}initializeAllDefaults(e){for(const[t,a]of Object.entries(e))this.initializeDefaults(t,a)}register(e,t,a={}){const i=this.extensions.get(e),n=a.mode!==void 0?a.mode:i?.options.mode,{weights:r={},validate:o=!0}=a,l=["races","classes"];let c=t;if(l.includes(e)&&(c=t.map(d=>typeof d=="string"?d:d&&typeof d=="object"&&typeof d.name=="string"?d.name:d)),o){const d=this.validate(e,c);if(!d.valid)throw new Error(`Invalid items for category '${e}':
2
2
  ${d.errors?.join(`
3
3
  `)}`)}if(e.startsWith("spells.")&&e!=="spells"){const d=this.get("spells"),h=new Set(d.map(m=>m.id)),p=[];if(t.length>0&&t[0]&&typeof t[0]=="object"&&"spells_by_level"in t[0])for(const m of t){const f=m;if(f.cantrips&&Array.isArray(f.cantrips))for(const v of f.cantrips)h.has(v)||p.push(v);if(f.spells_by_level){for(const v of Object.values(f.spells_by_level))if(Array.isArray(v))for(const w of v)h.has(w)||p.push(w)}}else{const m=t;for(const f of m)h.has(f)||p.push(f)}if(p.length>0)throw new Error(`Invalid spell IDs for ${e}: ${p.join(", ")}`)}if(n!=="replace"){const d=this.get(e),h=[];if(!["races","classes"].includes(e)){if(e.startsWith("appearance."))for(const g of c)typeof g=="string"&&d.includes(g)&&h.push(g);else for(const g of c)if(g&&typeof g=="object"){const m=g.name,f=g.id;(m||f)&&d.some(w=>!!(w&&typeof w=="object"&&(m&&w.name===m||f&&w.id===f)))&&h.push(m||f)}}if(h.length>0)throw new Error(`Duplicate items in category '${e}': ${h.join(", ")}. Each item must have a unique name or id.`)}const u=i&&n!=="replace"?[...i.items,...c]:[...c];if(this.extensions.set(e,{items:u,options:{mode:n==="replace"?"absolute":n,weights:r,validate:o},registeredAt:Date.now(),excludeDefaults:n==="replace"?!0:i?.excludeDefaults}),Object.keys(r).length>0){const d=this.customWeights.get(e)||{};this.customWeights.set(e,{...d,...r})}this.invalidateRegistryCache(e)}registerMultiple(e){for(const t of e)this.register(t.category,t.items,t.options)}get(e){const t=this.defaultData.get(e)||[],a=this.extensions.get(e);let i;return a?(a.options.mode,a.excludeDefaults?i=[...a.items]:i=[...t,...a.items]):i=[...t],this.isImageSupportedCategory(e)&&(i=this.applyImageOverrides(e,i)),i}isImageSupportedCategory(e){return["spells","skills","classFeatures","racialTraits","equipment","races.data","classes.data"].includes(e)}applyImageOverrides(e,t){const a=this.imageOverrides.get(e);if(!a||a.size===0)return t;const i=this.getDefaultIdentifierKey(e);return t.map(n=>{const r=n[i],o=a.get(r);return o?{...n,...o.icon!==void 0&&{icon:o.icon},...o.image!==void 0&&{image:o.image}}:n})}getDefaults(e){return[...this.defaultData.get(e)||[]]}getCustom(e){const t=this.extensions.get(e);return t?[...t.items]:[]}setWeights(e,t){this.customWeights.set(e,{...t})}getWeights(e){const t=this.getDefaultWeights(e),a=this.customWeights.get(e)||{},i=this.getCustom(e),n={};for(const r of i){const o=typeof r=="string"?r:r.name;o&&!(o in t)&&!(o in a)&&(n[o]=1)}return{...t,...n,...a}}getDefaultWeights(e){const t=this.defaultData.get(e)||[],a={};for(const i of t){const n=typeof i=="string"?i:i.name;n&&(a[n]=1)}return a}hasCustomData(e){return this.extensions.has(e)}setMode(e,t){const a=this.extensions.get(e);a?a.options.mode=t:this.extensions.set(e,{items:[],options:{mode:t},registeredAt:Date.now()})}getMode(e){return this.extensions.get(e)?.options.mode}getCurrentOptions(e){return this.extensions.get(e)?.options}validate(e,t){const a=[];if(!Array.isArray(t))return a.push("Items must be an array"),{valid:!1,errors:a};for(let i=0;i<t.length;i++){const n=t[i],r=this.validateItem(e,n,i);a.push(...r)}return a.length>0?{valid:!1,errors:a}:{valid:!0}}validateItem(e,t,a){const i=[],n=`Item ${a}:`;if(t==null)return i.push(`${n} Item is null or undefined`),i;if(e==="equipment"){const r=Dt.validateEquipment(t);r.valid||i.push(...(r.errors||[]).map(o=>`${n} ${o}`))}else if(e==="spells"||e.startsWith("spells.")){if(!(t&&typeof t=="object"&&"spells_by_level"in t)){const o=De.validateSpell(t);o.valid||i.push(...o.errors.map(l=>`${n} ${l}`))}}else if(e==="races"){const r=_r;if(r.includes(t))return i;const o=this.get("races");if(Array.isArray(o)&&o.includes(t))return i;const l=this.get("races.data");if(Array.isArray(l)&&l.map(u=>u.race).filter(Boolean).includes(t))return i;i.push(`${n} Invalid race "${t}". Must be one of: ${r.join(", ")} or register custom race via 'races.data' first.`)}else if(e==="races.data"){if((!t.race||typeof t.race!="string")&&i.push(`${n} Race data must have a valid 'race' property (string)`),(typeof t.speed!="number"||t.speed<=0)&&i.push(`${n} Race data must have a valid 'speed' property (positive number)`),Array.isArray(t.traits)||i.push(`${n} Race data must have a 'traits' property (array of strings)`),t.ability_bonuses)if(typeof t.ability_bonuses!="object"||Array.isArray(t.ability_bonuses))i.push(`${n} Race 'ability_bonuses' must be a record of ability -> number`);else{const o=["STR","DEX","CON","INT","WIS","CHA"];for(const[l,c]of Object.entries(t.ability_bonuses))o.includes(l)||i.push(`${n} Invalid ability "${l}" in ability_bonuses`),typeof c!="number"&&i.push(`${n} Ability bonus for "${l}" must be a number`)}t.subraces!==void 0&&!Array.isArray(t.subraces)&&i.push(`${n} Race 'subraces' must be an array of strings (if provided)`);const r=Be({icon:t.icon,image:t.image});i.push(...r.map(o=>`${n} Race ${o}`))}else if(e==="classes"){const r=t,o=Re.includes(r),c=this.get("classes")?.includes(r),d=this.get("classes.data")?.some(h=>h.name===r);!o&&!c&&!d&&i.push(`${n} Invalid class (must be one of: ${Re.join(", ")} or a custom class registered via 'classes.data')`)}else if(e==="classes.data"){(!t.name||typeof t.name!="string")&&i.push(`${n} Class data must have a valid 'name' property (string)`);const r=["STR","DEX","CON","INT","WIS","CHA"];if((!t.primary_ability||!r.includes(t.primary_ability))&&i.push(`${n} Class data must have a valid 'primary_ability' (one of: STR, DEX, CON, INT, WIS, CHA)`),(typeof t.hit_die!="number"||t.hit_die<=0)&&i.push(`${n} Class data must have a valid 'hit_die' property (positive number)`),!Array.isArray(t.saving_throws))i.push(`${n} Class data must have a 'saving_throws' property (array of abilities)`);else for(const l of t.saving_throws)r.includes(l)||i.push(`${n} Invalid saving throw "${l}" in class data`);if(typeof t.is_spellcaster!="boolean"&&i.push(`${n} Class data must have an 'is_spellcaster' property (boolean)`),(typeof t.skill_count!="number"||t.skill_count<0)&&i.push(`${n} Class data must have a valid 'skill_count' property (non-negative number)`),Array.isArray(t.available_skills)||i.push(`${n} Class data must have an 'available_skills' property (array of skill IDs)`),typeof t.has_expertise!="boolean"&&i.push(`${n} Class data must have a 'has_expertise' property (boolean)`),t.expertise_count!==void 0&&(typeof t.expertise_count!="number"||t.expertise_count<0)&&i.push(`${n} Class 'expertise_count' must be a non-negative number (if provided)`),t.baseClass!==void 0)if(typeof t.baseClass!="string")i.push(`${n} Class 'baseClass' must be a string (if provided)`);else{const l=Re.includes(t.baseClass),u=this.get("classes.data")?.some(d=>d.name===t.baseClass);!l&&!u&&i.push(`${n} Class 'baseClass' must be a valid default class (one of: ${Re.join(", ")}) or a registered custom class`)}t.audio_preferences!==void 0&&typeof t.audio_preferences!="object"&&i.push(`${n} Class 'audio_preferences' must be an object (if provided)`);const o=Be({icon:t.icon,image:t.image});i.push(...o.map(l=>`${n} Class ${l}`))}else if(e.startsWith("appearance."))typeof t!="string"&&i.push(`${n} Appearance options must be strings`);else if(e==="classFeatures"||e.startsWith("classFeatures.")){const r=this.get("classes")??[],o=Fe.validateClassFeature(t,{customClasses:r});o.valid||i.push(...o.errors.map(l=>`${n} ${l}`))}else if(e==="racialTraits"||e.startsWith("racialTraits.")){const r=this.get("races")??[],l=this.get("races.data")?.map(d=>d.race).filter(Boolean)??[],c=[...r,...l],u=Fe.validateRacialTrait(t,{customRaces:c});u.valid||i.push(...u.errors.map(d=>`${n} ${d}`))}else if(e==="skills"||e.startsWith("skills.")){const r=we.validateSkill(t);r.valid||i.push(...r.errors.map(o=>`${n} ${o}`))}else(e==="skillLists"||e.startsWith("skillLists."))&&((!t.class||typeof t.class!="string")&&i.push(`${n} Skill list must have a valid 'class'`),(typeof t.skillCount!="number"||t.skillCount<0)&&i.push(`${n} Invalid 'skillCount' (must be non-negative number)`),Array.isArray(t.availableSkills)||i.push(`${n} Invalid 'availableSkills' (must be an array of skill IDs)`),t.expertiseCount!==void 0&&(typeof t.expertiseCount!="number"||t.expertiseCount<0)&&i.push(`${n} Invalid 'expertiseCount' (must be non-negative number)`));return i}reset(e){this.extensions.delete(e),this.customWeights.delete(e),this.invalidateRegistryCache(e)}resetAll(){this.extensions.clear(),this.customWeights.clear(),Ie.getInstance().invalidateCache(),pe.getInstance().invalidateCache(),re.getInstance().invalidateCache()}getRegistryForCategory(e){return e==="spells"||e.startsWith("spells.")||e==="classSpellLists"||e.startsWith("classSpellLists.")?"spell":e==="skills"||e.startsWith("skills.")||e==="skillLists"||e.startsWith("skillLists.")?"skill":e==="classFeatures"||e.startsWith("classFeatures.")||e==="racialTraits"||e.startsWith("racialTraits.")?"feature":null}invalidateRegistryCache(e){const t=this.getRegistryForCategory(e);t==="spell"?Ie.getInstance().invalidateCache():t==="skill"?pe.getInstance().invalidateCache():t==="feature"&&re.getInstance().invalidateCache()}getInfo(e){if(e){const a=this.extensions.get(e),i=this.defaultData.get(e)||[];return{hasCustomData:this.hasCustomData(e),defaultCount:i.length,customCount:a?.items.length||0,totalCount:this.get(e).length,mode:this.getMode(e),weights:this.getWeights(e),registeredAt:a?.registeredAt}}const t={};for(const a of this.defaultData.keys())t[a]=this.getInfo(a);return t}exportCustomData(){const e={};for(const[a,i]of this.extensions.entries())e[a]={items:i.items,options:i.options,registeredAt:i.registeredAt};const t={};for(const[a,i]of this.customWeights.entries())t[a]=i;return{extensions:e,weights:t}}exportCustomDataForCategory(e){const t=this.extensions.get(e);return t?[...t.items]:[]}getRegisteredCategories(){return Array.from(this.defaultData.keys())}getAllFromPrefix(e){const t=[],a=new Set,i=r=>{for(const o of r){const l=o.id||JSON.stringify(o);a.has(l)||(a.add(l),t.push(o))}},n=new Set;for(const r of this.defaultData.keys())(r===e||r.startsWith(e+"."))&&n.add(r);for(const r of this.extensions.keys())(r===e||r.startsWith(e+"."))&&n.add(r);for(const r of n)i(this.get(r));return t}batchAddIcons(e,t,a){const i=a??this.getDefaultIdentifierKey(e),n=Object.entries(t).map(([u,d])=>[d,u]),r=this.validateImageFields(n);if(r.length>0)throw new Error(`Invalid icon URLs for category '${e}':
4
4
  ${r.map(u=>` - ${u}`).join(`