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.

253 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. # FOR DEBUG XXX NOTE
  91. npc_spell_list.append(106)
  92. return ','.join(str(sid) for sid in npc_spell_list)
  93. def calc_hp(conmod, hit_die_size, level):
  94. hp = 0
  95. hitdice = [randint(1, hit_die_size) for x in range(0, level)]
  96. for die in hitdice:
  97. die += conmod
  98. if die < 1:
  99. die = 1
  100. hp += die
  101. return hp
  102. def calc_armour(armour_mod, armours):
  103. armourval = randint(0, len(armours) - 1) + armour_mod
  104. if armourval < 0:
  105. armourval = 0
  106. if armourval > len(armours) - 1:
  107. armourval = len(armours) - 1
  108. return armours[armourval]
  109. def generate_npc(base_level, data):
  110. npc = CharacterNPC()
  111. npc.guild = npc_class(['Demi-Human'])
  112. npc.level = npc_baselevel(base_level)
  113. if npc.level > npc.guild.maximum_level:
  114. npc.level = npc.guild.maximum_level
  115. npc.alignment = npc_alignment()
  116. abilities = npc_abilities()
  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 create_party(base_level):
  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 name_party([generate_npc(base_level, data) for x in range(0, number_encountered())])
  190. def print_party(party):
  191. def print_npc(npc):
  192. print('Level {0} NPC, {1}, {2} HP'.format(npc.level, npc.guild, npc.hp))
  193. print('{0} Str, {1} Int, {2} Wis, {3} Dex, {4} Con, {5} Chr'.format(
  194. npc.str, npc.int, npc.wis,
  195. npc.dex, npc.con, npc.chr
  196. ))
  197. print('Armour Class: {0} - {1}, {2}gp'.format(npc.armour.name, npc.armour.ac_mod, npc.armour.gp_value))
  198. print('{:^16} - {:^10} - {:^10} - {:^10}'.format('Weapon', 'Gold', 'Throw Mod', 'Damage'))
  199. print('-------------------------------------------------------')
  200. print('{:^16} | {:^10} | {:^10} | {:^10}'.format(npc.melee.name, 0, npc.melee.damage_die))
  201. if npc['ranged']:
  202. print('{:^16} | {:^10} | {:^10} | {:^10}'.format(npc.ranged.name, 0, npc.melee.damage_die))
  203. print('\n')
  204. for npc in party:
  205. print_npc(npc)
  206. if __name__ == '__main__':
  207. party = create_party(2)
  208. print_party(party)