Fix random value generator, new CLI

This commit fixes a defect in the random value generator introduced when
the random_sets module was refactored. It also introduces three useful
CLI options, for generating roll tables of random trinkets, wild magic
surges, and psychadelic effects.
This commit is contained in:
evilchili 2024-01-16 22:05:06 -08:00
parent 2b3e82114f
commit 1c0bd7357d
7 changed files with 456 additions and 222 deletions

View File

@ -13,7 +13,7 @@ Clone the repo and install the virtual env:
Invoke the CLI: Invoke the CLI:
``` ```
% poetry run roll-table example.yaml % poetry run roll-table custom example.yaml
┏━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━┓ ┏━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━┓
┃ Roll ┃ Rarity ┃ Color ┃ Notes ┃ ┃ Roll ┃ Rarity ┃ Color ┃ Notes ┃
┡━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━┩ ┡━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━┩

View File

@ -2,7 +2,6 @@ from rolltable.types import RollTable
import typer import typer
from enum import Enum from enum import Enum
from rich import print from rich import print
from rich.table import Table
from pathlib import Path from pathlib import Path
from typing import List from typing import List
@ -16,8 +15,35 @@ class OUTPUT_FORMATS(Enum):
markdown = 'markdown' markdown = 'markdown'
@app.command("roll-table") @app.command("psychadelic-effects")
def create( def psycheffects():
"""
Generate a roll table of psychadelic effects.
"""
rt = RollTable([(Path(__file__).parent / 'sources' / 'psychadelic_effects.yaml').read_text()])
print(rt.as_table())
@app.command("trinkets")
def trinkets():
"""
Generate a roll table of random junk.
"""
rt = RollTable([(Path(__file__).parent / 'sources' / 'trinkets.yaml').read_text()])
print(rt.as_table())
@app.command("wild-magic")
def wildmagic():
"""
Generate a wild magic surge table.
"""
rt = RollTable([(Path(__file__).parent / 'sources' / 'wild_magic.yaml').read_text()])
print(rt.as_table())
@app.command("custom")
def custom(
sources: List[Path] = typer.Argument( sources: List[Path] = typer.Argument(
..., ...,
help="Path to one or more yaml-formatted source file."), help="Path to one or more yaml-formatted source file."),
@ -43,7 +69,7 @@ def create(
) )
): ):
""" """
CLI for creating roll tables. Create roll tables from custom sources.
""" """
rt = RollTable([Path(s).read_text() for s in sources], frequency=frequency, die=die, hide_rolls=hide_rolls) rt = RollTable([Path(s).read_text() for s in sources], frequency=frequency, die=die, hide_rolls=hide_rolls)

View File

@ -0,0 +1,40 @@
metadata:
headers:
- Effect
- Name
- Description
- Mechanic
frequencies:
default:
Effect: 1
Effect:
- Elation:
- You are consumed with love for all things
- Charmed by all creatures, +2 to AC
- Paranoia:
- Everyone is out to get you. Everyone. What are they hiding?
- Automatically succeed all insight checks vs deception; no one is your ally
- Baked:
- Everything is hilarious, and you are always hungry for snacks.
- Disadvantage on all attack rolls, Advantage on saves against psychic effects.
- Wasted:
- Your speech is slurred, you have difficulty thinking clearly, and have mild vertigo
- You cannot use reactions; disadvantage on all checks and saves using DEX. +2 to AC.
- Sleepy:
- Why do we have to keep doing things? Doing things is hard.
- Your movement speed is reduced by 10ft and you cannot use reactions. Advantage on saves against psychic effects.
- Horror:
- You have stared into the abyss of your soul and found only blackness.
- Frightened by all creatures. Disadvantage on all saves against psychic effects.
- Mania:
- You are possessed by feverish mania; ideas and words tumble out of as quickly as possible. You cannot stop them.
- Automatic critical success on all perception checks; advantage on DEX saves; disadvantage on all attack rolls.
- Rage:
- You are seething with anger at the injustice of your situation. Everything is a personal attack, a challengge.
- You are raged.
- Cosmic Awareness:
- Your mind expands to contain the cosmos, which fills you with a wonder you cannot convey. Whenever you speak it is in metaphor.
- Once per day, you can decide something about the world is true and it will become so.
- Ego-Death:
- Your sense of self, your identity, is revealed as an empty construct. You are all things and all things are you.
- Whenever an ally takes damage, you take 1 point of psychic damage. Whenever an ally heals, you regain 1 HP.

View File

@ -0,0 +1,213 @@
metadata:
headers:
- Trinket
frequencies:
default:
Trinket: 1
Trinket:
- A carved ivory mimic.
- A carved marble elephant.
- A small round cactus with two eyes.
- A pocket book of dwarven poetry.
- A bronze box containing a tiny wooden owl.
- A solid blue metal sphere, one inch in diameter, with three parallel grooves around the circumference.
- A pouch containing ten dried peas.
- A ceramic puzzle cube, with each face divided into four independently rotating squares enameled with astronomical signs.
- A sheet of vellum on which is crudely painted a herbal plant that you have yet to identify.
- A petrified frog.
- A twenty-sided die.
- A cut yellow chrysanthemum that never dies.
- A palm-sized iron cage; the door doesn't shut properly, as the tiny lock was broken from the inside.
- A blob of grey goo, slippy but safe to touch, kept in a ceramic pot.
- A glowing blue-green line, six inches long, but with no discernible radius.
- A pretty conch shell.
- A scrap of paper on which is written, in Goblin, "My dearest Bess,".
- A keychain holding the head of a broken key.
- An echo pearl from the depths of the Vibration Lake.
- A toy crossbow.
- A fossil of an extinct many-limbed critter.
- A brass prosthetic nose.
- A corkscrew.
- A dried poison gland of a jagged fish.
- A bronze gear on which is etched the word "Moon".
- A map of a labyrinth, on which is penciled a line that starts at the centre but fails to connect to the entrance.
- A cube of ice that never melts.
- A square of ironsilk sewn by the geargrubs of ancient Siclari.
- An ivory knitting needle.
- A peacock feather.
- A travel set of paints; someone has used up all the black.
- A wig of short platinum-blonde hair.
- A child's charm bracelet.
- A small bar of orichalcum, a metal only mentioned in ancient literature.
- A deed to a tower.
- An invitation to a formal ball to be held 1129 years ago.
- A smoking pipe carved from granite.
- A vial of scented oil.
- A preserved basilisk eye.
- An intricate knot that nobody seems to know how to tie or untie sand sailors believe it to be bad luck.
- Pages ripped out of an accounting journal of a local merchant.
- A ring with a poison reservoir for slipping into drinks and a tiny razor edge for cutting purse-strings.
- A glass globe of swirling green goop.
- A portfolio of pressed flowers, none of which still exist.
- A small handbook of foreign coins, for travelers to identify denominations.
- A slightly out of date guidebook to foreign inns, taverns, and transportation.
- Two false fingernails painted with mysterious symbols.
- A set of cosmetic tools for cleaning the ears.
- A harmless stage dagger with retracting blade and blood-compartment.
- A floating glass orb that follows you around and makes whirring sounds.
- A key that can lock any door, but unlock none.
- A translucent coin.
- A bronze ring engraved with dark symbols.
- A thimble on which is an enamel painting of a turtle.
- A puzzle box that cannot be solved.
- A pair of badly worn hairdressing scissors.
- A wax hand shaped to hold a large cup.
- A measuring tape, marked in ink at 23 inches.
- A seashell that is silent when held up to your ear.
- One piece of unknown paper currency with no obvious denomination.
- A bootlace entwined with gold thread.
- A dented sheriff's badge.
- A belt buckle.
- A letter of complaint to a toy shop owner written in high elvish.
- A decorative leather stud.
- A penny whistle that plays the same note no matter which holes are covered.
- A ticket admitting an adult and child onto a thing called a "semiotic tram".
- A small glass vial holding three eyelashes.
- A tub of petrified putty.
- A leather shoe made for a dog.
- A doll head with no hair and poorly applied makeup.
- A pewter spork.
- Illustrated instructions on how to make a paper hat.
- A clear glass dish with four round notches around the outside edge.
- A wire circlet that bestows upon its wearer perfect posture.
- A small hand-sized box covered with numbered buttons.
- An empty whiskey tumbler that causes any liquid poured into it to become bourbon.
- A hunk of metal which appears to be several gears jammed together at unnatural and impossible angles; attempting to turn it causes it to emit a horrible shrieking sound.
- A crystal prism that refracts shadow instead of light.
- A smokeless and odorless candle.
- A flat disc of layered metal and prismatic glass with a hole in the centre.
- An ornate pewter tankard made without a bottom.
- A wooden device designed to be gripped in two hands; two levers protrude from the top, and two triggers from the underside.
- Two perfectly identical pine cones.
- A sponge that can absorb 60 gallons of ale (and only ale).
- A pepper grinder containing an unlimited supply of pepper unless opened, at which point it becomes half empty.
- A poorly cultivated bonsai juniper in a glazed ceramic pot.
- An oval-shaped soapstone tablet inscribed with a short list of religious prohibitions.
- A wooden doll with a door that opens to reveal a slightly smaller, identical, doll; this one is empty, perhaps there are still smaller dolls that are missing?
- A stone figure of a snake that changes positions after every full moon.
- A silver key of unknown origins on a leather cord as a pendant that emits strange magical energy.
- The right half of a broken bronze circlet with a light leafy pattern that when placed on your head, stays in place as though the other half was still there.
- A small silver rod which when rolled between your hands emits sounds as though a lute were being played by a master softly nearby.
- A music box that can only be heard by someone who has wound it at least once.
- A small stone cube that, when tapped with a rod of metal, looks as though it were made of that metal for a few seconds.
- A wooden sphere with a white marking that always faces the sun, and a black marking that always faces the moon.
- An opaque jar that cannot be opened or broken, no matter how hard you try.
- A journal that details the great adventures of a hero you have never heard of, complete with vivid descriptions of nonsensical creatures and terms, all written in messy handwriting, but with impressive diction.
- A tiny sack that, when opened, is full of sand, but feels as heavy as a large stone when lifted.
- A puppet in the likeness of someone you distantly know, that echoes your movements when you (and only you) place it on the ground.
- An iron rod that bends in unusual ways when you look directly at it, but rights itself when you look away.
- A book that perfectly records the holder's dreams when held while sleeping.
- A tin pot that is just the right size for you to wear as a helmet.
- A marble sculpture of a tiny elf holding a lute seated on a chair that plays music every so often.
- A piece of pure white cloth that never gets dirty.
- A hat that never gets wet.
- A mask that copies its wearer's facial expressions.
- A humanoid skull with ethereal green orbs in its eye sockets.
- A wineskin that only holds wine. Any other liquid pours out after a few seconds.
- A leather belt that, when worn, glows with faint blue light.
- A flask that freezes any liquids stored in it at midnight, and unfreezes them at noon.
- A book with words that change every time it is read.
- A cube with tiny animals wandering on each face that change when they cross onto a different face.
- A mummified hand that wiggles when pointed at the ground.
- A terribly written novel whose plot seems to match events in the reader's life.
- A crown made out of ice that never melts.
- A piece of string that always emits smoke, like the wick of a snuffed candle.
- A rod of indeterminate metal that changes its length at random every other day.
- A stone carved to look like a head that occasionally speaks, asking questions that change every time.
- A petrified wooden stick that glows brightly when held by an undead creature.
- A flute that, when played, makes the sound of a random instrument, though never a flute.
- A copper ring that weighs literally nothing but remains precisely where it is placed.
- A towel embroidered with high elivsh writing which reads "In case of mind flayer, wear towel."
- A set of brass wind chimes that chime when still and are silent when moving.
- A grotesque statuette of a humanoid with rat-like features.
- A handbook of etiquette for nobles. Its contents can only be remembered for 6 seconds.
- A rolled parchment depicting a translation key for a ficticious language.
- A wooden plank that refuses to burn.
- A detailed guide on the anatomy of rocs.
- A compass that points towards the nearest bottle of rum.
- A stone that weighs 5 pounds but floats in liquid.
- A diagram of a forest on an island with no named artist.
- A brass locket with a strange rune carved inside.
- A green coat with numerous pockets, each with a separate piece of a moustache trimming kit inside.
- A vial of tree sap.
- A rod of metal that produces tiny sparks from a red marking when a black button on the other end is pressed.
- A wooden cane that, when placed on the ground, stands perfectly straight, and cannot be tipped over.
- A feather with a piece of red string tied on the end of the shaft.
- A book that you faintly remember from your childhood that you thought was lost for many years.
- A wooden training sword sized for a child.
- A perfectly normal brown leather glove that fits either hand.
- A golden statue of a beautiful maiden. It glows faintly in the presence of money.
- A gear that refuses to mesh with any other.
- A suit of armour made from oak wood, sized for a doll, painted in vivid red and black.
- A jug that turns liquids stored in it into goat's milk after a few minutes.
- A waxy white flower in a tiny pot that constantly moves as though in a light breeze.
- A robe with a tag on the inside that reads "to my dearest pupil" in draconic.
- A beige jar of red ointment without a label.
- A calligrapher's brush.
- A small cylinder of stone that smells faintly of blood.
- A tunic that smells of the sea.
- A curious looking pair of goggles with the words "Property of Ice! DON'T TOUCH!" scrawled into the side.
- A small blue cube that gives off a faint orange glow.
- A small orb with water and a small living jellyfish inside.
- A cat figurine.
- A hat that has a secret pocket on the inside.
- A bar of scented soap.
- A scrap of parchment with the phrase "I aint'nt dead" written on it in Common.
- An unusually sharp spoon.
- An unusually small humanoid skull.
- A bit of rock from a headstone.
- A tiny bag of purple powder.
- A box of black licorice.
- A strangely shaped bone.
- A sealed, corked bottle with a single globule of blood that never spreads or dries.
- A detailed guide to making pickled foods.
- A palm-sized coin with the carved image of a dragon's head on each side.
- A miniature shield.
- A sea shell with a strange rune carved into it.
- An arm band in the shape of a snake, with emeralds for eyes.
- An old coin, showing a hare on one side and the moon on the reverse.
- A bird carved out of lapis lazuli.
- A small pouch filled with various animal teeth.
- A tiny painting showing a vulture carrying a bone in its beak.
- An ingot of copper with an unusual hue.
- A small notebook full of drawings of food.
- A drawing of a flower with open petals that only appear open when viewed from an angle.
- A perfectly flat wheel made from terracotta.
- The journal of a philosopher, full of wise sayings and anecdotes.
- A malevolent-looking raven skull that has been charred black.
- A small glass case containing several glossy butterfly wings.
- A small glass box containing an undead frog.
- A guide to changing bowstrings written in draconic but full of grammatical errors.
- A ceramic troll statuette with no arms, just legs.
- A sliver ring that feels very slippery.
- An ornately carved figurine of a storm giant made from bone.
- A fork.
- A shoe made from crystal.
- A small wooden box of vibrantly-coloured powders, each colour in its own tiny drawer.
- A silver hand mirror with a dragon etched onto the back.
- A miniature porcelain teapot.
- A bejeweled statuette of a dragonborne knight.
- A stone carving of a piece of bacon.
- A horn that has been cut cleanly in half.
- A small clay pig.
- A small metal can containing a non-Newtonian black fluid that sparks when touched.
- A necklace strung with tiny beetles.
- A fist-sized green seed covered in brown spines.
- A fist-sized metal frog.
- A glass figurine in the shape of a giant ant.
- A petrified wooden spoon.
- A jeweled goblet that never spills.
- A bowl covered in ornate designs depicting hill giants in combat with dwarves.
- A set of ceramic castles that have a space for a candle in the middle. When the candle is lit the castle looks like it has lights on in the windows.
- A purple amulet in the shape of a frog's head.
- A vine covered in thorns that writhes occasionally.

View File

@ -0,0 +1,137 @@
# Wild Magic Table
metadata:
headers:
- Type
- Effect
- Notes
frequencies:
default:
Boon: 0.20
Bane: 0.20
Weird: 0.55
Trouble: 0.05
boon:
Boon: 0.65
Bane: 0.15
Weird: 0.15
Trouble: 0.05
bane:
Boon: 0.15
Bane: 0.65
Weird: 0.15
Trouble: 0.05
weird:
Boon: 0.05
Bane: 0.05
Weird: 0.85
Trouble: 0.05
trouble:
Boon: 0.1
Bane: 0.2
Weird: 0.3
Trouble: 0.4
Boon:
- A +1 martial weapon appears in an unoccupied space within 15ft of you: The weapon disappears if it is carried out of the Sahwat desert.
- Barkskin is cast on you: 1 minute duration.
- Stoneskin is cast on you: 1 minute duration.
- Invisibility is cast on you: 1 minute duration.
- You gain 1d4 temporary hit points:
- The Enlarge spell is cast on you: 1 minute duration.
- Echos of a celestial choir fills the air. Allies within 30ft of you gains 2d4+4 HP: Good-aligned creatures only.
- You are hasted.: 1 minute duration.
- You have truesight: 1 minute duration.
- A third eye opens in your forehead: Curse. Advantage on perception checks.
- Levitate is cast on you: 1 minute duration.
- Your next spell attack does max damage:
- You have resistance to nonmagical damage: 1 minute duration.
- You have resistance to magical damage: 1 minute duration.
- You have advantage on ability checks and saves against spells and magical effects: 1 minute duration
- d100 gold pieces appear in an unoccupied space within 30ft of you:
- You regain one 1st level spell slot: No effect if you do not have 1st level slots.
- Mirror Image is cast on you: 1 minute duration.
- No effect: If the caster dies in the next minute, they are resurrected with 1 HP
- You smell like apple pie: Every creature within 15ft of you must succeed on a DC 13 CHA save or be charmed by you for 1d4 rounds.
- Whenever you hit with a spell attack, it does an additional 1d6 radiant damage: 1d4 rounds
Bane:
- Magic Missile is cast at first level, targeting random creatures 60ft of you: One dart always hits the caster.
- Color spray is cast on a random creature within 30ft of you: 1 minute duration.
- The Reduce spell is cast on you: 1 minute duration.
- The call lightning spell appears centered on a random point 60ft away from you: Lasts for 1d4 rounds.
- A Fireball spell detonates centered on a random point 30ft away from you:
- 1d6 random creatures within 30ft of you must succeed on a Wisdom save or fall unconscious: Lasts until they take damage or someone spends an action to wake them up.
- A 50ft radius sphere of purple gas appears centered on a random point 25ft from you: The sphere lasts 1d4 rounds. Any creature which ends their turn in the sphere takes 1d10 poison damage.
- Infernal whispers fill the air. All creatures within 100ft of you must succeed on a DC14 Wisdom saving throw or take 1d6 psychic damage:
- You are slowed: 1 minute duration.
- Spectral shackles appear on the legs of each creature within 60ft of you: Speed reduced to 5ft. Lasts 1 round. Can be dispelled.
- You are vulnerable to nonmagical damage: 1 minute duration.
- You are vulnerable to magical damage: 1 minute duration.
- You have disadvantage on ability checks and saves against spells and magical effects: 1 minute duration
- You expend one additional 1st level spell slot: No effect if you do not have 1st level slots.
- Mirror Image is cast on hostile creature you can see: 1 minute duration.
- Your skin glows like a small sun. Every creature that can see you is blinded: Until the start of next turn.
- You use all your remaining movement to move in a random direction:
Weird:
- Your skin turns bright blue: Curse.
- It begins snowing from a small cloud 10ft directly over you: Duration 1d4 days. Can be dispelled.
- If it is daytime, you believes it is night. If it is night, you believe it is daytime: Curse. Duration 1d4 days.
- A Zone of Truth spell aura extends from you to a range of 300ft: Duration one hour. Can be dispelled.
- You hear every sound magnified 10x. You are weak to thunder damage: Curse. Duration 1d4 hours.
- Whatever you are holding in your left hand turns into a sticky bun: Duration 1 minute. Can be dispelled.
- You are wearing a hat shaped like your favourite animal:
- If you were alive, you are now undead. If you were undead, you are now alive: Curse.
- A surprised 3rd level wizard appears in an unoccupied space within 15ft of you: random alignment; will disappear again in 1d4 rounds.
- A planar portal opens in the fabric of spacetime in an unoccupied space within 30ft of you. 1d4 modrons appear and begin repairing it.: Disappears after 3 rounds.
- Dispel magic is cast at 4th level on you and every creature within 60ft of you:
- A random trinket falls from the sky: Roll on the trinkets table
- Desert flowers appear in the hair of a random creature within 30ft of you:
- You no longer cast a shadow: Curse. At dawn, shadow attacks as a wraith.
- You swap places with a random creature within 60ft:
- A random creature within 30ft of you is polymorphed into a bowl of petunias: Duration 1 round. 1 HP, AC 10.
- You smell like ham: Curse. Duration 1d4 days.
- A pillar of stone 15ft in diameter and 30ft tall erupts from the ground centered on you:
- A pig appears in the nearest unoccupied space: Immune to all damage. Disappears after 1d4 rounds.
- 1d4 stone statues appear depicting you at key moments in your life:
- A CR 15+ creature apperas 120ft away from you: Major Illusion.
- All plant life within 300ft of you dies:
- All plant life within 300ft of you doubles in size:
- A humanoid man appears and attacks you with 20lb fish: +6 to hit, 1d10 bludgeoning damage. Disappears afer attack.
- 1d8 ghostly figures appear, observing: Duration 1d4 rounds.
- Your body is covered in fur: Curse.
- You are teleported to an unoccupied space 15ft away:
- You age increases by 5d10 years: Can be reversed by Greater Restoration.
- 50lbs of cabbages appear stacked in a pyramid in an unoccupied space: You hear a faint voice yelling, "My cabbages!"
- You are immune to the effects of alcohol: Curse.
- A floating eyeball 1ft in diameter follows you: Disappears after 1d6 days.
- You can only shout when speaking: 1 minute duration.
- Your arms are polymorphed into tentacles: Cannot cast somatic spells. 1 minute duration.
- You are frightened of your favourite color: 1 hour duration.
- You are surrounded by magical silence: 10 minute duration.
- You are encased in a 10ft diameter sphere of glass: AC 10, 15 HP. Vulnerable to bludgeoning.
- A plant within 30ft of you sprouts limbs and runs away: walking speed 80ft.
- Invisibility is cast on every creature you can see: 1 minute duration.
- Any item held by a creature flies from its grip and lands in an unoccupied space 10ft away:
- Illusory butterflies flutter in 5ft radius about you: 1 minute duration.
- You generate a Draining Aura in a 10ft radius: Any creature ending its turn in the aura takes 1d4 necrotic damage. You heal this amount. Duration 1 minute.
- Levitate is cast on 1d4 random creatures within 120ft of you: 1 minute duration.
- No effect: Next phrase spoken by caster becomes true.
- Disguise Self is cast on you: You look like whoever you're most afraid of in life.
- Each creature that can see you must succeed on a DC 13 WIS save or laugh uncontrollably: Until the end of their next turn.
Trouble:
- A random creature within 60ft of you (including you) is banished: Duration 1 minute.
- The Resilient Sphere spell is cast on a random creature (including you) within 30ft of you:
- 1d6 zombies merge from the ground in random unoccupied spaces within 100ft of you:
- A swarm of beetles appears in your space:
- A pit 20ft deep and 5 feet wide appears directly beneath you: 2d6 fall damage.
- You swap current HP with a random creature within 30ft of you:
- 2d8 imps are summoned from the Nine Hells: Will attack closest creature. Disappear after 1d4 rounds.
- Nothing happens: Next time you speak, you breathe fire in a straight line 30ft long and 5ft wide. DEX 13 save or take 2d6 fire damage.
- Gravity reverses in a 30ft radius: Until start of next turn. Can be dispelled.
- Your left eye is a ruby worth 300 GP: You can see alignment. Eye restored with Greater Restoration.
- A random metal weapon bursts in a 60ft cloud of metallic shards: DC 13 DEX or 3d6 piercing damage.
- You sweat blood from every pore: CON 14 or your max HP is reduced by 1d4 per round.
- A random creature within 30ft becomes a pillar of sand: Effect ends if a drop of water touches the pillar.
- A random PC suffers from a random lingering injury: DMG pp272
- 1d6 random spaces within 100ft of you becomes lava 5ft deep: 10d10 Fire damage. Roll 2d10 for X/Y coords.
- Every creature and object within 120ft of you moves 10ft in a straight line towards you: Any object or creature that collides with something else takes 1d4 bludgeoning damage.
- You hear ethereal music: Lasts until dawn. You cannot long rest during this time.
- 10ft tall crystal spikes erupt from the ground in 1d10 spaces within 100ft of you: Any creature in a space must succeed on a DC 15 DEX save or take 3d6 piercing damage and be restrained.

View File

@ -56,14 +56,27 @@ class RollTable:
@property @property
def _values(self) -> List: def _values(self) -> List:
"""
For each data source, select N random values, where N is the size of the die.
we then zip those random values so that each member of the generated list
contains one value from each data source. So if _data is:
[
['axe', 'shortsword', 'dagger'],
['fire', 'ice', 'poison'],
]
and the die is 2, the resulting generated values might be:
[
['axe', 'fire'],
['dagger', 'ice'],
]
"""
if not self._generated_values: if not self._generated_values:
ds_values = [t.random_values(self.die) for t in self._data] self._generated_values = list(zip(*[
self._generated_values = [] t.random_values(self.die) for t in self._data
for face in range(self._die): ]))
value = []
for index, ds in enumerate(ds_values):
value += ds_values[index][face]
self._generated_values.append(value)
return self._generated_values return self._generated_values
@property @property

View File

@ -1,217 +1,22 @@
import pytest import pytest
from rolltable import types from pathlib import Path
from rolltable.types import RollTable
fixture_metadata = """
metadata:
headers:
- Header 1
- Header 2
- Header 3
die: 10
frequencies:
default:
Option 1: 0.3
Option 2: 0.5
Option 3: 0.2
nondefault:
Option 1: 0.0
Option 2: 0.1
Option 3: 0.9
"""
fixture_source = """
Option 1:
- choice 1: description 1
- choice 2: description 2
- choice 3: description 3
Option 2:
- choice 1: description 4
- choice 2: description 5
- choice 3: description 6
Option 3:
- choice 1: description 7
- choice 2: description 8
- choice 3: description 9
"""
fixture_one_choice = """
option 1:
- choice 1: description 1
"""
fixture_repeated_choices = """
option 1:
- choice 1: description 1
- choice 1: description 1
- choice 1: description 1
"""
fixture_no_descriptions = """
metadata:
headers:
- option
- choice
option 1:
- choice 1
"""
fixture_combined_A = """
A1:
- A choice 1
- A choice 2
- A choice 3
A2:
- A choice 4
- A choice 5
- A choice 6
A3:
- A choice 7
- A choice 8
- A choice 9
"""
fixture_combined_B = """
metadata:
headers:
- HeaderB
- HeaderB_Choice
B1:
- B choice 1
B2:
- B choice 2
B3:
- B choice 3
"""
fixture_no_options = """
metadata:
headers:
- headerA
- headerB
B1:
B2:
B3:
"""
fixture_lists_and_dicts = ["""
#
# category one two three four
# Category foo bar baz quz
#
metadata:
headers:
- category
- one
- two
- three
- four
list:
- foo:
- bar
- baz
- quz
""", """
#
# category one two three four
# Category foo bar baz quz
#
metadata:
headers:
- category
- one
- two
- three
- four
dict:
foo:
- bar
- baz
- quz
bar:
- a
- b
- c
"""]
@pytest.mark.parametrize('fixture', fixture_lists_and_dicts) sources = Path(__file__).parent / '..' / 'rolltable' / 'sources'
def test_lists_and_dicts(fixture):
t = types.RollTable([fixture], die=1) flat_list = (sources / 'trinkets.yaml').read_text()
assert(str(t)) dict_of_dicts = (sources / 'wild_magic.yaml').read_text()
dict_of_lists = (sources / 'psychadelic_effects.yaml').read_text()
def test_combined_tables(): @pytest.mark.parametrize('data, expected', [
combined = types.RollTable([fixture_combined_A, fixture_combined_B], die=6) ([dict_of_dicts], ['d1000 ', 'A third eye', 'Advantage on perception checks']),
assert str(combined) ([flat_list], ['d1000 ', 'ivory mimic']),
([dict_of_lists], ['d1000', 'Cosmic', 'mind expands', 'it will become so']),
def test_table_end_to_end():
assert str(types.RollTable([fixture_source]))
def test_table_end_to_end_with_metadata():
assert str(types.RollTable([fixture_metadata + fixture_source]))
def test_table_frequency():
t = types.RollTable([fixture_metadata + fixture_source], frequency='nondefault')
assert t._data[0].frequencies['Option 1'] == 0.0
assert t._data[0].frequencies['Option 2'] == 0.1
assert t._data[0].frequencies['Option 3'] == 0.9
def test_one_option():
t = types.RollTable([fixture_one_choice], die=1)
assert t._values == [['option 1', 'choice 1', 'description 1']]
def test_collapsed():
t = types.RollTable([fixture_repeated_choices], die=6)
assert len(list(t.rows)) == 2 # (+1 for headers)
def test_not_collapsed():
t = types.RollTable([fixture_repeated_choices], die=6)
assert len(list(t.expanded_rows)) == 7 # (+1 for headers)
def test_no_descriptions():
t = types.RollTable([fixture_no_descriptions], die=1)
assert 'd1' in str(t)
assert 'option 1' in str(t)
def test_no_options():
t = types.RollTable([fixture_no_options])
assert str(t)
@pytest.mark.parametrize('table', [
types.RollTable([fixture_no_options]),
types.RollTable([fixture_one_choice]),
types.RollTable([fixture_metadata + fixture_source]),
types.RollTable([fixture_source]),
]) ])
def test_yaml(table): def test_flat(data, expected):
assert table.as_yaml() rt = RollTable(data, die=1000)
for txt in expected:
assert txt in str(rt)
def test_text():
assert repr(types.RollTable([fixture_no_options]))
assert repr(types.RollTable([fixture_one_choice]))
assert repr(types.RollTable([fixture_metadata + fixture_source]))
assert repr(types.RollTable([fixture_source]))
@pytest.mark.parametrize('table', [
types.RollTable([fixture_no_options]),
types.RollTable([fixture_one_choice]),
types.RollTable([fixture_metadata + fixture_source]),
types.RollTable([fixture_source]),
types.RollTable([fixture_no_options]),
types.RollTable([fixture_lists_and_dicts]),
])
def test_as_dict(table):
for src in table.datasources:
assert src.as_dict()