Пример #1
0
class ConsumableInventory:
    def __init__(self, slots: Dict[int, List[ConsumableType]]):
        self.consumables_in_slots: Dict[int, List[ConsumableType]] = slots
        self.capacity_per_slot = 2
        self.was_updated = Observable()

    def set_slots(self, slots: Dict[int, List[ConsumableType]]):
        self.consumables_in_slots = slots
        self.notify_observers()

    def has_space_for_more(self) -> bool:
        slots_with_space = [consumables for consumables in self.consumables_in_slots.values() if
                            len(consumables) < self.capacity_per_slot]
        return len(slots_with_space) > 0

    def add_consumable(self, consumable_type: ConsumableType):
        non_full_slots_with_same_type = [consumables for consumables in self.consumables_in_slots.values()
                                         if 0 < len(consumables) < self.capacity_per_slot
                                         and consumables[0] == consumable_type]
        if non_full_slots_with_same_type:
            non_full_slots_with_same_type[0].append(consumable_type)
            self.notify_observers()
            return
        empty_slots = [consumables for consumables in self.consumables_in_slots.values() if len(consumables) == 0]
        if empty_slots:
            empty_slots[0].append(consumable_type)
            self.notify_observers()
            return
        for consumables in self.consumables_in_slots.values():
            if len(consumables) < self.capacity_per_slot:
                consumables.append(consumable_type)
                self.notify_observers()
                return
        raise Exception("No space for consumable!")

    def drag_consumable_between_inventory_slots(self, from_slot: int, to_slot: int):
        consumable_type_1 = self.remove_consumable_from_slot(from_slot)
        if len(self.consumables_in_slots[to_slot]) < self.capacity_per_slot:
            self.consumables_in_slots[to_slot].insert(0, consumable_type_1)
        else:
            consumable_type_2 = self.remove_consumable_from_slot(to_slot)
            self.consumables_in_slots[to_slot].insert(0, consumable_type_1)
            self.consumables_in_slots[from_slot].insert(0, consumable_type_2)
        self.notify_observers()

    def remove_consumable_from_slot(self, slot_number: int) -> Optional[ConsumableType]:
        if self.consumables_in_slots[slot_number]:
            removed = self.consumables_in_slots[slot_number].pop(0)
            self.notify_observers()
            return removed
        return None

    def get_consumable_at_slot(self, slot_number: int) -> Optional[ConsumableType]:
        return self.consumables_in_slots[slot_number][0] if len(self.consumables_in_slots[slot_number]) > 0 else None

    def notify_observers(self):
        self.was_updated.notify(self.consumables_in_slots)
Пример #2
0
def register_game_state_observers(game_state: GameState, ui_view: GameUiView, include_player_state: bool):
    game_state.game_world.player_movement_speed_was_updated.register_observer(ui_view.on_player_movement_speed_updated)
    game_state.game_world.notify_movement_speed_observers()  # Must notify the initial state
    game_state.game_world.player_entity.movement_changed = Observable()
    game_state.game_world.player_entity.movement_changed.register_observer(play_or_stop_footstep_sounds)
    game_state.game_world.player_entity.position_changed = Observable()
    game_state.game_world.player_entity.position_changed.register_observer(ui_view.on_player_position_updated)
    game_state.game_world.player_entity.position_changed.register_observer(
        lambda _: ui_view.on_walls_seen([w.get_position() for w in game_state.get_walls_in_sight_of_player()]))
    game_state.game_world.player_entity.notify_position_observers()  # Must notify the initial state

    if include_player_state:
        _register_player_state_observers(game_state.player_state, ui_view)
Пример #3
0
 def __init__(self, slots: List[ItemInventorySlot]):
     self.slots = slots
     self.was_updated = Observable()
Пример #4
0
class ItemInventory:
    def __init__(self, slots: List[ItemInventorySlot]):
        self.slots = slots
        self.was_updated = Observable()

    def switch_item_slots(self, slot_1_index: int,
                          slot_2_index: int) -> List[ItemActivationEvent]:
        slot_1 = self.slots[slot_1_index]
        slot_2 = self.slots[slot_2_index]
        content_1 = slot_1.item
        content_2 = slot_2.item
        events = []
        is_switch_allowed = slot_2.can_contain_item(
            content_1) and slot_1.can_contain_item(content_2)
        if is_switch_allowed:
            if content_1:
                item_type_1 = slot_1.get_item_type()
                if slot_1.is_active() and not slot_2.is_active():
                    event_1 = ItemWasDeactivated(item_type_1)
                elif not slot_1.is_active() and slot_2.is_active():
                    event_1 = ItemWasActivated(item_type_1)
                else:
                    event_1 = ItemActivationStateDidNotChange(item_type_1)
                events.append(event_1)
            if content_2:
                item_type_2 = slot_2.get_item_type()
                if slot_2.is_active() and not slot_1.is_active():
                    event_2 = ItemWasDeactivated(item_type_2)
                elif not slot_2.is_active() and slot_1.is_active():
                    event_2 = ItemWasActivated(item_type_2)
                else:
                    event_2 = ItemActivationStateDidNotChange(item_type_2)
                events.append(event_2)
            slot_1.item = content_2
            slot_2.item = content_1
        self.notify_observers()
        return events

    def try_switch_item_at_slot(self,
                                slot_index: int) -> List[ItemActivationEvent]:
        if self.slots[slot_index].is_empty():
            return []
        for other_slot_index in [
                s for s in range(len(self.slots)) if s != slot_index
        ]:
            events = self.switch_item_slots(slot_index, other_slot_index)
            if events:
                self.notify_observers()
                return events
        return []

    def has_item_in_inventory(self, item_type: ItemType):
        matches = [
            slot for slot in self.slots
            if not slot.is_empty() and slot.get_item_type() == item_type
        ]
        if len(matches) > 0:
            return True

    def lose_item_from_inventory(self, item_type: ItemType):
        for slot_number in range(len(self.slots)):
            slot = self.slots[slot_number]
            if not slot.is_empty() and slot.get_item_type() == item_type:
                self.slots[slot_number].item = None
                self.notify_observers()
                return
        print("WARN: item not found in inventory: " + item_type.name)

    # Note: this will need to return events, if it's used for items that have effects
    def is_slot_empty(self, slot_index: int) -> bool:
        return self.slots[slot_index].is_empty()

    def try_add_item(self, item_effect, item_equipment_category: ItemEquipmentCategory) \
            -> Optional[ItemActivationEvent]:
        item_in_slot = ItemInSlot(item_effect, item_equipment_category)
        empty_slot_index = self._find_empty_slot_for_item(item_in_slot)
        if empty_slot_index is not None:
            slot = self.slots[empty_slot_index]
            slot.item = item_in_slot
            self.notify_observers()
            if slot.is_active():
                return ItemWasActivated(item_effect.get_item_type())
            else:
                return ItemActivationStateDidNotChange(
                    item_effect.get_item_type())
        return None

    def put_item_in_inventory_slot(
            self, item_effect, item_equipment_category: ItemEquipmentCategory,
            slot_number: int) -> ItemActivationEvent:
        item_in_slot = ItemInSlot(item_effect, item_equipment_category)
        slot = self.slots[slot_number]
        if not slot.is_empty():
            raise Exception("Can't put item in non-empty slot!")
        slot.item = item_in_slot
        self.notify_observers()
        if slot.is_active():
            return ItemWasActivated(item_effect.get_item_type())
        else:
            return ItemActivationStateDidNotChange(item_effect.get_item_type())

    def _find_empty_slot_for_item(self, item: ItemInSlot) -> Optional[int]:
        empty_slot_indices = [
            i for i in range(len(self.slots)) if self.slots[i].is_empty()
            and self.slots[i].can_contain_item(item)
        ]
        if empty_slot_indices:
            return empty_slot_indices[0]
        return None

    def get_item_type_in_slot(self, slot_index: int) -> ItemType:
        if self.slots[slot_index].is_empty():
            raise Exception("Can't get item type from empty inventory slot: " +
                            str(slot_index))
        return self.slots[slot_index].get_item_type()

    def remove_item_from_slot(self, slot_index: int) -> ItemActivationEvent:
        slot = self.slots[slot_index]
        if slot.is_empty():
            raise Exception("Can't remove item from empty inventory slot: " +
                            str(slot_index))
        item_type = slot.get_item_type()
        slot.item = None
        self.notify_observers()
        if slot.is_active():
            return ItemWasDeactivated(item_type)
        return ItemActivationStateDidNotChange(item_type)

    def get_all_active_item_effects(self) -> List[Any]:
        return [
            slot.item.item_effect for slot in self.slots
            if slot.is_active() and not slot.is_empty()
        ]

    def notify_observers(self):
        self.was_updated.notify(self.slots)
    def run_one_frame(self, _time_passed: Millis) -> Optional[SceneTransition]:

        saved_player_state = self.flags.saved_player_state
        hero_start_level = self.flags.hero_start_level
        start_money = self.flags.start_money
        picked_hero = self.flags.picked_hero
        map_file_path = self.flags.map_file_path
        character_file = self.flags.character_file

        if saved_player_state:
            hero_from_saved_state = HeroId[saved_player_state.hero_id]
            if picked_hero is not None and picked_hero != hero_from_saved_state:
                raise Exception("Mismatch! Hero from saved state: " +
                                str(hero_from_saved_state) +
                                ", but picked hero: " + str(picked_hero))
            picked_hero = hero_from_saved_state

        total_time_played_on_character = saved_player_state.total_time_played_on_character if saved_player_state else 0

        # NPC's share a "global path finder" that needs to be initialized before we start creating NPCs.
        # TODO This is very messy
        path_finder = GlobalPathFinder()
        set_global_path_finder(path_finder)

        map_data = load_map_from_json_file(self.camera_size, map_file_path,
                                           picked_hero)

        path_finder.set_grid(map_data.game_state.pathfinder_wall_grid)

        game_state = map_data.game_state
        game_engine = GameEngine(game_state, self.ui_view.info_message)
        game_state.player_state.exp_was_updated.register_observer(
            self.ui_view.on_player_exp_updated)
        game_state.player_state.talents_were_updated.register_observer(
            self.ui_view.on_talents_updated)
        game_state.player_state.notify_talent_observers(
        )  # Must notify the initial state
        game_state.player_movement_speed_was_updated.register_observer(
            self.ui_view.on_player_movement_speed_updated)
        game_state.notify_movement_speed_observers(
        )  # Must notify the initial state
        game_state.player_state.stats_were_updated.register_observer(
            self.ui_view.on_player_stats_updated)
        game_state.player_state.notify_stats_observers(
        )  # Must notify the initial state
        game_engine.talent_was_unlocked.register_observer(
            self.ui_view.on_talent_was_unlocked)
        game_engine.ability_was_clicked.register_observer(
            self.ui_view.on_ability_was_clicked)
        game_engine.consumable_was_clicked.register_observer(
            self.ui_view.on_consumable_was_clicked)
        game_state.player_state.money_was_updated.register_observer(
            self.ui_view.on_money_updated)
        game_state.player_state.notify_money_observers(
        )  # Must notify the initial state
        game_state.player_state.abilities_were_updated.register_observer(
            self.ui_view.on_abilities_updated)
        game_state.player_state.notify_ability_observers(
        )  # Must notify the initial state
        game_state.player_state.cooldowns_were_updated.register_observer(
            self.ui_view.on_cooldowns_updated)
        game_state.player_state.health_resource.value_was_updated.register_observer(
            self.ui_view.on_health_updated)
        game_state.player_state.mana_resource.value_was_updated.register_observer(
            self.ui_view.on_mana_updated)
        game_state.player_state.buffs_were_updated.register_observer(
            self.ui_view.on_buffs_updated)
        game_state.player_state.quests_were_updated.register_observer(
            self.ui_view.on_player_quests_updated)
        game_state.player_entity.movement_changed = Observable()
        game_state.player_entity.movement_changed.register_observer(
            play_or_stop_footstep_sounds)
        game_state.player_entity.position_changed = Observable()
        game_state.player_entity.position_changed.register_observer(
            self.ui_view.on_player_position_updated)
        game_state.player_entity.position_changed.register_observer(
            lambda _: self.ui_view.on_walls_seen([
                w.get_position()
                for w in game_state.get_walls_in_sight_of_player()
            ]))
        self.ui_view.on_world_area_updated(game_state.entire_world_area)
        # Must center camera before notifying player position as it affects which walls are shown on the minimap
        game_state.center_camera_on_player()
        game_state.player_entity.notify_position_observers(
        )  # Must notify the initial state

        if map_file_path == 'resources/maps/challenge.json':
            world_behavior = ChallengeBehavior(self.picking_hero_scene,
                                               self.challenge_complete_scene,
                                               game_state,
                                               self.ui_view.info_message,
                                               game_engine, self.flags)
        else:
            world_behavior = StoryBehavior(self.victory_screen_scene,
                                           game_state,
                                           self.ui_view.info_message)

        if saved_player_state:
            game_engine.gain_levels(saved_player_state.level - 1)
            game_state.player_state.gain_exp(saved_player_state.exp)
            game_engine.set_item_inventory([
                ItemType[item] if item else None
                for item in saved_player_state.items
            ])
            game_state.player_state.consumable_inventory = ConsumableInventory(
                {
                    int(slot_number): [ConsumableType[c] for c in consumables]
                    for (slot_number, consumables
                         ) in saved_player_state.consumables_in_slots.items()
                })
            game_state.player_state.modify_money(saved_player_state.money)
            for portal in game_state.portals:
                if portal.portal_id.name in saved_player_state.enabled_portals:
                    sprite = saved_player_state.enabled_portals[
                        portal.portal_id.name]
                    portal.activate(Sprite[sprite])
            for tier_index, option_index in enumerate(
                    saved_player_state.talent_tier_choices):
                if option_index is not None:
                    pick_talent(game_state, tier_index, option_index)
            for completed_quest in saved_player_state.completed_quests:
                quest = get_quest(QuestId[completed_quest])
                game_state.player_state.start_quest(quest)
                game_state.player_state.complete_quest(quest)
            for active_quest in saved_player_state.active_quests:
                quest = get_quest(QuestId[active_quest])
                game_state.player_state.start_quest(quest)
        else:
            if hero_start_level > 1:
                game_engine.gain_levels(hero_start_level - 1)
            if start_money > 0:
                game_state.player_state.modify_money(start_money)

        game_state.player_state.item_inventory.was_updated.register_observer(
            self.ui_view.on_inventory_updated)
        game_state.player_state.item_inventory.notify_observers(
        )  # Must notify the initial state
        game_state.player_state.consumable_inventory.was_updated.register_observer(
            self.ui_view.on_consumables_updated)
        game_state.player_state.consumable_inventory.notify_observers(
        )  # Must notify the initial state
        self.ui_view.update_hero(game_state.player_state.hero_id)

        # When loading from a savefile a bunch of messages are generated (levelup, learning talents, etc), but they
        # are irrelevant, since we're loading an exiting character
        self.ui_view.info_message.clear_messages()

        # Talent toggle is highlighted when new talents are unlocked, but we don't want it to be highlighted on startup
        # when loading from a savefile
        self.ui_view.remove_highlight_from_talent_toggle()

        allocate_input_keys_for_abilities(game_state.player_state.abilities)

        new_hero_was_created = saved_player_state is None
        playing_scene = self.playing_scene(game_state, game_engine,
                                           world_behavior, self.ui_view,
                                           new_hero_was_created,
                                           character_file,
                                           total_time_played_on_character)
        return SceneTransition(playing_scene)
    def run_one_frame(self, _time_passed: Millis) -> Optional[SceneTransition]:

        saved_player_state = self.flags.saved_player_state
        hero_start_level = self.flags.hero_start_level
        start_money = self.flags.start_money
        picked_hero = self.flags.picked_hero
        map_file_path = self.flags.map_file_path
        character_file = self.flags.character_file

        if saved_player_state:
            hero_from_saved_state = HeroId[saved_player_state.hero_id]
            if picked_hero is not None and picked_hero != hero_from_saved_state:
                raise Exception("Mismatch! Hero from saved state: " +
                                str(hero_from_saved_state) +
                                ", but picked hero: " + str(picked_hero))
            picked_hero = hero_from_saved_state

        total_time_played_on_character = saved_player_state.total_time_played_on_character if saved_player_state else 0

        ui_state = GameUiState()
        game_state = create_game_state_from_json_file(self.camera_size,
                                                      map_file_path,
                                                      picked_hero)
        game_engine = GameEngine(game_state, ui_state)
        game_state.player_state.exp_was_updated.register_observer(
            self.ui_view.on_player_exp_updated)
        game_state.player_state.talents_were_updated.register_observer(
            self.ui_view.on_talents_updated)
        game_state.player_state.notify_talent_observers(
        )  # Must notify the initial state
        game_state.player_movement_speed_was_updated.register_observer(
            self.ui_view.on_player_movement_speed_updated)
        game_state.notify_movement_speed_observers(
        )  # Must notify the initial state
        game_state.player_state.stats_were_updated.register_observer(
            self.ui_view.on_player_stats_updated)
        game_state.player_state.notify_stats_observers(
        )  # Must notify the initial state
        game_engine.talent_was_unlocked.register_observer(
            self.ui_view.on_talent_was_unlocked)
        game_state.player_state.money_was_updated.register_observer(
            self.ui_view.on_money_updated)
        game_state.player_state.notify_money_observers(
        )  # Must notify the initial state
        game_state.player_state.abilities_were_updated.register_observer(
            self.ui_view.on_abilities_updated)
        game_state.player_state.notify_ability_observers(
        )  # Must notify the initial state
        game_state.player_state.cooldowns_were_updated.register_observer(
            self.ui_view.on_cooldowns_updated)
        game_state.player_state.health_resource.value_was_updated.register_observer(
            self.ui_view.on_health_updated)
        game_state.player_state.mana_resource.value_was_updated.register_observer(
            self.ui_view.on_mana_updated)
        game_state.player_state.buffs_were_updated.register_observer(
            self.ui_view.on_buffs_updated)
        game_state.player_entity.movement_changed = Observable()
        game_state.player_entity.movement_changed.register_observer(
            play_or_stop_footstep_sounds)

        if map_file_path == 'resources/maps/challenge.json':
            world_behavior = ChallengeBehavior(self.picking_hero_scene,
                                               self.challenge_complete_scene,
                                               game_state, ui_state,
                                               game_engine, self.flags)
        else:
            world_behavior = StoryBehavior(self.victory_screen_scene,
                                           game_state, ui_state)

        if saved_player_state:
            game_engine.gain_levels(saved_player_state.level - 1)
            game_state.player_state.gain_exp(saved_player_state.exp)
            game_engine.set_item_inventory([
                ItemType[item] if item else None
                for item in saved_player_state.items
            ])
            game_state.player_state.consumable_inventory = ConsumableInventory(
                {
                    int(slot_number): [ConsumableType[c] for c in consumables]
                    for (slot_number, consumables
                         ) in saved_player_state.consumables_in_slots.items()
                })
            game_state.player_state.modify_money(saved_player_state.money)
            for portal in game_state.portals:
                if portal.portal_id.name in saved_player_state.enabled_portals:
                    sprite = saved_player_state.enabled_portals[
                        portal.portal_id.name]
                    portal.activate(Sprite[sprite])
            for tier_index, option_index in enumerate(
                    saved_player_state.talent_tier_choices):
                if option_index is not None:
                    pick_talent(game_state, tier_index, option_index)
        else:
            if hero_start_level > 1:
                game_engine.gain_levels(hero_start_level - 1)
            if start_money > 0:
                game_state.player_state.modify_money(start_money)

        game_state.player_state.item_inventory.was_updated.register_observer(
            self.ui_view.on_inventory_updated)
        game_state.player_state.item_inventory.notify_observers(
        )  # Must notify the initial state
        game_state.player_state.consumable_inventory.was_updated.register_observer(
            self.ui_view.on_consumables_updated)
        game_state.player_state.consumable_inventory.notify_observers(
        )  # Must notify the initial state
        self.ui_view.update_hero(game_state.player_state.hero_id)

        # When loading from a savefile a bunch of messages are generated (levelup, learning talents, etc), but they
        # are irrelevant, since we're loading an exiting character
        ui_state.clear_messages()

        # Talent toggle is highlighted when new talents are unlocked, but we don't want it to be highlighted on startup
        # when loading from a savefile
        self.ui_view.remove_highlight_from_talent_toggle()

        allocate_input_keys_for_abilities(game_state.player_state.abilities)

        new_hero_was_created = saved_player_state is None
        playing_scene = self.playing_scene(game_state, game_engine,
                                           world_behavior, ui_state,
                                           self.ui_view, new_hero_was_created,
                                           character_file,
                                           total_time_played_on_character)
        return SceneTransition(playing_scene)
Пример #7
0
class ItemInventory:
    def __init__(self, slots: List[ItemInventorySlot]):
        self.slots = slots
        self.was_updated = Observable()

    def switch_item_slots(self, slot_1_index: int,
                          slot_2_index: int) -> List[ItemActivationEvent]:
        slot_1 = self.slots[slot_1_index]
        slot_2 = self.slots[slot_2_index]
        content_1 = slot_1.item
        content_2 = slot_2.item
        events = []
        is_switch_allowed = slot_2.can_contain_item(
            content_1) and slot_1.can_contain_item(content_2)
        if is_switch_allowed:
            if content_1:
                item_id_1 = slot_1.get_item_id()
                if slot_1.is_active() and not slot_2.is_active():
                    event_1 = ItemWasDeactivated(item_id_1)
                elif not slot_1.is_active() and slot_2.is_active():
                    event_1 = ItemWasActivated(item_id_1)
                else:
                    event_1 = ItemActivationStateDidNotChange(item_id_1)
                events.append(event_1)
            if content_2:
                item_id_2 = slot_2.get_item_id()
                if slot_2.is_active() and not slot_1.is_active():
                    event_2 = ItemWasDeactivated(item_id_2)
                elif not slot_2.is_active() and slot_1.is_active():
                    event_2 = ItemWasActivated(item_id_2)
                else:
                    event_2 = ItemActivationStateDidNotChange(item_id_2)
                events.append(event_2)
            slot_1.item = content_2
            slot_2.item = content_1

        if len(events) == 2:
            # If switching two items that both have active abilities, the currently active ability needs to be removed
            # before the new one can be activated. If we don't switch the order here, the consequence is that no ability
            # will be active.
            if isinstance(events[0], ItemWasActivated) and isinstance(
                    events[1], ItemWasDeactivated):
                events = [events[1], events[0]]

        self.notify_observers()
        return events

    def try_switch_item_at_slot(self,
                                slot_index: int) -> List[ItemActivationEvent]:
        if self.slots[slot_index].is_empty():
            return []
        for other_slot_index in [
                s for s in range(len(self.slots)) if s != slot_index
        ]:
            events = self.switch_item_slots(slot_index, other_slot_index)
            if events:
                self.notify_observers()
                return events
        return []

    def has_item_in_inventory(self, item_id: ItemId):
        matches = [
            slot for slot in self.slots
            if not slot.is_empty() and slot.get_item_id() == item_id
        ]
        if len(matches) > 0:
            return True

    def lose_item_from_inventory(self, item_id: ItemId):
        for slot_number in range(len(self.slots)):
            slot = self.slots[slot_number]
            if not slot.is_empty() and slot.get_item_id() == item_id:
                self.slots[slot_number].item = None
                self.notify_observers()
                return
        print("WARN: item not found in inventory: " + str(item_id))

    # Note: this will need to return events, if it's used for items that have effects
    def is_slot_empty(self, slot_index: int) -> bool:
        return self.slots[slot_index].is_empty()

    def try_add_item(self, item_id: ItemId, item_effect, item_equipment_category: ItemEquipmentCategory) \
            -> Optional[ItemActivationEvent]:
        item_in_slot = ItemInSlot(item_id, item_effect,
                                  item_equipment_category)
        empty_slot_index = self._find_empty_slot_for_item(item_in_slot)
        if empty_slot_index is not None:
            slot = self.slots[empty_slot_index]
            slot.item = item_in_slot
            self.notify_observers()
            if slot.is_active():
                return ItemWasActivated(item_id)
            else:
                return ItemActivationStateDidNotChange(item_id)
        return None

    def put_item_in_inventory_slot(
            self, item_id: ItemId, item_effect,
            item_equipment_category: ItemEquipmentCategory,
            slot_number: int) -> ItemActivationEvent:
        item_in_slot = ItemInSlot(item_id, item_effect,
                                  item_equipment_category)
        slot = self.slots[slot_number]
        if not slot.is_empty():
            raise Exception("Can't put item in non-empty slot!")
        slot.item = item_in_slot
        self.notify_observers()
        if slot.is_active():
            return ItemWasActivated(item_id)
        else:
            return ItemActivationStateDidNotChange(item_id)

    def _find_empty_slot_for_item(self, item: ItemInSlot) -> Optional[int]:
        empty_slot_indices = [
            i for i in range(len(self.slots)) if self.slots[i].is_empty()
            and self.slots[i].can_contain_item(item)
        ]
        if empty_slot_indices:
            return empty_slot_indices[0]
        return None

    def remove_item_from_slot(self, slot_index: int) -> ItemActivationEvent:
        slot = self.slots[slot_index]
        if slot.is_empty():
            raise Exception("Can't remove item from empty inventory slot: " +
                            str(slot_index))
        item_id = slot.get_item_id()
        slot.item = None
        self.notify_observers()
        if slot.is_active():
            return ItemWasDeactivated(item_id)
        return ItemActivationStateDidNotChange(item_id)

    def clear(self) -> List[ItemActivationEvent]:
        events = []
        for slot in self.slots:
            if not slot.is_empty() and slot.is_active():
                active_item_id = slot.get_item_id()
                events.append(ItemWasDeactivated(active_item_id))
            slot.item = None
        self.notify_observers()
        return events

    def get_all_active_item_effects(self) -> List[Any]:
        return [
            slot.item.item_effect for slot in self.slots
            if slot.is_active() and not slot.is_empty()
        ]

    def notify_observers(self):
        self.was_updated.notify(self.slots)

    def __repr__(self):
        return str(self.slots)
Пример #8
0
 def __init__(self, slots: Dict[int, List[ConsumableType]]):
     self.consumables_in_slots: Dict[int, List[ConsumableType]] = slots
     self.capacity_per_slot = 2
     self.was_updated = Observable()