123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365 |
- import random as r
- import argparse
-
- beg = ["a","e","i","o","u","ba","be","bi","bo","bu","by","y","da","de","di","do","du","dy","fa","fi","fo","fe","fu","ga","ge","gi","go","gu","ka","ke","ki","ko","ku","ky","ma","me","mi","mo","mu","na","ne","ni","no","nu","pa","pe","pi","po","pu","ra","re","ri","ro","ru","ry","sa","se","si","so","su","ta","te","ti","to","tu","ty","wa","we","wi","wo","wy","za","ze","zi","zo","zu","zy"]
- mid = beg + ["l","x","n","r"]
-
- def gen_name(length=None, minimum=3, maximum=9):
- if length == None:
- lgt = r.randint(minimum,maximum)
- else:
- lgt = length
- morae = []
- while len("".join(morae)) < lgt:
- if len(morae) == 0:
- mora = r.choice(beg)
- morae.append(mora[0].upper() + mora[1:])
- else:
- mora = r.choice(mid)
- if morae[-1] == mora:
- mora = r.choice(mid)
- morae.append(mora)
- return "".join(morae)
-
- class Plot:
- loc1 = ["friendly","hostile","derelict","airless","poison-filled/covered","overgrown","looted","burning","frozen","haunted","infested"]
- loc2 = ["asteroid","moon","space station","spaceship","ringworld","Dyson sphere","planet","Space Whale","pocket of folded space","time vortex","Reroll"]
- miss = ["to explore","to loot everything not bolted down too securely","to find the last group of kobolds who came here","to find a rumored secret weapon","to find a way to break someone else's secret weapon","to claim this place in the name of the Kobold Empire","to make friends","to rediscover lost technology","to find lost magical items","to find and defeat a powerful enemy"]
- prob = [
- {
- "id": 0,
- "name": "an Undead Sample Pack (swarm of zombies and skeletons)",
- "shortname": "undead",
- "stats": [0,5,2,6],
- },
- {
- "id": 1,
- "name": "a rival band of kobolds",
- "shortname": "kobold rivals",
- "stats": [3,3,4,4],
- },
- {
- "id": 2,
- "name": "a detachment from the Elf Armada",
- "shortname": "elves",
- "stats": [4,3,5,4],
- },
- {
- "id": 3,
- "name": "refugees with parasites. Big parasites",
- "shortname": "refugees",
- "stats": [2,4,0,0],
- },
- {
- "id": 4,
- "name": "an artificial intelligence bent on multi-world domination",
- "shortname": "AI",
- "stats": [4,1,6,3],
- },
- {
- "id": 5,
- "name": "robot spiders",
- "shortname": "spiders",
- "stats": [3,3,2,4],
- },
- {
- "id": 6,
- "name": "semi-intelligent metal eating slime",
- "shortname": "slime",
- "stats": [0,2,1,5],
- },
- {
- "id": 7,
- "name": "a living asteroid that intends to follow the kobolds home like the largest puppy",
- "shortname": "asteroid",
- "stats": [2,3,1,6],
- },
- {
- "id": 8,
- "name": "an old lich that wants everyone to stay off of their 'lawn'",
- "shortname": "lich",
- "stats": [5,2,6,3],
- },
- {
- "id": 9,
- "name": "elder gods hailing from the dark spaces between the stars",
- "shortname": "gods",
- "stats": [6,6,6,6],
- },
- {
- "id": 10,
- "name": "a Flying Brain Monster",
- "shortname": "Flying Brain Monster",
- "stats": [2,3,6,1],
- },
- ]
-
- def __init__(self):
- self.loc_desc = Plot.loc1[r.randint(0, len(Plot.loc1)-1)]
- self.locIndex = r.randint(0, len(Plot.loc2)-1)
- if self.locIndex == len(Plot.loc2) - 1:
- self.location = "battlefield on "
- self.locIndex = r.randint(0, len(Plot.loc2)-2)
- self.mainLocation = Plot.loc2[self.locIndex]
- if self.mainLocation[0].lower() in ["a","e","i","o","u"]:
- self.location += "an "
- else:
- self.location += "a "
- self.location += self.mainLocation
- else:
- self.location = Plot.loc2[self.locIndex]
- self.missIndex = r.randint(0, len(Plot.miss)-1)
- self.oops = r.randint(1,12)
- self.mission = ""
- if self.oops == 12:
- self.mission += "...well, they weren't paying attention, so don't tell them, but they're supposed "
- self.mission += Plot.miss[r.randint(0, len(Plot.miss)-1)] + "!"
- self.probIndex = r.randint(0, len(Plot.prob)-1)
- self.problem = Plot.prob[self.probIndex]
- self.secProblem = None
- self.thirdProblem = None
- if self.problem["id"] in [1,2]:
- self.problem["name"] += " led by " + gen_name()
- if self.problem["id"] in [4,7,8,10]:
- self.problem["name"] += " named " + gen_name(minimum=5, maximum=12)
- if self.problem["id"] == 3:
- self.secProblem = {"name": "Parasites", "shortname": "parasites", "stats": [3,4,2,3]}
- if self.problem["id"] == 10:
- self.secProbIndex = r.randint(0, len(Plot.prob)-2)
- self.secProblem = Plot.prob[self.secProbIndex]
- if self.secProbIndex == 3:
- self.thirdProblem = {"name": "Parasites", "shortname": "parasites", "stats": [3,4,2,3]}
- self.fullProblem = self.problem["name"]
- if self.secProblem and self.secProblem["name"] != "Parasites":
- self.fullProblem += " and its minion, " + self.secProblem["name"]
-
-
- class Character:
- def __init__(self):
- self.name = ""
- self.career = ""
- self.stats = []
-
- def generate(self):
- self.gen_name()
- self.gen_stats()
- self.gen_career()
-
- def gen_name(self):
- self.name = gen_name()
-
- def gen_stats(self, n=12):
- if n < 0:
- print("Too few stat points!")
- return [0,0,0,0]
- stats = [0,0,0,0]
- points = n
- slots = [0,1,2,3]
- for _ in range(points):
- tgl = False
- while tgl == False:
- slt = r.choice(slots)
- if slt <= 1:
- if stats[slt] == 6: continue
- if stats[slt] == 5 and r.randint(0,1) != 1: continue
- else:
- if stats[slt] == 6: continue
- if stats[slt] > 2 and r.randint(0,stats[slt]-2) != 1: continue
- stats[slt] += 1
- tgl = True
- stats[3] = stats[3] + 1
- if stats[3] > 6:
- stats[3] = 6
- stats[2] = stats[2] + 1
- if stats[2] > 6:
- stats[2] = 6
- self.stats = stats
-
- def gen_career(self):
- self.career = r.choice(["Soldier/Guard","Pilot","Medic","Mechanic","Politician","Spellcaster","Performer","Historian","Spy","Cook","Cartographer","Inventor","Merchant"])
-
- def print_name(self, html=False):
- if html:
- charText = f"<h4>Name: {self.name} (Kobold {self.career})</h4>"
- else:
- charText = f"\nName: {self.name} (Kobold {self.career})"
- print(charText)
-
- def print(self, html=False):
- if html:
- out = (
- f"<div class='kobold'>\n"
- f" <span class='koboldid'>\n"
- f" <span class='koboldname'>{self.name}</span><br>\n"
- f" <span class='koboldcareer'>Kobold {self.career}</span>\n"
- f" </span>\n"
- f" <br>\n"
- f" <span class='koboldstats'>\n"
- f" <ul>\n"
- f" <li>Order: {self.stats[0]}</li>\n"
- f" <li>Chaos: {self.stats[1]}</li>\n"
- f" <li>Brain: {self.stats[2]}</li>\n"
- f" <li>Body: {self.stats[3]}</li>\n"
- f" </ul>\n"
- f" </span>\n"
- f"</div>\n"
- )
- print(out)
- else:
- self.print_name()
- print(f"Order: {self.stats[0]}")
- print(f"Chaos: {self.stats[1]}")
- print(f"Brain: {self.stats[2]}")
- print(f"Body: {self.stats[3]}")
-
- class Ship:
- def __init__(self):
- self.name1 = r.choice(["Red","Orange","Yellow","Green","Blue","Violet","Dark","Light","Frenzied","Maniacal","Ancient"])
- self.name2 = r.choice(["Moon","Comet","Star","Saber","World-Eater","Dancer","Looter","Phlogiston","Fireball","Mecha","Raptor"])
- self.gqual = r.choice(["is stealthy & unarmored","is speedy & unarmored","is maneuverable & unarmored","is always repairable","is self-repairing","is flamboyant & speedy","is slow & armored","is flamboyant & armored","is hard to maneuver & armored","has Too Many Weapons!","has a prototype hyperdrive"])
- self.bqual = r.choice(["has an annoying AI","has inconveniently crossed circuits","has an unpredictable power source","drifts to the right","is haunted","was recently 'found' so the kobolds are unused to it","is too cold","has a constant odd smell","its interior design... changes","its water pressure shifts between slow drip and power wash","it leaves a visible smoke trail"])
- self.fullname = f"{self.name1} {self.name2}"
-
- def print(self, html=False):
- if (html):
- shipText = f"<p>The <strong>{self.fullname}</strong> <span style='color: blue;'>{self.gqual}</span>, but <span style='color: red;'>{self.bqual}</span>.</p>\n"
- else:
- shipText = f"The {self.fullname} {self.gqual}, but {self.bqual}.\n"
- print(shipText)
-
- class Campaign:
- def __init__(self, n=None, makeChars=True):
- n = 6 if n == None else n
- self.ship = Ship()
- self.params = Plot()
- if makeChars:
- self.characters = []
- for _ in range(n):
- c = Character()
- c.generate()
- self.characters.append(c)
- self.art = "an" if self.params.loc_desc[0] in ["a","e","i","o","u"] else "a"
-
-
- def print_params(self, endc=" ", html=False):
- st = ["Order:", "Chaos:", "Brains:", "Body:"]
- cst = ", ".join([" ".join(y) for y in list(zip(st, [str(x) for x in self.params.problem["stats"]]))])
- lines = [
- f"The Kobolds of the {self.ship.fullname}",
- f"have been sent out to {self.art} {self.params.loc_desc} {self.params.location}",
- f"in order {self.params.mission}",
- f"but they're challenged by {self.params.fullProblem}!",
- f"The stats of the {self.params.problem['shortname']}",
- f"{cst}"
- ]
- if self.params.secProblem:
- mst = ", ".join([" ".join(y) for y in list(zip(st, [str(x) for x in self.params.secProblem["stats"]]))])
- lines.append(f"The stats of the {self.params.secProblem['shortname']}")
- lines.append(f"{mst}")
- if self.params.thirdProblem:
- pst = ", ".join([" ".join(y) for y in list(zip(st, [str(x) for x in self.params.thirdProblem["stats"]]))])
- lines.append(f"The stats of the {self.params.thirdProblem['shortname']}")
- lines.append("{pst}")
- if html:
- out = (
- f"<div id='theship' class='firstrow'>\n"
- f" <span class='head'>The Ship</span>\n"
- f" <span id='shipname'>\n"
- f" The {self.ship.fullname}!\n"
- f" </span>\n"
- f" <br>\n"
- f" <span id='shipquality1'>\n"
- f" It {self.ship.gqual}...\n"
- f" </span><br>\n"
- f" <span id='shipquality2'>\n"
- f" But {self.ship.bqual}!\n"
- f" </span>\n"
- f"</div>\n"
- f"<div id='themission' class='firstrow'>\n"
- f" <span class='head'>The Mission</span>\n"
- f" <span id='missionloc'>\n"
- f" The kobolds have been sent to {self.art} {self.params.loc_desc} {self.params.location}\n"
- f" </span><br>\n"
- f" <span id='missiontarget'>\n"
- f" in order {self.params.mission}\n"
- f" </span>\n"
- f"</div>\n"
- f"<div id='theadversary' class='firstrow'>\n"
- f" <span class='head'>The Adversary</span>\n"
- f" They're challenged by <span id='advname'>{self.params.fullProblem}</span>!\n"
- f" <br>\n"
- f" <span id='problemstats'>\n"
- f" The stats of the {self.params.problem['shortname']}:<br>\n"
- f" {cst}\n"
- f" </span>\n"
- )
- if self.params.secProblem:
- out += (
- f" <br><span id='secprobstats'>\n"
- f" The stats of the {self.params.secProblem['shortname']}:<br>\n"
- f" {mst}\n"
- f" </span>\n"
- )
- if self.params.thirdProblem:
- out += (
- f" <br><span id='thirdprobstats'>\n"
- f" The stats of the {self.params.thirdProblem['shortname']}:<br>\n"
- f" {pst}\n"
- f" </span>\n"
- )
- out += f"</div>\n"
- print(out)
- print(f"<br clear='all'>\n")
- else:
- print(f"{lines[0]} {lines[1]} {lines[2]} -- {lines[3]}")
- print(f"{lines[4]}: {lines[5]}")
- if self.params.secProblem:
- print(f"- {lines[6]}: {lines[7]}")
- if self.params.thirdProblem:
- print(f"- - {lines[8]}: {lines[9]}")
- print()
- self.ship.print(html=html)
-
- def print_chars(self, html=False):
- if html:
- print(f"<div id='thekobolds'>\n")
- print(f"<span class='head'>The Kobolds</span>\n")
- else:
- print("The kobolds:")
- for k in self.characters:
- k.print(html=html)
- if html:
- print(f"</div>\n<br clear='all'>\n")
-
- if __name__ == "__main__":
- parser = argparse.ArgumentParser()
- group = parser.add_mutually_exclusive_group()
- group.add_argument("-c", "--campaign", help="print a full campaign block with N kobolds (default 6)", nargs="?", const=6, type=int, metavar="N")
- group.add_argument("-k", "--kobolds", help="print N kobolds", type=int, nargs="?", const=1, default=1, metavar="N")
- group.add_argument("-n", "--names", help="print N kobolds without stat blocks", nargs="?", const=1, type=int, metavar="N")
- group.add_argument("-p", "--params", help="print only the parameters of a campaign", action="store_true")
- group.add_argument("-s", "--ship", help="print only the ship name and description", action="store_true")
- parser.add_argument("--html", help="print in HTML instead of plain text", action="store_true")
- args = parser.parse_args()
- # print(args)
- html = True if args.html else False
- if args.campaign:
- cmp = Campaign(args.campaign)
- cmp.print_params(html=html)
- cmp.print_chars(html=html)
- elif args.params:
- cmp = Campaign(makeChars=False)
- cmp.print_params(html=html)
- elif args.ship:
- ship = Ship()
- ship.print(html=html)
- elif args.names:
- for _ in range(args.names):
- c = Character()
- c.generate()
- c.print_name(html=html)
- else:
- for _ in range(args.kobolds):
- c = Character()
- c.generate()
- c.print(html=html)
|