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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202
  1. import random as r
  2. import argparse
  3. beg = ["a","e","i","o","u","ba","be","bi","bo","bu","by","y","da","de","di","do","du","dy","fa","fi","fo","fe","fu","ga","ge","gi","go","gu","ka","ke","ki","ko","ku","ky","ma","me","mi","mo","mu","na","ne","ni","no","nu","pa","pe","pi","po","pu","ra","re","ri","ro","ru","ry","sa","se","si","so","su","ta","te","ti","to","tu","ty","wa","we","wi","wo","wy","za","ze","zi","zo","zu","zy"]
  4. mid = beg + ["l","x","n","r"]
  5. def gen_name(length=None, minimum=None, maximum=None):
  6. if length == None:
  7. minimum = 3 if minimum == None else minimum
  8. maximum = 9 if maximum == None else maximum
  9. lgt = r.randint(maximum,maximum)
  10. else:
  11. lgt = length
  12. morae = []
  13. while len("".join(morae)) < lgt:
  14. if len(morae) == 0:
  15. mora = r.choice(beg)
  16. morae.append(mora[0].upper() + mora[1:])
  17. else:
  18. mora = r.choice(mid)
  19. if morae[-1] == mora:
  20. mora = r.choice(mid)
  21. morae.append(mora)
  22. return "".join(morae)
  23. class Plot:
  24. loc1 = ["a friendly","a hostile","a derelict","an airless","a poison-filled/covered","an overgrown","a looted","a burning","a frozen","a haunted","an infested"]
  25. loc2 = ["asteroid","moon","space station","spaceship","ringworld","Dyson sphere","planet","Space Whale","pocket of folded space","time vortex","Reroll"]
  26. miss = ["to explore!","to loot everything not bolted down too securely","to find the last group of kobolds who came here","to find a rumored secret weapon","to find a way to break someone else’s secret weapon","to claim this place in the name of the Kobold Empire!","to make friends!","to rediscover lost technology","to find lost magical items","to find and defeat a powerful enemy"]
  27. prob = ["an Undead Sample Pack (swarm of zombies and skeletons)","a rival band of kobolds","a detachment from the Elf Armada","refugees with parasites. Big parasites","an artificial intelligence bent on multi-world domination","robot spiders","semi-intelligent metal eating slime","a living asteroid that intends to follow the kobolds home like the largest puppy","an old lich that wants everyone to stay off of their “lawn”","elder gods hailing from the dark spaces between the stars","a Flying Brain Monster"]
  28. pstats = [[0,5,2,6],[3,3,4,4],[4,3,5,4],[2,4,0,0],[4,1,6,3],[3,3,2,4],[0,2,1,5],[2,3,1,6],[5,2,6,3],[6,6,6,6],[2,3,6,1]]
  29. def __init__(self):
  30. self.loc_desc = Plot.loc1[r.randint(0, len(Plot.loc1)-1)]
  31. self.location = Plot.loc2[r.randint(0, len(Plot.loc2)-1)]
  32. self.missIndex = r.randint(0, len(Plot.miss)-1)
  33. self.oops = r.randint(1,12)
  34. self.mission = ""
  35. if self.oops == 12:
  36. self.mission += "...well, they weren't paying attention, so don't tell them, but they're supposed "
  37. self.mission += Plot.miss[r.randint(0, len(Plot.miss)-1)]
  38. self.probIndex = r.randint(0, len(Plot.prob)-1)
  39. self.problem = Plot.prob[self.probIndex]
  40. self.problemStats = Plot.pstats[self.probIndex]
  41. self.secProblem = None
  42. self.secProbStats = None
  43. self.thirdProblem = None
  44. self.thirdProbStats = None
  45. if self.probIndex in [1,2]:
  46. self.problem += " led by " + gen_name()
  47. if self.probIndex in [4,7,8,10]:
  48. self.problem += " named " + gen_name(minimum=5, maximum=12)
  49. if self.probIndex == 3:
  50. self.secProblem = "Parasites"
  51. self.secProbStats = [3,4,2,3]
  52. elif self.probIndex == len(Plot.prob) - 1:
  53. self.secProbIndex = r.randint(0, len(Plot.prob)-2)
  54. self.secProblem = Plot.prob[self.secProbIndex]
  55. self.secProbStats = Plot.pstats[self.secProbIndex]
  56. if self.secProbIndex == 3:
  57. self.thirdProblem = "Parasites"
  58. self.thirdProbStats = [3,4,2,3]
  59. self.fullProblem = self.problem
  60. if self.secProblem and self.secProblem != "Parasites":
  61. self.fullProblem += ", and its minion, " + self.secProblem
  62. class Character:
  63. def __init__(self):
  64. self.name = ""
  65. self.career = ""
  66. self.stats = []
  67. def generate(self):
  68. self.gen_name()
  69. self.gen_stats()
  70. self.gen_career()
  71. def gen_name(self):
  72. self.name = gen_name()
  73. def gen_stats(self, n=12):
  74. if n < 0:
  75. print("Too few stat points!")
  76. return [0,0,0,0]
  77. stats = [0,0,0,0]
  78. points = n
  79. slots = [0,1,2,3]
  80. for _ in range(points):
  81. tgl = False
  82. while tgl == False:
  83. slt = r.choice(slots)
  84. if slt <= 1:
  85. if stats[slt] == 6: continue
  86. if stats[slt] == 5 and r.randint(0,1) != 1: continue
  87. else:
  88. if stats[slt] == 6: continue
  89. if stats[slt] > 2 and r.randint(0,stats[slt]-2) != 1: continue
  90. stats[slt] += 1
  91. tgl = True
  92. stats[3] = stats[3] + 2
  93. if stats[3] > 6:
  94. stats[3] = 6
  95. stats[2] = stats[2] + 2
  96. if stats[2] > 6:
  97. stats[2] = 6
  98. self.stats = stats
  99. def gen_career(self):
  100. self.career = r.choice(["Soldier/Guard","Pilot","Medic","Mechanic","Politician","Spellcaster","Performer","Historian","Spy","Cook","Cartographer","Inventor","Merchant"])
  101. def print_name(self):
  102. print(f"Name: {self.name} (Kobold {self.career})")
  103. def print(self):
  104. self.print_name()
  105. print(f"Order: {self.stats[0]}")
  106. print(f"Chaos: {self.stats[1]}")
  107. print(f"Brain: {self.stats[2]}")
  108. print(f"Body: {self.stats[3]}")
  109. class Ship:
  110. def __init__(self):
  111. self.name1 = r.choice(["Red","Orange","Yellow","Green","Blue","Violet","Dark","Light","Frenzied","Maniacal","Ancient"])
  112. self.name2 = r.choice(["Moon","Comet","Star","Saber","World-Eater","Dancer","Looter","Phlogiston","Fireball","Mecha","Raptor"])
  113. self.gqual = r.choice(["is stealthy & unarmored","is speedy & unarmored","is maneuverable & unarmored","is always repairable","is self-repairing","is flamboyant & speedy","is slow & armored","is flamboyant & armored","is hard to maneuver & armored","has Too Many Weapons!","has a prototype hyperdrive"])
  114. self.bqual = r.choice(["has an annoying AI","has inconveniently crossed circuits","has an unpredictable power source","drifts to the right","is haunted","was recently 'found' so the kobolds are unused to it","is too cold","has a constant odd smell","its interior design... changes","its water pressure shifts between slow drip and power wash","it leaves a visible smoke trail"])
  115. self.fullname = f"{self.name1} {self.name2}"
  116. def print(self):
  117. print(f"The {self.fullname} {self.gqual}, but {self.bqual}.")
  118. print()
  119. class Campaign:
  120. def __init__(self, n=None, makeChars=True):
  121. n = 6 if n == None else n
  122. self.ship = Ship()
  123. self.params = Plot()
  124. if makeChars:
  125. self.characters = []
  126. for _ in range(n):
  127. c = Character()
  128. c.generate()
  129. self.characters.append(c)
  130. self.art = "an" if self.params.loc_desc in ["A","E","I","O","U"] else "a"
  131. def print_params(self, endc=""):
  132. st = ["Order:", "Chaos:", "Brains:", "Body:"]
  133. print(f"The Kobolds of the {self.ship.fullname} ", end=endc)
  134. print(f"have been sent out to {self.art} {self.params.loc_desc} {self.params.location} ", end=endc)
  135. print(f"in order {self.params.mission} ", end=endc)
  136. print(f"-- but they're challenged by {self.params.fullProblem}!")
  137. cst = ", ".join([" ".join(y) for y in list(zip(st, [str(x) for x in self.params.problemStats]))])
  138. print(f"The challenger's stats: {cst}")
  139. if self.params.secProblem != None:
  140. mst = ", ".join([" ".join(y) for y in list(zip(st, [str(x) for x in self.params.secProbStats]))])
  141. if self.params.secProblem == "Parasites":
  142. print(f"- The parasites' stats: {mst}")
  143. else:
  144. print(f"- Its minion's stats: {mst}")
  145. if self.params.thirdProblem != None:
  146. pst = ", ".join([" ".join(y) for y in list(zip(st, [str(x) for x in self.params.thirdProbStats]))])
  147. print(f"- - The parasites' stats: {pst}")
  148. print()
  149. self.ship.print()
  150. def print_chars(self):
  151. print("The kobolds:")
  152. for k in self.characters:
  153. k.print()
  154. if __name__ == "__main__":
  155. parser = argparse.ArgumentParser()
  156. group = parser.add_mutually_exclusive_group()
  157. group.add_argument("-c", "--campaign", help="print a full campaign block with N kobolds (default 6)", nargs="?", const=6, type=int, metavar="N")
  158. group.add_argument("-k", "--kobolds", help="print N kobolds", type=int, nargs="?", const=1, default=1, metavar="N")
  159. group.add_argument("-n", "--names", help="print N kobolds without stat blocks", nargs="?", const=1, type=int, metavar="N")
  160. group.add_argument("-p", "--params", help="print only the parameters of a campaign", action="store_true")
  161. group.add_argument("-s", "--ship", help="print only the ship name and description", action="store_true")
  162. args = parser.parse_args()
  163. # print(args)
  164. if args.campaign:
  165. cmp = Campaign(args.campaign)
  166. cmp.print_params()
  167. cmp.print_chars()
  168. elif args.params:
  169. cmp = Campaign(makeChars=False)
  170. cmp.print_params()
  171. elif args.ship:
  172. ship = Ship()
  173. ship.print()
  174. elif args.names:
  175. for _ in range(args.names):
  176. c = Character()
  177. c.generate()
  178. c.print_name()
  179. else:
  180. for _ in range(args.kobolds):
  181. c = Character()
  182. c.generate()
  183. c.print()