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.

250 lines
7.6 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):
  108. npc = CharacterNPC()
  109. npc.guild = npc_class(['Demi-Human'])
  110. npc.level = npc_baselevel(base_level)
  111. if npc.level > npc.guild.maximum_level:
  112. npc.level = npc.guild.maximum_level
  113. npc.alignment = npc_alignment()
  114. abilities = npc_abilities()
  115. npc.update(npc_abilities())
  116. npc.hit_points = calc_hp(attribute_mod(npc.constitution), npc.guild.hit_die_size, npc.level)
  117. npc.armour = calc_armour(npc.guild.armour_modifier, data['armours'])
  118. npc.melee = select_melee_weapon(npc.guild, data)
  119. npc.ranged = select_ranged_weapon(npc.guild, data)
  120. npc.spells = select_spell_list(npc, data)
  121. return npc
  122. def name_party_api(party):
  123. male_names = requests.get(
  124. 'http://names.drycodes.com/{}'.format(ceil(len(party))),
  125. params={'nameOptions': 'boy_names', 'separator': 'space'}
  126. ).json()
  127. female_names = requests.get(
  128. 'http://names.drycodes.com/{}'.format(floor(len(party))),
  129. params={'nameOptions': 'girl_names', 'separator': 'space'}
  130. ).json()
  131. names = female_names + male_names
  132. shuffle(names)
  133. for i in range(0, len(party)):
  134. party[i].name = names[i]
  135. return party
  136. def load_name_data():
  137. names = []
  138. surnames = []
  139. with open('acks/npc/data/fantasy_names.csv', newline='') as data:
  140. reader = csv.DictReader(data)
  141. for row in reader:
  142. for k,v in row.items():
  143. if v == '1':
  144. row[k] = True
  145. elif v == '':
  146. row[k] = False
  147. if row['Family Name']:
  148. surnames.append(row['Name'])
  149. else:
  150. names.append(row['Name'])
  151. return (names, surnames)
  152. def name_party(party):
  153. names, surnames = load_name_data()
  154. shuffle(names)
  155. shuffle(surnames)
  156. for i in range(0, len(party)):
  157. party[i].name = '{} {}'.format(choice(names), choice(surnames))
  158. return party
  159. def create_party(base_level):
  160. data = {
  161. 'armours': EquipmentArmour.query.all(),
  162. 'ranged': {
  163. 'light': EquipmentRangedWeapon.query.filter_by(bucket='Light').all(),
  164. 'heavy': EquipmentRangedWeapon.query.filter_by(bucket='Heavy').all()
  165. },
  166. 'melee': {
  167. 'light': EquipmentMeleeWeapon.query.filter_by(bucket='Light').all(),
  168. 'medium': EquipmentMeleeWeapon.query.filter_by(bucket='Medium').all(),
  169. 'heavy': EquipmentMeleeWeapon.query.filter_by(bucket='Heavy').all()
  170. },
  171. 'spells': {
  172. 'divine': {},
  173. 'arcane': {},
  174. }
  175. }
  176. for spell in Spell.query.all():
  177. if spell.is_arcane:
  178. spells = [spell]
  179. if data['spells']['arcane'] and spell.arcane in data['spells']['arcane']:
  180. spells.extend(data['spells']['arcane'][spell.arcane])
  181. data['spells']['arcane'][spell.arcane] = spells
  182. if spell.is_divine:
  183. spells = [spell]
  184. if data['spells']['divine'] and spell.divine in data['spells']['divine']:
  185. spells.extend(data['spells']['divine'][spell.divine])
  186. data['spells']['divine'][spell.divine] = spells
  187. return name_party([generate_npc(base_level, data) for x in range(0, number_encountered())])
  188. def print_party(party):
  189. def print_npc(npc):
  190. print('Level {0} NPC, {1}, {2} HP'.format(npc.level, npc.guild, npc.hp))
  191. print('{0} Str, {1} Int, {2} Wis, {3} Dex, {4} Con, {5} Chr'.format(
  192. npc.str, npc.int, npc.wis,
  193. npc.dex, npc.con, npc.chr
  194. ))
  195. print('Armour Class: {0} - {1}, {2}gp'.format(npc.armour.name, npc.armour.ac_mod, npc.armour.gp_value))
  196. print('{:^16} - {:^10} - {:^10} - {:^10}'.format('Weapon', 'Gold', 'Throw Mod', 'Damage'))
  197. print('-------------------------------------------------------')
  198. print('{:^16} | {:^10} | {:^10} | {:^10}'.format(npc.melee.name, 0, npc.melee.damage_die))
  199. if npc['ranged']:
  200. print('{:^16} | {:^10} | {:^10} | {:^10}'.format(npc.ranged.name, 0, npc.melee.damage_die))
  201. print('\n')
  202. for npc in party:
  203. print_npc(npc)
  204. if __name__ == '__main__':
  205. party = create_party(2)
  206. print_party(party)