def generate_caster(archetype, level, high_stat, second_high_stat, stats):
    # Two things these need to capture
    # spells known and the number of slots available
    # full caster, wizard, cleric, sorceror, bard
    # Full casters: smart characters or faithful with WIS as highest stat
    if roll.one_with_weights([(0, 10), (1, 4 + (level**2))]) == 1 and (
            archetype.value == 'Smart' or
        (archetype.value == 'Faithful' and stats['WIS'] > stats['CHA']) or
        (archetype.value == 'Crafty' and
         (high_stat == 'CHA' or second_high_stat == 'CHA'))):
        cantrips_known = floor(3 + (level / 10))
        spell_cap = 4 + level
        spells_known = roll.one(list(range(int(spell_cap / 2), spell_cap)))

        if archetype == 'Smart':
            cast_stat = high_stat
        elif archetype == 'Crafty':
            cast_stat = 'CHA'
        else:
            cast_stat = 'WIS'

        return 'Full Caster', cantrips_known, spells_known, cast_stat
        # return pick_spells(archetype, level, spells_known, cantrips_known, high_stat)

    # half caster: faithful characters with WIS as second highest stat, crafty characters with INT or CHA as high stat
    # only after level 2 - SHOULD BE WARLOCKS, RANGERS, AND PALADINS
    if roll.one_with_weights([(0, 20), (1, min(
            20, 2 * level))]) == 1 and level >= 2 and (
                (archetype.value == 'Crafty' and
                 (high_stat == 'WIS' or second_high_stat == 'WIS')) or
                (archetype.value == 'Faithful')):
        cantrips_known = 0
        spell_cap = floor(3 + ((level - 1) / 2))
        spells_known = roll.one(list(range(int(spell_cap / 2), spell_cap)))
        if archetype == 'Faithful':
            cast_stat = 'CHA'
        else:
            cast_stat = 'WIS'

        return 'Half Caster', cantrips_known, spells_known, cast_stat
        # return pick_spells(archetype, level, spells_known, cantrips_known, cast_stat)

    # 1/3 caster: Bulky characters with INT/WIS/CHA as one of top 2 stats
    # or crafty characters with INT or CHA as second high stat
    # only after level 3
    if roll.one_with_weights([(0, 20), (1, min(
            10, level))]) == 1 and level >= 3 and (
                (archetype.value == 'Bulky' and
                 (high_stat == 'INT' or second_high_stat == 'INT')) or
                (archetype.value == 'Crafty' and
                 (high_stat == 'INT' or second_high_stat == 'INT'))):
        cantrips_known = floor(1 + (level / 10))
        spell_cap = floor(4 + ((level - 1) / 3))
        spells_known = roll.one(list(range(int(spell_cap / 2), spell_cap)))

        return 'Third Caster', cantrips_known, spells_known, 'INT'
        # return pick_spells(archetype, level, spells_known, cantrips_known, cast_stat)

    return False, 0, 0, None
def generate_description(attrs, weapons, armors):
    desc_attrs = misc.get_attr_and_weights(attrs, 'Description')
    x = 0
    while x < 5:
        x += 1
        desc = roll.one_with_weights(desc_attrs)

        tag_dict = desc.get_tag_dict()
        tag_fills = {}
        for key, vals in tag_dict.items():
            if key != 'weapon_name_req' or key != 'armor_name_req':
                tag_fills[key] = roll.one(vals)
            elif key == 'weapon':
                tag_fills[key] = roll.one(weapons)
            elif key == 'armor':
                tag_fills[key] = roll.one(armors)

        if 'weapon_name_req' in tag_dict.keys():
            weapon = roll.from_list_if_list(tag_dict['weapon_name_req'],
                                            weapons)
            if weapon is None:
                continue
            else:
                tag_fills['weapon'] = weapon.lower()

        if 'armor_name_req' in tag_dict.keys():
            armor = roll.from_list_if_list(tag_dict['armor_name_req'], armors)
            if armor is None:
                continue
            else:
                tag_fills['armor'] = armor.lower()

        return desc.value.format_map(tag_fills)
    else:
        return 'They have a blank expression on their face'
def generate_skills(attrs, archetype, high_stat):
    skill_attrs = misc.get_attrs(attrs, 'Skill')

    class_skills = misc.get_attrs_by_tag(skill_attrs, 'archetype',
                                         archetype.value)

    trained_skills = []
    select = 3
    if high_stat is not 'CON':
        stat_skills = misc.get_attrs_by_tag(skill_attrs, 'skill_stat',
                                            high_stat)
        stat_skill = roll.one(stat_skills)
        trained_skills.append(stat_skill)
        if stat_skill in class_skills:
            select = 4

    skill_pool = roll.many(skill_attrs, select, selection=class_skills)

    skill_count = roll.one_with_weights([(3, 6), (4, 2), (5, 1)])
    for i in range(skill_count):
        skill_choice = roll.one_with_removal(skill_pool, trained_skills)
        if skill_choice is not None:
            trained_skills.append(skill_choice)

    return trained_skills
def generate_archetype(attrs, high_stat, low_stat):
    arch_attrs = misc.get_attrs(attrs, 'Archetype')
    archetype_weights = []
    for arch in arch_attrs:
        if arch.get_tag('stat') == high_stat:
            archetype_weights.append((arch, 30))
        elif arch.get_tag('stat') == low_stat:
            archetype_weights.append((arch, 1))
        else:
            archetype_weights.append((arch, 10))
    return roll.one_with_weights(archetype_weights)
def generate_saving_throws(attrs, archetype):
    saving_attrs = misc.get_attrs(attrs, 'Saving')
    class_saving = misc.get_attrs_by_tag(saving_attrs, 'archetype',
                                         archetype.value)

    saving_number = roll.one_with_weights([(0, 2), (1, 5), (2, 6), (3, 1)])
    if archetype == 'Faithful':
        saving_number += 1
    if saving_number == 0:
        return None
    else:
        saving_list = []
        for i in range(saving_number):
            saving_choice = roll.one_with_removal(class_saving, saving_list)
            saving_list.append(saving_choice)
    return saving_list
def generate_weapons(attr_query, archetype, stats, race, lowest_stat):
    if stats['DEX'] > stats['STR']:
        weapon_query = attr_query.filter_by(
            attribute='Weapon').filter((Attributes.tags.any(
                tag_name='archetype', tag_value=archetype.value)) & (
                    (Attributes.tags.any(tag_name='finesse', tag_value='True'))
                    | (Attributes.tags.any(tag_name='attack_type',
                                           tag_value='ranged'))))
    else:
        if race.get_tag('size') != 'small':
            weapon_query = attr_query.filter_by(attribute='Weapon').filter(
                Attributes.tags.any(tag_name='archetype',
                                    tag_value=archetype.value))
        else:
            weapon_query = attr_query.filter_by(attribute='Weapon').filter(
                (Attributes.tags.any(tag_name='archetype',
                                     tag_value=archetype.value))
                & (Attributes.tags.any(Tags.tag_value.notilike('heavy'))))

    if lowest_stat == 'DEX':
        weapon_query = weapon_query.filter(
            Attributes.tags.any(Tags.tag_value.notilike('ranged')))

    weapon_list = weapon_query.all()
    weapon_count = roll.one_with_weights([(1, 7), (2, 5), (3, 1)])
    if weapon_count == 1 and stats['STR'] > stats['DEX']:
        weapon_list = misc.get_attrs_by_tag(weapon_list, 'attack_type',
                                            'melee')

    weapons = []
    ranged_weapons = []

    while len(weapons) < weapon_count:
        wep_choice = roll.one_with_removal(weapon_list, ranged_weapons)
        if wep_choice is not None:
            weapons.append(wep_choice)
        else:
            break

        if wep_choice.get_tag('attack_type') == 'ranged':
            ranged_weapons = [
                x for x in weapon_list if x.get_tag('attack_type') == 'ranged'
            ]

    return weapons
def generate_stats(attrs, race, archetype):
    stat_attrs = misc.get_attrs(attrs, 'Stat')
    my_stats = {}
    array_choice = roll.one_with_weights([(1, 65), (2, 25), (3, 10)])

    if array_choice == 1:
        rolled_stats = roll.roll_stats(stat_array=True)
    elif array_choice == 2:
        rolled_stats = roll.roll_stats()
    else:
        rolled_stats = roll.roll_stats(drop_lowest=True)

    race_stat_array = [('STR', 10), ('DEX', 10), ('CON', 10), ('INT', 10),
                       ('WIS', 10), ('CHA', 10)]

    for k, stat in enumerate(race_stat_array):
        stat_weight = int(
            misc.get_attr_tag(stat_attrs, 'Stat', stat[0], race.value))
        if archetype is not None and archetype in misc.get_attr_tag(
                stat_attrs, 'Stat', stat[0], 'archetype'):
            stat_weight += 40
        race_stat_array[k] = (stat[0], stat_weight)

    removed_arch_weights = False
    for stat in rolled_stats:
        print(race_stat_array)
        chosen_stat = roll.one_with_weights_and_removal(
            race_stat_array, list(my_stats.keys()))
        print('{}: {}'.format(chosen_stat, stat))
        my_stats[chosen_stat] = stat
        print(my_stats)

        if not removed_arch_weights and archetype is not None \
                and archetype in misc.get_attr_tag(stat_attrs, 'Stat', chosen_stat, 'archetype'):
            removed_arch_weights = True
            print('removing additional archetype weights')
            for k, stat_val in enumerate(race_stat_array):
                if archetype in misc.get_attr_tag(stat_attrs, 'Stat',
                                                  stat_val[0], 'archetype'):
                    print('lowering {} weight from {} to {}'.format(
                        stat_val[0], stat_val[1], stat_val[1] - 40))
                    race_stat_array[k] = (stat_val[0], stat_val[1] - 40)

    return my_stats
def generate_race(attrs):
    race_attrs = misc.get_attrs(attrs, 'Race')
    race_weights = misc.get_attr_and_weights(race_attrs)
    return roll.one_with_weights(race_weights)