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

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