Modified source engine (2017) developed by valve and leaked in 2020. Not for commercial purporses
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

9706 lines
350 KiB

5 years ago
//========= 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 <google/protobuf/text_format.h>
#include <string.h>
#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 <memory> // 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<CUtlString> *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<CUtlString> *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<CUtlString> *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<CUtlString> *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<CUtlString> *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<CUtlString> *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<CUtlString> *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<CUtlString> *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<CUtlString> *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<const IEconItemPropertyGenerator *> *out_pvecGenerators, KeyValues *pKV, CUtlVector<CUtlString> *pVecErrors )
{
// Forward declaration so factory functions can be wherever.
IEconItemPropertyGenerator *CreateChangeQualityGenerator( KeyValues *, CUtlVector<CUtlString> * );
IEconItemPropertyGenerator *CreateRandomEvenChanceAttrGenerator( KeyValues *, CUtlVector<CUtlString> * );
IEconItemPropertyGenerator *CreateUniformLineItemLootListGenerator( KeyValues *, CUtlVector<CUtlString> * );
IEconItemPropertyGenerator *CreateDynamicAttrsGenerator( KeyValues *, CUtlVector<CUtlString> * );
// "Factory".
struct econ_item_property_generator_factory_entry_t
{
const char *m_pszGeneratorName;
IEconItemPropertyGenerator * (* m_funcCreateGeneratorInstance)( KeyValues *, CUtlVector<CUtlString> * );
};
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<CUtlString> *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<CUtlString> *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<CUtlString> *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<CEconLootListDefinition::rolled_item_defs_t>& vecItemDefsA, const CUtlVector<CEconLootListDefinition::rolled_item_defs_t>& vecItemDefsB )
{
CUtlHashtable<const CEconItemDefinition *> hashItemDefs;
auto BPopulateAndLookForDupes = [&] ( const CUtlVector<CEconLootListDefinition::rolled_item_defs_t>& 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<CEconItem *> *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<IUniformRandomStream> 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<rolled_item_defs_t> vecCumulativeItemDefs; // total list of everything we've generated so far in any number of result sets
CUtlVector<rolled_item_defs_t> 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<CEconItem *> 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, "<lootlist> <iterations> 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<attrib_value_t>( 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<CEconLootListDefinition::drop_item_t> 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<rolled_item_defs_t> *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<rolled_item_defs_t> *out_pVecRolledItems, const CUtlVector< item_definition_index_t > *pVecAvoidItemDefs ) const
{
Assert( out_pVecRolledItems );
CUtlVector< rolled_item_defs_t > vecScratchDefs;
CUtlVector<const drop_item_t*> 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; i<m_RandomAttribs.Count(); ++i )
{
const random_attrib_t* rattr = m_RandomAttribs[i];
rattr->RollRandomAttributes( 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<CUtlString> *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<CAttribute_String>( 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<CUtlString> *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<CEconItem*> *vecCraftingItems, KeyValues *out_pkvCraftParams, bool bIgnoreSlop, CUtlVector<uint64> *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<TAttribSchType *>( pRecordBase );
Assert( pTypedSch );
return pTypedSch;
#else
return static_cast<TAttribSchType *>( pRecordBase );
#endif
}
#endif // GC_DLL
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
template < GC_SCH_REFERENCE( typename TAttribSchType ) typename TAttribInMemoryType >
class CSchemaAttributeTypeBase : public ISchemaAttributeTypeBase<TAttribInMemoryType>
{
public:
#ifdef GC_DLL
virtual CColumnSet& GetFullColumnSet() const OVERRIDE
{
static CColumnSet sFullColumnSet( CColumnSet::Full<TAttribSchType>() );
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<CSchItemAttributeString>( 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<const CSchItemAttributeString>( 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<CSchItemAttributeDynamicRecipeComponentDefinedItem>( 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<const CSchItemAttributeDynamicRecipeComponentDefinedItem>( 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<CSchItemAttributeFloat>( 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<const CSchItemAttributeFloat>( 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<float *>( &((*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<const float *>( &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<CSchItemAttributeUInt64>( 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<const CSchItemAttributeUInt64>( 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<uint64 *>( &((*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<const uint64 *>( &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<CSchItemAttribute>( 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<const CSchItemAttribute>( 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<attrib_value_t *>( &((*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<const attrib_value_t *>( &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<CEconLootListDefinition::rolled_item_defs_t> 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<CUtlString> *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<CUtlString> *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<CUtlString>* 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<attrib_value_t>( 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<CUtlString> *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<const char *> *out_pVecModelStrings ) const
{
Assert( out_pVecModelStrings );
// Add base model.
out_pVecModelStrings->AddToTail( GetBasePlayerDisplayModel() );
// Add styles.
if ( GetNumStyles() )
{
for ( style_index_t i=0; i<GetNumStyles(); ++i )
{
const CEconStyleInfo *pStyle = GetStyleInfo( i );
Assert( pStyle );
pStyle->GeneratePrecacheModelStringsForStyle( 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<const char *> *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<CUtlString> *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<CUtlString> *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; i<TEAM_VISUAL_SECTIONS; i++ )
{
m_iViewmodelSkins[i] = iCommonViewmodelSkin;
}
}
// If we don't have a base entry, we look for a unique entry for each team. This will be
// handled in a subclass if necessary.
// Are we hiding additional bodygroups when this style is active?
KeyValues *pKVHideBodygroups = pKVStyle->FindKey( "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<const char *> *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<CUtlString> *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<const char *> 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<CUtlString> *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<CUtlString> *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<CUtlString> *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<CUtlString> *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<CUtlString> *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<CUtlString> *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<CUtlString> *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<CUtlString> *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<CUtlString> *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<CUtlString> *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<CUtlString> *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<CUtlString> *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<CUtlString> *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<CUtlString> *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<CUtlString> *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<CUtlString> *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<const char *> 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<CUtlString> *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<CUtlString> *pVecErrors )
{
CUtlVector<const char *> 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<CUtlString> *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<CUtlString> *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<const char *> 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<CUtlString> *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<const char *> 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<CUtlString> *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<CUtlString> *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<CUtlString> *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<CUtlString> *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<CUtlString> *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<CUtlString> *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<CUtlString> *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<CUtlString> *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<CUtlString> *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<CUtlString> *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<CUtlString> *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<CUtlString> *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<CUtlString> *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<CUtlString> *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<ID>.<AchName>
// 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<CUtlString> *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<CUtlString> *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<CUtlString> *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<CUtlString> *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<CUtlString> *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; i<ARRAYSIZE( system.pszControlPoints ); ++i )
{
system.pszControlPoints[i] = pKVEntry->GetString( 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<CUtlString> *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<CItemLevelingDefinition> *pLevelingData = new CUtlVector<CItemLevelingDefinition>;
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<CUtlString> *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<CUtlString> *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<CUtlString> *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<CItemLevelingDefinition> *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<CUtlString> *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<CEconItemSchema *>(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<CEconItemSchema *>(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<CEconItemSchema *>(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<CEconItemSchema *>(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<CEconItemSchema *>(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<CUtlString> *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<CUtlString> *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<CUtlString> *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<CUtlString> *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<const GameItemDefinition_t *>( definitionTests[i].m_pTool ),
*pTargetDef = dynamic_cast<const GameItemDefinition_t *>( 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<CUtlString> *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<IEconTool *>( 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<CUtlString> *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