Example #1
0
 def __init__(self, filename, monstats, superuniques, superuniques2):
     D2Data.__init__(self, filename, 'Id')
     # build monster_id -> [level_id] map
     norm_cols = ['mon'+str(i) for i in range(1,11)]
     nmh_cols = ['n'+s for s in norm_cols]
     self.monstats = monstats
     self.superuniques = superuniques
     self.superuniques2 = superuniques2
     self.norm_monmap = {}
     self.nmh_monmap = {}
     self.make_monmap(self.norm_monmap, norm_cols)
     self.make_monmap(self.nmh_monmap, nmh_cols)
Example #2
0
 def __init__(self, filename):
     D2Data.__init__(self, filename, 'Treasure_Class')
     self.group_map = {}
     for tc in self.data['Treasure_Class']:
         if tc:
             group = self.get_data(tc, 'group')
             if group > 0:
                 if group not in self.group_map:
                     self.group_map[group] = []
                 self.group_map[group].append({'Treasure_Class': tc, 'level': self.get_data(tc, 'level')})
     # make sure each group list is sorted by level
     for group, tcs in self.group_map.items():
         tcs.sort(key=lambda x: x['level'])
Example #3
0
class Set():
    sets = D2Data('data/global/excel/Sets.txt', 'index')

    def __init__(self, set_id):
        self.set_id = set_id
        logger.debug("{} is a {} piece set.".format(self.set_name(), self.num_items()))

    def num_items(self):
        return np.sum(SetItem.setitems.data['set'] == self.set_id)

    def sets_data(self, col):
        """Get data from a column of Sets.txt."""
        return self.sets.get_data(self.set_id, col)

    def set_name(self):
        """Return the name of the set."""
        return self.sets.get_data(self.set_id, 'name')

    def _attributes(self, prefix, suffix, start, stop):
        # partial set bonuses first
        for i in range(start, stop):
            for c in suffix:
                propstr = '{}Code{}{}'.format(prefix,i,c)
                parstr = '{}Param{}{}'.format(prefix,i,c)
                minstr = '{}Min{}{}'.format(prefix,i,c)
                maxstr = '{}Max{}{}'.format(prefix,i,c)
                prop = self.sets_data(propstr)
                par = self.sets_data(parstr)
                min_ = self.sets_data(minstr)
                max_ = self.sets_data(maxstr)
                # bool check is because an empty column has bool datatype
                # TODO: Figure out a better way to deal with this before it's all over the place
                if prop.dtype != 'bool' and prop != '':
                    logger.debug("Found property {} to include from {} set.".format(prop, self.set_name())
                            + " This property calls funcions {}.".format(list(Stat.property_functions(prop)))
                               + " Arguments to property function: param={} min={} max={}".format(par, min_, max_))
                    for property_function in Stat.property_functions(prop):
                        stat = Stat.create_stat(**property_function, param=par, min_=min_, max_=max_)
                        logger.debug("Created stat {}.".format(stat))

    def attributes(self, num_items):
        self._attributes('P', ['a','b'], 2, num_items+1)
        if num_items == self.num_items():
            self._attributes('F', [''], 1, 9)
Example #4
0
 def __init__(self, filename):
     D2Data.__init__(self, filename, 'constant_desc', usecols=['constant_desc', 'constants'])
Example #5
0
 def __init__(self, filename):
     D2Data.__init__(self, filename, 'Superunique')
Example #6
0
 def __init__(self, filename):
     D2Data.__init__(self, filename, 'Id')
Example #7
0
 def __init__(self, filename):
     D2Data.__init__(self, filename, 'Level')
Example #8
0
class SetItem(Item):
    # map the set_id from d2s parser to the index used by SetItems.txt
    setitems2 = D2Data('data2/SetItems2.txt', 'set_id')
    setitems = D2Data('data/global/excel/SetItems.txt', 'index')

    def __init__(self, itemdata):
        Item.__init__(self, itemdata)
        try:
            self.set_index = self.setitems2.get_data(itemdata['set_id'], 'index')
            #self.set = Set(self.sets_key())
            logger.debug("Creating {} set item {}.".format(self.sets_key(), self.set_index))
        except KeyError as e:
            logger.error("Set item by quality has no set_id. JSON dump: {}".format(itemdata))
            raise

    def setitems_key(self):
        """Return the key for lookups in the SetItems.txt file."""
        return self.set_index

    def sets_key(self):
        """Return the key for lookups in the Sets.txt file."""
        return self.setitems.get_data(self.set_index, 'set')

    def setitems_data(self, col):
        """Get data from a column of SetItems.txt."""
        return self.setitems.get_data(self.set_index, col)

    def all_set_attributes(self):
        """Return an iterator for lists of set item attributes, active or not.

        Set items have attributes organized as a list of lists. The inner lists
        contain the actual attributes. The outer list is for groups of attributes.
        These attributes are grouped because of the way set bonuses are applied.
        The first group is applied with x many items, second group with y many, etc."""
        for attr_list in self.attributes('set_attributes'):
            yield attr_list

    def set_attributes(self, num_items):
        """Return an iterator for active set attributes."""
        # first figure out if bonuses on this item depend on total items equipped or specific items equipped
        # (Civerb's shield is the only one in the latter category)
        if self.setitems_data('add_func') == 1:
            # add the stats based on which other specific items are present
            logger.error("Sets items with bonuses dependent on specific set items (e.g. Civerb's shield) are not"
                         " yet supported. Bonuses will not be applied on {}".format(self.setitems_key()))
        elif self.setitems_data('add_func') == 2:
            # add the stats based on total number of unique items present
            # first grab the set attributes iterator for the item. This is intentionally
            # only initialized once, and not again in the inner loop. It should advance each
            # time we match the exepcted stats from ItemStatCost with the attributes in the list.
            set_attr_iter = self.all_set_attributes()
            try:
                for i in range(1, num_items):
                    stat_ids = []
                    for c in ['a','b']:
                        propstr = 'aprop{}{}'.format(i,c)
                        #parstr = 'apar{}{}'.format(i,c)
                        #minstr = 'amin{}{}'.format(i,c)
                        #maxstr = 'amax{}{}'.format(i,c)
                        prop = self.setitems_data(propstr)
                        #par = item.setitems_data(parstr)
                        #min_ = item.setitems_data(minstr)
                        #max_ = item.setitems_data(maxstr)
                        # bool check is because an empty column has bool datatype
                        # TODO: Figure out a better way to deal with this before it's all over the place
                        if prop.dtype != 'bool' and prop != '':
                            logger.debug("Found property {} to include on {}.".format(prop, self.setitems_key())
                                + " This property adds stats {}.".format([x['stat'] for x in list(Stat.property_functions(prop))]))
                            stat_ids += list(Stat.property_functions(prop))
                    # we need to find the attribute(s) in the d2s parser that matches the stat ids we look
                    # up from the property to add. We could attempt to look up the stat values themselves
                    # in the txt files, but this isn't the right way to do it. Some stat bonuses on items
                    # are actually variable (see Civerb's shield), so we should respect the values in the
                    # d2s file.
                    if len(stat_ids) > 0:
                        # above condition means there is a bonus we should apply, now we need to match it
                        # to the d2s attributes
                        for attr_list in set_attr_iter:
                            tmp_map = {}
                            Stat.add_attributes_to_map(iter(attr_list), tmp_map)
                            if set(tmp_map.keys()) == set([x['stat'] for x in stat_ids]):
                                logger.debug("Attributes {} active on {}.".format(attr_list, self.setitems_key()))
                                for attr in attr_list:
                                    yield attr
                                break
                            else:
                                raise PydiabloError("Attributes {} did not match expected stat ids {} on {}.".format(attr_list,
                                    stat_ids, self.setitems_key()))
            except PydiabloError as e:
                logger.error("Problem matching the set bonuses from d2s to those expected"
                             " by SetItems.txt ({}). Don't trust set bonuses on this item.".format(str(e)))
                return
Example #9
0
class Stat:
    """This class is a collection of static functions for now.

    """
    #itemstatcost = D2Data('data/global/excel/ItemStatCost.txt', 'Stat')
    itemstatcostid = D2Data('data/global/excel/ItemStatCost.txt', 'ID', usecols=[0,1]);
    properties = D2Data('data/global/excel/Properties.txt', 'code', usecols=range(30))

    @classmethod
    def attribute_to_stats(cls, attr):
        # first handle some special cases where the d2s parser
        # combined some stats into ranges.
        if attr['id'] in [17, 48, 50, 52, 54, 57]:
            stats = []
            for i, value in enumerate(attr['values']):
                stat = cls.itemstatcostid.get_data(attr['id']+i, 'Stat')
                stats.append({'stat': stat, 'values': [value]})
            return stats

        # next deal with the properties giving charges.
        if attr['id'] in range(204,214):
            # override the stat reference to point to a new dict that we will
            # modify so that charges fits in better to our scheme. We recombine
            # the current and maximum charges into one number.
            new_attr = {}
            new_attr['id'] = attr['id']
            try:
                # MSB is maximum charges, LSB is current charges
                new_attr['values'] = [attr['values'][0], attr['values'][1],
                                      attr['values'][2] + 2**8*attr['values'][3]]
            except IndexError as e:
                logger.error("Unexpected values field in item charges attribute. JSON dump: {}".format(attr))
                raise
            attr = new_attr

        # next handle the general case.
        stat = cls.itemstatcostid.get_data(attr['id'], 'Stat')
        return [{'stat': stat, 'values': attr['values']}]

    @classmethod
    def add_attributes_to_map(cls, attr_iterator, stat_map):
        """Add attributes from the item to the stat map.

        Positional arguments:
        attr_iterator -- an iterator for the item attributes.
        stat_map -- add stats to this map

        First, some terminology. Nokka's d2s parser gives 'attributes' for the items.
        These 'attributes' are a little different than the 'stats' in ItemStatCost.txt.
        When referring to the stat as it exists in the JSON from the d2s parser, I will
        use the term 'attribute'. When referring to a stat consistent with ItemStatCost.txt,
        I will use the term 'stat'.

        attr_iterator must yield a map with an id and values field and can
        be created with the generator methods in the Item class. These maps are
        expected to follow the format of nokka's d2s parser. When converting from
        attribute to stat, we change a few things, notably with combined stat ranges
        (min-max dmg) and with charges.

        The stat_map will contain all item stats, keyed by stat id (ItemStatCost.txt).
        In the case of a simple stat (one value), the value for the stat id
        will be a list of all instance values of that stat. In the case of a complex
        stat, the value for the stat id will be another map, keyed by parameter.

        simple attribute:
        > stat_map[141] # deadly strike
        [20]

        complex attribute:
        > stat_map[204][62][30] # level 30 (30) hydra (62) charges (204 is the stat id)
        [2570]
        The game stores the current and max charges as one 16 bit value. In this case,
        there are 10 current charges (LSB) and 10 max (MSB): 2570 = 0x0A0A.
        """
        for attr in attr_iterator:
            for stat in cls.attribute_to_stats(attr):
                mdict = stat_map
                mkey = stat['stat']
                for value in attr['values'][:-1][::-1]:
                    if mkey not in mdict:
                        mdict[mkey] = {}
                    mdict = mdict[mkey]
                    mkey = value
                if mkey not in mdict:
                    mdict[mkey] = []
                mdict[mkey].append(attr['values'][-1])

    @staticmethod
    def create_stat(func, stat, set_, val, param, min_, max_, rand=False):
        """Return a newly created stat as a dict with 'stat_id' and 'values' fields.

        The values are ordered consistenly with the item stat order in the d2s file.
        """
        if rand:
            logger.error("Random generation of stats not yet supported.")
        # The funciton mapping below was reverse engineered from vanilla game data
        # and by comparing to the item stat order in the d2s file (easy to see in nokka's parser).
        # It may not be completely accurate. Surely there are some differences between
        # the otherwise identical functions.
        # TODO: func3 is same as 1, but it should reuse the func1 rolls.
        if func in [1, 3, 8]:
            #stat_id = cls.itemstatcost.get_data(stat, 'ID')
            return {'stat': stat, 'values': [(min_+max_)//2]}
        if func==21:
            #stat_id = cls.itemstatcost.get_data(stat, 'ID')
            return {'stat': stat, 'values': [val, (min_+max_)//2]}
        else:
            return {}

    @classmethod
    def property_functions(cls, prop):
        """Yield a map containing 'set', 'val', 'func', and 'stat' fields for each stat associated with the property."""
        for i in range(1,8): # 7 maximum stats per property
            stat = cls.properties.get_data(prop, 'stat{}'.format(i))
            #stat_id = cls.itemstatcost.get_data(stat, 'ID')
            set_ = cls.properties.get_data(prop, 'set{}'.format(i))
            val = cls.properties.get_data(prop, 'val{}'.format(i))
            func = cls.properties.get_data(prop, 'func{}'.format(i))
            if func.dtype == 'bool' or func == -1:
                return # no additional stats to yield
            yield {'stat': stat, 'set_': set_, 'val': val, 'func': func}