diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..c426507 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,12 @@ +# These are supported funding model platforms + +github: [eddiedover] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] +patreon: # Replace with a single Patreon username +open_collective: # Replace with a single Open Collective username +ko_fi: # Replace with a single Ko-fi username +tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel +community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry +liberapay: # Replace with a single Liberapay username +issuehunt: # Replace with a single IssueHunt username +otechie: # Replace with a single Otechie username +custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..dd84ea7 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,38 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: '' +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Desktop (please complete the following information):** + - OS: [e.g. iOS] + - Browser [e.g. chrome, safari] + - Version [e.g. 22] + +**Smartphone (please complete the following information):** + - Device: [e.g. iPhone6] + - OS: [e.g. iOS8.1] + - Browser [e.g. stock browser, safari] + - Version [e.g. 22] + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..bbcbbe7 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: '' +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/.gitignore b/.gitignore index 205a20c..91422ed 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,6 @@ node_modules/ github/ dist/ -deploy/ \ No newline at end of file +deploy/ +tools/ +copydist.bat \ No newline at end of file diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 0000000..dd9adab --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,3 @@ +{ + "quoteProps": "preserve" +} \ No newline at end of file diff --git a/README.md b/README.md index b25002c..f047459 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,8 @@ The original intent of this module was to provide a 'party view' feature that I ![Preview of Plugin Party Sheet](images/preview1.png) - -## TODO -* Maybe add Syrinscape support, to have sounds play after attack rolls and such. +## ALPHA Features +- Sound effects by Weapon Type on Attack Rolls. + - This feature will be optional and off by default, until it's deemed solid and stable. + - To enable this feature you must install both the [SyrinControl](https://foundryvtt.com/packages/fvtt-syrin-control) and the [Attack Roll Check D&D5e](https://foundryvtt.com/packages/attack-roll-check-5e) modules. + - When enabled, this module will scan for Attack Roll GM messages and play a sound with a matching weapon title (including hit, miss, and crit sounds if possible) through the SyrinControl module. diff --git a/module/lang/en.json b/module/lang/en.json new file mode 100644 index 0000000..15e506d --- /dev/null +++ b/module/lang/en.json @@ -0,0 +1,20 @@ +{ + "theater-of-the-mind": { + "party-sheet": { + "name": "Name", + "race": "Race", + "senses": "Senses", + "classes": "Classes" + }, + "settings": { + "enable-sounds": { + "name": "Enable Syrinscape Sounds", + "hint": "This option requires the Syrinscape Integration (fvtt-synrin-control) and the Attack Roll Check (foundryvtt-attack-roll-check-5e) modules." + }, + "enable-party-sheet": { + "name": "Enable Party Sheet", + "hint": "Enable the party sheet." + } + } + } +} \ No newline at end of file diff --git a/module/lang/es.json b/module/lang/es.json new file mode 100644 index 0000000..719a712 --- /dev/null +++ b/module/lang/es.json @@ -0,0 +1,20 @@ +{ + "theater-of-the-mind": { + "party-sheet": { + "name": "Nombre", + "race": "Raza", + "senses": "Sentidos", + "classes": "Clases" + }, + "settings": { + "enable-sounds": { + "name": "Habilitar Sonidos de Syrinscape", + "hint": "Esta opción requiere los módulos de Integración de Syrinscape (fvtt-syrin-control) y Comprobación de Tirada de Ataque (foundryvtt-attack-roll-check-5e)." + }, + "enable-party-sheet": { + "name": "Habilitar Hoja de Grupo", + "hint": "Habilita la hoja de grupo." + } + } + } +} diff --git a/module/module.json b/module/module.json index f971c66..0e9e454 100644 --- a/module/module.json +++ b/module/module.json @@ -1,6 +1,6 @@ { "id": "theater-of-the-mind", - "title": "The Theater of the Mind - DM Tool", + "title": "Theater of the Mind - DM Tool", "description": "A module to assist DMs who run theater of the mind adventures.", "url": "https://github.com/EddieDover/theater-of-the-mind", "bugs": "https://github.com/EddieDover/theater-of-the-mind/issues", @@ -12,6 +12,18 @@ "flags": { "allowBugReporter": true }, + "languages": [ + { + "lang": "en", + "name": "English", + "path": "lang/en.json" + }, + { + "lang": "es", + "name": "Spanish", + "path": "lang/es.json" + } + ], "media": [ { "type": "cover", @@ -25,7 +37,7 @@ "url": "https://github.com/eddiedover/" } ], - "version": "1.0.6", + "version": "1.1.0", "compatibility": { "minimum": "11", "verified": "11" diff --git a/module/sounds.js b/module/sounds.js new file mode 100644 index 0000000..fb69213 --- /dev/null +++ b/module/sounds.js @@ -0,0 +1,778 @@ +export const THEATER_SOUNDS = { + "ahhhhhh": { any: 16 }, + "gasp": { any: 17 }, + "blade spell": { any: 18 }, + "charm spell": { any: 19 }, + "fire spell": { any: 266819 }, + "ice spell": { any: 21 }, + "lightning spell": { any: 266822 }, + "ooooooo": { any: 23 }, + "alarm bell": { any: 77 }, + "diabolical machine": { any: 78 }, + "fire blast": { any: 79 }, + "fire blast trap": { any: 211804 }, + "smashing a wooden door": { any: 82 }, + "creaking door": { any: 1836260 }, + "trap sprung": { any: 211805 }, + "grinding stone on stone": { any: 89 }, + "thunder": { any: 1932013 }, + "roars": { any: 398167 }, + "weird distant animals": { any: 401082 }, + "battle cries": { any: 115 }, + "curses": { any: 116 }, + "growls": { any: 248239 }, + "horse": { any: 90495 }, + "thunder claps": { any: 266820 }, + "cow": { any: 362654 }, + "rooster": { any: 1511 }, + "tumbling rocks": { any: 176 }, + "scratching claws": { any: 177 }, + "dragon roars": { any: 178 }, + "watch out for that tree!": { any: 90500 }, + "something big's out there": { any: 182 }, + "freaky child": { any: 183 }, + "burps": { any: 211757 }, + "kira laughs": { any: 211761 }, + "sandar laughs": { any: 211766 }, + "dah dah dum": { any: 231 }, + "final fanfare": { any: 232 }, + "the dragon": { any: 233 }, + "the half dragon": { any: 234 }, + "oh no!": { any: 235 }, + "gulls": { any: 90705 }, + "evil laugh": { any: 241 }, + "crazy laugh": { any: 4390 }, + "witch cackle": { any: 243 }, + "drip": { any: 256 }, + "toss a rock in": { any: 5734 }, + "something big": { any: 258 }, + "passing a waterfall": { any: 259 }, + "wilhelm scream - yes really!": { any: 300 }, + "glass smash": { any: 2757809 }, + "punch": { any: 91272 }, + "a door swings shut": { any: 401068 }, + "creak!?!": { any: 347 }, + "knocked off the table": { any: 348 }, + "a ghost under your skin": { any: 349 }, + "scornful laugh": { any: 363 }, + "psycho scream": { any: 364 }, + "frantic knocks": { any: 365 }, + "hungry!": { any: 372 }, + "deep laugh": { any: 373 }, + "mad laugh": { any: 374 }, + "insane cackle": { any: 375 }, + "ship ram": { any: 609 }, + "ship's bell": { any: 612 }, + "ship ahoy!": { any: 664 }, + "arrrrrr!": { any: 665 }, + "barghest roar": { any: 5886 }, + "giant door": { any: 778 }, + "something big out there": { any: 974 }, + "thunder claps (softer)": { any: 90499 }, + "sword clash": { any: 1035 }, + "arrow in wood": { any: 1039 }, + "arrow in flesh": { any: 1040 }, + "sword swoosh": { any: 1041 }, + "weapon impact": { any: 2130 }, + "bridge collapse": { any: 1489 }, + "insect flyby": { any: 7218 }, + "dire bear": { any: 1492 }, + "war horn": { any: 5197 }, + "ogre growl": { any: 1500 }, + "tumbling pebble": { any: 1501 }, + "eagle call": { any: 1373071 }, + "sheep": { any: 82795 }, + "night bird": { any: 1519 }, + "crumbling building": { any: 1576 }, + "cathedral warning chime": { any: 1577 }, + "red dragon growl": { any: 1579 }, + "red dragon roar": { any: 1580 }, + "red dragon flame": { any: 6379 }, + "woman screams": { any: 1583 }, + "statue destruction": { any: 2051 }, + "statue activation": { any: 2055 }, + "distant roars": { any: 2056 }, + "breath of death": { any: 5907 }, + "disjunction pulse": { any: 2496 }, + "tillia screams": { any: 2127 }, + "tabitha screams": { any: 2128 }, + "black magga roar": { any: 2129 }, + "spark": { any: 1901915 }, + "steam release": { any: 1901917 }, + "ooze dividing": { any: 2541 }, + "ooze slam connects": { any: 2542 }, + "goldfish curse": { any: 2564 }, + "auguries": { any: 2716 }, + "summon ally": { any: 2719 }, + "silencing": { any: 2720 }, + "remove diseases": { any: 2721 }, + "protection from elements": { any: 2723 }, + "magical weapon": { any: 266824 }, + "magical circle": { any: 2725 }, + "channel energy": { any: 2726 }, + "hold": { any: 2727 }, + "eagle's charisma": { any: 2728 }, + "doom": { any: 5909 }, + "dispel sorcery": { any: 7201 }, + "detect magic": { any: 2713304 }, + "heal": { critical: 2732 }, + "heal serious": { any: 2733 }, + "heal moderate": { any: 2734 }, + "heal light": { any: 2735 }, + "creating water": { any: 2736 }, + "divine strength": { any: 7197 }, + "blessing": { any: 2738 }, + "bleeding": { any: 2739 }, + "laugh": { any: 2741 }, + "frustrated": { any: 2742 }, + "injury": { any: 6022 }, + "exertion": { any: 2744 }, + "scimitar unsheathe": { any: 2746 }, + "scimitar swoosh": { any: 2747 }, + "scimitar": { hit: 2717047, miss: 2717046, critical: 2717048 }, + "scimitar sheath": { any: 2749 }, + "scimitar crit": { any: 2750 }, + "chain-mail sound": { any: 2751 }, + "cloth sound": { any: 2752 }, + "weapon": { hit: 2753 }, + "thoughtful": { any: 2754 }, + "1st level spell": { any: 2755 }, + "2nd level spell": { any: 2756 }, + "3rd level spell": { any: 2757 }, + "4th level spell": { any: 2758 }, + "0th level spell": { any: 2759 }, + "9th level spell": { any: 2760 }, + "8th level spell": { any: 2761 }, + "7th level spell": { any: 2762 }, + "6th level spell": { any: 2763 }, + "cause terror": { any: 7225 }, + "comprehension": { any: 2769 }, + "detect poisons": { any: 2771 }, + "endure elements": { any: 2772 }, + "light bright": { any: 7202 }, + "mend": { any: 2774 }, + "obscuring fog": { any: 6003 }, + "info": { any: 3068 }, + "summon": { any: 3076 }, + "unsummon": { any: 3077 }, + "banish": { any: 3078 }, + "friendly spell": { any: 3079 }, + "unfriendly spell": { any: 3080 }, + "huzzah": { any: 3372 }, + "yay!": { any: 3373 }, + "sad trombone": { any: 3374 }, + "dramatic note": { any: 3375 }, + "soul in grief": { any: 3958 }, + "evil giggles": { any: 3959 }, + "gas releases": { any: 3960 }, + "ooze roar": { any: 6374 }, + "dragon speech": { any: 4366 }, + "strange passer by": { any: 4380 }, + "exhibitor": { any: 4381 }, + "scared gen con attendee": { any: 4383 }, + "big male warrior": { any: 4384 }, + "brave gen con attendee": { any: 4385 }, + "big scary humanoid": { any: 4386 }, + "munchkin": { any: 4387 }, + "annoyed attendee": { any: 4389 }, + "freaky pig creature": { any: 4391 }, + "strong female warrior": { any: 4392 }, + "werewolf howl": { any: 15803 }, + "gen con dragon": { any: 4394 }, + "destruction": { any: 4395 }, + "werewolf growl": { any: 4396 }, + "shopper": { any: 4397 }, + "goblin": { any: 4398 }, + "paladin": { any: 4399 }, + "being eaten": { any: 4400 }, + "scream": { any: 4401 }, + "diabolical villain laugh": { any: 4402 }, + "zombie": { any: 4403 }, + "huge monster": { any: 4404 }, + "pig": { any: 82798 }, + "welcome": { any: 33393 }, + "can i help u?": { any: 4782 }, + "have a look around": { any: 4783 }, + "anything u like": { any: 4784 }, + "a good price": { any: 4785 }, + "very expensive": { any: 4786 }, + "it's a deal": { any: 4787 }, + "yes, we can": { any: 4788 }, + "goodday": { any: 4789 }, + "come back soon": { any: 4790 }, + "a pleasure doing business with you": { any: 4791 }, + "don't get yourself killed": { any: 4792 }, + "get out": { any: 4793 }, + "out of that": { any: 4798 }, + "i'll give u a better price": { any: 4799 }, + "i'll hex you for that one": { any: 4800 }, + "yes": { any: 145804 }, + "no": { any: 145801 }, + "what?": { any: 33398 }, + "huge explosion": { any: 5127 }, + "blaster impact": { any: 5128 }, + "laser shot": { any: 5129 }, + "flamethrower": { any: 26541 }, + "rocket fire": { any: 26542 }, + "shattering explosion": { any: 5132 }, + "hand gun": { any: 5882 }, + "automatic": { any: 5135 }, + "chain rev": { any: 5142 }, + "chain cuts": { any: 26540 }, + "taser": { any: 5144 }, + "hold!": { any: 5195 }, + "orc roar": { any: 5196 }, + "scorching ray": { any: 5198 }, + "frog": { any: 5736 }, + "weird bug": { any: 5747 }, + "flies (click lots)": { any: 5748 }, + "bow fire": { any: 6012 }, + "shadowflame": { any: 5861 }, + "shadow lightning": { any: 5862 }, + "rayle": { any: 5865 }, + "trapped hallway": { any: 5866 }, + "meathedge attack": { any: 5871 }, + "crossbow nock": { any: 5887 }, + "crossbow fire": { any: 2208075 }, + "explosion": { any: 26537 }, + "force": { miss: 266823 }, + "zhon bonkers": { any: 5895 }, + "jumping": { any: 5899 }, + "shadow foreplay": { any: 5902 }, + "shadow's speech": { any: 5903 }, + "sinister bite": { any: 5908 }, + "ghostly wail": { any: 5932 }, + "thunder roll": { any: 5936 }, + "rumble": { any: 5937 }, + "ice forming": { any: 5938 }, + "evil hiss": { any: 5939 }, + "sneak attack": { any: 6004 }, + "goblin queen yell": { any: 6015 }, + "cause fright": { any: 6016 }, + "burning palms": { any: 6017 }, + "goblin laugh": { any: 6018 }, + "burp": { any: 91282 }, + "fart": { any: 6257 }, + "wretching": { any: 6270 }, + "vomit": { any: 6272 }, + "showertime": { any: 6274 }, + "disintegrate ray": { any: 6368 }, + "undead chicken": { any: 7192 }, + "bill i": { any: 7193 }, + "heirophant i": { any: 7195 }, + "summon fire elemental": { any: 7200 }, + "drazuul": { any: 7227 }, + "wyvern roar": { any: 7228 }, + "mort i": { any: 7229 }, + "true attack": { any: 7264 }, + "daze": { any: 7265 }, + "victory": { any: 7764 }, + "mort ii": { any: 7855 }, + "mort iii": { any: 7856 }, + "mort iv": { any: 7857 }, + "heirophant ii": { any: 7858 }, + "heirophant iii": { any: 7859 }, + "heirophant iv": { any: 7860 }, + "heirophant vi": { any: 7861 }, + "heirophant v": { any: 7862 }, + "bill ii": { any: 7863 }, + "bill iii": { any: 7864 }, + "terribly busy": { any: 10854 }, + "i don't know": { any: 2194091 }, + "goodbye": { any: 10859 }, + "that's true": { any: 10862 }, + "nostoc greyspine": { any: 10863 }, + "don't trust them": { any: 10864 }, + "greetings": { any: 10896 }, + "what do you want?": { any: 10895 }, + "don't give me those eyes": { any: 10867 }, + "not going to happen": { any: 10868 }, + "good question": { any: 10869 }, + "lower your weapons": { any: 10932 }, + "grease the palm?": { any: 10871 }, + "pay attention!": { any: 10872 }, + "a man after my own heart": { any: 10873 }, + "attitude": { any: 10874 }, + "shifty eyes": { any: 10875 }, + "who are you?": { any: 10876 }, + "just a scratch": { any: 10891 }, + "come back again": { any: 10897 }, + "bye bye": { any: 10898 }, + "thank you": { any: 33400 }, + "can't do that": { any: 10924 }, + "stay for a while": { any: 10906 }, + "hello again": { any: 10907 }, + "what else?": { any: 10908 }, + "very generous": { any: 10909 }, + "of course": { any: 10911 }, + "good bye": { any: 10912 }, + "go away": { any: 10922 }, + "it's true": { any: 10923 }, + "nice to meet you...": { any: 10925 }, + "be on your way": { any: 10926 }, + "please do": { any: 10927 }, + "let's chat": { any: 10928 }, + "take it and leave": { any: 10929 }, + "lost a friendship": { any: 10930 }, + "sigh": { any: 10931 }, + "drunk criodan": { any: 10945 }, + "norrington": { any: 10951 }, + "coughs": { any: 10952 }, + "criodan song (long)": { any: 10953 }, + "devil teleport arrival": { any: 20314 }, + "teleport fire": { any: 20316 }, + "teleport activation": { any: 20317 }, + "welcome note": { any: 20321 }, + "devil hisses": { any: 20343 }, + "devil growls": { any: 20344 }, + "devil speech": { any: 20345 }, + "fireball": { any: 25466 }, + "firebolt female": { any: 25467 }, + "firebolt male": { any: 25468 }, + "lightning": { any: 25469 }, + "shocking grasp": { any: 25470 }, + "dragon": { any: 26533 }, + "lion": { any: 26534 }, + "goat": { any: 26535 }, + "gnome": { any: 26536 }, + "cheers": { any: 26538 }, + "dog": { any: 26539 }, + "fire crackers": { any: 26543 }, + "smash": { any: 26544 }, + "dragon flame": { any: 32050 }, + "baby dragon": { any: 32058 }, + "not enough": { any: 33373 }, + "pipe down": { any: 33374 }, + "adventures?": { any: 33375 }, + "take it outside": { any: 33376 }, + "cheap beds": { any: 33377 }, + "not welcome here": { any: 33378 }, + "fine rooms": { any: 33379 }, + "we got places to stay": { any: 33380 }, + "get out!!": { any: 33381 }, + "farewell": { any: 2194093 }, + "food?": { any: 33383 }, + "eat up!": { any: 33384 }, + "good food": { any: 33385 }, + "a drink?": { any: 33386 }, + "a few coins to loosen my tongue": { any: 33387 }, + "10gp": { any: 33388 }, + "2gp": { any: 33389 }, + "1gp": { any: 33390 }, + "2cp": { any: 33391 }, + "a fine wine": { any: 33392 }, + "good evening": { any: 33394 }, + "i can't do that": { any: 33395 }, + "a mysterious stranger": { any: 33399 }, + "good afternoon": { any: 33401 }, + "shotgun pump": { any: 35047 }, + "shotgun shot": { any: 35049 }, + "woman scream": { any: 53275 }, + "man cry": { any: 53281 }, + "crockery cataclysm": { any: 53286 }, + "table tragedy": { any: 53287 }, + "culinary catastrophy": { any: 53288 }, + "gull": { any: 59428 }, + "pots and pans": { any: 59431 }, + "horse and cart pass": { any: 59432 }, + "bells": { any: 59433 }, + "smithy": { any: 59434 }, + "piano": { hit: 138158 }, + "bad words": { any: 82705 }, + "hawker": { any: 82793 }, + "scrap seller": { any: 82797 }, + "blacksmith": { any: 82799 }, + "ship building": { any: 82800 }, + "into the water": { any: 90502 }, + "beast": { any: 90529 }, + "creaking dock": { any: 90704 }, + "medium dogs": { any: 90706 }, + "big dogs": { any: 90707 }, + "crow": { any: 362655 }, + "wolf howls": { any: 90709 }, + "crow (hooded)": { any: 90710 }, + "giggle": { any: 90711 }, + "mead glasses clinking": { any: 90712 }, + "crow attack": { any: 90713 }, + "gong": { any: 110846 }, + "cup smash": { any: 91267 }, + "kick": { any: 91274 }, + "laughter": { any: 91283 }, + "swipe": { any: 97316 }, + "spell": { any: 97317 }, + "clashing attack": { any: 97318 }, + "clockwork words": { any: 97320 }, + "kiai shout": { any: 99531 }, + "jade bells": { any: 110844 }, + "nightingale sings": { any: 110845 }, + "door opens": { any: 110847 }, + "cloud attack": { any: 137835 }, + "single bird attack": { any: 137840 }, + "woodpeckers voice": { any: 138156 }, + "woodpecker sound": { any: 138157 }, + "creaking trees": { any: 138159 }, + "ghostly birds": { any: 138160 }, + "breaking inside": { any: 145774 }, + "undead dragon breath": { any: 145781 }, + "orb activation ii": { any: 145783 }, + "orb activation i": { any: 145788 }, + "earth elemental": { any: 145794 }, + "rogue - ah sparow": { any: 145800 }, + "well this is awkward": { any: 145802 }, + "well, hand it over": { any: 145803 }, + "you did explain the deal?": { any: 145805 }, + "don't you know your own name?": { any: 145806 }, + "that's not true": { any: 145807 }, + "elf commander i": { any: 145808 }, + "elf commander ii": { any: 145809 }, + "elf commander iii": { any: 145810 }, + "hammer strike": { any: 160565 }, + "bellows push": { any: 160566 }, + "woman screaming": { any: 211767 }, + "smashing glass": { any: 211769 }, + "acid dissolve": { any: 211800 }, + "acid drip": { any: 211801 }, + "seith's strike": { any: 211802 }, + "seith eats": { any: 211803 }, + "inferno trap": { any: 211806 }, + "the squasher": { any: 211807 }, + "acid spray": { any: 211808 }, + "blade trap": { any: 211809 }, + "spike trap": { any: 211810 }, + "creaking step": { any: 219746 }, + "stone wall slam": { any: 219760 }, + "roar (displacing)": { any: 222673 }, + "roar 1": { any: 222680 }, + "roar 2": { any: 222681 }, + "roar 3": { any: 222682 }, + "roar 4": { any: 222683 }, + "roar 5": { any: 222684 }, + "magical energy releases": { any: 222685 }, + "portal": { any: 222688 }, + "bb": { any: 225245 }, + "low d": { any: 225246 }, + "gb": { any: 225247 }, + "ab": { any: 225248 }, + "low bb": { any: 225249 }, + "low c": { any: 225250 }, + "bam bam": { any: 231920 }, + "dino roar": { any: 231998 }, + "fark bird": { any: 231999 }, + "hello bird": { any: 232002 }, + "whistler bird": { any: 232003 }, + "herrrup bird": { any: 232004 }, + "buhl buhp": { any: 232005 }, + "ka kah bird": { any: 232007 }, + "ball bopper": { any: 232009 }, + "laser guns": { any: 232010 }, + "bulb bulb frog": { any: 232012 }, + "narka nuk muk": { any: 232013 }, + "ribbut frog": { any: 232017 }, + "dino roar (distant)": { any: 232019 }, + "splosion": { any: 232020 }, + "battle chatter": { any: 232030 }, + "laughing man": { any: 248236 }, + "elephant trumpet": { any: 1931936 }, + "monkey hoots": { any: 248238 }, + "elephant roars": { any: 248240 }, + "seductive talk": { any: 248241 }, + "ape roars": { any: 248242 }, + "flirty": { any: 248243 }, + "pleasure": { any: 248244 }, + "room doors": { any: 248245 }, + "official realmsmith": { any: 264027 }, + "mystical lightning": { any: 266821 }, + "appeal 1": { any: 266834 }, + "appeal 2": { any: 266835 }, + "appeal 3": { any: 266836 }, + "you should have taken my offer": { any: 266840 }, + "devoratrix's monologue": { any: 266842 }, + "flap": { any: 279429 }, + "t-rex roar": { any: 279437 }, + "drudgeon 1": { any: 298140 }, + "pirate captain": { any: 319186 }, + "it's the kraken": { any: 319187 }, + "prepare to be boarded": { any: 319377 }, + "oraelus' speech": { any: 363583 }, + "door creaking open": { any: 425943 }, + "door slam": { any: 401080 }, + "baleful howl": { any: 401085 }, + "freaky child (one shot)": { any: 401088 }, + "secret door": { any: 2713172 }, + "secret stone door": { any: 1826476 }, + "distant wails": { any: 460223 }, + "extra scary stinger": { any: 460224 }, + "breach the earth": { any: 466211 }, + "savage bite": { any: 466278 }, + "hydra breach": { any: 481671 }, + "hydra roar": { any: 481901 }, + "distant hydra": { any: 481902 }, + "decayer": { any: 585276 }, + "he is the ancient": { any: 585277 }, + "ba dum tss": { any: 853914 }, + "open door": { any: 2710815 }, + "heavy stone door": { any: 1356391 }, + "entrance gong": { any: 1356400 }, + "big gong": { any: 1356610 }, + "spector hiss": { any: 1373065 }, + "magical windchimes": { any: 1373067 }, + "there be magic": { any: 1373070 }, + "transformation": { any: 1809707 }, + "howling at the moon": { any: 1809708 }, + "battle stabs": { any: 1809724 }, + "werewolf roar": { any: 1809764 }, + "claw swipe": { any: 1809820 }, + "hunting creature": { any: 1819543 }, + "avalanche!!!": { any: 1820834 }, + "thunder clap": { any: 1820970 }, + "ethereal element": { any: 1822137 }, + "portal deactivation": { any: 1822165 }, + "portal activation": { any: 1822207 }, + "sonic trap": { any: 1822210 }, + "swinging blade trap": { any: 1822212 }, + "net trap": { any: 1822215 }, + "stabbing blade trap": { any: 1822219 }, + "poison needle trap": { any: 1822220 }, + "fire trap": { any: 1822222 }, + "arrow trap": { any: 1822226 }, + "slashing cage trap": { any: 1822229 }, + "poison dart trap": { any: 2130492 }, + "ceiling spear trap": { any: 1822232 }, + "force blast trap": { any: 1822233 }, + "freezing spray trap": { any: 1822234 }, + "acid spray trap": { any: 1822235 }, + "gas trap chest": { any: 1822237 }, + "negatice energy trap": { any: 1822240 }, + "shattering crystal": { any: 1826481 }, + "powering up": { any: 1826547 }, + "lever activation": { any: 1827778 }, + "magic broom": { any: 1836567 }, + "incantation": { any: 1836568 }, + "beaker smash": { any: 1836569 }, + "door close": { any: 1836570 }, + "magical windchime": { any: 1836571 }, + "metal flex": { any: 1901916 }, + "mechanical arm": { any: 1901919 }, + "glitch": { any: 1901920 }, + "deep pulse": { any: 1901926 }, + "wildlife": { any: 1932011 }, + "distant thunder": { any: 1932014 }, + "surprised bird": { any: 1932015 }, + "lizard hiss": { any: 1932016 }, + "monkey chatter": { any: 1932017 }, + "monkey hoot": { any: 1932018 }, + "ape": { any: 1932020 }, + "official pathfinder": { any: 1944362 }, + "rust": { any: 1944363 }, + "antenna disarm": { any: 1944364 }, + "horror": { hit: 1947332 }, + "growl": { any: 1947341 }, + "chuckle": { any: 1947343 }, + "hiss": { any: 1947367 }, + "backwards thud": { any: 2870200 }, + "mastiff zombie growl": { any: 2065676 }, + "find clue/treasure stabs": { any: 2875361 }, + "ebon tide hero's music": { any: 2079958 }, + "shadow-touched panthers cry": { any: 2079993 }, + "ebon tide battle stabs": { any: 2910414 }, + "picking lock": { any: 2088575 }, + "find steed spell": { any: 2090039 }, + "horses upset": { any: 2090054 }, + "summon a shadowy mount": { any: 2090056 }, + "ghoul hiss": { any: 2094463 }, + "stryx screech": { any: 2094615 }, + "witchlight light blast": { any: 2095914 }, + "rune stones activated": { any: 2096215 }, + "opening creepy door": { any: 2910401 }, + "fall in water": { any: 2142184 }, + "shambling mound roar": { any: 2903519 }, + "giant snake bite": { any: 2143121 }, + "wight scream": { any: 2143123 }, + "shrieker scream": { any: 2903511 }, + "bulette roar": { any: 2145133 }, + "ankheg hisses": { any: 2145146 }, + "outsiders": { any: 2145282 }, + "orphans of the black cry": { any: 2155085 }, + "zombies calls": { any: 2155086 }, + "bandits cries": { any: 2155087 }, + "wererat scream": { any: 2155089 }, + "shadow goblin": { any: 2155090 }, + "thunderous wail": { any: 2156443 }, + "dramatic moments": { any: 2189690 }, + "undead dragon roar": { any: 2189757 }, + "attack us?!": { any: 2194089 }, + "i don't understand": { any: 2194090 }, + "good to see you are a person of your word": { any: 2194092 }, + "the lich will be thankful": { any: 2194094 }, + "run along now": { any: 2194095 }, + "i see your mind is made up": { any: 2194096 }, + "you're not making sense": { any: 2194097 }, + "what do you mean?": { any: 2194098 }, + "what are you talking about?": { any: 2194099 }, + "flirty giggle": { any: 2200613 }, + "smash door": { any: 2713163 }, + "they're breaking in": { any: 2200638 }, + "orb detonation": { any: 2201779 }, + "undead dragon bite": { any: 2208061 }, + "undead dragon claw": { any: 2208062 }, + "rapier slash": { any: 2208063 }, + "dagger slash": { any: 2208064 }, + "cheroot puff": { any: 2208065 }, + "earth elemental slam": { any: 2208066 }, + "earth elemental tremor": { any: 2208067 }, + "shortsword": { + any: 2208068, + miss: 2717043, + hit: 2717044, + critical: 2717045, + }, + "shortbow": { any: 2208069 }, + "skeleton hiss": { any: 2208070 }, + "elf commander commands": { any: 2208071 }, + "blue coats": { any: 2208073 }, + "greatsword": { hit: 3022880, miss: 3021165 }, + "opening a secret hatch": { any: 2377704 }, + "eerie moment": { any: 2412188 }, + "dramatic revelation": { any: 2412191 }, + "digging a grave": { any: 2459928 }, + "dispel the vines": { any: 2461598 }, + "fairy ring teleportation": { any: 2461599 }, + "barbed devil roar": { any: 2461641 }, + "ghast bite": { any: 2564388 }, + "otyugh roar": { any: 2461665 }, + "having a vision": { any: 2462780 }, + "drink water from well": { any: 2471491 }, + "mysterious something!!!": { any: 2487687 }, + "open chest": { any: 2865841 }, + "charm person spell": { any: 2487731 }, + "starting a fire": { any: 2487735 }, + "giant chicken cluck": { any: 2488719 }, + "giant pig grunts": { any: 2718004 }, + "big briar roar": { any: 2488758 }, + "trollstone appears": { any: 2488761 }, + "speak with animals spell": { any: 2488786 }, + "the switch": { any: 2488787 }, + "burning the horrid fetish": { any: 2488800 }, + "manticore roar": { any: 2492702 }, + "grick attack": { any: 2492706 }, + "black pudding roar": { any: 2492710 }, + "shadow attack": { any: 2853166 }, + "mahrax roar": { any: 2492725 }, + "flesh golem roar": { any: 2492792 }, + "the trumpeting of mahrax": { any: 2492800 }, + "freidel seals the entrance": { any: 2492801 }, + "dispel magic": { any: 2492802 }, + "maze spell": { any: 2492803 }, + "lighting the lantern": { any: 2495103 }, + "portal open": { any: 2554274 }, + "portal journey": { any: 2554289 }, + "roc call": { any: 2555952 }, + "roc cry": { any: 2555953 }, + "floating construct passby": { any: 2555954 }, + "walking construct passby": { any: 2555955 }, + "epic moment": { any: 2556119 }, + "surge of energy": { any: 2693484 }, + "blowing the horn": { any: 2693490 }, + "open creepy door": { any: 2713165 }, + "open door (revel)": { any: 2693767 }, + "glyph of warding": { any: 2699206 }, + "ghast stench": { any: 2709242 }, + "wall of thorns": { any: 2710829 }, + "statue teleports": { any: 2710832 }, + "rose golem roar": { any: 2710845 }, + "knock on door": { any: 2710923 }, + "large monastery door": { any: 2710947 }, + "ring desk bell": { any: 2710949 }, + "ink devil flys away": { any: 2710980 }, + "flail": { hit: 2712690, critical: 2712691, miss: 2712692 }, + "glaive": { hit: 2712706, critical: 2712708, miss: 3022882 }, + "halberd": { miss: 2712707, hit: 3022883, critical: 3022884 }, + "iron gate open": { any: 2712778 }, + "blast of frigid cold": { any: 2712779 }, + "glyph trap explodes": { any: 2713160 }, + "statue destroyed": { any: 2713448 }, + "lance": { hit: 2714420, critical: 2714421, miss: 2714422 }, + "net": { hit: 2714620, critical: 2714624, miss: 2714625 }, + "painful screech": { any: 2714789 }, + "restore the founder\u2019s statue": { any: 2715028 }, + "pike": { hit: 2717016, critical: 2717019, miss: 2717020 }, + "trident": { critical: 2717021, hit: 2717022, miss: 2717023 }, + "unarmed": { hit: 2717028, miss: 2717029, critical: 2717030 }, + "unsheathe slow": { any: 2717032 }, + "unsheathe fast": { any: 2717033 }, + "rapier": { miss: 2717034, hit: 2717035, critical: 2717036 }, + "dagger": { miss: 2717037, hit: 2717038, critical: 2717039 }, + "longsword": { miss: 2717040, hit: 2717041, critical: 2717042 }, + "sheathe": { any: 2717049 }, + "quarterstaff": { miss: 2717050, hit: 2717051, critical: 2717052 }, + "whip": { miss: 2717053, critical: 2717054, hit: 2717068 }, + "hammerbell alarms": { any: 2757992 }, + "stolen time": { any: 2760628 }, + "copper bell": { any: 2760629 }, + "chime of undoing": { any: 2760630 }, + "cuckoo alarms": { any: 2760633 }, + "command and suggestion spell": { any: 2767098 }, + "polymorph into an eagle": { any: 2769059 }, + "churchbells": { any: 2833555 }, + "hunting horns": { any: 2853172 }, + "vampire taunt": { any: 2853182 }, + "time splinter": { any: 2862659 }, + "portal activated": { any: 2862786 }, + "portal closes": { any: 2862787 }, + "stone chimera roar": { any: 2862953 }, + "black pudding": { any: 2862987 }, + "homunculus gibberish": { any: 2863004 }, + "gorgon roar": { any: 2864013 }, + "petrifying breath": { any: 2864014 }, + "gorgon stomp": { any: 2864028 }, + "hero ages dramatic thud": { any: 2864063 }, + "pour acid on resin": { any: 2865840 }, + "mal moves": { any: 2865842 }, + "opening mysterious door": { any: 2865844 }, + "something weird - music cue": { any: 2869182 }, + "hitting resin": { any: 2869190 }, + "flying rubble": { any: 2869205 }, + "collapsing bridge": { any: 2870188 }, + "explosive glyph": { any: 2870189 }, + "open sarcophagus": { any: 2870213 }, + "dive": { any: 2870244 }, + "crocodile bites": { any: 2873085 }, + "start of the sinking": { any: 2873162 }, + "arcane lift up": { any: 2874764 }, + "arcane lift down": { any: 2874766 }, + "wort appears": { any: 2875311 }, + "wort disappears": { any: 2875313 }, + "stirge screech": { any: 2879251 }, + "stunning stare": { any: 2879260 }, + "swamp naga bite": { any: 2879378 }, + "symbol of stunning": { any: 2879379 }, + "sacred flame spell": { any: 2879380 }, + "portal to the shadow realm": { any: 2879382 }, + "bugbear roar": { any: 2879385 }, + "spider hiss": { any: 2879448 }, + "awakened shrubs scream": { any: 2879458 }, + "receive shard": { any: 2903408 }, + "ominous braams": { any: 2903409 }, + "wraith cries": { any: 2903416 }, + "specter hiss": { any: 2903418 }, + "elder shadow drake roar": { any: 2903483 }, + "ettercap growl": { any: 2903515 }, + "she has returned": { any: 2910388 }, + "replacing the shards": { any: 2910836 }, + "club": { miss: 3021162, hit: 3021163, critical: 3021164 }, + "greatclub": { hit: 3021166, critical: 3021167 }, + "handaxe": { hit: 3021168, miss: 3021169, critical: 3021171 }, + "javelin": { miss: 3021173, hit: 3021177, critical: 3021180 }, + "warhammer": { miss: 3021181, critical: 3021182, hit: 3021183 }, + "light hammer": { miss: 3021184, hit: 3021185, critical: 3021186 }, + "sickle": { miss: 3021187, critical: 3021188, hit: 3021189 }, + "battle axe": { miss: 3021190, critical: 3021192, hit: 3021193 }, + "war pick": { miss: 3021196, hit: 3021197, critical: 3021198 }, + "morningstar": { miss: 3022792, hit: 3022794, critical: 3022887 }, + "mace": { critical: 3022793, miss: 3022885, hit: 3022886 }, + "spear": { miss: 3022795, critical: 3022796, hit: 3022797 }, + "great club": { miss: 3022876 }, + "greataxe": { critical: 3022877, hit: 3022878, miss: 3022879 }, + "greatsword critcal": { any: 3022881 }, + "maul": { miss: 3022928, hit: 3022929, critical: 3022930 }, +}; diff --git a/module/theater-of-the-mind.js b/module/theater-of-the-mind.js index 0c692df..a56935d 100644 --- a/module/theater-of-the-mind.js +++ b/module/theater-of-the-mind.js @@ -1,19 +1,8 @@ -const FEATURE_SYRIN = false; - -const THEATER_SOUNDS = { - "attack roll": { - rapier: { - miss: "2717034", - hit: "2717035", - crit: "2717036", - }, - }, -}; +import { THEATER_SOUNDS } from "./sounds.js"; import "./styles/theater-of-the-mind.scss"; let pendingMessages = []; -let enableSounds = false; let isSyrinscapeInstalled = false; let isAttackRollCheckInstalled = false; @@ -38,7 +27,7 @@ const getPlayerData = () => { }; const classNamesAndLevels = Object.values(userChar.classes).map( - (c) => `${c.name} ${c.system.levels}` + (c) => `${c.name} ${c.system.levels}`, ); const charToken = userChar.prototypeToken; @@ -46,22 +35,22 @@ const getPlayerData = () => { const charSenses = []; if (userSys.attributes.senses.darkvision) { charSenses.push( - `Darkvision ${userSys.attributes.senses.darkvision} ${userSys.attributes.senses.units}` + `Darkvision ${userSys.attributes.senses.darkvision} ${userSys.attributes.senses.units}`, ); } if (userSys.attributes.senses.blindsight) { charSenses.push( - `Blindsight ${userSys.attributes.senses.blindsight} ${userSys.attributes.senses.units}` + `Blindsight ${userSys.attributes.senses.blindsight} ${userSys.attributes.senses.units}`, ); } if (userSys.attributes.senses.tremorsense) { charSenses.push( - `Tremorsense ${userSys.attributes.senses.tremorsense} ${userSys.attributes.senses.units}` + `Tremorsense ${userSys.attributes.senses.tremorsense} ${userSys.attributes.senses.units}`, ); } if (userSys.attributes.senses.truesight) { charSenses.push( - `Truesight ${userSys.attributes.senses.truesight} ${userSys.attributes.senses.units}` + `Truesight ${userSys.attributes.senses.truesight} ${userSys.attributes.senses.units}`, ); } if (userSys.attributes.senses.special) { @@ -102,13 +91,20 @@ const convertPlayerDataToTable = () => { let table = ``; - table += ``; + const localize = { + name: game.i18n.localize("theater-of-the-mind.party-sheet.name"), + race: game.i18n.localize("theater-of-the-mind.party-sheet.race"), + senses: game.i18n.localize("theater-of-the-mind.party-sheet.senses"), + classes: game.i18n.localize("theater-of-the-mind.party-sheet.classes"), + }; + + table += ``; for (const stat in players[0].stats) { table += ``; } - table += ``; + table += ``; - table += ``; + table += ``; for (let i = 0; i < 6; i++) { table += ``; } @@ -161,7 +157,7 @@ const PartySheetDialog = new Dialog({ render: () => { PartySheetDialog.setPosition({ height: "auto", - width: "auto", + width: 600, }); }, }); @@ -178,7 +174,8 @@ function togglePartySheet() { async function playSound(message, type, hitmisscrit) { // If we're not using the fvtt-syrin-control (Syrinscape) module, return // TODO: Use our own implementation of Syrinscape? Maybe we can use the API to search instead of hard coding sounds. - if (!enableSounds) { + + if (!game.settings.get("theater-of-the-mind", "enableSounds")) { return; } @@ -189,19 +186,27 @@ async function playSound(message, type, hitmisscrit) { if (matchResult) { const weapon = matchResult[1]; + // Get the sound from the THEATER_SOUNDS object - const sound = - THEATER_SOUNDS[type.toLowerCase()][weapon.toLowerCase()][ - hitmisscrit.toLowerCase() - ]; - if (sound) { - game.syrinscape.playElement(sound); + const soundweapon = THEATER_SOUNDS[weapon.toLowerCase()]; + if (!soundweapon) { + log(`No weapon found [${weapon.toLowerCase()}].`); + return; + } + const soundid = + soundweapon[hitmisscrit.toLowerCase()] || soundweapon["any"]; + + if (!soundid) { + log(`No weapon sub-type found [${weapon.toLowerCase()}].`); + return; + } else { + game.syrinscape.playElement(soundid); } } } async function parseMessagesForSounds(message) { - if (!isSyrinscapeInstalled) { + if (!game.settings.get("theater-of-the-mind", "enableSounds")) { return; } const flavor = message?.flavor || ""; @@ -212,21 +217,21 @@ async function parseMessagesForSounds(message) { if (message.flags?.["attack-roll-check-5e"]?.isResultCard) { const roll = message.content.match( - /
(.*)<\/div>/ + /
(.*)<\/div>/, )[1]; const applicableMessages = pendingMessages.filter( (imessage) => imessage.flavor.toLowerCase().includes("attack roll") && - imessage.content === roll + imessage.content === roll, ) || []; if (applicableMessages.length > 0) { const applicableMessage = applicableMessages[0]; pendingMessages = pendingMessages.filter( - (imessage) => imessage !== applicableMessage + (imessage) => imessage !== applicableMessage, ); const hitmisscrit = message.content.match( - /
/ + /
/, )[1]; playSound(applicableMessage, "Attack Roll", hitmisscrit); } @@ -242,29 +247,51 @@ Hooks.on("preCreateChatMessage", (_chatLog, message, _chatData) => { Hooks.on("init", () => { log("Initializing"); + game.settings.register("theater-of-the-mind", "enableSounds", { + "name": "theater-of-the-mind.settings.enable-sounds.name", + "hint": "theater-of-the-mind.settings.enable-sounds.hint", + "scope": "world", + "config": true, + "default": false, + "type": Boolean, + }); + + game.settings.register("theater-of-the-mind", "enablePartySheet", { + "name": "theater-of-the-mind.settings.enable-party-sheet.name", + "hint": "theater-of-the-mind.settings.enable-party-sheet.hint", + "scope": "world", + "config": true, + "default": true, + "type": Boolean, + "onChange": () => { + Hooks.call("renderSceneControls"); + }, + }); }); Hooks.on("ready", async () => { log("Ready"); - if (FEATURE_SYRIN) { - // Check if Syrinscape plugin is installed - isSyrinscapeInstalled = - game.modules.get("fvtt-syrin-control")?.active || false; - log(`Syrinscape is installed: ${isSyrinscapeInstalled}`); - - // Check if Attack Roll Check is installed - isAttackRollCheckInstalled = - game.modules.get("foundryvtt-attack-roll-check-5e")?.active || false; - log(`Attack Roll Check is installed: ${isSyrinscapeInstalled}`); - enableSounds = isSyrinscapeInstalled && isAttackRollCheckInstalled; - } + // Check if Syrinscape plugin is installed + isSyrinscapeInstalled = + game.modules.get("fvtt-syrin-control")?.active || false; + log(`Syrinscape is installed: ${isSyrinscapeInstalled}`); + + // Check if Attack Roll Check is installed + isAttackRollCheckInstalled = + game.modules.get("attack-roll-check-5e")?.active || false; + log(`Attack Roll Check is installed: ${isAttackRollCheckInstalled}`); + const soundsReady = isSyrinscapeInstalled && isAttackRollCheckInstalled; + log(`Sounds enabled: ${soundsReady}`); }); // When a player connects or disconnects, refresh the combined view Hooks.on("renderPlayerList", () => { // Check if user is GM - if (!game.user.isGM) { + if ( + !game.user.isGM || + !game.settings.get("theater-of-the-mind", "enablePartySheet") + ) { return; } if (PartySheetDialog.rendered) { @@ -274,9 +301,10 @@ Hooks.on("renderPlayerList", () => { }); Hooks.on("renderSceneControls", () => { - if (!game.user.isGM) { - return; - } + const showButton = + game.user.isGM && + game.settings.get("theater-of-the-mind", "enablePartySheet"); + const button = $(`
  • {
  • `); button.click(() => togglePartySheet()); const controls = $("#tools-panel-token"); - controls.append(button); + + // Render the button if the showButton is true and the button doesn't already exist + if (showButton && controls.find(".control-tool[data-tool='PartySheet']")) { + controls.append(button); + } else if (!showButton) { + controls.find(".control-tool[data-tool='PartySheet']").remove(); + } }); diff --git a/rollup.config.mjs b/rollup.config.mjs index 4ef9873..aca25b2 100644 --- a/rollup.config.mjs +++ b/rollup.config.mjs @@ -48,7 +48,7 @@ export default () => { // Sanity check to make sure parent directory of FVTTDEV_DEPLOY_PATH exists. if (!fs.existsSync(path.dirname(process.env.FVTTDEV_DEPLOY_PATH))) { throw Error( - `FVTTDEV_DEPLOY_PATH does not exist: ${process.env.FVTTDEV_DEPLOY_PATH}` + `FVTTDEV_DEPLOY_PATH does not exist: ${process.env.FVTTDEV_DEPLOY_PATH}`, ); } @@ -91,7 +91,10 @@ export default () => { json(), // Allows import of JSON; used in dialog Handlebars content. string({ include: ["**/*.css", "**/*.html"] }), // Allows loading strings as ES6 modules; HTML and CSS. copy({ - targets: [{ src: "module/module.json", dest: DIR }], + targets: [ + { src: "module/module.json", dest: DIR }, + { src: "module/lang", dest: DIR }, + ], }), ], },
    Name
    Race
    ${localize.name}
    ${localize.race}
    ${stat.toUpperCase()}ACInvSenses
    ACInv${localize.senses}
    Classes
    ${localize.classes}