def draw_conditions(conditions, refresh=False):
    """
    Renders the player's current condition levels.
    :param conditions: A dict containing each condition and it's level as [current, max, counter]. Counter isn't
                       used in this module.
    :param refresh: As above.
    :return: The Rect object that encloses the conditions.
    """
    condition_rect = (PLAYER_PANEL_TOP_LEFT_X + SIDE_PANEL_LENGTH - 90, PLAYER_PANEL_TOP_LEFT_Y + 10, 80, 90)
    if refresh:
        MAIN_WINDOW.fill(colors.BLACK, condition_rect)
    condition_y_mapping = {'thirsty': 10, 'hungry': 35, 'tired': 60}
    for condition in conditions:
        # Condition names are only rendered if the level is below 50%
        if conditions[condition][0] < 0.5 * conditions[condition][1]:
            current = conditions[condition][0]
            max = conditions[condition][1]
            # Condition font color is:
            #   yellow if level is in [35%, 50%)
            #   orange if level is in [15%, 35%)
            #   red if level is in [0$%, 15)
            if current < 0.15 * max:
                color = colors.RED
            elif current < 0.35 * max:
                color = colors.ORANGE
            else:
                color = colors.YELLOW
            condition_indicator = FONT_20.render(condition.upper(), 1, color)
            MAIN_WINDOW.blit(condition_indicator, (PLAYER_PANEL_TOP_LEFT_X + SIDE_PANEL_LENGTH - 90,
                                                   PLAYER_PANEL_TOP_LEFT_Y + condition_y_mapping[condition]))
    return pg.Rect(condition_rect)
def draw_skill_tree_level_progression(player_level, space_between_levels,
                                      skill_tile_length):
    """
    Draws a margin on the skill tree displaying the required player level for each tree level, as well as filling in
    the background of the tree to indicate what level the player has reached so far.
    The required player level will be displayed in a badge icon (think an upside-down pentagon), which will be drawn
    by passing in a list of 5 xy-coordinate pairs to the draw_polygon() pygame function. The x and y variables below
    refer to the x and y coordinate values of the top-left corner of the first badge, from which all other points will
    be derived.
    """
    player_level = min(player_level, 10)
    window_fill_from_player_level = {
        1:
        0.2,  # Determine how much of the tree background to fill as a percentage of the full window, based
        2: 0.31,  # on player level (maxed out at 10)
        3: 0.365,
        4: 0.437,
        5: 0.564,
        6: 0.619,
        7: 0.691,
        8: 0.818,
        9: 0.873,
        10: 1
    }
    MAIN_WINDOW.fill(color=colors.DARK_RED,
                     rect=(PLAYER_PANEL_TOP_LEFT_X + 1,
                           PLAYER_PANEL_TOP_LEFT_Y + 1, SIDE_PANEL_LENGTH - 2,
                           window_fill_from_player_level[player_level] *
                           (SIDE_PANEL_HEIGHT - 2)))
    x = PLAYER_PANEL_TOP_LEFT_X + 0.3 * skill_tile_length
    y = PLAYER_PANEL_TOP_LEFT_Y + 2 * skill_tile_length
    badge_length = 0.3 * skill_tile_length
    base_badge_points = [(x, y), (x + badge_length, y),
                         (x + badge_length, y + badge_length),
                         (x + 0.5 * badge_length, y + 1.5 * badge_length),
                         (x, y + badge_length)]
    req_level_from_tree_level = {
        0: '1',
        1: '2',
        2: '4',
        3: '5',
        4: '7',
        5: '8',
        6: '10'
    }
    for i in range(7):
        badge_points = [(a, b + i * (skill_tile_length + space_between_levels))
                        for (a, b) in base_badge_points]
        pg.draw.polygon(MAIN_WINDOW, colors.YELLOW, badge_points)
        level_text = FONT_CALIBRI_12.render(req_level_from_tree_level[i], 1,
                                            colors.BLACK)
        text_rect = level_text.get_rect(
            center=(badge_points[0][0] + 0.5 * badge_length,
                    badge_points[0][1] + 0.75 * badge_length))
        MAIN_WINDOW.blit(level_text, text_rect)
def draw_skill_as_upgradable(player_level, ability_entry, rect):
    """
    If the player has skill points to spend, and a particular skill is upgradable, render a '+' sign on the skill
    to indicate that it can be upgraded.
    """
    if ability_entry['ability'].level < 3 and ability_entry['level_prereq'] <= player_level and \
            not ability_entry.get('disabled', False):
        plus_sign = FONT_50.render('+', 1, colors.YELLOW)
        text_rect = plus_sign.get_rect(center=(rect[0] + 0.5 * rect[2],
                                               rect[1] + 0.5 * rect[3]))
        MAIN_WINDOW.blit(plus_sign, text_rect)
def draw_active_abilities(abilities, refresh=False, skill_points=0):
    """
    Renders the player's active abilities. If the ability is on cooldown, fill it with a darker color and also
    draw the number of turns left in the cooldown on the tile. If skill_points > 0, then draw a message telling
    the player that they have skill points to spend.
    """
    abilities_rect = pg.Rect(ABILITIES_TOP_LEFT_X, ABILITIES_TOP_LEFT_Y, ABILITY_TILE_LENGTH * 5, ABILITY_TILE_LENGTH)
    if refresh:
        MAIN_WINDOW.fill(color=colors.BLACK, rect=abilities_rect)
    while len(abilities) < 5:  # Pad the abilities list with None until it is of length 5
        abilities.append(None)
    abilities_label = FONT_20.render('ABILITIES', 1, colors.WHITE)
    MAIN_WINDOW.blit(abilities_label, (ABILITIES_TOP_LEFT_X, ABILITIES_TOP_LEFT_Y - 25))
    ability_tiles = list()
    for i, ability in enumerate(abilities):
        ability_tile = pg.Rect((i * ABILITY_TILE_LENGTH) + ABILITIES_TOP_LEFT_X, ABILITIES_TOP_LEFT_Y,
                               ABILITY_TILE_LENGTH, ABILITY_TILE_LENGTH)
        pg.draw.rect(MAIN_WINDOW, colors.GREY, ability_tile, 1)
        if ability is not None:
            ability_tiles.append(ability_tile)
            populate_ability_tile(ability, ability_tile)

        ability_number = FONT_20.render(str(i + 1), 1, colors.YELLOW)
        MAIN_WINDOW.blit(ability_number, (ABILITIES_TOP_LEFT_X + (1 + i) * ABILITY_TILE_LENGTH - 20,
                                          ABILITIES_TOP_LEFT_Y + ABILITY_TILE_LENGTH - 30))

    if skill_points > 0:
        skill_point_message = FONT_20.render(f"{skill_points} unspent skill point{'s' if skill_points > 1 else ''}, "
                                             f"press T to allocate", 1,
                                             colors.YELLOW)
        MAIN_WINDOW.blit(skill_point_message, (ABILITIES_TOP_LEFT_X, ABILITIES_TOP_LEFT_Y + 1.1 * ABILITY_TILE_LENGTH))

    return ability_tiles, abilities_rect
def draw_player_panel(player_name, refresh=False):
    """
    Renders the main window and player name.
    :param player_dict:
    :return: panel_rect, the rect that makes up the main panel
    """
    panel_rect = pg.Rect(PLAYER_PANEL_TOP_LEFT_X, PLAYER_PANEL_TOP_LEFT_Y, SIDE_PANEL_LENGTH, SIDE_PANEL_HEIGHT)
    if refresh:
        MAIN_WINDOW.fill(colors.BLACK, panel_rect)
    pg.draw.rect(MAIN_WINDOW, colors.WHITE, panel_rect, 2)
    player_name = FONT_30.render(player_name, 1, colors.WHITE)
    MAIN_WINDOW.blit(player_name, (PLAYER_PANEL_TOP_LEFT_X + 5, PLAYER_PANEL_TOP_LEFT_Y + 5))

    return panel_rect
def render_console(lines):
    """
    Prints every line in 'lines' to the console at the top of the screen.
    :param lines: A list of strings.
    :return: n/a
    """
    #TODO: Re-do dimensions in terms of config variables
    console_rect = (TOP_LEFT_X * 0.956, TOP_LEFT_Y * 0.128, PLAY_LENGTH * 1.05, TOP_LEFT_Y * 0.84)
    MAIN_WINDOW.fill(colors.BLACK, rect=console_rect)
    for i, line in enumerate(lines):
        # Add an effect so that most recent lines in the console are brightest, and oldest get gradually darker
        color_offset = 25 * (len(lines) - i)
        color = (colors.WHITE[0] - color_offset, colors.WHITE[1] - color_offset, colors.WHITE[2] - color_offset)
        line_render = FONT_15.render(line, 1, color)
        MAIN_WINDOW.blit(line_render, (TOP_LEFT_X - 20, 16 + (i * 16)))
        pg.display.update(console_rect)
def render_focus_window(focus_info=None, refresh=False):
    """
    Draws the outer frame of the focus window, and fills it based on the focus tile, if it's not None.

    :param focus_info: A dict with a 'type' key that determines that specific function to draw the info, as well as all
                       the info needed by that function.
    :param refresh: Flag that determines whether or not the window is re-filled with black, as a refresh.
    :return: n/a
    """
    f_window_rect = (F_WINDOW_TOP_LEFT_X, F_WINDOW_TOP_LEFT_Y, F_WINDOW_LENGTH,
                     F_WINDOW_HEIGHT)
    pg.draw.rect(MAIN_WINDOW, colors.WHITE, f_window_rect, 1)
    if refresh:
        MAIN_WINDOW.fill(colors.BLACK,
                         rect=(f_window_rect[0] + 1, f_window_rect[1] + 1,
                               f_window_rect[2] - 2, f_window_rect[3] - 2))
    if focus_info is not None:
        if focus_info['type'] == 'enemy':
            render_enemy_info(focus_info)
def draw_attributes(attributes, level_up_points, refresh=False):
    """
    Renders the player's attributes.
    :param attributes: A dict containing all of the players attribute values.
    :param level_up_points: An int representing the number of level_up points the player has to allocate for their
                            attributes. If > 0, then the level-up buttons are also rendered
    :param refresh: A boolean determining if the area around this info is filled to black, as a refresh.
    :return: The Rect object enclosing the attributes.
    """
    attributes_rect = pg.Rect(PLAYER_PANEL_TOP_LEFT_X + 10, PLAYER_PANEL_TOP_LEFT_Y + 95, 150, 180)
    if refresh:
        MAIN_WINDOW.fill(colors.BLACK, attributes_rect)
    coord_mapping = {
        'str': (PLAYER_PANEL_TOP_LEFT_X + 10, PLAYER_PANEL_TOP_LEFT_Y + 120),
        'dex': (PLAYER_PANEL_TOP_LEFT_X + 10, PLAYER_PANEL_TOP_LEFT_Y + 145),
        'int': (PLAYER_PANEL_TOP_LEFT_X + 10, PLAYER_PANEL_TOP_LEFT_Y + 170),
        'end': (PLAYER_PANEL_TOP_LEFT_X + 10, PLAYER_PANEL_TOP_LEFT_Y + 195),
        'vit': (PLAYER_PANEL_TOP_LEFT_X + 10, PLAYER_PANEL_TOP_LEFT_Y + 220),
        'wis': (PLAYER_PANEL_TOP_LEFT_X + 10, PLAYER_PANEL_TOP_LEFT_Y + 245)
    }
    font = pg.font.Font(font_SIL, 20)
    for stat in coord_mapping:
        # Render the stat names and values separately, so that they can be properly aligned
        # TODO: Add logic to color stat values differently based on buffs/debuffs
        stat_name = font.render(f"{stat.upper()}: ", 1, colors.WHITE)
        stat_value = font.render(str(attributes[stat]), 1, colors.WHITE)
        MAIN_WINDOW.blit(stat_name, coord_mapping[stat])
        MAIN_WINDOW.blit(stat_value, (coord_mapping[stat][0] + 50, coord_mapping[stat][1]))

    if level_up_points > 0:
        draw_attribute_level_up_buttons(level_up_points)

    return attributes_rect
def draw_inventory(inventory, refresh=False):
    """
    Draws the player inventory.
    :param inventory: A list of every Item object in the players inventory.
    :param refresh: As above.
    :return: inventory_tile, a list of every item rect in the inventory, and inventory_rect the rect of the whole
             inventory
    """
    inventory_rect = pg.Rect(INVENTORY_TOP_LEFT_X, INVENTORY_TOP_LEFT_Y,
                             ITEM_LENGTH * int(INVENTORY_LIMIT / INVENTORY_NUM_ROWS), ITEM_LENGTH * INVENTORY_NUM_ROWS)
    if refresh:
        MAIN_WINDOW.fill(color=colors.BLACK, rect=inventory_rect)
    inventory_label = FONT_20.render("INVENTORY", 1, colors.WHITE)
    MAIN_WINDOW.blit(inventory_label, (INVENTORY_TOP_LEFT_X, INVENTORY_TOP_LEFT_Y - 25))
    inventory_tiles = list()
    for y in range(INVENTORY_NUM_ROWS):
        for x in range(int(INVENTORY_LIMIT / INVENTORY_NUM_ROWS)):
            item_tile = pg.Rect((x * ITEM_LENGTH) + INVENTORY_TOP_LEFT_X,
                                (y * ITEM_LENGTH) + INVENTORY_TOP_LEFT_Y, ITEM_LENGTH, ITEM_LENGTH)
            pg.draw.rect(MAIN_WINDOW, colors.GREY, item_tile, 1)
            if len(inventory) >= (y * 6) + x + 1:
                MAIN_WINDOW.fill(color=colors.ORANGE, rect=((x * ITEM_LENGTH) + INVENTORY_TOP_LEFT_X + 1,
                                                            (y * ITEM_LENGTH) + INVENTORY_TOP_LEFT_Y + 1,
                                                            ITEM_LENGTH - 2, ITEM_LENGTH - 2))
                inventory_tiles.append(item_tile)
    return inventory_tiles, inventory_rect
def draw_status(buffs, debuffs, refresh=False):
    """
    Draws indicators for player buffs and debuffs. Each will be represented by a 15x15 square, green for buffs and red
    for debuffs. Buffs will all go along one row, and debuffs along another row below.
    :param buffs: List of dict representations of every buff.
    :param debuffs: List of dict representations of every debuff.
    :param refresh: Same as above.
    :return: Lists of each rect for the buff and debuff indicators.
    """
    status_rect = pg.Rect(PLAYER_PANEL_TOP_LEFT_X + 130, PLAYER_PANEL_TOP_LEFT_Y + 40, 200, 50)
    if refresh:
        MAIN_WINDOW.fill(colors.BLACK, status_rect)
    buff_rects = list()
    debuff_rects = list()

    for i, buff in enumerate(buffs):
        buff_indicator = pg.Rect((i*17) + PLAYER_PANEL_TOP_LEFT_X + 130, PLAYER_PANEL_TOP_LEFT_Y + 40, 15, 15)
        buff_rects.append(buff_indicator)
        buff_turns_left = FONT_CALIBRI_12.render(str(buff['turns_left']), 1, colors.YELLOW)
        MAIN_WINDOW.blit(buff_turns_left, (buff_indicator[0] + 2, buff_indicator[1] + 2))
        pg.draw.rect(MAIN_WINDOW, colors.GREEN, buff_indicator, 1)

    for i, debuff in enumerate(debuffs):
        debuff_indicator = pg.Rect((i*17) + PLAYER_PANEL_TOP_LEFT_X + 130, PLAYER_PANEL_TOP_LEFT_Y + 57, 15, 15)
        debuff_rects.append(debuff_indicator)
        debuff_turns_left = FONT_CALIBRI_12.render(str(debuff['turns_left']), 1, colors.YELLOW)
        MAIN_WINDOW.blit(debuff_turns_left, (debuff_indicator[0] + 2, debuff_indicator[1] + 2))
        pg.draw.rect(MAIN_WINDOW, colors.RED, debuff_indicator, 1)

    return status_rect, buff_rects, debuff_rects
示例#11
0
def animate_enemy_death(enemy_x, enemy_y):
    """
    Function to gradually change the color of an enemy, on death, so as to create the effect of fading to the
    background grey color, currently set to (173, 173, 173). On input of the enemy's coordinates, it works as follows:
        i. use the tile_from_xy_coords() function to get the dimensions and location of the enemy's Rect on the board
        ii. starting from the base red color for enemies, iteratively set the value of each hue to the average of
            the current value and 173. This will eventually converge to ~ 173.
        iii. The condition on the while loop can be read as:
                iterate until the sum of the 3 hue values lies between 3*170 and 3*175.
             This is done so that the animation doesn't end prematurely when one of the hues reaches ~173 before the
             other two, and to allow the loop to iterate fewer times as the visual difference of values from
             170-175 is largely negligible.
    """
    new_color = copy(TILE_COLORS['E'])
    enemy_rect = pg.Rect(tile_from_xy_coords(x=enemy_x, y=enemy_y))

    while not 510 < new_color[0] + new_color[1] + new_color[2] < 525:
        new_color = (int(
            (new_color[0] + 173) / 2), int(
                (new_color[1] + 173) / 2), int((new_color[2] + 173) / 2))
        MAIN_WINDOW.fill(rect=enemy_rect, color=new_color)
        pg.display.update(enemy_rect)
        sleep(0.1)
def render_enemy_statuses(buffs, debuffs):
    """ Draws indicators for enemy buffs and debuffs. """
    buff_top_left_x = PORTRAIT_TOP_LEFT_X + PORTRAIT_LENGTH + 5
    buff_top_left_y = PORTRAIT_TOP_LEFT_Y + 25
    for i, buff in enumerate(buffs):
        buff_indicator = pg.Rect((i * 17) + buff_top_left_x, buff_top_left_y,
                                 15, 15)
        buff_turns_left = FONT_CALIBRI_12.render(str(buff['turns_left']), 1,
                                                 colors.YELLOW)
        MAIN_WINDOW.blit(buff_turns_left,
                         (buff_indicator[0] + 2, buff_indicator[1] + 2))
        pg.draw.rect(MAIN_WINDOW, colors.GREEN, buff_indicator, 1)

    debuff_top_left_x = buff_top_left_x
    debuff_top_left_y = buff_top_left_y + 17
    for i, debuff in enumerate(debuffs):
        debuff_indicator = pg.Rect((i * 17) + debuff_top_left_x,
                                   debuff_top_left_y, 15, 15)
        debuff_turns_left = FONT_CALIBRI_12.render(str(debuff['turns_left']),
                                                   1, colors.YELLOW)
        MAIN_WINDOW.blit(debuff_turns_left,
                         (debuff_indicator[0] + 2, debuff_indicator[1] + 2))
        pg.draw.rect(MAIN_WINDOW, colors.RED, debuff_indicator, 1)
def draw_level_and_experience(level, profession, experience, refresh=False):
    """
    Renders the players level, type, and experience bar.
    :param level: An int which is the player's current level.
    :param profession: A string which is the player's current profession.
    :param experience: A list which stores the players experience progress as [current, max].
    :param refresh: A boolean which determines if the area around this info is filled to black, as a refresh.
    :return: The Rect enclosing all of the level and experience info.
    """
    level_and_exp_rect = pg.Rect(LEVEL_EXP_TOP_LEFT_X, LEVEL_EXP_TOP_LEFT_Y, LEVEL_EXP_LENGTH, LEVEL_EXP_HEIGHT)
    if refresh:
        MAIN_WINDOW.fill(colors.BLACK, level_and_exp_rect)
    level_indicator = FONT_20.render(f"LEVEL {level} {profession.upper()}", 1, colors.WHITE)
    MAIN_WINDOW.blit(level_indicator, (LEVEL_EXP_TOP_LEFT_X, LEVEL_EXP_TOP_LEFT_Y))
    pg.draw.rect(MAIN_WINDOW, colors.GREY,
                 (LEVEL_EXP_TOP_LEFT_X, LEVEL_EXP_TOP_LEFT_Y + 24, LEVEL_EXP_LENGTH, LEVEL_EXP_HEIGHT - 27), 1)

    exp_percent = experience[0] / experience[1]
    current_exp_length = int(exp_percent * (SIDE_PANEL_LENGTH - 18 - PLAYER_PANEL_TOP_LEFT_X))
    if current_exp_length > 0:
        pg.draw.rect(MAIN_WINDOW, colors.PALE_YELLOW,
                     (LEVEL_EXP_TOP_LEFT_X, LEVEL_EXP_TOP_LEFT_Y + 26, current_exp_length, 6), 0)

    return level_and_exp_rect
def populate_ability_tile(ability, ability_tile):
    """Adds color and cooldown timer to ability tile depending on if the ability is currently on cooldown."""
    if ability['turns_left'] > 0:  # Check if the ability is currently on cooldown
        turns_left_label = FONT_30.render(str(ability['turns_left']), 1, colors.WHITE)
        MAIN_WINDOW.fill(color=colors.DARK_BLUE, rect=(ability_tile[0] + 1, ability_tile[1] + 1,
                                                       ability_tile[2] - 2, ability_tile[3] - 2))
        MAIN_WINDOW.blit(turns_left_label, (ability_tile[0] + (ABILITY_TILE_LENGTH * 0.4),
                                            ability_tile[1] + (ABILITY_TILE_LENGTH * 0.2)))
    else:
        MAIN_WINDOW.fill(color=colors.BLUE, rect=(ability_tile[0] + 1, ability_tile[1] + 1,
                                                  ability_tile[2] - 2, ability_tile[3] - 2))
def draw_hp_mp(hp, mp, refresh=False):
    """
    Renders the player's HP and MP.
    :param hp: A list storing the player's current HP level as  [current, max]
    :param mp: A list storing the player's current MP level as  [current, max]
    :param refresh: As above.
    :return: The Rect object that encloses hp and mp.
    """
    hp_mp_rect = pg.Rect(PLAYER_PANEL_TOP_LEFT_X + 10, PLAYER_PANEL_TOP_LEFT_Y + 40, 100, 50)
    if refresh:
        MAIN_WINDOW.fill(colors.BLACK, hp_mp_rect)
    hp_indicator = FONT_20.render("HP: {0} / {1}".format(hp[0], hp[1]), 1, colors.RED)
    mp_indicator = FONT_20.render("MP: {0} / {1}".format(mp[0], mp[1]), 1, colors.BLUE)
    MAIN_WINDOW.blit(hp_indicator, (PLAYER_PANEL_TOP_LEFT_X + 10, PLAYER_PANEL_TOP_LEFT_Y + 40))
    MAIN_WINDOW.blit(mp_indicator, (PLAYER_PANEL_TOP_LEFT_X + 10, PLAYER_PANEL_TOP_LEFT_Y + 65))
    return hp_mp_rect
def draw_equipment(equipment_dict, refresh=False):
    """
    Draws the players current equipment. Each equipment slot will be the size of an item tile in the inventory, arranged
    in a cross formation, e.g.
            X
           XXX
            X
    To do this, we render the equipment as a 3x3 grid, but only the squares that would appear in this cross are
    rendered, and the coordinates for this squares are mapped to the corresponding equipment slot in the
    grid_equipment_mapping dict below.
    :param equipment_dict: A dict containing the player's current equipment.
    :param refresh: As above.
    :return: equipment_tiles, a dict where the key is an equipment slot and the value is the Rect enclosing that slot,
             equipment_rect, a Rect object enclosing all of the equipment info.
    """
    equipment_rect = pg.Rect(EQUIPMENT_TOP_LEFT_X, EQUIPMENT_TOP_LEFT_Y, EQUIPMENT_LENGTH, EQUIPMENT_HEIGHT)
    if refresh:
        MAIN_WINDOW.fill(color=colors.BLACK, rect=equipment_rect)
    equipment_label = FONT_20.render("EQUIPMENT", 1, colors.WHITE)
    MAIN_WINDOW.blit(equipment_label, (EQUIPMENT_TOP_LEFT_X, EQUIPMENT_TOP_LEFT_Y - 8))
    grid_equipment_mapping = {
        (1, 0): 'head',
        (0, 1): 'hands',
        (1, 1): 'body',
        (2, 1): 'weapon',
        (1, 2): 'feet'
    }
    equipment_tiles = dict()
    for y in range(3):
        for x in range(3):
            if (x, y) not in grid_equipment_mapping:
                continue
            item_tile = pg.Rect((0.5 + x) * EQUIP_ITEM_LENGTH + EQUIPMENT_TOP_LEFT_X,
                                (0.5 + y) * EQUIP_ITEM_LENGTH + EQUIPMENT_TOP_LEFT_Y,
                                EQUIP_ITEM_LENGTH, EQUIP_ITEM_LENGTH)
            pg.draw.rect(MAIN_WINDOW, colors.GREY, item_tile, 1)
            # Check if the corresponding equipment slot currently has any items equipped.
            if equipment_dict[grid_equipment_mapping[(x, y)]]:
                MAIN_WINDOW.fill(color=colors.ORANGE, rect=((0.5 + x) * EQUIP_ITEM_LENGTH + EQUIPMENT_TOP_LEFT_X + 1,
                                                            (0.5 + y) * EQUIP_ITEM_LENGTH + EQUIPMENT_TOP_LEFT_Y + 1,
                                                            EQUIP_ITEM_LENGTH - 2, EQUIP_ITEM_LENGTH - 2))

            equipment_tiles[grid_equipment_mapping[(x, y)]] = item_tile
    return equipment_tiles, equipment_rect
def render_enemy_info(enemy_dict):
    """
    Function for filling in all the focus window info when focusing an enemy.
    :param enemy_dict: A dict with all of the enemy details needed to display.
    :return: n/a
    """
    pg.draw.rect(MAIN_WINDOW, colors.WHITE,
                 (PORTRAIT_TOP_LEFT_X, PORTRAIT_TOP_LEFT_Y, PORTRAIT_LENGTH,
                  PORTRAIT_HEIGHT), 1)

    enemy_hp_percentage = float(enemy_dict['hp'][0]) / float(
        enemy_dict['hp'][1])
    # A bit of text will be added to the enemy description to indicate their health level.
    # TODO: These might need some work
    if enemy_hp_percentage > 0.66:
        health_text = 'This creature looks quite healthy.'
    elif enemy_hp_percentage > 0.33:
        health_text = 'This creature seems to be in pain.'
    else:
        health_text = 'This creature is on the brink of death.'

    enemy_name = FONT_20.render(enemy_dict['name'], 1, colors.WHITE)
    parsed_flavour_text = parse_description(enemy_dict['flavour_text'],
                                            char_limit=55)
    flavour_text = [
        FONT_15.render(line, 1, colors.WHITE) for line in parsed_flavour_text
    ]
    health_indicator = FONT_15.render(health_text, 1, colors.WHITE)

    MAIN_WINDOW.blit(
        enemy_name,
        (PORTRAIT_TOP_LEFT_X + PORTRAIT_LENGTH + 5, PORTRAIT_TOP_LEFT_Y))
    for i, string in enumerate(flavour_text):
        MAIN_WINDOW.blit(string,
                         (PORTRAIT_TOP_LEFT_X,
                          PORTRAIT_TOP_LEFT_Y + PORTRAIT_HEIGHT + 3 + 16 * i))
    MAIN_WINDOW.blit(health_indicator,
                     (PORTRAIT_TOP_LEFT_X,
                      PORTRAIT_TOP_LEFT_Y + PORTRAIT_HEIGHT + 45 + 16 * i))
    if len(enemy_dict['buffs']) + len(enemy_dict['debuffs']) > 0:
        render_enemy_statuses(buffs=enemy_dict['buffs'],
                              debuffs=enemy_dict['debuffs'])
def draw_attribute_level_up_buttons(level_up_points, return_only=False):
    """
    Draws buttons next to player attributes for the purpose of allocating level-up points to increase attributes.
    :param level_up_points: Int, number of points available.
    :param return_only: Boolean, if True then don't draw anything, only return the list of button rects, to be used
                        for event-handling.
    :return: level_up_buttons, a list of Rect objects that make up all of the buttons.
    """
    top_left_x = PLAYER_PANEL_TOP_LEFT_X + 85
    top_left_y = PLAYER_PANEL_TOP_LEFT_Y + 125
    if not return_only:
        level_up_label = FONT_20.render(f"Points Available: {level_up_points}", 1, colors.GREY)
        MAIN_WINDOW.blit(level_up_label, (top_left_x - 75, top_left_y - 27))
    level_up_buttons = list()
    button_label = FONT_20.render("+", 1, colors.WHITE)
    for i in range(6):
        button_rect = pg.Rect(top_left_x, top_left_y + (i * 25), 20, 20)
        level_up_buttons.append(button_rect)
        if not return_only:
            MAIN_WINDOW.fill(color=colors.DARK_RED, rect=button_rect)
            MAIN_WINDOW.blit(button_label, (button_rect[0] + 4, button_rect[1]  - 5))

    return level_up_buttons
def draw_skill_tree(skill_tree, profession, player_level, skill_points):
    """
    Draws the character's skill tree in the player panel. Skill trees will be made of 7 layers, each layer alternating
    between active and passive skills. Extra rendering logic is added to the layers with active abilities after the
    first, so that a piece of text saying "OR" will appear between the skills, to indicate that the player can only
    choose one active skill for each layer after the first. All the parameters are taken from the player_dict saved
    as an attribute in the PlayerPanel class.
    :param profession: String, the Player's profession
    :param skill_tree: Nested dict of the Player's skill tree
    :param skill_points: Int, number of skill points the player has to allocate
    :return: rect_map, a dict that has as keys the name of the tree level and index in the level, and it's value is the
             corresponding rect. E.g., the entry for the 2nd skill in the active_2 row would have the entry:
                ('active_2', 1) : pg.Rect(...)
    """
    # Reset the player panel to black.
    MAIN_WINDOW.fill(color=colors.BLACK,
                     rect=(PLAYER_PANEL_TOP_LEFT_X + 1,
                           PLAYER_PANEL_TOP_LEFT_Y + 1, SIDE_PANEL_LENGTH - 2,
                           SIDE_PANEL_HEIGHT - 2))

    skill_tile_length = 0.7 * ABILITY_TILE_LENGTH
    space_between_levels = 0.9 * skill_tile_length  # The vertical space between each layer of the tree
    draw_skill_tree_level_progression(player_level, space_between_levels,
                                      skill_tile_length)
    skill_tree_title = FONT_20.render(f'PATH OF THE {profession.upper()}', 1,
                                      colors.WHITE)  # Skill tree title
    MAIN_WINDOW.blit(
        skill_tree_title,
        (PLAYER_PANEL_TOP_LEFT_X + 5, PLAYER_PANEL_TOP_LEFT_Y + 5))
    points_remaining = FONT_20.render(
        f'Skill points remaining: {skill_points}', 1, colors.WHITE)
    MAIN_WINDOW.blit(
        points_remaining,
        (PLAYER_PANEL_TOP_LEFT_X + 5, PLAYER_PANEL_TOP_LEFT_Y + 30))
    rect_map = dict()
    for tree_level, level_name in enumerate(skill_tree):
        num_skills_in_row = len(skill_tree[level_name])
        # Horizontal space between skills within a layer will be based off the number of skills in each layer
        space_between_skills = (SIDE_PANEL_LENGTH -
                                (num_skills_in_row * skill_tile_length)) / (
                                    num_skills_in_row + 1)
        for i in range(num_skills_in_row):
            # This formula for the top-left xy-coordinates of each skill works out such that the space between each
            # skill in a row will come out equal to space_between_skills, and the vertical space between each layer
            # will be space_between_levels
            skill_rect = ((i + 1) * space_between_skills +
                          i * skill_tile_length + PLAYER_PANEL_TOP_LEFT_X,
                          (tree_level + 1) * space_between_levels +
                          tree_level * skill_tile_length +
                          PLAYER_PANEL_TOP_LEFT_Y + 40, skill_tile_length,
                          skill_tile_length)
            rect_map[(level_name, i)] = pg.Rect(skill_rect)
            pg.draw.rect(
                MAIN_WINDOW, colors.GREY, skill_rect,
                0 if skill_tree[level_name][i]['ability'].level > 0 else 1)
            if skill_points > 0:
                draw_skill_as_upgradable(
                    player_level,
                    ability_entry=skill_tree[level_name][i],
                    rect=pg.Rect(skill_rect))
            if level_name in {'active_2', 'active_3', 'active_4'} and i > 0:
                # On the active skill layers (excluding the first), render an 'OR' between each skill. Location of the
                # text will be calculated based on the top-left coordinates of the skill that will appear after it
                # in a left-to-right order.
                MAIN_WINDOW.blit(
                    FONT_20.render('OR', 1, colors.PALE_YELLOW),
                    (skill_rect[0] - 0.54 * space_between_skills - 10,
                     skill_rect[1] + 0.2 * space_between_levels))

    return rect_map