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
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]
|
|
}
|