mirror of
https://github.com/rr-/pq-cli.git
synced 2026-09-28 22:33:59 -06:00
mechanic: port game logic
This commit is contained in:
+428
-432
@@ -3,14 +3,7 @@ import typing as T
|
||||
from dataclasses import dataclass
|
||||
|
||||
|
||||
class Traits(enum.Enum):
|
||||
name = "Name"
|
||||
race = "Race"
|
||||
class_ = "Class"
|
||||
level = "Level"
|
||||
|
||||
|
||||
class Stat(enum.Enum):
|
||||
class StatType(enum.Enum):
|
||||
strength = "STR"
|
||||
condition = "CON"
|
||||
dexterity = "DEX"
|
||||
@@ -21,7 +14,7 @@ class Stat(enum.Enum):
|
||||
mp_max = "MP Max"
|
||||
|
||||
|
||||
class Equipment(enum.Enum):
|
||||
class EquipmentType(enum.Enum):
|
||||
weapon = "Weapon"
|
||||
shield = "Shield"
|
||||
helm = "Helm"
|
||||
@@ -36,15 +29,15 @@ class Equipment(enum.Enum):
|
||||
|
||||
|
||||
PRIME_STATS = [
|
||||
Stat.strength,
|
||||
Stat.condition,
|
||||
Stat.dexterity,
|
||||
Stat.intelligence,
|
||||
Stat.wisdom,
|
||||
Stat.charisma,
|
||||
StatType.strength,
|
||||
StatType.condition,
|
||||
StatType.dexterity,
|
||||
StatType.intelligence,
|
||||
StatType.wisdom,
|
||||
StatType.charisma,
|
||||
]
|
||||
|
||||
ALL_STATS = PRIME_STATS + [Stat.hp_max, Stat.mp_max]
|
||||
ALL_STATS = PRIME_STATS + [StatType.hp_max, StatType.mp_max]
|
||||
|
||||
|
||||
SPELLS = [
|
||||
@@ -97,114 +90,157 @@ SPELLS = [
|
||||
"Infinite Confusion",
|
||||
]
|
||||
|
||||
|
||||
@dataclass
|
||||
class Modifier:
|
||||
name: str
|
||||
quality: int
|
||||
|
||||
|
||||
OFFENSE_ATTRIB = [
|
||||
"Polished|+1",
|
||||
"Serrated|+1",
|
||||
"Heavy|+1",
|
||||
"Pronged|+2",
|
||||
"Steely|+2",
|
||||
"Vicious|+3",
|
||||
"Venomed|+4",
|
||||
"Stabbity|+4",
|
||||
"Dancing|+5",
|
||||
"Invisible|+6",
|
||||
"Vorpal|+7",
|
||||
Modifier("Polished", 1),
|
||||
Modifier("Serrated", 1),
|
||||
Modifier("Heavy", 1),
|
||||
Modifier("Pronged", 2),
|
||||
Modifier("Steely", 2),
|
||||
Modifier("Vicious", 3),
|
||||
Modifier("Venomed", 4),
|
||||
Modifier("Stabbity", 4),
|
||||
Modifier("Dancing", 5),
|
||||
Modifier("Invisible", 6),
|
||||
Modifier("Vorpal", 7),
|
||||
]
|
||||
|
||||
DEFENSE_ATTRIB = [
|
||||
"Studded|+1",
|
||||
"Banded|+2",
|
||||
"Gilded|+2",
|
||||
"Festooned|+3",
|
||||
"Holy|+4",
|
||||
"Cambric|+1",
|
||||
"Fine|+4",
|
||||
"Impressive|+5",
|
||||
"Custom|+3",
|
||||
Modifier("Studded", 1),
|
||||
Modifier("Banded", 2),
|
||||
Modifier("Gilded", 2),
|
||||
Modifier("Festooned", 3),
|
||||
Modifier("Holy", 4),
|
||||
Modifier("Cambric", 1),
|
||||
Modifier("Fine", 4),
|
||||
Modifier("Impressive", 5),
|
||||
Modifier("Custom", 3),
|
||||
]
|
||||
|
||||
OFFENSE_BAD = [
|
||||
Modifier("Dull", -2),
|
||||
Modifier("Tarnished", -1),
|
||||
Modifier("Rusty", -3),
|
||||
Modifier("Padded", -5),
|
||||
Modifier("Bent", -4),
|
||||
Modifier("Mini", -4),
|
||||
Modifier("Rubber", -6),
|
||||
Modifier("Nerf", -7),
|
||||
Modifier("Unbalanced", -2),
|
||||
]
|
||||
|
||||
DEFENSE_BAD = [
|
||||
Modifier("Holey", -1),
|
||||
Modifier("Patched", -1),
|
||||
Modifier("Threadbare", -2),
|
||||
Modifier("Faded", -1),
|
||||
Modifier("Rusty", -3),
|
||||
Modifier("Motheaten", -3),
|
||||
Modifier("Mildewed", -2),
|
||||
Modifier("Torn", -3),
|
||||
Modifier("Dented", -3),
|
||||
Modifier("Cursed", -5),
|
||||
Modifier("Plastic", -4),
|
||||
Modifier("Cracked", -4),
|
||||
Modifier("Warped", -3),
|
||||
Modifier("Corroded", -3),
|
||||
]
|
||||
|
||||
|
||||
@dataclass
|
||||
class Equipment:
|
||||
name: str
|
||||
quality: int
|
||||
|
||||
|
||||
SHIELDS = [
|
||||
"Parasol|0",
|
||||
"Pie Plate|1",
|
||||
"Garbage Can Lid|2",
|
||||
"Buckler|3",
|
||||
"Plexiglass|4",
|
||||
"Fender|4",
|
||||
"Round Shield|5",
|
||||
"Carapace|5",
|
||||
"Scutum|6",
|
||||
"Propugner|6",
|
||||
"Kite Shield|7",
|
||||
"Pavise|8",
|
||||
"Tower Shield|9",
|
||||
"Baroque Shield|11",
|
||||
"Aegis|12",
|
||||
"Magnetic Field|18",
|
||||
Equipment("Parasol", 0),
|
||||
Equipment("Pie Plate", 1),
|
||||
Equipment("Garbage Can Lid", 2),
|
||||
Equipment("Buckler", 3),
|
||||
Equipment("Plexiglass", 4),
|
||||
Equipment("Fender", 4),
|
||||
Equipment("Round Shield", 5),
|
||||
Equipment("Carapace", 5),
|
||||
Equipment("Scutum", 6),
|
||||
Equipment("Propugner", 6),
|
||||
Equipment("Kite Shield", 7),
|
||||
Equipment("Pavise", 8),
|
||||
Equipment("Tower Shield", 9),
|
||||
Equipment("Baroque Shield", 11),
|
||||
Equipment("Aegis", 12),
|
||||
Equipment("Magnetic Field", 18),
|
||||
]
|
||||
|
||||
ARMORS = [
|
||||
"Lace|1",
|
||||
"Macrame|2",
|
||||
"Burlap|3",
|
||||
"Canvas|4",
|
||||
"Flannel|5",
|
||||
"Chamois|6",
|
||||
"Pleathers|7",
|
||||
"Leathers|8",
|
||||
"Bearskin|9",
|
||||
"Ringmail|10",
|
||||
"Scale Mail|12",
|
||||
"Chainmail|14",
|
||||
"Splint Mail|15",
|
||||
"Platemail|16",
|
||||
"ABS|17",
|
||||
"Kevlar|18",
|
||||
"Titanium|19",
|
||||
"Mithril Mail|20",
|
||||
"Diamond Mail|25",
|
||||
"Plasma|30",
|
||||
Equipment("Lace", 1),
|
||||
Equipment("Macrame", 2),
|
||||
Equipment("Burlap", 3),
|
||||
Equipment("Canvas", 4),
|
||||
Equipment("Flannel", 5),
|
||||
Equipment("Chamois", 6),
|
||||
Equipment("Pleathers", 7),
|
||||
Equipment("Leathers", 8),
|
||||
Equipment("Bearskin", 9),
|
||||
Equipment("Ringmail", 10),
|
||||
Equipment("Scale Mail", 12),
|
||||
Equipment("Chainmail", 14),
|
||||
Equipment("Splint Mail", 15),
|
||||
Equipment("Platemail", 16),
|
||||
Equipment("ABS", 17),
|
||||
Equipment("Kevlar", 18),
|
||||
Equipment("Titanium", 19),
|
||||
Equipment("Mithril Mail", 20),
|
||||
Equipment("Diamond Mail", 25),
|
||||
Equipment("Plasma", 30),
|
||||
]
|
||||
|
||||
WEAPONS = [
|
||||
"Stick|0",
|
||||
"Broken Bottle|1",
|
||||
"Shiv|1",
|
||||
"Sprig|1",
|
||||
"Oxgoad|1",
|
||||
"Eelspear|2",
|
||||
"Bowie Knife|2",
|
||||
"Claw Hammer|2",
|
||||
"Handpeen|2",
|
||||
"Andiron|3",
|
||||
"Hatchet|3",
|
||||
"Tomahawk|3",
|
||||
"Hackbarm|3",
|
||||
"Crowbar|4",
|
||||
"Mace|4",
|
||||
"Battleadze|4",
|
||||
"Leafmace|5",
|
||||
"Shortsword|5",
|
||||
"Longiron|5",
|
||||
"Poachard|5",
|
||||
"Baselard|5",
|
||||
"Whinyard|6",
|
||||
"Blunderbuss|6",
|
||||
"Longsword|6",
|
||||
"Crankbow|6",
|
||||
"Blibo|7",
|
||||
"Broadsword|7",
|
||||
"Kreen|7",
|
||||
"Warhammer|7",
|
||||
"Morning Star|8",
|
||||
"Pole-adze|8",
|
||||
"Spontoon|8",
|
||||
"Bastard Sword|9",
|
||||
"Peen-arm|9",
|
||||
"Culverin|10",
|
||||
"Lance|10",
|
||||
"Halberd|11",
|
||||
"Poleax|12",
|
||||
"Bandyclef|15",
|
||||
Equipment("Stick", 0),
|
||||
Equipment("Broken Bottle", 1),
|
||||
Equipment("Shiv", 1),
|
||||
Equipment("Sprig", 1),
|
||||
Equipment("Oxgoad", 1),
|
||||
Equipment("Eelspear", 2),
|
||||
Equipment("Bowie Knife", 2),
|
||||
Equipment("Claw Hammer", 2),
|
||||
Equipment("Handpeen", 2),
|
||||
Equipment("Andiron", 3),
|
||||
Equipment("Hatchet", 3),
|
||||
Equipment("Tomahawk", 3),
|
||||
Equipment("Hackbarm", 3),
|
||||
Equipment("Crowbar", 4),
|
||||
Equipment("Mace", 4),
|
||||
Equipment("Battleadze", 4),
|
||||
Equipment("Leafmace", 5),
|
||||
Equipment("Shortsword", 5),
|
||||
Equipment("Longiron", 5),
|
||||
Equipment("Poachard", 5),
|
||||
Equipment("Baselard", 5),
|
||||
Equipment("Whinyard", 6),
|
||||
Equipment("Blunderbuss", 6),
|
||||
Equipment("Longsword", 6),
|
||||
Equipment("Crankbow", 6),
|
||||
Equipment("Blibo", 7),
|
||||
Equipment("Broadsword", 7),
|
||||
Equipment("Kreen", 7),
|
||||
Equipment("Warhammer", 7),
|
||||
Equipment("Morning Star", 8),
|
||||
Equipment("Pole-adze", 8),
|
||||
Equipment("Spontoon", 8),
|
||||
Equipment("Bastard Sword", 9),
|
||||
Equipment("Peen-arm", 9),
|
||||
Equipment("Culverin", 10),
|
||||
Equipment("Lance", 10),
|
||||
Equipment("Halberd", 11),
|
||||
Equipment("Poleax", 12),
|
||||
Equipment("Bandyclef", 15),
|
||||
]
|
||||
|
||||
SPECIALS = [
|
||||
@@ -383,346 +419,306 @@ BORING_ITEMS = [
|
||||
"writ",
|
||||
]
|
||||
|
||||
|
||||
@dataclass
|
||||
class Monster:
|
||||
name: str
|
||||
level: int
|
||||
item: T.Optional[str]
|
||||
|
||||
|
||||
MONSTERS = [
|
||||
"Anhkheg|6|chitin",
|
||||
"Ant|0|antenna",
|
||||
"Ape|4|ass",
|
||||
"Baluchitherium|14|ear",
|
||||
"Beholder|10|eyestalk",
|
||||
"Black Pudding|10|saliva",
|
||||
"Blink Dog|4|eyelid",
|
||||
"Cub Scout|1|neckerchief",
|
||||
"Girl Scout|2|cookie",
|
||||
"Boy Scout|3|merit badge",
|
||||
"Eagle Scout|4|merit badge",
|
||||
"Bugbear|3|skin",
|
||||
"Bugboar|3|tusk",
|
||||
"Boogie|3|slime",
|
||||
"Camel|2|hump",
|
||||
"Carrion Crawler|3|egg",
|
||||
"Catoblepas|6|neck",
|
||||
"Centaur|4|rib",
|
||||
"Centipede|0|leg",
|
||||
"Cockatrice|5|wattle",
|
||||
"Couatl|9|wing",
|
||||
"Crayfish|0|antenna",
|
||||
"Demogorgon|53|tentacle",
|
||||
"Jubilex|17|gel",
|
||||
"Manes|1|tooth",
|
||||
"Orcus|27|wand",
|
||||
"Succubus|6|bra",
|
||||
"Vrock|8|neck",
|
||||
"Hezrou|9|leg",
|
||||
"Glabrezu|10|collar",
|
||||
"Nalfeshnee|11|tusk",
|
||||
"Marilith|7|arm",
|
||||
"Balor|8|whip",
|
||||
"Yeenoghu|25|flail",
|
||||
"Asmodeus|52|leathers",
|
||||
"Baalzebul|43|pants",
|
||||
"Barbed Devil|8|flame",
|
||||
"Bone Devil|9|hook",
|
||||
"Dispater|30|matches",
|
||||
"Erinyes|6|thong",
|
||||
"Geryon|30|cornucopia",
|
||||
"Malebranche|5|fork",
|
||||
"Ice Devil|11|snow",
|
||||
"Lemure|3|blob",
|
||||
"Pit Fiend|13|seed",
|
||||
"Ankylosaurus|9|tail",
|
||||
"Brontosaurus|30|brain",
|
||||
"Diplodocus|24|fin",
|
||||
"Elasmosaurus|15|neck",
|
||||
"Gorgosaurus|13|arm",
|
||||
"Iguanadon|6|thumb",
|
||||
"Megalosaurus|12|jaw",
|
||||
"Monoclonius|8|horn",
|
||||
"Pentasaurus|12|head",
|
||||
"Stegosaurus|18|plate",
|
||||
"Triceratops|16|horn",
|
||||
"Tyranosauraus Rex|18|forearm",
|
||||
"Djinn|7|lamp",
|
||||
"Doppleganger|4|face",
|
||||
"Black Dragon|7|*",
|
||||
"Plaid Dragon|7|sporrin",
|
||||
"Blue Dragon|9|*",
|
||||
"Beige Dragon|9|*",
|
||||
"Brass Dragon|7|pole",
|
||||
"Tin Dragon|8|*",
|
||||
"Bronze Dragon|9|medal",
|
||||
"Chromatic Dragon|16|scale",
|
||||
"Copper Dragon|8|loafer",
|
||||
"Gold Dragon|8|filling",
|
||||
"Green Dragon|8|*",
|
||||
"Platinum Dragon|21|*",
|
||||
"Red Dragon|10|cocktail",
|
||||
"Silver Dragon|10|*",
|
||||
"White Dragon|6|tooth",
|
||||
"Dragon Turtle|13|shell",
|
||||
"Dryad|2|acorn",
|
||||
"Dwarf|1|drawers",
|
||||
"Eel|2|sashimi",
|
||||
"Efreet|10|cinder",
|
||||
"Sand Elemental|8|glass",
|
||||
"Bacon Elemental|10|bit",
|
||||
"Porn Elemental|12|lube",
|
||||
"Cheese Elemental|14|curd",
|
||||
"Hair Elemental|16|follicle",
|
||||
"Swamp Elf|1|lilypad",
|
||||
"Brown Elf|1|tusk",
|
||||
"Sea Elf|1|jerkin",
|
||||
"Ettin|10|fur",
|
||||
"Frog|0|leg",
|
||||
"Violet Fungi|3|spore",
|
||||
"Gargoyle|4|gravel",
|
||||
"Gelatinous Cube|4|jam",
|
||||
"Ghast|4|vomit",
|
||||
"Ghost|10|*",
|
||||
"Ghoul|2|muscle",
|
||||
"Humidity Giant|12|drops",
|
||||
"Beef Giant|11|steak",
|
||||
"Quartz Giant|10|crystal",
|
||||
"Porcelain Giant|9|fixture",
|
||||
"Rice Giant|8|grain",
|
||||
"Cloud Giant|12|condensation",
|
||||
"Fire Giant|11|cigarettes",
|
||||
"Frost Giant|10|snowman",
|
||||
"Hill Giant|8|corpse",
|
||||
"Stone Giant|9|hatchling",
|
||||
"Storm Giant|15|barometer",
|
||||
"Mini Giant|4|pompadour",
|
||||
"Gnoll|2|collar",
|
||||
"Gnome|1|hat",
|
||||
"Goblin|1|ear",
|
||||
"Grid Bug|1|carapace",
|
||||
"Jellyrock|9|seedling",
|
||||
"Beer Golem|15|foam",
|
||||
"Oxygen Golem|17|platelet",
|
||||
"Cardboard Golem|14|recycling",
|
||||
"Rubber Golem|16|ball",
|
||||
"Leather Golem|15|fob",
|
||||
"Gorgon|8|testicle",
|
||||
"Gray Ooze|3|gravy",
|
||||
"Green Slime|2|sample",
|
||||
"Griffon|7|nest",
|
||||
"Banshee|7|larynx",
|
||||
"Harpy|3|mascara",
|
||||
"Hell Hound|5|tongue",
|
||||
"Hippocampus|4|mane",
|
||||
"Hippogriff|3|egg",
|
||||
"Hobgoblin|1|patella",
|
||||
"Homonculus|2|fluid",
|
||||
"Hydra|8|gyrum",
|
||||
"Imp|2|tail",
|
||||
"Invisible Stalker|8|*",
|
||||
"Iron Peasant|3|chaff",
|
||||
"Jumpskin|3|shin",
|
||||
"Kobold|1|penis",
|
||||
"Leprechaun|1|wallet",
|
||||
"Leucrotta|6|hoof",
|
||||
"Lich|11|crown",
|
||||
"Lizard Man|2|tail",
|
||||
"Lurker|10|sac",
|
||||
"Manticore|6|spike",
|
||||
"Mastodon|12|tusk",
|
||||
"Medusa|6|eye",
|
||||
"Multicell|2|dendrite",
|
||||
"Pirate|1|booty",
|
||||
"Berserker|1|shirt",
|
||||
"Caveman|2|club",
|
||||
"Dervish|1|robe",
|
||||
"Merman|1|trident",
|
||||
"Mermaid|1|gills",
|
||||
"Mimic|9|hinge",
|
||||
"Mind Flayer|8|tentacle",
|
||||
"Minotaur|6|map",
|
||||
"Yellow Mold|1|spore",
|
||||
"Morkoth|7|teeth",
|
||||
"Mummy|6|gauze",
|
||||
"Naga|9|rattle",
|
||||
"Nebbish|1|belly",
|
||||
"Neo-Otyugh|11|organ ",
|
||||
"Nixie|1|webbing",
|
||||
"Nymph|3|hanky",
|
||||
"Ochre Jelly|6|nucleus",
|
||||
"Octopus|2|beak",
|
||||
"Ogre|4|talon",
|
||||
"Ogre Mage|5|apparel",
|
||||
"Orc|1|snout",
|
||||
"Otyugh|7|organ",
|
||||
"Owlbear|5|feather",
|
||||
"Pegasus|4|aileron",
|
||||
"Peryton|4|antler",
|
||||
"Piercer|3|tip",
|
||||
"Pixie|1|dust",
|
||||
"Man-o-war|3|tentacle",
|
||||
"Purple Worm|15|dung",
|
||||
"Quasit|3|tail",
|
||||
"Rakshasa|7|pajamas",
|
||||
"Rat|0|tail",
|
||||
"Remorhaz|11|protrusion",
|
||||
"Roc|18|wing",
|
||||
"Roper|11|twine",
|
||||
"Rot Grub|1|eggsac",
|
||||
"Rust Monster|5|shavings",
|
||||
"Satyr|5|hoof",
|
||||
"Sea Hag|3|wart",
|
||||
"Silkie|3|fur",
|
||||
"Shadow|3|silhouette",
|
||||
"Shambling Mound|10|mulch",
|
||||
"Shedu|9|hoof",
|
||||
"Shrieker|3|stalk",
|
||||
"Skeleton|1|clavicle",
|
||||
"Spectre|7|vestige",
|
||||
"Sphinx|10|paw",
|
||||
"Spider|0|web",
|
||||
"Sprite|1|can",
|
||||
"Stirge|1|proboscis",
|
||||
"Stun Bear|5|tooth",
|
||||
"Stun Worm|2|trode",
|
||||
"Su-monster|5|tail",
|
||||
"Sylph|3|thigh",
|
||||
"Titan|20|sandal",
|
||||
"Trapper|12|shag",
|
||||
"Treant|10|acorn",
|
||||
"Triton|3|scale",
|
||||
"Troglodyte|2|tail",
|
||||
"Troll|6|hide",
|
||||
"Umber Hulk|8|claw",
|
||||
"Unicorn|4|blood",
|
||||
"Vampire|8|pancreas",
|
||||
"Wight|4|lung",
|
||||
"Will-o'-the-Wisp|9|wisp",
|
||||
"Wraith|5|finger",
|
||||
"Wyvern|7|wing",
|
||||
"Xorn|7|jaw",
|
||||
"Yeti|4|fur",
|
||||
"Zombie|2|forehead",
|
||||
"Wasp|0|stinger",
|
||||
"Rat|1|tail",
|
||||
"Bunny|0|ear",
|
||||
"Moth|0|dust",
|
||||
"Beagle|0|collar",
|
||||
"Midge|0|corpse",
|
||||
"Ostrich|1|beak",
|
||||
"Billy Goat|1|beard",
|
||||
"Bat|1|wing",
|
||||
"Koala|2|heart",
|
||||
"Wolf|2|paw",
|
||||
"Whippet|2|collar",
|
||||
"Uruk|2|boot",
|
||||
"Poroid|4|node",
|
||||
"Moakum|8|frenum",
|
||||
"Fly|0|*",
|
||||
"Hogbird|3|curl",
|
||||
"Wolog|4|lemma",
|
||||
]
|
||||
|
||||
MON_MODS = [
|
||||
"-4 fœtal *",
|
||||
"-4 dying *",
|
||||
"-3 crippled *",
|
||||
"-3 baby *",
|
||||
"-2 adolescent *",
|
||||
"-2 very sick *",
|
||||
"-1 lesser *",
|
||||
"-1 undernourished *",
|
||||
"+1 greater *",
|
||||
"+1 * Elder",
|
||||
"+2 war *",
|
||||
"+2 Battle-*",
|
||||
"+3 Were-*",
|
||||
"+3 undead *",
|
||||
"+4 giant *",
|
||||
"+4 * Rex",
|
||||
]
|
||||
|
||||
OFFENSE_BAD = [
|
||||
"Dull|-2",
|
||||
"Tarnished|-1",
|
||||
"Rusty|-3",
|
||||
"Padded|-5",
|
||||
"Bent|-4",
|
||||
"Mini|-4",
|
||||
"Rubber|-6",
|
||||
"Nerf|-7",
|
||||
"Unbalanced|-2",
|
||||
]
|
||||
|
||||
DEFENSE_BAD = [
|
||||
"Holey|-1",
|
||||
"Patched|-1",
|
||||
"Threadbare|-2",
|
||||
"Faded|-1",
|
||||
"Rusty|-3",
|
||||
"Motheaten|-3",
|
||||
"Mildewed|-2",
|
||||
"Torn|-3",
|
||||
"Dented|-3",
|
||||
"Cursed|-5",
|
||||
"Plastic|-4",
|
||||
"Cracked|-4",
|
||||
"Warped|-3",
|
||||
"Corroded|-3",
|
||||
Monster("Anhkheg", 6, "chitin"),
|
||||
Monster("Ant", 0, "antenna"),
|
||||
Monster("Ape", 4, "ass"),
|
||||
Monster("Baluchitherium", 14, "ear"),
|
||||
Monster("Beholder", 10, "eyestalk"),
|
||||
Monster("Black Pudding", 10, "saliva"),
|
||||
Monster("Blink Dog", 4, "eyelid"),
|
||||
Monster("Cub Scout", 1, "neckerchief"),
|
||||
Monster("Girl Scout", 2, "cookie"),
|
||||
Monster("Boy Scout", 3, "merit badge"),
|
||||
Monster("Eagle Scout", 4, "merit badge"),
|
||||
Monster("Bugbear", 3, "skin"),
|
||||
Monster("Bugboar", 3, "tusk"),
|
||||
Monster("Boogie", 3, "slime"),
|
||||
Monster("Camel", 2, "hump"),
|
||||
Monster("Carrion Crawler", 3, "egg"),
|
||||
Monster("Catoblepas", 6, "neck"),
|
||||
Monster("Centaur", 4, "rib"),
|
||||
Monster("Centipede", 0, "leg"),
|
||||
Monster("Cockatrice", 5, "wattle"),
|
||||
Monster("Couatl", 9, "wing"),
|
||||
Monster("Crayfish", 0, "antenna"),
|
||||
Monster("Demogorgon", 53, "tentacle"),
|
||||
Monster("Jubilex", 17, "gel"),
|
||||
Monster("Manes", 1, "tooth"),
|
||||
Monster("Orcus", 27, "wand"),
|
||||
Monster("Succubus", 6, "bra"),
|
||||
Monster("Vrock", 8, "neck"),
|
||||
Monster("Hezrou", 9, "leg"),
|
||||
Monster("Glabrezu", 10, "collar"),
|
||||
Monster("Nalfeshnee", 11, "tusk"),
|
||||
Monster("Marilith", 7, "arm"),
|
||||
Monster("Balor", 8, "whip"),
|
||||
Monster("Yeenoghu", 25, "flail"),
|
||||
Monster("Asmodeus", 52, "leathers"),
|
||||
Monster("Baalzebul", 43, "pants"),
|
||||
Monster("Barbed Devil", 8, "flame"),
|
||||
Monster("Bone Devil", 9, "hook"),
|
||||
Monster("Dispater", 30, "matches"),
|
||||
Monster("Erinyes", 6, "thong"),
|
||||
Monster("Geryon", 30, "cornucopia"),
|
||||
Monster("Malebranche", 5, "fork"),
|
||||
Monster("Ice Devil", 11, "snow"),
|
||||
Monster("Lemure", 3, "blob"),
|
||||
Monster("Pit Fiend", 13, "seed"),
|
||||
Monster("Ankylosaurus", 9, "tail"),
|
||||
Monster("Brontosaurus", 30, "brain"),
|
||||
Monster("Diplodocus", 24, "fin"),
|
||||
Monster("Elasmosaurus", 15, "neck"),
|
||||
Monster("Gorgosaurus", 13, "arm"),
|
||||
Monster("Iguanadon", 6, "thumb"),
|
||||
Monster("Megalosaurus", 12, "jaw"),
|
||||
Monster("Monoclonius", 8, "horn"),
|
||||
Monster("Pentasaurus", 12, "head"),
|
||||
Monster("Stegosaurus", 18, "plate"),
|
||||
Monster("Triceratops", 16, "horn"),
|
||||
Monster("Tyranosauraus Rex", 18, "forearm"),
|
||||
Monster("Djinn", 7, "lamp"),
|
||||
Monster("Doppleganger", 4, "face"),
|
||||
Monster("Black Dragon", 7, None),
|
||||
Monster("Plaid Dragon", 7, "sporrin"),
|
||||
Monster("Blue Dragon", 9, None),
|
||||
Monster("Beige Dragon", 9, None),
|
||||
Monster("Brass Dragon", 7, "pole"),
|
||||
Monster("Tin Dragon", 8, None),
|
||||
Monster("Bronze Dragon", 9, "medal"),
|
||||
Monster("Chromatic Dragon", 16, "scale"),
|
||||
Monster("Copper Dragon", 8, "loafer"),
|
||||
Monster("Gold Dragon", 8, "filling"),
|
||||
Monster("Green Dragon", 8, None),
|
||||
Monster("Platinum Dragon", 21, None),
|
||||
Monster("Red Dragon", 10, "cocktail"),
|
||||
Monster("Silver Dragon", 10, None),
|
||||
Monster("White Dragon", 6, "tooth"),
|
||||
Monster("Dragon Turtle", 13, "shell"),
|
||||
Monster("Dryad", 2, "acorn"),
|
||||
Monster("Dwarf", 1, "drawers"),
|
||||
Monster("Eel", 2, "sashimi"),
|
||||
Monster("Efreet", 10, "cinder"),
|
||||
Monster("Sand Elemental", 8, "glass"),
|
||||
Monster("Bacon Elemental", 10, "bit"),
|
||||
Monster("Porn Elemental", 12, "lube"),
|
||||
Monster("Cheese Elemental", 14, "curd"),
|
||||
Monster("Hair Elemental", 16, "follicle"),
|
||||
Monster("Swamp Elf", 1, "lilypad"),
|
||||
Monster("Brown Elf", 1, "tusk"),
|
||||
Monster("Sea Elf", 1, "jerkin"),
|
||||
Monster("Ettin", 10, "fur"),
|
||||
Monster("Frog", 0, "leg"),
|
||||
Monster("Violet Fungi", 3, "spore"),
|
||||
Monster("Gargoyle", 4, "gravel"),
|
||||
Monster("Gelatinous Cube", 4, "jam"),
|
||||
Monster("Ghast", 4, "vomit"),
|
||||
Monster("Ghost", 10, None),
|
||||
Monster("Ghoul", 2, "muscle"),
|
||||
Monster("Humidity Giant", 12, "drops"),
|
||||
Monster("Beef Giant", 11, "steak"),
|
||||
Monster("Quartz Giant", 10, "crystal"),
|
||||
Monster("Porcelain Giant", 9, "fixture"),
|
||||
Monster("Rice Giant", 8, "grain"),
|
||||
Monster("Cloud Giant", 12, "condensation"),
|
||||
Monster("Fire Giant", 11, "cigarettes"),
|
||||
Monster("Frost Giant", 10, "snowman"),
|
||||
Monster("Hill Giant", 8, "corpse"),
|
||||
Monster("Stone Giant", 9, "hatchling"),
|
||||
Monster("Storm Giant", 15, "barometer"),
|
||||
Monster("Mini Giant", 4, "pompadour"),
|
||||
Monster("Gnoll", 2, "collar"),
|
||||
Monster("Gnome", 1, "hat"),
|
||||
Monster("Goblin", 1, "ear"),
|
||||
Monster("Grid Bug", 1, "carapace"),
|
||||
Monster("Jellyrock", 9, "seedling"),
|
||||
Monster("Beer Golem", 15, "foam"),
|
||||
Monster("Oxygen Golem", 17, "platelet"),
|
||||
Monster("Cardboard Golem", 14, "recycling"),
|
||||
Monster("Rubber Golem", 16, "ball"),
|
||||
Monster("Leather Golem", 15, "fob"),
|
||||
Monster("Gorgon", 8, "testicle"),
|
||||
Monster("Gray Ooze", 3, "gravy"),
|
||||
Monster("Green Slime", 2, "sample"),
|
||||
Monster("Griffon", 7, "nest"),
|
||||
Monster("Banshee", 7, "larynx"),
|
||||
Monster("Harpy", 3, "mascara"),
|
||||
Monster("Hell Hound", 5, "tongue"),
|
||||
Monster("Hippocampus", 4, "mane"),
|
||||
Monster("Hippogriff", 3, "egg"),
|
||||
Monster("Hobgoblin", 1, "patella"),
|
||||
Monster("Homonculus", 2, "fluid"),
|
||||
Monster("Hydra", 8, "gyrum"),
|
||||
Monster("Imp", 2, "tail"),
|
||||
Monster("Invisible Stalker", 8, None),
|
||||
Monster("Iron Peasant", 3, "chaff"),
|
||||
Monster("Jumpskin", 3, "shin"),
|
||||
Monster("Kobold", 1, "penis"),
|
||||
Monster("Leprechaun", 1, "wallet"),
|
||||
Monster("Leucrotta", 6, "hoof"),
|
||||
Monster("Lich", 11, "crown"),
|
||||
Monster("Lizard Man", 2, "tail"),
|
||||
Monster("Lurker", 10, "sac"),
|
||||
Monster("Manticore", 6, "spike"),
|
||||
Monster("Mastodon", 12, "tusk"),
|
||||
Monster("Medusa", 6, "eye"),
|
||||
Monster("Multicell", 2, "dendrite"),
|
||||
Monster("Pirate", 1, "booty"),
|
||||
Monster("Berserker", 1, "shirt"),
|
||||
Monster("Caveman", 2, "club"),
|
||||
Monster("Dervish", 1, "robe"),
|
||||
Monster("Merman", 1, "trident"),
|
||||
Monster("Mermaid", 1, "gills"),
|
||||
Monster("Mimic", 9, "hinge"),
|
||||
Monster("Mind Flayer", 8, "tentacle"),
|
||||
Monster("Minotaur", 6, "map"),
|
||||
Monster("Yellow Mold", 1, "spore"),
|
||||
Monster("Morkoth", 7, "teeth"),
|
||||
Monster("Mummy", 6, "gauze"),
|
||||
Monster("Naga", 9, "rattle"),
|
||||
Monster("Nebbish", 1, "belly"),
|
||||
Monster("Neo-Otyugh", 11, "organ "),
|
||||
Monster("Nixie", 1, "webbing"),
|
||||
Monster("Nymph", 3, "hanky"),
|
||||
Monster("Ochre Jelly", 6, "nucleus"),
|
||||
Monster("Octopus", 2, "beak"),
|
||||
Monster("Ogre", 4, "talon"),
|
||||
Monster("Ogre Mage", 5, "apparel"),
|
||||
Monster("Orc", 1, "snout"),
|
||||
Monster("Otyugh", 7, "organ"),
|
||||
Monster("Owlbear", 5, "feather"),
|
||||
Monster("Pegasus", 4, "aileron"),
|
||||
Monster("Peryton", 4, "antler"),
|
||||
Monster("Piercer", 3, "tip"),
|
||||
Monster("Pixie", 1, "dust"),
|
||||
Monster("Man-o-war", 3, "tentacle"),
|
||||
Monster("Purple Worm", 15, "dung"),
|
||||
Monster("Quasit", 3, "tail"),
|
||||
Monster("Rakshasa", 7, "pajamas"),
|
||||
Monster("Rat", 0, "tail"),
|
||||
Monster("Remorhaz", 11, "protrusion"),
|
||||
Monster("Roc", 18, "wing"),
|
||||
Monster("Roper", 11, "twine"),
|
||||
Monster("Rot Grub", 1, "eggsac"),
|
||||
Monster("Rust Monster", 5, "shavings"),
|
||||
Monster("Satyr", 5, "hoof"),
|
||||
Monster("Sea Hag", 3, "wart"),
|
||||
Monster("Silkie", 3, "fur"),
|
||||
Monster("Shadow", 3, "silhouette"),
|
||||
Monster("Shambling Mound", 10, "mulch"),
|
||||
Monster("Shedu", 9, "hoof"),
|
||||
Monster("Shrieker", 3, "stalk"),
|
||||
Monster("Skeleton", 1, "clavicle"),
|
||||
Monster("Spectre", 7, "vestige"),
|
||||
Monster("Sphinx", 10, "paw"),
|
||||
Monster("Spider", 0, "web"),
|
||||
Monster("Sprite", 1, "can"),
|
||||
Monster("Stirge", 1, "proboscis"),
|
||||
Monster("Stun Bear", 5, "tooth"),
|
||||
Monster("Stun Worm", 2, "trode"),
|
||||
Monster("Su-monster", 5, "tail"),
|
||||
Monster("Sylph", 3, "thigh"),
|
||||
Monster("Titan", 20, "sandal"),
|
||||
Monster("Trapper", 12, "shag"),
|
||||
Monster("Treant", 10, "acorn"),
|
||||
Monster("Triton", 3, "scale"),
|
||||
Monster("Troglodyte", 2, "tail"),
|
||||
Monster("Troll", 6, "hide"),
|
||||
Monster("Umber Hulk", 8, "claw"),
|
||||
Monster("Unicorn", 4, "blood"),
|
||||
Monster("Vampire", 8, "pancreas"),
|
||||
Monster("Wight", 4, "lung"),
|
||||
Monster("Will-o'-the-Wisp", 9, "wisp"),
|
||||
Monster("Wraith", 5, "finger"),
|
||||
Monster("Wyvern", 7, "wing"),
|
||||
Monster("Xorn", 7, "jaw"),
|
||||
Monster("Yeti", 4, "fur"),
|
||||
Monster("Zombie", 2, "forehead"),
|
||||
Monster("Wasp", 0, "stinger"),
|
||||
Monster("Rat", 1, "tail"),
|
||||
Monster("Bunny", 0, "ear"),
|
||||
Monster("Moth", 0, "dust"),
|
||||
Monster("Beagle", 0, "collar"),
|
||||
Monster("Midge", 0, "corpse"),
|
||||
Monster("Ostrich", 1, "beak"),
|
||||
Monster("Billy Goat", 1, "beard"),
|
||||
Monster("Bat", 1, "wing"),
|
||||
Monster("Koala", 2, "heart"),
|
||||
Monster("Wolf", 2, "paw"),
|
||||
Monster("Whippet", 2, "collar"),
|
||||
Monster("Uruk", 2, "boot"),
|
||||
Monster("Poroid", 4, "node"),
|
||||
Monster("Moakum", 8, "frenum"),
|
||||
Monster("Fly", 0, None),
|
||||
Monster("Hogbird", 3, "curl"),
|
||||
Monster("Wolog", 4, "lemma"),
|
||||
]
|
||||
|
||||
|
||||
@dataclass
|
||||
class Race:
|
||||
name: str
|
||||
attr: T.List[Stat]
|
||||
attr: T.List[StatType]
|
||||
|
||||
|
||||
RACES = [
|
||||
Race("Half Orc", [Stat.hp_max]),
|
||||
Race("Half Man", [Stat.charisma]),
|
||||
Race("Half Halfling", [Stat.dexterity]),
|
||||
Race("Double Hobbit", [Stat.strength]),
|
||||
Race("Hob-Hobbit", [Stat.dexterity, Stat.condition]),
|
||||
Race("Low Elf", [Stat.condition]),
|
||||
Race("Dung Elf", [Stat.wisdom]),
|
||||
Race("Talking Pony", [Stat.mp_max, Stat.intelligence]),
|
||||
Race("Gyrognome", [Stat.dexterity]),
|
||||
Race("Lesser Dwarf", [Stat.condition]),
|
||||
Race("Crested Dwarf", [Stat.charisma]),
|
||||
Race("Eel Man", [Stat.dexterity]),
|
||||
Race("Panda Man", [Stat.condition, Stat.strength]),
|
||||
Race("Trans-Kobold", [Stat.wisdom]),
|
||||
Race("Enchanted Motorcycle", [Stat.mp_max]),
|
||||
Race("Will o' the Wisp", [Stat.wisdom]),
|
||||
Race("Battle-Finch", [Stat.dexterity, Stat.intelligence]),
|
||||
Race("Double Wookiee", [Stat.strength]),
|
||||
Race("Skraeling", [Stat.wisdom]),
|
||||
Race("Demicanadian", [Stat.condition]),
|
||||
Race("Land Squid", [Stat.strength, Stat.hp_max]),
|
||||
Race("Half Orc", [StatType.hp_max]),
|
||||
Race("Half Man", [StatType.charisma]),
|
||||
Race("Half Halfling", [StatType.dexterity]),
|
||||
Race("Double Hobbit", [StatType.strength]),
|
||||
Race("Hob-Hobbit", [StatType.dexterity, StatType.condition]),
|
||||
Race("Low Elf", [StatType.condition]),
|
||||
Race("Dung Elf", [StatType.wisdom]),
|
||||
Race("Talking Pony", [StatType.mp_max, StatType.intelligence]),
|
||||
Race("Gyrognome", [StatType.dexterity]),
|
||||
Race("Lesser Dwarf", [StatType.condition]),
|
||||
Race("Crested Dwarf", [StatType.charisma]),
|
||||
Race("Eel Man", [StatType.dexterity]),
|
||||
Race("Panda Man", [StatType.condition, StatType.strength]),
|
||||
Race("Trans-Kobold", [StatType.wisdom]),
|
||||
Race("Enchanted Motorcycle", [StatType.mp_max]),
|
||||
Race("Will o' the Wisp", [StatType.wisdom]),
|
||||
Race("Battle-Finch", [StatType.dexterity, StatType.intelligence]),
|
||||
Race("Double Wookiee", [StatType.strength]),
|
||||
Race("Skraeling", [StatType.wisdom]),
|
||||
Race("Demicanadian", [StatType.condition]),
|
||||
Race("Land Squid", [StatType.strength, StatType.hp_max]),
|
||||
]
|
||||
|
||||
|
||||
@dataclass
|
||||
class Class:
|
||||
name: str
|
||||
attr: T.List[Stat]
|
||||
attr: T.List[StatType]
|
||||
|
||||
|
||||
CLASSES = [
|
||||
Class("Ur-Paladin", [Stat.wisdom, Stat.condition]),
|
||||
Class("Voodoo Princess", [Stat.intelligence, Stat.charisma]),
|
||||
Class("Robot Monk", [Stat.strength]),
|
||||
Class("Mu-Fu Monk", [Stat.dexterity]),
|
||||
Class("Mage Illusioner", [Stat.intelligence, Stat.mp_max]),
|
||||
Class("Shiv-Knight", [Stat.dexterity]),
|
||||
Class("Inner Mason", [Stat.condition]),
|
||||
Class("Fighter/Organist", [Stat.charisma, Stat.strength]),
|
||||
Class("Puma Burgular", [Stat.dexterity]),
|
||||
Class("Runeloremaster", [Stat.wisdom]),
|
||||
Class("Hunter Strangler", [Stat.dexterity, Stat.intelligence]),
|
||||
Class("Battle-Felon", [Stat.strength]),
|
||||
Class("Tickle-Mimic", [Stat.wisdom, Stat.intelligence]),
|
||||
Class("Slow Poisoner", [Stat.condition]),
|
||||
Class("Bastard Lunatic", [Stat.condition]),
|
||||
Class("Lowling", [Stat.wisdom]),
|
||||
Class("Birdrider", [Stat.wisdom]),
|
||||
Class("Vermineer", [Stat.intelligence]),
|
||||
Class("Ur-Paladin", [StatType.wisdom, StatType.condition]),
|
||||
Class("Voodoo Princess", [StatType.intelligence, StatType.charisma]),
|
||||
Class("Robot Monk", [StatType.strength]),
|
||||
Class("Mu-Fu Monk", [StatType.dexterity]),
|
||||
Class("Mage Illusioner", [StatType.intelligence, StatType.mp_max]),
|
||||
Class("Shiv-Knight", [StatType.dexterity]),
|
||||
Class("Inner Mason", [StatType.condition]),
|
||||
Class("Fighter/Organist", [StatType.charisma, StatType.strength]),
|
||||
Class("Puma Burgular", [StatType.dexterity]),
|
||||
Class("Runeloremaster", [StatType.wisdom]),
|
||||
Class("Hunter Strangler", [StatType.dexterity, StatType.intelligence]),
|
||||
Class("Battle-Felon", [StatType.strength]),
|
||||
Class("Tickle-Mimic", [StatType.wisdom, StatType.intelligence]),
|
||||
Class("Slow Poisoner", [StatType.condition]),
|
||||
Class("Bastard Lunatic", [StatType.condition]),
|
||||
Class("Lowling", [StatType.wisdom]),
|
||||
Class("Birdrider", [StatType.wisdom]),
|
||||
Class("Vermineer", [StatType.intelligence]),
|
||||
]
|
||||
|
||||
TITLES = [
|
||||
|
||||
+124
@@ -0,0 +1,124 @@
|
||||
import typing as T
|
||||
|
||||
from pqcli import random
|
||||
|
||||
|
||||
def generate_name() -> str:
|
||||
parts = [
|
||||
"br|cr|dr|fr|gr|j|kr|l|m|n|pr||||r|sh|tr|v|wh|x|y|z".split("|"),
|
||||
"a|a|e|e|i|i|o|o|u|u|ae|ie|oo|ou".split("|"),
|
||||
"b|ck|d|g|k|m|n|p|t|v|x|z".split("|"),
|
||||
]
|
||||
result = ""
|
||||
for i in range(6):
|
||||
result += random.choice(parts[i % 3])
|
||||
return result.title()
|
||||
|
||||
|
||||
def to_roman(num: int) -> str:
|
||||
if not num:
|
||||
return "N"
|
||||
|
||||
ret = ""
|
||||
|
||||
def _rome(dn: int, ds: str) -> bool:
|
||||
nonlocal num, ret
|
||||
if num >= dn:
|
||||
num -= dn
|
||||
ret += ds
|
||||
return True
|
||||
return False
|
||||
|
||||
if num < 0:
|
||||
ret = "-"
|
||||
num = -num
|
||||
|
||||
while _rome(1000, "M"):
|
||||
pass
|
||||
_rome(900, "CM")
|
||||
_rome(500, "D")
|
||||
_rome(400, "CD")
|
||||
while _rome(100, "C"):
|
||||
pass
|
||||
_rome(90, "XC")
|
||||
_rome(50, "L")
|
||||
_rome(40, "XL")
|
||||
while _rome(10, "X"):
|
||||
pass
|
||||
_rome(9, "IX")
|
||||
_rome(5, "V")
|
||||
_rome(4, "IV")
|
||||
while _rome(1, "I"):
|
||||
pass
|
||||
return ret
|
||||
|
||||
|
||||
def act_name(act: int) -> str:
|
||||
if act == 0:
|
||||
return "Prologue"
|
||||
return f"Act {to_roman(act)}"
|
||||
|
||||
|
||||
def plural(subject: str) -> str:
|
||||
if subject.endswith("y"):
|
||||
return subject[:-1] + "ies"
|
||||
if subject.endswith("us"):
|
||||
return subject[:-2] + "i"
|
||||
if subject.endswith(("ch", "x", "s", "sh")):
|
||||
return subject + "es"
|
||||
if subject.endswith("f"):
|
||||
return subject[:-1] + "ves"
|
||||
if subject.endswith(("man", "Man")):
|
||||
return subject[:-2] + "en"
|
||||
return subject + "s"
|
||||
|
||||
|
||||
def indefinite(subject: str, qty: int) -> str:
|
||||
if qty == 1:
|
||||
if subject.startswith(tuple("AEIOU?aeiou?")):
|
||||
return "an " + subject
|
||||
return "a " + subject
|
||||
return str(qty) + " " + plural(subject)
|
||||
|
||||
|
||||
def definite(subject: str, qty: int) -> str:
|
||||
if qty > 1:
|
||||
subject = plural(subject)
|
||||
return "the " + subject
|
||||
|
||||
|
||||
def prefix(a: T.List[str], m: int, subject: str, sep: str = " ") -> str:
|
||||
m = abs(m)
|
||||
if m < 1 or m > len(a):
|
||||
return subject
|
||||
return a[m - 1] + sep + subject
|
||||
|
||||
|
||||
def sick(m: int, subject: str) -> str:
|
||||
m = 6 - abs(m)
|
||||
return prefix(
|
||||
["dead", "comatose", "crippled", "sick", "undernourished"], m, subject
|
||||
)
|
||||
|
||||
|
||||
def young(m: int, subject: str) -> str:
|
||||
m = 6 - abs(m)
|
||||
return prefix(
|
||||
["foetal", "baby", "preadolescent", "teenage", "underage"], m, subject
|
||||
)
|
||||
|
||||
|
||||
def big(m: int, subject: str) -> str:
|
||||
return prefix(
|
||||
["greater", "massive", "enormous", "giant", "titanic"], m, subject
|
||||
)
|
||||
|
||||
|
||||
def special(m: int, subject: str) -> str:
|
||||
if " " in subject:
|
||||
return prefix(
|
||||
["veteran", "cursed", "warrior", "undead", "demon"], m, subject
|
||||
)
|
||||
return prefix(
|
||||
["Battle-", "cursed ", "Were-", "undead ", "demon "], m, subject, ""
|
||||
)
|
||||
+656
-108
@@ -1,10 +1,14 @@
|
||||
import datetime
|
||||
import enum
|
||||
import itertools
|
||||
import logging
|
||||
import typing as T
|
||||
from dataclasses import dataclass, field
|
||||
from dataclasses import dataclass
|
||||
|
||||
from pqcli import random
|
||||
from pqcli.config import *
|
||||
from pqcli.lingo import *
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def level_up_time(level: int) -> int:
|
||||
@@ -12,32 +16,58 @@ def level_up_time(level: int) -> int:
|
||||
return 20 * level * 60
|
||||
|
||||
|
||||
def generate_name() -> str:
|
||||
parts = [
|
||||
"br|cr|dr|fr|gr|j|kr|l|m|n|pr||||r|sh|tr|v|wh|x|y|z".split("|"),
|
||||
"a|a|e|e|i|i|o|o|u|u|ae|ie|oo|ou".split("|"),
|
||||
"b|ck|d|g|k|m|n|p|t|v|x|z".split("|"),
|
||||
]
|
||||
result = ""
|
||||
for i in range(6):
|
||||
result += random.choice(parts[i % 3])
|
||||
return result.title()
|
||||
class Bar:
|
||||
def __init__(self, max_: int, position: float = 0.0) -> None:
|
||||
self.position = position
|
||||
self.max_ = max_
|
||||
|
||||
def reset(self, new_max: int, position: float = 0.0) -> None:
|
||||
self.max_ = new_max
|
||||
self.position = position
|
||||
|
||||
def increment(self, inc: float) -> None:
|
||||
self.reposition(self.position + inc)
|
||||
|
||||
@property
|
||||
def done(self) -> bool:
|
||||
return self.position >= self.max_
|
||||
|
||||
def reposition(self, new_pos: float) -> None:
|
||||
self.position = float(min(new_pos, self.max_))
|
||||
|
||||
|
||||
@dataclass
|
||||
class Stats:
|
||||
def __init__(self) -> None:
|
||||
self.values = {stat: 0 for stat in Stat}
|
||||
def __init__(self, values: T.Dict[StatType, int]) -> None:
|
||||
self.values = values
|
||||
|
||||
def __iter__(self) -> T.Iterable[T.Tuple[Stat, int]]:
|
||||
def __iter__(self) -> T.Iterator[T.Tuple[StatType, int]]:
|
||||
return iter(self.values.items())
|
||||
|
||||
def __setitem__(self, stat: Stat, value: int) -> None:
|
||||
self.values[stat] = value
|
||||
|
||||
def __getitem__(self, stat: Stat) -> int:
|
||||
def __getitem__(self, stat: StatType) -> int:
|
||||
return self.values[stat]
|
||||
|
||||
def increment(self, stat: StatType, qty: int = 1) -> None:
|
||||
self.values[stat] += qty
|
||||
logger.info("Increased %s to %d", stat.value, self[stat])
|
||||
|
||||
|
||||
class QuestBook:
|
||||
def __init__(self) -> None:
|
||||
self._quests: T.List[str] = []
|
||||
self.quest_bar = Bar(max_=1)
|
||||
self.monster: T.Optional[Monster] = None
|
||||
|
||||
@property
|
||||
def current(self) -> T.Optional[str]:
|
||||
if self._quests:
|
||||
return self._quests[-1]
|
||||
return None
|
||||
|
||||
def add(self, name: str) -> None:
|
||||
logger.info("Commencing quest: %s", name)
|
||||
self._quests = self._quests[-100:]
|
||||
self._quests.append(name)
|
||||
|
||||
|
||||
@dataclass
|
||||
class Item:
|
||||
@@ -45,58 +75,113 @@ class Item:
|
||||
quantity: int
|
||||
|
||||
|
||||
class Inventory:
|
||||
def __init__(self, capacity: int = 0) -> None:
|
||||
self._gold = 0
|
||||
self._items: T.List[Item] = []
|
||||
self.encum_bar = Bar(max_=capacity)
|
||||
|
||||
@property
|
||||
def gold(self) -> int:
|
||||
return self._gold
|
||||
|
||||
def __len__(self) -> int:
|
||||
return len(self._items)
|
||||
|
||||
def __iter__(self) -> T.Iterator[Item]:
|
||||
return iter(self._items)
|
||||
|
||||
def add_gold(self, quantity: int) -> None:
|
||||
logger.info(
|
||||
"%s %s",
|
||||
"Spent" if quantity < 0 else "Got paid",
|
||||
indefinite("gold piece", abs(quantity)),
|
||||
)
|
||||
self._gold += quantity
|
||||
|
||||
def at(self, index: int) -> Item:
|
||||
return self._items[index]
|
||||
|
||||
def pop(self, index: int) -> None:
|
||||
item = self._items[index]
|
||||
logger.info("Lost %s (qty=%d)", item.name, item.quantity)
|
||||
self._items.pop(index)
|
||||
self.sync_encumbrance()
|
||||
|
||||
def add(self, item_name: str, quantity: int) -> None:
|
||||
logger.info("Gained %s", indefinite(item_name, quantity))
|
||||
for item in self._items:
|
||||
if item.name == item_name:
|
||||
item.quantity += quantity
|
||||
break
|
||||
else:
|
||||
self._items.append(Item(name=item_name, quantity=quantity))
|
||||
self.sync_encumbrance()
|
||||
|
||||
def sync_encumbrance(self) -> None:
|
||||
self.encum_bar.reposition(sum(item.quantity for item in self._items))
|
||||
|
||||
def set_capacity(self, capacity: int) -> None:
|
||||
self.encum_bar.reset(capacity, self.encum_bar.position)
|
||||
|
||||
|
||||
@dataclass
|
||||
class Spell:
|
||||
name: str
|
||||
level: int
|
||||
|
||||
|
||||
class Inventory:
|
||||
def __init__(self, items: T.Optional[T.List[Item]] = []) -> None:
|
||||
self.items = [] if items is None else items
|
||||
|
||||
def add(self, item_name: str, quantity: int) -> None:
|
||||
for item in self.items:
|
||||
if item.name == item_name:
|
||||
item.quantity += quantity
|
||||
break
|
||||
else:
|
||||
self.items.append(Item(name=item_name, quantity=quantity))
|
||||
|
||||
|
||||
class SpellBook:
|
||||
def __init__(self, spells: T.Optional[T.List[Item]] = None) -> None:
|
||||
self.spells = [] if spells is None else spells
|
||||
def __init__(self) -> None:
|
||||
self.spells: T.List[Spell] = []
|
||||
|
||||
def __iter__(self) -> T.Iterator[Spell]:
|
||||
return iter(self.spells)
|
||||
|
||||
def add(self, spell_name: str, level: int) -> None:
|
||||
for item in self.spells:
|
||||
if item.name == spell_name:
|
||||
item.level += level
|
||||
for spell in self.spells:
|
||||
if spell.name == spell_name:
|
||||
spell.level += level
|
||||
logger.info("Learned %s at level %d", spell_name, spell.level)
|
||||
break
|
||||
else:
|
||||
self.spells.append(Spell(name=spell_name, level=level))
|
||||
|
||||
|
||||
class Bar:
|
||||
def __init__(self, max_: int, position: int = 0) -> None:
|
||||
self.position = position
|
||||
self.max_ = max_
|
||||
|
||||
def reset(self, new_max: int, position: int = 0) -> None:
|
||||
self.max_ = new_max
|
||||
self.position = position
|
||||
|
||||
|
||||
class TaskType(enum.Enum):
|
||||
task = "task"
|
||||
plot = "plot"
|
||||
logger.info("Learned %s at level %d", spell_name, level)
|
||||
|
||||
|
||||
@dataclass
|
||||
class Task:
|
||||
task_type: TaskType
|
||||
length: int
|
||||
class BaseTask:
|
||||
description: str
|
||||
duration: int
|
||||
|
||||
|
||||
@dataclass
|
||||
class KillTask(BaseTask):
|
||||
monster: T.Optional[Monster] = None
|
||||
|
||||
|
||||
class BuyTask(BaseTask):
|
||||
pass
|
||||
|
||||
|
||||
class HeadingToKillingFieldsTask(BaseTask):
|
||||
pass
|
||||
|
||||
|
||||
class HeadingToMarketTask(BaseTask):
|
||||
pass
|
||||
|
||||
|
||||
class SellTask(BaseTask):
|
||||
pass
|
||||
|
||||
|
||||
class RegularTask(BaseTask):
|
||||
pass
|
||||
|
||||
|
||||
class PlotTask(RegularTask):
|
||||
pass
|
||||
|
||||
|
||||
class Player:
|
||||
@@ -113,53 +198,66 @@ class Player:
|
||||
self.race: Race = race
|
||||
self.class_: Class = class_
|
||||
self.stats: Stats = stats
|
||||
self.level = 1
|
||||
self.act = 0
|
||||
|
||||
self.queue: T.List[Task] = [
|
||||
Task(
|
||||
TaskType.task,
|
||||
10,
|
||||
"Experiencing an enigmatic and foreboding night vision",
|
||||
self.exp_bar = Bar(max_=level_up_time(1))
|
||||
self.level = 1
|
||||
|
||||
self.task_bar = Bar(max_=0)
|
||||
self.task: T.Optional[BaseTask] = None
|
||||
self.queue: T.List[BaseTask] = [
|
||||
RegularTask(
|
||||
"Experiencing an enigmatic and foreboding night vision", 10_000
|
||||
),
|
||||
Task(
|
||||
TaskType.task,
|
||||
6,
|
||||
"Much is revealed about that wise old bastard you'd underestimated",
|
||||
RegularTask(
|
||||
"Much is revealed about that wise old bastard "
|
||||
"you'd underestimated",
|
||||
6_000,
|
||||
),
|
||||
Task(
|
||||
TaskType.task,
|
||||
6,
|
||||
"A shocking series of events leaves you alone and bewildered, but resolute",
|
||||
RegularTask(
|
||||
"A shocking series of events leaves you "
|
||||
"alone and bewildered, but resolute",
|
||||
6_000,
|
||||
),
|
||||
Task(
|
||||
TaskType.task,
|
||||
4,
|
||||
"Drawing upon an unrealized reserve of determination, you set out on a long and dangerous journey",
|
||||
RegularTask(
|
||||
"Drawing upon an unrealized reserve of determination, "
|
||||
"you set out on a long and dangerous journey",
|
||||
4_000,
|
||||
),
|
||||
Task(TaskType.plot, 2, "Loading"),
|
||||
PlotTask(f"Loading {act_name(self.act + 1)}", 2_000),
|
||||
]
|
||||
|
||||
self.exp_bar = Bar(max_=level_up_time(1))
|
||||
self.encum_bar = Bar(max_=stats[Stat.strength] + 10)
|
||||
self.quest_bar = Bar(max_=1)
|
||||
self.task_bar = Bar(max_=2000)
|
||||
self.plot_bar = Bar(max_=sum(task.length for task in self.queue))
|
||||
self.plot_bar = Bar(max_=sum(task.duration for task in self.queue))
|
||||
self.quest_book = QuestBook()
|
||||
|
||||
self.inventory = Inventory([Item(name="Gold", quantity=0)])
|
||||
self.spell_book = SpellBook()
|
||||
self.equipment: T.Dict[Equipment, str] = {
|
||||
Equipment.weapon: "Sharp Rock",
|
||||
Equipment.hauberk: "-3 Burlap",
|
||||
self.inventory = Inventory(capacity=10 + self.stats[StatType.strength])
|
||||
self.equipment: T.Dict[EquipmentType, str] = {
|
||||
EquipmentType.weapon: "Sharp Rock",
|
||||
EquipmentType.hauberk: "-3 Burlap",
|
||||
}
|
||||
|
||||
self.spell_book = SpellBook()
|
||||
|
||||
self.set_task(RegularTask("Loading", 2_000))
|
||||
|
||||
def set_task(self, task: BaseTask) -> None:
|
||||
self.task = task
|
||||
self.task_bar.reset(task.duration)
|
||||
logger.info("%s...", task.description)
|
||||
|
||||
def equip_price(self) -> int:
|
||||
return 5 * (self.level ** 2) + 10 * self.level + 20
|
||||
|
||||
def level_up(self) -> None:
|
||||
self.level += 1
|
||||
self.stats[Stat.hp_max] += (
|
||||
self.stats[Stat.condition] // 3 + 1 + random.below(4)
|
||||
logger.info("Leveled up to level %d!", self.level)
|
||||
self.stats.increment(
|
||||
StatType.hp_max,
|
||||
self.stats[StatType.condition] // 3 + 1 + random.below(4),
|
||||
)
|
||||
self.stats[Stat.mp_max] += (
|
||||
self.stats[Stat.intelligence] // 3 + 1 + random.below(4)
|
||||
self.stats.increment(
|
||||
StatType.mp_max,
|
||||
self.stats[StatType.intelligence] // 3 + 1 + random.below(4),
|
||||
)
|
||||
self.win_stat()
|
||||
self.win_stat()
|
||||
@@ -167,50 +265,496 @@ class Player:
|
||||
self.exp_bar.reset(level_up_time(self.level))
|
||||
|
||||
def win_stat(self) -> bool:
|
||||
chosen_stat: T.Optional[StatType] = None
|
||||
|
||||
if random.odds(1, 2):
|
||||
chosen_stat = random.choice(list(Stat))
|
||||
chosen_stat = random.choice(list(StatType))
|
||||
else:
|
||||
# favor the beststat so it will tend to clump
|
||||
# favor the best stat so it will tend to clump
|
||||
t = sum(value ** 2 for _stat, value in self.stats)
|
||||
t = random.below(t)
|
||||
chosen_stat: Stat = None
|
||||
chosen_stat = None
|
||||
for stat, value in self.stats:
|
||||
chosen_stat = stat
|
||||
t -= value ** 2
|
||||
if t < 0:
|
||||
return False
|
||||
break
|
||||
|
||||
self.stats[chosen_stat] += 1
|
||||
if chosen_stat == Stat.strength:
|
||||
self.encum_bar.reset(
|
||||
10 + self.stats[Stat.strength], self.encum_bar.position
|
||||
)
|
||||
assert chosen_stat is not None
|
||||
self.stats.increment(chosen_stat)
|
||||
if chosen_stat == StatType.strength:
|
||||
self.inventory.set_capacity(10 + self.stats[StatType.strength])
|
||||
return True
|
||||
|
||||
def win_spell(self) -> None:
|
||||
self.spell_book.add(
|
||||
SPELLS[
|
||||
random.below_low(
|
||||
min(self.stats[Stat.wisdom] + self.level, len(SPELLS))
|
||||
min(self.stats[StatType.wisdom] + self.level, len(SPELLS))
|
||||
)
|
||||
],
|
||||
1,
|
||||
)
|
||||
|
||||
def win_equipment(self) -> None:
|
||||
choice = random.choice(list(EquipmentType))
|
||||
|
||||
stuff: T.List[Equipment]
|
||||
better: T.List[Modifier]
|
||||
worse: T.List[Modifier]
|
||||
|
||||
if choice == EquipmentType.weapon:
|
||||
stuff = WEAPONS
|
||||
better = OFFENSE_ATTRIB
|
||||
worse = OFFENSE_BAD
|
||||
else:
|
||||
stuff = SHIELDS if choice == EquipmentType.shield else ARMORS
|
||||
better = DEFENSE_ATTRIB
|
||||
worse = DEFENSE_BAD
|
||||
|
||||
equipment = pick_equipment(stuff, self.level)
|
||||
name = equipment.name
|
||||
plus = self.level - equipment.quality
|
||||
if plus < 0:
|
||||
modifier_pool = worse
|
||||
else:
|
||||
modifier_pool = better
|
||||
count = 0
|
||||
while count < 2 and plus:
|
||||
modifier = random.choice(modifier_pool)
|
||||
if modifier.name in name:
|
||||
break # no repeats
|
||||
if abs(plus) < abs(modifier.quality):
|
||||
break # too much
|
||||
name = modifier.name + " " + name
|
||||
plus -= modifier.quality
|
||||
count += 1
|
||||
|
||||
if plus < 0:
|
||||
name = f"{plus} {name}"
|
||||
if plus > 0:
|
||||
name = f"+{plus} {name}"
|
||||
|
||||
logger.info("Gained %s %s", name, choice.value)
|
||||
self.equipment[choice] = name
|
||||
|
||||
def win_item(self) -> None:
|
||||
self.inventory.add(special_item(), 1)
|
||||
|
||||
|
||||
class Simulation:
|
||||
def __init__(self, player: Player) -> None:
|
||||
self.player = player
|
||||
self.last_tick = datetime.datetime.now()
|
||||
self.elapsed = 0
|
||||
|
||||
def tick(self) -> None:
|
||||
if not self.player.task_bar.done:
|
||||
self.elapsed = 100
|
||||
self.player.task_bar.increment(100)
|
||||
return
|
||||
|
||||
# gain XP / level up
|
||||
gain = self.player.task and isinstance(self.player.task, KillTask)
|
||||
if gain:
|
||||
if self.player.exp_bar.done:
|
||||
self.player.level_up()
|
||||
else:
|
||||
self.player.exp_bar.increment(self.player.task_bar.max_ / 1000)
|
||||
|
||||
# advance quest
|
||||
if gain and self.player.act >= 1:
|
||||
if (
|
||||
self.player.quest_book.quest_bar.done
|
||||
or self.player.quest_book.current is None
|
||||
):
|
||||
self.complete_quest()
|
||||
else:
|
||||
self.player.quest_book.quest_bar.increment(
|
||||
self.player.task_bar.max_ / 1000
|
||||
)
|
||||
|
||||
# advance plot
|
||||
if gain or not self.player.act:
|
||||
if self.player.plot_bar.done:
|
||||
self.interplot_cinematic()
|
||||
else:
|
||||
self.player.plot_bar.increment(
|
||||
self.player.task_bar.max_ / 1000
|
||||
)
|
||||
|
||||
self.dequeue()
|
||||
|
||||
def dequeue(self) -> None:
|
||||
while self.player.task_bar.done:
|
||||
if isinstance(self.player.task, KillTask):
|
||||
if (
|
||||
self.player.task.monster is None
|
||||
or self.player.task.monster.item is None
|
||||
):
|
||||
# npc
|
||||
self.player.win_item()
|
||||
elif self.player.task.monster.item:
|
||||
self.player.inventory.add(
|
||||
(
|
||||
self.player.task.monster.name
|
||||
+ " "
|
||||
+ self.player.task.monster.item
|
||||
).lower(),
|
||||
1,
|
||||
)
|
||||
|
||||
elif isinstance(self.player.task, BuyTask):
|
||||
# buy some equipment
|
||||
self.player.inventory.add_gold(-self.player.equip_price())
|
||||
self.player.win_equipment()
|
||||
|
||||
elif isinstance(self.player.task, (HeadingToMarketTask, SellTask)):
|
||||
if isinstance(self.player.task, SellTask):
|
||||
item = self.player.inventory.at(0)
|
||||
amount = item.quantity * self.player.level
|
||||
if " of " in item.name:
|
||||
amount *= (1 + random.below_low(10)) * (
|
||||
1 + random.below_low(self.player.level)
|
||||
)
|
||||
self.player.inventory.pop(0)
|
||||
self.player.inventory.add_gold(amount)
|
||||
if len(self.player.inventory):
|
||||
item = self.player.inventory.at(0)
|
||||
self.player.set_task(
|
||||
SellTask(
|
||||
"Selling " + indefinite(item.name, item.quantity),
|
||||
1_000,
|
||||
)
|
||||
)
|
||||
break
|
||||
|
||||
elif isinstance(self.player.task, PlotTask):
|
||||
self.complete_act()
|
||||
|
||||
old = self.player.task
|
||||
if self.player.queue:
|
||||
self.player.set_task(self.player.queue.pop(0))
|
||||
elif self.player.inventory.encum_bar.done:
|
||||
self.player.set_task(
|
||||
HeadingToMarketTask(
|
||||
"Heading to market to sell loot", 4_000
|
||||
)
|
||||
)
|
||||
elif not isinstance(old, (KillTask, HeadingToKillingFieldsTask)):
|
||||
if self.player.inventory.gold > self.player.equip_price():
|
||||
self.player.set_task(
|
||||
BuyTask(
|
||||
"Negotiating purchase of better equipment", 5_000
|
||||
)
|
||||
)
|
||||
else:
|
||||
self.player.set_task(
|
||||
HeadingToKillingFieldsTask(
|
||||
"Heading to the killing fields", 4_000
|
||||
)
|
||||
)
|
||||
else:
|
||||
self.player.set_task(
|
||||
monster_task(
|
||||
self.player.level, self.player.quest_book.monster
|
||||
)
|
||||
)
|
||||
|
||||
def complete_act(self) -> None:
|
||||
self.player.act += 1
|
||||
self.player.plot_bar.reset(60 * 60 * (1 + 5 * self.player.act))
|
||||
if self.player.act > 1:
|
||||
self.player.win_item()
|
||||
self.player.win_equipment()
|
||||
|
||||
def complete_quest(self) -> None:
|
||||
self.player.quest_book.quest_bar.reset(50 + random.below_low(1000))
|
||||
if self.player.quest_book.current:
|
||||
logger.info("Quest completed: %s", self.player.quest_book.current)
|
||||
random.choice(
|
||||
[
|
||||
self.player.win_spell,
|
||||
self.player.win_equipment,
|
||||
self.player.win_stat,
|
||||
self.player.win_item,
|
||||
]
|
||||
)()
|
||||
|
||||
self.player.quest_book.monster = None
|
||||
caption = ""
|
||||
choice = random.below(5)
|
||||
if choice == 0:
|
||||
self.player.quest_book.monster = unnamed_monster(
|
||||
self.player.level, iterations=3
|
||||
)
|
||||
caption = "Exterminate " + definite(
|
||||
self.player.quest_book.monster.name, 2
|
||||
)
|
||||
elif choice == 1:
|
||||
caption = "Seek " + definite(interesting_item(), 1)
|
||||
elif choice == 2:
|
||||
caption = "Deliver this " + boring_item()
|
||||
elif choice == 3:
|
||||
caption = "Fetch me " + indefinite(boring_item(), 1)
|
||||
elif choice == 4:
|
||||
monster = unnamed_monster(self.player.level, iterations=1)
|
||||
caption = "Placate " + definite(monster.name, 2)
|
||||
else:
|
||||
raise AssertionError
|
||||
|
||||
self.player.quest_book.add(caption)
|
||||
|
||||
def interplot_cinematic(self) -> None:
|
||||
def enqueue(task: BaseTask) -> None:
|
||||
self.player.queue.append(task)
|
||||
self.dequeue()
|
||||
|
||||
choice = random.below(3)
|
||||
if choice == 0:
|
||||
enqueue(
|
||||
RegularTask(
|
||||
"Exhausted, you arrive at a friendly oasis "
|
||||
"in a hostile land",
|
||||
1_000,
|
||||
)
|
||||
)
|
||||
enqueue(
|
||||
RegularTask("You greet old friends and meet new allies", 2_000)
|
||||
)
|
||||
enqueue(
|
||||
RegularTask(
|
||||
"You are privy to a council of powerful do-gooders", 2_000
|
||||
)
|
||||
)
|
||||
enqueue(
|
||||
RegularTask("There is much to be done. You are chosen!", 1_000)
|
||||
)
|
||||
|
||||
elif choice == 1:
|
||||
enqueue(
|
||||
RegularTask(
|
||||
"Your quarry is in sight, "
|
||||
"but a mighty enemy bars your path!",
|
||||
1_000,
|
||||
)
|
||||
)
|
||||
|
||||
nemesis = named_monster(self.player.level + 3)
|
||||
|
||||
enqueue(
|
||||
RegularTask(
|
||||
f"A desperate struggle commences with {nemesis}", 4_000
|
||||
)
|
||||
)
|
||||
|
||||
s = random.below(3)
|
||||
for i in itertools.count(start=1):
|
||||
if i > random.below(1 + self.player.act + 1):
|
||||
break
|
||||
s += 1 + random.below(2)
|
||||
if s % 3 == 0:
|
||||
enqueue(
|
||||
RegularTask(
|
||||
f"Locked in grim combat with {nemesis}", 2_000
|
||||
)
|
||||
)
|
||||
elif s % 3 == 1:
|
||||
enqueue(
|
||||
RegularTask(
|
||||
f"{nemesis} seems to have the upper hand", 2_000
|
||||
)
|
||||
)
|
||||
elif s % 3 == 2:
|
||||
enqueue(
|
||||
RegularTask(
|
||||
f"You seem to gain the advantage over {nemesis}",
|
||||
2_000,
|
||||
)
|
||||
)
|
||||
else:
|
||||
raise AssertionError()
|
||||
|
||||
enqueue(
|
||||
RegularTask(
|
||||
f"Victory! {nemesis} is slain! "
|
||||
"Exhausted, you lose conciousness",
|
||||
3_000,
|
||||
)
|
||||
)
|
||||
enqueue(
|
||||
RegularTask(
|
||||
"You awake in a friendly place, but the road awaits", 2_000
|
||||
)
|
||||
)
|
||||
|
||||
elif choice == 2:
|
||||
nemesis = impressive_guy()
|
||||
enqueue(
|
||||
RegularTask(
|
||||
"Oh sweet relief! "
|
||||
f"You've reached the protection of the good {nemesis}",
|
||||
2_000,
|
||||
)
|
||||
)
|
||||
enqueue(
|
||||
RegularTask(
|
||||
"There is rejoicing, "
|
||||
f"and an unnerving encouter with {nemesis} in private",
|
||||
3_000,
|
||||
)
|
||||
)
|
||||
enqueue(
|
||||
RegularTask(
|
||||
f"You forget your {boring_item()} and go back to get it",
|
||||
2_000,
|
||||
)
|
||||
)
|
||||
enqueue(
|
||||
RegularTask(
|
||||
"What's this!? You overhear something shocking!", 2_000
|
||||
)
|
||||
)
|
||||
enqueue(
|
||||
RegularTask(
|
||||
f"Could {nemesis} be a dirty double-dealer?", 2_000
|
||||
)
|
||||
)
|
||||
enqueue(
|
||||
RegularTask(
|
||||
"Who can possibly be trusted with this news!? ... "
|
||||
"Oh yes, of course",
|
||||
3_000,
|
||||
)
|
||||
)
|
||||
|
||||
else:
|
||||
raise AssertionError
|
||||
|
||||
enqueue(PlotTask(f"Loading {act_name(self.player.act + 1)}", 1_000))
|
||||
|
||||
|
||||
def special_item() -> str:
|
||||
return interesting_item() + " of " + random.choice(ITEM_OFS)
|
||||
return interesting_item() + " of " + T.cast(str, random.choice(ITEM_OFS))
|
||||
|
||||
|
||||
def interesting_item() -> str:
|
||||
return random.choice(ITEM_ATTRIB) + " " + random.choice(SPECIALS)
|
||||
return (
|
||||
T.cast(str, random.choice(ITEM_ATTRIB))
|
||||
+ " "
|
||||
+ T.cast(str, random.choice(SPECIALS))
|
||||
)
|
||||
|
||||
|
||||
def boring_item() -> str:
|
||||
return random.choice(BORING_ITEMS)
|
||||
return T.cast(str, random.choice(BORING_ITEMS))
|
||||
|
||||
|
||||
def impressive_guy() -> str:
|
||||
return T.cast(str, random.choice(IMPRESSIVE_TITLES)) + (
|
||||
" of the " + T.cast(Race, random.choice(RACES)).name
|
||||
if random.below(2)
|
||||
else " of " + generate_name()
|
||||
)
|
||||
|
||||
|
||||
def unnamed_monster(level: int, iterations: int) -> Monster:
|
||||
result = T.cast(Monster, random.choice(MONSTERS))
|
||||
for _ in range(iterations):
|
||||
alternative = T.cast(Monster, random.choice(MONSTERS))
|
||||
if abs(level - alternative.level) < abs(level - result.level):
|
||||
result = alternative
|
||||
return result
|
||||
|
||||
|
||||
def named_monster(level: int) -> str:
|
||||
monster = unnamed_monster(level, iterations=4)
|
||||
return generate_name() + " the " + monster.name
|
||||
|
||||
|
||||
def pick_equipment(source: T.List[Equipment], goal: int) -> Equipment:
|
||||
result = T.cast(Equipment, random.choice(source))
|
||||
for _ in range(5):
|
||||
alternative = T.cast(Equipment, random.choice(source))
|
||||
if abs(goal - alternative.quality) < abs(goal - result.quality):
|
||||
result = alternative
|
||||
return result
|
||||
|
||||
|
||||
def monster_task(
|
||||
player_level: int, quest_monster: T.Optional[Monster]
|
||||
) -> KillTask:
|
||||
level = player_level
|
||||
for _ in range(level):
|
||||
if random.odds(2, 5):
|
||||
level += random.below(2) * 2 - 1
|
||||
if level < 1:
|
||||
level = 1
|
||||
|
||||
definite = False
|
||||
monster: T.Optional[Monster] = None
|
||||
if random.odds(1, 25):
|
||||
# use an NPC every once in a while
|
||||
race = random.choice(RACES)
|
||||
if random.odds(1, 2):
|
||||
result = "passing " + race.name + " " + random.choice(CLASSES).name
|
||||
else:
|
||||
result = (
|
||||
random.choice_low(TITLES)
|
||||
+ " "
|
||||
+ generate_name()
|
||||
+ " the "
|
||||
+ race.name
|
||||
)
|
||||
definite = True
|
||||
lev = level
|
||||
elif quest_monster and random.odds(1, 4):
|
||||
# use the quest monster
|
||||
monster = quest_monster
|
||||
result = monster.name
|
||||
lev = monster.level
|
||||
else:
|
||||
# pick the monster out of so many random ones closest to the level we want
|
||||
monster = unnamed_monster(level, iterations=5)
|
||||
result = monster.name
|
||||
lev = monster.level
|
||||
|
||||
qty = 1
|
||||
if level - lev > 10:
|
||||
# lev is too low. multiply
|
||||
qty = (level + random.below(max(lev, 1))) // max(lev, 1)
|
||||
if qty < 1:
|
||||
qty = 1
|
||||
level //= qty
|
||||
|
||||
if level - lev <= -10:
|
||||
result = "imaginary " + result
|
||||
elif level - lev < -5:
|
||||
i = 10 + level - lev
|
||||
i = 5 - random.below(i + 1)
|
||||
result = sick(i, young(lev - level - i, result))
|
||||
elif level - lev < 0 and random.below(2) == 1:
|
||||
result = sick(level - lev, result)
|
||||
elif level - lev < 0:
|
||||
result = young(level - lev, result)
|
||||
elif level - lev >= 10:
|
||||
result = "messianic " + result
|
||||
elif level - lev > 5:
|
||||
i = 10 - (level - lev)
|
||||
i = 5 - random.below(i + 1)
|
||||
result = big(i, special(level - lev - i, result))
|
||||
elif level - lev > 0 and random.below(2) == 1:
|
||||
result = big(level - lev, result)
|
||||
elif level - lev > 0:
|
||||
result = special(level - lev, result)
|
||||
|
||||
lev = level
|
||||
level = lev * qty
|
||||
if not definite:
|
||||
result = indefinite(result, qty)
|
||||
|
||||
duration = (2 * 3 * level * 1000) // player_level
|
||||
return KillTask(f"Executing {result}", duration, monster=monster)
|
||||
|
||||
|
||||
class Roster:
|
||||
@@ -226,13 +770,17 @@ class StatsBuilder:
|
||||
self.history: T.List[Stats] = []
|
||||
|
||||
def roll(self) -> Stats:
|
||||
stats = Stats()
|
||||
for stat in PRIME_STATS:
|
||||
stats[stat] = (
|
||||
3 + random.below(6) + random.below(6) + random.below(6)
|
||||
values: T.Dict[StatType, int] = {
|
||||
stat: 3 + random.below(6) + random.below(6) + random.below(6)
|
||||
for stat in PRIME_STATS
|
||||
}
|
||||
values[StatType.hp_max] = (
|
||||
random.below(8) + values[StatType.condition] // 6
|
||||
)
|
||||
stats[Stat.hp_max] = random.below(8) + stats[Stat.condition] // 6
|
||||
stats[Stat.mp_max] = random.below(8) + stats[Stat.intelligence] // 6
|
||||
values[StatType.mp_max] = (
|
||||
random.below(8) + values[StatType.intelligence] // 6
|
||||
)
|
||||
stats = Stats(values)
|
||||
self.history.append(stats)
|
||||
return stats
|
||||
|
||||
|
||||
+6
-2
@@ -6,8 +6,12 @@ def seed(source: T.Any) -> None:
|
||||
random.seed(source)
|
||||
|
||||
|
||||
def choice(source: T.Iterable[T.Any]) -> T.Any:
|
||||
return random.choice(source)
|
||||
def choice(source: T.Sequence[T.Any]) -> T.Any:
|
||||
return source[below(len(source))]
|
||||
|
||||
|
||||
def choice_low(source: T.Sequence[T.Any]) -> T.Any:
|
||||
return source[below_low(len(source))]
|
||||
|
||||
|
||||
def below(num: int) -> int:
|
||||
|
||||
Reference in New Issue
Block a user