Skip to content

Exploration

Niphyr edited this page Sep 28, 2020 · 4 revisions

Shadowlands

Item stats

Entry point

ItemId. The item can then be looked up in item_data.inc.

Items initialised at init_items()

sc_player.cpp:1700:player_t.init_items() calls 1777:item.init() to get item stats.

item.cpp:1260:item_t::init() This contains a bunch of methods to do various things, one of which is to use item data from the lookup to calculate through decode_stats() then stat_value().

  for ( size_t i = 0; i < range::size( parsed.data.stat_type_e ); i++ )
  {
    stat_e s = stat( i );
    if ( s == STAT_NONE )
      continue;

    base_stats.add_stat( s, stat_value( i ) );
    stats.add_stat( s, stat_value( i ) );
  }

First stat_type_e is checked, then a stat_e is cast against the stat stat_type_e value as item_mod:

stat_e util::translate_item_mod( int item_mod )
{
  switch ( item_mod )
  {
    case ITEM_MOD_AGILITY:             return STAT_AGILITY;
    case ITEM_MOD_STRENGTH:            return STAT_STRENGTH;
    case ITEM_MOD_INTELLECT:           return STAT_INTELLECT;
    case ITEM_MOD_SPIRIT:              return STAT_SPIRIT;
    case ITEM_MOD_STAMINA:             return STAT_STAMINA;
    case ITEM_MOD_DODGE_RATING:        return STAT_DODGE_RATING;
    case ITEM_MOD_PARRY_RATING:        return STAT_PARRY_RATING;
    case ITEM_MOD_BLOCK_RATING:        return STAT_BLOCK_RATING;
    case ITEM_MOD_HIT_RATING:          return STAT_HIT_RATING;
    case ITEM_MOD_CRIT_RATING:         return STAT_CRIT_RATING;
    case ITEM_MOD_HASTE_RATING:        return STAT_HASTE_RATING;
    case ITEM_MOD_EXPERTISE_RATING:    return STAT_EXPERTISE_RATING;
    case ITEM_MOD_ATTACK_POWER:        return STAT_ATTACK_POWER;
    case ITEM_MOD_RANGED_ATTACK_POWER: return STAT_ATTACK_POWER;
    case ITEM_MOD_SPELL_POWER:         return STAT_SPELL_POWER;
    case ITEM_MOD_MASTERY_RATING:      return STAT_MASTERY_RATING;
    case ITEM_MOD_EXTRA_ARMOR:         return STAT_BONUS_ARMOR;
    case ITEM_MOD_RESILIENCE_RATING:   return STAT_RESILIENCE_RATING;
    case ITEM_MOD_PVP_POWER:           return STAT_PVP_POWER;
    case ITEM_MOD_STRENGTH_AGILITY_INTELLECT: return STAT_STR_AGI_INT;
    case ITEM_MOD_AGILITY_INTELLECT:   return STAT_AGI_INT;
    case ITEM_MOD_STRENGTH_AGILITY:    return STAT_STR_AGI;
    case ITEM_MOD_STRENGTH_INTELLECT:  return STAT_STR_INT;
    case ITEM_MOD_VERSATILITY_RATING:  return STAT_VERSATILITY_RATING;
    case ITEM_MOD_LEECH_RATING:        return STAT_LEECH_RATING;
    case ITEM_MOD_SPEED_RATING:        return STAT_SPEED_RATING;
    case ITEM_MOD_AVOIDANCE_RATING:    return STAT_AVOIDANCE_RATING;
    case ITEM_MOD_CORRUPTION:          return STAT_CORRUPTION;
    case ITEM_MOD_CORRUPTION_RESISTANCE: return STAT_CORRUPTION_RESISTANCE;
    default:                           return STAT_NONE;
  }
}

The first enum is item_mod_type and it is translated to stat_e in sc_enums.hpp:926.

This is used to determine what the stat is that it needs to calculate. Then it's added to the item based on the scaled stat calculations from stat_value(). item.cpp:521:item_t::stat_value then gets its value from item_database::scaled_stat.

Scaled Stat calculations

sc_item_data.cpp:458:item_database::scaled_stat runs through calculating stat scaling.

slot_type is calculated at 479

int slot_type = random_suffix_type( item.parsed.data );

item_database::random_suffix_type is a giant switch, returning the item type.

int item_database::random_suffix_type( const dbc_item_data_t& item )
{
  switch ( item.item_class )
  {
    case ITEM_CLASS_WEAPON:
      switch ( item.item_subclass )
      {
        case ITEM_SUBCLASS_WEAPON_AXE2:
        case ITEM_SUBCLASS_WEAPON_MACE2:
        case ITEM_SUBCLASS_WEAPON_POLEARM:
        case ITEM_SUBCLASS_WEAPON_SWORD2:
        case ITEM_SUBCLASS_WEAPON_STAFF:
        case ITEM_SUBCLASS_WEAPON_GUN:
        case ITEM_SUBCLASS_WEAPON_BOW:
        case ITEM_SUBCLASS_WEAPON_CROSSBOW:
        case ITEM_SUBCLASS_WEAPON_THROWN:
          return 0;
        default:
          return 3;
      }
    case ITEM_CLASS_ARMOR:
      switch ( item.inventory_type )
      {
        case INVTYPE_HEAD:
        case INVTYPE_CHEST:
        case INVTYPE_LEGS:
        case INVTYPE_ROBE:
          return 0;

        case INVTYPE_SHOULDERS:
        case INVTYPE_WAIST:
        case INVTYPE_FEET:
        case INVTYPE_HANDS:
        case INVTYPE_TRINKET:
          return 1;

        case INVTYPE_NECK:
        case INVTYPE_FINGER:
        case INVTYPE_CLOAK:
        case INVTYPE_WRISTS:
          return 2;

        case INVTYPE_WEAPONOFFHAND:
        case INVTYPE_HOLDABLE:
        case INVTYPE_SHIELD:
          return 3;

        default:
          return -1;
      }
    default:
      return -1;
  }

  return -1;
}

Calculate item budget

Item budget is used as part of the stat scaling. The budget is calculated based on slot type and item quality.

The end result is a lookup against the rand_prop_data_t based on item level, slot type (which of the 5 values) and which set of propdata to use depends on the item quality.

First the prop data is obtained for the desired item level, then the item_budget is calculated from there. random property data is stored in the rand_prop_points.inc generated file.

https://github.com/simulationcraft/simc/blob/shadowlands/engine/dbc/generated/rand_prop_points.inc
    const random_prop_data_t& ilevel_data = dbc.random_property( new_ilevel );
    //const random_prop_data_t& orig_data = dbc.random_property( item.level );

    // Epic/Legendary
    if ( item.parsed.data.quality == 4 || item.parsed.data.quality == 5 )
    {
      item_budget = ilevel_data.p_epic[ slot_type ];
      //orig_budget = orig_data.p_epic[ slot_type ];
    }
    // Rare/Heirloom
    else if ( item.parsed.data.quality == 3 || item.parsed.data.quality == 7 )
    {
      item_budget = ilevel_data.p_rare[ slot_type ];
      //orig_budget = orig_data.p_rare[ slot_type ];
    }
    // Rest
    else
    {
      item_budget = ilevel_data.p_uncommon[ slot_type ];
      //orig_budget = orig_data.p_uncommon[ slot_type ];
    }

Obtain socket penalty

sc_item_data.cpp:509 starts the block that has the meat of the calculations. It calculates the socket penalty, then the raw value, gets the stat type then apply the rating multipliers.

We can mostly ignore socket penalty as it always seems to be 0 in bfa/sl items.

The raw stat value is calculated as:

int v_raw = static_cast<int>(item.parsed.data.stat_alloc[ idx ] * item_budget * 0.0001 - v_socket_penalty + 0.5);

Using this specific rounding, and using the items stat allocation value, the item_budget from earlier and the socket penalty of 0 we end up with a raw value of how much stat there should be, as an integer.

Then the stat_type is determined, this is the associated stat_type_e that relates to the same idx we're using for stat_alloc as part of the item.parsed.data properties. Cast this to a item_mod_type (found in data_enums.hh:456)

auto stat_type = static_cast<item_mod_type>( item.parsed.data.stat_type_e[ idx ] );

Apply rating multipliers

Now we have the stat type and raw stat values, we can apply rating multipliers.

    if ( util::is_combat_rating( stat_type ) )
    {
      v_raw = static_cast<int>(apply_combat_rating_multiplier( item, as<double>( v_raw ) ));
    }
    else if ( stat_type == ITEM_MOD_STAMINA )
    {
      v_raw = static_cast<int>(apply_stamina_multiplier( item, as<double>( v_raw ) ));
    }

Combat ratings are applied based on the type of stat. util::is_combat_rating is in `util.cpp:2846:is_combat_rating: and is a big switch statement against item_mod_type. This type comes from the same enum as the previous item_mod_type as previous.

If it matches one of these types, then the apply_combat_rating_multiplier is run, if its stamina then apply_stamina_multiplier otherwise the value is already correct.

apply_combat_rating_multiplier

This is a busy set of nested methods that do a bunch of checks to properly apply the right rating.

First, if the sc_item_data.cpp:1504:item_combat_rating_type method returns rating type. This checks with a giant switch the data_enums.hh:337:inventory_type enum casted data.inventory_type item property and returns a data_enums.hh:14:combat_rating_multiplier_type.

combat_rating_multiplier_type item_database::item_combat_rating_type( const dbc_item_data_t& data )
{
  switch ( data.inventory_type )
  {
    case INVTYPE_NECK:
    case INVTYPE_FINGER:
      return CR_MULTIPLIER_JEWLERY;
    case INVTYPE_TRINKET:
      return CR_MULTIPLIER_TRINKET;
    case INVTYPE_WEAPON:
    case INVTYPE_2HWEAPON:
    case INVTYPE_WEAPONMAINHAND:
    case INVTYPE_WEAPONOFFHAND:
    case INVTYPE_RANGED:
    case INVTYPE_RANGEDRIGHT:
    case INVTYPE_THROWN:
      return CR_MULTIPLIER_WEAPON;
    case INVTYPE_ROBE:
    case INVTYPE_HEAD:
    case INVTYPE_SHOULDERS:
    case INVTYPE_CHEST:
    case INVTYPE_CLOAK:
    case INVTYPE_BODY:
    case INVTYPE_WRISTS:
    case INVTYPE_WAIST:
    case INVTYPE_LEGS:
    case INVTYPE_FEET:
    case INVTYPE_SHIELD:
    case INVTYPE_HOLDABLE:
    case INVTYPE_HANDS:
      return CR_MULTIPLIER_ARMOR;
    default:
      return CR_MULTIPLIER_INVALID;
  }
}

This then feeds into another overload of apply_combat_rating_multiplier.

Applying the correct combat_rating_multiplier

Now that we have type, a lookup is made on ilvl/type against the __combat_ratings_mult_by_ilvl data structure in sc_scale_data.inc.

__combat_ratings_mult_by_ilvl[type][ item_level - 1 ]

This is then multiplied against our earlier raw value to get a new value (referred to in the code still as raw value).

Debugging

FILE.simc html=output.html output=output.txt debug=1 as the project command line parameter.

Then place the FILE.simc file in the .\vs\ folder.

sc_player.cpp:1700:player_t.init_items() calls 1777:item.init() to get item stats.

item.cpp:1260:item_t::init()

item mods are translated in util.cpp line 2026 @ translate_item_mod

slot type comes from sc_item_data.cpp line 598

    case INVTYPE_SHOULDERS:
    case INVTYPE_WAIST:
    case INVTYPE_FEET:
    case INVTYPE_HANDS:
    case INVTYPE_TRINKET:
      return 1;

Get random prop data for the ilvl:

random_prop_data_t based on ilvl_ - this comes from ???

Then depending on the quality type we modify it again from random_prop_data_t

4/5 use p_epic 3/7 use p_rare rest use p_uncommon

For ilvl 158, slot type 1 (trinket), the item budget is 62. v_socket_socket_penalty is 0

stat allocation is 6666 from items stat_alloc.

stat_alloc * item_budget * 0.001 - socket_penalty + 0.5

6666 * 62 * .0001 - 0 + 0.5

Item Budget

First you need the slot type. Trinket is 1 Then you need the quality. This trinket is quality 3 or rare. Now you use that to look at ilvldata.p_rare in the random prop data for that ilvl

Get the ilvl row from rand_prop_points.inc in the __rand_prop_points_data collection. This item is ilvl 158:

{  158,20,28, {82,62,46,41,41 }, {82,62,46,41,41 }, {82,62,46,41,41 } },
   ^

Format of this object is from random_prop_points.hpp

struct random_prop_data_t
{
  unsigned ilevel;
  unsigned damage_replace_stat;
  unsigned damage_secondary;
  float    p_epic[5];
  float    p_rare[5];
  float    p_uncommon[5];
}

So now to get the 62, we dive into the p_rare which is the 2nd subarray and grab the trinket value (element 1).

{  158,20,28, {82,62,46,41,41 }, {82,62,46,41,41 }, {82,62,46,41,41 } },
                                     ^

Stat Allocation

The 6666 is from this entry on line 39905 in __item_stats_data (item_data.inc)

  { 71,  6666, 0.000000 },

This line number comes from the item entry. Find itemId 184053 ("Sinful Aspirant's Relentless Brooch") in the same file in the __item_data list. which references &__item_stats_data[39903] for item stats data (entry 39903 in __item_stats_data).

Spell Data

spelleffect_data_t::average in spell_data.cpp

  auto budget = item_database::item_budget( item, _spell -> max_scaling_level() );
  if ( _spell -> scaling_class() == PLAYER_SPECIAL_SCALE7 )
  {
    budget = item_database::apply_combat_rating_multiplier( *item, budget );
  }
  else if ( _spell -> scaling_class() == PLAYER_SPECIAL_SCALE8 )
  {
    const auto& props = item -> player -> dbc->random_property( item -> item_level() );
    budget = props.damage_replace_stat;
  }
  else if ( _spell->scaling_class() == PLAYER_NONE &&
            _spell->flags( spell_attribute::SX_SCALE_ILEVEL ) )
  {
    const auto& props = item -> player -> dbc->random_property( item -> item_level() );
    budget = props.damage_secondary;
  }

  return _m_coeff * budget;

base_budget = 52 combat_rating_multiplier type = TRINKET (2)

ilvl = 110

cr multi = (using type & ilvl): return __combat_ratings_mult_by_ilvl[type][ item_level - 1 ];

cr multi = 1.2892817710000000

cr multi (scaled budget) = 1.2892817710000000 * 52 = 67.042652092


For ilvl 183 trinket Crimson Chorus (id=344806) with coeff of 0.424566

trinket penalty is 1.528306865 from sc_scale_data.inc's trinket array (2) in __combat_ratings_mult_by_ilvl at 183 ilvl.

base_budget is 104 from rand_prop_points.inc @ ilvl 183. The first array is Epic quality, and the first value is the base scale value.

104 * 1.528306865 = 158.94391396 158.94391396 * 0.424566 = 67.48218177434136

Tooltip value @ ilvl 183 = 201. int(67.48218177434136) * 3 = 201

BFA Version

Add item bonuses and stats:

sc_item_data.cpp:577 
item_database::apply_item_bonus

Switches based on entry.type. entry is item_bonus_entry_t the definition is found at data_definitions.hh

struct item_bonus_entry_t
{
  unsigned id;
  unsigned bonus_id;
  unsigned type;
  int      value_1;
  int      value_2;
  unsigned index;
};

type is the enum item_bonus_type from data_enums.hh_

enum item_bonus_type
{
  ITEM_BONUS_ILEVEL    = 1,
  ITEM_BONUS_MOD       = 2,
  ITEM_BONUS_QUALITY   = 3,
  ITEM_BONUS_DESC      = 4,
  ITEM_BONUS_SUFFIX    = 5,
  ITEM_BONUS_SOCKET    = 6,
  ITEM_BONUS_REQ_LEVEL = 8,
  ITEM_BONUS_SCALING   = 11, // Scaling based on ScalingStatDistribution.db2
  ITEM_BONUS_SCALING_2 = 13, // Scaling based on ScalingStatDistribution.db2
  ITEM_BONUS_SET_ILEVEL= 14,
  ITEM_BONUS_ADD_RANK  = 17, // Add artifact power rank to a specific trait
  ITEM_BONUS_ADD_ITEM_EFFECT = 23,
};

For adding bonuses:

  • ITEM_BONUS_LEVEL the value is in the entry.value_1 field, gets added to the items ilvl
  • ITEM_BONUS_MOD the value_1 is the item modifier and the value_2 is the allocation percent

the item modifier is one of these from data_enums.hh:

enum item_mod_type {
  ITEM_MOD_NONE                     = -1,
  ITEM_MOD_MANA                     = 0,
  ITEM_MOD_HEALTH                   = 1,
  ITEM_MOD_AGILITY                  = 3,
  ITEM_MOD_STRENGTH                 = 4,
  ITEM_MOD_INTELLECT                = 5,
  ITEM_MOD_SPIRIT                   = 6,
  ITEM_MOD_STAMINA                  = 7,
  ITEM_MOD_DEFENSE_SKILL_RATING     = 12,
  ITEM_MOD_DODGE_RATING             = 13,
  ITEM_MOD_PARRY_RATING             = 14,
  ITEM_MOD_BLOCK_RATING             = 15,
  ITEM_MOD_HIT_MELEE_RATING         = 16,
  ITEM_MOD_HIT_RANGED_RATING        = 17,
  ITEM_MOD_HIT_SPELL_RATING         = 18,
  ITEM_MOD_CRIT_MELEE_RATING        = 19,
  ITEM_MOD_CRIT_RANGED_RATING       = 20,
  ITEM_MOD_CRIT_SPELL_RATING        = 21,
  ITEM_MOD_CORRUPTION               = 22,
  ITEM_MOD_CORRUPTION_RESISTANCE    = 23, // TODO: Guess
  ITEM_MOD_UNSUSED                  = 24,
  ITEM_MOD_CRIT_TAKEN_MELEE_RATING  = 25,
  ITEM_MOD_CRIT_TAKEN_RANGED_RATING = 26,
  ITEM_MOD_CRIT_TAKEN_SPELL_RATING  = 27,
  ITEM_MOD_HASTE_MELEE_RATING       = 28,
  ITEM_MOD_HASTE_RANGED_RATING      = 29,
  ITEM_MOD_HASTE_SPELL_RATING       = 30,
  ITEM_MOD_HIT_RATING               = 31,
  ITEM_MOD_CRIT_RATING              = 32,
  ITEM_MOD_HIT_TAKEN_RATING         = 33,
  ITEM_MOD_CRIT_TAKEN_RATING        = 34,
  ITEM_MOD_RESILIENCE_RATING        = 35,
  ITEM_MOD_HASTE_RATING             = 36,
  ITEM_MOD_EXPERTISE_RATING         = 37,
  ITEM_MOD_ATTACK_POWER             = 38,
  ITEM_MOD_RANGED_ATTACK_POWER      = 39,
  ITEM_MOD_VERSATILITY_RATING       = 40,
  ITEM_MOD_SPELL_HEALING_DONE       = 41,                 // deprecated
  ITEM_MOD_SPELL_DAMAGE_DONE        = 42,                 // deprecated
  ITEM_MOD_MANA_REGENERATION        = 43,
  ITEM_MOD_ARMOR_PENETRATION_RATING = 44,
  ITEM_MOD_SPELL_POWER              = 45,
  ITEM_MOD_HEALTH_REGEN             = 46,
  ITEM_MOD_SPELL_PENETRATION        = 47,
  ITEM_MOD_BLOCK_VALUE              = 48,
  ITEM_MOD_MASTERY_RATING           = 49,
  ITEM_MOD_EXTRA_ARMOR              = 50,
  ITEM_MOD_FIRE_RESISTANCE          = 51,
  ITEM_MOD_FROST_RESISTANCE         = 52,
  ITEM_MOD_HOLY_RESISTANCE          = 53,
  ITEM_MOD_SHADOW_RESISTANCE        = 54,
  ITEM_MOD_NATURE_RESISTANCE        = 55,
  ITEM_MOD_ARCANE_RESISTANCE        = 56,
  ITEM_MOD_PVP_POWER                = 57,
  ITEM_MOD_MULTISTRIKE_RATING       = 59,
  ITEM_MOD_READINESS_RATING         = 60,
  ITEM_MOD_SPEED_RATING             = 61,
  ITEM_MOD_LEECH_RATING             = 62,
  ITEM_MOD_AVOIDANCE_RATING         = 63,
  ITEM_MOD_INDESTRUCTIBLE           = 64,
  ITEM_MOD_WOD_5                    = 65,
  ITEM_MOD_WOD_6                    = 66,
  ITEM_MOD_STRENGTH_AGILITY_INTELLECT = 71,
  ITEM_MOD_STRENGTH_AGILITY         = 72,
  ITEM_MOD_AGILITY_INTELLECT        = 73,
  ITEM_MOD_STRENGTH_INTELLECT       = 74,
};
  • ITEM_BONUS_SOCKET has number of sockets in value_1 and the type (colour) of socket in value_2

  • ITEM_BONUS_ADD_ITEM_EFFECT- ???

Clone this wiki locally