summaryrefslogtreecommitdiff
path: root/game/shared/tf/tf_item_schema.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'game/shared/tf/tf_item_schema.cpp')
-rw-r--r--game/shared/tf/tf_item_schema.cpp3219
1 files changed, 3219 insertions, 0 deletions
diff --git a/game/shared/tf/tf_item_schema.cpp b/game/shared/tf/tf_item_schema.cpp
new file mode 100644
index 0000000..d8925a3
--- /dev/null
+++ b/game/shared/tf/tf_item_schema.cpp
@@ -0,0 +1,3219 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#include "cbase.h"
+#include "game_item_schema.h"
+#include "schemainitutils.h"
+#include "tf_shareddefs.h"
+#include "tf_item_tools.h"
+#include "in_buttons.h"
+#include "econ_holidays.h"
+
+#ifndef GC_DLL
+ #include "econ_item_system.h"
+ #include "tf_quest_restriction.h"
+ #include "engine/IEngineSound.h"
+
+ extern ISoundEmitterSystemBase *soundemitterbase;
+#endif // !GC_DLL
+
+#ifdef CLIENT_DLL
+ #include "materialsystem/itexturecompositor.h"
+#endif
+
+extern const char *s_pszMatchGroups[];
+
+
+// For a particular set of KeyValues, ensure that all of the one-level-deep subkeys are a subset of the values in testKeys.
+// This ensures that there are no typos in the keynames.
+static bool ValidateKeysAreSubset( KeyValues* kv, const CUtlVector<const char *>& testKeys, CUtlVector<CUtlString> *pVecErrors )
+{
+ int numTestEntries = testKeys.Count();
+ Assert(numTestEntries >= 0);
+ if (numTestEntries == 0)
+ return true;
+
+ // This currently is inefficient, it's O(len(_keyvalues) * len(_testKeys)). It could easily be made faster for large N, but for small lengths
+ // cache dominates. It also has the nice property that it will show up on the profiler if it's a problem.
+ for ( KeyValues *pKey = kv->GetFirstSubKey(); pKey; pKey = pKey->GetNextKey() )
+ {
+ bool matchAny = false;
+ const char* testVal = pKey->GetName();
+
+ for ( auto it = testKeys.begin(); it != testKeys.end(); ++it ) {
+ if (0 == V_stricmp((*it), testVal)) {
+ matchAny = true;
+ break;
+ }
+ }
+
+ if (!matchAny)
+ {
+ if (pVecErrors)
+ {
+ CUtlString choices(CFmtStr("Unexpected key '%s', expected one of: ", testVal));
+ int numTestEntriesLessOne = numTestEntries - 1;
+ for (int i = 0; i < numTestEntriesLessOne; ++i)
+ {
+ choices.Append(CFmtStr("\"%s\", ", testKeys[i]));
+ }
+
+ choices.Append(CFmtStr("\"%s\".", testKeys[numTestEntriesLessOne]));
+ pVecErrors->AddToTail(choices);
+ }
+
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool SchemaMMGroup_t::IsCategoryValid() const
+{
+ FOR_EACH_VEC( m_vecModes, i )
+ {
+ if ( m_vecModes[i]->PassesRestrictions() )
+ return true;
+ }
+
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// 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 CTFCraftingRecipeDefinition::ItemListMatchesInputs( CUtlVector<CEconItem*> *vecCraftingItems, KeyValues *out_pkvCraftParams, bool bIgnoreSlop, CUtlVector<uint64> *vecChosenItems ) const
+{
+ CUtlVector<CEconItem*> vecTmp;
+ vecTmp = *vecCraftingItems;
+
+ int hack_iForcedClass = -1,
+ hack_iForcedSlot = LOADOUT_POSITION_INVALID;
+ const CEconItemSetDefinition *hack_pForcedItemSetDef = NULL;
+
+ int *iForcedClass = NULL,
+ *iForcedSlot = NULL;
+ const CEconItemSetDefinition **ppForcedItemSetDef = NULL;
+
+ if ( out_pkvCraftParams )
+ {
+ iForcedClass = &hack_iForcedClass;
+ iForcedSlot = &hack_iForcedSlot;
+ ppForcedItemSetDef = &hack_pForcedItemSetDef;
+ }
+
+ // If we require all items to be used by the same class, find the matching classes for all items
+ CBitVec<LOADOUT_COUNT> iCUForAllItems;
+ if ( m_bRequiresAllSameClass )
+ {
+ iCUForAllItems.SetAll();
+ for ( int iVC = 0; iVC < vecCraftingItems->Count(); iVC++ )
+ {
+ const CTFItemDefinition *pDef = GetItemSchema()->GetTFItemDefinition( vecCraftingItems->Element(iVC)->GetDefinitionIndex() );
+ const CBitVec<LOADOUT_COUNT> *pDefCU = pDef->GetClassUsability();
+ if ( pDefCU )
+ {
+ iCUForAllItems.And( *pDefCU, &iCUForAllItems );
+ }
+ }
+
+ // If we didn't find a single class that all items can be used by, we're done
+ if ( iCUForAllItems.IsAllClear() )
+ return false;
+ }
+
+ CBitVec<LOADOUT_COUNT> bvSlotCoverage;
+ bvSlotCoverage.SetAll();
+
+ for ( int i = 0; i < m_InputItemsCriteria.Count(); i++ )
+ {
+ int32 iDefFound = -1;
+
+ // Find the required count of each item
+ for ( int iItem = 0; iItem < vecTmp.Count(); iItem++ )
+ {
+ CTFItemDefinition *pItemDef = GetItemSchema()->GetTFItemDefinition( vecTmp[iItem]->GetDefinitionIndex() );
+ if ( m_InputItemsCriteria[i].BEvaluate( pItemDef ) )
+ {
+ if ( m_bRequiresAllSameSlot )
+ {
+ CBitVec<LOADOUT_COUNT> bvSlots;
+ pItemDef->FilloutSlotUsage( &bvSlots );
+ bvSlotCoverage.And( bvSlots, &bvSlotCoverage );
+
+ // If we have no slots that are used by all our weapons, we're done
+ if ( bvSlotCoverage.IsAllClear() )
+ return false;
+ }
+
+ if ( iForcedClass && m_iCacheClassUsageForOutputFromItem == i )
+ {
+ // If the item def has a class_token_id key, we use that. Otherwise, we find a class that uses it.
+ const char *pszToken = pItemDef->GetClassToken();
+ if ( pszToken && pszToken[0] )
+ {
+ *iForcedClass = StringFieldToInt( pszToken, GetItemSchema()->GetClassUsabilityStrings() );
+ }
+ else if ( *iForcedClass == -1 )
+ {
+ const CBitVec<LOADOUT_COUNT> *pCU;
+ if ( m_bRequiresAllSameClass )
+ {
+ // We need to find the first class that can use all the items
+ pCU = &iCUForAllItems;
+ }
+ else
+ {
+ pCU = pItemDef->GetClassUsability();
+ }
+
+ // Find the first class
+ if ( pCU )
+ {
+ for ( int iCU = 0; iCU < LOADOUT_COUNT; iCU++ )
+ {
+ if ( pCU->IsBitSet(iCU) )
+ {
+ *iForcedClass = iCU;
+ break;
+ }
+ }
+ }
+
+ // If we need to be the same class, but couldn't find a common class across the items, we're done.
+ if ( m_bRequiresAllSameClass && *iForcedClass == -1 )
+ return false;
+ }
+ }
+
+ if ( ppForcedItemSetDef && m_iCacheSetForOutputFromItem == i )
+ {
+ // If they've passed in a set item, remember it's set index
+
+ // Abort if they somehow have an item here that doesn't have a set index
+ const CEconItemSetDefinition *pItemSetDef = pItemDef->GetItemSetDefinition();
+ if ( !pItemSetDef )
+ return false;
+
+ *ppForcedItemSetDef = pItemSetDef;
+ }
+
+ if ( iForcedSlot && m_iCacheSlotUsageForOutputFromItem == i )
+ {
+ // If the item def has a slot_token_id key, we use that. Otherwise, we find the first class that uses it.
+ const char *pszToken = pItemDef->GetSlotToken();
+ if ( pszToken && pszToken[0] )
+ {
+ *iForcedSlot = StringFieldToInt( pszToken, GetItemSchema()->GetLoadoutStrings( EQUIP_TYPE_CLASS ) );
+ }
+ else if ( *iForcedSlot == LOADOUT_POSITION_INVALID )
+ {
+ // If we have a forced class, we find the slot that class uses. Otherwise, we find the first slot used.
+ if ( iForcedClass )
+ {
+ *iForcedSlot = pItemDef->GetLoadoutSlot( *iForcedClass );
+ }
+ else
+ {
+ *iForcedSlot = pItemDef->GetLoadoutSlot( 0 );
+ }
+ }
+ }
+
+ // Matched. Remove the item and continue
+ iDefFound = pItemDef->GetDefinitionIndex();
+
+ if ( vecChosenItems )
+ {
+ vecChosenItems->AddToTail( vecTmp[iItem]->GetItemID() );
+ }
+
+ vecTmp.Remove(iItem);
+ break;
+ }
+ }
+
+ if ( iDefFound == -1 )
+ return false;
+
+ // If we want dupes of the above item, look for them
+ int iDupes = (int)m_InputItemDupeCounts[i];
+ if ( iDupes > 1 )
+ {
+ iDupes--;
+ for ( int iItem = vecTmp.Count()-1; iItem >= 0; iItem-- )
+ {
+ if ( (int)vecTmp[iItem]->GetDefinitionIndex() == iDefFound )
+ {
+ vecTmp.Remove(iItem);
+ iDupes--;
+ }
+ }
+
+ if ( iDupes != 0 )
+ return false;
+ }
+ }
+
+ if ( out_pkvCraftParams )
+ {
+ out_pkvCraftParams->SetInt( "forced_class", hack_iForcedClass );
+ out_pkvCraftParams->SetInt( "forced_slot", hack_iForcedSlot );
+
+ if ( hack_pForcedItemSetDef )
+ {
+ out_pkvCraftParams->SetString( "forced_set_def_name", hack_pForcedItemSetDef->m_pszName );
+ }
+ }
+
+ // We've only matched if there aren't any leftover items, or we're ignoring slop
+ return ( vecTmp.Count() == 0 || bIgnoreSlop );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CTFCraftingRecipeDefinition::CanMatchAgainstBackpack( CUtlVector<CEconItem*> *vecAllItems, CUtlVector<CEconItem*> vecItemsByClass[ LOADOUT_COUNT ], CUtlVector<CEconItem*> vecItemsBySlot[ CLASS_LOADOUT_POSITION_COUNT ], CUtlVector<uint64> *vecChosenItems ) const
+{
+ // If we require all the same class, examine the class lists individually
+ if ( m_bRequiresAllSameClass )
+ {
+ for (int iClass = 0; iClass < CLASS_LOADOUT_POSITION_COUNT; iClass++ )
+ {
+ if ( !vecItemsByClass[iClass].Count() )
+ continue;
+
+ if ( CheckSubItemListAgainstBackpack( &vecItemsByClass[iClass], vecChosenItems ) )
+ return true;
+ }
+
+ return false;
+ }
+
+ // If we require all the same slot, examine the slot lists individually
+ if ( m_bRequiresAllSameSlot )
+ {
+ for (int iSlot = 0; iSlot < CLASS_LOADOUT_POSITION_COUNT; iSlot++ )
+ {
+ if ( !vecItemsBySlot[iSlot].Count() )
+ continue;
+
+ if ( CheckSubItemListAgainstBackpack( &vecItemsBySlot[iSlot], vecChosenItems ) )
+ return true;
+ }
+
+ return false;
+ }
+
+ return CheckSubItemListAgainstBackpack( vecAllItems, vecChosenItems );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CTFCraftingRecipeDefinition::CheckSubItemListAgainstBackpack( CUtlVector<CEconItem*> *vecCraftingItems, CUtlVector<uint64> *vecChosenItems ) const
+{
+ CUtlVector<CEconItem*> vecTmp;
+ vecTmp = *vecCraftingItems;
+
+ CBitVec<LOADOUT_COUNT> bvSlotCoverage;
+ bvSlotCoverage.SetAll();
+
+ int iForcedClass = 0;
+ int iForcedSlot = 0;
+
+ for ( int i = 0; i < m_InputItemsCriteria.Count(); i++ )
+ {
+ int32 iDefFound = -1;
+
+ // Find the required count of each item
+ for ( int iItem = 0; iItem < vecTmp.Count(); iItem++ )
+ {
+ CTFItemDefinition *pItemDef = GetItemSchema()->GetTFItemDefinition( vecTmp[iItem]->GetDefinitionIndex() );
+ if ( m_InputItemsCriteria[i].BEvaluate( pItemDef ) )
+ {
+ if ( m_iCacheClassUsageForOutputFromItem == i )
+ {
+ // If the item def has a class_token_id key, we use that. Otherwise, we find a class that uses it.
+ const char *pszToken = pItemDef->GetClassToken();
+ if ( pszToken && pszToken[0] )
+ {
+ iForcedClass = StringFieldToInt( pszToken, GetItemSchema()->GetClassUsabilityStrings() );
+ }
+ else if ( iForcedClass == -1 )
+ {
+ const CBitVec<LOADOUT_COUNT> *pCU;
+ if ( m_bRequiresAllSameClass )
+ {
+ // We need to find the first class that can use all the items
+ pCU = pItemDef->GetClassUsability();//&iCUForAllItems;
+ }
+ else
+ {
+ pCU = pItemDef->GetClassUsability();
+ }
+
+ // Find the first class
+ if ( pCU )
+ {
+ for ( int iCU = 0; iCU < LOADOUT_COUNT; iCU++ )
+ {
+ if ( pCU->IsBitSet(iCU) )
+ {
+ iForcedClass = iCU;
+ break;
+ }
+ }
+ }
+
+ // If we need to be the same class, but couldn't find a common class across the items, we're done.
+ if ( m_bRequiresAllSameClass && iForcedClass == -1 )
+ return false;
+ }
+ }
+
+ if ( iForcedSlot && m_iCacheSlotUsageForOutputFromItem == i )
+ {
+ // If the item def has a slot_token_id key, we use that. Otherwise, we find the first class that uses it.
+ const char *pszToken = pItemDef->GetSlotToken();
+ if ( pszToken && pszToken[0] )
+ {
+ iForcedSlot = StringFieldToInt( pszToken, GetItemSchema()->GetLoadoutStrings( EQUIP_TYPE_CLASS ) );
+ }
+ else if ( iForcedSlot == LOADOUT_POSITION_INVALID )
+ {
+ // If we have a forced class, we find the slot that class uses. Otherwise, we find the first slot used.
+ if ( iForcedClass )
+ {
+ iForcedSlot = pItemDef->GetLoadoutSlot( iForcedClass );
+ }
+ else
+ {
+ iForcedSlot = pItemDef->GetLoadoutSlot( 0 );
+ }
+ }
+ }
+
+ // Found a match.
+ iDefFound = pItemDef->GetDefinitionIndex();
+ bool bValidMatch = true;
+
+ // If we want dupes of the above item, look for them before settling on this item.
+ int iDupes = (int)m_InputItemDupeCounts[i];
+ if ( iDupes > 1 )
+ {
+ CUtlVector<int> vecDupeItems;
+
+ // We've already found one of the items.
+ iDupes--;
+ for ( int iDupeItem = vecTmp.Count()-1; iDupeItem >= 0 && iDupes > 0; iDupeItem-- )
+ {
+ // Ignore the item we first found
+ if ( iDupeItem == iItem )
+ continue;
+
+ if ( (int)vecTmp[iDupeItem]->GetDefinitionIndex() == iDefFound )
+ {
+ vecDupeItems.AddToTail( iDupeItem );
+ iDupes--;
+ }
+ }
+
+ bValidMatch = (iDupes == 0);
+ if ( bValidMatch )
+ {
+ // We found all the dupes we wanted, so remove them all
+ FOR_EACH_VEC( vecDupeItems, iDupeItem )
+ {
+ if ( vecChosenItems )
+ {
+ vecChosenItems->AddToTail( vecTmp[ vecDupeItems[iDupeItem] ]->GetItemID() );
+ }
+ vecTmp.Remove( vecDupeItems[iDupeItem] );
+ }
+ }
+ }
+
+ if ( bValidMatch )
+ {
+ if ( vecChosenItems )
+ {
+ vecChosenItems->AddToTail( vecTmp[iItem]->GetItemID() );
+ }
+ vecTmp.Remove(iItem);
+ break;
+ }
+ }
+ }
+
+ if ( iDefFound == -1 )
+ return false;
+ }
+
+ return true;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+static void InitPerClassStringArray( KeyValues *pPerClassData, const char *(&outputArray)[LOADOUT_COUNT] )
+{
+ if ( pPerClassData )
+ {
+ const char* pszBaseName = pPerClassData->GetString( "basename", NULL );
+
+ for ( int i = TF_FIRST_NORMAL_CLASS; i < TF_LAST_NORMAL_CLASS; i++ )
+ {
+ if ( outputArray[i] && *outputArray[i] )
+ {
+ delete outputArray[i];
+ outputArray[i] = NULL;
+ }
+
+ char* pszOut = NULL;
+
+ CUtlString strClassString( pPerClassData->GetString( GetItemSchema()->GetClassUsabilityStrings()[i], NULL ) );
+
+ // If there's a class specific string defined, use that
+ if ( !strClassString.IsEmpty() )
+ {
+ size_t nLength = strClassString.Length() + 1;
+ pszOut = new char[ nLength ];
+ V_strncpy( pszOut, strClassString, nLength );
+ }
+ else if ( pszBaseName )
+ {
+ // If we have a basename specified, use that to construct our class-specific string
+ // ( ex. models/badge_%s.mdl turns into models/badge_scout.mdl, etc. )
+
+ // So this is fun. ClassUsabilityStrings refers to the "Demoman", but the vast majority of his models are whatever_demo.mdl
+ // The RIGHT fix would be to either:
+ // 1) change all the model and content files to whatever_demoman.mdl
+ // 2) fixup the schema so every reference to "demoman" is changed to "demo" and update GetClassUsabilityStrings
+ // and fix everything that breaks
+ // But we're not doing that right now. If this class is the TF_CLASS_DEMOMAN, just force "demo"
+ CFmtStr fmtStr;
+ if ( i == TF_CLASS_DEMOMAN )
+ {
+ fmtStr.sprintf( pszBaseName, "demo", "demo", "demo" );
+ }
+ else
+ {
+ fmtStr.sprintf( pszBaseName, GetItemSchema()->GetClassUsabilityStrings()[i], GetItemSchema()->GetClassUsabilityStrings()[i], GetItemSchema()->GetClassUsabilityStrings()[i] );
+ }
+
+ int nLength = fmtStr.Length() + 1;
+ pszOut = new char[ nLength ];
+ V_strncpy( pszOut, fmtStr, nLength );
+ }
+
+ outputArray[i] = pszOut;
+
+ if ( outputArray[0] == NULL )
+ {
+ outputArray[0] = outputArray[i];
+ }
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+static bool InitPerClassStringVectorArray( KeyValues *pPerClassData, CUtlVector< const char * > (&outputArray)[LOADOUT_COUNT], CUtlVector<CUtlString>* pVecErrors )
+{
+ if ( pPerClassData )
+ {
+ if ( !ValidateKeysAreSubset( pPerClassData, GetItemSchema()->GetClassUsabilityStrings(), pVecErrors ) )
+ {
+ return false;
+ }
+
+ for ( int i = 1; i < LOADOUT_COUNT; i++ )
+ {
+ KeyValues *pClassKey = pPerClassData->FindKey( GetItemSchema()->GetClassUsabilityStrings()[i] );
+ if ( pClassKey )
+ {
+ // check single line case
+ const char *pszValue = pClassKey->GetString();
+ if ( pszValue && *pszValue )
+ {
+ outputArray[i].AddToTail( pszValue );
+ }
+
+ // check multi line case
+ FOR_EACH_SUBKEY( pClassKey, pValueKey )
+ {
+ pszValue = pValueKey->GetString();
+ if ( pszValue && *pszValue )
+ {
+ outputArray[i].AddToTail( pszValue );
+ }
+ }
+ }
+ }
+ }
+
+ return true;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CRandomChanceString::CRandomChanceString()
+{
+ m_unTotalChance = 0;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CRandomChanceString::AddString( const char *pszString, int nChance )
+{
+ Assert( nChance > 0 );
+ std::pair< const char *, int > toAdd( pszString, nChance );
+ m_vecChoices.AddToTail( toAdd );
+ m_unTotalChance += nChance;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+const char *CRandomChanceString::GetRandomString() const
+{
+ int nRandomRoll = RandomInt( 1, m_unTotalChance );
+ int nStartWindow = 0;
+ FOR_EACH_VEC( m_vecChoices, i )
+ {
+ int nEndWindow = nStartWindow + m_vecChoices[i].second;
+ if ( nRandomRoll > nStartWindow && nRandomRoll <= nEndWindow )
+ {
+ return m_vecChoices[i].first;
+ }
+ nStartWindow = nEndWindow;
+ }
+
+ return NULL;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+static bool ParseRandomChanceStringFromKV( KeyValues *pClassKey, CRandomChanceString *pOut )
+{
+ Assert( pClassKey );
+ Assert( pOut );
+
+ // check single line case
+ const char *pszName = pClassKey->GetString();
+ if ( pszName && *pszName )
+ {
+ // there's only one choice
+ pOut->AddString( pszName, 1 );
+ }
+ else
+ {
+ // check multi line case
+ FOR_EACH_SUBKEY( pClassKey, pValueKey )
+ {
+ const char *pszChoice = pValueKey->GetName();
+ int nChance = pValueKey->GetInt();
+ if ( pszChoice && *pszChoice && nChance > 0 )
+ {
+ pOut->AddString( pszChoice, nChance );
+ }
+ }
+ }
+
+ return true;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+static bool InitPerClassRandomChanceStringArray( KeyValues *pPerClassData, CRandomChanceString (&outputArray)[LOADOUT_COUNT], CUtlVector<CUtlString>* pVecErrors )
+{
+ if ( pPerClassData )
+ {
+ if ( !ValidateKeysAreSubset( pPerClassData, GetItemSchema()->GetClassUsabilityStrings(), pVecErrors ) )
+ {
+ return false;
+ }
+
+ for ( int i = 0; i < LOADOUT_COUNT; i++ )
+ {
+ KeyValues *pClassKey = pPerClassData->FindKey( GetItemSchema()->GetClassUsabilityStrings()[i] );
+ if ( pClassKey )
+ {
+ ParseRandomChanceStringFromKV( pClassKey, &outputArray[i] );
+ }
+ }
+ }
+
+ return true;
+}
+
+
+CTFTauntInfo::CTFTauntInfo()
+{
+ for ( int i=0; i<LOADOUT_COUNT; ++i )
+ {
+ m_pszProp[i] = NULL;
+ m_pszPropIntroScene[i] = NULL;
+ m_pszPropOutroScene[i] = NULL;
+ }
+
+ m_pszParticleAttachment = NULL;
+
+ m_flTauntSeparationForwardDistance = 0;
+ m_flTauntSeparationRightDistance = 0;
+ m_flMinTauntTime = 2.f;
+ m_bIsPartnerTaunt = false;
+ m_bStopTauntIfMoved = false;
+
+ m_nFOV = 0;
+ m_flCameraDist = 0;
+ m_flCameraDistUp = -15;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CTFTauntInfo::InitTauntInputRemap( KeyValues *pKV, CUtlVector<CUtlString> *pVecErrors )
+{
+ static const char *s_pszAllowedTauntInputButtonNames[] =
+ {
+ "IN_ATTACK",
+ "IN_ATTACK2",
+ "IN_FORWARD",
+ "IN_BACK"
+ };
+ static int s_iAllowedTauntInputButtons[] =
+ {
+ IN_ATTACK,
+ IN_ATTACK2,
+ IN_FORWARD,
+ IN_BACK
+ };
+ COMPILE_TIME_ASSERT( ARRAYSIZE( s_pszAllowedTauntInputButtonNames ) == ARRAYSIZE( s_iAllowedTauntInputButtons ) );
+
+ FOR_EACH_SUBKEY( pKV, pButtonKey )
+ {
+ const char *pszButtonName = pButtonKey->GetName();
+ int iButton = 0;
+ for ( int i=0; i<ARRAYSIZE( s_pszAllowedTauntInputButtonNames ); i++ )
+ {
+ if ( !V_strcmp( pszButtonName, s_pszAllowedTauntInputButtonNames[i] ) )
+ {
+ iButton = s_iAllowedTauntInputButtons[i];
+ break;
+ }
+ }
+
+ if ( iButton == 0 )
+ {
+ AssertMsg( 0, "Taunt input button [%s] is not valid.\n", pszButtonName );
+ return false;
+ }
+
+ KeyValues *pPressedKey = pButtonKey->FindKey( "pressed" );
+ KeyValues *pReleasedKey = pButtonKey->FindKey( "released" );
+ if ( pPressedKey || pReleasedKey )
+ {
+ int iNew = m_vecTauntInputRemap.AddToTail();
+ m_vecTauntInputRemap[iNew].m_iButton = iButton;
+
+ if ( !InitPerClassStringVectorArray( pPressedKey, m_vecTauntInputRemap[iNew].m_vecButtonPressedScenes, pVecErrors ) )
+ return false;
+
+ if ( !InitPerClassStringVectorArray( pReleasedKey, m_vecTauntInputRemap[iNew].m_vecButtonReleasedScenes, pVecErrors ) )
+ return false;
+ }
+ }
+
+ return true;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CTFTauntInfo::BInitFromKV( KeyValues *pKV, CUtlVector<CUtlString> *pVecErrors )
+{
+ FOR_EACH_SUBKEY( pKV, pSubKey )
+ {
+ const char *pszKeyName = pSubKey->GetName();
+ if ( !V_strcmp( pszKeyName, "custom_taunt_scene_per_class" ) )
+ {
+ if ( !InitPerClassStringVectorArray( pSubKey, m_vecIntroScenes, pVecErrors ) )
+ return false;
+ }
+ else if ( !V_strcmp( pszKeyName, "custom_taunt_outro_scene_per_class" ) )
+ {
+ if ( !InitPerClassStringVectorArray( pSubKey, m_vecOutroScenes, pVecErrors ) )
+ return false;
+ }
+ else if ( !V_strcmp( pszKeyName, "custom_partner_taunt_per_class" ) )
+ {
+ if ( !InitPerClassStringVectorArray( pSubKey, m_vecPartnerTauntInitiatorScenes, pVecErrors ) )
+ return false;
+ if ( !InitPerClassStringVectorArray( pSubKey, m_vecPartnerTauntReceiverScenes, pVecErrors ) )
+ return false;
+ }
+ else if ( !V_strcmp( pszKeyName, "custom_partner_taunt_initiator_per_class" ) )
+ {
+ if ( !InitPerClassStringVectorArray( pSubKey, m_vecPartnerTauntInitiatorScenes, pVecErrors ) )
+ return false;
+ }
+ else if ( !V_strcmp( pszKeyName, "custom_partner_taunt_receiver_per_class" ) )
+ {
+ if ( !InitPerClassStringVectorArray( pSubKey, m_vecPartnerTauntReceiverScenes, pVecErrors ) )
+ return false;
+ }
+ else if ( !V_strcmp( pszKeyName, "custom_taunt_input_remap" ) )
+ {
+ if ( !InitTauntInputRemap( pSubKey, pVecErrors ) )
+ {
+ return false;
+ }
+ }
+ else if ( !V_strcmp( pszKeyName, "custom_taunt_prop_per_class" ) )
+ {
+ InitPerClassStringArray( pSubKey, m_pszProp );
+ }
+ else if ( !V_strcmp( pszKeyName, "custom_taunt_prop_scene_per_class" ) )
+ {
+ InitPerClassStringArray( pSubKey, m_pszPropIntroScene );
+ }
+ else if ( !V_strcmp( pszKeyName, "custom_taunt_prop_outro_scene_per_class" ) )
+ {
+ InitPerClassStringArray( pSubKey, m_pszPropOutroScene );
+ }
+ else if ( !V_strcmp( pszKeyName, "taunt_separation_forward_distance" ) )
+ {
+ m_flTauntSeparationForwardDistance = pSubKey->GetFloat();
+ }
+ else if ( !V_strcmp( pszKeyName, "taunt_separation_right_distance" ) )
+ {
+ m_flTauntSeparationRightDistance = pSubKey->GetFloat();
+ }
+ else if ( !V_strcmp( pszKeyName, "min_taunt_time" ) )
+ {
+ m_flMinTauntTime = pSubKey->GetFloat();
+ }
+ else if ( !V_strcmp( pszKeyName, "is_partner_taunt" ) )
+ {
+ m_bIsPartnerTaunt = pSubKey->GetBool();
+ }
+ else if ( !V_strcmp( pszKeyName, "stop_taunt_if_moved" ) )
+ {
+ m_bStopTauntIfMoved = pSubKey->GetBool();
+ }
+ else if ( !V_strcmp( pszKeyName, "fov" ) )
+ {
+ m_nFOV = pSubKey->GetInt();
+ }
+ else if ( !V_strcmp( pszKeyName, "camera_dist" ) )
+ {
+ m_flCameraDist = pSubKey->GetFloat();
+ }
+ else if ( !V_strcmp( pszKeyName, "camera_dist_up" ) )
+ {
+ m_flCameraDistUp = pSubKey->GetFloat();
+ }
+ else if ( !V_strcmp( pszKeyName, "particle_attachment" ) )
+ {
+ m_pszParticleAttachment = pSubKey->GetString();
+ }
+ else
+ {
+ AssertMsg( 0, "'%s' key is invalid", pszKeyName );
+ return false;
+ }
+ }
+
+ return true;
+}
+
+CQuestThemeDefinition::CQuestThemeDefinition()
+ : m_pRawKVs( NULL )
+ , m_pszName( NULL )
+ , m_pszNotificationRes( NULL )
+ , m_pszQuestItemRes( NULL )
+ , m_pszRewardString( NULL )
+ , m_pszDiscardString( NULL )
+ , m_pszInGameTrackerRes( NULL )
+ , m_eUnackPos( UNACK_ITEM_QUEST_OUTPUT )
+{
+ memset( m_vecGiveStrings, NULL, sizeof( m_vecGiveStrings ) );
+ memset( m_vecCompleteStrings, NULL, sizeof( m_vecCompleteStrings ) );
+ memset( m_vecFullyCompleteStrings, NULL, sizeof( m_vecFullyCompleteStrings ) );
+}
+
+CQuestThemeDefinition::~CQuestThemeDefinition()
+{
+ if ( m_pRawKVs )
+ {
+ m_pRawKVs->deleteThis();
+ m_pRawKVs = NULL;
+ }
+}
+
+bool CQuestThemeDefinition::BInitFromKV( KeyValues *pKV, CUtlVector<CUtlString> *pVecErrors /* = NULL */ )
+{
+ if ( m_pRawKVs )
+ {
+ m_pRawKVs->deleteThis();
+ m_pRawKVs = NULL;
+ }
+
+ m_pRawKVs = new KeyValues( pKV->GetName() );
+ MergeDefinitionPrefab( m_pRawKVs, pKV );
+
+ m_pszName = m_pRawKVs->GetName();
+ m_pszNotificationRes = m_pRawKVs->GetString( "notification_res", NULL );
+ m_pszQuestItemRes = m_pRawKVs->GetString( "quest_item_res", NULL );
+ m_pszInGameTrackerRes = m_pRawKVs->GetString( "in_game_res", NULL );
+ m_eUnackPos = (unacknowledged_item_inventory_positions_t)m_pRawKVs->GetInt( "unack_position", UNACK_ITEM_QUEST_OUTPUT );
+
+ KeyValues *pKVSounds = m_pRawKVs->FindKey( "sounds" );
+ if ( pKVSounds )
+ {
+ // "I have a mission for you"
+ KeyValues *pKVGiveSounds = pKVSounds->FindKey( "give_quest" );
+ if ( pKVGiveSounds )
+ {
+ InitPerClassRandomChanceStringArray( pKVGiveSounds, m_vecGiveStrings, pVecErrors );
+ }
+
+ // "You completed a quest"
+ KeyValues *pKVCompleteSounds = pKVSounds->FindKey( "complete_quest" );
+ if ( pKVCompleteSounds )
+ {
+ InitPerClassRandomChanceStringArray( pKVCompleteSounds, m_vecCompleteStrings, pVecErrors );
+ }
+
+ // "You completed a quest"
+ KeyValues *pKVFullyCompleteSounds = pKVSounds->FindKey( "fully_complete_quest" );
+ if ( pKVFullyCompleteSounds )
+ {
+ InitPerClassRandomChanceStringArray( pKVFullyCompleteSounds, m_vecFullyCompleteStrings, pVecErrors );
+ }
+
+ m_pszRewardString = pKVSounds->GetString( "give_reward", NULL );
+ m_pszDiscardString = pKVSounds->GetString( "discard_quest", NULL );
+ m_pszOnRevealText = pKVSounds->GetString( "reveal_sound", NULL );
+ }
+
+ SCHEMA_INIT_CHECK( m_pszName != NULL, "No name given for quest theme!" );
+ SCHEMA_INIT_CHECK( m_pszNotificationRes != NULL, "No notification res file specified for theme '%s'", m_pszName );
+ SCHEMA_INIT_CHECK( m_pszQuestItemRes != NULL, "No quest item res file specified for theme '%s'", m_pszName );
+ SCHEMA_INIT_CHECK ( m_pszInGameTrackerRes != NULL, "No in game tracker res file specified for theme '%s'", m_pszName );
+
+ return SCHEMA_INIT_SUCCESS();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CQuestDefinition::CQuestDefinition( void )
+{}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CQuestDefinition::BInitFromKV( KeyValues *pKVItem, CUtlVector<CUtlString> *pVecErrors )
+{
+ KeyValues* pKVOjectives = pKVItem->FindKey( "objectives" );
+ if ( pKVOjectives )
+ {
+ FOR_EACH_TRUE_SUBKEY( pKVOjectives, pKVObj )
+ {
+ const CQuestObjectiveDefinition* pObjective = NULL;
+ SCHEMA_INIT_SUBSTEP( GEconItemSchema().AddQuestObjective( &pObjective, pKVObj, pVecErrors ) );
+ SCHEMA_INIT_CHECK( pObjective != NULL, "Could not create quest objective" );
+
+ m_vecObjectiveDefinitions.AddToTail( (CTFQuestObjectiveDefinition*)pObjective );
+ }
+ }
+
+ m_nNumObjectivesToRoll = (uint16)pKVItem->GetInt( "objectives_to_roll", 0 );
+ SCHEMA_INIT_CHECK( m_nNumObjectivesToRoll >= 0, "Num objectives to roll is < 0!" );
+ SCHEMA_INIT_CHECK( m_nNumObjectivesToRoll <= m_vecObjectiveDefinitions.Count(), "Num objectives to roll is greater than the number of objectives" );
+
+ m_pszRewardLootlistName = pKVItem->GetString( "reward", NULL );
+ SCHEMA_INIT_CHECK( m_pszRewardLootlistName != NULL, "No reward specified for quest!" );
+
+ m_nMaxStandardPoints = pKVItem->GetInt( "max_standard_points" );
+ m_nMaxBonusPoints = pKVItem->GetInt( "max_bonus_points" );
+
+
+ m_pszQuestThemeName = pKVItem->GetString( "theme", NULL );
+ SCHEMA_INIT_CHECK( m_pszQuestThemeName != NULL, "Invalid quest theme \"%s\"", m_pszQuestThemeName );
+
+ m_pszCorrespondingOperationName = pKVItem->GetString( "operation", NULL );
+ SCHEMA_INIT_CHECK( m_pszCorrespondingOperationName != NULL, "Quest missing \"operation\"!" );
+
+ KeyValues* pKVSDescriptions = pKVItem->FindKey( "descriptions" );
+ if ( pKVSDescriptions )
+ {
+ FOR_EACH_TRUE_SUBKEY( pKVSDescriptions, pKVDesc )
+ {
+ const char* pszDescToken = pKVDesc->GetString( "token", NULL );
+ SCHEMA_INIT_CHECK( pszDescToken != NULL, "Description token not set!" );
+
+ m_vecQuestDescriptions.AddToTail( pszDescToken );
+ }
+ }
+
+ KeyValues* pKVNamesBlock = pKVItem->FindKey( "names" );
+ if ( pKVNamesBlock )
+ {
+ FOR_EACH_TRUE_SUBKEY( pKVNamesBlock, pKVName )
+ {
+ const char* pszNameToken = pKVName->GetString( "token", NULL );
+ SCHEMA_INIT_CHECK( pszNameToken != NULL, "Name token not set!" );
+
+ m_vecQuestNames.AddToTail( pszNameToken );
+ }
+ }
+
+ SCHEMA_INIT_CHECK( m_nMaxStandardPoints > 0, "Max standard points is <= 0!" );
+ SCHEMA_INIT_CHECK( m_nMaxBonusPoints >= 0, "Max bonus points is < 0!" );
+
+ m_pszQuickplayMapName = pKVItem->GetString( "quickplay_map" );
+
+ m_strMatchmakingGroupName = pKVItem->GetString( "mm_group" );
+ m_strMatchmakingCategoryName = pKVItem->GetString( "mm_category" );
+ m_strMatchmakingMapName = pKVItem->GetString( "mm_map" );
+
+ // loaner items for this quest
+ m_vecRequiredItemSets.Purge();
+ KeyValues* pKVRequiredItemsBlock = pKVItem->FindKey( "required_items" );
+ if ( pKVRequiredItemsBlock )
+ {
+ FOR_EACH_TRUE_SUBKEY( pKVRequiredItemsBlock, pRequiredItem )
+ {
+ int iNewLoaner = m_vecRequiredItemSets.AddToTail();
+ m_vecRequiredItemSets[ iNewLoaner ].BInitFromKV( pRequiredItem );
+ SCHEMA_INIT_SUBSTEP( m_vecRequiredItemSets[ iNewLoaner ].BPostInit( pVecErrors ) );
+ }
+ }
+
+ return SCHEMA_INIT_SUCCESS();
+}
+
+void CQuestDefinition::GetRolledObjectivesForItem( QuestObjectiveDefVec_t& vecRolledObjectives, const CEconItem* pItem ) const
+{
+ // See if we need to roll some optional objectives, or if we just have all of them
+ if ( m_nNumObjectivesToRoll > 0 )
+ {
+ QuestObjectiveDefVec_t vecAdvancedObjectives;
+ QuestObjectiveDefVec_t vecOptionalObjectives;
+ FOR_EACH_VEC( m_vecObjectiveDefinitions, i )
+ {
+ if ( m_vecObjectiveDefinitions[ i ]->IsAdvanced() )
+ {
+ vecAdvancedObjectives.AddToTail( m_vecObjectiveDefinitions[ i ] );
+ }
+ else if ( m_vecObjectiveDefinitions[ i ]->IsOptional() )
+ {
+ vecOptionalObjectives.AddToTail( m_vecObjectiveDefinitions[ i ] );
+ }
+ else
+ {
+ vecRolledObjectives.AddToTail( m_vecObjectiveDefinitions[ i ] );
+ }
+ }
+
+ // Figure out how many to remove
+ uint16 nNumToAdd = m_nNumObjectivesToRoll;
+
+ CUniformRandomStream randomStream;
+ // Don't use the global RNG for the shuffling
+ // Seed with the original ID
+ randomStream.SetSeed( pItem->GetOriginalID() );
+
+ // You always get 1 advanced objective
+ if ( vecAdvancedObjectives.Count() )
+ {
+ int nRandomIndex = randomStream.RandomInt( 0, vecAdvancedObjectives.Count() - 1 );
+ vecRolledObjectives.AddToTail( vecAdvancedObjectives[ nRandomIndex ] );
+ vecAdvancedObjectives.Remove( nRandomIndex );
+ --nNumToAdd;
+ }
+
+ QuestObjectiveDefVec_t vecPossibleRolls;
+ vecPossibleRolls.AddVectorToTail( vecAdvancedObjectives );
+ vecPossibleRolls.AddVectorToTail( vecOptionalObjectives );
+
+ // Roll from all rest of the optional objectives until we've got enough
+ while( nNumToAdd && vecPossibleRolls.Count() )
+ {
+ int nRandomIndex = randomStream.RandomInt( 0, vecPossibleRolls.Count() - 1 );
+ vecRolledObjectives.AddToTail( vecPossibleRolls[ nRandomIndex ] );
+ vecPossibleRolls.Remove( nRandomIndex );
+ --nNumToAdd;
+ }
+ }
+ else
+ {
+ vecRolledObjectives.AddVectorToTail( m_vecObjectiveDefinitions );
+ }
+}
+
+
+const char *CQuestDefinition::GetRolledDescriptionForItem( const CEconItem* pItem ) const
+{
+ if ( m_vecQuestDescriptions.Count() )
+ {
+ // Don't use the global RNG for the shuffling
+ CUniformRandomStream randomStream;
+ randomStream.SetSeed( pItem->GetOriginalID() );
+ return m_vecQuestDescriptions[ randomStream.RandomInt( 0, m_vecQuestDescriptions.Count() - 1 ) ];
+ }
+
+ Assert( 0 );
+ return NULL;
+}
+
+const char *CQuestDefinition::GetRolledNameForItem( const CEconItem* pItem ) const
+{
+ if ( m_vecQuestNames.Count() )
+ {
+ // Don't use the global RNG for the shuffling
+ CUniformRandomStream randomStream;
+ randomStream.SetSeed( pItem->GetOriginalID() );
+ return m_vecQuestNames[ randomStream.RandomInt( 0, m_vecQuestNames.Count() - 1 ) ];
+ }
+
+ Assert( 0 );
+ return NULL;
+}
+
+const CQuestThemeDefinition *CQuestDefinition::GetQuestTheme() const
+{
+ return GetItemSchema()->GetQuestThemeByName( m_pszQuestThemeName );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFItemDefinition::InternalInitialize()
+{
+ m_eEquipType = EQUIP_TYPE_INVALID;
+ m_iDefaultLoadoutSlot = LOADOUT_POSITION_INVALID;
+ m_iAnimationSlot = -1;
+
+ m_vbClassUsability.ClearAll();
+ for ( int i = 0; i < ARRAYSIZE( m_iLoadoutSlots ); i++ )
+ {
+ m_iLoadoutSlots[i] = LOADOUT_POSITION_INVALID;
+ m_pszPlayerDisplayModel[i] = NULL;
+ m_pszPlayerDisplayModelAlt[i] = NULL;
+ }
+
+ m_pTauntData = NULL;
+ m_pQuestData = NULL;
+
+#ifndef GC_DLL
+ m_pszAdText = NULL;
+ m_pszAdResFile = NULL;
+#endif // GC_DLL
+
+#ifdef CLIENT_DLL
+ m_bHasDetailedIcon = false;
+#endif // CLIENT_DLL
+}
+#include "filesystem.h"
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CTFItemDefinition::BInitFromKV( KeyValues *pKVItem, CUtlVector<CUtlString> *pVecErrors )
+{
+ CEconItemDefinition::BInitFromKV( pKVItem, pVecErrors );
+
+ // Our superclass should initialize our raw definition, including any prefab work.
+ KeyValues *pKVInitValues = GetRawDefinition();
+ Assert( pKVInitValues );
+
+ // Reset default properties.
+ InternalInitialize();
+
+ CUtlDict< EEquipType_t > dictEquipType;
+ dictEquipType.Insert( "account", EQUIP_TYPE_ACCOUNT );
+ dictEquipType.Insert( "class", EQUIP_TYPE_CLASS );
+ // Default to class equip type
+ const char *pszEquipType = pKVInitValues->GetString( "equip_type", "class" );
+ auto idx = dictEquipType.Find( pszEquipType );
+ if ( idx != dictEquipType.InvalidIndex() )
+ {
+ m_eEquipType = dictEquipType[ idx ];
+ }
+ SCHEMA_INIT_CHECK( m_eEquipType != EQUIP_TYPE_INVALID,
+ "Item definition %i \"%s\" used uknown equip type: %s!", GetDefinitionIndex(), GetItemBaseName(), pszEquipType );
+
+ // Get the default loadout slot
+ const char *pszLoadoutSlot = pKVInitValues->GetString("item_slot", "");
+ if ( *pszLoadoutSlot )
+ {
+ if ( !V_strcmp( pszLoadoutSlot, "head" ) )
+ {
+ pszLoadoutSlot = "misc";
+ }
+
+ m_iDefaultLoadoutSlot = StringFieldToInt( pszLoadoutSlot, GetItemSchema()->GetLoadoutStrings( m_eEquipType ), true );
+ SCHEMA_INIT_CHECK(
+ m_iDefaultLoadoutSlot >= 0,
+ "Item definition %i \"%s\" used unknown loadout slot: %s!", GetDefinitionIndex(), GetItemBaseName(), pszLoadoutSlot );
+ }
+
+ // Class usability--use our copy of kv item
+ KeyValues *pClasses = pKVInitValues->FindKey( "used_by_classes" );
+ if ( pClasses )
+ {
+ KeyValues *pKVClass = pClasses->GetFirstSubKey();
+ while ( pKVClass )
+ {
+ int iClass = StringFieldToInt( pKVClass->GetName(), GetItemSchema()->GetClassUsabilityStrings() );
+ if ( iClass > -1 )
+ {
+ m_vbClassUsability.Set(iClass);
+ m_iLoadoutSlots[iClass] = m_iDefaultLoadoutSlot;
+
+ // If the value is "1", the class uses this item in the default loadout slot.
+ const char *pszValue = pKVClass->GetString();
+ if ( pszValue[0] != '1' )
+ {
+ int iSlot = StringFieldToInt( pszValue, GetItemSchema()->GetLoadoutStrings( EQUIP_TYPE_CLASS ) );
+ Assert( iSlot != -1 );
+ if ( iSlot != -1 )
+ {
+ m_iLoadoutSlots[iClass] = iSlot;
+ }
+ }
+ }
+
+ pKVClass = pKVClass->GetNextKey();
+ }
+
+ // add "all_class" if applicable
+ if ( CanBeUsedByAllClasses() )
+ {
+ KeyValues *pKVAllClassKey = new KeyValues( "all_class", "all_class", "1" );
+ pClasses->AddSubKey( pKVAllClassKey );
+ }
+ }
+
+ // Verify that no items are set up to be equipped in a wearable slot for some classes and a
+ // non-wearable slot other times. "Is this in a wearable slot?" is used to determine whether
+ // or not content can be allowed to stream, so we don't allow an item to overlap.
+ bool bHasAnyWearableSlots = false,
+ bHasAnyNonwearableSlots = false;
+
+ for ( int i = 0; i < LOADOUT_COUNT; i++ )
+ {
+ if ( m_iLoadoutSlots[i] != LOADOUT_POSITION_INVALID )
+ {
+ const bool bThisIsWearableSlot = IsWearableSlot( m_iLoadoutSlots[i] );
+
+ (bThisIsWearableSlot ? bHasAnyWearableSlots : bHasAnyNonwearableSlots) = true;
+ }
+ }
+
+ SCHEMA_INIT_CHECK(
+ !(bHasAnyWearableSlots && bHasAnyNonwearableSlots),
+ "Item definition %i \"%s\" used in both wearable and not wearable slots!", GetDefinitionIndex(), GetItemBaseName() );
+
+ // "anim_slot"
+ const char *pszAnimSlot = pKVInitValues->GetString("anim_slot");
+ if ( pszAnimSlot && pszAnimSlot[0] )
+ {
+ if ( Q_stricmp(pszAnimSlot, "FORCE_NOT_USED") == 0 )
+ {
+ m_iAnimationSlot = -2;
+ }
+ else
+ {
+ m_iAnimationSlot = StringFieldToInt( pszAnimSlot, GetItemSchema()->GetWeaponTypeSubstrings() );
+ }
+ }
+
+ InitPerClassStringArray( pKVInitValues->FindKey( "model_player_per_class" ), m_pszPlayerDisplayModel );
+ InitPerClassStringArray( pKVInitValues->FindKey( "model_player_per_class_alt" ), m_pszPlayerDisplayModelAlt );
+
+#if defined DEBUG && defined CLIENT_DLL
+ for ( int i = 0; i < m_vbClassUsability.GetNumBits(); i++ )
+ {
+ if ( m_vbClassUsability[i] && m_pszPlayerDisplayModel[ i ] )
+ {
+ // SCHEMA_INIT_CHECK( g_pFullFileSystem->FileExists( m_pszPlayerDisplayModel[ i ] ), "Missing model %s specified in model_player_per_class in item %s", m_pszPlayerDisplayModel[ i ], GetItemBaseName() );
+ }
+ }
+#endif
+
+ KeyValues *pTauntKV = pKVInitValues->FindKey( "taunt" );
+ if ( pTauntKV )
+ {
+ Assert( !m_pTauntData );
+ m_pTauntData = new CTFTauntInfo();
+ SCHEMA_INIT_CHECK(
+ m_pTauntData->BInitFromKV( pTauntKV, pVecErrors ),
+ "Item definition %i \"%s\" failed to initialize taunt data!", GetDefinitionIndex(), GetItemBaseName()
+ );
+ }
+
+ // Init quest data if we have any
+ KeyValues *pQuestKV = pKVInitValues->FindKey( "quest" );
+ if ( pQuestKV )
+ {
+ Assert( !m_pQuestData );
+ m_pQuestData = new CQuestDefinition();
+ SCHEMA_INIT_CHECK( m_pQuestData->BInitFromKV( pQuestKV, pVecErrors ), "Item def %i \"%s\" failed to initialize quest data!", GetDefinitionIndex(), GetItemBaseName() );
+ }
+
+ // Stomp duplicate properties.
+ if ( !m_pszPlayerDisplayModel[0] )
+ {
+ m_pszPlayerDisplayModel[0] = GetBasePlayerDisplayModel();
+ }
+
+ // Auto-generated tags based on slot/class.
+ m_vecTags.AddToTail( GetItemSchema()->GetHandleForTag( CFmtStr( "auto__slot_%s", pszLoadoutSlot ).Get() ) );
+
+ for ( int i = 0; i < m_vbClassUsability.GetNumBits(); i++ )
+ {
+ if ( m_vbClassUsability[i] )
+ {
+ m_vecTags.AddToTail( GetItemSchema()->GetHandleForTag( CFmtStr( "auto__class_%s", GetItemSchema()->GetClassUsabilityStrings()[i] ).Get() ) );
+ }
+ }
+
+#ifndef GC_DLL
+ m_pszAdText = pKVInitValues->GetString( "ad_text", NULL );
+ m_pszAdResFile = pKVInitValues->GetString( "ad_res_file", "Resource/UI/econ/ItemAdDefault.res" );
+#endif
+
+ const char * pszPaintKit = pKVInitValues->GetString( "item_paintkit", NULL );
+ if ( pszPaintKit )
+ {
+ int iPaintIndex = GetItemSchema()->GetItemPaintKits().Find( pszPaintKit );
+ SCHEMA_INIT_CHECK(
+ GetItemSchema()->GetItemPaintKits().IsValidIndex( iPaintIndex ),
+ "Item paintkit [%s] in definition %i \"%s\" does not exist", pszPaintKit, GetDefinitionIndex(), GetItemBaseName()
+ );
+
+ SetItemPaintKitDefinition( GetItemSchema()->GetItemPaintKits()[iPaintIndex] );
+ }
+
+#ifdef CLIENT_DLL
+ m_bHasDetailedIcon = pKVInitValues->GetBool( "has_detailed_icon" );
+#endif // CLIENT_DLL
+
+ return SCHEMA_INIT_SUCCESS();
+}
+
+#if defined(CLIENT_DLL) || defined(GAME_DLL)
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CTFItemDefinition::BInitFromTestItemKVs( int iNewDefIndex, KeyValues *pKVItem, CUtlVector<CUtlString>* pVecErrors )
+{
+ if ( !CEconItemDefinition::BInitFromTestItemKVs( iNewDefIndex, pKVItem, pVecErrors ) )
+ return false;
+
+ // Use the tester's class usage choices, even when testing existing items
+ m_vbClassUsability.ClearAll();
+ int iClassUsage = pKVItem->GetInt( "class_usage", 0 );
+ for ( int i = 0; i < LOADOUT_COUNT; i++ )
+ {
+ if ( iClassUsage & (1 << i) || (iClassUsage & 1) )
+ {
+ m_vbClassUsability.Set(i);
+ m_iLoadoutSlots[i] = m_iDefaultLoadoutSlot;
+ }
+ }
+
+ // Initialize player display model.
+ for ( int i = 0; i < LOADOUT_COUNT; i++ )
+ {
+ m_pszPlayerDisplayModel[i] = NULL;
+ m_pszPlayerDisplayModelAlt[i] = NULL;
+ }
+
+ InitPerClassStringArray( pKVItem->FindKey( "model_player_per_class" ), m_pszPlayerDisplayModel );
+ InitPerClassStringArray( pKVItem->FindKey( "model_player_per_class_alt" ), m_pszPlayerDisplayModelAlt );
+
+ KeyValues *pTauntKV = pKVItem->FindKey( "taunt" );
+ if ( pTauntKV )
+ {
+ Assert( !m_pTauntData );
+ m_pTauntData = new CTFTauntInfo();
+ if ( !m_pTauntData->BInitFromKV( pTauntKV, pVecErrors ) )
+ return false;
+ }
+
+ // Stomp duplicate properties.
+ if ( !m_pszPlayerDisplayModel[0] )
+ {
+ m_pszPlayerDisplayModel[0] = GetBasePlayerDisplayModel();
+ }
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFItemDefinition::CopyPolymorphic( const CEconItemDefinition *pSourceDef )
+{
+ Assert( dynamic_cast<const CTFItemDefinition *>( pSourceDef ) != NULL );
+
+ *this = *(const CTFItemDefinition *)pSourceDef;
+}
+#endif // defined(CLIENT_DLL) || defined(GAME_DLL)
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFStyleInfo::BInitFromKV( KeyValues *pKVStyle, CUtlVector<CUtlString> *pVecErrors )
+{
+ Assert( pKVStyle );
+
+ m_iSkins[ TF_TEAM_RED ] = pKVStyle->GetInt( "skin_red", 0 );
+ m_iSkins[ TF_TEAM_BLUE ] = pKVStyle->GetInt( "skin_blu", 0 );
+
+ m_iViewmodelSkins[ TF_TEAM_RED ] = pKVStyle->GetInt( "v_skin_red", -1 );
+ m_iViewmodelSkins[ TF_TEAM_BLUE ] = pKVStyle->GetInt( "v_skin_blu", -1 );
+
+ const char *pszPlayerModel = pKVStyle->GetString( "model_player", NULL );
+ if ( pszPlayerModel )
+ {
+ for ( int i = 0; i < LOADOUT_COUNT; i++ )
+ {
+ m_pszPlayerDisplayModel[0][i] = pszPlayerModel;
+ }
+ }
+ else
+ {
+ InitPerClassStringArray( pKVStyle->FindKey( "model_player_per_class" ), m_pszPlayerDisplayModel[0] );
+ InitPerClassStringArray( pKVStyle->FindKey( "model_player_per_class_red" ), m_pszPlayerDisplayModel[0] );
+ InitPerClassStringArray( pKVStyle->FindKey( "model_player_per_class_blue" ), m_pszPlayerDisplayModel[1] );
+ }
+
+ CEconStyleInfo::BInitFromKV( pKVStyle, pVecErrors );
+}
+
+#if defined(CLIENT_DLL) || defined(GAME_DLL)
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFItemDefinition::GeneratePrecacheModelStrings( bool bDynamicLoad, CUtlVector<const char *> *out_pVecModelStrings ) const
+{
+ Assert( out_pVecModelStrings );
+
+ // Is this definition supposed to use dynamic-loaded content or precache it?
+ if ( !bDynamicLoad || !IsContentStreamable() )
+ {
+ // Parent class base meshes, if relevant.
+ CEconItemDefinition::GeneratePrecacheModelStrings( bDynamicLoad, out_pVecModelStrings );
+
+ for ( int i = 0; i < LOADOUT_COUNT; i++ )
+ {
+ // Per-class models.
+ const char *pszModel = GetPlayerDisplayModel(i);
+ if ( pszModel && pszModel[0] )
+ {
+ out_pVecModelStrings->AddToTail( pszModel );
+ }
+
+ // Per-class alt-models
+ const char *pszModelAlt = GetPlayerDisplayModelAlt(i);
+ if ( pszModelAlt && pszModelAlt[0] )
+ {
+ out_pVecModelStrings->AddToTail( pszModelAlt );
+ }
+
+ // Per-class custom taunt prop
+ if ( GetTauntData() )
+ {
+ const char *pszCustomTauntProp = GetTauntData()->GetProp(i);
+ if ( pszCustomTauntProp && pszCustomTauntProp[0] )
+ {
+ out_pVecModelStrings->AddToTail( pszCustomTauntProp );
+ }
+ }
+ }
+
+ const char *pszModel = GetWorldDisplayModel();
+ if ( pszModel && pszModel[0] )
+ {
+ out_pVecModelStrings->AddToTail( pszModel );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFStyleInfo::GeneratePrecacheModelStringsForStyle( CUtlVector<const char *> *out_pVecModelStrings ) const
+{
+ Assert( out_pVecModelStrings );
+
+ for ( int i = 0; i < ARRAYSIZE( m_pszPlayerDisplayModel ); i++ )
+ {
+ for ( int j = 0; j < ARRAYSIZE( m_pszPlayerDisplayModel[i] ); j++ )
+ {
+ const char* pszModelName = m_pszPlayerDisplayModel[i][j];
+ if ( pszModelName && *pszModelName )
+ {
+ out_pVecModelStrings->AddToTail( pszModelName );
+ }
+ }
+ }
+
+ CEconStyleInfo::GeneratePrecacheModelStringsForStyle( out_pVecModelStrings );
+}
+#endif // defined(CLIENT_DLL) || defined(GAME_DLL)
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+const char *CTFStyleInfo::GetPlayerDisplayModel( int iClass, int iTeam ) const
+{
+ Assert( iClass >= 0 );
+ Assert( iClass < ARRAYSIZE( m_pszPlayerDisplayModel[0] ) );
+
+ const char *pszBlueModel = m_pszPlayerDisplayModel[1][iClass];
+ if ( iTeam == TF_TEAM_BLUE && pszBlueModel && *pszBlueModel )
+ {
+ return pszBlueModel;
+ }
+
+ // always return red team as default
+ return m_pszPlayerDisplayModel[0][iClass];
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Return the load-out slot that this item must be placed into
+//-----------------------------------------------------------------------------
+int CTFItemDefinition::GetLoadoutSlot( int iLoadoutClass ) const
+{
+ if ( iLoadoutClass == GEconItemSchema().GetAccountIndex() )
+ {
+ return GetAccountLoadoutSlot();
+ }
+
+ if ( iLoadoutClass <= 0 || iLoadoutClass >= LOADOUT_COUNT )
+ return m_iDefaultLoadoutSlot;
+
+ return m_iLoadoutSlots[iLoadoutClass];
+}
+
+#ifndef GC_DLL
+//-----------------------------------------------------------------------------
+// Purpose: Returns true if this item is in a wearable slot, or is acting as a wearable
+//-----------------------------------------------------------------------------
+bool CTFItemDefinition::IsAWearable() const
+{
+ if ( IsWearableSlot( GetDefaultLoadoutSlot() ) && !IsActingAsAWeapon() )
+ return true;
+
+ if ( IsActingAsAWearable() )
+ return true;
+
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Returns true if the content for this item view should be streamed. If false,
+// it should be preloaded.
+//-----------------------------------------------------------------------------
+ConVar item_enable_content_streaming( "item_enable_content_streaming", "1", FCVAR_DEVELOPMENTONLY | FCVAR_REPLICATED );
+
+bool CTFItemDefinition::IsContentStreamable() const
+{
+#if defined( WITH_STREAMABLE_WEAPONS )
+ extern ConVar tf_loadondemand_default;
+
+ // If we support streamable weapons and loadondemand_default is true, then we do not want to restrict demand loading
+ // to wearables only, so skip that check.
+ if (!tf_loadondemand_default.GetBool())
+#endif
+ {
+ if (!IsAWearable())
+ return false;
+ }
+ return item_enable_content_streaming.GetBool()
+ && CEconItemDefinition::IsContentStreamable();
+}
+#endif // !GC_DLL
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFItemDefinition::FilloutSlotUsage( CBitVec<LOADOUT_COUNT> *pBV ) const
+{
+ pBV->ClearAll();
+
+ for ( int i = 0; i < LOADOUT_COUNT; i++ )
+ {
+ if ( m_iLoadoutSlots[i] == LOADOUT_POSITION_INVALID )
+ continue;
+
+ pBV->Set( m_iLoadoutSlots[i] );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CTFItemDefinition::CanBeUsedByAllClasses( void ) const
+{
+ // Right now, Civilian isn't a real class, so we only have 9 classes in this check
+ for ( int iClass = 1; iClass < (LOADOUT_COUNT-1); iClass++ )
+ {
+ if ( !CanBeUsedByClass(iClass) )
+ return false;
+ }
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CTFItemDefinition::CanBePlacedInSlot( int nSlot ) const
+{
+ for ( int i = 0; i < LOADOUT_COUNT; i++ )
+ {
+ if ( m_iLoadoutSlots[i] == nSlot )
+ return true;
+ }
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+KeyValues *CTFItemDefinition::GetPaintKitWearDefinition( int nWear ) const
+{
+ CEconItemPaintKitDefinition *pPaintKit = GetCustomPainkKitDefinition();
+ if ( pPaintKit )
+ {
+ return pPaintKit->GetPaintKitWearKV( nWear );
+ }
+
+ return NULL;
+}
+
+//-----------------------------------------------------------------------------
+const char *CTFItemDefinition::GetPaintKitName() const
+{
+ CEconItemPaintKitDefinition *pPaintKit = GetCustomPainkKitDefinition();
+ if ( pPaintKit )
+ {
+ return pPaintKit->GetName( );
+ }
+
+ return NULL;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CTFRequiredQuestItemsSet::BInitFromKV( KeyValues *pKV, CUtlVector<CUtlString> *pVecErrors )
+{
+ KeyValues* pKVQualifyingItems = pKV->FindKey( "qualifying_items" );
+ if ( pKVQualifyingItems )
+ {
+ FOR_EACH_TRUE_SUBKEY( pKVQualifyingItems, pItem )
+ {
+ m_vecQualifyingItemDefs.AddToTail( pItem->GetInt( "defindex", INVALID_ITEM_DEF_INDEX ) );
+ }
+ }
+
+ m_LoanerItemDef = pKV->GetInt( "loaner_defindex", INVALID_ITEM_DEF_INDEX );
+
+ return SCHEMA_INIT_SUCCESS();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Make sure all the defindexes point to actual item defs
+//-----------------------------------------------------------------------------
+bool CTFRequiredQuestItemsSet::BPostInit( CUtlVector<CUtlString> *pVecErrors )
+{
+ // Verify all of the item defindex
+ FOR_EACH_VEC( m_vecQualifyingItemDefs, i )
+ {
+ const CEconItemDefinition* pItemDef = GetItemSchema()->GetItemDefinition( m_vecQualifyingItemDefs[ i ] );
+ SCHEMA_INIT_CHECK( pItemDef != NULL, "No item definition for defindex %d!", m_vecQualifyingItemDefs[ i ] );
+ }
+
+ const CEconItemDefinition* pItemDef = GetItemSchema()->GetItemDefinition( m_LoanerItemDef );
+ SCHEMA_INIT_CHECK( pItemDef != NULL, "No item definition for defindex %d!", m_LoanerItemDef );
+
+ return SCHEMA_INIT_SUCCESS();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Given a vector of item defs, check if it contains ANY of our qualifying items
+//-----------------------------------------------------------------------------
+bool CTFRequiredQuestItemsSet::OwnsRequiredItems( const CUtlVector< item_definition_index_t >& vecOwnedItemDefs ) const
+{
+ FOR_EACH_VEC( vecOwnedItemDefs, i )
+ {
+ FOR_EACH_VEC( m_vecQualifyingItemDefs, j )
+ {
+ if ( vecOwnedItemDefs[ i ] == m_vecQualifyingItemDefs[ j ] )
+ return true;
+ }
+ }
+
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CTFQuestObjectiveConditionsDefinition::CTFQuestObjectiveConditionsDefinition( void )
+ : m_nDefIndex( INVALID_QUEST_OBJECTIVE_CONDITIONS_INDEX )
+#ifndef GC_DLL
+ , m_pConditionsKey( NULL )
+#endif
+{}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CTFQuestObjectiveConditionsDefinition::~CTFQuestObjectiveConditionsDefinition( void )
+{}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CTFQuestObjectiveConditionsDefinition::BInitFromKV( KeyValues *pKVItem, CUtlVector<CUtlString> *pVecErrors )
+{
+ m_nDefIndex = atoi( pKVItem->GetName() );
+ SCHEMA_INIT_CHECK( m_nDefIndex != INVALID_QUEST_OBJECTIVE_CONDITIONS_INDEX, "Invalid quest objective conditions def index!" );
+
+ m_vecRequiredItemSets.Purge();
+
+ KeyValues* pKVRequiredItemsBlock = pKVItem->FindKey( "required_items" );
+ if ( pKVRequiredItemsBlock )
+ {
+ FOR_EACH_TRUE_SUBKEY( pKVRequiredItemsBlock, pRequiredItem )
+ {
+ m_vecRequiredItemSets[ m_vecRequiredItemSets.AddToTail() ].BInitFromKV( pRequiredItem );
+ }
+ }
+
+#ifndef GC_DLL
+ m_pConditionsKey = pKVItem->FindKey( "condition_logic" );
+ SCHEMA_INIT_CHECK( m_pConditionsKey != NULL, "Missing conditions block for condition def %d!", m_nDefIndex );
+
+ // Conditions don't get created until needed on the server, so let's create them right now
+ // as a test to make sure they're valid and fail early rather than later.
+ CTFQuestCondition *pTempConditions = NULL;
+
+ const char *pszType = m_pConditionsKey->GetString( "type" );
+ pTempConditions = CreateEvaluatorByName( pszType, NULL );
+
+ SCHEMA_INIT_CHECK( pTempConditions != NULL, "Failed to create evaluators" );
+
+ if ( !pTempConditions->BInitFromKV( m_pConditionsKey, pVecErrors ) )
+ {
+ delete pTempConditions;
+ SCHEMA_INIT_CHECK( false, "Failed to init conditions" );
+ }
+
+
+ if ( pTempConditions && pKVItem->GetBool( "spew" ) )
+ {
+ pTempConditions->PrintDebugText();
+ DevMsg( "\n" );
+ }
+
+ // clean up after test parsing quest conditions
+ delete pTempConditions;
+#endif
+
+ return SCHEMA_INIT_SUCCESS();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CTFQuestObjectiveConditionsDefinition::BPostInit( CUtlVector<CUtlString> *pVecErrors )
+{
+ // Verify all of the item defindex
+ FOR_EACH_VEC( m_vecRequiredItemSets, i )
+ {
+ SCHEMA_INIT_SUBSTEP( m_vecRequiredItemSets[i].BPostInit( pVecErrors ) );
+ }
+
+ return SCHEMA_INIT_SUCCESS();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CTFQuestObjectiveDefinition::CTFQuestObjectiveDefinition( void )
+{
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CTFQuestObjectiveDefinition::~CTFQuestObjectiveDefinition()
+{
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Init our restrictions
+//-----------------------------------------------------------------------------
+bool CTFQuestObjectiveDefinition::BInitFromKV( KeyValues *pKVItem, CUtlVector<CUtlString> *pVecErrors /* = NULL */ )
+{
+ if ( !CQuestObjectiveDefinition::BInitFromKV( pKVItem, pVecErrors ) )
+ return false;
+
+ m_nConditionDefIndex = pKVItem->GetInt( "conditions_def_index", INVALID_QUEST_OBJECTIVE_CONDITIONS_INDEX );
+ SCHEMA_INIT_CHECK( GetItemSchema()->GetQuestObjectiveConditionByDefIndex( m_nConditionDefIndex ) != NULL, "Could not find quest objective conditions for defindex %d!", m_nConditionDefIndex );
+
+ return SCHEMA_INIT_SUCCESS();
+}
+
+const CTFQuestObjectiveConditionsDefinition* CTFQuestObjectiveDefinition::GetConditions() const
+{
+ return GetItemSchema()->GetQuestObjectiveConditionByDefIndex( m_nConditionDefIndex );
+}
+
+#ifndef GC_DLL
+KeyValues *CTFQuestObjectiveDefinition::GetConditionsKeyValues() const
+{
+ const CTFQuestObjectiveConditionsDefinition* pDef = GetItemSchema()->GetQuestObjectiveConditionByDefIndex( m_nConditionDefIndex );
+ if ( pDef )
+ {
+ return pDef->GetKeyValues();
+ }
+
+ return NULL;
+}
+#endif
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+// Used to convert strings to ints for class usability
+struct PlayerClassInfo_t
+{
+ const char *m_pchName;
+ const char *m_pchLocalizationKey;
+};
+
+static PlayerClassInfo_t gs_PlayerClassData[] =
+{
+ { "Undefined", "#TF_Class_Name_Undefined" },
+ { "Scout", "#TF_Class_Name_Scout" },
+ { "Sniper", "#TF_Class_Name_Sniper" },
+ { "Soldier", "#TF_Class_Name_Soldier" },
+ { "Demoman", "#TF_Class_Name_Demoman" },
+ { "Medic", "#TF_Class_Name_Medic" },
+ { "Heavy", "#TF_Class_Name_HWGuy" },
+ { "Pyro", "#TF_Class_Name_Pyro" },
+ { "Spy", "#TF_Class_Name_Spy" },
+ { "Engineer", "#TF_Class_Name_Engineer" },
+ { "Invalid", "" } // lots of code loops over these classes based on LOADOUT_COUNT, which is wrong, but this allows them to do it safely
+};
+
+bool BIsPlayerClassValid( int iClass )
+{
+ return iClass >= 0 && iClass < ARRAYSIZE( gs_PlayerClassData );
+}
+
+const char *GetPlayerClassName( int iClass )
+{
+ if ( !BIsPlayerClassValid( iClass ) )
+ return NULL;
+
+ return gs_PlayerClassData[ iClass ].m_pchName;
+}
+
+const char *GetPlayerClassLocalizationKey( int iClass )
+{
+ if ( !BIsPlayerClassValid( iClass ) )
+ return NULL;
+
+ return gs_PlayerClassData[ iClass ].m_pchLocalizationKey;
+}
+
+itemid_t GetAssociatedQuestItemID( const IEconItemInterface *pEconItem )
+{
+ static CSchemaAttributeDefHandle pLoanerIDLowAttrib( "quest loaner id low" );
+ static CSchemaAttributeDefHandle pLoanerIDHiAttrib( "quest loaner id hi" );
+ if ( !pLoanerIDLowAttrib || !pLoanerIDHiAttrib )
+ return INVALID_ITEM_ID;
+
+ itemid_t questItemID = INVALID_ITEM_ID;
+ uint32 nLow, nHi;
+ if ( pEconItem->FindAttribute( pLoanerIDLowAttrib, &nLow ) && pEconItem->FindAttribute( pLoanerIDHiAttrib, &nHi ) )
+ {
+ // Reconstruct the itemID
+ itemid_t nIDLow = 0x00000000FFFFFFFF & (itemid_t)nLow;
+ itemid_t nIDHi = 0xFFFFFFFF00000000 & (itemid_t)nHi << 32;
+ questItemID = nIDLow | nIDHi;
+ }
+
+ return questItemID;
+}
+
+// Loadout positions
+const char *g_szLoadoutStrings[] =
+{
+ // Weapons & Equipment
+ "primary", // LOADOUT_POSITION_PRIMARY = 0,
+ "secondary", // LOADOUT_POSITION_SECONDARY,
+ "melee", // LOADOUT_POSITION_MELEE,
+ "utility", // LOADOUT_POSITION_UTILITY,
+ "building", // LOADOUT_POSITION_BUILDING,
+ "pda", // LOADOUT_POSITION_PDA,
+ "pda2", // LOADOUT_POSITION_PDA2,
+
+ // Wearables
+ "head", // LOADOUT_POSITION_HEAD,
+ "misc", // LOADOUT_POSITION_MISC,
+ "action", // LOADOUT_POSITION_ACTION,
+ "", // LOADOUT_POSITION_MISC2
+
+ "taunt", // LOADOUT_POSITION_TAUNT
+ "", // LOADOUT_POSITION_TAUNT2
+ "", // LOADOUT_POSITION_TAUNT3
+ "", // LOADOUT_POSITION_TAUNT4
+ "", // LOADOUT_POSITION_TAUNT5
+ "", // LOADOUT_POSITION_TAUNT6
+ "", // LOADOUT_POSITION_TAUNT7
+ "", // LOADOUT_POSITION_TAUNT8
+
+#ifdef STAGING_ONLY
+ "dispenser", // LOADOUT_POSITION_PDA_ADDON1
+ "teleporter", // LOADOUT_POSITION_PDA_ADDON2
+
+ "pda3", // LOADOUT_POSITION_PDA3,
+ //"", // LOADOUT_POSITION_MISC3
+ //"", // LOADOUT_POSITION_MISC4
+ //"", // LOADOUT_POSITION_MISC5
+ //"", // LOADOUT_POSITION_MISC6
+ //"", // LOADOUT_POSITION_MISC7
+ //"", // LOADOUT_POSITION_MISC8
+ //"", // LOADOUT_POSITION_MISC9
+ //"", // LOADOUT_POSITION_MISC10
+ "", // LOADOUT_POSITION_BUILDING2,
+#endif // STAGING_ONLY
+};
+COMPILE_TIME_ASSERT( ARRAYSIZE( g_szLoadoutStrings ) <= CLASS_LOADOUT_POSITION_COUNT ); // we don't support mapping directly to slots like "misc2", "taunt2-8", etc.
+
+// Loadout positions used to display loadout slots to players (localized)
+const char *g_szLoadoutStringsForDisplay[] =
+{
+ // Weapons & Equipment
+ "#LoadoutSlot_Primary", // LOADOUT_POSITION_PRIMARY = 0,
+ "#LoadoutSlot_Secondary", // LOADOUT_POSITION_SECONDARY,
+ "#LoadoutSlot_Melee", // LOADOUT_POSITION_MELEE,
+ "#LoadoutSlot_Utility", // LOADOUT_POSITION_UTILITY,
+ "#LoadoutSlot_Building", // LOADOUT_POSITION_BUILDING,
+ "#LoadoutSlot_pda", // LOADOUT_POSITION_PDA,
+ "#LoadoutSlot_pda2", // LOADOUT_POSITION_PDA2
+
+ // Wearables
+ "#LoadoutSlot_Misc", // LOADOUT_POSITION_HEAD
+ "#LoadoutSlot_Misc", // LOADOUT_POSITION_MISC
+ "#LoadoutSlot_Action", // LOADOUT_POSITION_ACTION
+ "#LoadoutSlot_Misc", // LOADOUT_POSITION_MISC2
+
+ "#LoadoutSlot_Taunt", // LOADOUT_POSITION_TAUNT,
+ "#LoadoutSlot_Taunt2", // LOADOUT_POSITION_TAUNT2,
+ "#LoadoutSlot_Taunt3", // LOADOUT_POSITION_TAUNT3,
+ "#LoadoutSlot_Taunt4", // LOADOUT_POSITION_TAUNT4,
+ "#LoadoutSlot_Taunt5", // LOADOUT_POSITION_TAUNT5,
+ "#LoadoutSlot_Taunt6", // LOADOUT_POSITION_TAUNT6,
+ "#LoadoutSlot_Taunt7", // LOADOUT_POSITION_TAUNT7,
+ "#LoadoutSlot_Taunt8", // LOADOUT_POSITION_TAUNT8,
+
+#ifdef STAGING_ONLY
+ "#LoadoutSlot_pda_addon1", // LOADOUT_POSITION_PDA_ADDON1,
+ "#LoadoutSlot_pda_addon2", // LOADOUT_POSITION_PDA_ADDON2,
+
+ "#LoadoutSlot_pda3", // LOADOUT_POSITION_PDA3
+ //"#LoadoutSlot_Misc", // LOADOUT_POSITION_MISC3
+ //"#LoadoutSlot_Misc", // LOADOUT_POSITION_MISC4
+ //"#LoadoutSlot_Misc", // LOADOUT_POSITION_MISC5
+ //"#LoadoutSlot_Misc", // LOADOUT_POSITION_MISC6
+ //"#LoadoutSlot_Misc", // LOADOUT_POSITION_MISC7
+ //"#LoadoutSlot_Misc", // LOADOUT_POSITION_MISC8
+ //"#LoadoutSlot_Misc", // LOADOUT_POSITION_MISC9
+ //"#LoadoutSlot_Misc", // LOADOUT_POSITION_MISC10
+ "#LoadoutSlot_Building", // LOADOUT_POSITION_BUILDING2,
+#endif // STAGING_ONLY
+};
+COMPILE_TIME_ASSERT( ARRAYSIZE( g_szLoadoutStringsForDisplay ) == CLASS_LOADOUT_POSITION_COUNT );
+
+// Loadout positions
+const char *g_szAccountLoadoutStrings[] =
+{
+ "quest",
+ ""
+ ""
+};
+COMPILE_TIME_ASSERT( ARRAYSIZE( g_szAccountLoadoutStrings ) <= ACCOUNT_LOADOUT_POSITION_COUNT ); // we don't support mapping directly to slots like "misc2", "taunt2-8", etc.
+
+// Loadout positions used to display loadout slots to players (localized)
+const char *g_szAccountLoadoutStringsForDisplay[] =
+{
+ "#LoadoutSlot_Account1",
+ "#LoadoutSlot_Account2",
+ "#LoadoutSlot_Account3",
+};
+COMPILE_TIME_ASSERT( ARRAYSIZE( g_szAccountLoadoutStringsForDisplay ) == ACCOUNT_LOADOUT_POSITION_COUNT );
+
+// Weapon types
+const char *g_szWeaponTypeSubstrings[] =
+{
+ // Weapons & Equipment
+ "PRIMARY",
+ "SECONDARY",
+ "MELEE",
+ "GRENADE",
+ "BUILDING",
+ "PDA",
+ "ITEM1",
+ "ITEM2",
+ "HEAD",
+ "MISC",
+ "MELEE_ALLCLASS",
+ "SECONDARY2",
+ "PRIMARY2"
+};
+COMPILE_TIME_ASSERT( ARRAYSIZE( g_szWeaponTypeSubstrings ) == TF_WPN_TYPE_COUNT );
+
+CTFItemSchema::CTFItemSchema()
+ : m_mapQuestObjectiveConditions( DefLessFunc( ObjectiveConditionDefIndex_t ) )
+ , m_mapQuestThemes( CaselessStringLessThan )
+ , m_mapWars( DefLessFunc( WarDefinitionMap_t::KeyType_t ) )
+ , m_mapGameCategories( DefLessFunc( GameCategoryMap_t::KeyType_t ) )
+ , m_mapMMGroups( DefLessFunc( MMGroupMap_t::KeyType_t ) )
+{
+ // Runs at global constructor time, please don't put anything in here, especially asserts
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFItemSchema::Reset()
+{
+ m_vecMvMMaps.Purge();
+ m_vecMvMMissions.Purge();
+ m_vecMvMTours.Purge();
+ m_mapGameCategories.PurgeAndDeleteElements();
+#ifndef GC_DLL
+ m_mapQuestThemes.PurgeAndDeleteElements();
+#endif
+
+ CEconItemSchema::Reset();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFItemSchema::InitializeStringTable( const char **ppStringTable, unsigned int unStringCount, CUtlVector<const char *> *out_pvecStringTable )
+{
+ Assert( ppStringTable != NULL );
+ Assert( out_pvecStringTable != NULL );
+ Assert( out_pvecStringTable->Size() == 0 );
+
+ for ( unsigned int i = 0; i < unStringCount; i++ )
+ {
+ Assert( ppStringTable[i] != NULL );
+ out_pvecStringTable->AddToTail( ppStringTable[i] );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Parses game specific items_master data.
+//-----------------------------------------------------------------------------
+bool CTFItemSchema::BInitSchema( KeyValues *pKVRawDefinition, CUtlVector<CUtlString> *pVecErrors )
+{
+ // First time through, prepare string tables. Must happen before calling parent BInitSchema.
+ if ( m_vecClassUsabilityStrings.Size() == 0 )
+ {
+ // Special case, since player class data is an array of structs, not an array of strings
+ for ( unsigned int i = 0; i < ARRAYSIZE( gs_PlayerClassData ); i++ )
+ {
+ m_vecClassUsabilityStrings.AddToTail( gs_PlayerClassData[i].m_pchName );
+ }
+ Assert( m_vecClassUsabilityStrings.Size() == LOADOUT_COUNT );
+
+ InitializeStringTable( &g_szLoadoutStrings[0], ARRAYSIZE(g_szLoadoutStrings), &m_vecClassLoadoutStrings );
+ Assert( m_vecClassLoadoutStrings.Size() <= CLASS_LOADOUT_POSITION_COUNT );
+
+ InitializeStringTable( &g_szLoadoutStringsForDisplay[0], ARRAYSIZE(g_szLoadoutStringsForDisplay), &m_vecClassLoadoutStringsForDisplay );
+ Assert( m_vecClassLoadoutStringsForDisplay.Size() == CLASS_LOADOUT_POSITION_COUNT );
+
+ InitializeStringTable( &g_szAccountLoadoutStrings[0], ARRAYSIZE(g_szAccountLoadoutStrings), &m_vecAccountLoadoutStrings );
+ Assert( m_vecAccountLoadoutStrings.Size() <= ACCOUNT_LOADOUT_POSITION_COUNT );
+
+ InitializeStringTable( &g_szAccountLoadoutStringsForDisplay[0], ARRAYSIZE(g_szAccountLoadoutStringsForDisplay), &m_vecAccountLoadoutStringsForDisplay );
+ Assert( m_vecAccountLoadoutStringsForDisplay.Size() == ACCOUNT_LOADOUT_POSITION_COUNT );
+
+ InitializeStringTable( &g_szWeaponTypeSubstrings[0], ARRAYSIZE(g_szWeaponTypeSubstrings), &m_vecWeaponTypeSubstrings );
+ Assert( m_vecWeaponTypeSubstrings.Size() == TF_WPN_TYPE_COUNT );
+ }
+
+ // This needs to happen BEFORE we get the quest objectives since they're going to reference these.
+ KeyValues *pKVQuestObjectiveConditions = pKVRawDefinition->FindKey( "quest_objective_conditions" );
+ SCHEMA_INIT_SUBSTEP( BInitQuestObjectiveConditions( pKVQuestObjectiveConditions, pVecErrors ) );
+
+ SCHEMA_INIT_SUBSTEP( CEconItemSchema::BInitSchema( pKVRawDefinition, pVecErrors ) );
+
+ KeyValues *pKVMvmMaps = pKVRawDefinition->FindKey( "mvm_maps" );
+ SCHEMA_INIT_SUBSTEP( BInitMvmMissions( pKVMvmMaps, pVecErrors ) );
+
+ KeyValues *pKVMvmTours = pKVRawDefinition->FindKey( "mvm_tours" );
+ SCHEMA_INIT_SUBSTEP( BInitMvmTours( pKVMvmTours, pVecErrors ) );
+
+ KeyValues *pKVMMCats = pKVRawDefinition->FindKey( "matchmaking_categories" );
+ SCHEMA_INIT_SUBSTEP( BInitMMCategories( pKVMMCats, pVecErrors ) );
+
+ KeyValues *pKVMasterMaps = pKVRawDefinition->FindKey( "master_maps_list" );
+ SCHEMA_INIT_SUBSTEP( BInitMaps( pKVMasterMaps, pVecErrors ) );
+
+ KeyValues *pKVMaps = pKVRawDefinition->FindKey( "maps" );
+ SCHEMA_INIT_SUBSTEP( BInitGameModes( pKVMaps, pVecErrors ) );
+ SCHEMA_INIT_SUBSTEP( BPostInitMaps( pVecErrors ) );
+
+ KeyValues *pKVQuestThemes = pKVRawDefinition->FindKey( "quest_themes" );
+ SCHEMA_INIT_SUBSTEP( BInitQuestThemes( pKVQuestThemes, pVecErrors ) );
+
+ KeyValues* pKVWarDefs = pKVRawDefinition->FindKey( "war_definitions" );
+ SCHEMA_INIT_SUBSTEP( BInitWarDefs( pKVWarDefs, pVecErrors ) );
+
+#ifdef GAME_DLL
+ IGameEvent * event = gameeventmanager->CreateEvent( "schema_updated" );
+ if ( event )
+ {
+ gameeventmanager->FireEvent( event, true );
+ }
+#elif defined( CLIENT_DLL )
+ IGameEvent * event = gameeventmanager->CreateEvent( "schema_updated" );
+ if ( event )
+ {
+ gameeventmanager->FireEventClientSide( event );
+ }
+#endif
+
+ return SCHEMA_INIT_SUCCESS();
+}
+
+const char CTFItemSchema::k_rchOverrideItemLevelDescStringAttribName[] = "override item level desc string";
+
+const char CTFItemSchema::k_rchMvMTicketItemDefName[] = "Tour of Duty Ticket";
+const char CTFItemSchema::k_rchMvMSquadSurplusVoucherItemDefName[] = "MvM Squad Surplus Voucher";
+const char CTFItemSchema::k_rchMvMPowerupBottleItemDefName[] = "Power Up Canteen (MvM)";
+const char CTFItemSchema::k_rchMvMChallengeCompletedMaskAttribName[] = "mvm completed challenges bitmask";
+const char CTFItemSchema::k_rchLadderPassItemDefName[] = "Competitive Matchmaking Official";
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+const char *s_pszGameModes[eNumGameCategories] =
+{
+ "payload", // kGameCategory_Escort
+ "ctf", // kGameCategory_CTF
+ "attack_defense", // kGameCategory_AttackDefense
+ "koth", // kGameCategory_Koth
+ "capture_point", // kGameCategory_CP
+ "payload_race", // kGameCategory_EscortRace
+ "event_mix", // kGameCategory_EventMix
+ "special_delivery", // kGameCategory_SD
+ "", // kGameCategory_Quickplay
+ "event_24_7", // kGameCategory_Event247,
+ "arena", // kGameCategory_Arena
+ "robot_destruction", // kGameCategory_RobotDestruction
+ "powerup", // kGameCategory_Powerup
+ "featured", // kGameCategory_Featured
+ "passtime", // kGameCategory_Passtime
+ "community_update", // kGameCategory_Community_Update
+ "misc", // kGameCategory_Misc
+ "competitive_6v6", // kGameCategory_Competitive_6v6
+ "other", // kGameCategory_Other
+ "halloween", // kGameCategory_Halloween
+};
+COMPILE_TIME_ASSERT( ARRAYSIZE( s_pszGameModes ) == eNumGameCategories );
+
+static const char *s_pszQuickplayMatchTypes[] =
+{
+ "advanced_only", // kQuickplay_AdvancedUsersOnly (default)
+ "all_users", // kQuickplay_AllUsers
+ "disabled", // kQuickplay_Disabled
+};
+COMPILE_TIME_ASSERT( ARRAYSIZE( s_pszQuickplayMatchTypes ) == kQuickplayTypeCount );
+
+static const char *s_pszMvMBadgeContractPointsAttributes[] =
+{
+ NULL, // we don't support normal for contract
+ "mvm contract points intermediate",
+ "mvm contract points advanced",
+ "mvm contract points expert",
+ NULL, // should we do haunted?
+};
+COMPILE_TIME_ASSERT( ARRAYSIZE( s_pszMvMBadgeContractPointsAttributes ) == k_EMvMChallengeDifficultyLastValid );
+
+const char *CTFItemSchema::GetMvMBadgeContractPointsAttributeName( EMvMChallengeDifficulty difficulty )
+{
+ if ( difficulty != k_EMvMChallengeDifficulty_Invalid )
+ return s_pszMvMBadgeContractPointsAttributes[ difficulty - 1 ];
+
+ return NULL;
+}
+
+static const char *s_pszMvMBadgeContractLevelAttributes[] =
+{
+ NULL, // we don't support normal for contract
+ "mvm contract level intermediate",
+ "mvm contract level advanced",
+ "mvm contract level expert",
+ NULL, // should we do haunted?
+};
+COMPILE_TIME_ASSERT( ARRAYSIZE( s_pszMvMBadgeContractLevelAttributes ) == k_EMvMChallengeDifficultyLastValid );
+
+const char *CTFItemSchema::GetMvMBadgeContractLevelAttributeName( EMvMChallengeDifficulty difficulty )
+{
+ if ( difficulty != k_EMvMChallengeDifficulty_Invalid )
+ return s_pszMvMBadgeContractLevelAttributes[ difficulty - 1 ];
+
+ return NULL;
+}
+
+const char* s_pszMMTypes[kMatchmakingTypeCount] =
+{
+ "special_events",
+ "core",
+ "alternative",
+ "competitive_6v6",
+};
+COMPILE_TIME_ASSERT( ARRAYSIZE( s_pszMMTypes ) == kMatchmakingTypeCount );
+
+bool CTFItemSchema::BInitMMCategories( KeyValues *pKVCategories, CUtlVector<CUtlString> *pVecErrors )
+{
+ m_mapMMGroups.PurgeAndDeleteElements();
+
+ FOR_EACH_TRUE_SUBKEY( pKVCategories, pKVCategory )
+ {
+ int nCatType = StringFieldToInt( pKVCategory->GetName(), s_pszMMTypes, ARRAYSIZE( s_pszMMTypes ) );
+ SCHEMA_INIT_CHECK( nCatType != -1, "BInitMMCategories: unknown mm category type '%s'", pKVCategory->GetName() );
+ EMatchmakingGroupType eType = (EMatchmakingGroupType)nCatType;
+
+ SchemaMMGroup_t *pCat = m_mapMMGroups[ m_mapMMGroups.Insert( eType, new SchemaMMGroup_t() ) ];
+ pCat->m_pszName = pKVCategory->GetName();
+ pCat->m_eMMGroup = eType;
+ pCat->m_pszLocalizedName = pKVCategory->GetString( "localized_name" );
+ pCat->m_nMaxExcludes = pKVCategory->GetInt( "max_excludes", -1 );
+ SCHEMA_INIT_CHECK( pCat->m_nMaxExcludes != -1, "BInitMMCategories: missing 'max_excludes' for mm category type '%s'", pKVCategory->GetName() );
+
+ KeyValues* pKVValidMatchGroups = pKVCategory->FindKey( "valid_match_groups" );
+ if ( pKVValidMatchGroups )
+ {
+ FOR_EACH_SUBKEY( pKVValidMatchGroups, pKVGroup )
+ {
+ EMatchGroup eGroup = (EMatchGroup)StringFieldToInt( pKVGroup->GetName(), s_pszMatchGroups, (int)k_nMatchGroup_Count, false );
+ pCat->m_bitsValidMMGroups.Set( eGroup, 1 );
+ }
+ }
+ }
+
+ return SCHEMA_INIT_SUCCESS();
+}
+
+bool SchemaGameCategory_t::PassesRestrictions() const
+{
+ if ( m_vecRestrictions.Count() <= 0 )
+ return true;
+
+ FOR_EACH_VEC( m_vecRestrictions, i )
+ {
+ switch ( m_vecRestrictions[i].m_eType )
+ {
+ case kMatchmakingGameModeRestrictionType_Holiday:
+#ifndef GC_DLL
+ if ( UTIL_IsHolidayActive( m_vecRestrictions[i].m_nValue ) )
+#else
+ if ( EconHolidays_IsHolidayActive( m_vecRestrictions[i].m_nValue, CRTime::RTime32TimeCur() ) )
+#endif
+ return true;
+ break;
+ case kMatchmakingGameModeRestrictionType_Operation:
+ if ( GetItemSchema() )
+ {
+ FOR_EACH_MAP_FAST( GetItemSchema()->GetOperationDefinitions(), iOperation )
+ {
+ CEconOperationDefinition *pOperation = GetItemSchema()->GetOperationDefinitions()[iOperation];
+ if ( pOperation && pOperation->IsActive() )
+ {
+ if ( Q_stricmp( pOperation->GetName(), m_vecRestrictions[i].m_strValue ) == 0 )
+ return true;
+ }
+ }
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ return false;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CTFItemSchema::BInitGameModes( KeyValues *pKVMaps, CUtlVector<CUtlString> *pVecErrors )
+{
+ m_mapGameCategories.PurgeAndDeleteElements();
+
+ if ( NULL == pKVMaps )
+ return true;
+
+ FOR_EACH_TRUE_SUBKEY( pKVMaps, pKVGameMode )
+ {
+ int iGameType = StringFieldToInt( pKVGameMode->GetName(), s_pszGameModes, ARRAYSIZE( s_pszGameModes ) );
+ SCHEMA_INIT_CHECK( iGameType != -1,
+ "BInitMaps(): unknown game type '%s'", pKVGameMode->GetName() );
+
+ EGameCategory eGameType = (EGameCategory)iGameType;
+
+ SchemaGameCategory_t* pGameMode = m_mapGameCategories[ m_mapGameCategories.Insert( eGameType, new SchemaGameCategory_t() ) ];
+ pGameMode->m_eGameCategory = eGameType;
+ pGameMode->m_pszLocalizedName = pKVGameMode->GetString( "localized_name", NULL );
+ pGameMode->m_pszLocalizedDesc = pKVGameMode->GetString( "localized_desc", NULL );
+ pGameMode->m_pszListImage = pKVGameMode->GetString( "list_image", NULL );
+ pGameMode->m_pszName = pKVGameMode->GetName();
+ pGameMode->m_pszMMType = pKVGameMode->GetString( "mm_type" );
+ int nMMType = StringFieldToInt( pKVGameMode->GetString( "mm_type" ), s_pszMMTypes, ARRAYSIZE( s_pszMMTypes ) );
+ if ( nMMType != (int)kMatchmakingType_None )
+ {
+ EMatchmakingGroupType eMMType = (EMatchmakingGroupType)nMMType;
+ auto idx = m_mapMMGroups.Find( eMMType );
+ SCHEMA_INIT_CHECK( idx != m_mapMMGroups.InvalidIndex(), "mm_type '%s' does not have a matchmaking_categories entry", pKVGameMode->GetString( "mm_type" ) );
+ SCHEMA_INIT_CHECK( pGameMode->m_pszLocalizedName != NULL, "game mode '%s' missing localized name!", pKVGameMode->GetName() );
+ SCHEMA_INIT_CHECK( pGameMode->m_pszLocalizedDesc != NULL, "game mode '%s' missing localized desc!", pKVGameMode->GetName() );
+ SCHEMA_INIT_CHECK( pGameMode->m_pszListImage != NULL, "game mode '%s' missing list image!", pKVGameMode->GetName() );
+ pGameMode->m_pMMGroup = m_mapMMGroups[ idx ];
+ m_mapMMGroups[ idx ]->m_vecModes.AddToTail( pGameMode );
+
+ KeyValues *pKVRestrictions = pKVGameMode->FindKey( "restrictions" );
+ if ( pKVRestrictions )
+ {
+ FOR_EACH_SUBKEY( pKVRestrictions, pKVRestriction )
+ {
+ EMatchmakingGameModeRestrictionType eType = kMatchmakingGameModeRestrictionType_None;
+ int nValue = -1;
+ const char *pszValue = NULL;
+
+ const char *pszType = pKVRestriction->GetName();
+ if ( Q_stricmp( pszType, "holiday" ) == 0 )
+ {
+ eType = kMatchmakingGameModeRestrictionType_Holiday;
+#ifndef GC_DLL
+ nValue = UTIL_GetHolidayForString( pKVRestriction->GetString() );
+#else
+ nValue = EconHolidays_GetHolidayForString( pKVRestriction->GetString() );
+#endif
+ }
+ else if ( Q_stricmp( pszType, "operation" ) == 0 )
+ {
+ eType = kMatchmakingGameModeRestrictionType_Operation;
+ pszValue = pKVRestriction->GetString();
+ }
+
+ if ( eType != kMatchmakingGameModeRestrictionType_None )
+ {
+ int iIndex = pGameMode->m_vecRestrictions.AddToTail();
+ pGameMode->m_vecRestrictions[iIndex].m_eType = eType;
+ pGameMode->m_vecRestrictions[iIndex].m_nValue = nValue;
+ pGameMode->m_vecRestrictions[iIndex].m_strValue = pszValue;
+ }
+ }
+ }
+ }
+
+ KeyValues *pKVMapList = pKVGameMode->FindKey( "maplist" );
+ if ( pKVMapList )
+ {
+ FOR_EACH_TRUE_SUBKEY( pKVMapList, pKVMap )
+ {
+ MapDef_t* pMap = const_cast< MapDef_t* >( GetMasterMapDefByName( pKVMap->GetString( "name" ) ) );
+ SCHEMA_INIT_CHECK ( pMap != NULL, "Map %s listed in game mode %s doesn't exist!", pKVMap->GetString( "name" ), pGameMode->m_pszName );
+ pMap->m_vecAssociatedGameCategories.AddToTail( eGameType );
+ pGameMode->AddMap( pMap, pKVMap->GetBool( "enabled" ) );
+ }
+ }
+
+ SCHEMA_INIT_CHECK( pGameMode->m_vecEnabledMaps.Count(), "BInitMaps(): ERROR!! No valid maps for game type %s (at least one must be \"enabled\" in _maps.txt).", pKVGameMode->GetName() );
+ }
+ return SCHEMA_INIT_SUCCESS();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CTFItemSchema::BInitMaps( KeyValues *pKVMaps, CUtlVector<CUtlString> *pVecErrors )
+{
+ m_vecMasterListOfMaps.PurgeAndDeleteElements();
+
+ FOR_EACH_TRUE_SUBKEY( pKVMaps, pKVMap )
+ {
+ const char* pszMapStampname = pKVMap->GetString( "maptoken", "" );
+ MapDef_t* pMap = new MapDef_t( pszMapStampname );
+
+ pMap->pszMapName = pKVMap->GetString( "name", NULL );
+ SCHEMA_INIT_CHECK( pMap->pszMapName != NULL,
+ "BInitMaps(): missing map name for master map entry %s.", pKVMap->GetName() );
+
+ pMap->m_nDefIndex = V_atoi( pKVMap->GetName() );
+ pMap->pszMapNameLocKey = pKVMap->GetString( "localizedname", NULL );
+
+ SCHEMA_INIT_CHECK( (pMap->mapStampDef == NULL) == (pszMapStampname[0] == '\0'),
+ "BInitGameModes(): unable to find map stamp definition '%s' for map '%s'.", pszMapStampname, pMap->pszMapName );
+
+ pMap->pszMapNameLocKey = pKVMap->GetString( "localizedname", NULL );
+ pMap->pszAuthorsLocKey = pKVMap->GetString( "authors", NULL );
+ pMap->pszStrangePrefixLocKey = pKVMap->GetString( "strangeprefixtoken", NULL );
+ pMap->m_nStatsIdentifier = pKVMap->GetInt( "statsidentifier", -1 );
+
+ // initialize from optional "tags" block
+ KeyValues *pKVTags = pKVMap->FindKey( "tags" );
+ if ( pKVTags )
+ {
+ FOR_EACH_SUBKEY( pKVTags, pKVTag )
+ {
+ pMap->vecTags.AddToTail( GetHandleForTag( pKVTag->GetName() ) );
+ }
+ }
+
+ // Init rolling match tags
+ pKVTags = pKVMap->FindKey( "rolling_match_tags" );
+ if ( pKVTags )
+ {
+ FOR_EACH_SUBKEY( pKVTags, pKVTag )
+ {
+ pMap->m_vecRollingMatchTags.AddToTail( GetHandleForTag( pKVTag->GetName() ) );
+ }
+ }
+
+ // Init rolling match targets
+ pKVTags = pKVMap->FindKey( "rolling_match_target_tags" );
+ if ( pKVTags )
+ {
+ FOR_EACH_SUBKEY( pKVTags, pKVTag )
+ {
+ pMap->m_vecRollingMatchTargets.AddToTail( { GetHandleForTag( pKVTag->GetName() ), pKVTag->GetFloat( "weight", 1.f ) } );
+ }
+ }
+
+ m_vecMasterListOfMaps.AddToTail( pMap );
+ }
+
+ return true;
+}
+
+bool CTFItemSchema::BPostInitMaps( CUtlVector<CUtlString> *pVecErrors )
+{
+ //
+ // Go through each map and check if any of the other maps have a matching tag. If it does,
+ // add it as a map that we could roll for a rolling match "next map" vote. We're doing this
+ // now so we don't compute it every time choose maps to vote on
+ //
+ FOR_EACH_VEC( m_vecMasterListOfMaps, i )
+ {
+ MapDef_t* pMapOuter = m_vecMasterListOfMaps[ i ];
+
+ bool bRequiredToHaveRollingMatchTags = false;
+
+ // Some maps dont need to have rolling match tags (ex. MvM, the "invalid map", maps we choose because reasons )
+ FOR_EACH_VEC( pMapOuter->m_vecAssociatedGameCategories, j )
+ {
+ const SchemaGameCategory_t* pCategory = GetItemSchema()->GetGameCategory( pMapOuter->m_vecAssociatedGameCategories[j] );
+
+ if ( !pCategory )
+ {
+ continue;
+ }
+
+ const SchemaMMGroup_t* pMMGroup = pCategory->m_pMMGroup;
+ if ( !pMMGroup )
+ {
+ continue;
+ }
+
+ if ( pCategory->m_vecEnabledMaps.Find( pMapOuter ) == pCategory->m_vecEnabledMaps.InvalidIndex() )
+ {
+ continue;
+ }
+
+ if ( pMMGroup->m_bitsValidMMGroups.IsBitSet( k_nMatchGroup_Casual_12v12 ) )
+ {
+ bRequiredToHaveRollingMatchTags = true;
+ break;
+ }
+ }
+
+ if ( !bRequiredToHaveRollingMatchTags )
+ continue;
+
+ //
+ // For each map, figure out which maps it can vote into. Use the highest weight if it's
+ // in multiple groups.
+ //
+ FOR_EACH_VEC( pMapOuter->m_vecRollingMatchTargets, j )
+ {
+ // Figure out how many maps match this tag so we can get the weighting right
+ const MapDef_t::WeightedNextMapTargets_t& tag = pMapOuter->m_vecRollingMatchTargets[ j ];
+ CUtlVector< MapDef_t* > vecMatchedMaps;
+ FOR_EACH_VEC( m_vecMasterListOfMaps, k )
+ {
+ MapDef_t *pCandidate = m_vecMasterListOfMaps[ k ];
+
+ // Don't have ourselves as a potential target
+ if ( m_vecMasterListOfMaps[ k ]->m_nDefIndex == pMapOuter->m_nDefIndex )
+ continue;
+
+ bool bEnabled = false;
+ FOR_EACH_VEC( pCandidate->m_vecAssociatedGameCategories, idxCandidateCategory )
+ {
+ const SchemaGameCategory_t* pCategory = GetItemSchema()->GetGameCategory( pCandidate->m_vecAssociatedGameCategories[idxCandidateCategory] );
+
+ if ( pCategory && pCategory->m_vecEnabledMaps.Find( pCandidate ) != pCategory->m_vecEnabledMaps.InvalidIndex() )
+ {
+ bEnabled = true;
+ break;
+ }
+ }
+
+ if ( !bEnabled )
+ {
+ continue;
+ }
+
+ if ( m_vecMasterListOfMaps[ k ]->BHasRollingMatchTag( tag.m_tag ) )
+ {
+ vecMatchedMaps.AddToTail( m_vecMasterListOfMaps[ k ] );
+ }
+ }
+
+ // Add each map to THIS map's list
+ float flTargetIndividualWeight = tag.m_flWeight / (float)vecMatchedMaps.Count();
+ FOR_EACH_VEC( vecMatchedMaps, k )
+ {
+ pMapOuter->AddMapAsTargetWithWeight( { vecMatchedMaps[ k ]->m_nDefIndex, flTargetIndividualWeight } );
+ }
+
+ //
+ // Normalize the weights so we can do scaling later on
+ //
+ float flMaxWeight = 0.f;
+ FOR_EACH_VEC( pMapOuter->m_vecRollingMatchMaps, k )
+ {
+ flMaxWeight = Max( pMapOuter->m_vecRollingMatchMaps[ k ].m_flWeight, flMaxWeight );
+ }
+
+ // Normalize
+ FOR_EACH_VEC( pMapOuter->m_vecRollingMatchMaps, k )
+ {
+ pMapOuter->m_vecRollingMatchMaps[ k ].m_flWeight = pMapOuter->m_vecRollingMatchMaps[ k ].m_flWeight / flMaxWeight;
+ }
+ }
+
+ // We only have to roll one-less than the amount needed because the current map is always selected
+ SCHEMA_INIT_CHECK( pMapOuter->m_vecRollingMatchMaps.Count() >= NEXT_MAP_VOTE_OPTIONS - 1, "Not enough maps with matching tags for map %s",
+ pMapOuter->pszMapName );
+ }
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Inits data for quest themes
+//-----------------------------------------------------------------------------
+bool CTFItemSchema::BInitQuestThemes( KeyValues *pKVThemes, CUtlVector<CUtlString> *pVecErrors )
+{
+ if ( NULL != pKVThemes )
+ {
+ FOR_EACH_TRUE_SUBKEY( pKVThemes, pKVTheme )
+ {
+ CQuestThemeDefinition *pTheme = new CQuestThemeDefinition();
+ SCHEMA_INIT_SUBSTEP( pTheme->BInitFromKV( pKVTheme, pVecErrors ) );
+
+ m_mapQuestThemes.Insert( pKVTheme->GetName(), pTheme );
+ }
+ }
+
+ return SCHEMA_INIT_SUCCESS();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Inits data for quest objective conditions
+//-----------------------------------------------------------------------------
+bool CTFItemSchema::BInitQuestObjectiveConditions( KeyValues *pKVConditionsBlock, CUtlVector<CUtlString> *pVecErrors )
+{
+ m_mapQuestObjectiveConditions.PurgeAndDeleteElements();
+
+ SCHEMA_INIT_CHECK( pKVConditionsBlock != NULL, "No quest objective conditions block found!" );
+
+ FOR_EACH_TRUE_SUBKEY( pKVConditionsBlock, pKVCondition )
+ {
+ CTFQuestObjectiveConditionsDefinition *pNewCondition = new CTFQuestObjectiveConditionsDefinition();
+ SCHEMA_INIT_SUBSTEP( pNewCondition->BInitFromKV( pKVCondition, pVecErrors ) );
+
+ m_mapQuestObjectiveConditions.Insert( pNewCondition->GetDefIndex(), pNewCondition );
+ }
+
+ return SCHEMA_INIT_SUCCESS();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Post init for all quest objective conditions
+//-----------------------------------------------------------------------------
+bool CTFItemSchema::BObjectiveConditionsPostInit( CUtlVector<CUtlString> *pVecErrors )
+{
+ FOR_EACH_MAP_FAST( m_mapQuestObjectiveConditions, i )
+ {
+ SCHEMA_INIT_SUBSTEP( m_mapQuestObjectiveConditions[ i ]->BPostInit( pVecErrors ) );
+ }
+
+ return SCHEMA_INIT_SUCCESS();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Init all war definitions
+//-----------------------------------------------------------------------------
+bool CTFItemSchema::BInitWarDefs( KeyValues *pKVWarDefs, CUtlVector<CUtlString> *pVecErrors )
+{
+ m_mapWars.PurgeAndDeleteElements();
+
+ FOR_EACH_TRUE_SUBKEY( pKVWarDefs, pKVWar )
+ {
+ CWarDefinition* pNewWarDef = new CWarDefinition();
+ SCHEMA_INIT_SUBSTEP( pNewWarDef->BInitFromKV( pKVWar, pVecErrors ) );
+
+ m_mapWars.Insert( pNewWarDef->GetDefIndex(), pNewWarDef );
+ }
+
+ return SCHEMA_INIT_SUCCESS();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Inits data for MVM maps / missions
+//-----------------------------------------------------------------------------
+bool CTFItemSchema::BInitMvmMissions( KeyValues *pKVMvmMaps, CUtlVector<CUtlString> *pVecErrors )
+{
+ m_vecMvMMaps.RemoveAll();
+ m_vecMvMMissions.RemoveAll();
+
+ if ( NULL == pKVMvmMaps )
+ return true;
+
+ // initialize the rewards sections
+ bool bResult = true;
+
+ FOR_EACH_TRUE_SUBKEY( pKVMvmMaps, pKVMap )
+ {
+ int nMapIndex = m_vecMvMMaps.AddToTail();
+ MvMMap_t &map = m_vecMvMMaps[nMapIndex];
+ map.m_sMap = pKVMap->GetName();
+ int nMapNameLen = strlen( map.m_sMap.Get() );
+ map.m_sDisplayName = pKVMap->GetString( "display_name" );
+
+ // Locate missions
+ KeyValues *pKVMissions = pKVMap->FindKey( "missions" );
+ if ( pKVMissions )
+ {
+ // Parse mission subkeys
+ FOR_EACH_TRUE_SUBKEY( pKVMissions, pKVMission )
+ {
+ int nMissionIndex = m_vecMvMMissions.AddToTail(); // global mission index (not map-specific)
+ MvMMission_t &mission = m_vecMvMMissions[nMissionIndex];
+ map.m_vecMissions.AddToTail( nMissionIndex ); // index from map -> global mission list
+ mission.m_sPop = pKVMission->GetName();
+ mission.m_iDisplayMapIndex = nMapIndex;
+ mission.m_sDisplayName = pKVMission->GetString( "display_name" );
+ mission.m_sMode = pKVMission->GetString( "mode" );
+ mission.m_sMapNameActual = pKVMission->GetString( "map_file_override", map.m_sMap.Get() );
+ const char *pszDiff = pKVMission->GetString( "difficulty", "" );
+ mission.m_eDifficulty = GetMvMChallengeDifficultyByInternalName( pszDiff );
+ mission.m_unMannUpPoints = pKVMission->GetInt( "mannup_points" );
+ if ( mission.m_eDifficulty == k_EMvMChallengeDifficulty_Invalid )
+ {
+ pVecErrors->AddToTail( CUtlString( CFmtStr(
+ "MvM mission '%s' on map %s has missing or invalid 'difficulty' value", mission.m_sPop.Get(), map.m_sMap.Get() ) ) );
+ bResult = false;
+ continue;
+ }
+
+ // Pop filenames are required to obey a naming convention.
+ if ( ( Q_stricmp( mission.m_sPop.Get(), map.m_sMap.Get() ) != 0 )
+ && ( Q_strnicmp( mission.m_sPop.Get(), map.m_sMap.Get(), nMapNameLen ) != 0
+ || mission.m_sPop.Get()[nMapNameLen] != '_' ) )
+ {
+ pVecErrors->AddToTail( CUtlString( CFmtStr(
+ "MvM mission '%s' on map %s does not obey map prefix naming convention", mission.m_sPop.Get(), map.m_sMap.Get() ) ) );
+ bResult = false;
+ continue;
+ }
+ }
+ }
+ SCHEMA_INIT_CHECK( map.m_vecMissions.Count() > 0,
+ "MvM map %s doesn't have any associated missions", map.m_sMap.Get() );
+ }
+
+ return bResult;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Inits data for MVM tours (sets of missions)
+//-----------------------------------------------------------------------------
+bool CTFItemSchema::BInitMvmTours( KeyValues *pKVMvmTours, CUtlVector<CUtlString> *pVecErrors )
+{
+ m_vecMvMTours.RemoveAll();
+
+ if ( NULL == pKVMvmTours )
+ return true;
+
+ // initialize the rewards sections
+ bool bResult = true;
+
+ FOR_EACH_TRUE_SUBKEY( pKVMvmTours, pKVTour )
+ {
+ MvMTour_t tour;
+ tour.m_sTourInternalName = pKVTour->GetName();
+ SCHEMA_INIT_CHECK( FindMvmMissionByName( tour.m_sTourInternalName.Get() ) < 0,
+ "Duplicate MvM tour \"%s\"", tour.m_sTourInternalName.Get() );
+
+ tour.m_sTourNameLocalizationToken = pKVTour->GetString( "tour_name" );
+ SCHEMA_INIT_CHECK( tour.m_sTourNameLocalizationToken.Get() && tour.m_sTourNameLocalizationToken.Get()[0] == '#',
+ "MvM tour \"%s\" didn't specify valid localization token for 'tour_name'", tour.m_sTourInternalName.Get() );
+
+ const char *pszBadgeItemDefName = pKVTour->GetString( "badge_item_def" );
+ tour.m_pBadgeItemDef = pszBadgeItemDefName ? GetItemSchema()->GetItemDefinitionByName( pszBadgeItemDefName ) : NULL;
+ SCHEMA_INIT_CHECK( (pszBadgeItemDefName == NULL) == (tour.m_pBadgeItemDef == NULL),
+ "MvM tour \"%s\" specified invalid badge definition name '%s'", tour.m_sTourInternalName.Get(), pszBadgeItemDefName );
+
+ const char *pszDiff = pKVTour->GetString( "difficulty", "" );
+ tour.m_eDifficulty = GetMvMChallengeDifficultyByInternalName( pszDiff );
+ SCHEMA_INIT_CHECK( tour.m_eDifficulty != k_EMvMChallengeDifficulty_Invalid,
+ "MvM tour \"%s\" specified invalid difficulty '%s'", tour.m_sTourInternalName.Get(), pszDiff );
+
+ tour.m_bIsNew = pKVTour->GetBool( "is_new", false );
+
+ tour.m_sLootImageName = pKVTour->GetString( "loot_image" );
+ SCHEMA_INIT_CHECK( tour.m_sTourNameLocalizationToken.Get() && tour.m_sTourNameLocalizationToken.Get()[0] == '#',
+ "MvM tour \"%s\" didn't specify valid localization token for 'tour_name'", tour.m_sTourInternalName.Get() );
+
+#ifdef GC
+ const char *pszMissionCompleteLootListName = pKVTour->GetString( "mission_complete_loot_list" );
+ tour.m_pMissionCompleteLootList = pszMissionCompleteLootListName ? GetItemSchema()->GetLootListByName( pszMissionCompleteLootListName ) : NULL;
+ SCHEMA_INIT_CHECK( (pszMissionCompleteLootListName == NULL) == (tour.m_pMissionCompleteLootList == NULL),
+ "MvM tour \"%s\" specified invalid mission completion loot list '%s'", tour.m_sTourInternalName.Get(), pszMissionCompleteLootListName );
+
+ const char *pszTourCompleteLootListName = pKVTour->GetString( "tour_complete_loot_list" );
+ tour.m_pTourCompleteLootList = pszTourCompleteLootListName ? GetItemSchema()->GetLootListByName( pszTourCompleteLootListName ) : NULL;
+ SCHEMA_INIT_CHECK( (pszTourCompleteLootListName == NULL) == (tour.m_pMissionCompleteLootList == NULL),
+ "MvM tour \"%s\" specified invalid tour completion loot list '%s'", tour.m_sTourInternalName.Get(), pszTourCompleteLootListName );
+#endif
+
+
+ // Locate missions
+ tour.m_nAllChallengesBits = 0;
+
+ KeyValues *pKVMissions = pKVTour->FindKey( "missions" );
+ if ( pKVMissions )
+ {
+ bool bContainsNonBitMissions = false; // does this contain any missions with bit set to -1 (practice)?
+ bool bContainsBitMissions = false; // does this contain any missions with bit set to anything besides -1 (non-practice)?
+
+ // Parse mission values
+ FOR_EACH_VALUE( pKVMissions, pKVMission )
+ {
+ const char *pszMissionName = pKVMission->GetName();
+ int iMissionBit = pKVMission->GetInt();
+
+ // Bounds check our bits. -1 is valid because it means "don't track".
+ SCHEMA_INIT_CHECK( iMissionBit >= -1 && iMissionBit <= 31,
+ "MvM tour \"%s\" mission \"%s\" specifies invalid tour completion bit %i", tour.m_sTourInternalName.Get(), pszMissionName, iMissionBit );
+
+ // Find our mission information to link to.
+ int iMissionIndex = FindMvmMissionByName( pszMissionName );
+ SCHEMA_INIT_CHECK( m_vecMvMMissions.IsValidIndex( iMissionIndex ),
+ "MvM tour \"%s\" unable to locate mission \"%s\"", tour.m_sTourInternalName.Get(), pszMissionName );
+
+ // Make sure the same tour doesn't contain both badge-adjusting missions and non-adjusting
+ // missions.
+ bContainsNonBitMissions |= (iMissionBit == -1);
+ bContainsBitMissions |= (iMissionBit != -1);
+
+ SCHEMA_INIT_CHECK( !bContainsNonBitMissions || !bContainsBitMissions,
+ "MvM tour \"%s\" mission \"%s\" contains both practice (-1 bit) and non-practice missions", tour.m_sTourInternalName.Get(), pszMissionName );
+
+ // Make sure we haven't already used this bit for this tour.
+ if ( iMissionBit >= 0 )
+ {
+ uint32 unMask = 1U << (unsigned int)iMissionBit;
+ SCHEMA_INIT_CHECK( (tour.m_nAllChallengesBits & unMask) == 0,
+ "MvM tour \"%s\" mission \"%s\" re-uses bit %i", tour.m_sTourInternalName.Get(), pszMissionName, iMissionBit );
+
+ tour.m_nAllChallengesBits |= unMask;
+ }
+
+ // Success for this mission.
+ MvMTourMission_t mission;
+ mission.m_iMissionIndex = iMissionIndex;
+ mission.m_iBadgeSlot = iMissionBit;
+
+ tour.m_vecMissions.AddToTail( mission );
+ }
+ }
+
+ SCHEMA_INIT_CHECK( tour.m_vecMissions.Count() > 0,
+ "MvM tour \"%s\" has no missions specified", tour.m_sTourInternalName.Get() );
+
+ m_vecMvMTours.AddToTail( tour );
+ }
+
+ return bResult;
+}
+
+//-----------------------------------------------------------------------------
+const CQuestThemeDefinition *CTFItemSchema::GetQuestThemeByName( const char *pszDefName ) const
+{
+ Assert( pszDefName );
+ if ( pszDefName )
+ {
+ FOR_EACH_MAP_FAST( m_mapQuestThemes, i )
+ {
+ if ( !Q_stricmp( m_mapQuestThemes[ i ]->GetName(), pszDefName ) )
+ {
+ return m_mapQuestThemes[ i ];
+ }
+ }
+ }
+
+ return NULL;
+}
+
+
+//-----------------------------------------------------------------------------
+const CTFQuestObjectiveConditionsDefinition* CTFItemSchema::GetQuestObjectiveConditionByDefIndex( ObjectiveConditionDefIndex_t nDefIndex )
+{
+ const CTFQuestObjectiveConditionsDefinition* pDef = NULL;
+
+ auto idx = m_mapQuestObjectiveConditions.Find( nDefIndex );
+ if ( idx != m_mapQuestObjectiveConditions.InvalidIndex() )
+ {
+ pDef = m_mapQuestObjectiveConditions[ idx ];
+ }
+
+ return pDef;
+}
+
+//-----------------------------------------------------------------------------
+const CWarDefinition *CTFItemSchema::GetWarDefinitionByIndex( war_definition_index_t nDefIndex ) const
+{
+ auto idx = m_mapWars.Find( nDefIndex );
+ if ( idx != m_mapWars.InvalidIndex() )
+ {
+ return m_mapWars[ idx ];
+ }
+
+ return NULL;
+}
+
+//-----------------------------------------------------------------------------
+const CWarDefinition *CTFItemSchema::GetWarDefinitionByName( const char* pszDefName ) const
+{
+ FOR_EACH_MAP_FAST( m_mapWars, i )
+ {
+ if ( !V_stricmp( pszDefName, m_mapWars[i]->GetDefName() ) )
+ {
+ return m_mapWars[i];
+ }
+ }
+
+ return NULL;
+}
+
+//-----------------------------------------------------------------------------
+const char *CTFItemSchema::GetMvmMissionName( int iChallengeIndex ) const
+{
+ if ( iChallengeIndex == k_iMvmMissionIndex_Any )
+ return "(any)";
+
+ if ( m_vecMvMMissions.IsValidIndex( iChallengeIndex ) )
+ return m_vecMvMMissions[iChallengeIndex].m_sPop.Get();
+
+ Assert( iChallengeIndex == k_iMvmMissionIndex_NotInSchema );
+ return "(invalid)";
+
+}
+
+//-----------------------------------------------------------------------------
+int CTFItemSchema::FindMvmMissionByName( const char *pszMissionName ) const
+{
+ if ( pszMissionName == NULL || *pszMissionName == '\0' )
+ return k_iMvmMissionIndex_Any;
+
+ FOR_EACH_VEC( m_vecMvMMissions, i )
+ {
+ if ( !V_stricmp( m_vecMvMMissions[i].m_sPop.Get(), pszMissionName ) )
+ return i;
+ }
+ return k_iMvmMissionIndex_NotInSchema;
+}
+
+//-----------------------------------------------------------------------------
+int CTFItemSchema::FindMvmTourByName( const char *pszTourName ) const
+{
+ if ( pszTourName == NULL || *pszTourName == '\0' )
+ return k_iMvmTourIndex_Empty;
+
+ FOR_EACH_VEC( m_vecMvMTours, i )
+ {
+ if ( !V_stricmp( m_vecMvMTours[i].m_sTourInternalName.Get(), pszTourName ) )
+ return i;
+ }
+ return k_iMvmTourIndex_NotInSchema;
+}
+
+//-----------------------------------------------------------------------------
+int CTFItemSchema::FindMvmMissionInTour( int idxTour, int idxMissionInSchema ) const
+{
+ if ( idxTour < 0 || idxTour >= m_vecMvMTours.Count() )
+ return -1;
+ const MvMTour_t &tour = m_vecMvMTours[idxTour];
+ FOR_EACH_VEC( tour.m_vecMissions, i )
+ {
+ if ( tour.m_vecMissions[i].m_iMissionIndex == idxMissionInSchema )
+ return i;
+ }
+
+ // Not found
+ return -1;
+}
+
+//-----------------------------------------------------------------------------
+int CTFItemSchema::GetMvmMissionBadgeSlotForTour( int idxTour, int idxMissionInSchema ) const
+{
+ int idxMissionInTour = FindMvmMissionInTour( idxTour, idxMissionInSchema );
+ if ( idxMissionInTour < 0 )
+ return -1;
+ int iBadgeSlot = m_vecMvMTours[idxTour].m_vecMissions[ idxMissionInTour ].m_iBadgeSlot;
+ Assert( iBadgeSlot >= 0 );
+ return iBadgeSlot;
+}
+
+
+
+//-----------------------------------------------------------------------------
+const MapDef_t *CTFItemSchema::GetMasterMapDefByName( const char *pszSearchName ) const
+{
+ FOR_EACH_VEC( m_vecMasterListOfMaps, i )
+ {
+ if ( !V_stricmp( m_vecMasterListOfMaps[i]->pszMapName, pszSearchName ) )
+ return m_vecMasterListOfMaps[i];
+ }
+
+ return NULL;
+}
+
+//-----------------------------------------------------------------------------
+const MapDef_t *CTFItemSchema::GetMasterMapDefByIndex( MapDefIndex_t unIndex ) const
+{
+ FOR_EACH_VEC( m_vecMasterListOfMaps, i )
+ {
+ if ( m_vecMasterListOfMaps[i]->m_nDefIndex == unIndex )
+ return m_vecMasterListOfMaps[i];
+ }
+
+ return NULL;
+}
+
+const SchemaGameCategory_t* CTFItemSchema::GetGameCategory( EGameCategory eType ) const
+{
+ auto idx = m_mapGameCategories.Find( eType );
+ if ( idx != m_mapGameCategories.InvalidIndex() )
+ {
+ return m_mapGameCategories[ idx ];
+ }
+
+ return NULL;
+}
+
+const SchemaMMGroup_t* CTFItemSchema::GetMMGroup( EMatchmakingGroupType eCat ) const
+{
+ auto idx = m_mapMMGroups.Find( eCat );
+ if ( idx != m_mapMMGroups.InvalidIndex() )
+ {
+ return m_mapMMGroups[ idx ];
+ }
+
+ return NULL;
+}
+
+#ifdef TF_CLIENT_DLL
+//-----------------------------------------------------------------------------
+// Purpose: Returns the number of actual "real" items referenced by the item definition
+// (i.e. items that would take up space in the backpack). Overriding this here
+// to account for map stamps and any other TF-specific item types.
+//-----------------------------------------------------------------------------
+int CTFItemSchema::CalculateNumberOfConcreteItems( const CEconItemDefinition *pItemDef )
+{
+ AssertMsg( pItemDef, "NULL item definition! This should not happen!" );
+ if ( !pItemDef )
+ return 0;
+
+ if ( pItemDef->GetItemClass() && !Q_strcmp( pItemDef->GetItemClass(), "map_token" ) )
+ return 0;
+
+ return CEconItemSchema::CalculateNumberOfConcreteItems( pItemDef );
+}
+#endif // TF_CLIENT_DLL
+
+RTime32 CTFItemSchema::GetCustomExpirationDate( const char *pszExpirationDate ) const
+{
+ if ( !V_stricmp( pszExpirationDate, "end_of_halloween" ) )
+ return EconHolidays_TerribleHack_GetHalloweenEndData();
+
+ return CEconItemSchema::GetCustomExpirationDate( pszExpirationDate );
+}
+
+EMvMChallengeDifficulty GetMvMChallengeDifficultyByInternalName( const char *pszEnglishID )
+{
+ if ( !Q_stricmp( pszEnglishID, "normal" ) )
+ return k_EMvMChallengeDifficulty_Normal;
+ if ( !Q_stricmp( pszEnglishID, "intermediate" ) )
+ return k_EMvMChallengeDifficulty_Intermediate;
+ if ( !Q_stricmp( pszEnglishID, "advanced" ) )
+ return k_EMvMChallengeDifficulty_Advanced;
+ if ( !Q_stricmp( pszEnglishID, "expert" ) )
+ return k_EMvMChallengeDifficulty_Expert;
+ if ( !Q_stricmp( pszEnglishID, "haunted" ) )
+ return k_EMvMChallengeDifficulty_Haunted;
+ return k_EMvMChallengeDifficulty_Invalid;
+}
+
+const char *GetMvMChallengeDifficultyLocName( EMvMChallengeDifficulty eDifficulty )
+{
+ switch ( eDifficulty )
+ {
+ default:
+ AssertMsg( false, "Bogus challenge difficulty" );
+ case k_EMvMChallengeDifficulty_Normal:
+ return "#TF_MvM_Normal";
+ case k_EMvMChallengeDifficulty_Intermediate:
+ return "#TF_MvM_Intermediate";
+ case k_EMvMChallengeDifficulty_Advanced:
+ return "#TF_MvM_Advanced";
+ case k_EMvMChallengeDifficulty_Expert:
+ return "#TF_MvM_Expert";
+ case k_EMvMChallengeDifficulty_Haunted:
+ return "#TF_MvM_Haunted";
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CTFItemSchema::BCanStrangeFilterApplyToStrangeSlotInItem( uint32 /*strange_event_restriction_t*/ unRestrictionType, uint32 unRestrictionValue, const IEconItemInterface *pItem, int iStrangeSlot, uint32 *out_pOptionalScoreType ) const
+{
+ uint32 unStrangeScoreType;
+ if ( !CEconItemSchema::BCanStrangeFilterApplyToStrangeSlotInItem( unRestrictionType, unRestrictionValue, pItem, iStrangeSlot, &unStrangeScoreType ) )
+ return false;
+
+ // do not apply to operation type trackers. Makes no sense and is not intended
+ if ( unStrangeScoreType == kKillEaterEvent_CosmeticOperationContractsCompleted
+ || unStrangeScoreType == kKillEaterEvent_CosmeticOperationKills
+ || unStrangeScoreType == kKillEaterEvent_CosmeticOperationContractsPoints
+ || unStrangeScoreType == kKillEaterEvent_CosmeticOperationBonusPoints
+ ) {
+ return false;
+ }
+
+ // Items that can't be restored cannot have filters applied
+ static CSchemaAttributeDefHandle pAttrDef_CannotRestore( "cannot restore" );
+ if ( pAttrDef_CannotRestore && pItem->FindAttribute( pAttrDef_CannotRestore ) )
+ return false;
+
+ // Operation Passes cannot have filters
+ static CSchemaAttributeDefHandle pAttrDef_IsOperationPass( "is_operation_pass" );
+ if ( pAttrDef_IsOperationPass && pItem->FindAttribute( pAttrDef_IsOperationPass ) )
+ return false;
+
+ // Strange filters can never apply to gifts-given-out. It doesn't make any sense in any event, but
+ // we also only use this for the Spirit of Giving, and didn't really intend for that to be customizable
+ // like this.
+ if ( unStrangeScoreType == kKillEaterEvent_GiftsGiven )
+ return false;
+
+ if ( unRestrictionType == kStrangeEventRestriction_Map )
+ {
+ const MapDef_t *pSchemaMap = GetItemSchema()->GetMasterMapDefByIndex( unRestrictionValue );
+ if ( !pSchemaMap )
+ return false;
+
+ if ( unStrangeScoreType == kKillEaterEvent_UnderwaterKill )
+ {
+ // Don't allow this filter to apply to underwater kills if it's set to filter to a level where
+ // no-one can go underwater.
+ if ( !pSchemaMap->vecTags.HasElement( GetItemSchema()->GetHandleForTag( "map_has_deep_water" ) ) )
+ return false;
+ }
+ else if ( unStrangeScoreType == kKillEaterEvent_DefenderKill )
+ {
+ // All TF game modes besides arena have some sort of objective that will count for defender
+ // kills -- a flag, a capture point, or a cart.
+ const SchemaGameCategory_t* pArenaCategory = GetGameCategory( kGameCategory_Arena );
+ FOR_EACH_VEC( pArenaCategory->m_vecEnabledMaps, i )
+ {
+ if ( pSchemaMap == pArenaCategory->m_vecEnabledMaps[ i ] )
+ {
+ return false;
+ }
+ }
+ }
+ }
+ else if (unRestrictionType == kStrangeEventRestriction_Competitive)
+ {
+ // Get the Current Competitive Season? Assuming its just season 1 right now
+ // Just put this in the Schema somewhere I assume
+ //return true;
+ }
+
+ if ( out_pOptionalScoreType )
+ {
+ *out_pOptionalScoreType = unStrangeScoreType;
+ }
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+IEconTool *CTFItemSchema::CreateEconToolImpl( const char *pszToolType, const char *pszUseString, const char *pszUsageRestriction, item_capabilities_t unCapabilities, KeyValues *pUsageKV )
+{
+ if ( pszToolType )
+ {
+ if ( !V_stricmp( pszToolType, "tf_spellbook_page" ) )
+ {
+ // Error checking -- make sure we aren't setting properties in the schema that we don't support.
+ if ( pszUsageRestriction ) return NULL;
+
+ return new CEconTool_TFSpellbookPage( pszToolType, unCapabilities );
+ }
+
+ if ( !V_stricmp( pszToolType, "tf_event_enable" ) )
+ {
+ // 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_TFEventEnableHalloween( pszToolType, pszUseString );
+ }
+ }
+
+ return CEconItemSchema::CreateEconToolImpl( pszToolType, pszUseString, pszUsageRestriction, unCapabilities, pUsageKV );
+}
+
+#if defined( STAGING_ONLY ) && defined( CLIENT_DLL )
+int PaintkitAutocomplete( char const *partial, char commands[ COMMAND_COMPLETION_MAXITEMS ][ COMMAND_COMPLETION_ITEM_LENGTH ] )
+{
+ char *commandName = "r_texcomp_debug";
+ int numMatches = 0;
+ partial += Q_strlen( commandName );
+
+ while ( partial[ 0 ] != 0 && V_isspace( partial[ 0 ] ) )
+ ++partial;
+
+ int partialLen = Q_strlen( partial );
+
+ // Don't autocomplete until there are at least 3 characters to guess on.
+ if ( partialLen < 3 )
+ return 0;
+
+ Assert( GetItemSchema() );
+ const CEconItemSchema::ItemPaintKitMap_t& paintKits = GetItemSchema()->GetItemPaintKits();
+
+ FOR_EACH_MAP_FAST( paintKits, i )
+ {
+ const char* pThisKeyName = paintKits.Key( i );
+ if ( Q_stristr( pThisKeyName, partial ) != NULL )
+ Q_snprintf( commands[ numMatches++ ], COMMAND_COMPLETION_ITEM_LENGTH, "%s %s", commandName, pThisKeyName );
+
+ if ( numMatches == COMMAND_COMPLETION_MAXITEMS )
+ break;
+ }
+
+ return numMatches;
+}
+
+CON_COMMAND_F_COMPLETION( r_texcomp_debug, "Usage: r_texcomp_debug <paintkit_name> [wear level=1]", 0, PaintkitAutocomplete )
+{
+ if ( args.ArgC() < 2 )
+ {
+ Msg( "usage: r_texcomp_debug <paintkit_name> [wear level=1]\n" );
+ return;
+ }
+
+ int wearlevel = ( args.ArgC() >= 3 )
+ ? atoi( args[ 2 ] )
+ : 1;
+
+ Assert( GetItemSchema() );
+ const CEconItemSchema::ItemPaintKitMap_t& paintKits = GetItemSchema()->GetItemPaintKits();
+
+ int ndx = paintKits.Find( args[ 1 ] );
+ if ( ndx == paintKits.InvalidIndex() )
+ {
+ Msg( "Couldn't find paintkit named %s\n", args[ 1 ] );
+ return;
+ }
+
+ KeyValues* pKV = paintKits[ ndx ]->GetPaintKitWearKV( wearlevel );
+
+ if ( pKV == NULL )
+ {
+ Msg( "Couldn't find wear level %d for painkit %s\n", wearlevel, args[ 1 ] );
+ return;
+ }
+
+ ITextureCompositor* pWeaponSkinBaseCompositor = materials->NewTextureCompositor( 1, 1, args[ 1 ], TF_TEAM_RED, 0, pKV, TEX_COMPOSITE_CREATE_FLAGS_LOG_NODES_ONLY );
+ Assert( pWeaponSkinBaseCompositor == NULL ); pWeaponSkinBaseCompositor;
+}
+
+#endif // STAGING_ONLY && CLIENT_DLL
+