diff options
Diffstat (limited to 'game/shared/tf/tf_item_schema.cpp')
| -rw-r--r-- | game/shared/tf/tf_item_schema.cpp | 3219 |
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 + |