import csv import requests from math import floor, ceil from random import randint, choice, shuffle from .models import ( CharacterClass, EquipmentArmour, EquipmentRangedWeapon, EquipmentMeleeWeapon, CharacterNPC, Spell ) def number_encountered(): return (randint(1, 4) + 2) def npc_class(excluded_buckets): if excluded_buckets: classes = [c for cls in CharacterClass.query.filter(CharacterClass.bucket.notin_(excluded_buckets)).all() for c in ([cls] * cls.frequency_modifier)] else: classes = [c for cls in CharacterClass.query.all() for c in ([cls] * cls.frequency_modifier)] return choice(classes) def npc_alignment(): roll = randint(1, 6) if roll == 1 or roll == 2: return 'Lawful' elif roll == 3 or roll == 4 or roll == 5: return 'Neutral' elif roll == 6: return 'Chaotic' def npc_baselevel(base_level): roll = randint(1, 6) mod = 0 if roll == 1: mod = -2 elif roll == 2: mod = -1 elif roll == 3 or roll == 4: mod = 0 elif roll == 5: mod = 1 elif roll == 6: mod = 2 base_level += mod if base_level < 1: base_level = 1 return base_level def npc_abilities(): ability_list = ['strength', 'intelligence', 'wisdom', 'dexterity', 'constitution', 'charisma'] abilities = [(randint(1, 6) + randint(1, 6) + randint(1, 6)) for x in range(0,6)] return zip(ability_list, abilities) def attribute_mod(atr): mod = 0 if atr <= 3: mod = -3 if atr >= 4 and atr <= 5: mod = -2 if atr >= 6 and atr <= 8: mod = -1 if atr >= 9 and atr <= 12: mod = 0 if atr >= 13 and atr <= 15: mod = 1 if atr >= 16 and atr <= 17: mod = 2 if atr >= 18: mod = 3 return mod def select_melee_weapon(guild, data): weapons = [] for x in range(0, guild.melee_heavy): weapons.extend(data['melee']['heavy']) for x in range(0, guild.melee_medium): weapons.extend(data['melee']['medium']) for x in range(0, guild.melee_light): weapons.extend(data['melee']['light']) return choice(weapons) def select_ranged_weapon(guild, data): weapons = [] for x in range(0, guild.ranged_light): weapons.extend(data['ranged']['light']) for x in range(0, guild.ranged_heavy): weapons.extend(data['ranged']['heavy']) if not weapons: return None return choice(weapons) def select_spell_list(npc, data): npc_spell_list = [] overall_spell_list = [] if npc.is_arcane_spellcaster: overall_spell_list = data['spells']['arcane'] if npc.is_divine_spellcaster: overall_spell_list = data['spells']['divine'] for level, known in enumerate(npc.spells_known(), start=1): level_set = set() while len(level_set) < known: level_set.add(choice(overall_spell_list[level]).id) npc_spell_list.extend(level_set) return ','.join(str(sid) for sid in npc_spell_list) def calc_hp(conmod, hit_die_size, level): hp = 0 hitdice = [randint(1, hit_die_size) for x in range(0, level)] for die in hitdice: die += conmod if die < 1: die = 1 hp += die return hp def calc_armour(armour_mod, armours): armourval = randint(0, len(armours) - 1) + armour_mod if armourval < 0: armourval = 0 if armourval > len(armours) - 1: armourval = len(armours) - 1 return armours[armourval] def generate_npc(base_level, data, guild_id=False): npc = CharacterNPC() if not guild_id: npc.guild = npc_class(['Demi-Human']) else: npc.guild = CharacterClass.query.filter_by(id=guild_id).first() npc.level = npc_baselevel(base_level) if npc.level > npc.guild.maximum_level: npc.level = npc.guild.maximum_level npc.alignment = npc_alignment() npc.update(npc_abilities()) npc.hit_points = calc_hp(attribute_mod(npc.constitution), npc.guild.hit_die_size, npc.level) npc.armour = calc_armour(npc.guild.armour_modifier, data['armours']) npc.melee = select_melee_weapon(npc.guild, data) npc.ranged = select_ranged_weapon(npc.guild, data) npc.spells = select_spell_list(npc, data) return npc def name_party_api(party): male_names = requests.get( 'http://names.drycodes.com/{}'.format(ceil(len(party))), params={'nameOptions': 'boy_names', 'separator': 'space'} ).json() female_names = requests.get( 'http://names.drycodes.com/{}'.format(floor(len(party))), params={'nameOptions': 'girl_names', 'separator': 'space'} ).json() names = female_names + male_names shuffle(names) for i in range(0, len(party)): party[i].name = names[i] return party def load_name_data(): names = [] surnames = [] with open('acks/npc/data/fantasy_names.csv', newline='') as data: reader = csv.DictReader(data) for row in reader: for k,v in row.items(): if v == '1': row[k] = True elif v == '': row[k] = False if row['Family Name']: surnames.append(row['Name']) else: names.append(row['Name']) return (names, surnames) def name_party(party): names, surnames = load_name_data() shuffle(names) shuffle(surnames) for i in range(0, len(party)): party[i].name = '{} {}'.format(choice(names), choice(surnames)) return party def load_db_data(): data = { 'armours': EquipmentArmour.query.all(), 'ranged': { 'light': EquipmentRangedWeapon.query.filter_by(bucket='Light').all(), 'heavy': EquipmentRangedWeapon.query.filter_by(bucket='Heavy').all() }, 'melee': { 'light': EquipmentMeleeWeapon.query.filter_by(bucket='Light').all(), 'medium': EquipmentMeleeWeapon.query.filter_by(bucket='Medium').all(), 'heavy': EquipmentMeleeWeapon.query.filter_by(bucket='Heavy').all() }, 'spells': { 'divine': {}, 'arcane': {}, } } for spell in Spell.query.all(): if spell.is_arcane: spells = [spell] if data['spells']['arcane'] and spell.arcane in data['spells']['arcane']: spells.extend(data['spells']['arcane'][spell.arcane]) data['spells']['arcane'][spell.arcane] = spells if spell.is_divine: spells = [spell] if data['spells']['divine'] and spell.divine in data['spells']['divine']: spells.extend(data['spells']['divine'][spell.divine]) data['spells']['divine'][spell.divine] = spells return data def create_npc(base_level, guild_id): data = load_db_data() if guild_id: return name_party([generate_npc(base_level, data, guild_id=guild_id)])[0] return name_party([generate_npc(base_level, data)])[0] def create_party(base_level): data = load_db_data() return name_party([generate_npc(base_level, data) for x in range(0, number_encountered())]) def print_party(party): def print_npc(npc): print('Level {0} NPC, {1}, {2} HP'.format(npc.level, npc.guild, npc.hp)) print('{0} Str, {1} Int, {2} Wis, {3} Dex, {4} Con, {5} Chr'.format( npc.str, npc.int, npc.wis, npc.dex, npc.con, npc.chr )) print('Armour Class: {0} - {1}, {2}gp'.format(npc.armour.name, npc.armour.ac_mod, npc.armour.gp_value)) print('{:^16} - {:^10} - {:^10} - {:^10}'.format('Weapon', 'Gold', 'Throw Mod', 'Damage')) print('-------------------------------------------------------') print('{:^16} | {:^10} | {:^10} | {:^10}'.format(npc.melee.name, 0, npc.melee.damage_die)) if npc['ranged']: print('{:^16} | {:^10} | {:^10} | {:^10}'.format(npc.ranged.name, 0, npc.melee.damage_die)) print('\n') for npc in party: print_npc(npc) if __name__ == '__main__': party = create_party(2) print_party(party)