This commit is contained in:
TheGiddyLimit
2024-04-16 23:10:29 +01:00
parent 5e0cc455b9
commit adec95d4ab
36 changed files with 1730 additions and 294 deletions

File diff suppressed because one or more lines are too long

View File

@@ -6381,6 +6381,37 @@
}
]
},
{
"type": "gallery",
"images": [
{
"type": "image",
"href": {
"type": "internal",
"path": "adventure/CoS/map-4.02-walls.webp"
},
"title": "Walls of Ravenloft (Battlemap; DM Version)",
"width": 3840,
"height": 5211,
"imageType": "map",
"id": "411"
},
{
"type": "image",
"href": {
"type": "internal",
"path": "adventure/CoS/map-4.02-walls-player.webp"
},
"title": "Walls of Ravenloft (Battlemap; Player Version)",
"width": 3840,
"height": 5211,
"imageType": "mapPlayer",
"mapParent": {
"id": "411"
}
}
]
},
{
"type": "entries",
"name": "K1. Front Courtyard",
@@ -7462,6 +7493,37 @@
}
]
},
{
"type": "gallery",
"images": [
{
"type": "image",
"href": {
"type": "internal",
"path": "adventure/CoS/map-4.03-main-floor.webp"
},
"title": "Main Floor (Battlemap; DM Version)",
"width": 3000,
"height": 2072,
"imageType": "map",
"id": "4bc"
},
{
"type": "image",
"href": {
"type": "internal",
"path": "adventure/CoS/map-4.03-main-floor-player.webp"
},
"title": "Main Floor (Battlemap; Player Version)",
"width": 3000,
"height": 2072,
"imageType": "mapPlayer",
"mapParent": {
"id": "4bc"
}
}
]
},
{
"type": "entries",
"name": "K7. Entry",
@@ -8570,6 +8632,37 @@
}
]
},
{
"type": "gallery",
"images": [
{
"type": "image",
"href": {
"type": "internal",
"path": "adventure/CoS/map-4.04-court-revised.webp"
},
"title": "Court of the Count (Battlemap; DM Version)",
"width": 5560,
"height": 3840,
"imageType": "map",
"id": "530"
},
{
"type": "image",
"href": {
"type": "internal",
"path": "adventure/CoS/map-4.04-court-player-revised.webp"
},
"title": "Court of the Count (Battlemap; Player Version)",
"width": 5560,
"height": 3840,
"imageType": "mapPlayer",
"mapParent": {
"id": "530"
}
}
]
},
{
"type": "entries",
"name": "K25. Audience Hall",
@@ -9710,6 +9803,37 @@
}
]
},
{
"type": "gallery",
"images": [
{
"type": "image",
"href": {
"type": "internal",
"path": "adventure/CoS/map-4.05-weeping.webp"
},
"title": "Rooms of Weeping (Battlemap; DM Version)",
"width": 3000,
"height": 2072,
"imageType": "map",
"id": "54d"
},
{
"type": "image",
"href": {
"type": "internal",
"path": "adventure/CoS/map-4.05-weeping-player.webp"
},
"title": "Rooms of Weeping (Battlemap; Player Version)",
"width": 3000,
"height": 2072,
"imageType": "mapPlayer",
"mapParent": {
"id": "54d"
}
}
]
},
{
"type": "entries",
"name": "K35. Guardian Vermin",
@@ -10544,6 +10668,37 @@
}
]
},
{
"type": "gallery",
"images": [
{
"type": "image",
"href": {
"type": "internal",
"path": "adventure/CoS/map-4.06-spires12.webp"
},
"title": "Spires and Tower of Ravenloft (Battlemap; DM Version)",
"width": 5000,
"height": 3372,
"imageType": "map",
"id": "573"
},
{
"type": "image",
"href": {
"type": "internal",
"path": "adventure/CoS/map-4.06-spires12-player.webp"
},
"title": "Spires and Tower of Ravenloft (Battlemap; Player Version)",
"width": 5000,
"height": 3372,
"imageType": "mapPlayer",
"mapParent": {
"id": "573"
}
}
]
},
{
"type": "entries",
"name": "K47. Portrait of Strahd",
@@ -11673,7 +11828,8 @@
"path": "adventure/CoS/037-cos19-10.webp"
},
"width": 1000,
"height": 820
"height": 820,
"maxWidth": 480
},
{
"type": "entries",
@@ -14048,6 +14204,68 @@
}
]
},
{
"type": "gallery",
"images": [
{
"type": "image",
"href": {
"type": "internal",
"path": "adventure/CoS/map-4.08-larders.webp"
},
"title": "Larders of Ill Omen (Battlemap; DM Version)",
"width": 3000,
"height": 2175,
"imageType": "map",
"id": "591"
},
{
"type": "image",
"href": {
"type": "internal",
"path": "adventure/CoS/map-4.08-larders-player.webp"
},
"title": "Larders of Ill Omen (Battlemap; Player Version)",
"width": 3000,
"height": 2175,
"imageType": "mapPlayer",
"mapParent": {
"id": "591"
}
}
]
},
{
"type": "gallery",
"images": [
{
"type": "image",
"href": {
"type": "internal",
"path": "adventure/CoS/map-4.09-dungeons-and-catacombs.webp"
},
"title": "Dungeon and Catacombs (Battlemap; DM Version)",
"width": 3000,
"height": 2929,
"imageType": "map",
"id": "673"
},
{
"type": "image",
"href": {
"type": "internal",
"path": "adventure/CoS/map-4.09-dungeons-and-catacombs-player.webp"
},
"title": "Dungeon and Catacombs (Battlemap; Player Version)",
"width": 3000,
"height": 2929,
"imageType": "mapPlayer",
"mapParent": {
"id": "673"
}
}
]
},
{
"type": "entries",
"name": "K61. Elevator Trap",
@@ -33707,7 +33925,7 @@
"page": 193,
"id": "64b",
"entries": [
"The vestige within this sarcophagus offers the dark gift of Great Taar Haak, the Five-Headed Destroyer. Taar Haak's gift is great strength. This dark gift grants its beneficiary the benefit of a {@item belt of fire giant strength}. This benefit lasts for 10 days, after which the dark giftvanishes.",
"The vestige within this sarcophagus offers the dark gift of Great Taar Haak, the Five-Headed Destroyer. Taar Haak's gift is great strength. This dark gift grants its beneficiary the benefit of a {@item belt of fire giant strength}. This benefit lasts for 10 days, after which the dark gift vanishes.",
"The beneficiary of this dark gift the following flaw: \"I like to bully others and make them feel weak and inferior.\""
]
},

File diff suppressed because it is too large Load Diff

View File

@@ -5,7 +5,8 @@
"name": "Introduction",
"entries": [
"Coming soon! In the meantime, check out the {@adventure preview adventure|DitLCoT}."
]
],
"id": "000"
}
]
}

View File

@@ -0,0 +1,12 @@
{
"data": [
{
"type": "section",
"name": "Introduction",
"entries": [
"Coming soon! In the meantime, check out the {@adventure preview adventure|VNotEE}."
],
"id": "000"
}
]
}

View File

@@ -0,0 +1,826 @@
{
"data": [
{
"type": "section",
"name": "Vecna: Nest of the Eldritch Eye",
"id": "000",
"entries": [
"{@i Vecna: Nest of the Eldritch Eye} is a fifth edition Dungeons & Dragons adventure for four to six characters of level 3. This adventure takes place in the city of Neverwinter on the Sword Coast. A sinister cult has crept into the city's bowels, and the characters must infiltrate the cult's hideout and root out its members before harm befalls the city.",
"The information presented here is for the DM's eyes only. If you're planning to play through the adventure with someone else as your DM, stop reading now!",
{
"type": "image",
"href": {
"type": "internal",
"path": "adventure/VNotEE/000-00-001.something-wicked-approaches.webp"
},
"credit": "Greg Staples",
"width": 1700,
"height": 2490
},
{
"type": "section",
"name": "Running the Adventure",
"id": "001",
"entries": [
"This adventure is designed to be playable in two or three hours. The characters are contracted by Lord Protector Dagult Neverember of Neverwinter to patrol the city. When a dead man is found clutching a withered eyeball, the characters are tasked with uncovering the cause of the man's death. This leads the characters to investigate the city's old catacombs and confront cultists of the lich-god Vecna.",
{
"type": "insetReadaloud",
"id": "002",
"entries": [
"Text that appears in a box like this is meant to be read aloud or paraphrased for the players when their characters first arrive at a location or under a specific circumstance, as described in the text."
]
},
"When a creature's name appears in {@b bold} type, that's a visual clue pointing you to its stat block as a way of saying, \"Hey, DM, get this creature's stat block ready. You're going to need it.\" All monster stat blocks referenced in this adventure can be found in the {@book Monster Manual|MM}.",
"You can make the adventure easier or harder, or adjust it for smaller or larger groups of player characters, by adjusting the number of monsters or by adding or removing encounters."
]
},
{
"type": "section",
"name": "Beginning the Adventure",
"id": "003",
"entries": [
"Begin the adventure by reading aloud the following:",
{
"type": "insetReadaloud",
"id": "004",
"entries": [
"The city of Neverwinter stands proudly on the Sword Coast, west and north of Dessarin Valley. Fifty years ago, the city was nearly destroyed by the eruption of Mount Hotenow. Today, the city stands mostly rebuilt. It bustles with skilled tradespeople, intrepid adventurers, and hardy townsfolk.",
"Leadership in Neverwinter falls to Dagult Neverember, lord protector of the city\u2014and your employer. The city lacks a formal militia, so Lord Neverember often hires mercenaries and adventurers such as yourselves to keep the city secure."
]
},
"Regardless of whether the characters know each other prior to their arrival in Neverwinter, they are assigned to patrol the city together. The first few days of the patrol are uneventful (unless you decide otherwise), allowing the characters to familiarize themselves with the city."
]
},
{
"type": "section",
"name": "Dead Man's Message",
"id": "005",
"entries": [
"After the characters' first few days patrolling the city, tragedy strikes. Read or paraphrase the following:",
{
"type": "insetReadaloud",
"id": "006",
"entries": [
"An anguished scream erupts from a nearby alley. The sound comes from a slender human woman clad in the light leather armor of a scout or investigator. Clutched in her arms is the dead body of a human man clad in a tattered gray robe. The man and woman bear a striking resemblance to each other.",
"With a dull thump, a desiccated eyeball rolls out of the dead man's palm."
]
},
"The dead man is Delvin Fearnehart, an investigator in Lord Neverember's employ; the woman holding his body is Kevori Fearnehart, Delvin's sister. A character who succeeds on a DC 8 Intelligence ({@skill History}) check recognizes both Delvin and Kevori as mercenaries employed by Lord Neverember to patrol the city.",
"If the characters question Kevori (neutral good, human {@creature scout}), she shares the following information:",
{
"type": "entries",
"id": "007",
"entries": [
{
"type": "entries",
"id": "008",
"entries": [
{
"type": "entries",
"name": "Investigators for Hire",
"id": "009",
"entries": [
"Kevori and her brother were tasked by Lord Neverember to investigate suspicious cult activity in the city."
]
},
{
"type": "entries",
"name": "Rendezvous Gone Wrong",
"id": "00a",
"entries": [
"Kevori came here to rendezvous with Delvin after he'd infiltrated a potential cult hideout."
]
},
{
"type": "entries",
"name": "What She Knows",
"id": "00b",
"entries": [
"The only clue Kevori has is the desiccated eyeball that dropped from Delvin's hand.",
"Kevori requests the characters' aid in uncovering who or what killed her brother while she returns to the Hall of Justice to inform Lord Neverember of these developments. She implores the characters to avenge her brother's death, but she also reminds the characters that Lord Neverember values information and will pay more for capturing cultists alive."
]
}
]
}
]
},
{
"type": "entries",
"name": "Investigating the Eyeball",
"id": "00c",
"entries": [
"The eyeball serves as both a map and a key to the cult's hideout. When a creature holding the eyeball speaks the key phrase, \"Hail the Undying,\" the eyeball glows with green light before spinning to look in the direction of the cult's hideout (acting like a compass).",
"A {@spell Detect Magic} spell or similar effect reveals an aura of divination magic around the eyeball. Casting {@spell Identify} while targeting the eyeball reveals its function and its key phrase. Alternatively, a character can rifle through Delvin's pockets and find a slip of parchment with the key phrase scribbled on it.",
"Following the activated eyeball's directions leads the characters to a catacomb entrance in one of the still-ruined sections of Neverwinter (see \"Into Neverwinter's Catacombs\")."
]
}
]
},
{
"type": "section",
"name": "Into Neverwinter's Catacombs",
"id": "00d",
"entries": [
"Several months ago, cultists dedicated to Vecna commandeered sections of Neverwinter's catacombs for their nefarious purposes. The sect the characters are pursuing is in the western ruins of the city.",
"Unless you decide otherwise, the characters encounter no difficulties following the eyeball's directions to a dilapidated entrance into the city's western catacombs. Read or paraphrase the following:",
{
"type": "insetReadaloud",
"id": "00e",
"entries": [
"The withered eyeball aims its empty gaze down an alley strewn with rubble. Rats scurry between chunks of old stone and dusty crates. At the back of the alley is a set of moldering wood boards, propped up just enough to block a dark tunnel leading below the surface."
]
},
"The boards blocking the entrance to the catacombs can be moved easily, revealing a sloping tunnel ({@area area N1|011|x}).",
{
"type": "entries",
"name": "Area Features",
"id": "00f",
"entries": [
"This adventure explores only a small section of Neverwinter's sprawling catacombs. Unless otherwise stated, areas of the catacombs shown in this adventure have the following features:",
{
"type": "list",
"style": "list-hang-notitle",
"items": [
{
"type": "item",
"name": "Ceilings",
"entry": "The ceilings throughout the catacombs are 10 feet high and made of roughly hewn stone."
},
{
"type": "item",
"name": "Eyeball Compass",
"entry": "The activated eyeball continuously points in the direction of the cult hideout's entrance ({@area area N11|02d|x}). The eyeball is also needed to unlock the door in area N11."
},
{
"type": "item",
"name": "Light",
"entry": "Rusted wall sconces hold lit torches. The area is dimly lit."
},
{
"type": "item",
"name": "Walls and Floors",
"entry": "Walls are constructed of stone blocks and are slick with moisture, making climbing impossible without magic or gear. Floors are made from a mix of stone slabs and packed earth."
}
]
}
]
},
{
"type": "entries",
"name": "Area Locations",
"id": "010",
"entries": [
"The following locations are keyed to the Nest of the Eldritch Eye map.",
{
"type": "gallery",
"images": [
{
"type": "image",
"href": {
"type": "internal",
"path": "adventure/VNotEE/001-map-0.01-catacombs.webp"
},
"imageType": "map",
"title": "Map: Nest of the Eldritch Eye",
"credit": "Marco Bernardini",
"width": 4096,
"height": 5301,
"id": "03e"
},
{
"type": "image",
"href": {
"type": "internal",
"path": "adventure/VNotEE/002-map-0.01-catacombs-player.webp"
},
"imageType": "map",
"title": "Player Version",
"width": 4096,
"height": 5301,
"credit": "Marco Bernardini",
"mapParent": {
"id": "03e"
}
}
]
},
{
"type": "entries",
"name": "N1: Entrance Tunnel",
"id": "011",
"entries": [
"The tunnel proceeds at a downward slope, bringing the characters 20 feet beneath street level."
]
},
{
"type": "entries",
"name": "N2: Half-Collapsed Chamber",
"id": "012",
"entries": [
{
"type": "insetReadaloud",
"id": "013",
"entries": [
"The soft lapping of water echoes through this wide chamber. Wood and stone debris from collapsed buildings litter the area. Brackish water floods nearly half of the chamber, getting deeper toward a dark tunnel to the west."
]
},
"Characters who have a passive Wisdom ({@skill Perception}) score of 12 or higher notice the entrance to a cramped tunnel hidden behind some collapsed rubble to the south. A character who spends 1 minute clearing the rubble opens the entrance to a crawlspace ({@area area N7|024|x}).",
{
"type": "entries",
"name": "Making a Skiff",
"id": "014",
"entries": [
"Characters have the option of assembling a makeshift skiff from the debris here to make traversing the flooded area easier. A character attempting to assemble a skiff must make a DC 11 Wisdom ({@skill Survival}) check. On a successful check, the character makes a skiff sturdy enough to support up to two Medium or smaller creatures.",
"While riding a skiff, a creature can use its movement to propel the skiff up to 20 feet in a direction of its choice."
]
}
]
},
{
"type": "entries",
"name": "N3: Flooded Tunnel",
"id": "015",
"entries": [
"The water filling this tunnel is 3 feet deep; without the use of a skiff or other water vehicle, the water counts as {@book difficult terrain|PHB|8|Difficult Terrain} for Medium or larger creatures that lack a swimming speed, while Small and smaller creatures must swim to traverse the tunnel.",
{
"type": "entries",
"name": "Water Weird",
"id": "016",
"entries": [
"A neutral evil {@creature water weird} lurks here. It tries to ambush and kill any creature that enters the tunnel.",
"Upon entering the tunnel for the first time, a character who has a passive Wisdom ({@skill Perception}) score of 15 or higher notices odd waves and currents that suggest the presence of a creature in the water. If a character's passive Wisdom ({@skill Perception}) score is lower than 15, the character has disadvantage on their initiative roll when the water weird attacks."
]
}
]
},
{
"type": "entries",
"name": "N4: Cinerary Rotunda",
"id": "017",
"entries": [
{
"type": "insetReadaloud",
"id": "018",
"entries": [
"Glossy urns and cinerary boxes line the walls of this vaulted rotunda. Drifting in the center of the room above a drain is a ghostly, transparent, humanoid figure. The figure is clad in plate armor, and where a head should be, there is instead a featureless, luminescent orb."
]
},
"The neutral {@creature ghost} haunting this area has no memory of its name or existence in life\u2014a fact which pains it greatly. Upon noticing the characters enter, the ghost beseeches them for help in recovering its previous identity. If the characters refuse, the ghost sulks but isn't hostile.",
{
"type": "entries",
"name": "Discovering the Ghost's Identity",
"id": "019",
"entries": [
"The ghost is the spirit of the knight entombed in {@area area N6|01f|x}. Clues regarding the ghost's identity can be found in areas N5 and N6. A character who searches the rotunda for 10 minutes finds no clues related to the ghost in this room.",
"To regain its identity, the ghost must hear its full name: Chanelle Hallwinter. If a character speaks this name aloud within 20 feet of the ghost, the featureless orb of the ghost's head coalesces into the face of a middle-aged human woman with braided hair, and the ghost's memories return. The ghost is grateful to the characters and resolves to aid them."
]
},
{
"type": "entries",
"name": "What the Ghost Knows",
"id": "01a",
"entries": [
"At first, the ghost remembers nothing about Delvin, the cult, or the catacombs. Once the ghost's identity has been restored, it remembers not only its life as Chanelle Hallwinter but also what it has witnessed while haunting these catacombs. The ghost can share the following information with the characters:",
{
"type": "list",
"style": "list-hang-notitle",
"items": [
{
"type": "item",
"name": "Catacomb Invaders",
"entry": "A small group of Humanoids descended into the catacombs several months ago, whispering about strange rituals and secrets."
},
{
"type": "item",
"name": "Delvin",
"entry": "Chanelle saw a man matching Delvin's description flee through the rotunda from deeper within the catacombs, pursued by an individual in a hooded gray robe."
},
{
"type": "item",
"name": "Zombie Infestation",
"entry": "The crypt to the south of the rotunda was desecrated and now swarms with zombies."
}
]
}
]
}
]
},
{
"type": "entries",
"name": "N5: Tomb of the Mage",
"id": "01b",
"entries": [
{
"type": "insetReadaloud",
"id": "01c",
"entries": [
"The smell of old parchment fills this tomb. Bookshelves carrying various tomes and scholarly implements stand against the walls. At the center is a stone, gold-painted sarcophagus, the sides of which bear a beautiful relief carving of two humanoid women exploring a forest together."
]
},
"This is the tomb of Makalia Siannodel, a female elf mage and the partner of the knight entombed in area N6.",
{
"type": "entries",
"name": "Discovering the Ghost's Identity",
"id": "01d",
"entries": [
"Characters who search this tomb find treasure (see \"Treasure\" below) and the following clues:",
{
"type": "list",
"style": "list-hang-notitle",
"items": [
{
"type": "item",
"name": "Golden Locket",
"entry": "A heart-shaped golden locket sits on one of the bookshelves. The locket is locked but can be either pried open with a successful DC 11 Strength ({@skill Athletics}) check or picked open with a successful DC 10 Dexterity ({@skill Sleight of Hand}) check using thieves' tools. Inside is a small portrait of a human woman, next to which is the inscription, \"My dearest Chanelle.\""
},
{
"type": "item",
"name": "Relief Details",
"entry": "One of the figures depicted in the sarcophagus's relief carving wears armor that matches the armor of the ghost in area N4."
}
]
}
]
},
{
"type": "entries",
"name": "Treasure",
"id": "01e",
"entries": [
"Nestled among the bookshelves are five incense sticks (worth 10 gp each) and a {@item component pouch|PHB}. A character who inspects the area and succeeds on a DC 15 Intelligence ({@skill Investigation}) check also finds a hidden compartment in one of the shelved books. Inside is a {@item Spell Scroll} of {@spell Protection from Evil and Good}."
]
}
]
},
{
"type": "entries",
"name": "N6: Tomb of the Knight",
"id": "01f",
"entries": [
{
"type": "insetReadaloud",
"id": "020",
"entries": [
"A stone, gold-painted sarcophagus rests in the center of this tomb. The sides of the sarcophagus bear a relief depicting two women gazing lovingly over a field and a city, and the lid has a faintly distinguishable family crest carved into it. Old weapons and decorated armor line the walls of this room."
]
},
"This is the tomb of Chanelle Hallwinter, a female human knight and the partner of the mage entombed in area N5. This is also the tomb of the ghost found in {@area area N4|017|x}.",
{
"type": "entries",
"name": "Discovering the Ghost's Identity",
"id": "021",
"entries": [
"Characters who search this tomb for clues find the following pieces of information:",
{
"type": "list",
"style": "list-hang-notitle",
"items": [
{
"type": "item",
"name": "Family Crest",
"entry": "Engraved on the lid of the sarcophagus is a coat of arms depicting a blunted six-point crown. A character who succeeds on a DC 10 Intelligence ({@skill History}) check recognizes this symbol as the crest of the Hallwinter family, whose lineage produced renowned knights throughout the Sword Coast."
},
{
"type": "item",
"name": "Relief Details",
"entry": "One of the figures depicted in the sarcophagus's relief carving wears armor that matches the armor of the ghost in area N4."
}
]
}
]
},
{
"type": "entries",
"name": "Treasure",
"id": "022",
"entries": [
"Most items stored in this tomb have rusted, but one shield remains intact. This shield is a cursed {@item Shield of Missile Attraction}; however, if the characters restore the ghost's identity, Chanelle Hallwinter gratefully gives the shield to the characters and removes the curse, transforming the shield into a {@item +1 Shield}."
]
},
{
"type": "entries",
"name": "Secret Passage",
"id": "023",
"entries": [
"A character who searches the tomb for secret doors and succeeds on a DC 14 Wisdom ({@skill Perception}) check sees the faint outline of a doorway at the back of the tomb. The door is unlocked, swings open with a gentle push, and leads to {@area area N8|026|x}."
]
}
]
},
{
"type": "entries",
"name": "N7: Crawlspace",
"id": "024",
"entries": [
"The cramped passage is large enough to accommodate Small and smaller creatures; Medium creatures must squeeze to move through it.",
{
"type": "entries",
"name": "Disturbed Rubble",
"id": "025",
"entries": [
"When a creature enters the crawlspace for the first time, its movement disturbs some of the rubble above. The creature must succeed on a DC 12 Dexterity saving throw to reach the other side of the crawlspace unscathed. On a failed save, the creature takes 7 ({@dice 3d4}) bludgeoning damage from the falling rubble."
]
}
]
},
{
"type": "entries",
"name": "N8: Forked Tunnel",
"id": "026",
"entries": [
{
"type": "insetReadaloud",
"id": "027",
"entries": [
"The tunnel splits three ways: north toward a tight crawlspace, west toward a stone door, and south toward a foul-smelling chamber."
]
},
"At the end of the west tunnel is a secret door to area N6. From this side, the door is clearly visible and pulls open easily."
]
},
{
"type": "entries",
"name": "N9: Sewerage Chamber",
"id": "028",
"entries": [
{
"type": "insetReadaloud",
"id": "029",
"entries": [
"A foul stench rises from the murky sewage that fills most of this large chamber. Three four-foot-tall burbling masses slink through the muck."
]
},
"The sewage is 1 foot deep and {@book difficult terrain|PHB|8|Difficult Terrain}.",
"The three burbling masses are awakened piles of undead sludge, sloughed from the cult's twisted experiments (each uses the {@creature gray ooze} stat block but is an Undead instead of Ooze). The masses ignore the room's difficult terrain and attack if approached."
]
},
{
"type": "entries",
"name": "N10: Crypt of the Silenced Singers",
"id": "02a",
"entries": [
{
"type": "insetReadaloud",
"id": "02b",
"entries": [
"What was once a serene crypt now lies in ruin. Two chipped stone pillars brace the vaulted ceiling. Tombs are cracked open, and reanimated corpses prowl the room. At the center of the room's back wall is a dirtied and desecrated shrine bearing the image of a blank scroll."
]
},
"Eight {@creature Zombie||zombies} shamble around this crypt. The zombies are hungry and immediately attack upon seeing a creature unless that creature succeeds on a DC 16 Dexterity ({@skill Stealth}) check.",
{
"type": "entries",
"name": "Shrine to Oghma",
"id": "02c",
"entries": [
"A character who examines the shrine and succeeds on a DC 10 Intelligence ({@skill Religion}) check recognizes the image on the shrine as the holy symbol of Oghma, god of knowledge and patron to bards and wizards. If the check succeeds by 3 or more, the character intuits that rededicating the shrine to Oghma could help against the zombies in the area.",
"To rededicate the shrine, a creature must first use an action to clean it. Once the filth is cleaned off, a creature can use an action to do one of the following:",
{
"type": "list",
"items": [
"Make a DC 14 Charisma ({@skill Performance}) check using an instrument within 5 feet of the shrine, performing a song dedicated to Oghma.",
"Touch the shrine and expend a spell slot of 1st level or higher."
]
},
"Once one of the two options has been done successfully, the shrine glows with white light, and all zombies in the area immediately collapse with the {@condition unconscious} condition for 24 hours. The condition ends early for a zombie if it takes damage."
]
}
]
},
{
"type": "entries",
"name": "N11: Door of the Eldritch Eye",
"id": "02d",
"entries": [
{
"type": "insetReadaloud",
"id": "02e",
"entries": [
"A double door made of green-tinged stone seals off further progression into the catacombs. Facing the double door is a large carving of a grinning skull; one eye socket bears a wide eyeball with a jeweled iris, while the other is an empty divot."
]
},
"The double door is locked and has no keyholes. To open the door from the north side, a desiccated eyeball (such as the one obtained from Delvin) must be placed in the divot of the grinning skull. Once placed there, the eyeball pulses with sickly green light, and the double door swings open to reveal a staircase leading further down. If the eyeball is removed from the divot, the door remains open for 1 minute before closing and locking on its own. A {@spell Knock} spell or similar magic also opens the door.",
"Neither magic nor an eyeball is needed to open the double door from the south. On the door's south side is a stone pedal; stepping on it causes the double door to swing open."
]
},
{
"type": "entries",
"name": "N12: Hall of the Whispered One",
"id": "02f",
"entries": [
{
"type": "insetReadaloud",
"id": "030",
"entries": [
"The bottom of the stairway opens into a wide sanctuary with a vaulted ceiling. Stone pews are arranged in orderly rows. Black candles burning green flames occupy niches along the walls. Atop a pulpit at the far end of the hall stands a jagged sculpture of an emaciated hand with one eyeball in its palm."
]
},
"A character who succeeds on a DC 11 Intelligence ({@skill Religion}) check recognizes the sculpture as the symbol of Vecna, a powerful lich and god of secrets known and feared on many worlds.",
"Any loud noises here awaken the cultists in area N13, who investigate at once."
]
},
{
"type": "entries",
"name": "N13: Cultist Quarters",
"id": "031",
"entries": [
"If the occupants of this room were lured to area N12 by noise there, omit the last sentence of the following boxed text:",
{
"type": "insetReadaloud",
"id": "032",
"entries": [
"Makeshift cots are strewn about two adjoining caverns separated by an open doorway. Several individuals in gray robes slumber here peacefully despite the flickering torchlight."
]
},
"Seven {@creature Cultist||cultists} sleep in these two chambers. Each cultist wears a hooded gray robe that has a desiccated eyeball (like the one obtained from Delvin) tucked in one pocket.",
{
"type": "entries",
"name": "Being Sneaky",
"id": "033",
"entries": [
"To move through these rooms without waking the cultists, a creature must succeed on a DC 14 Dexterity ({@skill Stealth}) check. On a failed check, {@dice 1d4} of the cultists wake up.",
"Awake cultists are hostile but easily cowed. A character can use an action to make a DC 12 Charisma ({@skill Intimidation}) check. On a successful check, all awake cultists that can see or hear the character surrender."
]
},
{
"type": "entries",
"name": "Interrogating the Cultists",
"id": "034",
"entries": [
"Characters can attempt to glean more information from captured cultists by making a DC 10 Charisma ({@skill Intimidation} or {@skill Persuasion}) check. On a successful check, a character learns one of the following pieces of information:",
{
"type": "list",
"style": "list-hang-notitle",
"items": [
{
"type": "item",
"name": "Cult Leader",
"entry": "The cult's leader is Zalryr, who is conducting a profane experiment in the ritual chamber ({@area area N15|039|x}). Zalryr aims to \"draw secrets from the depths of the mortal soul.\""
},
{
"type": "item",
"name": "Cult Worship",
"entry": "The cultists all serve Vecna, whom they refer to as the Whispered One."
},
{
"type": "item",
"name": "Delvin's Fate",
"entry": "Zalryr uncovered the presence of a spy recently. That spy was chased from the catacombs and assassinated for his transgression. The cultists' description of the spy confirms it was Delvin Fearnehart."
}
]
}
]
},
{
"type": "entries",
"name": "Treasure",
"id": "035",
"entries": [
"A character who searches these rooms and makes a successful DC 12 Intelligence ({@skill Investigation}) check finds two gray cultist robes and three gaudy jeweled rings worth 10 gp each. If the check succeeds by 3 or more, the character also finds a {@item Hat of Disguise} tucked beneath one of the unused cots."
]
}
]
},
{
"type": "entries",
"name": "N14: Eldritch Library",
"id": "036",
"entries": [
{
"type": "insetReadaloud",
"id": "037",
"entries": [
"Crooked shelves filled with books and scrolls stand against the walls. In the center of the room is a square table covered in scribbled notes and ink-stained parchment."
]
},
"The notes spread across the table detail the cult's experiments and history. A character who spends at least 10 minutes studying the notes learns the following:",
{
"type": "list",
"style": "list-hang-notitle",
"items": [
{
"type": "item",
"name": "Cult Experiments",
"entry": "The cult's leader, Zalryr, is experimenting with ways to magically siphon secrets from an individual's soul. Early experiments reduced volunteers to piles of necrotic sludge, which were disposed of in the sewerage ({@area area N9|028|x})."
},
{
"type": "item",
"name": "Others?",
"entry": "The cultists here are but one sect of many that have infiltrated Neverwinter. The other sects have chosen different areas of the city's catacombs as their respective hideouts."
}
]
},
{
"type": "entries",
"name": "Trapped Lockbox",
"id": "038",
"entries": [
"A character who inspects the shelves and succeeds on a DC 10 Intelligence ({@skill Investigation}) check finds a small ebony box. The box is locked; Zalryr in area N15 has the key. The box can be picked open by a creature that makes a successful DC 13 Dexterity ({@skill Sleight of Hand}) check using thieves' tools, or it can be forced open with a successful DC 15 Strength ({@skill Athletics}) check.",
"The box is trapped. A creature that opens the box by any means other than using the proper key must make a DC 13 Constitution saving throw, taking 7 ({@dice 2d6}) necrotic damage on a failed saved or half as much damage on a successful one. This trap can't be detected or disarmed.",
"Inside the box are two {@item Potion of Greater Healing||Potions of Healing (greater)}."
]
}
]
},
{
"type": "entries",
"name": "N15: Ritual Chamber",
"id": "039",
"entries": [
{
"type": "insetReadaloud",
"id": "03a",
"entries": [
"Cacophonous whispers echo through this expansive cave. An imposing human cultist in a sweeping robe stands against the wall, chanting. His oily hair is slicked back, and his skin is gaunt and gray.",
"Carved into the floor before him is a runic circle that pulses with sickly green light. Two cultists\u2014one human, one elf\u2014stand inside the circle, heads thrown back and mouths agape. Their leader commands, \"Now! Release your secrets unto me! Let the truths hidden in your soul come forth and become stronger!\" As if in response, the cultists' silhouettes warp into two lanky shadowy entities."
]
},
"Zalryr (neutral evil, human {@creature cult fanatic}) stands at the north end of the room, conducting an experiment on the two {@creature Cultist||cultists} standing inside the runic circle. Zalryr's experiment has magically siphoned the secrets from the cultists' souls to create the two shadowy entities (each uses the {@creature shadow} stat block).",
"Each cultist, including Zalryr, wears a hooded gray robe that has a desiccated eyeball (like the one obtained from Delvin) tucked in one pocket.",
"Upon noticing the characters, Zalryr commands the cultists and the shadowy entities to attack. The cultists and entities fight to their deaths, but Zalryr surrenders when he is reduced to 10 hit points or fewer.",
{
"type": "entries",
"name": "Vecna Appears",
"id": "03b",
"entries": [
"When Zalryr either surrenders or is slain, read or paraphrase the following:",
{
"type": "insetReadaloud",
"id": "03c",
"entries": [
"The runic circle suddenly hisses and flashes with lurid light. As if being extinguished, the glow of the runes dims as shadowy smoke rises from the carvings. The smoke gathers in the center of the chamber, where it coalesces into an apparition of an emaciated skull with one glowing green eye.",
"The apparition speaks in a hissing baritone. \"Good news, Zalryr. Though your efforts have been disappointing, you have brought me new... points of interest.\" The apparition swivels to look at each of you in turn. \"Yes, there is great potential here. May we meet again\u2014but until then, I have my eye on you.\" The apparition then explodes into streaks of shadow, passing through each of you with a whispered scream before vanishing."
]
},
"Vecna bestows one of two supernatural gifts on each character. Have each player roll a die, with the result determining which charm their character receives: the {@reward Charm of the Creeping Hand|VEoR} if the die roll is an odd number or the {@reward Charm of the Eldritch Eye|VEoR} if the die roll is an even number. While in possession of either charm, the character has the unshakeable feeling that they are being watched. See the next section for descriptions of these charms."
]
}
]
}
]
},
{
"type": "entries",
"name": "Vecna's Supernatural Gifts",
"id": "03d",
"entries": [
"Vecna is known to bestow supernatural gifts on mortals who impress him, regardless of their affiliations. The following charms are two of his favorites.",
{
"type": "list",
"items": [
"{@reward Charm of the Creeping Hand|VEoR}",
"{@reward Charm of the Eldritch Eye|VEoR}"
]
}
]
}
]
},
{
"type": "section",
"name": "Concluding the Adventure",
"id": "040",
"entries": [
"The characters can take any captives, leave the catacombs, and report to Lord Neverember without further incident. Lord Neverember is grateful for the characters' efforts and pays each character a base sum of 100 gp for the job, plus an additional 10 gp each for each cultist captured alive (25 gp each for Zalryr).",
{
"type": "entries",
"name": "Continuing the Campaign",
"id": "041",
"entries": [
"Though this sect of cultists has been neutralized, others still lurk in the shadows of Neverwinter, with even more nefarious schemes in service of Vecna. Lord Neverember might call on the characters to continue rooting out these cultists, culminating in an adventure through the sprawling catacombs beneath Neverwinter's Neverdeath Graveyard. If you need help planning further, {@adventure Vecna: Eve of Ruin|VEoR} contains more information about cult activities in Neverdeath Graveyard and provides an excellent campaign that follows Vecna's machinations."
]
}
]
},
{
"type": "section",
"name": "Credits",
"id": "042",
"entries": [
{
"type": "entries",
"entries": [
{
"type": "list",
"style": "list-hang-notitle",
"columns": 2,
"items": [
{
"type": "list",
"style": "list-hang-notitle",
"items": [
{
"type": "item",
"name": "Lead Designer:",
"entry": "Christopher Perkins"
},
{
"type": "item",
"name": "Art Director:",
"entry": "Fury Galuzzi"
},
{
"type": "item",
"name": "Designer:",
"entry": "Makenzie De Armas"
},
{
"type": "item",
"name": "Rules Developer:",
"entry": "Ron Lundeen"
},
{
"type": "item",
"name": "Editors:",
"entry": "Christopher Perkins, Patrick Renie"
},
{
"type": "item",
"name": "Graphic Designer:",
"entry": "Bowen Welles"
},
{
"type": "item",
"name": "Cover Illustrator:",
"entry": "Greg Staples"
},
{
"type": "item",
"name": "Cartographer:",
"entry": "Marco Bernardini"
},
{
"type": "item",
"name": "Illustrator:",
"entry": "Greg Staples"
},
{
"type": "item",
"name": "Consultants:",
"entry": "Rico Corazon, James Mendez, Pam Punzalan"
},
{
"type": "item",
"name": "Game Architects:",
"entry": "Jeremy Crawford, Christopher Perkins"
},
{
"type": "item",
"name": "Studio Art Director:",
"entry": "Josh Herman"
},
{
"type": "item",
"name": "Senior Producer:",
"entry": "Dan Tovar"
},
{
"type": "item",
"name": "Producer:",
"entry": "Siera Bruggeman"
},
{
"type": "item",
"name": "Product Manager:",
"entry": "Brian Perry"
}
]
},
{
"type": "entries",
"name": "D&D Beyond",
"entries": [
{
"type": "list",
"style": "list-hang-notitle",
"items": [
{
"type": "item",
"name": "Product Manager",
"entries": [
"Jeff Turriff"
]
},
{
"type": "item",
"name": "Digital Design Team",
"entries": [
"Jay Jani, Adam Walton"
]
}
]
}
],
"id": "043"
}
]
}
],
"id": "03f"
}
]
}
]
}
]
}

View File

@@ -10181,20 +10181,20 @@
"id": "1c8",
"entries": [
"{@b Ceiling}. This cave has a jagged, 30-foot-high ceiling.",
"{@b Wreckage}. A 20-foot-high mountain of rotted-out rowboats fills the back 40 feet of this damp cave. Jutting like needles from the mound are shattered, barnacle-covered spars that were once masts. At the top of the mountain of junk is a {@creature crow|WDMM}'s nest that contains the sea hags' treasure (see \"Treasure\" below).",
"{@b Wreckage}. A 20-foot-high mountain of rotted-out rowboats fills the back 40 feet of this damp cave. Jutting like needles from the mound are shattered, barnacle-covered spars that were once masts. At the top of the mountain of junk is a crow's nest that contains the sea hags' treasure (see \"Treasure\" below).",
"{@b Wall Decor}. Hanging from iron spikes pounded into the walls are dozens of captain's wheels plundered from sunken ships and festooned with skulls.",
"{@b Figurehead}. Leaning against the south wall is a ship's figurehead carved in the form of a wailing banshee."
]
},
"The rowboat wreckage is {@quickref difficult terrain||3}, and sections of it are prone to collapse. Any creature that ends its turn on the wreckage must succeed on a DC 10 Dexterity saving throw or fall {@condition prone}.",
"The waterlogged figurehead stands 8 feet tall and weighs 1,200 pounds. A {@spell detect magic} spell reveals an aura of abjuration magic around it. Casting {@spell dispel magic} on the figurehead renders it nonmagical. Unless its magic is dispelled, the banshee figurehead lets out a tremendous wail if any creature other than a sea hag approaches within 10 feet of the {@creature crow|WDMM}'s nest. The banshee's wail echoes throughout the cavern and can be heard as far away as {@area area 10c|1c0|x}. If the hags survive, they gather reinforcements from {@area areas 10a|1be|x} and {@area 10b|1bf} and rush to defend their precious treasure.",
"The waterlogged figurehead stands 8 feet tall and weighs 1,200 pounds. A {@spell detect magic} spell reveals an aura of abjuration magic around it. Casting {@spell dispel magic} on the figurehead renders it nonmagical. Unless its magic is dispelled, the banshee figurehead lets out a tremendous wail if any creature other than a sea hag approaches within 10 feet of the crow's nest. The banshee's wail echoes throughout the cavern and can be heard as far away as {@area area 10c|1c0|x}. If the hags survive, they gather reinforcements from {@area areas 10a|1be|x} and {@area 10b|1bf} and rush to defend their precious treasure.",
{
"type": "entries",
"name": "Treasure",
"page": 50,
"id": "1c9",
"entries": [
"The {@creature crow|WDMM}'s nest contains 2,000 cp inside a lidless wooden chest, 250 sp inside a tin urn, an umbrella, a set of weaver's tools, an ebony walking cane with an octopus-shaped handle made of pewter (25 gp), and a cracked spyglass (250 gp)."
"The crow's nest contains 2,000 cp inside a lidless wooden chest, 250 sp inside a tin urn, an umbrella, a set of weaver's tools, an ebony walking cane with an octopus-shaped handle made of pewter (25 gp), and a cracked spyglass (250 gp)."
]
}
]

View File

@@ -34272,6 +34272,117 @@
}
]
},
{
"name": "Vecna: Nest of the Eldritch Eye",
"id": "VNotEE",
"source": "VNotEE",
"group": "supplement",
"cover": {
"type": "internal",
"path": "covers/VEoR.webp"
},
"published": "2024-04-16",
"storyline": "Vecna",
"level": {
"start": 3,
"end": 3
},
"contents": [
{
"name": "Vecna: Nest of the Eldritch Eye",
"headers": [
"Running the Adventure",
"Beginning the Adventure",
"Dead Man's Message",
"Into Neverwinter's Catacombs",
{
"depth": 1,
"header": "N1: Entrance Tunnel"
},
{
"depth": 1,
"header": "N2: Half-Collapsed Chamber"
},
{
"depth": 1,
"header": "N3: Flooded Tunnel"
},
{
"depth": 1,
"header": "N4: Cinerary Rotunda"
},
{
"depth": 1,
"header": "N5: Tomb of the Mage"
},
{
"depth": 1,
"header": "N6: Tomb of the Knight"
},
{
"depth": 1,
"header": "N7: Crawlspace"
},
{
"depth": 1,
"header": "N8: Forked Tunnel"
},
{
"depth": 1,
"header": "N9: Sewerage Chamber"
},
{
"depth": 1,
"header": "N10: Crypt of the Silenced Singers"
},
{
"depth": 1,
"header": "N11: Door of the Eldritch Eye"
},
{
"depth": 1,
"header": "N12: Hall of the Whispered One"
},
{
"depth": 1,
"header": "N13: Cultist Quarters"
},
{
"depth": 1,
"header": "N14: Eldritch Library"
},
{
"depth": 1,
"header": "N15: Ritual Chamber"
},
"Concluding the Adventure",
"Credits"
]
}
]
},
{
"name": "Vecna: Eve of Ruin",
"id": "VEoR",
"source": "VEoR",
"group": "supplement",
"cover": {
"type": "internal",
"path": "covers/VEoR.webp"
},
"published": "2024-05-21",
"author": "Wizards RPG Team",
"storyline": "Vecna",
"level": {
"start": 10,
"end": 20
},
"contents": [
{
"name": "Introduction"
}
]
},
{
"name": "Quests from the Infinite Staircase",
"id": "QftIS",

View File

@@ -4604,7 +4604,7 @@
"wis": "+6"
},
"skill": {
"acrobatics": "+10",
"athletics": "+10",
"perception": "+6"
},
"passive": 16,

View File

@@ -65,7 +65,8 @@
"piercing",
"slashing"
],
"note": "from non-magical attacks"
"note": "from non-magical attacks",
"cond": true
}
],
"conditionImmune": [
@@ -1415,7 +1416,8 @@
"piercing",
"slashing"
],
"note": "from non-magical attacks"
"note": "from non-magical attacks",
"cond": true
}
],
"immune": [
@@ -3043,7 +3045,8 @@
"lightning",
"thunder"
],
"note": "while enlarged"
"note": "while enlarged",
"cond": true
},
{
"resist": [
@@ -3051,7 +3054,8 @@
"piercing",
"slashing"
],
"note": "while raging"
"note": "while raging",
"cond": true
}
],
"conditionImmune": [
@@ -3060,7 +3064,8 @@
"charmed",
"frightened"
],
"note": "while raging"
"note": "while raging",
"cond": true
}
],
"languages": [
@@ -4168,7 +4173,8 @@
"piercing",
"slashing"
],
"note": "from non-magical attacks"
"note": "from non-magical attacks",
"cond": true
}
],
"conditionImmune": [

View File

@@ -90,17 +90,9 @@
{
"type": "entries",
"entries": [
{
"type": "entries",
"entries": [
{
"type": "entries",
"entries": [
"Onyx cannot be overcome or killed by combat. Any weapon attack against her that hits AC 12 makes contact but deals no lasting damage. However, if the attack would deal 10 or more damage, Onyx has disadvantage on attack rolls until the end of her next turn. If Onyx would take 10 or more damage from spells or other effects, it yields the same result. Spells that impose conditions function normally against Onyx, but those conditions end automatically at the end of the cat's next turn."
]
}
]
}
"Onyx is a regular cat, with special traits, and a modified attack, that only apply when used in the {@adventure Onyx Ascendant|OoW|6|Onyx Ascendant} encounter.",
"Further information can be found within that encounter on how to run it.",
"Without the special circumstances from that encounter, Onyx can be considered a standard cat"
]
}
],

View File

@@ -3442,7 +3442,7 @@
"items": [
"Illusory duplicates of the hag appear in random places at random times (but never more than one in any given location). An illusory duplicate has no substance, but it looks, sounds, and moves like the hag. The hag can sense when one or more creatures are within 60 feet of her duplicate and can interact with them as if she were present and standing in the duplicate's space. If the illusory duplicate takes any damage, it disappears.",
"The region takes twice as long as normal to traverse, since the plants grow thick and twisted, and the swamps are thick with reeking mud.",
"Trees transform into awakened trees and attack when hostile intruders are near."
"Trees transform into {@creature awakened tree||awakened trees} and attack when hostile intruders are near."
]
}
]

View File

@@ -2685,5 +2685,11 @@
"date": "2024-04-02",
"title": "Brickolage",
"txt": "- Added Red Dragon's Tale: A LEGO Adventure content\n- Added optional \"feeling lucky\" parameter to Search page for use in browser custom search engine configuration. Note that this changes the syntax for any custom search engines; `?<search>` -> `?q=<search>[&lucky]` (i.e. `?q=goblin` or `?q=goblin&lucky`)\n- Added \"Page\" column to List page Table Views\n- Fixed dice rollers in Decks page card viewer\n- (Brew) Improved Creature Text Converter handling of spells in spellcasting headers\n- (Fixed typos/added tags)"
},
{
"ver": "1.205.0",
"date": "2024-04-16",
"title": "Quick Peek",
"txt": "- Added Vecna: Nest of the Eldritch Eye content\n- Added top-down Castle Ravenloft battlemaps to Curse of Strahd\n- Bestiary \"Use Proficiency Dice\"/\"Use Proficiency Bonus\" button now remembers state between reloads\n- (Fixed typos/added tags)"
}
]

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -2270,6 +2270,7 @@
"source": "DMG",
"page": 152,
"srd": true,
"baseItem": "plate armor|phb",
"type": "HA",
"tier": "major",
"rarity": "legendary",

View File

@@ -1846,6 +1846,7 @@
"languageScript": [
{
"name": "Draconic",
"source": "PHB",
"fonts": [
"fonts/languages/PHB/Draconic/Iokharic.otf",
"fonts/languages/PHB/Draconic/Iokharic Bold.otf",
@@ -1855,6 +1856,7 @@
},
{
"name": "Dwarvish",
"source": "PHB",
"fonts": [
"fonts/languages/PHB/Dwarvish/Dethek.otf",
"fonts/languages/PHB/Dwarvish/Dethek Bold.otf",
@@ -1872,6 +1874,7 @@
},
{
"name": "Elvish",
"source": "PHB",
"fonts": [
"fonts/languages/PHB/Elvish/Espruar 5e.ttf",
"fonts/languages/PHB/Elvish/Espruar 3e.otf",
@@ -1888,6 +1891,7 @@
},
{
"name": "Infernal",
"source": "PHB",
"fonts": [
"fonts/languages/PHB/Infernal/Infernal.ttf",
"fonts/languages/PHB/Infernal/Barazhad.otf",
@@ -1898,6 +1902,7 @@
},
{
"name": "Thorass",
"source": "PHB",
"fonts": [
"fonts/languages/PHB/Human/Thorass.otf",
"fonts/languages/PHB/Human/Thorass Bold.otf",

View File

@@ -13626,7 +13626,7 @@
{
"name": "Drow",
"alias": [
"Dark"
"Elf (Dark)"
],
"source": "PHB",
"raceName": "Elf",

View File

@@ -805,6 +805,14 @@
"As an action, you can force a creature you can see within 60 feet of you to focus on you. For 1 minute, creatures other than you and the target are {@condition invisible} to the target. The effect ends if any creature other than you damages the target or forces the target to make a saving throw. Once used three times, the charm vanishes from you."
]
},
{
"name": "Charm of the Creeping Hand",
"source": "VEoR",
"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."
]
},
{
"name": "Charm of the Crystal Heart",
"source": "ToA",
@@ -823,6 +831,14 @@
"You can cast {@spell Otiluke's Resilient Sphere}, requiring no spell components and using your Intelligence, Wisdom, or Charisma as the spellcasting ability (your choice). Once used three times, the charm vanishes from you."
]
},
{
"name": "Charm of the Eldritch Eye",
"source": "VEoR",
"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."
]
},
{
"name": "Charm of the Fates",
"source": "BMT",

View File

@@ -374,7 +374,7 @@ class BestiaryPage extends ListPageMultiSource {
this._$wrpBtnProf = null;
this._$btnProf = null;
this._profDicMode = _BestiaryConsts.PROF_MODE_BONUS;
this._profDiceMode = null;
this._encounterBuilder = null;
@@ -520,27 +520,36 @@ class BestiaryPage extends ListPageMultiSource {
}
async _pOnLoad_pPreDataAdd () {
this._pPageInit_profBonusDiceToggle();
await this._pPageInit_pProfBonusDiceToggle();
}
_pOnLoad_pPostLoad () {
this._encounterBuilder.render();
}
_pPageInit_profBonusDiceToggle () {
async _pPageInit_pProfBonusDiceToggle () {
const $btnProfBonusDice = $("button#profbonusdice");
this._profDiceMode = await StorageUtil.pGetForPage("proficiencyDiceMode") || _BestiaryConsts.PROF_MODE_BONUS;
const hk = () => {
$btnProfBonusDice.text(this._profDiceMode === _BestiaryConsts.PROF_MODE_DICE ? "Use Proficiency Bonus" : "Use Proficiency Dice");
this._$pgContent.attr("data-proficiency-dice-mode", this._profDiceMode);
StorageUtil.pSetForPage("proficiencyDiceMode", this._profDiceMode).then(null);
};
$btnProfBonusDice.click(() => {
if (this._profDicMode === _BestiaryConsts.PROF_MODE_DICE) {
this._profDicMode = _BestiaryConsts.PROF_MODE_BONUS;
$btnProfBonusDice.html("Use Proficiency Dice");
this._$pgContent.attr("data-proficiency-dice-mode", this._profDicMode);
} else {
this._profDicMode = _BestiaryConsts.PROF_MODE_DICE;
$btnProfBonusDice.html("Use Proficiency Bonus");
this._$pgContent.attr("data-proficiency-dice-mode", this._profDicMode);
if (this._profDiceMode === _BestiaryConsts.PROF_MODE_DICE) {
this._profDiceMode = _BestiaryConsts.PROF_MODE_BONUS;
hk();
return;
}
this._profDiceMode = _BestiaryConsts.PROF_MODE_DICE;
hk();
});
hk();
}
_handleBestiaryLiClick (evt, listItem) {
@@ -557,11 +566,9 @@ class BestiaryPage extends ListPageMultiSource {
}
_bindProfDiceHandlers () {
this._$pgContent.attr("data-proficiency-dice-mode", this._profDicMode);
this._$pgContent
.on(`mousedown`, `[data-roll-prof-type]`, evt => {
if (this._profDicMode !== _BestiaryConsts.PROF_MODE_BONUS) evt.preventDefault();
if (this._profDiceMode !== _BestiaryConsts.PROF_MODE_BONUS) evt.preventDefault();
})
.on(`click`, `[data-roll-prof-type]`, evt => {
const parent = evt.currentTarget.closest(`[data-roll-prof-type]`);
@@ -571,7 +578,7 @@ class BestiaryPage extends ListPageMultiSource {
switch (type) {
case "d20": {
if (this._profDicMode === _BestiaryConsts.PROF_MODE_BONUS) return;
if (this._profDiceMode === _BestiaryConsts.PROF_MODE_BONUS) return;
evt.stopPropagation();
evt.preventDefault();
@@ -585,7 +592,7 @@ class BestiaryPage extends ListPageMultiSource {
}
case "dc": {
if (this._profDicMode === _BestiaryConsts.PROF_MODE_BONUS) {
if (this._profDiceMode === _BestiaryConsts.PROF_MODE_BONUS) {
evt.stopPropagation();
evt.preventDefault();
return;
@@ -736,7 +743,7 @@ class BestiaryPage extends ListPageMultiSource {
if (isNaN(text) || expectedPB <= 0) return null;
const withoutPB = Number(text) - expectedPB;
const profDiceString = BestiaryPage._addSpacesToDiceExp(`+1d${(expectedPB * 2)}${withoutPB >= 0 ? "+" : ""}${withoutPB}`);
const profDiceString = BestiaryPage._addSpacesToDiceExp(`1d${(expectedPB * 2)}${withoutPB >= 0 ? "+" : ""}${withoutPB}`);
return `DC <span class="rd__dc rd__dc--rollable" data-roll-prof-type="dc" data-roll-prof-dice="${profDiceString.qq()}"><span class="rd__dc--rollable-text">${text}</span><span class="rd__dc--rollable-dice">${profDiceString}</span></span>`;
};

View File

@@ -1160,14 +1160,12 @@ class ClassesPage extends MixinComponentGlobalState(MixinBaseComponent(MixinProx
// region HP/hit dice
let $ptHp = null;
if (cls.hd) {
const hdEntry = Renderer.class.getHitDiceEntry(cls.hd);
$ptHp = $(`<tr></tr>`)
.fastSetHtml(`<td colspan="6" class="cls-side__section">
<h5 class="cls-side__section-head">Hit Points</h5>
<div><strong>Hit Dice:</strong> ${Renderer.getEntryDice(hdEntry, "Hit die")}</div>
<div><strong>Hit Dice:</strong> ${Renderer.get().render(Renderer.class.getHitDiceEntry(cls.hd))}</div>
<div><strong>Hit Points at 1st Level:</strong> ${Renderer.class.getHitPointsAtFirstLevel(cls.hd)}</div>
<div><strong>Hit Points at Higher Levels:</strong> ${Renderer.class.getHitPointsAtHigherLevels(cls.name, cls.hd, hdEntry)}</div>
<div><strong>Hit Points at Higher Levels:</strong> ${Renderer.class.getHitPointsAtHigherLevels(cls.name, cls.hd)}</div>
</td>`);
$ptsToToggle.push($ptHp);
}

View File

@@ -95,10 +95,6 @@ class RaceTraitTag {
traitTags.add("Magic Resistance");
}
if (/\bblindsight\b/i.test(str)) {
traitTags.add("Blindsight");
}
if (/\bsunlight sensitivity\b/i.test(str) || /\bdisadvantage [^.?!]+ direct sunlight\b/i.test(str)) {
traitTags.add("Sunlight Sensitivity");
}
@@ -143,11 +139,24 @@ class RaceLanguageTag {
return outStack;
}
static _LANGUAGES = new Set(["Abyssal", "Aquan", "Auran", "Celestial", "Common", "Draconic", "Dwarvish", "Elvish", "Giant", "Gnomish", "Goblin", "Halfling", "Ignan", "Infernal", "Orc", "Primordial", "Sylvan", "Terran", "Undercommon"]);
static _LANGUAGES = new Set([...Parser.LANGUAGES_STANDARD, ...Parser.LANGUAGES_EXOTIC]);
static _STOPWORDS = new Set(["Almost", "Elven", "Gifted", "It", "Languages", "Many", "Mimicry", "Only", "Or", "The", "Their", "They", "You", "Humans", "Conclave", "Kryta", "Hyperium", "Ithean", "Illyrian", "Speak"]);
static _isCaps (str) { return /^[A-Z]/.test(str); }
static _getTokens (str) {
let tokens = str.split(" ");
for (let i = 0; i < tokens.length; ++i) {
if (tokens[i] === "Deep" && /^Speech\W?$/.test(tokens[i + 1] || "")) {
tokens[i] = [tokens[i], tokens[i + 1]].join(" ");
tokens.splice(i + 1, 1);
}
}
return tokens;
}
static _handleString ({str, outStack, cbWarning, cbError}) {
// Remove the first word of each sentence, as it has non-title-based caps
str = str.trim().replace(/(^\w+|[.?!]\s*\w+)/g, "");
@@ -165,9 +174,9 @@ class RaceLanguageTag {
// Tokenize, removing anything that we don't care about
const reChoose = /^(?:choice|choose|choosing|chosen|chooses|chose)$/;
const tokens = str.split(" ")
const tokens = this._getTokens(str)
// replace all non-word characters (i.e. remove punctuation from tokens)
.map(it => it.replace(/\W/g, "").trim())
.map(it => it.replace(/[^\w ]/g, "").trim())
.filter(t => {
if (!t) return false;

View File

@@ -63,14 +63,26 @@ class PageFilterClassesRaw extends PageFilterClassesBase {
}
static async pPostLoad (data, {...opts} = {}) {
data = MiscUtil.copy(data);
// region Copy data
data = {...data};
const {propsCopied} = opts;
if (!data.class) data.class = [];
else data.class = MiscUtil.copyFast(data.class);
if (data.subclass) data.subclass = MiscUtil.copyFast(data.subclass);
if (propsCopied) {
propsCopied.add("class");
propsCopied.add("subclass");
}
// endregion
// Ensure prerelease/homebrew is initialised
await PrereleaseUtil.pGetBrewProcessed();
await BrewUtil2.pGetBrewProcessed();
if (!data.class) data.class = [];
// Attach subclasses to parent classes
if (data.subclass) {
// Do this sequentially, to avoid double-adding the same base classes
@@ -84,7 +96,7 @@ class PageFilterClassesRaw extends PageFilterClassesBase {
cls = await this._pGetParentClass(sc);
if (cls) {
// If a base class exists, make a stripped-down copy and override its subclasses with our own
cls = MiscUtil.copy(cls);
cls = MiscUtil.copyFast(cls);
cls.subclasses = [];
data.class.push(cls);
} else {
@@ -313,7 +325,7 @@ class PageFilterClassesRaw extends PageFilterClassesBase {
ent.name = name;
ent.source = source;
const entityRoot = raw != null ? MiscUtil.copy(raw) : await DataLoader.pCacheAndGet(`raw_${prop}`, ent.source, ent.hash, {isCopy: true});
const entityRoot = raw != null ? MiscUtil.copyFast(raw) : await DataLoader.pCacheAndGet(`raw_${prop}`, ent.source, ent.hash, {isCopy: true});
const loadedRoot = {
type: prop,
entity: entityRoot,
@@ -362,8 +374,8 @@ class PageFilterClassesRaw extends PageFilterClassesBase {
if (!sideData) return false;
if (sideData.isIgnored) return true;
if (sideData.entries) entity.entries = MiscUtil.copy(sideData.entries);
if (sideData.entryData) entity.entryData = MiscUtil.copy(sideData.entryData);
if (sideData.entries) entity.entries = MiscUtil.copyFast(sideData.entries);
if (sideData.entryData) entity.entryData = MiscUtil.copyFast(sideData.entryData);
return false;
}
@@ -679,7 +691,7 @@ class ModalFilterClasses extends ModalFilter {
if (it.data.ixSubclass == null) out.class = this._filterCache.allData[it.data.ixClass];
else out.subclass = this._filterCache.allData[it.data.ixClass].subclasses[it.data.ixSubclass];
});
resolve(MiscUtil.copy(out));
resolve(MiscUtil.copyFast(out));
doClose(true);
@@ -912,7 +924,7 @@ class ModalFilterClasses extends ModalFilter {
async _pLoadAllData () {
this._pLoadingAllData = this._pLoadingAllData || (async () => {
const [data, prerelease, brew] = await Promise.all([
MiscUtil.copy(await DataUtil.class.loadRawJSON()),
MiscUtil.copyFast(await DataUtil.class.loadRawJSON()),
PrereleaseUtil.pGetBrewProcessed(),
BrewUtil2.pGetBrewProcessed(),
]);
@@ -932,13 +944,13 @@ class ModalFilterClasses extends ModalFilter {
const clsProps = brewUtil.getPageProps({page: UrlUtil.PG_CLASSES});
if (!clsProps.includes("*")) {
clsProps.forEach(prop => data[prop] = [...(data[prop] || []), ...MiscUtil.copy(brew[prop] || [])]);
clsProps.forEach(prop => data[prop] = [...(data[prop] || []), ...MiscUtil.copyFast(brew[prop] || [])]);
return;
}
Object.entries(brew)
.filter(([, brewVal]) => brewVal instanceof Array)
.forEach(([prop, brewArr]) => data[prop] = [...(data[prop] || []), ...MiscUtil.copy(brewArr)]);
.forEach(([prop, brewArr]) => data[prop] = [...(data[prop] || []), ...MiscUtil.copyFast(brewArr)]);
}
_getListItems (pageFilter, cls, clsI) {

View File

@@ -2645,6 +2645,7 @@ Parser.SRC_MPP = "MPP";
Parser.SRC_BMT = "BMT";
Parser.SRC_DMTCRG = "DMTCRG";
Parser.SRC_QftIS = "QftIS";
Parser.SRC_VEoR = "VEoR";
Parser.SRC_GHLoE = "GHLoE";
Parser.SRC_DoDk = "DoDk";
Parser.SRC_HWCS = "HWCS";
@@ -2677,6 +2678,7 @@ Parser.SRC_LK = "LK";
Parser.SRC_CoA = "CoA";
Parser.SRC_PiP = "PiP";
Parser.SRC_DitLCoT = "DitLCoT";
Parser.SRC_VNotEE = "VNotEE";
Parser.SRC_LRDT = "LRDT";
Parser.SRC_AL_PREFIX = "AL";
@@ -2825,6 +2827,7 @@ Parser.SOURCE_JSON_TO_FULL[Parser.SRC_MPP] = "Morte's Planar Parade";
Parser.SOURCE_JSON_TO_FULL[Parser.SRC_BMT] = "The Book of Many Things";
Parser.SOURCE_JSON_TO_FULL[Parser.SRC_DMTCRG] = "The Deck of Many Things: Card Reference Guide";
Parser.SOURCE_JSON_TO_FULL[Parser.SRC_QftIS] = "Quests from the Infinite Staircase";
Parser.SOURCE_JSON_TO_FULL[Parser.SRC_VEoR] = "Vecna: Eve of Ruin";
Parser.SOURCE_JSON_TO_FULL[Parser.SRC_GHLoE] = "Grim Hollow: Lairs of Etharis";
Parser.SOURCE_JSON_TO_FULL[Parser.SRC_DoDk] = "Dungeons of Drakkenheim";
Parser.SOURCE_JSON_TO_FULL[Parser.SRC_HWCS] = "Humblewood Campaign Setting";
@@ -2857,6 +2860,7 @@ Parser.SOURCE_JSON_TO_FULL[Parser.SRC_LK] = "Lightning Keep";
Parser.SOURCE_JSON_TO_FULL[Parser.SRC_CoA] = "Chains of Asmodeus";
Parser.SOURCE_JSON_TO_FULL[Parser.SRC_PiP] = "Peril in Pinebrook";
Parser.SOURCE_JSON_TO_FULL[Parser.SRC_DitLCoT] = "Descent into the Lost Caverns of Tsojcanth";
Parser.SOURCE_JSON_TO_FULL[Parser.SRC_VNotEE] = "Vecna: Nest of the Eldritch Eye";
Parser.SOURCE_JSON_TO_FULL[Parser.SRC_LRDT] = "Red Dragon's Tale: A LEGO Adventure";
Parser.SOURCE_JSON_TO_FULL[Parser.SRC_ALCoS] = `${Parser.AL_PREFIX}Curse of Strahd`;
Parser.SOURCE_JSON_TO_FULL[Parser.SRC_ALEE] = `${Parser.AL_PREFIX}Elemental Evil`;
@@ -2980,6 +2984,7 @@ Parser.SOURCE_JSON_TO_ABV[Parser.SRC_MPP] = "MPP";
Parser.SOURCE_JSON_TO_ABV[Parser.SRC_BMT] = "BMT";
Parser.SOURCE_JSON_TO_ABV[Parser.SRC_DMTCRG] = "DMTCRG";
Parser.SOURCE_JSON_TO_ABV[Parser.SRC_QftIS] = "QftIS";
Parser.SOURCE_JSON_TO_ABV[Parser.SRC_VEoR] = "VEoR";
Parser.SOURCE_JSON_TO_ABV[Parser.SRC_GHLoE] = "GHLoE";
Parser.SOURCE_JSON_TO_ABV[Parser.SRC_DoDk] = "DoDk";
Parser.SOURCE_JSON_TO_ABV[Parser.SRC_HWCS] = "HWCS";
@@ -3012,6 +3017,7 @@ Parser.SOURCE_JSON_TO_ABV[Parser.SRC_LK] = "LK";
Parser.SOURCE_JSON_TO_ABV[Parser.SRC_CoA] = "CoA";
Parser.SOURCE_JSON_TO_ABV[Parser.SRC_PiP] = "PiP";
Parser.SOURCE_JSON_TO_ABV[Parser.SRC_DitLCoT] = "DitLCoT";
Parser.SOURCE_JSON_TO_ABV[Parser.SRC_VNotEE] = "VNotEE";
Parser.SOURCE_JSON_TO_ABV[Parser.SRC_LRDT] = "LRDT";
Parser.SOURCE_JSON_TO_ABV[Parser.SRC_ALCoS] = "ALCoS";
Parser.SOURCE_JSON_TO_ABV[Parser.SRC_ALEE] = "ALEE";
@@ -3134,6 +3140,7 @@ Parser.SOURCE_JSON_TO_DATE[Parser.SRC_MPP] = "2023-10-17";
Parser.SOURCE_JSON_TO_DATE[Parser.SRC_BMT] = "2023-11-14";
Parser.SOURCE_JSON_TO_DATE[Parser.SRC_DMTCRG] = "2023-11-14";
Parser.SOURCE_JSON_TO_DATE[Parser.SRC_QftIS] = "2024-07-16";
Parser.SOURCE_JSON_TO_DATE[Parser.SRC_VEoR] = "2024-05-21";
Parser.SOURCE_JSON_TO_DATE[Parser.SRC_GHLoE] = "2023-11-30";
Parser.SOURCE_JSON_TO_DATE[Parser.SRC_DoDk] = "2023-12-21";
Parser.SOURCE_JSON_TO_DATE[Parser.SRC_HWCS] = "2019-06-17";
@@ -3166,6 +3173,7 @@ Parser.SOURCE_JSON_TO_DATE[Parser.SRC_LK] = "2023-09-26";
Parser.SOURCE_JSON_TO_DATE[Parser.SRC_CoA] = "2023-10-30";
Parser.SOURCE_JSON_TO_DATE[Parser.SRC_PiP] = "2023-11-20";
Parser.SOURCE_JSON_TO_DATE[Parser.SRC_DitLCoT] = "2024-03-26";
Parser.SOURCE_JSON_TO_DATE[Parser.SRC_VNotEE] = "2024-04-16";
Parser.SOURCE_JSON_TO_DATE[Parser.SRC_LRDT] = "2024-04-01";
Parser.SOURCE_JSON_TO_DATE[Parser.SRC_ALCoS] = "2016-03-15";
Parser.SOURCE_JSON_TO_DATE[Parser.SRC_ALEE] = "2015-04-07";
@@ -3185,6 +3193,7 @@ Parser.SOURCE_JSON_TO_DATE[Parser.SRC_MCV4EC] = "2023-09-21";
Parser.SOURCE_JSON_TO_DATE[Parser.SRC_MisMV1] = "2023-05-03";
Parser.SOURCE_JSON_TO_DATE[Parser.SRC_AATM] = "2023-10-17";
// region Source categories
Parser.SOURCES_ADVENTURES = new Set([
Parser.SRC_LMoP,
Parser.SRC_HotDQ,
@@ -3264,6 +3273,7 @@ Parser.SOURCES_ADVENTURES = new Set([
Parser.SRC_CoA,
Parser.SRC_PiP,
Parser.SRC_DitLCoT,
Parser.SRC_VNotEE,
Parser.SRC_LRDT,
Parser.SRC_HFStCM,
Parser.SRC_GHLoE,
@@ -3336,7 +3346,11 @@ Parser.SOURCES_PARTNERED_WOTC = new Set([
Parser.SRC_TD,
Parser.SRC_LRDT,
]);
// region Source categories
Parser.SOURCES_LEGACY_WOTC = new Set([
Parser.SRC_EEPC,
Parser.SRC_VGM,
Parser.SRC_MTF,
]);
// An opinionated set of source that could be considered "core-core"
Parser.SOURCES_VANILLA = new Set([
@@ -3564,6 +3578,8 @@ Parser.SOURCES_AVAILABLE_DOCS_ADVENTURE = {};
Parser.SRC_HWAitW,
Parser.SRC_QftIS,
Parser.SRC_LRDT,
Parser.SRC_VEoR,
Parser.SRC_VNotEE,
].forEach(src => {
Parser.SOURCES_AVAILABLE_DOCS_ADVENTURE[src] = src;
Parser.SOURCES_AVAILABLE_DOCS_ADVENTURE[src.toLowerCase()] = src;

View File

@@ -399,7 +399,7 @@ class RendererMarkdown {
this._recursiveRender(by, tempStack, meta);
if (i < len - 1) tempStack[0] += "\n";
}
textStack[0] += `\u2014 ${tempStack.join("")}${entry.from ? `, *${entry.from}*` : ""}`;
textStack[0] += `\u2014 ${tempStack.join("")}${entry.from ? `, *${this.render(entry.from)}*` : ""}`;
}
}

View File

@@ -1172,7 +1172,7 @@ globalThis.Renderer = function () {
if (i < len - 1) tempStack[0] += "<br>";
}
}
textStack[0] += `<span class="rd__quote-by">\u2014 ${byArr ? tempStack.join("") : ""}${byArr && entry.from ? `, ` : ""}${entry.from ? `<i>${entry.from}</i>` : ""}</span>`;
textStack[0] += `<span class="rd__quote-by">\u2014 ${byArr ? tempStack.join("") : ""}${byArr && entry.from ? `, ` : ""}${entry.from ? `<i>${this.render(entry.from)}</i>` : ""}</span>`;
textStack[0] += `</p>`;
}
@@ -1251,7 +1251,21 @@ globalThis.Renderer = function () {
this._renderDice = function (entry, textStack, meta, options) {
const pluginResults = this._applyPlugins_getAll("dice", {textStack, meta, options}, {input: entry});
textStack[0] += Renderer.getEntryDice(entry, entry.name, {isAddHandlers: this._isAddHandlers, pluginResults});
for (const res of pluginResults) {
if (res.rendered) {
textStack[0] += res.rendered;
return;
}
}
const toDisplay = Renderer.getEntryDiceDisplayText(entry);
if (entry.rollable === true) {
textStack[0] += Renderer.getRollableEntryDice(entry, entry.name, toDisplay, {isAddHandlers: this._isAddHandlers, pluginResults});
return;
}
textStack[0] += toDisplay;
};
this._renderActions = function (entry, textStack, meta, options) {
@@ -2386,13 +2400,6 @@ Renderer._splitByPipeBase = function (leadingCharacter) {
Renderer.splitTagByPipe = Renderer._splitByPipeBase("@");
Renderer.getEntryDice = function (entry, name, opts = {}) {
const toDisplay = Renderer.getEntryDiceDisplayText(entry);
if (entry.rollable === true) return Renderer.getRollableEntryDice(entry, name, toDisplay, opts);
else return toDisplay;
};
Renderer.getRollableEntryDice = function (
entry,
name,
@@ -3685,7 +3692,12 @@ Renderer.utils = class {
if ((!fauxEntry.displayText && (rollText || "").includes("#$")) || (fauxEntry.displayText && fauxEntry.displayText.includes("#$"))) fauxEntry.displayText = (fauxEntry.displayText || rollText).replace(/#\$prompt_number[^$]*\$#/g, "(n)");
fauxEntry.displayText = fauxEntry.displayText || fauxEntry.toRoll;
if (tag === "@damage") fauxEntry.subType = "damage";
if (tag === "@damage") {
fauxEntry.subType = "damage";
const [damageType] = others;
if (damageType) fauxEntry.damageType = damageType;
}
if (tag === "@autodice") fauxEntry.autoRoll = true;
return fauxEntry;
@@ -4439,13 +4451,13 @@ Renderer.tag = class {
}
};
static TagHit = class extends this._TagBaseAt {
static TagHitText = class extends this._TagBaseAt {
tagName = "h";
_getStripped (tag, text) { return "Hit: "; }
};
static TagMiss = class extends this._TagBaseAt {
static TagMissText = class extends this._TagBaseAt {
tagName = "m";
_getStripped (tag, text) { return "Miss: "; }
@@ -4525,43 +4537,43 @@ Renderer.tag = class {
}
};
static TaChance = class extends this._TagDiceFlavor {
static TagChance = class extends this._TagDiceFlavor {
tagName = "chance";
};
static TaD20 = class extends this._TagDiceFlavor {
static TagD20 = class extends this._TagDiceFlavor {
tagName = "d20";
};
static TaDamage = class extends this._TagDiceFlavor {
static TagDamage = class extends this._TagDiceFlavor {
tagName = "damage";
};
static TaDice = class extends this._TagDiceFlavor {
static TagDice = class extends this._TagDiceFlavor {
tagName = "dice";
};
static TaAutodice = class extends this._TagDiceFlavor {
static TagAutodice = class extends this._TagDiceFlavor {
tagName = "autodice";
};
static TaHit = class extends this._TagDiceFlavor {
static TagHit = class extends this._TagDiceFlavor {
tagName = "hit";
};
static TaRecharge = class extends this._TagDiceFlavor {
static TagRecharge = class extends this._TagDiceFlavor {
tagName = "recharge";
};
static TaAbility = class extends this._TagDiceFlavor {
static TagAbility = class extends this._TagDiceFlavor {
tagName = "ability";
};
static TaSavingThrow = class extends this._TagDiceFlavor {
static TagSavingThrow = class extends this._TagDiceFlavor {
tagName = "savingThrow";
};
static TaSkillCheck = class extends this._TagDiceFlavor {
static TagSkillCheck = class extends this._TagDiceFlavor {
tagName = "skillCheck";
};
@@ -4970,8 +4982,8 @@ Renderer.tag = class {
new this.TagUnit(),
new this.TagHit(),
new this.TagMiss(),
new this.TagHitText(),
new this.TagMissText(),
new this.TagAtk(),
@@ -4981,16 +4993,16 @@ Renderer.tag = class {
new this.TagDcYourSpellSave(),
new this.TaChance(),
new this.TaD20(),
new this.TaDamage(),
new this.TaDice(),
new this.TaAutodice(),
new this.TaHit(),
new this.TaRecharge(),
new this.TaAbility(),
new this.TaSavingThrow(),
new this.TaSkillCheck(),
new this.TagChance(),
new this.TagD20(),
new this.TagDamage(),
new this.TagDice(),
new this.TagAutodice(),
new this.TagHit(),
new this.TagRecharge(),
new this.TagAbility(),
new this.TagSavingThrow(),
new this.TagSkillCheck(),
new this.TagScaledice(),
new this.TagScaledamage(),
@@ -5370,9 +5382,13 @@ Renderer.class = class {
return Renderer.hover.getGenericCompactRenderedString(clsEntry);
}
static getHitDiceEntry (clsHd) { return clsHd ? {toRoll: `${clsHd.number}d${clsHd.faces}`, rollable: true} : null; }
static getHitDiceEntry (clsHd) { return clsHd ? `{@dice ${clsHd.number}d${clsHd.faces}||Hit die}` : null; }
static getHitPointsAtFirstLevel (clsHd) { return clsHd ? `${clsHd.number * clsHd.faces} + your Constitution modifier` : null; }
static getHitPointsAtHigherLevels (className, clsHd, hdEntry) { return className && clsHd && hdEntry ? `${Renderer.getEntryDice(hdEntry, "Hit die")} (or ${((clsHd.number * clsHd.faces) / 2 + 1)}) + your Constitution modifier per ${className} level after 1st` : null; }
static getHitPointsAtHigherLevels (className, clsHd) {
return className && clsHd
? `${Renderer.get().render(Renderer.class.getHitDiceEntry(clsHd))} (or ${((clsHd.number * clsHd.faces) / 2 + 1)}) + your Constitution modifier per ${className} level after 1st`
: null;
}
static getRenderedArmorProfs (armorProfs) { return armorProfs.map(a => Renderer.get().render(a.full ? a.full : a === "light" || a === "medium" || a === "heavy" ? `{@filter ${a} armor|items|type=${a} armor}` : a)).join(", "); }
static getRenderedWeaponProfs (weaponProfs) { return weaponProfs.map(w => Renderer.get().render(w === "simple" || w === "martial" ? `{@filter ${w} weapons|items|type=${w} weapon}` : w.optional ? `<span class="help help--hover" title="Optional Proficiency">${w.proficiency}</span>` : w)).join(", "); }
@@ -6529,11 +6545,6 @@ Renderer.race = class {
if (cpySr.name) {
cpy._subraceName = cpySr.name;
if (cpySr.alias) {
cpy.alias = cpySr.alias.map(it => Renderer.race.getSubraceName(cpy.name, it));
delete cpySr.alias;
}
cpy.name = Renderer.race.getSubraceName(cpy.name, cpySr.name);
delete cpySr.name;
}
@@ -9533,6 +9544,7 @@ Renderer.variantrule = class {
static getCompactRenderedString (rule) {
const cpy = MiscUtil.copyFast(rule);
delete cpy.name;
if (cpy.entries && cpy.ruleType) cpy.entries.unshift(`{@i ${Parser.ruleTypeToFull(cpy.ruleType)} Rule}`);
return `
${Renderer.utils.getExcludedTr({entity: rule, dataProp: "variantrule", page: UrlUtil.PG_VARIANTRULES})}
${Renderer.utils.getNameTr(rule, {page: UrlUtil.PG_VARIANTRULES})}

View File

@@ -1886,7 +1886,7 @@ class DataLoader {
*/
static async pCacheAndGet (page, source, hash, {isCopy = false, isRequired = false, isSilent = false, lockToken2} = {}) {
const fromCache = this.getFromCache(page, source, hash, {isCopy, _isReturnSentinel: true});
if (fromCache === _DataLoaderConst.ENTITY_NULL) return null;
if (fromCache === _DataLoaderConst.ENTITY_NULL) return this._getVerifiedRequiredEntity({pageClean: page, sourceClean: source, hashClean: hash, ent: null, isRequired});
if (fromCache) return fromCache;
const {page: pageClean, source: sourceClean, hash: hashClean} = _DataLoaderInternalUtil.getCleanPageSourceHash({page, source, hash});

View File

@@ -117,6 +117,13 @@ PropOrder._ArrayKey = class {
}
};
PropOrder._PROPS_FOUNDRY_DATA = [
"foundrySystem",
"foundryFlags",
"foundryEffects",
"foundryImg",
];
PropOrder._META = [
"sources",
@@ -472,10 +479,7 @@ PropOrder._SPELL = [
"fluff",
"foundrySystem",
"foundryFlags",
"foundryEffects",
"foundryImg",
...PropOrder._PROPS_FOUNDRY_DATA,
new PropOrder._ObjectKey("roll20Spell", {
order: PropOrder._ROLL20_SPELL,
@@ -793,6 +797,17 @@ PropOrder._SUBCLASS__COPY_MOD = [
"_",
...PropOrder._SUBCLASS,
];
PropOrder._SUBCLASS_FLUFF = [
"name",
"source",
"className",
"classSource",
"_copy",
"entries",
"images",
];
PropOrder._FOUNDRY_SUBCLASS = [
"name",
"source",
@@ -973,6 +988,9 @@ PropOrder._LANGUAGE = [
];
PropOrder._LANGUAGE_SCRIPT = [
"name",
"source",
"fonts",
];
PropOrder._NAME = [
@@ -1017,10 +1035,7 @@ PropOrder._DISEASE = [
"entries",
"foundrySystem",
"foundryFlags",
"foundryEffects",
"foundryImg",
...PropOrder._PROPS_FOUNDRY_DATA,
];
PropOrder._STATUS = [
"name",
@@ -1173,10 +1188,7 @@ PropOrder._FEAT = [
"fluff",
"foundrySystem",
"foundryFlags",
"foundryEffects",
"foundryImg",
...PropOrder._PROPS_FOUNDRY_DATA,
];
PropOrder._FEAT__COPY_MOD = [
"*",
@@ -1450,10 +1462,7 @@ PropOrder._ITEM = [
"fluff",
"foundryType",
"foundrySystem",
"foundryFlags",
"foundryEffects",
"foundryImg",
...PropOrder._PROPS_FOUNDRY_DATA,
];
PropOrder._ITEM__COPY_MOD = [
"*",
@@ -1586,10 +1595,7 @@ PropOrder._OPTIONALFEATURE = [
"fluff",
"foundrySystem",
"foundryFlags",
"foundryEffects",
"foundryImg",
...PropOrder._PROPS_FOUNDRY_DATA,
];
PropOrder._OPTIONALFEATURE__COPY_MOD = [
"*",
@@ -1626,6 +1632,8 @@ PropOrder._REWARD = [
"hasFluffImages",
"fluff",
...PropOrder._PROPS_FOUNDRY_DATA,
];
PropOrder._VARIANTRULE = [
"name",
@@ -1694,10 +1702,7 @@ PropOrder._RACE_SUBRACE = [
"fluff",
"foundrySystem",
"foundryFlags",
"foundryEffects",
"foundryImg",
...PropOrder._PROPS_FOUNDRY_DATA,
new PropOrder._ArrayKey("_versions", {
fnGetOrder: () => [
@@ -2029,8 +2034,10 @@ PropOrder._PROP_TO_LIST = {
"background": PropOrder._BACKGROUND,
"legendaryGroup": PropOrder._LEGENDARY_GROUP,
"class": PropOrder._CLASS,
"classFluff": PropOrder._GENERIC_FLUFF,
"foundryClass": PropOrder._FOUNDRY_CLASS,
"subclass": PropOrder._SUBCLASS,
"subclassFluff": PropOrder._SUBCLASS_FLUFF,
"foundrySubclass": PropOrder._FOUNDRY_SUBCLASS,
"classFeature": PropOrder._CLASS_FEATURE,
"subclassFeature": PropOrder._SUBCLASS_FEATURE,

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.204.0"/* 5ETOOLS_VERSION__CLOSE */;
globalThis.VERSION_NUMBER = /* 5ETOOLS_VERSION__OPEN */"1.205.0"/* 5ETOOLS_VERSION__CLOSE */;
globalThis.DEPLOYED_IMG_ROOT = undefined;
// for the roll20 script to set
globalThis.IS_VTT = false;
@@ -55,6 +55,7 @@ globalThis.VeCt = {
URL_BREW: `https://github.com/TheGiddyLimit/homebrew`,
URL_ROOT_BREW: `https://raw.githubusercontent.com/TheGiddyLimit/homebrew/master/`, // N.b. must end with a slash
URL_ROOT_BREW_IMG: `https://raw.githubusercontent.com/TheGiddyLimit/homebrew-img/main/`, // N.b. must end with a slash
URL_PRERELEASE: `https://github.com/TheGiddyLimit/unearthed-arcana`,
URL_ROOT_PRERELEASE: `https://raw.githubusercontent.com/TheGiddyLimit/unearthed-arcana/master/`, // As above
@@ -506,7 +507,7 @@ globalThis.SourceUtil = class {
static isLegacySourceWotc (source) {
if (source == null) return false;
return source === Parser.SRC_VGM || source === Parser.SRC_MTF;
return Parser.SOURCES_LEGACY_WOTC.has(source);
}
// TODO(Future) remove this in favor of simply checking existence in `PrereleaseUtil`
@@ -2975,6 +2976,7 @@ UrlUtil.URL_TO_HASH_BUILDER["skill"] = UrlUtil.URL_TO_HASH_GENERIC;
UrlUtil.URL_TO_HASH_BUILDER["sense"] = UrlUtil.URL_TO_HASH_GENERIC;
UrlUtil.URL_TO_HASH_BUILDER["raceFeature"] = (it) => UrlUtil.encodeArrayForHash(it.name, it.raceName, it.raceSource, it.source);
UrlUtil.URL_TO_HASH_BUILDER["citation"] = UrlUtil.URL_TO_HASH_GENERIC;
UrlUtil.URL_TO_HASH_BUILDER["languageScript"] = UrlUtil.URL_TO_HASH_GENERIC;
// Add lowercase aliases
Object.keys(UrlUtil.URL_TO_HASH_BUILDER)
@@ -5449,13 +5451,13 @@ globalThis.DataUtil = {
// region Populate fonts, based on script
const scriptLookup = {};
(rawData.languageScript || []).forEach(script => scriptLookup[script.name] = script);
(rawData.languageScript || []).forEach(script => MiscUtil.set(scriptLookup, script.source, script.name, script));
const out = {language: MiscUtil.copyFast(rawData.language)};
out.language.forEach(lang => {
if (!lang.script || lang.fonts === false) return;
const script = scriptLookup[lang.script];
const script = MiscUtil.get(scriptLookup, lang.source, lang.script);
if (!script) return;
lang._fonts = [...script.fonts];

View File

@@ -26,18 +26,24 @@ function getFileProbeTarget (path) {
};
}
function getProbeTarget (imgEntry, {localBrewDir = null, isAllowExternal = false}) {
function getProbeTarget (imgEntry, {localBrewDir = null, localBrewDirImg = null, isAllowExternal = false}) {
if (imgEntry.href.type === "internal") {
return getFileProbeTarget(`img/${imgEntry.href.path}`);
}
if (imgEntry.href.type === "external") {
if (localBrewDir && imgEntry.href.url.startsWith(`${VeCt.URL_ROOT_BREW}`)) {
if (localBrewDir && imgEntry.href.url.startsWith(VeCt.URL_ROOT_BREW)) {
return getFileProbeTarget(
decodeURI(imgEntry.href.url.replace(`${VeCt.URL_ROOT_BREW}_img`, `${localBrewDir}/_img`)),
);
}
if (localBrewDirImg && imgEntry.href.url.startsWith(VeCt.URL_ROOT_BREW_IMG)) {
return getFileProbeTarget(
decodeURI(imgEntry.href.url.replace(VeCt.URL_ROOT_BREW_IMG, `${localBrewDirImg}/`)),
);
}
if (!isAllowExternal) { // Local files are not truly "external"
console.warn(`Skipping "external" image (URL was "${imgEntry.href.url}"); run with the "--allow-external" option if you wish to probe external URLs.`);
return null;
@@ -59,8 +65,8 @@ function getImageEntries (imageEntries, obj) {
return obj;
}
async function pMutImageDimensions (imgEntry, {localBrewDir = null, isAllowExternal = false}) {
const probeMeta = getProbeTarget(imgEntry, {localBrewDir, isAllowExternal});
async function pMutImageDimensions (imgEntry, {localBrewDir = null, localBrewDirImg = null, isAllowExternal = false}) {
const probeMeta = getProbeTarget(imgEntry, {localBrewDir, localBrewDirImg, isAllowExternal});
if (probeMeta == null) return;
const {target, location, fnCleanup} = probeMeta;
@@ -80,6 +86,7 @@ async function main (
dirs,
files,
localBrewDir = null,
localBrewDirImg = null,
isAllowExternal = false,
},
) {
@@ -106,7 +113,7 @@ async function main (
.map(async () => {
while (imageEntries.length) {
const imageEntry = imageEntries.pop();
await pMutImageDimensions(imageEntry, {localBrewDir, isAllowExternal});
await pMutImageDimensions(imageEntry, {localBrewDir, localBrewDirImg, isAllowExternal});
}
}),
);
@@ -122,6 +129,7 @@ const program = new Command()
.option("--dir <dir...>", `Input directories`)
.option("--allow-external", "Allow external URLs to be probed.")
.option("--local-brew-dir <localBrewDir>", "Use local homebrew directory for relevant URLs.")
.option("--local-brew-dir-img <localBrewDirImg>", "Use local homebrew-img directory for relevant URLs.")
;
program.parse(process.argv);
@@ -142,6 +150,7 @@ main({
dirs,
files,
localBrewDir: params.localBrewDir,
localBrewDirImg: params.localBrewDirImg,
isAllowExternal: params.allowExternal,
})
.catch(e => console.error(e));

View File

@@ -265,9 +265,11 @@ function getFnListSort (prop) {
case "card":
return SortUtil.ascSortCard.bind(SortUtil);
case "class":
case "classFluff":
case "foundryClass":
return (a, b) => SortUtil.ascSortDateString(Parser.sourceJsonToDate(b.source), Parser.sourceJsonToDate(a.source)) || SortUtil.ascSortLower(a.name, b.name) || SortUtil.ascSortLower(a.source, b.source);
case "subclass":
case "subclassFluff":
case "foundrySubclass":
return (a, b) => SortUtil.ascSortDateString(Parser.sourceJsonToDate(b.source), Parser.sourceJsonToDate(a.source)) || SortUtil.ascSortLower(a.name, b.name);
case "classFeature":

18
package-lock.json generated
View File

@@ -1,15 +1,15 @@
{
"name": "5etools",
"version": "1.204.0",
"version": "1.205.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "5etools",
"version": "1.204.0",
"version": "1.205.0",
"license": "MIT",
"devDependencies": {
"5etools-utils": "^0.10.21",
"5etools-utils": "^0.10.23",
"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.21",
"resolved": "https://registry.npmjs.org/5etools-utils/-/5etools-utils-0.10.21.tgz",
"integrity": "sha512-m5Oroj31XkXa2S7eMATDxZ7nuo77uy12eAI+4HMzmxNNZiDNp1v/I4TAWmrDUb0jGX+iIv8O6qMDQHTc6k8cYw==",
"version": "0.10.23",
"resolved": "https://registry.npmjs.org/5etools-utils/-/5etools-utils-0.10.23.tgz",
"integrity": "sha512-3vPWruCllFMQRkUm5C1GOLmccorQuljFYnR47jTk1vB4sR43m2hINAgiWmHWCk2SNq/3H4VJQUqgB81anR4HAQ==",
"dev": true,
"dependencies": {
"ajv": "^8.12.0",
@@ -14589,9 +14589,9 @@
"dev": true
},
"5etools-utils": {
"version": "0.10.21",
"resolved": "https://registry.npmjs.org/5etools-utils/-/5etools-utils-0.10.21.tgz",
"integrity": "sha512-m5Oroj31XkXa2S7eMATDxZ7nuo77uy12eAI+4HMzmxNNZiDNp1v/I4TAWmrDUb0jGX+iIv8O6qMDQHTc6k8cYw==",
"version": "0.10.23",
"resolved": "https://registry.npmjs.org/5etools-utils/-/5etools-utils-0.10.23.tgz",
"integrity": "sha512-3vPWruCllFMQRkUm5C1GOLmccorQuljFYnR47jTk1vB4sR43m2hINAgiWmHWCk2SNq/3H4VJQUqgB81anR4HAQ==",
"dev": true,
"requires": {
"ajv": "^8.12.0",

View File

@@ -1,7 +1,7 @@
{
"name": "5etools",
"author": "TheGiddyLimit",
"version": "1.204.0",
"version": "1.205.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.21",
"5etools-utils": "^0.10.23",
"ajv": "^8.12.0",
"ajv-formats": "^2.1.1",
"commander": "^12.0.0",

View File

@@ -85,6 +85,7 @@
$rgb-source-HFFotM: #7b702c;
$rgb-source-BMT: #694165;
$rgb-source-QftIS: #6191ae;
$rgb-source-VEoR: #b74c62;
$rgb-source-GHLoE: #c07e4e;
$rgb-source-DoDk: #825494;
$rgb-source-HWCS: #d0914b;
@@ -459,6 +460,11 @@
@include mix-source-color($rgb-source-QftIS);
}
&VEoR,
&VNotEE {
@include mix-source-color($rgb-source-VEoR);
}
&GHLoE {
@include mix-source-color($rgb-source-GHLoE);
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long