Beispiel #1
0
 def __init__(self, slot_index_attr, restriction_type):
     # This attribute's value on holder
     # represents their index of slot
     self.__slot_index_attr = slot_index_attr
     self.__restriction_type = restriction_type
     # All holders which possess index of slot
     # are stored in this container
     # Format: {slot index: {holders}}
     self.__slotted_holders = KeyedSet()
Beispiel #2
0
 def __init__(self, max_group_attr, restriction_type):
     # Attribute ID whose value contains group restriction
     # of holder
     self.__max_group_attr = max_group_attr
     self.__restriction_type = restriction_type
     # Container for all tracked holders, keyed
     # by their group ID
     # Format: {group ID: {holders}}
     self.__group_all = KeyedSet()
     # Container for holders, which have max group
     # restriction to become operational
     # Format: {holders}
     self.__group_restricted = set()
Beispiel #3
0
    def __init__(self, fit):
        # Link tracker which is assigned to fit we're
        # keeping data for
        self._fit = fit

        # Keep track of holders belonging to certain domain
        # Format: {domain: {targetHolders}}
        self.__affectee_domain = KeyedSet()

        # Keep track of holders belonging to certain domain and group
        # Format: {(domain, group): {targetHolders}}
        self.__affectee_domain_group = KeyedSet()

        # Keep track of holders belonging to certain domain and having certain skill requirement
        # Format: {(domain, skill): {targetHolders}}
        self.__affectee_domain_skill = KeyedSet()

        # Keep track of affectors influencing all holders belonging to certain domain
        # Format: {domain: {affectors}}
        self.__affector_domain = KeyedSet()

        # Keep track of affectors influencing holders belonging to certain domain and group
        # Format: {(domain, group): {affectors}}
        self.__affector_domain_group = KeyedSet()

        # Keep track of affectors influencing holders belonging to certain domain and having certain skill requirement
        # Format: {(domain, skill): {affectors}}
        self.__affector_domain_skill = KeyedSet()

        # Keep track of affectors influencing holders directly
        # Format: {targetHolder: {affectors}}
        self.__active_direct_affectors = KeyedSet()

        # Keep track of affectors which influence something directly,
        # but are disabled as their target domain is not available
        # Format: {source_holder: {affectors}}
        self.__disabled_direct_affectors = KeyedSet()
Beispiel #4
0
    def __calculate(self, attr):
        """
        Run calculations to find the actual value of attribute.

        Required arguments:
        attr -- ID of attribute to be calculated

        Return value:
        Calculated attribute value

        Possible exceptions:
        BaseValueError -- attribute cannot be calculated, as its
        base value is not available
        """
        # Assign base item attributes first to make sure than in case when
        # we're calculating attribute for item/fit without source, it fails
        # with null source error (triggered by accessing item's attribute)
        item_attrs = self.__holder.item.attributes
        # Attribute object for attribute being calculated
        try:
            attr_meta = self.__holder._fit.source.cache_handler.get_attribute(
                attr)
        # Raise error if we can't get metadata for requested attribute
        except (AttributeError, AttributeFetchError) as e:
            raise AttributeMetaError(attr) from e
        # Base attribute value which we'll use for modification
        try:
            result = item_attrs[attr]
        # If attribute isn't available on base item,
        # base off its default value
        except KeyError:
            result = attr_meta.default_value
            # If original attribute is not specified and default
            # value isn't available, raise error - without valid
            # base we can't go on
            if result is None:
                raise BaseValueError(attr)
        # Container for non-penalized modifiers
        # Format: {operator: [values]}
        normal_mods = {}
        # Container for penalized modifiers
        # Format: {operator: [values]}
        penalized_mods = {}
        # Now, go through all affectors affecting our holder
        for affector in self.__holder._fit._link_tracker.get_affectors(
                self.__holder, attr=attr):
            try:
                source_holder, modifier = affector
                operator = modifier.operator
                # Decide if it should be stacking penalized or not, based on stackable property,
                # source item category and operator
                penalize = (attr_meta.stackable is False
                            and source_holder.item.category
                            not in PENALTY_IMMUNE_CATEGORIES
                            and operator in PENALIZABLE_OPERATORS)
                try:
                    mod_value = source_holder.attributes[modifier.src_attr]
                # Silently skip current affector: error should already
                # be logged by map before it raised KeyError
                except KeyError:
                    continue
                # Normalize operations to just three types:
                # assignments, additions, multiplications
                try:
                    normalization_func = NORMALIZATION_MAP[operator]
                # Raise error on any unknown operator types
                except KeyError as e:
                    raise OperatorError(operator) from e
                mod_value = normalization_func(mod_value)
                # Add value to appropriate dictionary
                if penalize is True:
                    mod_list = penalized_mods.setdefault(operator, [])
                else:
                    mod_list = normal_mods.setdefault(operator, [])
                mod_list.append(mod_value)
            # Handle operator type failure
            except OperatorError as e:
                msg = 'malformed modifier on item {}: unknown operator {}'.format(
                    source_holder.item.id, e.args[0])
                logger.warning(msg)
                continue
        # When data gathering is complete, process penalized modifiers
        # They are penalized on per-operator basis
        for operator, mod_list in penalized_mods.items():
            penalized_value = self.__penalize_values(mod_list)
            mod_list = normal_mods.setdefault(operator, [])
            mod_list.append(penalized_value)
        # Calculate result of normal dictionary, according to operator order
        for operator in sorted(normal_mods):
            mod_list = normal_mods[operator]
            # Pick best modifier for assignments, based on high_is_good value
            if operator in ASSIGNMENTS:
                result = max(
                    mod_list) if attr_meta.high_is_good is True else min(
                        mod_list)
            elif operator in ADDITIONS:
                for mod_val in mod_list:
                    result += mod_val
            elif operator in MULTIPLICATIONS:
                for mod_val in mod_list:
                    result *= mod_val
        # If attribute has upper cap, do not let
        # its value to grow above it
        if attr_meta.max_attribute is not None:
            try:
                max_value = self[attr_meta.max_attribute]
            # If max value isn't available, don't
            # cap anything
            except KeyError:
                pass
            else:
                result = min(result, max_value)
                # Let map know that capping attribute
                # restricts current attribute
                if self._cap_map is None:
                    self._cap_map = KeyedSet()
                # Fill cap map with data: capping attribute and capped attribute
                self._cap_map.add_data(attr_meta.max_attribute, attr)
        # Some of attributes are rounded for whatever reason,
        # deal with it after all the calculations
        if attr in LIMITED_PRECISION:
            result = round(result, 2)
        return result