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_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_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_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_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_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_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 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