コード例 #1
0
ファイル: max_group.py プロジェクト: DarkFenX/eos
class MaxGroupRestrictionRegister(BaseRestrictionRegister, metaclass=ABCMeta):
    """Base class for all max modules per group restrictions."""

    def __init__(self, fit):
        # Container for all tracked items, keyed by their group ID
        # Format: {group ID: {items}}
        self.__group_item_map = KeyedStorage()
        # Container for items, which have max group restriction to become
        # operational
        # Format: {items}
        self.__restricted_items = set()
        fit._subscribe(self, self._handler_map.keys())

    @property
    @abstractmethod
    def _max_group_attr_id(self):
        """Attribute ID whose value contains group restriction of item."""
        ...

    def _register_item(self, item):
        if not isinstance(item, TRACKED_ITEM_CLASSES):
            return
        group_id = item._type.group_id
        # Ignore items, whose type isn't assigned to any group
        if group_id is None:
            return
        # Having group ID is sufficient condition to enter container of all
        # fitted items
        self.__group_item_map.add_data_entry(group_id, item)
        # To enter restriction container, item's type must have restriction
        # attribute
        if self._max_group_attr_id not in item._type_attrs:
            return
        self.__restricted_items.add(item)

    def _unregister_item(self, item):
        # Just clear data containers
        group_id = item._type.group_id
        self.__group_item_map.rm_data_entry(group_id, item)
        self.__restricted_items.discard(item)

    def validate(self):
        # Container for tainted items
        tainted_items = {}
        # Go through all restricted items
        for item in self.__restricted_items:
            # Get quantity of registered items, assigned to group of current
            # restricted item, and item's restriction value
            group_id = item._type.group_id
            quantity = len(self.__group_item_map.get(group_id, ()))
            max_allowed_quantity = item.attrs[self._max_group_attr_id]
            if quantity > max_allowed_quantity:
                tainted_items[item] = MaxGroupErrorData(
                    group_id=group_id,
                    quantity=quantity,
                    max_allowed_quantity=max_allowed_quantity)
        # Raise error if we detected any tainted items
        if tainted_items:
            raise RestrictionValidationError(tainted_items)
コード例 #2
0
ファイル: max_group.py プロジェクト: miceky4/eos
class MaxGroupRestrictionRegister(BaseRestrictionRegister, metaclass=ABCMeta):
    """Base class for all max modules per group restrictions."""

    def __init__(self, fit):
        # Container for all tracked items, keyed by their group ID
        # Format: {group ID: {items}}
        self.__group_item_map = KeyedStorage()
        # Container for items, which have max group restriction to become
        # operational
        # Format: {items}
        self.__restricted_items = set()
        fit._subscribe(self, self._handler_map.keys())

    @property
    @abstractmethod
    def _max_group_attr_id(self):
        """Attribute ID whose value contains group restriction of item."""
        ...

    def _register_item(self, item):
        if not isinstance(item, TRACKED_ITEM_CLASSES):
            return
        group_id = item._type.group_id
        # Ignore items, whose type isn't assigned to any group
        if group_id is None:
            return
        # Having group ID is sufficient condition to enter container of all
        # fitted items
        self.__group_item_map.add_data_entry(group_id, item)
        # To enter restriction container, item's type must have restriction
        # attribute
        if self._max_group_attr_id not in item._type_attrs:
            return
        self.__restricted_items.add(item)

    def _unregister_item(self, item):
        # Just clear data containers
        group_id = item._type.group_id
        self.__group_item_map.rm_data_entry(group_id, item)
        self.__restricted_items.discard(item)

    def validate(self):
        # Container for tainted items
        tainted_items = {}
        # Go through all restricted items
        for item in self.__restricted_items:
            # Get quantity of registered items, assigned to group of current
            # restricted item, and item's restriction value
            group_id = item._type.group_id
            quantity = len(self.__group_item_map.get(group_id, ()))
            max_allowed_quantity = item._type_attrs[self._max_group_attr_id]
            if quantity > max_allowed_quantity:
                tainted_items[item] = MaxGroupErrorData(
                    group_id=group_id,
                    quantity=quantity,
                    max_allowed_quantity=max_allowed_quantity)
        # Raise error if we detected any tainted items
        if tainted_items:
            raise RestrictionValidationError(tainted_items)
コード例 #3
0
ファイル: service.py プロジェクト: miceky4/eos
 def __init__(self, solar_system):
     self.__solar_system = solar_system
     self.__affections = AffectionRegister()
     self.__projections = ProjectionRegister()
     # Format: {projector: {modifiers}}
     self.__warfare_buffs = KeyedStorage()
     # Container with affector specs which will receive messages
     # Format: {message type: set(affector specs)}
     self.__subscribed_affectors = KeyedStorage()
コード例 #4
0
ファイル: max_group.py プロジェクト: miceky4/eos
 def __init__(self, fit):
     # Container for all tracked items, keyed by their group ID
     # Format: {group ID: {items}}
     self.__group_item_map = KeyedStorage()
     # Container for items, which have max group restriction to become
     # operational
     # Format: {items}
     self.__restricted_items = set()
     fit._subscribe(self, self._handler_map.keys())
コード例 #5
0
class DmgDealerRegister(BaseStatRegister):
    """Class which tracks all effects which deal damage.

    Provides functionality to fetch various aggregated stats.
    """
    def __init__(self, fit):
        # Format: {item:, {effect1, effect2}}
        self.__dmg_dealers = KeyedStorage()
        fit._subscribe(self, self._handler_map.keys())

    def get_volley(self, item_filter, tgt_resists):
        volleys = []
        for item in self.__dd_iter(item_filter):
            volley = item.get_volley(tgt_resists)
            volleys.append(volley)
        return DmgStats._combine(volleys)

    def get_dps(self, item_filter, reload, tgt_resists):
        dpss = []
        for item in self.__dd_iter(item_filter):
            dps = item.get_dps(reload, tgt_resists)
            dpss.append(dps)
        return DmgStats._combine(dpss)

    def __dd_iter(self, item_filter):
        for item in self.__dmg_dealers:
            if item_filter is None or item_filter(item):
                yield item

    # Message handling
    def _handle_effects_started(self, msg):
        item_effects = msg.item._type_effects
        for effect_id in msg.effect_ids:
            effect = item_effects[effect_id]
            if isinstance(effect, DmgDealerEffect):
                self.__dmg_dealers.add_data_entry(msg.item, effect)

    def _handle_effects_stopped(self, msg):
        item_effects = msg.item._type_effects
        for effect_id in msg.effect_ids:
            effect = item_effects[effect_id]
            if isinstance(effect, DmgDealerEffect):
                self.__dmg_dealers.rm_data_entry(msg.item, effect)

    _handler_map = {
        EffectsStarted: _handle_effects_started,
        EffectsStopped: _handle_effects_stopped
    }
コード例 #6
0
ファイル: dmg_dealer.py プロジェクト: DarkFenX/eos
class DmgDealerRegister(BaseStatRegister):
    """Class which tracks all effects which deal damage.

    Provides functionality to fetch various aggregated stats.
    """

    def __init__(self, fit):
        # Format: {item:, {effect1, effect2}}
        self.__dmg_dealers = KeyedStorage()
        fit._subscribe(self, self._handler_map.keys())

    def get_volley(self, item_filter, tgt_resists):
        volleys = []
        for item in self.__dd_iter(item_filter):
            volley = item.get_volley(tgt_resists)
            volleys.append(volley)
        return DmgStats._combine(volleys)

    def get_dps(self, item_filter, reload, tgt_resists):
        dpss = []
        for item in self.__dd_iter(item_filter):
            dps = item.get_dps(reload, tgt_resists)
            dpss.append(dps)
        return DmgStats._combine(dpss)

    def __dd_iter(self, item_filter):
        for item in self.__dmg_dealers:
            if item_filter is None or item_filter(item):
                yield item

    # Message handling
    def _handle_effects_started(self, msg):
        item_effects = msg.item._type_effects
        for effect_id in msg.effect_ids:
            effect = item_effects[effect_id]
            if isinstance(effect, DmgDealerEffect):
                self.__dmg_dealers.add_data_entry(msg.item, effect)

    def _handle_effects_stopped(self, msg):
        item_effects = msg.item._type_effects
        for effect_id in msg.effect_ids:
            effect = item_effects[effect_id]
            if isinstance(effect, DmgDealerEffect):
                self.__dmg_dealers.rm_data_entry(msg.item, effect)

    _handler_map = {
        EffectsStarted: _handle_effects_started,
        EffectsStopped: _handle_effects_stopped}
コード例 #7
0
ファイル: slot_index.py プロジェクト: miceky4/eos
class SlotIndexRestrictionRegister(BaseRestrictionRegister, metaclass=ABCMeta):
    """Base class for all slot index restrictions.

    It doesn't allow multiple items to take the same numbered slot.
    """
    def __init__(self, fit):
        # All items which possess index of slot are stored in this container
        # Format: {slot index: {items}}
        self.__index_item_map = KeyedStorage()
        fit._subscribe(self, self._handler_map.keys())

    @property
    @abstractmethod
    def _slot_index_attr_id(self):
        """This attribute's value on item represents index of slot."""
        ...

    def _handle_item_loaded(self, msg):
        # Skip items which don't have index specified
        slot_index = msg.item._type_attrs.get(self._slot_index_attr_id)
        if slot_index is None:
            return
        self.__index_item_map.add_data_entry(slot_index, msg.item)

    def _handle_item_unloaded(self, msg):
        slot_index = msg.item._type_attrs.get(self._slot_index_attr_id)
        if slot_index is None:
            return
        self.__index_item_map.rm_data_entry(slot_index, msg.item)

    _handler_map = {
        ItemLoaded: _handle_item_loaded,
        ItemUnloaded: _handle_item_unloaded
    }

    def validate(self):
        tainted_items = {}
        for slot_index, slot_index_items in self.__index_item_map.items():
            # If more than one item occupies the same slot, all items in this
            # slot are tainted
            if len(slot_index_items) > 1:
                for item in slot_index_items:
                    tainted_items[item] = SlotIndexErrorData(
                        slot_index=slot_index)
        if tainted_items:
            raise RestrictionValidationError(tainted_items)
コード例 #8
0
ファイル: max_group.py プロジェクト: DarkFenX/eos
 def __init__(self, fit):
     # Container for all tracked items, keyed by their group ID
     # Format: {group ID: {items}}
     self.__group_item_map = KeyedStorage()
     # Container for items, which have max group restriction to become
     # operational
     # Format: {items}
     self.__restricted_items = set()
     fit._subscribe(self, self._handler_map.keys())
コード例 #9
0
ファイル: projection.py プロジェクト: DarkFenX/eos
    def __init__(self):
        # Format: {projectors}
        self.__projectors = set()

        # Projectors residing on solar system item
        # Format: {carrier item: {projectors}}
        self.__carrier_projectors = KeyedStorage()

        # Projectors whose carrying solar system item is not present
        # Format: {projectors}
        self.__carrierless_projectors = set()

        # Solar system items affected by projector
        # Format: {projector: {target items}}
        self.__projector_tgts = KeyedStorage()

        # Projectors affecting solar system item
        # Format: {target item: {projectors}}
        self.__tgt_projectors = KeyedStorage()
コード例 #10
0
ファイル: affection.py プロジェクト: DarkFenX/eos
class AffectionRegister:
    """Keeps track of connections between affector specs and affectee items.

    Having information about such connections is a hard requirement for
    efficient partial attribute recalculation.
    """

    def __init__(self):
        # All known affectee items
        # Format: {affectee items}
        self.__affectees = set()

        # Items belonging to certain fit and domain
        # Format: {(affectee fit, affectee domain): {affectee items}}
        self.__affectees_domain = KeyedStorage()

        # Items belonging to certain fit, domain and group
        # Format: {(affectee fit, affectee domain, affectee group ID): {affectee
        # items}}
        self.__affectees_domain_group = KeyedStorage()

        # Items belonging to certain fit and domain, and having certain skill
        # requirement
        # Format: {(affectee fit, affectee domain, affectee skill requirement
        # type ID): {affectee items}}
        self.__affectees_domain_skillrq = KeyedStorage()

        # Owner-modifiable items which belong to certain fit and have certain
        # skill requirement
        # Format: {(affectee fit, affectee skill requirement type ID): {affectee
        # items}}
        self.__affectees_owner_skillrq = KeyedStorage()

        # Affector specs with modifiers which affect 'other' location are always
        # stored here, regardless if they actually affect something or not
        # Format: {affector item: {affector specs}}
        self.__affectors_item_other = KeyedStorage()

        # Affector specs which should affect only one item (ship, character or
        # self), when this item is not registered as affectee
        # Format: {affectee fit: {affector specs}}
        self.__affectors_item_awaiting = KeyedStorage()

        # All active affector specs which affect one specific item (via ship,
        # character, other reference or self) are kept here
        # Format: {affectee item: {affector specs}}
        self.__affectors_item_active = KeyedStorage()

        # Affector specs influencing all items belonging to certain fit and
        # domain
        # Format: {(affectee fit, affectee domain): {affector specs}}
        self.__affectors_domain = KeyedStorage()

        # Affector specs influencing items belonging to certain fit, domain and
        # group
        # Format: {(affectee fit, affectee domain, affectee group ID): {affector
        # specs}}
        self.__affectors_domain_group = KeyedStorage()

        # Affector specs influencing items belonging to certain fit and domain,
        # and having certain skill requirement
        # Format: {(affectee fit, affectee domain, affectee skill requirement
        # type ID): {affector specs}}
        self.__affectors_domain_skillrq = KeyedStorage()

        # Affector specs influencing owner-modifiable items belonging to certain
        # fit and having certain skill requirement
        # Format: {(affectee fit, affectee skill requirement type ID): {affector
        # specs}}
        self.__affectors_owner_skillrq = KeyedStorage()

    # Query methods
    def get_local_affectee_items(self, affector_spec):
        """Get iterable with items influenced by passed local affector spec."""
        try:
            affectee_filter = affector_spec.modifier.affectee_filter
            # Direct item modification needs to use local-specific getters
            if affectee_filter == ModAffecteeFilter.item:
                affectee_domain = affector_spec.modifier.affectee_domain
                try:
                    getter = self.__local_affectees_getters[affectee_domain]
                except KeyError as e:
                    raise UnexpectedDomainError(affectee_domain) from e
                return getter(self, affector_spec)
            # En-masse filtered modification can use shared affectee item
            # getters
            else:
                try:
                    getter = self.__affectees_getters[affectee_filter]
                except KeyError as e:
                    raise UnknownAffecteeFilterError(affectee_filter) from e
                affectee_domain = self.__resolve_local_domain(affector_spec)
                affectee_fits = affector_spec.item._fit,
                return getter(
                    self, affector_spec, affectee_domain, affectee_fits)
        except Exception as e:
            self.__handle_affector_spec_errors(e, affector_spec)
            return ()

    def get_projected_affectee_items(self, affector_spec, tgt_items):
        """Get iterable with items influenced by projected affector spec."""
        affectee_filter = affector_spec.modifier.affectee_filter
        # Return targeted items when modification affects just them directly
        if affectee_filter == ModAffecteeFilter.item:
            return {i for i in tgt_items if i in self.__affectees}
        # En-masse modifications of items located on targeted items use shared
        # affectee item getters
        else:
            try:
                getter = self.__affectees_getters[affectee_filter]
            except KeyError as e:
                raise UnknownAffecteeFilterError(affectee_filter) from e
            affectee_fits = {i._fit for i in tgt_items if isinstance(i, Ship)}
            return getter(self, affector_spec, ModDomain.ship, affectee_fits)

    def get_affector_specs(self, affectee_item):
        """Get all affector specs, which influence passed item."""
        affectee_fit = affectee_item._fit
        affector_specs = set()
        # Item
        affector_storage = self.__affectors_item_active
        key = affectee_item
        affector_specs.update(affector_storage.get(key, ()))
        affectee_domain = affectee_item._modifier_domain
        if affectee_domain is not None:
            # Domain
            affector_storage = self.__affectors_domain
            key = (affectee_fit, affectee_domain)
            affector_specs.update(affector_storage.get(key, ()))
            # Domain and group
            affector_storage = self.__affectors_domain_group
            key = (affectee_fit, affectee_domain, affectee_item._type.group_id)
            affector_specs.update(affector_storage.get(key, ()))
            # Domain and skill requirement
            affector_storage = self.__affectors_domain_skillrq
            for affectee_srq_type_id in affectee_item._type.required_skills:
                key = (affectee_fit, affectee_domain, affectee_srq_type_id)
                affector_specs.update(affector_storage.get(key, ()))
        # Owner-modifiable and skill requirement
        if affectee_item._owner_modifiable:
            affector_storage = self.__affectors_owner_skillrq
            for affectee_srq_type_id in affectee_item._type.required_skills:
                key = (affectee_fit, affectee_srq_type_id)
                affector_specs.update(affector_storage.get(key, ()))
        return affector_specs

    # Maintenance methods
    def register_affectee_item(self, affectee_item):
        """Add passed affectee item to the register.

        We track affectee items to efficiently update attributes when set of
        items influencing them changes.
        """
        self.__affectees.add(affectee_item)
        affectee_fit = affectee_item._fit
        for key, storage in self.__get_affectee_storages(
            affectee_fit, affectee_item
        ):
            storage.add_data_entry(key, affectee_item)
        # Process special affector specs separately. E.g., when item like ship
        # is added, there might already be affector specs which should affect
        # it, and in this method we activate such affector specs
        self.__activate_special_affector_specs(affectee_fit, affectee_item)

    def unregister_affectee_item(self, affectee_item):
        """Remove passed affectee item from the register."""
        self.__affectees.remove(affectee_item)
        affectee_fit = affectee_item._fit
        for key, storage in self.__get_affectee_storages(
            affectee_fit, affectee_item
        ):
            storage.rm_data_entry(key, affectee_item)
        # Deactivate all special affector specs for item being unregistered
        self.__deactivate_special_affector_specs(affectee_fit, affectee_item)

    def register_local_affector_spec(self, affector_spec):
        """Make the register aware of the local affector spec.

        It makes it possible for the affector spec to modify other items within
        its fit.
        """
        try:
            storages = self.__get_local_affector_storages(affector_spec)
        except Exception as e:
            self.__handle_affector_spec_errors(e, affector_spec)
        else:
            for key, storage in storages:
                storage.add_data_entry(key, affector_spec)

    def unregister_local_affector_spec(self, affector_spec):
        """Remove local affector spec from the register.

        It makes it impossible for the affector spec to modify any items.
        """
        try:
            storages = self.__get_local_affector_storages(affector_spec)
        except Exception as e:
            self.__handle_affector_spec_errors(e, affector_spec)
        else:
            for key, storage in storages:
                storage.rm_data_entry(key, affector_spec)

    def register_projected_affector_spec(self, affector_spec, tgt_items):
        """Make register aware that projected affector spec affects items.

        Should be called every time projected effect with modifiers is applied
        onto any items.
        """
        try:
            storages = self.__get_projected_affector_storages(
                affector_spec, tgt_items)
        except Exception as e:
            self.__handle_affector_spec_errors(e, affector_spec)
        else:
            for key, storage in storages:
                storage.add_data_entry(key, affector_spec)

    def unregister_projected_affector(self, affector_spec, tgt_items):
        """Remove effect of affector spec from items.

        Should be called every time projected effect with modifiers stops
        affecting any object.
        """
        try:
            storages = self.__get_projected_affector_storages(
                affector_spec, tgt_items)
        except Exception as e:
            self.__handle_affector_spec_errors(e, affector_spec)
        else:
            for key, storage in storages:
                storage.rm_data_entry(key, affector_spec)

    # Helpers for affectee getter
    def __get_local_affectees_self(self, affector_spec):
        return affector_spec.item,

    def __get_local_affectees_character(self, affector_spec):
        affectee_fit = affector_spec.item._fit
        affectee_character = affectee_fit.character
        if affectee_character in self.__affectees:
            return affectee_character,
        else:
            return ()

    def __get_local_affectees_ship(self, affector_spec):
        affectee_fit = affector_spec.item._fit
        affectee_ship = affectee_fit.ship
        if affectee_ship in self.__affectees:
            return affectee_ship,
        else:
            return ()

    def __get_local_affectees_other(self, affector_spec):
        return [i for i in affector_spec.item._others if i in self.__affectees]

    __local_affectees_getters = {
        ModDomain.self: __get_local_affectees_self,
        ModDomain.character: __get_local_affectees_character,
        ModDomain.ship: __get_local_affectees_ship,
        ModDomain.other: __get_local_affectees_other}

    def __get_affectees_domain(self, _, affectee_domain, affectee_fits):
        affectee_items = set()
        storage = self.__affectees_domain
        for affectee_fit in affectee_fits:
            key = (affectee_fit, affectee_domain)
            affectee_items.update(storage.get(key, ()))
        return affectee_items

    def __get_affectees_domain_group(
        self, affector_spec, affectee_domain, affectee_fits
    ):
        affectee_group_id = affector_spec.modifier.affectee_filter_extra_arg
        affectee_items = set()
        storage = self.__affectees_domain_group
        for affectee_fit in affectee_fits:
            key = (affectee_fit, affectee_domain, affectee_group_id)
            affectee_items.update(storage.get(key, ()))
        return affectee_items

    def __get_affectees_domain_skillrq(
        self, affector_spec, affectee_domain, affectee_fits
    ):
        affectee_srq_type_id = affector_spec.modifier.affectee_filter_extra_arg
        if affectee_srq_type_id == EosTypeId.current_self:
            affectee_srq_type_id = affector_spec.item._type_id
        affectee_items = set()
        storage = self.__affectees_domain_skillrq
        for affectee_fit in affectee_fits:
            key = (affectee_fit, affectee_domain, affectee_srq_type_id)
            affectee_items.update(storage.get(key, ()))
        return affectee_items

    def __get_affectees_owner_skillrq(self, affector_spec, _, affectee_fits):
        affectee_srq_type_id = affector_spec.modifier.affectee_filter_extra_arg
        if affectee_srq_type_id == EosTypeId.current_self:
            affectee_srq_type_id = affector_spec.item._type_id
        affectee_items = set()
        storage = self.__affectees_owner_skillrq
        for affectee_fit in affectee_fits:
            key = (affectee_fit, affectee_srq_type_id)
            affectee_items.update(storage.get(key, ()))
        return affectee_items

    __affectees_getters = {
        ModAffecteeFilter.domain: __get_affectees_domain,
        ModAffecteeFilter.domain_group: __get_affectees_domain_group,
        ModAffecteeFilter.domain_skillrq: __get_affectees_domain_skillrq,
        ModAffecteeFilter.owner_skillrq: __get_affectees_owner_skillrq}

    # Helpers for affectee registering/unregistering
    def __get_affectee_storages(self, affectee_fit, affectee_item):
        """Return all places where passed affectee item should be stored.

        Returns:
            Iterable with multiple elements, where each element is tuple in
            (key, affectee map) format.
        """
        storages = []
        affectee_domain = affectee_item._modifier_domain
        if affectee_domain is not None:
            # Domain
            key = (affectee_fit, affectee_domain)
            storage = self.__affectees_domain
            storages.append((key, storage))
            # Domain and group
            affectee_group_id = affectee_item._type.group_id
            if affectee_group_id is not None:
                key = (affectee_fit, affectee_domain, affectee_group_id)
                storage = self.__affectees_domain_group
                storages.append((key, storage))
            # Domain and skill requirement
            storage = self.__affectees_domain_skillrq
            for affectee_srq_type_id in affectee_item._type.required_skills:
                key = (affectee_fit, affectee_domain, affectee_srq_type_id)
                storages.append((key, storage))
        # Owner-modifiable and skill requirement
        if affectee_item._owner_modifiable:
            storage = self.__affectees_owner_skillrq
            for affectee_srq_type_id in affectee_item._type.required_skills:
                key = (affectee_fit, affectee_srq_type_id)
                storages.append((key, storage))
        return storages

    def __activate_special_affector_specs(self, affectee_fit, affectee_item):
        """Activate special affector specs which should affect passed item."""
        awaiting_to_activate = set()
        for affector_spec in self.__affectors_item_awaiting.get(
            affectee_fit, ()
        ):
            affectee_domain = affector_spec.modifier.affectee_domain
            # Ship
            if (
                affectee_domain == ModDomain.ship and
                isinstance(affectee_item, Ship)
            ):
                awaiting_to_activate.add(affector_spec)
            # Character
            elif (
                affectee_domain == ModDomain.character and
                isinstance(affectee_item, Character)
            ):
                awaiting_to_activate.add(affector_spec)
            # Self
            elif (
                affectee_domain == ModDomain.self and
                affectee_item is affector_spec.item
            ):
                awaiting_to_activate.add(affector_spec)
        # Move awaiting affector specs from awaiting storage to active storage
        if awaiting_to_activate:
            self.__affectors_item_awaiting.rm_data_set(
                affectee_fit, awaiting_to_activate)
            self.__affectors_item_active.add_data_set(
                affectee_item, awaiting_to_activate)
        # Other
        other_to_activate = set()
        for affector_item, affector_specs in (
            self.__affectors_item_other.items()
        ):
            if affectee_item in affector_item._others:
                other_to_activate.update(affector_specs)
        # Just add affector specs to active storage, 'other' affector specs
        # should never be removed from 'other'-specific storage
        if other_to_activate:
            self.__affectors_item_active.add_data_set(
                affectee_item, other_to_activate)

    def __deactivate_special_affector_specs(self, affectee_fit, affectee_item):
        """Deactivate special affector specs which affect passed item."""
        if affectee_item not in self.__affectors_item_active:
            return
        awaitable_to_deactivate = set()
        for affector_spec in (
            self.__affectors_item_active.get(affectee_item, ())
        ):
            if affector_spec.modifier.affectee_domain in (
                ModDomain.ship, ModDomain.character, ModDomain.self
            ):
                awaitable_to_deactivate.add(affector_spec)
        # Remove all affector specs influencing this item directly, including
        # 'other' affectors
        del self.__affectors_item_active[affectee_item]
        # And make sure awaitable affectors become awaiting - moved to
        # appropriate container for future use
        if awaitable_to_deactivate:
            self.__affectors_item_awaiting.add_data_set(
                affectee_fit, awaitable_to_deactivate)

    # Helpers for affector spec registering/unregistering
    def __get_local_affector_storages(self, affector_spec):
        """Get places where passed local affector spec should be stored.

        Raises:
            UnexpectedDomainError: If modifier affectee domain is not supported
                for context of passed affector spec.
            UnknownAffecteeFilterError: If modifier affectee filter type is not
                supported.
        """
        affectee_filter = affector_spec.modifier.affectee_filter
        if affectee_filter == ModAffecteeFilter.item:
            affectee_domain = affector_spec.modifier.affectee_domain
            try:
                getter = self.__local_affector_storages_getters[affectee_domain]
            except KeyError as e:
                raise UnexpectedDomainError(affectee_domain) from e
            return getter(self, affector_spec)
        else:
            try:
                getter = self.__affector_storages_getters[affectee_filter]
            except KeyError as e:
                raise UnknownAffecteeFilterError(affectee_filter) from e
            affectee_domain = self.__resolve_local_domain(affector_spec)
            affectee_fits = affector_spec.item._fit,
            return getter(self, affector_spec, affectee_domain, affectee_fits)

    def __get_projected_affector_storages(self, affector_spec, tgt_items):
        """Get places where passed projected affector spec should be stored.

        Raises:
            UnknownAffecteeFilterError: If modifier affectee filter type is not
                supported.
        """
        affectee_filter = affector_spec.modifier.affectee_filter
        # Modifier affects just targeted items directly
        if affectee_filter == ModAffecteeFilter.item:
            storages = []
            storage = self.__affectors_item_active
            for tgt_item in tgt_items:
                if tgt_item in self.__affectees:
                    key = tgt_item
                    storages.append((key, storage))
            return storages
        # Modifier affects multiple items via affectee filter
        else:
            try:
                getter = self.__affector_storages_getters[affectee_filter]
            except KeyError as e:
                raise UnknownAffecteeFilterError(affectee_filter) from e
            affectee_domain = ModDomain.ship
            affectee_fits = {i._fit for i in tgt_items if isinstance(i, Ship)}
            return getter(self, affector_spec, affectee_domain, affectee_fits)

    def __get_local_affector_storages_self(self, affector_spec):
        affectee_item = affector_spec.item
        if affectee_item in self.__affectees:
            key = affectee_item
            storage = self.__affectors_item_active
        else:
            key = affectee_item._fit
            storage = self.__affectors_item_awaiting
        return (key, storage),

    def __get_local_affector_storages_character(self, affector_spec):
        affectee_fit = affector_spec.item._fit
        affectee_character = affectee_fit.character
        if affectee_character in self.__affectees:
            key = affectee_character
            storage = self.__affectors_item_active
        else:
            key = affectee_fit
            storage = self.__affectors_item_awaiting
        return (key, storage),

    def __get_local_affector_storages_ship(self, affector_spec):
        affectee_fit = affector_spec.item._fit
        affectee_ship = affectee_fit.ship
        if affectee_ship in self.__affectees:
            key = affectee_ship
            storage = self.__affectors_item_active
        else:
            key = affectee_fit
            storage = self.__affectors_item_awaiting
        return (key, storage),

    def __get_local_affector_storages_other(self, affector_spec):
        # Affectors with 'other' modifiers are always stored in their special
        # place
        storages = [(affector_spec.item, self.__affectors_item_other)]
        # And all those which have valid affectee item are also stored in
        # storage for active direct affectors
        storage = self.__affectors_item_active
        for other_item in affector_spec.item._others:
            if other_item in self.__affectees:
                key = other_item
                storages.append((key, storage))
        return storages

    __local_affector_storages_getters = {
        ModDomain.self: __get_local_affector_storages_self,
        ModDomain.character: __get_local_affector_storages_character,
        ModDomain.ship: __get_local_affector_storages_ship,
        ModDomain.other: __get_local_affector_storages_other}

    def __get_affector_storages_domain(self, _, affectee_domain, affectee_fits):
        storages = []
        storage = self.__affectors_domain
        for affectee_fit in affectee_fits:
            key = (affectee_fit, affectee_domain)
            storages.append((key, storage))
        return storages

    def __get_affector_storages_domain_group(
        self, affector_spec, affectee_domain, affectee_fits
    ):
        affectee_group_id = affector_spec.modifier.affectee_filter_extra_arg
        storages = []
        storage = self.__affectors_domain_group
        for affectee_fit in affectee_fits:
            key = (affectee_fit, affectee_domain, affectee_group_id)
            storages.append((key, storage))
        return storages

    def __get_affector_storages_domain_skillrq(
        self, affector_spec, affectee_domain, affectee_fits
    ):
        affectee_srq_type_id = affector_spec.modifier.affectee_filter_extra_arg
        if affectee_srq_type_id == EosTypeId.current_self:
            affectee_srq_type_id = affector_spec.item._type_id
        storages = []
        storage = self.__affectors_domain_skillrq
        for affectee_fit in affectee_fits:
            key = (affectee_fit, affectee_domain, affectee_srq_type_id)
            storages.append((key, storage))
        return storages

    def __get_affector_storages_owner_skillrq(
        self, affector_spec, _, affectee_fits
    ):
        affectee_srq_type_id = affector_spec.modifier.affectee_filter_extra_arg
        if affectee_srq_type_id == EosTypeId.current_self:
            affectee_srq_type_id = affector_spec.item._type_id
        storages = []
        storage = self.__affectors_owner_skillrq
        for affectee_fit in affectee_fits:
            key = (affectee_fit, affectee_srq_type_id)
            storages.append((key, storage))
        return storages

    __affector_storages_getters = {
        ModAffecteeFilter.domain:
            __get_affector_storages_domain,
        ModAffecteeFilter.domain_group:
            __get_affector_storages_domain_group,
        ModAffecteeFilter.domain_skillrq:
            __get_affector_storages_domain_skillrq,
        ModAffecteeFilter.owner_skillrq:
            __get_affector_storages_owner_skillrq}

    # Shared helpers
    def __resolve_local_domain(self, affector_spec):
        """Convert relative domain into absolute for local affector spec.

        Applicable only to en-masse modifications - that is, when modification
        affects multiple items in affectee domain.

        Raises:
            UnexpectedDomainError: If modifier affectee domain is not supported.
        """
        affector_item = affector_spec.item
        affectee_domain = affector_spec.modifier.affectee_domain
        if affectee_domain == ModDomain.self:
            if isinstance(affector_item, Ship):
                return ModDomain.ship
            elif isinstance(affector_item, Character):
                return ModDomain.character
            else:
                raise UnexpectedDomainError(affectee_domain)
        # Just return untouched domain for all other valid cases. Valid cases
        # include 'globally' visible (within the fit scope) domains only. I.e.
        # if item on fit refers this affectee domain, it should always refer the
        # same affectee item regardless of position of source item.
        elif affectee_domain in (ModDomain.character, ModDomain.ship):
            return affectee_domain
        # Raise error if domain is invalid
        else:
            raise UnexpectedDomainError(affectee_domain)

    def __handle_affector_spec_errors(self, error, affector_spec):
        """Handles exceptions related to affector spec.

        Multiple register methods which get data based on passed affector spec
        raise similar exceptions. To handle them in consistent fashion, it is
        done from this method. If error cannot be handled by the method, it is
        re-raised.
        """
        if isinstance(error, UnexpectedDomainError):
            msg = (
                'malformed modifier on item type {}: '
                'unsupported affectee domain {}'
            ).format(affector_spec.item._type_id, error.args[0])
            logger.warning(msg)
        elif isinstance(error, UnknownAffecteeFilterError):
            msg = (
                'malformed modifier on item type {}: invalid affectee filter {}'
            ).format(affector_spec.item._type_id, error.args[0])
            logger.warning(msg)
        else:
            raise error
コード例 #11
0
ファイル: slot_index.py プロジェクト: pyfa-org/eos
class SlotIndexRestrictionRegister(BaseRestrictionRegister, metaclass=ABCMeta):
    """Base class for all slot index restrictions.

    It doesn't allow multiple items to take the same numbered slot.
    """
    def __init__(self, fit):
        # All items which possess index of slot are stored in this container
        # Format: {slot index: {items}}
        self.__index_item_map = KeyedStorage()
        fit._subscribe(self, self._handler_map.keys())

    @property
    @abstractmethod
    def _slot_index_attr_id(self):
        """This attribute's value on item represents index of slot."""
        ...

    @property
    @abstractmethod
    def _item_class(self):
        """Items belonging to this class are restricted."""
        ...

    def _handle_item_loaded(self, msg):
        item = msg.item
        # Skip items which do not belong to specified class. Initially there was
        # no such check, but there was issue with Amarr Battlecruisers skill. As
        # of 2018-09-19, it still has subSystemSlot attribute with value 125,
        # which overlaps with t3c core subsystems
        if not isinstance(item, self._item_class):
            return
        # Skip items which don't have index specified
        slot_index = item._type_attrs.get(self._slot_index_attr_id)
        if slot_index is None:
            return
        self.__index_item_map.add_data_entry(slot_index, item)

    def _handle_item_unloaded(self, msg):
        item = msg.item
        if not isinstance(item, self._item_class):
            return
        slot_index = item._type_attrs.get(self._slot_index_attr_id)
        if slot_index is None:
            return
        self.__index_item_map.rm_data_entry(slot_index, item)

    _handler_map = {
        ItemLoaded: _handle_item_loaded,
        ItemUnloaded: _handle_item_unloaded
    }

    def validate(self):
        tainted_items = {}
        for slot_index, slot_index_items in self.__index_item_map.items():
            # If more than one item occupies the same slot, all items in this
            # slot are tainted
            if len(slot_index_items) > 1:
                for item in slot_index_items:
                    tainted_items[item] = SlotIndexErrorData(
                        slot_index=slot_index)
        if tainted_items:
            raise RestrictionValidationError(tainted_items)
コード例 #12
0
ファイル: service.py プロジェクト: miceky4/eos
class CalculationService(BaseSubscriber):
    """Service which supports attribute calculation.

    This class collects data about various items and relations between them, and
    via exposed methods which provice data about these connections helps
    attribute map to calculate modified attribute values.
    """
    def __init__(self, solar_system):
        self.__solar_system = solar_system
        self.__affections = AffectionRegister()
        self.__projections = ProjectionRegister()
        # Format: {projector: {modifiers}}
        self.__warfare_buffs = KeyedStorage()
        # Container with affector specs which will receive messages
        # Format: {message type: set(affector specs)}
        self.__subscribed_affectors = KeyedStorage()

    def get_modifications(self, affectee_item, affectee_attr_id):
        """Get modifications of affectee attribute on affectee item.

        Args:
            affectee_item: Item, for which we're getting modifications.
            affectee_attr_id: Affectee attribute ID; only modifications which
                influence attribute with this ID will be returned.

        Returns:
            Set with tuples in (modification operator, modification value,
            resistance value, affector item) format.
        """
        # Use list because we can have multiple tuples with the same values
        # as valid configuration
        mods = []
        for affector_spec in self.__affections.get_affector_specs(
                affectee_item):
            affector_modifier = affector_spec.modifier
            affector_item = affector_spec.item
            if affector_modifier.affectee_attr_id != affectee_attr_id:
                continue
            try:
                mod_op, mod_value, mod_aggregate_mode, mod_aggregate_key = (
                    affector_modifier.get_modification(affector_item))
            # Do nothing here - errors should be logged in modification
            # getter or even earlier
            except ModificationCalculationError:
                continue
            # Get resistance value
            resist_attr_id = affector_spec.effect.resist_attr_id
            carrier_item = affectee_item._solsys_carrier
            if resist_attr_id and carrier_item is not None:
                try:
                    resist_value = carrier_item.attrs[resist_attr_id]
                except KeyError:
                    resist_value = 1
            else:
                resist_value = 1
            mods.append((mod_op, mod_value, resist_value, mod_aggregate_mode,
                         mod_aggregate_key, affector_item))
        return mods

    # Handle fits
    def _handle_fit_added(self, fit):
        fit._subscribe(self, self._handler_map.keys())

    def _handle_fit_removed(self, fit):
        fit._unsubscribe(self, self._handler_map.keys())

    # Handle item changes which are significant for calculator
    def _handle_fleet_fit_added(self, msg):
        fits_effect_applications = {}
        for projector in self.__projections.get_projectors():
            if not isinstance(projector.effect, WarfareBuffEffect):
                continue
            projector_fit = projector.item._fit
            # Affect this fit by buffs existing in fleet
            if (msg.fit.ship is not None
                    and projector_fit.fleet is msg.fit.fleet):
                fits_effect_applications.setdefault(projector_fit, []).append(
                    (projector, [msg.fit.ship]))
            # Affect other fits by buffs from this fit
            if projector_fit is msg.fit:
                for fit in msg.fit.fleet.fits:
                    if fit is msg.fit:
                        continue
                    fits_effect_applications.setdefault(projector_fit,
                                                        []).append(
                                                            (projector,
                                                             [fit.ship]))
        # Apply warfare buffs
        if fits_effect_applications:
            for fit, effect_applications in fits_effect_applications.items():
                msgs = []
                for projector, tgt_items in effect_applications:
                    msgs.append(
                        EffectApplied(projector.item, projector.effect.id,
                                      tgt_items))
                fit._publish_bulk(msgs)

    def _handle_fleet_fit_removed(self, msg):
        fits_effect_unapplications = {}
        for projector in self.__projections.get_projectors():
            if not isinstance(projector.effect, WarfareBuffEffect):
                continue
            projector_fit = projector.item._fit
            # Unaffect this fit by buffs existing in fleet
            if (msg.fit.ship is not None
                    and projector_fit.fleet is msg.fit.fleet):
                fits_effect_unapplications.setdefault(projector_fit,
                                                      []).append(
                                                          (projector,
                                                           [msg.fit.ship]))
            # Unaffect other fits by buffs from this fit
            if projector_fit is msg.fit:
                for fit in msg.fit.fleet.fits:
                    if fit is msg.fit:
                        continue
                    fits_effect_unapplications.setdefault(projector_fit,
                                                          []).append(
                                                              (projector,
                                                               [fit.ship]))
        # Unapply warfare buffs
        if fits_effect_unapplications:
            for fit, effect_unapplications in (
                    fits_effect_unapplications.items()):
                msgs = []
                for projector, tgt_items in effect_unapplications:
                    msgs.append(
                        EffectUnapplied(projector.item, projector.effect.id,
                                        tgt_items))
                fit._publish_bulk(msgs)

    def _handle_item_loaded(self, msg):
        item = msg.item
        self.__affections.register_affectee_item(item)
        if isinstance(item, SolarSystemItemMixin):
            self.__projections.register_solsys_item(item)

    def _handle_item_unloaded(self, msg):
        item = msg.item
        self.__affections.unregister_affectee_item(item)
        if isinstance(item, SolarSystemItemMixin):
            self.__projections.unregister_solsys_item(item)

    def _handle_effects_started(self, msg):
        item = msg.item
        effect_ids = msg.effect_ids
        attr_changes = {}
        for affector_spec in self.__generate_local_affector_specs(
                item, effect_ids):
            # Register the affector spec
            if isinstance(affector_spec.modifier, BasePythonModifier):
                self.__subscribe_python_affector_spec(msg.fit, affector_spec)
            self.__affections.register_local_affector_spec(affector_spec)
            # Clear values of attributes dependent on the affector spec
            for affectee_item in self.__affections.get_local_affectee_items(
                    affector_spec):
                attr_id = affector_spec.modifier.affectee_attr_id
                if affectee_item.attrs._force_recalc(attr_id):
                    attr_ids = attr_changes.setdefault(affectee_item, set())
                    attr_ids.add(attr_id)
        # Register projectors
        for projector in self.__generate_projectors(item, effect_ids):
            self.__projections.register_projector(projector)
        # Register warfare buffs
        effect_applications = []
        item_fleet = msg.fit.fleet
        for effect_id in effect_ids:
            effect = item._type_effects[effect_id]
            if not isinstance(effect, WarfareBuffEffect):
                continue
            projector = Projector(item, effect)
            for buff_id_attr_id in WARFARE_BUFF_ATTRS:
                try:
                    buff_id = item.attrs[buff_id_attr_id]
                except KeyError:
                    continue
                getter = (self.__solar_system.source.cache_handler.
                          get_buff_templates)
                try:
                    buff_templates = getter(buff_id)
                except BuffTemplatesFetchError:
                    continue
                affector_attr_id = WARFARE_BUFF_ATTRS[buff_id_attr_id]
                if not buff_templates:
                    continue
                for buff_template in buff_templates:
                    modifier = DogmaModifier._make_from_buff_template(
                        buff_template, affector_attr_id)
                    affector_spec = AffectorSpec(item, effect, modifier)
                    self.__warfare_buffs.add_data_entry(
                        projector, affector_spec)
                tgt_ships = []
                for tgt_fit in self.__solar_system.fits:
                    if (tgt_fit is msg.fit
                            or (item_fleet is not None
                                and tgt_fit.fleet is item_fleet)):
                        tgt_ship = tgt_fit.ship
                        if tgt_ship is not None:
                            tgt_ships.append(tgt_ship)
                effect_applications.append((projector, tgt_ships))
        if attr_changes:
            self.__publish_attr_changes(attr_changes)
        # Apply warfare buffs
        if effect_applications:
            msgs = []
            for projector, tgt_items in effect_applications:
                msgs.append(
                    EffectApplied(projector.item, projector.effect.id,
                                  tgt_items))
            msg.fit._publish_bulk(msgs)

    def _handle_effects_stopped(self, msg):
        # Unregister warfare buffs
        effect_unapplications = []
        for projector in self.__generate_projectors(msg.item, msg.effect_ids):
            if projector not in self.__warfare_buffs:
                continue
            tgt_ships = self.__projections.get_projector_tgts(projector)
            effect_unapplications.append((projector, tgt_ships))
            del self.__warfare_buffs[projector]
        # Unapply warfare buffs
        if effect_unapplications:
            msgs = []
            for projector, tgt_items in effect_unapplications:
                msgs.append(
                    EffectUnapplied(projector.item, projector.effect.id,
                                    tgt_items))
            msg.fit._publish_bulk(msgs)
        attr_changes = {}
        # Remove values of affectee attributes
        for affector_spec in self.__generate_local_affector_specs(
                msg.item, msg.effect_ids):
            # Clear values of attributes dependent on the affector spec
            for affectee_item in self.__affections.get_local_affectee_items(
                    affector_spec):
                attr_id = affector_spec.modifier.affectee_attr_id
                if affectee_item.attrs._force_recalc(attr_id):
                    attr_ids = attr_changes.setdefault(affectee_item, set())
                    attr_ids.add(attr_id)
            # Unregister the affector spec
            self.__affections.unregister_local_affector_spec(affector_spec)
            if isinstance(affector_spec.modifier, BasePythonModifier):
                self.__unsubscribe_python_affector_spec(msg.fit, affector_spec)
        # Unregister projectors
        for projector in self.__generate_projectors(msg.item, msg.effect_ids):
            self.__projections.unregister_projector(projector)
        if attr_changes:
            self.__publish_attr_changes(attr_changes)

    def _handle_effect_applied(self, msg):
        attr_changes = {}
        for affector_spec in self.__generate_projected_affectors(
                msg.item, (msg.effect_id, )):
            # Register the affector spec
            self.__affections.register_projected_affector_spec(
                affector_spec, msg.tgt_items)
            # Clear values of attributes dependent on the affector spec
            for affectee_item in self.__affections.get_projected_affectee_items(
                    affector_spec, msg.tgt_items):
                attr_id = affector_spec.modifier.affectee_attr_id
                if affectee_item.attrs._force_recalc(attr_id):
                    attr_ids = attr_changes.setdefault(affectee_item, set())
                    attr_ids.add(attr_id)
        # Apply projector
        for projector in self.__generate_projectors(msg.item,
                                                    (msg.effect_id, )):
            self.__projections.apply_projector(projector, msg.tgt_items)
        if attr_changes:
            self.__publish_attr_changes(attr_changes)

    def _handle_effect_unapplied(self, msg):
        attr_changes = {}
        for affector_spec in self.__generate_projected_affectors(
                msg.item, (msg.effect_id, )):
            # Clear values of attributes dependent on the affector spec
            for affectee_item in self.__affections.get_projected_affectee_items(
                    affector_spec, msg.tgt_items):
                attr_id = affector_spec.modifier.affectee_attr_id
                if affectee_item.attrs._force_recalc(attr_id):
                    attr_ids = attr_changes.setdefault(affectee_item, set())
                    attr_ids.add(attr_id)
            # Unregister the affector spec
            self.__affections.unregister_projected_affector(
                affector_spec, msg.tgt_items)
        # Un-apply projector
        for projector in self.__generate_projectors(msg.item,
                                                    (msg.effect_id, )):
            self.__projections.unapply_projector(projector, msg.tgt_items)
        if attr_changes:
            self.__publish_attr_changes(attr_changes)

    # Methods to clear calculated child attributes when parent attributes change
    def _revise_regular_attr_dependents(self, msg):
        """Remove calculated attribute values which rely on passed attribute.

        Removing them allows to recalculate updated value. Here we process all
        regular dependents, which include dependencies specified via capped
        attribute map and via affector specs with dogma modifiers. Affector
        specs with python modifiers are processed separately.
        """
        affections = self.__affections
        projections = self.__projections
        effect_unapplications = []
        # Unapply warfare buffs
        for item, attr_ids in msg.attr_changes.items():
            for effect in item._type_effects.values():
                projector = Projector(item, effect)
                if projector not in self.__warfare_buffs:
                    continue
                if not attr_ids.intersection(WARFARE_BUFF_ATTRS):
                    continue
                tgt_items = self.__projections.get_projector_tgts(projector)
                effect_unapplications.append((projector, tgt_items))
        msgs = []
        for projector, tgt_items in effect_unapplications:
            msgs.append(
                EffectUnapplied(projector.item, projector.effect.id,
                                tgt_items))
        msg.fit._publish_bulk(msgs)
        attr_changes = {}
        for item, attr_ids in msg.attr_changes.items():
            # Remove values of affectee attributes capped by the changing
            # attribute
            for attr_id in attr_ids:
                for capped_attr_id in item.attrs._cap_map.get(attr_id, ()):
                    if item.attrs._force_recalc(capped_attr_id):
                        attr_changes.setdefault(item,
                                                set()).add(capped_attr_id)
            # Force attribute recalculation when local affector spec
            # modification changes
            for affector_spec in self.__generate_local_affector_specs(
                    item, item._running_effect_ids):
                affector_modifier = affector_spec.modifier
                # Only dogma modifiers have source attribute specified, python
                # modifiers are processed separately
                if (not isinstance(affector_modifier, DogmaModifier)
                        or affector_modifier.affector_attr_id not in attr_ids):
                    continue
                # Remove values
                for affectee_item in affections.get_local_affectee_items(
                        affector_spec):
                    attr_id = affector_modifier.affectee_attr_id
                    if affectee_item.attrs._force_recalc(attr_id):
                        attr_changes.setdefault(affectee_item,
                                                set()).add(attr_id)
            # Force attribute recalculation when projected affector spec
            # modification changes
            for projector in self.__generate_projectors(
                    item, item._running_effect_ids):
                tgt_items = projections.get_projector_tgts(projector)
                # When projector doesn't target any items, then we do not need
                # to clean anything
                if not tgt_items:
                    continue
                for affector_spec in self.__generate_projected_affectors(
                        item, (projector.effect.id, )):
                    affector_modifier = affector_spec.modifier
                    # Only dogma modifiers have source attribute specified,
                    # python modifiers are processed separately
                    if (not isinstance(affector_modifier, DogmaModifier)
                            or affector_modifier.affector_attr_id
                            not in attr_ids):
                        continue
                    for affectee_item in (
                            affections.get_projected_affectee_items(
                                affector_spec, tgt_items)):
                        attr_id = affector_modifier.affectee_attr_id
                        if affectee_item.attrs._force_recalc(attr_id):
                            attr_changes.setdefault(affectee_item,
                                                    set()).add(attr_id)
            # Force attribute recalculation if changed attribute defines
            # resistance to some effect
            for projector in projections.get_tgt_projectors(item):
                effect = projector.effect
                if effect.resist_attr_id not in attr_ids:
                    continue
                tgt_items = projections.get_projector_tgts(projector)
                for affector_spec in self.__generate_projected_affectors(
                        projector.item, (effect.id, )):
                    for affectee_item in (
                            affections.get_projected_affectee_items(
                                affector_spec, tgt_items)):
                        attr_id = affector_spec.modifier.affectee_attr_id
                        if affectee_item.attrs._force_recalc(attr_id):
                            attr_changes.setdefault(affectee_item,
                                                    set()).add(attr_id)
        # Unregister warfare buffs only after composing list of attributes we
        # should update
        for projector, tgt_items in effect_unapplications:
            del self.__warfare_buffs[projector]
        if attr_changes:
            self.__publish_attr_changes(attr_changes)
        # Register warfare buffs
        effect_applications = []
        for item, attr_ids in msg.attr_changes.items():
            if not attr_ids.intersection(WARFARE_BUFF_ATTRS):
                continue
            item_fleet = item._fit.fleet
            for effect_id in item._running_effect_ids:
                effect = item._type_effects[effect_id]
                if not isinstance(effect, WarfareBuffEffect):
                    continue
                projector = Projector(item, effect)
                for buff_id_attr_id in WARFARE_BUFF_ATTRS:
                    try:
                        buff_id = item.attrs[buff_id_attr_id]
                    except KeyError:
                        continue
                    getter = (self.__solar_system.source.cache_handler.
                              get_buff_templates)
                    try:
                        buff_templates = getter(buff_id)
                    except BuffTemplatesFetchError:
                        continue
                    affector_attr_id = WARFARE_BUFF_ATTRS[buff_id_attr_id]
                    if not buff_templates:
                        continue
                    for buff_template in buff_templates:
                        modifier = DogmaModifier._make_from_buff_template(
                            buff_template, affector_attr_id)
                        affector_spec = AffectorSpec(item, effect, modifier)
                        self.__warfare_buffs.add_data_entry(
                            projector, affector_spec)
                    tgt_ships = []
                    for tgt_fit in self.__solar_system.fits:
                        if (tgt_fit is msg.fit
                                or (item_fleet is not None
                                    and tgt_fit.fleet is item_fleet)):
                            tgt_ship = tgt_fit.ship
                            if tgt_ship is not None:
                                tgt_ships.append(tgt_ship)
                    effect_applications.append((projector, tgt_ships))
        if attr_changes:
            self.__publish_attr_changes(attr_changes)
        # Apply warfare buffs
        if effect_applications:
            msgs = []
            for projector, tgt_items in effect_applications:
                msgs.append(
                    EffectApplied(projector.item, projector.effect.id,
                                  tgt_items))
            msg.fit._publish_bulk(msgs)

    def _revise_python_attr_dependents(self, msg):
        """Remove calculated attribute values when necessary.

        Here we go through python modifiers, deliver to them message, and if,
        based on contents of the message, they decide that calculated values
        should be removed, we remove values which depend on such modifiers.
        """
        attr_changes = {}
        # If there's no subscribed affector specs for received message type, do
        # nothing
        msg_type = type(msg)
        if msg_type not in self.__subscribed_affectors:
            return
        # Otherwise, ask modifier if value of attribute it calculates may
        # change, and force recalculation if answer is yes
        for affector_spec in self.__subscribed_affectors[msg_type]:
            if not affector_spec.modifier.revise_modification(
                    msg, affector_spec.item):
                continue
            for affectee_item in self.__affections.get_local_affectee_items(
                    affector_spec):
                attr_id = affector_spec.modifier.affectee_attr_id
                if affectee_item.attrs._force_recalc(attr_id):
                    attr_ids = attr_changes.setdefault(affectee_item, set())
                    attr_ids.add(attr_id)
        if attr_changes:
            self.__publish_attr_changes(attr_changes)

    # Message routing
    _handler_map = {
        FleetFitAdded: _handle_fleet_fit_added,
        FleetFitRemoved: _handle_fleet_fit_removed,
        ItemLoaded: _handle_item_loaded,
        ItemUnloaded: _handle_item_unloaded,
        EffectsStarted: _handle_effects_started,
        EffectsStopped: _handle_effects_stopped,
        EffectApplied: _handle_effect_applied,
        EffectUnapplied: _handle_effect_unapplied,
        AttrsValueChanged: _revise_regular_attr_dependents
    }

    def _notify(self, msg):
        BaseSubscriber._notify(self, msg)
        # Relay all messages to python modifiers, as in case of python modifiers
        # any message may result in deleting dependent attributes
        self._revise_python_attr_dependents(msg)

    # Affector-related methods
    def __generate_local_affector_specs(self, item, effect_ids):
        """Get local affector specs for passed item and effects."""
        affector_specs = set()
        item_effects = item._type_effects
        for effect_id in effect_ids:
            effect = item_effects[effect_id]
            for modifier in effect.local_modifiers:
                affector_spec = AffectorSpec(item, effect, modifier)
                affector_specs.add(affector_spec)
        return affector_specs

    def __generate_projected_affectors(self, item, effect_ids):
        """Get projected affector specs for passed item and effects."""
        affector_specs = set()
        item_effects = item._type_effects
        for effect_id in effect_ids:
            effect = item_effects[effect_id]
            projector = Projector(item, effect)
            if projector in self.__warfare_buffs:
                affector_specs.update(self.__warfare_buffs[projector])
            for modifier in effect.projected_modifiers:
                affector_spec = AffectorSpec(item, effect, modifier)
                affector_specs.add(affector_spec)
        return affector_specs

    def __subscribe_python_affector_spec(self, fit, affector_spec):
        """Subscribe affector spec with python modifier."""
        to_subscribe = set()
        for msg_type in affector_spec.modifier.revise_msg_types:
            # Subscribe service to new message type only if there's no such
            # subscription yet
            if (msg_type not in self._handler_map
                    and msg_type not in self.__subscribed_affectors):
                to_subscribe.add(msg_type)
            # Add affector spec to subscriber map to let it receive messages
            self.__subscribed_affectors.add_data_entry(msg_type, affector_spec)
        if to_subscribe:
            fit._subscribe(self, to_subscribe)

    def __unsubscribe_python_affector_spec(self, fit, affector_spec):
        """Unsubscribe affector spec with python modifier."""
        to_ubsubscribe = set()
        for msg_type in affector_spec.modifier.revise_msg_types:
            # Make sure affector spec will not receive messages anymore
            self.__subscribed_affectors.rm_data_entry(msg_type, affector_spec)
            # Unsubscribe service from message type if there're no recipients
            # anymore
            if (msg_type not in self._handler_map
                    and msg_type not in self.__subscribed_affectors):
                to_ubsubscribe.add(msg_type)
        if to_ubsubscribe:
            fit._unsubscribe(self, to_ubsubscribe)

    # Warfare buffs-related methods

    # Projector-related methods
    def __generate_projectors(self, item, effect_ids):
        """Get projectors spawned by the item."""
        projectors = set()
        item_effects = item._type_effects
        for effect_id in effect_ids:
            effect = item_effects[effect_id]
            if (effect.category_id == EffectCategoryId.target
                    or isinstance(effect, WarfareBuffEffect)):
                projector = Projector(item, effect)
                projectors.add(projector)
        return projectors

    # Auxiliary methods
    def __publish_attr_changes(self, attr_changes):
        # Format: {fit: {item: {attr_ids}}}
        fit_changes_regular = {}
        # Format: {fit: {item: {attr_ids}}}
        fit_changes_masked = {}
        for item, attr_ids in attr_changes.items():
            item_fit = item._fit
            item_attr_overrides = item.attrs._override_callbacks
            item_changes_regular = attr_ids.difference(item_attr_overrides)
            item_changes_masked = attr_ids.intersection(item_attr_overrides)
            if item_changes_regular:
                fit_changes_regular.setdefault(item_fit,
                                               {})[item] = item_changes_regular
            if item_changes_masked:
                fit_changes_masked.setdefault(item_fit,
                                              {})[item] = item_changes_masked
        # Format: {fit, [messages]}
        fits_msgs = {}
        for fit, attr_changes in fit_changes_regular.items():
            msg = AttrsValueChanged(attr_changes)
            fits_msgs.setdefault(fit, []).append(msg)
        for fit, attr_changes in fit_changes_masked.items():
            msg = AttrsValueChangedMasked(attr_changes)
            fits_msgs.setdefault(fit, []).append(msg)
        for fit, msgs in fits_msgs.items():
            fit._publish_bulk(msgs)
コード例 #13
0
ファイル: slot_index.py プロジェクト: DarkFenX/eos
 def __init__(self, fit):
     # All items which possess index of slot are stored in this container
     # Format: {slot index: {items}}
     self.__index_item_map = KeyedStorage()
     fit._subscribe(self, self._handler_map.keys())
コード例 #14
0
ファイル: slot_index.py プロジェクト: DarkFenX/eos
class SlotIndexRestrictionRegister(BaseRestrictionRegister, metaclass=ABCMeta):
    """Base class for all slot index restrictions.

    It doesn't allow multiple items to take the same numbered slot.
    """

    def __init__(self, fit):
        # All items which possess index of slot are stored in this container
        # Format: {slot index: {items}}
        self.__index_item_map = KeyedStorage()
        fit._subscribe(self, self._handler_map.keys())

    @property
    @abstractmethod
    def _slot_index_attr_id(self):
        """This attribute's value on item represents index of slot."""
        ...

    @property
    @abstractmethod
    def _item_class(self):
        """Items belonging to this class are restricted."""
        ...

    def _handle_item_loaded(self, msg):
        item = msg.item
        # Skip items which do not belong to specified class. Initially there was
        # no such check, but there was issue with Amarr Battlecruisers skill. As
        # of 2018-09-19, it still has subSystemSlot attribute with value 125,
        # which overlaps with t3c core subsystems
        if not isinstance(item, self._item_class):
            return
        # Skip items which don't have index specified
        slot_index = item._type_attrs.get(self._slot_index_attr_id)
        if slot_index is None:
            return
        self.__index_item_map.add_data_entry(slot_index, item)

    def _handle_item_unloaded(self, msg):
        item = msg.item
        if not isinstance(item, self._item_class):
            return
        slot_index = item._type_attrs.get(self._slot_index_attr_id)
        if slot_index is None:
            return
        self.__index_item_map.rm_data_entry(slot_index, item)

    _handler_map = {
        ItemLoaded: _handle_item_loaded,
        ItemUnloaded: _handle_item_unloaded}

    def validate(self):
        tainted_items = {}
        for slot_index, slot_index_items in self.__index_item_map.items():
            # If more than one item occupies the same slot, all items in this
            # slot are tainted
            if len(slot_index_items) > 1:
                for item in slot_index_items:
                    tainted_items[item] = SlotIndexErrorData(
                        slot_index=slot_index)
        if tainted_items:
            raise RestrictionValidationError(tainted_items)
コード例 #15
0
 def __init__(self):
     self.__affections = AffectionRegister()
     self.__projections = ProjectionRegister()
     # Container with affector specs which will receive messages
     # Format: {message type: set(affector specs)}
     self.__subscribed_affectors = KeyedStorage()
コード例 #16
0
ファイル: slot_index.py プロジェクト: miceky4/eos
 def __init__(self, fit):
     # All items which possess index of slot are stored in this container
     # Format: {slot index: {items}}
     self.__index_item_map = KeyedStorage()
     fit._subscribe(self, self._handler_map.keys())
コード例 #17
0
ファイル: dmg_dealer.py プロジェクト: DarkFenX/eos
 def __init__(self, fit):
     # Format: {item:, {effect1, effect2}}
     self.__dmg_dealers = KeyedStorage()
     fit._subscribe(self, self._handler_map.keys())
コード例 #18
0
class AffectionRegister:
    """Keeps track of connections between affector specs and affectee items.

    Having information about such connections is a hard requirement for
    efficient partial attribute recalculation.
    """
    def __init__(self):
        # All known affectee items
        # Format: {affectee items}
        self.__affectees = set()

        # Items belonging to certain fit and domain
        # Format: {(affectee fit, affectee domain): {affectee items}}
        self.__affectees_domain = KeyedStorage()

        # Items belonging to certain fit, domain and group
        # Format: {(affectee fit, affectee domain, affectee group ID): {affectee
        # items}}
        self.__affectees_domain_group = KeyedStorage()

        # Items belonging to certain fit and domain, and having certain skill
        # requirement
        # Format: {(affectee fit, affectee domain, affectee skill requirement
        # type ID): {affectee items}}
        self.__affectees_domain_skillrq = KeyedStorage()

        # Owner-modifiable items which belong to certain fit and have certain
        # skill requirement
        # Format: {(affectee fit, affectee skill requirement type ID): {affectee
        # items}}
        self.__affectees_owner_skillrq = KeyedStorage()

        # Affector specs with modifiers which affect 'other' location are always
        # stored here, regardless if they actually affect something or not
        # Format: {affector item: {affector specs}}
        self.__affectors_item_other = KeyedStorage()

        # Affector specs which should affect only one item (ship, character or
        # self), when this item is not registered as affectee
        # Format: {affectee fit: {affector specs}}
        self.__affectors_item_awaiting = KeyedStorage()

        # All active affector specs which affect one specific item (via ship,
        # character, other reference or self) are kept here
        # Format: {affectee item: {affector specs}}
        self.__affectors_item_active = KeyedStorage()

        # Affector specs influencing all items belonging to certain fit and
        # domain
        # Format: {(affectee fit, affectee domain): {affector specs}}
        self.__affectors_domain = KeyedStorage()

        # Affector specs influencing items belonging to certain fit, domain and
        # group
        # Format: {(affectee fit, affectee domain, affectee group ID): {affector
        # specs}}
        self.__affectors_domain_group = KeyedStorage()

        # Affector specs influencing items belonging to certain fit and domain,
        # and having certain skill requirement
        # Format: {(affectee fit, affectee domain, affectee skill requirement
        # type ID): {affector specs}}
        self.__affectors_domain_skillrq = KeyedStorage()

        # Affector specs influencing owner-modifiable items belonging to certain
        # fit and having certain skill requirement
        # Format: {(affectee fit, affectee skill requirement type ID): {affector
        # specs}}
        self.__affectors_owner_skillrq = KeyedStorage()

    # Query methods
    def get_local_affectee_items(self, affector_spec):
        """Get iterable with items influenced by passed local affector spec."""
        try:
            affectee_filter = affector_spec.modifier.affectee_filter
            # Direct item modification needs to use local-specific getters
            if affectee_filter == ModAffecteeFilter.item:
                affectee_domain = affector_spec.modifier.affectee_domain
                try:
                    getter = self.__local_affectees_getters[affectee_domain]
                except KeyError as e:
                    raise UnexpectedDomainError(affectee_domain) from e
                return getter(self, affector_spec)
            # En-masse filtered modification can use shared affectee item
            # getters
            else:
                try:
                    getter = self.__affectees_getters[affectee_filter]
                except KeyError as e:
                    raise UnknownAffecteeFilterError(affectee_filter) from e
                affectee_domain = self.__resolve_local_domain(affector_spec)
                affectee_fits = affector_spec.item._fit,
                return getter(self, affector_spec, affectee_domain,
                              affectee_fits)
        except Exception as e:
            self.__handle_affector_spec_errors(e, affector_spec)
            return ()

    def get_projected_affectee_items(self, affector_spec, tgt_items):
        """Get iterable with items influenced by projected affector spec."""
        affectee_filter = affector_spec.modifier.affectee_filter
        # Return targeted items when modification affects just them directly
        if affectee_filter == ModAffecteeFilter.item:
            return {i for i in tgt_items if i in self.__affectees}
        # En-masse modifications of items located on targeted items use shared
        # affectee item getters
        else:
            try:
                getter = self.__affectees_getters[affectee_filter]
            except KeyError as e:
                raise UnknownAffecteeFilterError(affectee_filter) from e
            affectee_fits = {i._fit for i in tgt_items if isinstance(i, Ship)}
            return getter(self, affector_spec, ModDomain.ship, affectee_fits)

    def get_affector_specs(self, affectee_item):
        """Get all affector specs, which influence passed item."""
        affectee_fit = affectee_item._fit
        affector_specs = set()
        # Item
        affector_storage = self.__affectors_item_active
        key = affectee_item
        affector_specs.update(affector_storage.get(key, ()))
        affectee_domain = affectee_item._modifier_domain
        if affectee_domain is not None:
            # Domain
            affector_storage = self.__affectors_domain
            key = (affectee_fit, affectee_domain)
            affector_specs.update(affector_storage.get(key, ()))
            # Domain and group
            affector_storage = self.__affectors_domain_group
            key = (affectee_fit, affectee_domain, affectee_item._type.group_id)
            affector_specs.update(affector_storage.get(key, ()))
            # Domain and skill requirement
            affector_storage = self.__affectors_domain_skillrq
            for affectee_srq_type_id in affectee_item._type.required_skills:
                key = (affectee_fit, affectee_domain, affectee_srq_type_id)
                affector_specs.update(affector_storage.get(key, ()))
        # Owner-modifiable and skill requirement
        if affectee_item._owner_modifiable:
            affector_storage = self.__affectors_owner_skillrq
            for affectee_srq_type_id in affectee_item._type.required_skills:
                key = (affectee_fit, affectee_srq_type_id)
                affector_specs.update(affector_storage.get(key, ()))
        return affector_specs

    # Maintenance methods
    def register_affectee_item(self, affectee_item):
        """Add passed affectee item to the register.

        We track affectee items to efficiently update attributes when set of
        items influencing them changes.
        """
        self.__affectees.add(affectee_item)
        affectee_fit = affectee_item._fit
        for key, storage in self.__get_affectee_storages(
                affectee_fit, affectee_item):
            storage.add_data_entry(key, affectee_item)
        # Process special affector specs separately. E.g., when item like ship
        # is added, there might already be affector specs which should affect
        # it, and in this method we activate such affector specs
        self.__activate_special_affector_specs(affectee_fit, affectee_item)

    def unregister_affectee_item(self, affectee_item):
        """Remove passed affectee item from the register."""
        self.__affectees.remove(affectee_item)
        affectee_fit = affectee_item._fit
        for key, storage in self.__get_affectee_storages(
                affectee_fit, affectee_item):
            storage.rm_data_entry(key, affectee_item)
        # Deactivate all special affector specs for item being unregistered
        self.__deactivate_special_affector_specs(affectee_fit, affectee_item)

    def register_local_affector_spec(self, affector_spec):
        """Make the register aware of the local affector spec.

        It makes it possible for the affector spec to modify other items within
        its fit.
        """
        try:
            storages = self.__get_local_affector_storages(affector_spec)
        except Exception as e:
            self.__handle_affector_spec_errors(e, affector_spec)
        else:
            for key, storage in storages:
                storage.add_data_entry(key, affector_spec)

    def unregister_local_affector_spec(self, affector_spec):
        """Remove local affector spec from the register.

        It makes it impossible for the affector spec to modify any items.
        """
        try:
            storages = self.__get_local_affector_storages(affector_spec)
        except Exception as e:
            self.__handle_affector_spec_errors(e, affector_spec)
        else:
            for key, storage in storages:
                storage.rm_data_entry(key, affector_spec)

    def register_projected_affector_spec(self, affector_spec, tgt_items):
        """Make register aware that projected affector spec affects items.

        Should be called every time projected effect with modifiers is applied
        onto any items.
        """
        try:
            storages = self.__get_projected_affector_storages(
                affector_spec, tgt_items)
        except Exception as e:
            self.__handle_affector_spec_errors(e, affector_spec)
        else:
            for key, storage in storages:
                storage.add_data_entry(key, affector_spec)

    def unregister_projected_affector(self, affector_spec, tgt_items):
        """Remove effect of affector spec from items.

        Should be called every time projected effect with modifiers stops
        affecting any object.
        """
        try:
            storages = self.__get_projected_affector_storages(
                affector_spec, tgt_items)
        except Exception as e:
            self.__handle_affector_spec_errors(e, affector_spec)
        else:
            for key, storage in storages:
                storage.rm_data_entry(key, affector_spec)

    # Helpers for affectee getter
    def __get_local_affectees_self(self, affector_spec):
        return affector_spec.item,

    def __get_local_affectees_character(self, affector_spec):
        affectee_fit = affector_spec.item._fit
        affectee_character = affectee_fit.character
        if affectee_character in self.__affectees:
            return affectee_character,
        else:
            return ()

    def __get_local_affectees_ship(self, affector_spec):
        affectee_fit = affector_spec.item._fit
        affectee_ship = affectee_fit.ship
        if affectee_ship in self.__affectees:
            return affectee_ship,
        else:
            return ()

    def __get_local_affectees_other(self, affector_spec):
        return [i for i in affector_spec.item._others if i in self.__affectees]

    __local_affectees_getters = {
        ModDomain.self: __get_local_affectees_self,
        ModDomain.character: __get_local_affectees_character,
        ModDomain.ship: __get_local_affectees_ship,
        ModDomain.other: __get_local_affectees_other
    }

    def __get_affectees_domain(self, _, affectee_domain, affectee_fits):
        affectee_items = set()
        storage = self.__affectees_domain
        for affectee_fit in affectee_fits:
            key = (affectee_fit, affectee_domain)
            affectee_items.update(storage.get(key, ()))
        return affectee_items

    def __get_affectees_domain_group(self, affector_spec, affectee_domain,
                                     affectee_fits):
        affectee_group_id = affector_spec.modifier.affectee_filter_extra_arg
        affectee_items = set()
        storage = self.__affectees_domain_group
        for affectee_fit in affectee_fits:
            key = (affectee_fit, affectee_domain, affectee_group_id)
            affectee_items.update(storage.get(key, ()))
        return affectee_items

    def __get_affectees_domain_skillrq(self, affector_spec, affectee_domain,
                                       affectee_fits):
        affectee_srq_type_id = affector_spec.modifier.affectee_filter_extra_arg
        if affectee_srq_type_id == EosTypeId.current_self:
            affectee_srq_type_id = affector_spec.item._type_id
        affectee_items = set()
        storage = self.__affectees_domain_skillrq
        for affectee_fit in affectee_fits:
            key = (affectee_fit, affectee_domain, affectee_srq_type_id)
            affectee_items.update(storage.get(key, ()))
        return affectee_items

    def __get_affectees_owner_skillrq(self, affector_spec, _, affectee_fits):
        affectee_srq_type_id = affector_spec.modifier.affectee_filter_extra_arg
        if affectee_srq_type_id == EosTypeId.current_self:
            affectee_srq_type_id = affector_spec.item._type_id
        affectee_items = set()
        storage = self.__affectees_owner_skillrq
        for affectee_fit in affectee_fits:
            key = (affectee_fit, affectee_srq_type_id)
            affectee_items.update(storage.get(key, ()))
        return affectee_items

    __affectees_getters = {
        ModAffecteeFilter.domain: __get_affectees_domain,
        ModAffecteeFilter.domain_group: __get_affectees_domain_group,
        ModAffecteeFilter.domain_skillrq: __get_affectees_domain_skillrq,
        ModAffecteeFilter.owner_skillrq: __get_affectees_owner_skillrq
    }

    # Helpers for affectee registering/unregistering
    def __get_affectee_storages(self, affectee_fit, affectee_item):
        """Return all places where passed affectee item should be stored.

        Returns:
            Iterable with multiple elements, where each element is tuple in
            (key, affectee map) format.
        """
        storages = []
        affectee_domain = affectee_item._modifier_domain
        if affectee_domain is not None:
            # Domain
            key = (affectee_fit, affectee_domain)
            storage = self.__affectees_domain
            storages.append((key, storage))
            # Domain and group
            affectee_group_id = affectee_item._type.group_id
            if affectee_group_id is not None:
                key = (affectee_fit, affectee_domain, affectee_group_id)
                storage = self.__affectees_domain_group
                storages.append((key, storage))
            # Domain and skill requirement
            storage = self.__affectees_domain_skillrq
            for affectee_srq_type_id in affectee_item._type.required_skills:
                key = (affectee_fit, affectee_domain, affectee_srq_type_id)
                storages.append((key, storage))
        # Owner-modifiable and skill requirement
        if affectee_item._owner_modifiable:
            storage = self.__affectees_owner_skillrq
            for affectee_srq_type_id in affectee_item._type.required_skills:
                key = (affectee_fit, affectee_srq_type_id)
                storages.append((key, storage))
        return storages

    def __activate_special_affector_specs(self, affectee_fit, affectee_item):
        """Activate special affector specs which should affect passed item."""
        awaiting_to_activate = set()
        for affector_spec in self.__affectors_item_awaiting.get(
                affectee_fit, ()):
            affectee_domain = affector_spec.modifier.affectee_domain
            # Ship
            if (affectee_domain == ModDomain.ship
                    and isinstance(affectee_item, Ship)):
                awaiting_to_activate.add(affector_spec)
            # Character
            elif (affectee_domain == ModDomain.character
                  and isinstance(affectee_item, Character)):
                awaiting_to_activate.add(affector_spec)
            # Self
            elif (affectee_domain == ModDomain.self
                  and affectee_item is affector_spec.item):
                awaiting_to_activate.add(affector_spec)
        # Move awaiting affector specs from awaiting storage to active storage
        if awaiting_to_activate:
            self.__affectors_item_awaiting.rm_data_set(affectee_fit,
                                                       awaiting_to_activate)
            self.__affectors_item_active.add_data_set(affectee_item,
                                                      awaiting_to_activate)
        # Other
        other_to_activate = set()
        for affector_item, affector_specs in (
                self.__affectors_item_other.items()):
            if affectee_item in affector_item._others:
                other_to_activate.update(affector_specs)
        # Just add affector specs to active storage, 'other' affector specs
        # should never be removed from 'other'-specific storage
        if other_to_activate:
            self.__affectors_item_active.add_data_set(affectee_item,
                                                      other_to_activate)

    def __deactivate_special_affector_specs(self, affectee_fit, affectee_item):
        """Deactivate special affector specs which affect passed item."""
        if affectee_item not in self.__affectors_item_active:
            return
        awaitable_to_deactivate = set()
        for affector_spec in (self.__affectors_item_active.get(
                affectee_item, ())):
            if affector_spec.modifier.affectee_domain in (ModDomain.ship,
                                                          ModDomain.character,
                                                          ModDomain.self):
                awaitable_to_deactivate.add(affector_spec)
        # Remove all affector specs influencing this item directly, including
        # 'other' affectors
        del self.__affectors_item_active[affectee_item]
        # And make sure awaitable affectors become awaiting - moved to
        # appropriate container for future use
        if awaitable_to_deactivate:
            self.__affectors_item_awaiting.add_data_set(
                affectee_fit, awaitable_to_deactivate)

    # Helpers for affector spec registering/unregistering
    def __get_local_affector_storages(self, affector_spec):
        """Get places where passed local affector spec should be stored.

        Raises:
            UnexpectedDomainError: If modifier affectee domain is not supported
                for context of passed affector spec.
            UnknownAffecteeFilterError: If modifier affectee filter type is not
                supported.
        """
        affectee_filter = affector_spec.modifier.affectee_filter
        if affectee_filter == ModAffecteeFilter.item:
            affectee_domain = affector_spec.modifier.affectee_domain
            try:
                getter = self.__local_affector_storages_getters[
                    affectee_domain]
            except KeyError as e:
                raise UnexpectedDomainError(affectee_domain) from e
            return getter(self, affector_spec)
        else:
            try:
                getter = self.__affector_storages_getters[affectee_filter]
            except KeyError as e:
                raise UnknownAffecteeFilterError(affectee_filter) from e
            affectee_domain = self.__resolve_local_domain(affector_spec)
            affectee_fits = affector_spec.item._fit,
            return getter(self, affector_spec, affectee_domain, affectee_fits)

    def __get_projected_affector_storages(self, affector_spec, tgt_items):
        """Get places where passed projected affector spec should be stored.

        Raises:
            UnknownAffecteeFilterError: If modifier affectee filter type is not
                supported.
        """
        affectee_filter = affector_spec.modifier.affectee_filter
        # Modifier affects just targeted items directly
        if affectee_filter == ModAffecteeFilter.item:
            storages = []
            storage = self.__affectors_item_active
            for tgt_item in tgt_items:
                if tgt_item in self.__affectees:
                    key = tgt_item
                    storages.append((key, storage))
            return storages
        # Modifier affects multiple items via affectee filter
        else:
            try:
                getter = self.__affector_storages_getters[affectee_filter]
            except KeyError as e:
                raise UnknownAffecteeFilterError(affectee_filter) from e
            affectee_domain = ModDomain.ship
            affectee_fits = {i._fit for i in tgt_items if isinstance(i, Ship)}
            return getter(self, affector_spec, affectee_domain, affectee_fits)

    def __get_local_affector_storages_self(self, affector_spec):
        affectee_item = affector_spec.item
        if affectee_item in self.__affectees:
            key = affectee_item
            storage = self.__affectors_item_active
        else:
            key = affectee_item._fit
            storage = self.__affectors_item_awaiting
        return (key, storage),

    def __get_local_affector_storages_character(self, affector_spec):
        affectee_fit = affector_spec.item._fit
        affectee_character = affectee_fit.character
        if affectee_character in self.__affectees:
            key = affectee_character
            storage = self.__affectors_item_active
        else:
            key = affectee_fit
            storage = self.__affectors_item_awaiting
        return (key, storage),

    def __get_local_affector_storages_ship(self, affector_spec):
        affectee_fit = affector_spec.item._fit
        affectee_ship = affectee_fit.ship
        if affectee_ship in self.__affectees:
            key = affectee_ship
            storage = self.__affectors_item_active
        else:
            key = affectee_fit
            storage = self.__affectors_item_awaiting
        return (key, storage),

    def __get_local_affector_storages_other(self, affector_spec):
        # Affectors with 'other' modifiers are always stored in their special
        # place
        storages = [(affector_spec.item, self.__affectors_item_other)]
        # And all those which have valid affectee item are also stored in
        # storage for active direct affectors
        storage = self.__affectors_item_active
        for other_item in affector_spec.item._others:
            if other_item in self.__affectees:
                key = other_item
                storages.append((key, storage))
        return storages

    __local_affector_storages_getters = {
        ModDomain.self: __get_local_affector_storages_self,
        ModDomain.character: __get_local_affector_storages_character,
        ModDomain.ship: __get_local_affector_storages_ship,
        ModDomain.other: __get_local_affector_storages_other
    }

    def __get_affector_storages_domain(self, _, affectee_domain,
                                       affectee_fits):
        storages = []
        storage = self.__affectors_domain
        for affectee_fit in affectee_fits:
            key = (affectee_fit, affectee_domain)
            storages.append((key, storage))
        return storages

    def __get_affector_storages_domain_group(self, affector_spec,
                                             affectee_domain, affectee_fits):
        affectee_group_id = affector_spec.modifier.affectee_filter_extra_arg
        storages = []
        storage = self.__affectors_domain_group
        for affectee_fit in affectee_fits:
            key = (affectee_fit, affectee_domain, affectee_group_id)
            storages.append((key, storage))
        return storages

    def __get_affector_storages_domain_skillrq(self, affector_spec,
                                               affectee_domain, affectee_fits):
        affectee_srq_type_id = affector_spec.modifier.affectee_filter_extra_arg
        if affectee_srq_type_id == EosTypeId.current_self:
            affectee_srq_type_id = affector_spec.item._type_id
        storages = []
        storage = self.__affectors_domain_skillrq
        for affectee_fit in affectee_fits:
            key = (affectee_fit, affectee_domain, affectee_srq_type_id)
            storages.append((key, storage))
        return storages

    def __get_affector_storages_owner_skillrq(self, affector_spec, _,
                                              affectee_fits):
        affectee_srq_type_id = affector_spec.modifier.affectee_filter_extra_arg
        if affectee_srq_type_id == EosTypeId.current_self:
            affectee_srq_type_id = affector_spec.item._type_id
        storages = []
        storage = self.__affectors_owner_skillrq
        for affectee_fit in affectee_fits:
            key = (affectee_fit, affectee_srq_type_id)
            storages.append((key, storage))
        return storages

    __affector_storages_getters = {
        ModAffecteeFilter.domain: __get_affector_storages_domain,
        ModAffecteeFilter.domain_group: __get_affector_storages_domain_group,
        ModAffecteeFilter.domain_skillrq:
        __get_affector_storages_domain_skillrq,
        ModAffecteeFilter.owner_skillrq: __get_affector_storages_owner_skillrq
    }

    # Shared helpers
    def __resolve_local_domain(self, affector_spec):
        """Convert relative domain into absolute for local affector spec.

        Applicable only to en-masse modifications - that is, when modification
        affects multiple items in affectee domain.

        Raises:
            UnexpectedDomainError: If modifier affectee domain is not supported.
        """
        affector_item = affector_spec.item
        affectee_domain = affector_spec.modifier.affectee_domain
        if affectee_domain == ModDomain.self:
            if isinstance(affector_item, Ship):
                return ModDomain.ship
            elif isinstance(affector_item, Character):
                return ModDomain.character
            else:
                raise UnexpectedDomainError(affectee_domain)
        # Just return untouched domain for all other valid cases. Valid cases
        # include 'globally' visible (within the fit scope) domains only. I.e.
        # if item on fit refers this affectee domain, it should always refer the
        # same affectee item regardless of position of source item.
        elif affectee_domain in (ModDomain.character, ModDomain.ship):
            return affectee_domain
        # Raise error if domain is invalid
        else:
            raise UnexpectedDomainError(affectee_domain)

    def __handle_affector_spec_errors(self, error, affector_spec):
        """Handles exceptions related to affector spec.

        Multiple register methods which get data based on passed affector spec
        raise similar exceptions. To handle them in consistent fashion, it is
        done from this method. If error cannot be handled by the method, it is
        re-raised.
        """
        if isinstance(error, UnexpectedDomainError):
            msg = ('malformed modifier on item type {}: '
                   'unsupported affectee domain {}').format(
                       affector_spec.item._type_id, error.args[0])
            logger.warning(msg)
        elif isinstance(error, UnknownAffecteeFilterError):
            msg = (
                'malformed modifier on item type {}: invalid affectee filter {}'
            ).format(affector_spec.item._type_id, error.args[0])
            logger.warning(msg)
        else:
            raise error
コード例 #19
0
ファイル: projection.py プロジェクト: DarkFenX/eos
class ProjectionRegister:
    """Keeps track of various projection-related connections."""

    def __init__(self):
        # Format: {projectors}
        self.__projectors = set()

        # Projectors residing on solar system item
        # Format: {carrier item: {projectors}}
        self.__carrier_projectors = KeyedStorage()

        # Projectors whose carrying solar system item is not present
        # Format: {projectors}
        self.__carrierless_projectors = set()

        # Solar system items affected by projector
        # Format: {projector: {target items}}
        self.__projector_tgts = KeyedStorage()

        # Projectors affecting solar system item
        # Format: {target item: {projectors}}
        self.__tgt_projectors = KeyedStorage()

    # Query methods
    def get_projector_tgts(self, projector):
        """Get solar system items which are under effect of passed projector."""
        return self.__projector_tgts.get(projector, ())

    def get_tgt_projectors(self, tgt_item):
        """Get projectors influencing passed solar system item."""
        return self.__tgt_projectors.get(tgt_item, ())

    def get_carrier_projectors(self, carrier_item):
        """Get projectors which are exerted by passed carrier's items."""
        return self.__carrier_projectors.get(carrier_item, ())

    def get_projectors(self):
        """Get all known projectors."""
        return self.__projectors

    # Maintenance methods
    def register_projector(self, projector):
        self.__projectors.add(projector)
        carrier_item = projector.item._solsys_carrier
        if carrier_item is not None:
            self.__carrier_projectors.add_data_entry(carrier_item, projector)
        else:
            self.__carrierless_projectors.add(projector)

    def unregister_projector(self, projector):
        self.__projectors.discard(projector)
        carrier_item = projector.item._solsys_carrier
        if carrier_item is not None:
            self.__carrier_projectors.rm_data_entry(carrier_item, projector)
        else:
            self.__carrierless_projectors.discard(projector)

    def apply_projector(self, projector, tgt_items):
        self.__projector_tgts.add_data_set(projector, tgt_items)
        for tgt_item in tgt_items:
            self.__tgt_projectors.add_data_entry(tgt_item, projector)

    def unapply_projector(self, projector, tgt_items):
        self.__projector_tgts.rm_data_set(projector, tgt_items)
        for tgt_item in tgt_items:
            self.__tgt_projectors.rm_data_entry(tgt_item, projector)

    def register_solsys_item(self, solsys_item):
        projectors = set()
        for projector in self.__carrierless_projectors:
            if projector.item._solsys_carrier is solsys_item:
                projectors.add(projector)
        if projectors:
            self.__carrierless_projectors.difference_update(projectors)
            self.__carrier_projectors.add_data_set(solsys_item, projectors)

    def unregister_solsys_item(self, solsys_item):
        projectors = self.__carrier_projectors.get(solsys_item, ())
        if projectors:
            self.__carrierless_projectors.update(projectors)
            self.__carrier_projectors.rm_data_set(solsys_item, projectors)
コード例 #20
0
ファイル: map.py プロジェクト: pyfa-org/eos
class MutableAttrMap:
    """Map which contains modified attribute values.

    It provides some of facilities which help to calculate, store and provide
    access to modified attribute values.
    """
    def __init__(self, item):
        self.__item = item
        # Actual container of calculated attributes.
        # Format: {attribute ID: value}
        self.__modified_attrs = {}
        # Override and cap maps are initialized as None to save memory, as they
        # are not needed most of the time
        self.__override_callbacks = None
        self.__cap_map = None

    def __getitem__(self, attr_id):
        # Overridden values are priority. Access 'private' override callbacks
        # map directly due to performance reasons
        if (self.__override_callbacks is not None
                and attr_id in self.__override_callbacks):
            callback, args, kwargs = self.__override_callbacks[attr_id]
            return callback(*args, **kwargs)
        # If no override is set, use modified value. If value is stored in
        # modified map, it's considered valid
        try:
            value = self.__modified_attrs[attr_id]
        # Else, we have to run full calculation process
        except KeyError:
            try:
                value = self.__calculate(attr_id)
            except CALCULATE_RAISABLE_EXCEPTIONS as e:
                raise KeyError(attr_id) from e
            else:
                self.__modified_attrs[attr_id] = value
        return value

    def __len__(self):
        return len(self.keys())

    def __contains__(self, attr_id):
        return attr_id in self.keys()

    def __iter__(self):
        for k in self.keys():
            yield k

    def _force_recalc(self, attr_id):
        """
        Force recalculation of attribute with passed ID.

        Returns:
            True if attribute was calculated, False if it wasn't.
        """
        try:
            del self.__modified_attrs[attr_id]
        except KeyError:
            return False
        else:
            return True

    def get(self, attr_id, default=None):
        # Almost copy-paste of __getitem__ due to performance reasons -
        # attribute getters should make as few calls as possible, especially
        # when attribute is already calculated
        if (self.__override_callbacks is not None
                and attr_id in self.__override_callbacks):
            callback, args, kwargs = self.__override_callbacks[attr_id]
            return callback(*args, **kwargs)
        try:
            value = self.__modified_attrs[attr_id]
        except KeyError:
            try:
                value = self.__calculate(attr_id)
            except CALCULATE_RAISABLE_EXCEPTIONS:
                return default
            else:
                self.__modified_attrs[attr_id] = value
        return value

    def keys(self):
        # Return union of attributes from base, modified and override dictionary
        return set(
            chain(self.__item._type_attrs, self.__modified_attrs,
                  self.__override_callbacks or {}))

    def items(self):
        return set((attr_id, self.get(attr_id)) for attr_id in self.keys())

    def _clear(self):
        """
        Reset map to its initial state.

        Overrides are not removed. Messages for cleared attributes are not sent.
        """
        self.__modified_attrs.clear()
        self.__cap_map = None

    def __calculate(self, attr_id):
        """Run calculations to find the actual value of attribute.

        Args:
            attr_id: ID of attribute to be calculated.

        Returns:
            Calculated attribute value.

        Raises:
            AttrMetadataError: If metadata of attribute being calculated cannot
                be fetched.
            BaseValueError: If base value for attribute being calculated cannot
                be found.
        """
        item = self.__item
        # Attribute object for attribute being calculated
        try:
            attr = item._fit.solar_system.source.cache_handler.get_attr(
                attr_id)
        # Raise error if we can't get metadata for requested attribute
        except (AttributeError, AttrFetchError) as e:
            msg = ('unable to fetch metadata for attribute {}, '
                   'requested for item type {}').format(
                       attr_id, item._type_id)
            logger.warning(msg)
            raise AttrMetadataError(attr_id) from e
        # Base attribute value which we'll use for modification
        try:
            value = item._type_attrs[attr_id]
        # If attribute isn't available on item type, base off its default value
        except KeyError:
            value = attr.default_value
            # If item type attribute is not specified and default value isn't
            # available, raise error - without valid base we can't keep going
            if value is None:
                msg = (
                    'unable to find base value for attribute {} on item type {}'
                ).format(attr_id, item._type_id)
                logger.info(msg)
                raise BaseValueError(attr_id)
        # Format: {operator: [values]}
        stack = {}
        # Format: {operator: [values]}
        stack_penalized = {}
        # Format: {(operator, aggregate key): [(value, penalize)]}
        aggregate_min = {}
        # Format: {(operator, aggregate key): [(value, penalize)]}
        aggregate_max = {}
        # Now, go through all affectors affecting our item
        for (mod_operator, mod_value, resist_value, mod_aggregate_mode,
             mod_aggregate_key, affector_item) in (
                 item._fit.solar_system._calculator.get_modifications(
                     item, attr_id)):
            # Normalize operations to just three types: assignments, additions,
            # reduced multiplications
            try:
                normalization_func = NORMALIZATION_MAP[mod_operator]
            # Log error on any unknown operator types
            except KeyError:
                msg = (
                    'malformed modifier on item type {}: unknown operator {}'
                ).format(affector_item._type_id, mod_operator)
                logger.warning(msg)
                continue
            # Resistance attribute actually defines resonance, where 1 means 0%
            # resistance and 0 means 100% resistance
            mod_value = normalization_func(mod_value) * resist_value
            # Decide if modification should be stacking penalized or not
            penalize = (not attr.stackable and affector_item._type.category_id
                        not in PENALTY_IMMUNE_CATEGORY_IDS
                        and mod_operator in PENALIZABLE_OPERATORS)
            if mod_aggregate_mode == ModAggregateMode.stack:
                if penalize:
                    stack_penalized.setdefault(mod_operator,
                                               []).append(mod_value)
                else:
                    stack.setdefault(mod_operator, []).append(mod_value)
            elif mod_aggregate_mode == ModAggregateMode.minimum:
                aggregate_min.setdefault((mod_operator, mod_aggregate_key),
                                         []).append((mod_value, penalize))
            elif mod_aggregate_mode == ModAggregateMode.maximum:
                aggregate_max.setdefault((mod_operator, mod_aggregate_key),
                                         []).append((mod_value, penalize))
        for container, aggregate_func, sort_func in ((aggregate_min, min,
                                                      lambda i: (i[0], i[1])),
                                                     (aggregate_max, max,
                                                      lambda i:
                                                      (i[0], not i[1]))):
            for k, v in container.items():
                mod_operator = k[0]
                mod_value, penalize = aggregate_func(v, key=sort_func)
                if penalize:
                    stack_penalized.setdefault(mod_operator,
                                               []).append(mod_value)
                else:
                    stack.setdefault(mod_operator, []).append(mod_value)
        # When data gathering is complete, process penalized modifications. They
        # are penalized on per-operator basis
        for mod_operator, mod_values in stack_penalized.items():
            penalized_value = self.__penalize_values(mod_values)
            stack.setdefault(mod_operator, []).append(penalized_value)
        # Calculate value of non-penalized modifications, according to operator
        # order
        for mod_operator in sorted(stack):
            mod_values = stack[mod_operator]
            # Pick best modification for assignments, based on high_is_good
            # value
            if mod_operator in ASSIGNMENT_OPERATORS:
                if attr.high_is_good:
                    value = max(mod_values)
                else:
                    value = min(mod_values)
            elif mod_operator in ADDITION_OPERATORS:
                for mod_value in mod_values:
                    value += mod_value
            elif mod_operator in MULTIPLICATION_OPERATORS:
                for mod_value in mod_values:
                    value *= 1 + mod_value
        # If attribute has upper cap, do not let its value to grow above it
        if attr.max_attr_id is not None:
            try:
                max_value = self[attr.max_attr_id]
            # If max value isn't available, don't cap anything
            except KeyError:
                pass
            else:
                value = min(value, max_value)
                # Let map know that capping attribute restricts current
                # attribute
                self._cap_set(attr.max_attr_id, attr_id)
        # Some of attributes are rounded for whatever reason, deal with it after
        # all the calculations
        if attr_id in LIMITED_PRECISION_ATTR_IDS:
            value = round(value, 2)
        return value

    def __penalize_values(self, mod_values):
        """Calculate aggregated reduced multiplier.

        Assuming all multipliers received should be stacking penalized, and that
        they are normalized to reduced multiplier form, calculate final
        reduced multiplier.

        Args:
            mod_values: Iterable with reduced multipliers.

        Returns:
            Final aggregated reduced multiplier.
        """
        # Gather positive multipliers into one chain, negative into another
        chain_positive = []
        chain_negative = []
        for mod_value in mod_values:
            if mod_value >= 0:
                chain_positive.append(mod_value)
            else:
                chain_negative.append(mod_value)
        # Strongest modifications always go first
        chain_positive.sort(reverse=True)
        chain_negative.sort()
        # Base final multiplier on 1
        value = 1
        for penalization_chain in (chain_positive, chain_negative):
            # Same for intermediate per-chain value
            chain_value = 1
            for pos, mod_value in enumerate(penalization_chain):
                # Ignore 12th modification and further as non-significant
                if pos > 10:
                    break
                # Apply stacking penalty based on modification position
                chain_value *= 1 + mod_value * PENALTY_BASE**(pos**2)
            value *= chain_value
        return value - 1

    # Override-related methods
    @property
    def _override_callbacks(self):
        return self.__override_callbacks or {}

    def _set_override_callback(self, attr_id, callback):
        """Set override for the attribute in the form of callback."""
        if self.__override_callbacks is None:
            self.__override_callbacks = {}
        # If the same callback is set, do nothing
        if self.__override_callbacks.get(attr_id) == callback:
            return
        self.__override_callbacks[attr_id] = callback
        # Exposed attribute value may change after setting/resetting override
        self.__publish(AttrsValueChanged({self.__item: {attr_id}}))

    def _del_override_callback(self, attr_id):
        """Remove override callback from attribute."""
        overrides = self.__override_callbacks or {}
        if attr_id not in overrides:
            return
        del overrides[attr_id]
        # Set overrides map to None if there're none left to save some memory
        if not overrides:
            self.__override_callbacks = None
        # Exposed attribute value may change after removing override
        self.__publish(AttrsValueChanged({self.__item: {attr_id}}))

    def _override_value_may_change(self, attr_id):
        """Notify everyone that callback value may change.

        When originator of callback knows that callback return value may (or
        will) change for an attribute, it should invoke this method.
        """
        self.__publish(AttrsValueChanged({self.__item: {attr_id}}))

    def _get_without_overrides(self, attr_id, default=None):
        """Get attribute value without using overrides."""
        # Partially borrowed from get() method
        try:
            value = self.__modified_attrs[attr_id]
        except KeyError:
            try:
                value = self.__calculate(attr_id)
            except CALCULATE_RAISABLE_EXCEPTIONS:
                return default
            else:
                self.__modified_attrs[attr_id] = value
        return value

    # Cap-related methods
    @property
    def _cap_map(self):
        """Returns map which defines value caps.

        It includes attributes which cap something, and attributes being capped
        by them.
        """
        # Format {capping attribute ID: {capped attribute IDs}}
        return self.__cap_map or {}

    def _cap_set(self, capping_attr_id, capped_attr_id):
        if self.__cap_map is None:
            self.__cap_map = KeyedStorage()
        self.__cap_map.add_data_entry(capping_attr_id, capped_attr_id)

    def _cap_del(self, capping_attr_id, capped_attr_id):
        self.__cap_map.rm_data_entry(capping_attr_id, capped_attr_id)
        if not self.__cap_map:
            self.__cap_map = None

    # Auxiliary methods
    def __publish(self, msg):
        try:
            publish_func = self.__item._fit._publish
        except AttributeError:
            pass
        else:
            publish_func(msg)
コード例 #21
0
ファイル: map.py プロジェクト: pyfa-org/eos
 def _cap_set(self, capping_attr_id, capped_attr_id):
     if self.__cap_map is None:
         self.__cap_map = KeyedStorage()
     self.__cap_map.add_data_entry(capping_attr_id, capped_attr_id)
コード例 #22
0
 def __init__(self, fit):
     # Format: {item:, {effect1, effect2}}
     self.__dmg_dealers = KeyedStorage()
     fit._subscribe(self, self._handler_map.keys())
コード例 #23
0
    def __init__(self):
        # All known affectee items
        # Format: {affectee items}
        self.__affectees = set()

        # Items belonging to certain fit and domain
        # Format: {(affectee fit, affectee domain): {affectee items}}
        self.__affectees_domain = KeyedStorage()

        # Items belonging to certain fit, domain and group
        # Format: {(affectee fit, affectee domain, affectee group ID): {affectee
        # items}}
        self.__affectees_domain_group = KeyedStorage()

        # Items belonging to certain fit and domain, and having certain skill
        # requirement
        # Format: {(affectee fit, affectee domain, affectee skill requirement
        # type ID): {affectee items}}
        self.__affectees_domain_skillrq = KeyedStorage()

        # Owner-modifiable items which belong to certain fit and have certain
        # skill requirement
        # Format: {(affectee fit, affectee skill requirement type ID): {affectee
        # items}}
        self.__affectees_owner_skillrq = KeyedStorage()

        # Affector specs with modifiers which affect 'other' location are always
        # stored here, regardless if they actually affect something or not
        # Format: {affector item: {affector specs}}
        self.__affectors_item_other = KeyedStorage()

        # Affector specs which should affect only one item (ship, character or
        # self), when this item is not registered as affectee
        # Format: {affectee fit: {affector specs}}
        self.__affectors_item_awaiting = KeyedStorage()

        # All active affector specs which affect one specific item (via ship,
        # character, other reference or self) are kept here
        # Format: {affectee item: {affector specs}}
        self.__affectors_item_active = KeyedStorage()

        # Affector specs influencing all items belonging to certain fit and
        # domain
        # Format: {(affectee fit, affectee domain): {affector specs}}
        self.__affectors_domain = KeyedStorage()

        # Affector specs influencing items belonging to certain fit, domain and
        # group
        # Format: {(affectee fit, affectee domain, affectee group ID): {affector
        # specs}}
        self.__affectors_domain_group = KeyedStorage()

        # Affector specs influencing items belonging to certain fit and domain,
        # and having certain skill requirement
        # Format: {(affectee fit, affectee domain, affectee skill requirement
        # type ID): {affector specs}}
        self.__affectors_domain_skillrq = KeyedStorage()

        # Affector specs influencing owner-modifiable items belonging to certain
        # fit and having certain skill requirement
        # Format: {(affectee fit, affectee skill requirement type ID): {affector
        # specs}}
        self.__affectors_owner_skillrq = KeyedStorage()
コード例 #24
0
ファイル: affection.py プロジェクト: DarkFenX/eos
    def __init__(self):
        # All known affectee items
        # Format: {affectee items}
        self.__affectees = set()

        # Items belonging to certain fit and domain
        # Format: {(affectee fit, affectee domain): {affectee items}}
        self.__affectees_domain = KeyedStorage()

        # Items belonging to certain fit, domain and group
        # Format: {(affectee fit, affectee domain, affectee group ID): {affectee
        # items}}
        self.__affectees_domain_group = KeyedStorage()

        # Items belonging to certain fit and domain, and having certain skill
        # requirement
        # Format: {(affectee fit, affectee domain, affectee skill requirement
        # type ID): {affectee items}}
        self.__affectees_domain_skillrq = KeyedStorage()

        # Owner-modifiable items which belong to certain fit and have certain
        # skill requirement
        # Format: {(affectee fit, affectee skill requirement type ID): {affectee
        # items}}
        self.__affectees_owner_skillrq = KeyedStorage()

        # Affector specs with modifiers which affect 'other' location are always
        # stored here, regardless if they actually affect something or not
        # Format: {affector item: {affector specs}}
        self.__affectors_item_other = KeyedStorage()

        # Affector specs which should affect only one item (ship, character or
        # self), when this item is not registered as affectee
        # Format: {affectee fit: {affector specs}}
        self.__affectors_item_awaiting = KeyedStorage()

        # All active affector specs which affect one specific item (via ship,
        # character, other reference or self) are kept here
        # Format: {affectee item: {affector specs}}
        self.__affectors_item_active = KeyedStorage()

        # Affector specs influencing all items belonging to certain fit and
        # domain
        # Format: {(affectee fit, affectee domain): {affector specs}}
        self.__affectors_domain = KeyedStorage()

        # Affector specs influencing items belonging to certain fit, domain and
        # group
        # Format: {(affectee fit, affectee domain, affectee group ID): {affector
        # specs}}
        self.__affectors_domain_group = KeyedStorage()

        # Affector specs influencing items belonging to certain fit and domain,
        # and having certain skill requirement
        # Format: {(affectee fit, affectee domain, affectee skill requirement
        # type ID): {affector specs}}
        self.__affectors_domain_skillrq = KeyedStorage()

        # Affector specs influencing owner-modifiable items belonging to certain
        # fit and having certain skill requirement
        # Format: {(affectee fit, affectee skill requirement type ID): {affector
        # specs}}
        self.__affectors_owner_skillrq = KeyedStorage()