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.

262 lines
7.9 KiB

  1. import csv
  2. import requests
  3. from math import floor, ceil
  4. from random import randint, choice, shuffle
  5. from .models import (
  6. CharacterClass,
  7. EquipmentArmour,
  8. EquipmentRangedWeapon,
  9. EquipmentMeleeWeapon,
  10. CharacterNPC,
  11. Spell
  12. )
  13. def number_encountered():
  14. return (randint(1, 4) + 2)
  15. def npc_class(excluded_buckets):
  16. if excluded_buckets:
  17. classes = [c for cls in CharacterClass.query.filter(CharacterClass.bucket.notin_(excluded_buckets)).all() for c in ([cls] * cls.frequency_modifier)]
  18. else:
  19. classes = [c for cls in CharacterClass.query.all() for c in ([cls] * cls.frequency_modifier)]
  20. return choice(classes)
  21. def npc_alignment():
  22. roll = randint(1, 6)
  23. if roll == 1 or roll == 2:
  24. return 'Lawful'
  25. elif roll == 3 or roll == 4 or roll == 5:
  26. return 'Neutral'
  27. elif roll == 6:
  28. return 'Chaotic'
  29. def npc_baselevel(base_level):
  30. roll = randint(1, 6)
  31. mod = 0
  32. if roll == 1:
  33. mod = -2
  34. elif roll == 2:
  35. mod = -1
  36. elif roll == 3 or roll == 4:
  37. mod = 0
  38. elif roll == 5:
  39. mod = 1
  40. elif roll == 6:
  41. mod = 2
  42. base_level += mod
  43. if base_level < 1:
  44. base_level = 1
  45. return base_level
  46. def npc_abilities():
  47. ability_list = ['strength', 'intelligence', 'wisdom', 'dexterity', 'constitution', 'charisma']
  48. abilities = [(randint(1, 6) + randint(1, 6) + randint(1, 6)) for x in range(0,6)]
  49. return zip(ability_list, abilities)
  50. def attribute_mod(atr):
  51. mod = 0
  52. if atr <= 3: mod = -3
  53. if atr >= 4 and atr <= 5: mod = -2
  54. if atr >= 6 and atr <= 8: mod = -1
  55. if atr >= 9 and atr <= 12: mod = 0
  56. if atr >= 13 and atr <= 15: mod = 1
  57. if atr >= 16 and atr <= 17: mod = 2
  58. if atr >= 18: mod = 3
  59. return mod
  60. def select_melee_weapon(guild, data):
  61. weapons = []
  62. for x in range(0, guild.melee_heavy):
  63. weapons.extend(data['melee']['heavy'])
  64. for x in range(0, guild.melee_medium):
  65. weapons.extend(data['melee']['medium'])
  66. for x in range(0, guild.melee_light):
  67. weapons.extend(data['melee']['light'])
  68. return choice(weapons)
  69. def select_ranged_weapon(guild, data):
  70. weapons = []
  71. for x in range(0, guild.ranged_light):
  72. weapons.extend(data['ranged']['light'])
  73. for x in range(0, guild.ranged_heavy):
  74. weapons.extend(data['ranged']['heavy'])
  75. if not weapons:
  76. return None
  77. return choice(weapons)
  78. def select_spell_list(npc, data):
  79. npc_spell_list = []
  80. overall_spell_list = []
  81. if npc.is_arcane_spellcaster:
  82. overall_spell_list = data['spells']['arcane']
  83. if npc.is_divine_spellcaster:
  84. overall_spell_list = data['spells']['divine']
  85. for level, known in enumerate(npc.spells_known(), start=1):
  86. level_set = set()
  87. while len(level_set) < known:
  88. level_set.add(choice(overall_spell_list[level]).id)
  89. npc_spell_list.extend(level_set)
  90. return ','.join(str(sid) for sid in npc_spell_list)
  91. def calc_hp(conmod, hit_die_size, level):
  92. hp = 0
  93. hitdice = [randint(1, hit_die_size) for x in range(0, level)]
  94. for die in hitdice:
  95. die += conmod
  96. if die < 1:
  97. die = 1
  98. hp += die
  99. return hp
  100. def calc_armour(armour_mod, armours):
  101. armourval = randint(0, len(armours) - 1) + armour_mod
  102. if armourval < 0:
  103. armourval = 0
  104. if armourval > len(armours) - 1:
  105. armourval = len(armours) - 1
  106. return armours[armourval]
  107. def generate_npc(base_level, data, guild_id=False):
  108. npc = CharacterNPC()
  109. if not guild_id:
  110. npc.guild = npc_class(['Demi-Human'])
  111. else:
  112. npc.guild = CharacterClass.query.filter_by(id=guild_id).first()
  113. npc.level = npc_baselevel(base_level)
  114. if npc.level > npc.guild.maximum_level:
  115. npc.level = npc.guild.maximum_level
  116. npc.alignment = npc_alignment()
  117. npc.update(npc_abilities())
  118. npc.hit_points = calc_hp(attribute_mod(npc.constitution), npc.guild.hit_die_size, npc.level)
  119. npc.armour = calc_armour(npc.guild.armour_modifier, data['armours'])
  120. npc.melee = select_melee_weapon(npc.guild, data)
  121. npc.ranged = select_ranged_weapon(npc.guild, data)
  122. npc.spells = select_spell_list(npc, data)
  123. return npc
  124. def name_party_api(party):
  125. male_names = requests.get(
  126. 'http://names.drycodes.com/{}'.format(ceil(len(party))),
  127. params={'nameOptions': 'boy_names', 'separator': 'space'}
  128. ).json()
  129. female_names = requests.get(
  130. 'http://names.drycodes.com/{}'.format(floor(len(party))),
  131. params={'nameOptions': 'girl_names', 'separator': 'space'}
  132. ).json()
  133. names = female_names + male_names
  134. shuffle(names)
  135. for i in range(0, len(party)):
  136. party[i].name = names[i]
  137. return party
  138. def load_name_data():
  139. names = []
  140. surnames = []
  141. with open('acks/npc/data/fantasy_names.csv', newline='') as data:
  142. reader = csv.DictReader(data)
  143. for row in reader:
  144. for k,v in row.items():
  145. if v == '1':
  146. row[k] = True
  147. elif v == '':
  148. row[k] = False
  149. if row['Family Name']:
  150. surnames.append(row['Name'])
  151. else:
  152. names.append(row['Name'])
  153. return (names, surnames)
  154. def name_party(party):
  155. names, surnames = load_name_data()
  156. shuffle(names)
  157. shuffle(surnames)
  158. for i in range(0, len(party)):
  159. party[i].name = '{} {}'.format(choice(names), choice(surnames))
  160. return party
  161. def load_db_data():
  162. data = {
  163. 'armours': EquipmentArmour.query.all(),
  164. 'ranged': {
  165. 'light': EquipmentRangedWeapon.query.filter_by(bucket='Light').all(),
  166. 'heavy': EquipmentRangedWeapon.query.filter_by(bucket='Heavy').all()
  167. },
  168. 'melee': {
  169. 'light': EquipmentMeleeWeapon.query.filter_by(bucket='Light').all(),
  170. 'medium': EquipmentMeleeWeapon.query.filter_by(bucket='Medium').all(),
  171. 'heavy': EquipmentMeleeWeapon.query.filter_by(bucket='Heavy').all()
  172. },
  173. 'spells': {
  174. 'divine': {},
  175. 'arcane': {},
  176. }
  177. }
  178. for spell in Spell.query.all():
  179. if spell.is_arcane:
  180. spells = [spell]
  181. if data['spells']['arcane'] and spell.arcane in data['spells']['arcane']:
  182. spells.extend(data['spells']['arcane'][spell.arcane])
  183. data['spells']['arcane'][spell.arcane] = spells
  184. if spell.is_divine:
  185. spells = [spell]
  186. if data['spells']['divine'] and spell.divine in data['spells']['divine']:
  187. spells.extend(data['spells']['divine'][spell.divine])
  188. data['spells']['divine'][spell.divine] = spells
  189. return data
  190. def create_npc(base_level, guild_id):
  191. data = load_db_data()
  192. if guild_id:
  193. return name_party([generate_npc(base_level, data, guild_id=guild_id)])[0]
  194. return name_party([generate_npc(base_level, data)])[0]
  195. def create_party(base_level):
  196. data = load_db_data()
  197. return name_party([generate_npc(base_level, data) for x in range(0, number_encountered())])
  198. def print_party(party):
  199. def print_npc(npc):
  200. print('Level {0} NPC, {1}, {2} HP'.format(npc.level, npc.guild, npc.hp))
  201. print('{0} Str, {1} Int, {2} Wis, {3} Dex, {4} Con, {5} Chr'.format(
  202. npc.str, npc.int, npc.wis,
  203. npc.dex, npc.con, npc.chr
  204. ))
  205. print('Armour Class: {0} - {1}, {2}gp'.format(npc.armour.name, npc.armour.ac_mod, npc.armour.gp_value))
  206. print('{:^16} - {:^10} - {:^10} - {:^10}'.format('Weapon', 'Gold', 'Throw Mod', 'Damage'))
  207. print('-------------------------------------------------------')
  208. print('{:^16} | {:^10} | {:^10} | {:^10}'.format(npc.melee.name, 0, npc.melee.damage_die))
  209. if npc['ranged']:
  210. print('{:^16} | {:^10} | {:^10} | {:^10}'.format(npc.ranged.name, 0, npc.melee.damage_die))
  211. print('\n')
  212. for npc in party:
  213. print_npc(npc)
  214. if __name__ == '__main__':
  215. party = create_party(2)
  216. print_party(party)