コード例 #1
0
    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
コード例 #2
0
    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
コード例 #3
0
    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
コード例 #4
0
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
コード例 #5
0
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
コード例 #6
0
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
コード例 #7
0
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
コード例 #8
0
    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
コード例 #9
0
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
コード例 #10
0
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
コード例 #11
0
    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
コード例 #12
0
    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
コード例 #13
0
    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)
コード例 #14
0
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