"""Generate random gemstones for D&D 5e It always bothered me that the tables in the DMG won't let you randomly generate a diamond worth 50gp, or 300gp, or 500gp, when these are all common spell components. This script generates random gemstones and values. It considers that you might have a small diamond worth 50gp, but probably not a giant azurite worth 5000gp. First, we get a base and a multiplier. A base could be e.g. 10, 100, 1000. A multiplier is any integer between 1 and 15. These choices are weighted, and you can adjust the weights if you wish. The total value of the gem is base * multiplier, so the most common gem should be 10 * 1 or 10gp, and the rarest would be the maximum at 15,000gp. You can also add additional bases, like 50gp, or change the range of multipliers, etc. Then we use this value to generate a type of gemstone. Each collection in the DMG is listed, with a couple of additions based on costly spell components (e.g. it must be possible to have an agate worth 1000gp since Awaken requires it). Each group is also given a maximum of 10x its typical value, e.g. you are not likely to find a carnelian worth more than 500gp. Gemstones do not have a minimum value, so it is possible to generate a 10gp diamond. After all, gems can always be cut down. Certain types of gemstone are more common than others, which picking the type of gem, there is a COMMON_CHANCE * 100% chance that you'll get one of the common types, which are, themselves, weighted. All types of gemstones are equally weighted within their groups. These weights can be changed. Run this script on the command line with the -h flag to see arguments. """ import argparse import random COMMON_CHANCE = 0.4 common_gems = [ (5, 'diamond'), (5, 'ruby'), (2, 'jade'), (2, 'blue sapphire'), (1, 'emerald'), (1, 'opal'), ] gp10 = [ (1, 'azurite'), (1, 'banded agate'), (1, 'blue quartz'), (1, 'eye agate'), (1, 'hematite'), (1, 'lapis lazuli'), (1, 'malachite'), (1, 'moss agate'), (1, 'obsidian'), (1, 'rhodochrosite'), (1, 'tiger eye'), (1, 'turquoise'), ] gp50 = [ (1, 'bloodstone'), (1, 'carnelian'), (1, 'chalcedony'), (1, 'chrysoprase'), (1, 'citrine'), (1, 'jasper'), (1, 'moonstone'), (1, 'onyx'), (1, 'quartz'), (1, 'sardonyx'), (1, 'star rose quartz'), (1, 'zircon'), ] gp100 = [ (1, 'amber'), (1, 'amethyst'), (1, 'black onyx'), (1, 'chrysoberyl'), (1, 'coral'), (1, 'garnet'), (1, 'jade'), (1, 'jet'), (1, 'pearl'), (1, 'spinel'), (1, 'tourmaline'), ] gp500 = [ (1, 'alexandrite'), (1, 'aquamarine'), (1, 'black pearl'), (1, 'blue spinel'), (1, 'peridot'), (1, 'topaz'), ] gp1000 = [ (1, 'agate'), (1, 'black opal'), (1, 'blue sapphire'), (1, 'emerald'), (1, 'fire opal'), (1, 'opal'), (1, 'star ruby'), (1, 'star sapphire'), (1, 'yellow sapphire'), ] gp5000 = [ (1, 'black sapphire'), (1, 'diamond'), (1, 'jacinth'), (1, 'ruby'), ] max_values = { 100: gp10, 500: gp50, 1000: gp100, 5000: gp500, 10000: gp1000, 50000: gp5000, } bases_relative = ( (60, 10), (35, 100), (5, 1000), ) multipliers_relative = ( (15, 1), (6, 2), (5, 3), (4, 4), (12, 5), (4, 6), (3, 7), (3, 8), (2, 9), (10, 10), (1, 11), (1, 12), (1, 13), (1, 14), (1, 15), ) def absolute_weights(weights, max_value=None): result = {} total = 0 for w, v in weights: if max_value is not None and v > max_value: continue total += w result[total] = v return result def get_weighted_value(relative, max_value=None): weighted = absolute_weights(relative, max_value) k = random.randint(1, max(k for k in weighted.keys())) for m, v in weighted.items(): if k < m: return v return v def get_base(max_base=None): return get_weighted_value(bases_relative, max_value=max_base) def get_multiplier(max_m=None): return get_weighted_value(multipliers_relative, max_value=max_m) def get_possible_gems(value): gems = [] for v, g in max_values.items(): if value < v: gems += g return gems def pick_gem(gems): if random.random() < COMMON_CHANCE: return get_weighted_value(common_gems) return get_weighted_value(gems) def get_random_gem(max_base=None, max_multiplier=None): base = get_base(max_base) multiplier = get_multiplier(max_multiplier) value = base * multiplier gems = get_possible_gems(value) type_ = pick_gem(gems) return { 'type': type_, 'value': value, } def gem_phrase(gem): article = 'an' if gem['type'][0] in 'aeiou' else 'a' return '{} {} worth {}gp'.format(article, gem['type'], gem['value']) def main(): parser = argparse.ArgumentParser() parser.add_argument('-b', '--max-base', type=int, default=None) parser.add_argument('-m', '--max-multiplier', type=int, default=None) parser.add_argument('-n', '--count', type=int, default=1) options = parser.parse_args() max_base = options.max_base max_multiplier = options.max_multiplier for _ in range(options.count): gem = get_random_gem(max_base, max_multiplier) print(gem_phrase(gem)) __main__ = main if __name__ == '__main__': main()