You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

267 lines
15 KiB

import math
import random
import dice
DUNGEON_WANDERING_MONSTER_LEVEL = {
# 1 2 3 4 5 6 7 8 9 0 1 2
1: [1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 3],
2: [1, 1, 1, 2, 2, 2, 2, 2, 2, 3, 3, 4],
3: [1, 2, 2, 3, 3, 3, 3, 3, 3, 4, 4, 5],
4: [2, 3, 3, 4, 4, 4, 4, 4, 4, 5, 5, 6],
5: [3, 4, 4, 5, 5, 5, 5, 5, 5, 6, 6, 6],
6: [4, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6],
}
DUNGEON_RANDOM_MONSTERS_BY_LEVEL = {
1: [
("Goblin", "2d4"), ("Kobold", "4d4"), ("Morlock", "1d12"),
("Orc", "2d4"), ("Beetle, Fire", "1d8"), ("Centipede, Giant", "2d4"),
("Ferret, Giant", "1d8"), ("Rat, Giant", "3d6"), ("Men, Brigand", "2d4"),
("Skeleton", "3d4"), ("Stirge", "1d10"), ("NPC Party (Lvl 1)", "1d4+2")
],
2: [
("Gnoll", "1d6"), ("Hobgoblin", "1d6"), ("Lizardman", "2d4"),
("Troglodyte", "1d8"), ("Bat, Giant", "1d10"),
("Fly, Giant Carnivorous", "1d8"), ("Locust, Cavern", "1d10"),
("Snake, Pit Viper", "1d8"), ("Ghoul", "1d6"), ("Men, Berserker", "1d6"),
("Zombie", "2d4"), ("NPC Party (Lvl 2)", "1d4+2")
],
3: [
("Bugbear", "2d4"), ("Lycanthrope, Werewolf", "1d6"), ("Ogre", "1d6"),
("Throghrin", "1d6"), ("Ant, Giant", "2d4"), ("Lizard, Drago", "1d3"),
("Scorpion, Giant", "1d6"), ("Wolf, Dire", "1d4"),
("Carcass Scavenger", "1d3"), ("Gargoyle", "1d6"), ("Wight", "1d6"),
("NPC Party (Lvl 4)", "1d4+2")
],
4: [
("Lycanthrope, Werebear", "1d4"), ("Lycanthrope, Weretiger", "1d4"),
("Minotaur", "1d6"), ("Boar, Giant", "1d4"), ("Owl Bear", "1d4"),
("Phase Tiger", "1d4"), ("Rhagodessa, Giant", "1d4"),
("Snake, Giant Python", "1d4"), ("Cockatrice", "1d4"), ("Medusa", "1d3"),
("Wraith", "1d4"), ("NPC Party (Lvl 5)", "1d4+2")
],
5: [
("Ettin", "1d2"), ("Giant, Hill", "1d4"), ("Giant, Stone", "1d2"),
("Troll", "1d8"), ("Ankheg", "1d6"), ("Caecilian", "1d3"),
("Basilisk", "1d6"), ("Hell Hound, Greater", "2d4"),
("Salamander, Flame", "1d4+1"), ("Spectre", "1d4"), ("Wyvern", "1d2"),
("NPC Party (Lvl 8)", "1d4+3")
],
6: [
("Cyclops", "1`"), ("Giant, Cloud", "1d2"), ("Purple Worm", "1d2"),
("Demon Boar", "1d4"), ("Dragon (20 HD)", "1"), ("Hydra (12 HD)", "1"),
("Gorgon", "1d2"), ("Lamia", "1"), ("Remorhaz (15 HD)", "1"),
("Skittering Maw", "1"), ("Vampire (9 HD)", "1d4"),
("NPC Party (Lvl 14)", "1d4+3")
]
}
WILDERNESS_TYPE_BY_ID = {
1: ["Clear", "Grass", "Scrub"],
2: ["Woods"],
3: ["River"],
4: ["Swamp"],
5: ["Mountains", "Hills"],
6: ["Barren", "Desert"],
7: ["Inhabited"],
8: ["City"],
9: ["Ocean"],
10: ["Jungle"],
}
WILDERNESS_FREQUENCY_BY_TERRAIN = {
1: 6, # Clear, Grass, Scrub
2: 5, # Woods
3: 5, # River
4: 4, # Swamp
5: 4, # Mountains, Hills
6: 4, # Barren, Desert
7: 6, # Inhabited
8: 6, # City
9: 5, # Ocean
10: 4, # Jungle
}
WILDERNESS_ENCOUNTER_BY_TERRAIN = {
1: ["Men", "Flyer", "Humanoid", "Animal", "Animal", "Unusual", "Dragon", "Insect"],
2: ["Men", "Flyer", "Humanoid", "Insect", "Unusual", "Animal", "Animal", "Dragon"],
3: ["Men", "Flyer", "Humanoid", "Insect", "Swimmer", "Swimmer", "Animal", "Dragon"],
4: ["Men", "Flyer", "Humanoid", "Swimmer", "Undead", "Undead", "Insect", "Dragon"],
5: ["Men", "Flyer", "Humanoid", "Unusual", "Animal", "Humanoid", "Dragon", "Flyer"],
6: ["Men", "Flyer", "Humanoid", "Humanoid", "Animanl", "Dragon", "Undead", "Unusual"],
7: ["Men", "Flyer", "Humanoid", "Men", "Men", "Insect", "Animal", "Dragon"],
8: ["Men", "Undead", "Humanoid", "Men", "Men", "Men", "Men", "Men", "Men", "Men"],
9: ["Men", "Flyer", "Insect", "Insect", "Humanoid", "Animal", "Animal", "Dragon"],
10: ["Men", "Flyer", "Insect", "Insect", "Humanoid", "Animal", "Animal", "Dragon"],
}
WILDERNESS_ENCOUNTER_SUBTABLES = {
"Men": {
"Clear, Grass, Scrub": ["Brigand", "Noble", "Mage", "Fighter", "Thief", "Cleric", "Nomad", "Thief", "NPC Party", "Merchant", "Berserker", "Venturer"],
"Woods": ["Brigand", "Thief", "NPC Party", "Merchant", "Berserker", "Brigand", "Cleric", "Mage", "Fighter", "Thief", "Brigand", "NPC Party"],
"River": ["Brigand", "Thief", "NPC Party", "Merchant", "Buccaneer", "Buccaneer", "Cleric", "Mage", "Fighter", "Merchant", "Buccaneer", "NPC Party"],
"Swamp": ["Brigand", "Thief", "NPC Party", "NPC Party", "Merchant", "Cleric", "Venturer", "Berserker", "Fighter", "Mage", "NPC Party", "Thief"],
"Barrens, Mountains, Hills": ["Brigand", "Thief", "NPC Party", "Venturer", "Berserker", "NPC Party", "Cleric", "Mage", "Fighter", "Brigand", "NPC Party", "Barbarian"],
"Desert": ["Nomad", "Nomad", "NPC Party", "Merchant", "Nomad", "Nomad", "Cleric", "Mage", "Fighter", "Noble", "Nomad", "Nomad"],
"Inhabited": ["Thief", "Venturer", "NPC Party", "NPC Party", "Merchant", "Fighter", "Merchant", "Fighter", "Mage", "Cleric", "Cleric", "Noble"],
"City": ["Thief", "Venterer", "NPC Party", "NPC Party", "Merchant", "Fighter", "Merchant", "Fighter", "Mage", "Cleric", "Cleric", "Noble"],
"Ocean": ["Buccaneer", "Pirate", "Merchant", "NPC Party", "Pirate", "Merchant", "Merchant", "Merchant", "Buccaneer", "Pirate", "Merchant", "Pirate"],
"Jungle": ["Brigand", "Venturer", "Thief", "NPC Party", "Cleric", "Fighter", "Medium", "Berserker", "Brigand", "Barbarian", "NPC Party", "Brigand"],
},
"Humanoid": {
"Clear, Grass, Scrub": ["Bugbear", "Elf", "Gnoll", "Goblin", "Halfling", "Hobgoblin", "Kobold", "Ogre", "Orc", "Pixie", "Throghrin", "Troll"],
"Woods": ["Bugbear", "Cyclops", "Dryad", "Elf", "Giant, Hill", "Gnoll", "Goblin", "Hobgoblin", "Ogre", "Orc", "Pixie", "Troll"],
"River": ["Bugbear", "Elf", "Gnoll", "Hobgoblin", "Lizard Man", "Lizard Man", "Naiad", "Naiad", "Ogre", "Orc", "Sprite", "Troll"],
"Swamp": ["Gnoll", "Goblin", "Hobgoblin", "Lizard Man", "Lizard Man", "Lizard Man", "Naiad", "Ogre", "Orc", "Troglodyte", "Troll", "Troll"],
"Hills, Mountain": ["Dwarf", "Giant, Cloud", "Giant, Frost", "Giant, Hill", "Giant, Stone", "Giant, Storm", "Goblin", "Kobold", "Ogre", "Orc", "Troglodyte", "Troll"],
"Barrens": ["Bugbear", "Giant, Hill", "Goblin", "Gnoll", "Hobgoblin", "Hobgoblin", "Ogre", "Orc", "Orc", "Throghrin", "Troll"],
"Desert": ["Bugbear", "Gnoll", "Giant, Fire", "Hobgoblin", "Hobgoblin", "Minotaur", "Ogre", "Ogre", "Orc", "Orc", "Troll", "Troll"],
"City": ["Doppelganger", "Dwarf", "Elf", "Gnome", "Halfling", "Pixie", "Sprite", "Werebear", "Wereboar", "Wererat", "Weretiger", "Werewolf"],
"Inhabited": ["Doppelganger", "Dwarf", "Elf", "Gnome", "Goblin", "Halfling", "Kobold", "Ogre", "Orc", "Pixie", "Sprite", "Wererat"],
"Jungle": ["Bugbear", "Cyclops", "Elf", "Giant, Fire", "Giant, Hill", "Gnoll", "Goblin", "Lizard Man", "Ogre", "Orc", "Troglodyte", "Troll"],
},
"Animal": {
"Clear, Grass, Scrub": ["Herd Animal (Antelope)", "Boar*", "Cat, Lion", "Cat, Panther", "Elephant", "Hawk*", "Horse, Light", "Lizard, Tuatara", "Mule (Donkey)", "Snake, Pit Viper", "Snake, Giant Rattler", "Weasel, Giant"],
"Woods": ["Herd Animal (Antelope)", "Bat*", "Bear, Grizzly", "Boar*", "Cat, Panther", "Hawk*", "Owl*", "Snake, Pit Viper", "Spider, Black Widow", "Unicorn", "Wolf", "Wolf, Dire"],
"River": ["Herd Animal (Antelope)", "Bear, Black", "Boar*", "Cat, Panther", "Crab, Giant", "Crocodile*", "Leech, Giant", "Piranha, Giant", "Rat*", "Shrew, Giant", "Swan", "Toad, Giant"],
"Hills": ["Herd Animal (Antelope)", "Bear, Grizzly", "Boar*", "Cat, Mountain Lion", "Eagle*", "Hawk*", "Horse, Light", "Herd Animal (Sheep)", "Snake, Pit Viper", "Owl*", "Wolf", "Wolf, Dire"],
"Mountains": ["Herd Animal (Antelope)", "Bear, Cave", "Cat, Mountain Lion", "Eagle*", "Herd Animal (Goat)", "Hawk*", "Mule (Donkey)", "Baboon, Rock", "Snake, Pit Viper", "Snake, Giant Rattler", "Wolf", "Wolf, Dire"],
"Barrens": ["Herd Animal (Antelope)", "Bear, Cave", "Cat, Mountain Lion", "Eagle*", "Goat, Wild", "Hawk*", "Rock Babooon", "Snake, Pit Viper", "Snake, Giant Rattler", "Spider, Crab", "Wolf, Dire", "Vulture"],
"Desert": ["Herd Animal (Antelope)", "Herd Animal (Antelope)", "Camel", "Camel", "Cat, Lion", "Hawk*", "Lizard, Gecko", "Lizard, Tuatara", "Snake, Giant Rattler", "Wolf (Wild Dog)", "Wolf, Dire", "Vulture"],
"Inhabited": ["Herd Animal (Antelope)", "Boar*", "Cat, Panther", "Lizard, Draco", "Lizard, Gecko", "Lizard, Horned", "Monkey", "Shrew, Giant", "Snake, Pit Viper", "Snake, Python", "Snake, Spitting Cobra", "Spider, Crab"],
"Jungle": ["Goat", "Boar*", "Dog", "Ferret, Giant", "Hawk*", "Horse, Light", "Mule (Donkey)", "Rat*", "Snake, Pit Viper", "Herd Animal (Sheep)", "Weasel, Giant", "Wolf"],
"Other": ["Bear, Cave", "Cat, Sabre-Tooth Tiger", "Crocodile, Giant", "Mastodon", "Pteranodon", "Rhino, Woolly", "Snake, Python", "Stegosaurus", "Titanothere", "Triceratops", "Tyrannosaurus Rex", "Wolf, Dire"],
"*": "Roll 1d6: 1-4 Normal or Swarm, 5-6 Giant"
},
"Flyer": {
"Barrens": ["Cockatrice", "Gargoyle", "Griffon", "Harpy", "Hawk, Giant", "Hippogriff", "Lammasu", "Manticore", "Pegasus", "Roc, Small", "Stirge", "Wyvern"],
"Desert": ["Chimera", "Cockatrice", "Gargoyle", "Griffon", "Hawk, Giant", "Lammasu", "Manticore", "Pterodactyl", "Roc, Small", "Sphinx", "Wyvern", "Vulture"],
"Mountains": ["Swarm, Bat", "Chimera", "Cockatrice", "Gargoyle", "Griffon", "Harpy", "Hawk, Giant", "Hippogriff", "Manticore", "Pegasus", "Roc*", "Wyvern"],
"Woods": ["Bat, Giant", "Swarm, Bat", "Cockatrice", "Griffon", "Hawk, Giant", "Hippogriff", "Pegasus", "Owl, Giant", "Pixie", "Roc, Small", "Sprite", "Stirge"],
"Other": ["Cockatrice", "Fly, Giant Carnivorous", "Gargoyle", "Griffon", "Hawk, Giant", "Hippogriff", "Bee, Killer Giant", "Pegasus", "Pixie", "Roc, Small", "Sprite", "Stirge"],
"*": "Roll 1d6: 1-3 Small, 4-5 Large, 6 Giant"
},
"Swimmer": {
"River, Lake": ["Crab, Giant", "Crocodile", "Crocodile", "Crocodile, Large", "Fish, Catfish", "Fish, Piranha", "Fish, Sturgeon", "Leech, Giant", "Lizard Man", "Merman", "Naiad", "Skittering Maw"],
"Ocean": ["Dragon Turtle", "Hydra, Sea", "Merman", "Octopus, Giant", "Sea Dragon", "Sea Serpent", "Shark*", "Shark*", "Skittering Maw", "Snake, Sea", "Squid, Giant", "Whale"],
"Swamp": ["Crab, Giant", "Crocodile", "Crocodile, Large", "Crocodile, Giant", "Fish, Catfish", "Insect Swarm", "Insect Swawrm", "Leech, Giant", "Leech, Giant", "Lizard Man", "Lizard Man", "Skittering Maw"],
"*": "Roll 1d6: 1-2 Bull Shark, 3-4 Mako Shark, 5-6 Great White Shark"
},
"Dragon": {
"Other": ["Basilisk", "Caecilian", "Chimera", "Dragon*", "Dragon*", "Sphinx", "Hydra/Sea Hydra", "Lamia", "Purple Worm", "Snake, Python", "Salamander", "Wyvern"],
"*": "Always Black in Swamp, Blue in Mountains, Brown in Desert, Green in Woods, Red in Barrens, Sea in Oceans/Rivers/Lake. Otherwise roll 1d10: 1 Black, 2 Blue, 3 Brown, 4 Green, 5 Metallic, 6 Red, 7 Sea, 8 White, 9 Wyrm, 10 Judge’s Choice.",
},
"Insect": {
"Other": ["Beetle, Fire", "Beetle, Giant Bombardier", "Beetle, Tiger", "Carcass Scavenger", "Centipede, Giant", "Ant, Giant", "Fly, Giant Carnivorous", "Killer Bee", "Rhagodessa", "Scorpion, Giant", "Spider, Black Widow", "Spider, Crab"],
},
"Undead": {
"Other": ["Ghoul", "Ghoul", "Mummy", "Mummy", "Skeleton", "Skeleton", "Spectre", "Wight", "Wraith", "Vampire", "Zombie", "Zombie"],
},
"Unusual": {
"Other": ["Basilisk", "Blink Dog", "Centaur", "Gorgon", "Hellhound", "Lycanthrope", "Medusa", "Phase Tiger", "Rust Monster", "Skittering Maw", "Treant", "Ape, White"],
}
}
def surprise_roll(mod):
roll = int(dice.roll("1d6 + {}".format(mod)))
status = "Surprised" if roll < 3 else "Ready"
return (status, roll)
def reaction_roll(mod, difference):
roll = int(dice.roll("2d6 + {}".format(mod)))
roll += difference
if roll <= 2:
status = "Hostile, attacks"
elif roll >= 3 and roll <= 5:
status = "Unfriendly, may attack"
elif roll >= 6 and roll <= 8:
status = "Neutral, uncertain"
elif roll >= 9 and roll <= 11:
status = "Indifferent, uninterested"
elif roll >= 12:
status = "Friendly, helpful"
return (status, roll)
def check_encounter(mod=0, throw=6):
roll = int(dice.roll("1d6 + {}".format(mod)))
return ((roll >= throw), roll)
def generate_dungeon_monster(check_mod, dungeon_level, reaction_mod, surprise_mod):
check = check_encounter(check_mod)
if not check[0]:
return {"check": False, "check_roll": check[1]}
if not dungeon_level in DUNGEON_WANDERING_MONSTER_LEVEL:
return None
monster_table = random.choice(DUNGEON_WANDERING_MONSTER_LEVEL[dungeon_level])
encounter = random.choice(DUNGEON_RANDOM_MONSTERS_BY_LEVEL[monster_table])
difference = monster_table - dungeon_level
quantity = int(dice.roll(encounter[1]))
adjusted = quantity
for _ in range(abs(difference)):
adjusted = adjusted + (adjusted * 0.5) if difference > 0 else adjusted - (adjusted * 0.5)
adjusted = math.floor(adjusted) if adjusted > 1 else 1
distance = int(dice.roll("2d6")) * 10
surprise = surprise_roll(surprise_mod)
reaction = reaction_roll(reaction_mod, difference)
return {
"dungeon_level": dungeon_level,
"monster_table": monster_table,
"entity": encounter[0],
"quantity": adjusted,
"initial": quantity,
"roll": encounter[1],
"adjustment": difference,
"distance": distance,
"surprise": surprise,
"reaction": reaction,
"check": True,
"check_roll": check[1]
}
def generate_wilderness_monster(check_mod, terrain_type, reaction_mod, surprise_mod):
check_throw = WILDERNESS_FREQUENCY_BY_TERRAIN[terrain_type]
check = check_encounter(check_mod, check_throw)
if not check[0]:
return {"check": False, "check_roll": check[1]}
encounter_type_roll = int(dice.roll("1d8"))
encounter_type = WILDERNESS_ENCOUNTER_BY_TERRAIN[terrain_type][encounter_type_roll - 1]
subtable = WILDERNESS_ENCOUNTER_SUBTABLES[encounter_type]
terrain_type_labels = WILDERNESS_TYPE_BY_ID[terrain_type]
endtable = None
for k in subtable.keys():
for l in terrain_type_labels:
if l in k:
endtable = subtable[k]
if endtable == None and "Other" in subtable:
endtable = subtable["Other"]
entity_roll = int(dice.roll("1d12"))
entity = endtable[entity_roll - 1]
surprise = surprise_roll(surprise_mod)
reaction = reaction_roll(reaction_mod, 0)
return {
"terrain_type": ', '.join(terrain_type_labels),
"monster_table": encounter_type,
"entity": entity,
"surprise": surprise,
"reaction": reaction,
"check": True,
"check_roll": check[1]
}