def __init__(self, name, text, options, width, height): Console.__init__(self, width, height) self.name = name self.text = text self.options = options self.max_options_len = 26 self.letter_index = ord('a')
def draw_log(self, root_console: tdl.Console): log_width = root_console.width log_height = 20 log_y = root_console.height - 3 - log_height log = tdl.Console(log_width, log_height) log.set_colors((255, 255, 255), (50, 50, 50)) log.clear() log.set_mode('scroll') for entry in self._log: log.set_colors(fg=entry[1]) log.print_str(entry[0]) log.print_str('\n') root_console.blit(log, 0, log_y)
def state(game: GameData, root_console: tdl.Console) -> GameState: root_console.clear() game.build_map() text = f'Dungeon Level {game.current_level}' root_console.draw_str(int(root_console.width / 2) - int(len(text) / 2), 15, text, fg=(255, 255, 0), bg=None) tdl.flush() user_input = wait_for_keys([ 'ESCAPE', 'ENTER', 'SPACE' ]) if user_input == 'ESCAPE': return GameState.CLOSE return GameState.ROOM
def state(game: GameData, root_console: tdl.Console) -> GameState: root_console.clear() text = f'You have died. Would you like to play again?' root_console.draw_str(root_console.width / 2 - len(text) / 2, 10, text) text = 'Y/N' root_console.draw_str(root_console.width / 2 - len(text) / 2, 12, text) tdl.flush() choice = wait_for_keys(['ESCAPE', 'y', 'n']) if choice == 'ESCAPE' or choice == 'n': # quit game return GameState.CLOSE root_console.clear() tdl.flush() return GameState.GAME_RESTART
def travel_direction(direction, root_console: tdl.Console): """ self-contained "animation" of moving through a hallway in a certain direction """ start_x = 0 start_y = 0 end_x = 0 end_y = 0 room_draw_width = 9 room_draw_height = 9 top = 5 left = 5 for x in range(0, room_draw_width): for y in range(0, room_draw_height): tile = ' ' if direction == 'north' or direction == 'south': if x == math.floor(room_draw_width / 2): tile = '.' if x == math.floor(room_draw_width / 2) - 1 or x == math.floor( room_draw_width / 2) + 1: tile = '#' elif direction == 'east' or direction == 'west': if y == math.floor(room_draw_height / 2): tile = '.' if y == math.floor( room_draw_height / 2) - 1 or y == math.floor( room_draw_height / 2) + 1: tile = '#' root_console.draw_char(left + x, top + y, tile, bg=None, fg=(255, 255, 255)) if direction == 'north' or direction == 'south': start_x = math.floor(room_draw_width / 2) end_x = start_x elif direction == 'east' or direction == 'west': start_y = math.floor(room_draw_height / 2) end_y = start_y if direction == 'north': start_y = room_draw_height - 1 elif direction == 'south': end_y = room_draw_height - 1 elif direction == 'east': end_x = room_draw_width - 1 elif direction == 'west': start_x = room_draw_width - 1 x = start_x y = start_y while x != end_x or y != end_y: root_console.draw_char(left + x, top + y, '.', bg=None, fg=(255, 255, 255)) if direction == 'north': y -= 1 elif direction == 'south': y += 1 elif direction == 'east': x += 1 elif direction == 'west': x -= 1 root_console.draw_char(left + x, top + y, '@', bg=None, fg=(255, 255, 255)) tdl.flush() time.sleep(.1)
def state(game: GameData, root_console: tdl.Console) -> GameState: player: Character = game.the_player # create panels inventory_panel = tdl.Console(int((root_console.width - 2) * .7), 30) inventory_panel.set_colors(fg=(220, 220, 220), bg=(0, 0, 50)) equipped_panel = tdl.Console(int((root_console.width - 2) * .3), 30) equipped_panel.set_colors(fg=(220, 220, 220), bg=(0, 30, 0)) # height = root height - heading x2 - space between panels - equipment panel height tooltip_panel = tdl.Console(root_console.width - 2, root_console.height - 34) tooltip_panel.set_colors(fg=(255, 255, 255), bg=(10, 10, 10)) tooltip_panel.set_mode('scroll') selected_index = 0 current_top = 0 max_lines = inventory_panel.height while True: # clear console root_console.clear() # titles root_console.draw_str(1, 0, 'INVENTORY', fg=(255, 255, 0), bg=None) root_console.draw_str(int((root_console.width - 2) * .7 + 2), 0, 'EQUIPPED', fg=(255, 255, 0), bg=None) root_console.draw_str(1, 32, 'TOOLTIP', fg=(200, 200, 200), bg=None) # inventory list panel inventory_panel.clear() text_y = 0 text_x = 0 for index, item in enumerate(player.inventory.items[current_top:current_top+max_lines]): text_color = (200, 200, 200) item_text = f'{str(item)}' if index + current_top == selected_index: text_color = (255, 255, 0) if item.is_equipable(): if item == player.weapon or item == player.armor: # unequip item_text = f'{item_text} [U]nequip' else: # equip item_text = f'{item_text} [E]quip' if item.can_activate_outside_encounter(): item_text = f'{item_text} [A]ctivate' if item != player.weapon and item != player.armor: item_text = f'{item_text} [D]rop' inventory_panel.draw_str(text_x, text_y, item_text, fg=text_color) text_y += 1 # draw panel root_console.blit(inventory_panel, 1, 1) # equipped panel equipped_panel.clear() # TODO: handle long lines of text equipped_panel.draw_str(1, 1, 'WEAPON:') equipped_panel.draw_str(2, 2, f'{player.weapon.name if player.weapon else "EMPTY"}') equipped_panel.draw_str(1, 4, 'ARMOR:') equipped_panel.draw_str(2, 5, f'{player.armor.name if player.armor else "EMPTY"}') # combat stats equipped_panel.draw_str(1, 8, 'STATS:') equipped_panel.draw_str(2, 9, f'HIT CHANCE: {player.combined_hit_chance}%') equipped_panel.draw_str(2, 10, f'CRIT CHANCE: {player.combined_crit_chance}%') equipped_panel.draw_str(2, 11, f'DAMAGE: {player.min_damage}-{player.max_damage}') equipped_panel.draw_str(2, 12, f'BLOCK: {player.combined_block}') root_console.blit(equipped_panel, 2 + inventory_panel.width, 1) # draw tooltip tooltip_panel.clear() if selected_index < len(player.inventory.items): selected_item: Item = player.inventory.items[selected_index] if selected_item.description: tooltip_panel.move(0, 0) tooltip_panel.print_str(selected_item.description) # show weapon stats if isinstance(selected_item, Weapon): as_weapon: Weapon = selected_item tooltip_panel.print_str('\n\n') damage = f'+{as_weapon.damage}' hit = f'{"+" if as_weapon.hit_chance_modifier > 0 else ""}{as_weapon.hit_chance_modifier}' crit = f'+{as_weapon.crit_chance_modifier}' tooltip_panel.print_str(f'DAMAGE: {damage} HIT: {hit} CRIT: {crit}') # TODO: show information about prefix and suffix # show armor stats if isinstance(selected_item, Armor): as_armor: Armor = selected_item tooltip_panel.print_str('\n\n') tooltip_panel.print_str(f'BLOCK: +{as_armor.block}') root_console.blit(tooltip_panel, 1, 33) tdl.flush() user_input = wait_for_keys([ 'ESCAPE', 'UP', 'DOWN', 'ENTER', 'i', 'u', 'e', 'a', 'd' ]) if user_input == 'UP': # scroll selection up and scroll list up if needed if current_top <= 0 and selected_index <= 0: # TODO: maybe loop around to bottom of list? continue selected_index -= 1 if selected_index < current_top: current_top = selected_index elif user_input == 'DOWN': # scroll selection down and scroll list down if needed if selected_index >= len(player.inventory.items) - 1: # TODO: maybe loop around to top of list? continue selected_index += 1 if selected_index >= current_top + max_lines: current_top = selected_index elif user_input == 'd' and selected_index < len(player.inventory.items): # drop the item from inventory # TODO: add item to room like treasure? selected_item: Item = player.inventory.items[selected_index] if player.is_item_equipped(selected_item): continue game.log(f'Dropped {selected_item.name} to the floor. Gone Forever.') player.inventory.remove_item(selected_item) if selected_index >= len(player.inventory.items): selected_index = max(0, len(player.inventory.items) - 1) elif user_input == 'a' and selected_index < len(player.inventory.items): item_to_use: Item = player.inventory.items[selected_index] if item_to_use.can_activate_outside_encounter() and item_to_use.can_activate_on_player(): result_text = item_to_use.activate(player) if result_text: player.inventory.remove_item(item_to_use) game.log(result_text, fg=(255, 255, 0)) elif user_input == 'u' and selected_index < len(player.inventory.items): item_to_unequip: Item = player.inventory.items[selected_index] if not player.is_item_equipped(item_to_unequip): continue response = player.unequip_item(item_to_unequip) if response: game.log(response) elif user_input == 'e' and selected_index < len(player.inventory.items): item_to_equip: Item = player.inventory.items[selected_index] if not item_to_equip.is_equipable() or player.is_item_equipped(item_to_equip): continue for_log = player.equip_item(item_to_equip) if for_log: game.log(for_log) elif user_input == 'ESCAPE' or user_input == 'i': return GameState.ROOM return GameState.ROOM
def state(game: GameData, root_console: tdl.Console) -> GameState: root_console.clear() draw_player_status_bar(game.the_player, 1, root_console.height - 2, root_console) # draw the room if game.current_room: # odd number of tiles makes "centering" easier room_draw_width = game.room_draw_width room_draw_height = game.room_draw_height top = 5 left = 5 center_x = int(math.floor(room_draw_width / 2)) center_y = int(math.floor(room_draw_height / 2)) for x in range(0, room_draw_width): for y in range(0, room_draw_height): tile = '.' if x == 0 or y == 0 or x == room_draw_width - 1 or y == room_draw_height - 1: tile = '#' if game.current_room.north and x == math.floor( room_draw_width / 2) and y == 0: tile = '.' if game.current_room.south and x == math.floor( room_draw_width / 2) and y == room_draw_height - 1: tile = '.' if game.current_room.west and x == 0 and y == math.floor( room_draw_height / 2): tile = '.' if game.current_room.east and x == room_draw_width - 1 and y == math.floor( room_draw_height / 2): tile = '.' root_console.draw_char(left + x, top + y, tile, bg=None, fg=(255, 255, 255)) # draw the player in the room p_x = math.floor(room_draw_width / 2) p_y = math.floor(room_draw_height / 2) if game.previous_room: if game.previous_room == game.current_room.north: # came from the north p_y = 2 elif game.previous_room == game.current_room.south: # came from the south p_y = room_draw_height - 3 elif game.previous_room == game.current_room.east: # came from the east p_x = room_draw_width - 3 elif game.previous_room == game.current_room.west: # came from the west p_x = 2 root_console.draw_char(left + p_x, top + p_y, '@', bg=None, fg=(255, 255, 255)) # draw encounter if game.current_room.encounter and not game.current_room.encountered: if game.current_room.encounter == EncounterType.MONSTER: root_console.draw_char(left + center_x, top + center_y, game.current_room.monster.tile, bg=None, fg=(255, 255, 255)) elif game.current_room.encounter == EncounterType.STAIRS: game.log( 'You find stairs to the next level! Press [ENTER] to advance.', (0, 200, 235)) # draw info bar info_con = tdl.Console(int(root_console.width / 2), root_console.height - 21) info_con.set_colors(fg=(220, 220, 220), bg=(0, 0, 50)) info_con.clear() left = 1 top = 1 info_con.draw_str(left, top, 'MOVE:') top += 1 info_con.draw_str(left, top, '[UP] North') top += 1 info_con.draw_str(left, top, '[DOWN] South') top += 1 info_con.draw_str(left, top, '[RIGHT] East') top += 1 info_con.draw_str(left, top, '[LEFT] West') top += 2 if len(game.the_player.get_explore_skills()) > 0: info_con.draw_str(left, top, 'SKILLS:') top += 1 for index, skill in enumerate(game.the_player.get_explore_skills(), 1): skill_text = f'{skill.name}' if not skill.ready(): skill_text = f'{skill_text} ({skill.cooldown_left} turns)' info_con.draw_str(left, top, f'[{index}] {skill_text}') top += 1 top += 1 info_con.draw_str(left, top, 'MENU:') top += 1 info_con.draw_str(left, top, '[m] Map') top += 1 info_con.draw_str(left, top, '[i] Inventory') top += 1 info_con.draw_str(left, top, '[c] Character') top += 1 info_con.draw_str(left, top, '[ESC] Quit') root_console.blit(info_con, int(root_console.width / 2), 0) # draw the log game.draw_log(root_console) tdl.flush() user_input = wait_for_keys([ 'ESCAPE', 'UP', 'DOWN', 'LEFT', 'RIGHT', 'ENTER', 'i', 'm', 'c', '1', '2' ]) if user_input == 'UP': return GameState.MOVE_NORTH elif user_input == 'DOWN': return GameState.MOVE_SOUTH elif user_input == 'LEFT': return GameState.MOVE_WEST elif user_input == 'RIGHT': return GameState.MOVE_EAST elif user_input == 'ESCAPE': return GameState.CLOSE elif user_input == 'm': return GameState.MAP elif user_input == 'i': return GameState.INVENTORY elif user_input == 'c': return GameState.CHARACTER_SHEET elif user_input == 'ENTER': return GameState.NEXT_LEVEL elif user_input == '1' and len(game.the_player.get_explore_skills()) > 0: # User skill 1 skill: ActiveSkill = game.the_player.get_explore_skills()[0] if skill.ready(): if skill.activate(game): if skill.uses_turn: game.advance_turn() elif user_input == '2' and len(game.the_player.get_explore_skills()) > 1: # User skill 2 skill: ActiveSkill = game.the_player.get_explore_skills()[1] if skill.ready(): if skill.activate(game): if skill.uses_turn: game.advance_turn() return GameState.ROOM
def state(game: GameData, root_console: tdl.Console) -> GameState: # minimum points for stats: min_power = 1 min_block = 1 min_health = 6 # values starting_power = min_power starting_block = min_block starting_health = min_health name = '' name_max_length = 12 # points to spend points_left = 8 active = 'name' normal_color = (255, 255, 255) active_color = (255, 255, 0) while True: root_console.clear() # page title root_console.draw_str(1, 0, 'CREATE YOUR CHARACTER') left = 1 top = 2 # Player Name root_console.draw_str( left, top, f'NAME: {name}', fg=active_color if active == 'name' else normal_color) # Power top += 2 power_text = f'POWER: {starting_power}' if starting_power > min_power: power_text = f'{power_text} [LEFT] Decrease' if points_left > 0: power_text = f'{power_text} [RIGHT] Increase' root_console.draw_str( left, top, power_text, fg=active_color if active == 'power' else normal_color) # Block top += 2 block_text = f'BLOCK: {starting_block}' if starting_block > min_block: block_text = f'{block_text} [LEFT] Decrease' if points_left > 0: block_text = f'{block_text} [RIGHT] Increase' root_console.draw_str( left, top, block_text, fg=active_color if active == 'block' else normal_color) # Health top += 2 health_text = f'HEALTH: {starting_health}' if starting_health > min_health: health_text = f'{health_text} [LEFT] Decrease' if points_left > 0: health_text = f'{health_text} [RIGHT] Increase' root_console.draw_str( left, top, health_text, fg=active_color if active == 'health' else normal_color) # show remaining points top += 4 root_console.draw_str(left, top, f'POINTS REMAINING: {points_left}', fg=(255, 0, 0)) # Help top += 4 root_console.draw_str(left, top, '*Use UP/DOWN to change active field.') tdl.flush() if active == 'name': alpha = list('qwertyuiopasdfghjklzxcvbnm') alpha.extend( ['ESCAPE', 'DOWN', 'UP', 'SPACE', 'BACKSPACE', 'ENTER', '1']) user_input = wait_for_keys(alpha) if user_input == 'ESCAPE': # quit the game return GameState.CLOSE elif user_input == 'ENTER': # start game with current stats break elif user_input == '1': # TODO: remove this shortcut name = 'Player' starting_health = 10 starting_power = 4 starting_block = 2 break elif user_input == 'UP': active = 'health' elif user_input == 'DOWN': active = 'power' elif user_input == 'BACKSPACE': if len(name) > 0: name = name[:-1] elif len(name) < name_max_length: if user_input == 'SPACE': name += ' ' else: name += user_input elif active == 'power': user_input = wait_for_keys( ['ESCAPE', 'DOWN', 'UP', 'LEFT', 'RIGHT', 'ENTER']) if user_input == 'ESCAPE': # quit the game return GameState.CLOSE elif user_input == 'ENTER': # start game with current stats break elif user_input == 'UP': active = 'name' elif user_input == 'DOWN': active = 'block' elif user_input == 'LEFT' and starting_power > min_power: starting_power -= 1 points_left += 1 elif user_input == 'RIGHT' and points_left > 0: starting_power += 1 points_left -= 1 elif active == 'block': user_input = wait_for_keys( ['ESCAPE', 'DOWN', 'UP', 'LEFT', 'RIGHT', 'ENTER']) if user_input == 'ESCAPE': # quit the game return GameState.CLOSE elif user_input == 'ENTER': # start game with current stats break elif user_input == 'UP': active = 'power' elif user_input == 'DOWN': active = 'health' elif user_input == 'LEFT' and starting_block > min_block: starting_block -= 1 points_left += 1 elif user_input == 'RIGHT' and points_left > 0: starting_block += 1 points_left -= 1 elif active == 'health': user_input = wait_for_keys( ['ESCAPE', 'DOWN', 'UP', 'LEFT', 'RIGHT', 'ENTER']) if user_input == 'ESCAPE': # quit the game return GameState.CLOSE elif user_input == 'ENTER': # start game with current stats break elif user_input == 'UP': active = 'block' elif user_input == 'DOWN': active = 'name' elif user_input == 'LEFT' and starting_health > min_health: starting_health -= 1 points_left += 1 elif user_input == 'RIGHT' and points_left > 0: starting_health += 1 points_left -= 1 # make the player game.the_player = create_player(name, starting_power, starting_block, starting_health) # start game return GameState.ROOM
def state(game: GameData, root_console: tdl.Console) -> GameState: player: Character = game.the_player while True: root_console.clear() # page title root_console.draw_str(1, 0, 'CHARACTER') left = 1 top = 2 # basic player info root_console.draw_str(left, top, f'NAME: {player.name}') top += 1 root_console.draw_str(left, top, f'HP: {player.health}/{player.max_health}') top += 1 root_console.draw_str( left, top, f'LEVEL {player.level} ({player.experience}/{player.experience_to_next_level}XP)' ) top += 1 root_console.draw_str(left, top, f'FOOD: {player.food}') # base stats top += 3 root_console.draw_str(left, top, f'POWER: {player.power}') top += 1 root_console.draw_str(left, top, f'BLOCK: {player.block}') # combat stats top += 3 root_console.draw_str(left, top, f'HIT CHANCE: {player.combined_hit_chance}%') top += 1 root_console.draw_str(left, top, f'CRIT CHANCE: {player.combined_crit_chance}%') top += 1 root_console.draw_str( left, top, f'DAMAGE: {player.min_damage}-{player.max_damage}') # level-up point application top += 5 root_console.draw_str(left, top, f'UNSPENT POINTS: {player.unspent_points}', fg=(255, 255, 0)) if player.unspent_points > 0: top += 1 root_console.draw_str(left + 2, top, f'[1] INCREASE MAX HEALTH (+1)') top += 1 root_console.draw_str(left + 2, top, f'[2] INCREASE POWER (+1)') top += 1 root_console.draw_str(left + 2, top, f'[3] INCREASE BLOCK (+1)') tdl.flush() user_input = wait_for_keys( ['ESCAPE', 'LEFT', 'RIGHT', 'DOWN', 'UP', 'c', '1', '2', '3']) if user_input == 'ESCAPE' or user_input == 'c': return GameState.ROOM elif player.unspent_points > 0: if user_input == '1': # increase max health player.max_health += 1 player.spend_points(1) elif user_input == '2': # increase power player.power += 1 player.spend_points(1) elif user_input == '3': # increase block player.block += 1 player.spend_points(1) else: continue
def state(game: GameData, root_console: tdl.Console) -> GameState: root_console.clear() tdl.flush() room_size = 5 spacing = 0 the_map = game.current_map total_width = (room_size + (spacing * 2)) * the_map.size[0] total_height = (room_size + (spacing * 2)) * the_map.size[1] map_console = tdl.Console(total_width, total_height) legend_scheme = { EncounterType.MONSTER: (180, 0, 0), EncounterType.SHRINE: (255, 255, 200), EncounterType.TREASURE: (200, 200, 0), EncounterType.TRAP: (149, 108, 53), EncounterType.STAIRS: (0, 200, 235), EncounterType.EMPTY: (255, 255, 255) } # legend x = root_console.width - 20 y = 1 root_console.draw_str(x, y, 'LEGEND', bg=None, fg=(255, 255, 255)) y += 1 root_console.draw_str(x, y, '# Shrine', bg=None, fg=legend_scheme[EncounterType.SHRINE]) y += 1 root_console.draw_str(x, y, '# Treasure', bg=None, fg=legend_scheme[EncounterType.TREASURE]) y += 1 root_console.draw_str(x, y, '# Monster', bg=None, fg=legend_scheme[EncounterType.MONSTER]) y += 1 root_console.draw_str(x, y, '# Trap', bg=None, fg=legend_scheme[EncounterType.TRAP]) y += 1 root_console.draw_str(x, y, '# Stairs', bg=None, fg=legend_scheme[EncounterType.STAIRS]) y += 1 root_console.draw_str(x, y, '# Empty', bg=None, fg=legend_scheme[EncounterType.EMPTY]) y += 1 root_console.draw_str(x, y, '@ Player', bg=None, fg=(255, 255, 255)) # bottom bar root_console.draw_rect(0, root_console.height - 3, root_console.width, 3, None, bg=(200, 200, 200)) root_console.draw_str(1, root_console.height - 2, '[ESC] back', fg=(0, 0, 0), bg=None) root_console.draw_str(15, root_console.height - 2, '[Arrow Keys] scroll map', fg=(0, 0, 0), bg=None) root_console.draw_str(50, root_console.height - 2, '[Enter] center map', fg=(0, 0, 0), bg=None) map_center_x = game.current_room.x * (room_size + spacing) + spacing map_center_y = game.current_room.y * (room_size + spacing) + spacing # draw the rooms while True: map_console.clear() for room in [room for room in the_map.rooms if room.discovered]: x = room.x * (room_size + spacing) + spacing y = room.y * (room_size + spacing) + spacing for xx in range(x, x + room_size): for yy in range(y, y + room_size): t = '.' if xx == x or xx == x + room_size - 1 or yy == y or yy == y + room_size - 1: t = '#' if room.north and yy == y and xx == x + 2: t = '.' if room.south and yy == y + room_size - 1 and xx == x + 2: t = '.' if room.east and yy == y + 2 and xx == x + room_size - 1: t = '.' if room.west and yy == y + 2 and xx == x: t = '.' color = (255, 255, 255) if room.encounter and room.encounter in legend_scheme: color = legend_scheme[room.encounter] map_console.draw_char(xx, yy, t, bg=None, fg=color) if room == game.current_room: map_console.draw_char(x + int(room_size / 2), y + int(room_size / 2), game.the_player.tile, fg=(255, 255, 255)) src_x = max(map_center_x - 15, 0) src_y = max(map_center_y - 15, 0) root_console.draw_rect(0, 0, 30, 30, ' ', bg=(50, 50, 50)) root_console.blit(map_console, 0, 0, 30, 30, src_x, src_y, 1.0, 0.0) tdl.flush() response = wait_for_keys([ 'ESCAPE', 'SPACE', 'ENTER', 'm', 'UP', 'DOWN', 'LEFT', 'RIGHT', '0' ]) if response in ['ESCAPE', 'SPACE', 'm']: break # scroll the map if response == 'UP': if map_center_y > 0: map_center_y -= 1 elif response == 'DOWN': if map_center_y < total_height - 1: map_center_y += 1 elif response == 'LEFT': if map_center_x > 0: map_center_x -= 1 elif response == 'RIGHT': if map_center_x < total_width - 1: map_center_x += 1 elif response == 'ENTER': # recenter the map map_center_x = game.current_room.x * (room_size + spacing) + spacing map_center_y = game.current_room.y * (room_size + spacing) + spacing elif response == '0': # TODO: remove this cheat # reveal entire map for room in the_map.rooms: room.discovered = True return GameState.ROOM
def state_usable_inventory(game: GameData, root_console: tdl.Console): # Used for player-inventory interaction during the combat the_player = game.the_player # get user's inventory that is allowed to be used in combat available_items = [item for item in the_player.inventory.items if item.can_activate_in_combat()] # set up console inventory_console = tdl.Console(int(root_console.width / 2), root_console.height - 23) inventory_console.set_colors(fg=(200, 200, 200), bg=(0, 0, 90)) selected_index = 0 current_top = 0 while True: inventory_console.clear() inventory_console.draw_str(1, 0, 'COMBAT ITEMS:') text_y = 2 text_x = 1 counter = 1 max_lines = 14 if len(available_items) > 0: for index, item in enumerate(available_items[current_top:current_top+max_lines]): text_color = (200, 200, 200) if index + current_top == selected_index: text_color = (255, 255, 0) inventory_console.draw_str(text_x, text_y, f'{str(item)}', fg=text_color) text_y += 1 counter += 1 else: inventory_console.draw_str(text_x, text_y, 'YOU HAVE NO ITEMS THAT CAN BE USED') # blit to root and flush root_console.blit(inventory_console, int(root_console.width / 2), 0) tdl.flush() user_input = wait_for_keys(['ENTER', 'ESCAPE', 'SPACE', 'UP', 'DOWN']) if user_input in ('ENTER', 'SPACE', ): if len(available_items) == 0: continue item_to_use: Item = available_items[selected_index] result_text = None if item_to_use.can_activate_on_player(): result_text = item_to_use.activate(the_player) elif item_to_use.can_activate_on_monster(): result_text = item_to_use.activate(game.current_room.monster) if result_text: the_player.inventory.remove_item(item_to_use) game.log(result_text, fg=(255, 255, 0)) break elif user_input == 'ESCAPE': # nevermind return False elif user_input == 'UP': # scroll selection up and scroll list up if needed if current_top <= 0 and selected_index <= 0: # TODO: maybe loop around to bottom of list? continue selected_index -= 1 if selected_index < current_top: current_top = selected_index elif user_input == 'DOWN': # scroll selection down and scroll list down if needed if selected_index >= len(available_items) - 1: # TODO: maybe loop around to top of list? continue selected_index += 1 if selected_index >= current_top + max_lines: current_top = selected_index return True
def state(game: GameData, root_console: tdl.Console) -> GameState: if game.current_room.monster is None: monster = game.current_room.monster = create_monster_for_level(game.current_level) else: monster = game.current_room.monster player: Character = game.the_player player_x = int(root_console.width / 2) monster_x = player_x top = 5 left = 5 room_draw_width = game.room_draw_width room_draw_height = game.room_draw_height center_x = int(math.floor(room_draw_width / 2)) center_y = int(math.floor(room_draw_height / 2)) # if you flee a monster and come back, it will return to full health monster.reset() game.log(f'You encounter a {monster.name}!', (255, 0, 0)) fighting: bool = True did_flee: bool = False player_used_turn: bool = False player_combat_skills = player.get_combat_skills() while fighting: if player_used_turn: # tick status effects player.tick_status_effects() monster.tick_status_effects() # tick skills player.tick_skill_cooldowns() monster.tick_skill_cooldowns() # clear console root_console.clear() # draw room root_console.draw_rect(left, top, room_draw_width, room_draw_height, '.', (255, 255, 255), (0, 0, 0)) root_console.draw_frame(left, top, room_draw_width, room_draw_height, '#', (255, 255, 255), (0, 0, 0)) if game.current_room.north: root_console.draw_char(left + center_x, top, '.', (255, 255, 255), (0, 0, 0)) if game.current_room.south: root_console.draw_char(left + center_x, top + room_draw_height - 1, '.', (255, 255, 255), (0, 0, 0)) if game.current_room.west: root_console.draw_char(left, top + center_y, '.', (255, 255, 255), (0, 0, 0)) if game.current_room.east: root_console.draw_char(left + room_draw_width - 1, top + center_y, '.', (255, 255, 255), (0, 0, 0)) # draw player p_x = center_x p_y = center_y if game.previous_room: if game.previous_room == game.current_room.north: # came from the north p_y = 2 elif game.previous_room == game.current_room.south: # came from the south p_y = room_draw_height - 3 elif game.previous_room == game.current_room.east: # came from the east p_x = room_draw_width - 3 elif game.previous_room == game.current_room.west: # came from the west p_x = 2 root_console.draw_char(left + p_x, top + p_y, player.tile, bg=None, fg=(255, 255, 255)) # draw monster root_console.draw_char(left + center_x, top + center_y, monster.tile, bg=None, fg=(255, 0, 0)) # draw the log game.draw_log(root_console) # Draw Monster Information monster_y = 0 root_console.draw_str(monster_x, monster_y, f'{monster.name}') monster_y += 1 root_console.draw_str(monster_x, monster_y, f'HP: {monster.health} / {monster.max_health}') monster_y += 1 for status in monster.active_status_effects: # list each status effect root_console.draw_str(monster_x, monster_y, f'({status.status_effect.name} x{status.duration})', fg=(255, 255, 0)) monster_y += 1 # Draw Player Information player_y = 10 root_console.draw_str(player_x, player_y, f'{player.name}') player_y += 1 root_console.draw_str(player_x, player_y, f'HP: {player.health} / {player.max_health}') player_y += 1 for status in player.active_status_effects: # list each status effect root_console.draw_str(player_x, player_y, f'({status.status_effect.name} x{status.duration})', fg=(255, 0, 0)) player_y += 1 player_y += 2 root_console.draw_str(player_x, player_y, '[A] Attack') player_y += 1 if len(player_combat_skills) > 0: for index, skill in enumerate(player_combat_skills, 1): skill_text = f'{skill.name}' if not skill.ready(): skill_text = f'{skill_text} ({skill.cooldown_left} turns)' root_console.draw_str(player_x, player_y, f'[{index}] {skill_text}') player_y += 1 root_console.draw_str(player_x, player_y + 5, '[U] Use Item') root_console.draw_str(player_x, player_y + 6, '[F] Flee') tdl.flush() # only allow available keys available_keys = ['ESCAPE', 'a', 'f', 'u'] if len(player_combat_skills) >= 1 and player_combat_skills[0].ready(): available_keys.append('1') if len(player_combat_skills) >= 2 and player_combat_skills[1].ready(): available_keys.append('2') user_input = wait_for_keys(available_keys) if user_input == 'ESCAPE': return GameState.CLOSE # will be set to True if the player attempts to flee below tried_to_flee: bool = False player_used_turn = False # ATTACK if user_input == 'a': game.log(f'You attack the {monster.name}...') player_used_turn = True results = do_attack(player, monster) if not results.dodged: game.log(f'...and hit for {results.damage} damage.') # if the player's weapon has the "Vampiric" trait, heal the player (maybe) if player.weapon and player.weapon.suffix == WeaponSuffix.Vampirism: did_vamp = random.randint(1, 101) <= player.weapon.life_steal_chance vamp_amount = int(math.ceil(player.weapon.life_steal_percent * results.damage)) # TODO: decide whether or not to tell the player... player.heal(vamp_amount) # if the player's weapon has the "Doom" trait, kill the monster if it only has 1 HP left elif player.weapon and player.weapon.suffix == WeaponSuffix.Doom and monster.health == 1: monster.health = 0 game.log(f'DOOM has befallen the {monster.name}!') else: # enemy dodged the attack game.log(f'...and miss as the {monster.name} dodges out of the way.') # USE SKILL 1 elif user_input == '1' and len(player_combat_skills) > 0: skill: ActiveSkill = player_combat_skills[0] if skill.ready(): if skill.activate(game): player_used_turn = skill.uses_turn # USE SKILL 2 elif user_input == '2' and len(player_combat_skills) > 1: skill: ActiveSkill = player_combat_skills[1] if skill.ready(): if skill.activate(game): player_used_turn = skill.uses_turn # ATTEMPT TO FLEE THE MONSTER elif user_input == 'f': player_used_turn = True game.log('You attempt to flee.') tried_to_flee = True chance_to_flee = 80 roll = random.randrange(0, 100) if roll <= chance_to_flee: did_flee = True # OPEN INVENTORY elif user_input == 'u': # only uses a turn if the player uses an item player_used_turn = state_usable_inventory(game, root_console) # if the monster is dead, combat is over if monster.health <= 0: fighting = False game.log(f'You slay the {monster.name}!') continue # if monster is alive, it takes a turn (assuming the player did a "used_turn" action) # monster gets a turn even if the player succeeded at fleeing # make sure the monster is ABLE to attack if player_used_turn: if not monster.has_status_effect(StatusEffectType.Stunned): game.log(f'The {monster.name} attacks...', (255, 0, 0)) if tried_to_flee and player.armor and player.armor.suffix == ArmorSuffix.Fleeing: game.log(f'...and misses.') else: results = do_attack(monster, player) if not results.dodged: game.log(f'...and hits for {results.damage} damage') else: game.log(f'...and misses you when you dodge out of the way.') if player.health <= 0: fighting = False # cannot flee if you are dead, Jim did_flee = False continue elif tried_to_flee and not did_flee: game.log("You failed to run away", (255, 0, 0)) elif tried_to_flee and did_flee: fighting = False continue else: game.log(f'The {monster.name} is STUNNED and cannot attack.') if did_flee: # fleeing does not mark the encounter as complete text = f'You run away from the {monster.name}, returning to the previous room.' game.log(text) game.current_room = game.previous_room game.previous_room = None return GameState.ROOM game.current_room.encountered = True # killed the monster! yay! if monster.health <= 0: experience_gained = monster.experience if player.weapon and player.weapon.suffix == WeaponSuffix.Adventuring: # 15% boost to XP gain experience_gained = int(math.ceil(experience_gained * 1.15)) text = f'You have killed the {monster.name}! You have been awarded {experience_gained} XP.' game.log(text) if player.grant_experience(experience_gained): # player leveled up! game.log(f'LEVEL UP! You have reached level {player.level}!', (255, 255, 0)) return GameState.ROOM # player is dead return GameState.GAME_OVER
def draw_player_status_bar(the_player, x: int, y: int, root_console: tdl.Console): """ Helper function that draws the player's status bar at the bottom of the screen. """ root_console.draw_rect(0, root_console.height - 3, root_console.width, 3, None, bg=(200, 200, 200)) name = f'@{the_player.name}' food = f'FOOD: {the_player.food}' health = f'{the_player.health}/{the_player.max_health}HP' level = f'LEVEL {the_player.level}' xp = f'{the_player.experience}/{the_player.experience_to_next_level}XP' root_console.draw_str(x, y, name, fg=(0, 0, 0), bg=None) x += len(name) + 4 root_console.draw_str(x, y, health, fg=(0, 0, 0), bg=None) x += len(health) + 4 root_console.draw_str(x, y, food, fg=(0, 0, 0), bg=None) x += len(food) + 4 root_console.draw_str(x, y, level, fg=(0, 0, 0), bg=None) x += len(level) + 4 root_console.draw_str(x, y, xp, fg=(0, 0, 0), bg=None)