A character/one-shot generator for KOBOLDS IN SPACE!
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

koboldgen.py 32KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715
  1. import random as r
  2. import argparse
  3. 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"]
  4. mid = beg + ["l","x","n","r"]
  5. def binarize(num, l=None):
  6. r = bin(num)[2:]
  7. try:
  8. l = int(l)
  9. except:
  10. l = None
  11. if l != None:
  12. while len(r) < l:
  13. r = "0" + r
  14. return r
  15. def gen_name(length=None, minimum=3):
  16. maximum = 8
  17. if length == None:
  18. lgt = r.randint(minimum,maximum)
  19. else:
  20. if length > maximum:
  21. length = maximum
  22. print("Maximum name length is 8.")
  23. lgt = length
  24. morae = []
  25. while len("".join(morae)) < lgt:
  26. if len(morae) == 0:
  27. mora = r.choice(beg)
  28. morae.append(mora[0].upper() + mora[1:])
  29. else:
  30. mora = r.choice(mid)
  31. if morae[-1] == mora:
  32. mora = r.choice(mid)
  33. morae.append(mora)
  34. return "".join(morae)
  35. class Plot:
  36. loc1 = ["friendly","hostile","derelict","airless","poison-filled/covered","overgrown","looted","burning","frozen","haunted","infested"]
  37. loc2 = ["asteroid","moon","space station","spaceship","ringworld","Dyson sphere","planet","Space Whale","pocket of folded space","time vortex","Reroll"]
  38. 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"]
  39. prob = [
  40. {
  41. "id": 0,
  42. "name": "an Undead Sample Pack (swarm of zombies and skeletons)",
  43. "shortname": "undead",
  44. "stats": [0,5,2,6],
  45. },
  46. {
  47. "id": 1,
  48. "name": "a rival band of kobolds",
  49. "shortname": "kobold rivals",
  50. "stats": [3,3,4,4],
  51. },
  52. {
  53. "id": 2,
  54. "name": "a detachment from the Elf Armada",
  55. "shortname": "elves",
  56. "stats": [4,3,5,4],
  57. },
  58. {
  59. "id": 3,
  60. "name": "refugees with parasites. Big parasites",
  61. "shortname": "refugees",
  62. "stats": [2,4,0,0],
  63. },
  64. {
  65. "id": 4,
  66. "name": "an artificial intelligence bent on multi-world domination",
  67. "shortname": "AI",
  68. "stats": [4,1,6,3],
  69. },
  70. {
  71. "id": 5,
  72. "name": "robot spiders",
  73. "shortname": "spiders",
  74. "stats": [3,3,2,4],
  75. },
  76. {
  77. "id": 6,
  78. "name": "semi-intelligent metal eating slime",
  79. "shortname": "slime",
  80. "stats": [0,2,1,5],
  81. },
  82. {
  83. "id": 7,
  84. "name": "a living asteroid that intends to follow the kobolds home like the largest puppy",
  85. "shortname": "asteroid",
  86. "stats": [2,3,1,6],
  87. },
  88. {
  89. "id": 8,
  90. "name": "an old lich that wants everyone to stay off of their 'lawn'",
  91. "shortname": "lich",
  92. "stats": [5,2,6,3],
  93. },
  94. {
  95. "id": 9,
  96. "name": "elder gods hailing from the dark spaces between the stars",
  97. "shortname": "gods",
  98. "stats": [0,6,6,6],
  99. },
  100. {
  101. "id": 10,
  102. "name": "a Flying Brain Monster",
  103. "shortname": "Flying Brain Monster",
  104. "stats": [2,3,6,1],
  105. },
  106. ]
  107. def __init__(self):
  108. self.loc_desc = Plot.loc1[r.randint(0, len(Plot.loc1)-1)]
  109. self.locIndex = r.randint(0, len(Plot.loc2)-1)
  110. if self.locIndex == len(Plot.loc2) - 1:
  111. self.battlefield = 1
  112. self.locIndex = r.randint(0, len(Plot.loc2)-2)
  113. self.location = Plot.loc2[self.locIndex]
  114. if self.location[0].lower() in ["a","e","i","o","u"]:
  115. self.locart = 1
  116. else:
  117. self.locart = 0
  118. else:
  119. self.location = Plot.loc2[self.locIndex]
  120. self.battlefield = 0
  121. self.locart = 0
  122. self.missIndex = r.randint(0, len(Plot.miss)-1)
  123. self.oops = r.randint(1,12)
  124. if self.oops == 12:
  125. self.oops = 1
  126. else:
  127. self.oops = 0
  128. self.mission = Plot.miss[r.randint(0, len(Plot.miss)-1)]
  129. self.probIndex = r.randint(0, len(Plot.prob)-1)
  130. self.problem = Plot.prob[self.probIndex]
  131. self.secProblem = None
  132. self.thirdProblem = None
  133. self.problem["name"] = gen_name()
  134. if self.problem["id"] == 3:
  135. self.secProblem = {"name": "Parasites", "shortname": "parasites", "stats": [3,4,2,3]}
  136. if self.problem["id"] == 10:
  137. self.secProbIndex = r.randint(0, len(Plot.prob)-2)
  138. self.secProblem = Plot.prob[self.secProbIndex]
  139. if self.secProbIndex == 3:
  140. self.thirdProblem = {"name": "Parasites", "shortname": "parasites", "stats": [3,4,2,3]}
  141. self.fullProblem = self.problem["name"]
  142. if self.secProblem and self.secProblem["name"] != "Parasites":
  143. self.fullProblem += " and its minion, " + self.secProblem["name"]
  144. class Character:
  145. GADGETS = [ {"id": 0, "name": "Awesome Dagger of Sneak(?) Attacks", "description": "Yell 'Sneak Attack!' to count a mixed-success Body attack as a success.", "reusable": True},
  146. {"id": 1, "name": "Button of Uselessness", "description": f"This large, red button can be stuck onto any flat surface, horizontal, vertical, or otherwise. A short time after it has been placed, any character (friend or foe) nearby must succeed a Brains roll to avoid pressing the button. Pressing the button does nothing, but this action takes the place of anything that might otherwise be done during an Event if the Brains roll is failed. The button can be pressed {r.randint(1,6)} times before it breaks and no longer compels others to press it.", "reusable": False},
  147. {"id": 2, "name": "Encyclopedia of Stuff I Totally Knew", "description": "Add 2 points to the target number of any uncontested Brains roll, or 1 point to the target number of a contested Brains roll.", "reusable": True},
  148. {"id": 3, "name": "Medkit", "description": "Make a Brains/Order roll to restore 2 lost Body points, or 1 on a Mixed Success. If the character has medical training, restored Body points may be doubled.", "reusable": False},
  149. {"id": 4, "name": "Potion of Healing", "description": "Restore 1 lost Body point. Using counts as an Event but no roll is needed unless it’s contested.", "reusable": False},
  150. {"id": 5, "name": "Tinfoil Helm of Shielding", "description": "Count any Brains damage as a mixed success. If you take Body damage, roll 1d6; on a 6, the Helm is useless until you take a nap.", "reusable": True},
  151. {"id": 6, "name": "Handy Toothbrush", "description": "Scrub the Space Gunk off whatever object you're interacting with to turn a mixed Order success to make that object work into a full success.", "reusable": True},
  152. {"id": 7, "name": "Jar of ... Something", "description": "Throw it (Body/Chaos) at another character to make them spend an Event cleaning it off, or throw it at the floor to make a ten-foot circle of difficult terrain.", "reusable": False},
  153. {"id": 8, "name": "Pocket Sand!", "description": "Yell 'Pocket Sand!' to count a successful Body attack against you as a mixed success.", "reusable": False},
  154. {"id": 9, "name": "The Fabulous Grappling Hook", "description": "As an Event, instantly move 50 feet in any direction. (Make sure you have 50 feet available to move in or take 1 Body damage when you arrive short of that.)", "reusable": True},
  155. {"id": 10, "name": "Pocket Accordion", "description": "Make a Brains/Order or Brains/Chaos roll. On a success, any nearby opponent has -1 Brains during their next event. On a failure, everybody nearby, including you, has -1 Brains on their next event.", "reusable": True},
  156. {"id": 11, "name": "Cloak of Adsorption", "description": "The cloak starts out white. Whenever you are the target of a successful attack, the cloak becomes the color of whatever hit you. If your cloak is already the same color as whatever hit you, the attack becomes a mixed success and the cloak turns black and can no longer absorb colors. The cloak becomes white again after a nap.", "reusable": True},
  157. {"id": 12, "name": "Cloak of Absorption", "description": "The cloak starts out white. You can remove the cloak and lay it over difficult terrain to make it easy terrain; if the terrain is difficult because the ground is wet, the cloak becomes wet and the ground in that area becomes dry. The cloak dries out after a nap.", "reusable": True},
  158. {"id": 13, "name": "Cloak of Desorption", "description": "The cloak starts out black. As an Event, you can cause a gas to evaporate from the cloak, leaving it a dingy gray and healing 1 Body to anyone within ten feet of you. The cloak becomes black again after a nap.", "reusable": True},
  159. {"id": 14, "name": "Cloak of Food Portions", "description": "Wearing this cloak causes it to flare out at the base, making the wearer resemble a pyramid. In conversations regarding food the wearer can add +2 to Brains rolls when trying to convince others to alter their diets. This can be done once per nap. Considering how many large things tend to snack on kobolds, this has been found to actually be quite useful.", "reusable": True},
  160. {"id": 15, "name": "Huge Goggles", "description": "If you take Body damage, roll 1d6; on a 6, the lenses of the Goggles break until you take a nap.", "reusable": True},
  161. ]
  162. CAREERS = [ {"id": 0, "name": "Soldier/Guard"},
  163. {"id": 1, "name": "Pilot"},
  164. {"id": 2, "name": "Medic"},
  165. {"id": 3, "name": "Mechanic"},
  166. {"id": 4, "name": "Politician"},
  167. {"id": 5, "name": "Spellcaster"},
  168. {"id": 6, "name": "Performer"},
  169. {"id": 7, "name": "Historian"},
  170. {"id": 8, "name": "Spy"},
  171. {"id": 9, "name": "Cook"},
  172. {"id": 10, "name": "Cartographer"},
  173. {"id": 11, "name": "Inventor"},
  174. {"id": 12, "name": "Merchant"}
  175. ]
  176. def __init__(self):
  177. self.name = ""
  178. self.career = ""
  179. self.stats = []
  180. def generate(self):
  181. self.gen_name()
  182. self.gen_stats()
  183. self.gen_career()
  184. self.gen_gadget()
  185. def gen_name(self):
  186. self.name = gen_name()
  187. def gen_stats(self, n=12):
  188. if n < 0:
  189. print("Too few stat points!")
  190. return [0,0,0,0]
  191. stats = [0,0,0,0]
  192. points = n
  193. slots = [0,1,2,3]
  194. for _ in range(points):
  195. tgl = False
  196. while tgl == False:
  197. slt = r.choice(slots)
  198. if slt <= 1:
  199. if stats[slt] == 6: continue
  200. if stats[slt] == 5 and r.randint(0,1) != 1: continue
  201. else:
  202. if stats[slt] == 6: continue
  203. if stats[slt] > 2 and r.randint(0,stats[slt]-2) != 1: continue
  204. stats[slt] += 1
  205. tgl = True
  206. stats[3] = stats[3] + 1
  207. if stats[3] > 6:
  208. stats[3] = 6
  209. stats[2] = stats[2] + 1
  210. if stats[2] > 6:
  211. stats[2] = 6
  212. self.stats = stats
  213. def gen_career(self):
  214. self.career = r.choice(Character.CAREERS)
  215. def gen_gadget(self):
  216. self.gadget = r.choice(Character.GADGETS)
  217. def print_name(self, html=False):
  218. if html:
  219. charText = f"<h4>Name: {self.name} (Kobold {self.career})</h4>"
  220. else:
  221. charText = f"\nName: {self.name} (Kobold {self.career})"
  222. print(charText)
  223. def print(self, html=False):
  224. if html:
  225. out = (
  226. f"<div class='kobold'>\n"
  227. f" <span class='koboldid'>\n"
  228. f" <span class='koboldname'>{self.name}</span><br>\n"
  229. f" <span class='koboldcareer'>Kobold {self.career.name}</span>\n"
  230. f" </span>\n"
  231. f" <br>\n"
  232. f" <span class='koboldstats'>\n"
  233. f" <ul>\n"
  234. f" <li>Order: {self.stats[0]}</li>\n"
  235. f" <li>Chaos: {self.stats[1]}</li>\n"
  236. f" <li>Brain: {self.stats[2]}</li>\n"
  237. f" <li>Body: {self.stats[3]}</li>\n"
  238. f" </ul>\n"
  239. f" </span>\n"
  240. f" <br>\n"
  241. f" <span class='koboldgadget'>\n"
  242. f" <span class='koboldgadgetname'>{self.gadget['name']}</span><br>\n"
  243. f" <span class='koboldgadgetdescription'>{self.gadget['description']}</span>\n"
  244. f" <span class='koboldgadgetreuse'>{'<br>Reusable' if self.gadget['reusable'] else ''}</span>\n"
  245. f" </span>"
  246. f"</div>\n"
  247. )
  248. print(out)
  249. else:
  250. self.print_name()
  251. print(f"Order: {self.stats[0]}")
  252. print(f"Chaos: {self.stats[1]}")
  253. print(f"Brain: {self.stats[2]}")
  254. print(f"Body: {self.stats[3]}")
  255. print(f"Gadget: {self.gadget['name']} ({self.gadget['description']}{'- Reusable' if self.gadget['reusable'] else ''})")
  256. class Ship:
  257. NAME1 = ["Red","Orange","Yellow","Green","Blue","Violet","Dark","Light","Frenzied","Maniacal","Ancient"]
  258. NAME2 = ["Moon","Comet","Star","Saber","World-Eater","Dancer","Looter","Phlogiston","Fireball","Mecha","Raptor"]
  259. GQUAL = ["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"]
  260. BQUAL = ["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"]
  261. def __init__(self, name1=None, name2=None, gqual = None, bqual = None):
  262. self.name1 = name1 if name1 != None else r.choice(Ship.NAME1)
  263. self.name2 = name2 if name2 != None else r.choice(Ship.NAME2)
  264. self.gqual = gqual if gqual != None else r.choice(Ship.GQUAL)
  265. self.bqual = bqual if bqual != None else r.choice(Ship.BQUAL)
  266. self.fullname = f"{self.name1} {self.name2}"
  267. def print(self, html=False):
  268. if (html):
  269. 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"
  270. else:
  271. shipText = f"The {self.fullname} {self.gqual}, but {self.bqual}.\n"
  272. print(shipText)
  273. class Campaign:
  274. ALPHABET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz?- "
  275. NAMELETS = ['a', 'e', 'i', 'o', 'u', 'b', 'y', 'd', 'f', 'g', 'k', 'm', 'n', 'p', 'r', 's', 't', 'w', 'z', 'l', 'x']
  276. def __init__(self, n=None, makeChars=True, fromPW=False, pw=None):
  277. if fromPW == True:
  278. self.ship = None
  279. self.params. = None
  280. self.characters = None
  281. self.art = None
  282. self.decode_key(pw)
  283. else:
  284. n = 6 if n == None else n
  285. self.ship = Ship()
  286. self.params = Plot()
  287. self.params.problem["fullname"] = ""
  288. if self.params.problem["id"] in [1,2]:
  289. self.params.problem["fullname"] += " led by " + self.params.problem["name"]
  290. if self.params.problem["id"] in [4,7,8,10]:
  291. self.params.problem["fullname"] += " named " + self.params.problem["name"]
  292. if makeChars:
  293. self.characters = []
  294. for _ in range(n):
  295. c = Character()
  296. c.generate()
  297. self.characters.append(c)
  298. self.art = "an" if self.params.loc_desc[0] in ["a","e","i","o","u"] else "a"
  299. def generate_key(self):
  300. """
  301. "PACKTA CTICS! ------- ------- ------- ------- ------- ------- ------- ------- ------- ------- ------- -------" should generate the Season 3 Pack Tactics crew.
  302. "JUSTIN BAILEY" and "NARPAS SWORD" should do something too.
  303. Key is analphanumeric string generated from a bitfield
  304. Bitfield is:
  305. Location 1: 7 bits
  306. Location 2: 7 bits
  307. Location Battlefield: 1 bit
  308. Mission: 7 bits
  309. Oops: 1 bit
  310. Problem 1: 7 bits
  311. Problem 1 name: 40 bits
  312. Problem 2: 7 bits
  313. Ship name 1: 7 bits
  314. Ship name 2: 7 bits
  315. Ship gqual: 7 bits
  316. Ship bqual: 7 bits
  317. Character 1 name: 40 bits
  318. Character 1 career: 7 bits
  319. Character 1 order: 3 bits
  320. Character 1 chaos: 3 bits
  321. Character 1 body: 3 bits
  322. Character 1 brain: 3 bits
  323. Character 1 gadget: 7 bits
  324. Character 2 name: 40 bits
  325. Character 2 career: 7 bits
  326. Character 2 order: 3 bits
  327. Character 2 chaos: 3 bits
  328. Character 2 body: 3 bits
  329. Character 2 brain: 3 bits
  330. Character 2 gadget: 7 bits
  331. Character 3 name: 40 bits
  332. Character 3 career: 7 bits
  333. Character 3 order: 3 bits
  334. Character 3 chaos: 3 bits
  335. Character 3 body: 3 bits
  336. Character 3 brain: 3 bits
  337. Character 3 gadget: 7 bits
  338. Character 4 name: 40 bits
  339. Character 4 career: 7 bits
  340. Character 4 order: 3 bits
  341. Character 4 chaos: 3 bits
  342. Character 4 body: 3 bits
  343. Character 4 brain: 3 bits
  344. Character 4 gadget: 7 bits
  345. Character 5 name: 40 bits
  346. Character 5 career: 7 bits
  347. Character 5 order: 3 bits
  348. Character 5 chaos: 3 bits
  349. Character 5 body: 3 bits
  350. Character 5 brain: 3 bits
  351. Character 5 gadget: 7 bits
  352. Character 6 name: 40 bits
  353. Character 6 career: 7 bits
  354. Character 6 order: 3 bits
  355. Character 6 chaos: 3 bits
  356. Character 6 body: 3 bits
  357. Character 6 brain: 3 bits
  358. Character 6 gadget: 7 bits
  359. 104 for campaign
  360. 396 for characters
  361. total 501
  362. 11 remaining
  363. """
  364. self.key = ""
  365. l1 = bin(Plot.loc1.index(self.params.loc_desc))[2:]
  366. l2 = bin(Plot.loc2.index(self.params.location))[2:]
  367. lb = bin(self.params.battlefield)[2:]
  368. op = bin(self.params.oops)[2:]
  369. ms = bin(Plot.miss.index(self.params.mission))[2:]
  370. pb1 = bin(self.params.probIndex)[2:]
  371. if self.params.probIndex == 10:
  372. pb2 = bin(self.params.secProbIndex)[2:]
  373. else:
  374. pb2 = bin(127)[2:]
  375. pbname = "".join([bin(Campaign.NAMELETS.index(x.lower()))[2:] for x in self.params.problem["name"]])
  376. n1 = bin(Ship.NAME1.index(self.ship.name1))[2:]
  377. n2 = bin(Ship.NAME2.index(self.ship.name2))[2:]
  378. gq = bin(Ship.GQUAL.index(self.ship.gqual))[2:]
  379. bq = bin(Ship.BQUAL.index(self.ship.bqual))[2:]
  380. chars = {}
  381. i = 0
  382. for chct in self.characters:
  383. chars[i] = {
  384. "name": "".join([bin(Campaign.NAMELETS.index(x.lower()))[2:] for x in chct.name]),
  385. "career": bin(chct.career["id"])[2:],
  386. "order": bin(chct.stats[0])[2:],
  387. "chaos": bin(chct.stats[1])[2:],
  388. "body": bin(chct.stats[2])[2:],
  389. "brains": bin(chct.stats[3])[2:],
  390. "gadget": bin(chct.gadget["id"])[2:]
  391. }
  392. i += 1
  393. # print(chars)
  394. while len(l1) < 7:
  395. l1 = "0" + l1
  396. # print(f"Len(l1) = {len(l1)}")
  397. while len(l2) < 7:
  398. l2 = "0" + l2
  399. # print(f"Len(l2) = {len(l2)}")
  400. while len(ms) < 7:
  401. ms = "0" + ms
  402. # print(f"Len(ms) = {len(ms)}")
  403. while len(pb1) < 7:
  404. pb1 = "0" + pb1
  405. # print(f"Len(pb1) = {len(pb1)}")
  406. while len(pb2) < 7:
  407. pb2 = "0" + pb2
  408. # print(f"Len(pb2) = {len(pb2)}")
  409. while len(pbname) < 40:
  410. pbname = "0" + pbname
  411. # print(f"Len(pbname) = {len(pbname)}")
  412. while len(n1) < 7:
  413. n1 = "0" + n1
  414. # print(f"Len(n1) = {len(n1)}")
  415. while len(n2) < 7:
  416. n2 = "0" + n2
  417. # print(f"Len(n2) = {len(n2)}")
  418. while len(gq) < 7:
  419. gq = "0" + gq
  420. # print(f"Len(gq) = {len(gq)}")
  421. while len(bq) < 7:
  422. bq = "0" + bq
  423. # print(f"Len(bq) = {len(bq)}")
  424. self.key += l1 + l2 + lb + op + ms + pb1 + pb2 + pbname + n1 + n2 + gq + bq
  425. for k,chct in chars.items():
  426. while len(chct["name"]) < 40:
  427. chct["name"] = "0" + chct["name"]
  428. # print(f"Len(chct.name) = {len(chct['name'])}")
  429. while len(chct["career"]) < 7:
  430. chct["career"] = "0" + chct["career"]
  431. # print(f"Len(chct.career) = {len(chct['career'])}")
  432. while len(chct["order"]) < 3:
  433. chct["order"] = "0" + chct["order"]
  434. # print(f"Len(chct.order) = {len(chct['order'])}")
  435. while len(chct["chaos"]) < 3:
  436. chct["chaos"] = "0" + chct["chaos"]
  437. # print(f"Len(chct.chaos) = {len(chct['chaos'])}")
  438. while len(chct["body"]) < 3:
  439. chct["body"] = "0" + chct["body"]
  440. # print(f"Len(chct.body) = {len(chct['body'])}")
  441. while len(chct["brains"]) < 3:
  442. chct["brains"] = "0" + chct["brains"]
  443. # print(f"Len(chct.brains) = {len(chct['brains'])}")
  444. while len(chct["gadget"]) < 7:
  445. chct["gadget"] = "0" + chct["gadget"]
  446. # print(f"Len(chct.gadget) = {len(chct['gadget'])}")
  447. self.key += chct["name"] + chct["career"] + chct["order"] + chct["chaos"] + chct["body"] + chct["brains"] + chct["gadget"]
  448. # print(len(self.key))
  449. while len(self.key) < 509:
  450. self.key = self.key + "0"
  451. # print(len(self.key))
  452. self.okey = ""
  453. letters = []
  454. letter = []
  455. for bit in self.key:
  456. letter.append(bit)
  457. if len(letter) == 6:
  458. letters.append(Campaign.ALPHABET[int("".join(letter),2)])
  459. letter = []
  460. words = []
  461. word = []
  462. for lt in letters:
  463. word.append(lt)
  464. if len(word) == 6:
  465. words.append("".join(word))
  466. word = []
  467. words.append("".join(word))
  468. self.password = " ".join(words)
  469. # print(self.password)
  470. return self.password
  471. def decode_key(self, pw):
  472. """
  473. Repeating this here for the sake of convenience.
  474. Location 1: 7 bits
  475. Location 2: 7 bits
  476. Location Battlefield: 1 bit
  477. Mission: 7 bits
  478. Oops: 1 bit
  479. Problem 1: 7 bits
  480. Problem 1 name: 40 bits
  481. Problem 2: 7 bits
  482. Ship name 1: 7 bits
  483. Ship name 2: 7 bits
  484. Ship gqual: 7 bits
  485. Ship bqual: 7 bits
  486. Character 1 name: 40 bits
  487. Character 1 career: 7 bits
  488. Character 1 order: 3 bits
  489. Character 1 chaos: 3 bits
  490. Character 1 body: 3 bits
  491. Character 1 brain: 3 bits
  492. Character 1 gadget: 7 bits
  493. Character 2 name: 40 bits
  494. Character 2 career: 7 bits
  495. Character 2 order: 3 bits
  496. Character 2 chaos: 3 bits
  497. Character 2 body: 3 bits
  498. Character 2 brain: 3 bits
  499. Character 2 gadget: 7 bits
  500. Character 3 name: 40 bits
  501. Character 3 career: 7 bits
  502. Character 3 order: 3 bits
  503. Character 3 chaos: 3 bits
  504. Character 3 body: 3 bits
  505. Character 3 brain: 3 bits
  506. Character 3 gadget: 7 bits
  507. Character 4 name: 40 bits
  508. Character 4 career: 7 bits
  509. Character 4 order: 3 bits
  510. Character 4 chaos: 3 bits
  511. Character 4 body: 3 bits
  512. Character 4 brain: 3 bits
  513. Character 4 gadget: 7 bits
  514. Character 5 name: 40 bits
  515. Character 5 career: 7 bits
  516. Character 5 order: 3 bits
  517. Character 5 chaos: 3 bits
  518. Character 5 body: 3 bits
  519. Character 5 brain: 3 bits
  520. Character 5 gadget: 7 bits
  521. Character 6 name: 40 bits
  522. Character 6 career: 7 bits
  523. Character 6 order: 3 bits
  524. Character 6 chaos: 3 bits
  525. Character 6 body: 3 bits
  526. Character 6 brain: 3 bits
  527. Character 6 gadget: 7 bits
  528. """
  529. densePwd = pw.replace(" ", "")
  530. if densePwd == "PACKTACTICS!------------------------------------------------------------------------------------":
  531. # Create a campaign featuring the Season 3 Pack Tactics crew.
  532. return
  533. elif densePwd == "JUSTINBAILEY------------------------------------------------------------------------------------":
  534. # Create a random campaign, but everyone's Gadget is a leotard that somehow is also an environment suit
  535. return
  536. elif densePwd == "NARPASSWORD------------------------------------------------------------------------------------":
  537. # Create a random campaign, but all the kobolds' stats are set to 6
  538. return
  539. numPwd = []
  540. for c in densePwd:
  541. numPwd.append(Campaign.ALPHABET.index(c))
  542. bitPwd = [bin(x).replace("0b","") for x in numPwd]
  543. longBitPwd = []
  544. for word in bitPwd:
  545. longword = word
  546. while len(longword) < 6:
  547. longword = "0" + longword
  548. longBitPwd.append(longword)
  549. self.newBitfield = "".join(longBitPwd)
  550. print(self.newBitfield)
  551. return self.newBitfield
  552. def print_params(self, endc=" ", html=False):
  553. print(len(Plot.miss))
  554. st = ["Order:", "Chaos:", "Brains:", "Body:"]
  555. cst = ", ".join([" ".join(y) for y in list(zip(st, [str(x) for x in self.params.problem["stats"]]))])
  556. if self.params.oops == 1:
  557. oops = "...well, they weren't paying attention, so don't tell them, but they're supposed "
  558. else:
  559. oops = ""
  560. location = oops + self.params.location
  561. lines = [
  562. f"The Kobolds of the {self.ship.fullname}",
  563. f"have been sent out to {self.art} {self.params.loc_desc} {location}!",
  564. f"in order {self.params.mission}",
  565. f"but they're challenged by {self.params.fullProblem}!",
  566. f"The stats of the {self.params.problem['shortname']}",
  567. f"{cst}"
  568. ]
  569. if self.params.secProblem:
  570. mst = ", ".join([" ".join(y) for y in list(zip(st, [str(x) for x in self.params.secProblem["stats"]]))])
  571. lines.append(f"The stats of the {self.params.secProblem['shortname']}")
  572. lines.append(f"{mst}")
  573. if self.params.thirdProblem:
  574. pst = ", ".join([" ".join(y) for y in list(zip(st, [str(x) for x in self.params.thirdProblem["stats"]]))])
  575. lines.append(f"The stats of the {self.params.thirdProblem['shortname']}")
  576. lines.append("{pst}")
  577. if html:
  578. out = (
  579. f"<div id='theship' class='firstrow'>\n"
  580. f" <span class='head'>The Ship</span>\n"
  581. f" <span id='shipname'>\n"
  582. f" The {self.ship.fullname}!\n"
  583. f" </span>\n"
  584. f" <br>\n"
  585. f" <span id='shipquality1'>\n"
  586. f" It {self.ship.gqual}...\n"
  587. f" </span><br>\n"
  588. f" <span id='shipquality2'>\n"
  589. f" But {self.ship.bqual}!\n"
  590. f" </span>\n"
  591. f"</div>\n"
  592. f"<div id='themission' class='firstrow'>\n"
  593. f" <span class='head'>The Mission</span>\n"
  594. f" <span id='missionloc'>\n"
  595. f" The kobolds have been sent to {self.art} {self.params.loc_desc} {self.params.location}!\n"
  596. f" </span><br>\n"
  597. f" <span id='missiontarget'>\n"
  598. f" in order {self.params.mission}\n"
  599. f" </span>\n"
  600. f"</div>\n<br clear='all'>\n"
  601. f"<div id='theadversary' class='firstrow'>\n"
  602. f" <span class='head'>The Adversary</span>\n"
  603. f" They're challenged by <span id='advname'>{self.params.fullProblem}</span>!\n"
  604. f" <br>\n"
  605. f" <span id='problemstats'>\n"
  606. f" The stats of the {self.params.problem['shortname']}:<br>\n"
  607. f" {cst}\n"
  608. f" </span>\n"
  609. )
  610. if self.params.secProblem:
  611. out += (
  612. f" <br><span id='secprobstats'>\n"
  613. f" The stats of the {self.params.secProblem['shortname']}:<br>\n"
  614. f" {mst}\n"
  615. f" </span>\n"
  616. )
  617. if self.params.thirdProblem:
  618. out += (
  619. f" <br><span id='thirdprobstats'>\n"
  620. f" The stats of the {self.params.thirdProblem['shortname']}:<br>\n"
  621. f" {pst}\n"
  622. f" </span>\n"
  623. )
  624. out += f"</div>\n"
  625. print(out)
  626. print(f"<br clear='all'>\n")
  627. else:
  628. print(f"{lines[0]} {lines[1]} {lines[2]} -- {lines[3]}")
  629. print(f"{lines[4]}: {lines[5]}")
  630. if self.params.secProblem:
  631. print(f"- {lines[6]}: {lines[7]}")
  632. if self.params.thirdProblem:
  633. print(f"- - {lines[8]}: {lines[9]}")
  634. print()
  635. self.ship.print(html=html)
  636. def print_chars(self, html=False):
  637. if html:
  638. print(f"<div id='thekobolds'>\n")
  639. print(f"<span class='head'>The Kobolds</span>\n")
  640. else:
  641. print("The kobolds:")
  642. for k in self.characters:
  643. k.print(html=html)
  644. if html:
  645. print(f"</div>\n<br clear='all'>\n")
  646. def decode(self, pwd):
  647. pass
  648. class Password:
  649. pass
  650. if __name__ == "__main__":
  651. parser = argparse.ArgumentParser()
  652. group = parser.add_mutually_exclusive_group()
  653. group.add_argument("-c", "--campaign", help="print a full campaign block with N kobolds (default 6)", nargs="?", const=6, type=int, metavar="N")
  654. group.add_argument("-k", "--kobolds", help="print N kobolds", type=int, nargs="?", const=1, default=1, metavar="N")
  655. group.add_argument("-n", "--names", help="print N kobolds without stat blocks", nargs="?", const=1, type=int, metavar="N")
  656. group.add_argument("-p", "--params", help="print only the parameters of a campaign", action="store_true")
  657. group.add_argument("-s", "--ship", help="print only the ship name and description", action="store_true")
  658. group.add_argument("-pw", "--password", help="print the campaign defined by the submitted password", type=str, nargs="?", const=1, metavar="PW")
  659. parser.add_argument("--html", help="print in HTML instead of plain text", action="store_true")
  660. args = parser.parse_args()
  661. # print(args)
  662. html = True if args.html else False
  663. if args.password:
  664. pw = Password(args.password)
  665. # print(pw.newBitfield)
  666. cmp = Campaign(fromPW = True)
  667. cmp.decode(pw)
  668. elif args.campaign:
  669. cmp = Campaign(args.campaign)
  670. cmp.print_params(html=html)
  671. cmp.print_chars(html=html)
  672. print(cmp.generate_key())
  673. elif args.params:
  674. cmp = Campaign(makeChars=False)
  675. cmp.print_params(html=html)
  676. elif args.ship:
  677. ship = Ship()
  678. ship.print(html=html)
  679. elif args.names:
  680. for _ in range(args.names):
  681. c = Character()
  682. c.generate()
  683. c.print_name(html=html)
  684. else:
  685. for _ in range(args.kobolds):
  686. c = Character()
  687. c.generate()
  688. c.print(html=html)