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 36KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847
  1. import random as r
  2. import argparse
  3. import sys
  4. import adversaries
  5. import gear
  6. 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"]
  7. mid = beg + ["l","x","n","r"]
  8. def binarize(num, l=None):
  9. r = bin(num)[2:]
  10. try:
  11. l = int(l)
  12. except:
  13. l = None
  14. if l != None:
  15. while len(r) < l:
  16. r = "0" + r
  17. return r
  18. def gen_name(length=None, minimum=3):
  19. maximum = 8
  20. if length == None:
  21. lgt = r.randint(minimum,maximum)
  22. else:
  23. if length > maximum:
  24. length = maximum
  25. print("Maximum name length is 8.")
  26. lgt = length
  27. morae = []
  28. while len("".join(morae)) < lgt:
  29. if len(morae) == 0:
  30. mora = r.choice(beg)
  31. morae.append(mora[0].upper() + mora[1:])
  32. else:
  33. mora = r.choice(mid)
  34. if morae[-1] == mora:
  35. mora = r.choice(mid)
  36. morae.append(mora)
  37. fname = "".join(morae)[:lgt]
  38. return fname
  39. class Plot:
  40. loc1 = ["friendly","hostile","derelict","airless","poison-filled/covered","overgrown","looted","burning","frozen","haunted","infested"]
  41. loc2 = ["asteroid","moon","space station","spaceship","ringworld","Dyson sphere","planet","Space Whale","pocket of folded space","time vortex","Reroll"]
  42. miss = ["explore","loot everything not bolted down too securely","find the last group of kobolds who came here","find a rumored secret weapon","find a way to break someone else's secret weapon","claim this place in the name of the Kobold Empire","make friends","rediscover lost technology","find lost magical items","find and defeat a powerful enemy"]
  43. prob = adversaries.prob
  44. def __init__(self, loc_desc=None, locIndex=None, battlefield=None, location=None, missIndex=None, oops=None, mission=None, probIndex=None, problem=None, probName=None, secProblem=None, thirdProblem=None):
  45. self.loc_desc = loc_desc if loc_desc != None else Plot.loc1[r.randint(0, len(Plot.loc1)-1)]
  46. self.locIndex = int(locIndex) if locIndex != None else r.randint(0, len(Plot.loc2)-1)
  47. self.battlefield = int(battlefield) if battlefield != None else 0
  48. self.location = location if location != None else Plot.loc2[self.locIndex]
  49. if locIndex == None and self.locIndex == len(Plot.loc2) - 2:
  50. if self.battlefield == 0:
  51. self.battlefield = 1
  52. self.locIndex = r.randint(0, len(Plot.loc2)-3)
  53. elif locIndex == None and self.locIndex != len(Plot.loc2) - 1:
  54. if self.location == "":
  55. self.location = Plot.loc2[self.locIndex]
  56. if self.location[0].lower() in ["a","e","i","o","u"]:
  57. self.locart = 1
  58. else:
  59. self.locart = 0
  60. self.missIndex = missIndex if missIndex != None else r.randint(0, len(Plot.miss)-1)
  61. self.oops = int(oops) if oops != None else r.randint(1,12)
  62. if self.oops != 1:
  63. self.oops = 0
  64. self.mission = Plot.miss[self.missIndex]
  65. self.probIndex = probIndex if probIndex != None else r.randint(0, len(Plot.prob)-1)
  66. self.problem = Plot.prob[self.probIndex]
  67. if type(self.problem["name"]) != str:
  68. self.problem["name"] = self.problem["name"]()
  69. if type(self.problem["note"]) != str:
  70. self.problem["note"] = self.problem["note"]()
  71. if self.problem["hasMinion"]:
  72. if secProblem != None:
  73. try:
  74. self.secProblem = Plot.prob[secProblem]
  75. except:
  76. self.secProblem = Plot.prob[r.choice(self.problem["potentialMinions"])]
  77. else:
  78. self.secProblem = Plot.prob[r.choice(self.problem["potentialMinions"])]
  79. if type(self.secProblem["name"]) != str:
  80. self.secProblem["name"] = self.secProblem["name"]()
  81. if type(self.secProblem["note"]) != str:
  82. self.secProblem["note"] = self.secProblem["note"]()
  83. self.fullProblem = ""
  84. if self.problem["needsName"]:
  85. self.problem["givenname"] = probName if probName != None else gen_name()
  86. self.fullProblem += self.problem["givenname"] + ", "
  87. if not self.problem["isPlural"] and self.problem["name"].split(" ")[0] != "Old":
  88. if self.problem["name"][0].lower() in ["a","e","i","o","u"]:
  89. self.fullProblem += "an "
  90. else:
  91. self.fullProblem += "a "
  92. self.fullProblem += self.problem["name"]
  93. if self.problem["hasMinion"]:
  94. self.fullProblem += " and their minion, "
  95. # if self.secProblem["name"][0].lower() in ["a","e","i","o","u"]:
  96. # self.fullProblem += "an "
  97. # else:
  98. # self.fullProblem += "a "
  99. self.fullProblem += self.secProblem["name"]
  100. class Character:
  101. # Remember to update gen_gadget() when you add gadgets
  102. GADGETS = gear.gadgets
  103. # Remember to update gen_career() when you add careers
  104. CAREERS = [ {"id": 0, "name": "Soldier/Guard"},
  105. {"id": 1, "name": "Pilot"},
  106. {"id": 2, "name": "Medic"},
  107. {"id": 3, "name": "Mechanic"},
  108. {"id": 4, "name": "Politician"},
  109. {"id": 5, "name": "Spellcaster"},
  110. {"id": 6, "name": "Performer"},
  111. {"id": 7, "name": "Historian"},
  112. {"id": 8, "name": "Spy"},
  113. {"id": 9, "name": "Cook"},
  114. {"id": 10, "name": "Cartographer"},
  115. {"id": 11, "name": "Inventor"},
  116. {"id": 12, "name": "Merchant"},
  117. {"id": 123, "name": "Ranger"},
  118. {"id": 124, "name": "Barbarian"},
  119. {"id": 125, "name": "Artificer"},
  120. {"id": 126, "name": "Druid"},
  121. {"id": 127, "name": "Wizard"}
  122. ]
  123. def __init__(self, name=None, career=None, stats=None, gadget=None):
  124. self.name = name if name != None else ""
  125. if career == None:
  126. self.career = ""
  127. elif isinstance(career, str):
  128. self.career = career
  129. elif isinstance(career, int) and (career in range(13) or career in range(123,128)):
  130. self.career = [x for x in Character.CAREERS if x["id"] == career][0]
  131. else:
  132. self.career = ""
  133. self.stats = stats if stats != None else []
  134. if gadget == None:
  135. self.gadget = ""
  136. elif isinstance(gadget, str) or isinstance(gadget, dict):
  137. self.gadget = gadget
  138. if type(self.gadget["description"]) != str:
  139. self.gadget["description"] = self.gadget["description"]()
  140. else:
  141. self.gadget = next((x for x in Character.GADGETS if x["id"] == gadget), "")
  142. if type(self.gadget["description"]) != str:
  143. self.gadget["description"] = self.gadget["description"]()
  144. self.generate()
  145. def generate(self):
  146. if self.name == "" or self.name == None:
  147. self.gen_name()
  148. if self.stats == [] or self.stats == None:
  149. self.gen_stats()
  150. if self.career == "" or self.career == None:
  151. self.gen_career()
  152. if self.gadget == "" or self.gadget == None:
  153. self.gen_gadget()
  154. def gen_name(self):
  155. self.name = gen_name()
  156. def gen_stats(self, n=12):
  157. if n < 0:
  158. print("Too few stat points!")
  159. return [0,0,0,0]
  160. stats = [0,0,0,0]
  161. points = n
  162. slots = [0,1,2,3]
  163. for _ in range(points):
  164. tgl = False
  165. while tgl == False:
  166. slt = r.choice(slots)
  167. if slt <= 1:
  168. if stats[slt] == 6: continue
  169. if stats[slt] == 5 and r.randint(0,1) != 1: continue
  170. else:
  171. if stats[slt] == 6: continue
  172. if stats[slt] > 2 and r.randint(0,stats[slt]-2) != 1: continue
  173. stats[slt] += 1
  174. tgl = True
  175. stats[3] = stats[3] + 1
  176. if stats[3] > 6:
  177. stats[3] = 6
  178. stats[2] = stats[2] + 1
  179. if stats[2] > 6:
  180. stats[2] = 6
  181. self.stats = stats
  182. def gen_career(self):
  183. cid = r.randint(0,12)
  184. self.career = next((x for x in Character.CAREERS if x["id"] == cid), "")
  185. def gen_gadget(self):
  186. gid = r.randint(0,(len(gear.gadgets) - 5))
  187. self.gadget = [x for x in Character.GADGETS if x["id"] == gid][0]
  188. if type(self.gadget["description"]) != str:
  189. self.gadget["description"] = self.gadget["description"]()
  190. def print_name(self, html=False):
  191. if isinstance(self.career, str):
  192. cname = self.career
  193. cid = next((x for x in Character.CAREERS if x["name"] == cname), "")
  194. else:
  195. cname = self.career["name"]
  196. c = dict(next((x for x in Character.CAREERS if x["name"] == cname), ""))
  197. cid = c["id"]
  198. if html:
  199. charText = f"<h4>Name: {self.name} (Kobold {cname})</h4>"
  200. else:
  201. charText = f"\nName: {self.name} (Kobold {cname} {cid})"
  202. print(charText)
  203. def print(self, html=False):
  204. if html:
  205. if isinstance(self.career, str):
  206. cname = self.career
  207. else:
  208. cname = self.career["name"]
  209. if isinstance(self.gadget, str):
  210. gdg = {"id": 127, "name": self.gadget, "description": "", "reusable": True}
  211. else:
  212. gdg = self.gadget
  213. out = (
  214. f"<div class='kobold'>\n"
  215. f" <span class='koboldid'>\n"
  216. f" <span class='koboldname'>{self.name}</span><br>\n"
  217. f" <span class='koboldcareer'>Kobold {cname}</span>\n"
  218. f" </span>\n"
  219. f" <br>\n"
  220. f" <span class='koboldstats'>\n"
  221. f" <ul>\n"
  222. f" <li>Order: {self.stats[0]}</li>\n"
  223. f" <li>Chaos: {self.stats[1]}</li>\n"
  224. f" <li>Brain: {self.stats[2]}</li>\n"
  225. f" <li>Body: {self.stats[3]}</li>\n"
  226. f" </ul>\n"
  227. f" </span>\n"
  228. f" <br>\n"
  229. f" <span class='koboldgadget'>\n"
  230. f" <span class='koboldgadgetname'>{gdg['name']}</span><br>\n"
  231. f" <span class='koboldgadgetdescription'>{gdg['description']}</span>\n"
  232. f" <span class='koboldgadgetreuse'>{'<br>Reusable' if gdg['reusable'] else ''}</span>\n"
  233. f" </span>"
  234. f"</div>\n"
  235. )
  236. print(out)
  237. else:
  238. self.print_name()
  239. print(f"Order: {self.stats[0]}")
  240. print(f"Chaos: {self.stats[1]}")
  241. print(f"Brain: {self.stats[2]}")
  242. print(f"Body: {self.stats[3]}")
  243. if isinstance(self.gadget, str):
  244. print(f"Gadget: {self.gadget}")
  245. else:
  246. print(f"Gadget: {self.gadget['name']} ({self.gadget['description']}{'- Reusable' if self.gadget['reusable'] else ''})")
  247. class Ship:
  248. NAME1 = ["Red","Orange","Yellow","Green","Blue","Violet","Dark","Light","Frenzied","Maniacal","Ancient"]
  249. NAME2 = ["Moon","Comet","Star","Saber","World-Eater","Dancer","Looter","Phlogiston","Fireball","Mecha","Raptor"]
  250. 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"]
  251. 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"]
  252. def __init__(self, name1=None, name2=None, gqual = None, bqual = None):
  253. self.name1 = name1 if name1 != None else r.choice(Ship.NAME1)
  254. self.name2 = name2 if name2 != None else r.choice(Ship.NAME2)
  255. self.gqual = gqual if gqual != None else r.choice(Ship.GQUAL)
  256. self.bqual = bqual if bqual != None else r.choice(Ship.BQUAL)
  257. self.fullname = f"{self.name1} {self.name2}"
  258. def print(self, html=False):
  259. if (html):
  260. 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"
  261. else:
  262. shipText = f"The {self.fullname} {self.gqual}, but {self.bqual}.\n"
  263. print(shipText)
  264. class Campaign:
  265. ALPHABET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz?- "
  266. NAMELETS = ['a', 'e', 'i', 'o', 'u', 'b', 'y', 'd', 'f', 'g', 'k', 'm', 'n', 'p', 'r', 's', 't', 'w', 'z', 'l', 'x']
  267. def __init__(self, n=None, makeChars=True, fromPW=False, pw=None):
  268. if fromPW == True:
  269. self.ship = None
  270. self.params = None
  271. self.characters = None
  272. self.art = None
  273. self.masterpassword = pw
  274. self.decode_key(pw)
  275. else:
  276. self.masterpassword = None
  277. self.create_campaign(n, makeChars)
  278. def create_campaign(self, n, makeChars):
  279. n = 6 if n == None else n
  280. self.ship = Ship()
  281. self.params = Plot()
  282. self.params.problem["fullname"] = ""
  283. if self.params.problem["id"] in [1,2]:
  284. self.params.problem["fullname"] += " led by " + self.params.problem["name"]
  285. if self.params.problem["id"] in [4,7,8,10]:
  286. self.params.problem["fullname"] += " named " + self.params.problem["name"]
  287. if makeChars:
  288. self.characters = []
  289. for _ in range(n):
  290. c = Character()
  291. c.generate()
  292. self.characters.append(c)
  293. self.art = "an" if self.params.loc_desc[0] in ["a","e","i","o","u"] else "a"
  294. def generate_key(self):
  295. """
  296. "PACKTA CTICS! ------- ------- ------- ------- ------- ------- ------- ------- ------- ------- ------- -------" should generate the Season 3 Pack Tactics crew.
  297. "JUSTIN BAILEY" and "NARPAS SWORD" should do something too.
  298. Key is analphanumeric string generated from a bitfield
  299. Bitfield is:
  300. Location 1: 7 bits
  301. Location 2: 7 bits
  302. Location Battlefield: 1 bit
  303. Mission: 7 bits
  304. Oops: 1 bit
  305. Problem 1: 7 bits
  306. Problem 1 name: 40 bits
  307. Problem 2: 7 bits
  308. Ship name 1: 7 bits
  309. Ship name 2: 7 bits
  310. Ship gqual: 7 bits
  311. Ship bqual: 7 bits
  312. Character 1 name: 40 bits
  313. Character 1 career: 7 bits
  314. Character 1 order: 3 bits
  315. Character 1 chaos: 3 bits
  316. Character 1 body: 3 bits
  317. Character 1 brain: 3 bits
  318. Character 1 gadget: 7 bits
  319. Character 2 name: 40 bits
  320. Character 2 career: 7 bits
  321. Character 2 order: 3 bits
  322. Character 2 chaos: 3 bits
  323. Character 2 body: 3 bits
  324. Character 2 brain: 3 bits
  325. Character 2 gadget: 7 bits
  326. Character 3 name: 40 bits
  327. Character 3 career: 7 bits
  328. Character 3 order: 3 bits
  329. Character 3 chaos: 3 bits
  330. Character 3 body: 3 bits
  331. Character 3 brain: 3 bits
  332. Character 3 gadget: 7 bits
  333. Character 4 name: 40 bits
  334. Character 4 career: 7 bits
  335. Character 4 order: 3 bits
  336. Character 4 chaos: 3 bits
  337. Character 4 body: 3 bits
  338. Character 4 brain: 3 bits
  339. Character 4 gadget: 7 bits
  340. Character 5 name: 40 bits
  341. Character 5 career: 7 bits
  342. Character 5 order: 3 bits
  343. Character 5 chaos: 3 bits
  344. Character 5 body: 3 bits
  345. Character 5 brain: 3 bits
  346. Character 5 gadget: 7 bits
  347. Character 6 name: 40 bits
  348. Character 6 career: 7 bits
  349. Character 6 order: 3 bits
  350. Character 6 chaos: 3 bits
  351. Character 6 body: 3 bits
  352. Character 6 brain: 3 bits
  353. Character 6 gadget: 7 bits
  354. 104 for campaign
  355. 396 for characters
  356. total 501
  357. 11 remaining
  358. """
  359. self.key = ""
  360. l1 = bin(Plot.loc1.index(self.params.loc_desc))[2:]
  361. l2 = bin(Plot.loc2.index(self.params.location))[2:]
  362. lb = bin(self.params.battlefield)[2:]
  363. op = bin(self.params.oops)[2:]
  364. ms = bin(self.params.missIndex)[2:]
  365. pb1 = bin(self.params.probIndex)[2:]
  366. if self.params.problem["hasMinion"]:
  367. pb2 = bin(self.params.secProblem["id"])[2:]
  368. else:
  369. pb2 = bin(127)[2:]
  370. if self.params.problem["needsName"]:
  371. pbname = self.encode_name(self.params.problem["givenname"])
  372. else:
  373. pbname = self.encode_name("yyyyyyyy")
  374. n1 = bin(Ship.NAME1.index(self.ship.name1))[2:]
  375. n2 = bin(Ship.NAME2.index(self.ship.name2))[2:]
  376. gq = bin(Ship.GQUAL.index(self.ship.gqual))[2:]
  377. bq = bin(Ship.BQUAL.index(self.ship.bqual))[2:]
  378. chars = {}
  379. i = 0
  380. for chct in self.characters:
  381. chars[i] = {
  382. "name": self.encode_name(chct.name),
  383. "career": bin(chct.career["id"])[2:],
  384. "order": bin(chct.stats[0])[2:],
  385. "chaos": bin(chct.stats[1])[2:],
  386. "body": bin(chct.stats[2])[2:],
  387. "brains": bin(chct.stats[3])[2:],
  388. "gadget": bin(chct.gadget["id"])[2:]
  389. }
  390. i += 1
  391. l1 = lpad(l1, 7)
  392. l2 = lpad(l2, 7)
  393. ms = lpad(ms, 7)
  394. pb1 = lpad(pb1, 7)
  395. pb2 = lpad(pb2, 7)
  396. pbname = lpad(pbname, 40, "1")
  397. n1 = lpad(n1, 7)
  398. n2 = lpad(n2, 7)
  399. gq = lpad(gq, 7)
  400. bq = lpad(bq, 7)
  401. self.key += l1 + l2 + lb + ms + op + pb1 + pbname + pb2 + n1 + n2 + gq + bq
  402. for k,chct in chars.items():
  403. chct["name"] = lpad(chct["name"], 40, "1")
  404. chct["career"] = lpad(chct["career"], 7)
  405. chct["order"] = lpad(chct["order"],3)
  406. chct["chaos"] = lpad(chct["chaos"], 3)
  407. chct["body"] = lpad(chct["body"], 3)
  408. chct["brains"] = lpad(chct["brains"], 3)
  409. chct["gadget"] = lpad(chct["gadget"], 7)
  410. self.key += chct["name"] + chct["career"] + chct["order"] + chct["chaos"] + chct["body"] + chct["brains"] + chct["gadget"]
  411. while len(self.key) < 509:
  412. self.key = self.key + "0"
  413. letters = []
  414. letter = []
  415. for bit in self.key:
  416. letter.append(bit)
  417. if len(letter) == 6:
  418. letters.append(Campaign.ALPHABET[int("".join(letter),2)])
  419. letter = []
  420. words = []
  421. word = []
  422. for lt in letters:
  423. word.append(lt)
  424. if len(word) == 6:
  425. words.append("".join(word))
  426. word = []
  427. words.append("".join(word))
  428. self.password = " ".join(words)
  429. return self.password
  430. def decode_key(self, pw):
  431. densePwd = pw.replace(" ", "")
  432. if len(densePwd) != 84 and (densePwd not in ["PACKTACTICS!", "JUSTINBAILEY", "NARPASSWORD"]):
  433. print("This password is not valid. If this is a password that this generator created, please email noelle@noelle.codes and let me know.")
  434. sys.exit(0)
  435. if densePwd[:12] == "PACKTACTICS!":
  436. # Create a campaign featuring the Season 3 Pack Tactics crew.
  437. self.ship = Ship("Red", "Star", "is maneuverable & unarmored", "has a politician who thinks they're in charge of it")
  438. self.params = Plot()
  439. self.art = "an" if self.params.loc_desc[0] in ["a","e","i","o","u"] else "a"
  440. self.characters = []
  441. self.characters.append(Character("Niwri", 123, [3, 4, 4, 3], 123))
  442. self.characters.append(Character("Zax", 124, [1, 6, 2, 5], 124))
  443. self.characters.append(Character("Chroma", 125, [4, 2, 5, 3], 125))
  444. self.characters.append(Character("Zenosha", 126, [2, 3, 5, 2], 126))
  445. self.characters.append(Character("Snax", 127, [3, 4, 6, 1], 127))
  446. # self.print_params()
  447. # self.print_chars()
  448. return
  449. elif densePwd[:12] == "JUSTINBAILEY":
  450. # Create a random campaign, but everyone's Gadget is a leotard that somehow is also an environment suit
  451. self.create_campaign(n=6, makeChars=True)
  452. for c in self.characters:
  453. c.gadget = {"id":127, "name": "The Bailey", "description": "A form-fitting leotard that somehow protects the wearer from all environmental effects except extreme heat - including vacuum and poison.", "reusable": True}
  454. # self.print_params()
  455. # self.print_chars()
  456. return
  457. elif densePwd[:12] == "NARPASSWORD":
  458. # Create a random campaign, but all the kobolds' stats are set to 6
  459. self.create_campaign(n=6, makeChars=True)
  460. for c in self.characters:
  461. c.stats = [6,6,6,6]
  462. # self.print_params()
  463. # self.print_chars()
  464. return
  465. numPwd = []
  466. for c in densePwd:
  467. numPwd.append(Campaign.ALPHABET.index(c))
  468. bitPwd = [lpad(bin(x).replace("0b",""), 6) for x in numPwd]
  469. longBitPwd = []
  470. for word in bitPwd:
  471. longword = lpad(word,6)
  472. longBitPwd.append(longword)
  473. self.newBitfield = "".join(longBitPwd)
  474. while len(self.newBitfield) < 509:
  475. self.newBitfield += "0"
  476. outkey = {}
  477. # Location 1: 7 bits
  478. i,j = 0,7
  479. outkey["loc1"] = int(self.newBitfield[i:j], 2)
  480. # Location 2: 7 bits
  481. i,j = j,j+7
  482. outkey["loc2"] = int(self.newBitfield[i:j], 2)
  483. # Location Battlefield: 1 bit
  484. i,j = j,j+1
  485. outkey["battlefield"] = int(self.newBitfield[i:j], 2)
  486. # Mission: 7 bits
  487. i,j = j,j+7
  488. outkey["miss"] = int(self.newBitfield[i:j], 2)
  489. # Oops: 1 bit
  490. i,j = j,j+1
  491. outkey["oops"] = int(self.newBitfield[i:j], 2)
  492. # Problem 1: 7 bits
  493. i,j = j,j+7
  494. outkey["prob1"] = int(self.newBitfield[i:j], 2)
  495. # Problem 1 name: 40 bits
  496. i,j = j,j+40
  497. outkey["prob1name"] = self.newBitfield[i:j]
  498. # Problem 2: 7 bits
  499. i,j = j,j+7
  500. outkey["prob2"] = int(self.newBitfield[i:j], 2)
  501. # Ship name 1: 7 bits
  502. i,j = j,j+7
  503. outkey["sname1"] = int(self.newBitfield[i:j], 2)
  504. # Ship name 2: 7 bits
  505. i,j = j,j+7
  506. outkey["sname2"] = int(self.newBitfield[i:j], 2)
  507. # Ship gqual: 7 bits
  508. i,j = j,j+7
  509. outkey["gqual"] = int(self.newBitfield[i:j], 2)
  510. # Ship bqual: 7 bits
  511. i,j = j,j+7
  512. outkey["bqual"] = int(self.newBitfield[i:j], 2)
  513. # Character 1 name: 40 bits
  514. i,j = j,j+40
  515. outkey["char1name"] = self.newBitfield[i:j]
  516. # Character 1 career: 7 bits
  517. i,j = j,j+7
  518. outkey["char1career"] = int(self.newBitfield[i:j], 2)
  519. # Character 1 order: 3 bits
  520. i,j = j,j+3
  521. outkey["char1ord"] = int(self.newBitfield[i:j], 2)
  522. # Character 1 chaos: 3 bits
  523. i,j = j,j+3
  524. outkey["char1cha"] = int(self.newBitfield[i:j], 2)
  525. # Character 1 body: 3 bits
  526. i,j = j,j+3
  527. outkey["char1bod"] = int(self.newBitfield[i:j], 2)
  528. # Character 1 brain: 3 bits
  529. i,j = j,j+3
  530. outkey["char1bra"] = int(self.newBitfield[i:j], 2)
  531. # Character 1 gadget: 7 bits
  532. i,j = j,j+7
  533. outkey["char1gad"] = int(self.newBitfield[i:j], 2)
  534. # Character 2 name: 40 bits
  535. i,j = j,j+40
  536. outkey["char2name"] = self.newBitfield[i:j]
  537. # Character 2 career: 7 bits
  538. i,j = j,j+7
  539. outkey["char2career"] = int(self.newBitfield[i:j], 2)
  540. # Character 2 order: 3 bits
  541. i,j = j,j+3
  542. outkey["char2ord"] = int(self.newBitfield[i:j], 2)
  543. # Character 2 chaos: 3 bits
  544. i,j = j,j+3
  545. outkey["char2cha"] = int(self.newBitfield[i:j], 2)
  546. # Character 2 body: 3 bits
  547. i,j = j,j+3
  548. outkey["char2bod"] = int(self.newBitfield[i:j], 2)
  549. # Character 2 brain: 3 bits
  550. i,j = j,j+3
  551. outkey["char2bra"] = int(self.newBitfield[i:j], 2)
  552. # Character 2 gadget: 7 bits
  553. i,j = j,j+7
  554. outkey["char2gad"] = int(self.newBitfield[i:j], 2)
  555. # Character 3 name: 40 bits
  556. i,j = j,j+40
  557. outkey["char3name"] = self.newBitfield[i:j]
  558. # Character 3 career: 7 bits
  559. i,j = j,j+7
  560. outkey["char3career"] = int(self.newBitfield[i:j], 2)
  561. # Character 3 order: 3 bits
  562. i,j = j,j+3
  563. outkey["char3ord"] = int(self.newBitfield[i:j], 2)
  564. # Character 3 chaos: 3 bits
  565. i,j = j,j+3
  566. outkey["char3cha"] = int(self.newBitfield[i:j], 2)
  567. # Character 3 body: 3 bits
  568. i,j = j,j+3
  569. outkey["char3bod"] = int(self.newBitfield[i:j], 2)
  570. # Character 3 brain: 3 bits
  571. i,j = j,j+3
  572. outkey["char3bra"] = int(self.newBitfield[i:j], 2)
  573. # Character 3 gadget: 7 bits
  574. i,j = j,j+7
  575. outkey["char3gad"] = int(self.newBitfield[i:j], 2)
  576. # Character 4 name: 40 bits
  577. i,j = j,j+40
  578. outkey["char4name"] = self.newBitfield[i:j]
  579. # Character 4 career: 7 bits
  580. i,j = j,j+7
  581. outkey["char4career"] = int(self.newBitfield[i:j], 2)
  582. # Character 4 order: 3 bits
  583. i,j = j,j+3
  584. outkey["char4ord"] = int(self.newBitfield[i:j], 2)
  585. # Character 4 chaos: 3 bits
  586. i,j = j,j+3
  587. outkey["char4cha"] = int(self.newBitfield[i:j], 2)
  588. # Character 4 body: 3 bits
  589. i,j = j,j+3
  590. outkey["char4bod"] = int(self.newBitfield[i:j], 2)
  591. # Character 4 brain: 3 bits
  592. i,j = j,j+3
  593. outkey["char4bra"] = int(self.newBitfield[i:j], 2)
  594. # Character 4 gadget: 7 bits
  595. i,j = j,j+7
  596. outkey["char4gad"] = int(self.newBitfield[i:j], 2)
  597. # Character 5 name: 40 bits
  598. i,j = j,j+40
  599. outkey["char5name"] = self.newBitfield[i:j]
  600. # Character 5 career: 7 bits
  601. i,j = j,j+7
  602. outkey["char5career"] = int(self.newBitfield[i:j], 2)
  603. # Character 5 order: 3 bits
  604. i,j = j,j+3
  605. outkey["char5ord"] = int(self.newBitfield[i:j], 2)
  606. # Character 5 chaos: 3 bits
  607. i,j = j,j+3
  608. outkey["char5cha"] = int(self.newBitfield[i:j], 2)
  609. # Character 5 body: 3 bits
  610. i,j = j,j+3
  611. outkey["char5bod"] = int(self.newBitfield[i:j], 2)
  612. # Character 5 brain: 3 bits
  613. i,j = j,j+3
  614. outkey["char5bra"] = int(self.newBitfield[i:j], 2)
  615. # Character 5 gadget: 7 bits
  616. i,j = j,j+7
  617. outkey["char5gad"] = int(self.newBitfield[i:j], 2)
  618. # Character 6 name: 40 bits
  619. i,j = j,j+40
  620. outkey["char6name"] = self.newBitfield[i:j]
  621. # Character 6 career: 7 bits
  622. i,j = j,j+7
  623. outkey["char6career"] = int(self.newBitfield[i:j], 2)
  624. # Character 6 order: 3 bits
  625. i,j = j,j+3
  626. outkey["char6ord"] = int(self.newBitfield[i:j], 2)
  627. # Character 6 chaos: 3 bits
  628. i,j = j,j+3
  629. outkey["char6cha"] = int(self.newBitfield[i:j], 2)
  630. # Character 6 body: 3 bits
  631. i,j = j,j+3
  632. outkey["char6bod"] = int(self.newBitfield[i:j], 2)
  633. # Character 6 brain: 3 bits
  634. i,j = j,j+3
  635. outkey["char6bra"] = int(self.newBitfield[i:j], 2)
  636. # Character 6 gadget: 7 bits
  637. i,j = j,j+7
  638. outkey["char6gad"] = int(self.newBitfield[i:j], 2)
  639. self.ship = Ship(Ship.NAME1[outkey["sname1"]], Ship.NAME2[outkey["sname2"]], Ship.GQUAL[outkey["gqual"]], Ship.BQUAL[outkey["bqual"]])
  640. self.params = Plot(loc_desc=Plot.loc1[outkey["loc1"]], locIndex=outkey["loc2"], battlefield=outkey["battlefield"], location=None, missIndex=outkey["miss"], oops=outkey["oops"], mission=None, probIndex=outkey["prob1"], problem=None, probName=self.decode_name(outkey["prob1name"]), secProblem=outkey["prob2"], thirdProblem=None)
  641. self.art = "an" if self.params.loc_desc[0] in ["a","e","i","o","u"] else "a"
  642. self.characters = []
  643. for q in range(1,7):
  644. keys = [f"char{q}name", f"char{q}career", f"char{q}ord", f"char{q}cha", f"char{q}bod", f"char{q}bra", f"char{q}gad"]
  645. c = Character(name=self.decode_name(outkey[keys[0]]), career=outkey[keys[1]], stats=[outkey[keys[2]], outkey[keys[3]], outkey[keys[4]], outkey[keys[5]]], gadget=outkey[keys[6]])
  646. self.characters.append(c)
  647. #return self.newBitfield
  648. def encode_name(self, name):
  649. field = "".join([lpad(bin(Campaign.NAMELETS.index(c.lower()))[2:], 5) for c in name])
  650. return field
  651. def decode_name(self, field):
  652. i,j = 35,40
  653. name = ""
  654. for _ in range(8):
  655. k = int(field[i:j], 2)
  656. if k != 31:
  657. name = Campaign.NAMELETS[k] + name
  658. i,j = i-5, i
  659. name = name[0].upper() + name[1:]
  660. return name
  661. def print_params(self, endc=" ", html=False):
  662. st = ["Order:", "Chaos:", "Brains:", "Body:"]
  663. cst = ", ".join([" ".join(y) for y in list(zip(st, [str(x) for x in self.params.problem["stats"]]))])
  664. if self.params.oops == 1:
  665. oops = "...well, they weren't paying attention, so don't tell them, but they're supposed to "
  666. else:
  667. oops = ""
  668. mission = oops + self.params.mission
  669. # pl = "s" if self.params.problem["isPlural"] else ""
  670. note = self.params.problem["note"]
  671. secnote = ""
  672. if self.params.problem["hasMinion"]:
  673. secnote = "Their minion: " + self.params.secProblem["note"]
  674. lines = [
  675. f"The Kobolds of the {self.ship.fullname}",
  676. f"have been sent out to {self.art} {self.params.loc_desc} {self.params.location}!",
  677. f"in order to {mission}",
  678. f"but they're challenged by {self.params.fullProblem}!",
  679. f"{note} {secnote}",
  680. f"The stats of the {self.params.problem['shortname']}",
  681. f"{cst}"
  682. ]
  683. if self.params.problem["hasMinion"]:
  684. mst = ", ".join([" ".join(y) for y in list(zip(st, [str(x) for x in self.params.secProblem["stats"]]))])
  685. # spl = "s" if self.params.secProblem["isPlural"] else ""
  686. lines.append(f"The stats of the {self.params.secProblem['shortname']}")
  687. lines.append(f"{mst}")
  688. if html:
  689. secnote = ""
  690. if self.params.problem["hasMinion"]:
  691. secnote = "Their minion: " + self.params.secProblem["note"] + "<br>\n"
  692. out = (
  693. f"<div id='theship' class='firstrow'>\n"
  694. f" <span class='head'>The Ship</span>\n"
  695. f" <span id='shipname'>\n"
  696. f" The {self.ship.fullname}!\n"
  697. f" </span>\n"
  698. f" <br>\n"
  699. f" <span id='shipquality1'>\n"
  700. f" It {self.ship.gqual}...\n"
  701. f" </span><br>\n"
  702. f" <span id='shipquality2'>\n"
  703. f" But {self.ship.bqual}!\n"
  704. f" </span>\n"
  705. f"</div>\n"
  706. f"<div id='themission' class='firstrow'>\n"
  707. f" <span class='head'>The Mission</span>\n"
  708. f" <span id='missionloc'>\n"
  709. f" The kobolds have been sent to {self.art} {self.params.loc_desc} {self.params.location}\n"
  710. f" </span><br>\n"
  711. f" <span id='missiontarget'>\n"
  712. f" in order to {mission}!\n"
  713. f" </span>\n"
  714. f"</div>\n<br clear='all'>\n"
  715. f"<div id='theadversary' class='firstrow'>\n"
  716. f" <span class='head'>The Adversary</span>\n"
  717. f" They're challenged by <span id='advname'>{self.params.fullProblem}</span>!\n"
  718. f" <br>\n"
  719. f" <span id='problemnote'>\n"
  720. f" {self.params.problem['note']}<br>\n"
  721. f" {secnote}"
  722. f" </span>\n"
  723. f" <span id='problemstats'>\n"
  724. f" The stats of the {self.params.problem['shortname']}:<br>\n"
  725. f" {cst}\n"
  726. f" </span>\n"
  727. )
  728. if self.params.problem["hasMinion"]:
  729. out += (
  730. f" <br><span id='secprobstats'>\n"
  731. f" The stats of the {self.params.secProblem['shortname']}:<br>\n"
  732. f" {mst}\n"
  733. f" </span>\n"
  734. )
  735. out += f"</div>\n"
  736. print(out)
  737. print(f"<br clear='all'>\n")
  738. else:
  739. print(f"{lines[0]} {lines[1]} {lines[2]} -- {lines[3]}")
  740. print(f"{lines[4]}")
  741. print(f"{lines[5]}: {lines[6]}")
  742. if self.params.problem["hasMinion"]:
  743. print(f"- {lines[7]}: {lines[8]}")
  744. print()
  745. self.ship.print(html=html)
  746. def print_chars(self, html=False):
  747. if html:
  748. print(f"<div id='thekobolds'>\n")
  749. print(f"<span class='head'>The Kobolds</span>\n")
  750. else:
  751. print("The kobolds:")
  752. for k in self.characters:
  753. k.print(html=html)
  754. if html:
  755. print(f"</div>\n<br clear='all'>\n")
  756. def print_password(self, html=False):
  757. if self.masterpassword:
  758. pw = self.masterpassword
  759. else:
  760. pw = self.generate_key()
  761. if html:
  762. out = (
  763. f"<div id='passwordbox'>"
  764. f"<span class='passwordhead'>Permalink to this campaign:</span><br>"
  765. f"<span><a href='http://node.noelle.codes/kobold?pw={pw.replace(' ', '')}'>{pw.replace(' ', '')}</a></span><br><br>"
  766. f"<span class='passwordhead'><a href='http://node.noelle.codes/kobold'>Generate a new random campaign</a></span><br>"
  767. f"</div>"
  768. )
  769. print(out)
  770. print(f"<br clear='all'>\n")
  771. else:
  772. print(f"Password: {pw}")
  773. def decode(self, pwd):
  774. pass
  775. def lpad(s, n, c="0"):
  776. while len(s) < n:
  777. s = c + s
  778. return s
  779. if __name__ == "__main__":
  780. parser = argparse.ArgumentParser()
  781. group = parser.add_mutually_exclusive_group()
  782. group.add_argument("-c", "--campaign", help="print a full campaign block with N kobolds (default 6)", nargs="?", const=6, type=int, metavar="N")
  783. group.add_argument("-k", "--kobolds", help="print N kobolds", type=int, nargs="?", const=1, default=1, metavar="N")
  784. group.add_argument("-n", "--names", help="print N kobolds without stat blocks", nargs="?", const=1, type=int, metavar="N")
  785. group.add_argument("-p", "--params", help="print only the parameters of a campaign", action="store_true")
  786. group.add_argument("-s", "--ship", help="print only the ship name and description", action="store_true")
  787. group.add_argument("-pw", "--password", help="print the campaign defined by the submitted password", type=str, nargs="?", const=1, metavar="PW")
  788. parser.add_argument("--html", help="print in HTML instead of plain text", action="store_true")
  789. args = parser.parse_args()
  790. html = True if args.html else False
  791. if args.password:
  792. cmp = Campaign(fromPW = True, pw=args.password)
  793. cmp.print_params(html=html)
  794. cmp.print_chars(html=html)
  795. cmp.print_password(html=html)
  796. elif args.campaign:
  797. cmp = Campaign(args.campaign)
  798. cmp.print_params(html=html)
  799. cmp.print_chars(html=html)
  800. cmp.print_password(html=html)
  801. elif args.params:
  802. cmp = Campaign(makeChars=False)
  803. cmp.print_params(html=html)
  804. elif args.ship:
  805. ship = Ship()
  806. ship.print(html=html)
  807. elif args.names:
  808. for _ in range(args.names):
  809. c = Character()
  810. c.generate()
  811. c.print_name(html=html)
  812. else:
  813. for _ in range(args.kobolds):
  814. c = Character()
  815. c.generate()
  816. c.print(html=html)