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)
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(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