This commit is contained in:
TheGiddyLimit
2024-05-21 22:49:09 +01:00
parent 9c8ae15ff7
commit d2f2daa34e
58 changed files with 44706 additions and 397 deletions

File diff suppressed because one or more lines are too long

View File

@@ -592,7 +592,8 @@
"path": "adventure/AitFR-FCD/thumbnail/16_1476395147.webp"
},
"id": "053",
"credit": "Will Hindmarch"
"credit": "Will Hindmarch",
"expectsLightBackground": true
},
{
"type": "entries",
@@ -1268,7 +1269,8 @@
},
"credit": "Russ Nicholson",
"width": 1243,
"height": 1060
"height": 1060,
"expectsLightBackground": true
},
{
"type": "image",
@@ -1278,7 +1280,8 @@
},
"credit": "Justine Jones",
"width": 1927,
"height": 1624
"height": 1624,
"expectsLightBackground": true
}
]
}

File diff suppressed because it is too large Load Diff

View File

@@ -22898,6 +22898,7 @@
"grid": {
"type": "none"
},
"mapName": "Great City of Ank'Harel",
"title": "A map of the great city of Ank'Harel, the caverns of the last city of Cael Morrow, and the mysterious realm beneath\u2014as explored by the Allegiance of Allsight. 836 PD",
"width": 1189,
"height": 1700,

File diff suppressed because it is too large Load Diff

View File

@@ -23650,7 +23650,8 @@
"distance": 10
},
"mapParent": {
"id": "5d4"
"id": "5d4",
"offsetY": -244
},
"credit": "Dyson Logos"
}

View File

@@ -1493,7 +1493,7 @@
"distance": 10
},
"mapParent": {
"id": "1b8"
"id": "1b7"
},
"credit": "Claudio Pozas"
}

View File

@@ -2806,7 +2806,7 @@
"distance": 50
},
"mapParent": {
"id": "0db",
"id": "0da",
"autoScale": true
}
},

View File

@@ -1744,21 +1744,21 @@
"type": "image",
"href": {
"type": "internal",
"path": "adventure/LRDT/knight.webp"
"path": "adventure/LRDT/mage.webp"
},
"title": "{@5etoolsImg Elf Wizard|pdf/LRDT/Elf Wizard.pdf}",
"width": 883,
"height": 993
"width": 649,
"height": 908
},
{
"type": "image",
"href": {
"type": "internal",
"path": "adventure/LRDT/mage.webp"
"path": "adventure/LRDT/knight.webp"
},
"title": "{@5etoolsImg Gnome Fighter|pdf/LRDT/Gnome Fighter.pdf}",
"width": 649,
"height": 908
"width": 883,
"height": 993
},
{
"type": "image",

View File

@@ -21586,7 +21586,7 @@
"page": 112,
"id": "26a",
"entries": [
"Shortly after the characters arrive in Waterdeep, a cloud giant castle emerges from the clouds overhead and looms above the city, its ominous shadow causing widespread panic. Lady Laeral Silverhand dispatches heralds to calm the people and assure them that no harm will befall the city. If the adventurers decide to investigate, they must devise a method of reaching the giant castle, which hangs 1,000 feet in the air. The castle has a configuration similar to that of Lyn Armaal (see chapter 9, \"{@adventure Castle of the Cloud Giants|SKT|11}\") and is home to a neutral good clan made up of four adult {@creature Cloud Giant||cloud giants}, three {@creature Young Cloud Giant|SKT|young cloud giants}, four pet {@creature Griffon||griffons}, and eight {@creature Stone Giant||stone giants} that serve as guards. The {@creature Young Cloud Giant|SKT|young cloud giants} use the {@creature hill giant} statistics, with the following changes:",
"Shortly after the characters arrive in Waterdeep, a cloud giant castle emerges from the clouds overhead and looms above the city, its ominous shadow causing widespread panic. Lady Laeral Silverhand dispatches heralds to calm the people and assure them that no harm will befall the city. If the adventurers decide to investigate, they must devise a method of reaching the giant castle, which hangs 1,000 feet in the air. The castle has a configuration similar to that of Lyn Armaal (see chapter 9, \"{@adventure Castle of the Cloud Giants|SKT|9}\") and is home to a neutral good clan made up of four adult {@creature Cloud Giant||cloud giants}, three {@creature Young Cloud Giant|SKT|young cloud giants}, four pet {@creature Griffon||griffons}, and eight {@creature Stone Giant||stone giants} that serve as guards. The {@creature Young Cloud Giant|SKT|young cloud giants} use the {@creature hill giant} statistics, with the following changes:",
{
"type": "list",
"items": [

View File

@@ -329,8 +329,9 @@
"type": "insetReadaloud",
"id": "01c",
"entries": [
"Leilon is a ruined town encircled by an earthen rampart. To the southwest, new settlers attempt to build docks for barges, made to cross the marsh and meet merchant ships in the sea.",
"Outside of town, a settler camp nestles under the trees alongside the High Road. At the center of tower, the House of Thalivar, a wizard tower, rises like a beacon, four times the height of every other building. The town lies in ruin, but the settlers from Neverwinter work quickly, clearing and reconstructing."
"The half-finished palisade of Leilon will soon make a complete semicircle on the northeast side of the town, defended by an earthen rampart. To the southwest, new settlers attempt to build docks for barges, made to cross the marsh and meet merchant ships in the sea.",
"Outside of town, the settlers' campground becomes ever smaller as new buildings made of wood or stone with thatched roofs are erected in Leilon's muddy streets. At the center of it all, the New House of Thalivar, a cylindrical wizard tower, rises like a beacon, four times the height of every other building. Some lots still lie in ruins, but the settlers work quickly, clearing and reconstructing.",
"Visitors with coin to spend are welcome in Leilon, and adventurers are the settlers' favorites. While there is much work to be done within the town, there are also missions to accomplish outside the settlement, listed on the job board at the fishery."
]
},
{

View File

@@ -936,10 +936,10 @@
"imageType": "map",
"grid": {
"type": "square",
"size": 73,
"offsetX": -37,
"offsetY": 34,
"scale": 2
"size": 145,
"offsetX": -45,
"offsetY": 9,
"scale": 4
},
"id": "146",
"mapRegions": [

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1728,15 +1728,6 @@
"width": 2400,
"height": 1724
},
{
"type": "image",
"href": {
"type": "internal",
"path": "adventure/WDH/Yawning-Portal-Key.webp"
},
"width": 1000,
"height": 587
},
{
"type": "entries",
"name": "Familiar Faces",
@@ -3597,52 +3588,7 @@
"page": 31,
"id": "096",
"entries": [
"See {@adventure appendix C|WDH|12} for a handout showing the floor plan of this building. Give a copy of this handout to your players as their characters begin to explore Trollskull Manor.",
{
"type": "gallery",
"images": [
{
"type": "image",
"imageType": "map",
"grid": {
"type": "square",
"size": 57,
"offsetY": 19,
"scale": 2
},
"href": {
"type": "internal",
"path": "adventure/WDH/Trollskull-Manor-DM.webp"
},
"title": "Trollskull Manor",
"width": 589,
"height": 800,
"id": "528"
},
{
"type": "image",
"imageType": "mapPlayer",
"grid": {
"type": "square",
"size": 71,
"offsetX": 2,
"offsetY": 28,
"scale": 2
},
"href": {
"type": "internal",
"path": "adventure/WDH/Trollskull-Manor-Players.webp"
},
"title": "Player Version",
"width": 737,
"height": 1000,
"mapParent": {
"id": "528",
"autoScale": true
}
}
]
},
"See {@adventure appendix C|WDH|13|Trollskull Manor} for a handout showing the floor plan of this building. Give a copy of this handout to your players as their characters begin to explore Trollskull Manor.",
"Four stories tall and boasting balconies, a turret, and five chimneys, the abandoned building is one of the grandest in Trollskull Alley. Characters can refurnish, rebuild, rename, and otherwise personalize their new stronghold to their hearts' content.",
{
"type": "entries",
@@ -33288,62 +33234,182 @@
"id": "4e6",
"entries": [
{
"type": "image",
"href": {
"type": "internal",
"path": "adventure/WDH/Friendly-Faces.webp"
},
"title": "Yawning Portal Friendly Faces",
"width": 1000,
"height": 1276
"type": "entries",
"name": "Yawning Portal Friendly Faces",
"entries": [
{
"type": "image",
"href": {
"type": "internal",
"path": "adventure/WDH/Friendly-Faces.webp"
},
"width": 1000,
"height": 1276
}
],
"id": "53b"
},
{
"type": "image",
"href": {
"type": "internal",
"path": "adventure/WDH/The-Code-Legal.webp"
},
"title": "The Code Legal",
"width": 2201,
"height": 2714
"type": "entries",
"name": "The Code Legal",
"entries": [
{
"type": "image",
"href": {
"type": "internal",
"path": "adventure/WDH/The-Code-Legal.webp"
},
"width": 2201,
"height": 2714
}
],
"id": "53c"
},
{
"type": "image",
"imageType": "mapPlayer",
"href": {
"type": "internal",
"path": "adventure/WDH/Trollskull-Manor-Players.webp"
},
"title": "Trollskull Manor",
"width": 737,
"height": 1000,
"grid": {
"type": "square",
"size": 71,
"offsetX": 2,
"offsetY": 28,
"scale": 2
}
"type": "entries",
"name": "Trollskull Manor",
"entries": [
{
"type": "gallery",
"images": [
{
"type": "image",
"imageType": "map",
"href": {
"type": "internal",
"path": "adventure/WDH/Trollskull-Manor-DM.webp"
},
"title": "Trollskull Manor",
"id": "528",
"width": 2513,
"height": 3338
},
{
"type": "image",
"imageType": "mapPlayer",
"href": {
"type": "internal",
"path": "adventure/WDH/Trollskull-Manor-Players.webp"
},
"title": "Trollskull Manor (Player Version)",
"width": 2100,
"height": 2850
}
]
}
],
"id": "53d"
},
{
"type": "image",
"href": {
"type": "internal",
"path": "adventure/WDH/The-Yawning-Portal.webp"
},
"title": "The Yawning Portal",
"width": 2400,
"height": 1724
},
{
"type": "image",
"href": {
"type": "internal",
"path": "adventure/WDH/Yawning-Portal-Key.webp"
},
"title": "The Yawning Portal Key",
"width": 1000,
"height": 587
"type": "entries",
"name": "Key to the Yawning Portal",
"page": 224,
"entries": [
"Here are the names of the numbered characters featured in the Yawning Portal illustration in the Introduction.",
{
"type": "list",
"style": "list-hang-notitle",
"columns": 2,
"items": [
"1. Victoro Cassalanter",
"2. Ammalia Cassalanter",
"3. Vajra Safahr",
"4. Renaer Neverember",
"5. Laeral Silverhand",
"6. Mordenkainen",
"7. Qilué Veladorn",
"8. Alustriel Silverhand",
"9. The Simbul",
"10. Elminster",
"11. Harkle Harpell",
"12. Storm Silverhand",
"13. Syluné Silverhand",
"14. Dove Falconhand",
"15. Florin Falconhand",
"16. Illistyl Elventree",
"17. Jhessail Silventree",
"18. Merith Strongbow",
"19. Lanseril Snowmantle",
"20. Artus Cimber",
"21. Volothamp Geddarm",
"22. Minsc and Boo",
"23. Krydle",
"24. Delina",
"25. Vartan Hai Sylvar",
"26. Priam Agrivar",
"27. Ishi Barasume",
"28. Minder",
"29. Foxilon Cardluck",
"30. Shandie",
"31. Obaya Uday",
"32. Manshoon",
"33. Yoshimo",
"34. The Nameless One",
"35. Valygar Corthala",
"36. Abdel Adrian",
"37. Hexxat",
"38. Pikel Bouldershoulder",
"39. Ivan Bouldershoulder",
"40. Cadderly Bonaduce",
"41. Hrolf",
"42. Drizzt Do'Urden",
"43. Guenhwyvar",
"44. Ruqiah",
"45. Reginald Roundshield",
"46. Krebbyg Masq'il'yr",
"47. Spider Parrafin",
"48. Arkhan the Cruel",
"49. Tyril Tallguy",
"50. Dagny Halvor",
"51. Jamilah",
"52. Hitch",
"53. Dragonbait",
"54. Brawlwin Chainminer",
"55. Durnan (proprietor)",
"56. Skip Brickard",
"57. Diath Woodrow",
"58. Evelyn Marthain",
"59. Strix",
"60. Alias",
"61. Akabar Bel Akash",
"62. Olive Rustkettle",
"63. Mirt",
"64. The Black Viper",
"65. Artemis Entreri",
"66. Joppa",
"67. Fel'rekt Lafeen",
"68. Soluun Xibrindas",
"69. Jarlaxle Baenre",
"70. Danilo Thann",
"71. Paultin Seppa",
"72. Calliope",
"73. Ziraj the Hunter",
"74. Skeemo Weirdbottle",
"75. Davil Starsong",
"76. Tashlyn Yafeera",
"77. Istrid Horn",
"78. Nihiloor",
"79. Noska Ur'gray",
"80. Nar'l Xibrindas",
"81. Ahmaergo",
"82. Thorvin Twinbeard",
"83. Ott Steeltoes",
"84. Xanathar",
"85. Matthew Mercer"
]
},
{
"type": "image",
"href": {
"type": "internal",
"path": "adventure/WDH/The-Yawning-Portal.webp"
},
"title": "The Yawning Portal",
"width": 2400,
"height": 1724
}
],
"id": "53e"
}
]
},

File diff suppressed because it is too large Load Diff

View File

@@ -393,6 +393,9 @@
"otherSources": [
{
"source": "LoX"
},
{
"source": "VEoR"
}
],
"size": [
@@ -2409,6 +2412,11 @@
"name": "Cosmic Horror",
"source": "BAM",
"page": 18,
"otherSources": [
{
"source": "VEoR"
}
],
"size": [
"G"
],
@@ -2796,6 +2804,11 @@
"name": "Eye Monger",
"source": "BAM",
"page": 21,
"otherSources": [
{
"source": "VEoR"
}
],
"size": [
"L"
],
@@ -5637,6 +5650,11 @@
"name": "Night Scavver",
"source": "BAM",
"page": 49,
"otherSources": [
{
"source": "VEoR"
}
],
"size": [
"H"
],
@@ -8927,6 +8945,23 @@
"see Disembodied Voice below"
],
"cr": "16",
"spellcasting": [
{
"name": "Wish",
"type": "spellcasting",
"headerEntries": [
"The zodar casts the {@spell wish} spell, requiring no spell components and using Charisma as the spellcasting ability (spell save {@dc 17}). After casting this spell, the zodar turns to dust and is destroyed."
],
"will": [
{
"entry": "{@spell wish}",
"hidden": true
}
],
"ability": "cha",
"displayAs": "action"
}
],
"trait": [
{
"name": "Disembodied Voice",
@@ -8971,12 +9006,6 @@
"entries": [
"The zodar magically warps space around one creature it can see within 60 feet of itself. The target must make a {@dc 21} Constitution saving throw. On a failed save, the target takes 22 ({@damage 4d10}) force damage, and the zodar teleports it, along with any equipment it's wearing or carrying, up to 60 feet to an unoccupied space that the zodar can see and that can support the target. On a successful save, the target takes half as much damage and isn't teleported."
]
},
{
"name": "Wish",
"entries": [
"The zodar casts the {@spell wish} spell, requiring no spell components and using Charisma as the spellcasting ability (spell save {@dc 17}). After casting this spell, the zodar turns to dust and is destroyed."
]
}
],
"traitTags": [

View File

@@ -6954,6 +6954,11 @@
"isNamedCreature": true,
"source": "DSotDQ",
"page": 206,
"otherSources": [
{
"source": "VEoR"
}
],
"size": [
"M"
],
@@ -7108,6 +7113,12 @@
]
}
],
"altArt": [
{
"name": "Lord Soth",
"source": "VEoR"
}
],
"traitTags": [
"Legendary Resistances",
"Magic Resistance",

View File

@@ -4022,6 +4022,9 @@
},
{
"source": "DoDk"
},
{
"source": "VEoR"
}
],
"size": [
@@ -6387,6 +6390,9 @@
},
{
"source": "SatO"
},
{
"source": "VEoR"
}
],
"size": [
@@ -7606,6 +7612,9 @@
},
{
"source": "DoDk"
},
{
"source": "VEoR"
}
],
"size": [
@@ -8377,6 +8386,9 @@
},
{
"source": "DoDk"
},
{
"source": "VEoR"
}
],
"size": [
@@ -8698,6 +8710,9 @@
},
{
"source": "DoDk"
},
{
"source": "VEoR"
}
],
"size": [
@@ -10215,6 +10230,9 @@
},
{
"source": "GHLoE"
},
{
"source": "VEoR"
}
],
"size": [
@@ -10414,6 +10432,9 @@
},
{
"source": "BMT"
},
{
"source": "VEoR"
}
],
"size": [
@@ -10825,6 +10846,9 @@
},
{
"source": "DoDk"
},
{
"source": "VEoR"
}
],
"size": [
@@ -11018,6 +11042,9 @@
},
{
"source": "BMT"
},
{
"source": "VEoR"
}
],
"size": [
@@ -11495,6 +11522,9 @@
},
{
"source": "ToFW"
},
{
"source": "VEoR"
}
],
"size": [
@@ -11929,6 +11959,9 @@
"otherSources": [
{
"source": "TftYP"
},
{
"source": "VEoR"
}
],
"size": [
@@ -12301,6 +12334,9 @@
},
{
"source": "BMT"
},
{
"source": "VEoR"
}
],
"size": [
@@ -16278,6 +16314,9 @@
},
{
"source": "DoDk"
},
{
"source": "VEoR"
}
],
"size": [
@@ -16837,6 +16876,9 @@
},
{
"source": "DoDk"
},
{
"source": "VEoR"
}
],
"size": [
@@ -17238,6 +17280,9 @@
},
{
"source": "BMT"
},
{
"source": "VEoR"
}
],
"size": [
@@ -17433,6 +17478,9 @@
},
{
"source": "BGDIA"
},
{
"source": "VEoR"
}
],
"size": [
@@ -17834,6 +17882,9 @@
},
{
"source": "DoDk"
},
{
"source": "VEoR"
}
],
"size": [
@@ -18079,6 +18130,9 @@
},
{
"source": "DoDk"
},
{
"source": "VEoR"
}
],
"size": [
@@ -18211,6 +18265,9 @@
},
{
"source": "SatO"
},
{
"source": "VEoR"
}
],
"size": [
@@ -18840,6 +18897,9 @@
},
{
"source": "BMT"
},
{
"source": "VEoR"
}
],
"size": [
@@ -19076,6 +19136,9 @@
},
{
"source": "PSI"
},
{
"source": "VEoR"
}
],
"size": [
@@ -20053,6 +20116,9 @@
},
{
"source": "DoDk"
},
{
"source": "VEoR"
}
],
"size": [
@@ -21296,6 +21362,9 @@
},
{
"source": "SatO"
},
{
"source": "VEoR"
}
],
"size": [
@@ -22062,6 +22131,9 @@
},
{
"source": "WBtW"
},
{
"source": "VEoR"
}
],
"size": [
@@ -22605,6 +22677,9 @@
},
{
"source": "DoDk"
},
{
"source": "VEoR"
}
],
"size": [
@@ -22797,6 +22872,9 @@
},
{
"source": "DoDk"
},
{
"source": "VEoR"
}
],
"size": [
@@ -23525,6 +23603,9 @@
},
{
"source": "DoDk"
},
{
"source": "VEoR"
}
],
"size": [
@@ -24355,6 +24436,9 @@
},
{
"source": "DoDk"
},
{
"source": "VEoR"
}
],
"size": [
@@ -24769,6 +24853,9 @@
},
{
"source": "ToFW"
},
{
"source": "VEoR"
}
],
"size": [
@@ -26565,6 +26652,9 @@
},
{
"source": "SatO"
},
{
"source": "VEoR"
}
],
"size": [
@@ -27209,6 +27299,9 @@
},
{
"source": "DoDk"
},
{
"source": "VEoR"
}
],
"size": [
@@ -27337,6 +27430,9 @@
},
{
"source": "ToFW"
},
{
"source": "VEoR"
}
],
"size": [
@@ -28485,6 +28581,9 @@
},
{
"source": "DoDk"
},
{
"source": "VEoR"
}
],
"size": [
@@ -28718,6 +28817,9 @@
},
{
"source": "GHLoE"
},
{
"source": "VEoR"
}
],
"size": [
@@ -29436,6 +29538,9 @@
},
{
"source": "CM"
},
{
"source": "VEoR"
}
],
"size": [
@@ -30295,6 +30400,9 @@
},
{
"source": "PSA"
},
{
"source": "VEoR"
}
],
"size": [
@@ -31168,6 +31276,9 @@
},
{
"source": "PSX"
},
{
"source": "VEoR"
}
],
"size": [
@@ -31325,6 +31436,9 @@
},
{
"source": "BMT"
},
{
"source": "VEoR"
}
],
"size": [
@@ -32139,6 +32253,9 @@
},
{
"source": "BMT"
},
{
"source": "VEoR"
}
],
"size": [
@@ -32679,6 +32796,9 @@
},
{
"source": "DoDk"
},
{
"source": "VEoR"
}
],
"size": [
@@ -34303,6 +34423,9 @@
"otherSources": [
{
"source": "SatO"
},
{
"source": "VEoR"
}
],
"size": [
@@ -35420,6 +35543,9 @@
},
{
"source": "BMT"
},
{
"source": "VEoR"
}
],
"size": [
@@ -35651,6 +35777,9 @@
},
{
"source": "BMT"
},
{
"source": "VEoR"
}
],
"size": [
@@ -36170,6 +36299,9 @@
},
{
"source": "GHLoE"
},
{
"source": "VEoR"
}
],
"size": [
@@ -36455,6 +36587,9 @@
},
{
"source": "BMT"
},
{
"source": "VEoR"
}
],
"size": [
@@ -37574,6 +37709,9 @@
},
{
"source": "BMT"
},
{
"source": "VEoR"
}
],
"size": [
@@ -37731,6 +37869,9 @@
},
{
"source": "BMT"
},
{
"source": "VEoR"
}
],
"size": [
@@ -38853,6 +38994,9 @@
},
{
"source": "DoDk"
},
{
"source": "VEoR"
}
],
"size": [
@@ -39386,6 +39530,9 @@
},
{
"source": "SatO"
},
{
"source": "VEoR"
}
],
"size": [
@@ -39805,6 +39952,9 @@
},
{
"source": "DoDk"
},
{
"source": "VEoR"
}
],
"size": [
@@ -40343,6 +40493,9 @@
},
{
"source": "DoDk"
},
{
"source": "VEoR"
}
],
"size": [
@@ -40503,6 +40656,9 @@
},
{
"source": "DoDk"
},
{
"source": "VEoR"
}
],
"size": [
@@ -41499,6 +41655,9 @@
},
{
"source": "BMT"
},
{
"source": "VEoR"
}
],
"size": [
@@ -42580,6 +42739,9 @@
},
{
"source": "PSI"
},
{
"source": "VEoR"
}
],
"size": [
@@ -43925,6 +44087,9 @@
},
{
"source": "DoDk"
},
{
"source": "VEoR"
}
],
"size": [
@@ -44811,6 +44976,9 @@
},
{
"source": "BMT"
},
{
"source": "VEoR"
}
],
"size": [
@@ -45087,6 +45255,9 @@
},
{
"source": "DoDk"
},
{
"source": "VEoR"
}
],
"size": [
@@ -46051,6 +46222,9 @@
},
{
"source": "DoDk"
},
{
"source": "VEoR"
}
],
"size": [
@@ -46796,6 +46970,9 @@
},
{
"source": "AATM"
},
{
"source": "VEoR"
}
],
"size": [
@@ -48211,6 +48388,9 @@
},
{
"source": "DoDk"
},
{
"source": "VEoR"
}
],
"size": [
@@ -48887,6 +49067,9 @@
},
{
"source": "BMT"
},
{
"source": "VEoR"
}
],
"size": [
@@ -49072,6 +49255,9 @@
},
{
"source": "DoDk"
},
{
"source": "VEoR"
}
],
"size": [
@@ -49210,6 +49396,9 @@
},
{
"source": "BMT"
},
{
"source": "VEoR"
}
],
"size": [
@@ -50027,6 +50216,9 @@
},
{
"source": "DoDk"
},
{
"source": "VEoR"
}
],
"size": [
@@ -52155,6 +52347,9 @@
},
{
"source": "ToFW"
},
{
"source": "VEoR"
}
],
"size": [
@@ -53330,6 +53525,9 @@
},
{
"source": "DoDk"
},
{
"source": "VEoR"
}
],
"size": [
@@ -53765,6 +53963,9 @@
},
{
"source": "DoDk"
},
{
"source": "VEoR"
}
],
"size": [
@@ -54427,6 +54628,9 @@
},
{
"source": "DoDk"
},
{
"source": "VEoR"
}
],
"size": [
@@ -54708,6 +54912,9 @@
},
{
"source": "CM"
},
{
"source": "VEoR"
}
],
"size": [
@@ -54983,6 +55190,9 @@
},
{
"source": "KftGV"
},
{
"source": "VEoR"
}
],
"size": [
@@ -56068,6 +56278,9 @@
},
{
"source": "SatO"
},
{
"source": "VEoR"
}
],
"size": [
@@ -58379,6 +58592,9 @@
},
{
"source": "DoDk"
},
{
"source": "VEoR"
}
],
"size": [
@@ -58741,6 +58957,9 @@
},
{
"source": "DoDk"
},
{
"source": "VEoR"
}
],
"size": [
@@ -58905,6 +59124,9 @@
},
{
"source": "DoDk"
},
{
"source": "VEoR"
}
],
"size": [
@@ -59041,6 +59263,9 @@
},
{
"source": "PaBTSO"
},
{
"source": "VEoR"
}
],
"size": [
@@ -59415,6 +59640,9 @@
},
{
"source": "DoDk"
},
{
"source": "VEoR"
}
],
"size": [
@@ -60307,6 +60535,9 @@
},
{
"source": "BMT"
},
{
"source": "VEoR"
}
],
"size": [
@@ -60732,6 +60963,9 @@
},
{
"source": "PaBTSO"
},
{
"source": "VEoR"
}
],
"size": [
@@ -61771,6 +62005,9 @@
},
{
"source": "DoDk"
},
{
"source": "VEoR"
}
],
"size": [
@@ -62804,6 +63041,9 @@
},
{
"source": "JttRC"
},
{
"source": "VEoR"
}
],
"size": [
@@ -64364,6 +64604,9 @@
},
{
"source": "DoDk"
},
{
"source": "VEoR"
}
],
"size": [
@@ -65965,6 +66208,9 @@
},
{
"source": "SatO"
},
{
"source": "VEoR"
}
],
"size": [
@@ -66213,6 +66459,9 @@
},
{
"source": "BMT"
},
{
"source": "VEoR"
}
],
"size": [
@@ -66466,6 +66715,9 @@
},
{
"source": "GHLoE"
},
{
"source": "VEoR"
}
],
"size": [
@@ -67213,6 +67465,9 @@
},
{
"source": "DoDk"
},
{
"source": "VEoR"
}
],
"size": [
@@ -67598,6 +67853,9 @@
},
{
"source": "DoDk"
},
{
"source": "VEoR"
}
],
"size": [
@@ -68243,6 +68501,9 @@
},
{
"source": "DoDk"
},
{
"source": "VEoR"
}
],
"size": [
@@ -68414,6 +68675,9 @@
},
{
"source": "LK"
},
{
"source": "VEoR"
}
],
"size": [
@@ -69379,6 +69643,9 @@
},
{
"source": "BMT"
},
{
"source": "VEoR"
}
],
"size": [
@@ -69801,6 +70068,9 @@
},
{
"source": "DoDk"
},
{
"source": "VEoR"
}
],
"size": [
@@ -70008,6 +70278,9 @@
},
{
"source": "DoDk"
},
{
"source": "VEoR"
}
],
"size": [
@@ -70769,6 +71042,9 @@
},
{
"source": "DoDk"
},
{
"source": "VEoR"
}
],
"size": [
@@ -71333,6 +71609,9 @@
},
{
"source": "PaBTSO"
},
{
"source": "VEoR"
}
],
"size": [
@@ -75395,6 +75674,9 @@
},
{
"source": "DoDk"
},
{
"source": "VEoR"
}
],
"size": [

View File

@@ -2179,6 +2179,9 @@
{
"source": "MTF",
"page": 117
},
{
"source": "VEoR"
}
],
"size": [
@@ -4619,6 +4622,9 @@
{
"source": "MTF",
"page": 161
},
{
"source": "VEoR"
}
],
"size": [
@@ -5499,6 +5505,9 @@
},
{
"source": "AATM"
},
{
"source": "VEoR"
}
],
"size": [
@@ -18532,6 +18541,9 @@
{
"source": "MTF",
"page": 162
},
{
"source": "VEoR"
}
],
"size": [
@@ -23008,6 +23020,9 @@
{
"source": "MTF",
"page": 232
},
{
"source": "VEoR"
}
],
"size": [
@@ -23143,6 +23158,9 @@
},
{
"source": "AATM"
},
{
"source": "VEoR"
}
],
"size": [
@@ -26232,6 +26250,9 @@
{
"source": "VGM",
"page": 217
},
{
"source": "VEoR"
}
],
"size": [
@@ -29042,6 +29063,9 @@
{
"source": "MTF",
"page": 160
},
{
"source": "VEoR"
}
],
"size": [

File diff suppressed because it is too large Load Diff

View File

@@ -1632,6 +1632,11 @@
"name": "Inquisitor of the Tome",
"source": "VRGR",
"page": 249,
"otherSources": [
{
"source": "VEoR"
}
],
"size": [
"M"
],
@@ -2787,6 +2792,11 @@
"name": "Priest of Osybus",
"source": "VRGR",
"page": 241,
"otherSources": [
{
"source": "VEoR"
}
],
"size": [
"M"
],

View File

@@ -2281,6 +2281,11 @@
"isNamedCreature": true,
"source": "WBtW",
"page": 205,
"otherSources": [
{
"source": "VEoR"
}
],
"size": [
"M"
],

File diff suppressed because it is too large Load Diff

View File

@@ -79,6 +79,7 @@
"ToB1-2023": "fluff-bestiary-tob1-2023.json",
"ToFW": "fluff-bestiary-tofw.json",
"VD": "fluff-bestiary-vd.json",
"VEoR": "fluff-bestiary-veor.json",
"VGM": "fluff-bestiary-vgm.json",
"VRGR": "fluff-bestiary-vrgr.json",
"WBtW": "fluff-bestiary-wbtw.json",

View File

@@ -92,6 +92,7 @@
"ToB1-2023": "bestiary-tob1-2023.json",
"ToFW": "bestiary-tofw.json",
"VD": "bestiary-vd.json",
"VEoR": "bestiary-veor.json",
"VGM": "bestiary-vgm.json",
"VRGR": "bestiary-vrgr.json",
"XGE": "bestiary-xge.json",

View File

@@ -910,7 +910,7 @@
"type": "internal",
"path": "book/MOT/055-map-3.1-world-of-theros.webp"
},
"title": "Map 3.1: world of theros",
"title": "Map 3.1: The World of Theros",
"width": 3631,
"height": 5000,
"imageType": "map",
@@ -4690,7 +4690,7 @@
"type": "internal",
"path": "book/MOT/081-map-4.2-Asora.webp"
},
"title": "Map 4.2: Asora",
"title": "Map 4.2: Agora",
"width": 4200,
"height": 5700,
"imageType": "map",

View File

@@ -2702,5 +2702,12 @@
"ver": "1.206.1",
"date": "2024-05-06",
"txt": "- Fixed list page \"Export as Image\" failing to function on Firefox\n- Fixed Classes page failing to hide subclass images when \"Info\" is disabled\n- Fixed Dice Roller handling of unicode \"minus sign\" character\n- (Brew) Improved/fixed Creature Text Converter handling of: `&`-separated creature types; non-Oxford-comma-seperated immunity/resistance/vulnerability lists\n- (Fixed typos/added tags)"
},
{
"ver": "1.207.0",
"date": "2024-05-21",
"title": "Ruinous Power",
"titleAlt": "Adam of Riches",
"txt": "- Added Vecna: Eve of Ruin content\n- (Brew) Added `\"expectsLightBackground\"`/`\"expectsDarkBackground\"` for `\"image\"`-type entries, allowing a theme-dependent background to be rendered under the image\n- (Brew) Fixed crash when attempting to view subclass with multiple fluff images\n- (Fixed typos/added tags)"
}
]

View File

@@ -1000,6 +1000,20 @@
}
]
},
{
"name": "Chime of Exile",
"source": "VEoR",
"images": [
{
"type": "image",
"href": {
"type": "internal",
"path": "items/VEoR/Chime of Exile.webp"
},
"credit": "Irina Nordsol"
}
]
},
{
"name": "Chime of Opening",
"source": "DMG",
@@ -1265,6 +1279,20 @@
}
]
},
{
"name": "Crown of Lies",
"source": "VEoR",
"images": [
{
"type": "image",
"href": {
"type": "internal",
"path": "items/VEoR/Crown of Lies.webp"
},
"credit": "Lauren Walsh"
}
]
},
{
"name": "Crown of Whirling Comets",
"source": "BMT",
@@ -2027,18 +2055,26 @@
]
},
{
"name": "Eye of Vecna",
"name": "Eye and Hand of Vecna",
"source": "DMG",
"images": [
{
"type": "image",
"href": {
"type": "internal",
"path": "items/DMG/Eye of Vecna.webp"
"path": "items/DMG/Eye and Hand of Vecna.webp"
}
}
]
},
{
"name": "Eye of Vecna",
"source": "DMG",
"_copy": {
"name": "Eye and Hand of Vecna",
"source": "DMG"
}
},
{
"name": "Eyes of Charming",
"source": "DMG",
@@ -2673,15 +2709,10 @@
{
"name": "Hand of Vecna",
"source": "DMG",
"images": [
{
"type": "image",
"href": {
"type": "internal",
"path": "items/DMG/Hand of Vecna.webp"
}
}
]
"_copy": {
"name": "Eye and Hand of Vecna",
"source": "DMG"
}
},
{
"name": "Hardened Delerium-tipped Arrows",
@@ -5319,6 +5350,29 @@
}
]
},
{
"name": "Rod of Seven Parts",
"source": "VEoR",
"images": [
{
"type": "image",
"href": {
"type": "internal",
"path": "items/VEoR/Rod of Seven Parts.webp"
},
"credit": "CoupleofKooks"
},
{
"type": "image",
"href": {
"type": "internal",
"path": "items/VEoR/Rod of Seven Parts Piece.webp"
},
"title": "A piece of the Rod of Seven Parts",
"credit": "CoupleofKooks"
}
]
},
{
"name": "Rogue Card",
"source": "BMT",

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -7958,6 +7958,20 @@
"rarity": "none",
"value": 2
},
{
"name": "Chime of Exile",
"source": "VEoR",
"page": 46,
"rarity": "very rare",
"wondrous": true,
"entries": [
"This silver chime is engraved with delicate magic sigils. While holding the chime, you can use an action to cast the {@spell Banishment} spell (spell save DC 20). If the target of the spell has 50 hit points or fewer, it automatically fails its saving throw. Once the chime has been used to cast the spell, it can't be used this way again until the next dawn."
],
"attachedSpells": [
"banishment"
],
"hasFluffImages": true
},
{
"name": "Chime of Opening",
"source": "DMG",
@@ -9565,6 +9579,52 @@
"Using a crowbar grants advantage to Strength checks where the crowbar's leverage can be applied."
]
},
{
"name": "Crown of Lies",
"source": "VEoR",
"page": 6,
"rarity": "artifact",
"reqAttune": true,
"wondrous": true,
"entries": [
"After betraying and nearly destroying the lich Vecna, the warrior Kas found himself trapped in the Shadowfell, imprisoned in a Domain of Dread called Tovag. There, he languished as a vampire. In time, the Dark Powers of the Domain of Dread lured Kas to a hidden forge, where he found the Crown of Lies. Once Kas vowed to deliver Vecna into the Dark Powers' clutches and donned the crown, the Dark Powers released Kas. From there, Kas set out to ruin his former master. Should Kas fail, the Dark Powers will reclaim him.",
"The crown is made of burnished and entwined metal rods. To attune to it, you must place it on your head and speak a true desire of your heart. You know how to attune to the crown when you touch it.",
{
"type": "entries",
"name": "Random Properties",
"entries": [
"The Crown of Lies has the following random properties (see the Dungeon Master's Guide for options):",
{
"type": "list",
"items": [
"1 {@table Artifact Properties; Minor Beneficial Properties|dmg|minor beneficial} property",
"1 {@table Artifact Properties; Major Beneficial Properties|dmg|major beneficial} property",
"1 {@table Artifact Properties; Minor Detrimental Properties|dmg|minor detrimental} property"
]
}
]
},
{
"type": "entries",
"name": "Perfect Disguise",
"entries": [
"While attuned to the crown, you can use an action to transform yourself to look and feel like any creature you've seen at least once and whose size is no more than one size smaller or larger than yours.",
"The new form mimics the chosen creature's appearance exactly, including its voice. Your size and speed are replaced by the chosen creature's. You otherwise retain your own game statistics. While in this new form, the crown melds into your person and is undetectable.",
"Your new form lasts until you die, your attunement to the crown ends, or you use another action to transform into a different creature or your true form. Interactions with you while you are transformed by the crown reveal no illusory magic, nor do they reveal anything other than details about the creature you're disguised as. You count as the chosen creature for the purposes of spells, traps, and other defenses that wouldn't target the chosen creature.",
"While in your disguised form, any lies you tell always seem to be true, no matter what magical or mundane methods are used to try to detect your falsehoods. You are the recipient of {@spell Sending} spells addressed to you and the creature you are disguised as, and {@spell Scrying} and similar spells that target the creature you are disguised as actually target you. The only way to reveal your true nature while transformed by the crown is with a {@spell Wish} spell.",
"While wearing this crown in your true form, you can choose for the crown to be visible if you wish."
]
},
{
"type": "entries",
"name": "Destroying the Crown",
"entries": [
"If a creature wearing the crown is killed by the creature it is disguised as, the crown disintegrates and is destroyed."
]
}
],
"hasFluffImages": true
},
{
"name": "Crown of the Forest",
"source": "IMR",
@@ -15972,15 +16032,15 @@
"name": "Properties of the Eye",
"type": "entries",
"entries": [
"Your alignment changes to neutral evil, and you gain the following benefits:"
]
},
{
"type": "list",
"items": [
"You have {@sense truesight}.",
"You can use an action to see as if you were wearing a {@item ring of X-ray vision}. You can end this effect as a bonus action.",
"The eye has 8 charges. You can use an action and expend 1 or more charges to cast one of the following spells (save DC 18) from it: {@spell clairvoyance} (2 charges), {@spell crown of madness} (1 charge), {@spell disintegrate} (4 charges), {@spell dominate monster} (5 charges), or {@spell eyebite} (4 charges). The eye regains {@dice 1d4 + 4} expended charges daily at dawn. Each time you cast a spell from the eye, there is a {@chance 5} chance that Vecna tears your soul from your body, devours it, and then takes control of the body like a puppet. If that happens, you become an NPC under the DM's control."
"Your alignment changes to neutral evil, and you gain the following benefits:",
{
"type": "list",
"items": [
"You have {@sense truesight}.",
"You can use an action to see as if you were wearing a {@item ring of X-ray vision}. You can end this effect as a bonus action.",
"The eye has 8 charges. You can use an action and expend 1 or more charges to cast one of the following spells (save DC 18) from it: {@spell clairvoyance} (2 charges), {@spell crown of madness} (1 charge), {@spell disintegrate} (4 charges), {@spell dominate monster} (5 charges), or {@spell eyebite} (4 charges). The eye regains {@dice 1d4 + 4} expended charges daily at dawn. Each time you cast a spell from the eye, there is a {@chance 5} chance that Vecna tears your soul from your body, devours it, and then takes control of the body like a puppet. If that happens, you become an NPC under the DM's control."
]
}
]
},
{
@@ -19720,15 +19780,15 @@
"name": "Properties of the Hand",
"type": "entries",
"entries": [
"Your alignment changes to neutral evil, and you gain the following benefits:"
]
},
{
"type": "list",
"items": [
"Your Strength score becomes 20, unless it is already 20 or higher.",
"Any melee spell attack you make with the hand, and any melee weapon attack made with a weapon held by it, deals an extra {@damage 2d8} cold damage on a hit.",
"The hand has 8 charges. You can use an action and expend 1 or more charges to cast one of the following spells (save DC 18) from it: {@spell finger of death} (5 charges), {@spell sleep} (1 charge), {@spell slow} (2 charges), or {@spell teleport} (3 charges). The hand regains {@dice 1d4 + 4} expended charges daily at dawn. Each time you cast a spell from the hand, it casts the {@spell suggestion} spell on you (save DC 18), demanding that you commit an evil act. The hand might have a specific act in mind or leave it up to you."
"Your alignment changes to neutral evil, and you gain the following benefits:",
{
"type": "list",
"items": [
"Your Strength score becomes 20, unless it is already 20 or higher.",
"Any melee spell attack you make with the hand, and any melee weapon attack made with a weapon held by it, deals an extra {@damage 2d8} cold damage on a hit.",
"The hand has 8 charges. You can use an action and expend 1 or more charges to cast one of the following spells (save DC 18) from it: {@spell finger of death} (5 charges), {@spell sleep} (1 charge), {@spell slow} (2 charges), or {@spell teleport} (3 charges). The hand regains {@dice 1d4 + 4} expended charges daily at dawn. Each time you cast a spell from the hand, it casts the {@spell suggestion} spell on you (save DC 18), demanding that you commit an evil act. The hand might have a specific act in mind or leave it up to you."
]
}
]
},
{
@@ -35324,6 +35384,128 @@
"Magic Item Table H"
]
},
{
"name": "Rod of Seven Parts",
"source": "VEoR",
"page": 8,
"baseItem": "quarterstaff|PHB",
"type": "RD",
"rarity": "artifact",
"reqAttune": true,
"weight": 4,
"weaponCategory": "simple",
"property": [
"V"
],
"dmg1": "1d6",
"dmgType": "B",
"dmg2": "1d8",
"bonusWeapon": "+3",
"recharge": "dawn",
"rechargeAmount": "{@dice 1d4 + 3}",
"charges": 7,
"entries": [
"Eons ago, a war between the primordials and the gods scarred the planes of existence. A demon lord named {@creature Miska the Wolf-Spider|VEoR} eventually pushed the primordials' enemies to the brink of annihilation.",
"Desperate to save themselves and their allies, powerful elemental beings called the Wind Dukes of Aaqa rose against Miska. Committed to the concept of law, the Wind Dukes descended from a people called the vaati, who once ruled many worlds. Seven Wind Dukes wove their power into an artifact called the Rod of Law. The dukes used the rod to imprison Miska on the plane of Pandemonium. As a result, the rod shattered into seven parts that were scattered throughout the multiverse. The rod thus became known as the Rod of Seven Parts.",
{
"type": "entries",
"name": "Possessing the Broken Rod",
"entries": [
"The rod can't be attuned to while it is broken. While holding one piece of the broken rod, you know the general location of the next consecutive piece, as the rod yearns to be a complete artifact. Multiple rod pieces can be assembled into one piece or disassembled again, each requiring an action, although a partially complete rod doesn't gain any other abilities.",
"Additionally, while holding one piece of the broken rod, you can use an action to cast the spell associated with that piece, as listed on the Rod Pieces table. Once that piece of the rod has been used to cast a spell, it can't be used that way again until the next dawn."
]
},
{
"type": "table",
"caption": "Rod Pieces",
"colStyles": [
"col-2",
"col-10"
],
"colLabels": [
"Piece",
"Spell"
],
"rows": [
[
"First",
"{@spell Commune}"
],
[
"Second",
"{@spell Arcane Gate}"
],
[
"Third",
"{@spell Reverse Gravity} (spell save DC 18)"
],
[
"Fourth",
"{@spell Regenerate}"
],
[
"Fifth",
"{@spell Find the Path}"
],
[
"Sixth",
"{@spell Mirage Arcane}"
],
[
"Seventh",
"{@spell Simulacrum}"
]
]
},
{
"type": "entries",
"name": "Possessing the Whole Rod",
"entries": [
"Once all seven pieces are reassembled, a creature can attune to the Rod of Seven Parts. While attuned to the rod, you gain the following benefits:"
]
},
{
"type": "entries",
"name": "Magic Weapon",
"entries": [
"The Rod of Seven Parts functions for you as a magic quarterstaff that grants a +3 bonus to attack and damage rolls made with it."
]
},
{
"type": "entries",
"name": "Rod Spellcasting",
"entries": [
"The Rod of Seven Parts has 7 charges and regains {@dice 1d4 + 3} expended charges daily at dawn. While holding the rod, you can use an action to expend 1 charge and cast any of the spells in the Rod Pieces table. You can also use an action to cast {@spell Detect Evil and Good} from the rod without using any charges."
]
},
{
"type": "entries",
"name": "Ultimate Law",
"entries": [
"If you are not of a lawful alignment, you find your worldview shifting toward keeping a personal code. You are more apt to keep your promises, follow through on your declarations, and adhere to your beliefs."
]
},
{
"type": "entries",
"name": "Destroying the Rod",
"entries": [
"The only way to destroy the Rod of Seven Parts is to immerse the assembled rod in lava in the Abyss. It must remain in the lava for fifty years before it finally is consumed.",
"A piece of the rod may be temporarily destroyed in this way, but each piece re-forms one year after it has succumbed. A re-formed piece teleports to a random place in the multiverse."
]
}
],
"attachedSpells": [
"commune",
"arcane gate",
"reverse gravity",
"regenerate",
"find the path",
"mirage arcane",
"simulacrum",
"detect evil and good"
],
"hasFluffImages": true
},
{
"name": "Rod of the Vonindod",
"source": "SKT",
@@ -51072,6 +51254,114 @@
],
"hasFluffImages": true
},
{
"name": "Eye and Hand of Vecna",
"source": "DMG",
"page": 224,
"rarity": "artifact",
"reqAttune": true,
"wondrous": true,
"ability": {
"static": {
"str": 20
}
},
"entries": [
"Seldom is the name of Vecna spoken except in a hushed voice. Vecna was, in his time, one of the mightiest of all wizards. Through dark magic and conquest, he forged a terrible empire. For all his power, Vecna couldn't escape his own mortality. He began to fear death and take steps to prevent his end from ever coming about.",
"{@creature Orcus|MTF}, the demon prince of undeath, taught Vecna a ritual that would allow him to live on as a {@creature lich}. Beyond death, he became the greatest of all liches. Even though his body gradually withered and decayed, Vecna continued to expand his evil dominion. So formidable and hideous was his temper that his subjects feared to speak his name. He was the Whispered One, the Master of the Spider Throne, the Undying King, and the Lord of the Rotted Tower.",
"Some say that Vecna's lieutenant Kas coveted the Spider Throne for himself, or that the sword his lord made for him seduced him into rebellion. Whatever the reason, Kas brought the Undying King's rule to an end in a terrible battle that left Vecna's tower a heap of ash. Of Vecna, all that remained were one hand and one eye, grisly artifacts that still seek to work the Whispered One's will in the world.",
"The {@item Eye of Vecna} and the {@item Hand of Vecna} might be found together or separately. The eye looks like a bloodshot organ torn free from the socket. The hand is a mummified and shriveled left extremity.",
"To attune to the eye, you must gouge out your own eye and press the artifact into the empty socket. The eye grafts itself to your head and remains there until you die. Once in place, the eye transforms into a golden eye with a slit for a pupil, much like that of a cat. If the eye is ever removed, you die.",
"To attune to the hand, you must lop off your left hand at the wrist and the press the artifact against the stump. The hand grafts itself to your arm and becomes a functioning appendage. If the hand is ever removed, you die.",
{
"name": "Random Properties",
"type": "entries",
"entries": [
"The {@item Eye of Vecna} and the {@item Hand of Vecna} each have the following random properties:",
{
"type": "list",
"items": [
"1 {@table Artifact Properties; Minor Beneficial Properties|dmg|minor beneficial property}",
"1 {@table Artifact Properties; Major Beneficial Properties|dmg|major beneficial property}",
"1 {@table Artifact Properties; Minor Detrimental Properties|dmg|minor detrimental property}"
]
}
]
},
{
"name": "Properties of the Eye",
"type": "entries",
"entries": [
"Your alignment changes to neutral evil, and you gain the following benefits:",
{
"type": "list",
"items": [
"You have {@sense truesight}.",
"You can use an action to see as if you were wearing a {@item ring of X-ray vision}. You can end this effect as a bonus action.",
"The eye has 8 charges. You can use an action and expend 1 or more charges to cast one of the following spells (save DC 18) from it: {@spell clairvoyance} (2 charges), {@spell crown of madness} (1 charge), {@spell disintegrate} (4 charges), {@spell dominate monster} (5 charges), or {@spell eyebite} (4 charges). The eye regains {@dice 1d4 + 4} expended charges daily at dawn. Each time you cast a spell from the eye, there is a {@chance 5} chance that Vecna tears your soul from your body, devours it, and then takes control of the body like a puppet. If that happens, you become an NPC under the DM's control."
]
}
]
},
{
"name": "Properties of the Hand",
"type": "entries",
"entries": [
"Your alignment changes to neutral evil, and you gain the following benefits:",
{
"type": "list",
"items": [
"Your Strength score becomes 20, unless it is already 20 or higher.",
"Any melee spell attack you make with the hand, and any melee weapon attack made with a weapon held by it, deals an extra {@damage 2d8} cold damage on a hit.",
"The hand has 8 charges. You can use an action and expend 1 or more charges to cast one of the following spells (save DC 18) from it: {@spell finger of death} (5 charges), {@spell sleep} (1 charge), {@spell slow} (2 charges), or {@spell teleport} (3 charges). The hand regains {@dice 1d4 + 4} expended charges daily at dawn. Each time you cast a spell from the hand, it casts the {@spell suggestion} spell on you (save DC 18), demanding that you commit an evil act. The hand might have a specific act in mind or leave it up to you."
]
}
]
},
{
"name": "Properties of the Eye and Hand",
"type": "entries",
"entries": [
"If you are attuned to both the hand and eye, you gain the following additional benefits:",
{
"type": "list",
"items": [
"You are immune to disease and poison.",
"Using the eye's X-ray vision never causes you to suffer {@condition exhaustion}.",
"You experience premonitions of danger and, unless you are {@condition incapacitated}, can't be {@quickref Surprise|PHB|3|0|surprised}.",
"If you start your turn with at least 1 hit point, you regain {@dice 1d10} hit points.",
"If a creature has a skeleton, you can attempt to turn its bones to jelly with a touch of the {@item Hand of Vecna}. You can do so by using an action to make a melee attack against a creature you can reach, using your choice of your melee attack bonus for weapons or spells. On a hit, the target must succeed on a DC 18 Constitution saving throw or drop to 0 hit points.",
"You can use an action to cast {@spell wish}. This property can't be used again until 30 days have passed."
]
}
]
},
{
"name": "Destroying the Eye and Hand",
"type": "entries",
"entries": [
"If the {@item Eye of Vecna} and the {@item Hand of Vecna} are both attached to the same creature, and that creature is slain by the {@item Sword of Kas}, both the eye and the hand burst into flame, turn to ash, and are destroyed forever. Any other attempt to destroy the eye or hand seems to work, but the artifact reappears in one of Vecna's many hidden vaults, where it waits to be rediscovered."
]
}
],
"items": [
"Eye of Vecna|DMG",
"Hand of Vecna|DMG"
],
"attachedSpells": [
"wish",
"finger of death",
"sleep",
"slow",
"teleport",
"clairvoyance",
"crown of madness",
"disintegrate",
"dominate monster",
"eyebite"
],
"hasFluffImages": true
},
{
"name": "Fenthras",
"source": "TDCSR",

View File

@@ -1413,6 +1413,7 @@
],
"rarity": "rare",
"reqAttune": true,
"stealth": false,
"entries": [
"This magic armor appears as a pot of bubbling black ichor. When you attune to it, the ichor adheres to and contours to your skin, and the pot disappears. The armor can be worn under normal clothes, and it doesn't impede bodily functions. Once you put it on, it can't be removed unless you choose to do so or you die, at which point the pot reappears and the ichor flows back into it.",
"While wearing the armor, you have resistance to poison damage. The armor also doesn't impose disadvantage on Dexterity ({@skill Stealth}) checks."

View File

@@ -16997,6 +16997,7 @@
"raceName": "Human",
"raceSource": "PHB",
"page": 29,
"srd": true,
"basicRules": true,
"ability": [
{

View File

@@ -807,7 +807,7 @@
},
{
"name": "Charm of the Creeping Hand",
"source": "VEoR",
"source": "VNotEE",
"type": "Charm",
"entries": [
"Once per turn, when you hit a creature with an attack roll using a weapon or an unarmed strike, you can infuse your strike with life-stealing energy. Your attack then deals an extra {@dice 1d10} necrotic damage, and you gain 5 {@book temporary hit points|PHB|9|Temporary Hit Points}. Once used five times, the charm vanishes."
@@ -833,7 +833,7 @@
},
{
"name": "Charm of the Eldritch Eye",
"source": "VEoR",
"source": "VNotEE",
"type": "Charm",
"entries": [
"You can cast {@spell Clairvoyance} as an action, without using a spell slot and requiring no material components. Once used three times, the charm vanishes."

View File

@@ -2848,6 +2848,67 @@
"hasFluff": true,
"hasFluffImages": true
},
{
"name": "Venatrix",
"source": "VEoR",
"vehicleType": "INFWAR",
"size": "G",
"weight": 12000,
"terrain": [
"land"
],
"capCreature": 8,
"capCargo": 2000,
"ac": 19,
"speed": 100,
"str": 18,
"dex": 18,
"con": 10,
"int": 0,
"wis": 0,
"cha": 0,
"hp": {
"hp": 200,
"dt": 10
},
"immune": [
"fire",
"poison",
"psychic"
],
"conditionImmune": [
"blinded",
"charmed",
"deafened",
"frightened",
"paralyzed",
"petrified",
"poisoned",
"stunned",
"unconscious"
],
"actionStation": [
{
"name": "Helm (Requires 1 Crew)",
"entries": [
"Drive and steer the Venatrix."
]
},
{
"name": "2 Harpoon Guns (Each Station Requires 1 Crew)",
"entries": [
"{@atk rw} {@hit 6} to hit, range 120 ft., one target. {@h}10 ({@damage 2d8 + 1}) piercing damage."
]
},
{
"name": "Infernal Screamer (Requires 1 Crew)",
"entries": [
"One target the operator can see within 120 feet of itself must make a DC 15 Wisdom saving throw, taking 26 ({@damage 4d12}) psychic damage on a failed save or half as much damage on a successful one."
]
}
],
"hasToken": true
},
{
"name": "Warship",
"source": "GoS",
@@ -3417,7 +3478,7 @@
"source": "BGDIA",
"page": 222,
"upgradeType": [
"IWM:W"
"IWM:A"
],
"entries": [
"The vehicle is covered with spikes inscribed with blasphemous symbols. Luminous, ghostly figures impaled on these spikes wail and reach out in agony.",

View File

@@ -189,10 +189,12 @@ class UtilClassesPage {
];
}
return {
type: "gallery",
images: [...images],
};
return [
{
type: "gallery",
images: [...images],
},
];
}
}

View File

@@ -225,9 +225,15 @@ class ItemParser extends BaseParser {
const isGenericWeaponArmor = this._setCleanTaglineInfo_mutIsGenericWeaponArmor({stats, part, partLower, options});
if (isGenericWeaponArmor) continue;
const mBaseWeapon = /^(?<ptPre>weapon|staff) \((?<ptParens>[^)]+)\)$/i.exec(part);
const mBaseWeapon = /^(?<ptPre>weapon|staff|rod) \((?<ptParens>[^)]+)\)$/i.exec(part);
if (mBaseWeapon) {
if (mBaseWeapon.groups.ptPre.toLowerCase() === "staff") stats.staff = true;
if (mBaseWeapon.groups.ptPre.toLowerCase() === "rod") {
if (stats.type) {
throw new Error(`Multiple types! "${stats.type}" -> "${mBaseWeapon.groups.ptParens}"`);
}
stats.type = "RD";
}
if (mBaseWeapon.groups.ptParens === "spear or javelin") {
(stats.requires ||= []).push(...this._setCleanTaglineInfo_getGenericRequires({stats, str: "spear", options}));

View File

@@ -373,7 +373,7 @@ class RenderMap {
`);
});
const $wrpCvs = $$`<div class="w-100 h-100 ve-overflow-x-scroll ve-overflow-y-scroll rd__scroller-viewer">
const $wrpCvs = $$`<div class="w-100 h-100 ve-overflow-x-scroll ve-overflow-y-scroll rd__scroller-viewer ${mapData.expectsLightBackground ? "rd__scroller-viewer--bg-light" : mapData.expectsDarkBackground ? "rd__scroller-viewer--bg-dark" : ""}">
${$cvs}
</div>`
.on("mousewheel DOMMouseScroll", evt => {

View File

@@ -581,6 +581,11 @@ globalThis.Renderer = function () {
page: entry.page,
source: entry.source,
hash: entry.hash,
...entry.expectsLightBackground
? {expectsLightBackground: true}
: entry.expectsDarkBackground
? {expectsDarkBackground: true}
: {},
};
};
@@ -591,6 +596,8 @@ globalThis.Renderer = function () {
this._renderImage_getWrapperClasses = function (entry) {
const out = ["rd__wrp-image", "relative"];
if (entry.expectsLightBackground) out.push("rd__wrp-image--bg", "rd__wrp-image--bg-light");
else if (entry.expectsDarkBackground) out.push("rd__wrp-image--bg", "rd__wrp-image--bg-dark");
if (entry.style) {
switch (entry.style) {
case "comic-speaker-left": out.push("rd__comic-img-speaker", "rd__comic-img-speaker--left"); break;
@@ -10138,12 +10145,21 @@ Renderer.vehicle = class {
static getVehicleInfwarRenderableEntriesMeta (ent) {
const dexMod = Parser.getAbilityModNumber(ent.dex);
const ptDtMt = [
ent.hp.dt != null ? `damage threshold ${ent.hp.dt}` : null,
ent.hp.mt != null ? `ishap threshold ${ent.hp.mt}` : null,
]
.filter(Boolean)
.join(", ");
const ptAc = ent.ac ?? dexMod === 0 ? `19` : `${19 + dexMod} (19 while motionless)`;
return {
entrySizeWeight: `{@i ${Parser.sizeAbvToFull(ent.size)} vehicle (${ent.weight.toLocaleString()} lb.)}`,
entryCreatureCapacity: `{@b Creature Capacity} ${Renderer.vehicle.getInfwarCreatureCapacity(ent)}`,
entryCargoCapacity: `{@b Cargo Capacity} ${Parser.weightToFull(ent.capCargo)}`,
entryArmorClass: `{@b Armor Class} ${dexMod === 0 ? `19` : `${19 + dexMod} (19 while motionless)`}`,
entryHitPoints: `{@b Hit Points} ${ent.hp.hp} (damage threshold ${ent.hp.dt}, mishap threshold ${ent.hp.mt})`,
entryArmorClass: `{@b Armor Class} ${ptAc}`,
entryHitPoints: `{@b Hit Points} ${ent.hp.hp}${ptDtMt ? ` (${ptDtMt})` : ""}`,
entrySpeed: `{@b Speed} ${ent.speed} ft.`,
entrySpeedNote: `[{@b Travel Pace} ${Math.floor(ent.speed / 10)} miles per hour (${Math.floor(ent.speed * 24 / 10)} miles per day)]`,
entrySpeedNoteTitle: `Based on "Special Travel Pace," DMG p242`,

View File

@@ -420,6 +420,7 @@ PropOrder._GENERIC_FLUFF = [
];
PropOrder._SPELL = [
"name",
"alias",
"source",
"page",
@@ -536,6 +537,7 @@ PropOrder._SPELL_LIST = [
];
PropOrder._ACTION = [
"name",
"alias",
"source",
"page",
@@ -608,6 +610,7 @@ PropOrder._BOOK_DATA = [
];
PropOrder._BACKGROUND = [
"name",
"alias",
"source",
"page",
@@ -662,6 +665,8 @@ PropOrder._BACKGROUND__COPY_MOD = [
];
PropOrder._LEGENDARY_GROUP = [
"name",
"alias",
"source",
"page",
@@ -680,6 +685,7 @@ PropOrder._LEGENDARY_GROUP__COPY_MOD = [
];
PropOrder._CLASS = [
"name",
"alias",
"source",
"page",
@@ -853,6 +859,7 @@ PropOrder._ENTRY_DATA_OBJECT = [
];
PropOrder._CLASS_FEATURE = [
"name",
"alias",
"source",
"page",
@@ -881,6 +888,7 @@ PropOrder._CLASS_FEATURE = [
];
PropOrder._SUBCLASS_FEATURE = [
"name",
"alias",
"source",
"page",
@@ -973,6 +981,8 @@ PropOrder._FOUNDRY_SUBCLASS_FEATURE = [
];
PropOrder._LANGUAGE = [
"name",
"alias",
"dialects",
"source",
@@ -1013,6 +1023,7 @@ PropOrder._NAME = [
];
PropOrder._CONDITION = [
"name",
"alias",
"source",
"page",
@@ -1031,6 +1042,7 @@ PropOrder._CONDITION = [
];
PropOrder._DISEASE = [
"name",
"alias",
"source",
"page",
@@ -1048,6 +1060,7 @@ PropOrder._DISEASE = [
];
PropOrder._STATUS = [
"name",
"alias",
"source",
"page",
@@ -1060,6 +1073,7 @@ PropOrder._STATUS = [
];
PropOrder._CULT = [
"name",
"alias",
"source",
"page",
@@ -1079,6 +1093,7 @@ PropOrder._CULT = [
];
PropOrder._BOON = [
"name",
"alias",
"source",
"page",
@@ -1100,6 +1115,7 @@ PropOrder._BOON = [
];
PropOrder._DEITY = [
"name",
"alias",
"reprintAlias",
"altNames",
@@ -1153,6 +1169,7 @@ PropOrder._DEITY__COPY_MOD = [
];
PropOrder._FEAT = [
"name",
"alias",
"source",
"page",
@@ -1206,6 +1223,7 @@ PropOrder._FEAT__COPY_MOD = [
];
PropOrder._VEHICLE = [
"name",
"alias",
"source",
"page",
@@ -1279,6 +1297,7 @@ PropOrder._VEHICLE = [
];
PropOrder._VEHICLE_UPGRADE = [
"name",
"alias",
"source",
"page",
@@ -1513,6 +1532,7 @@ PropOrder._ITEM_MASTERY = [
];
PropOrder._OBJECT = [
"name",
"alias",
"isNpc",
@@ -1560,6 +1580,7 @@ PropOrder._OBJECT = [
];
PropOrder._OPTIONALFEATURE = [
"name",
"alias",
"source",
"page",
@@ -1613,6 +1634,7 @@ PropOrder._OPTIONALFEATURE__COPY_MOD = [
];
PropOrder._PSIONIC = [
"name",
"alias",
"source",
"page",
@@ -1627,6 +1649,7 @@ PropOrder._PSIONIC = [
];
PropOrder._REWARD = [
"name",
"alias",
"source",
"page",
@@ -1646,6 +1669,7 @@ PropOrder._REWARD = [
];
PropOrder._VARIANTRULE = [
"name",
"alias",
"source",
"page",
@@ -1782,6 +1806,7 @@ PropOrder._FOUNDRY_RACE_FEATURE__COPY_MOD = [
];
PropOrder._TABLE = [
"name",
"alias",
"source",
"page",
@@ -1812,6 +1837,7 @@ PropOrder._TABLE = [
];
PropOrder._TRAP = [
"name",
"alias",
"source",
"page",
@@ -1845,6 +1871,7 @@ PropOrder._TRAP = [
];
PropOrder._HAZARD = [
"name",
"alias",
"source",
"page",
@@ -1897,6 +1924,7 @@ PropOrder._RECIPE = [
];
PropOrder._CHAROPTION = [
"name",
"alias",
"source",
"page",
@@ -1916,6 +1944,7 @@ PropOrder._CHAROPTION = [
];
PropOrder._SKILL = [
"name",
"alias",
"source",
"page",
@@ -1926,6 +1955,7 @@ PropOrder._SKILL = [
];
PropOrder._SENSE = [
"name",
"alias",
"source",
"page",
@@ -1936,6 +1966,7 @@ PropOrder._SENSE = [
];
PropOrder._DECK = [
"name",
"alias",
"source",
"page",
@@ -2010,6 +2041,15 @@ PropOrder._CITATION = [
"entries",
];
PropOrder._FOUNDRY_MAP = [
"name",
"source",
"lights",
"walls",
];
PropOrder._PROP_TO_LIST = {
"_meta": PropOrder._META,
"monster": PropOrder._MONSTER,
@@ -2099,6 +2139,7 @@ PropOrder._PROP_TO_LIST = {
"card": PropOrder._CARD,
"encounter": PropOrder._ENCOUNTER,
"citation": PropOrder._CITATION,
"foundryMap": PropOrder._FOUNDRY_MAP,
};
globalThis.PropOrder = PropOrder;

View File

@@ -5088,17 +5088,33 @@ class ComponentUiUtil {
* @param [opts.isDisabled] If the selector should be display-only
* @return {jQuery}
*/
static $getSelSearchable (comp, prop, opts) {
opts = opts || {};
static $getSelSearchable (
comp,
prop,
{
values,
isHiddenPerValue,
$ele,
html,
isAllowNull,
fnDisplay,
displayNullAs,
fnGetAdditionalStyleClasses,
asMeta,
isDisabled,
propProxy = "state",
} = {},
) {
const _propProxy = `_${propProxy}`;
const $iptDisplay = (opts.$ele || $(opts.html || `<input class="form-control input-xs form-control--minimal">`))
const $iptDisplay = ($ele || $(html || `<input class="form-control input-xs form-control--minimal">`))
.addClass("ui-sel2__ipt-display")
.attr("tabindex", "-1")
.click(() => {
if (opts.isDisabled) return;
if (isDisabled) return;
$iptSearch.focus().select();
})
.prop("disabled", !!opts.isDisabled)
.prop("disabled", !!isDisabled)
.disableSpellcheck();
const handleSearchChange = () => {
@@ -5110,10 +5126,10 @@ class ComponentUiUtil {
};
const handleSearchChangeDebounced = MiscUtil.debounce(handleSearchChange, 30);
const $iptSearch = (opts.$ele || $(opts.html || `<input class="form-control input-xs form-control--minimal">`))
const $iptSearch = ($ele || $(html || `<input class="form-control input-xs form-control--minimal">`))
.addClass("absolute ui-sel2__ipt-search")
.keydown(evt => {
if (opts.isDisabled) return;
if (isDisabled) return;
switch (evt.key) {
case "Escape": evt.stopPropagation(); return $iptSearch.blur();
@@ -5140,10 +5156,10 @@ class ComponentUiUtil {
})
.change(() => handleSearchChangeDebounced())
.click(() => {
if (opts.isDisabled) return;
if (isDisabled) return;
$iptSearch.focus().select();
})
.prop("disabled", !!opts.isDisabled)
.prop("disabled", !!isDisabled)
.disableSpellcheck();
const $wrpChoices = $(`<div class="absolute ui-sel2__wrp-options ve-overflow-y-scroll"></div>`);
@@ -5155,74 +5171,112 @@ class ComponentUiUtil {
<div class="ui-sel2__disp-arrow absolute no-events bold"><span class="glyphicon glyphicon-menu-down"></span></div>
</div>`;
const procValues = opts.isAllowNull ? [null, ...opts.values] : opts.values;
const metaOptions = procValues.map((v, i) => {
const display = v == null ? (opts.displayNullAs || "\u2014") : opts.fnDisplay ? opts.fnDisplay(v) : v;
const additionalStyleClasses = opts.fnGetAdditionalStyleClasses ? opts.fnGetAdditionalStyleClasses(v) : null;
let metaOptions = [];
const $ele = $(`<div class="ve-flex-v-center py-1 px-1 clickable ui-sel2__disp-option ${v == null ? `italic` : ""} ${additionalStyleClasses ? additionalStyleClasses.join(" ") : ""}" tabindex="0">${display}</div>`)
.click(() => {
if (opts.isDisabled) return;
const hk = () => {
if (comp._state[prop] == null) $iptDisplay.addClass("italic").addClass("ve-muted").val(displayNullAs || "\u2014");
else $iptDisplay.removeClass("italic").removeClass("ve-muted").val(fnDisplay ? fnDisplay(comp._state[prop]) : comp._state[prop]);
comp._state[prop] = v;
$(document.activeElement).blur();
// Temporarily remove pointer events from the dropdown, so it collapses thanks to its :hover CSS
$wrp.addClass("no-events");
setTimeout(() => $wrp.removeClass("no-events"), 50);
})
.keydown(evt => {
if (opts.isDisabled) return;
metaOptions.forEach(it => it.$ele.removeClass("active"));
const metaActive = metaOptions.find(it => it.value == null ? comp._state[prop] == null : it.value === comp._state[prop]);
if (metaActive) metaActive.$ele.addClass("active");
};
comp._addHookBase(prop, hk);
switch (evt.key) {
case "Escape": evt.stopPropagation(); return $ele.blur();
let values_;
const setValues = (nxtValues, {isResetOnMissing = false, isForce = false} = {}) => {
if (!isForce && CollectionUtil.deepEquals(values_, nxtValues)) return;
values_ = nxtValues;
case "ArrowDown": {
evt.preventDefault();
const visibleMetaOptions = metaOptions.filter(it => it.isVisible && !it.isForceHidden);
if (!visibleMetaOptions.length) return;
const ixCur = visibleMetaOptions.indexOf(out);
const nxt = visibleMetaOptions[ixCur + 1];
if (nxt) nxt.$ele.focus();
break;
}
metaOptions
.forEach(metaOption => {
metaOption.$ele.remove();
});
case "ArrowUp": {
evt.preventDefault();
const visibleMetaOptions = metaOptions.filter(it => it.isVisible && !it.isForceHidden);
if (!visibleMetaOptions.length) return;
const ixCur = visibleMetaOptions.indexOf(out);
const prev = visibleMetaOptions[ixCur - 1];
if (prev) return prev.$ele.focus();
$iptSearch.focus();
break;
}
const procValues = isAllowNull ? [null, ...nxtValues] : nxtValues;
metaOptions = procValues
.map((v, i) => {
const display = v == null ? (displayNullAs || "\u2014") : fnDisplay ? fnDisplay(v) : v;
const additionalStyleClasses = fnGetAdditionalStyleClasses ? fnGetAdditionalStyleClasses(v) : null;
const $ele = $(`<div class="ve-flex-v-center py-1 px-1 clickable ui-sel2__disp-option ${v == null ? `italic` : ""} ${additionalStyleClasses ? additionalStyleClasses.join(" ") : ""}" tabindex="0">${display}</div>`)
.on("click", () => {
if (isDisabled) return;
case "Enter": {
comp._state[prop] = v;
$ele.blur();
break;
}
}
})
.appendTo($wrpChoices);
$(document.activeElement).blur();
// Temporarily remove pointer events from the dropdown, so it collapses thanks to its :hover CSS
$wrp.addClass("no-events");
setTimeout(() => $wrp.removeClass("no-events"), 50);
})
.on("keydown", evt => {
if (isDisabled) return;
const isForceHidden = opts.isHiddenPerValue && !!(opts.isAllowNull ? opts.isHiddenPerValue[i - 1] : opts.isHiddenPerValue[i]);
if (isForceHidden) $ele.hideVe();
switch (evt.key) {
case "Escape": evt.stopPropagation(); return $ele.blur();
const out = {
value: v,
isVisible: true,
isForceHidden,
searchTerm: this._$getSelSearchable_getSearchString(display),
$ele,
};
return out;
});
case "ArrowDown": {
evt.preventDefault();
const visibleMetaOptions = metaOptions.filter(it => it.isVisible && !it.isForceHidden);
if (!visibleMetaOptions.length) return;
const ixCur = visibleMetaOptions.indexOf(out);
const nxt = visibleMetaOptions[ixCur + 1];
if (nxt) nxt.$ele.focus();
break;
}
case "ArrowUp": {
evt.preventDefault();
const visibleMetaOptions = metaOptions.filter(it => it.isVisible && !it.isForceHidden);
if (!visibleMetaOptions.length) return;
const ixCur = visibleMetaOptions.indexOf(out);
const prev = visibleMetaOptions[ixCur - 1];
if (prev) return prev.$ele.focus();
$iptSearch.focus();
break;
}
case "Enter": {
comp._state[prop] = v;
$ele.blur();
break;
}
}
})
.appendTo($wrpChoices);
const isForceHidden = isHiddenPerValue && !!(isAllowNull ? isHiddenPerValue[i - 1] : isHiddenPerValue[i]);
if (isForceHidden) $ele.hideVe();
const out = {
value: v,
isVisible: true,
isForceHidden,
searchTerm: this._$getSelSearchable_getSearchString(display),
$ele,
};
return out;
});
this._$getSel_setValues_handleResetOnMissing({
component: comp,
_propProxy,
prop,
isResetOnMissing,
nxtValues,
isAllowNull,
});
hk();
};
setValues(values);
const fnUpdateHidden = (isHiddenPerValue, isHideNull = false) => {
let metaOptions_ = metaOptions;
if (opts.isAllowNull) {
if (isAllowNull) {
metaOptions_[0].isForceHidden = isHideNull;
metaOptions_ = metaOptions_.slice(1);
}
@@ -5231,24 +5285,14 @@ class ComponentUiUtil {
handleSearchChange();
};
const hk = () => {
if (comp._state[prop] == null) $iptDisplay.addClass("italic").addClass("ve-muted").val(opts.displayNullAs || "\u2014");
else $iptDisplay.removeClass("italic").removeClass("ve-muted").val(opts.fnDisplay ? opts.fnDisplay(comp._state[prop]) : comp._state[prop]);
metaOptions.forEach(it => it.$ele.removeClass("active"));
const metaActive = metaOptions.find(it => it.value == null ? comp._state[prop] == null : it.value === comp._state[prop]);
if (metaActive) metaActive.$ele.addClass("active");
};
comp._addHookBase(prop, hk);
hk();
return opts.asMeta
return asMeta
? ({
$wrp,
unhook: () => comp._removeHookBase(prop, hk),
$iptDisplay,
$iptSearch,
fnUpdateHidden,
setValues,
})
: $wrp;
}
@@ -5258,6 +5302,37 @@ class ComponentUiUtil {
return CleanUtil.getCleanString(str.trim().toLowerCase().replace(/\s+/g, " "));
}
// If the new value list doesn't contain our current value, reset our current value
static _$getSel_setValues_handleResetOnMissing (
{
component,
_propProxy,
prop,
isResetOnMissing,
nxtValues,
isSetIndexes,
isAllowNull,
},
) {
if (!isResetOnMissing) return;
if (component[_propProxy][prop] == null) return;
if (isSetIndexes) {
if (component[_propProxy][prop] >= 0 && component[_propProxy][prop] < nxtValues.length) {
if (isAllowNull) return component[_propProxy][prop] = null;
return component[_propProxy][prop] = 0;
}
return;
}
if (!nxtValues.includes(component[_propProxy][prop])) {
if (isAllowNull) return component[_propProxy][prop] = null;
return component[_propProxy][prop] = nxtValues[0];
}
}
/**
* @param component An instance of a class which extends BaseComponent.
* @param prop Component to hook on.
@@ -5272,7 +5347,21 @@ class ComponentUiUtil {
* @param [opts.propProxy] Proxy prop.
* @param [opts.isSetIndexes] If the index of the selected item should be set as state, rather than the item itself.
*/
static $getSelEnum (component, prop, {values, $ele, html, isAllowNull, fnDisplay, displayNullAs, asMeta, propProxy = "state", isSetIndexes = false} = {}) {
static $getSelEnum (
component,
prop,
{
values,
$ele,
html,
isAllowNull,
fnDisplay,
displayNullAs,
asMeta,
propProxy = "state",
isSetIndexes = false,
} = {},
) {
const _propProxy = `_${propProxy}`;
let values_;
@@ -5289,27 +5378,6 @@ class ComponentUiUtil {
component[_propProxy][prop] = isSetIndexes ? 0 : values_[0];
});
// If the new value list doesn't contain our current value, reset our current value
const setValues_handleResetOnMissing = ({isResetOnMissing, nxtValues}) => {
if (!isResetOnMissing) return;
if (component[_propProxy][prop] == null) return;
if (isSetIndexes) {
if (component[_propProxy][prop] >= 0 && component[_propProxy][prop] < nxtValues.length) {
if (isAllowNull) return component[_propProxy][prop] = null;
return component[_propProxy][prop] = 0;
}
return;
}
if (!nxtValues.includes(component[_propProxy][prop])) {
if (isAllowNull) return component[_propProxy][prop] = null;
return component[_propProxy][prop] = nxtValues[0];
}
};
const setValues = (nxtValues, {isResetOnMissing = false, isForce = false} = {}) => {
if (!isForce && CollectionUtil.deepEquals(values_, nxtValues)) return;
values_ = nxtValues;
@@ -5318,7 +5386,15 @@ class ComponentUiUtil {
if (isAllowNull) { const opt = document.createElement("option"); opt.value = "-1"; opt.text = displayNullAs || "\u2014"; $sel.append(opt); }
values_.forEach((it, i) => { const opt = document.createElement("option"); opt.value = `${i}`; opt.text = fnDisplay ? fnDisplay(it) : it; $sel.append(opt); });
setValues_handleResetOnMissing({isResetOnMissing, nxtValues});
this._$getSel_setValues_handleResetOnMissing({
component,
_propProxy,
prop,
isResetOnMissing,
nxtValues,
isSetIndexes,
isAllowNull,
});
hook();
};

View File

@@ -2,7 +2,7 @@
// in deployment, `IS_DEPLOYED = "<version number>";` should be set below.
globalThis.IS_DEPLOYED = undefined;
globalThis.VERSION_NUMBER = /* 5ETOOLS_VERSION__OPEN */"1.206.1"/* 5ETOOLS_VERSION__CLOSE */;
globalThis.VERSION_NUMBER = /* 5ETOOLS_VERSION__OPEN */"1.207.0"/* 5ETOOLS_VERSION__CLOSE */;
globalThis.DEPLOYED_IMG_ROOT = undefined;
// for the roll20 script to set
globalThis.IS_VTT = false;
@@ -4945,7 +4945,16 @@ globalThis.DataUtil = {
return DataUtil.generic._getVersions_basic({ver});
})
.flat()
.map(ver => DataUtil.generic._getVersion({parentEntity: parent, version: ver, impl, isExternalApplicationIdentityOnly}));
.map(ver => DataUtil.generic._getVersion({parentEntity: parent, version: ver, impl, isExternalApplicationIdentityOnly}))
.filter(ver => {
if (!UrlUtil.URL_TO_HASH_BUILDER[ver.__prop]) throw new Error(`Unhandled version prop "${ver.__prop}"!`);
return !ExcludeUtil.isExcluded(
UrlUtil.URL_TO_HASH_BUILDER[ver.__prop](ver),
ver.__prop,
SourceUtil.getEntitySource(ver),
{isNoCount: true},
);
});
},
_getVersions_template ({ver}) {
@@ -4997,6 +5006,7 @@ globalThis.DataUtil = {
_versionBase_hasToken: parentEntity.hasToken,
_versionBase_hasFluff: parentEntity.hasFluff,
_versionBase_hasFluffImages: parentEntity.hasFluffImages,
__prop: parentEntity.__prop,
};
const cpyParentEntity = MiscUtil.copyFast(parentEntity);
@@ -5005,6 +5015,12 @@ globalThis.DataUtil = {
delete cpyParentEntity.hasFluff;
delete cpyParentEntity.hasFluffImages;
["additionalSources", "otherSources"]
.forEach(prop => {
if (cpyParentEntity[prop]?.length) cpyParentEntity[prop] = cpyParentEntity[prop].filter(srcMeta => srcMeta.source !== version.source);
if (!cpyParentEntity[prop]?.length) delete cpyParentEntity[prop];
});
DataUtil.generic.copyApplier.getCopy(
impl,
cpyParentEntity,
@@ -7495,15 +7511,15 @@ globalThis.ExcludeUtil = {
};
// EXTENSIONS ==========================================================================================================
globalThis.ExtensionUtil = {
ACTIVE: false,
globalThis.ExtensionUtil = class {
static ACTIVE = false;
_doSend (type, data) {
static _doSend (type, data) {
const detail = MiscUtil.copy({type, data}); // Note that this needs to include `JSON.parse` to function
window.dispatchEvent(new CustomEvent("rivet.send", {detail}));
},
}
async pDoSendStats (evt, ele) {
static async pDoSendStats (evt, ele) {
const {page, source, hash, extensionData} = ExtensionUtil._getElementData({ele});
if (page && source && hash) {
@@ -7522,9 +7538,9 @@ globalThis.ExtensionUtil = {
ExtensionUtil._doSend("entity", {page, entity: toSend, isTemp: !!evt.shiftKey});
}
},
}
async doDragStart (evt, ele) {
static async doDragStart (evt, ele) {
const {page, source, hash} = ExtensionUtil._getElementData({ele});
const meta = {
type: VeCt.DRAG_TYPE_IMPORT,
@@ -7533,9 +7549,9 @@ globalThis.ExtensionUtil = {
hash,
};
evt.dataTransfer.setData("application/json", JSON.stringify(meta));
},
}
_getElementData ({ele}) {
static _getElementData ({ele}) {
const $parent = $(ele).closest(`[data-page]`);
const page = $parent.attr("data-page");
const source = $parent.attr("data-source");
@@ -7544,31 +7560,31 @@ globalThis.ExtensionUtil = {
const extensionData = rawExtensionData ? JSON.parse(rawExtensionData) : null;
return {page, source, hash, extensionData};
},
}
pDoSendStatsPreloaded ({page, entity, isTemp, options}) {
static pDoSendStatsPreloaded ({page, entity, isTemp, options}) {
ExtensionUtil._doSend("entity", {page, entity, isTemp, options});
},
}
pDoSendCurrency ({currency}) {
static pDoSendCurrency ({currency}) {
ExtensionUtil._doSend("currency", {currency});
},
}
doSendRoll (data) { ExtensionUtil._doSend("roll", data); },
static doSendRoll (data) { ExtensionUtil._doSend("roll", data); }
pDoSend ({type, data}) { ExtensionUtil._doSend(type, data); },
static pDoSend ({type, data}) { ExtensionUtil._doSend(type, data); }
/* -------------------------------------------- */
_CACHE_EMBEDDED_STATS: {},
static _CACHE_EMBEDDED_STATS = {};
addEmbeddedToCache (page, source, hash, ent) {
static addEmbeddedToCache (page, source, hash, ent) {
MiscUtil.set(ExtensionUtil._CACHE_EMBEDDED_STATS, page.toLowerCase(), source.toLowerCase(), hash.toLowerCase(), MiscUtil.copyFast(ent));
},
}
_getEmbeddedFromCache (page, source, hash) {
static _getEmbeddedFromCache (page, source, hash) {
return MiscUtil.get(ExtensionUtil._CACHE_EMBEDDED_STATS, page.toLowerCase(), source.toLowerCase(), hash.toLowerCase());
},
}
/* -------------------------------------------- */
};

View File

@@ -3,6 +3,7 @@ async function main () {
await import("./generate-quick-reference.js");
await (await import("./generate-tables-data.js")).default;
await import("./generate-subclass-lookup.js");
await import("./generate-variantrules-data.js");
await (await import("./generate-spell-source-lookup.js")).default;
await import("./generate-nav-adventure-book-index.js");
await import("./generate-all-maps.js");

View File

@@ -34,8 +34,10 @@ const _FILE_PROP_ORDER = [
// region Player options
"class",
"foundryClass",
"classFluff",
"subclass",
"foundrySubclass",
"subclassFluff",
"classFeature",
"foundryClassFeature",
"subclassFeature",
@@ -259,6 +261,7 @@ function getFnListSort (prop) {
case "skill":
case "deck":
case "citation":
case "foundryMap":
return SortUtil.ascSortGenericEntity.bind(SortUtil);
case "deity":
return SortUtil.ascSortDeity.bind(SortUtil);

18
package-lock.json generated
View File

@@ -1,15 +1,15 @@
{
"name": "5etools",
"version": "1.206.1",
"version": "1.207.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "5etools",
"version": "1.206.1",
"version": "1.207.0",
"license": "MIT",
"devDependencies": {
"5etools-utils": "^0.10.30",
"5etools-utils": "^0.11.2",
"ajv": "^8.12.0",
"ajv-formats": "^2.1.1",
"commander": "^12.0.0",
@@ -3926,9 +3926,9 @@
"dev": true
},
"node_modules/5etools-utils": {
"version": "0.10.30",
"resolved": "https://registry.npmjs.org/5etools-utils/-/5etools-utils-0.10.30.tgz",
"integrity": "sha512-Udr0QfxlBLBohsgMklfv4f3XZbueHTR/EzHFn4TTYZ8OnW1IsPb4XFxbH86RDmylPY3Jqz3OAHy8CukrhQF0/g==",
"version": "0.11.2",
"resolved": "https://registry.npmjs.org/5etools-utils/-/5etools-utils-0.11.2.tgz",
"integrity": "sha512-79x7asL9VBerejHpSzkdS4vjZhHszaUnn12lhv7/bNIuQWyMqyPKIKM7/yVCzp1HG0/w3O2V3Mlkk2SJxYdG0w==",
"dev": true,
"dependencies": {
"ajv": "^8.12.0",
@@ -14589,9 +14589,9 @@
"dev": true
},
"5etools-utils": {
"version": "0.10.30",
"resolved": "https://registry.npmjs.org/5etools-utils/-/5etools-utils-0.10.30.tgz",
"integrity": "sha512-Udr0QfxlBLBohsgMklfv4f3XZbueHTR/EzHFn4TTYZ8OnW1IsPb4XFxbH86RDmylPY3Jqz3OAHy8CukrhQF0/g==",
"version": "0.11.2",
"resolved": "https://registry.npmjs.org/5etools-utils/-/5etools-utils-0.11.2.tgz",
"integrity": "sha512-79x7asL9VBerejHpSzkdS4vjZhHszaUnn12lhv7/bNIuQWyMqyPKIKM7/yVCzp1HG0/w3O2V3Mlkk2SJxYdG0w==",
"dev": true,
"requires": {
"ajv": "^8.12.0",

View File

@@ -1,7 +1,7 @@
{
"name": "5etools",
"author": "TheGiddyLimit",
"version": "1.206.1",
"version": "1.207.0",
"license": "MIT",
"description": "A site dedicated to making playing games with your friends as easy as possible.",
"type": "module",
@@ -46,7 +46,7 @@
"url": "git+https://github.com/5etools-mirror-2/5etools-mirror-2.github.io.git"
},
"devDependencies": {
"5etools-utils": "^0.10.30",
"5etools-utils": "^0.11.2",
"ajv": "^8.12.0",
"ajv-formats": "^2.1.1",
"commander": "^12.0.0",

View File

@@ -2,6 +2,16 @@
.ve-night-mode .rd {
&__ {
&wrp-image {
&--bg-light {
background-color: vars-night.$rgb-font--night;
}
&--bg-dark {
background-color: vars-night.$rgb-bg--alt-night;
}
}
&h--0,
&h--1,
&h--2 {
@@ -52,6 +62,16 @@
background-color: vars-night.$rgb-bg-highlight--night;
color: vars-night.$rgb-bg--night;
}
&scroller-viewer {
&--bg-light {
background-color: vars-night.$rgb-font--night;
}
&--bg-dark {
background-color: vars-night.$rgb-bg--alt-night;
}
}
}
&-item__ {

View File

@@ -118,6 +118,19 @@ $rgb-inset-border: #656565;
&wrp-image {
margin: 5px auto 0;
text-align: center;
&--bg {
border-radius: 5px;
padding: 7px;
}
&--bg-light {
background: vars.$rgb-bg;
}
&--bg-dark {
background: vars.$rgb-off-black;
}
}
&image {
@@ -209,6 +222,14 @@ $rgb-inset-border: #656565;
&scroller-viewer {
scrollbar-width: auto;
&--bg-light {
background: vars.$rgb-bg;
}
&--bg-dark {
background: vars.$rgb-off-black;
}
&::-webkit-scrollbar {
width: 15px;
height: 15px;

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,90 @@
import "../js/parser.js";
import "../js/utils.js";
import * as ut from "../node/util.js";
const getClosestEntryId = stack => {
const ent = [...stack].reverse().find(ent => ent.id);
if (!ent) return null;
return ent.id;
};
const getMapLogName = ({obj, stack}) => {
const closestEntryId = getClosestEntryId(stack);
const ptsId = [
obj.id ? `id "${obj.id}"` : "",
obj.mapParent?.id ? `parent id "${obj.mapParent.id}"` : "",
closestEntryId ? `closest entry id "${closestEntryId}"` : "",
]
.filter(Boolean)
.join("; ");
return `${obj.title ? `"${obj.title}"` : "[Untitled]"}${ptsId ? ` (${ptsId})` : ""}`;
};
const getJoinedWarnings = ({filename, warnings}) => {
return `in "${filename}"\n${warnings.map(it => `\t${it}`).join("\n")}`;
};
let ixLogGroup = 0;
const logGroup = ({name, lines}) => {
if (!lines.length) return;
if (ixLogGroup++) console.log(`\n${"-".repeat(20)}`);
console.log(`\n=== ${name} ===\n`);
lines.forEach(wrn => console.warn(wrn));
};
async function main () {
console.log(`##### Validating adventure/book map grids #####`);
const warningsNoParent = [];
const warningsNoGrid = [];
const walker = MiscUtil.getWalker({isNoModification: true, keyBlocklist: MiscUtil.GENERIC_WALKER_ENTRIES_KEY_BLOCKLIST});
const IMAGE_TYPES_MAP = new Set(["map", "mapPlayer"]);
[
{filename: "adventures.json", prop: "adventure", dir: "adventure"},
{filename: "books.json", prop: "book", dir: "book"},
]
.flatMap(({filename, prop, dir}) => ut.readJson(`./data/${filename}`)[prop]
.map(({id}) => ({filename: `./data/${dir}/${dir}-${id.toLowerCase()}.json`})))
.forEach(({filename}) => {
const json = ut.readJson(filename);
const warningsNoParentFile = [];
const warningsNoGridFile = [];
const stack = [];
walker.walk(
json,
{
object: (obj) => {
if (obj.type !== "image" || !IMAGE_TYPES_MAP.has(obj.imageType)) return;
if (obj.imageType === "mapPlayer" && !obj.mapParent?.id) {
warningsNoParentFile.push(getMapLogName({obj, stack}));
}
if (obj.grid !== undefined) return;
warningsNoGridFile.push(getMapLogName({obj, stack}));
},
},
null,
stack,
);
if (warningsNoParentFile.length) warningsNoParent.push(`Found "mapPlayer"${warningsNoParentFile.length === 1 ? "" : "s"} with no "mapParent" ${getJoinedWarnings({filename, warnings: warningsNoParentFile})}`);
if (warningsNoGridFile.length) warningsNoGrid.push(`Found map${warningsNoGridFile.length === 1 ? "" : "s"} with no "grid" ${getJoinedWarnings({filename, warnings: warningsNoGridFile})}`);
});
logGroup({name: "Map Parents", lines: warningsNoParent});
logGroup({name: "Map Grids", lines: warningsNoGrid});
if (warningsNoParent.length || warningsNoGrid.length) return false;
return true;
}
export default main();

View File

@@ -1,65 +0,0 @@
import "../js/parser.js";
import "../js/utils.js";
import * as ut from "../node/util.js";
const getClosestEntryId = stack => {
const ent = [...stack].reverse().find(ent => ent.id);
if (!ent) return null;
return ent.id;
};
async function main () {
console.log(`##### Validating adventure/book map grids #####`);
const errors = [];
const walker = MiscUtil.getWalker({isNoModification: true, keyBlocklist: MiscUtil.GENERIC_WALKER_ENTRIES_KEY_BLOCKLIST});
const IMAGE_TYPES_MAP = new Set(["map", "mapPlayer"]);
[
{filename: "adventures.json", prop: "adventure", dir: "adventure"},
{filename: "books.json", prop: "book", dir: "book"},
]
.flatMap(({filename, prop, dir}) => ut.readJson(`./data/${filename}`)[prop]
.map(({id}) => ({filename: `./data/${dir}/${dir}-${id.toLowerCase()}.json`})))
.forEach(({filename}) => {
const json = ut.readJson(filename);
const errorsFile = [];
const stack = [];
walker.walk(
json,
{
object: (obj) => {
if (obj.type !== "image" || !IMAGE_TYPES_MAP.has(obj.imageType)) return;
if (obj.grid !== undefined) return;
const closestEntryId = getClosestEntryId(stack);
const ptsId = [
obj.id ? `id "${obj.id}"` : "",
obj.mapParent?.id ? `parent id "${obj.mapParent.id}"` : "",
closestEntryId ? `closest entry id "${closestEntryId}"` : "",
]
.filter(Boolean)
.join("; ");
errorsFile.push(`${obj.title ? `"${obj.title}"` : "[Untitled]"}${ptsId ? ` (${ptsId})` : ""}`);
},
},
null,
stack,
);
if (!errorsFile.length) return;
errors.push(`Found maps with no "grid" in "${filename}"\n${errorsFile.map(it => `\t${it}`).join("\n")}`);
});
if (errors.length) {
errors.forEach(err => console.error(err));
return false;
}
return true;
}
export default main();

View File

@@ -15,7 +15,7 @@ async function main () {
if (!(await (await import("./test-multisource.js")).default)) handleFail();
if (!(await (await import("./test-language-fonts.js")).default)) handleFail();
if (!(await (await import("./test-adventure-book-contents.js")).default)) handleFail();
await (await import("./test-adventure-book-map-grids.js")).default; // don't fail on missing map grids
await (await import("./test-adventure-book-map-grids-parents.js")).default; // don't fail on missing map grids
if (!(await (await import("./test-foundry.js")).default)) handleFail();
process.exit(0);
}

View File

@@ -1,11 +1,19 @@
import * as fs from "fs";
import fs from "fs";
import path from "path";
import "../js/parser.js";
import "../js/utils.js";
import * as ut from "../node/util.js";
import {listFiles} from "../node/util.js";
class _TestTokenImages {
static _IS_CLEAN_MM_EXTRAS = false;
static _IS_CLEAN_EXTRAS = false;
static _IS_MOVE_EXTRAS = false;
static _SOURCES_CLEAN_EXTRAS = [
Parser.SRC_MM,
Parser.SRC_MPMM,
Parser.SRC_BAM,
Parser.SRC_VRGR,
];
static _PATH_BASE = `./img/bestiary/tokens`;
static _EXT = "webp";
@@ -20,11 +28,11 @@ class _TestTokenImages {
static _existing = new Set();
static _expectedFromHashToken = {};
static _mmTokens = null;
static _existingSourceTokens = null;
static _isMmToken (filename) {
if (!this._mmTokens) this._mmTokens = fs.readdirSync(`${this._PATH_BASE}/${Parser.SRC_MM}`).mergeMap(it => ({[it]: true}));
return !!this._mmTokens[filename.split("/").last()];
static _isExistingSourceToken ({filename, src}) {
(this._existingSourceTokens ||= {})[src] ||= fs.readdirSync(`${this._PATH_BASE}/${src}`).mergeMap(it => ({[it]: true}));
return !!this._existingSourceTokens[src][filename.split("/").last()];
}
static _readBestiaryJson () {
@@ -79,12 +87,25 @@ class _TestTokenImages {
});
this._existing.forEach((img) => {
delete this._expectedFromHashToken[img];
if (!this._expected.has(img)) {
if (this._IS_CLEAN_MM_EXTRAS && this._isMmToken(img)) {
fs.unlinkSync(img);
results.push(`[ !DELETE] ${img}`);
return;
if (this._IS_CLEAN_EXTRAS) {
const srcExisting = this._SOURCES_CLEAN_EXTRAS
.find(src => this._isExistingSourceToken({filename: img, src}));
if (srcExisting) {
fs.unlinkSync(img);
results.push(`[ !DELETE] ${img} (found in "${srcExisting}")`);
return;
}
}
if (this._IS_MOVE_EXTRAS) {
const dir = path.join(path.dirname(img), "extras");
fs.mkdirSync(dir, {recursive: true});
fs.copyFileSync(img, path.join(dir, path.basename(img)));
fs.unlinkSync(img);
}
results.push(`[ EXTRA] ${img}`);
isError = true;
}

View File

@@ -31,6 +31,7 @@ async function main () {
return relativeFilePath;
},
});
await jsonTester.pInit();
const fileList = Uf.listJsonFiles("data")
.filter(filePath => {

View File

@@ -968,6 +968,11 @@ class DuplicateEntityCheck extends DataTesterBase {
.forEach(([prop, arr]) => {
const positions = {};
arr.forEach((ent, i) => {
if (ent == null) return;
if (typeof ent !== "object" || ent instanceof Array) return;
ent.__prop = prop;
isSkipBaseCheck || this._doAddPosition({prop, ent, ixArray: i, positions});
if (!ent._versions) return;

View File

@@ -12,6 +12,7 @@ export const BLOCKLIST_SOURCES_PAGES = new Set([
Parser.SRC_LK,
Parser.SRC_AATM,
Parser.SRC_HFStCM,
Parser.SRC_VNotEE,
// N.b.: other MCV source creatures mysteriously have page numbers on Beyond
Parser.SRC_MCV4EC,