//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: EconItemSchema: Defines a schema for econ items // //============================================================================= #include "cbase.h" #include "econ_item_schema.h" #include "tier1/fmtstr.h" #include "tier1/UtlSortVector.h" #include "tier2/tier2.h" #include "filesystem.h" #include "schemainitutils.h" #include "gcsdk/gcsdk_auto.h" #include "rtime.h" #include "item_selection_criteria.h" #include "crypto.h" #include "checksum_sha1.h" #include #include #include "materialsystem/imaterialsystem.h" #include "materialsystem/itexture.h" #include "materialsystem/itexturecompositor.h" #if ( defined( _MSC_VER ) && _MSC_VER >= 1900 ) #define timezone _timezone #define daylight _daylight #endif // For holiday-limited loot lists. #include "econ_holidays.h" // Only used for startup testing. #include "econ_item_tools.h" #if defined(CLIENT_DLL) || defined(GAME_DLL) #include "econ_item_system.h" #include "econ_item.h" #include "activitylist.h" #if defined(TF_CLIENT_DLL) || defined(TF_DLL) #include "tf_gcmessages.h" #endif #endif #ifdef GC_DLL #include "gcgamebase.h" #include // unique_ptr #endif // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" using namespace GCSDK; CEconItemSchema & GEconItemSchema() { #if defined( EXTERNALTESTS_DLL ) static CEconItemSchema g_econItemSchema; return g_econItemSchema; #elif defined( GC_DLL ) return *GEconManager()->GetItemSchema(); #else return *ItemSystem()->GetItemSchema(); #endif } const char *g_szDropTypeStrings[] = { "", // Blank and none mean the same thing: stay attached to the body. "none", "drop", // The item drops off the body. "break", // Not implemented, but an example of a type that could be added. }; const char *g_TeamVisualSections[TEAM_VISUAL_SECTIONS] = { "visuals", // TF_TEAM_UNASSIGNED. Visual changes applied to both teams. NULL, // TF_TEAM_SPECTATOR. Unused. "visuals_red", // TF_TEAM_RED "visuals_blu", // TF_TEAM_BLUE "visuals_mvm_boss", // Hack to override things in MvM at a general level }; int GetTeamVisualsFromString( const char *pszString ) { for ( int i = 0; i < TEAM_VISUAL_SECTIONS; i++ ) { // There's a NULL hidden in g_TeamVisualSections if ( g_TeamVisualSections[i] && !Q_stricmp( pszString, g_TeamVisualSections[i] ) ) return i; } return -1; } #if defined(CLIENT_DLL) || defined(GAME_DLL) // Used to convert strings to ints for wearable animation types const char *g_WearableAnimTypeStrings[ NUM_WAP_TYPES ] = { "on_spawn", // WAP_ON_SPAWN, "start_building", // WAP_START_BUILDING, "stop_building", // WAP_STOP_BUILDING, "start_taunting", // WAP_START_TAUNTING, "stop_taunting", // WAP_STOP_TAUNTING, }; #endif const char *g_AttributeDescriptionFormats[] = { "value_is_percentage", // ATTDESCFORM_VALUE_IS_PERCENTAGE, "value_is_inverted_percentage", // ATTDESCFORM_VALUE_IS_INVERTED_PERCENTAGE "value_is_additive", // ATTDESCFORM_VALUE_IS_ADDITIVE "value_is_additive_percentage", // ATTDESCFORM_VALUE_IS_ADDITIVE_PERCENTAGE "value_is_or", // ATTDESCFORM_VALUE_IS_OR "value_is_date", // ATTDESCFORM_VALUE_IS_DATE "value_is_account_id", // ATTDESCFORM_VALUE_IS_ACCOUNT_ID "value_is_particle_index", // ATTDESCFORM_VALUE_IS_PARTICLE_INDEX -> Could change to "string index" "value_is_killstreakeffect_index", // ATTDESCFORM_VALUE_IS_KILLSTREAKEFFECT_INDEX -> Could change to "string index" "value_is_killstreak_idleeffect_index", // ATTDESCFORM_VALUE_IS_KILLSTREAK_IDLEEFFECT_INDEX "value_is_item_def", // ATTDESCFORM_VALUE_IS_ITEM_DEF "value_is_from_lookup_table", // ATTDESCFORM_VALUE_IS_FROM_LOOKUP_TABLE }; const char *g_EffectTypes[NUM_EFFECT_TYPES] = { "unusual", // ATTRIB_EFFECT_UNUSUAL, "strange", // ATTRIB_EFFECT_STRANGE, "neutral", // ATTRIB_EFFECT_NEUTRAL = 0, "positive", // ATTRIB_EFFECT_POSITIVE, "negative", // ATTRIB_EFFECT_NEGATIVE, }; //----------------------------------------------------------------------------- // Purpose: Set the capabilities bitfield based on whether the entry is true/false. //----------------------------------------------------------------------------- const char *g_Capabilities[] = { "paintable", // ITEM_CAP_PAINTABLE "nameable", // ITEM_CAP_NAMEABLE "decodable", // ITEM_CAP_DECODABLE "can_craft_if_purchased", // ITEM_CAP_CAN_BE_CRAFTED_IF_PURCHASED "can_customize_texture", // ITEM_CAP_CAN_CUSTOMIZE_TEXTURE "usable", // ITEM_CAP_USABLE "usable_gc", // ITEM_CAP_USABLE_GC "can_gift_wrap", // ITEM_CAP_CAN_GIFT_WRAP "usable_out_of_game", // ITEM_CAP_USABLE_OUT_OF_GAME "can_collect", // ITEM_CAP_CAN_COLLECT "can_craft_count", // ITEM_CAP_CAN_CRAFT_COUNT "can_craft_mark", // ITEM_CAP_CAN_CRAFT_MARK "paintable_team_colors", // ITEM_CAP_PAINTABLE_TEAM_COLORS "can_be_restored", // ITEM_CAP_CAN_BE_RESTORED "strange_parts", // ITEM_CAP_CAN_USE_STRANGE_PARTS "can_card_upgrade", // ITEM_CAP_CAN_CARD_UPGRADE "can_strangify", // ITEM_CAP_CAN_STRANGIFY "can_killstreakify", // ITEM_CAP_CAN_KILLSTREAKIFY "can_consume", // ITEM_CAP_CAN_CONSUME_ITEMS "can_spell_page", // ITEM_CAP_CAN_SPELLBOOK_PAGE "has_slots", // ITEM_CAP_HAS_SLOTS "duck_upgradable", // ITEM_CAP_DUCK_UPGRADABLE "can_unusualify", // ITEM_CAP_CAN_UNUSUALIFY }; COMPILE_TIME_ASSERT( ARRAYSIZE(g_Capabilities) == NUM_ITEM_CAPS ); #define RETURN_ATTRIBUTE_STRING( attrib_name, default_string ) \ static CSchemaAttributeDefHandle pAttribString( attrib_name ); \ const char *pchResultAttribString = default_string; \ FindAttribute_UnsafeBitwiseCast< CAttribute_String >( this, pAttribString, &pchResultAttribString ); \ return pchResultAttribString; #define RETURN_ATTRIBUTE_STRING_F( func_name, attrib_name, default_string ) \ const char *func_name( void ) const { RETURN_ATTRIBUTE_STRING( attrib_name, default_string ) } static void ParseCapability( item_capabilities_t &capsBitfield, KeyValues* pEntry ) { int idx = StringFieldToInt( pEntry->GetName(), g_Capabilities, ARRAYSIZE(g_Capabilities) ); if ( idx < 0 ) { return; } int bit = 1 << idx; if ( pEntry->GetBool() ) { (int&)capsBitfield |= bit; } else { (int&)capsBitfield &= ~bit; } } #ifdef GC_DLL static bool BGetPaymentRule( KeyValues *pKVRule, EPaymentRuleType *out_pePaymentRuleType, double *out_pRevenueShare ) { Assert( out_pePaymentRuleType ); Assert( out_pRevenueShare ); struct payment_rule_lookup_t { const char *m_pszStr; EPaymentRuleType m_eRuleType; }; static payment_rule_lookup_t s_Lookup[] = { { "workshop_revenue_share", kPaymentRule_SteamWorkshopFileID }, { "partner_revenue_share", kPaymentRule_PartnerSteamID }, { "bundle_revenue_share", kPaymentRule_Bundle }, }; for ( int i = 0; i < ARRAYSIZE( s_Lookup ); i++ ) { KeyValues *pKVKey = pKVRule->FindKey( s_Lookup[i].m_pszStr ); if ( !pKVKey ) continue; *out_pePaymentRuleType = s_Lookup[i].m_eRuleType; *out_pRevenueShare = atof( pKVKey->GetString() ) * 100.0; // KeyValues doesn't support parsing a string as a double-precision value, so we do it by hand return true; } return false; } #endif // GC_DLL //----------------------------------------------------------------------------- // Purpose: CEconItemSeriesDefinition //----------------------------------------------------------------------------- CEconItemSeriesDefinition::CEconItemSeriesDefinition( void ) : m_nValue( INT_MAX ) { } //----------------------------------------------------------------------------- // Purpose: Copy constructor //----------------------------------------------------------------------------- CEconItemSeriesDefinition::CEconItemSeriesDefinition( const CEconItemSeriesDefinition &that ) { ( *this ) = that; } //----------------------------------------------------------------------------- // Purpose: Operator= //----------------------------------------------------------------------------- CEconItemSeriesDefinition &CEconItemSeriesDefinition::operator=( const CEconItemSeriesDefinition &rhs ) { m_nValue = rhs.m_nValue; m_strName = rhs.m_strName; return *this; } //----------------------------------------------------------------------------- // Purpose: Initialize the quality definition // Input: pKVQuality - The KeyValues representation of the quality // schema - The overall item schema for this attribute // pVecErrors - An optional vector that will contain error messages if // the init fails. // Output: True if initialization succeeded, false otherwise //----------------------------------------------------------------------------- bool CEconItemSeriesDefinition::BInitFromKV( KeyValues *pKVSeries, CUtlVector *pVecErrors /* = NULL */ ) { m_nValue = pKVSeries->GetInt( "value", -1 ); m_strName = pKVSeries->GetName(); m_strLockKey = pKVSeries->GetString( "loc_key" ); m_strUiFile = pKVSeries->GetString( "ui" ); // Check for required fields SCHEMA_INIT_CHECK( NULL != pKVSeries->FindKey( "value" ), "Quality definition %s: Missing required field \"value\"", pKVSeries->GetName() ); return SCHEMA_INIT_SUCCESS(); } //----------------------------------------------------------------------------- // Purpose: Constructor //----------------------------------------------------------------------------- CEconItemQualityDefinition::CEconItemQualityDefinition( void ) : m_nValue( INT_MAX ) , m_bCanSupportSet( false ) { } //----------------------------------------------------------------------------- // Purpose: Copy constructor //----------------------------------------------------------------------------- CEconItemQualityDefinition::CEconItemQualityDefinition( const CEconItemQualityDefinition &that ) { (*this) = that; } //----------------------------------------------------------------------------- // Purpose: Operator= //----------------------------------------------------------------------------- CEconItemQualityDefinition &CEconItemQualityDefinition::operator=( const CEconItemQualityDefinition &rhs ) { m_nValue = rhs.m_nValue; m_strName = rhs.m_strName; m_bCanSupportSet = rhs.m_bCanSupportSet; return *this; } //----------------------------------------------------------------------------- // Purpose: Initialize the quality definition // Input: pKVQuality - The KeyValues representation of the quality // schema - The overall item schema for this attribute // pVecErrors - An optional vector that will contain error messages if // the init fails. // Output: True if initialization succeeded, false otherwise //----------------------------------------------------------------------------- bool CEconItemQualityDefinition::BInitFromKV( KeyValues *pKVQuality, CUtlVector *pVecErrors /* = NULL */ ) { m_nValue = pKVQuality->GetInt( "value", -1 ); m_strName = pKVQuality->GetName(); m_bCanSupportSet = pKVQuality->GetBool( "canSupportSet" ); #ifdef GC_DLL m_strHexColor = pKVQuality->GetString( "hexColor" ); #endif // GC_DLL // Check for required fields SCHEMA_INIT_CHECK( NULL != pKVQuality->FindKey( "value" ), "Quality definition %s: Missing required field \"value\"", pKVQuality->GetName() ); #if defined(CLIENT_DLL) || defined(GAME_DLL) return SCHEMA_INIT_SUCCESS(); #endif // GC_DLL // Check for data consistency SCHEMA_INIT_CHECK( 0 != Q_stricmp( GetName(), "any" ), "Quality definition any: The quality name \"any\" is a reserved keyword and cannot be used." ); SCHEMA_INIT_CHECK( m_nValue != k_unItemQuality_Any, "Quality definition %s: Invalid value (%d). It is reserved for Any", GetName(), k_unItemQuality_Any ); return SCHEMA_INIT_SUCCESS(); } //----------------------------------------------------------------------------- // CEconItemRarityDefinition //----------------------------------------------------------------------------- CEconItemRarityDefinition::CEconItemRarityDefinition( void ) : m_nValue( INT_MAX ) , m_nLootlistWeight( 0 ) { } //----------------------------------------------------------------------------- // Purpose: Initialize the rarity definition //----------------------------------------------------------------------------- bool CEconItemRarityDefinition::BInitFromKV( KeyValues *pKVRarity, KeyValues *pKVRarityWeights, CEconItemSchema &pschema, CUtlVector *pVecErrors /* = NULL */ ) { m_nValue = pKVRarity->GetInt( "value", -1 ); m_strName = pKVRarity->GetName(); m_strLocKey = pKVRarity->GetString( "loc_key" ); m_strWepLocKey = pKVRarity->GetString( "loc_key_weapon" ); m_iAttribColor = GetAttribColorIndexForName( pKVRarity->GetString( "color" ) ); m_strDropSound = pKVRarity->GetString( "drop_sound" ); m_strNextRarity = pKVRarity->GetString( "next_rarity" ); // Not required. #ifdef GC_DLL if ( pKVRarityWeights ) { m_nLootlistWeight = pKVRarityWeights->GetInt( m_strName, 0 ); } #endif // // Check for required fields SCHEMA_INIT_CHECK( NULL != pKVRarity->FindKey( "value" ), "Rarity definition %s: Missing required field \"value\"", pKVRarity->GetName() ); SCHEMA_INIT_CHECK( NULL != pKVRarity->FindKey( "loc_key" ), "Rarity definition %s: Missing required field \"loc_key\"", pKVRarity->GetName() ); return SCHEMA_INIT_SUCCESS(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CEconColorDefinition::BInitFromKV( KeyValues *pKVColor, CUtlVector *pVecErrors /* = NULL */ ) { m_strName = pKVColor->GetName(); m_strColorName = pKVColor->GetString( "color_name" ); #ifdef GC_DLL m_strHexColor = pKVColor->GetString( "hex_color" ); #endif // GC_DLL SCHEMA_INIT_CHECK( !m_strColorName.IsEmpty(), "Quality definition %s: missing \"color_name\"", GetName() ); #ifdef GC_DLL SCHEMA_INIT_CHECK( !m_strHexColor.IsEmpty(), "Quality definition %s: missing \"hex_color\"", GetName() ); #endif // GC_DLL return SCHEMA_INIT_SUCCESS(); } //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- CEconItemSetDefinition::CEconItemSetDefinition( void ) : m_pszName( NULL ) , m_pszLocalizedName( NULL ) , m_iBundleItemDef( INVALID_ITEM_DEF_INDEX ) , m_bIsHiddenSet( false ) { } //----------------------------------------------------------------------------- // Purpose: Copy constructor //----------------------------------------------------------------------------- CEconItemSetDefinition::CEconItemSetDefinition( const CEconItemSetDefinition &that ) { (*this) = that; } //----------------------------------------------------------------------------- // Purpose: Operator= //----------------------------------------------------------------------------- CEconItemSetDefinition &CEconItemSetDefinition::operator=( const CEconItemSetDefinition &other ) { m_pszName = other.m_pszName; m_pszLocalizedName = other.m_pszLocalizedName; m_iItemDefs = other.m_iItemDefs; m_iAttributes = other.m_iAttributes; m_iBundleItemDef = other.m_iBundleItemDef; m_bIsHiddenSet = other.m_bIsHiddenSet; return *this; } //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- bool CEconItemSetDefinition::BInitFromKV( KeyValues *pKVItemSet, CUtlVector *pVecErrors ) { m_pszName = pKVItemSet->GetName(); m_iBundleItemDef = INVALID_ITEM_DEF_INDEX; const char *pszBundleName = pKVItemSet->GetString( "store_bundle" ); if ( pszBundleName && pszBundleName[0] ) { CEconItemDefinition *pDef = GetItemSchema()->GetItemDefinitionByName( pszBundleName ); if ( pDef ) { m_iBundleItemDef = pDef->GetDefinitionIndex(); } SCHEMA_INIT_CHECK( pDef != NULL, "Item set %s: Bundle definition \"%s\" was not found", m_pszName, pszBundleName ); } m_pszLocalizedName = pKVItemSet->GetString( "name", NULL ); m_bIsHiddenSet = pKVItemSet->GetBool( "is_hidden_set", false ); KeyValues *pKVItems = pKVItemSet->FindKey( "items" ); if ( pKVItems ) { FOR_EACH_SUBKEY( pKVItems, pKVItem ) { const char *pszName = pKVItem->GetName(); CEconItemDefinition *pDef = GetItemSchema()->GetItemDefinitionByName( pszName ); SCHEMA_INIT_CHECK( pDef != NULL, "Item set %s: Item definition \"%s\" was not found", m_pszName, pszName ); const item_definition_index_t unDefIndex = pDef->GetDefinitionIndex(); SCHEMA_INIT_CHECK( !m_iItemDefs.IsValidIndex( m_iItemDefs.Find( unDefIndex ) ), "Item set %s: item definition \"%s\" appears multiple times", m_pszName, pszName ); SCHEMA_INIT_CHECK( !pDef->GetItemSetDefinition(), "Item set %s: item definition \"%s\" specified in multiple item sets", m_pszName, pszName ); m_iItemDefs.AddToTail( unDefIndex ); pDef->SetItemSetDefinition( this ); // FIXME: hack to work around crafting item criteria pDef->GetRawDefinition()->SetString( "item_set", m_pszName ); } } KeyValues *pKVAttributes = pKVItemSet->FindKey( "attributes" ); if ( pKVAttributes ) { FOR_EACH_SUBKEY( pKVAttributes, pKVAttribute ) { const char *pszName = pKVAttribute->GetName(); const CEconItemAttributeDefinition *pAttrDef = GetItemSchema()->GetAttributeDefinitionByName( pszName ); SCHEMA_INIT_CHECK( pAttrDef != NULL, "Item set %s: Attribute definition \"%s\" was not found", m_pszName, pszName ); SCHEMA_INIT_CHECK( pAttrDef->BIsSetBonusAttribute(), "Item set %s: Attribute definition \"%s\" is not a set bonus attribute", m_pszName, pszName ); int iIndex = m_iAttributes.AddToTail(); m_iAttributes[iIndex].m_iAttribDefIndex = pAttrDef->GetDefinitionIndex(); m_iAttributes[iIndex].m_flValue = pKVAttribute->GetFloat( "value" ); } } // Sanity check. SCHEMA_INIT_CHECK( m_pszLocalizedName != NULL, "Item set %s: Set contains no localized name", m_pszName ); SCHEMA_INIT_CHECK( m_iItemDefs.Count() > 0, "Item set %s: Set contains no items", m_pszName ); return SCHEMA_INIT_SUCCESS(); } //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- void CEconItemSetDefinition::IterateAttributes( class IEconItemAttributeIterator *pIterator ) const { FOR_EACH_VEC( m_iAttributes, i ) { const itemset_attrib_t& itemsetAttrib = m_iAttributes[i]; const CEconItemAttributeDefinition *pAttrDef = GetItemSchema()->GetAttributeDefinition( itemsetAttrib.m_iAttribDefIndex ); if ( !pAttrDef ) continue; const ISchemaAttributeType *pAttrType = pAttrDef->GetAttributeType(); Assert( pAttrType ); // We know (and assert) that we only need 32 bits of data to store this attribute // data. We don't know anything about the type but we'll let the type handle it // below. attribute_data_union_t value; value.asFloat = itemsetAttrib.m_flValue; if ( !pAttrType->OnIterateAttributeValue( pIterator, pAttrDef, value ) ) return; } } //----------------------------------------------------------------------------- CEconItemCollectionDefinition::CEconItemCollectionDefinition( void ) : m_pszName( NULL ) , m_pszLocalizedName( NULL ) , m_pszLocalizedDesc( NULL ) , m_iRarityMin( k_unItemRarity_Any ) , m_iRarityMax( k_unItemRarity_Any ) { } //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- static int SortCollectionByRarity( item_definition_index_t const *a, item_definition_index_t const *b ) { Assert( a ); Assert( *a ); Assert( b ); Assert( *b ); CEconItemDefinition *pItemA = GetItemSchema()->GetItemDefinition( *a ); CEconItemDefinition *pItemB = GetItemSchema()->GetItemDefinition( *b ); if ( !pItemA || !pItemB ) { AssertMsg( 0, "ItemDef Doesn't exist for sorting" ); return 1; } // If same Rarity, leave in current position? if ( pItemA->GetRarity() == pItemB->GetRarity() && pItemA->GetCustomPainkKitDefinition() && pItemB->GetCustomPainkKitDefinition() ) { #ifdef CLIENT_DLL // Sort by localized name // paintkits sort by paintkit name auto paintkitA = pItemA->GetCustomPainkKitDefinition(); auto paintkitB = pItemB->GetCustomPainkKitDefinition(); auto paintkitALocName = paintkitA->GetLocalizeName(); auto paintkitBLocName = paintkitB->GetLocalizeName(); auto pkALocalized = g_pVGuiLocalize->Find( paintkitALocName ); auto pkBLocalized = g_pVGuiLocalize->Find( paintkitBLocName ); if ( pkALocalized ) { if ( pkBLocalized ) { return V_wcscmp( pkALocalized, pkBLocalized ); } else { return -1; } } else { return pkBLocalized ? 1 : -1; } #else return 0; #endif } return ( pItemA->GetRarity() > pItemB->GetRarity() ) ? -1 : 1; } //----------------------------------------------------------------------------- bool CEconItemCollectionDefinition::BInitFromKV( KeyValues *pKVPItemCollection, CUtlVector *pVecErrors ) { m_pszName = pKVPItemCollection->GetName(); m_pszLocalizedName = pKVPItemCollection->GetString( "name", NULL ); m_pszLocalizedDesc = pKVPItemCollection->GetString( "description", NULL ); m_bIsReferenceCollection = pKVPItemCollection->GetBool( "is_reference_collection", false ); KeyValues *pKVItems = pKVPItemCollection->FindKey( "items" ); // Create a 'lootlist' from this collection KeyValues *pCollectionLootList = NULL; bool bIsLootList = false; if ( !m_bIsReferenceCollection ) { pCollectionLootList = new KeyValues( m_pszName ); } if ( pKVItems ) { // Traverse rarity items and set rarity // Create a lootlist if applicable FOR_EACH_TRUE_SUBKEY( pKVItems, pKVRarity ) { bIsLootList = true; // Get the Rarity Value const CEconItemRarityDefinition *pRarity = GetItemSchema()->GetRarityDefinitionByName( pKVRarity->GetName() ); SCHEMA_INIT_CHECK( pRarity != NULL, "Item collection %s: Rarity type \"%s\" was not found", m_pszName, pKVRarity->GetName() ); // Create a lootlist if ( !m_bIsReferenceCollection ) { CFmtStr lootlistname( "%s_%s", m_pszName, pRarity->GetName() ); const char *pszName = V_strdup( lootlistname.Get() ); pKVRarity->SetInt( "rarity", pRarity->GetDBValue() ); SCHEMA_INIT_CHECK( GetItemSchema()->BInsertLootlist( pszName, pKVRarity, pVecErrors ), "Invalid collection lootlist %s", pszName ); KeyValues *pTempRarityKey = pKVRarity->FindKey( "rarity" ); Assert( pTempRarityKey ); pKVRarity->RemoveSubKey( pTempRarityKey ); pTempRarityKey->deleteThis(); pCollectionLootList->SetInt( pszName, pRarity->GetLootlistWeight() ); } // Items in the Rarity FOR_EACH_VALUE( pKVRarity, pKVItem ) { const char *pszName = pKVItem->GetName(); CEconItemDefinition *pDef = GetItemSchema()->GetItemDefinitionByName( pszName ); SCHEMA_INIT_CHECK( pDef != NULL, "Item set %s: Item definition \"%s\" was not found", m_pszName, pszName ); const item_definition_index_t unDefIndex = pDef->GetDefinitionIndex(); SCHEMA_INIT_CHECK( !m_iItemDefs.IsValidIndex( m_iItemDefs.Find( unDefIndex ) ), "Item Collection %s: item definition \"%s\" appears multiple times", m_pszName, pszName ); m_iItemDefs.AddToTail( unDefIndex ); // Collection Reference if ( !m_bIsReferenceCollection ) { SCHEMA_INIT_CHECK( !pDef->GetItemCollectionDefinition(), "Item Collection %s: item definition \"%s\" specified in multiple item sets", m_pszName, pszName ); pDef->SetItemCollectionDefinition( this ); } // Item Rarity pDef->SetRarity( pRarity->GetDBValue() ); } } // Loose Items FOR_EACH_VALUE( pKVItems, pKVItem ) { const char *pszName = pKVItem->GetName(); CEconItemDefinition *pDef = GetItemSchema()->GetItemDefinitionByName( pszName ); SCHEMA_INIT_CHECK( pDef != NULL, "Item set %s: Item definition \"%s\" was not found", m_pszName, pszName ); const item_definition_index_t unDefIndex = pDef->GetDefinitionIndex(); SCHEMA_INIT_CHECK( !m_iItemDefs.IsValidIndex( m_iItemDefs.Find( unDefIndex ) ), "Item Collection %s: item definition \"%s\" appears multiple times", m_pszName, pszName ); m_iItemDefs.AddToTail( unDefIndex ); if ( !m_bIsReferenceCollection ) { SCHEMA_INIT_CHECK( !pDef->GetItemCollectionDefinition(), "Item Collection %s: item definition \"%s\" specified in multiple item sets", m_pszName, pszName ); pDef->SetItemCollectionDefinition( this ); } } // Sort by Rarity m_iItemDefs.Sort( &SortCollectionByRarity ); } if ( !m_bIsReferenceCollection && bIsLootList ) { // Insert collection lootlist GetItemSchema()->BInsertLootlist( m_pszName, pCollectionLootList, pVecErrors ); } if ( pCollectionLootList ) { pCollectionLootList->deleteThis(); } // Sorted high to low m_iRarityMax = GetItemSchema()->GetItemDefinition( m_iItemDefs[ 0 ] )->GetRarity(); m_iRarityMin = GetItemSchema()->GetItemDefinition( m_iItemDefs[ m_iItemDefs.Count() - 1] )->GetRarity(); // Verify that there is no gaps in the Rarity (would cause crafting problems and makes no sense) if ( !m_bIsReferenceCollection ) { int iRarityVerify = m_iRarityMax; FOR_EACH_VEC( m_iItemDefs, i ) { int iNextRarity = GetItemSchema()->GetItemDefinition( m_iItemDefs[i] )->GetRarity(); SCHEMA_INIT_CHECK( iRarityVerify - iNextRarity <= 1, "Items in Collection %s: Have a gap in rarity tiers", m_pszName ); iRarityVerify = iNextRarity; } } // Sanity check. SCHEMA_INIT_CHECK( m_pszLocalizedName != NULL, "Item Collection %s: Collection contains no localized name", m_pszName ); SCHEMA_INIT_CHECK( m_pszLocalizedDesc != NULL, "Item Collection %s: Collection contains no localized description", m_pszName ); SCHEMA_INIT_CHECK( m_iItemDefs.Count() > 0, "Item Collection %s: Collection contains no items", m_pszName ); return SCHEMA_INIT_SUCCESS(); } //----------------------------------------------------------------------------- // CEconItemPaintKitDefinition //----------------------------------------------------------------------------- CEconItemPaintKitDefinition::CEconItemPaintKitDefinition( void ) : m_pszName( NULL ) { } //----------------------------------------------------------------------------- CEconItemPaintKitDefinition::~CEconItemPaintKitDefinition( void ) { FOR_EACH_VEC( m_vecPaintKitWearKVP, i ) { if ( m_vecPaintKitWearKVP[i] ) { m_vecPaintKitWearKVP[i]->deleteThis(); } } m_vecPaintKitWearKVP.Purge(); } bool VerifyPaintKitComposite( KeyValues *pKVWearInputItems, const char* pName, int iWearLevel, CUtlVector *pVecErrors ) { SCHEMA_INIT_CHECK( pKVWearInputItems != NULL, "Paint Kit %s: Does not contain Wear Level %d", pName, iWearLevel ); #ifdef CLIENT_DLL int w = 1; int h = 1; int seed = 0; ITextureCompositor* pWeaponSkinBaseCompositor = NULL; SafeAssign( &pWeaponSkinBaseCompositor, materials->NewTextureCompositor( w, h, pName, TF_TEAM_RED, seed, pKVWearInputItems, TEX_COMPOSITE_CREATE_FLAGS_VERIFY_SCHEMA_ONLY ) ); SCHEMA_INIT_CHECK( pWeaponSkinBaseCompositor != NULL, "Could Not Create Weapon Skin Compositor for [%s][Wear %d][Team Red]", pName, iWearLevel); SafeRelease( &pWeaponSkinBaseCompositor ); SafeAssign( &pWeaponSkinBaseCompositor, materials->NewTextureCompositor( w, h, pName, TF_TEAM_BLUE, seed, pKVWearInputItems, TEX_COMPOSITE_CREATE_FLAGS_VERIFY_SCHEMA_ONLY ) ); SCHEMA_INIT_CHECK( pWeaponSkinBaseCompositor != NULL, "Could Not Create Weapon Skin Compositor for [%s][Wear %d][Team BLUE]", pName, iWearLevel ); SafeRelease( &pWeaponSkinBaseCompositor ); #endif // CLIENT_DLL return SCHEMA_INIT_SUCCESS(); } //----------------------------------------------------------------------------- bool CEconItemPaintKitDefinition::BInitFromKV( KeyValues *pKVPItemPaintKit, CUtlVector *pVecErrors ) { m_pszName = pKVPItemPaintKit->GetName(); m_pszLocalizedName = m_pszName; // localization key is same as paintkit name for ease of generation SCHEMA_INIT_CHECK( m_pszLocalizedName != NULL, "Paint Kit %s: PaintKit contains no localized name", m_pszName ); KeyValues *pKVWearInputItems = NULL; pKVWearInputItems = pKVPItemPaintKit->FindKey( "wear_level_1", false ); SCHEMA_INIT_CHECK( VerifyPaintKitComposite( pKVWearInputItems, m_pszName, 1, pVecErrors ), "Could Not Create Weapon Skin Compositor for [%s][Wear %d]", m_pszName, 1 ); m_vecPaintKitWearKVP.AddToTail( pKVWearInputItems->MakeCopy() ); pKVWearInputItems = pKVPItemPaintKit->FindKey( "wear_level_2", false ); SCHEMA_INIT_CHECK( VerifyPaintKitComposite( pKVWearInputItems, m_pszName, 2, pVecErrors ), "Could Not Create Weapon Skin Compositor for [%s][Wear %d]", m_pszName, 2 ); m_vecPaintKitWearKVP.AddToTail( pKVWearInputItems->MakeCopy() ); pKVWearInputItems = pKVPItemPaintKit->FindKey( "wear_level_3", false ); SCHEMA_INIT_CHECK( VerifyPaintKitComposite( pKVWearInputItems, m_pszName, 3, pVecErrors ), "Could Not Create Weapon Skin Compositor for [%s][Wear %d]", m_pszName, 3 ); m_vecPaintKitWearKVP.AddToTail( pKVWearInputItems->MakeCopy() ); pKVWearInputItems = pKVPItemPaintKit->FindKey( "wear_level_4", false ); SCHEMA_INIT_CHECK( VerifyPaintKitComposite( pKVWearInputItems, m_pszName, 4, pVecErrors ), "Could Not Create Weapon Skin Compositor for [%s][Wear %d]", m_pszName, 4 ); m_vecPaintKitWearKVP.AddToTail( pKVWearInputItems->MakeCopy() ); pKVWearInputItems = pKVPItemPaintKit->FindKey( "wear_level_5", false ); SCHEMA_INIT_CHECK( VerifyPaintKitComposite( pKVWearInputItems, m_pszName, 5, pVecErrors ), "Could Not Create Weapon Skin Compositor for [%s][Wear %d]", m_pszName, 5 ); m_vecPaintKitWearKVP.AddToTail( pKVWearInputItems->MakeCopy() ); return SCHEMA_INIT_SUCCESS(); } KeyValues *CEconItemPaintKitDefinition::GetPaintKitWearKV( int nWear ) { // Wear is 1-5 but this vec is 0-4 int iIndex = nWear - 1; if ( !m_vecPaintKitWearKVP.IsValidIndex( iIndex ) ) { iIndex = 0; Assert( m_vecPaintKitWearKVP.IsValidIndex( iIndex ) ); DevMsg( "Invalid Paint Kit or Paint Kit Wear Entry (%s) Wear (%d).", m_pszName, nWear ); return NULL; } return m_vecPaintKitWearKVP[iIndex]; } //----------------------------------------------------------------------------- // CEconOperationDefinition //----------------------------------------------------------------------------- CEconOperationDefinition::CEconOperationDefinition( void ) : m_pszName( NULL ) , m_unRequiredItemDefIndex( INVALID_ITEM_DEF_INDEX ) , m_unGatewayItemDefIndex( INVALID_ITEM_DEF_INDEX ) { } //----------------------------------------------------------------------------- CEconOperationDefinition::~CEconOperationDefinition( void ) { if ( m_pKVItem ) m_pKVItem->deleteThis(); m_pKVItem = NULL; } //----------------------------------------------------------------------------- bool CEconOperationDefinition::BInitFromKV( KeyValues *pKVPOperation, CUtlVector *pVecErrors ) { m_unOperationID = V_atoi( pKVPOperation->GetName() ); m_pszName = pKVPOperation->GetString( "name", NULL ); SCHEMA_INIT_CHECK( m_pszName != NULL, "OperationDefinition %s does not have 'name'", m_pszName ); // initialize required item def index if we specified one const char *pszRequiredName = pKVPOperation->GetString( "required_item_name", NULL ); if ( pszRequiredName ) { CEconItemDefinition *pDef = GetItemSchema()->GetItemDefinitionByName( pszRequiredName ); SCHEMA_INIT_CHECK( pDef != NULL, "OperationDefinition couldn't find item def from required name '%s'", pszRequiredName ); m_unRequiredItemDefIndex = pDef->GetDefinitionIndex(); } const char *pszGatewayItemName = pKVPOperation->GetString( "gateway_item_name", NULL ); if ( pszGatewayItemName ) { CEconItemDefinition *pDef = GetItemSchema()->GetItemDefinitionByName( pszGatewayItemName ); SCHEMA_INIT_CHECK( pDef != NULL, "OperationDefinition couldn't find item def from gateway name '%s'", pszGatewayItemName ); m_unGatewayItemDefIndex = pDef->GetDefinitionIndex(); } if ( m_unGatewayItemDefIndex != INVALID_ITEM_DEF_INDEX ) { SCHEMA_INIT_CHECK( m_unRequiredItemDefIndex != INVALID_ITEM_DEF_INDEX, "If a gateway item is set, a required item must be set! Mismatch in %d", m_unOperationID ); } const char *pszOperationStartDate = pKVPOperation->GetString( "operation_start_date", NULL ); SCHEMA_INIT_CHECK( pszOperationStartDate != NULL, "OperationDefinition %s does not have 'operation_start_date'", m_pszName ); m_OperationStartDate = ( pszOperationStartDate && pszOperationStartDate[0] ) ? CRTime::RTime32FromFmtString( "YYYY-MM-DD hh:mm:ss" , pszOperationStartDate ) : RTime32(0); const char *pszDropEndDate = pKVPOperation->GetString( "stop_giving_to_player_date", NULL ); SCHEMA_INIT_CHECK( pszDropEndDate != NULL, "OperationDefinition %s does not have 'stop_giving_to_player_date'", m_pszName ); m_StopGivingToPlayerDate = ( pszDropEndDate && pszDropEndDate[0] ) ? CRTime::RTime32FromFmtString( "YYYY-MM-DD hh:mm:ss" , pszDropEndDate ) : RTime32(0); const char *pszOperationEndDate = pKVPOperation->GetString( "stop_adding_to_queue_date", NULL ); SCHEMA_INIT_CHECK( pszOperationEndDate != NULL, "OperationDefinition %s does not have 'stop_adding_to_queue_date'", m_pszName ); m_StopAddingToQueueDate = ( pszOperationEndDate && pszOperationEndDate[0] ) ? CRTime::RTime32FromFmtString( "YYYY-MM-DD hh:mm:ss" , pszOperationEndDate ) : RTime32(0); m_pszQuestLogResFile = pKVPOperation->GetString( "quest_log_res_file", NULL ); m_pszQuestListResFile = pKVPOperation->GetString( "quest_list_res_file", NULL ); m_pszOperationLootList = pKVPOperation->GetString( "operation_lootlist" ); SCHEMA_INIT_CHECK( m_pszOperationLootList != NULL, "OperationDefinition %s does not have 'operation_lootlist'", m_pszName ); m_bIsCampaign = pKVPOperation->GetBool( "is_campaign" ); m_unMaxDropCount = pKVPOperation->GetInt( "max_drop_count" ); #ifdef GC_DLL m_rtQueueFreqMin = pKVPOperation->GetFloat( "queue_freq_min" ) * k_nSecondsPerHour; // converts specified hours to seconds SCHEMA_INIT_CHECK( m_rtQueueFreqMin != 0.f, "OperationDefinition %s does not have 'queue_freq_min'", m_pszName ); m_rtQueueFreqMax = pKVPOperation->GetFloat( "queue_freq_max" ) * k_nSecondsPerHour; // converts specified hours to seconds SCHEMA_INIT_CHECK( m_rtQueueFreqMax != 0.f, "OperationDefinition %s does not have 'queue_freq_max'", m_pszName ); SCHEMA_INIT_CHECK( m_rtQueueFreqMin <= m_rtQueueFreqMax, "OperationDefinition %s 'queue_freq_min' must be less than 'queue_freq_max'", m_pszName ); m_rtDropFreqMin = pKVPOperation->GetFloat( "drop_freq_min" ) * k_nSecondsPerHour; // converts specified hours to seconds SCHEMA_INIT_CHECK( m_rtDropFreqMin != 0.f, "OperationDefinition %s does not have 'drop_freq_min'", m_pszName ); m_rtDropFreqMax = pKVPOperation->GetFloat( "drop_freq_max" ) * k_nSecondsPerHour; // converts specified hours to seconds SCHEMA_INIT_CHECK( m_rtDropFreqMax != 0.f, "OperationDefinition %s does not have 'drop_freq_max'", m_pszName ); SCHEMA_INIT_CHECK( m_rtDropFreqMin <= m_rtDropFreqMax, "OperationDefinition %s 'drop_freq_min' must be less than 'drop_freq_max'", m_pszName ); m_unSeed = pKVPOperation->GetInt( "seed_drops" ); m_unMaxHeldDrops = pKVPOperation->GetInt( "max_held_drops" ); m_nMaxQueueCount = pKVPOperation->GetInt( "max_queue_count" ); m_unMaxDropPerThink = pKVPOperation->GetInt( "max_drop_per_think", 1 ); m_pszContractRewardLootlist[ REWARD_CASE ] = pKVPOperation->GetString( "contract_reward_case_lootlist" ); m_pszContractRewardLootlist[ REWARD_WEAPON ] = pKVPOperation->GetString( "contract_reward_weapon_lootlist" ); #endif // GC_DLL m_pKVItem = pKVPOperation->MakeCopy(); return SCHEMA_INIT_SUCCESS(); } #ifdef GC_DLL #ifdef STAGING_ONLY GCConVar gc_quick_operation_drop_name( "gc_quick_operation_drop_name", "" ); GCConVar gc_quick_operation_drop_rate( "gc_quick_operation_drop_rate", "10" ); #endif // STAGING_ONLY RTime32 CEconOperationDefinition::GetMinQueueFreq() const { #ifdef STAGING_ONLY if ( Q_stricmp( gc_quick_operation_drop_name.GetString(), m_pszName ) == 0 ) { return gc_quick_operation_drop_rate.GetInt(); } #endif return m_rtQueueFreqMin; } RTime32 CEconOperationDefinition::GetMaxQueueFreq() const { #ifdef STAGING_ONLY if ( Q_stricmp( gc_quick_operation_drop_name.GetString(), m_pszName ) == 0 ) { return gc_quick_operation_drop_rate.GetInt() + 2; } #endif return m_rtQueueFreqMax; } RTime32 CEconOperationDefinition::GetMinDropFreq() const { #ifdef STAGING_ONLY if ( Q_stricmp( gc_quick_operation_drop_name.GetString(), m_pszName ) == 0 ) { return gc_quick_operation_drop_rate.GetInt(); } #endif return m_rtDropFreqMin; } RTime32 CEconOperationDefinition::GetMaxDropFreq() const { #ifdef STAGING_ONLY if ( Q_stricmp( gc_quick_operation_drop_name.GetString(), m_pszName ) == 0 ) { return gc_quick_operation_drop_rate.GetInt() + 2; } #endif return m_rtDropFreqMax; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool BCommonInitPropertyGeneratorsFromKV( const char *pszContext, CUtlVector *out_pvecGenerators, KeyValues *pKV, CUtlVector *pVecErrors ) { // Forward declaration so factory functions can be wherever. IEconItemPropertyGenerator *CreateChangeQualityGenerator( KeyValues *, CUtlVector * ); IEconItemPropertyGenerator *CreateRandomEvenChanceAttrGenerator( KeyValues *, CUtlVector * ); IEconItemPropertyGenerator *CreateUniformLineItemLootListGenerator( KeyValues *, CUtlVector * ); IEconItemPropertyGenerator *CreateDynamicAttrsGenerator( KeyValues *, CUtlVector * ); // "Factory". struct econ_item_property_generator_factory_entry_t { const char *m_pszGeneratorName; IEconItemPropertyGenerator * (* m_funcCreateGeneratorInstance)( KeyValues *, CUtlVector * ); }; static econ_item_property_generator_factory_entry_t s_Generators[] = { { "change_quality", &CreateChangeQualityGenerator }, { "random_even_chance_attr", &CreateRandomEvenChanceAttrGenerator }, { "uniform_line_item_loot_list", &CreateUniformLineItemLootListGenerator }, { "dynamic_attrs", &CreateDynamicAttrsGenerator }, }; Assert( out_pvecGenerators ); Assert( pVecErrors ); // No input data means "we succeeded here". if ( !pKV ) return true; // Handle each generator one at a time. We'll try to initialize the whole set to get as many // errors as possible and then return accumulated errors. FOR_EACH_SUBKEY( pKV, pKVGenerator ) { const char *pszGeneratorName = pKVGenerator->GetName(); IEconItemPropertyGenerator *pGenerator = NULL; for ( const auto& gen : s_Generators ) { if ( Q_stricmp( gen.m_pszGeneratorName, pszGeneratorName ) != 0 ) continue; pGenerator = (*gen.m_funcCreateGeneratorInstance)( pKVGenerator, pVecErrors ); SCHEMA_INIT_CHECK( pGenerator != nullptr, "%s: property generator \"%s\" failed to initialize.\n", pszContext, pszGeneratorName ); out_pvecGenerators->AddToTail( pGenerator ); break; } // Make sure we found a way to create this instance. SCHEMA_INIT_CHECK( pGenerator != nullptr, "%s: unknown type for property generator \"%s\".\n", pszContext, pszGeneratorName ); } return SCHEMA_INIT_SUCCESS(); } #endif // GC_DLL //----------------------------------------------------------------------------- // Dtor //----------------------------------------------------------------------------- CEconLootListDefinition::~CEconLootListDefinition( void ) { #ifdef GC_DLL m_RandomAttribs.PurgeAndDeleteElements(); m_PropertyGenerators.PurgeAndDeleteElements(); #endif } #ifdef GC_DLL bool CEconLootListDefinition::AddRandomAtrributes( KeyValues *pRandomAttributesKV, CEconItemSchema &pschema, CUtlVector *pVecErrors /*= NULL*/ ) { const char *pszAttrName = pRandomAttributesKV->GetName(); // We've found the random attribute block. Parse it. random_attrib_t *pRandomAttr = pschema.CreateRandomAttribute( m_pszName, pRandomAttributesKV, pVecErrors ); SCHEMA_INIT_CHECK( NULL != pRandomAttr, CFmtStr( "Loot List %s: Failed to create random_attrib_t '%s'", m_pszName, pszAttrName ) ); m_RandomAttribs.AddToTail( pRandomAttr ); return true; } bool CEconLootListDefinition::AddRandomAttributesFromTemplates( KeyValues *pRandomAttributesKV, CEconItemSchema &pschema, CUtlVector *pVecErrors /*= NULL*/ ) { const char *pszAttrName = pRandomAttributesKV->GetName(); // try to find attr by template name random_attrib_t *pRandomAttrTemplate = pschema.GetRandomAttributeTemplateByName( pszAttrName ); SCHEMA_INIT_CHECK( NULL != pRandomAttrTemplate, CFmtStr( "Loot List %s: Couldn't find random_attrib_t '%s' from attribute_templates", m_pszName, pszAttrName ) ); // craete a copy of the template and add to the list random_attrib_t *pRandomAttr = new random_attrib_t; *pRandomAttr = *pRandomAttrTemplate; m_RandomAttribs.AddToTail( pRandomAttr ); return true; } #endif // GC_DLL //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- static const char *g_pszDefaultRevolvingLootListHeader = "#Econ_Revolving_Loot_List"; bool CEconLootListDefinition::BInitFromKV( KeyValues *pKVLootList, CEconItemSchema &pschema, CUtlVector *pVecErrors ) { m_pszName = pKVLootList->GetName(); m_pszLootListHeader = g_pszDefaultRevolvingLootListHeader; m_pszLootListFooter = NULL; m_pszCollectionReference = NULL; m_bPublicListContents = true; #ifdef GC_DLL m_iNoDupesIterations = -1; // disable no-dupes functionality by default m_unRarity = pKVLootList->GetInt( "rarity", k_unItemRarity_Any ); #endif // GC_DLL bool bCollectionLootList = false; FOR_EACH_SUBKEY( pKVLootList, pKVListItem ) { const char *pszName = pKVListItem->GetName(); if ( !Q_strcmp( pszName, "loot_list_header_desc" ) ) { // Make sure we didn't specify multiple entries. SCHEMA_INIT_CHECK( g_pszDefaultRevolvingLootListHeader == m_pszLootListHeader, "Loot list %s: Multiple header descriptions specified", m_pszName ); m_pszLootListHeader = pKVListItem->GetString(); SCHEMA_INIT_CHECK( NULL != m_pszLootListHeader, "Loot list %s: Invalid header description specified", m_pszName ); continue; } else if ( !Q_strcmp( pszName, "loot_list_footer_desc" ) ) { // Make sure we didn't specify multiple entries. SCHEMA_INIT_CHECK( NULL == m_pszLootListFooter, "Loot list %s: Multiple footer descriptions specified", m_pszName ); m_pszLootListFooter = pKVListItem->GetString(); SCHEMA_INIT_CHECK( NULL != m_pszLootListFooter, "Loot list %s: Invalid header description specified", m_pszName ); continue; } else if ( !Q_strcmp( pszName, "loot_list_collection" ) ) { // Set name as the collection lootlist name pszName = pKVListItem->GetString(); m_pszCollectionReference = pszName; bCollectionLootList = true; } else if ( !Q_strcmp( pszName, "hide_lootlist" ) ) { m_bPublicListContents = !pKVListItem->GetBool( nullptr, true ); continue; } else if ( !Q_strcmp( pszName, "rarity" ) ) { // already parsed up top continue; } #ifdef GC_DLL else if ( !Q_strcmp( pszName, "random_attributes" ) ) { AddRandomAtrributes( pKVListItem, pschema, pVecErrors ); continue; } else if ( !Q_strcmp( pszName, "attribute_templates" ) ) { FOR_EACH_SUBKEY( pKVListItem, pKVAttributeTemplate ) { if ( pKVAttributeTemplate->GetInt() == 0 ) continue; bool bAdded = AddRandomAttributesFromTemplates( pKVAttributeTemplate, pschema, pVecErrors ); SCHEMA_INIT_CHECK( bAdded, "Loot list %s: Failed to attribute_templates '%s'", m_pszName, pKVAttributeTemplate->GetName() ); } continue; } else if ( !Q_strcmp( pszName, "public_list_contents" ) ) { m_bPublicListContents = pKVListItem->GetBool( nullptr, true ); continue; } else if ( !Q_stricmp( pszName, "__no_dupes_iter_count" ) ) { m_iNoDupesIterations = pKVListItem->GetInt( nullptr, -1 ); continue; } else if ( !Q_strcmp( pszName, "additional_drop" ) ) { float fChance = pKVListItem->GetFloat( "chance", 0.0f ); bool bPremiumOnly = pKVListItem->GetBool( "premium_only", false ); const char *pszLootList = pKVListItem->GetString( "loot_list", "" ); const char *pszRequiredHoliday = pKVListItem->GetString( "required_holiday", NULL ); const char *pszDropPerdiodStartDate = pKVListItem->GetString( "start_date", NULL ); const char *pszDropPerdiodEndDate = pKVListItem->GetString( "end_date", NULL ); int iRequiredHolidayIndex = pszRequiredHoliday ? EconHolidays_GetHolidayForString( pszRequiredHoliday ) : kHoliday_None; RTime32 dropStartDate = ( pszDropPerdiodStartDate && pszDropPerdiodStartDate[0] ) ? CRTime::RTime32FromFmtString( "YYYY-MM-DD hh:mm:ss" , pszDropPerdiodStartDate ) : RTime32(0); // Default to the start of time // Check that if we convert back to a string, we get the same value char rtimeBuf[k_RTimeRenderBufferSize]; SCHEMA_INIT_CHECK( pszDropPerdiodStartDate == NULL || Q_strcmp( CRTime::RTime32ToString( dropStartDate, rtimeBuf ), pszDropPerdiodStartDate ) == 0, "Malformed start drop date \"%s\" for additional_drop in lootlist %s. Must be of the form \"YYYY-MM-DD hh:mm:ss\"", pszDropPerdiodStartDate, m_pszName ); RTime32 dropEndDate = ( pszDropPerdiodEndDate && pszDropPerdiodEndDate[0] ) ? CRTime::RTime32FromFmtString( "YYYY-MM-DD hh:mm:ss" , pszDropPerdiodEndDate ) : ~RTime32(0); // Default to the end of time // Check that if we convert back to a string, we get the same value SCHEMA_INIT_CHECK( pszDropPerdiodEndDate == NULL || Q_strcmp( CRTime::RTime32ToString( dropEndDate, rtimeBuf ), pszDropPerdiodEndDate ) == 0, "Malformed end drop date \"%s\" for additional_drop in lootlist %s. Must be of the form \"YYYY-MM-DD hh:mm:ss\"", pszDropPerdiodEndDate, m_pszName ); SCHEMA_INIT_CHECK( fChance > 0.0f && fChance <= 1.0f, "Loot list %s: Invalid \"additional_drop\" chance %.2f", m_pszName, fChance ); SCHEMA_INIT_CHECK( pszLootList && pszLootList[0], "Loot list %s: Missing \"additional_drop\" loot list name", m_pszName ); SCHEMA_INIT_CHECK( (pszRequiredHoliday == NULL) == (iRequiredHolidayIndex == kHoliday_None), "Loot list %s: Unknown or missing holiday \"%s\"", m_pszName, pszRequiredHoliday ? pszRequiredHoliday : "(null)" ); if ( pszLootList ) { const CEconLootListDefinition *pLootListDef = GetItemSchema()->GetLootListByName( pszLootList ); SCHEMA_INIT_CHECK( pLootListDef, "Loot list %s: Invalid \"additional_drop\" loot list \"%s\"", m_pszName, pszLootList ); if ( pLootListDef ) { drop_period_t dropPeriod = { dropStartDate, dropEndDate }; loot_list_additional_drop_t additionalDrop = { fChance, bPremiumOnly, pszLootList, iRequiredHolidayIndex, dropPeriod }; m_AdditionalDrops.AddToTail( additionalDrop ); } } continue; } else if ( !Q_strcmp( pszName, "property_generators" ) ) { SCHEMA_INIT_SUBSTEP( BCommonInitPropertyGeneratorsFromKV( m_pszName, &m_PropertyGenerators, pKVListItem, pVecErrors ) ); continue; } #endif // GC_DLL int iDef = 0; // First, see if we've got a loot list name, for embedded loot lists int iIdx = 0; if ( GetItemSchema()->GetLootListByName( pszName, &iIdx ) ) { // HACKY: Store loot list indices as negatives, starting from -1, because 0 is a valid item index iDef = 0 - 1 - iIdx; } else { // Not a loot list. See if it's an item index. Check the first character. if ( pszName[0] >= '0' && pszName[0] <= '9' ) { iDef = atoi( pszName ); } else { // Not a number. See if we can find an item def with a matching name. const CEconItemDefinition *pDef = GetItemSchema()->GetItemDefinitionByName( pszName ); if ( pDef ) { iDef = pDef->GetDefinitionIndex(); } SCHEMA_INIT_CHECK( pDef != NULL, "Loot list %s: Item definition \"%s\" was not found", m_pszName, pszName ); } } // Default to the start dropping at the start of time and end dropping at the end of time drop_period_t dropPeriod = { RTime32(0), ~RTime32(0) }; // Make sure we never put non-enabled items into loot lists if ( iDef > 0 ) { const CEconItemDefinition *pItemDef = GetItemSchema()->GetItemDefinition( iDef ); SCHEMA_INIT_CHECK( pItemDef != NULL, "Loot list %s: Item definition index \"%s\" (%d) was not found", m_pszName, pszName, iDef ); static CSchemaAttributeDefHandle pAttribDef_StartDropDate( "start drop date" ); static CSchemaAttributeDefHandle pAttribDef_EndDropDate( "end drop date" ); CAttribute_String value; // Check for start drop date attribute on this item if ( FindAttribute( pItemDef, pAttribDef_StartDropDate, &value ) ) { const char* pszStartDate = value.value().c_str(); dropPeriod.m_DropStartDate = CRTime::RTime32FromFmtString( "YYYY-MM-DD hh:mm:ss" , pszStartDate ); // Check that if we convert back to a string, we get the same value char rtimeBuf[k_RTimeRenderBufferSize]; SCHEMA_INIT_CHECK( Q_strcmp( CRTime::RTime32ToString( dropPeriod.m_DropStartDate, rtimeBuf ), pszStartDate ) == 0, "Malformed start drop date \"%s\" for item %s. Must be of the form \"YYYY-MM-DD hh:mm:ss\"", pszStartDate, pItemDef->GetDefinitionName() ); } // Check for end drop date attribute on this item if ( FindAttribute( pItemDef, pAttribDef_EndDropDate, &value ) ) { const char* pszEndDate = value.value().c_str(); dropPeriod.m_DropEndDate = CRTime::RTime32FromFmtString( "YYYY-MM-DD hh:mm:ss" , pszEndDate ); // Check that if we convert back to a string, we get the same value char rtimeBuf[k_RTimeRenderBufferSize]; SCHEMA_INIT_CHECK( Q_strcmp( CRTime::RTime32ToString( dropPeriod.m_DropEndDate, rtimeBuf ), pszEndDate ) == 0, "Malformed end drop date \"%s\" for item %s. Must be of the form \"YYYY-MM-DD hh:mm:ss\"", pszEndDate, pItemDef->GetDefinitionName() ); } #ifdef GC_DLL if ( pItemDef ) { SCHEMA_INIT_CHECK( true == pItemDef->BEnabled(), "Loot list %s: Item definition \"%s\" (%d) isn't enabled, not allowed in loot lists", m_pszName, pItemDef->GetDefinitionName(), iDef ); } #endif // GC_DLL } float fItemWeight = 0.f; #ifdef GC_DLL fItemWeight = bCollectionLootList ? 1.0f : pKVListItem->GetFloat(); SCHEMA_INIT_CHECK( fItemWeight > 0.0f, "Loot list %s: Item definition index \"%s\" (%d) has invalid weight %.2f", m_pszName, pszName, iDef, fItemWeight ); #endif // GC_DLL // Add this item drop_item_t dropItem = { iDef, fItemWeight, dropPeriod }; m_DropList.AddToTail( dropItem ); } #ifdef GC_DLL int nNumTimeLimitedItems = 0; FOR_EACH_VEC( m_DropList, i ) { // If either the start date or the end date is set, tally it up as a time limited item if( m_DropList[i].m_dropPeriod.m_DropStartDate != RTime32(0) || m_DropList[i].m_dropPeriod.m_DropEndDate != ~RTime32(0) ) { ++nNumTimeLimitedItems; } } // Verify that at least one item in a lootlist does not have a drop period that limits when it can drop. // This guarantees that we will always drop *something* SCHEMA_INIT_CHECK( m_DropList.Count() > nNumTimeLimitedItems, "Lootlist \"%s\" is made up entirely of limited-time items! At least one must not be time-limited.", m_pszName ); #endif return SCHEMA_INIT_SUCCESS(); } #ifdef GC_DLL //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- static bool BContainsDuplicateItemDefs( const CUtlVector& vecItemDefsA, const CUtlVector& vecItemDefsB ) { CUtlHashtable hashItemDefs; auto BPopulateAndLookForDupes = [&] ( const CUtlVector& vecItemDefs ) { for ( const auto& rolledItemDef : vecItemDefs ) { if ( hashItemDefs.HasElement( rolledItemDef.m_pItemDef ) ) return true; hashItemDefs.Insert( rolledItemDef.m_pItemDef ); } return false; }; return BPopulateAndLookForDupes( vecItemDefsA ) || BPopulateAndLookForDupes( vecItemDefsB ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CEconLootListDefinition::BGenerateSingleRollRandomItems( const CEconGameAccount *pGameAccount, bool bFreeAccount, CUtlVector *out_pvecItems, const CUtlVector< item_definition_index_t > *pVecAvoidItemDefs /*= NULL*/ ) const { Assert( out_pvecItems ); // Where is our source of random numbers coming from? If we're a no-dupe list, // we want to have reproducible state so we use our account ID as a unique-ish // seed. // // Wrap the whole thing in a smart pointer so we clean up whenever/however we // leave. std::unique_ptr pRandomStream( [=]() -> IUniformRandomStream * { if ( !BIsInternalNoDupesLootList() || !pGameAccount ) return new CDefaultUniformRandomStream; CUniformRandomStream *pAccountUniformRandomStream = new CUniformRandomStream; pAccountUniformRandomStream->SetSeed( pGameAccount->Obj().m_unAccountID ); return pAccountUniformRandomStream; }() ); // Make however many passes through our loot list code until we've generated the // right number of passing sets. (Most loot lists let anything pass. Some specify // no duplicate definitions allowed.) CUtlVector vecCumulativeItemDefs; // total list of everything we've generated so far in any number of result sets CUtlVector vecItemDefs; // current list under evaluation int iNoDupesIterations = 0; // This actually isn't guaranteed to converge, and is guaranteed not to converge if // we set up a broken lootlist. We set a really high bar here to catch broken/pathologically // bad cases here without grinding the whole GC to a halt. enum { kUpperBoundIterationSanityCheck = 2000 }; int iTotalIterations = 0; while ( true ) { // Don't runaway. iTotalIterations++; if ( iTotalIterations >= kUpperBoundIterationSanityCheck ) return false; // Generate all of our item defs and their lootlists vecItemDefs.Purge(); if ( !RollRandomItemsAndAdditionalItems( pRandomStream.get(), bFreeAccount, &vecItemDefs, pVecAvoidItemDefs ) ) return false; // If we don't care about dupes and we got any results at all we're done. if ( !BIsInternalNoDupesLootList() ) break; // If we do care about dupes and we have some, ignore this set of items. if ( BContainsDuplicateItemDefs( vecItemDefs, vecCumulativeItemDefs ) ) continue; // Did we get to the right result set? iNoDupesIterations++; if ( iNoDupesIterations > m_iNoDupesIterations ) break; // Store off the list of definition indices we've already used them so they don't get reused // in a later set. vecCumulativeItemDefs.AddVectorToTail( vecItemDefs ); } // If we get down to here, we expect that we've rolled at least one item def Assert( vecItemDefs.Count() > 0 ); FOR_EACH_VEC( vecItemDefs, i ) { const rolled_item_defs_t& rolledDef = vecItemDefs[i]; Assert( rolledDef.m_pItemDef ); Assert( rolledDef.m_vecAffectingLootLists.Count() > 0 ); // Create the items CEconItem *pItem = GEconManager()->GetItemFactory().CreateSpecificItem( pGameAccount, rolledDef.m_pItemDef->GetDefinitionIndex() ); out_pvecItems->AddToTail( pItem ); // Go through and let all the affecting lootlists attach their attributes to the item FOR_EACH_VEC( rolledDef.m_vecAffectingLootLists, j ) { const CEconLootListDefinition *pLootList = rolledDef.m_vecAffectingLootLists[j]; Assert( pLootList ); if ( !pLootList->BAttachLootListAttributes( pGameAccount, pItem ) ) return false; } } return ( out_pvecItems->Count() > 0 ); } class CRollSimulator { public: CRollSimulator() : m_mapRarityCounts( StringLessThan ) , m_mapCounts( DefLessFunc( item_definition_index_t ) ) , m_mapUnusualHatEffectsCount( DefLessFunc( uint32 ) ) , m_mapUnusualTauntEffectsCount( DefLessFunc( uint32 ) ) , m_nNumIters( 0 ) {} void RollLootlist( const char* pszLootListName, int nRolls, bool bWipePreviousResults = false ) { const CEconLootListDefinition* pLootlist = GetItemSchema()->GetLootListByName( pszLootListName ); if ( !pLootlist ) { EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, "Invalid lootlist \"%s\".\n", pszLootListName ); return; } // Clear out results first? if ( bWipePreviousResults ) { m_mapCounts.Purge(); m_mapRarityCounts.Purge(); m_DropsMaxes.Clear(); m_DropsTotals.Clear(); } while( nRolls-- ) { AutoYield(); CUtlVector vecItems; pLootlist->BGenerateSingleRollRandomItems( NULL, false, &vecItems ); // Tally up what we got. for( auto pItem : vecItems ) { AutoYield(); // Insert item def if we need item_definition_index_t nDefIndex = pItem->GetDefinitionIndex(); auto idx = m_mapCounts.Find( nDefIndex ); if ( m_mapCounts.InvalidIndex() == idx ) { idx = m_mapCounts.Insert( nDefIndex ); } // Insert rarity if needed const CEconItemRarityDefinition* pItemRarity = GetItemSchema()->GetRarityDefinition( pItem->GetItemDefinition()->GetRarity() ); if ( pItemRarity ) { const char* pszRarity = GGCGameBase()->LocalizeToken( pItemRarity->GetLocKey() , k_Lang_English ); auto rarityIdx = m_mapRarityCounts.Find( pszRarity ); if ( m_mapRarityCounts.InvalidIndex() == rarityIdx ) { rarityIdx = m_mapRarityCounts.Insert( pszRarity, 0 ); } m_mapRarityCounts[ rarityIdx ] += 1; } // Count DropResult_t& result = m_mapCounts[ idx ]; ++result.m_nRollCount; ++m_DropsTotals.m_nRollCount; m_DropsMaxes.m_nRollCount = Max( m_DropsMaxes.m_nRollCount, result.m_nRollCount ); // Strange count if ( BIsItemStrange( pItem ) ) { ++result.m_nStrangeCount; ++m_DropsTotals.m_nStrangeCount; m_DropsMaxes.m_nStrangeCount = Max( m_DropsMaxes.m_nStrangeCount, result.m_nStrangeCount ); } // Unusual count static CSchemaAttributeDefHandle pAttrDef_ParticleEffect( "attach particle effect" ); static CSchemaAttributeDefHandle pAttrDef_TauntUnusualAttr( "on taunt attach particle index" ); if ( pAttrDef_ParticleEffect && pAttrDef_TauntUnusualAttr ) { uint32 nUnusualHatValue = 0; uint32 nUnusualTauntValue = 0; pItem->FindAttribute( pAttrDef_ParticleEffect, &nUnusualHatValue ); pItem->FindAttribute( pAttrDef_TauntUnusualAttr, &nUnusualTauntValue ); // Cant use quality cause of old legacy items. Quality is just a quick test if ( nUnusualHatValue != 0 || nUnusualTauntValue != 0 ) { ++result.m_nUnusualCount; ++m_DropsTotals.m_nUnusualCount; m_DropsMaxes.m_nUnusualCount = Max( m_DropsMaxes.m_nUnusualCount, result.m_nUnusualCount ); } if ( nUnusualHatValue ) { nUnusualHatValue = (uint32)((float&)nUnusualHatValue); auto idx = m_mapUnusualHatEffectsCount.Find( nUnusualHatValue ); if ( idx == m_mapUnusualHatEffectsCount.InvalidIndex() ) { idx = m_mapUnusualHatEffectsCount.Insert( nUnusualHatValue, 0 ); } ++m_mapUnusualHatEffectsCount[ idx ]; } if ( nUnusualTauntValue ) { nUnusualTauntValue = (uint32)((float&)nUnusualTauntValue); auto idx = m_mapUnusualTauntEffectsCount.Find( nUnusualTauntValue ); if ( idx == m_mapUnusualTauntEffectsCount.InvalidIndex() ) { idx = m_mapUnusualTauntEffectsCount.Insert( nUnusualTauntValue, 0 ); } ++m_mapUnusualTauntEffectsCount[ idx ]; } } } // Delete what we got vecItems.PurgeAndDeleteElements(); } } void PrintRarityBreakdwn() { EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, "-- Rarity breakdown:\n" ); int nMaxDigits = 0; while( m_mapRarityCounts.Count() ) { // Go through and print the most rolled to least rolled rarities auto maxIdx = m_mapRarityCounts.InvalidIndex(); // Find the most hit FOR_EACH_MAP_FAST( m_mapRarityCounts, i ) { AutoYield(); if ( maxIdx == m_mapRarityCounts.InvalidIndex() || m_mapRarityCounts[ i ] > m_mapRarityCounts[ maxIdx ] ) { maxIdx = i; } nMaxDigits = Max( nMaxDigits, NumDigits( m_mapRarityCounts[ maxIdx ] ) ); } EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, "%9s %*d %.3f%%\n", m_mapRarityCounts.Key( maxIdx ), nMaxDigits, m_mapRarityCounts[ maxIdx ], 100.f * (float)m_mapRarityCounts[ maxIdx ] / m_DropsTotals.m_nRollCount ); m_mapRarityCounts.RemoveAt( maxIdx ); } EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, "%-*s %*s %*s\n", 9 + 1 + NumDigits( m_DropsMaxes.m_nRollCount ) + 6, "-- Item breakdown", NumDigits( m_DropsMaxes.m_nStrangeCount ) + 1, "S", NumDigits( m_DropsMaxes.m_nUnusualCount ), "U" ); } void PrintUnusualCounts() { EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, "-- Unusual Hats breakdown:\n" ); int nTotal = PrintUnusualsForType( m_mapUnusualHatEffectsCount ); EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, "-- %d total unusual hats\n", nTotal ); EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, "-- Unusual Taunts breakdown:\n" ); nTotal = PrintUnusualsForType( m_mapUnusualTauntEffectsCount ); EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, "-- %d total unusual taunts\n", nTotal ); } void PrintTotals() { while( m_mapCounts.Count() ) { // Go through and print the most rolled to least rolled auto maxIdx = m_mapCounts.InvalidIndex(); // Find the most hit FOR_EACH_MAP_FAST( m_mapCounts, i ) { AutoYield(); if ( maxIdx == m_mapCounts.InvalidIndex() || m_mapCounts[ i ].m_nRollCount > m_mapCounts[ maxIdx ].m_nRollCount ) { maxIdx = i; } } DropResult_t& result = m_mapCounts[ maxIdx ]; CEconItemDefinition *pItemDef = GetItemSchema()->GetItemDefinition( m_mapCounts.Key( maxIdx ) ); const CEconItemRarityDefinition* pItemRarity = GetItemSchema()->GetRarityDefinition( pItemDef->GetRarity() ); const char* pszRarity = pItemRarity ? GGCGameBase()->LocalizeToken( pItemRarity->GetLocKey() , k_Lang_English ) : ""; CFmtStr rollString( "%*d %2.3f%%", NumDigits( m_DropsMaxes.m_nRollCount ), result.m_nRollCount, 100.f * (float)result.m_nRollCount / m_DropsTotals.m_nRollCount ); CFmtStr strangeString( "%*d", NumDigits( m_DropsMaxes.m_nStrangeCount ), result.m_nStrangeCount ); CFmtStr unusualString( "%*d", NumDigits( m_DropsMaxes.m_nUnusualCount ), result.m_nUnusualCount ); const char* pszItemname = GGCGameBase()->LocalizeToken( pItemDef->GetCustomPainkKitDefinition() ? pItemDef->GetCustomPainkKitDefinition()->GetName() : pItemDef->GetItemBaseName(), k_Lang_English ); EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, "%9s %s %s %s %s\n", pszRarity, rollString.Get(), strangeString.Get(), unusualString.Get(), pszItemname ); m_mapCounts.RemoveAt( maxIdx ); } EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, "-- Total Items: %d Strange: %d (%.3f%%) Unusual: %d (%.3f%%)\n" , m_DropsTotals.m_nRollCount , m_DropsTotals.m_nStrangeCount , ( 100.f * (float)m_DropsTotals.m_nStrangeCount / m_DropsTotals.m_nRollCount ) , m_DropsTotals.m_nUnusualCount , ( 100.f * (float)m_DropsTotals.m_nUnusualCount / m_DropsTotals.m_nRollCount ) ); } struct DropResult_t { DropResult_t() { Clear(); } void Clear() { m_nStrangeCount = 0; m_nUnusualCount = 0; m_nRollCount = 0; } int m_nStrangeCount; int m_nUnusualCount; int m_nRollCount; }; const DropResult_t& GetTotalDrops() const { return m_DropsTotals; } const DropResult_t& GetMaxesDrops() const { return m_DropsMaxes; } private: int PrintUnusualsForType( CUtlMap< uint32, int >& mapUnusuals ) { int nMaxDigits = 0; int nTotal = 0; FOR_EACH_MAP_FAST( mapUnusuals, i ) { nTotal += mapUnusuals[ i ]; } while( mapUnusuals.Count() ) { // Go through and print the most rolled to least rolled unusual effects auto maxIdx = mapUnusuals.InvalidIndex(); // Find the most hit FOR_EACH_MAP_FAST( mapUnusuals, i ) { AutoYield(); if ( maxIdx == mapUnusuals.InvalidIndex() || mapUnusuals[ i ] > mapUnusuals[ maxIdx ] ) { maxIdx = i; } nMaxDigits = Max( nMaxDigits, NumDigits( mapUnusuals[ maxIdx ] ) ); } char particleNameEntry[128]; Q_snprintf( particleNameEntry, ARRAYSIZE( particleNameEntry ), "#Attrib_Particle%d", mapUnusuals.Key( maxIdx ) ); const char* pszParticleName = GGCGameBase()->LocalizeToken( particleNameEntry, k_Lang_English ); EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, "%*d %.3f%% %s (%d)\n", nMaxDigits, mapUnusuals[ maxIdx ], 100.f * (float)mapUnusuals[ maxIdx ] / nTotal, pszParticleName, mapUnusuals.Key( maxIdx ) ); mapUnusuals.RemoveAt( maxIdx ); } return nTotal; } void AutoYield() { if ( ++m_nNumIters % 100 == 0 ) { if ( GJobCur().BYieldIfNeeded() ) { // If we re-entered logon surge we should go away for a while while ( GGCGameBase()->BIsInLogonSurge() ) { GJobCur().BYieldingWaitOneFrame(); } } } }; int NumDigits( int nNumber ) { int digits = 0; if ( nNumber <= 0) { digits = 1; } while ( nNumber ) { nNumber /= 10; ++digits; } return digits; } CUtlMap< item_definition_index_t, DropResult_t > m_mapCounts; CUtlMap< uint32, int > m_mapUnusualHatEffectsCount; CUtlMap< uint32, int > m_mapUnusualTauntEffectsCount; // Rarity name is the key CUtlMap< const char*, int > m_mapRarityCounts; DropResult_t m_DropsTotals; DropResult_t m_DropsMaxes; size_t m_nNumIters; }; GC_CON_COMMAND( simulate_lootlist_contents, " Check item distribution from a given lootlist n times" ) { if ( !BCheckArgs( 2, args, simulate_lootlist_contents_command ) ) return; const char* pszLootListName = args[1]; int nRolls = args.ArgC() == 3 ? atoi( args[2] ) : 1000; CRollSimulator simulator; simulator.RollLootlist( pszLootListName, nRolls ); simulator.PrintRarityBreakdwn(); simulator.PrintUnusualCounts(); simulator.PrintTotals(); } #ifdef GC_DLL class CItemSourceFinder { public: CItemSourceFinder( const char* pszItemName ) : m_pItemDef( GetItemSchema()->GetItemDefinitionByName( pszItemName ) ) { Assert( m_pItemDef ); if ( !m_pItemDef ) { EG_ERROR( SPEW_CONSOLE, "%s is not a valid item", pszItemName ); return; } // Find out what series this crate belongs to. static CSchemaAttributeDefHandle pAttr_CrateSeries( "set supply crate series" ); if ( !pAttr_CrateSeries ) return; auto& mapItemDefs = GetItemSchema()->GetItemDefinitionMap(); auto& mapRevolvingLootlists = GetItemSchema()->GetRevolvingLootLists(); CUtlDict< int > dictSeenLootlists; // Look through all the item defs and see if any of them statically specify a lootlist that they want to open FOR_EACH_MAP_FAST( mapItemDefs, i ) { const CEconItemDefinition* pSourceItemDef = mapItemDefs[ i ]; const CEconLootListDefinition* pLootlist = NULL; const CEconTool_Gift* pGift = pSourceItemDef->GetTypedEconTool< CEconTool_Gift >(); // Self-opening crate? if ( pGift ) { pLootlist = GetItemSchema()->GetLootListByName( pGift->GetLootListName() ); } else // Crate with an item series? { int iCrateSeries; { float fCrateSeries; // crate series ID is stored as a float internally because we hate ourselves if ( !FindAttribute_UnsafeBitwiseCast( pSourceItemDef, pAttr_CrateSeries, &fCrateSeries ) || fCrateSeries == 0.0f ) continue; iCrateSeries = fCrateSeries; } auto idx = mapRevolvingLootlists.Find( iCrateSeries ); if ( idx == mapRevolvingLootlists.InvalidIndex() ) continue; pLootlist = GetItemSchema()->GetLootListByName( mapRevolvingLootlists[ idx ] ); } if ( !pLootlist ) continue; // Mark that we've seen this lootlist already dictSeenLootlists.Insert( pLootlist->GetName() ); DropSource_t& source = m_vecSources[ m_vecSources.AddToTail() ]; source.m_pDroppingItem = pSourceItemDef; ChanceForItemFromLootlist( m_pItemDef, pLootlist, source.lootlistSource ); } CEconItemDefinition* pCrateItemDef = GetItemSchema()->GetItemDefinitionByName( "Supply Crate" ); Assert( pCrateItemDef ); // Go through all the revolving lootlists and see if they have the item. Assume that // they're from a "Supply Crate". FOR_EACH_MAP_FAST( mapRevolvingLootlists, i ) { if ( !pCrateItemDef ) continue; if ( mapRevolvingLootlists.Key( i ) <= 0 ) continue; auto pLootlist = GetItemSchema()->GetLootListByName( mapRevolvingLootlists[ i ] ); if ( !pLootlist ) continue; // This lootlist was on a different crate already if ( dictSeenLootlists.Find( pLootlist->GetName() ) != dictSeenLootlists.InvalidIndex() ) continue; DropSource_t& source = m_vecSources[ m_vecSources.AddToTail() ]; source.m_pDroppingItem = pCrateItemDef; ChanceForItemFromLootlist( m_pItemDef, pLootlist, source.lootlistSource ); } // Not available at all! if ( !m_vecSources.Count() ) { EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, "Not available from any sources!\n" ); return; } // Sort greatest % chance auto lambdaSort = [] ( DropSource_t const *pLHS, DropSource_t const *pRHS ) -> int { return pLHS->lootlistSource.m_flChance < pRHS->lootlistSource.m_flChance; }; m_vecSources.Sort( lambdaSort ); } void PrintSources() { FOR_EACH_VEC( m_vecSources, i ) { m_vecSources[ i ].PrintSources(); } } bool BDropsFromLootlist( const CEconLootListDefinition* pLootlist ) { FOR_EACH_VEC( m_vecSources, i ) { if ( m_vecSources[ i ].lootlistSource.BDropsFromLootlist( pLootlist ) ) return true; } return false; } private: struct DropSource_t { void PrintSources() { if ( lootlistSource.m_flChance == 0.f ) return; bool bSelfOpening = m_pDroppingItem->GetTypedEconTool< CEconTool_Gift >() != NULL; // Print the name of the item, and whether it's a self-opening item, or a crate EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, "%3.5f%% %s (%d - %s)\n", lootlistSource.m_flChance * 100.f, m_pDroppingItem->GetDefinitionName(), m_pDroppingItem->GetDefinitionIndex(), bSelfOpening ? "Self-Opening" : "Crate/Case" ); // Print all the sources lootlistSource.PrintSources( 1 ); EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, "\n" ); } struct LootListSource_t { LootListSource_t() : m_flChance( 0.f ) {} bool BDropsFromLootlist( const CEconLootListDefinition* pLootlist ) { // Skip no-chance entries if ( m_flChance == 0.f ) return false; if ( m_pDroppingLootlist == pLootlist ) return true; FOR_EACH_VEC( m_vecLootlistSources, i ) { if ( m_vecLootlistSources[ i ].BDropsFromLootlist( pLootlist ) ) return true; } return false; } void PrintSources( int nInset ) { // Skip no-chance entries if ( m_flChance == 0.f ) return; auto& mapRevolvingLootlists = GetItemSchema()->GetRevolvingLootLists(); int nRevolvingIdx = mapRevolvingLootlists.InvalidIndex(); // Check if our lootlist is one of the revolving lootlists. If so, we want // to print out its index within revolving_lootlists, so we can map that to // the attribute value of supply_crate_series (187) FOR_EACH_MAP_FAST( mapRevolvingLootlists, i ) { if ( V_stricmp( mapRevolvingLootlists[ i ], m_pDroppingLootlist->GetName() ) == 0 ) { nRevolvingIdx = mapRevolvingLootlists.Key( i ); break; } } if ( nRevolvingIdx != mapRevolvingLootlists.InvalidIndex() && nRevolvingIdx > 0 ) { // It's in revolving_lootlists. Print its index in there. EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, "%*.5f%% (%d) %s\n", 4 * nInset + 5, m_flChance * 100.f, nRevolvingIdx, m_pDroppingLootlist->GetName() ); } else { // Not in the revolving lootlist EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, "%*.5f%% %s\n", 4 * nInset + 5, m_flChance * 100.f, m_pDroppingLootlist->GetName() ); } // Sort greatest % chance auto lambdaSort = [] ( LootListSource_t const *pLHS, LootListSource_t const *pRHS ) -> int { return pLHS->m_flChance < pRHS->m_flChance; }; m_vecLootlistSources.Sort( lambdaSort ); // Print out children indented a lil bit FOR_EACH_VEC( m_vecLootlistSources, i ) { m_vecLootlistSources[i].PrintSources( nInset + 1 ); } } float m_flChance; const CEconLootListDefinition* m_pDroppingLootlist; CCopyableUtlVector< LootListSource_t > m_vecLootlistSources; }; const CEconItemDefinition* m_pDroppingItem; LootListSource_t lootlistSource; }; float ChanceForItemFromLootlist( const CEconItemDefinition* pItemDef, const CEconLootListDefinition* pLootlist, DropSource_t::LootListSource_t& lootlistSource ) { auto& vecContents = pLootlist->GetLootListContents(); // Accumulate our chance of dropping the specified item lootlistSource.m_flChance = 0.f; lootlistSource.m_pDroppingLootlist = pLootlist; // Gather the items in this lootlist that we're able to roll for at this time float flTotalWeight = 0.f; CUtlVector vecValidContents; FOR_EACH_VEC( vecContents, i ) { if( !vecContents[ i ].m_dropPeriod.IsValidForTime( CRTime::RTime32TimeCur() ) ) continue; flTotalWeight += vecContents[ i ].m_flWeight; vecValidContents.AddToTail( vecContents[ i ] ); } // Go through valid contents, and see if the specified item is in there FOR_EACH_VEC( vecValidContents, i ) { const int iItemDef = vecValidContents[ i ].m_iItemOrLootlistDef; const float flChance = vecValidContents[ i ].m_flWeight / flTotalWeight; if ( iItemDef < 0 ) // Lootlist { int iLLIndex = (iItemDef * -1) - 1; auto pSubLootlist = GetItemSchema()->GetLootListByIndex( iLLIndex ); // One of our sub-lootlists might drop it. Add in it's chance within // the sub-lootlist scaled by the chance to roll that sub-lootlist. auto& subSource = lootlistSource.m_vecLootlistSources[ lootlistSource.m_vecLootlistSources.AddToTail() ]; lootlistSource.m_flChance += ChanceForItemFromLootlist( pItemDef, pSubLootlist, subSource ) * flChance; } else if ( pItemDef->GetDefinitionIndex() == iItemDef ) { // We drop it! Add the chance lootlistSource.m_flChance += flChance; } } // Treat additional drops just the same as nested lootlists. auto& vecAdditionalDrops = pLootlist->GetAdditionalDrops(); FOR_EACH_VEC( vecAdditionalDrops, i ) { auto& additionalDrop = vecAdditionalDrops[ i ]; if ( !additionalDrop.m_dropPeriod.IsValidForTime( CRTime::RTime32TimeCur() ) ) continue; auto pSubLootlist = GetItemSchema()->GetLootListByName( additionalDrop.m_pszLootListDefName ); auto& subSource = lootlistSource.m_vecLootlistSources[ lootlistSource.m_vecLootlistSources.AddToTail() ]; lootlistSource.m_flChance += ChanceForItemFromLootlist( pItemDef, pSubLootlist, subSource ) * additionalDrop.m_fChance; } // Return the total chance return lootlistSource.m_flChance; } CUtlVector< DropSource_t > m_vecSources; const CEconItemDefinition* m_pItemDef; }; GC_CON_COMMAND( item_sources, "Lists the sources for obtaining a list of specific items" ) { if ( !BCheckArgs( 1, args, item_sources_command ) ) return; for ( int i=1; i < args.ArgC(); ++i ) { const char* pszItemName = args[i]; const CEconItemDefinition* pItemDef = GetItemSchema()->GetItemDefinitionByName( pszItemName ); if ( pItemDef ) { EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, "\nChecking items for lootlists containing (%d) %s...\n", pItemDef->GetDefinitionIndex(), pItemDef->GetDefinitionName() ); CItemSourceFinder source( pszItemName ); source.PrintSources(); } else { EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, "\n\"%s\" is not a valid item name\n", pszItemName ); } } } GC_CON_COMMAND( list_keys, "Lists all the keys in the schema" ) { EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, "Spewing keys...\n" ); auto& mapItemDefs = GetItemSchema()->GetItemDefinitionMap(); FOR_EACH_MAP_FAST( mapItemDefs, i ) { const CEconItemDefinition* pItemDef = mapItemDefs[ i ]; if ( !pItemDef || !pItemDef->GetEconTool() || ( Q_strcmp( pItemDef->GetEconTool()->GetTypeName(), "decoder_ring" ) != 0 ) ) { continue; } EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, "%llu - %s\n", pItemDef->GetDefinitionIndex(), pItemDef->GetItemDefinitionName() ); } } ConVar case_behavior_rolls_per_lootlist( "case_behavior_rolls_per_lootlist", "1000000", FCVAR_REPLICATED, "How many times to roll a lootlist" ); class CJobCaseBehaviorCheck : public CGCGameBaseJob { public: CJobCaseBehaviorCheck() : CGCGameBaseJob( GGCGameBase() ) {} virtual bool BYieldingRunGCJob() { // Wait until logon surge ends to get going while ( GGCGameBase()->BIsInLogonSurge() ) { GJobCur().BYieldingWaitOneFrame(); } CRollSimulator simulator; // Convert the hash to something readable char pchSHAHex[41]; memset( pchSHAHex, 0, sizeof( pchSHAHex ) ); V_binarytohex( GetItemSchema()->GetSchemaSHA().m_shaDigest, 20, pchSHAHex, 41 ); RTime32 now = CRTime::RTime32TimeCur(); int nNewRecords = 0; EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, "Beginning CJobCaseBehaviorCheck\n" ); auto& lootlists = GetItemSchema()->GetRevolvingLootLists(); // Go through all of the lootlists FOR_EACH_MAP( lootlists, i ) { const CEconLootListDefinition* pEconLootlist = GetItemSchema()->GetLootListByName( lootlists[ i ] ); if ( pEconLootlist ) { // Check if we have data for this lootlist on this hash already { CSQLAccess sqlReadAccess; CUtlVector< CSchCaseBehavior > vecExistingResult; sqlReadAccess.AddBindParam( pchSHAHex ); sqlReadAccess.AddBindParam( i ); if ( sqlReadAccess.BYieldingReadRecordsWithWhereClause( &vecExistingResult, "SchemaSHA = ? and Series = ?", CSET_FULL( CSchCaseBehavior ) ) ) { // Already got it? Skip the work if ( vecExistingResult.Count() ) { continue; } } } // Roll 'em up simulator.RollLootlist( pEconLootlist->GetName(), case_behavior_rolls_per_lootlist.GetInt(), true ); // Insert record CSchCaseBehavior behavior; behavior.SetVarCharField( behavior.m_VarCharSchemaSHA, pchSHAHex, true, CSchCaseBehavior::k_iField_VarCharSchemaSHA ); behavior.m_RTime32Date = now; behavior.m_unSeries = i; behavior.SetVarCharField( behavior.m_VarCharLootListName, pEconLootlist->GetName(), true, CSchCaseBehavior::k_iField_VarCharLootListName ); behavior.m_fStrangeChance = 100.f * (float)simulator.GetTotalDrops().m_nStrangeCount / simulator.GetTotalDrops().m_nRollCount; behavior.m_fUnusualChance = 100.f * (float)simulator.GetTotalDrops().m_nUnusualCount / simulator.GetTotalDrops().m_nRollCount; CSQLAccess sqlAccess; sqlAccess.BBeginTransaction( "CJobCaseBehaviorCheck" ); sqlAccess.BYieldingInsertOrUpdateOnPK( &behavior ); sqlAccess.BCommitTransaction( true ); ++nNewRecords; } BYieldIfNeeded(); } EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, "Completed CJobUnusualChecker. %d updated records.\n", nNewRecords ); return true; } }; void CEconItemSchema::PerformCaseBehaviorCheck() { CJob* pJob = new CJobCaseBehaviorCheck(); pJob->StartJobDelayed( NULL ); } #endif //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CEconLootListDefinition::RollRandomItemsAndAdditionalItems( IUniformRandomStream *pRandomStream, bool bFreeAccount, CUtlVector *out_pVecRolledItems, const CUtlVector< item_definition_index_t > *pVecAvoidItemDefs ) const { Assert( out_pVecRolledItems ); // Roll to see what items we get from this loot list. bool bCreatedItems = RollRandomItemDef( pRandomStream, bFreeAccount, out_pVecRolledItems, pVecAvoidItemDefs ); // Do we have additional drops? FOR_EACH_VEC( m_AdditionalDrops, i ) { if ( !bCreatedItems ) break; // Is this within the period this is allowed to drop? if ( !m_AdditionalDrops[i].m_dropPeriod.IsValidForTime( CRTime::RTime32TimeCur() ) ) continue; // Does this only apply to premium accounts? if ( m_AdditionalDrops[i].m_bPremiumOnly && bFreeAccount ) continue; // Does this only apply on certain holidays? if ( m_AdditionalDrops[i].m_iRequiredHolidayIndex != kHoliday_None && !EconHolidays_IsHolidayActive( m_AdditionalDrops[i].m_iRequiredHolidayIndex, CRTime::RTime32TimeCur() ) ) continue; // Random chance is in the range 0-1 so generate a value in that range to "roll". if ( pRandomStream->RandomFloat( 0.0f, 1.0f ) > m_AdditionalDrops[i].m_fChance ) continue; // Roll! const char *pszAdditionalDropLootList = m_AdditionalDrops[i].m_pszLootListDefName; const CEconLootListDefinition *pLootListDef = GetItemSchema()->GetLootListByName( pszAdditionalDropLootList ); if ( pLootListDef == NULL ) { AssertMsg2( false, "Loot list '%s' specifies unknown additional drop '%s'", GetName(), pszAdditionalDropLootList ); return false; } bCreatedItems &= pLootListDef->RollRandomItemsAndAdditionalItems( pRandomStream, bFreeAccount, out_pVecRolledItems ); } // If we failed to create some items, we might still have chosen some item defs before those failures. In that case, clear // out any choices we've made so far if ( !bCreatedItems ) { out_pVecRolledItems->Purge(); } Assert( bCreatedItems == (out_pVecRolledItems->Count() > 0) ); return bCreatedItems; } bool CEconLootListDefinition::RollRandomItemDef( IUniformRandomStream *pRandomStream, bool bFreeAccount, CUtlVector *out_pVecRolledItems, const CUtlVector< item_definition_index_t > *pVecAvoidItemDefs ) const { Assert( out_pVecRolledItems ); CUtlVector< rolled_item_defs_t > vecScratchDefs; CUtlVector vecValidDrops; // Gather the items in this lootlist that we're able to roll for at this time float flTotalWeight = 0.f; FOR_EACH_VEC( m_DropList, i ) { if( !m_DropList[i].m_dropPeriod.IsValidForTime( CRTime::RTime32TimeCur() ) ) continue; // Skip any item defs that are in our avoid list (if we have one) if ( pVecAvoidItemDefs ) { item_definition_index_t defIndex = m_DropList[i].m_iItemOrLootlistDef; if ( pVecAvoidItemDefs->Find( defIndex ) != pVecAvoidItemDefs->InvalidIndex() ) continue; } // If this is valid, add it to the list and add its weight to the total vecValidDrops.AddToTail( &m_DropList[i] ); flTotalWeight += m_DropList[i].m_flWeight; } // Roll to see what item drops. float flRand = pRandomStream->RandomFloat(0.0f, 1.0f) * flTotalWeight; float flAccum = 0.0f; FOR_EACH_VEC( vecValidDrops, i ) { flAccum += vecValidDrops[i]->m_flWeight; if ( flRand <= flAccum ) { const int iItemDef = vecValidDrops[i]->m_iItemOrLootlistDef; // not item_definition_index because it might also be a negative value to indicate a sub lootlist if ( iItemDef >= 0 ) { const CEconItemDefinition* pItemDef = GetItemSchema()->GetItemDefinition( iItemDef ); if( !pItemDef ) return false; // Add the item def and the lootlist rolled_item_defs_t& rolledDef = vecScratchDefs[ vecScratchDefs.AddToTail() ]; rolledDef.m_pItemDef = pItemDef; } else { // In the case where iItemDef is negative, it's a nested loot list. Ask that list to choose an item. // HACKY: Store loot list indices as negatives, starting from -1, because 0 is a valid item index int iLLIndex = (iItemDef * -1) - 1; const CEconLootListDefinition *pNestedLootList = GetItemSchema()->GetLootListByIndex( iLLIndex ); if ( !pNestedLootList ) return false; if( !pNestedLootList->RollRandomItemsAndAdditionalItems( pRandomStream, bFreeAccount, &vecScratchDefs, pVecAvoidItemDefs ) ) return false; } // Add ourselves to the list of affecting lootlists, so that we and all nested loot lists will affect this item // We intentionally don't do this in our calling function because we don't want to include additional drops. FOR_EACH_VEC( vecScratchDefs, j ) { vecScratchDefs[j].m_vecAffectingLootLists.AddToTail( this ); } // We want to exit the loop here regardless of whether items were successfully so that we only perform a single // item-generating roll on this list. break; } } // Feed item defs, if they exist, back to our caller out_pVecRolledItems->AddVectorToTail( vecScratchDefs ); // Did we successfully create any items? return ( vecScratchDefs.Count() > 0 ); } //----------------------------------------------------------------------------- // Purpose: find a list of lootlists with rarity from this lootlist //----------------------------------------------------------------------------- void CEconLootListDefinition::GetRarityLootLists( CUtlVector< const CEconLootListDefinition* > *out_pVecRarityLootList ) const { Assert( out_pVecRarityLootList ); if ( m_unRarity != k_unItemRarity_Any ) { out_pVecRarityLootList->AddToTail( this ); } FOR_EACH_VEC( m_DropList, i ) { const int iItemDef = m_DropList[i].m_iItemOrLootlistDef; // not item_definition_index because it might also be a negative value to indicate a sub lootlist if ( iItemDef < 0 ) { // In the case where iItemDef is negative, it's a nested loot list. Ask that list to choose an item. // HACKY: Store loot list indices as negatives, starting from -1, because 0 is a valid item index int iLLIndex = (iItemDef * -1) - 1; const CEconLootListDefinition *pNestedLootList = GetItemSchema()->GetLootListByIndex( iLLIndex ); if ( !pNestedLootList ) return; pNestedLootList->GetRarityLootLists( out_pVecRarityLootList ); } } } //----------------------------------------------------------------------------- // Purpose: get all item defs from this lootlist ( not lootlist item def ) //----------------------------------------------------------------------------- void CEconLootListDefinition::GetItemDefs( CUtlVector< item_definition_index_t > *out_pVecItemDefs ) const { Assert( out_pVecItemDefs ); FOR_EACH_VEC( m_DropList, i ) { const int iItemDef = m_DropList[i].m_iItemOrLootlistDef; // not item_definition_index because it might also be a negative value to indicate a sub lootlist if ( iItemDef >= 0 ) { out_pVecItemDefs->AddToTail( (item_definition_index_t)iItemDef ); } else { // In the case where iItemDef is negative, it's a nested loot list. Ask that list to choose an item. // HACKY: Store loot list indices as negatives, starting from -1, because 0 is a valid item index int iLLIndex = (iItemDef * -1) - 1; const CEconLootListDefinition *pNestedLootList = GetItemSchema()->GetLootListByIndex( iLLIndex ); if ( !pNestedLootList ) return; pNestedLootList->GetItemDefs( out_pVecItemDefs ); } } } //----------------------------------------------------------------------------- // Purpose: Given a vector of possible attributes, roll to see which ones are // chosen. We allocate memory for these new attributes, so it's the // responsibility of the caller to free these attributes when they're // done with them! //----------------------------------------------------------------------------- void CEconLootListDefinition::RollRandomAttributes( CUtlVector< static_attrib_t >& vecAttributes, const CEconGameAccount *pGameAccount ) const { for ( int i=0; iRollRandomAttributes( vecAttributes, pGameAccount ); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CEconLootListDefinition::drop_period_t::IsValidForTime( const RTime32& time ) const { if( time >= m_DropStartDate && time < m_DropEndDate ) return true; return false; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CEconLootListDefinition::BAttachLootListAttributes( const CEconGameAccount *pGameAccount, CEconItem *pItem ) const { //static CSchemaAttributeDefHandle pAttr_ElevateQuality( "elevate quality" ); // Gather and apply old-style random attributes. CUtlVector< static_attrib_t > vecAttributes; RollRandomAttributes( vecAttributes, pGameAccount ); FOR_EACH_VEC( vecAttributes, i ) { GEconManager()->GetItemFactory().ApplyStaticAttributeToItem( pItem, vecAttributes[i], pGameAccount ); vecAttributes[i].GetAttributeDefinition()->GetAttributeType()->UnloadEconAttributeValue( &vecAttributes[i].m_value ); } // Apply all relevant property generators. for ( auto pGenerator : m_PropertyGenerators ) { if ( !pGenerator->BGenerateProperties( pItem ) ) return false; } return true; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool lootlist_attrib_t::BInitFromKV( const char *pszContext, KeyValues *pKVKey, CEconItemSchema &pschema, CUtlVector *pVecErrors ) { SCHEMA_INIT_SUBSTEP( m_staticAttrib.BInitFromKV_MultiLine( pszContext, pKVKey, pVecErrors ) ); SCHEMA_INIT_CHECK( pKVKey->FindKey( "weight" ), "Context '%s': Attribute \"%s\" missing required 'weight' field", pszContext, pKVKey->GetName() ); m_flWeight = pKVKey->GetFloat( "weight" ); return SCHEMA_INIT_SUCCESS(); } //----------------------------------------------------------------------------- // Purpose: returns true if we should stop rolling from this random_attrib_t //----------------------------------------------------------------------------- bool random_attrib_t::RollRandomAttributes( CUtlVector< static_attrib_t >& vecAttributes, const CEconGameAccount *pGameAccount ) const { if ( m_flChanceOfRandomAttribute && RandomFloat() <= m_flChanceOfRandomAttribute ) { // We're attaching a random attribute. Determine which attribute. float flRand = 0.0f; if ( !m_bPickAllAttributes ) { // Pick one attribute to add // Otherwise we'll pick them all flRand = RandomFloat( 0.f, 1.f ) * m_flTotalAttributeWeight; } float flAccum = 0.f; for ( int iAttrib = 0; iAttrib < m_RandomAttributes.Count(); ++iAttrib ) { const lootlist_attrib_t& randomAttrib = m_RandomAttributes[iAttrib]; flAccum += randomAttrib.m_flWeight; if ( flRand <= flAccum ) { // Add the attribute static_attrib_t &staticAttrib = vecAttributes[ vecAttributes.AddToTail( randomAttrib.m_staticAttrib ) ]; const CEconItemAttributeDefinition *pAttrDef = staticAttrib.GetAttributeDefinition(); const ISchemaAttributeType *pAttrType = pAttrDef->GetAttributeType(); // Generate a special value? pAttrType->InitializeNewEconAttributeValue( &staticAttrib.m_value ); pAttrDef->GetAttributeType()->GenerateEconAttributeValue( pAttrDef, staticAttrib, pGameAccount, &staticAttrib.m_value ); if ( !m_bPickAllAttributes ) { // We're only picking one attribute from the list return true; } } } } return false; } #endif // GC_DLL //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CEconLootListDefinition::EnumerateUserFacingPotentialDrops( IEconLootListIterator *pIt ) const { Assert( pIt ); // Loot lists have the option of specifying that their contents should not be publicly // listed. This is used on the GC for things like the "rare item drop list" to prevent // every single potentially-unusual hat from showing up. if ( !BPublicListContents() ) return; FOR_EACH_VEC( GetLootListContents(), i ) { const int iID = GetLootListContents()[i].m_iItemOrLootlistDef; // Nested loot lists are stored as negative indices. if ( iID < 0 ) { const CEconLootListDefinition *pLootListDef = GetItemSchema()->GetLootListByIndex( (-iID) - 1 ); if ( !pLootListDef ) continue; pLootListDef->EnumerateUserFacingPotentialDrops( pIt ); } else { pIt->OnIterate( iID ); } } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- /*static*/ CSchemaAttributeDefHandle CAttributeLineItemLootList::s_pAttrDef_RandomDropLineItems[] = { CSchemaAttributeDefHandle( "random drop line item 0" ), CSchemaAttributeDefHandle( "random drop line item 1" ), CSchemaAttributeDefHandle( "random drop line item 2" ), CSchemaAttributeDefHandle( "random drop line item 3" ), }; #ifdef GC_DLL /*static*/ CSchemaAttributeDefHandle CAttributeLineItemLootList::s_pAttrDef_RandomDropLineItemUnusualChance( "random drop line item unusual chance" ); // "one out of this many" /*static*/ CSchemaAttributeDefHandle CAttributeLineItemLootList::s_pAttrDef_RandomDropLineItemUnusualList( "random drop line item unusual list" ); #endif // GC_DLL CSchemaAttributeDefHandle CAttributeLineItemLootList::s_pAttrDef_RandomDropLineItemFooterDesc( "random drop line item footer desc" ); //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CAttributeLineItemLootList::EnumerateUserFacingPotentialDrops( IEconLootListIterator *pIt ) const { Assert( pIt ); for ( int i = 0; i < ARRAYSIZE( s_pAttrDef_RandomDropLineItems ); i++ ) { uint32 unItemDef; COMPILE_TIME_ASSERT( sizeof( unItemDef ) >= sizeof( item_definition_index_t ) ); // If we run out of attributes we have set we're done. if ( !m_pEconItem->FindAttribute( s_pAttrDef_RandomDropLineItems[i], &unItemDef ) ) break; pIt->OnIterate( unItemDef ); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- const char *CAttributeLineItemLootList::GetLootListHeaderLocalizationKey() const { return g_pszDefaultRevolvingLootListHeader; } //----------------------------------------------------------------------------- const char *CAttributeLineItemLootList::GetLootListFooterLocalizationKey() const { CAttribute_String sFooter; const char* pszFooter = NULL; if ( FindAttribute_UnsafeBitwiseCast( m_pEconItem, s_pAttrDef_RandomDropLineItemFooterDesc, &pszFooter ) ) { return pszFooter; } return NULL; } //----------------------------------------------------------------------------- const char *CAttributeLineItemLootList::GetLootListCollectionReference() const { // TODO : Implement me! return NULL; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- const CEconLootListDefinition* CEconItemSchema::GetLootListByName( const char* pListName, int *out_piIndex ) const { auto idx = m_mapLootLists.Find( pListName ); if ( !m_mapLootLists.IsValidIndex( idx ) ) return NULL; if ( out_piIndex ) { *out_piIndex = idx; } return m_mapLootLists[idx]; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- const CQuestObjectiveDefinition* CEconItemSchema::GetQuestObjectiveByDefIndex( int iIdx ) const { auto nMapIndex = m_mapQuestObjectives.Find( iIdx ); if ( nMapIndex != m_mapQuestObjectives.InvalidIndex() ) { return m_mapQuestObjectives[ nMapIndex ]; } return NULL; } //----------------------------------------------------------------------------- // Purpose: Constructor //----------------------------------------------------------------------------- CEconCraftingRecipeDefinition::CEconCraftingRecipeDefinition( void ) : m_nDefIndex( 0 ) #ifdef GC_DLL , m_bIsCraftableByUnverifiedClient( false ) #endif // GC_DLL { } //----------------------------------------------------------------------------- // Purpose: Initialize the attribute definition // Input: pKVAttribute - The KeyValues representation of the attribute // schema - The overall item schema for this attribute // pVecErrors - An optional vector that will contain error messages if // the init fails. // Output: True if initialization succeeded, false otherwise //----------------------------------------------------------------------------- bool CEconCraftingRecipeDefinition::BInitFromKV( KeyValues *pKVRecipe, CUtlVector *pVecErrors /* = NULL */ ) { m_nDefIndex = Q_atoi( pKVRecipe->GetName() ); // Check for required fields SCHEMA_INIT_CHECK( NULL != pKVRecipe->FindKey( "input_items" ), "Recipe definition %d: Missing required field \"input_items\"", m_nDefIndex ); SCHEMA_INIT_CHECK( NULL != pKVRecipe->FindKey( "output_items" ), "Recipe definition %d: Missing required field \"output_items\"", m_nDefIndex ); m_bDisabled = pKVRecipe->GetBool( "disabled" ); m_strName = pKVRecipe->GetString( "name" ); m_strN_A = pKVRecipe->GetString( "n_A" ); m_strDescInputs = pKVRecipe->GetString( "desc_inputs" ); m_strDescOutputs = pKVRecipe->GetString( "desc_outputs" ); m_strDI_A = pKVRecipe->GetString( "di_A" ); m_strDI_B = pKVRecipe->GetString( "di_B" ); m_strDI_C = pKVRecipe->GetString( "di_C" ); m_strDO_A = pKVRecipe->GetString( "do_A" ); m_strDO_B = pKVRecipe->GetString( "do_B" ); m_strDO_C = pKVRecipe->GetString( "do_C" ); #ifdef GC_DLL m_bIsCraftableByUnverifiedClient = pKVRecipe->GetBool( "is_craftable_by_unverified_clients", false ); #endif // GC_DLL m_bRequiresAllSameClass = pKVRecipe->GetBool( "all_same_class" ); m_bRequiresAllSameSlot = pKVRecipe->GetBool( "all_same_slot" ); m_iCacheClassUsageForOutputFromItem = pKVRecipe->GetInt( "add_class_usage_to_output", -1 ); m_iCacheSlotUsageForOutputFromItem = pKVRecipe->GetInt( "add_slot_usage_to_output", -1 ); m_iCacheSetForOutputFromItem = pKVRecipe->GetInt( "add_set_to_output", -1 ); m_bPremiumAccountOnly = pKVRecipe->GetBool( "premium_only", false ); m_iCategory = (recipecategories_t)StringFieldToInt( pKVRecipe->GetString("category"), g_szRecipeCategoryStrings, ARRAYSIZE(g_szRecipeCategoryStrings) ); // Read in all the input items KeyValues *pKVInputItems = pKVRecipe->FindKey( "input_items" ); if ( NULL != pKVInputItems ) { FOR_EACH_TRUE_SUBKEY( pKVInputItems, pKVInputItem ) { int index = m_InputItemsCriteria.AddToTail(); SCHEMA_INIT_SUBSTEP( m_InputItemsCriteria[index].BInitFromKV( pKVInputItem ) ); // Recipes ignore the enabled flag when generating items m_InputItemsCriteria[index].SetIgnoreEnabledFlag( true ); index = m_InputItemDupeCounts.AddToTail(); m_InputItemDupeCounts[index] = atoi( pKVInputItem->GetName() ); } } // Read in all the output items KeyValues *pKVOutputItems = pKVRecipe->FindKey( "output_items" ); if ( NULL != pKVOutputItems ) { FOR_EACH_TRUE_SUBKEY( pKVOutputItems, pKVOutputItem ) { int index = m_OutputItemsCriteria.AddToTail(); SCHEMA_INIT_SUBSTEP( m_OutputItemsCriteria[index].BInitFromKV( pKVOutputItem ) ); // Recipes ignore the enabled flag when generating items m_OutputItemsCriteria[index].SetIgnoreEnabledFlag( true ); } } return SCHEMA_INIT_SUCCESS(); } //----------------------------------------------------------------------------- // Purpose: Serializes the criteria to and from messages //----------------------------------------------------------------------------- bool CEconCraftingRecipeDefinition::BSerializeToMsg( CSOItemRecipe & msg ) const { msg.set_def_index( m_nDefIndex ); msg.set_name( m_strName ); msg.set_n_a( m_strN_A ); msg.set_desc_inputs( m_strDescInputs ); msg.set_desc_outputs( m_strDescOutputs ); msg.set_di_a( m_strDI_A ); msg.set_di_b( m_strDI_B ); msg.set_di_c( m_strDI_C ); msg.set_do_a( m_strDO_A ); msg.set_do_b( m_strDO_B ); msg.set_do_c( m_strDO_C ); msg.set_requires_all_same_class( m_bRequiresAllSameClass ); msg.set_requires_all_same_slot( m_bRequiresAllSameSlot ); msg.set_class_usage_for_output( m_iCacheClassUsageForOutputFromItem ); msg.set_slot_usage_for_output( m_iCacheSlotUsageForOutputFromItem ); msg.set_set_for_output( m_iCacheSetForOutputFromItem ); FOR_EACH_VEC( m_InputItemsCriteria, i ) { CSOItemCriteria *pCrit = msg.add_input_items_criteria(); if ( !m_InputItemsCriteria[i].BSerializeToMsg( *pCrit ) ) return false; } FOR_EACH_VEC( m_InputItemDupeCounts, i ) { msg.add_input_item_dupe_counts( m_InputItemDupeCounts[i] ); } FOR_EACH_VEC( m_OutputItemsCriteria, i ) { CSOItemCriteria *pCrit = msg.add_output_items_criteria(); if ( !m_OutputItemsCriteria[i].BSerializeToMsg( *pCrit ) ) return false; } return true; } //----------------------------------------------------------------------------- // Purpose: Serializes the criteria to and from messages //----------------------------------------------------------------------------- bool CEconCraftingRecipeDefinition::BDeserializeFromMsg( const CSOItemRecipe & msg ) { m_nDefIndex = msg.def_index(); m_strName = msg.name().c_str(); m_strN_A = msg.n_a().c_str(); m_strDescInputs = msg.desc_inputs().c_str(); m_strDescOutputs = msg.desc_outputs().c_str(); m_strDI_A = msg.di_a().c_str(); m_strDI_B = msg.di_b().c_str(); m_strDI_C = msg.di_c().c_str(); m_strDO_A = msg.do_a().c_str(); m_strDO_B = msg.do_b().c_str(); m_strDO_C = msg.do_c().c_str(); m_bRequiresAllSameClass = msg.requires_all_same_class(); m_bRequiresAllSameSlot = msg.requires_all_same_slot(); m_iCacheClassUsageForOutputFromItem = msg.class_usage_for_output(); m_iCacheSlotUsageForOutputFromItem = msg.slot_usage_for_output(); m_iCacheSetForOutputFromItem = msg.set_for_output(); // Read how many input items there are uint32 unCount = msg.input_items_criteria_size(); m_InputItemsCriteria.SetSize( unCount ); for ( uint32 i = 0; i < unCount; i++ ) { if ( !m_InputItemsCriteria[i].BDeserializeFromMsg( msg.input_items_criteria( i ) ) ) return false; } // Read how many input item dupe counts there are unCount = msg.input_item_dupe_counts_size(); m_InputItemDupeCounts.SetSize( unCount ); for ( uint32 i = 0; i < unCount; i++ ) { m_InputItemDupeCounts[i] = msg.input_item_dupe_counts( i ); } // Read how many output items there are unCount = msg.output_items_criteria_size(); m_OutputItemsCriteria.SetSize( unCount ); for ( uint32 i = 0; i < unCount; i++ ) { if ( !m_OutputItemsCriteria[i].BDeserializeFromMsg( msg.output_items_criteria( i ) ) ) return false; } return true; } //----------------------------------------------------------------------------- // Purpose: Returns true if the vector contains a set of items that matches the inputs for this recipe // Note it will fail if the vector contains extra items that aren't needed. // //----------------------------------------------------------------------------- bool CEconCraftingRecipeDefinition::ItemListMatchesInputs( CUtlVector *vecCraftingItems, KeyValues *out_pkvCraftParams, bool bIgnoreSlop, CUtlVector *vecChosenItems ) const { return false; } //----------------------------------------------------------------------------- // Purpose: Constructor //----------------------------------------------------------------------------- int CEconCraftingRecipeDefinition::GetTotalInputItemsRequired( void ) const { int iCount = 0; FOR_EACH_VEC( m_InputItemsCriteria, i ) { if ( m_InputItemDupeCounts[i] ) { iCount += m_InputItemDupeCounts[i]; } else { iCount++; } } return iCount; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- #ifdef GC_DLL #define GC_SCH_REFERENCE( TAttribSchType ) \ TAttribSchType, #else #define GC_SCH_REFERENCE( TAttribSchType ) #endif //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- unsigned int Internal_GetAttributeTypeUniqueIdentifierNextValue() { static unsigned int s_unUniqueCounter = 0; unsigned int unCounter = s_unUniqueCounter; s_unUniqueCounter++; return unCounter; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- #ifdef GC_DLL template < typename TAttribSchType, typename TRecordBaseType > static TAttribSchType *GetTypedSch( TRecordBaseType *pRecordBase ) { Assert( pRecordBase->GetITable() == TAttribSchType::k_iTable ); #if ENABLE_TYPED_ATTRIBUTE_PARANOIA TAttribSchType *pTypedSch = dynamic_cast( pRecordBase ); Assert( pTypedSch ); return pTypedSch; #else return static_cast( pRecordBase ); #endif } #endif // GC_DLL //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- template < GC_SCH_REFERENCE( typename TAttribSchType ) typename TAttribInMemoryType > class CSchemaAttributeTypeBase : public ISchemaAttributeTypeBase { public: #ifdef GC_DLL virtual CColumnSet& GetFullColumnSet() const OVERRIDE { static CColumnSet sFullColumnSet( CColumnSet::Full() ); return sFullColumnSet; } virtual CRecordBase *CreateTypedSchRecord() const OVERRIDE { return new TAttribSchType; } #endif // GC_DLL }; //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- template < GC_SCH_REFERENCE( typename TAttribSchType ) typename TProtobufValueType > class CSchemaAttributeTypeProtobufBase : public CSchemaAttributeTypeBase< GC_SCH_REFERENCE( TAttribSchType ) TProtobufValueType > { public: virtual void ConvertTypedValueToByteStream( const TProtobufValueType& typedValue, ::std::string *out_psBytes ) const OVERRIDE { DbgVerify( typedValue.SerializeToString( out_psBytes ) ); } virtual void ConvertByteStreamToTypedValue( const ::std::string& sBytes, TProtobufValueType *out_pTypedValue ) const OVERRIDE { DbgVerify( out_pTypedValue->ParseFromString( sBytes ) ); } virtual bool BConvertStringToEconAttributeValue( const CEconItemAttributeDefinition *pAttrDef, const char *pszValue, union attribute_data_union_t *out_pValue, bool bEnableTerribleBackwardsCompatibilitySchemaParsingCode ) const OVERRIDE { Assert( pAttrDef ); Assert( out_pValue ); std::string sValue( pszValue ); TProtobufValueType typedValue; if ( !google::protobuf::TextFormat::ParseFromString( sValue, &typedValue ) ) return false; this->ConvertTypedValueToEconAttributeValue( typedValue, out_pValue ); return true; } virtual void ConvertEconAttributeValueToString( const CEconItemAttributeDefinition *pAttrDef, const attribute_data_union_t& value, std::string *out_ps ) const OVERRIDE { Assert( pAttrDef ); Assert( out_ps ); google::protobuf::TextFormat::PrintToString( this->GetTypedValueContentsFromEconAttributeValue( value ), out_ps ); } }; //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- class CSchemaAttributeType_String : public CSchemaAttributeTypeProtobufBase< GC_SCH_REFERENCE( CSchItemAttributeString ) CAttribute_String > { public: #ifdef GC_DLL virtual void ConvertEconAttributeValueToSch( itemid_t unItemId, const CEconItemAttributeDefinition *pAttrDef, const attribute_data_union_t& value, GCSDK::CRecordBase *out_pSchRecord ) const OVERRIDE { Assert( out_pSchRecord ); Assert( pAttrDef ); Assert( pAttrDef->GetAttributeType() == this ); CSchItemAttributeString *out_psch = GetTypedSch( out_pSchRecord ); CAttribute_String typedValue; this->ConvertEconAttributeValueToTypedValue( value, &typedValue ); // const CAttribute_String& typedValue = GetTypedValueContentsFromEconAttributeValue( value ); out_psch->m_ulItemID = unItemId; out_psch->m_unAttrDefIndex = pAttrDef->GetDefinitionIndex(); WRITE_VAR_CHAR_FIELD( (*out_psch), VarCharAttrStrValue, typedValue.value().c_str() ); } virtual void LoadSchToEconAttributeValue( CEconItem *pTargetItem, const CEconItemAttributeDefinition *pAttrDef, const GCSDK::CRecordBase *pSchRecord ) const OVERRIDE { Assert( pTargetItem ); Assert( pAttrDef ); Assert( pSchRecord ); Assert( pAttrDef->GetAttributeType() == this ); const CSchItemAttributeString *psch = GetTypedSch( pSchRecord ); CAttribute_String typedValue; typedValue.set_value( READ_VAR_CHAR_FIELD( (*psch), m_VarCharAttrStrValue ) ); pTargetItem->SetDynamicAttributeValue( pAttrDef, typedValue ); } #endif // GC_DLL // We intentionally override the convert-to-/convert-from-string functions for strings so that string literals can be // specified in the schema, etc. without worrying about the protobuf text format. virtual bool BConvertStringToEconAttributeValue( const CEconItemAttributeDefinition *pAttrDef, const char *pszValue, union attribute_data_union_t *out_pValue, bool bEnableTerribleBackwardsCompatibilitySchemaParsingCode ) const OVERRIDE { Assert( pAttrDef ); Assert( out_pValue ); CAttribute_String typedValue; typedValue.set_value( pszValue ); this->ConvertTypedValueToEconAttributeValue( typedValue, out_pValue ); return true; } virtual void ConvertEconAttributeValueToString( const CEconItemAttributeDefinition *pAttrDef, const attribute_data_union_t& value, std::string *out_ps ) const OVERRIDE { Assert( pAttrDef ); Assert( out_ps ); *out_ps = this->GetTypedValueContentsFromEconAttributeValue( value ).value().c_str(); } }; void CopyStringAttributeValueToCharPointerOutput( const CAttribute_String *pValue, const char **out_pValue ) { Assert( pValue ); Assert( out_pValue ); *out_pValue = pValue->value().c_str(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- class CSchemaAttributeType_DynamicRecipeComponentDefinedItem : public CSchemaAttributeTypeProtobufBase< GC_SCH_REFERENCE( CSchItemAttributeDynamicRecipeComponentDefinedItem ) CAttribute_DynamicRecipeComponent > { public: #ifdef GC_DLL virtual void ConvertEconAttributeValueToSch( itemid_t unItemId, const CEconItemAttributeDefinition *pAttrDef, const union attribute_data_union_t& value, GCSDK::CRecordBase *out_pSchRecord ) const OVERRIDE { Assert( out_pSchRecord ); Assert( pAttrDef ); Assert( pAttrDef->GetAttributeType() == this ); CSchItemAttributeDynamicRecipeComponentDefinedItem *out_psch = GetTypedSch( out_pSchRecord ); CAttribute_DynamicRecipeComponent typedValue; ConvertEconAttributeValueToTypedValue( value, &typedValue ); out_psch->m_ulItemID = unItemId; out_psch->m_unAttrDefIndex = pAttrDef->GetDefinitionIndex(); out_psch->m_unItemDef = typedValue.def_index(); out_psch->m_unItemQuality = typedValue.item_quality(); out_psch->m_unFlags = typedValue.component_flags(); out_psch->m_unItemCount = typedValue.num_required(); out_psch->m_unItemsFulfilled = typedValue.num_fulfilled(); WRITE_VAR_CHAR_FIELD( (*out_psch), VarCharAttrStr, typedValue.attributes_string().c_str() ); } virtual void LoadSchToEconAttributeValue( CEconItem *pTargetItem, const CEconItemAttributeDefinition *pAttrDef, const GCSDK::CRecordBase *pSchRecord ) const OVERRIDE { Assert( pTargetItem ); Assert( pAttrDef ); Assert( pSchRecord ); Assert( pAttrDef->GetAttributeType() == this ); const CSchItemAttributeDynamicRecipeComponentDefinedItem *psch = GetTypedSch( pSchRecord ); CAttribute_DynamicRecipeComponent typedValue; typedValue.set_def_index( psch->m_unItemDef ); typedValue.set_item_quality( psch->m_unItemQuality ); typedValue.set_component_flags( psch->m_unFlags ); typedValue.set_attributes_string( READ_VAR_CHAR_FIELD( (*psch), m_VarCharAttrStr ) ); typedValue.set_num_required( psch->m_unItemCount ); typedValue.set_num_fulfilled( psch->m_unItemsFulfilled ); pTargetItem->SetDynamicAttributeValue( pAttrDef, typedValue ); } virtual bool BConvertStringToEconAttributeValue( const CEconItemAttributeDefinition *pAttrDef, const char *pszValue, union attribute_data_union_t *out_pValue, bool bEnableTerribleBackwardsCompatibilitySchemaParsingCode ) const OVERRIDE { Assert( pAttrDef ); Assert( out_pValue ); std::string sValue( pszValue ); // What's happened here is we've renamed some fields within CAttribute_DynamicRecipeComponent, // but steam contains the strings of the old format serialized, and keeps sending them to us. // Rather than updating steam, we're going to made a protobuff class that can accept the new // and old formats, and put the corret values into the correct members of the new format. CAttribute_DynamicRecipeComponent_COMPAT_NEVER_SERIALIZE_THIS_OUT typedCompatValue; CAttribute_DynamicRecipeComponent typedActualValue; #ifdef STAGING_ONLY auto *pActualFields = typedActualValue.descriptor(); auto *pCompatFields = typedCompatValue.descriptor(); for ( int i=0; i < pActualFields->field_count(); ++i ) { const bool bFoundField = pCompatFields->FindFieldByName( pActualFields->field( i )->name() ) != NULL; Assert( bFoundField ); if ( !bFoundField ) { EmitError( SPEW_GC, "Missing field '%s' in CAttribute_DynamicRecipeComponent_COMPAT_NEVER_SERIALIZE_THIS_OUT\n", pActualFields->field( i )->name() ); return false; } } #endif // STAGING_ONLY if ( !google::protobuf::TextFormat::ParseFromString( sValue, &typedCompatValue ) ) { EmitError( SPEW_GC, "Failed to parse recipe component into compatible protobuf\n" ); return false; } if ( typedCompatValue.has_component_flags() ) typedActualValue.set_component_flags( typedCompatValue.component_flags() ); else if ( typedCompatValue.has_item_flags() ) typedActualValue.set_component_flags( typedCompatValue.item_flags() ); else { EmitError( SPEW_GC, "Failed to parse component_flags. component_flags: %d, item_flags: %d\n", typedCompatValue.component_flags(), typedCompatValue.item_flags() ); return false; } if ( typedCompatValue.has_def_index() ) typedActualValue.set_def_index( typedCompatValue.def_index() ); else if ( typedCompatValue.has_item_def() ) typedActualValue.set_def_index( typedCompatValue.item_def() ); else if ( typedActualValue.component_flags() & DYNAMIC_RECIPE_FLAG_PARAM_ITEM_DEF_SET ) { EmitError( SPEW_GC, "Failed to parse item_def. def_index: %d, item_def: %d\n", typedCompatValue.def_index(), typedCompatValue.item_def() ); return false; } typedActualValue.set_item_quality( typedCompatValue.item_quality() ); typedActualValue.set_attributes_string( typedCompatValue.attributes_string() ); if( typedCompatValue.has_num_required() ) typedActualValue.set_num_required( typedCompatValue.num_required() ); else if ( typedCompatValue.has_item_count() ) typedActualValue.set_num_required( typedCompatValue.item_count() ); else { EmitError( SPEW_GC, "Failed to parse component_flags. num_required: %d, item_count: %d\n", typedCompatValue.num_required(), typedCompatValue.item_count() ); return false; } if ( typedCompatValue.has_items_fulfilled() ) typedActualValue.set_num_fulfilled( typedCompatValue.items_fulfilled() ); else if ( typedCompatValue.has_num_fulfilled() ) typedActualValue.set_num_fulfilled( typedCompatValue.num_fulfilled() ); else { EmitError( SPEW_GC, "Failed to parse num_fulfilled. items_fulfilled: %d, num_fulfilled: %d\n", typedCompatValue.items_fulfilled(), typedCompatValue.num_fulfilled() ); return false; } this->ConvertTypedValueToEconAttributeValue( typedActualValue, out_pValue ); return true; } #endif // GC_DLL }; //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- class CSchemaAttributeType_ItemSlotCriteria : public CSchemaAttributeTypeProtobufBase< GC_SCH_REFERENCE( CSchItemAttributeItemSlotCriteria ) CAttribute_ItemSlotCriteria > { public: #ifdef GC_DLL virtual void ConvertEconAttributeValueToSch( itemid_t unItemId, const CEconItemAttributeDefinition *pAttrDef, const union attribute_data_union_t& value, GCSDK::CRecordBase *out_pSchRecord ) const OVERRIDE { Assert( out_pSchRecord ); Assert( pAttrDef ); Assert( pAttrDef->GetAttributeType() == this ); AssertMsg( 0, "Implement this when we want this attribute to be dynamic" ); } virtual void LoadSchToEconAttributeValue( CEconItem *pTargetItem, const CEconItemAttributeDefinition *pAttrDef, const GCSDK::CRecordBase *pSchRecord ) const OVERRIDE { Assert( pTargetItem ); Assert( pAttrDef ); Assert( pSchRecord ); Assert( pAttrDef->GetAttributeType() == this ); AssertMsg( 0, "Implement this when we want this attribute to be dynamic" ); } #endif // GC_DLL virtual bool BConvertStringToEconAttributeValue( const CEconItemAttributeDefinition *pAttrDef, const char *pszValue, union attribute_data_union_t *out_pValue, bool bEnableTerribleBackwardsCompatibilitySchemaParsingCode ) const OVERRIDE { Assert( pAttrDef ); Assert( out_pValue ); std::string sValue( pszValue ); CAttribute_ItemSlotCriteria typedValue; if ( !google::protobuf::TextFormat::ParseFromString( sValue, &typedValue ) ) return false; this->ConvertTypedValueToEconAttributeValue( typedValue, out_pValue ); return true; } virtual void ConvertEconAttributeValueToString( const CEconItemAttributeDefinition *pAttrDef, const attribute_data_union_t& value, std::string *out_ps ) const OVERRIDE { Assert( pAttrDef ); Assert( out_ps ); this->ConvertEconAttributeValueToString( pAttrDef, value, out_ps ); } }; //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- class CSchemaAttributeType_WorldItemPlacement : public CSchemaAttributeTypeProtobufBase < GC_SCH_REFERENCE( CSchItemAttributeWorldItemPlacement ) CAttribute_WorldItemPlacement > { public: #ifdef GC_DLL virtual void ConvertEconAttributeValueToSch( itemid_t unItemId, const CEconItemAttributeDefinition *pAttrDef, const union attribute_data_union_t& value, GCSDK::CRecordBase *out_pSchRecord ) const OVERRIDE { Assert( out_pSchRecord ); Assert( pAttrDef ); Assert( pAttrDef->GetAttributeType() == this ); CSchItemAttributeWorldItemPlacement *out_psch = GetTypedSch< CSchItemAttributeWorldItemPlacement >( out_pSchRecord ); CAttribute_WorldItemPlacement typedValue; ConvertEconAttributeValueToTypedValue( value, &typedValue ); out_psch->m_ulItemID = unItemId; out_psch->m_unAttrDefIndex = pAttrDef->GetDefinitionIndex(); out_psch->m_ulOriginalItemID = typedValue.original_item_id(); out_psch->m_fPosX = typedValue.pos_x(); out_psch->m_fPosY = typedValue.pos_y(); out_psch->m_fPosZ = typedValue.pos_z(); out_psch->m_fAngX = typedValue.ang_x(); out_psch->m_fAngY = typedValue.ang_y(); out_psch->m_fAngZ = typedValue.ang_z(); } virtual void LoadSchToEconAttributeValue( CEconItem *pTargetItem, const CEconItemAttributeDefinition *pAttrDef, const GCSDK::CRecordBase *pSchRecord ) const OVERRIDE { Assert( pTargetItem ); Assert( pAttrDef ); Assert( pSchRecord ); Assert( pAttrDef->GetAttributeType() == this ); const CSchItemAttributeWorldItemPlacement *psch = GetTypedSch< const CSchItemAttributeWorldItemPlacement >( pSchRecord ); CAttribute_WorldItemPlacement typedValue; typedValue.set_original_item_id( psch->m_ulOriginalItemID ); typedValue.set_pos_x( psch->m_fPosX ); typedValue.set_pos_y( psch->m_fPosY ); typedValue.set_pos_x( psch->m_fPosZ ); typedValue.set_ang_x( psch->m_fAngX ); typedValue.set_ang_y( psch->m_fAngY ); typedValue.set_ang_z( psch->m_fAngZ ); pTargetItem->SetDynamicAttributeValue( pAttrDef, typedValue ); } #endif // GC_DLL virtual bool BConvertStringToEconAttributeValue( const CEconItemAttributeDefinition *pAttrDef, const char *pszValue, union attribute_data_union_t *out_pValue, bool bEnableTerribleBackwardsCompatibilitySchemaParsingCode ) const OVERRIDE { Assert( pAttrDef ); Assert( out_pValue ); CAttribute_WorldItemPlacement typedValue; uint32 unValue = ( pszValue ) ? atoi( pszValue ) : 0; // Item forcing us to create the attribute (via force_gc_to_generate) if ( unValue == 0 ) { typedValue.set_original_item_id( INVALID_ITEM_ID ); typedValue.set_pos_x( 0.f ); typedValue.set_pos_y( 0.f ); typedValue.set_pos_z( 0.f ); typedValue.set_ang_x( 0.f ); typedValue.set_ang_y( 0.f ); typedValue.set_ang_z( 0.f ); } else { std::string sValue( pszValue ); if ( !google::protobuf::TextFormat::ParseFromString( sValue, &typedValue ) ) return false; } this->ConvertTypedValueToEconAttributeValue( typedValue, out_pValue ); return true; } virtual void ConvertEconAttributeValueToString( const CEconItemAttributeDefinition *pAttrDef, const attribute_data_union_t& value, std::string *out_ps ) const OVERRIDE { Assert( pAttrDef ); Assert( out_ps ); this->ConvertEconAttributeValueToString( pAttrDef, value, out_ps ); } }; //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- class CSchemaAttributeType_Float : public CSchemaAttributeTypeBase< GC_SCH_REFERENCE( CSchItemAttributeFloat ) float > { public: #ifdef GC_DLL virtual void ConvertEconAttributeValueToSch( itemid_t unItemId, const CEconItemAttributeDefinition *pAttrDef, const union attribute_data_union_t& value, GCSDK::CRecordBase *out_pSchRecord ) const OVERRIDE { Assert( out_pSchRecord ); Assert( pAttrDef ); Assert( pAttrDef->GetAttributeType() == this ); CSchItemAttributeFloat *out_pschItemAttribute = GetTypedSch( out_pSchRecord ); // @note Tom Bui: we store the value as an unsigned integer in the DB, so just treat the field as a bunch of bits out_pschItemAttribute->m_ulItemID = unItemId; out_pschItemAttribute->m_unAttrDefIndex = pAttrDef->GetDefinitionIndex(); out_pschItemAttribute->m_fValue = value.asFloat; } virtual void LoadSchToEconAttributeValue( CEconItem *pTargetItem, const CEconItemAttributeDefinition *pAttrDef, const GCSDK::CRecordBase *pSchRecord ) const OVERRIDE { Assert( pTargetItem ); Assert( pAttrDef ); Assert( pSchRecord ); Assert( pAttrDef->GetAttributeType() == this ); const CSchItemAttributeFloat *pschItemAttribute = GetTypedSch( pSchRecord ); pTargetItem->SetDynamicAttributeValue( pAttrDef, pschItemAttribute->m_fValue ); } #endif // GC_DLL virtual bool BConvertStringToEconAttributeValue( const CEconItemAttributeDefinition *pAttrDef, const char *pszValue, union attribute_data_union_t *out_pValue, bool bEnableTerribleBackwardsCompatibilitySchemaParsingCode ) const OVERRIDE { Assert( pAttrDef ); Assert( out_pValue ); out_pValue->asFloat = Q_atof( pszValue ); return true; } virtual void ConvertEconAttributeValueToString( const CEconItemAttributeDefinition *pAttrDef, const attribute_data_union_t& value, std::string *out_ps ) const OVERRIDE { Assert( pAttrDef ); Assert( out_ps ); *out_ps = CFmtStr( "%f", value.asFloat ).Get(); } virtual void ConvertTypedValueToByteStream( const float& typedValue, ::std::string *out_psBytes ) const OVERRIDE { Assert( out_psBytes ); Assert( out_psBytes->size() == 0 ); out_psBytes->resize( sizeof( float ) ); *reinterpret_cast( &((*out_psBytes)[0]) ) = typedValue; // overwrite string contents (sizeof( float ) bytes) } virtual void ConvertByteStreamToTypedValue( const ::std::string& sBytes, float *out_pTypedValue ) const OVERRIDE { Assert( out_pTypedValue ); Assert( sBytes.size() == sizeof( float ) ); *out_pTypedValue = *reinterpret_cast( &sBytes[0] ); } virtual bool BSupportsGameplayModificationAndNetworking() const OVERRIDE { return true; } }; //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- class CSchemaAttributeType_UInt64 : public CSchemaAttributeTypeBase< GC_SCH_REFERENCE( CSchItemAttributeUInt64 ) uint64 > { public: #ifdef GC_DLL virtual void ConvertEconAttributeValueToSch( itemid_t unItemId, const CEconItemAttributeDefinition *pAttrDef, const union attribute_data_union_t& value, GCSDK::CRecordBase *out_pSchRecord ) const OVERRIDE { Assert( out_pSchRecord ); Assert( pAttrDef ); Assert( pAttrDef->GetAttributeType() == this ); uint64 ulValue; ConvertEconAttributeValueToTypedValue( value, &ulValue ); CSchItemAttributeUInt64 *out_pschItemAttribute = GetTypedSch( out_pSchRecord ); // @note Tom Bui: we store the value as an unsigned integer in the DB, so just treat the field as a bunch of bits out_pschItemAttribute->m_ulItemID = unItemId; out_pschItemAttribute->m_unAttrDefIndex = pAttrDef->GetDefinitionIndex(); out_pschItemAttribute->m_ulValue = ulValue; } virtual void LoadSchToEconAttributeValue( CEconItem *pTargetItem, const CEconItemAttributeDefinition *pAttrDef, const GCSDK::CRecordBase *pSchRecord ) const OVERRIDE { Assert( pTargetItem ); Assert( pAttrDef ); Assert( pSchRecord ); Assert( pAttrDef->GetAttributeType() == this ); const CSchItemAttributeUInt64 *pschItemAttribute = GetTypedSch( pSchRecord ); pTargetItem->SetDynamicAttributeValue( pAttrDef, pschItemAttribute->m_ulValue ); } #endif // GC_DLL virtual bool BConvertStringToEconAttributeValue( const CEconItemAttributeDefinition *pAttrDef, const char *pszValue, union attribute_data_union_t *out_pValue, bool bEnableTerribleBackwardsCompatibilitySchemaParsingCode ) const OVERRIDE { Assert( pAttrDef ); Assert( out_pValue ); out_pValue->asUint32 = V_atoui64( pszValue ); return true; } virtual void ConvertEconAttributeValueToString( const CEconItemAttributeDefinition *pAttrDef, const attribute_data_union_t& value, std::string *out_ps ) const OVERRIDE { Assert( pAttrDef ); Assert( out_ps ); uint64 ulValue; ConvertEconAttributeValueToTypedValue( value, &ulValue ); *out_ps = CFmtStr( "%llu", ulValue ).Get(); } virtual void ConvertTypedValueToByteStream( const uint64& typedValue, ::std::string *out_psBytes ) const OVERRIDE { Assert( out_psBytes ); Assert( out_psBytes->size() == 0 ); out_psBytes->resize( sizeof( uint64 ) ); *reinterpret_cast( &((*out_psBytes)[0]) ) = typedValue; // overwrite string contents (sizeof( uint64 ) bytes) } virtual void ConvertByteStreamToTypedValue( const ::std::string& sBytes, uint64 *out_pTypedValue ) const OVERRIDE { Assert( out_pTypedValue ); Assert( sBytes.size() == sizeof( uint64 ) ); *out_pTypedValue = *reinterpret_cast( &sBytes[0] ); } }; //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- class CSchemaAttributeType_Default : public CSchemaAttributeTypeBase< GC_SCH_REFERENCE( CSchItemAttribute ) attrib_value_t > { public: #ifdef GC_DLL virtual bool BAssetClassExportedAttributeValue( const CEconItemAttributeDefinition *pAttrDef, const attribute_data_union_t& value ) const OVERRIDE { Assert( pAttrDef ); static CSchemaAttributeDefHandle pAttrib_TradableAfter( "tradable after date" ); // Don't include "tradable after date" if it's in the past // See IEconItemInterface::IsTradable for the specific logic on how this affects tradability if ( pAttrDef == pAttrib_TradableAfter && CRTime::RTime32TimeCur() > value.asUint32 ) return false; return CSchemaAttributeTypeBase< GC_SCH_REFERENCE( CSchItemAttribute ) attrib_value_t >::BAssetClassExportedAttributeValue( pAttrDef, value ); } virtual void ConvertEconAttributeValueToSch( itemid_t unItemId, const CEconItemAttributeDefinition *pAttrDef, const union attribute_data_union_t& value, GCSDK::CRecordBase *out_pSchRecord ) const OVERRIDE { Assert( out_pSchRecord ); Assert( pAttrDef ); Assert( pAttrDef->GetAttributeType() == this ); CSchItemAttribute *out_pschItemAttribute = GetTypedSch( out_pSchRecord ); // @note Tom Bui: we store the value as an unsigned integer in the DB, so just treat the field as a bunch of bits out_pschItemAttribute->m_ulItemID = unItemId; out_pschItemAttribute->m_unAttrDefIndex = pAttrDef->GetDefinitionIndex(); out_pschItemAttribute->m_unValue = value.asUint32; } virtual void LoadSchToEconAttributeValue( CEconItem *pTargetItem, const CEconItemAttributeDefinition *pAttrDef, const GCSDK::CRecordBase *pSchRecord ) const OVERRIDE { Assert( pTargetItem ); Assert( pAttrDef ); Assert( pSchRecord ); Assert( pAttrDef->GetAttributeType() == this ); const CSchItemAttribute *pschItemAttribute = GetTypedSch( pSchRecord ); pTargetItem->SetDynamicAttributeValue( pAttrDef, pschItemAttribute->m_unValue ); } virtual void LoadOrGenerateEconAttributeValue( CEconItem *pTargetItem, const CEconItemAttributeDefinition *pAttrDef, const static_attrib_t& staticAttrib, const CEconGameAccount *pGameAccount ) const OVERRIDE { Assert( pTargetItem ); Assert( pTargetItem->GetItemDefinition() ); Assert( pAttrDef ); // Wear is reassigned by attributes but has a default value from itemdef prefab static CSchemaAttributeDefHandle pAttrDef_PaintkitWear( "set_item_texture_wear" ); // do not apply an attribute if it already exists. If the new and the old attribute value is different then we assert (and use the latest value) attrib_value_t unValue = 0; if ( pTargetItem->FindAttribute( pAttrDef, &unValue ) && pAttrDef != pAttrDef_PaintkitWear ) { AssertMsg4( unValue == staticAttrib.m_value.asUint32, "Item id %llu (%s) attempting to generate dynamic attribute value for '%s' (%d) when attribute already exists with a different Value! This probably indicates some sort of code flow error calling LoadOrGenerateEconAttributeValue() late.", pTargetItem->GetItemID(), pTargetItem->GetItemDefinition()->GetDefinitionName(), pAttrDef->GetDefinitionName(), pAttrDef->GetDefinitionIndex() ); if ( unValue == staticAttrib.m_value.asUint32 ) return; } // Could be raw integer bits or raw floating-point bits depending on where in the union we stored the value. We copy the // bit pattern indiscriminately. attribute_data_union_t ResultValue; ResultValue = staticAttrib.m_value; GenerateEconAttributeValue( pAttrDef, staticAttrib, pGameAccount, &ResultValue ); LoadEconAttributeValue( pTargetItem, pAttrDef, ResultValue ); } virtual void GenerateEconAttributeValue( const CEconItemAttributeDefinition *pAttrDef, const static_attrib_t& staticAttrib, const CEconGameAccount *pGameAccount, attribute_data_union_t* out_pValue ) const { Assert( pAttrDef ); AssertMsg( pGameAccount || !staticAttrib.m_pKVCustomData, "Cannot run custom logic with no game account object! Passing in NULL for pGameAccount is only supported when we know we won't be running custom value generation code!" ); Assert( out_pValue ); if( staticAttrib.m_pKVCustomData ) { Internal_RunCustomAttributeValueLogic( staticAttrib, pGameAccount, out_pValue ); } } #endif // GC_DLL virtual bool BConvertStringToEconAttributeValue( const CEconItemAttributeDefinition *pAttrDef, const char *pszValue, union attribute_data_union_t *out_pValue, bool bEnableTerribleBackwardsCompatibilitySchemaParsingCode ) const OVERRIDE { Assert( pAttrDef ); Assert( out_pValue ); if ( bEnableTerribleBackwardsCompatibilitySchemaParsingCode ) { // Not having any value specified is valid -- we interpret this as "default", or 0 as both an in int and a float. out_pValue->asFloat = pszValue ? atof( pszValue ) : 0.0f; } // This is terrible backwards-compatibility code to support the pulling of values from econ asset classes. else { if ( pAttrDef->IsStoredAsInteger() ) { out_pValue->asUint32 = (uint32)Q_atoui64( pszValue ); } else if ( pAttrDef->IsStoredAsFloat() ) { out_pValue->asFloat = Q_atof( pszValue ); } else { Assert( !"Unknown storage type for CSchemaAttributeType_Default::BConvertStringToEconAttributeValue()!" ); return false; } } return true; } virtual void ConvertEconAttributeValueToString( const CEconItemAttributeDefinition *pAttrDef, const attribute_data_union_t& value, std::string *out_ps ) const OVERRIDE { Assert( pAttrDef ); Assert( out_ps ); if( pAttrDef->IsStoredAsFloat() ) { *out_ps = CFmtStr( "%f", value.asFloat ).Get(); } else if( pAttrDef->IsStoredAsInteger() ) { *out_ps = CFmtStr( "%u", value.asUint32 ).Get(); } else { Assert( !"Unknown storage type for CSchemaAttributeType_Default::ConvertEconAttributeValueToString()!" ); } } virtual void ConvertTypedValueToByteStream( const attrib_value_t& typedValue, ::std::string *out_psBytes ) const OVERRIDE { Assert( out_psBytes ); Assert( out_psBytes->size() == 0 ); out_psBytes->resize( sizeof( attrib_value_t ) ); *reinterpret_cast( &((*out_psBytes)[0]) ) = typedValue; // overwrite string contents (sizeof( attrib_value_t ) bytes) } virtual void ConvertByteStreamToTypedValue( const ::std::string& sBytes, attrib_value_t *out_pTypedValue ) const OVERRIDE { Assert( out_pTypedValue ); #ifdef GC_DLL // The GC is expected to always have internally-consistent information. Assert( sBytes.size() == sizeof( attrib_value_t ) ); #else // Game clients and servers may have partially out-of-date information, or may have downloaded a new schema // but not know how to parse an attribute of a certain type, etc. In these cases, because we know we // aren't on the GC, temporarily failing to load these values until the client shuts down and updates // is about the best we can hope for. if ( sBytes.size() < sizeof( attrib_value_t ) ) { *out_pTypedValue = attrib_value_t(); return; } #endif *out_pTypedValue = *reinterpret_cast( &sBytes[0] ); } virtual bool BSupportsGameplayModificationAndNetworking() const OVERRIDE { return true; } private: #ifdef GC_DLL void Internal_RunCustomAttributeValueLogic( const static_attrib_t& staticAttrib, const CEconGameAccount *pGameAccount, attribute_data_union_t* out_pValue ) const { AssertMsg( pGameAccount, "No game account when running custom attribute value logic!" ); float flValue = 0; const char *pszMethod = staticAttrib.m_pKVCustomData->GetString( "method", NULL ); if ( Q_stricmp( pszMethod, "employee_number" ) == 0 ) { flValue = pGameAccount->Obj().m_rtime32FirstPlayed; } else if ( Q_stricmp( pszMethod, "date" ) == 0 ) // Not used? { flValue = CRTime::RTime32TimeCur(); } else if ( Q_stricmp( pszMethod, "year" ) == 0 ) { flValue = CRTime( CRTime::RTime32TimeCur() ).GetYear(); } else if ( Q_stricmp( pszMethod, "gifts_given_out" ) == 0 ) { flValue = pGameAccount->Obj().m_unNumGiftsGiven; } else if ( Q_stricmp( pszMethod, "expiration_period_hours_from_now" ) == 0 ) { flValue = CRTime::RTime32DateAdd( CRTime::RTime32TimeCur(), staticAttrib.m_value.asFloat, k_ETimeUnitHour ); } else if ( Q_stricmp( pszMethod, "def index from lootlist" ) == 0 ) { const char* pszLootlistName = staticAttrib.m_pKVCustomData->GetString( "lootlist" ); // Custom data stores the lootlist const CEconLootListDefinition* pLootList = GEconItemSchema().GetLootListByName( pszLootlistName ); if( !pLootList ) { AssertMsg1( 0, "Lootlist '%s' not found when performing custom attribute logic", pszLootlistName ); return; } CUtlVector vecRolledItems; // Roll our item def CDefaultUniformRandomStream RandomStream; if( !pLootList->RollRandomItemsAndAdditionalItems( &RandomStream, false, &vecRolledItems ) ) { AssertMsg1( 0, "Error generating item defs from lootlist '%s'", pszLootlistName ); return; } // Just take the first one's def index flValue = vecRolledItems.Head().m_pItemDef->GetDefinitionIndex(); } else { AssertMsg1( false, "Unknown value for 'method': '%s'", pszMethod ); } // Put the value into the right part of the union if ( staticAttrib.GetAttributeDefinition()->IsStoredAsFloat() ) { (*out_pValue).asFloat = flValue; } else if ( staticAttrib.GetAttributeDefinition()->IsStoredAsInteger() ) { (*out_pValue).asUint32 = (uint32)flValue; } else { AssertMsg1( 0, "Unknown storage type for CSchemaAttributeType_Default::Internal_RunCustomAttributeValueLogic() for attribute %s", staticAttrib.GetAttributeDefinition()->GetDefinitionName() ); } } #endif }; //----------------------------------------------------------------------------- // Purpose: Constructor //----------------------------------------------------------------------------- CEconItemAttributeDefinition::CEconItemAttributeDefinition( void ) : m_pKVAttribute( NULL ), m_pAttrType( NULL ), m_bHidden( false ), m_bWebSchemaOutputForced( false ), m_bStoredAsInteger( false ), m_bInstanceData( false ), m_bIsSetBonus( false ), m_iUserGenerationType( 0 ), m_iEffectType( ATTRIB_EFFECT_NEUTRAL ), m_iDescriptionFormat( 0 ), m_pszDescriptionString( NULL ), m_pszArmoryDesc( NULL ), m_pszDefinitionName( NULL ), m_pszAttributeClass( NULL ), m_ItemDefinitionTag( INVALID_ECON_TAG_HANDLE ), m_bCanAffectMarketName( false ), m_bCanAffectRecipeComponentName( false ) #ifndef GC_DLL , m_iszAttributeClass( NULL_STRING ) #endif { } //----------------------------------------------------------------------------- // Purpose: Copy constructor //----------------------------------------------------------------------------- CEconItemAttributeDefinition::CEconItemAttributeDefinition( const CEconItemAttributeDefinition &that ) { (*this) = that; } //----------------------------------------------------------------------------- // Purpose: Operator= //----------------------------------------------------------------------------- CEconItemAttributeDefinition &CEconItemAttributeDefinition::operator=( const CEconItemAttributeDefinition &rhs ) { m_nDefIndex = rhs.m_nDefIndex; m_pAttrType = rhs.m_pAttrType; m_bHidden = rhs.m_bHidden; m_bWebSchemaOutputForced = rhs.m_bWebSchemaOutputForced; m_bStoredAsInteger = rhs.m_bStoredAsInteger; m_iUserGenerationType = rhs.m_iUserGenerationType; m_bInstanceData = rhs.m_bInstanceData; m_bIsSetBonus = rhs.m_bIsSetBonus; m_iEffectType = rhs.m_iEffectType; m_iDescriptionFormat = rhs.m_iDescriptionFormat; m_pszDescriptionString = rhs.m_pszDescriptionString; m_pszArmoryDesc = rhs.m_pszArmoryDesc; m_pszDefinitionName = rhs.m_pszDefinitionName; m_pszAttributeClass = rhs.m_pszAttributeClass; m_ItemDefinitionTag = rhs.m_ItemDefinitionTag; m_bCanAffectMarketName = rhs.m_bCanAffectMarketName; m_bCanAffectRecipeComponentName = rhs.m_bCanAffectRecipeComponentName; #ifndef GC_DLL m_iszAttributeClass = rhs.m_iszAttributeClass; #endif m_pKVAttribute = NULL; if ( NULL != rhs.m_pKVAttribute ) { m_pKVAttribute = rhs.m_pKVAttribute->MakeCopy(); // Re-assign string pointers m_pszDefinitionName = m_pKVAttribute->GetString("name"); m_pszDescriptionString = m_pKVAttribute->GetString( "description_string", NULL ); m_pszArmoryDesc = m_pKVAttribute->GetString( "armory_desc", NULL ); m_pszAttributeClass = m_pKVAttribute->GetString( "attribute_class", NULL ); Assert( V_strcmp( m_pszDefinitionName, rhs.m_pszDefinitionName ) == 0 ); Assert( V_strcmp( m_pszDescriptionString, rhs.m_pszDescriptionString ) == 0 ); Assert( V_strcmp( m_pszArmoryDesc, rhs.m_pszArmoryDesc ) == 0 ); Assert( V_strcmp( m_pszAttributeClass, rhs.m_pszAttributeClass ) == 0 ); } else { Assert( m_pszDefinitionName == NULL ); Assert( m_pszDescriptionString == NULL ); Assert( m_pszArmoryDesc == NULL ); Assert( m_pszAttributeClass == NULL ); } return *this; } //----------------------------------------------------------------------------- // Purpose: Destructor //----------------------------------------------------------------------------- CEconItemAttributeDefinition::~CEconItemAttributeDefinition( void ) { if ( m_pKVAttribute ) m_pKVAttribute->deleteThis(); m_pKVAttribute = NULL; } //----------------------------------------------------------------------------- // Purpose: Initialize the attribute definition // Input: pKVAttribute - The KeyValues representation of the attribute // schema - The overall item schema for this attribute // pVecErrors - An optional vector that will contain error messages if // the init fails. // Output: True if initialization succeeded, false otherwise //----------------------------------------------------------------------------- bool CEconItemAttributeDefinition::BInitFromKV( KeyValues *pKVAttribute, CUtlVector *pVecErrors /* = NULL */ ) { m_pKVAttribute = pKVAttribute->MakeCopy(); m_nDefIndex = Q_atoi( m_pKVAttribute->GetName() ); m_pszDefinitionName = m_pKVAttribute->GetString("name", "(unnamed)"); m_bHidden = m_pKVAttribute->GetInt( "hidden", 0 ) != 0; m_bWebSchemaOutputForced = m_pKVAttribute->GetInt( "force_output_description", 0 ) != 0; m_bStoredAsInteger = m_pKVAttribute->GetInt( "stored_as_integer", 0 ) != 0; m_bIsSetBonus = m_pKVAttribute->GetBool( "is_set_bonus", false ); m_bCanAffectMarketName = m_pKVAttribute->GetBool( "can_affect_market_name", false ); m_bCanAffectRecipeComponentName = m_pKVAttribute->GetBool( "can_affect_recipe_component_name", false ); m_iUserGenerationType = m_pKVAttribute->GetInt( "is_user_generated", 0 ); m_iEffectType = (attrib_effect_types_t)StringFieldToInt( m_pKVAttribute->GetString("effect_type"), g_EffectTypes, ARRAYSIZE(g_EffectTypes) ); m_iDescriptionFormat = StringFieldToInt( m_pKVAttribute->GetString("description_format"), g_AttributeDescriptionFormats, ARRAYSIZE(g_AttributeDescriptionFormats) ); m_pszDescriptionString = m_pKVAttribute->GetString( "description_string", NULL ); m_pszArmoryDesc = m_pKVAttribute->GetString( "armory_desc", NULL ); m_pszAttributeClass = m_pKVAttribute->GetString( "attribute_class", NULL ); m_bInstanceData = pKVAttribute->GetBool( "instance_data", false ); const char *pszTag = m_pKVAttribute->GetString( "apply_tag_to_item_definition", NULL ); m_ItemDefinitionTag = pszTag ? GetItemSchema()->GetHandleForTag( pszTag ) : INVALID_ECON_TAG_HANDLE; #if defined(CLIENT_DLL) || defined(GAME_DLL) m_iszAttributeClass = NULL_STRING; #endif const char *pszAttrType = m_pKVAttribute->GetString( "attribute_type", NULL ); // NULL implies "default type" for backwards compatibility m_pAttrType = GetItemSchema()->GetAttributeType( pszAttrType ); SCHEMA_INIT_CHECK( NULL != m_pKVAttribute->FindKey( "name" ), "Attribute definition %s: Missing required field \"name\"", m_pKVAttribute->GetName() ); SCHEMA_INIT_CHECK( NULL != m_pAttrType, "Attribute definition %s: Unable to find attribute data type '%s'", m_pszDefinitionName, pszAttrType ? pszAttrType : "(default)" ); if ( m_bIsSetBonus ) { SCHEMA_INIT_CHECK( m_pAttrType->BSupportsGameplayModificationAndNetworking(), "Attribute definition %s: set as set bonus attribute but does not support gameplay modification/networking!", m_pszDefinitionName ); } m_unAssetClassBucket = pKVAttribute->GetInt( "asset_class_bucket", 0 ); m_eAssetClassAttrExportRule = k_EAssetClassAttrExportRule_Default; if ( char const *szRule = pKVAttribute->GetString( "asset_class_export", NULL ) ) { if ( !V_stricmp( szRule, "skip" ) ) { m_eAssetClassAttrExportRule = k_EAssetClassAttrExportRule_Skip; } else if ( !V_stricmp( szRule, "gconly" ) ) { m_eAssetClassAttrExportRule = EAssetClassAttrExportRule_t( k_EAssetClassAttrExportRule_GCOnly | k_EAssetClassAttrExportRule_Skip ); } else if ( !V_stricmp( szRule, "bucketed" ) ) { SCHEMA_INIT_CHECK( m_unAssetClassBucket, "Attribute definition %s: Asset class export rule '%s' is incompatible", m_pszDefinitionName, szRule ); m_eAssetClassAttrExportRule = k_EAssetClassAttrExportRule_Bucketed; } else if ( !V_stricmp( szRule, "default" ) ) { m_eAssetClassAttrExportRule = k_EAssetClassAttrExportRule_Default; } else { SCHEMA_INIT_CHECK( false, "Attribute definition %s: Invalid asset class export rule '%s'", m_pszDefinitionName, szRule ); } } // Check for misuse of asset class bucket SCHEMA_INIT_CHECK( ( !m_unAssetClassBucket || m_bInstanceData ), "Attribute definition %s: Cannot use \"asset_class_bucket\" on class-level attributes", m_pKVAttribute->GetName() ); return SCHEMA_INIT_SUCCESS(); } CQuestObjectiveDefinition::CQuestObjectiveDefinition( void ) : m_pszDescriptionToken( NULL ) , m_nDefIndex( 0 ) , m_nPoints( 0 ) {} CQuestObjectiveDefinition::~CQuestObjectiveDefinition() {} bool CQuestObjectiveDefinition::BInitFromKV( KeyValues *pKVItem, CUtlVector *pVecErrors /* = NULL */ ) { m_nDefIndex = pKVItem->GetInt( "defindex", -1 ); m_pszDescriptionToken = pKVItem->GetString( "description_string" ); m_bOptional = pKVItem->GetBool( "optional", false ); m_bAdvanced = pKVItem->GetBool( "advanced", false ); m_nPoints = pKVItem->GetInt( "points", 0 ); SCHEMA_INIT_CHECK( m_nDefIndex != -1, "Quest objective missing def index" ); SCHEMA_INIT_CHECK( m_pszDescriptionToken != NULL, "Quest objective is missing a description" ); return SCHEMA_INIT_SUCCESS(); } //----------------------------------------------------------------------------- // Purpose: Constructor //----------------------------------------------------------------------------- CEconItemDefinition::CEconItemDefinition( void ) : m_pKVItem( NULL ), m_bEnabled( false ), m_unMinItemLevel( 1 ), m_unMaxItemLevel( 1 ), m_iArmoryRemap( 0 ), m_iStoreRemap( 0 ), m_nItemQuality( k_unItemQuality_Any ), m_nForcedItemQuality( k_unItemQuality_Any ), m_nDefaultDropQuantity( 1 ), m_bLoadOnDemand( false ), m_pTool( NULL ), m_rtExpiration( 0 ), m_BundleInfo( NULL ), #ifdef TF_CLIENT_DLL m_unNumConcreteItems( 0 ), #endif // TF_CLIENT_DLL m_nPopularitySeed( 0 ), m_pszDefinitionName( NULL ), m_pszItemClassname( NULL ), m_pszClassToken( NULL ), m_pszSlotToken( NULL ), m_pszItemBaseName( NULL ), m_pszItemTypeName( NULL ), m_pszItemDesc( NULL ), m_pszArmoryDesc( NULL ), m_pszInventoryModel( NULL ), m_pszInventoryImage( NULL ), m_pszHolidayRestriction( NULL ), m_iSubType( 0 ), m_pszBaseDisplayModel( NULL ), m_iDefaultSkin( -1 ), m_pszWorldDisplayModel( NULL ), m_pszWorldExtraWearableModel( NULL ), m_pszWorldExtraWearableViewModel( NULL ), m_pszVisionFilteredDisplayModel( NULL ), m_pszBrassModelOverride( NULL ), m_bHideBodyGroupsDeployedOnly( false ), m_bAttachToHands( false ), m_bAttachToHandsVMOnly( false ), m_bProperName( false ), m_bFlipViewModel( false ), m_bActAsWearable( false ), m_bActAsWeapon( false ), m_iDropType( 1 ), m_bHidden( false ), m_bShouldShowInArmory( false ), m_bIsPackBundle( false ), m_pOwningPackBundle( NULL ), m_bIsPackItem( false ), m_bBaseItem( false ), m_pszItemLogClassname( NULL ), m_pszItemIconClassname( NULL ), m_pszDatabaseAuditTable( NULL ), m_bImported( false ), m_pItemSetDef( NULL ), m_pItemCollectionDef( NULL ), m_pItemPaintKitDef( NULL ), m_pszArmoryRemap( NULL ), m_pszStoreRemap( NULL ), m_unSetItemRemapDefIndex( INVALID_ITEM_DEF_INDEX ), m_pszXifierRemapClass( NULL ), m_pszBaseFunctionalItemName( NULL ), m_pszParticleSuffix( NULL ), m_pszCollectionReference( NULL ), m_nItemRarity( k_unItemRarity_Any ), m_unItemSeries( 0 ), m_bValidForShuffle( false ), m_bValidForSelfMade( true ) { for ( int team = 0; team < TEAM_VISUAL_SECTIONS; team++ ) { m_PerTeamVisuals[team] = NULL; } m_pDictIcons = new CUtlDict< CUtlString >; } //----------------------------------------------------------------------------- // Purpose: Destructor //----------------------------------------------------------------------------- CEconItemDefinition::~CEconItemDefinition( void ) { for ( int i = 0; i < ARRAYSIZE( m_PerTeamVisuals ); i++ ) delete m_PerTeamVisuals[i]; #ifdef GC_DLL m_vecPropertyGenerators.PurgeAndDeleteElements(); #endif // GC_DLL if ( m_pKVItem ) m_pKVItem->deleteThis(); m_pKVItem = NULL; delete m_pTool; delete m_BundleInfo; delete m_pDictIcons; } #if defined(CLIENT_DLL) || defined(GAME_DLL) //----------------------------------------------------------------------------- // Purpose: Stomp our base data with extra testing data specified by the player //----------------------------------------------------------------------------- bool CEconItemDefinition::BInitFromTestItemKVs( int iNewDefIndex, KeyValues *pKVItem, CUtlVector* pVecErrors ) { // The KeyValues are stored in the player entity, so we can cache our name there m_nDefIndex = iNewDefIndex; m_unSetItemRemapDefIndex = m_nDefIndex; bool bTestingExistingItem = pKVItem->GetBool( "test_existing_item", false ); if ( !bTestingExistingItem ) { m_pszDefinitionName = pKVItem->GetString( "name", NULL ); m_pszItemBaseName = pKVItem->GetString( "name", NULL ); #ifdef CLIENT_DLL pKVItem->SetString( "name", VarArgs("Test Item %d", iNewDefIndex) ); #else pKVItem->SetString( "name", UTIL_VarArgs("Test Item %d", iNewDefIndex) ); #endif m_pszBaseDisplayModel = pKVItem->GetString( "model_player", NULL ); m_pszVisionFilteredDisplayModel = pKVItem->GetString( "model_vision_filtered", NULL ); m_bAttachToHands = pKVItem->GetInt( "attach_to_hands", 0 ) != 0; BInitVisualBlockFromKV( pKVItem ); } // Handle attributes m_vecStaticAttributes.Purge(); int iPaintCanIndex = pKVItem->GetInt("paintcan_index", 0); if ( iPaintCanIndex ) { static CSchemaAttributeDefHandle pAttrDef_PaintRGB( "set item tint RGB" ); const CEconItemDefinition *pCanDef = GetItemSchema()->GetItemDefinition(iPaintCanIndex); float flRGBVal; if ( pCanDef && pAttrDef_PaintRGB && FindAttribute_UnsafeBitwiseCast( pCanDef, pAttrDef_PaintRGB, &flRGBVal ) ) { static_attrib_t& StaticAttrib = m_vecStaticAttributes[ m_vecStaticAttributes.AddToTail() ]; StaticAttrib.iDefIndex = pAttrDef_PaintRGB->GetDefinitionIndex(); StaticAttrib.m_value.asFloat = flRGBVal; // this is bad! but we're in crazy hack code for UI customization of item definitions that don't exist so } } int iUnusualEffectIndex = pKVItem->GetInt( "unusual_index", 0 ); if ( iUnusualEffectIndex ) { static CSchemaAttributeDefHandle pAttrDef_AttachParticleStatic( "attach particle effect static" ); const attachedparticlesystem_t *pSystem = GetItemSchema()->GetAttributeControlledParticleSystem( iUnusualEffectIndex ); if ( pAttrDef_AttachParticleStatic && pSystem ) { static_attrib_t& StaticAttrib = m_vecStaticAttributes[ m_vecStaticAttributes.AddToTail() ]; StaticAttrib.iDefIndex = pAttrDef_AttachParticleStatic->GetDefinitionIndex(); StaticAttrib.m_value.asFloat = iUnusualEffectIndex; // this is bad! but we're in crazy hack code for UI customization of item definitions that don't exist so } } return true; } animation_on_wearable_t *GetOrCreateAnimationActivity( perteamvisuals_t *pVisData, const char *pszActivityName ) { FOR_EACH_VEC( pVisData->m_Animations, i ) { if ( Q_stricmp(pVisData->m_Animations[i].pszActivity, pszActivityName) == 0 ) return &pVisData->m_Animations[i]; } animation_on_wearable_t *pEntry = &pVisData->m_Animations[pVisData->m_Animations.AddToTail()]; pEntry->iActivity = kActivityLookup_Unknown; // We can't look it up yet, the activity list hasn't been populated. pEntry->pszActivity = pszActivityName; pEntry->iReplacement = kActivityLookup_Unknown; pEntry->pszReplacement = NULL; pEntry->pszSequence = NULL; pEntry->pszScene = NULL; pEntry->pszRequiredItem = NULL; return pEntry; } activity_on_wearable_t *GetOrCreatePlaybackActivity( perteamvisuals_t *pVisData, wearableanimplayback_t iPlayback ) { FOR_EACH_VEC( pVisData->m_Animations, i ) { if ( pVisData->m_Activities[i].iPlayback == iPlayback ) return &pVisData->m_Activities[i]; } activity_on_wearable_t *pEntry = &pVisData->m_Activities[pVisData->m_Activities.AddToTail()]; pEntry->iPlayback = iPlayback; pEntry->iActivity = kActivityLookup_Unknown; // We can't look it up yet, the activity list hasn't been populated. pEntry->pszActivity = NULL; return pEntry; } #endif // defined(CLIENT_DLL) || defined(GAME_DLL) //----------------------------------------------------------------------------- // Purpose: Handle parsing the per-team visual block from the keyvalues //----------------------------------------------------------------------------- void CEconItemDefinition::BInitVisualBlockFromKV( KeyValues *pKVItem, CUtlVector *pVecErrors ) { // Visuals for ( int team = 0; team < TEAM_VISUAL_SECTIONS; team++ ) { m_PerTeamVisuals[team] = NULL; if ( !g_TeamVisualSections[team] ) continue; KeyValues *pVisualsKV = pKVItem->FindKey( g_TeamVisualSections[team] ); if ( pVisualsKV ) { perteamvisuals_t *pVisData = new perteamvisuals_t(); #if defined(CLIENT_DLL) || defined(GAME_DLL) KeyValues *pKVEntry = pVisualsKV->GetFirstSubKey(); while ( pKVEntry ) { const char *pszEntry = pKVEntry->GetName(); if ( !Q_stricmp( pszEntry, "use_visualsblock_as_base" ) ) { // Start with a copy of an existing PerTeamVisuals const char *pszString = pKVEntry->GetString(); int nOverrideTeam = GetTeamVisualsFromString( pszString ); if ( nOverrideTeam != -1 ) { *pVisData = *m_PerTeamVisuals[nOverrideTeam]; } else { pVecErrors->AddToTail( CFmtStr( "Unknown visuals block: %s", pszString ).Access() ); } } else if ( !Q_stricmp( pszEntry, "attached_models" ) ) { FOR_EACH_SUBKEY( pKVEntry, pKVAttachedModelData ) { int iAtt = pVisData->m_AttachedModels.AddToTail(); pVisData->m_AttachedModels[iAtt].m_iModelDisplayFlags = pKVAttachedModelData->GetInt( "model_display_flags", kAttachedModelDisplayFlag_MaskAll ); pVisData->m_AttachedModels[iAtt].m_pszModelName = pKVAttachedModelData->GetString( "model", NULL ); } } else if ( !Q_stricmp( pszEntry, "attached_models_festive" ) ) { FOR_EACH_SUBKEY( pKVEntry, pKVAttachedModelData ) { int iAtt = pVisData->m_AttachedModelsFestive.AddToTail(); pVisData->m_AttachedModelsFestive[iAtt].m_iModelDisplayFlags = pKVAttachedModelData->GetInt( "model_display_flags", kAttachedModelDisplayFlag_MaskAll ); pVisData->m_AttachedModelsFestive[iAtt].m_pszModelName = pKVAttachedModelData->GetString( "model", NULL ); } } else if ( !Q_stricmp( pszEntry, "attached_particlesystems" ) ) { FOR_EACH_SUBKEY( pKVEntry, pKVAttachedParticleSystemData ) { int iAtt = pVisData->m_AttachedParticles.AddToTail(); pVisData->m_AttachedParticles[iAtt].pszSystemName = pKVAttachedParticleSystemData->GetString( "system", NULL ); pVisData->m_AttachedParticles[iAtt].pszControlPoints[0] = pKVAttachedParticleSystemData->GetString( "attachment", NULL ); pVisData->m_AttachedParticles[iAtt].bFollowRootBone = pKVAttachedParticleSystemData->GetBool( "attach_to_rootbone" ); pVisData->m_AttachedParticles[iAtt].iCustomType = 0; } } else if ( !Q_stricmp( pszEntry, "custom_particlesystem2" ) ) { int iAtt = pVisData->m_AttachedParticles.AddToTail(); pVisData->m_AttachedParticles[iAtt].pszSystemName = pKVEntry->GetString( "system", NULL ); pVisData->m_AttachedParticles[iAtt].iCustomType = 2; } else if ( !Q_stricmp( pszEntry, "custom_particlesystem" ) ) { int iAtt = pVisData->m_AttachedParticles.AddToTail(); pVisData->m_AttachedParticles[iAtt].pszSystemName = pKVEntry->GetString( "system", NULL ); pVisData->m_AttachedParticles[iAtt].iCustomType = 1; } else if ( !Q_stricmp( pszEntry, "playback_activity" ) ) { FOR_EACH_SUBKEY( pKVEntry, pKVSubKey ) { int iPlaybackInt = StringFieldToInt( pKVSubKey->GetName(), g_WearableAnimTypeStrings, ARRAYSIZE(g_WearableAnimTypeStrings) ); if ( iPlaybackInt >= 0 ) { activity_on_wearable_t *pEntry = GetOrCreatePlaybackActivity( pVisData, (wearableanimplayback_t)iPlaybackInt ); pEntry->pszActivity = pKVSubKey->GetString(); } } } else if ( !Q_stricmp( pszEntry, "animation_replacement" ) ) { FOR_EACH_SUBKEY( pKVEntry, pKVSubKey ) { animation_on_wearable_t *pEntry = GetOrCreateAnimationActivity( pVisData, pKVSubKey->GetName() ); pEntry->pszReplacement = pKVSubKey->GetString(); } } else if ( !Q_stricmp( pszEntry, "animation_sequence" ) ) { FOR_EACH_SUBKEY( pKVEntry, pKVSubKey ) { animation_on_wearable_t *pEntry = GetOrCreateAnimationActivity( pVisData, pKVSubKey->GetName() ); pEntry->pszSequence = pKVSubKey->GetString(); } } else if ( !Q_stricmp( pszEntry, "animation_scene" ) ) { FOR_EACH_SUBKEY( pKVEntry, pKVSubKey ) { animation_on_wearable_t *pEntry = GetOrCreateAnimationActivity( pVisData, pKVSubKey->GetName() ); pEntry->pszScene = pKVSubKey->GetString(); } } else if ( !Q_stricmp( pszEntry, "animation_required_item" ) ) { FOR_EACH_SUBKEY( pKVEntry, pKVSubKey ) { animation_on_wearable_t *pEntry = GetOrCreateAnimationActivity( pVisData, pKVSubKey->GetName() ); pEntry->pszRequiredItem = pKVSubKey->GetString(); } } else if ( !Q_stricmp( pszEntry, "player_bodygroups" ) ) { FOR_EACH_SUBKEY( pKVEntry, pKVBodygroupKey ) { const char *pszBodygroupName = pKVBodygroupKey->GetName(); int iValue = pKVBodygroupKey->GetInt(); // Track bodygroup information for this item in particular. pVisData->m_Maps.m_ModifiedBodyGroupNames.Insert( pszBodygroupName, iValue ); // Track global schema state. GetItemSchema()->AssignDefaultBodygroupState( pszBodygroupName, iValue ); } } else if ( !Q_stricmp( pszEntry, "skin" ) ) { pVisData->iSkin = pKVEntry->GetInt(); } else if ( !Q_stricmp( pszEntry, "use_per_class_bodygroups" ) ) { pVisData->bUsePerClassBodygroups = pKVEntry->GetBool(); } else if ( !Q_stricmp( pszEntry, "muzzle_flash" ) ) { pVisData->pszMuzzleFlash = pKVEntry->GetString(); } else if ( !Q_stricmp( pszEntry, "tracer_effect" ) ) { pVisData->pszTracerEffect = pKVEntry->GetString(); } else if ( !Q_stricmp( pszEntry, "particle_effect" ) ) { pVisData->pszParticleEffect = pKVEntry->GetString(); } else if ( !Q_strnicmp( pszEntry, "custom_sound", 12 ) ) // intentionally comparing prefixes { int iIndex = 0; if ( pszEntry[12] ) { iIndex = clamp( atoi( &pszEntry[12] ), 0, MAX_VISUALS_CUSTOM_SOUNDS-1 ); } pVisData->pszCustomSounds[iIndex] = pKVEntry->GetString(); } else if ( !Q_stricmp( pszEntry, "material_override" ) ) { pVisData->pszMaterialOverride = pKVEntry->GetString(); } else if ( !Q_strnicmp( pszEntry, "sound_", 6 ) ) // intentionally comparing prefixes { int iIndex = GetWeaponSoundFromString( &pszEntry[6] ); if ( iIndex != -1 ) { pVisData->pszWeaponSoundReplacements[iIndex] = pKVEntry->GetString(); } } else if ( !Q_stricmp( pszEntry, "code_controlled_bodygroup" ) ) { const char *pBodyGroupName = pKVEntry->GetString( "bodygroup", NULL ); const char *pFuncName = pKVEntry->GetString( "function", NULL ); if ( pBodyGroupName && pFuncName ) { codecontrolledbodygroupdata_t ccbgd = { pFuncName, NULL }; pVisData->m_Maps.m_CodeControlledBodyGroupNames.Insert( pBodyGroupName, ccbgd ); } } else if ( !Q_stricmp( pszEntry, "vm_bodygroup_override" ) ) { pVisData->m_iViewModelBodyGroupOverride = pKVEntry->GetInt(); } else if ( !Q_stricmp( pszEntry, "vm_bodygroup_state_override" ) ) { pVisData->m_iViewModelBodyGroupStateOverride = pKVEntry->GetInt(); } else if ( !Q_stricmp( pszEntry, "wm_bodygroup_override" ) ) { pVisData->m_iWorldModelBodyGroupOverride = pKVEntry->GetInt(); } else if ( !Q_stricmp( pszEntry, "wm_bodygroup_state_override" ) ) { pVisData->m_iWorldModelBodyGroupStateOverride = pKVEntry->GetInt(); } pKVEntry = pKVEntry->GetNextKey(); } #endif // defined(CLIENT_DLL) || defined(GAME_DLL) KeyValues *pStylesDataKV = pVisualsKV->FindKey( "styles" ); if ( pStylesDataKV ) { // Styles are only valid in the base "visuals" section. if ( team == 0 ) { BInitStylesBlockFromKV( pStylesDataKV, pVisData, pVecErrors ); } // ...but they used to be valid everywhere, so spit out a warning if people are trying to use // the old style of per-team styles. else { pVecErrors->AddToTail( "Per-team styles blocks are no longer valid. Use \"skin_red\" and \"skin_blu\" in a style entry instead." ); } } m_PerTeamVisuals[team] = pVisData; } } } #ifdef GC_DLL //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- template < typename T > static void NthPermutation ( T *pData, unsigned int unDataCount, unsigned int unIdx ) { for ( unsigned int i = 1; i < unDataCount; i++ ) { std::swap( pData[ unIdx % (i + 1) ], pData[ i ] ); unIdx = unIdx / (i + 1); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CEconItemDefinition::BApplyPropertyGenerators( CEconItem *pItem ) const { Assert( pItem ); for ( const IEconItemPropertyGenerator *pPropertyGenerator : m_vecPropertyGenerators ) { if ( !pPropertyGenerator->BGenerateProperties( pItem ) ) return false; } return true; } #endif // GC_DLL #if defined(CLIENT_DLL) || defined(GAME_DLL) //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CEconItemDefinition::GeneratePrecacheModelStrings( bool bDynamicLoad, CUtlVector *out_pVecModelStrings ) const { Assert( out_pVecModelStrings ); // Add base model. out_pVecModelStrings->AddToTail( GetBasePlayerDisplayModel() ); // Add styles. if ( GetNumStyles() ) { for ( style_index_t i=0; iGeneratePrecacheModelStringsForStyle( out_pVecModelStrings ); } } // Precache all the attached models for ( int team = 0; team < TEAM_VISUAL_SECTIONS; team++ ) { perteamvisuals_t *pPerTeamVisuals = GetPerTeamVisual( team ); if ( !pPerTeamVisuals ) continue; for ( int model = 0; model < pPerTeamVisuals->m_AttachedModels.Count(); model++ ) { out_pVecModelStrings->AddToTail( pPerTeamVisuals->m_AttachedModels[model].m_pszModelName ); } // Festive for ( int model = 0; model < pPerTeamVisuals->m_AttachedModelsFestive.Count(); model++ ) { out_pVecModelStrings->AddToTail( pPerTeamVisuals->m_AttachedModelsFestive[model].m_pszModelName ); } } if ( GetExtraWearableModel() ) { out_pVecModelStrings->AddToTail( GetExtraWearableModel() ); } if ( GetExtraWearableViewModel() ) { out_pVecModelStrings->AddToTail( GetExtraWearableViewModel() ); } if ( GetVisionFilteredDisplayModel() ) { out_pVecModelStrings->AddToTail( GetVisionFilteredDisplayModel() ); } // We don't need to cache the inventory model, because it's never loaded by the game } void CEconItemDefinition::GeneratePrecacheSoundStrings( bool bDynamicLoad, CUtlVector *out_pVecSoundStrings ) const { Assert( out_pVecSoundStrings ); for ( int iTeam = 0; iTeam < TEAM_VISUAL_SECTIONS; ++iTeam ) { for ( int iSound = 0; iSound < MAX_VISUALS_CUSTOM_SOUNDS; ++iSound ) { const char *pSoundName = GetCustomSound( iTeam, iSound ); if ( pSoundName && pSoundName[ 0 ] != '\0' ) { out_pVecSoundStrings->AddToTail( pSoundName ); } } } } #endif // #if defined(CLIENT_DLL) || defined(GAME_DLL) //----------------------------------------------------------------------------- const char *CEconItemDefinition::GetDefinitionString( const char *pszKeyName, const char *pszDefaultValue ) const { // !FIXME! Here we could do a dynamic lookup to apply the prefab overlay logic. // This could save a lot of duplicated data if ( m_pKVItem ) return m_pKVItem->GetString( pszKeyName, pszDefaultValue ); return pszDefaultValue; } //----------------------------------------------------------------------------- KeyValues *CEconItemDefinition::GetDefinitionKey( const char *pszKeyName ) const { // !FIXME! Here we could do a dynamic lookup to apply the prefab overlay logic. // This could save a lot of duplicated data if ( m_pKVItem ) return m_pKVItem->FindKey( pszKeyName ); return NULL; } //----------------------------------------------------------------------------- // Purpose: Parse the styles sub-section of the visuals block. //----------------------------------------------------------------------------- void CEconItemDefinition::BInitStylesBlockFromKV( KeyValues *pKVStyles, perteamvisuals_t *pVisData, CUtlVector *pVecErrors ) { FOR_EACH_SUBKEY( pKVStyles, pKVStyle ) { CEconStyleInfo *pStyleInfo = GetItemSchema()->CreateEconStyleInfo(); Assert( pStyleInfo ); pStyleInfo->BInitFromKV( pKVStyle, pVecErrors ); pVisData->m_Styles.AddToTail( pStyleInfo ); } } //----------------------------------------------------------------------------- // Purpose: Parse one style from the styles block. //----------------------------------------------------------------------------- void CEconStyleInfo::BInitFromKV( KeyValues *pKVStyle, CUtlVector *pVecErrors ) { enum { kInvalidSkinKey = -1, }; Assert( pKVStyle ); // A "skin" entry means "use this index for all of our teams, no matter how many we have". int iCommonSkin = pKVStyle->GetInt( "skin", kInvalidSkinKey ); if ( iCommonSkin != kInvalidSkinKey ) { for ( int i = 0; i < TEAM_VISUAL_SECTIONS; i++ ) { m_iSkins[i] = iCommonSkin; } } int iCommonViewmodelSkin = pKVStyle->GetInt( "v_skin", kInvalidSkinKey ); if ( iCommonViewmodelSkin != kInvalidSkinKey ) { for ( int i=0; iFindKey( "additional_hidden_bodygroups" ); if ( pKVHideBodygroups ) { FOR_EACH_SUBKEY( pKVHideBodygroups, pKVBodygroup ) { m_vecAdditionalHideBodygroups.AddToTail( pKVBodygroup->GetName() ); } } // Remaining common properties. m_pszName = pKVStyle->GetString( "name", "#TF_UnknownStyle" ); m_pszBasePlayerModel = pKVStyle->GetString( "model_player", NULL ); m_bIsSelectable = pKVStyle->GetBool( "selectable", true ); m_pszInventoryImage = pKVStyle->GetString( "image_inventory", NULL ); KeyValues *pKVBodygroup = pKVStyle->FindKey( "bodygroup" ); if ( pKVBodygroup ) { m_pszBodygroupName = pKVBodygroup->GetString( "name", NULL ); Assert( m_pszBodygroupName ); m_iBodygroupSubmodelIndex = pKVBodygroup->GetInt( "submodel_index", -1 ); Assert( m_iBodygroupSubmodelIndex != -1 ); } } #if defined(CLIENT_DLL) || defined(GAME_DLL) //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CEconStyleInfo::GeneratePrecacheModelStringsForStyle( CUtlVector *out_pVecModelStrings ) const { Assert( out_pVecModelStrings ); if ( GetBasePlayerDisplayModel() != NULL ) { out_pVecModelStrings->AddToTail( GetBasePlayerDisplayModel() ); } } #endif //----------------------------------------------------------------------------- // Purpose: Item definition initialization helpers. //----------------------------------------------------------------------------- static void RecursiveInheritKeyValues( KeyValues *out_pValues, KeyValues *pInstance ) { KeyValues *pPrevSubKey = NULL; for ( KeyValues * pSubKey = pInstance->GetFirstSubKey(); pSubKey != NULL; pPrevSubKey = pSubKey, pSubKey = pSubKey->GetNextKey() ) { // If this assert triggers, you have an item that uses a prefab but has multiple keys with the same name AssertMsg2 ( !pPrevSubKey || pPrevSubKey->GetNameSymbol() != pSubKey->GetNameSymbol(), "Item definition \"%s\" has multiple attributes of the same name (%s) can't use prefabs", pInstance->GetName(), pSubKey->GetName() ); KeyValues::types_t eType = pSubKey->GetDataType(); switch ( eType ) { case KeyValues::TYPE_STRING: out_pValues->SetString( pSubKey->GetName(), pSubKey->GetString() ); break; case KeyValues::TYPE_INT: out_pValues->SetInt( pSubKey->GetName(), pSubKey->GetInt() ); break; case KeyValues::TYPE_FLOAT: out_pValues->SetFloat( pSubKey->GetName(), pSubKey->GetFloat() ); break; case KeyValues::TYPE_WSTRING: out_pValues->SetWString( pSubKey->GetName(), pSubKey->GetWString() ); break; case KeyValues::TYPE_COLOR: out_pValues->SetColor( pSubKey->GetName(), pSubKey->GetColor() ) ; break; case KeyValues::TYPE_UINT64: out_pValues->SetUint64( pSubKey->GetName(), pSubKey->GetUint64() ) ; break; // "NONE" means "KeyValues" case KeyValues::TYPE_NONE: { // We may already have this part of the tree to stuff data into/overwrite, or we // may have to make a new block. KeyValues *pNewChild = out_pValues->FindKey( pSubKey->GetName() ); if ( !pNewChild ) { pNewChild = out_pValues->CreateNewKey(); pNewChild->SetName( pSubKey->GetName() ); } RecursiveInheritKeyValues( pNewChild, pSubKey ); break; } case KeyValues::TYPE_PTR: default: Assert( !"Unhandled data type for KeyValues inheritance!" ); break; } } } void MergeDefinitionPrefab( KeyValues *pKVWriteItem, KeyValues *pKVSourceItem ) { Assert( pKVWriteItem ); Assert( pKVSourceItem ); const char *svPrefabName = pKVSourceItem->GetString( "prefab", NULL ); if ( svPrefabName ) { CUtlStringList vecPrefabs; Q_SplitString( svPrefabName, " ", vecPrefabs ); // Iterate backwards so adjectives get applied over the noun prefab // e.g. wet scared cat would apply cat first, then scared and wet. FOR_EACH_VEC_BACK( vecPrefabs, i ) { KeyValues *pKVPrefab = GetItemSchema()->FindDefinitionPrefabByName( vecPrefabs[i] ); AssertMsg1( pKVPrefab, "Unable to find prefab \"%s\".", vecPrefabs[i] ); if ( pKVPrefab ) { MergeDefinitionPrefab( pKVWriteItem, pKVPrefab ); } } } RecursiveInheritKeyValues( pKVWriteItem, pKVSourceItem ); } KeyValues *CEconItemSchema::FindDefinitionPrefabByName( const char *pszPrefabName ) const { int iIndex = m_mapDefinitionPrefabs.Find( pszPrefabName ); if ( m_mapDefinitionPrefabs.IsValidIndex( iIndex ) ) return m_mapDefinitionPrefabs[iIndex]; return NULL; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- const char *CEconItemSchema::FindStringTableEntry( const char *pszTableName, int iIndex ) const { SchemaStringTableDict_t::IndexType_t i = m_dictStringTable.Find( pszTableName ); if ( !m_dictStringTable.IsValidIndex( i ) ) return NULL; const CUtlVector< schema_string_table_entry_t >& vec = *m_dictStringTable[i]; FOR_EACH_VEC( vec, j ) { if ( vec[j].m_iIndex == iIndex ) return vec[j].m_pszStr; } return NULL; } //----------------------------------------------------------------------------- // Purpose: Initialize the item definition // Input: pKVItem - The KeyValues representation of the item // schema - The overall item schema for this item // pVecErrors - An optional vector that will contain error messages if // the init fails. // Output: True if initialization succeeded, false otherwise //----------------------------------------------------------------------------- #ifdef GC_DLL GCConVar gc_steam_payment_rules_kv_key( "gc_steam_payment_rules_kv_key", "payment_rules" ); #endif // GC_DLL #if defined( WITH_STREAMABLE_WEAPONS ) #if defined( CLIENT_DLL ) ConVar tf_loadondemand_default("cl_loadondemand_default", "1", FCVAR_ARCHIVE | FCVAR_CLIENTDLL, "The default value for whether items should be delay loaded (1) or loaded now (0)."); #elif defined( GAME_DLL ) // The server doesn't load on demand by default because it can crash sometimes when this is set. We need to run that down, but in the meantime // we just have it load on demand. ConVar tf_loadondemand_default("sv_loadondemand_default", "0", FCVAR_ARCHIVE | FCVAR_GAMEDLL, "The default value for whether items should be delay loaded (1) or loaded now (0)."); #elif defined( GC_DLL ) GCConVar tf_loadondemand_default("gc_loadondemand_default", "1", FCVAR_ARCHIVE, "The default value for whether items should be delay loaded (1) or loaded now (0)."); #else #error "Need to add support for streamable weapons to this configuration, or disable streamable weapons here." #endif #endif // WITH_STREAMABLE_WEAPONS bool CEconItemDefinition::BInitFromKV( KeyValues *pKVItem, CUtlVector *pVecErrors /* = NULL */ ) { // Set standard members m_pKVItem = new KeyValues( pKVItem->GetName() ); MergeDefinitionPrefab( m_pKVItem, pKVItem ); m_bEnabled = m_pKVItem->GetBool( "enabled" ); // initializing this one first so that it will be available for all the errors below m_pszDefinitionName = m_pKVItem->GetString( "name", NULL ); #if defined( WITH_STREAMABLE_WEAPONS ) bool bGotDefault = false; m_bLoadOnDemand = m_pKVItem->GetBool( "loadondemand", tf_loadondemand_default.GetBool(), &bGotDefault ); // This logging is useful for tracking down bugs that crop up because we've (possibly) swapped the default value for loadondemand. // But it can be removed once we're satisfied there aren't any bugs as a result of the change (when we cleanup WITH_STREAMABLE_WEAPONS). if (bGotDefault) { DevMsg(10, "Item %s received default value for loadondemand\n", m_pszDefinitionName); } #else // Keep the old behavior, which is that loadondemand is defaulted to false. m_bLoadOnDemand = m_pKVItem->GetBool("loadondemand"); #endif m_nDefIndex = Q_atoi( m_pKVItem->GetName() ); m_unMinItemLevel = (uint32)m_pKVItem->GetInt( "min_ilevel", GetItemSchema()->GetMinLevel() ); m_unMaxItemLevel = (uint32)m_pKVItem->GetInt( "max_ilevel", GetItemSchema()->GetMaxLevel() ); m_nDefaultDropQuantity = m_pKVItem->GetInt( "default_drop_quantity", 1 ); m_nPopularitySeed = m_pKVItem->GetInt( "popularity_seed", 0 ); #if defined(CLIENT_DLL) || defined(GAME_DLL) // We read this manually here in the game dlls. The GC reads it below while checking the global schema. GetItemSchema()->BGetItemQualityFromName( m_pKVItem->GetString( "item_quality" ), &m_nItemQuality ); GetItemSchema()->BGetItemQualityFromName( m_pKVItem->GetString( "forced_item_quality" ), &m_nForcedItemQuality ); #endif // Check for required fields SCHEMA_INIT_CHECK( NULL != m_pKVItem->FindKey( "name" ), "Item definition %s: Missing required field \"name\"", m_pKVItem->GetName() ); SCHEMA_INIT_CHECK( NULL != m_pKVItem->FindKey( "item_class" ), "Item definition %s: Missing required field \"item_class\"", m_pKVItem->GetName() ); // Check value ranges SCHEMA_INIT_CHECK( m_pKVItem->GetInt( "min_ilevel" ) >= 0, "Item definition %s: \"min_ilevel\" must be greater than or equal to 0", GetDefinitionName() ); SCHEMA_INIT_CHECK( m_pKVItem->GetInt( "max_ilevel" ) >= 0, "Item definition %s: \"max_ilevel\" must be greater than or equal to 0", GetDefinitionName() ); // Check for consistency #ifdef GC_DLL // We don't do these consistency checks in the game, because it doesn't have the data to do them SCHEMA_INIT_CHECK( m_unMinItemLevel >= GetItemSchema()->GetMinLevel(), "Item definition %s: min_ilevel (%d) must be greater or equal to Minimum Item Level (%d)", GetDefinitionName(), m_unMinItemLevel, GetItemSchema()->GetMinLevel() ); SCHEMA_INIT_CHECK( m_unMinItemLevel <= m_unMaxItemLevel, "Item definition %s: min_ilevel (%d) must be greater or equal to min_ilevel (%d)", GetDefinitionName(), m_unMaxItemLevel, m_unMinItemLevel ); SCHEMA_INIT_CHECK( m_unMaxItemLevel <= GetItemSchema()->GetMaxLevel(), "Item definition %s: max_ilevel (%d) must be less than or equal to Maximum Item Level (%d)", GetDefinitionName(), m_unMaxItemLevel, GetItemSchema()->GetMaxLevel() ); // Read the item quality if ( m_pKVItem->FindKey( "item_quality" ) ) { SCHEMA_INIT_CHECK( GetItemSchema()->BGetItemQualityFromName( m_pKVItem->GetString( "item_quality" ), &m_nItemQuality ), "Item definition %s: Undefined item_quality \"%s\"", GetDefinitionName(), m_pKVItem->GetString( "item_quality" ) ); } if ( m_pKVItem->FindKey( "forced_item_quality" ) ) { SCHEMA_INIT_CHECK( GetItemSchema()->BGetItemQualityFromName( m_pKVItem->GetString( "forced_item_quality" ), &m_nForcedItemQuality ), "Item definition %s: Undefined item_quality \"%s\"", GetDefinitionName(), m_pKVItem->GetString( "forced_item_quality" ) ); } #endif // Rarity // Get Index from this string and save the index if ( m_pKVItem->FindKey( "item_rarity" ) ) { SCHEMA_INIT_CHECK( GetItemSchema()->BGetItemRarityFromName( m_pKVItem->GetString( "item_rarity" ), &m_nItemRarity ), "Item definition %s: Undefined item_rarity \"%s\"", GetDefinitionName(), m_pKVItem->GetString( "item_rarity" ) ); } if ( m_pKVItem->FindKey( "item_series" ) ) { // Make sure this is a valid series SCHEMA_INIT_CHECK( GetItemSchema()->BGetItemSeries( m_pKVItem->GetString( "item_series" ), &m_unItemSeries ), "Item definition %s: Undefined item_series \"%s\"", GetDefinitionName(), m_pKVItem->GetString( "item_series" ) ); } // Get the item class m_pszItemClassname = m_pKVItem->GetString( "item_class", NULL ); m_pszClassToken = m_pKVItem->GetString( "class_token_id", NULL ); m_pszSlotToken = m_pKVItem->GetString( "slot_token_id", NULL ); // expiration data const char *pchExpiration = m_pKVItem->GetString( "expiration_date", NULL ); if( pchExpiration && pchExpiration[0] ) { if ( pchExpiration[0] == '!' ) { m_rtExpiration = GetItemSchema()->GetCustomExpirationDate( &pchExpiration[1] ); SCHEMA_INIT_CHECK( m_rtExpiration != k_RTime32Nil, "Unknown/malformed expiration_date string \"%s\" in item %s.", pchExpiration, m_pszDefinitionName ); } else { m_rtExpiration = CRTime::RTime32FromFmtString( "YYYY-MM-DD hh:mm:ss" , pchExpiration ); #ifdef GC_DLL // Check that if we convert back to a string, we get the same value char rtimeBuf[k_RTimeRenderBufferSize]; SCHEMA_INIT_CHECK( Q_strcmp( CRTime::RTime32ToString( m_rtExpiration, rtimeBuf ), pchExpiration ) == 0, "Malformed expiration_date \"%s\" for expiration_date in item %s. Must be of the form \"YYYY-MM-DD hh:mm:ss\". Input: %s Output: %s InputTime: %u LocalTime: %u Timezone: %lu", pchExpiration, m_pszDefinitionName, pchExpiration, rtimeBuf, m_rtExpiration, CRTime::RTime32TimeCur(), timezone ); #else // Check that if we convert back to a string, we get the same value. Emit an error, but don't fail in the game code char rtimeBuf[k_RTimeRenderBufferSize]; if ( Q_strcmp( CRTime::RTime32ToString( m_rtExpiration, rtimeBuf ), pchExpiration ) != 0 ) { #if ( defined( _MSC_VER ) && _MSC_VER >= 1900 ) #define timezone _timezone #define daylight _daylight #endif Assert( false ); Warning( "Malformed expiration_date \"%s\" for expiration_date in item %s. Must be of the form \"YYYY-MM-DD hh:mm:ss\". Input: %s Output: %s InputTime: %u LocalTime: %u Timezone: %lu\n", pchExpiration, m_pszDefinitionName, pchExpiration, rtimeBuf, m_rtExpiration, CRTime::RTime32TimeCur(), timezone ); } #endif } } // Display data m_pszItemBaseName = m_pKVItem->GetString( "item_name", "" ); // non-NULL to ensure we can sort m_pszItemTypeName = m_pKVItem->GetString( "item_type_name", "" ); // non-NULL to ensure we can sort m_pszItemDesc = m_pKVItem->GetString( "item_description", NULL ); m_pszArmoryDesc = m_pKVItem->GetString( "armory_desc", NULL ); m_pszInventoryModel = m_pKVItem->GetString( "model_inventory", NULL ); m_pszInventoryImage = m_pKVItem->GetString( "image_inventory", NULL ); const char* pOverlay = m_pKVItem->GetString( "image_inventory_overlay", NULL ); if ( pOverlay ) { m_pszInventoryOverlayImages.AddToTail( pOverlay ); } pOverlay = m_pKVItem->GetString( "image_inventory_overlay2", NULL ); if ( pOverlay ) { m_pszInventoryOverlayImages.AddToTail( pOverlay ); } m_iInventoryImagePosition[0] = atoi( m_pKVItem->GetString( "image_inventory_pos_x", "0" ) ); m_iInventoryImagePosition[1] = atoi( m_pKVItem->GetString( "image_inventory_pos_y", "0" ) ); m_iInventoryImageSize[0] = atoi( m_pKVItem->GetString( "image_inventory_size_w", "128" ) ); m_iInventoryImageSize[1] = atoi( m_pKVItem->GetString( "image_inventory_size_h", "82" ) ); m_iInspectPanelDistance = m_pKVItem->GetInt( "inspect_panel_dist", 70 ); m_pszHolidayRestriction = m_pKVItem->GetString( "holiday_restriction", NULL ); m_nVisionFilterFlags = m_pKVItem->GetInt( "vision_filter_flags", 0 ); m_iSubType = atoi( m_pKVItem->GetString( "subtype", "0" ) ); m_pszBaseDisplayModel = m_pKVItem->GetString( "model_player", NULL ); m_iDefaultSkin = m_pKVItem->GetInt( "default_skin", -1 ); m_pszWorldDisplayModel = m_pKVItem->GetString( "model_world", NULL ); // Not the ideal method. c_models are better, but this is to solve a retrofit problem with the sticky launcher. m_pszWorldExtraWearableModel = m_pKVItem->GetString( "extra_wearable", NULL ); m_pszWorldExtraWearableViewModel = m_pKVItem->GetString( "extra_wearable_vm", NULL ); m_pszVisionFilteredDisplayModel = pKVItem->GetString( "model_vision_filtered", NULL ); m_pszBrassModelOverride = m_pKVItem->GetString( "brass_eject_model", NULL ); m_bHideBodyGroupsDeployedOnly = m_pKVItem->GetBool( "hide_bodygroups_deployed_only" ); m_bAttachToHands = m_pKVItem->GetInt( "attach_to_hands", 0 ) != 0; m_bAttachToHandsVMOnly = m_pKVItem->GetInt( "attach_to_hands_vm_only", 0 ) != 0; m_bProperName = m_pKVItem->GetInt( "propername", 0 ) != 0; m_bFlipViewModel = m_pKVItem->GetInt( "flip_viewmodel", 0 ) != 0; m_bActAsWearable = m_pKVItem->GetInt( "act_as_wearable", 0 ) != 0; m_bActAsWeapon = m_pKVItem->GetInt( "act_as_weapon", 0 ) != 0; m_bIsTool = m_pKVItem->GetBool( "is_tool", 0 ) || ( GetItemClass() && !V_stricmp( GetItemClass(), "tool" ) ); m_iDropType = StringFieldToInt( m_pKVItem->GetString("drop_type"), g_szDropTypeStrings, ARRAYSIZE(g_szDropTypeStrings) ); m_pszCollectionReference = m_pKVItem->GetString( "collection_reference", NULL ); // Creation data m_bHidden = m_pKVItem->GetInt( "hidden", 0 ) != 0; m_bShouldShowInArmory = m_pKVItem->GetInt( "show_in_armory", 0 ) != 0; m_bBaseItem = m_pKVItem->GetInt( "baseitem", 0 ) != 0; m_pszItemLogClassname = m_pKVItem->GetString( "item_logname", NULL ); m_pszItemIconClassname = m_pKVItem->GetString( "item_iconname", NULL ); m_pszDatabaseAuditTable = m_pKVItem->GetString( "database_audit_table", NULL ); m_bImported = m_pKVItem->FindKey( "import_from" ) != NULL; // Tool data m_pTool = NULL; KeyValues *pToolDataKV = m_pKVItem->FindKey( "tool" ); if ( pToolDataKV ) { const char *pszType = pToolDataKV->GetString( "type", NULL ); SCHEMA_INIT_CHECK( pszType != NULL, "Tool '%s' missing required type.", m_pKVItem->GetName() ); // Common-to-all-tools settings. const char *pszUseString = pToolDataKV->GetString( "use_string", NULL ); const char *pszUsageRestriction = pToolDataKV->GetString( "restriction", NULL ); KeyValues *pToolUsageKV = pToolDataKV->FindKey( "usage" ); // Common-to-all-tools usage capability flags. item_capabilities_t usageCapabilities = (item_capabilities_t)ITEM_CAP_TOOL_DEFAULT; KeyValues *pToolUsageCapsKV = pToolDataKV->FindKey( "usage_capabilities" ); if ( pToolUsageCapsKV ) { KeyValues *pEntry = pToolUsageCapsKV->GetFirstSubKey(); while ( pEntry ) { ParseCapability( usageCapabilities, pEntry ); pEntry = pEntry->GetNextKey(); } } m_pTool = GetItemSchema()->CreateEconToolImpl( pszType, pszUseString, pszUsageRestriction, usageCapabilities, pToolUsageKV ); SCHEMA_INIT_CHECK( m_pTool != NULL, "Unable to create tool implementation for '%s', of type '%s'.", m_pKVItem->GetName(), pszType ); } // Bundle KeyValues *pBundleDataKV = m_pKVItem->FindKey( "bundle" ); if ( pBundleDataKV ) { m_BundleInfo = new bundleinfo_t(); FOR_EACH_SUBKEY( pBundleDataKV, pKVCurItem ) { CEconItemDefinition *pItemDef = GetItemSchema()->GetItemDefinitionByName( pKVCurItem->GetName() ); SCHEMA_INIT_CHECK( pItemDef != NULL, "Unable to find item definition '%s' for bundle '%s'.", pKVCurItem->GetName(), m_pszDefinitionName ); m_BundleInfo->vecItemDefs.AddToTail( pItemDef ); } // Only check for pack bundle if the item is actually a bundle - note that we could do this programatically by checking that all items in the bundle are flagged as a "pack item" - but for now the bundle needs to be explicitly flagged as a pack bundle. m_bIsPackBundle = m_pKVItem->GetInt( "is_pack_bundle", 0 ) != 0; } // capabilities m_iCapabilities = (item_capabilities_t)ITEM_CAP_DEFAULT; KeyValues *pCapsKV = m_pKVItem->FindKey( "capabilities" ); if ( pCapsKV ) { KeyValues *pEntry = pCapsKV->GetFirstSubKey(); while ( pEntry ) { ParseCapability( m_iCapabilities, pEntry ); pEntry = pEntry->GetNextKey(); } } // item_set SCHEMA_INIT_CHECK( (!m_pKVItem->GetString( "item_set", NULL )), "Item definition '%s' specifies deprecated \"item_set\" field. Items sets are now specified only in the set itself, not on the definition.", GetDefinitionName() ); const char *pszSetItemRemapDefIndexName = m_pKVItem->GetString( "set_item_remap", NULL ); if ( pszSetItemRemapDefIndexName ) { const CEconItemDefinition *pRemapItemDef = GetItemSchema()->GetItemDefinitionByName( pszSetItemRemapDefIndexName ); m_unSetItemRemapDefIndex = pRemapItemDef ? pRemapItemDef->GetDefinitionIndex() : INVALID_ITEM_DEF_INDEX; SCHEMA_INIT_CHECK( m_unSetItemRemapDefIndex != INVALID_ITEM_DEF_INDEX, "Unable to find set item remap definition '%s' for '%s'.", pszSetItemRemapDefIndexName, GetDefinitionName() ); SCHEMA_INIT_CHECK( m_unSetItemRemapDefIndex != GetDefinitionIndex(), "Unable to set set item remap for definition '%s' to itself.", GetDefinitionName() ); } else { m_unSetItemRemapDefIndex = GetDefinitionIndex(); } // cache item map names m_pszArmoryRemap = m_pKVItem->GetString( "armory_remap", NULL ); m_pszStoreRemap = m_pKVItem->GetString( "store_remap", NULL ); m_pszXifierRemapClass = m_pKVItem->GetString( "xifier_class_remap", NULL ); m_pszBaseFunctionalItemName = m_pKVItem->GetString( "base_item_name", "" ); m_pszParticleSuffix = m_pKVItem->GetString( "particle_suffix", NULL ); m_bValidForShuffle = m_pKVItem->GetBool( "valid_for_shuffle", false ); m_bValidForSelfMade = m_pKVItem->GetBool( "valid_for_self_made", true ); // Init our visuals blocks. BInitVisualBlockFromKV( m_pKVItem, pVecErrors ); // Calculate our equip region mask. { m_unEquipRegionMask = 0; m_unEquipRegionConflictMask = 0; // Our equip region will come from one of two places -- either we have an "equip_regions" (plural) section, // in which case we have any number of regions specified; or we have an "equip_region" (singular) section // which will have one and exactly one region. If we have "equip_regions" (plural), we ignore whatever is // in "equip_region" (singular). // // Yes, this is sort of dumb. CUtlVector vecEquipRegionNames; KeyValues *pKVMultiEquipRegions = m_pKVItem->FindKey( "equip_regions" ), *pKVSingleEquipRegion = m_pKVItem->FindKey( "equip_region" ); // Maybe we have multiple entries? if ( pKVMultiEquipRegions ) { for ( KeyValues *pKVRegion = pKVMultiEquipRegions->GetFirstSubKey(); pKVRegion; pKVRegion = pKVRegion->GetNextKey() ) { vecEquipRegionNames.AddToTail( pKVRegion->GetName() ); } } // This is our one-and-only-one equip region. else if ( pKVSingleEquipRegion ) { const char *pEquipRegionName = pKVSingleEquipRegion->GetString( (const char *)NULL, NULL ); if ( pEquipRegionName ) { vecEquipRegionNames.AddToTail( pEquipRegionName ); } } // For each of our regions, add to our conflict mask both ourself and all the regions // that we conflict with. FOR_EACH_VEC( vecEquipRegionNames, i ) { const char *pszEquipRegionName = vecEquipRegionNames[i]; equip_region_mask_t unThisRegionMask = GetItemSchema()->GetEquipRegionMaskByName( pszEquipRegionName ); SCHEMA_INIT_CHECK( unThisRegionMask != 0, "Item definition %s: Unable to find equip region mask for region named \"%s\"", GetDefinitionName(), vecEquipRegionNames[i] ); m_unEquipRegionMask |= GetItemSchema()->GetEquipRegionBitMaskByName( pszEquipRegionName ); m_unEquipRegionConflictMask |= unThisRegionMask; } } // Single-line static attribute parsing. { KeyValues *pKVStaticAttrsKey = m_pKVItem->FindKey( "static_attrs" ); if ( pKVStaticAttrsKey ) { FOR_EACH_SUBKEY( pKVStaticAttrsKey, pKVKey ) { static_attrib_t staticAttrib; SCHEMA_INIT_SUBSTEP( staticAttrib.BInitFromKV_SingleLine( GetDefinitionName(), pKVKey, pVecErrors, false ) ); m_vecStaticAttributes.AddToTail( staticAttrib ); // Does this attribute specify a tag to apply to this item definition? Assert( staticAttrib.GetAttributeDefinition() ); } } } // Old style attribute parsing. Really only useful now for GC-generated attributes. KeyValues *pKVAttribKey = m_pKVItem->FindKey( "attributes" ); if ( pKVAttribKey ) { FOR_EACH_SUBKEY( pKVAttribKey, pKVKey ) { static_attrib_t staticAttrib; SCHEMA_INIT_SUBSTEP( staticAttrib.BInitFromKV_MultiLine( GetDefinitionName(), pKVKey, pVecErrors ) ); m_vecStaticAttributes.AddToTail( staticAttrib ); // Does this attribute specify a tag to apply to this item definition? Assert( staticAttrib.GetAttributeDefinition() ); } } // Initialize tags based on all static attributes for this item. for ( const static_attrib_t& attr : m_vecStaticAttributes ) { const econ_tag_handle_t tag = attr.GetAttributeDefinition()->GetItemDefinitionTag(); if ( tag != INVALID_ECON_TAG_HANDLE ) { m_vecTags.AddToTail( tag ); } } // Auto-generate tags based on capabilities. for ( int i = 0; i < NUM_ITEM_CAPS; i++ ) { if ( m_iCapabilities & (1 << i) ) { m_vecTags.AddToTail( GetItemSchema()->GetHandleForTag( CFmtStr( "auto__cap_%s", g_Capabilities[i] ).Get() ) ); } } // Initialize used-specified tags for this item if present. KeyValues *pKVTags = m_pKVItem->FindKey( "tags" ); if ( pKVTags ) { FOR_EACH_SUBKEY( pKVTags, pKVTag ) { m_vecTags.AddToTail( GetItemSchema()->GetHandleForTag( pKVTag->GetName() ) ); } } #ifdef GC_DLL SCHEMA_INIT_SUBSTEP( BCommonInitPropertyGeneratorsFromKV( GetDefinitionName(), &m_vecPropertyGenerators, m_pKVItem->FindKey( "property_generators" ), pVecErrors ) ); // Parse payment rules on the GC if any exist. KeyValues *pKVPaymentRules = m_pKVItem->FindKey( gc_steam_payment_rules_kv_key.GetString() ); if ( pKVPaymentRules ) { FOR_EACH_TRUE_SUBKEY( pKVPaymentRules, pKVRule ) { econ_item_payment_rule_t rule; const bool bFoundPaymentRule = BGetPaymentRule( pKVRule, &rule.m_eRuleType, &rule.m_RevenueShare ); SCHEMA_INIT_CHECK( bFoundPaymentRule, "Item definition '%s': payment rule %s didn't specify a known payment rule type", GetDefinitionName(), pKVRule->GetName() ); // Allow us to override some of our checks if we want to claim we really know what we're doing. if ( !pKVRule->FindKey( "sanity_check_override" ) ) { SCHEMA_INIT_CHECK( rule.m_RevenueShare > 0.0, "Item definition '%s': payment rule %s has invalid revenue share %0.2f", GetDefinitionName(), pKVRule->GetName(), rule.m_RevenueShare ); // Ordinarily, bundles can only specify the "bundle_revenue_share" payment rule type. However, for backwards compatability // with previous payment rules that pre-date the Workshop and had manual percentages set offline, we allow people who know // what they're doing to specify the "sanity_check_override" key and then use custom rules. SCHEMA_INIT_CHECK( (rule.m_eRuleType == kPaymentRule_Bundle) == IsBundle(), "Item definition '%s': payment rule %s has invalid bundle rules", GetDefinitionName(), pKVRule->GetName() ); } KeyValues *pPaymentRuleForItemdef = pKVRule->FindKey( "payment_rule_for_itemdef" ); SCHEMA_INIT_CHECK( pPaymentRuleForItemdef && ( pPaymentRuleForItemdef->GetInt() == m_nDefIndex ), "Item definition '%s': payment rule %s has invalid payment_rule_for_itemdef", GetDefinitionName(), pKVRule->GetName() ); KeyValues *pKVMultiTargets = pKVRule->FindKey( "targets" ); KeyValues *pKVSingleTarget = pKVRule->FindKey( "target" ); SCHEMA_INIT_CHECK( pKVMultiTargets == NULL || pKVSingleTarget == NULL, "Item definition '%s': payment rule %s specifies both single- and multi-targets", GetDefinitionName(), pKVRule->GetName() ); if ( pKVMultiTargets ) { FOR_EACH_SUBKEY( pKVMultiTargets, pKVTarget ) { rule.m_vecValues.AddToTail( (uint64)Q_atoi64( pKVTarget->GetName() ) ); } } if ( pKVSingleTarget ) { rule.m_vecValues.AddToTail( pKVSingleTarget->GetUint64() ); } // We expect bundles to have no associated account data at all -- their payment processing // is done by splitting the total value of the bundle between each of the items in it. All // non-bundle payment rules require at least one data entry. SCHEMA_INIT_CHECK( (rule.m_eRuleType == kPaymentRule_Bundle) == (rule.m_vecValues.Count() == 0), "Item definition '%s': payment rule %s has invalid number of target entries %i", GetDefinitionName(), pKVRule->GetName(), rule.m_vecValues.Count() ); // It doesn't make any sense to have multiple entries for a bundle. We expect their to be one // rule, and that's "process this like a bundle". if ( rule.m_eRuleType == kPaymentRule_Bundle ) { SCHEMA_INIT_CHECK( m_vecPaymentRules.Count() == 0, "Item definition '%s': only the first payment rule can be specified as 'bundle'", GetDefinitionName() ); // We only allow bundle payment rules to be applied to actual bundles with contents // specified. Without doing this, we would have nowhere to pull the metadata about // which items are contained. SCHEMA_INIT_CHECK( GetBundleInfo() && GetBundleInfo()->vecItemDefs.Count() > 0, "Item definition '%s': payment rule %s is specified as a bundle but outer item definition has no bundle contents.\n", GetDefinitionName(), pKVRule->GetName() ); // Bundles rely on sub-items for figuring out payment so the revenue share for the // bundle itself is expected to be 100%. SCHEMA_INIT_CHECK( rule.m_RevenueShare == 100.0, "Item definition '%s': payment rule %s has invalid bundle revenue share %0.2f", GetDefinitionName(), pKVRule->GetName(), rule.m_RevenueShare ); } const bool bWasNumberedCorrectly = (AddPaymentRule( rule ) == atoi( pKVRule->GetName() )); SCHEMA_INIT_CHECK( bWasNumberedCorrectly, "Item definition '%s': misnumbered payment rule %s", GetDefinitionName(), pKVRule->GetName() ); } } #endif // GC_DLL return SCHEMA_INIT_SUCCESS(); } #ifdef GC_DLL int CEconItemDefinition::AddPaymentRule( const econ_item_payment_rule_t& newRule ) { return m_vecPaymentRules.AddToTail( newRule ); } #endif // GC_DLL bool static_attrib_t::BInitFromKV_MultiLine( const char *pszContext, KeyValues *pKVAttribute, CUtlVector *pVecErrors ) { const CEconItemAttributeDefinition *pAttrDef = GetItemSchema()->GetAttributeDefinitionByName( pKVAttribute->GetName() ); SCHEMA_INIT_CHECK( NULL != pAttrDef, "Context '%s': Attribute \"%s\" in \"attributes\" did not match any attribute definitions", pszContext, pKVAttribute->GetName() ); if ( pAttrDef ) { iDefIndex = pAttrDef->GetDefinitionIndex(); const ISchemaAttributeType *pAttrType = pAttrDef->GetAttributeType(); Assert( pAttrType ); pAttrType->InitializeNewEconAttributeValue( &m_value ); const char *pszValue = pKVAttribute->GetString( "value", NULL ); const bool bSuccessfullyLoadedValue = pAttrType->BConvertStringToEconAttributeValue( pAttrDef, pszValue, &m_value, true ); SCHEMA_INIT_CHECK( bSuccessfullyLoadedValue, "Context '%s': Attribute \"%s\" could not parse value \"%s\"!", pszContext, pKVAttribute->GetName(), pszValue ? pszValue : "(null)" ); SCHEMA_INIT_CHECK( !pAttrDef->BIsSetBonusAttribute(), "Context '%s': Attribute \"%s\" is a set bonus attribute and not supported here", pszContext, pKVAttribute->GetName() ); #ifdef GC_DLL bForceGCToGenerate = pKVAttribute->GetBool( "force_gc_to_generate" ); KeyValues *pKVLogicData = pKVAttribute->FindKey( "custom_value_logic" ); if ( pKVLogicData ) { m_pKVCustomData = pKVLogicData->MakeCopy(); } SCHEMA_INIT_CHECK( m_pKVCustomData == NULL || bForceGCToGenerate, "Context '%s': Attribute \"%s\" is set to have custom logic but is not GC-generated so that logic will never get used!", pszContext, pKVAttribute->GetName() ); SCHEMA_INIT_CHECK( m_pKVCustomData != NULL || pKVAttribute->FindKey( "value" ), "Context '%s': Attribute \"%s\" has no value set", pszContext, pKVAttribute->GetName() ); SCHEMA_INIT_CHECK( m_pKVCustomData == NULL || m_pKVCustomData->FindKey( "method" ) != NULL, "Context '%s': Attribute \"%s\" custom logic data is set, but custom logic method is not set!", pszContext, pKVAttribute->GetName() ); #endif // GC_DLL } return SCHEMA_INIT_SUCCESS(); } bool static_attrib_t::BInitFromKV_SingleLine( const char *pszContext, KeyValues *pKVAttribute, CUtlVector *pVecErrors, bool bEnableTerribleBackwardsCompatibilitySchemaParsingCode /* = true */ ) { const CEconItemAttributeDefinition *pAttrDef = GetItemSchema()->GetAttributeDefinitionByName( pKVAttribute->GetName() ); SCHEMA_INIT_CHECK( NULL != pAttrDef, "Context '%s': Attribute \"%s\" in \"attributes\" did not match any attribute definitions", pszContext, pKVAttribute->GetName() ); if ( pAttrDef ) { iDefIndex = pAttrDef->GetDefinitionIndex(); const ISchemaAttributeType *pAttrType = pAttrDef->GetAttributeType(); Assert( pAttrType ); pAttrType->InitializeNewEconAttributeValue( &m_value ); const char *pszValue = pKVAttribute->GetString(); const bool bSuccessfullyLoadedValue = pAttrType->BConvertStringToEconAttributeValue( pAttrDef, pszValue, &m_value, bEnableTerribleBackwardsCompatibilitySchemaParsingCode ); SCHEMA_INIT_CHECK( bSuccessfullyLoadedValue, "Context '%s': Attribute \"%s\" could not parse value \"%s\"!", pszContext, pKVAttribute->GetName(), pszValue ? pszValue : "(null)" ); SCHEMA_INIT_CHECK( !pAttrDef->BIsSetBonusAttribute(), "Context '%s': Attribute \"%s\" is a set bonus attribute and not supported here", pszContext, pKVAttribute->GetName() ); #ifdef GC_DLL bForceGCToGenerate = false; m_pKVCustomData = NULL; #endif // GC_DLL } return SCHEMA_INIT_SUCCESS(); } bool CEconItemDefinition::BInitItemMappings( CUtlVector *pVecErrors ) { // Armory remapping if ( m_pszArmoryRemap && m_pszArmoryRemap[0] ) { CEconItemDefinition *pDef = GetItemSchema()->GetItemDefinitionByName( m_pszArmoryRemap ); if ( pDef ) { m_iArmoryRemap = pDef->GetDefinitionIndex(); } SCHEMA_INIT_CHECK( pDef != NULL, "Item %s: Armory remap definition \"%s\" was not found", m_pszItemBaseName, m_pszArmoryRemap ); } // Store remapping if ( m_pszStoreRemap && m_pszStoreRemap[0] ) { CEconItemDefinition *pDef = GetItemSchema()->GetItemDefinitionByName( m_pszStoreRemap ); if ( pDef ) { m_iStoreRemap = pDef->GetDefinitionIndex(); } SCHEMA_INIT_CHECK( pDef != NULL, "Item %s: Store remap definition \"%s\" was not found", m_pszItemBaseName, m_pszStoreRemap ); } return SCHEMA_INIT_SUCCESS(); } const char* CEconItemDefinition::GetIconURL( const char* pszKey ) const { auto idx = m_pDictIcons->Find( pszKey ); if ( idx == m_pDictIcons->InvalidIndex() ) { return NULL; } return (*m_pDictIcons)[ idx ]; } //----------------------------------------------------------------------------- // Purpose: Generate and return a random level according to whatever leveling // curve this definition uses. //----------------------------------------------------------------------------- uint32 CEconItemDefinition::RollItemLevel( void ) const { return RandomInt( GetMinLevel(), GetMaxLevel() ); } const char *CEconItemDefinition::GetFirstSaleDate() const { return GetDefinitionString( "first_sale_date", "1960/00/00" ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CEconItemDefinition::IterateAttributes( IEconItemAttributeIterator *pIterator ) const { FOR_EACH_VEC( GetStaticAttributes(), i ) { const static_attrib_t& staticAttrib = GetStaticAttributes()[i]; #ifdef GC_DLL // we skip over static attributes that the GC will turn into dynamic attributes because otherwise we'll have // the appearance of iterating over them twice; for clients these attributes won't even make it into the // list if ( staticAttrib.bForceGCToGenerate ) continue; #endif // GC_DLL const CEconItemAttributeDefinition *pAttrDef = GetItemSchema()->GetAttributeDefinition( staticAttrib.iDefIndex ); if ( !pAttrDef ) continue; const ISchemaAttributeType *pAttrType = pAttrDef->GetAttributeType(); Assert( pAttrType ); if ( !pAttrType->OnIterateAttributeValue( pIterator, pAttrDef, staticAttrib.m_value ) ) return; } } #if defined(CLIENT_DLL) || defined(GAME_DLL) //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- Activity CEconItemDefinition::GetActivityOverride( int iTeam, Activity baseAct ) const { int iAnims = GetNumAnimations( iTeam ); for ( int i = 0; i < iAnims; i++ ) { animation_on_wearable_t *pData = GetAnimationData( iTeam, i ); if ( !pData ) continue; if ( pData->iActivity == kActivityLookup_Unknown ) { pData->iActivity = ActivityList_IndexForName( pData->pszActivity ); } if ( pData->iActivity == baseAct ) { if ( pData->iReplacement == kActivityLookup_Unknown ) { pData->iReplacement = ActivityList_IndexForName( pData->pszReplacement ); } if ( pData->iReplacement > 0 ) { return (Activity) pData->iReplacement; } } } return baseAct; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- const char *CEconItemDefinition::GetActivityOverride( int iTeam, const char *pszActivity ) const { int iAnims = GetNumAnimations( iTeam ); for ( int i = 0; i < iAnims; i++ ) { animation_on_wearable_t *pData = GetAnimationData( iTeam, i ); if ( Q_stricmp( pszActivity, pData->pszActivity ) == 0 ) return pData->pszReplacement; } return NULL; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- const char *CEconItemDefinition::GetReplacementForActivityOverride( int iTeam, Activity baseAct ) const { int iAnims = GetNumAnimations( iTeam ); for ( int i = 0; i < iAnims; i++ ) { animation_on_wearable_t *pData = GetAnimationData( iTeam, i ); if ( pData->iActivity == kActivityLookup_Unknown ) { pData->iActivity = ActivityList_IndexForName( pData->pszActivity ); } if ( pData && pData->iActivity == baseAct ) return pData->pszReplacement; } return NULL; } //----------------------------------------------------------------------------- // Purpose: Returns true if the content for this item view should be streamed. If false, // it should be preloaded. //----------------------------------------------------------------------------- // DO NOT MERGE THIS CONSOLE VARIABLE TO REL WE SHOULD NOT SHIP THIS OH GOD #ifdef STAGING_ONLY ConVar item_enable_dynamic_loading( "item_enable_dynamic_loading", "1", FCVAR_REPLICATED, "Enable/disable dynamic streaming of econ content." ); #endif // STAGING_ONLY bool CEconItemDefinition::IsContentStreamable() const { if ( !BLoadOnDemand() ) return false; #ifdef STAGING_ONLY return item_enable_dynamic_loading.GetBool(); #else return true; #endif } #endif // defined(CLIENT_DLL) || defined(GAME_DLL) RETURN_ATTRIBUTE_STRING_F( CEconItemDefinition::GetIconDisplayModel, "icon display model", m_pszWorldDisplayModel ); //----------------------------------------------------------------------------- // Purpose: Constructor //----------------------------------------------------------------------------- CTimedItemRewardDefinition::CTimedItemRewardDefinition( void ) : m_unMinFreq( 0 ), m_unMaxFreq( UINT_MAX ), m_flChance( 0.0f ), m_pLootList( NULL ), m_iRequiredItemDef(INVALID_ITEM_DEF_INDEX) { } //----------------------------------------------------------------------------- // Purpose: Copy constructor //----------------------------------------------------------------------------- CTimedItemRewardDefinition::CTimedItemRewardDefinition( const CTimedItemRewardDefinition &that ) { (*this) = that; } //----------------------------------------------------------------------------- // Purpose: Operator= //----------------------------------------------------------------------------- CTimedItemRewardDefinition &CTimedItemRewardDefinition::operator=( const CTimedItemRewardDefinition &rhs ) { m_unMinFreq = rhs.m_unMinFreq; m_unMaxFreq = rhs.m_unMaxFreq; m_flChance = rhs.m_flChance; m_criteria = rhs.m_criteria; m_pLootList = rhs.m_pLootList; m_iRequiredItemDef = rhs.m_iRequiredItemDef; return *this; } //----------------------------------------------------------------------------- // Purpose: Initialize the attribute definition // Input: pKVTimedReward - The KeyValues representation of the timed reward // schema - The overall item schema // pVecErrors - An optional vector that will contain error messages if // the init fails. // Output: True if initialization succeeded, false otherwise //----------------------------------------------------------------------------- bool CTimedItemRewardDefinition::BInitFromKV( KeyValues *pKVTimedReward, CUtlVector *pVecErrors /* = NULL */ ) { // Parse the basic values m_flChance = pKVTimedReward->GetFloat( "pctChance" ); m_unMinFreq = pKVTimedReward->GetInt( "value_min", 0 ); m_unMaxFreq = pKVTimedReward->GetInt( "value_max", UINT_MAX ); m_iRequiredItemDef = INVALID_ITEM_DEF_INDEX; const char *pszRequiredItem = pKVTimedReward->GetString( "required_item", NULL ); if ( pszRequiredItem ) { // Find the ItemDef CEconItemDefinition *pDef = GetItemSchema()->GetItemDefinitionByName( pszRequiredItem ); SCHEMA_INIT_CHECK( pDef != NULL, "Invalid Item Def Required for a for TimedReward Definition"); m_iRequiredItemDef = pDef->GetDefinitionIndex(); } // Check required fields SCHEMA_INIT_CHECK( NULL != pKVTimedReward->FindKey( "value_min" ), "Time reward %s: Missing required field \"value_min\"", pKVTimedReward->GetName() ); SCHEMA_INIT_CHECK( NULL != pKVTimedReward->FindKey( "value_max" ), "Time reward %s: Missing required field \"value_max\"", pKVTimedReward->GetName() ); SCHEMA_INIT_CHECK( NULL != pKVTimedReward->FindKey( "pctChance" ), "Time reward %s: Missing required field \"pctChance\"", pKVTimedReward->GetName() ); SCHEMA_INIT_CHECK( NULL == pKVTimedReward->FindKey( "criteria" ), "Time reward %s: \"criteria\" is no longer supported. Restructure as \"loot_list\"?", pKVTimedReward->GetName() ); SCHEMA_INIT_CHECK( NULL != pKVTimedReward->FindKey( "loot_list" ), "Time reward %s: Missing required field \"loot_list\" ", pKVTimedReward->GetName() ); // Parse the loot list const char *pszLootList = pKVTimedReward->GetString("loot_list", NULL); if ( pszLootList && pszLootList[0] ) { m_pLootList = GetItemSchema()->GetLootListByName( pszLootList ); // Make sure the item index is correct because we use this index as a reference SCHEMA_INIT_CHECK( NULL != m_pLootList, "Time Reward %s: loot_list (%s) does not exist", pKVTimedReward->GetName(), pszLootList ); } // Other integrity checks SCHEMA_INIT_CHECK( m_flChance >= 0.0f, "Time Reward %s: pctChance (%f) must be greater or equal to 0.0", pKVTimedReward->GetName(), m_flChance ); SCHEMA_INIT_CHECK( m_flChance <= 1.0f, "Time Reward %s: pctChance (%f) must be less than or equal to 1.0", pKVTimedReward->GetName(), m_flChance ); SCHEMA_INIT_CHECK( pKVTimedReward->GetInt( "value_min" ) > 0, "Time Reward %s: value_min (%d) must be greater than 0", pKVTimedReward->GetName(), m_unMinFreq ); SCHEMA_INIT_CHECK( pKVTimedReward->GetInt( "value_max" ) > 0, "Time Reward %s: value_max (%d) must be greater than 0", pKVTimedReward->GetName(), m_unMaxFreq ); SCHEMA_INIT_CHECK( (m_unMaxFreq >= m_unMinFreq), "Time Reward %s: value_max (%d) must be greater than or equal to value_min (%d)", pKVTimedReward->GetName(), m_unMaxFreq, m_unMinFreq ); return SCHEMA_INIT_SUCCESS(); } //----------------------------------------------------------------------------- // Purpose: Adds a foreign item definition to local definition mapping for a // foreign app //----------------------------------------------------------------------------- void CForeignAppImports::AddMapping( uint16 unForeignDefIndex, const CEconItemDefinition *pDefn ) { m_mapDefinitions.InsertOrReplace( unForeignDefIndex, pDefn ); } //----------------------------------------------------------------------------- // Purpose: Adds a foreign item definition to local definition mapping for a // foreign app //----------------------------------------------------------------------------- const CEconItemDefinition *CForeignAppImports::FindMapping( uint16 unForeignDefIndex ) const { int i = m_mapDefinitions.Find( unForeignDefIndex ); if( m_mapDefinitions.IsValidIndex( i ) ) return m_mapDefinitions[i]; else return NULL; } //----------------------------------------------------------------------------- // Purpose: Constructor //----------------------------------------------------------------------------- CEconItemSchema::CEconItemSchema( ) : m_unResetCount( 0 ) , m_pKVRawDefinition( NULL ) , m_mapItemSeries( DefLessFunc(int) ) , m_mapRarities( DefLessFunc(int) ) , m_mapQualities( DefLessFunc(int) ) , m_mapAttributes( DefLessFunc(int) ) , m_mapRecipes( DefLessFunc(int) ) , m_mapQuestObjectives( DefLessFunc(int) ) , m_mapItemsSorted( DefLessFunc(int) ) , m_mapToolsItems( DefLessFunc(int) ) , m_mapBaseItems( DefLessFunc(int) ) , m_unVersion( 0 ) #if defined(CLIENT_DLL) || defined(GAME_DLL) , m_pDefaultItemDefinition( NULL ) #endif , m_mapItemSets( CaselessStringLessThan ) , m_mapItemCollections( CaselessStringLessThan ) , m_mapItemPaintKits( CaselessStringLessThan ) , m_mapOperationDefinitions( CaselessStringLessThan ) , m_mapLootLists( CaselessStringLessThan ) , m_mapRevolvingLootLists( DefLessFunc(int) ) , m_mapDefinitionPrefabs( CaselessStringLessThan ) , m_mapAchievementRewardsByData( DefLessFunc( uint32 ) ) , m_mapAttributeControlledParticleSystems( DefLessFunc(int) ) , m_mapDefaultBodygroupState( CaselessStringLessThan ) #ifdef GC_DLL , m_mapForeignImports( DefLessFunc(AppId_t) ) #elif defined(CLIENT_DLL) || defined(GAME_DLL) , m_pDelayedSchemaData( NULL ) #endif , m_mapKillEaterScoreTypes( DefLessFunc( unsigned int ) ) , m_mapCommunityMarketDefinitionIndexRemap( DefLessFunc( item_definition_index_t ) ) #ifdef CLIENT_DLL , m_mapSteamPackageLocalizationTokens( DefLessFunc( uint32 ) ) #endif { Reset(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- IEconTool *CEconItemSchema::CreateEconToolImpl( const char *pszToolType, const char *pszUseString, const char *pszUsageRestriction, item_capabilities_t unCapabilities, KeyValues *pUsageKV ) { if ( pszToolType ) { if ( !V_stricmp( pszToolType, "duel_minigame" ) ) { // Error checking -- make sure we aren't setting properties in the schema that we don't support. if ( pszUsageRestriction ) return NULL; if ( unCapabilities != ITEM_CAP_NONE ) return NULL; if ( pUsageKV ) return NULL; return new CEconTool_DuelingMinigame( pszToolType, pszUseString ); } if ( !V_stricmp( pszToolType, "noise_maker" ) ) { // Error checking -- make sure we aren't setting properties in the schema that we don't support. if ( pszUsageRestriction ) return NULL; if ( unCapabilities != ITEM_CAP_NONE ) return NULL; if ( pUsageKV ) return NULL; return new CEconTool_Noisemaker( pszToolType, pszUseString ); } if ( !V_stricmp( pszToolType, "wrapped_gift" ) ) { // Error checking -- make sure we aren't setting properties in the schema that we don't support. if ( pszUsageRestriction ) return NULL; return new CEconTool_WrappedGift( pszToolType, pszUseString, unCapabilities, pUsageKV ); } if ( !V_stricmp( pszToolType, "backpack_expander" ) ) { // Error checking -- make sure we aren't setting properties in the schema that we don't support. if ( pszUsageRestriction ) return NULL; if ( unCapabilities != ITEM_CAP_NONE ) return NULL; return new CEconTool_BackpackExpander( pszToolType, pszUseString, pUsageKV ); } if ( !V_stricmp( pszToolType, "account_upgrade_to_premium" ) ) { // Error checking -- make sure we aren't setting properties in the schema that we don't support. if ( pszUsageRestriction ) return NULL; if ( unCapabilities != ITEM_CAP_NONE ) return NULL; if ( pUsageKV ) return NULL; return new CEconTool_AccountUpgradeToPremium( pszToolType, pszUseString ); } if ( !V_stricmp( pszToolType, "claimcode" ) ) { // Error checking -- make sure we aren't setting properties in the schema that we don't support. if ( pszUsageRestriction ) return NULL; if ( unCapabilities != ITEM_CAP_NONE ) return NULL; return new CEconTool_ClaimCode( pszToolType, pszUseString, pUsageKV ); } if ( !V_stricmp( pszToolType, "gift" ) ) { // Error checking -- make sure we aren't setting properties in the schema that we don't support. if ( pszUsageRestriction ) return NULL; if ( unCapabilities != ITEM_CAP_NONE ) return NULL; return new CEconTool_Gift( pszToolType, pszUseString, pUsageKV ); } if ( !V_stricmp( pszToolType, "paint_can" ) ) { // Error checking -- make sure we aren't setting properties in the schema that we don't support. if ( pszUsageRestriction ) return NULL; if ( pUsageKV ) return NULL; return new CEconTool_PaintCan( pszToolType, unCapabilities ); } if ( !V_stricmp( pszToolType, "name" ) ) { // Error checking -- make sure we aren't setting properties in the schema that we don't support. if ( pszUsageRestriction ) return NULL; if ( pUsageKV ) return NULL; return new CEconTool_NameTag( pszToolType, unCapabilities ); } if ( !V_stricmp( pszToolType, "desc" ) ) { // Error checking -- make sure we aren't setting properties in the schema that we don't support. if ( pszUsageRestriction ) return NULL; if ( pUsageKV ) return NULL; return new CEconTool_DescTag( pszToolType, unCapabilities ); } if ( !V_stricmp( pszToolType, "decoder_ring" ) ) { // Error checking -- make sure we aren't setting properties in the schema that we don't support. if ( pUsageKV ) return NULL; return new CEconTool_CrateKey( pszToolType, pszUsageRestriction, unCapabilities ); } if ( !V_stricmp( pszToolType, "customize_texture_item" ) ) { // Error checking -- make sure we aren't setting properties in the schema that we don't support. if ( pszUsageRestriction ) return NULL; if ( pUsageKV ) return NULL; return new CEconTool_CustomizeTexture( pszToolType, unCapabilities ); } if ( !V_stricmp( pszToolType, "gift_wrap" ) ) { // Error checking -- make sure we aren't setting properties in the schema that we don't support. if ( pszUsageRestriction ) return NULL; return new CEconTool_GiftWrap( pszToolType, pszUseString, unCapabilities, pUsageKV ); } if ( !V_stricmp( pszToolType, "wedding_ring" ) ) { // Error checking -- make sure we aren't setting properties in the schema that we don't support. if ( pszUsageRestriction ) return NULL; if ( pUsageKV ) return NULL; return new CEconTool_WeddingRing( pszToolType, pszUseString, unCapabilities ); } if ( !V_stricmp( pszToolType, "strange_part" ) ) { // Error checking -- make sure we aren't setting properties in the schema that we don't support. if ( pszUsageRestriction ) return NULL; return new CEconTool_StrangePart( pszToolType, pszUseString, unCapabilities, pUsageKV ); } if ( !V_stricmp( pszToolType, "strange_part_restriction" ) ) { // Error checking -- make sure we aren't setting properties in the schema that we don't support. if ( pszUsageRestriction ) return NULL; if ( !pUsageKV ) return NULL; // required return new CEconTool_StrangePartRestriction( pszToolType, pszUseString, unCapabilities, pUsageKV ); } if ( !V_stricmp( pszToolType, "apply_custom_attrib" ) ) { // Error checking -- make sure we aren't setting properties in the schema that we don't support. if ( pszUsageRestriction ) return NULL; return new CEconTool_UpgradeCard( pszToolType, pszUseString, unCapabilities, pUsageKV ); } if ( !V_stricmp( pszToolType, "strangifier" ) ) { // Error checking -- make sure we aren't setting properties in the schema that we don't support. if ( pszUsageRestriction ) return NULL; return new CEconTool_Strangifier( pszToolType, pszUseString, unCapabilities, pUsageKV ); } if ( !V_stricmp( pszToolType, "killstreakifier" ) ) { // Error checking -- make sure we aren't setting properties in the schema that we don't support. if ( pszUsageRestriction ) return NULL; return new CEconTool_KillStreakifier( pszToolType, pszUseString, unCapabilities, pUsageKV ); } if( !V_stricmp( pszToolType, "dynamic_recipe" ) ) { // Error checking -- make sure we aren't setting properties in the schema that we don't support. if ( pszUsageRestriction ) return NULL; return new CEconTool_ItemDynamicRecipe( pszToolType, pszUseString, unCapabilities, pUsageKV ); } if ( !V_stricmp( pszToolType, "item_eater_recharger" ) ) { // Error checking -- make sure we aren't setting properties in the schema that we don't support. if ( pszUsageRestriction ) return NULL; return new CEconTool_ItemEaterRecharger( pszToolType, pszUseString, unCapabilities, pUsageKV ); } if ( !V_stricmp( pszToolType, "class_transmogrifier" ) ) { // Error checking -- make sure we aren't setting properties in the schema that we don't support. if ( pszUsageRestriction ) return NULL; return new CEconTool_ClassTransmogrifier( pszToolType, pszUseString, unCapabilities, pUsageKV ); } if ( !V_stricmp( pszToolType, "duck_token" ) ) { // Error checking -- make sure we aren't setting properties in the schema that we don't support. if ( pszUsageRestriction ) return NULL; if ( pUsageKV ) return NULL; return new CEconTool_DuckToken( pszToolType, unCapabilities ); } if ( !V_stricmp( pszToolType, "grant_operation_pass" ) ) { // Error checking -- make sure we aren't setting properties in the schema that we don't support. if ( pszUsageRestriction ) return NULL; return new CEconTool_GrantOperationPass( pszToolType, pszUseString, unCapabilities, pUsageKV ); } if ( !V_stricmp( pszToolType, "strange_count_transfer" ) ) { // Error checking -- make sure we aren't setting properties in the schema that we don't support. if ( pszUsageRestriction ) return NULL; if ( pUsageKV ) return NULL; return new CEconTool_StrangeCountTransfer( pszToolType, unCapabilities ); } if ( !V_stricmp( pszToolType, "paintkit_weapon_festivizer" ) ) { return new CEconTool_Festivizer( pszToolType, pszUseString, unCapabilities, pUsageKV ); } if ( !V_stricmp( pszToolType, "unusualifier" ) ) { return new CEconTool_Unusualifier( pszToolType, pszUseString, unCapabilities, pUsageKV ); } } // Default behavior. return new CEconTool_Default( pszToolType, pszUseString, pszUsageRestriction, unCapabilities ); } #ifdef GC_DLL random_attrib_t *CEconItemSchema::CreateRandomAttribute( const char *pszContext, KeyValues *pRandomAttributesKV, CUtlVector *pVecErrors /*= NULL*/ ) { // We've found the random attribute block. Parse it. if ( pRandomAttributesKV->FindKey( "chance" ) == NULL ) { CUtlString msg; \ msg.Format( CFmtStr( "Missing required field \"chance\" in the \"random_attributes\" block." ) ); if ( pVecErrors ) { pVecErrors->AddToTail( msg ); } else { AssertMsg( pRandomAttributesKV->FindKey( "chance" ) != NULL, msg.String() ); } return NULL; } random_attrib_t randomAttrib; randomAttrib.m_flChanceOfRandomAttribute = pRandomAttributesKV->GetFloat( "chance" ); randomAttrib.m_bPickAllAttributes = ( pRandomAttributesKV->GetFloat( "pick_all_attributes" ) != 0 ); randomAttrib.m_flTotalAttributeWeight = 0; FOR_EACH_TRUE_SUBKEY( pRandomAttributesKV, pKVAttribute ) { const char *pszName = pKVAttribute->GetName(); if ( !Q_strcmp( pszName, "chance" ) ) continue; // Quick block list of attrs that have equal weight if ( !Q_strcmp( pszName, "is_even_chance_attr" ) ) { FOR_EACH_VALUE( pKVAttribute, pKVListItem ) { const CEconItemAttributeDefinition *pDef = GetAttributeDefinitionByName( pKVListItem->GetName() ); if ( pDef == NULL ) { CUtlString msg; \ msg.Format( CFmtStr( "Attribute definition \"%s\" was not found", pszName ) ); if ( pVecErrors ) { pVecErrors->AddToTail( msg ); } else { AssertMsg( pDef != NULL, msg.String() ); } return NULL; } lootlist_attrib_t lootListAttrib; if ( !lootListAttrib.m_staticAttrib.BInitFromKV_SingleLine( __FUNCTION__, pKVListItem, pVecErrors, false ) ) { if ( pVecErrors ) { pVecErrors->AddToTail( __FUNCTION__ ": error initializing line-item attribute from lootlist definition (possible attr template).\n" ); } return NULL; } // Weight is set to 1 for even chance attr lootListAttrib.m_flWeight = 1.0f; randomAttrib.m_flTotalAttributeWeight += 1.0f; randomAttrib.m_RandomAttributes.AddToTail( lootListAttrib ); } } else { const CEconItemAttributeDefinition *pDef = GetAttributeDefinitionByName( pszName ); if ( pDef == NULL ) { CUtlString msg; \ msg.Format( CFmtStr( "Attribute definition \"%s\" was not found", pszName ) ); if ( pVecErrors ) { pVecErrors->AddToTail( msg ); } else { AssertMsg( pDef != NULL, msg.String() ); } return NULL; } lootlist_attrib_t lootListAttrib; if ( !lootListAttrib.BInitFromKV( pszContext, pKVAttribute, *this, pVecErrors ) ) { return NULL; } randomAttrib.m_flTotalAttributeWeight += lootListAttrib.m_flWeight; randomAttrib.m_RandomAttributes.AddToTail( lootListAttrib ); } } random_attrib_t *pRandomAttr = new random_attrib_t; *pRandomAttr = randomAttrib; return pRandomAttr; } #endif // GC_DLL //----------------------------------------------------------------------------- // Purpose: Resets the schema to before BInit was called //----------------------------------------------------------------------------- void CEconItemSchema::Reset( void ) { ++m_unResetCount; m_unFirstValidClass = 0; m_unLastValidClass = 0; m_unAccoutClassIndex = 0; m_unFirstValidClassItemSlot = 0; m_unLastValidClassItemSlot = 0; m_unFirstValidAccountItemSlot = 0; m_unLastValidAccountItemSlot = 0; m_unNumItemPresets = 0; m_unMinLevel = 0; m_unMaxLevel = 0; m_unVersion = 0; m_unSumQualityWeights = 0; FOR_EACH_VEC( m_vecAttributeTypes, i ) { delete m_vecAttributeTypes[i].m_pAttrType; } m_vecAttributeTypes.Purge(); m_mapItems.PurgeAndDeleteElements(); m_mapItems.Purge(); m_mapRarities.Purge(); m_mapQualities.Purge(); m_mapItemsSorted.Purge(); m_mapToolsItems.Purge(); m_mapBaseItems.Purge(); m_mapRecipes.PurgeAndDeleteElements(); m_vecTimedRewards.Purge(); m_mapItemSets.PurgeAndDeleteElements(); m_mapLootLists.PurgeAndDeleteElements(); #ifdef GC_DLL m_dictRandomAttributeTemplates.PurgeAndDeleteElements(); #endif // GC_DLL m_mapAttributeControlledParticleSystems.Purge(); m_vecAttributeControlledParticleSystemsCosmetics.Purge(); m_vecAttributeControlledParticleSystemsWeapons.Purge(); m_vecAttributeControlledParticleSystemsTaunts.Purge(); m_mapAttributes.Purge(); if ( m_pKVRawDefinition ) { m_pKVRawDefinition->deleteThis(); m_pKVRawDefinition = NULL; } #if defined(CLIENT_DLL) || defined(GAME_DLL) delete m_pDefaultItemDefinition; m_pDefaultItemDefinition = NULL; #endif FOR_EACH_MAP_FAST( m_mapRecipes, i ) { delete m_mapRecipes[i]; } FOR_EACH_MAP_FAST( m_mapDefinitionPrefabs, i ) { m_mapDefinitionPrefabs[i]->deleteThis(); } m_mapDefinitionPrefabs.Purge(); m_vecEquipRegionsList.Purge(); m_vecItemLevelingData.PurgeAndDeleteElements(); m_dictStringTable.PurgeAndDeleteElements(); m_mapCommunityMarketDefinitionIndexRemap.Purge(); } //----------------------------------------------------------------------------- // Purpose: Operator= //----------------------------------------------------------------------------- CEconItemSchema &CEconItemSchema::operator=( CEconItemSchema &rhs ) { Reset(); BInitSchema( rhs.m_pKVRawDefinition ); return *this; } bool g_bLastSignatureCheck; bool CheckValveSignature( const void *data, uint32 nDataSize, const void *signature, uint32 nSignatureSize ) { // Must match the PUBLIC KEY in src\devtools\valve_source_officialcontent.privatekey.vdf static const unsigned char valvePublicKey[] = "\x30\x81\x9D\x30\x0D\x06\x09\x2A\x86\x48\x86\xF7\x0D\x01\x01\x01" "\x05\x00\x03\x81\x8B\x00\x30\x81\x87\x02\x81\x81\x00\xB1\xC0\xF1" "\x1C\xB2\x98\x2F\x29\x25\x95\x07\xA7\x74\xD4\x83\x43\x77\xC5\xB7" "\xA3\x8D\x9A\x4B\x38\x92\xB5\x98\x00\x9F\x16\xAA\x10\x95\x65\xCB" "\x09\xAD\x25\xDE\x0D\x3D\x1A\x08\x9C\x3C\xB6\x8E\x49\x19\x21\xCC" "\x14\x2F\x38\x33\x83\x20\x1D\xE9\x82\x62\xA7\x6E\xD8\xA6\xCC\x78" "\xBC\x51\x68\x5A\x0A\x64\xA6\x17\x2C\x67\x12\x7A\xF2\x3E\x78\x73" "\x1F\x4A\x82\xC2\x01\xD6\x4C\x9A\xB8\x09\x37\x32\x21\x84\xB6\x42" "\x72\x7F\xE1\x42\xD1\x5C\xC0\x45\xF3\x58\x3E\x19\xE3\xE3\xE1\xA9" "\xC5\x0C\x0F\xC8\x41\x13\x57\x3A\x52\x0A\x8F\x73\x23\x02\x01\x11"; // Put into a global var. Could help with VAC detection, if this // code gets detoured g_bLastSignatureCheck = CCrypto::RSAVerifySignatureSHA256( (const uint8 *)data, nDataSize, (const uint8 *)signature, nSignatureSize, valvePublicKey, sizeof(valvePublicKey) ); return g_bLastSignatureCheck; } //----------------------------------------------------------------------------- // Initializes the schema, given KV filename //----------------------------------------------------------------------------- bool CEconItemSchema::BInit( const char *fileName, const char *pathID, CUtlVector *pVecErrors /* = NULL */) { Reset(); // Read the raw data CUtlBuffer bufRawData; bool bReadFileOK = g_pFullFileSystem->ReadFile( fileName, pathID, bufRawData ); SCHEMA_INIT_CHECK( bReadFileOK, "Cannot load file '%s'", fileName ); // Do we need to check the signature? #if defined(TF_DLL) || defined(TF_CLIENT_DLL) { // Load up the signature CUtlString sSignatureFilename( fileName ); sSignatureFilename.Append( ".sig" ); CUtlBuffer bufSignatureBinary; bool bReadSignatureOK = g_pFullFileSystem->ReadFile( sSignatureFilename.String(), pathID, bufSignatureBinary ); SCHEMA_INIT_CHECK( bReadSignatureOK, "Cannot load file '%s'", sSignatureFilename.String() ); // Check it with the Valve public key bool bSignatureValid = CheckValveSignature( bufRawData.Base(), bufRawData.TellPut(), bufSignatureBinary.Base(), bufSignatureBinary.TellPut() ); // If they have a signature for a zero-byte file, that's OK, too. // That's the secret code that is checked into P4 internally that // let's us run with any items_game file if ( !bSignatureValid ) { bSignatureValid = CheckValveSignature( "", 0, bufSignatureBinary.Base(), bufSignatureBinary.TellPut() ); } SCHEMA_INIT_CHECK( bSignatureValid, "'%s' is corrupt. Please verify your local game files. (https://support.steampowered.com/kb_article.php?ref=2037-QEUH-3335)", fileName ); } #endif // Compute version hash CSHA1 sha1; sha1.Update( (unsigned char *)bufRawData.Base(), bufRawData.Size() ); sha1.Final(); sha1.GetHash( m_schemaSHA.m_shaDigest ); // Wrap it with a text buffer reader CUtlBuffer bufText( bufRawData.Base(), bufRawData.TellPut(), CUtlBuffer::READ_ONLY | CUtlBuffer::TEXT_BUFFER ); // Use the standard init path return BInitTextBuffer( bufText, pVecErrors ); } //----------------------------------------------------------------------------- // Initializes the schema, given KV in binary form //----------------------------------------------------------------------------- bool CEconItemSchema::BInitBinaryBuffer( CUtlBuffer &buffer, CUtlVector *pVecErrors /* = NULL */ ) { Reset(); m_pKVRawDefinition = new KeyValues( "CEconItemSchema" ); if ( m_pKVRawDefinition->ReadAsBinary( buffer ) ) { return BInitSchema( m_pKVRawDefinition, pVecErrors ) && BPostSchemaInit( pVecErrors ); } if ( pVecErrors ) { pVecErrors->AddToTail( "Error parsing keyvalues" ); } return false; } unsigned char g_sha1ItemSchemaText[ k_cubHash ]; //----------------------------------------------------------------------------- // Initializes the schema, given KV in text form //----------------------------------------------------------------------------- bool CEconItemSchema::BInitTextBuffer( CUtlBuffer &buffer, CUtlVector *pVecErrors /* = NULL */ ) { // Save off the hash into a global variable, so VAC can check it // later GenerateHash( g_sha1ItemSchemaText, buffer.Base(), buffer.TellPut() ); Reset(); m_pKVRawDefinition = new KeyValues( "CEconItemSchema" ); if ( m_pKVRawDefinition->LoadFromBuffer( NULL, buffer ) ) { return BInitSchema( m_pKVRawDefinition, pVecErrors ) && BPostSchemaInit( pVecErrors ); } if ( pVecErrors ) { pVecErrors->AddToTail( "Error parsing keyvalues" ); } return false; } bool CEconItemSchema::DumpItems ( const char *fileName, const char *pathID ) { // create a write file FileHandle_t f = g_pFullFileSystem->Open(fileName, "wb", pathID); if ( f == FILESYSTEM_INVALID_HANDLE ) { DevMsg(1, "CEconItemSchema::DumpItems: couldn't open file \"%s\" in path \"%s\".\n", fileName?fileName:"NULL", pathID?pathID:"NULL" ); return false; } CUtlSortVector< KeyValues*, CUtlSortVectorKeyValuesByName > vecSortedItems; FOR_EACH_MAP_FAST( m_mapItems, i ) { vecSortedItems.InsertNoSort( m_mapItems[ i ]->GetRawDefinition() ); } vecSortedItems.RedoSort(); CUtlBuffer buf; FOR_EACH_VEC( vecSortedItems, i ) { vecSortedItems[i]->RecursiveSaveToFile( buf, 0, true ); } int iBufSize = buf.GetBytesRemaining(); bool bSuccess = false; if ( g_pFullFileSystem->Write(buf.PeekGet(), iBufSize, f) == iBufSize ) bSuccess = true; g_pFullFileSystem->Close(f); return bSuccess; } //----------------------------------------------------------------------------- // Called once the price sheet's been loaded //----------------------------------------------------------------------------- #ifdef GC_DLL GCConVar econ_orphaned_sold_items_owned_by_account_id( "econ_orphaned_sold_items_owned_by_account_id", "121416792" ); bool CEconItemSchema::DoPostPriceSheetLoadInit( CEconStorePriceSheet *pPriceSheet ) { FOR_EACH_MAP_FAST( m_mapItems, iItem ) { CEconItemDefinition *pItemDef = m_mapItems[ iItem ]; // Is this item being sold? const econ_store_entry_t *pStoreEntry = pPriceSheet->GetEntry( pItemDef->GetDefinitionIndex() ); if ( pStoreEntry ) { // Cache off whether this item is a pack item pItemDef->SetIsPackItem( pStoreEntry->m_bIsPackItem ); // If an item is being sold and it has no payment rules set up, we can optionally force-create // a dummy payment rule that will redirect that item revenue to a "hey, these are orphan items!" // account. if ( pItemDef->GetPaymentRules().Count() == 0 && econ_orphaned_sold_items_owned_by_account_id.GetInt() ) { econ_item_payment_rule_t rule; rule.m_RevenueShare = 1.0; rule.m_eRuleType = kPaymentRule_PartnerSteamID; rule.m_vecValues.AddToTail( econ_orphaned_sold_items_owned_by_account_id.GetInt() ); DbgVerify( pItemDef->AddPaymentRule( rule ) == 0 ); } } // Go through the cache of all bundles... FOR_EACH_VEC( m_vecBundles, iBundle ) { const CEconItemDefinition *pBundleItemDef = m_vecBundles[ iBundle ]; const bundleinfo_t *pBundle = pBundleItemDef->GetBundleInfo(); const bool bBundleItemIsForSale = pPriceSheet->BItemExistsInPriceSheet( pBundleItemDef->GetDefinitionIndex() ) != NULL; // Only add bundles that are actually for sale bool bAddToContainingBundleItemDefs = false; if ( pItemDef->IsPackBundle() ) { // If the current item is a pack bundle, look for the first pack item in the current bundle (pBundle). We can safely assume that all pack items will be // in pBundle if the first pack item is, since the GC won't startup otherwise. Don't add self as a containing bundle. bAddToContainingBundleItemDefs = pItemDef->GetDefinitionIndex() != pBundleItemDef->GetDefinitionIndex() && pBundle->vecItemDefs.HasElement( pItemDef->GetBundleInfo()->vecItemDefs[0] ); } else { bAddToContainingBundleItemDefs = pBundle->vecItemDefs.HasElement( pItemDef ); } // Does the current bundle contain the given item? if ( bBundleItemIsForSale && bAddToContainingBundleItemDefs ) { pItemDef->m_vecContainingBundleItemDefs.AddToTail( pBundleItemDef ); } } } return true; } #endif #if defined(CLIENT_DLL) || defined(GAME_DLL) //----------------------------------------------------------------------------- // Set up the buffer to use to reinitialize our schema next time we can do so safely. //----------------------------------------------------------------------------- bool CEconItemSchema::MaybeInitFromBuffer( IDelayedSchemaData *pDelayedSchemaData ) { bool bDidInit = false; // Use whatever our most current data block is. if ( m_pDelayedSchemaData ) { delete m_pDelayedSchemaData; } m_pDelayedSchemaData = pDelayedSchemaData; #ifdef CLIENT_DLL // If we aren't in a game we can parse immediately now. if ( !engine->IsInGame() ) { BInitFromDelayedBuffer(); bDidInit = true; } #endif // CLIENT_DLL return bDidInit; } //----------------------------------------------------------------------------- // We're in a safe place to change the contents of the schema, so do so and clean // up whatever memory we were using. //----------------------------------------------------------------------------- bool CEconItemSchema::BInitFromDelayedBuffer() { if ( !m_pDelayedSchemaData ) return true; bool bSuccess = m_pDelayedSchemaData->InitializeSchema( this ); delete m_pDelayedSchemaData; m_pDelayedSchemaData = NULL; return bSuccess; } #endif // !GC_DLL static void CalculateKeyValuesCRCRecursive( KeyValues *pKV, CRC32_t *crc, bool bIgnoreName = false ) { // Hash in the key name in LOWERCASE. Keyvalues files are not deterministic due // to the case insensitivity of the keys and the dependence on the existing // state of the name table upon entry. if ( !bIgnoreName ) { const char *s = pKV->GetName(); for (;;) { unsigned char x = tolower(*s); CRC32_ProcessBuffer( crc, &x, 1 ); // !SPEED! This is slow, but it works. if (*s == '\0') break; ++s; } } // Now hash in value, depending on type // !FIXME! This is not byte-order independent! switch ( pKV->GetDataType() ) { case KeyValues::TYPE_NONE: { FOR_EACH_SUBKEY( pKV, pChild ) { CalculateKeyValuesCRCRecursive( pChild, crc ); } break; } case KeyValues::TYPE_STRING: { const char *val = pKV->GetString(); CRC32_ProcessBuffer( crc, val, strlen(val)+1 ); break; } case KeyValues::TYPE_INT: { int val = pKV->GetInt(); CRC32_ProcessBuffer( crc, &val, sizeof(val) ); break; } case KeyValues::TYPE_UINT64: { uint64 val = pKV->GetUint64(); CRC32_ProcessBuffer( crc, &val, sizeof(val) ); break; } case KeyValues::TYPE_FLOAT: { float val = pKV->GetFloat(); CRC32_ProcessBuffer( crc, &val, sizeof(val) ); break; } case KeyValues::TYPE_COLOR: { int val = pKV->GetColor().GetRawColor(); CRC32_ProcessBuffer( crc, &val, sizeof(val) ); break; } default: case KeyValues::TYPE_PTR: case KeyValues::TYPE_WSTRING: { Assert( !"Unsupport data type!" ); break; } } } uint32 CEconItemSchema::CalculateKeyValuesVersion( KeyValues *pKV ) { CRC32_t crc; CRC32_Init( &crc ); // Calc CRC recursively. Ignore the very top-most // key name, which isn't set consistently CalculateKeyValuesCRCRecursive( pKV, &crc, true ); CRC32_Final( &crc ); return crc; } EEquipType_t CEconItemSchema::GetEquipTypeFromClassIndex( int iClass ) const { if ( iClass == GetAccountIndex() ) return EEquipType_t::EQUIP_TYPE_ACCOUNT; if ( iClass >= GetFirstValidClass() && iClass <= GetLastValidClass() ) return EEquipType_t::EQUIP_TYPE_CLASS; return EEquipType_t::EQUIP_TYPE_INVALID; } //----------------------------------------------------------------------------- // Purpose: Initializes the schema // Input: pKVRawDefinition - The raw KeyValues representation of the schema // pVecErrors - An optional vector that will contain error messages if // the init fails. // Output: True if initialization succeeded, false otherwise //----------------------------------------------------------------------------- bool CEconItemSchema::BInitSchema( KeyValues *pKVRawDefinition, CUtlVector *pVecErrors /* = NULL */ ) { m_unMinLevel = pKVRawDefinition->GetInt( "item_level_min", 0 ); m_unMaxLevel = pKVRawDefinition->GetInt( "item_level_max", 0 ); #if !defined( GC_DLL ) m_unVersion = CalculateKeyValuesVersion( pKVRawDefinition ); #endif #ifdef GC_DLL // Validate the integrity of the base data. SCHEMA_INIT_CHECK( 0 <= m_unMinLevel, "Minimum Item Level must be at least 0" ); SCHEMA_INIT_CHECK( m_unMinLevel <= m_unMaxLevel, "Minimum Item Level must be less than or equal to Maximum Item Level" ); #endif // GC_DLL // Parse the prefabs block first so the prefabs will be populated in case anything else wants // to use them later. KeyValues *pKVPrefabs = pKVRawDefinition->FindKey( "prefabs" ); if ( NULL != pKVPrefabs ) { SCHEMA_INIT_SUBSTEP( BInitDefinitionPrefabs( pKVPrefabs, pVecErrors ) ); } // Initialize the game info block KeyValues *pKVGameInfo = pKVRawDefinition->FindKey( "game_info" ); SCHEMA_INIT_CHECK( NULL != pKVGameInfo, "Required key \"game_info\" missing.\n" ); if ( NULL != pKVGameInfo ) { SCHEMA_INIT_SUBSTEP( BInitGameInfo( pKVGameInfo, pVecErrors ) ); } // Initialize our attribute types. We don't actually pull this data from the schema right now but it // still makes sense to initialize it at this point. SCHEMA_INIT_SUBSTEP( BInitAttributeTypes( pVecErrors ) ); // Initialize the item series block KeyValues *pKVItemSeries = pKVRawDefinition->FindKey( "item_series_types" ); SCHEMA_INIT_CHECK( NULL != pKVItemSeries, "Required key \"item_series_types\" missing.\n" ); if ( NULL != pKVItemSeries ) { SCHEMA_INIT_SUBSTEP( BInitItemSeries( pKVItemSeries, pVecErrors ) ); } // Initialize the rarity block KeyValues *pKVRarities = pKVRawDefinition->FindKey( "rarities" ); KeyValues *pKVRarityWeights = pKVRawDefinition->FindKey( "rarities_lootlist_weights" ); SCHEMA_INIT_CHECK( NULL != pKVRarities, "Required key \"rarities\" missing.\n" ); if ( NULL != pKVRarities ) { SCHEMA_INIT_SUBSTEP( BInitRarities( pKVRarities, pKVRarityWeights, pVecErrors ) ); } // Initialize the qualities block KeyValues *pKVQualities = pKVRawDefinition->FindKey( "qualities" ); SCHEMA_INIT_CHECK( NULL != pKVQualities, "Required key \"qualities\" missing.\n" ); if ( NULL != pKVQualities ) { SCHEMA_INIT_SUBSTEP( BInitQualities( pKVQualities, pVecErrors ) ); } // Initialize the colors block KeyValues *pKVColors = pKVRawDefinition->FindKey( "colors" ); SCHEMA_INIT_CHECK( NULL != pKVColors, "Required key \"colors\" missing.\n" ); if ( NULL != pKVColors ) { SCHEMA_INIT_SUBSTEP( BInitColors( pKVColors, pVecErrors ) ); } // Initialize the attributes block KeyValues *pKVAttributes = pKVRawDefinition->FindKey( "attributes" ); SCHEMA_INIT_CHECK( NULL != pKVAttributes, "Required key \"attributes\" missing.\n" ); if ( NULL != pKVAttributes ) { SCHEMA_INIT_SUBSTEP( BInitAttributes( pKVAttributes, pVecErrors ) ); } #ifdef GC // Initialize the motd block KeyValues *pKVMOTD = pKVRawDefinition->FindKey( "motd_entries" ); SCHEMA_INIT_CHECK( NULL != pKVMOTD, "Required key \"motd_entries\" missing.\n" ); if ( NULL != pKVMOTD ) { SCHEMA_INIT_SUBSTEP( GGCGameBase()->GetMOTDManager().BInitMOTDEntries( pKVMOTD, pVecErrors ) ); } #endif // Initialize the "equip_regions_list" block -- this is an optional block KeyValues *pKVEquipRegions = pKVRawDefinition->FindKey( "equip_regions_list" ); if ( NULL != pKVEquipRegions ) { SCHEMA_INIT_SUBSTEP( BInitEquipRegions( pKVEquipRegions, pVecErrors ) ); } // Initialize the "equip_conflicts" block -- this is an optional block, though it doesn't // make any sense and will probably fail internally if there is no corresponding "equip_regions" // block as well KeyValues *pKVEquipRegionConflicts = pKVRawDefinition->FindKey( "equip_conflicts" ); if ( NULL != pKVEquipRegionConflicts ) { SCHEMA_INIT_SUBSTEP( BInitEquipRegionConflicts( pKVEquipRegionConflicts, pVecErrors ) ); } // TF2 Paint Kits // No included in schema file (Too Big). Loaded Seperately // Load the KV and add it to the pKVRawDefinition KeyValues *pPaintkitKV = new KeyValues( "item_paintkit_definitions" ); SCHEMA_INIT_CHECK( pPaintkitKV->LoadFromFile( g_pFullFileSystem, "scripts/items/paintkits_master.txt", "GAME" ), "Unable to Load paintkits_master.txt KV File!" ); pKVRawDefinition->AddSubKey( pPaintkitKV ); // Init Item Paint Kits // Must be BEFORE Item defs KeyValues *pKVItemPaintKits = pKVRawDefinition->FindKey( "item_paintkit_definitions" ); if ( NULL != pKVItemPaintKits ) { SCHEMA_INIT_SUBSTEP( BInitItemPaintKitDefinitions( pKVItemPaintKits, pVecErrors ) ); } #ifdef GC_DLL // Parse the loot lists block (on the GC) // Must be BEFORE Item defs KeyValues *pKVRandomAttributeTemplates = pKVRawDefinition->FindKey( "random_attribute_templates" ); SCHEMA_INIT_SUBSTEP( BInitRandomAttributeTemplates( pKVRandomAttributeTemplates, pVecErrors ) ); #endif // GC_DLL // Initialize the items block KeyValues *pKVItems = pKVRawDefinition->FindKey( "items" ); SCHEMA_INIT_CHECK( NULL != pKVItems, "Required key \"items\" missing.\n" ); if ( NULL != pKVItems ) { SCHEMA_INIT_SUBSTEP( BInitItems( pKVItems, pVecErrors ) ); } // Verify base item names are proper in item schema SCHEMA_INIT_SUBSTEP( BVerifyBaseItemNames( pVecErrors ) ); // Parse the item_sets block. KeyValues *pKVItemSets = pKVRawDefinition->FindKey( "item_sets" ); SCHEMA_INIT_SUBSTEP( BInitItemSets( pKVItemSets, pVecErrors ) ); // Particles KeyValues *pKVParticleSystems = pKVRawDefinition->FindKey( "attribute_controlled_attached_particles" ); SCHEMA_INIT_SUBSTEP( BInitAttributeControlledParticleSystems( pKVParticleSystems, pVecErrors ) ); // Parse any recipes block KeyValues *pKVRecipes = pKVRawDefinition->FindKey( "recipes" ); SCHEMA_INIT_SUBSTEP( BInitRecipes( pKVRecipes, pVecErrors ) ); // Reset our loot lists. m_mapLootLists.RemoveAll(); // Init Item Collections - Must be before lootlists since collections are lootlists themselves and are referenced by lootlists KeyValues *pKVItemCollections = pKVRawDefinition->FindKey( "item_collections" ); if ( NULL != pKVItemCollections ) { SCHEMA_INIT_SUBSTEP( BInitItemCollections( pKVItemCollections, pVecErrors ) ); } #ifdef GC_DLL // Parse the loot lists block (on the GC) KeyValues *pKVLootLists = pKVRawDefinition->FindKey( "loot_lists" ); SCHEMA_INIT_SUBSTEP( BInitLootLists( pKVLootLists, pVecErrors ) ); // Initialize the periodic score accumulation block (this needs to take place after items) KeyValues *pKVPeriodicScoring = pKVRawDefinition->FindKey( "periodic_score_accumulation" ); if ( NULL != pKVPeriodicScoring ) { SCHEMA_INIT_SUBSTEP( BInitPeriodicScoring( pKVPeriodicScoring, pVecErrors ) ); } #endif // GC_DLL // Parse the client loot lists block (everywhere) KeyValues *pKVClientLootLists = pKVRawDefinition->FindKey( "client_loot_lists" ); SCHEMA_INIT_SUBSTEP( BInitLootLists( pKVClientLootLists, pVecErrors ) ); // Parse the revolving loot lists block KeyValues *pKVRevolvingLootLists = pKVRawDefinition->FindKey( "revolving_loot_lists" ); SCHEMA_INIT_SUBSTEP( BInitRevolvingLootLists( pKVRevolvingLootLists, pVecErrors ) ); // Init Items that may reference Collections SCHEMA_INIT_SUBSTEP( BInitCollectionReferences( pVecErrors ) ); // Validate Operation Pass KeyValues *pKVOperationDefinitions = pKVRawDefinition->FindKey( "operations" ); if ( NULL != pKVOperationDefinitions ) { SCHEMA_INIT_SUBSTEP( BInitOperationDefinitions( pKVGameInfo, pKVOperationDefinitions, pVecErrors ) ); } #if defined( GC_DLL ) // Parse any time-based rewards KeyValues *pKVTimeRewards = pKVRawDefinition->FindKey( "time_rewards" ); SCHEMA_INIT_SUBSTEP( BInitTimedRewards( pKVTimeRewards, pVecErrors ) ); KeyValues *pKVExperiments = pKVRawDefinition->FindKey( "experiments" ); SCHEMA_INIT_SUBSTEP( BInitExperiements( pKVExperiments, pVecErrors ) ); SCHEMA_INIT_SUBSTEP( BInitForeignImports( pVecErrors ) ); #elif defined( CLIENT_DLL ) || defined( GAME_DLL ) KeyValues *pKVArmoryData = pKVRawDefinition->FindKey( "armory_data" ); SCHEMA_INIT_SUBSTEP( BInitArmoryData( pKVArmoryData, pVecErrors ) ); #endif // GC_DLL // Parse any achievement rewards KeyValues *pKVAchievementRewards = pKVRawDefinition->FindKey( "achievement_rewards" ); SCHEMA_INIT_SUBSTEP( BInitAchievementRewards( pKVAchievementRewards, pVecErrors ) ); #ifdef TF_CLIENT_DLL // Compute the number of concrete items, for each item, and cache for quick access SCHEMA_INIT_SUBSTEP( BInitConcreteItemCounts( pVecErrors ) ); // We don't have access to Steam's full library of app data on the client so initialize whichever packages // we want to reference. KeyValues *pKVSteamPackages = pKVRawDefinition->FindKey( "steam_packages" ); SCHEMA_INIT_SUBSTEP( BInitSteamPackageLocalizationToken( pKVSteamPackages, pVecErrors ) ); #endif // TF_CLIENT_DLL // Parse the item levels block KeyValues *pKVItemLevels = pKVRawDefinition->FindKey( "item_levels" ); SCHEMA_INIT_SUBSTEP( BInitItemLevels( pKVItemLevels, pVecErrors ) ); // Parse the kill eater score types KeyValues *pKVKillEaterScoreTypes = pKVRawDefinition->FindKey( "kill_eater_score_types" ); SCHEMA_INIT_SUBSTEP( BInitKillEaterScoreTypes( pKVKillEaterScoreTypes, pVecErrors ) ); // Initialize the string tables, if present KeyValues *pKVStringTables = pKVRawDefinition->FindKey( "string_lookups" ); SCHEMA_INIT_SUBSTEP( BInitStringTables( pKVStringTables, pVecErrors ) ); // Initialize the community Market remaps, if present KeyValues *pKVCommunityMarketRemaps = pKVRawDefinition->FindKey( "community_market_item_remaps" ); SCHEMA_INIT_SUBSTEP( BInitCommunityMarketRemaps( pKVCommunityMarketRemaps, pVecErrors ) ); return SCHEMA_INIT_SUCCESS(); } //----------------------------------------------------------------------------- // Purpose: Initializes the "game_info" section of the schema //----------------------------------------------------------------------------- bool CEconItemSchema::BInitGameInfo( KeyValues *pKVGameInfo, CUtlVector *pVecErrors ) { m_unFirstValidClass = pKVGameInfo->GetInt( "first_valid_class", 0 ); m_unLastValidClass = pKVGameInfo->GetInt( "last_valid_class", 0 ); SCHEMA_INIT_CHECK( 0 < m_unFirstValidClass, "First valid class must be greater than 0." ); SCHEMA_INIT_CHECK( m_unFirstValidClass <= m_unLastValidClass, "First valid class must be less than or equal to last valid class." ); m_unAccoutClassIndex = pKVGameInfo->GetInt( "account_class_index", 0 ); SCHEMA_INIT_CHECK( m_unAccoutClassIndex > m_unLastValidClass, "Account class index must be greater than 'last_valid_class'" ); m_unFirstValidClassItemSlot = pKVGameInfo->GetInt( "first_valid_item_slot", INVALID_EQUIPPED_SLOT ); m_unLastValidClassItemSlot = pKVGameInfo->GetInt( "last_valid_item_slot", INVALID_EQUIPPED_SLOT ); SCHEMA_INIT_CHECK( INVALID_EQUIPPED_SLOT != m_unFirstValidClassItemSlot, "first_valid_item_slot not set!" ); SCHEMA_INIT_CHECK( INVALID_EQUIPPED_SLOT != m_unFirstValidClassItemSlot, "last_valid_item_slot not set!" ); SCHEMA_INIT_CHECK( m_unFirstValidClassItemSlot <= m_unLastValidClassItemSlot, "First valid item slot must be less than or equal to last valid item slot." ); m_unFirstValidAccountItemSlot = pKVGameInfo->GetInt( "account_first_valid_item_slot", INVALID_EQUIPPED_SLOT ); m_unLastValidAccountItemSlot = pKVGameInfo->GetInt( "account_last_valid_item_slot", INVALID_EQUIPPED_SLOT ); SCHEMA_INIT_CHECK( INVALID_EQUIPPED_SLOT != m_unFirstValidAccountItemSlot, "account_first_valid_item_slot not set!" ); SCHEMA_INIT_CHECK( INVALID_EQUIPPED_SLOT != m_unLastValidAccountItemSlot, "account_last_valid_item_slot not set!" ); SCHEMA_INIT_CHECK( m_unFirstValidAccountItemSlot <= m_unLastValidAccountItemSlot, "First vlid account item slot must be less than or equal to the last valid account item slot." ); m_unNumItemPresets = pKVGameInfo->GetInt( "num_item_presets", -1 ); SCHEMA_INIT_CHECK( (uint32)-1 != m_unNumItemPresets, "num_item_presets not set!" ); return SCHEMA_INIT_SUCCESS(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CEconItemSchema::BInitAttributeTypes( CUtlVector *pVecErrors ) { FOR_EACH_VEC( m_vecAttributeTypes, i ) { delete m_vecAttributeTypes[i].m_pAttrType; } m_vecAttributeTypes.Purge(); m_vecAttributeTypes.AddToTail( attr_type_t( NULL, new CSchemaAttributeType_Default ) ); m_vecAttributeTypes.AddToTail( attr_type_t( "float", new CSchemaAttributeType_Float ) ); m_vecAttributeTypes.AddToTail( attr_type_t( "uint64", new CSchemaAttributeType_UInt64 ) ); m_vecAttributeTypes.AddToTail( attr_type_t( "string", new CSchemaAttributeType_String ) ); m_vecAttributeTypes.AddToTail( attr_type_t( "dynamic_recipe_component_defined_item", new CSchemaAttributeType_DynamicRecipeComponentDefinedItem ) ); m_vecAttributeTypes.AddToTail( attr_type_t( "item_slot_criteria", new CSchemaAttributeType_ItemSlotCriteria ) ); m_vecAttributeTypes.AddToTail( attr_type_t( "item_placement", new CSchemaAttributeType_WorldItemPlacement ) ); // Make sure that all attribute types specified have the item ID in the 0th column. We use this // when loading items to map between item IDs and the attributes they own. FOR_EACH_VEC( m_vecAttributeTypes, i ) { #ifdef GC_DLL const CColumnSet& cs = m_vecAttributeTypes[i].m_pAttrType->GetFullColumnSet(); SCHEMA_INIT_CHECK( cs.GetColumnCount() >= 2, "BInitAttributeTypes(): '%s' has invalid column count.\n", cs.GetRecordInfo()->GetName() ); const CColumnInfo& Column0 = cs.GetColumnInfo( 0 ); SCHEMA_INIT_CHECK( Column0.GetType() == k_EGCSQLType_int64, "BInitAttributeTypes(): '%s' column 0 has invalid data type %u.\n", cs.GetRecordInfo()->GetName(), Column0.GetType() ); SCHEMA_INIT_CHECK( Column0.GetName() && !V_stricmp( Column0.GetName(), "ItemID" ), "BInitAttributeTypes(): '%s' has invalid name '%s'.\n", cs.GetRecordInfo()->GetName(), Column0.GetName() ? Column0.GetName() : "[null]" ); SCHEMA_INIT_CHECK( Column0.BIsPrimaryKey(), "BInitAttributeTypes(): '%s' has an item ID column that isn't in the PK.\n", cs.GetRecordInfo()->GetName() ); const CColumnInfo& Column1 = cs.GetColumnInfo( 1 ); SCHEMA_INIT_CHECK( Column1.GetType() == k_EGCSQLType_int16, "BInitAttributeTypes(): '%s' column 1 has invalid data type %u.\n", cs.GetRecordInfo()->GetName(), Column0.GetType() ); SCHEMA_INIT_CHECK( Column1.GetName() && !V_stricmp( Column1.GetName(), "AttrDefIndex" ), "BInitAttributeTypes(): '%s' has invalid name '%s'.\n", cs.GetRecordInfo()->GetName(), Column1.GetName() ? Column1.GetName() : "[null]" ); // Make sure two different attribute types don't point to the same DB table. There's nothing // technically that would prevent this from working, but right now the way we load from the // DB would make this super-inefficient so we'd want to fix that code if we are rolling content // that would hit this error. for ( int j = i + 1; j < m_vecAttributeTypes.Count(); j++ ) { SCHEMA_INIT_CHECK( cs.GetRecordInfo() != m_vecAttributeTypes[j].m_pAttrType->GetFullColumnSet().GetRecordInfo(), "BInitAttributeTypes(): multiple attribute types reference the same table '%s'.\n", cs.GetRecordInfo()->GetName() ); } #endif // GC_DLL } return SCHEMA_INIT_SUCCESS(); } #ifdef GC_DLL //----------------------------------------------------------------------------- // Purpose: Initializes the "periodic_score_accumulation" section of the schema //----------------------------------------------------------------------------- struct periodic_score_event_lookup_entry_t { const char *m_pszName; eEconPeriodicScoreEvents m_eValue; bool m_bGCUpdateOnly; }; static const periodic_score_event_lookup_entry_t sPeriodicScoreEvents[] = { { "gifts_distributed", kPeriodicScoreEvent_GiftsDistributed, true }, { "duels_won", kPeriodicScoreEvent_DuelsWon, true }, { "map_stamps_purchased", kPeriodicScoreEvent_MapStampsPurchased, true }, }; struct periodic_score_duration_lookup_entry_t { const char *m_pszName; uint32 m_unValue; }; static const periodic_score_duration_lookup_entry_t sPeriodicScoreDurations[] = { { "disabled", 0 }, { "hourly", 60 * 60 }, { "daily", 60 * 60 * 24 }, { "weekly", 60 * 60 * 24 * 7 }, { "monthly", 60 * 60 * 24 * 7 * 4 }, // four weeks, not necessarily a month boundary }; template < typename search_entry_type, int search_entry_array_size > static bool LookupValueFromString( const search_entry_type(&searchArray)[search_entry_array_size], const char *pszSearch, search_entry_type *out_pResult ) { Assert( out_pResult ); for ( int i = 0; i < search_entry_array_size; i++ ) { if ( !V_stricmp( pszSearch, searchArray[i].m_pszName ) ) { *out_pResult = searchArray[i]; return true; } } return false; } bool CEconItemSchema::BInitPeriodicScoring( KeyValues *pKVPeriodicScoring, CUtlVector *pVecErrors ) { FOR_EACH_TRUE_SUBKEY( pKVPeriodicScoring, pKVScoreType ) { int index = Q_atoi( pKVScoreType->GetName() ); SCHEMA_INIT_CHECK( index == m_vecPeriodicScoreTypes.Count(), "Invalid or out-of-order periodic score type '%s'", pKVScoreType->GetName() ); periodic_score_t PeriodicScore; // Reward item definition. const char *pszRewardItemDefName = pKVScoreType->GetString( "reward_item_def_name", NULL ); PeriodicScore.m_pRewardItemDefinition = pszRewardItemDefName ? GetItemDefinitionByName( pszRewardItemDefName ) : NULL; SCHEMA_INIT_CHECK( PeriodicScore.m_pRewardItemDefinition, "Periodic score type '%s' missing reward item definition name", pKVScoreType->GetName() ); // Event type via string lookup. const char *pszEventName = pKVScoreType->GetString( "event", "" ); { periodic_score_event_lookup_entry_t EventEntry; SCHEMA_INIT_CHECK( LookupValueFromString( sPeriodicScoreEvents, pszEventName, &EventEntry ), "Periodic score type '%s' could not find event name '%s'", pKVScoreType->GetName(), pszEventName ); PeriodicScore.m_eEventType = EventEntry.m_eValue; // Note: other parts of the code assume that the event type is associated with the GC-only updatability flag.) PeriodicScore.m_bGCUpdateOnly = EventEntry.m_bGCUpdateOnly; } // Time period via string lookup. { const char *pszTimePeriodName = pKVScoreType->GetString( "time_period", "" ); periodic_score_duration_lookup_entry_t DurationEntry; SCHEMA_INIT_CHECK( LookupValueFromString( sPeriodicScoreDurations, pszTimePeriodName, &DurationEntry ), "Periodic score type '%s' could not find time period name '%s'", pKVScoreType->GetName(), pszEventName ); PeriodicScore.m_unTimePeriodLengthInSeconds = DurationEntry.m_unValue; } // Alternate time period specified for use in internal Steam? if ( GGCHost()->GetUniverse() != k_EUniversePublic ) { const char *pszInternalTimePeriodName = pKVScoreType->GetString( "time_period_internal", NULL ); if ( pszInternalTimePeriodName ) { periodic_score_duration_lookup_entry_t InternalDurationEntry; SCHEMA_INIT_CHECK( LookupValueFromString( sPeriodicScoreDurations, pszInternalTimePeriodName, &InternalDurationEntry ), "Periodic score type '%s' could not find internal time period name '%s'", pKVScoreType->GetName(), pszEventName ); PeriodicScore.m_unTimePeriodLengthInSeconds = InternalDurationEntry.m_unValue; } } m_vecPeriodicScoreTypes.AddToTail( PeriodicScore ); } return SCHEMA_INIT_SUCCESS(); } #endif // GC_DLL //----------------------------------------------------------------------------- // Purpose: Initializes the "prefabs" section of the schema //----------------------------------------------------------------------------- bool CEconItemSchema::BInitDefinitionPrefabs( KeyValues *pKVPrefabs, CUtlVector *pVecErrors ) { FOR_EACH_TRUE_SUBKEY( pKVPrefabs, pKVPrefab ) { const char *pszPrefabName = pKVPrefab->GetName(); int nMapIndex = m_mapDefinitionPrefabs.Find( pszPrefabName ); // Make sure the item index is correct because we use this index as a reference SCHEMA_INIT_CHECK( !m_mapDefinitionPrefabs.IsValidIndex( nMapIndex ), "Duplicate prefab name (%s)", pszPrefabName ); m_mapDefinitionPrefabs.Insert( pszPrefabName, pKVPrefab->MakeCopy() ); } return SCHEMA_INIT_SUCCESS(); } //----------------------------------------------------------------------------- // Purpose: Initializes the Item Series section of the schema //----------------------------------------------------------------------------- bool CEconItemSchema::BInitItemSeries( KeyValues *pKVSeries, CUtlVector *pVecErrors ) { // initialize the item definitions if ( NULL != pKVSeries) { FOR_EACH_TRUE_SUBKEY( pKVSeries, pKVItem ) { int nSeriesIndex = pKVItem->GetInt( "value" ); int nMapIndex = m_mapItemSeries.Find( nSeriesIndex ); // Make sure the item index is correct because we use this index as a reference SCHEMA_INIT_CHECK( !m_mapItemSeries.IsValidIndex( nMapIndex ), "Duplicate item series value (%d)", nSeriesIndex ); nMapIndex = m_mapItemSeries.Insert( nMapIndex ); SCHEMA_INIT_SUBSTEP( m_mapItemSeries[nMapIndex].BInitFromKV( pKVItem, pVecErrors ) ); } } return SCHEMA_INIT_SUCCESS(); } //----------------------------------------------------------------------------- // Purpose: Initializes the rarity section of the schema //----------------------------------------------------------------------------- bool CEconItemSchema::BInitRarities( KeyValues *pKVRarities, KeyValues *pKVRarityWeights, CUtlVector *pVecErrors ) { // initialize the item definitions if ( NULL != pKVRarities ) { FOR_EACH_TRUE_SUBKEY( pKVRarities, pKVRarity ) { int nRarityIndex = pKVRarity->GetInt( "value" ); int nMapIndex = m_mapRarities.Find( nRarityIndex ); // Make sure the item index is correct because we use this index as a reference SCHEMA_INIT_CHECK( !m_mapRarities.IsValidIndex( nMapIndex ), "Duplicate rarity value (%d)", nRarityIndex ); nMapIndex = m_mapRarities.Insert( nRarityIndex ); SCHEMA_INIT_SUBSTEP( m_mapRarities[nMapIndex].BInitFromKV( pKVRarity, pKVRarityWeights, *this, pVecErrors ) ); } } return SCHEMA_INIT_SUCCESS(); } //----------------------------------------------------------------------------- // Purpose: Initializes the qualities section of the schema // Input: pKVQualities - The qualities section of the KeyValues // representation of the schema // pVecErrors - An optional vector that will contain error messages if // the init fails. // Output: True if initialization succeeded, false otherwise //----------------------------------------------------------------------------- bool CEconItemSchema::BInitQualities( KeyValues *pKVQualities, CUtlVector *pVecErrors ) { // initialize the item definitions if ( NULL != pKVQualities ) { FOR_EACH_TRUE_SUBKEY( pKVQualities, pKVQuality ) { int nQualityIndex = pKVQuality->GetInt( "value" ); int nMapIndex = m_mapQualities.Find( nQualityIndex ); // Make sure the item index is correct because we use this index as a reference SCHEMA_INIT_CHECK( !m_mapQualities.IsValidIndex( nMapIndex ), "Duplicate quality value (%d)", nQualityIndex ); nMapIndex = m_mapQualities.Insert( nQualityIndex ); SCHEMA_INIT_SUBSTEP( m_mapQualities[nMapIndex].BInitFromKV( pKVQuality, pVecErrors ) ); } } // Check the integrity of the quality definitions // Check for duplicate quality names CUtlRBTree rbQualityNames( CaselessStringLessThan ); rbQualityNames.EnsureCapacity( m_mapQualities.Count() ); FOR_EACH_MAP_FAST( m_mapQualities, i ) { int iIndex = rbQualityNames.Find( m_mapQualities[i].GetName() ); SCHEMA_INIT_CHECK( !rbQualityNames.IsValidIndex( iIndex ), "Quality definition %d: Duplicate quality name %s", m_mapQualities[i].GetDBValue(), m_mapQualities[i].GetName() ); if( !rbQualityNames.IsValidIndex( iIndex ) ) rbQualityNames.Insert( m_mapQualities[i].GetName() ); } return SCHEMA_INIT_SUCCESS(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CEconItemSchema::BInitColors( KeyValues *pKVColors, CUtlVector *pVecErrors ) { // initialize the color definitions if ( NULL != pKVColors ) { FOR_EACH_TRUE_SUBKEY( pKVColors, pKVColor ) { CEconColorDefinition *pNewColorDef = new CEconColorDefinition; SCHEMA_INIT_SUBSTEP( pNewColorDef->BInitFromKV( pKVColor, pVecErrors ) ); m_vecColorDefs.AddToTail( pNewColorDef ); } } return SCHEMA_INIT_SUCCESS(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- int CEconItemSchema::GetEquipRegionIndexByName( const char *pRegionName ) const { FOR_EACH_VEC( m_vecEquipRegionsList, i ) { const char *szEntryRegionName = m_vecEquipRegionsList[i].m_sName.Get(); if ( !V_stricmp( szEntryRegionName, pRegionName ) ) return i; } return -1; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- equip_region_mask_t CEconItemSchema::GetEquipRegionBitMaskByName( const char *pRegionName ) const { int iRegionIndex = GetEquipRegionIndexByName( pRegionName ); if ( !m_vecEquipRegionsList.IsValidIndex( iRegionIndex ) ) return 0; equip_region_mask_t unRegionMask = 1 << m_vecEquipRegionsList[iRegionIndex].m_unBitIndex; Assert( unRegionMask > 0 ); return unRegionMask; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CEconItemSchema::SetEquipRegionConflict( int iRegion, unsigned int unBit ) { Assert( m_vecEquipRegionsList.IsValidIndex( iRegion ) ); equip_region_mask_t unRegionMask = 1 << unBit; Assert( unRegionMask > 0 ); m_vecEquipRegionsList[iRegion].m_unMask |= unRegionMask; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- equip_region_mask_t CEconItemSchema::GetEquipRegionMaskByName( const char *pRegionName ) const { int iRegionIdx = GetEquipRegionIndexByName( pRegionName ); if ( iRegionIdx < 0 ) return 0; return m_vecEquipRegionsList[iRegionIdx].m_unMask; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CEconItemSchema::AssignDefaultBodygroupState( const char *pszBodygroupName, int iValue ) { // Flip the value passed in -- if we specify in the schema that a region should be off, we assume that it's // on by default. // actually the schemas are all authored assuming that the default is 0, so let's use that int iDefaultValue = 0; //iValue == 0 ? 1 : 0; // Make sure that we're constantly reinitializing our default value to the same default value. This is sort // of dumb but it works for everything we've got now. In the event that conflicts start cropping up it would // be easy enough to make a new schema section. int iIndex = m_mapDefaultBodygroupState.Find( pszBodygroupName ); if ( (m_mapDefaultBodygroupState.IsValidIndex( iIndex ) && m_mapDefaultBodygroupState[iIndex] != iDefaultValue) || (iValue < 0 || iValue > 1) ) { EmitWarning( SPEW_GC, 4, "Unable to get accurate read on whether bodygroup '%s' is enabled or disabled by default. (The schema is fine, but the code is confused and could stand to be made smarter.)\n", pszBodygroupName ); } if ( !m_mapDefaultBodygroupState.IsValidIndex( iIndex ) ) { m_mapDefaultBodygroupState.Insert( pszBodygroupName, iDefaultValue ); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CEconItemSchema::BInitEquipRegions( KeyValues *pKVEquipRegions, CUtlVector *pVecErrors ) { CUtlVector vecNames; FOR_EACH_SUBKEY( pKVEquipRegions, pKVRegion ) { const char *pRegionKeyName = pKVRegion->GetName(); vecNames.Purge(); // The "shared" name is special for equip regions -- it means that all of the sub-regions specified // will use the same bit to store equipped-or-not data, but that one bit can be accessed by a whole // bunch of different names. This is useful in TF where different classes have different regions, but // those regions cannot possibly conflict with each other. For example, "scout_backpack" cannot possibly // overlap with "pyro_shoulder" because they can't even be equipped on the same character. if ( pRegionKeyName && !Q_stricmp( pRegionKeyName, "shared" ) ) { FOR_EACH_SUBKEY( pKVRegion, pKVSharedRegionName ) { vecNames.AddToTail( pKVSharedRegionName->GetName() ); } } // We have a standard name -- this one entry is its own equip region. else { vecNames.AddToTail( pRegionKeyName ); } // What bit will this equip region use to mask against conflicts? If we don't have any equip regions // at all, we'll use the base bit, otherwise we just grab one higher than whatever we used last. unsigned int unNewBitIndex = m_vecEquipRegionsList.Count() <= 0 ? 0 : m_vecEquipRegionsList.Tail().m_unBitIndex + 1; FOR_EACH_VEC( vecNames, i ) { const char *pRegionName = vecNames[i]; // Make sure this name is unique. if ( GetEquipRegionIndexByName( pRegionName ) >= 0 ) { pVecErrors->AddToTail( CFmtStr( "Duplicate equip region named \"%s\".", pRegionName ).Access() ); continue; } // Make a new region. EquipRegion newEquipRegion; newEquipRegion.m_sName = pRegionName; newEquipRegion.m_unMask = 0; // we'll update this mask later newEquipRegion.m_unBitIndex = unNewBitIndex; int iIdx = m_vecEquipRegionsList.AddToTail( newEquipRegion ); // Tag this region to conflict with itself so that if nothing else two items in the same // region can't equip over each other. SetEquipRegionConflict( iIdx, unNewBitIndex ); } } return SCHEMA_INIT_SUCCESS(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CEconItemSchema::BInitEquipRegionConflicts( KeyValues *pKVEquipRegionConflicts, CUtlVector *pVecErrors ) { FOR_EACH_TRUE_SUBKEY( pKVEquipRegionConflicts, pKVConflict ) { // What region is the base of this conflict? const char *pRegionName = pKVConflict->GetName(); int iRegionIdx = GetEquipRegionIndexByName( pRegionName ); if ( iRegionIdx < 0 ) { pVecErrors->AddToTail( CFmtStr( "Unable to find base equip region named \"%s\" for conflicts.", pRegionName ).Access() ); continue; } FOR_EACH_SUBKEY( pKVConflict, pKVConflictOther ) { const char *pOtherRegionName = pKVConflictOther->GetName(); int iOtherRegionIdx = GetEquipRegionIndexByName( pOtherRegionName ); if ( iOtherRegionIdx < 0 ) { pVecErrors->AddToTail( CFmtStr( "Unable to find other equip region named \"%s\" for conflicts.", pOtherRegionName ).Access() ); continue; } SetEquipRegionConflict( iRegionIdx, m_vecEquipRegionsList[iOtherRegionIdx].m_unBitIndex ); SetEquipRegionConflict( iOtherRegionIdx, m_vecEquipRegionsList[iRegionIdx].m_unBitIndex ); } } return SCHEMA_INIT_SUCCESS(); } //----------------------------------------------------------------------------- // Purpose: Initializes the attributes section of the schema // Input: pKVAttributes - The attributes section of the KeyValues // representation of the schema // pVecErrors - An optional vector that will contain error messages if // the init fails. // Output: True if initialization succeeded, false otherwise //----------------------------------------------------------------------------- bool CEconItemSchema::BInitAttributes( KeyValues *pKVAttributes, CUtlVector *pVecErrors ) { // Initialize the attribute definitions FOR_EACH_TRUE_SUBKEY( pKVAttributes, pKVAttribute ) { int nAttrIndex = Q_atoi( pKVAttribute->GetName() ); int nMapIndex = m_mapAttributes.Find( nAttrIndex ); // Make sure the index is positive SCHEMA_INIT_CHECK( nAttrIndex >= 0, "Attribute definition index %d must be greater than or equal to zero", nAttrIndex ); // Make sure the attribute index is not repeated SCHEMA_INIT_CHECK( !m_mapAttributes.IsValidIndex( nMapIndex ), "Duplicate attribute definition index (%d)", nAttrIndex ); nMapIndex = m_mapAttributes.Insert( nAttrIndex ); SCHEMA_INIT_SUBSTEP( m_mapAttributes[nMapIndex].BInitFromKV( pKVAttribute, pVecErrors ) ); } // Check the integrity of the attribute definitions // Check for duplicate attribute definition names CUtlRBTree rbAttributeNames( CaselessStringLessThan ); rbAttributeNames.EnsureCapacity( m_mapAttributes.Count() ); FOR_EACH_MAP_FAST( m_mapAttributes, i ) { int iIndex = rbAttributeNames.Find( m_mapAttributes[i].GetDefinitionName() ); SCHEMA_INIT_CHECK( !rbAttributeNames.IsValidIndex( iIndex ), "Attribute definition %d: Duplicate name \"%s\"", m_mapAttributes.Key( i ), m_mapAttributes[i].GetDefinitionName() ); if( !rbAttributeNames.IsValidIndex( iIndex ) ) rbAttributeNames.Insert( m_mapAttributes[i].GetDefinitionName() ); } return SCHEMA_INIT_SUCCESS(); } //----------------------------------------------------------------------------- // Purpose: Initializes the items section of the schema // Input: pKVItems - The items section of the KeyValues // representation of the schema // pVecErrors - An optional vector that will contain error messages if // the init fails. // Output: True if initialization succeeded, false otherwise //----------------------------------------------------------------------------- bool CEconItemSchema::BInitItems( KeyValues *pKVItems, CUtlVector *pVecErrors ) { m_mapItems.PurgeAndDeleteElements(); m_mapItemsSorted.Purge(); m_mapToolsItems.Purge(); m_mapBaseItems.Purge(); m_vecBundles.Purge(); m_mapQuestObjectives.PurgeAndDeleteElements(); m_vecItemCollectionCrates.Purge(); #if defined(CLIENT_DLL) || defined(GAME_DLL) if ( m_pDefaultItemDefinition ) { delete m_pDefaultItemDefinition; m_pDefaultItemDefinition = NULL; } #endif // initialize the item definitions if ( NULL != pKVItems ) { FOR_EACH_TRUE_SUBKEY( pKVItems, pKVItem ) { if ( Q_stricmp( pKVItem->GetName(), "default" ) == 0 ) { #if defined(CLIENT_DLL) || defined(GAME_DLL) SCHEMA_INIT_CHECK( m_pDefaultItemDefinition == NULL, "Duplicate 'default' item definition." ); m_pDefaultItemDefinition = CreateEconItemDefinition(); SCHEMA_INIT_SUBSTEP( m_pDefaultItemDefinition->BInitFromKV( pKVItem, pVecErrors ) ); #endif } else { int nItemIndex = Q_atoi( pKVItem->GetName() ); int nMapIndex = m_mapItems.Find( nItemIndex ); // Make sure the item index is correct because we use this index as a reference SCHEMA_INIT_CHECK( !m_mapItems.IsValidIndex( nMapIndex ), "Duplicate item definition (%d)", nItemIndex ); // Check to make sure the index is positive SCHEMA_INIT_CHECK( nItemIndex >= 0, "Item definition index %d must be greater than or equal to zero", nItemIndex ); CEconItemDefinition *pItemDef = CreateEconItemDefinition(); nMapIndex = m_mapItems.Insert( nItemIndex, pItemDef ); m_mapItemsSorted.Insert( nItemIndex, pItemDef ); SCHEMA_INIT_SUBSTEP( m_mapItems[nMapIndex]->BInitFromKV( pKVItem, pVecErrors ) ); // Cache off Tools references if ( pItemDef->IsTool() ) { m_mapToolsItems.Insert( nItemIndex, pItemDef ); } if ( pItemDef->IsBaseItem() ) { m_mapBaseItems.Insert( nItemIndex, pItemDef ); } // Cache off bundles for the link phase below. if ( pItemDef->IsBundle() ) { // Cache off the item def for the bundle, since we'll need both the bundle info and the item def index later. m_vecBundles.AddToTail( pItemDef ); // If the bundle is a pack bundle, mark all the contained items as pack items / link to the owning pack bundle if ( pItemDef->IsPackBundle() ) { const bundleinfo_t *pBundleInfo = pItemDef->GetBundleInfo(); FOR_EACH_VEC( pBundleInfo->vecItemDefs, iCurItem ) { CEconItemDefinition *pCurItemDef = pBundleInfo->vecItemDefs[ iCurItem ]; SCHEMA_INIT_CHECK( NULL == pCurItemDef->m_pOwningPackBundle, "Pack item \"%s\" included in more than one pack bundle - not allowed!", pCurItemDef->GetDefinitionName() ); pCurItemDef->m_pOwningPackBundle = pItemDef; } } } static CSchemaAttributeDefHandle pAttrDef_ContainsCollection( "contains collection" ); if ( pAttrDef_ContainsCollection ) { FOR_EACH_VEC( pItemDef->GetStaticAttributes(), i ) { const static_attrib_t& staticAttrib = pItemDef->GetStaticAttributes()[i]; if ( staticAttrib.iDefIndex == pAttrDef_ContainsCollection->GetDefinitionIndex() ) { // Add to collection crate list m_vecItemCollectionCrates.AddToTail( pItemDef->GetDefinitionIndex() ); } } } } } } // Check the integrity of the item definitions CUtlRBTree rbItemNames( CaselessStringLessThan ); rbItemNames.EnsureCapacity( m_mapItems.Count() ); FOR_EACH_MAP_FAST( m_mapItems, i ) { CEconItemDefinition *pItemDef = m_mapItems[ i ]; // Check for duplicate item definition names int iIndex = rbItemNames.Find( pItemDef->GetDefinitionName() ); SCHEMA_INIT_CHECK( !rbItemNames.IsValidIndex( iIndex ), "Item definition %s: Duplicate name on index %d", pItemDef->GetDefinitionName(), m_mapItems.Key( i ) ); if( !rbItemNames.IsValidIndex( iIndex ) ) rbItemNames.Insert( m_mapItems[i]->GetDefinitionName() ); // Link up armory and store mappings for the item SCHEMA_INIT_SUBSTEP( pItemDef->BInitItemMappings( pVecErrors ) ); } #ifdef DOTA // Go through all regular (ie non-pack) bundles and ensure that if any pack items are included, *all* pack items in the owning pack bundle are included FOR_EACH_VEC( m_vecBundles, iBundle ) { const CEconItemDefinition *pBundleItemDef = m_vecBundles[ iBundle ]; if ( pBundleItemDef->IsPackBundle() ) continue; // Go through all items in the bundle and look for pack items const bundleinfo_t *pBundle = pBundleItemDef->GetBundleInfo(); if ( pBundle ) { FOR_EACH_VEC( pBundle->vecItemDefs, iContainedBundleItem ) { // Get the associated pack bundle const CEconItemDefinition *pContainedBundleItemDef = pBundle->vecItemDefs[ iContainedBundleItem ]; // Ignore non-pack items if ( !pContainedBundleItemDef || !pContainedBundleItemDef->IsPackItem() ) continue; // Get the pack bundle that contains this particular pack item const CEconItemDefinition *pOwningPackBundleItemDef = pContainedBundleItemDef->GetOwningPackBundle(); // Make sure all items in the owning pack bundle are in pBundleItemDef's bundle info (pBundle) const bundleinfo_t *pOwningPackBundle = pOwningPackBundleItemDef->GetBundleInfo(); FOR_EACH_VEC( pOwningPackBundle->vecItemDefs, iCurPackBundleItem ) { CEconItemDefinition *pCurPackBundleItem = pOwningPackBundle->vecItemDefs[ iCurPackBundleItem ]; if ( !pBundle->vecItemDefs.HasElement( pCurPackBundleItem ) ) { SCHEMA_INIT_CHECK( false, "The bundle \"%s\" contains some, but not all pack items required specified by pack bundle \"%s.\"", pBundleItemDef->GetDefinitionName(), pOwningPackBundleItemDef->GetDefinitionName() ); } } } } } #endif return SCHEMA_INIT_SUCCESS(); } #if 0 // Compiled out until some DotA changes from the item editor are brought over //----------------------------------------------------------------------------- // Purpose: Delete an item definition. Moderately dangerous as cached references will become bad. // Intended for use by the item editor. //----------------------------------------------------------------------------- bool CEconItemSchema::DeleteItemDefinition( int iDefIndex ) { m_mapItemsSorted.Remove( iDefIndex ); int nMapIndex = m_mapItems.Find( iDefIndex ); if ( m_mapItems.IsValidIndex( nMapIndex ) ) { CEconItemDefinition* pItemDef = m_mapItems[nMapIndex]; if ( pItemDef ) { m_mapItems.RemoveAt( nMapIndex ); delete pItemDef; return true; } } return false; } #endif //----------------------------------------------------------------------------- // Purpose: Parses the Item Sets section. //----------------------------------------------------------------------------- bool CEconItemSchema::BInitItemSets( KeyValues *pKVItemSets, CUtlVector *pVecErrors ) { m_mapItemSets.RemoveAll(); if ( NULL != pKVItemSets ) { FOR_EACH_TRUE_SUBKEY( pKVItemSets, pKVItemSet ) { const char* setName = pKVItemSet->GetName(); SCHEMA_INIT_CHECK( setName != NULL, "All itemsets must have names." ); SCHEMA_INIT_CHECK( m_mapItemSets.Find( setName ) == m_mapItemSets.InvalidIndex(), "Duplicate itemset name (%s) found!", setName ); int idx = m_mapItemSets.Insert( setName, new CEconItemSetDefinition ); SCHEMA_INIT_SUBSTEP( m_mapItemSets[idx]->BInitFromKV( pKVItemSet, pVecErrors ) ); } // Once we've initialized all of our item sets, loop through all of our item definitions looking // for pseudo set items. For example, the Festive Holy Mackerel is a different item definition from // the regular Holy Mackerel, but for set completion and set listing purposes, we want it to show // as part of the base set. FOR_EACH_MAP_FAST( m_mapItems, i ) { CEconItemDefinition *pItemDef = m_mapItems[i]; Assert( pItemDef ); // Items that point to themselves are the base set items and got initialized as part of the // set initialization above. if ( pItemDef->GetSetItemRemap() == pItemDef->GetDefinitionIndex() ) continue; // Which item are we stealing set information from? const CEconItemDefinition *pRemappedSetItemDef = GetItemDefinition( pItemDef->GetSetItemRemap() ); AssertMsg( pRemappedSetItemDef, "Somehow got through item and set initialization but have a broken set remap item!" ); pItemDef->SetItemSetDefinition( pRemappedSetItemDef->GetItemSetDefinition() ); } } return SCHEMA_INIT_SUCCESS(); } //----------------------------------------------------------------------------- bool CEconItemSchema::BVerifyBaseItemNames( CUtlVector *pVecErrors ) { FOR_EACH_MAP_FAST( m_mapItems, i ) { CEconItemDefinition *pItemDef = m_mapItems[i]; // get base item name const char* pBaseName = pItemDef->GetBaseFunctionalItemName(); if ( !pBaseName || pBaseName[0] == '\0' ) { continue; } // look up base item name SCHEMA_INIT_CHECK( GetItemDefinitionByName( pBaseName ) != NULL, "Base item name not found %s.", pBaseName ); } return SCHEMA_INIT_SUCCESS(); } //----------------------------------------------------------------------------- bool CEconItemSchema::BInitItemCollections( KeyValues *pKVItemCollections, CUtlVector *pVecErrors ) { m_mapItemCollections.Purge(); if ( NULL != pKVItemCollections ) { FOR_EACH_TRUE_SUBKEY( pKVItemCollections, pKVItemCollection ) { const char* setName = pKVItemCollection->GetName(); SCHEMA_INIT_CHECK( setName != NULL, "All item collections must have names." ); SCHEMA_INIT_CHECK( m_mapItemCollections.Find( setName ) == m_mapItemCollections.InvalidIndex(), "Duplicate item collection name (%s) found!", setName ); int idx = m_mapItemCollections.Insert( setName, new CEconItemCollectionDefinition ); SCHEMA_INIT_SUBSTEP( m_mapItemCollections[idx]->BInitFromKV( pKVItemCollection, pVecErrors ) ); } } return SCHEMA_INIT_SUCCESS(); } //----------------------------------------------------------------------------- bool CEconItemSchema::BInitCollectionReferences( CUtlVector *pVecErrors ) { FOR_EACH_MAP_FAST( m_mapItems, i ) { CEconItemDefinition *pItemDef = m_mapItems[i]; const char *pszCollectionName = pItemDef->GetCollectionReference(); if ( pszCollectionName ) { // Find the collection bool bFound = false; FOR_EACH_MAP_FAST( m_mapItemCollections, iCollectionIndex ) { const char * pszTemp = m_mapItemCollections[iCollectionIndex]->m_pszName; if ( !V_strcmp( pszTemp, pszCollectionName) ) { bFound = true; pItemDef->SetItemCollectionDefinition( m_mapItemCollections[iCollectionIndex] ); break; } } SCHEMA_INIT_CHECK( bFound == true, "Collection %s referenced by item %s not found", pszCollectionName, pItemDef->GetDefinitionName() ); } } return SCHEMA_INIT_SUCCESS(); } //----------------------------------------------------------------------------- const CEconItemCollectionDefinition *CEconItemSchema::GetCollectionByName( const char* pCollectionName ) { if ( !pCollectionName ) return NULL; FOR_EACH_MAP_FAST( m_mapItemCollections, iCollectionIndex ) { const char * pszTemp = m_mapItemCollections[iCollectionIndex]->m_pszName; if ( !V_strcmp( pszTemp, pCollectionName ) ) { return m_mapItemCollections[iCollectionIndex]; } } return NULL; } //----------------------------------------------------------------------------- bool CEconItemSchema::BInitItemPaintKitDefinitions( KeyValues *pKVItemPaintKits, CUtlVector *pVecErrors ) { m_mapItemPaintKits.Purge(); const char* cWhitespace = " \r\n\t"; // space, end of line, tab. cWhitespace; // Compiler happiness for GC build if ( NULL != pKVItemPaintKits ) { #ifdef CLIENT_DLL FOR_EACH_TRUE_SUBKEY( pKVItemPaintKits, pKVPaintKit ) { const char* keyField = pKVPaintKit->GetName(); SCHEMA_INIT_CHECK( keyField != NULL, "All item collections must have names." ); if ( V_stristr( keyField, "paintkit_template" ) != NULL ) { static const int cSkipLen = strlen( "paintkit_template" ); keyField += cSkipLen; keyField += strspn( keyField, cWhitespace ); bool createTmplResult = materials->AddTextureCompositorTemplate( keyField, pKVPaintKit ); SCHEMA_INIT_CHECK( createTmplResult, "Could Not Create paintkit_template '%s'", keyField ); } } // Do post-load validation before moving on to paintkits. SCHEMA_INIT_CHECK( materials->VerifyTextureCompositorTemplates(), "Paintkit template post-init validation failed." ); #endif // Now do all the paintkits FOR_EACH_TRUE_SUBKEY( pKVItemPaintKits, pKVPaintKit ) { // We know the keyField is valid, it was checked above. const char* keyField = pKVPaintKit->GetName(); if ( V_stristr( keyField, "paintkit_template" ) == NULL ) { SCHEMA_INIT_CHECK( m_mapItemPaintKits.Find( keyField ) == m_mapItemPaintKits.InvalidIndex(), "Duplicate paint kit definition name (%s) found!", keyField ); int idx = m_mapItemPaintKits.Insert( keyField, new CEconItemPaintKitDefinition ); SCHEMA_INIT_SUBSTEP( m_mapItemPaintKits[ idx ]->BInitFromKV( pKVPaintKit, pVecErrors ) ); } } } return SCHEMA_INIT_SUCCESS(); } //----------------------------------------------------------------------------- bool CEconItemSchema::BInitOperationDefinitions( KeyValues *pKVGameInfo, KeyValues *pKVOperationDefinitions, CUtlVector *pVecErrors ) { m_mapOperationDefinitions.Purge(); if ( NULL != pKVOperationDefinitions ) { FOR_EACH_TRUE_SUBKEY( pKVOperationDefinitions, pKVOperation ) { const char* setName = pKVOperation->GetName(); SCHEMA_INIT_CHECK( setName != NULL, "All operations must have names." ); SCHEMA_INIT_CHECK( m_mapOperationDefinitions.Find( setName ) == m_mapOperationDefinitions.InvalidIndex(), "Duplicate operation definition name (%s) found!", setName ); CEconOperationDefinition *pNewOperation = new CEconOperationDefinition(); SCHEMA_INIT_SUBSTEP( pNewOperation->BInitFromKV( pKVOperation, pVecErrors ) ); // don't add expired operation to list if ( pNewOperation->IsExpired() ) { delete pNewOperation; continue; } m_mapOperationDefinitions.Insert( setName, pNewOperation ); } } return SCHEMA_INIT_SUCCESS(); } //----------------------------------------------------------------------------- // Purpose: Initializes the timed rewards section of the schema // Input: pKVTimedRewards - The timed_rewards section of the KeyValues // representation of the schema // pVecErrors - An optional vector that will contain error messages if // the init fails. // Output: True if initialization succeeded, false otherwise //----------------------------------------------------------------------------- bool CEconItemSchema::BInitTimedRewards( KeyValues *pKVTimedRewards, CUtlVector *pVecErrors ) { m_vecTimedRewards.RemoveAll(); // initialize the rewards sections if ( NULL != pKVTimedRewards ) { FOR_EACH_TRUE_SUBKEY( pKVTimedRewards, pKVTimedReward ) { int index = m_vecTimedRewards.AddToTail(); SCHEMA_INIT_SUBSTEP( m_vecTimedRewards[index].BInitFromKV( pKVTimedReward, pVecErrors ) ); } } return SCHEMA_INIT_SUCCESS(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- const CTimedItemRewardDefinition* CEconItemSchema::GetTimedReward( eTimedRewardType type ) const { if ( (int)type < m_vecTimedRewards.Count() ) { return &m_vecTimedRewards[type]; } return NULL; } //----------------------------------------------------------------------------- // Purpose: Initializes the loot lists section of the schema //----------------------------------------------------------------------------- bool CEconItemSchema::BInitLootLists( KeyValues *pKVLootLists, CUtlVector *pVecErrors ) { if ( NULL != pKVLootLists ) { FOR_EACH_TRUE_SUBKEY( pKVLootLists, pKVLootList ) { const char* pListName = pKVLootList->GetName(); SCHEMA_INIT_SUBSTEP( BInsertLootlist( pListName, pKVLootList, pVecErrors ) ); } } FOR_EACH_MAP_FAST( m_mapLootLists, i ) { const CEconLootListDefinition *pLootList = m_mapLootLists[i]; BVerifyLootListItemDropDates( pLootList, pVecErrors ); } return SCHEMA_INIT_SUCCESS(); } //----------------------------------------------------------------------------- bool CEconItemSchema::BInsertLootlist( const char *pListName, KeyValues *pKVLootList, CUtlVector *pVecErrors ) { SCHEMA_INIT_CHECK( pListName != NULL, "All lootlists must have names." ); if ( m_mapLootLists.Count() > 0 ) { SCHEMA_INIT_CHECK( GetLootListByName( pListName ) == NULL, "Duplicate lootlist name (%s) found!", pListName ); } CEconLootListDefinition *pLootList = new CEconLootListDefinition; SCHEMA_INIT_SUBSTEP( pLootList->BInitFromKV( pKVLootList, *this, pVecErrors ) ); m_mapLootLists.Insert( pListName, pLootList ); return SCHEMA_INIT_SUCCESS(); } //----------------------------------------------------------------------------- // Purpose: Initializes the revolving loot lists section of the schema //----------------------------------------------------------------------------- bool CEconItemSchema::BInitRevolvingLootLists( KeyValues *pKVLootLists, CUtlVector *pVecErrors ) { m_mapRevolvingLootLists.RemoveAll(); if ( NULL != pKVLootLists ) { FOR_EACH_SUBKEY( pKVLootLists, pKVList ) { int iListIdx = pKVList->GetInt(); const char* strListName = pKVList->GetName(); m_mapRevolvingLootLists.Insert( iListIdx, strListName ); } } FOR_EACH_MAP_FAST( m_mapRevolvingLootLists, i ) { const CEconLootListDefinition* pLootList = GetLootListByName(m_mapRevolvingLootLists[i]); BVerifyLootListItemDropDates( pLootList, pVecErrors ); } return SCHEMA_INIT_SUCCESS(); } //----------------------------------------------------------------------------- // Purpose: Create and return a new quest objective definition. Verify that // a definition with the same name doesnt alreay exist. //----------------------------------------------------------------------------- bool CEconItemSchema::AddQuestObjective( const CQuestObjectiveDefinition **ppQuestObjective, KeyValues *pKVObjective, CUtlVector *pVecErrors ) { // These need to be unique int nDefIndex = pKVObjective->GetInt( "defindex", -1 ); SCHEMA_INIT_CHECK( nDefIndex != -1, "Missing defindex for quest objective" ); // Verify defindex is unique auto nMapIndex = m_mapQuestObjectives.Find( nDefIndex ); SCHEMA_INIT_CHECK( nMapIndex == m_mapQuestObjectives.InvalidIndex(), "Multiple quest objectives with defindex: %d", nDefIndex ); // Create the quest def nMapIndex = m_mapQuestObjectives.Insert( nDefIndex ); m_mapQuestObjectives[ nMapIndex ] = CreateQuestDefinition(); // Init SCHEMA_INIT_SUBSTEP( m_mapQuestObjectives[nMapIndex]->BInitFromKV( pKVObjective, pVecErrors ) ); if ( ppQuestObjective ) { (*ppQuestObjective) = m_mapQuestObjectives[nMapIndex]; } return SCHEMA_INIT_SUCCESS(); } //----------------------------------------------------------------------------- // Purpose: Verify that the contents of visible lootlist do not have drop dates // associated with them. The thinking being that we dont want to have // items listed that could potentially not drop, or items disappear/appear // in from a list. //----------------------------------------------------------------------------- bool CEconItemSchema::BVerifyLootListItemDropDates( const CEconLootListDefinition* pLootList, CUtlVector *pVecErrors ) const { if ( pLootList && pLootList->BPublicListContents() ) { BRecurseiveVerifyLootListItemDropDates( pLootList, pLootList, pVecErrors ); } return SCHEMA_INIT_SUCCESS(); } //----------------------------------------------------------------------------- // Purpose: Recursively dig through all entries in the passed in lootlist to see // if any of the containted items have drop dates. //----------------------------------------------------------------------------- bool CEconItemSchema::BRecurseiveVerifyLootListItemDropDates( const CEconLootListDefinition* pLootList, const CEconLootListDefinition* pRootLootList, CUtlVector *pVecErrors ) const { FOR_EACH_VEC( pLootList->GetLootListContents(), j ) { const CEconLootListDefinition::drop_item_t& item = pLootList->GetLootListContents()[j]; // 0 and greater means item. Less than 0 means nested lootlist if( item.m_iItemOrLootlistDef >= 0 ) { const CEconItemDefinition* pItemDef = GetItemSchema()->GetItemDefinition( item.m_iItemOrLootlistDef ); if( pItemDef ) { static CSchemaAttributeDefHandle pAttribDef_StartDropDate( "start drop date" ); static CSchemaAttributeDefHandle pAttribDef_EndDropDate( "end drop date" ); CAttribute_String value; // Check for start drop date attribute on this item SCHEMA_INIT_CHECK( !FindAttribute( pItemDef, pAttribDef_StartDropDate, &value ), "Lootlist \"%s\" contains lootlist \"%s\", which contains item \"%s\", which has start drop date.", pRootLootList->GetName(), pLootList->GetName(), pItemDef->GetDefinitionName() ); // Check for end drop date attribute on this item SCHEMA_INIT_CHECK( !FindAttribute( pItemDef, pAttribDef_EndDropDate, &value ), "Lootlist \"%s\" contains lootlist \"%s\", which contains item \"%s\", which has end drop date.", pRootLootList->GetName(), pLootList->GetName(), pItemDef->GetDefinitionName() ); } } else { // Get the nested lootlist int iLLIndex = (item.m_iItemOrLootlistDef * -1) - 1; const CEconLootListDefinition *pNestedLootList = GetItemSchema()->GetLootListByIndex( iLLIndex ); if ( !pNestedLootList ) return SCHEMA_INIT_SUCCESS(); // Dig through all of this lootlist's entries BRecurseiveVerifyLootListItemDropDates( pNestedLootList, pRootLootList, pVecErrors ); } } return SCHEMA_INIT_SUCCESS(); } //----------------------------------------------------------------------------- // Purpose: Initializes the recipes section of the schema // Input: pKVRecipes - The recipes section of the KeyValues // representation of the schema // pVecErrors - An optional vector that will contain error messages if // the init fails. // Output: True if initialization succeeded, false otherwise //----------------------------------------------------------------------------- bool CEconItemSchema::BInitRecipes( KeyValues *pKVRecipes, CUtlVector *pVecErrors ) { m_mapRecipes.RemoveAll(); // initialize the rewards sections if ( NULL != pKVRecipes ) { FOR_EACH_TRUE_SUBKEY( pKVRecipes, pKVRecipe ) { int nRecipeIndex = Q_atoi( pKVRecipe->GetName() ); int nMapIndex = m_mapRecipes.Find( nRecipeIndex ); // Make sure the recipe index is correct because we use this index as a reference SCHEMA_INIT_CHECK( !m_mapRecipes.IsValidIndex( nMapIndex ), "Duplicate recipe definition (%d)", nRecipeIndex ); // Check to make sure the index is positive SCHEMA_INIT_CHECK( nRecipeIndex >= 0, "Recipe definition index %d must be greater than or equal to zero", nRecipeIndex ); CEconCraftingRecipeDefinition *recipeDef = CreateCraftingRecipeDefinition(); SCHEMA_INIT_SUBSTEP( recipeDef->BInitFromKV( pKVRecipe, pVecErrors ) ); #ifdef _DEBUG // Sanity check in debug builds so that we know we aren't putting the same recipe in // multiple times. FOR_EACH_MAP_FAST( m_mapRecipes, i ) { Assert( i != nRecipeIndex ); Assert( m_mapRecipes[i] != recipeDef ); } #endif // _DEBUG // Store this recipe. m_mapRecipes.Insert( nRecipeIndex, recipeDef ); } } return SCHEMA_INIT_SUCCESS(); } //----------------------------------------------------------------------------- // Purpose: Builds the name of a achievement in the form App. // Input: unAppID - native app ID // pchNativeAchievementName - name of the achievement in its native app // Returns: The combined achievement name //----------------------------------------------------------------------------- CUtlString CEconItemSchema::ComputeAchievementName( AppId_t unAppID, const char *pchNativeAchievementName ) { return CFmtStr1024( "App%u.%s", unAppID, pchNativeAchievementName ).Access(); } //----------------------------------------------------------------------------- // Purpose: Initializes the achievement rewards section of the schema // Input: pKVAchievementRewards - The achievement_rewards section of the KeyValues // representation of the schema // pVecErrors - An optional vector that will contain error messages if // the init fails. // Output: True if initialization succeeded, false otherwise //----------------------------------------------------------------------------- bool CEconItemSchema::BInitAchievementRewards( KeyValues *pKVAchievementRewards, CUtlVector *pVecErrors ) { m_dictAchievementRewards.RemoveAll(); m_mapAchievementRewardsByData.PurgeAndDeleteElements(); // initialize the rewards sections if ( NULL != pKVAchievementRewards ) { FOR_EACH_SUBKEY( pKVAchievementRewards, pKVReward ) { AchievementAward_t award; if( pKVReward->GetDataType() == KeyValues::TYPE_NONE ) { int32 nItemIndex = pKVReward->GetInt( "DefIndex", -1 ); if( nItemIndex != -1 ) { award.m_vecDefIndex.AddToTail( (uint16)nItemIndex ); } else { KeyValues *pkvItems = pKVReward->FindKey( "Items" ); SCHEMA_INIT_CHECK( pkvItems != NULL, "Complex achievement %s must have an Items key or a DefIndex field", pKVReward->GetName() ); if( !pkvItems ) { continue; } FOR_EACH_VALUE( pkvItems, pkvItem ) { award.m_vecDefIndex.AddToTail( (uint16)Q_atoi( pkvItem->GetName() ) ); } } } else { award.m_vecDefIndex.AddToTail( (uint16)pKVReward->GetInt("", -1 ) ); } // make sure all the item types are valid bool bFoundAllItems = true; FOR_EACH_VEC( award.m_vecDefIndex, nItem ) { const CEconItemDefinition *pDefn = GetItemDefinition( award.m_vecDefIndex[nItem] ); SCHEMA_INIT_CHECK( pDefn != NULL, "Item definition index %d in achievement reward %s was not found", award.m_vecDefIndex[nItem], pKVReward->GetName() ); if( !pDefn ) { bFoundAllItems = false; } } if( !bFoundAllItems ) continue; SCHEMA_INIT_CHECK( award.m_vecDefIndex.Count() > 0, "Achievement reward %s has no items!", pKVReward->GetName() ); if( award.m_vecDefIndex.Count() == 0 ) continue; #ifdef GC_DLL award.m_unSourceAppId = GGCBase()->GetAppID(); #else award.m_unSourceAppId = k_uAppIdInvalid; #endif if( pKVReward->GetDataType() == KeyValues::TYPE_NONE ) { // cross game achievement award.m_sNativeName = pKVReward->GetName(); award.m_unAuditData = pKVReward->GetInt( "AuditData", 0 ); award.m_unSourceAppId = pKVReward->GetInt( "SourceAppID", award.m_unSourceAppId ); } else { award.m_sNativeName = pKVReward->GetName(); award.m_unAuditData = 0; } #ifdef GC_DLL // Check to make sure the audit data is valid SCHEMA_INIT_CHECK( award.m_unSourceAppId >= 0, "Source App ID %d in achievement reward %s must be valid", award.m_unSourceAppId, pKVReward->GetName() ); if( award.m_unSourceAppId == k_uAppIdInvalid ) continue; if( !GGCGameBase()->BYieldingLoadStats( award.m_unSourceAppId ) ) { // this will often fail in a dev universe if( GGCHost()->GetUniverse() != k_EUniverseDev ) { SCHEMA_INIT_CHECK( false, "Unable to load stats schema for cross-game achievement %s for app %d", pKVReward->GetName(), award.m_unSourceAppId ); } continue; } const CGCStatsSchema *pStatsSchema = GGCGameBase()->GetStatsSchema( award.m_unSourceAppId ); if( !pStatsSchema ) { SCHEMA_INIT_CHECK( false, "Unable to retrieve stats schema for cross-game achievement %s for app %d", pKVReward->GetName(), award.m_unSourceAppId ); continue; } if( award.m_unAuditData == 0 ) { uint16 usStatID, usBitID; if( !pStatsSchema->BGetAchievementBit( award.m_sNativeName, &usStatID, &usBitID ) ) { SCHEMA_INIT_CHECK( false, "Unable to find achievement %s for app %d", award.m_sNativeName.Get(), award.m_unSourceAppId ); continue; } award.m_unAuditData = ( usStatID <<16 ) | usBitID; } #endif // GC_DLL AchievementAward_t *pAward = new AchievementAward_t; *pAward = award; m_dictAchievementRewards.Insert( ComputeAchievementName( pAward->m_unSourceAppId, pAward->m_sNativeName ), pAward ); m_mapAchievementRewardsByData.Insert( pAward->m_unAuditData, pAward ); } } return SCHEMA_INIT_SUCCESS(); } #ifdef GC_DLL bool CEconItemSchema::BInitRandomAttributeTemplates( KeyValues *pKVRandomAttributeTemplates, CUtlVector *pVecErrors ) { m_dictRandomAttributeTemplates.PurgeAndDeleteElements(); FOR_EACH_TRUE_SUBKEY( pKVRandomAttributeTemplates, pKVAttributeTemplate ) { const char *pszAttrName = pKVAttributeTemplate->GetName(); // try to create random attrib from template random_attrib_t *pRandomAttr = CreateRandomAttribute( __FUNCTION__, pKVAttributeTemplate, pVecErrors ); SCHEMA_INIT_CHECK( NULL != pRandomAttr, CFmtStr( "%s: Failed to create random_attrib_t '%s'", __FUNCTION__, pszAttrName ) ); m_dictRandomAttributeTemplates.Insert( pszAttrName, pRandomAttr ); } return true; } #endif // GC_DLL #ifdef TF_CLIENT_DLL //----------------------------------------------------------------------------- // Purpose: Go through all items and cache the number of concrete items in each. //----------------------------------------------------------------------------- bool CEconItemSchema::BInitConcreteItemCounts( CUtlVector *pVecErrors ) { FOR_EACH_MAP_FAST( m_mapItems, i ) { CEconItemDefinition *pItemDef = m_mapItems[ i ]; pItemDef->m_unNumConcreteItems = CalculateNumberOfConcreteItems( pItemDef ); } return true; } //----------------------------------------------------------------------------- // Purpose: Returns the number of actual "real" items referenced by the item definition // (i.e. items that would take up space in the inventory) //----------------------------------------------------------------------------- int CEconItemSchema::CalculateNumberOfConcreteItems( const CEconItemDefinition *pItemDef ) { AssertMsg( pItemDef, "NULL item definition! This should not happen!" ); if ( !pItemDef ) return 0; if ( pItemDef->IsBundle() ) { uint32 unNumConcreteItems = 0; const bundleinfo_t *pBundle = pItemDef->GetBundleInfo(); Assert( pBundle ); FOR_EACH_VEC( pBundle->vecItemDefs, i ) { unNumConcreteItems += CalculateNumberOfConcreteItems( pBundle->vecItemDefs[i] ); } return unNumConcreteItems; } return 1; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CEconItemSchema::BInitSteamPackageLocalizationToken( KeyValues *pKVSteamPackages, CUtlVector *pVecErrors ) { if ( NULL != pKVSteamPackages ) { FOR_EACH_TRUE_SUBKEY( pKVSteamPackages, pKVEntry ) { // Check to make sure the index is positive int iRawPackageId = atoi( pKVEntry->GetName() ); SCHEMA_INIT_CHECK( iRawPackageId > 0, "Invalid package ID %i for localization", iRawPackageId ); // Store off our data. uint32 unPackageId = (uint32)iRawPackageId; const char *pszLocalizationToken = pKVEntry->GetString( "localization_key" ); m_mapSteamPackageLocalizationTokens.InsertOrReplace( unPackageId, pszLocalizationToken ); } } return SCHEMA_INIT_SUCCESS(); } #endif // TF_CLIENT_DLL static const char *s_particle_controlpoint_names[] = { "attachment", "control_point_1", "control_point_2", "control_point_3", "control_point_4", "control_point_5", "control_point_6", }; //----------------------------------------------------------------------------- // Purpose: Initializes the attribute-controlled-particle-systems section of the schema //----------------------------------------------------------------------------- bool CEconItemSchema::BInitAttributeControlledParticleSystems( KeyValues *pKVParticleSystems, CUtlVector *pVecErrors ) { m_mapAttributeControlledParticleSystems.RemoveAll(); m_vecAttributeControlledParticleSystemsCosmetics.RemoveAll(); m_vecAttributeControlledParticleSystemsWeapons.RemoveAll(); m_vecAttributeControlledParticleSystemsTaunts.RemoveAll(); CUtlVector< int > *pVec = NULL; // Addictional groups we are tracking for. // "cosmetic_unusual_effects" // "weapon_unusual_effects" // "taunt_unusual_effects" if ( NULL != pKVParticleSystems ) { FOR_EACH_TRUE_SUBKEY( pKVParticleSystems, pKVCategory ) { // There is 3 Categories we want to track with additional info if ( !V_strcmp( pKVCategory->GetName(), "cosmetic_unusual_effects" ) ) { pVec = &m_vecAttributeControlledParticleSystemsCosmetics; } else if ( !V_strcmp( pKVCategory->GetName(), "weapon_unusual_effects" ) ) { pVec = &m_vecAttributeControlledParticleSystemsWeapons; } else if ( !V_strcmp( pKVCategory->GetName(), "taunt_unusual_effects" ) ) { pVec = &m_vecAttributeControlledParticleSystemsTaunts; } else { pVec = NULL; // reset } FOR_EACH_TRUE_SUBKEY( pKVCategory, pKVEntry ) { int32 nItemIndex = atoi( pKVEntry->GetName() ); // Check to make sure the index is positive SCHEMA_INIT_CHECK( nItemIndex > 0, "Particle system index %d greater than zero", nItemIndex ); if ( nItemIndex <= 0 ) continue; int iIndex = m_mapAttributeControlledParticleSystems.Insert( nItemIndex ); attachedparticlesystem_t &system = m_mapAttributeControlledParticleSystems[iIndex]; system.pszSystemName = pKVEntry->GetString( "system", NULL ); system.bFollowRootBone = pKVEntry->GetInt( "attach_to_rootbone", 0 ) != 0; system.iCustomType = 0; system.nSystemID = nItemIndex; system.fRefireTime = pKVEntry->GetFloat( "refire_time", 0.0f ); system.bDrawInViewModel = pKVEntry->GetBool( "draw_in_viewmodel", false ); system.bUseSuffixName = pKVEntry->GetBool( "use_suffix_name", false ); system.bHasViewModelSpecificEffect = pKVEntry->GetBool( "has_viewmodel_specific_effect", false ); COMPILE_TIME_ASSERT( ARRAYSIZE( system.pszControlPoints ) == ARRAYSIZE( s_particle_controlpoint_names ) ); for ( int i=0; iGetString( s_particle_controlpoint_names[i], NULL ); } if ( pVec ) { pVec->AddToTail( nItemIndex ); } } } } return SCHEMA_INIT_SUCCESS(); } #ifdef CLIENT_DLL locchar_t *CEconItemSchema::GetParticleSystemLocalizedName( int index ) const { const attachedparticlesystem_t *pSystem = GetItemSchema()->GetAttributeControlledParticleSystem( index ); if ( !pSystem ) return NULL; char particleNameEntry[128]; Q_snprintf( particleNameEntry, ARRAYSIZE( particleNameEntry ), "#Attrib_Particle%d", pSystem->nSystemID ); return g_pVGuiLocalize->Find( particleNameEntry ); } #endif //----------------------------------------------------------------------------- // Purpose: Inits data for items that can level up through kills, etc. //----------------------------------------------------------------------------- bool CEconItemSchema::BInitItemLevels( KeyValues *pKVItemLevels, CUtlVector *pVecErrors ) { m_vecItemLevelingData.RemoveAll(); // initialize the rewards sections if ( NULL != pKVItemLevels ) { FOR_EACH_TRUE_SUBKEY( pKVItemLevels, pKVItemLevelBlock ) { const char *pszLevelBlockName = pKVItemLevelBlock->GetName(); SCHEMA_INIT_CHECK( GetItemLevelingData( pszLevelBlockName ) == NULL, "Duplicate leveling data block named \"%s\".", pszLevelBlockName ); // Allocate a new structure for this block and assign it. We'll fill in the contents later. CUtlVector *pLevelingData = new CUtlVector; m_vecItemLevelingData.Insert( pszLevelBlockName, pLevelingData ); FOR_EACH_TRUE_SUBKEY( pKVItemLevelBlock, pKVItemLevel ) { int index = pLevelingData->AddToTail(); SCHEMA_INIT_SUBSTEP( (*pLevelingData)[index].BInitFromKV( pKVItemLevel, pszLevelBlockName, pVecErrors ) ); } } } return SCHEMA_INIT_SUCCESS(); } //----------------------------------------------------------------------------- // Purpose: Inits data for kill eater types. //----------------------------------------------------------------------------- bool CEconItemSchema::BInitKillEaterScoreTypes( KeyValues *pKVKillEaterScoreTypes, CUtlVector *pVecErrors ) { m_mapKillEaterScoreTypes.RemoveAll(); // initialize the rewards sections if ( NULL != pKVKillEaterScoreTypes ) { FOR_EACH_TRUE_SUBKEY( pKVKillEaterScoreTypes, pKVScoreType ) { unsigned int unIndex = (unsigned int)atoi( pKVScoreType->GetName() ); SCHEMA_INIT_CHECK( m_mapKillEaterScoreTypes.Find( unIndex ) == KillEaterScoreMap_t::InvalidIndex(), "Duplicate kill eater score type index %u.", unIndex ); kill_eater_score_type_t ScoreType; ScoreType.m_pszTypeString = pKVScoreType->GetString( "type_name" ); ScoreType.m_bAllowBotVictims = pKVScoreType->GetBool( "allow_bot_victims", false ); #ifdef GC_DLL ScoreType.m_bGCUpdateOnly = pKVScoreType->GetBool( "gc_update_only", false ); ScoreType.m_AllowIncrementValues = pKVScoreType->GetBool( "gc_allow_increment_values", false ); ScoreType.m_bIsBaseKillType = pKVScoreType->GetBool( "gc_is_base_kill_type", false ); #endif const char *pszLevelBlockName = pKVScoreType->GetString( "level_data", "KillEaterRank" ); SCHEMA_INIT_CHECK( GetItemLevelingData( pszLevelBlockName ) != NULL, "Unable to find leveling data block named \"%s\" for kill eater score type %u.", pszLevelBlockName, unIndex ); ScoreType.m_pszLevelBlockName = pszLevelBlockName; m_mapKillEaterScoreTypes.Insert( unIndex, ScoreType ); } } return SCHEMA_INIT_SUCCESS(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CEconItemSchema::BInitStringTables( KeyValues *pKVStringTables, CUtlVector *pVecErrors ) { m_dictStringTable.PurgeAndDeleteElements(); // initialize the rewards sections if ( NULL != pKVStringTables ) { FOR_EACH_SUBKEY( pKVStringTables, pKVTable ) { SCHEMA_INIT_CHECK( !m_dictStringTable.IsValidIndex( m_dictStringTable.Find( pKVTable->GetName() ) ), "Duplicate string table name '%s'.", pKVTable->GetName() ); SchemaStringTableDict_t::IndexType_t i = m_dictStringTable.Insert( pKVTable->GetName(), new CUtlVector< schema_string_table_entry_t > ); FOR_EACH_SUBKEY( pKVTable, pKVEntry ) { schema_string_table_entry_t s = { atoi( pKVEntry->GetName() ), pKVEntry->GetString() }; m_dictStringTable[i]->AddToTail( s ); } } } return SCHEMA_INIT_SUCCESS(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CEconItemSchema::BInitCommunityMarketRemaps( KeyValues *pKVCommunityMarketRemaps, CUtlVector *pVecErrors ) { m_mapCommunityMarketDefinitionIndexRemap.Purge(); if ( NULL != pKVCommunityMarketRemaps ) { FOR_EACH_SUBKEY( pKVCommunityMarketRemaps, pKVRemapBase ) { const char *pszBaseDefName = pKVRemapBase->GetName(); const CEconItemDefinition *pBaseItemDef = GetItemSchema()->GetItemDefinitionByName( pszBaseDefName ); SCHEMA_INIT_CHECK( pBaseItemDef != NULL, "Unknown Market remap base definition '%s'.", pszBaseDefName ); FOR_EACH_SUBKEY( pKVRemapBase, pKVRemap ) { const char *pszDefName = pKVRemap->GetName(); const CEconItemDefinition *pItemDef = GetItemSchema()->GetItemDefinitionByName( pszDefName ); SCHEMA_INIT_CHECK( pItemDef != NULL, "Unknown Market remap definition '%s' (under '%s').", pszDefName, pszBaseDefName ); SCHEMA_INIT_CHECK( m_mapCommunityMarketDefinitionIndexRemap.Find( pItemDef->GetDefinitionIndex() ) == m_mapCommunityMarketDefinitionIndexRemap.InvalidIndex(), "Duplicate Market remap definition '%s'.\n", pszDefName ); m_mapCommunityMarketDefinitionIndexRemap.Insert( pItemDef->GetDefinitionIndex(), pBaseItemDef->GetDefinitionIndex() ); } } } return SCHEMA_INIT_SUCCESS(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- item_definition_index_t CEconItemSchema::GetCommunityMarketRemappedDefinitionIndex( item_definition_index_t unSearchItemDef ) const { CommunityMarketDefinitionRemapMap_t::IndexType_t index = m_mapCommunityMarketDefinitionIndexRemap.Find( unSearchItemDef ); if ( index == m_mapCommunityMarketDefinitionIndexRemap.InvalidIndex() ) return unSearchItemDef; return m_mapCommunityMarketDefinitionIndexRemap[index]; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- const ISchemaAttributeType *CEconItemSchema::GetAttributeType( const char *pszAttrTypeName ) const { FOR_EACH_VEC( m_vecAttributeTypes, i ) { if ( m_vecAttributeTypes[i].m_sName == pszAttrTypeName ) return m_vecAttributeTypes[i].m_pAttrType; } return NULL; } //----------------------------------------------------------------------------- // CItemLevelingDefinition Accessor //----------------------------------------------------------------------------- const CItemLevelingDefinition *CEconItemSchema::GetItemLevelForScore( const char *pszLevelBlockName, uint32 unScore ) const { const CUtlVector *pLevelingData = GetItemLevelingData( pszLevelBlockName ); if ( !pLevelingData ) return NULL; if ( pLevelingData->Count() == 0 ) return NULL; FOR_EACH_VEC( (*pLevelingData), i ) { if ( unScore < (*pLevelingData)[i].GetRequiredScore() ) return &(*pLevelingData)[i]; } return &(*pLevelingData).Tail(); } //----------------------------------------------------------------------------- // Kill eater score type accessor //----------------------------------------------------------------------------- const kill_eater_score_type_t *CEconItemSchema::FindKillEaterScoreType( uint32 unScoreType ) const { KillEaterScoreMap_t::IndexType_t i = m_mapKillEaterScoreTypes.Find( unScoreType ); if ( i == KillEaterScoreMap_t::InvalidIndex() ) return NULL; return &m_mapKillEaterScoreTypes[i]; } //----------------------------------------------------------------------------- // Kill eater score type accessor //----------------------------------------------------------------------------- const char *CEconItemSchema::GetKillEaterScoreTypeLocString( uint32 unScoreType ) const { const kill_eater_score_type_t *pScoreType = FindKillEaterScoreType( unScoreType ); return pScoreType ? pScoreType->m_pszTypeString : NULL; } //----------------------------------------------------------------------------- // Kill eater score type accessor //----------------------------------------------------------------------------- const char *CEconItemSchema::GetKillEaterScoreTypeLevelingDataName( uint32 unScoreType ) const { const kill_eater_score_type_t *pScoreType = FindKillEaterScoreType( unScoreType ); return pScoreType ? pScoreType->m_pszLevelBlockName : NULL; } #if defined(STAGING_ONLY) && ( defined(TF_CLIENT_DLL) || defined(TF_DLL) ) ConVar tf_allow_strange_bot_kills( "tf_allow_strange_bot_kills", "0", FCVAR_REPLICATED ); #endif //----------------------------------------------------------------------------- // Kill eater score type accessor //----------------------------------------------------------------------------- bool CEconItemSchema::GetKillEaterScoreTypeAllowsBotVictims( uint32 unScoreType ) const { #if defined(STAGING_ONLY) && ( defined(TF_CLIENT_DLL) || defined(TF_DLL) ) if ( tf_allow_strange_bot_kills.GetBool() ) { return true; } #endif const kill_eater_score_type_t *pScoreType = FindKillEaterScoreType( unScoreType ); return pScoreType ? pScoreType->m_bAllowBotVictims : false; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- econ_tag_handle_t CEconItemSchema::GetHandleForTag( const char *pszTagName ) { EconTagDict_t::IndexType_t i = m_dictTags.Find( pszTagName ); if ( m_dictTags.IsValidIndex( i ) ) return i; return m_dictTags.Insert( pszTagName ); } #ifdef GC_DLL //----------------------------------------------------------------------------- // Kill eater score type accessor //----------------------------------------------------------------------------- bool CEconItemSchema::GetKillEaterScoreTypeGCOnlyUpdate( uint32 unScoreType ) const { const kill_eater_score_type_t *pScoreType = FindKillEaterScoreType( unScoreType ); return pScoreType ? pScoreType->m_bGCUpdateOnly : true; // default to being more restrictive } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CEconItemSchema::GetKillEaterScoreTypeAllowsIncrementValues( uint32 unScoreType ) const { const kill_eater_score_type_t *pScoreType = FindKillEaterScoreType( unScoreType ); return pScoreType ? pScoreType->m_AllowIncrementValues : true; // default to being more restrictive } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- const CEconItemSchema::periodic_score_t& CEconItemSchema::GetPeriodicScoreInfo( int iPeriodicScoreIndex ) const { Assert( GetPeriodicScoreTypeList().IsValidIndex( iPeriodicScoreIndex ) ); return GetPeriodicScoreTypeList()[ iPeriodicScoreIndex ]; } #endif #if defined(CLIENT_DLL) || defined(GAME_DLL) //----------------------------------------------------------------------------- // Purpose: Clones the specified item definition, and returns the new item def. //----------------------------------------------------------------------------- void CEconItemSchema::ItemTesting_CreateTestDefinition( int iCloneFromItemDef, int iNewDef, KeyValues *pNewKV ) { int nMapIndex = m_mapItems.Find( iNewDef ); if ( !m_mapItems.IsValidIndex( nMapIndex ) ) { nMapIndex = m_mapItems.Insert( iNewDef, CreateEconItemDefinition() ); m_mapItemsSorted.Insert( iNewDef, m_mapItems[nMapIndex] ); } // Find & copy the clone item def's data in CEconItemDefinition *pCloneDef = GetItemDefinition( iCloneFromItemDef ); if ( !pCloneDef ) return; m_mapItems[nMapIndex]->CopyPolymorphic( pCloneDef ); // Then stomp it with the KV test contents m_mapItems[nMapIndex]->BInitFromTestItemKVs( iNewDef, pNewKV ); } //----------------------------------------------------------------------------- // Purpose: Discards the specified item definition //----------------------------------------------------------------------------- void CEconItemSchema::ItemTesting_DiscardTestDefinition( int iDef ) { m_mapItems.Remove( iDef ); m_mapItemsSorted.Remove( iDef ); } //----------------------------------------------------------------------------- // Purpose: Initializes the armory data section of the schema //----------------------------------------------------------------------------- bool CEconItemSchema::BInitArmoryData( KeyValues *pKVArmoryData, CUtlVector *pVecErrors ) { m_dictArmoryItemDataStrings.RemoveAll(); m_dictArmoryAttributeDataStrings.RemoveAll(); if ( NULL != pKVArmoryData ) { KeyValues *pKVItemTypes = pKVArmoryData->FindKey( "armory_item_types" ); if ( pKVItemTypes ) { FOR_EACH_SUBKEY( pKVItemTypes, pKVEntry ) { const char *pszDataKey = pKVEntry->GetName(); const CUtlConstString sLocString( pKVEntry->GetString() ); m_dictArmoryItemTypesDataStrings.Insert( pszDataKey, sLocString ); } } pKVItemTypes = pKVArmoryData->FindKey( "armory_item_classes" ); if ( pKVItemTypes ) { FOR_EACH_SUBKEY( pKVItemTypes, pKVEntry ) { const char *pszDataKey = pKVEntry->GetName(); const CUtlConstString sLocString( pKVEntry->GetString() ); m_dictArmoryItemClassesDataStrings.Insert( pszDataKey, sLocString ); } } KeyValues *pKVAttribs = pKVArmoryData->FindKey( "armory_attributes" ); if ( pKVAttribs ) { FOR_EACH_SUBKEY( pKVAttribs, pKVEntry ) { const char *pszDataKey = pKVEntry->GetName(); const CUtlConstString sLocString( pKVEntry->GetString() ); m_dictArmoryAttributeDataStrings.Insert( pszDataKey, sLocString ); } } KeyValues *pKVItems = pKVArmoryData->FindKey( "armory_items" ); if ( pKVItems ) { FOR_EACH_SUBKEY( pKVItems, pKVEntry ) { const char *pszDataKey = pKVEntry->GetName(); const CUtlConstString sLocString( pKVEntry->GetString() ); m_dictArmoryItemDataStrings.Insert( pszDataKey, sLocString ); } } } return SCHEMA_INIT_SUCCESS(); } #endif #ifdef GC_DLL //----------------------------------------------------------------------------- // Purpose: Returns the item awarded for an achievement. // Input: pchAchievementName - The achievement that was awarded. // Output: The achievement struct for this reward. //----------------------------------------------------------------------------- const AchievementAward_t * CEconItemSchema::GetAchievementReward( const char *pchAchievementName, AppId_t unAppID ) const { int nRewardIndex = m_dictAchievementRewards.Find( ComputeAchievementName( unAppID, pchAchievementName ) ); if( m_dictAchievementRewards.IsValidIndex( nRewardIndex ) ) return m_dictAchievementRewards[ nRewardIndex ]; else return NULL; } //----------------------------------------------------------------------------- // Purpose: Returns the achievement award that matches the provided data or NULL // if there is no such award. // Input: unData - The data field that would be stored in ItemAudit //----------------------------------------------------------------------------- const AchievementAward_t *CEconItemSchema::GetAchievementRewardByData( uint32 unData ) const { uint nIndex = m_mapAchievementRewardsByData.Find( unData ); if( m_mapAchievementRewardsByData.IsValidIndex( nIndex ) ) { return m_mapAchievementRewardsByData[nIndex]; } else { return NULL; } } #endif // GC_DLL //----------------------------------------------------------------------------- // Purpose: Returns the achievement award that matches the provided defindex or NULL // if there is no such award. // Input: unData - The data field that would be stored in ItemAudit //----------------------------------------------------------------------------- const AchievementAward_t *CEconItemSchema::GetAchievementRewardByDefIndex( uint16 usDefIndex ) const { FOR_EACH_MAP_FAST( m_mapAchievementRewardsByData, nIndex ) { if( m_mapAchievementRewardsByData[nIndex]->m_vecDefIndex.HasElement( usDefIndex ) ) return m_mapAchievementRewardsByData[nIndex]; } return NULL; } //----------------------------------------------------------------------------- // Purpose: Gets a rarity value for a name. //----------------------------------------------------------------------------- bool CEconItemSchema::BGetItemRarityFromName( const char *pchName, uint8 *nRarity ) const { if ( 0 == Q_stricmp( "any", pchName ) ) { *nRarity = k_unItemRarity_Any; return true; } FOR_EACH_MAP_FAST( m_mapRarities, i ) { if ( 0 == Q_stricmp( m_mapRarities[i].GetName(), pchName ) ) { *nRarity = m_mapRarities[i].GetDBValue(); return true; } } return false; } //----------------------------------------------------------------------------- // Purpose: Gets a quality value for a name. // Input: pchName - The name to translate. // nQuality - (out)The quality number for this name, if found. // Output: True if the string matched a quality for this schema, false otherwise. //----------------------------------------------------------------------------- bool CEconItemSchema::BGetItemQualityFromName( const char *pchName, uint8 *nQuality ) const { if ( 0 == Q_stricmp( "any", pchName ) ) { *nQuality = k_unItemQuality_Any; return true; } FOR_EACH_MAP_FAST( m_mapQualities, i ) { if ( 0 == Q_stricmp( m_mapQualities[i].GetName(), pchName ) ) { *nQuality = m_mapQualities[i].GetDBValue(); return true; } } return false; } //----------------------------------------------------------------------------- // Purpose: Gets a quality definition for an index // Input: nQuality - The quality to get. // Output: A pointer to the desired definition, or NULL if it is not found. //----------------------------------------------------------------------------- const CEconItemQualityDefinition *CEconItemSchema::GetQualityDefinition( int nQuality ) const { int iIndex = m_mapQualities.Find( nQuality ); if ( m_mapQualities.IsValidIndex( iIndex ) ) return &m_mapQualities[iIndex]; return NULL; } const CEconItemQualityDefinition *CEconItemSchema::GetQualityDefinitionByName( const char *pszDefName ) const { FOR_EACH_MAP_FAST( m_mapQualities, i ) { if ( V_stricmp( pszDefName, m_mapQualities[i].GetName()) == 0 ) return &m_mapQualities[i]; } return NULL; } //----------------------------------------------------------------------------- // ItemRarity //----------------------------------------------------------------------------- const CEconItemRarityDefinition *CEconItemSchema::GetRarityDefinitionByMapIndex( int nRarityIndex ) const { if ( m_mapRarities.IsValidIndex( nRarityIndex ) ) return &m_mapRarities[nRarityIndex]; return NULL; } //----------------------------------------------------------------------------- const CEconItemRarityDefinition *CEconItemSchema::GetRarityDefinition( int nRarity ) const { int iIndex = m_mapRarities.Find( nRarity ); if ( m_mapRarities.IsValidIndex( iIndex ) ) return &m_mapRarities[iIndex]; return NULL; } //----------------------------------------------------------------------------- const CEconItemRarityDefinition *CEconItemSchema::GetRarityDefinitionByName( const char *pszDefName ) const { FOR_EACH_MAP_FAST( m_mapRarities, i ) { if ( !strcmp( pszDefName, m_mapRarities[i].GetName() ) ) return &m_mapRarities[i]; } return NULL; } //----------------------------------------------------------------------------- const char* CEconItemSchema::GetRarityName( uint8 iRarity ) { const CEconItemRarityDefinition* pItemRarity = GetRarityDefinition( iRarity ); if ( !pItemRarity ) return NULL; else return pItemRarity->GetName(); } //----------------------------------------------------------------------------- const char* CEconItemSchema::GetRarityLocKey( uint8 iRarity ) { const CEconItemRarityDefinition* pItemRarity = GetRarityDefinition( iRarity ); if ( !pItemRarity ) return NULL; else return pItemRarity->GetLocKey(); } //----------------------------------------------------------------------------- const char* CEconItemSchema::GetRarityColor( uint8 iRarity ) { const CEconItemRarityDefinition* pItemRarity = GetRarityDefinition( iRarity ); if ( !pItemRarity ) return NULL; else return GetColorNameForAttribColor( pItemRarity->GetAttribColor() ); } //----------------------------------------------------------------------------- int CEconItemSchema::GetRarityIndex( const char* pszRarity ) { const CEconItemRarityDefinition* pRarity = GetRarityDefinitionByName( pszRarity ); if ( pRarity ) return pRarity->GetDBValue(); else return 0; } //----------------------------------------------------------------------------- bool CEconItemSchema::BGetItemSeries( const char* pchName, uint8 *nItemSeries ) const { FOR_EACH_MAP_FAST( m_mapItemSeries, i ) { if ( 0 == Q_stricmp( m_mapItemSeries[i].GetName(), pchName ) ) { *nItemSeries = m_mapItemSeries[i].GetDBValue(); return true; } } return false; } //----------------------------------------------------------------------------- const CEconItemSeriesDefinition *CEconItemSchema::GetItemSeriesDefinition( int iSeries ) const { int iIndex = m_mapItemSeries.Find( iSeries ); if ( m_mapItemSeries.IsValidIndex( iIndex ) ) return &m_mapItemSeries[iIndex]; return NULL; } //----------------------------------------------------------------------------- // Purpose: Gets an item definition for the specified definition index // Input: iItemIndex - The index of the desired definition. // Output: A pointer to the desired definition, or NULL if it is not found. //----------------------------------------------------------------------------- CEconItemDefinition *CEconItemSchema::GetItemDefinition( int iItemIndex ) { #if defined(CLIENT_DLL) || defined(GAME_DLL) #if !defined(CSTRIKE_DLL) AssertMsg( GetDefaultItemDefinition(), "No default item definition set up for item schema." ); #endif // CSTRIKE_DLL #endif // defined(CLIENT_DLL) || defined(GAME_DLL) int iIndex = m_mapItems.Find( iItemIndex ); if ( m_mapItems.IsValidIndex( iIndex ) ) return m_mapItems[iIndex]; #if defined( GC_DLL ) || defined( EXTERNALTESTS_DLL ) return NULL; #else // !GC_DLL if ( GetDefaultItemDefinition() ) return GetDefaultItemDefinition(); #if !defined(CSTRIKE_DLL) // We shouldn't ever get down here, but all the same returning a valid pointer is very slightly // a better plan than returning an invalid pointer to code that won't check to see if it's valid. static CEconItemDefinition *s_pEmptyDefinition = CreateEconItemDefinition(); return s_pEmptyDefinition; #else return NULL; #endif // CSTRIKE_DLL #endif // GC_DLL } const CEconItemDefinition *CEconItemSchema::GetItemDefinition( int iItemIndex ) const { return const_cast(this)->GetItemDefinition( iItemIndex ); } //----------------------------------------------------------------------------- // Purpose: Gets an item definition that has a name matching the specified name. // Input: pszDefName - The name of the desired definition. // Output: A pointer to the desired definition, or NULL if it is not found. //----------------------------------------------------------------------------- CEconItemDefinition *CEconItemSchema::GetItemDefinitionByName( const char *pszDefName ) { // This shouldn't happen, but let's not crash if it ever does. Assert( pszDefName != NULL ); if ( pszDefName == NULL ) return NULL; FOR_EACH_MAP_FAST( m_mapItems, i ) { if ( V_stricmp( pszDefName, m_mapItems[i]->GetDefinitionName()) == 0 ) return m_mapItems[i]; } return NULL; } const CEconItemDefinition *CEconItemSchema::GetItemDefinitionByName( const char *pszDefName ) const { return const_cast(this)->GetItemDefinitionByName( pszDefName ); } #ifdef GC_DLL random_attrib_t *CEconItemSchema::GetRandomAttributeTemplateByName( const char *pszAttrTemplateName ) const { int index = m_dictRandomAttributeTemplates.Find( pszAttrTemplateName ); if ( index != m_dictRandomAttributeTemplates.InvalidIndex() ) { return m_dictRandomAttributeTemplates[index]; } return NULL; } #endif // GC_DLL //----------------------------------------------------------------------------- // Purpose: Gets an attribute definition for an index // Input: iAttribIndex - The index of the desired definition. // Output: A pointer to the desired definition, or NULL if it is not found. //----------------------------------------------------------------------------- CEconItemAttributeDefinition *CEconItemSchema::GetAttributeDefinition( int iAttribIndex ) { int iIndex = m_mapAttributes.Find( iAttribIndex ); if ( m_mapAttributes.IsValidIndex( iIndex ) ) return &m_mapAttributes[iIndex]; return NULL; } const CEconItemAttributeDefinition *CEconItemSchema::GetAttributeDefinition( int iAttribIndex ) const { return const_cast(this)->GetAttributeDefinition( iAttribIndex ); } CEconItemAttributeDefinition *CEconItemSchema::GetAttributeDefinitionByName( const char *pszDefName ) { Assert( pszDefName ); if ( !pszDefName ) return NULL; VPROF_BUDGET( "CEconItemSchema::GetAttributeDefinitionByName", VPROF_BUDGETGROUP_STEAM ); FOR_EACH_MAP_FAST( m_mapAttributes, i ) { Assert( m_mapAttributes[i].GetDefinitionName() ); if ( !m_mapAttributes[i].GetDefinitionName() ) continue; if ( V_stricmp( pszDefName, m_mapAttributes[i].GetDefinitionName() ) == 0 ) return &m_mapAttributes[i]; } return NULL; } const CEconItemAttributeDefinition *CEconItemSchema::GetAttributeDefinitionByName( const char *pszDefName ) const { return const_cast(this)->GetAttributeDefinitionByName( pszDefName ); } //----------------------------------------------------------------------------- // Purpose: Gets a recipe definition for an index // Input: iRecipeIndex - The index of the desired definition. // Output: A pointer to the desired definition, or NULL if it is not found. //----------------------------------------------------------------------------- CEconCraftingRecipeDefinition *CEconItemSchema::GetRecipeDefinition( int iRecipeIndex ) { int iIndex = m_mapRecipes.Find( iRecipeIndex ); if ( m_mapRecipes.IsValidIndex( iIndex ) ) return m_mapRecipes[iIndex]; return NULL; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- CEconColorDefinition *CEconItemSchema::GetColorDefinitionByName( const char *pszDefName ) { FOR_EACH_VEC( m_vecColorDefs, i ) { if ( !Q_stricmp( m_vecColorDefs[i]->GetName(), pszDefName ) ) return m_vecColorDefs[i]; } return NULL; } const CEconColorDefinition *CEconItemSchema::GetColorDefinitionByName( const char *pszDefName ) const { return const_cast(this)->GetColorDefinitionByName( pszDefName ); } #ifdef CLIENT_DLL //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- const char *CEconItemSchema::GetSteamPackageLocalizationToken( uint32 unPackageId ) const { SteamPackageLocalizationTokenMap_t::IndexType_t i = m_mapSteamPackageLocalizationTokens.Find( unPackageId ); if ( m_mapSteamPackageLocalizationTokens.IsValidIndex( i ) ) return m_mapSteamPackageLocalizationTokens[i]; return NULL; } #endif // CLIENT_DLL //----------------------------------------------------------------------------- // Purpose: Return the attribute specified attachedparticlesystem_t* associated with the given id. //----------------------------------------------------------------------------- attachedparticlesystem_t* CEconItemSchema::GetAttributeControlledParticleSystem( int id ) { int iIndex = m_mapAttributeControlledParticleSystems.Find( id ); if ( m_mapAttributeControlledParticleSystems.IsValidIndex( iIndex ) ) return &m_mapAttributeControlledParticleSystems[iIndex]; return NULL; } attachedparticlesystem_t* CEconItemSchema::FindAttributeControlledParticleSystem( const char *pchSystemName ) { FOR_EACH_MAP_FAST( m_mapAttributeControlledParticleSystems, nSystem ) { if( !Q_stricmp( m_mapAttributeControlledParticleSystems[nSystem].pszSystemName, pchSystemName ) ) return &m_mapAttributeControlledParticleSystems[nSystem]; } return NULL; } #if defined(CLIENT_DLL) || defined(GAME_DLL) bool CEconItemSchema::SetupPreviewItemDefinition( KeyValues *pKV ) { int nMapIndex = m_mapItems.Find( PREVIEW_ITEM_DEFINITION_INDEX ); if ( !m_mapItems.IsValidIndex( nMapIndex ) ) { nMapIndex = m_mapItems.Insert( PREVIEW_ITEM_DEFINITION_INDEX, CreateEconItemDefinition() ); } CEconItemDefinition *pItemDef = m_mapItems[ nMapIndex ]; return pItemDef->BInitFromKV( pKV ); } #endif // defined(CLIENT_DLL) || defined(GAME_DLL) #ifdef GC_DLL //----------------------------------------------------------------------------- // Purpose: Returns all the foreign item imports for an app ID //----------------------------------------------------------------------------- const CEconItemDefinition *CEconItemSchema::GetAppItemImport( AppId_t unAppID, uint16 usDefIndex ) const { int i = m_mapForeignImports.Find( unAppID ); if( m_mapForeignImports.IsValidIndex( i ) ) return m_mapForeignImports[i]->FindMapping( usDefIndex ); else return NULL; } //----------------------------------------------------------------------------- // Purpose: Returns all the foreign item imports for an app ID //----------------------------------------------------------------------------- bool CEconItemSchema::BInitForeignImports( CUtlVector *pVecErrors ) { FOR_EACH_MAP_FAST( m_mapItems, nItem ) { CEconItemDefinition *pDefn = m_mapItems[nItem]; KeyValues *pkvImport = pDefn->GetDefinitionKey( "import_from" ); if( !pkvImport ) continue; FOR_EACH_VALUE( pkvImport, pkvApp ) { CForeignAppImports *pAppImports = FindOrAddAppImports( Q_atoi( pkvApp->GetName() ) ); pAppImports->AddMapping( pkvApp->GetInt(), pDefn ); } } return true; } //----------------------------------------------------------------------------- // Purpose: Returns all the foreign item imports for an app ID //----------------------------------------------------------------------------- CForeignAppImports *CEconItemSchema::FindOrAddAppImports( AppId_t unAppID ) { int i = m_mapForeignImports.Find( unAppID ); if( m_mapForeignImports.IsValidIndex( i ) ) return m_mapForeignImports[i]; else { m_vecForeignApps.AddToTail( unAppID ); CForeignAppImports *pApp = new CForeignAppImports(); m_mapForeignImports.Insert( unAppID, pApp ); return pApp; } } #endif // GC_DLL bool CEconItemSchema::BCanStrangeFilterApplyToStrangeSlotInItem( uint32 /*strange_event_restriction_t*/ unRestrictionType, uint32 unRestrictionValue, const IEconItemInterface *pItem, int iStrangeSlot, uint32 *out_pOptionalScoreType ) const { Assert( unRestrictionType != kStrangeEventRestriction_None ); // Do we have a type for this slot? If not, move on, with the exception: all user-custom scores // we expect to have types, but certain weapons may not specify a base type and just use // kills implicitly. uint32 unStrangeScoreTypeBits = kKillEaterEvent_PlayerKill; if ( !pItem->FindAttribute( GetKillEaterAttr_Type( iStrangeSlot ), &unStrangeScoreTypeBits ) && iStrangeSlot != 0 ) return false; // Do we have a restriction already in this slot? If so, move on. if ( pItem->FindAttribute( GetKillEaterAttr_Restriction( iStrangeSlot ) ) ) return false; // We've found an open slot. Make sure that adding our restriction to this slot // won't result in a duplicate score-type/restriction-type entry. for ( int j = 0; j < GetKillEaterAttrCount(); j++ ) { // Don't compare against ourself. if ( iStrangeSlot == j ) continue; // Ignore this entry if we don't have a score type or if the score type differs from // our search criteria above. uint32 unAltStrangeScoreType; if ( !pItem->FindAttribute( GetKillEaterAttr_Type( j ), &unAltStrangeScoreType ) || unAltStrangeScoreType != unStrangeScoreTypeBits ) { continue; } // This entry does have the same type, so tag us as a duplicate if we also have the same // restriction that we're trying to apply. uint32 unAltRestrictionType; uint32 unAltRestrictionValue; if ( pItem->FindAttribute( GetKillEaterAttr_Restriction( j ), &unAltRestrictionType ) && unAltRestrictionType == unRestrictionType && pItem->FindAttribute( GetKillEaterAttr_RestrictionValue( j ), &unAltRestrictionValue ) && unAltRestrictionValue == unRestrictionValue ) { return false; } } if ( out_pOptionalScoreType ) { *out_pOptionalScoreType = *(float *)&unStrangeScoreTypeBits; } // Everything seems alright. return true; } //----------------------------------------------------------------------------- // Purpose: Ensure that all of our internal structures are consistent, and // account for all memory that we've allocated. // Input: validator - Our global validator object // pchName - Our name (typically a member var in our container) //----------------------------------------------------------------------------- #ifdef DBGFLAG_VALIDATE void CEconItemSchema::Validate( CValidator &validator, const char *pchName ) { VALIDATE_SCOPE(); ValidateObj( m_mapQualities ); FOR_EACH_MAP_FAST( m_mapQualities, i ) { ValidateObj( m_mapQualities[i] ); } ValidateObj( m_mapItems ); FOR_EACH_MAP_FAST( m_mapItems, i ) { ValidateObj( m_mapItems[i] ); } ValidateObj( m_mapUpgradeableBaseItems ); FOR_EACH_MAP_FAST( m_mapUpgradeableBaseItems, i ) { ValidateObj( m_mapUpgradeableBaseItems[i] ); } ValidateObj( m_mapAttributes ); FOR_EACH_MAP_FAST( m_mapAttributes, i ) { ValidateObj( m_mapAttributes[i] ); } ValidateObj( m_mapRecipes ); FOR_EACH_MAP_FAST( m_mapRecipes, i ) { ValidateObj( m_mapRecipes[i] ); } FOR_EACH_VEC( m_vecTimedRewards, i ) { ValidateObj( m_vecTimedRewards[i] ); } ValidateObj( m_vecTimedRewards ); } #endif // DBGFLAG_VALIDATE #ifdef GC_DLL //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CEconItemSchema::BInitExperiements( KeyValues *pKVExperiments, CUtlVector *pVecErrors ) { m_vecExperiments.RemoveAll(); if ( NULL != pKVExperiments ) { FOR_EACH_TRUE_SUBKEY( pKVExperiments, pKVEntry ) { const char *listName = pKVEntry->GetName(); SCHEMA_INIT_CHECK( listName != NULL, "All experiments must have titles."); int idx = m_vecExperiments.AddToTail(); SCHEMA_INIT_SUBSTEP( m_vecExperiments[idx].BInitFromKV( pKVEntry, pVecErrors ) ); } } return SCHEMA_INIT_SUCCESS(); } //----------------------------------------------------------------------------- // CExperimentDefinition //----------------------------------------------------------------------------- CExperimentDefinition::CExperimentDefinition( void ) : m_bEnabled( false ) , m_unExperimentID( 0 ) , m_unNumParticipants( 0 ) , m_unMaxParticipants( 0 ) , m_pKeyValues( NULL ) { } CExperimentDefinition::~CExperimentDefinition( void ) { if ( m_pKeyValues ) { m_pKeyValues->deleteThis(); } } bool CExperimentDefinition::BInitFromKV( KeyValues *pKVExperiment, CUtlVector *pVecErrors ) { m_pKeyValues = pKVExperiment->MakeCopy(); m_unExperimentID = Q_atoi( m_pKeyValues->GetName() ); m_bEnabled = m_pKeyValues->GetBool( "enabled" ); m_unNumParticipants = 0; m_unMaxParticipants = 0; KeyValues *pKVGroups = m_pKeyValues->FindKey( "groups" ); if ( pKVGroups ) { FOR_EACH_TRUE_SUBKEY( pKVGroups, pKVEntry ) { int idx = m_vecGroups.AddToTail(); experiment_group_t &group = m_vecGroups[idx]; group.m_pKeyValues = pKVEntry; group.m_pName = pKVEntry->GetName(); group.m_unNumParticipants = 0; group.m_unMaxParticipants = pKVEntry->GetInt( "num_participants", 0 ); m_unMaxParticipants += group.m_unMaxParticipants; } } return SCHEMA_INIT_SUCCESS(); } bool CExperimentDefinition::ChooseGroup( uint32 &unGroup ) { CUtlVector< uint32 > vecGroupIndices; FOR_EACH_VEC( m_vecGroups, i ) { experiment_group_t &group = m_vecGroups[i]; if ( group.m_unNumParticipants < group.m_unMaxParticipants ) { vecGroupIndices.AddToTail( i ); } } if ( vecGroupIndices.Count() == 0 ) { return false; } uint32 idx = vecGroupIndices[ RandomInt( 0, vecGroupIndices.Count() - 1 ) ]; experiment_group_t &group = m_vecGroups[ idx ]; ++group.m_unNumParticipants; unGroup = idx; return true; } //----------------------------------------------------------------------------- // Purpose: Give the chance after the schema has been initialized to sanity-check // individual parts or interactions before moving forward. //----------------------------------------------------------------------------- #ifdef TF_GC_DLL static bool BTestToolApplicability( CUtlVector *pVecErrors ) { static CSchemaItemDefHandle pItem_TeamPaint ( "Paint Can Team Color" ), // tools pItem_Paint ( "Paint Can 14" ), pItem_DescriptionTag ( "Description Tag" ), pItem_NameTag ( "Name Tag" ), pItem_Key ( "Decoder Ring" ), pItem_SummerKey ( "Summer Key" ), pItem_GiftWrap ( "Gift Wrap" ), pItem_CustomTextureTool ( "Customize Texture Tool" ), pItem_SupplyCrateGeneric ( "Supply Crate 2" ), // tool targets pItem_SummerCrate ( "Summer Crate" ), pItem_PaintableItem ( "Summer Hat" ), //pItem_TeamPaintableItem ( "" ), pItem_UnpaintableItem ( "Big Steel Jaw of Summer Fun" ), //pItem_UnnameableWeapon ( "" ), pItem_NameableWeapon ( "The Axtinguisher" ), pItem_GiftWrappableItem ( "Supply Crate 3" ), pItem_NonGiftWrappableItem ( "Wrapped Gift" ), pItem_StampableObject ( "The Conscientious Objector" ), pItem_NonstampableObject ( "Spiral Sallet" ); struct ToolValidityTest_t { const CEconItemDefinition *m_pTool; const CEconItemDefinition *m_pTarget; bool m_bExpectedValidity; }; ToolValidityTest_t definitionTests[] = { { pItem_TeamPaint, pItem_TeamPaint, false }, { pItem_TeamPaint, pItem_PaintableItem, true }, //{ pItem_TeamPaint, pItem_TeamPaintableItem, true }, { pItem_TeamPaint, pItem_UnpaintableItem, false }, { pItem_Paint, pItem_PaintableItem, true }, //{ pItem_Paint, pItem_TeamPaintableItem, false }, { pItem_Paint, pItem_UnpaintableItem, false }, { pItem_Paint, pItem_SupplyCrateGeneric, false }, { pItem_Key, pItem_SupplyCrateGeneric, true }, { pItem_Key, pItem_SummerCrate, false }, { pItem_SummerKey, pItem_SupplyCrateGeneric, true }, // summer keys in staging are now regular keys and should... { pItem_SummerKey, pItem_SummerCrate, false }, // ...be able to open regular crates { pItem_NameTag, pItem_NameableWeapon, true }, //{ pItem_NameTag, pItem_UnnameableWeapon, false }, { pItem_DescriptionTag, pItem_NameableWeapon, true }, //{ pItem_DescriptionTag, pItem_UnnameableWeapon, false }, { pItem_CustomTextureTool, pItem_StampableObject, true }, { pItem_CustomTextureTool, pItem_NonstampableObject, false }, }; bool bAllSuccess = true; for ( int i = 0; i < ARRAYSIZE( definitionTests ); i++ ) { const GameItemDefinition_t *pToolDef = dynamic_cast( definitionTests[i].m_pTool ), *pTargetDef = dynamic_cast( definitionTests[i].m_pTarget ); if ( !pToolDef ) { bAllSuccess = false; pVecErrors->AddToTail( CFmtStr( "Tool validity test %i failed: Tool is NULL.", i ).Access() ); continue; } if ( !pTargetDef ) { bAllSuccess = false; pVecErrors->AddToTail( CFmtStr( "Tool validity test %i failed: Target is NULL.", i ).Access() ); continue; } if ( CEconSharedToolSupport::ToolCanApplyToDefinition( pToolDef, pTargetDef ) != definitionTests[i].m_bExpectedValidity ) { bAllSuccess = false; pVecErrors->AddToTail( CFmtStr( "Tool validity test %i failed: %s %s have been able to apply to %s.", i, pToolDef->GetDefinitionName(), definitionTests[i].m_bExpectedValidity ? "should" : "shouldn't", pTargetDef->GetDefinitionName() ).Access() ); } } // These tests require actual instances of the item--not just the definitions. ToolValidityTest_t interfaceTests[] = { { pItem_GiftWrap, pItem_GiftWrappableItem, true }, { pItem_GiftWrap, pItem_NonGiftWrappableItem, false }, }; // Skip if we already have failures. if ( bAllSuccess ) { for ( int i = 0; i < ARRAYSIZE( interfaceTests ); i++ ) { const GameItemDefinition_t *pToolDef = dynamic_cast< const GameItemDefinition_t * >( interfaceTests[ i ].m_pTool ), *pTargetDef = dynamic_cast< const GameItemDefinition_t * >( interfaceTests[ i ].m_pTarget ); if ( !pToolDef ) { bAllSuccess = false; pVecErrors->AddToTail( CFmtStr( "Tool validity test %i failed: Tool is NULL.", i ).Access() ); continue; } if ( !pTargetDef ) { bAllSuccess = false; pVecErrors->AddToTail( CFmtStr( "Tool validity test %i failed: Target is NULL.", i ).Access() ); continue; } CEconItem *pTool = GEconManager()->GetItemFactory().CreateSpecificItem( ( const CEconGameAccount *) NULL, pToolDef->GetDefinitionIndex() ); CEconItem *pTarget = GEconManager()->GetItemFactory().CreateSpecificItem( ( const CEconGameAccount *) NULL, pTargetDef->GetDefinitionIndex() ); if ( CEconSharedToolSupport::ToolCanApplyTo( pTool, pTarget ) != interfaceTests[ i ].m_bExpectedValidity ) { bAllSuccess = false; pVecErrors->AddToTail( CFmtStr( "Tool validity test %i failed: %s %s have been able to apply to %s.", i, pToolDef->GetDefinitionName(), interfaceTests[ i ].m_bExpectedValidity ? "should" : "shouldn't", pTargetDef->GetDefinitionName() ).Access() ); } delete pTool; delete pTarget; } } return bAllSuccess; } #endif // TF_GC_DLL #endif // defined(GC_DLL) bool CEconItemSchema::BPostSchemaInit( CUtlVector *pVecErrors ) const { bool bAllSuccess = true; // Make sure all of our tools are valid. We have to do this after the whole schema is initialized so // that we don't run into circular reference problems with items referencing loot lists that reference // items, etc. FOR_EACH_MAP_FAST( m_mapItems, i ) { const CEconItemDefinition *pItemDef = m_mapItems[i]; const IEconTool *pTool = pItemDef->GetEconTool(); if ( pTool && !const_cast( pTool )->BFinishInitialization() ) { #ifdef GC_DLL bAllSuccess = false; pVecErrors->AddToTail( CFmtStr( "BPostSchemaInit(): tool '%s' is invalid.", pItemDef->GetDefinitionName() ).Get() ); #endif // GC_DLL } #if TF_GC_DLL else { // all cosmetic items should have these two attributes from the // cosmetic_killeater_attribs prefab in case we ever try to drop them as Strange static CSchemaAttributeDefHandle pAttribDef_KillEaterScoreType( "kill eater score type" ); static CSchemaAttributeDefHandle pAttribDef_KillEaterKillType( "kill eater kill type" ); const CTFItemDefinition *pTFItemDef = assert_cast< const CTFItemDefinition* >( pItemDef ); if ( pTFItemDef ) { int nSlot = pTFItemDef->GetLoadoutSlot( 0 ); // 0 gives use the default slot if ( ( nSlot == LOADOUT_POSITION_HEAD ) || ( nSlot == LOADOUT_POSITION_MISC ) || ( nSlot == LOADOUT_POSITION_MISC2 ) ) { bool bFoundScore = false; bool bFoundKill = false; FOR_EACH_VEC( pTFItemDef->GetStaticAttributes(), iIndex ) { const static_attrib_t& staticAttrib = pTFItemDef->GetStaticAttributes()[iIndex]; const CEconItemAttributeDefinition *pAttrDef = GetItemSchema()->GetAttributeDefinition( staticAttrib.iDefIndex ); if ( pAttrDef == pAttribDef_KillEaterScoreType ) { bFoundScore = true; } else if ( pAttrDef == pAttribDef_KillEaterKillType ) { bFoundKill = true; } } if ( !bFoundScore || !bFoundKill ) { bAllSuccess = false; pVecErrors->AddToTail( CFmtStr( "BPostSchemaInit(): '%s' is missing the standard cosmetic killeater attributes.", pItemDef->GetDefinitionName() ).Get() ); } } } } #endif // TF_GC_DLL } #if TF_GC_DLL // Make sure our tool application code validity works correctly. if ( !BTestToolApplicability( pVecErrors ) ) { bAllSuccess = false; pVecErrors->AddToTail( "BPostSchemaInit(): error with tool application validity." ); } const CEconLootListDefinition* pUnusualLootlist = GetItemSchema()->GetLootListByName( "all_particle_hats" ); if ( !pUnusualLootlist ) { bAllSuccess = false; pVecErrors->AddToTail( "No lootlist \"all_particle_hats\"" ); } else { auto& contents = pUnusualLootlist->GetLootListContents(); FOR_EACH_VEC( contents, i ) { if ( contents[ i ].m_iItemOrLootlistDef > 0 ) { const CEconItemDefinition* pItemDef = GetItemDefinition( contents[ i ].m_iItemOrLootlistDef ); if ( !( pItemDef->GetEquipRegionMask() & GetItemSchema()->GetEquipRegionBitMaskByName( "hat" ) ) && !( pItemDef->GetEquipRegionMask() & GetItemSchema()->GetEquipRegionBitMaskByName( "whole_head" ) ) ) { bAllSuccess = false; pVecErrors->AddToTail( CFmtStr( "Item \"%s\" is in all_particle_hats, but doesn't have equip region hat or whole_head, meaning it can't become unusual. REMOVE IT!", pItemDef->GetDefinitionName() ).Get() ); } } } } #endif // TF_GC_DLL return bAllSuccess; } //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- CItemLevelingDefinition::CItemLevelingDefinition( void ) { } //----------------------------------------------------------------------------- // Purpose: Copy constructor //----------------------------------------------------------------------------- CItemLevelingDefinition::CItemLevelingDefinition( const CItemLevelingDefinition &that ) { (*this) = that; } //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- CItemLevelingDefinition::~CItemLevelingDefinition() { // Free up strdup() memory. free( m_pszLocalizedName_LocalStorage ); } //----------------------------------------------------------------------------- // Purpose: Operator= //----------------------------------------------------------------------------- CItemLevelingDefinition &CItemLevelingDefinition::operator=( const CItemLevelingDefinition &other ) { m_unLevel = other.m_unLevel; m_unRequiredScore = other.m_unRequiredScore; m_pszLocalizedName_LocalStorage = strdup( other.m_pszLocalizedName_LocalStorage ); return *this; } //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- bool CItemLevelingDefinition::BInitFromKV( KeyValues *pKVItemLevel, const char *pszLevelBlockName, CUtlVector *pVecErrors ) { m_unLevel = Q_atoi( pKVItemLevel->GetName() ); m_unRequiredScore = pKVItemLevel->GetInt( "score" ); m_pszLocalizedName_LocalStorage = strdup( pKVItemLevel->GetString( "rank_name", CFmtStr( "%s%i", pszLevelBlockName, m_unLevel ).Access() ) ); return SCHEMA_INIT_SUCCESS(); } #ifdef GC_DLL EUniverse GetUniverse() { return GGCHost()->GetUniverse(); } #endif // GC_DLL