def add_item(self, item): """Puts an item in the inventory. Args: item(Item): The item to add. Returns: results(list): A list containing a dictionary with the results of adding an item. """ results = [] if len(self.items) >= self.capacity: results.append({ 'item_added': None, 'message': Message('You cannot carry any more, your inventory is full.', libtcod.yellow) }) else: results.append({ 'item_added': item, 'message': Message('You pick up the {0}!'.format(item.name), libtcod.light_blue) }) self.items.append(item) return results
def next_floor(self, player, message_log, constants): """Moves the player down one floor of the dungeon. Increases dungeon level by one, clears the entities list except for the player, and creates a new map. Also heals the player for half their max HP. Args: player(Entity): Entity object representing the player. message_log(MessageLog): MessageLog object containing game messages. constants(dict): Dictionary containing game's constant variables. Returns: entities(list): New entities list containing only the player. """ self.dungeon_level += 1 entities = [player] self.tiles = self.initialize_tiles() self.make_map(constants['max_rooms'], constants['room_min_size'], constants['room_max_size'], constants['map_width'], constants['map_height'], player, entities) player.fighter.heal( player.fighter.max_hp // 2) # '//' is integer division (e.g. 5 // 2 = 2, 5 / 2 = 2.5) message_log.add_message( Message('You take a moment to rest and recover your strength.', libtcod.light_violet)) return entities
def drop_item(self, item): """Drops an item in the inventory onto the ground. Args: item(Item): Item to drop. Returns: results(list): A list containing a dictionary with the results of dropping an item on the ground. """ results = [] if self.owner.equipment.main_hand == item or self.owner.equipment.off_hand == item: self.owner.equipment.toggle_equip(item) item.x = self.owner.x item.y = self.owner.y self.remove_item(item) results.append({ 'item_dropped': item, 'message': Message('You dropped the {0}.'.format(item.name), libtcod.yellow) }) return results
def kill_monster(monster): """Kills a monster. First creates a string death message based on the name of the monster, then changes the monster's ASCII character and color, stops it from blocking movement, removes the AI and Fighter components, changes the name to 'remains of (monster)', and changes RenderOrder to CORPSE. Args: monster(Entity): The monster Entity object to kill. Returns: death_message(Message): A formatted string death message. """ death_message = Message('{0} is dead!'.format(monster.name.capitalize()), libtcod.orange) monster.char = '%' monster.color = libtcod.dark_red monster.blocks = False monster.fighter = None monster.ai = None monster.name = monster.name + ' remains' monster.render_order = RenderOrder.CORPSE return death_message
def cast_confuse(*args, **kwargs): """Casts the Confuse spell at a chosen target. Args: *args: **kwargs: Gets Entity list, FOV map, x coordinate, and y coordinate. Returns: results(list): List with result dictionary. """ entities = kwargs.get('entities') fov_map = kwargs.get('fov_map') target_x = kwargs.get('target_x') target_y = kwargs.get('target_y') results = [] if not libtcod.map_is_in_fov(fov_map, target_x, target_y): results.append({'consumed': False, 'message': Message('You cannot target a tile outside your field of view.', libtcod.yellow)}) return results for entity in entities: if entity.x == target_x and entity.y == target_y and entity.ai: confused_ai = ConfusedMonster(entity.ai, 10) confused_ai.owner = entity entity.ai = confused_ai results.append({'consumed': True, 'message': Message( 'The eyes of the {0} look vacant and it starts to stumble around!'.format(entity.name), libtcod.light_green)}) break else: results.append( {'consumed': False, 'message': Message('There is no targetable enemy at that location.', libtcod.yellow)}) return results
def cast_lightning(*args, **kwargs): """Casts lightning at the closest enemy. Args: *args: Entity object that is casting the spell. *kwargs: Gets list of entities, Map for fov calculations, int damage amount, and max range of spell. Returns: results(list): List with result dictionary. """ caster = args[0] entities = kwargs.get('entities') fov_map = kwargs.get('fov_map') damage = kwargs.get('damage') maximum_range = kwargs.get('maximum_range') results = [] target = None closest_distance = maximum_range + 1 for entity in entities: if entity.fighter and entity != caster and libtcod.map_is_in_fov(fov_map, entity.x, entity.y): distance = caster.distance_to(entity) if distance < closest_distance: target = entity closest_distance = distance if target: results.append({'consumed': True, 'target': target, 'message': Message( 'A lightning bolt strikes the {0} with a loud CRACK! The damage is {1}.'.format(target.name, damage))}) results.extend(target.fighter.take_damage(damage)) else: results.append( {'consumed': False, 'target': None, 'message': Message('No enemy is close enough to strike.', libtcod.red)}) return results
def cast_fireball(*args, **kwargs): """Casts a fireball at a chosen target. Args: *args: **kwargs: Gets list of entities, FOV map, int damage, int radius, int x coordinate, and int y coordinate. Returns: results(list): List with result dictionary. """ entities = kwargs.get('entities') fov_map = kwargs.get('fov_map') damage = kwargs.get('damage') radius = kwargs.get('radius') target_x = kwargs.get('target_x') target_y = kwargs.get('target_y') results = [] if not libtcod.map_is_in_fov(fov_map, target_x, target_y): results.append({'consumed': False, 'message': Message('You cannot target a tile outside your field of view.', libtcod.yellow)}) return results results.append({'consumed': True, 'message': Message('The fireball explodes, burning everything within {0} tiles!'.format(radius), libtcod.orange)}) for entity in entities: if entity.distance(target_x, target_y) <= radius and entity.fighter: results.append( {'message': Message('The {0} takes {1} burn damage!'.format(entity.name, damage), libtcod.orange)}) results.extend(entity.fighter.take_damage(damage)) return results
def attack(self, target): """Calculates the damage to inflict upon an enemy. First calculates damage by subtracting target's defense from this Entity's power. Then adds a message and damage to a list called results, and returns the list. Args: target(Entity): The character being attacked. Returns: results(list): A list with a message for the in-game log. """ results = [] damage = self.power - target.fighter.defense if damage > 0: results.append({ 'message': Message( '{0} attacks {1} for {2} hit points.'.format( self.owner.name.capitalize(), target.name, str(damage)), libtcod.white) }) results.extend(target.fighter.take_damage(damage)) else: results.append({ 'message': Message( '{0} attacks {1} but does no damage.'.format( self.owner.name.capitalize(), target.name), libtcod.white) }) return results
def heal(*args, **kwargs): """Heals entity by x amount. Args: *args: Entity object that's using the item. **kwargs: Int amount extracted from kwargs. Returns: results(list): List with result dictionary. """ entity = args[0] amount = kwargs.get('amount') results = [] if entity.fighter.hp == entity.fighter.max_hp: results.append({'consumed': False, 'message': Message('You are already at full health.', libtcod.yellow)}) else: entity.fighter.heal(amount) results.append({'consumed': True, 'message': Message('Your wounds start to feel better!', libtcod.green)}) return results
def kill_player(player): """Kills the player. Changes the player's ASCII character and color, and returns a string message and a Enum GameState. Args: player(Entity): The player Entity object to kill. Returns: (Message): A string death message. PLAYER_DEAD(int): An Enum GameState. """ player.char = '%' player.color = libtcod.dark_red return Message('You died!', libtcod.red), GameStates.PLAYER_DEAD
def use(self, item_entity, **kwargs): """Uses an item. Args: item_entity(Entity): The item to be used. **kwargs: Any arguments needed for the item to be used. Returns: results(list): A list containing a dictionary with the results of using the item. """ results = [] item_component = item_entity.item if item_component.use_function is None: equippable_component = item_entity.equippable if equippable_component: results.append({'equip': item_entity}) else: results.append({ 'message': Message('The {0} cannot be used.'.format(item_entity.name), libtcod.yellow) }) else: if item_component.targeting and not (kwargs.get('target_x') or kwargs.get('target_y')): results.append({'targeting': item_entity}) else: kwargs = {**item_component.function_kwargs, **kwargs} item_use_results = item_component.use_function( self.owner, **kwargs) for item_use_result in item_use_results: if item_use_result.get('consumed'): self.remove_item(item_entity) results.extend(item_use_results) return results
def take_turn(self, target, fov_map, game_map, entities): """Runs one turn of Confused AI. Makes the monster Confused for a set number of turns. It wanders around in a random direction or stays put each turn. Args: target(Entity): Not Used for this AI. fov_map(Map): Not Used for this AI. game_map(Map): TCOD map used as the game map. entities(list): List of entities present on the map. Returns: results(list): A list containing the results of the turn. """ results = [] if self.number_of_turns > 0: random_x = self.owner.x + randint(0, 2) - 1 random_y = self.owner.y + randint(0, 2) - 1 if random_x != self.owner.x and random_y != self.owner.y: self.owner.move_towards(random_x, random_y, game_map, entities) self.number_of_turns -= 1 else: self.owner.ai = self.previous_ai results.append({ 'message': Message( 'The {0} is no longer confused!'.format(self.owner.name), libtcod.red) }) return results
def place_entities(self, room, entities): """Gets a random number of monsters and items, and spawns them in the room. First decides how many monsters and items can be in a room at once based on dungeon level. Then chooses a random number of items and monsters to spawn in this particular room. Iterates through the number of monsters and randomly spawns monsters based on their chances to appear. Does the same thing for items. Appends each spawned item to the entities list. Args: room(Rect): A Rect object that represents the room. entities(list): A list of Entity objects. """ max_monsters_per_room = from_dungeon_level([[2, 1], [3, 4], [5, 6]], self.dungeon_level) max_items_per_room = from_dungeon_level([[1, 1], [2, 4]], self.dungeon_level) number_of_monsters = randint(0, max_monsters_per_room) number_of_items = randint(0, max_items_per_room) monster_chances = { 'orc': 80, 'troll': from_dungeon_level([[15, 3], [30, 5], [60, 7]], self.dungeon_level) } item_chances = { 'healing_potion': 70, 'sword': from_dungeon_level([[5, 4]], self.dungeon_level), 'shield': from_dungeon_level([[15, 8]], self.dungeon_level), 'lightning_scroll': from_dungeon_level([[25, 4]], self.dungeon_level), 'fireball_scroll': from_dungeon_level([[25, 6]], self.dungeon_level), 'confusion_scroll': from_dungeon_level([[10, 2]], self.dungeon_level) } for i in range(number_of_monsters): # Choose a random location in the room x = randint(room.x1 + 1, room.x2 - 1) y = randint(room.y1 + 1, room.y2 - 1) # If nothing's there, create a monster. if not any([ entity for entity in entities if entity.x == x and entity.y == y ]): monster_choice = random_choice_from_dict(monster_chances) if monster_choice == 'orc': fighter_component = Fighter(hp=20, defense=0, power=4, xp=35) ai_component = BasicMonster() monster = Entity(x, y, 'o', libtcod.desaturated_green, 'Orc', blocks=True, render_order=RenderOrder.ACTOR, fighter=fighter_component, ai=ai_component) elif monster_choice == 'troll': fighter_component = Fighter(hp=30, defense=2, power=8, xp=100) ai_component = BasicMonster() monster = Entity(x, y, 'T', libtcod.darker_green, 'Troll', blocks=True, render_order=RenderOrder.ACTOR, fighter=fighter_component, ai=ai_component) entities.append(monster) for i in range(number_of_items): # Choose a random location in the room x = randint(room.x1 + 1, room.x2 - 1) y = randint(room.y1 + 1, room.y2 - 1) if not any([ entity for entity in entities if entity.x == x and entity.y == y ]): item_choice = random_choice_from_dict(item_chances) if item_choice == 'healing_potion': item_component = Item(use_function=heal, amount=40) item = Entity(x, y, '!', libtcod.violet, 'Healing Potion', render_order=RenderOrder.ITEM, item=item_component) elif item_choice == 'sword': equippable_component = Equippable(EquipmentSlots.MAIN_HAND, power_bonus=3) item = Entity(x, y, '/', libtcod.sky, 'Sword', equippable=equippable_component) elif item_choice == 'shield': equippable_component = Equippable(EquipmentSlots.OFF_HAND, defense_bonus=1) item = Entity(x, y, '[', libtcod.darker_orange, 'Shield', equippable=equippable_component) elif item_choice == 'fireball_scroll': item_component = Item( use_function=cast_fireball, targeting=True, targeting_message=Message( 'Left-click a target tile for the fireball, or right-click to cancel.', libtcod.light_cyan), damage=25, radius=3) item = Entity(x, y, '#', libtcod.red, 'Fireball Scroll', render_order=RenderOrder.ITEM, item=item_component) elif item_choice == 'confusion_scroll': item_component = Item( use_function=cast_confuse, targeting=True, targeting_message=Message( 'Left-click an enemy to confuse it, or right-click to cancel.', libtcod.light_cyan)) item = Entity(x, y, '#', libtcod.light_pink, 'Confusion Scroll', render_order=RenderOrder.ITEM, item=item_component) elif item_choice == 'lightning_scroll': item_component = Item(use_function=cast_lightning, damage=40, maximum_range=5) item = Entity(x, y, '#', libtcod.yellow, 'Lightning Scroll', render_order=RenderOrder.ITEM, item=item_component) entities.append(item)
def play_game(player, entities, game_map, message_log, game_state, con, panel, constants): """Main game loop. Args: player(Entity): Player Entity object. entities(list): List of entities on map. game_map(Map): TCOD map object used as game map. message_log(MessageLog): MessageLog object list of messages. game_state(Enum): Enum GameState (e.g. PLAYERS_TURN) con(Console): Console used to display whole game. panel(Console): Console used to display messages and UI info. constants(dict): Dictionary of constant game variables. """ key = libtcod.Key() mouse = libtcod.Mouse() previous_game_state = game_state fov_recompute = True fov_map = initialize_fov(game_map) targeting_item = None while not libtcod.console_is_window_closed(): libtcod.sys_check_for_event( libtcod.EVENT_KEY_PRESS | libtcod.EVENT_MOUSE, key, mouse) if fov_recompute: recompute_fov(fov_map, player.x, player.y, constants['fov_radius'], constants['fov_light_walls'], constants['fov_algorithm']) render_all(con, panel, entities, player, game_map, fov_map, fov_recompute, message_log, constants['screen_width'], constants['screen_height'], constants['bar_width'], constants['panel_height'], constants['panel_y'], mouse, constants['colors'], game_state) fov_recompute = False # Refresh scene (?) libtcod.console_flush() clear_all(con, entities) # Get any keys that have been pressed and execute their associated action(s). action = handle_keys(key, game_state) mouse_action = handle_mouse(mouse) move = action.get('move') wait = action.get('wait') pickup = action.get('pickup') show_inventory = action.get('show_inventory') drop_inventory = action.get('drop_inventory') inventory_index = action.get('inventory_index') take_stairs = action.get('take_stairs') level_up = action.get('level_up') show_character_screen = action.get('show_character_screen') exit = action.get('exit') fullscreen = action.get('fullscreen') left_click = mouse_action.get('left_click') right_click = mouse_action.get('right_click') player_turn_results = [] if move and game_state == GameStates.PLAYERS_TURN: dx, dy = move destination_x = player.x + dx destination_y = player.y + dy if not game_map.is_blocked(destination_x, destination_y): target = get_blocking_entities_at_location( entities, destination_x, destination_y) if target: attack_results = player.fighter.attack(target) player_turn_results.extend(attack_results) else: player.move(dx, dy) fov_recompute = True game_state = GameStates.ENEMY_TURN elif wait: game_state = GameStates.ENEMY_TURN if player.fighter.hp < player.fighter.max_hp: player.fighter.hp += 1 elif pickup and game_state == GameStates.PLAYERS_TURN: for entity in entities: if entity.item and entity.x == player.x and entity.y == player.y: pickup_results = player.inventory.add_item(entity) player_turn_results.extend(pickup_results) break else: message_log.add_message( Message('There is nothing here to pick up.', libtcod.yellow)) if show_inventory: previous_game_state = game_state game_state = GameStates.SHOW_INVENTORY if drop_inventory: previous_game_state = game_state game_state = GameStates.DROP_INVENTORY if inventory_index is not None and previous_game_state != GameStates.PLAYER_DEAD and inventory_index < len( player.inventory.items): item = player.inventory.items[inventory_index] if game_state == GameStates.SHOW_INVENTORY: player_turn_results.extend( player.inventory.use(item, entities=entities, fov_map=fov_map)) elif game_state == GameStates.DROP_INVENTORY: player_turn_results.extend(player.inventory.drop_item(item)) if take_stairs and game_state == GameStates.PLAYERS_TURN: for entity in entities: if entity.stairs and entity.x == player.x and entity.y == player.y: entities = game_map.next_floor(player, message_log, constants) fov_map = initialize_fov(game_map) fov_recompute = True con.clear() break else: message_log.add_message( Message('There are no stairs here.', libtcod.yellow)) if level_up: if level_up == 'hp': player.fighter.base_max_hp += 20 player.fighter.hp += 20 elif level_up == 'str': player.fighter.base_power += 1 elif level_up == 'def': player.fighter.base_defense += 1 game_state = previous_game_state if show_character_screen: previous_game_state = game_state game_state = GameStates.CHARACTER_SCREEN if game_state == GameStates.TARGETING: if left_click: target_x, target_y = left_click item_use_results = player.inventory.use(targeting_item, entities=entities, fov_map=fov_map, target_x=target_x, target_y=target_y) player_turn_results.extend(item_use_results) elif right_click: player_turn_results.append({'targeting_cancelled': True}) if exit: if game_state in (GameStates.SHOW_INVENTORY, GameStates.DROP_INVENTORY, GameStates.CHARACTER_SCREEN): game_state = previous_game_state elif game_state == GameStates.TARGETING: player_turn_results.append({'targeting_cancelled': True}) else: save_game(player, entities, game_map, message_log, game_state) return True if fullscreen: # Toggle fullscreen by making the set_fullscreen variable equal to the opposite of itself. libtcod.console_set_fullscreen(not libtcod.console_is_fullscreen()) # Check what happened on the player's turn and react accordingly. for player_turn_results in player_turn_results: message = player_turn_results.get('message') dead_entity = player_turn_results.get('dead') item_added = player_turn_results.get('item_added') item_consumed = player_turn_results.get('consumed') item_dropped = player_turn_results.get('item_dropped') equip = player_turn_results.get('equip') targeting = player_turn_results.get('targeting') targeting_cancelled = player_turn_results.get( 'targeting_cancelled') xp = player_turn_results.get('xp') if message: message_log.add_message(message) if dead_entity: if dead_entity == player: message, game_state = kill_player(dead_entity) else: message = kill_monster(dead_entity) message_log.add_message(message) if item_added: entities.remove(item_added) game_state: GameStates.ENEMY_TURN if item_consumed: game_state: GameStates.ENEMY_TURN if targeting: previous_game_state = GameStates.PLAYERS_TURN game_state = GameStates.TARGETING targeting_item = targeting message_log.add_message(targeting_item.item.targeting_message) if targeting_cancelled: game_state = previous_game_state message_log.add_message(Message('Targeting cancelled.')) if xp: leveled_up = player.level.add_xp(xp) message_log.add_message(Message('You gain {0} XP.'.format(xp))) if leveled_up: message_log.add_message( Message( 'Your fighting skills improve! You reached level {0}' .format(player.level.current_level) + '!', libtcod.yellow)) previous_game_state = game_state game_state = GameStates.LEVEL_UP if item_dropped: entities.append(item_dropped) game_state: GameStates.ENEMY_TURN if equip: equip_results = player.equipment.toggle_equip(equip) for equip_results in equip_results: equipped = equip_results.get('equipped') dequipped = equip_results.get('unequipped') if equipped: message_log.add_message( Message('You equip the {0}'.format(equipped.name))) if dequipped: message_log.add_message( Message('You unequip the {0}'.format( dequipped.name))) game_state = GameStates.ENEMY_TURN if game_state == GameStates.ENEMY_TURN: for entity in entities: # Checking for AI skips the player and any items/other stuff we implement later. if entity.ai: enemy_turn_results = entity.ai.take_turn( player, fov_map, game_map, entities) for enemy_turn_results in enemy_turn_results: message = enemy_turn_results.get('message') dead_entity = enemy_turn_results.get('dead') if message: message_log.add_message(message) if dead_entity: if dead_entity == player: message, game_state = kill_player(dead_entity) else: message = kill_monster(dead_entity) message_log.add_message(message) if game_state == GameStates.PLAYER_DEAD: break if game_state == GameStates.PLAYER_DEAD: break else: game_state = GameStates.PLAYERS_TURN