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.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890
  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": 13, "name": "Bard"},
  118. {"id": 14, "name": "Sorcerer"},
  119. {"id": 15, "name": "Cleric"},
  120. {"id": 16, "name": "Rogue"},
  121. {"id": 17, "name": "Ranger"},
  122. {"id": 18, "name": "Barbarian"},
  123. {"id": 19, "name": "Artificer"},
  124. {"id": 20, "name": "Druid"},
  125. {"id": 21, "name": "Wizard"},
  126. {"id": 22, "name": "Fighter"},
  127. {"id": 23, "name": "Monk"},
  128. {"id": 24, "name": "Paladin"},
  129. {"id": 25, "name": "Warlock"},
  130. {"id": 26, "name": "Blood Hunter"}
  131. ]
  132. def __init__(self, name=None, career=None, stats=None, gadget=None):
  133. self.name = name if name != None else ""
  134. if career == None:
  135. self.career = ""
  136. elif isinstance(career, str):
  137. self.career = career
  138. elif isinstance(career, int) and career in range(27):
  139. self.career = [x for x in Character.CAREERS if x["id"] == career][0]
  140. else:
  141. self.career = ""
  142. self.stats = stats if stats != None else []
  143. if gadget == None:
  144. self.gadget = ""
  145. elif isinstance(gadget, str) or isinstance(gadget, dict):
  146. self.gadget = gadget
  147. if type(self.gadget["description"]) != str:
  148. self.gadget["description"] = self.gadget["description"]()
  149. else:
  150. self.gadget = next((x for x in Character.GADGETS if x["id"] == gadget), "")
  151. if type(self.gadget["description"]) != str:
  152. self.gadget["description"] = self.gadget["description"]()
  153. self.generate()
  154. def generate(self):
  155. if self.name == "" or self.name == None:
  156. self.gen_name()
  157. if self.stats == [] or self.stats == None:
  158. self.gen_stats()
  159. if self.career == "" or self.career == None:
  160. self.gen_career()
  161. if self.gadget == "" or self.gadget == None:
  162. self.gen_gadget()
  163. def gen_name(self):
  164. self.name = gen_name()
  165. def gen_stats(self, n=12):
  166. if n < 0:
  167. print("Too few stat points!")
  168. return [0,0,0,0]
  169. stats = [0,0,0,0]
  170. points = n
  171. slots = [0,1,2,3]
  172. for _ in range(points):
  173. tgl = False
  174. while tgl == False:
  175. slt = r.choice(slots)
  176. if slt <= 1:
  177. if stats[slt] == 6: continue
  178. if stats[slt] == 5 and r.randint(0,1) != 1: continue
  179. else:
  180. if stats[slt] == 6: continue
  181. if stats[slt] > 2 and r.randint(0,stats[slt]-2) != 1: continue
  182. stats[slt] += 1
  183. tgl = True
  184. stats[3] = stats[3] + 1
  185. if stats[3] > 6:
  186. stats[3] = 6
  187. stats[2] = stats[2] + 1
  188. if stats[2] > 6:
  189. stats[2] = 6
  190. self.stats = stats
  191. def gen_career(self):
  192. cid = r.randint(0,26)
  193. self.career = next((x for x in Character.CAREERS if x["id"] == cid), "")
  194. def gen_gadget(self):
  195. gid = r.randint(0,36)
  196. self.gadget = [x for x in Character.GADGETS if x["id"] == gid][0]
  197. if type(self.gadget["description"]) != str:
  198. self.gadget["description"] = self.gadget["description"]()
  199. def print_name(self, html=False):
  200. if isinstance(self.career, str):
  201. cname = self.career
  202. cid = next((x for x in Character.CAREERS if x["name"] == cname), "")
  203. else:
  204. cname = self.career["name"]
  205. c = dict(next((x for x in Character.CAREERS if x["name"] == cname), ""))
  206. cid = c["id"]
  207. if html:
  208. charText = f"<h4>Name: {self.name} (Kobold {cname})</h4>"
  209. else:
  210. charText = f"\nName: {self.name} (Kobold {cname} {cid})"
  211. print(charText)
  212. def print(self, html=False):
  213. if html:
  214. if isinstance(self.career, str):
  215. cname = self.career
  216. else:
  217. cname = self.career["name"]
  218. if isinstance(self.gadget, str):
  219. gdg = {"id": 127, "name": self.gadget, "description": "", "reusable": True}
  220. else:
  221. gdg = self.gadget
  222. out = (
  223. f"<div class='kobold'>\n"
  224. f" <span class='koboldid'>\n"
  225. f" <span class='koboldname'>{self.name}</span><br>\n"
  226. f" <span class='koboldcareer'>Kobold {cname}</span>\n"
  227. f" </span>\n"
  228. f" <br>\n"
  229. f" <span class='koboldstats'>\n"
  230. f" <ul>\n"
  231. f" <li>Order: {self.stats[0]}</li>\n"
  232. f" <li>Chaos: {self.stats[1]}</li>\n"
  233. f" <li>Brain: {self.stats[2]}</li>\n"
  234. f" <li>Body: {self.stats[3]}</li>\n"
  235. f" </ul>\n"
  236. f" </span>\n"
  237. f" <br>\n"
  238. f" <span class='koboldgadget'>\n"
  239. f" <span class='koboldgadgetname'>{gdg['name']}</span><br>\n"
  240. f" <span class='koboldgadgetdescription'>{gdg['description']}</span>\n"
  241. f" <span class='koboldgadgetreuse'>{'<br>Reusable' if gdg['reusable'] else ''}</span>\n"
  242. f" </span>"
  243. f"</div>\n"
  244. )
  245. print(out)
  246. else:
  247. self.print_name()
  248. print(f"Order: {self.stats[0]}")
  249. print(f"Chaos: {self.stats[1]}")
  250. print(f"Brain: {self.stats[2]}")
  251. print(f"Body: {self.stats[3]}")
  252. if isinstance(self.gadget, str):
  253. print(f"Gadget: {self.gadget}")
  254. else:
  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.masterpassword = pw
  283. self.decode_key(pw)
  284. else:
  285. self.masterpassword = None
  286. self.create_campaign(n, makeChars)
  287. def create_campaign(self, n=None, makeChars=True):
  288. n = 6 if n == None else n
  289. self.ship = Ship()
  290. self.params = Plot()
  291. self.params.problem["fullname"] = ""
  292. # if self.params.problem["id"] in [1,2]:
  293. # self.params.problem["fullname"] += " led by " + self.params.problem["name"]
  294. # if self.params.problem["id"] in [4,7,8,10]:
  295. # self.params.problem["fullname"] += " named " + self.params.problem["name"]
  296. if makeChars:
  297. self.characters = []
  298. for _ in range(n):
  299. c = Character()
  300. c.generate()
  301. self.characters.append(c)
  302. self.art = "an" if self.params.loc_desc[0] in ["a","e","i","o","u"] else "a"
  303. def generate_key(self):
  304. """
  305. "PACKTA CTICS! ------- ------- ------- ------- ------- ------- ------- ------- ------- ------- ------- -------" should generate the Season 3 Pack Tactics crew.
  306. "JUSTIN BAILEY" and "NARPAS SWORD" should do something too.
  307. Key is analphanumeric string generated from a bitfield
  308. Bitfield is:
  309. Location 1: 7 bits
  310. Location 2: 7 bits
  311. Location Battlefield: 1 bit
  312. Mission: 7 bits
  313. Oops: 1 bit
  314. Problem 1: 7 bits
  315. Problem 1 name: 40 bits
  316. Problem 2: 7 bits
  317. Ship name 1: 7 bits
  318. Ship name 2: 7 bits
  319. Ship gqual: 7 bits
  320. Ship bqual: 7 bits
  321. Character 1 name: 40 bits
  322. Character 1 career: 7 bits
  323. Character 1 order: 3 bits
  324. Character 1 chaos: 3 bits
  325. Character 1 body: 3 bits
  326. Character 1 brain: 3 bits
  327. Character 1 gadget: 7 bits
  328. Character 2 name: 40 bits
  329. Character 2 career: 7 bits
  330. Character 2 order: 3 bits
  331. Character 2 chaos: 3 bits
  332. Character 2 body: 3 bits
  333. Character 2 brain: 3 bits
  334. Character 2 gadget: 7 bits
  335. Character 3 name: 40 bits
  336. Character 3 career: 7 bits
  337. Character 3 order: 3 bits
  338. Character 3 chaos: 3 bits
  339. Character 3 body: 3 bits
  340. Character 3 brain: 3 bits
  341. Character 3 gadget: 7 bits
  342. Character 4 name: 40 bits
  343. Character 4 career: 7 bits
  344. Character 4 order: 3 bits
  345. Character 4 chaos: 3 bits
  346. Character 4 body: 3 bits
  347. Character 4 brain: 3 bits
  348. Character 4 gadget: 7 bits
  349. Character 5 name: 40 bits
  350. Character 5 career: 7 bits
  351. Character 5 order: 3 bits
  352. Character 5 chaos: 3 bits
  353. Character 5 body: 3 bits
  354. Character 5 brain: 3 bits
  355. Character 5 gadget: 7 bits
  356. Character 6 name: 40 bits
  357. Character 6 career: 7 bits
  358. Character 6 order: 3 bits
  359. Character 6 chaos: 3 bits
  360. Character 6 body: 3 bits
  361. Character 6 brain: 3 bits
  362. Character 6 gadget: 7 bits
  363. 104 for campaign
  364. 396 for characters
  365. total 501
  366. 11 remaining
  367. """
  368. self.key = ""
  369. l1 = bin(Plot.loc1.index(self.params.loc_desc))[2:]
  370. l2 = bin(Plot.loc2.index(self.params.location))[2:]
  371. lb = bin(self.params.battlefield)[2:]
  372. op = bin(self.params.oops)[2:]
  373. ms = bin(self.params.missIndex)[2:]
  374. pb1 = bin(self.params.probIndex)[2:]
  375. if self.params.problem["hasMinion"]:
  376. pb2 = bin(self.params.secProblem["id"])[2:]
  377. else:
  378. pb2 = bin(127)[2:]
  379. if self.params.problem["needsName"]:
  380. pbname = self.encode_name(self.params.problem["givenname"])
  381. else:
  382. pbname = self.encode_name("yyyyyyyy")
  383. n1 = bin(Ship.NAME1.index(self.ship.name1))[2:]
  384. n2 = bin(Ship.NAME2.index(self.ship.name2))[2:]
  385. gq = bin(Ship.GQUAL.index(self.ship.gqual))[2:]
  386. bq = bin(Ship.BQUAL.index(self.ship.bqual))[2:]
  387. chars = {}
  388. i = 0
  389. for chct in self.characters:
  390. chars[i] = {
  391. "name": self.encode_name(chct.name),
  392. "career": bin(chct.career["id"])[2:],
  393. "order": bin(chct.stats[0])[2:],
  394. "chaos": bin(chct.stats[1])[2:],
  395. "body": bin(chct.stats[2])[2:],
  396. "brains": bin(chct.stats[3])[2:],
  397. "gadget": bin(chct.gadget["id"])[2:]
  398. }
  399. i += 1
  400. l1 = lpad(l1, 7)
  401. l2 = lpad(l2, 7)
  402. ms = lpad(ms, 7)
  403. pb1 = lpad(pb1, 7)
  404. pb2 = lpad(pb2, 7)
  405. pbname = lpad(pbname, 40, "1")
  406. n1 = lpad(n1, 7)
  407. n2 = lpad(n2, 7)
  408. gq = lpad(gq, 7)
  409. bq = lpad(bq, 7)
  410. self.key += l1 + l2 + lb + ms + op + pb1 + pbname + pb2 + n1 + n2 + gq + bq
  411. for k,chct in chars.items():
  412. chct["name"] = lpad(chct["name"], 40, "1")
  413. chct["career"] = lpad(chct["career"], 7)
  414. chct["order"] = lpad(chct["order"],3)
  415. chct["chaos"] = lpad(chct["chaos"], 3)
  416. chct["body"] = lpad(chct["body"], 3)
  417. chct["brains"] = lpad(chct["brains"], 3)
  418. chct["gadget"] = lpad(chct["gadget"], 7)
  419. self.key += chct["name"] + chct["career"] + chct["order"] + chct["chaos"] + chct["body"] + chct["brains"] + chct["gadget"]
  420. while len(self.key) < 509:
  421. self.key = self.key + "0"
  422. letters = []
  423. letter = []
  424. for bit in self.key:
  425. letter.append(bit)
  426. if len(letter) == 6:
  427. letters.append(Campaign.ALPHABET[int("".join(letter),2)])
  428. letter = []
  429. words = []
  430. word = []
  431. for lt in letters:
  432. word.append(lt)
  433. if len(word) == 6:
  434. words.append("".join(word))
  435. word = []
  436. words.append("".join(word))
  437. self.password = " ".join(words)
  438. return self.password
  439. def decode_key(self, pw):
  440. densePwd = pw.replace(" ", "")
  441. aBuiltin = densePwd[:12].upper()
  442. builtins = ["PACKTACTICS!", "JUSTINBAILEY", "NARPASSWORD", "PACKTACTICS1", "PACKTACTICS2", "PACKTACTICS3", "OHNOITSPEV!!"]
  443. if len(densePwd) != 84 and (aBuiltin not in builtins):
  444. print("This password is not valid. If this is a password that this generator created, please email noelle@noelle.codes and let me know.")
  445. sys.exit(0)
  446. if aBuiltin in builtins:
  447. if aBuiltin == "PACKTACTICS!" or aBuiltin == "PACKTACTICS3":
  448. # Create a campaign featuring the Season 3 Pack Tactics crew.
  449. self.ship = Ship("Red", "Star", "is maneuverable & unarmored", "has a politician who thinks they're in charge of it")
  450. self.params = Plot()
  451. self.art = "an" if self.params.loc_desc[0] in ["a","e","i","o","u"] else "a"
  452. self.characters = []
  453. self.characters.append(Character("Niwri", 17, [3, 4, 4, 3], 123))
  454. self.characters.append(Character("Zax", 18, [1, 6, 2, 5], 124))
  455. self.characters.append(Character("Chroma", 19, [4, 2, 5, 3], 125))
  456. self.characters.append(Character("Zenosha", 20, [3, 4, 5, 2], 126))
  457. self.characters.append(Character("Snax", 21, [3, 4, 6, 1], 127))
  458. # self.print_params()
  459. # self.print_chars()
  460. return
  461. elif aBuiltin == "PACKTACTICS1":
  462. # Create a campaign featuring the Season 1 Pack Tactics crew.
  463. self.ship = Ship()
  464. self.params = Plot()
  465. self.art = "an" if self.params.loc_desc[0] in ["a","e","i","o","u"] else "a"
  466. self.characters = []
  467. self.characters.append(Character("Daldain", 13, [3, 4, 5, 2], 114))
  468. self.characters.append(Character("Gox", 18, [2, 5, 2, 5], 115))
  469. self.characters.append(Character("Zeeli", 14, [4, 2, 5, 3], 116))
  470. self.characters.append(Character("Sox", 16, [3, 3, 3, 3], 117))
  471. # self.print_params()
  472. # self.print_chars()
  473. return
  474. elif aBuiltin == "PACKTACTICS2":
  475. # Create a campaign featuring the Season 2 Pack Tactics crew.
  476. self.ship = Ship("Red", "Star", "is maneuverable & unarmored", "has a politician who thinks they're in charge of it")
  477. self.params = Plot()
  478. self.art = "an" if self.params.loc_desc[0] in ["a","e","i","o","u"] else "a"
  479. self.characters = []
  480. self.characters.append(Character("Daldain", 13, [4, 5, 5, 3], 118))
  481. self.characters.append(Character("Gox", 18, [2, 6, 4, 5], 119))
  482. self.characters.append(Character("Zeeli", 14, [3, 4, 6, 5], 120))
  483. self.characters.append(Character("Marwyse", 15, [4, 4, 6, 3], 121))
  484. self.characters.append(Character("Sox", 16, [5, 4, 4, 4], 122))
  485. # self.print_params()
  486. # self.print_chars()
  487. return
  488. elif aBuiltin == "JUSTINBAILEY":
  489. # Create a random campaign, but everyone's Gadget is a leotard that somehow is also an environment suit
  490. self.create_campaign(n=6, makeChars=True)
  491. for c in self.characters:
  492. 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}
  493. # self.print_params()
  494. # self.print_chars()
  495. return
  496. elif aBuiltin == "NARPASSWORD":
  497. # Create a random campaign, but all the kobolds' stats are set to 6
  498. self.create_campaign(n=6, makeChars=True)
  499. for c in self.characters:
  500. c.stats = [6,6,6,6]
  501. # self.print_params()
  502. # self.print_chars()
  503. return
  504. elif aBuiltin == "OHNOITSPEV!!":
  505. # Create a random campaign, but the adversary is Pev
  506. self.create_campaign(n=6, makeChars=True)
  507. self.params = Plot(probIndex=121)
  508. numPwd = []
  509. for c in densePwd:
  510. numPwd.append(Campaign.ALPHABET.index(c))
  511. bitPwd = [lpad(bin(x).replace("0b",""), 6) for x in numPwd]
  512. longBitPwd = []
  513. for word in bitPwd:
  514. longword = lpad(word,6)
  515. longBitPwd.append(longword)
  516. self.newBitfield = "".join(longBitPwd)
  517. while len(self.newBitfield) < 509:
  518. self.newBitfield += "0"
  519. outkey = {}
  520. # Location 1: 7 bits
  521. i,j = 0,7
  522. outkey["loc1"] = int(self.newBitfield[i:j], 2)
  523. # Location 2: 7 bits
  524. i,j = j,j+7
  525. outkey["loc2"] = int(self.newBitfield[i:j], 2)
  526. # Location Battlefield: 1 bit
  527. i,j = j,j+1
  528. outkey["battlefield"] = int(self.newBitfield[i:j], 2)
  529. # Mission: 7 bits
  530. i,j = j,j+7
  531. outkey["miss"] = int(self.newBitfield[i:j], 2)
  532. # Oops: 1 bit
  533. i,j = j,j+1
  534. outkey["oops"] = int(self.newBitfield[i:j], 2)
  535. # Problem 1: 7 bits
  536. i,j = j,j+7
  537. outkey["prob1"] = int(self.newBitfield[i:j], 2)
  538. # Problem 1 name: 40 bits
  539. i,j = j,j+40
  540. outkey["prob1name"] = self.newBitfield[i:j]
  541. # Problem 2: 7 bits
  542. i,j = j,j+7
  543. outkey["prob2"] = int(self.newBitfield[i:j], 2)
  544. # Ship name 1: 7 bits
  545. i,j = j,j+7
  546. outkey["sname1"] = int(self.newBitfield[i:j], 2)
  547. # Ship name 2: 7 bits
  548. i,j = j,j+7
  549. outkey["sname2"] = int(self.newBitfield[i:j], 2)
  550. # Ship gqual: 7 bits
  551. i,j = j,j+7
  552. outkey["gqual"] = int(self.newBitfield[i:j], 2)
  553. # Ship bqual: 7 bits
  554. i,j = j,j+7
  555. outkey["bqual"] = int(self.newBitfield[i:j], 2)
  556. # Character 1 name: 40 bits
  557. i,j = j,j+40
  558. outkey["char1name"] = self.newBitfield[i:j]
  559. # Character 1 career: 7 bits
  560. i,j = j,j+7
  561. outkey["char1career"] = int(self.newBitfield[i:j], 2)
  562. # Character 1 order: 3 bits
  563. i,j = j,j+3
  564. outkey["char1ord"] = int(self.newBitfield[i:j], 2)
  565. # Character 1 chaos: 3 bits
  566. i,j = j,j+3
  567. outkey["char1cha"] = int(self.newBitfield[i:j], 2)
  568. # Character 1 body: 3 bits
  569. i,j = j,j+3
  570. outkey["char1bod"] = int(self.newBitfield[i:j], 2)
  571. # Character 1 brain: 3 bits
  572. i,j = j,j+3
  573. outkey["char1bra"] = int(self.newBitfield[i:j], 2)
  574. # Character 1 gadget: 7 bits
  575. i,j = j,j+7
  576. outkey["char1gad"] = int(self.newBitfield[i:j], 2)
  577. # Character 2 name: 40 bits
  578. i,j = j,j+40
  579. outkey["char2name"] = self.newBitfield[i:j]
  580. # Character 2 career: 7 bits
  581. i,j = j,j+7
  582. outkey["char2career"] = int(self.newBitfield[i:j], 2)
  583. # Character 2 order: 3 bits
  584. i,j = j,j+3
  585. outkey["char2ord"] = int(self.newBitfield[i:j], 2)
  586. # Character 2 chaos: 3 bits
  587. i,j = j,j+3
  588. outkey["char2cha"] = int(self.newBitfield[i:j], 2)
  589. # Character 2 body: 3 bits
  590. i,j = j,j+3
  591. outkey["char2bod"] = int(self.newBitfield[i:j], 2)
  592. # Character 2 brain: 3 bits
  593. i,j = j,j+3
  594. outkey["char2bra"] = int(self.newBitfield[i:j], 2)
  595. # Character 2 gadget: 7 bits
  596. i,j = j,j+7
  597. outkey["char2gad"] = int(self.newBitfield[i:j], 2)
  598. # Character 3 name: 40 bits
  599. i,j = j,j+40
  600. outkey["char3name"] = self.newBitfield[i:j]
  601. # Character 3 career: 7 bits
  602. i,j = j,j+7
  603. outkey["char3career"] = int(self.newBitfield[i:j], 2)
  604. # Character 3 order: 3 bits
  605. i,j = j,j+3
  606. outkey["char3ord"] = int(self.newBitfield[i:j], 2)
  607. # Character 3 chaos: 3 bits
  608. i,j = j,j+3
  609. outkey["char3cha"] = int(self.newBitfield[i:j], 2)
  610. # Character 3 body: 3 bits
  611. i,j = j,j+3
  612. outkey["char3bod"] = int(self.newBitfield[i:j], 2)
  613. # Character 3 brain: 3 bits
  614. i,j = j,j+3
  615. outkey["char3bra"] = int(self.newBitfield[i:j], 2)
  616. # Character 3 gadget: 7 bits
  617. i,j = j,j+7
  618. outkey["char3gad"] = int(self.newBitfield[i:j], 2)
  619. # Character 4 name: 40 bits
  620. i,j = j,j+40
  621. outkey["char4name"] = self.newBitfield[i:j]
  622. # Character 4 career: 7 bits
  623. i,j = j,j+7
  624. outkey["char4career"] = int(self.newBitfield[i:j], 2)
  625. # Character 4 order: 3 bits
  626. i,j = j,j+3
  627. outkey["char4ord"] = int(self.newBitfield[i:j], 2)
  628. # Character 4 chaos: 3 bits
  629. i,j = j,j+3
  630. outkey["char4cha"] = int(self.newBitfield[i:j], 2)
  631. # Character 4 body: 3 bits
  632. i,j = j,j+3
  633. outkey["char4bod"] = int(self.newBitfield[i:j], 2)
  634. # Character 4 brain: 3 bits
  635. i,j = j,j+3
  636. outkey["char4bra"] = int(self.newBitfield[i:j], 2)
  637. # Character 4 gadget: 7 bits
  638. i,j = j,j+7
  639. outkey["char4gad"] = int(self.newBitfield[i:j], 2)
  640. # Character 5 name: 40 bits
  641. i,j = j,j+40
  642. outkey["char5name"] = self.newBitfield[i:j]
  643. # Character 5 career: 7 bits
  644. i,j = j,j+7
  645. outkey["char5career"] = int(self.newBitfield[i:j], 2)
  646. # Character 5 order: 3 bits
  647. i,j = j,j+3
  648. outkey["char5ord"] = int(self.newBitfield[i:j], 2)
  649. # Character 5 chaos: 3 bits
  650. i,j = j,j+3
  651. outkey["char5cha"] = int(self.newBitfield[i:j], 2)
  652. # Character 5 body: 3 bits
  653. i,j = j,j+3
  654. outkey["char5bod"] = int(self.newBitfield[i:j], 2)
  655. # Character 5 brain: 3 bits
  656. i,j = j,j+3
  657. outkey["char5bra"] = int(self.newBitfield[i:j], 2)
  658. # Character 5 gadget: 7 bits
  659. i,j = j,j+7
  660. outkey["char5gad"] = int(self.newBitfield[i:j], 2)
  661. # Character 6 name: 40 bits
  662. i,j = j,j+40
  663. outkey["char6name"] = self.newBitfield[i:j]
  664. # Character 6 career: 7 bits
  665. i,j = j,j+7
  666. outkey["char6career"] = int(self.newBitfield[i:j], 2)
  667. # Character 6 order: 3 bits
  668. i,j = j,j+3
  669. outkey["char6ord"] = int(self.newBitfield[i:j], 2)
  670. # Character 6 chaos: 3 bits
  671. i,j = j,j+3
  672. outkey["char6cha"] = int(self.newBitfield[i:j], 2)
  673. # Character 6 body: 3 bits
  674. i,j = j,j+3
  675. outkey["char6bod"] = int(self.newBitfield[i:j], 2)
  676. # Character 6 brain: 3 bits
  677. i,j = j,j+3
  678. outkey["char6bra"] = int(self.newBitfield[i:j], 2)
  679. # Character 6 gadget: 7 bits
  680. i,j = j,j+7
  681. outkey["char6gad"] = int(self.newBitfield[i:j], 2)
  682. self.ship = Ship(Ship.NAME1[outkey["sname1"]], Ship.NAME2[outkey["sname2"]], Ship.GQUAL[outkey["gqual"]], Ship.BQUAL[outkey["bqual"]])
  683. 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)
  684. self.art = "an" if self.params.loc_desc[0] in ["a","e","i","o","u"] else "a"
  685. self.characters = []
  686. for q in range(1,7):
  687. 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"]
  688. 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]])
  689. self.characters.append(c)
  690. #return self.newBitfield
  691. def encode_name(self, name):
  692. field = "".join([lpad(bin(Campaign.NAMELETS.index(c.lower()))[2:], 5) for c in name])
  693. return field
  694. def decode_name(self, field):
  695. i,j = 35,40
  696. name = ""
  697. for _ in range(8):
  698. k = int(field[i:j], 2)
  699. if k != 31:
  700. name = Campaign.NAMELETS[k] + name
  701. i,j = i-5, i
  702. name = name[0].upper() + name[1:]
  703. return name
  704. def print_params(self, endc=" ", html=False):
  705. st = ["Order:", "Chaos:", "Brains:", "Body:"]
  706. cst = ", ".join([" ".join(y) for y in list(zip(st, [str(x) for x in self.params.problem["stats"]]))])
  707. if self.params.oops == 1:
  708. oops = "...well, they weren't paying attention, so don't tell them, but they're supposed to "
  709. else:
  710. oops = ""
  711. mission = oops + self.params.mission
  712. # pl = "s" if self.params.problem["isPlural"] else ""
  713. note = self.params.problem["note"]
  714. secnote = ""
  715. if self.params.problem["hasMinion"]:
  716. secnote = "Their minion: " + self.params.secProblem["note"]
  717. lines = [
  718. f"The Kobolds of the {self.ship.fullname}",
  719. f"have been sent out to {self.art} {self.params.loc_desc} {self.params.location}!",
  720. f"in order to {mission}",
  721. f"but they're challenged by {self.params.fullProblem}!",
  722. f"{note} {secnote}",
  723. f"The stats of the {self.params.problem['shortname']}",
  724. f"{cst}"
  725. ]
  726. if self.params.problem["hasMinion"]:
  727. mst = ", ".join([" ".join(y) for y in list(zip(st, [str(x) for x in self.params.secProblem["stats"]]))])
  728. # spl = "s" if self.params.secProblem["isPlural"] else ""
  729. lines.append(f"The stats of the {self.params.secProblem['shortname']}")
  730. lines.append(f"{mst}")
  731. if html:
  732. secnote = ""
  733. if self.params.problem["hasMinion"]:
  734. secnote = "Their minion: " + self.params.secProblem["note"] + "<br>\n"
  735. out = (
  736. f"<div id='theship' class='firstrow'>\n"
  737. f" <span class='head'>The Ship</span>\n"
  738. f" <span id='shipname'>\n"
  739. f" The {self.ship.fullname}!\n"
  740. f" </span>\n"
  741. f" <br>\n"
  742. f" <span id='shipquality1'>\n"
  743. f" It {self.ship.gqual}...\n"
  744. f" </span><br>\n"
  745. f" <span id='shipquality2'>\n"
  746. f" But {self.ship.bqual}!\n"
  747. f" </span>\n"
  748. f"</div>\n"
  749. f"<div id='themission' class='firstrow'>\n"
  750. f" <span class='head'>The Mission</span>\n"
  751. f" <span id='missionloc'>\n"
  752. f" The kobolds have been sent to {self.art} {self.params.loc_desc} {self.params.location}\n"
  753. f" </span><br>\n"
  754. f" <span id='missiontarget'>\n"
  755. f" in order to {mission}!\n"
  756. f" </span>\n"
  757. f"</div>\n<br clear='all'>\n"
  758. f"<div id='theadversary' class='firstrow'>\n"
  759. f" <span class='head'>The Adversary</span>\n"
  760. f" They're challenged by <span id='advname'>{self.params.fullProblem}</span>!\n"
  761. f" <br>\n"
  762. f" <span id='problemnote'>\n"
  763. f" {self.params.problem['note']}<br>\n"
  764. f" {secnote}"
  765. f" </span>\n"
  766. f" <span id='problemstats'>\n"
  767. f" The stats of the {self.params.problem['shortname']}:<br>\n"
  768. f" {cst}\n"
  769. f" </span>\n"
  770. )
  771. if self.params.problem["hasMinion"]:
  772. out += (
  773. f" <br><span id='secprobstats'>\n"
  774. f" The stats of the {self.params.secProblem['shortname']}:<br>\n"
  775. f" {mst}\n"
  776. f" </span>\n"
  777. )
  778. out += f"</div>\n"
  779. print(out)
  780. print(f"<br clear='all'>\n")
  781. else:
  782. print(f"{lines[0]} {lines[1]} {lines[2]} -- {lines[3]}")
  783. print(f"{lines[4]}")
  784. print(f"{lines[5]}: {lines[6]}")
  785. if self.params.problem["hasMinion"]:
  786. print(f"- {lines[7]}: {lines[8]}")
  787. print()
  788. self.ship.print(html=html)
  789. def print_chars(self, html=False):
  790. if html:
  791. print(f"<div id='thekobolds'>\n")
  792. print(f"<span class='head'>The Kobolds</span>\n")
  793. else:
  794. print("The kobolds:")
  795. for k in self.characters:
  796. k.print(html=html)
  797. if html:
  798. print(f"</div>\n<br clear='all'>\n")
  799. def print_password(self, html=False):
  800. if self.masterpassword:
  801. pw = self.masterpassword
  802. else:
  803. pw = self.generate_key()
  804. if html:
  805. out = (
  806. f"<div id='passwordbox'>"
  807. f"<span class='passwordhead'>Permalink to this campaign:</span><br>"
  808. f"<span><a href='http://node.noelle.codes/kobold?pw={pw.replace(' ', '')}'>{pw.replace(' ', '')}</a></span><br><br>"
  809. f"<span class='passwordhead'><a href='http://node.noelle.codes/kobold'>Generate a new random campaign</a></span><br>"
  810. f"</div>"
  811. )
  812. print(out)
  813. print(f"<br clear='all'>\n")
  814. else:
  815. print(f"Password: {pw}")
  816. def decode(self, pwd):
  817. pass
  818. def lpad(s, n, c="0"):
  819. while len(s) < n:
  820. s = c + s
  821. return s
  822. if __name__ == "__main__":
  823. parser = argparse.ArgumentParser()
  824. group = parser.add_mutually_exclusive_group()
  825. group.add_argument("-c", "--campaign", help="print a full campaign block with N kobolds (default 6)", nargs="?", const=6, type=int, metavar="N")
  826. group.add_argument("-k", "--kobolds", help="print N kobolds", type=int, nargs="?", const=1, default=1, metavar="N")
  827. group.add_argument("-n", "--names", help="print N kobolds without stat blocks", nargs="?", const=1, type=int, metavar="N")
  828. group.add_argument("-p", "--params", help="print only the parameters of a campaign", action="store_true")
  829. group.add_argument("-s", "--ship", help="print only the ship name and description", action="store_true")
  830. group.add_argument("-pw", "--password", help="print the campaign defined by the submitted password", type=str, nargs="?", const=1, metavar="PW")
  831. parser.add_argument("--html", help="print in HTML instead of plain text", action="store_true")
  832. args = parser.parse_args()
  833. html = True if args.html else False
  834. if args.password:
  835. cmp = Campaign(fromPW = True, pw=args.password)
  836. cmp.print_params(html=html)
  837. cmp.print_chars(html=html)
  838. cmp.print_password(html=html)
  839. elif args.campaign:
  840. cmp = Campaign(args.campaign)
  841. cmp.print_params(html=html)
  842. cmp.print_chars(html=html)
  843. cmp.print_password(html=html)
  844. elif args.params:
  845. cmp = Campaign(makeChars=False)
  846. cmp.print_params(html=html)
  847. elif args.ship:
  848. ship = Ship()
  849. ship.print(html=html)
  850. elif args.names:
  851. for _ in range(args.names):
  852. c = Character()
  853. c.generate()
  854. c.print_name(html=html)
  855. else:
  856. for _ in range(args.kobolds):
  857. c = Character()
  858. c.generate()
  859. c.print(html=html)