mirror of
https://github.com/rr-/pq-cli.git
synced 2026-09-29 06:44:00 -06:00
initial commit
This commit is contained in:
@@ -0,0 +1,27 @@
|
|||||||
|
import pickle
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import xdg
|
||||||
|
|
||||||
|
from pqcli.game_state import Roster
|
||||||
|
from pqcli.ui import Ui
|
||||||
|
|
||||||
|
|
||||||
|
SAVE_PATH = Path(xdg.XDG_CONFIG_HOME) / "pqcli" / "save.dat"
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
if SAVE_PATH.exists():
|
||||||
|
roster = pickle.loads(SAVE_PATH.read_bytes())
|
||||||
|
else:
|
||||||
|
roster = Roster()
|
||||||
|
|
||||||
|
try:
|
||||||
|
Ui(roster).run()
|
||||||
|
finally:
|
||||||
|
SAVE_PATH.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
SAVE_PATH.write_bytes(pickle.dumps(roster))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
+757
@@ -0,0 +1,757 @@
|
|||||||
|
import enum
|
||||||
|
import typing as T
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
|
||||||
|
def level_up_time(level) -> int:
|
||||||
|
# seconds
|
||||||
|
return 20 * level * 60
|
||||||
|
|
||||||
|
|
||||||
|
class Traits(enum.Enum):
|
||||||
|
name = "Name"
|
||||||
|
race = "Race"
|
||||||
|
class_ = "Class"
|
||||||
|
level = "Level"
|
||||||
|
|
||||||
|
|
||||||
|
class Stat(enum.Enum):
|
||||||
|
strength = "STR"
|
||||||
|
condition = "CON"
|
||||||
|
dexterity = "DEX"
|
||||||
|
intelligence = "INT"
|
||||||
|
wisdom = "WIS"
|
||||||
|
charisma = "CHA"
|
||||||
|
hp_max = "HP Max"
|
||||||
|
mp_max = "MP Max"
|
||||||
|
|
||||||
|
|
||||||
|
PRIME_STATS = [
|
||||||
|
Stat.strength,
|
||||||
|
Stat.condition,
|
||||||
|
Stat.dexterity,
|
||||||
|
Stat.intelligence,
|
||||||
|
Stat.wisdom,
|
||||||
|
Stat.charisma,
|
||||||
|
]
|
||||||
|
|
||||||
|
ALL_STATS = PRIME_STATS + [Stat.hp_max, Stat.mp_max]
|
||||||
|
|
||||||
|
|
||||||
|
EQUIPS = [
|
||||||
|
"Weapon",
|
||||||
|
"Shield",
|
||||||
|
"Helm",
|
||||||
|
"Hauberk",
|
||||||
|
"Brassairts",
|
||||||
|
"Vambraces",
|
||||||
|
"Gauntlets",
|
||||||
|
"Gambeson",
|
||||||
|
"Cuisses",
|
||||||
|
"Greaves",
|
||||||
|
"Sollerets",
|
||||||
|
]
|
||||||
|
|
||||||
|
SPELLS = [
|
||||||
|
"Slime Finger",
|
||||||
|
"Rabbit Punch",
|
||||||
|
"Hastiness",
|
||||||
|
"Good Move",
|
||||||
|
"Sadness",
|
||||||
|
"Seasick",
|
||||||
|
"Shoelaces",
|
||||||
|
"Innoculate",
|
||||||
|
"Cone of Annoyance",
|
||||||
|
"Magnetic Orb",
|
||||||
|
"Invisible Hands",
|
||||||
|
"Revolting Cloud",
|
||||||
|
"Aqueous Humor",
|
||||||
|
"Spectral Miasma",
|
||||||
|
"Clever Fellow",
|
||||||
|
"Lockjaw",
|
||||||
|
"History Lesson",
|
||||||
|
"Hydrophobia",
|
||||||
|
"Big Sister",
|
||||||
|
"Cone of Paste",
|
||||||
|
"Mulligan",
|
||||||
|
"Nestor's Bright Idea",
|
||||||
|
"Holy Batpole",
|
||||||
|
"Tumor (Benign)",
|
||||||
|
"Braingate",
|
||||||
|
"Summon a Bitch",
|
||||||
|
"Nonplus",
|
||||||
|
"Animate Nightstand",
|
||||||
|
"Eye of the Troglodyte",
|
||||||
|
"Curse Name",
|
||||||
|
"Dropsy",
|
||||||
|
"Vitreous Humor",
|
||||||
|
"Roger's Grand Illusion",
|
||||||
|
"Covet",
|
||||||
|
"Black Idaho",
|
||||||
|
"Astral Miasma",
|
||||||
|
"Spectral Oyster",
|
||||||
|
"Acrid Hands",
|
||||||
|
"Angioplasty",
|
||||||
|
"Grognor's Big Day Off",
|
||||||
|
"Tumor (Malignant)",
|
||||||
|
"Animate Tunic",
|
||||||
|
"Ursine Armor",
|
||||||
|
"Holy Roller",
|
||||||
|
"Tonsilectomy",
|
||||||
|
"Curse Family",
|
||||||
|
"Infinite Confusion",
|
||||||
|
]
|
||||||
|
|
||||||
|
OFFENSE_ATTRIB = [
|
||||||
|
"Polished|+1",
|
||||||
|
"Serrated|+1",
|
||||||
|
"Heavy|+1",
|
||||||
|
"Pronged|+2",
|
||||||
|
"Steely|+2",
|
||||||
|
"Vicious|+3",
|
||||||
|
"Venomed|+4",
|
||||||
|
"Stabbity|+4",
|
||||||
|
"Dancing|+5",
|
||||||
|
"Invisible|+6",
|
||||||
|
"Vorpal|+7",
|
||||||
|
]
|
||||||
|
|
||||||
|
DEFENSE_ATTRIB = [
|
||||||
|
"Studded|+1",
|
||||||
|
"Banded|+2",
|
||||||
|
"Gilded|+2",
|
||||||
|
"Festooned|+3",
|
||||||
|
"Holy|+4",
|
||||||
|
"Cambric|+1",
|
||||||
|
"Fine|+4",
|
||||||
|
"Impressive|+5",
|
||||||
|
"Custom|+3",
|
||||||
|
]
|
||||||
|
|
||||||
|
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",
|
||||||
|
]
|
||||||
|
|
||||||
|
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",
|
||||||
|
]
|
||||||
|
|
||||||
|
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",
|
||||||
|
]
|
||||||
|
|
||||||
|
SPECIALS = [
|
||||||
|
"Diadem",
|
||||||
|
"Festoon",
|
||||||
|
"Gemstone",
|
||||||
|
"Phial",
|
||||||
|
"Tiara",
|
||||||
|
"Scabbard",
|
||||||
|
"Arrow",
|
||||||
|
"Lens",
|
||||||
|
"Lamp",
|
||||||
|
"Hymnal",
|
||||||
|
"Fleece",
|
||||||
|
"Laurel",
|
||||||
|
"Brooch",
|
||||||
|
"Gimlet",
|
||||||
|
"Cobble",
|
||||||
|
"Albatross",
|
||||||
|
"Brazier",
|
||||||
|
"Bandolier",
|
||||||
|
"Tome",
|
||||||
|
"Garnet",
|
||||||
|
"Amethyst",
|
||||||
|
"Candelabra",
|
||||||
|
"Corset",
|
||||||
|
"Sphere",
|
||||||
|
"Sceptre",
|
||||||
|
"Ankh",
|
||||||
|
"Talisman",
|
||||||
|
"Orb",
|
||||||
|
"Gammel",
|
||||||
|
"Ornament",
|
||||||
|
"Brocade",
|
||||||
|
"Galoon",
|
||||||
|
"Bijou",
|
||||||
|
"Spangle",
|
||||||
|
"Gimcrack",
|
||||||
|
"Hood",
|
||||||
|
"Vulpeculum",
|
||||||
|
]
|
||||||
|
|
||||||
|
ITEM_ATTRIB = [
|
||||||
|
"Golden",
|
||||||
|
"Gilded",
|
||||||
|
"Spectral",
|
||||||
|
"Astral",
|
||||||
|
"Garlanded",
|
||||||
|
"Precious",
|
||||||
|
"Crafted",
|
||||||
|
"Dual",
|
||||||
|
"Filigreed",
|
||||||
|
"Cruciate",
|
||||||
|
"Arcane",
|
||||||
|
"Blessed",
|
||||||
|
"Reverential",
|
||||||
|
"Lucky",
|
||||||
|
"Enchanted",
|
||||||
|
"Gleaming",
|
||||||
|
"Grandiose",
|
||||||
|
"Sacred",
|
||||||
|
"Legendary",
|
||||||
|
"Mythic",
|
||||||
|
"Crystalline",
|
||||||
|
"Austere",
|
||||||
|
"Ostentatious",
|
||||||
|
"One True",
|
||||||
|
"Proverbial",
|
||||||
|
"Fearsome",
|
||||||
|
"Deadly",
|
||||||
|
"Benevolent",
|
||||||
|
"Unearthly",
|
||||||
|
"Magnificent",
|
||||||
|
"Iron",
|
||||||
|
"Ormolu",
|
||||||
|
"Puissant",
|
||||||
|
]
|
||||||
|
|
||||||
|
ITEM_OFS = [
|
||||||
|
"Foreboding",
|
||||||
|
"Foreshadowing",
|
||||||
|
"Nervousness",
|
||||||
|
"Happiness",
|
||||||
|
"Torpor",
|
||||||
|
"Danger",
|
||||||
|
"Craft",
|
||||||
|
"Silence",
|
||||||
|
"Invisibility",
|
||||||
|
"Rapidity",
|
||||||
|
"Pleasure",
|
||||||
|
"Practicality",
|
||||||
|
"Hurting",
|
||||||
|
"Joy",
|
||||||
|
"Petulance",
|
||||||
|
"Intrusion",
|
||||||
|
"Chaos",
|
||||||
|
"Suffering",
|
||||||
|
"Extroversion",
|
||||||
|
"Frenzy",
|
||||||
|
"Sisu",
|
||||||
|
"Solitude",
|
||||||
|
"Punctuality",
|
||||||
|
"Efficiency",
|
||||||
|
"Comfort",
|
||||||
|
"Patience",
|
||||||
|
"Internment",
|
||||||
|
"Incarceration",
|
||||||
|
"Misapprehension",
|
||||||
|
"Loyalty",
|
||||||
|
"Envy",
|
||||||
|
"Acrimony",
|
||||||
|
"Worry",
|
||||||
|
"Fear",
|
||||||
|
"Awe",
|
||||||
|
"Guile",
|
||||||
|
"Prurience",
|
||||||
|
"Fortune",
|
||||||
|
"Perspicacity",
|
||||||
|
"Domination",
|
||||||
|
"Submission",
|
||||||
|
"Fealty",
|
||||||
|
"Hunger",
|
||||||
|
"Despair",
|
||||||
|
"Cruelty",
|
||||||
|
"Grob",
|
||||||
|
"Dignard",
|
||||||
|
"Ra",
|
||||||
|
"the Bone",
|
||||||
|
"Diamonique",
|
||||||
|
"Electrum",
|
||||||
|
"Hydragyrum",
|
||||||
|
]
|
||||||
|
|
||||||
|
BORING_ITEMS = [
|
||||||
|
"nail",
|
||||||
|
"lunchpail",
|
||||||
|
"sock",
|
||||||
|
"I.O.U.",
|
||||||
|
"cookie",
|
||||||
|
"pint",
|
||||||
|
"toothpick",
|
||||||
|
"writ",
|
||||||
|
"newspaper",
|
||||||
|
"letter",
|
||||||
|
"plank",
|
||||||
|
"hat",
|
||||||
|
"egg",
|
||||||
|
"coin",
|
||||||
|
"needle",
|
||||||
|
"bucket",
|
||||||
|
"ladder",
|
||||||
|
"chicken",
|
||||||
|
"twig",
|
||||||
|
"dirtclod",
|
||||||
|
"counterpane",
|
||||||
|
"vest",
|
||||||
|
"teratoma",
|
||||||
|
"bunny",
|
||||||
|
"rock",
|
||||||
|
"pole",
|
||||||
|
"carrot",
|
||||||
|
"canoe",
|
||||||
|
"inkwell",
|
||||||
|
"hoe",
|
||||||
|
"bandage",
|
||||||
|
"trowel",
|
||||||
|
"towel",
|
||||||
|
"planter box",
|
||||||
|
"anvil",
|
||||||
|
"axle",
|
||||||
|
"tuppence",
|
||||||
|
"casket",
|
||||||
|
"nosegay",
|
||||||
|
"trinket",
|
||||||
|
"credenza",
|
||||||
|
"writ",
|
||||||
|
]
|
||||||
|
|
||||||
|
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",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Race:
|
||||||
|
name: str
|
||||||
|
attr: T.List[Stat]
|
||||||
|
|
||||||
|
|
||||||
|
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]),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Class:
|
||||||
|
name: str
|
||||||
|
attr: T.List[Stat]
|
||||||
|
|
||||||
|
|
||||||
|
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]),
|
||||||
|
]
|
||||||
|
|
||||||
|
TITLES = [
|
||||||
|
"Mr.",
|
||||||
|
"Mrs.",
|
||||||
|
"Sir",
|
||||||
|
"Sgt.",
|
||||||
|
"Ms.",
|
||||||
|
"Captain",
|
||||||
|
"Chief",
|
||||||
|
"Admiral",
|
||||||
|
"Saint",
|
||||||
|
]
|
||||||
|
|
||||||
|
IMPRESSIVE_TITLES = [
|
||||||
|
"King",
|
||||||
|
"Queen",
|
||||||
|
"Lord",
|
||||||
|
"Lady",
|
||||||
|
"Viceroy",
|
||||||
|
"Mayor",
|
||||||
|
"Prince",
|
||||||
|
"Princess",
|
||||||
|
"Chief",
|
||||||
|
"Boss",
|
||||||
|
"Archbishop",
|
||||||
|
]
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
import datetime
|
||||||
|
import random
|
||||||
|
import typing as T
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
from pqcli.config import Stat, PRIME_STATS, Race, Class, RACES, CLASSES
|
||||||
|
|
||||||
|
|
||||||
|
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()
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Stats:
|
||||||
|
def __init__(self):
|
||||||
|
self.values = {stat: 0 for stat in Stat}
|
||||||
|
|
||||||
|
def __iter__(self) -> T.Iterable[T.Tuple[Stat, int]]:
|
||||||
|
return iter(self.values.items())
|
||||||
|
|
||||||
|
def __setitem__(self, stat: Stat, value: int) -> None:
|
||||||
|
self.values[stat] = value
|
||||||
|
|
||||||
|
def __getitem__(self, stat: Stat) -> int:
|
||||||
|
return self.values[stat]
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Player:
|
||||||
|
name: str
|
||||||
|
stats: Stats
|
||||||
|
race: Race
|
||||||
|
class_: Class
|
||||||
|
birthday: datetime.datetime
|
||||||
|
|
||||||
|
|
||||||
|
class Roster:
|
||||||
|
def __init__(self) -> None:
|
||||||
|
self.players: T.List[Player] = []
|
||||||
|
|
||||||
|
|
||||||
|
class StatsBuilder:
|
||||||
|
def __init__(self):
|
||||||
|
self.history: T.List[Stats] = []
|
||||||
|
|
||||||
|
def roll(self):
|
||||||
|
stats = Stats()
|
||||||
|
for stat in PRIME_STATS:
|
||||||
|
stats[stat] = (
|
||||||
|
3
|
||||||
|
+ random.randint(0, 5)
|
||||||
|
+ random.randint(0, 5)
|
||||||
|
+ random.randint(0, 5)
|
||||||
|
)
|
||||||
|
stats[Stat.hp_max] = random.randint(0, 7) + stats[Stat.condition] // 6
|
||||||
|
stats[Stat.mp_max] = (
|
||||||
|
random.randint(0, 7) + stats[Stat.intelligence] // 6
|
||||||
|
)
|
||||||
|
self.history.append(stats)
|
||||||
|
return stats
|
||||||
|
|
||||||
|
def unroll(self):
|
||||||
|
self.history.pop()
|
||||||
|
return self.history[-1]
|
||||||
|
|
||||||
|
|
||||||
|
def create_player(stats_builder: StatsBuilder) -> Player:
|
||||||
|
now = datetime.datetime.now()
|
||||||
|
random.seed(now)
|
||||||
|
return Player(
|
||||||
|
birthday=now,
|
||||||
|
name=generate_name(),
|
||||||
|
stats=stats_builder.roll(),
|
||||||
|
race=random.choice(RACES),
|
||||||
|
class_=random.choice(CLASSES),
|
||||||
|
)
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
import typing as T
|
||||||
|
|
||||||
|
import urwid
|
||||||
|
|
||||||
|
from pqcli.game_state import Roster
|
||||||
|
from pqcli.ui.exit_view import ExitView
|
||||||
|
from pqcli.ui.new_game_view import NewGameView
|
||||||
|
from pqcli.ui.roster_view import RosterView
|
||||||
|
|
||||||
|
|
||||||
|
urwid.command_map["k"] = "cursor up"
|
||||||
|
urwid.command_map["K"] = "cursor up"
|
||||||
|
urwid.command_map["j"] = "cursor down"
|
||||||
|
urwid.command_map["J"] = "cursor down"
|
||||||
|
urwid.command_map["h"] = "cursor left"
|
||||||
|
urwid.command_map["H"] = "cursor left"
|
||||||
|
urwid.command_map["l"] = "cursor right"
|
||||||
|
urwid.command_map["L"] = "cursor right"
|
||||||
|
|
||||||
|
|
||||||
|
class Ui:
|
||||||
|
def __init__(self, roster: Roster) -> None:
|
||||||
|
self.roster = roster
|
||||||
|
self.loop = urwid.MainLoop(None, unhandled_input=self.unhandled_input)
|
||||||
|
self.old_view: T.Optional[urwid.Widget] = None
|
||||||
|
|
||||||
|
self.switch_to_roster_view()
|
||||||
|
|
||||||
|
def run(self) -> None:
|
||||||
|
self.loop.run()
|
||||||
|
|
||||||
|
def unhandled_input(self, key: str) -> bool:
|
||||||
|
callback = getattr(self.loop.widget, "unhandled_input", None)
|
||||||
|
if callback and callback(key):
|
||||||
|
return True
|
||||||
|
|
||||||
|
if key == "ctrl q":
|
||||||
|
self.old_view = self.loop.widget
|
||||||
|
self.switch_to_exit_view()
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
def switch_to_roster_view(self) -> None:
|
||||||
|
self.loop.widget = RosterView(
|
||||||
|
self.roster,
|
||||||
|
self.loop,
|
||||||
|
on_exit=self.switch_to_exit_view,
|
||||||
|
on_new_game=self.switch_to_new_game_view,
|
||||||
|
on_resume_game=self.switch_to_game_view,
|
||||||
|
)
|
||||||
|
|
||||||
|
def switch_to_exit_view(self) -> None:
|
||||||
|
self.old_view = self.loop.widget
|
||||||
|
self.loop.widget = ExitView(
|
||||||
|
self.old_view, on_exit=self.exit, on_cancel=self.cancel_exit
|
||||||
|
)
|
||||||
|
|
||||||
|
def switch_to_new_game_view(self) -> None:
|
||||||
|
self.loop.widget = NewGameView(
|
||||||
|
on_confirm=self.switch_to_game_view,
|
||||||
|
on_cancel=self.switch_to_roster_view,
|
||||||
|
)
|
||||||
|
|
||||||
|
def switch_to_game_view(self, player_name: str) -> None:
|
||||||
|
...
|
||||||
|
|
||||||
|
def exit(self) -> None:
|
||||||
|
raise urwid.ExitMainLoop()
|
||||||
|
|
||||||
|
def cancel_exit(self) -> None:
|
||||||
|
assert self.old_view is not None
|
||||||
|
self.loop.widget = self.old_view
|
||||||
|
self.old_view = None
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
import typing as T
|
||||||
|
|
||||||
|
import urwid
|
||||||
|
|
||||||
|
from pqcli.game_state import Roster
|
||||||
|
|
||||||
|
|
||||||
|
class ExitView(urwid.Overlay):
|
||||||
|
def __init__(
|
||||||
|
self, parent: urwid.Widget, on_exit: T.Callable, on_cancel: T.Callable
|
||||||
|
) -> None:
|
||||||
|
self.on_exit = on_exit
|
||||||
|
self.on_cancel = on_cancel
|
||||||
|
|
||||||
|
question = urwid.Text(("bold", "Really quit?"), "center")
|
||||||
|
yes_btn = urwid.Button("Yes", lambda _user_data: self.on_exit())
|
||||||
|
no_btn = urwid.Button("No", lambda _user_data: self.on_cancel())
|
||||||
|
|
||||||
|
line_box = urwid.LineBox(
|
||||||
|
urwid.ListBox(
|
||||||
|
urwid.SimpleFocusListWalker([question, no_btn, yes_btn])
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
super().__init__(line_box, parent, "center", 20, "middle", 5)
|
||||||
|
|
||||||
|
def keypress(self, size: T.Any, key: str) -> bool:
|
||||||
|
if key in {"y", "Y"}:
|
||||||
|
self.on_exit()
|
||||||
|
return True
|
||||||
|
if key in {"n", "N"}:
|
||||||
|
self.on_cancel()
|
||||||
|
return True
|
||||||
|
return super().keypress(size, key)
|
||||||
@@ -0,0 +1,141 @@
|
|||||||
|
import random
|
||||||
|
import typing as T
|
||||||
|
|
||||||
|
import urwid
|
||||||
|
import urwid_readline
|
||||||
|
|
||||||
|
from pqcli.game_state import Player, StatsBuilder, generate_name, create_player
|
||||||
|
from pqcli.config import RACES, CLASSES, PRIME_STATS
|
||||||
|
|
||||||
|
|
||||||
|
class StatsGroupBox(urwid.Pile):
|
||||||
|
def __init__(self, player: Player, stats_builder: StatsBuilder) -> None:
|
||||||
|
self.player = player
|
||||||
|
self.stats_builder = stats_builder
|
||||||
|
self.stat_labels = {stat: urwid.Text("0") for stat in PRIME_STATS}
|
||||||
|
self.total_label = urwid.Text("0")
|
||||||
|
self.update_values()
|
||||||
|
|
||||||
|
roll_button = urwid.Button("Roll", on_press=self.on_roll_press)
|
||||||
|
unroll_button = urwid.Button("Unroll", on_press=self.on_unroll_press)
|
||||||
|
|
||||||
|
value_texts = list(self.stat_labels.values()) + [self.total_label]
|
||||||
|
label_texts = [
|
||||||
|
urwid.Text(f"{stat.value}: ") for stat in PRIME_STATS
|
||||||
|
] + [urwid.Text("Total: ")]
|
||||||
|
|
||||||
|
super().__init__(
|
||||||
|
[
|
||||||
|
urwid.Columns(
|
||||||
|
[
|
||||||
|
(8, urwid.ListBox(label_texts)),
|
||||||
|
urwid.ListBox(value_texts),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
urwid.Filler(roll_button),
|
||||||
|
urwid.Filler(unroll_button),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
def on_roll_press(self, _user_data: T.Any) -> None:
|
||||||
|
self.player.stats = self.stats_builder.roll()
|
||||||
|
self.update_values()
|
||||||
|
|
||||||
|
def on_unroll_press(self, _user_data: T.Any) -> None:
|
||||||
|
try:
|
||||||
|
self.player.stats = self.stats_builder.unroll()
|
||||||
|
except IndexError:
|
||||||
|
return
|
||||||
|
self.update_values()
|
||||||
|
|
||||||
|
def update_values(self) -> None:
|
||||||
|
for stat, edit in self.stat_labels.items():
|
||||||
|
edit.set_text(str(self.player.stats[stat]))
|
||||||
|
self.total_label.set_text(
|
||||||
|
str(
|
||||||
|
sum(
|
||||||
|
self.player.stats[stat] for stat in self.stat_labels.keys()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class NewGameView(urwid.Pile):
|
||||||
|
def __init__(self, on_confirm: T.Callable, on_cancel: T.Callable) -> None:
|
||||||
|
self.on_confirm = on_confirm
|
||||||
|
self.on_cancel = on_cancel
|
||||||
|
|
||||||
|
stats_builder = StatsBuilder()
|
||||||
|
self.player = create_player(stats_builder)
|
||||||
|
|
||||||
|
race_checkboxes = []
|
||||||
|
for race in RACES:
|
||||||
|
urwid.RadioButton(
|
||||||
|
group=race_checkboxes,
|
||||||
|
label=race.name,
|
||||||
|
state=race.name == self.player.race.name,
|
||||||
|
)
|
||||||
|
|
||||||
|
class_checkboxes = []
|
||||||
|
for class_ in CLASSES:
|
||||||
|
urwid.RadioButton(
|
||||||
|
group=class_checkboxes,
|
||||||
|
label=class_.name,
|
||||||
|
state=class_.name == self.player.class_.name,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.char_name_edit = urwid_readline.ReadlineEdit(
|
||||||
|
"Name: ", self.player.name
|
||||||
|
)
|
||||||
|
generate_char_name_btn = urwid.Button(
|
||||||
|
"?", on_press=self.on_generate_char_name_press
|
||||||
|
)
|
||||||
|
|
||||||
|
urwid.connect_signal(
|
||||||
|
self.char_name_edit, "postchange", self.on_char_name_change
|
||||||
|
)
|
||||||
|
|
||||||
|
super().__init__(
|
||||||
|
[
|
||||||
|
(
|
||||||
|
1,
|
||||||
|
urwid.Columns(
|
||||||
|
[
|
||||||
|
urwid.Filler(self.char_name_edit),
|
||||||
|
urwid.Filler(generate_char_name_btn),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
),
|
||||||
|
urwid.Columns(
|
||||||
|
[
|
||||||
|
urwid.LineBox(
|
||||||
|
urwid.ListBox(race_checkboxes), title="Race"
|
||||||
|
),
|
||||||
|
urwid.LineBox(
|
||||||
|
urwid.ListBox(class_checkboxes), title="Class"
|
||||||
|
),
|
||||||
|
urwid.LineBox(
|
||||||
|
StatsGroupBox(self.player, stats_builder),
|
||||||
|
title="Stats",
|
||||||
|
),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
def generate_random_char_name(self) -> None:
|
||||||
|
self.char_name_edit.edit_text = generate_name()
|
||||||
|
self.char_name_edit.edit_pos = len(self.char_name_edit.edit_text)
|
||||||
|
|
||||||
|
def on_char_name_change(self, widget: urwid.Text, old_text: str) -> None:
|
||||||
|
self.player.name = widget.edit_text
|
||||||
|
|
||||||
|
def on_generate_char_name_press(self, _user_data: T.Any) -> None:
|
||||||
|
self.generate_random_char_name()
|
||||||
|
|
||||||
|
def unhandled_input(self, key: str) -> bool:
|
||||||
|
if key == "esc":
|
||||||
|
self.on_cancel()
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
import typing as T
|
||||||
|
|
||||||
|
import urwid
|
||||||
|
|
||||||
|
from pqcli.game_state import Roster
|
||||||
|
|
||||||
|
|
||||||
|
class RosterView(urwid.ListBox):
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
roster: Roster,
|
||||||
|
loop: urwid.MainLoop,
|
||||||
|
on_new_game: T.Callable,
|
||||||
|
on_resume_game: T.Callable,
|
||||||
|
on_exit: T.Callable,
|
||||||
|
) -> None:
|
||||||
|
self.roster = roster
|
||||||
|
self.on_new_game = on_new_game
|
||||||
|
self.on_resume_game = on_resume_game
|
||||||
|
self.on_exit = on_exit
|
||||||
|
|
||||||
|
logo = urwid.BigText("ProgressQuest", urwid.HalfBlock5x4Font())
|
||||||
|
|
||||||
|
buttons = []
|
||||||
|
for player in self.roster.players:
|
||||||
|
buttons.append(
|
||||||
|
urwid.Button(
|
||||||
|
label=player.name,
|
||||||
|
on_press=self.on_resume_game_press,
|
||||||
|
user_data=player.name,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
buttons.append(
|
||||||
|
urwid.Button(
|
||||||
|
label="Create a new character", on_press=self.on_new_game_press
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
buttons.append(urwid.Button(label="Exit", on_press=self.on_exit_press))
|
||||||
|
|
||||||
|
super().__init__(
|
||||||
|
[
|
||||||
|
urwid.Padding(logo, width="clip"),
|
||||||
|
urwid.Divider(),
|
||||||
|
urwid.Pile(buttons),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
def on_resume_game_press(self, user_data: T.Any) -> None:
|
||||||
|
player_name = T.cast(str, user_data)
|
||||||
|
self.on_resume_game(player_name)
|
||||||
|
|
||||||
|
def on_new_game_press(self, _user_data: T.Any) -> None:
|
||||||
|
self.on_new_game()
|
||||||
|
|
||||||
|
def on_exit_press(self, _user_data: T.Any) -> None:
|
||||||
|
self.on_exit()
|
||||||
|
|
||||||
|
def unhandled_input(self, key: str) -> bool:
|
||||||
|
if key == "esc":
|
||||||
|
self.on_exit()
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
Reference in New Issue
Block a user