Team Fortress 2 Source Code as on 22/4/2020
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

286 lines
10 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose: Load item upgrade data from KeyValues
  4. //
  5. // $NoKeywords: $
  6. //=============================================================================
  7. #include "cbase.h"
  8. #include "tf_shareddefs.h"
  9. #include "tf_upgrades_shared.h"
  10. #include "filesystem.h"
  11. #include "econ_item_system.h"
  12. #include "tf_gamerules.h"
  13. #include "tf_item_powerup_bottle.h"
  14. CMannVsMachineUpgradeManager g_MannVsMachineUpgrades;
  15. CMannVsMachineUpgradeManager::CMannVsMachineUpgradeManager()
  16. {
  17. SetDefLessFunc( m_AttribMap );
  18. }
  19. //-----------------------------------------------------------------------------
  20. // Purpose:
  21. //-----------------------------------------------------------------------------
  22. void CMannVsMachineUpgradeManager::LevelInitPostEntity()
  23. {
  24. LoadUpgradesFile();
  25. }
  26. //-----------------------------------------------------------------------------
  27. // Purpose:
  28. //-----------------------------------------------------------------------------
  29. void CMannVsMachineUpgradeManager::LevelShutdownPostEntity()
  30. {
  31. m_Upgrades.RemoveAll();
  32. }
  33. //-----------------------------------------------------------------------------
  34. // Purpose:
  35. //-----------------------------------------------------------------------------
  36. void CMannVsMachineUpgradeManager::ParseUpgradeBlockForUIGroup( KeyValues *pKV, int iDefaultUIGroup )
  37. {
  38. if ( !pKV )
  39. return;
  40. for ( KeyValues *pData = pKV->GetFirstSubKey(); pData != NULL; pData = pData->GetNextKey() )
  41. {
  42. // Check that the expected data is there
  43. KeyValues *pkvAttribute = pData->FindKey( "attribute" );
  44. KeyValues *pkvIcon = pData->FindKey( "icon" );
  45. KeyValues *pkvIncrement = pData->FindKey( "increment" );
  46. KeyValues *pkvCap = pData->FindKey( "cap" );
  47. KeyValues *pkvCost = pData->FindKey( "cost" );
  48. if ( !pkvAttribute || !pkvIcon || !pkvIncrement || !pkvCap || !pkvCost )
  49. {
  50. Warning( "Upgrades: One or more upgrades missing attribute, icon, increment, cap, or cost value.\n" );
  51. return;
  52. }
  53. int index = m_Upgrades.AddToTail();
  54. const char *pszAttrib = pData->GetString( "attribute" );
  55. V_strncpy( m_Upgrades[ index ].szAttrib, pszAttrib, sizeof( m_Upgrades[ index ].szAttrib ) );
  56. const CEconItemSchema *pSchema = ItemSystem()->GetItemSchema();
  57. if ( pSchema )
  58. {
  59. // If we can't find a matching attribute, nuke this entry completely
  60. const CEconItemAttributeDefinition *pAttr = pSchema->GetAttributeDefinitionByName( m_Upgrades[ index ].szAttrib );
  61. if ( !pAttr )
  62. {
  63. Warning( "Upgrades: Invalid attribute reference! -- %s.\n", m_Upgrades[ index ].szAttrib );
  64. m_Upgrades.Remove( index );
  65. continue;
  66. }
  67. Assert( pAttr->GetAttributeType() );
  68. if ( !pAttr->GetAttributeType()->BSupportsGameplayModificationAndNetworking() )
  69. {
  70. Warning( "Upgrades: Invalid attribute '%s' is of a type that doesn't support networking!\n", m_Upgrades[ index ].szAttrib );
  71. m_Upgrades.Remove( index );
  72. continue;
  73. }
  74. if ( !pAttr->IsStoredAsFloat() || pAttr->IsStoredAsInteger() )
  75. {
  76. Warning( "Upgrades: Attribute reference '%s' is not stored as a float!\n", m_Upgrades[ index ].szAttrib );
  77. m_Upgrades.Remove( index );
  78. continue;
  79. }
  80. }
  81. V_strncpy( m_Upgrades[index].szIcon, pData->GetString( "icon" ), sizeof( m_Upgrades[ index ].szIcon ) );
  82. m_Upgrades[ index ].flIncrement = pData->GetFloat( "increment" );
  83. m_Upgrades[ index ].flCap = pData->GetFloat( "cap" );
  84. m_Upgrades[ index ].nCost = pData->GetInt( "cost" );
  85. m_Upgrades[ index ].nUIGroup = pData->GetInt( "ui_group", iDefaultUIGroup );
  86. m_Upgrades[ index ].nQuality = pData->GetInt( "quality", MVM_UPGRADE_QUALITY_NORMAL );
  87. m_Upgrades[ index ].nTier = pData->GetInt( "tier", 0 );
  88. }
  89. }
  90. //-----------------------------------------------------------------------------
  91. // Purpose:
  92. //-----------------------------------------------------------------------------
  93. int CMannVsMachineUpgradeManager::GetAttributeIndexByName( const char* pszAttributeName )
  94. {
  95. // Already in the map?
  96. if( m_AttribMap.Find( pszAttributeName ) != m_AttribMap.InvalidIndex() )
  97. {
  98. return m_AttribMap.Element( m_AttribMap.Find( pszAttributeName ) );
  99. }
  100. // Not in the map. Find it in the vector and add it to the map
  101. for( int i=0, nCount = m_Upgrades.Count() ; i<nCount; ++i )
  102. {
  103. // Find the index
  104. const char* pszAttrib = m_Upgrades[i].szAttrib;
  105. if( FStrEq( pszAttributeName, pszAttrib ) )
  106. {
  107. // Add to map
  108. m_AttribMap.Insert( pszAttributeName, i );
  109. // Return value
  110. return i;
  111. }
  112. }
  113. AssertMsg1( 0, "Attribute \"%s\" not found!", pszAttributeName );
  114. return -1;
  115. }
  116. //-----------------------------------------------------------------------------
  117. // Purpose:
  118. //-----------------------------------------------------------------------------
  119. void CMannVsMachineUpgradeManager::LoadUpgradesFile( void )
  120. {
  121. // Determine the upgrades file to load
  122. const char *pszPath = "scripts/items/mvm_upgrades.txt";
  123. // Allow map to override
  124. const char *pszCustomUpgradesFile = TFGameRules()->GetCustomUpgradesFile();
  125. if ( TFGameRules() && pszCustomUpgradesFile && pszCustomUpgradesFile[0] )
  126. {
  127. pszPath = pszCustomUpgradesFile;
  128. }
  129. #ifdef STAGING_ONLY
  130. else if ( TFGameRules() && !TFGameRules()->IsMannVsMachineMode() )
  131. {
  132. pszPath = "scripts/items/bountymode_upgrades.txt";
  133. }
  134. #endif // STAGING_ONLY
  135. LoadUpgradesFileFromPath( pszPath );
  136. }
  137. //-----------------------------------------------------------------------------
  138. // Purpose: Loads an upgrade file from a specific path
  139. //-----------------------------------------------------------------------------
  140. void CMannVsMachineUpgradeManager::LoadUpgradesFileFromPath( const char *pszPath )
  141. {
  142. // Check that the path is valid
  143. const char *pszExtension = V_GetFileExtension( pszPath );
  144. if ( V_strstr( pszPath, ".." ) || V_strstr( pszPath, " " ) ||
  145. V_strstr( pszPath, "\r" ) || V_strstr( pszPath, "\n" ) ||
  146. V_strstr( pszPath, ":" ) || V_strstr( pszPath, "\\\\" ) ||
  147. V_IsAbsolutePath( pszPath ) ||
  148. pszExtension == NULL || V_strcmp( pszExtension, "txt" ) != 0 )
  149. {
  150. return;
  151. }
  152. KeyValues *pKV = new KeyValues( "Upgrades" );
  153. if ( !pKV->LoadFromFile( filesystem, pszPath, "MOD" ) )
  154. {
  155. Warning( "Can't open %s\n", pszPath );
  156. pKV->deleteThis();
  157. return;
  158. }
  159. m_Upgrades.RemoveAll();
  160. // Parse upgrades.txt
  161. ParseUpgradeBlockForUIGroup( pKV->FindKey( "ItemUpgrades" ), 0 );
  162. ParseUpgradeBlockForUIGroup( pKV->FindKey( "PlayerUpgrades" ), 1 );
  163. pKV->deleteThis();
  164. }
  165. int GetUpgradeStepData( CTFPlayer *pPlayer, int nWeaponSlot, int nUpgradeIndex, int &nCurrentStep, bool &bOverCap )
  166. {
  167. if ( !pPlayer )
  168. return 0;
  169. // Get the item entity. We use the entity, not the item in the loadout, because we want
  170. // the dynamic attributes that have already been purchases and attached.
  171. CEconEntity *pEntity = NULL;
  172. const CEconItemView *pItemData = CTFPlayerSharedUtils::GetEconItemViewByLoadoutSlot( pPlayer, nWeaponSlot, &pEntity );
  173. const CMannVsMachineUpgrades *pMannVsMachineUpgrade = &( g_MannVsMachineUpgrades.m_Upgrades[ nUpgradeIndex ] );
  174. CEconItemAttributeDefinition *pAttribDef = ItemSystem()->GetStaticDataForAttributeByName( pMannVsMachineUpgrade->szAttrib );
  175. if ( !pAttribDef )
  176. return 0;
  177. // Special-case short-circuit logic for the powerup bottle. I don't know why we do this, but
  178. // we did before so this seems like the safest way of not breaking anything.
  179. const CTFPowerupBottle *pPowerupBottle = dynamic_cast< CTFPowerupBottle* >( pEntity );
  180. if ( pPowerupBottle )
  181. {
  182. Assert( pMannVsMachineUpgrade->nUIGroup == UIGROUP_POWERUPBOTTLE );
  183. nCurrentStep = ::FindAttribute( pItemData, pAttribDef )
  184. ? pPowerupBottle->GetNumCharges()
  185. : 0;
  186. bOverCap = nCurrentStep == pPowerupBottle->GetMaxNumCharges();
  187. return pPowerupBottle->GetMaxNumCharges();
  188. }
  189. Assert( pAttribDef->IsStoredAsFloat() );
  190. Assert( !pAttribDef->IsStoredAsInteger() );
  191. int nFormat = pAttribDef->GetDescriptionFormat();
  192. bool bPercentage = nFormat == ATTDESCFORM_VALUE_IS_PERCENTAGE || nFormat == ATTDESCFORM_VALUE_IS_INVERTED_PERCENTAGE;
  193. // Find the baseline value for this attribute. We start by assuming that it has no default value on
  194. // the item level (CEconItem) and defaulting to 100% for percentages and 0 for anything else.
  195. float flBase = bPercentage ? 1.0f : 0.0f;
  196. // If the item has a backing store, we pull from that to find the attribute value before any
  197. // gameplay-specific (CEconItemView-level) attribute modifications. If we're a player we don't have
  198. // any persistent backing store. This will either stomp our above value if found or leave it unchanged
  199. // if not found.
  200. if ( pItemData && pItemData->GetSOCData() )
  201. {
  202. ::FindAttribute_UnsafeBitwiseCast<attrib_value_t>( pItemData->GetSOCData(), pAttribDef, &flBase );
  203. }
  204. // ...
  205. float flCurrentAttribValue = bPercentage ? 1.0f : 0.0f;
  206. if ( pMannVsMachineUpgrade->nUIGroup == UIGROUP_UPGRADE_ATTACHED_TO_PLAYER )
  207. {
  208. ::FindAttribute_UnsafeBitwiseCast<attrib_value_t>( pPlayer->GetAttributeList(), pAttribDef, &flCurrentAttribValue );
  209. }
  210. else
  211. {
  212. Assert( pMannVsMachineUpgrade->nUIGroup == UIGROUP_UPGRADE_ATTACHED_TO_ITEM );
  213. if ( pItemData )
  214. {
  215. ::FindAttribute_UnsafeBitwiseCast<attrib_value_t>( pItemData, pAttribDef, &flCurrentAttribValue );
  216. }
  217. }
  218. // ...
  219. const float flIncrement = pMannVsMachineUpgrade->flIncrement;
  220. // Figure out the cap value for this attribute. We start by trusting whatever is specified in our
  221. // upgrade config but if we're dealing with an item that specifies different properties at a level
  222. // before MvM upgrades (ie., the Soda Popper already specifies "Reload time decreased") then we
  223. // need to make sure we consider that the actual high end for UI purposes.
  224. const float flCap = pMannVsMachineUpgrade->flCap;
  225. if ( BIsAttributeValueWithDeltaOverCap( flCurrentAttribValue, flIncrement, flCap ) )
  226. {
  227. // Early out here -- we know we're over the cap already, so just fill out and return values
  228. // that show that.
  229. bOverCap = true;
  230. nCurrentStep = RoundFloatToInt( fabsf( ( flCurrentAttribValue - flBase ) / flIncrement ) );
  231. return nCurrentStep; // Include the 0th step
  232. }
  233. // Calculate the the total number of upgrade levels and current upgrade level
  234. int nNumSteps = 0;
  235. // ...
  236. nNumSteps = RoundFloatToInt( fabsf( ( flCap - flBase ) / flIncrement ) );
  237. nCurrentStep = RoundFloatToInt( fabsf( ( flCurrentAttribValue - flBase ) / flIncrement ) );
  238. // Include the 0th step
  239. return nNumSteps;
  240. }