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()
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()
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()
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