mechanic: port game logic

This commit is contained in:
rr-
2018-11-05 21:04:39 +01:00
parent c458938225
commit 35083e42e9
4 changed files with 1215 additions and 543 deletions
+428 -432
View File
@@ -3,14 +3,7 @@ import typing as T
from dataclasses import dataclass from dataclasses import dataclass
class Traits(enum.Enum): class StatType(enum.Enum):
name = "Name"
race = "Race"
class_ = "Class"
level = "Level"
class Stat(enum.Enum):
strength = "STR" strength = "STR"
condition = "CON" condition = "CON"
dexterity = "DEX" dexterity = "DEX"
@@ -21,7 +14,7 @@ class Stat(enum.Enum):
mp_max = "MP Max" mp_max = "MP Max"
class Equipment(enum.Enum): class EquipmentType(enum.Enum):
weapon = "Weapon" weapon = "Weapon"
shield = "Shield" shield = "Shield"
helm = "Helm" helm = "Helm"
@@ -36,15 +29,15 @@ class Equipment(enum.Enum):
PRIME_STATS = [ PRIME_STATS = [
Stat.strength, StatType.strength,
Stat.condition, StatType.condition,
Stat.dexterity, StatType.dexterity,
Stat.intelligence, StatType.intelligence,
Stat.wisdom, StatType.wisdom,
Stat.charisma, StatType.charisma,
] ]
ALL_STATS = PRIME_STATS + [Stat.hp_max, Stat.mp_max] ALL_STATS = PRIME_STATS + [StatType.hp_max, StatType.mp_max]
SPELLS = [ SPELLS = [
@@ -97,114 +90,157 @@ SPELLS = [
"Infinite Confusion", "Infinite Confusion",
] ]
@dataclass
class Modifier:
name: str
quality: int
OFFENSE_ATTRIB = [ OFFENSE_ATTRIB = [
"Polished|+1", Modifier("Polished", 1),
"Serrated|+1", Modifier("Serrated", 1),
"Heavy|+1", Modifier("Heavy", 1),
"Pronged|+2", Modifier("Pronged", 2),
"Steely|+2", Modifier("Steely", 2),
"Vicious|+3", Modifier("Vicious", 3),
"Venomed|+4", Modifier("Venomed", 4),
"Stabbity|+4", Modifier("Stabbity", 4),
"Dancing|+5", Modifier("Dancing", 5),
"Invisible|+6", Modifier("Invisible", 6),
"Vorpal|+7", Modifier("Vorpal", 7),
] ]
DEFENSE_ATTRIB = [ DEFENSE_ATTRIB = [
"Studded|+1", Modifier("Studded", 1),
"Banded|+2", Modifier("Banded", 2),
"Gilded|+2", Modifier("Gilded", 2),
"Festooned|+3", Modifier("Festooned", 3),
"Holy|+4", Modifier("Holy", 4),
"Cambric|+1", Modifier("Cambric", 1),
"Fine|+4", Modifier("Fine", 4),
"Impressive|+5", Modifier("Impressive", 5),
"Custom|+3", 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 = [ SHIELDS = [
"Parasol|0", Equipment("Parasol", 0),
"Pie Plate|1", Equipment("Pie Plate", 1),
"Garbage Can Lid|2", Equipment("Garbage Can Lid", 2),
"Buckler|3", Equipment("Buckler", 3),
"Plexiglass|4", Equipment("Plexiglass", 4),
"Fender|4", Equipment("Fender", 4),
"Round Shield|5", Equipment("Round Shield", 5),
"Carapace|5", Equipment("Carapace", 5),
"Scutum|6", Equipment("Scutum", 6),
"Propugner|6", Equipment("Propugner", 6),
"Kite Shield|7", Equipment("Kite Shield", 7),
"Pavise|8", Equipment("Pavise", 8),
"Tower Shield|9", Equipment("Tower Shield", 9),
"Baroque Shield|11", Equipment("Baroque Shield", 11),
"Aegis|12", Equipment("Aegis", 12),
"Magnetic Field|18", Equipment("Magnetic Field", 18),
] ]
ARMORS = [ ARMORS = [
"Lace|1", Equipment("Lace", 1),
"Macrame|2", Equipment("Macrame", 2),
"Burlap|3", Equipment("Burlap", 3),
"Canvas|4", Equipment("Canvas", 4),
"Flannel|5", Equipment("Flannel", 5),
"Chamois|6", Equipment("Chamois", 6),
"Pleathers|7", Equipment("Pleathers", 7),
"Leathers|8", Equipment("Leathers", 8),
"Bearskin|9", Equipment("Bearskin", 9),
"Ringmail|10", Equipment("Ringmail", 10),
"Scale Mail|12", Equipment("Scale Mail", 12),
"Chainmail|14", Equipment("Chainmail", 14),
"Splint Mail|15", Equipment("Splint Mail", 15),
"Platemail|16", Equipment("Platemail", 16),
"ABS|17", Equipment("ABS", 17),
"Kevlar|18", Equipment("Kevlar", 18),
"Titanium|19", Equipment("Titanium", 19),
"Mithril Mail|20", Equipment("Mithril Mail", 20),
"Diamond Mail|25", Equipment("Diamond Mail", 25),
"Plasma|30", Equipment("Plasma", 30),
] ]
WEAPONS = [ WEAPONS = [
"Stick|0", Equipment("Stick", 0),
"Broken Bottle|1", Equipment("Broken Bottle", 1),
"Shiv|1", Equipment("Shiv", 1),
"Sprig|1", Equipment("Sprig", 1),
"Oxgoad|1", Equipment("Oxgoad", 1),
"Eelspear|2", Equipment("Eelspear", 2),
"Bowie Knife|2", Equipment("Bowie Knife", 2),
"Claw Hammer|2", Equipment("Claw Hammer", 2),
"Handpeen|2", Equipment("Handpeen", 2),
"Andiron|3", Equipment("Andiron", 3),
"Hatchet|3", Equipment("Hatchet", 3),
"Tomahawk|3", Equipment("Tomahawk", 3),
"Hackbarm|3", Equipment("Hackbarm", 3),
"Crowbar|4", Equipment("Crowbar", 4),
"Mace|4", Equipment("Mace", 4),
"Battleadze|4", Equipment("Battleadze", 4),
"Leafmace|5", Equipment("Leafmace", 5),
"Shortsword|5", Equipment("Shortsword", 5),
"Longiron|5", Equipment("Longiron", 5),
"Poachard|5", Equipment("Poachard", 5),
"Baselard|5", Equipment("Baselard", 5),
"Whinyard|6", Equipment("Whinyard", 6),
"Blunderbuss|6", Equipment("Blunderbuss", 6),
"Longsword|6", Equipment("Longsword", 6),
"Crankbow|6", Equipment("Crankbow", 6),
"Blibo|7", Equipment("Blibo", 7),
"Broadsword|7", Equipment("Broadsword", 7),
"Kreen|7", Equipment("Kreen", 7),
"Warhammer|7", Equipment("Warhammer", 7),
"Morning Star|8", Equipment("Morning Star", 8),
"Pole-adze|8", Equipment("Pole-adze", 8),
"Spontoon|8", Equipment("Spontoon", 8),
"Bastard Sword|9", Equipment("Bastard Sword", 9),
"Peen-arm|9", Equipment("Peen-arm", 9),
"Culverin|10", Equipment("Culverin", 10),
"Lance|10", Equipment("Lance", 10),
"Halberd|11", Equipment("Halberd", 11),
"Poleax|12", Equipment("Poleax", 12),
"Bandyclef|15", Equipment("Bandyclef", 15),
] ]
SPECIALS = [ SPECIALS = [
@@ -383,346 +419,306 @@ BORING_ITEMS = [
"writ", "writ",
] ]
@dataclass
class Monster:
name: str
level: int
item: T.Optional[str]
MONSTERS = [ MONSTERS = [
"Anhkheg|6|chitin", Monster("Anhkheg", 6, "chitin"),
"Ant|0|antenna", Monster("Ant", 0, "antenna"),
"Ape|4|ass", Monster("Ape", 4, "ass"),
"Baluchitherium|14|ear", Monster("Baluchitherium", 14, "ear"),
"Beholder|10|eyestalk", Monster("Beholder", 10, "eyestalk"),
"Black Pudding|10|saliva", Monster("Black Pudding", 10, "saliva"),
"Blink Dog|4|eyelid", Monster("Blink Dog", 4, "eyelid"),
"Cub Scout|1|neckerchief", Monster("Cub Scout", 1, "neckerchief"),
"Girl Scout|2|cookie", Monster("Girl Scout", 2, "cookie"),
"Boy Scout|3|merit badge", Monster("Boy Scout", 3, "merit badge"),
"Eagle Scout|4|merit badge", Monster("Eagle Scout", 4, "merit badge"),
"Bugbear|3|skin", Monster("Bugbear", 3, "skin"),
"Bugboar|3|tusk", Monster("Bugboar", 3, "tusk"),
"Boogie|3|slime", Monster("Boogie", 3, "slime"),
"Camel|2|hump", Monster("Camel", 2, "hump"),
"Carrion Crawler|3|egg", Monster("Carrion Crawler", 3, "egg"),
"Catoblepas|6|neck", Monster("Catoblepas", 6, "neck"),
"Centaur|4|rib", Monster("Centaur", 4, "rib"),
"Centipede|0|leg", Monster("Centipede", 0, "leg"),
"Cockatrice|5|wattle", Monster("Cockatrice", 5, "wattle"),
"Couatl|9|wing", Monster("Couatl", 9, "wing"),
"Crayfish|0|antenna", Monster("Crayfish", 0, "antenna"),
"Demogorgon|53|tentacle", Monster("Demogorgon", 53, "tentacle"),
"Jubilex|17|gel", Monster("Jubilex", 17, "gel"),
"Manes|1|tooth", Monster("Manes", 1, "tooth"),
"Orcus|27|wand", Monster("Orcus", 27, "wand"),
"Succubus|6|bra", Monster("Succubus", 6, "bra"),
"Vrock|8|neck", Monster("Vrock", 8, "neck"),
"Hezrou|9|leg", Monster("Hezrou", 9, "leg"),
"Glabrezu|10|collar", Monster("Glabrezu", 10, "collar"),
"Nalfeshnee|11|tusk", Monster("Nalfeshnee", 11, "tusk"),
"Marilith|7|arm", Monster("Marilith", 7, "arm"),
"Balor|8|whip", Monster("Balor", 8, "whip"),
"Yeenoghu|25|flail", Monster("Yeenoghu", 25, "flail"),
"Asmodeus|52|leathers", Monster("Asmodeus", 52, "leathers"),
"Baalzebul|43|pants", Monster("Baalzebul", 43, "pants"),
"Barbed Devil|8|flame", Monster("Barbed Devil", 8, "flame"),
"Bone Devil|9|hook", Monster("Bone Devil", 9, "hook"),
"Dispater|30|matches", Monster("Dispater", 30, "matches"),
"Erinyes|6|thong", Monster("Erinyes", 6, "thong"),
"Geryon|30|cornucopia", Monster("Geryon", 30, "cornucopia"),
"Malebranche|5|fork", Monster("Malebranche", 5, "fork"),
"Ice Devil|11|snow", Monster("Ice Devil", 11, "snow"),
"Lemure|3|blob", Monster("Lemure", 3, "blob"),
"Pit Fiend|13|seed", Monster("Pit Fiend", 13, "seed"),
"Ankylosaurus|9|tail", Monster("Ankylosaurus", 9, "tail"),
"Brontosaurus|30|brain", Monster("Brontosaurus", 30, "brain"),
"Diplodocus|24|fin", Monster("Diplodocus", 24, "fin"),
"Elasmosaurus|15|neck", Monster("Elasmosaurus", 15, "neck"),
"Gorgosaurus|13|arm", Monster("Gorgosaurus", 13, "arm"),
"Iguanadon|6|thumb", Monster("Iguanadon", 6, "thumb"),
"Megalosaurus|12|jaw", Monster("Megalosaurus", 12, "jaw"),
"Monoclonius|8|horn", Monster("Monoclonius", 8, "horn"),
"Pentasaurus|12|head", Monster("Pentasaurus", 12, "head"),
"Stegosaurus|18|plate", Monster("Stegosaurus", 18, "plate"),
"Triceratops|16|horn", Monster("Triceratops", 16, "horn"),
"Tyranosauraus Rex|18|forearm", Monster("Tyranosauraus Rex", 18, "forearm"),
"Djinn|7|lamp", Monster("Djinn", 7, "lamp"),
"Doppleganger|4|face", Monster("Doppleganger", 4, "face"),
"Black Dragon|7|*", Monster("Black Dragon", 7, None),
"Plaid Dragon|7|sporrin", Monster("Plaid Dragon", 7, "sporrin"),
"Blue Dragon|9|*", Monster("Blue Dragon", 9, None),
"Beige Dragon|9|*", Monster("Beige Dragon", 9, None),
"Brass Dragon|7|pole", Monster("Brass Dragon", 7, "pole"),
"Tin Dragon|8|*", Monster("Tin Dragon", 8, None),
"Bronze Dragon|9|medal", Monster("Bronze Dragon", 9, "medal"),
"Chromatic Dragon|16|scale", Monster("Chromatic Dragon", 16, "scale"),
"Copper Dragon|8|loafer", Monster("Copper Dragon", 8, "loafer"),
"Gold Dragon|8|filling", Monster("Gold Dragon", 8, "filling"),
"Green Dragon|8|*", Monster("Green Dragon", 8, None),
"Platinum Dragon|21|*", Monster("Platinum Dragon", 21, None),
"Red Dragon|10|cocktail", Monster("Red Dragon", 10, "cocktail"),
"Silver Dragon|10|*", Monster("Silver Dragon", 10, None),
"White Dragon|6|tooth", Monster("White Dragon", 6, "tooth"),
"Dragon Turtle|13|shell", Monster("Dragon Turtle", 13, "shell"),
"Dryad|2|acorn", Monster("Dryad", 2, "acorn"),
"Dwarf|1|drawers", Monster("Dwarf", 1, "drawers"),
"Eel|2|sashimi", Monster("Eel", 2, "sashimi"),
"Efreet|10|cinder", Monster("Efreet", 10, "cinder"),
"Sand Elemental|8|glass", Monster("Sand Elemental", 8, "glass"),
"Bacon Elemental|10|bit", Monster("Bacon Elemental", 10, "bit"),
"Porn Elemental|12|lube", Monster("Porn Elemental", 12, "lube"),
"Cheese Elemental|14|curd", Monster("Cheese Elemental", 14, "curd"),
"Hair Elemental|16|follicle", Monster("Hair Elemental", 16, "follicle"),
"Swamp Elf|1|lilypad", Monster("Swamp Elf", 1, "lilypad"),
"Brown Elf|1|tusk", Monster("Brown Elf", 1, "tusk"),
"Sea Elf|1|jerkin", Monster("Sea Elf", 1, "jerkin"),
"Ettin|10|fur", Monster("Ettin", 10, "fur"),
"Frog|0|leg", Monster("Frog", 0, "leg"),
"Violet Fungi|3|spore", Monster("Violet Fungi", 3, "spore"),
"Gargoyle|4|gravel", Monster("Gargoyle", 4, "gravel"),
"Gelatinous Cube|4|jam", Monster("Gelatinous Cube", 4, "jam"),
"Ghast|4|vomit", Monster("Ghast", 4, "vomit"),
"Ghost|10|*", Monster("Ghost", 10, None),
"Ghoul|2|muscle", Monster("Ghoul", 2, "muscle"),
"Humidity Giant|12|drops", Monster("Humidity Giant", 12, "drops"),
"Beef Giant|11|steak", Monster("Beef Giant", 11, "steak"),
"Quartz Giant|10|crystal", Monster("Quartz Giant", 10, "crystal"),
"Porcelain Giant|9|fixture", Monster("Porcelain Giant", 9, "fixture"),
"Rice Giant|8|grain", Monster("Rice Giant", 8, "grain"),
"Cloud Giant|12|condensation", Monster("Cloud Giant", 12, "condensation"),
"Fire Giant|11|cigarettes", Monster("Fire Giant", 11, "cigarettes"),
"Frost Giant|10|snowman", Monster("Frost Giant", 10, "snowman"),
"Hill Giant|8|corpse", Monster("Hill Giant", 8, "corpse"),
"Stone Giant|9|hatchling", Monster("Stone Giant", 9, "hatchling"),
"Storm Giant|15|barometer", Monster("Storm Giant", 15, "barometer"),
"Mini Giant|4|pompadour", Monster("Mini Giant", 4, "pompadour"),
"Gnoll|2|collar", Monster("Gnoll", 2, "collar"),
"Gnome|1|hat", Monster("Gnome", 1, "hat"),
"Goblin|1|ear", Monster("Goblin", 1, "ear"),
"Grid Bug|1|carapace", Monster("Grid Bug", 1, "carapace"),
"Jellyrock|9|seedling", Monster("Jellyrock", 9, "seedling"),
"Beer Golem|15|foam", Monster("Beer Golem", 15, "foam"),
"Oxygen Golem|17|platelet", Monster("Oxygen Golem", 17, "platelet"),
"Cardboard Golem|14|recycling", Monster("Cardboard Golem", 14, "recycling"),
"Rubber Golem|16|ball", Monster("Rubber Golem", 16, "ball"),
"Leather Golem|15|fob", Monster("Leather Golem", 15, "fob"),
"Gorgon|8|testicle", Monster("Gorgon", 8, "testicle"),
"Gray Ooze|3|gravy", Monster("Gray Ooze", 3, "gravy"),
"Green Slime|2|sample", Monster("Green Slime", 2, "sample"),
"Griffon|7|nest", Monster("Griffon", 7, "nest"),
"Banshee|7|larynx", Monster("Banshee", 7, "larynx"),
"Harpy|3|mascara", Monster("Harpy", 3, "mascara"),
"Hell Hound|5|tongue", Monster("Hell Hound", 5, "tongue"),
"Hippocampus|4|mane", Monster("Hippocampus", 4, "mane"),
"Hippogriff|3|egg", Monster("Hippogriff", 3, "egg"),
"Hobgoblin|1|patella", Monster("Hobgoblin", 1, "patella"),
"Homonculus|2|fluid", Monster("Homonculus", 2, "fluid"),
"Hydra|8|gyrum", Monster("Hydra", 8, "gyrum"),
"Imp|2|tail", Monster("Imp", 2, "tail"),
"Invisible Stalker|8|*", Monster("Invisible Stalker", 8, None),
"Iron Peasant|3|chaff", Monster("Iron Peasant", 3, "chaff"),
"Jumpskin|3|shin", Monster("Jumpskin", 3, "shin"),
"Kobold|1|penis", Monster("Kobold", 1, "penis"),
"Leprechaun|1|wallet", Monster("Leprechaun", 1, "wallet"),
"Leucrotta|6|hoof", Monster("Leucrotta", 6, "hoof"),
"Lich|11|crown", Monster("Lich", 11, "crown"),
"Lizard Man|2|tail", Monster("Lizard Man", 2, "tail"),
"Lurker|10|sac", Monster("Lurker", 10, "sac"),
"Manticore|6|spike", Monster("Manticore", 6, "spike"),
"Mastodon|12|tusk", Monster("Mastodon", 12, "tusk"),
"Medusa|6|eye", Monster("Medusa", 6, "eye"),
"Multicell|2|dendrite", Monster("Multicell", 2, "dendrite"),
"Pirate|1|booty", Monster("Pirate", 1, "booty"),
"Berserker|1|shirt", Monster("Berserker", 1, "shirt"),
"Caveman|2|club", Monster("Caveman", 2, "club"),
"Dervish|1|robe", Monster("Dervish", 1, "robe"),
"Merman|1|trident", Monster("Merman", 1, "trident"),
"Mermaid|1|gills", Monster("Mermaid", 1, "gills"),
"Mimic|9|hinge", Monster("Mimic", 9, "hinge"),
"Mind Flayer|8|tentacle", Monster("Mind Flayer", 8, "tentacle"),
"Minotaur|6|map", Monster("Minotaur", 6, "map"),
"Yellow Mold|1|spore", Monster("Yellow Mold", 1, "spore"),
"Morkoth|7|teeth", Monster("Morkoth", 7, "teeth"),
"Mummy|6|gauze", Monster("Mummy", 6, "gauze"),
"Naga|9|rattle", Monster("Naga", 9, "rattle"),
"Nebbish|1|belly", Monster("Nebbish", 1, "belly"),
"Neo-Otyugh|11|organ ", Monster("Neo-Otyugh", 11, "organ "),
"Nixie|1|webbing", Monster("Nixie", 1, "webbing"),
"Nymph|3|hanky", Monster("Nymph", 3, "hanky"),
"Ochre Jelly|6|nucleus", Monster("Ochre Jelly", 6, "nucleus"),
"Octopus|2|beak", Monster("Octopus", 2, "beak"),
"Ogre|4|talon", Monster("Ogre", 4, "talon"),
"Ogre Mage|5|apparel", Monster("Ogre Mage", 5, "apparel"),
"Orc|1|snout", Monster("Orc", 1, "snout"),
"Otyugh|7|organ", Monster("Otyugh", 7, "organ"),
"Owlbear|5|feather", Monster("Owlbear", 5, "feather"),
"Pegasus|4|aileron", Monster("Pegasus", 4, "aileron"),
"Peryton|4|antler", Monster("Peryton", 4, "antler"),
"Piercer|3|tip", Monster("Piercer", 3, "tip"),
"Pixie|1|dust", Monster("Pixie", 1, "dust"),
"Man-o-war|3|tentacle", Monster("Man-o-war", 3, "tentacle"),
"Purple Worm|15|dung", Monster("Purple Worm", 15, "dung"),
"Quasit|3|tail", Monster("Quasit", 3, "tail"),
"Rakshasa|7|pajamas", Monster("Rakshasa", 7, "pajamas"),
"Rat|0|tail", Monster("Rat", 0, "tail"),
"Remorhaz|11|protrusion", Monster("Remorhaz", 11, "protrusion"),
"Roc|18|wing", Monster("Roc", 18, "wing"),
"Roper|11|twine", Monster("Roper", 11, "twine"),
"Rot Grub|1|eggsac", Monster("Rot Grub", 1, "eggsac"),
"Rust Monster|5|shavings", Monster("Rust Monster", 5, "shavings"),
"Satyr|5|hoof", Monster("Satyr", 5, "hoof"),
"Sea Hag|3|wart", Monster("Sea Hag", 3, "wart"),
"Silkie|3|fur", Monster("Silkie", 3, "fur"),
"Shadow|3|silhouette", Monster("Shadow", 3, "silhouette"),
"Shambling Mound|10|mulch", Monster("Shambling Mound", 10, "mulch"),
"Shedu|9|hoof", Monster("Shedu", 9, "hoof"),
"Shrieker|3|stalk", Monster("Shrieker", 3, "stalk"),
"Skeleton|1|clavicle", Monster("Skeleton", 1, "clavicle"),
"Spectre|7|vestige", Monster("Spectre", 7, "vestige"),
"Sphinx|10|paw", Monster("Sphinx", 10, "paw"),
"Spider|0|web", Monster("Spider", 0, "web"),
"Sprite|1|can", Monster("Sprite", 1, "can"),
"Stirge|1|proboscis", Monster("Stirge", 1, "proboscis"),
"Stun Bear|5|tooth", Monster("Stun Bear", 5, "tooth"),
"Stun Worm|2|trode", Monster("Stun Worm", 2, "trode"),
"Su-monster|5|tail", Monster("Su-monster", 5, "tail"),
"Sylph|3|thigh", Monster("Sylph", 3, "thigh"),
"Titan|20|sandal", Monster("Titan", 20, "sandal"),
"Trapper|12|shag", Monster("Trapper", 12, "shag"),
"Treant|10|acorn", Monster("Treant", 10, "acorn"),
"Triton|3|scale", Monster("Triton", 3, "scale"),
"Troglodyte|2|tail", Monster("Troglodyte", 2, "tail"),
"Troll|6|hide", Monster("Troll", 6, "hide"),
"Umber Hulk|8|claw", Monster("Umber Hulk", 8, "claw"),
"Unicorn|4|blood", Monster("Unicorn", 4, "blood"),
"Vampire|8|pancreas", Monster("Vampire", 8, "pancreas"),
"Wight|4|lung", Monster("Wight", 4, "lung"),
"Will-o'-the-Wisp|9|wisp", Monster("Will-o'-the-Wisp", 9, "wisp"),
"Wraith|5|finger", Monster("Wraith", 5, "finger"),
"Wyvern|7|wing", Monster("Wyvern", 7, "wing"),
"Xorn|7|jaw", Monster("Xorn", 7, "jaw"),
"Yeti|4|fur", Monster("Yeti", 4, "fur"),
"Zombie|2|forehead", Monster("Zombie", 2, "forehead"),
"Wasp|0|stinger", Monster("Wasp", 0, "stinger"),
"Rat|1|tail", Monster("Rat", 1, "tail"),
"Bunny|0|ear", Monster("Bunny", 0, "ear"),
"Moth|0|dust", Monster("Moth", 0, "dust"),
"Beagle|0|collar", Monster("Beagle", 0, "collar"),
"Midge|0|corpse", Monster("Midge", 0, "corpse"),
"Ostrich|1|beak", Monster("Ostrich", 1, "beak"),
"Billy Goat|1|beard", Monster("Billy Goat", 1, "beard"),
"Bat|1|wing", Monster("Bat", 1, "wing"),
"Koala|2|heart", Monster("Koala", 2, "heart"),
"Wolf|2|paw", Monster("Wolf", 2, "paw"),
"Whippet|2|collar", Monster("Whippet", 2, "collar"),
"Uruk|2|boot", Monster("Uruk", 2, "boot"),
"Poroid|4|node", Monster("Poroid", 4, "node"),
"Moakum|8|frenum", Monster("Moakum", 8, "frenum"),
"Fly|0|*", Monster("Fly", 0, None),
"Hogbird|3|curl", Monster("Hogbird", 3, "curl"),
"Wolog|4|lemma", Monster("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",
] ]
@dataclass @dataclass
class Race: class Race:
name: str name: str
attr: T.List[Stat] attr: T.List[StatType]
RACES = [ RACES = [
Race("Half Orc", [Stat.hp_max]), Race("Half Orc", [StatType.hp_max]),
Race("Half Man", [Stat.charisma]), Race("Half Man", [StatType.charisma]),
Race("Half Halfling", [Stat.dexterity]), Race("Half Halfling", [StatType.dexterity]),
Race("Double Hobbit", [Stat.strength]), Race("Double Hobbit", [StatType.strength]),
Race("Hob-Hobbit", [Stat.dexterity, Stat.condition]), Race("Hob-Hobbit", [StatType.dexterity, StatType.condition]),
Race("Low Elf", [Stat.condition]), Race("Low Elf", [StatType.condition]),
Race("Dung Elf", [Stat.wisdom]), Race("Dung Elf", [StatType.wisdom]),
Race("Talking Pony", [Stat.mp_max, Stat.intelligence]), Race("Talking Pony", [StatType.mp_max, StatType.intelligence]),
Race("Gyrognome", [Stat.dexterity]), Race("Gyrognome", [StatType.dexterity]),
Race("Lesser Dwarf", [Stat.condition]), Race("Lesser Dwarf", [StatType.condition]),
Race("Crested Dwarf", [Stat.charisma]), Race("Crested Dwarf", [StatType.charisma]),
Race("Eel Man", [Stat.dexterity]), Race("Eel Man", [StatType.dexterity]),
Race("Panda Man", [Stat.condition, Stat.strength]), Race("Panda Man", [StatType.condition, StatType.strength]),
Race("Trans-Kobold", [Stat.wisdom]), Race("Trans-Kobold", [StatType.wisdom]),
Race("Enchanted Motorcycle", [Stat.mp_max]), Race("Enchanted Motorcycle", [StatType.mp_max]),
Race("Will o' the Wisp", [Stat.wisdom]), Race("Will o' the Wisp", [StatType.wisdom]),
Race("Battle-Finch", [Stat.dexterity, Stat.intelligence]), Race("Battle-Finch", [StatType.dexterity, StatType.intelligence]),
Race("Double Wookiee", [Stat.strength]), Race("Double Wookiee", [StatType.strength]),
Race("Skraeling", [Stat.wisdom]), Race("Skraeling", [StatType.wisdom]),
Race("Demicanadian", [Stat.condition]), Race("Demicanadian", [StatType.condition]),
Race("Land Squid", [Stat.strength, Stat.hp_max]), Race("Land Squid", [StatType.strength, StatType.hp_max]),
] ]
@dataclass @dataclass
class Class: class Class:
name: str name: str
attr: T.List[Stat] attr: T.List[StatType]
CLASSES = [ CLASSES = [
Class("Ur-Paladin", [Stat.wisdom, Stat.condition]), Class("Ur-Paladin", [StatType.wisdom, StatType.condition]),
Class("Voodoo Princess", [Stat.intelligence, Stat.charisma]), Class("Voodoo Princess", [StatType.intelligence, StatType.charisma]),
Class("Robot Monk", [Stat.strength]), Class("Robot Monk", [StatType.strength]),
Class("Mu-Fu Monk", [Stat.dexterity]), Class("Mu-Fu Monk", [StatType.dexterity]),
Class("Mage Illusioner", [Stat.intelligence, Stat.mp_max]), Class("Mage Illusioner", [StatType.intelligence, StatType.mp_max]),
Class("Shiv-Knight", [Stat.dexterity]), Class("Shiv-Knight", [StatType.dexterity]),
Class("Inner Mason", [Stat.condition]), Class("Inner Mason", [StatType.condition]),
Class("Fighter/Organist", [Stat.charisma, Stat.strength]), Class("Fighter/Organist", [StatType.charisma, StatType.strength]),
Class("Puma Burgular", [Stat.dexterity]), Class("Puma Burgular", [StatType.dexterity]),
Class("Runeloremaster", [Stat.wisdom]), Class("Runeloremaster", [StatType.wisdom]),
Class("Hunter Strangler", [Stat.dexterity, Stat.intelligence]), Class("Hunter Strangler", [StatType.dexterity, StatType.intelligence]),
Class("Battle-Felon", [Stat.strength]), Class("Battle-Felon", [StatType.strength]),
Class("Tickle-Mimic", [Stat.wisdom, Stat.intelligence]), Class("Tickle-Mimic", [StatType.wisdom, StatType.intelligence]),
Class("Slow Poisoner", [Stat.condition]), Class("Slow Poisoner", [StatType.condition]),
Class("Bastard Lunatic", [Stat.condition]), Class("Bastard Lunatic", [StatType.condition]),
Class("Lowling", [Stat.wisdom]), Class("Lowling", [StatType.wisdom]),
Class("Birdrider", [Stat.wisdom]), Class("Birdrider", [StatType.wisdom]),
Class("Vermineer", [Stat.intelligence]), Class("Vermineer", [StatType.intelligence]),
] ]
TITLES = [ TITLES = [
+124
View File
@@ -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, ""
)
+657 -109
View File
@@ -1,10 +1,14 @@
import datetime import datetime
import enum import itertools
import logging
import typing as T import typing as T
from dataclasses import dataclass, field from dataclasses import dataclass
from pqcli import random from pqcli import random
from pqcli.config import * from pqcli.config import *
from pqcli.lingo import *
logger = logging.getLogger(__name__)
def level_up_time(level: int) -> int: def level_up_time(level: int) -> int:
@@ -12,32 +16,58 @@ def level_up_time(level: int) -> int:
return 20 * level * 60 return 20 * level * 60
def generate_name() -> str: class Bar:
parts = [ def __init__(self, max_: int, position: float = 0.0) -> None:
"br|cr|dr|fr|gr|j|kr|l|m|n|pr||||r|sh|tr|v|wh|x|y|z".split("|"), self.position = position
"a|a|e|e|i|i|o|o|u|u|ae|ie|oo|ou".split("|"), self.max_ = max_
"b|ck|d|g|k|m|n|p|t|v|x|z".split("|"),
] def reset(self, new_max: int, position: float = 0.0) -> None:
result = "" self.max_ = new_max
for i in range(6): self.position = position
result += random.choice(parts[i % 3])
return result.title() 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: class Stats:
def __init__(self) -> None: def __init__(self, values: T.Dict[StatType, int]) -> None:
self.values = {stat: 0 for stat in Stat} 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()) return iter(self.values.items())
def __setitem__(self, stat: Stat, value: int) -> None: def __getitem__(self, stat: StatType) -> int:
self.values[stat] = value
def __getitem__(self, stat: Stat) -> int:
return self.values[stat] 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 @dataclass
class Item: class Item:
@@ -45,58 +75,113 @@ class Item:
quantity: int 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 @dataclass
class Spell: class Spell:
name: str name: str
level: int 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: class SpellBook:
def __init__(self, spells: T.Optional[T.List[Item]] = None) -> None: def __init__(self) -> None:
self.spells = [] if spells is None else spells self.spells: T.List[Spell] = []
def __iter__(self) -> T.Iterator[Spell]:
return iter(self.spells)
def add(self, spell_name: str, level: int) -> None: def add(self, spell_name: str, level: int) -> None:
for item in self.spells: for spell in self.spells:
if item.name == spell_name: if spell.name == spell_name:
item.level += level spell.level += level
logger.info("Learned %s at level %d", spell_name, spell.level)
break break
else: else:
self.spells.append(Spell(name=spell_name, level=level)) self.spells.append(Spell(name=spell_name, level=level))
logger.info("Learned %s at level %d", spell_name, 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"
@dataclass @dataclass
class Task: class BaseTask:
task_type: TaskType
length: int
description: str 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: class Player:
@@ -113,53 +198,66 @@ class Player:
self.race: Race = race self.race: Race = race
self.class_: Class = class_ self.class_: Class = class_
self.stats: Stats = stats self.stats: Stats = stats
self.level = 1
self.act = 0 self.act = 0
self.queue: T.List[Task] = [ self.exp_bar = Bar(max_=level_up_time(1))
Task( self.level = 1
TaskType.task,
10, self.task_bar = Bar(max_=0)
"Experiencing an enigmatic and foreboding night vision", self.task: T.Optional[BaseTask] = None
self.queue: T.List[BaseTask] = [
RegularTask(
"Experiencing an enigmatic and foreboding night vision", 10_000
), ),
Task( RegularTask(
TaskType.task, "Much is revealed about that wise old bastard "
6, "you'd underestimated",
"Much is revealed about that wise old bastard you'd underestimated", 6_000,
), ),
Task( RegularTask(
TaskType.task, "A shocking series of events leaves you "
6, "alone and bewildered, but resolute",
"A shocking series of events leaves you alone and bewildered, but resolute", 6_000,
), ),
Task( RegularTask(
TaskType.task, "Drawing upon an unrealized reserve of determination, "
4, "you set out on a long and dangerous journey",
"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.plot_bar = Bar(max_=sum(task.duration for task in self.queue))
self.encum_bar = Bar(max_=stats[Stat.strength] + 10) self.quest_book = QuestBook()
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.inventory = Inventory([Item(name="Gold", quantity=0)]) self.inventory = Inventory(capacity=10 + self.stats[StatType.strength])
self.spell_book = SpellBook() self.equipment: T.Dict[EquipmentType, str] = {
self.equipment: T.Dict[Equipment, str] = { EquipmentType.weapon: "Sharp Rock",
Equipment.weapon: "Sharp Rock", EquipmentType.hauberk: "-3 Burlap",
Equipment.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: def level_up(self) -> None:
self.level += 1 self.level += 1
self.stats[Stat.hp_max] += ( logger.info("Leveled up to level %d!", self.level)
self.stats[Stat.condition] // 3 + 1 + random.below(4) self.stats.increment(
StatType.hp_max,
self.stats[StatType.condition] // 3 + 1 + random.below(4),
) )
self.stats[Stat.mp_max] += ( self.stats.increment(
self.stats[Stat.intelligence] // 3 + 1 + random.below(4) StatType.mp_max,
self.stats[StatType.intelligence] // 3 + 1 + random.below(4),
) )
self.win_stat() self.win_stat()
self.win_stat() self.win_stat()
@@ -167,50 +265,496 @@ class Player:
self.exp_bar.reset(level_up_time(self.level)) self.exp_bar.reset(level_up_time(self.level))
def win_stat(self) -> bool: def win_stat(self) -> bool:
chosen_stat: T.Optional[StatType] = None
if random.odds(1, 2): if random.odds(1, 2):
chosen_stat = random.choice(list(Stat)) chosen_stat = random.choice(list(StatType))
else: 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 = sum(value ** 2 for _stat, value in self.stats)
t = random.below(t) t = random.below(t)
chosen_stat: Stat = None chosen_stat = None
for stat, value in self.stats: for stat, value in self.stats:
chosen_stat = stat chosen_stat = stat
t -= value ** 2 t -= value ** 2
if t < 0: if t < 0:
return False break
self.stats[chosen_stat] += 1 assert chosen_stat is not None
if chosen_stat == Stat.strength: self.stats.increment(chosen_stat)
self.encum_bar.reset( if chosen_stat == StatType.strength:
10 + self.stats[Stat.strength], self.encum_bar.position self.inventory.set_capacity(10 + self.stats[StatType.strength])
)
return True return True
def win_spell(self) -> None: def win_spell(self) -> None:
self.spell_book.add( self.spell_book.add(
SPELLS[ SPELLS[
random.below_low( random.below_low(
min(self.stats[Stat.wisdom] + self.level, len(SPELLS)) min(self.stats[StatType.wisdom] + self.level, len(SPELLS))
) )
], ],
1, 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: def win_item(self) -> None:
self.inventory.add(special_item(), 1) 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: 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: 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: 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: class Roster:
@@ -226,13 +770,17 @@ class StatsBuilder:
self.history: T.List[Stats] = [] self.history: T.List[Stats] = []
def roll(self) -> Stats: def roll(self) -> Stats:
stats = Stats() values: T.Dict[StatType, int] = {
for stat in PRIME_STATS: stat: 3 + random.below(6) + random.below(6) + random.below(6)
stats[stat] = ( for stat in PRIME_STATS
3 + random.below(6) + random.below(6) + random.below(6) }
) values[StatType.hp_max] = (
stats[Stat.hp_max] = random.below(8) + stats[Stat.condition] // 6 random.below(8) + values[StatType.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) self.history.append(stats)
return stats return stats
+6 -2
View File
@@ -6,8 +6,12 @@ def seed(source: T.Any) -> None:
random.seed(source) random.seed(source)
def choice(source: T.Iterable[T.Any]) -> T.Any: def choice(source: T.Sequence[T.Any]) -> T.Any:
return random.choice(source) 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: def below(num: int) -> int: