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.
941 lines
29 KiB
941 lines
29 KiB
//========= Copyright Valve Corporation, All rights reserved. ============// |
|
// |
|
// Purpose: Load item upgrade data from KeyValues |
|
// |
|
// $NoKeywords: $ |
|
//============================================================================= |
|
|
|
#include "cbase.h" |
|
|
|
#include "tf_shareddefs.h" |
|
#include "tf_upgrades.h" |
|
#include "tf_upgrades_shared.h" |
|
#include "econ_item_system.h" |
|
#include "dt_utlvector_send.h" |
|
#include "tf_player.h" |
|
#include "econ_wearable.h" |
|
#include "tf_item_powerup_bottle.h" |
|
#include "tf_mann_vs_machine_stats.h" |
|
#include "tf_weapon_wrench.h" |
|
#include "tf_weapon_builder.h" |
|
#include "tf_objective_resource.h" |
|
|
|
|
|
extern ConVar tf_mvm_skill; |
|
extern ConVar *sv_cheats; |
|
|
|
CHandle<CUpgrades> g_hUpgradeEntity; |
|
|
|
|
|
BEGIN_DATADESC( CUpgrades ) |
|
DEFINE_KEYFIELD( m_nStartDisabled, FIELD_INTEGER, "start_disabled" ), |
|
|
|
DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ), |
|
DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputDisable ), |
|
|
|
DEFINE_ENTITYFUNC( UpgradeTouch ), |
|
END_DATADESC() |
|
|
|
LINK_ENTITY_TO_CLASS( func_upgradestation, CUpgrades ); |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CUpgrades::Spawn( void ) |
|
{ |
|
BaseClass::Spawn(); |
|
|
|
// Don't do anything if we don't have raid mode. |
|
g_hUpgradeEntity = this; |
|
|
|
AddSpawnFlags(SF_TRIGGER_ALLOW_CLIENTS); |
|
|
|
InitTrigger(); |
|
|
|
SetTouch( &CUpgrades::UpgradeTouch ); |
|
|
|
ListenForGameEvent( "round_start" ); |
|
ListenForGameEvent( "teamplay_round_start" ); |
|
|
|
m_bIsEnabled = true; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CUpgrades::InputEnable( inputdata_t &inputdata ) |
|
{ |
|
m_bIsEnabled = true; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CUpgrades::InputDisable( inputdata_t &inputdata ) |
|
{ |
|
m_bIsEnabled = false; |
|
|
|
int iCount = m_hTouchingEntities.Count(); |
|
for ( int i = 0; i < iCount; i++ ) |
|
{ |
|
CBaseEntity *pEntity = m_hTouchingEntities[i]; |
|
if ( pEntity && pEntity->IsPlayer() ) |
|
{ |
|
CTFPlayer *pTFPlayer = ToTFPlayer( pEntity ); |
|
if ( pTFPlayer ) |
|
{ |
|
pTFPlayer->m_Shared.SetInUpgradeZone( false ); |
|
} |
|
} |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CUpgrades::InputReset( inputdata_t &inputdata ) |
|
{ |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CUpgrades::FireGameEvent( IGameEvent *gameEvent ) |
|
{ |
|
if ( FStrEq( gameEvent->GetName(), "round_start" ) || FStrEq( gameEvent->GetName(), "teamplay_round_start" ) ) |
|
{ |
|
// Enable/disable based on round state |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CUpgrades::UpgradeTouch( CBaseEntity *pOther ) |
|
{ |
|
if ( m_bIsEnabled ) |
|
{ |
|
if ( PassesTriggerFilters(pOther) ) |
|
{ |
|
if ( pOther->IsPlayer() ) |
|
{ |
|
CTFPlayer *pTFPlayer = ToTFPlayer( pOther ); |
|
pTFPlayer->m_Shared.SetInUpgradeZone( true ); |
|
} |
|
} |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CUpgrades::EndTouch( CBaseEntity *pOther ) |
|
{ |
|
if ( IsTouching( pOther ) ) |
|
{ |
|
if ( pOther->IsPlayer() ) |
|
{ |
|
CTFPlayer *pTFPlayer = ToTFPlayer( pOther ); |
|
pTFPlayer->m_Shared.SetInUpgradeZone( false ); |
|
} |
|
} |
|
|
|
BaseClass::EndTouch( pOther ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CUpgrades::GrantOrRemoveAllUpgrades( CTFPlayer *pTFPlayer, bool bRemove /*= false*/, bool bRefund /*= true*/ ) |
|
{ |
|
// If we're being asked to remove and refund everything, it's a respec (the population manager actually handles refunding later) |
|
bool bRespec = bRemove && bRefund; |
|
|
|
if ( pTFPlayer && ( ( sv_cheats && sv_cheats->GetBool() ) || bRemove ) ) |
|
{ |
|
pTFPlayer->BeginPurchasableUpgrades(); |
|
|
|
// for each upgrade |
|
for ( int iUpgrade = 0 ; iUpgrade < g_MannVsMachineUpgrades.m_Upgrades.Count() ; iUpgrade++ ) |
|
{ |
|
CMannVsMachineUpgrades upgrade = g_MannVsMachineUpgrades.m_Upgrades[ iUpgrade ]; |
|
CEconItemAttributeDefinition *pAttribDef = ItemSystem()->GetStaticDataForAttributeByName( upgrade.szAttrib ); |
|
|
|
// don't process bottle upgrades |
|
if ( upgrade.nUIGroup == UIGROUP_POWERUPBOTTLE ) |
|
continue; |
|
|
|
if ( pAttribDef ) |
|
{ |
|
loadout_positions_t nLastLoadoutPos = bRespec ? LOADOUT_POSITION_MISC2 : LOADOUT_POSITION_HEAD; |
|
// for each item |
|
for ( int iItemSlot = LOADOUT_POSITION_PRIMARY ; iItemSlot < nLastLoadoutPos ; iItemSlot++ ) |
|
{ |
|
// Don't respec bottle charges |
|
if ( bRespec && iItemSlot == LOADOUT_POSITION_ACTION ) |
|
continue; |
|
|
|
// can this item use this upgrade? |
|
if ( bRespec || ( TFGameRules() && TFGameRules()->CanUpgradeWithAttrib( pTFPlayer, iItemSlot, pAttribDef->GetDefinitionIndex(), &upgrade ) ) ) |
|
{ |
|
// If we're not removing, assume we're giving the player everything for free (cheat) |
|
bool bFree = ( !bRemove || !bRefund ) ? true : false; |
|
|
|
// compute number of upgrade steps this upgrade has |
|
bool bOverCap = false; |
|
int iCurrentUpgradeStep = 0; |
|
const int iNumMaxUpgradeStep = GetUpgradeStepData( pTFPlayer, iItemSlot, iUpgrade, iCurrentUpgradeStep, bOverCap ); |
|
|
|
// for each upgrade step |
|
for ( int iStep = 0; iStep < iNumMaxUpgradeStep; ++iStep ) |
|
{ |
|
PlayerPurchasingUpgrade( pTFPlayer, iItemSlot, iUpgrade, bRemove, bFree, bRespec ); |
|
} |
|
} |
|
} |
|
} |
|
} |
|
pTFPlayer->EndPurchasableUpgrades(); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CUpgrades::PlayerPurchasingUpgrade( CTFPlayer *pTFPlayer, int iItemSlot, int iUpgrade, bool bDowngrade, bool bFree /*= false */, bool bRespec /*= false*/ ) |
|
{ |
|
if ( !pTFPlayer || |
|
iUpgrade < 0 || |
|
iUpgrade >= g_MannVsMachineUpgrades.m_Upgrades.Count() ) |
|
return; |
|
|
|
// Verify that this upgrade can be accepted on this player |
|
CMannVsMachineUpgrades upgrade = g_MannVsMachineUpgrades.m_Upgrades[ iUpgrade ]; |
|
CEconItemAttributeDefinition *pAttribDef = ItemSystem()->GetStaticDataForAttributeByName( upgrade.szAttrib ); |
|
if ( !bRespec && ( !TFGameRules() || !TFGameRules()->CanUpgradeWithAttrib( pTFPlayer, iItemSlot, pAttribDef->GetDefinitionIndex(), &upgrade ) ) ) |
|
{ |
|
return; |
|
} |
|
|
|
int nCost = 0; |
|
|
|
if ( bDowngrade ) |
|
{ |
|
// See if we know the actual price paid, rather than asking what the current price is, which is exploitable |
|
CUtlVector< CUpgradeInfo > *pUpgrades = pTFPlayer->GetRefundableUpgrades(); |
|
if ( pUpgrades && pUpgrades->Count() ) |
|
{ |
|
FOR_EACH_VEC( *pUpgrades, i ) |
|
{ |
|
CUpgradeInfo *pInfo = &pUpgrades->Element( i ); |
|
if ( pInfo && pInfo->m_upgrade == iUpgrade ) |
|
{ |
|
nCost = pInfo->m_nCost; |
|
break; |
|
} |
|
} |
|
} |
|
} |
|
if ( !nCost ) |
|
{ |
|
nCost = TFGameRules()->GetCostForUpgrade( &g_MannVsMachineUpgrades.m_Upgrades[iUpgrade], iItemSlot, pTFPlayer->GetPlayerClass()->GetClassIndex(), pTFPlayer ); |
|
} |
|
if ( bDowngrade ) |
|
{ |
|
nCost *= -1; |
|
} |
|
|
|
if ( !bFree ) |
|
{ |
|
// Make sure the player can afford it |
|
if ( pTFPlayer->GetCurrency() < nCost ) |
|
return; |
|
} |
|
|
|
CEconItemView *pItem = NULL; |
|
|
|
// Make sure the item slot is correct for attributes that need to attach to an item |
|
if ( g_MannVsMachineUpgrades.m_Upgrades[ iUpgrade ].nUIGroup != UIGROUP_UPGRADE_ATTACHED_TO_PLAYER ) |
|
{ |
|
if ( !( iItemSlot == LOADOUT_POSITION_ACTION || ( iItemSlot >= LOADOUT_POSITION_PRIMARY && iItemSlot <= LOADOUT_POSITION_PDA2 ) ) ) |
|
return; |
|
|
|
pItem = CTFPlayerSharedUtils::GetEconItemViewByLoadoutSlot( pTFPlayer, iItemSlot ); |
|
} |
|
|
|
if ( bDowngrade ) |
|
{ |
|
if ( bRespec ) |
|
{ |
|
// Approve everything and don't refund currency here. The population manager handles that later. |
|
nCost = 0; |
|
} |
|
else |
|
{ |
|
bool bCanSell = false; |
|
|
|
// Before we sell anything, make sure it's verified as valid |
|
item_definition_index_t iItemIndex = pItem ? pItem->GetItemDefIndex() : INVALID_ITEM_DEF_INDEX; |
|
|
|
for ( int i = 0; i < pTFPlayer->GetRefundableUpgrades()->Count(); ++i ) |
|
{ |
|
CUpgradeInfo pInfo = pTFPlayer->GetRefundableUpgrades()->Element( i ); |
|
if ( ( pInfo.m_iPlayerClass == pTFPlayer->GetPlayerClass()->GetClassIndex() ) && ( pInfo.m_itemDefIndex == iItemIndex ) && ( pInfo.m_upgrade == iUpgrade ) ) |
|
{ |
|
// Found a matching upgrade that we can sell |
|
nCost = ( pInfo.m_nCost * -1 ); |
|
bCanSell = true; |
|
break; |
|
} |
|
} |
|
|
|
if ( !bCanSell ) |
|
{ |
|
// No matched recent purchases! |
|
// No sale! |
|
return; |
|
} |
|
} |
|
} |
|
else |
|
{ |
|
// If the upgrade has a tier, it's mutually exclusive with upgrades of the same tier for the same itemslot |
|
int nTier = TFGameRules()->GetUpgradeTier( iUpgrade ); |
|
if ( nTier && !TFGameRules()->IsUpgradeTierEnabled( pTFPlayer, iItemSlot, iUpgrade ) ) |
|
return; |
|
} |
|
|
|
const attrib_definition_index_t nUpgradedAttrDefIndex = ApplyUpgradeToItem( pTFPlayer, pItem, iUpgrade, nCost, bDowngrade, !bFree ); |
|
if ( nUpgradedAttrDefIndex != INVALID_ATTRIB_DEF_INDEX ) |
|
{ |
|
if ( !bFree ) |
|
{ |
|
// Remove Currency |
|
pTFPlayer->RemoveCurrency( nCost ); |
|
} |
|
|
|
// remember our upgrades so we can restore them at a checkpoint |
|
pTFPlayer->RememberUpgrade( pTFPlayer->GetPlayerClass()->GetClassIndex(), pItem, iUpgrade, nCost, bDowngrade ); |
|
|
|
// Only regenerate if between waves |
|
pTFPlayer->Regenerate( TFObjectiveResource()->GetMannVsMachineIsBetweenWaves() ); |
|
} |
|
|
|
// See if we need to notify items about an upgrade |
|
NotifyItemOnUpgrade( pTFPlayer, nUpgradedAttrDefIndex, bDowngrade ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
static attrib_definition_index_t ApplyUpgrade_Bottle( int iUpgrade, CTFPlayer *pTFPlayer, CEconItemView *pEconItemView, int nCost, bool bDowngrade ) |
|
{ |
|
Assert( pTFPlayer ); |
|
|
|
if ( !pEconItemView ) |
|
return INVALID_ATTRIB_DEF_INDEX; |
|
|
|
const CMannVsMachineUpgrades& upgrade = g_MannVsMachineUpgrades.m_Upgrades[iUpgrade]; |
|
|
|
const CEconItemAttributeDefinition *pAttrDef = GetItemSchema()->GetAttributeDefinitionByName( upgrade.szAttrib ); |
|
if ( !pAttrDef ) |
|
return INVALID_ATTRIB_DEF_INDEX; |
|
|
|
CTFWearable *pWearable = pTFPlayer->GetEquippedWearableForLoadoutSlot( LOADOUT_POSITION_ACTION ); |
|
CTFPowerupBottle *pPowerupBottle = dynamic_cast< CTFPowerupBottle * >( pWearable ); |
|
if ( !pPowerupBottle ) |
|
return INVALID_ATTRIB_DEF_INDEX; |
|
|
|
Assert( pPowerupBottle->GetAttributeContainer()->GetItem() == pEconItemView ); |
|
|
|
const bool bMvM = TFGameRules() && TFGameRules()->IsMannVsMachineMode(); |
|
Assert( !bMvM || g_pPopulationManager ); |
|
|
|
CAttributeList *pAttrList = pEconItemView->GetAttributeList(); |
|
Assert( pAttrList ); |
|
|
|
// Attribute doesn't exist, remove any other attributes -- this code assumes that this is the |
|
// only code path by which bottles will manipulate on-item attributes! |
|
if ( !::FindAttribute( pAttrList, pAttrDef ) ) |
|
{ |
|
// Can't downgrade an attribute that doesn't exist. |
|
if ( bDowngrade ) |
|
return INVALID_ATTRIB_DEF_INDEX; |
|
|
|
// Remove the old attributes |
|
pPowerupBottle->RemoveEffect(); |
|
pAttrList->DestroyAllAttributes(); |
|
|
|
// Forget charges for other attributes and refund player money |
|
if ( bMvM ) |
|
{ |
|
g_pPopulationManager->ForgetOtherBottleUpgrades( pTFPlayer, pEconItemView, iUpgrade ); |
|
} |
|
|
|
// set this afterwards, since it may alter attributes |
|
pPowerupBottle->SetNumCharges( 1 ); |
|
|
|
pAttrList->SetRuntimeAttributeValue( pAttrDef, upgrade.flIncrement ); |
|
pAttrList->SetRuntimeAttributeRefundableCurrency( pAttrDef, nCost ); |
|
} |
|
// attribute exists, just increase number of charges |
|
else |
|
{ |
|
const int nChange = bDowngrade ? -1 : 1; |
|
const int nNewCharges = pPowerupBottle->GetNumCharges() + nChange; |
|
|
|
// is the bottle full? |
|
if ( nNewCharges < 0 || nNewCharges > pPowerupBottle->GetMaxNumCharges() ) |
|
return INVALID_ATTRIB_DEF_INDEX; |
|
|
|
pPowerupBottle->SetNumCharges( nNewCharges ); |
|
|
|
#ifdef DBGFLAG_ASSERT |
|
float flExistingValue; |
|
Assert( ::FindAttribute_UnsafeBitwiseCast<attrib_value_t>( pAttrList, pAttrDef, &flExistingValue ) ); |
|
Assert( AlmostEqual( flExistingValue, upgrade.flIncrement ) ); |
|
#endif // DBGFLAG_ASSERT |
|
pAttrList->AdjustRuntimeAttributeRefundableCurrency( pAttrDef, nCost * nChange ); |
|
|
|
if ( nNewCharges == 0 ) |
|
{ |
|
Assert( bDowngrade ); |
|
|
|
// Downgraded to 0... remove attributes |
|
pPowerupBottle->RemoveEffect(); |
|
pAttrList->DestroyAllAttributes(); |
|
|
|
// Forget charges for other attributes and refund player money |
|
if ( bMvM ) |
|
{ |
|
g_pPopulationManager->ForgetOtherBottleUpgrades( pTFPlayer, pEconItemView, iUpgrade ); |
|
} |
|
} |
|
} |
|
|
|
return pAttrDef->GetDefinitionIndex(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
static attrib_definition_index_t ApplyUpgrade_Default( const CMannVsMachineUpgrades& upgrade, CTFPlayer *pTFPlayer, CEconItemView *pEconItemView, int nCost, bool bDowngrade ) |
|
{ |
|
Assert( pTFPlayer ); |
|
Assert( upgrade.nUIGroup == UIGROUP_UPGRADE_ATTACHED_TO_PLAYER || upgrade.nUIGroup == UIGROUP_UPGRADE_ATTACHED_TO_ITEM ); |
|
// Assert( pEconItemView || upgrade.nUIGroup != UIGROUP_UPGRADE_ATTACHED_TO_ITEM ); // ugh, if loadouts change behind the scenes or if we have bugs elsewhere, we might |
|
// feed an empty slot in here -- check for this case below if ATTACHED_TO_ITEM |
|
|
|
const CEconItemAttributeDefinition *pAttrDef = GetItemSchema()->GetAttributeDefinitionByName( upgrade.szAttrib ); |
|
if ( !pAttrDef ) |
|
return INVALID_ATTRIB_DEF_INDEX; |
|
|
|
Assert( !pAttrDef->BIsSetBonusAttribute() ); |
|
|
|
|
|
CAttributeList *pAttrList = upgrade.nUIGroup == UIGROUP_UPGRADE_ATTACHED_TO_PLAYER |
|
? pTFPlayer->GetAttributeList() |
|
: pEconItemView->GetAttributeList(); |
|
Assert( pAttrList ); |
|
|
|
// ... |
|
float fDefaultValue = pAttrDef->GetDescriptionFormat() == ATTDESCFORM_VALUE_IS_PERCENTAGE || pAttrDef->GetDescriptionFormat() == ATTDESCFORM_VALUE_IS_INVERTED_PERCENTAGE |
|
? 1.0f |
|
: 0.0f; |
|
|
|
// ... |
|
if ( upgrade.nUIGroup == UIGROUP_UPGRADE_ATTACHED_TO_ITEM ) |
|
{ |
|
// If we're trying to attach to an item and we don't have an item to attach to, give up. |
|
if ( !pEconItemView ) |
|
return INVALID_ATTRIB_DEF_INDEX; |
|
|
|
::FindAttribute_UnsafeBitwiseCast<attrib_value_t>( pEconItemView->GetItemDefinition(), pAttrDef, &fDefaultValue ); |
|
} |
|
|
|
// if the attribute exists, add the increment (but not if it's a set bonus attribute, they're recreated on each respawn) |
|
float flIncrement = upgrade.flIncrement; |
|
|
|
float flCurrentValue; |
|
if ( ::FindAttribute_UnsafeBitwiseCast<attrib_value_t>( pAttrList, pAttrDef, &flCurrentValue ) ) |
|
{ |
|
const float flCap = upgrade.flCap; |
|
|
|
if ( !bDowngrade && BIsAttributeValueWithDeltaOverCap( flCurrentValue, flIncrement, flCap ) ) |
|
return INVALID_ATTRIB_DEF_INDEX; |
|
|
|
// Add the increment |
|
float flNewValue = 0.0f; |
|
|
|
if ( bDowngrade ) |
|
{ |
|
float flInitialValue = fDefaultValue; |
|
flIncrement = fabsf( flIncrement ); |
|
|
|
if ( AlmostEqual( flCurrentValue, flCap ) && !AlmostEqual( flInitialValue, fDefaultValue ) ) |
|
{ |
|
// If we're at the cap and the initial value isn't normal, we might need to do a smaller increment |
|
// This is because incrementing from the initial value in steps would have gone past the hard cap |
|
float flStart = flInitialValue; |
|
|
|
if ( flIncrement > 0 ) |
|
{ |
|
while ( flStart < flCap && !AlmostEqual( flStart, flCap ) ) |
|
{ |
|
flStart += flIncrement; |
|
} |
|
} |
|
else |
|
{ |
|
while ( flStart > flCap && !AlmostEqual( flStart, flCap ) ) |
|
{ |
|
flStart += flIncrement; |
|
} |
|
} |
|
|
|
const float flDiff = fabsf( flCap - flStart ); |
|
if ( !AlmostEqual( flIncrement, flDiff ) ) |
|
{ |
|
flIncrement -= flDiff; |
|
} |
|
} |
|
|
|
flNewValue = Approach( flInitialValue, flCurrentValue, flIncrement ); |
|
|
|
// We downgraded back to the point of not needing the attribute |
|
if ( AlmostEqual( flNewValue, flInitialValue ) ) |
|
{ |
|
Assert( bDowngrade ); |
|
|
|
pAttrList->RemoveAttribute( pAttrDef ); |
|
return pAttrDef->GetDefinitionIndex(); |
|
} |
|
} |
|
else |
|
{ |
|
flNewValue = Approach( flCap, flCurrentValue, fabsf( flIncrement ) ); |
|
} |
|
|
|
pAttrList->SetRuntimeAttributeValue( pAttrDef, flNewValue ); |
|
pAttrList->AdjustRuntimeAttributeRefundableCurrency( pAttrDef, nCost ); |
|
return pAttrDef->GetDefinitionIndex(); |
|
} |
|
|
|
if ( bDowngrade ) |
|
{ |
|
// Can't downgrade an attribute we didn't find |
|
return INVALID_ATTRIB_DEF_INDEX; |
|
} |
|
|
|
// Didn't exist, so we need to add the attribute. |
|
|
|
// Convert the increment into an actual multiplier amount |
|
pAttrList->SetRuntimeAttributeValue( pAttrDef, fDefaultValue + flIncrement ); |
|
pAttrList->SetRuntimeAttributeRefundableCurrency( pAttrDef, nCost ); |
|
|
|
// For NotifyItemOnUpgrade() - can't do it here because we Regenerate() downstream |
|
return pAttrDef->GetDefinitionIndex(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
attrib_definition_index_t CUpgrades::ApplyUpgradeToItem( CTFPlayer *pTFPlayer, CEconItemView *pView, int iUpgrade, int nCost, bool bDowngrade, bool bIsFresh ) |
|
{ |
|
Assert( pTFPlayer ); |
|
Assert( g_MannVsMachineUpgrades.m_Upgrades.IsValidIndex( iUpgrade ) ); |
|
|
|
if ( !pTFPlayer || !pTFPlayer->CanPurchaseUpgrades() ) |
|
return INVALID_ATTRIB_DEF_INDEX; |
|
|
|
const CMannVsMachineUpgrades& upgrade = g_MannVsMachineUpgrades.m_Upgrades[iUpgrade]; |
|
|
|
const CEconItemAttributeDefinition *pAttrDef = GetItemSchema()->GetAttributeDefinitionByName( upgrade.szAttrib ); |
|
if ( !pAttrDef ) |
|
return INVALID_ATTRIB_DEF_INDEX; |
|
|
|
bool bIsBottle = upgrade.nUIGroup == UIGROUP_POWERUPBOTTLE; |
|
|
|
ReportUpgrade( |
|
pTFPlayer, |
|
pView ? pView->GetItemDefIndex() : 0, |
|
pAttrDef->GetDefinitionIndex(), |
|
upgrade.nQuality, |
|
nCost, |
|
bDowngrade, |
|
bIsFresh, |
|
bIsBottle |
|
); |
|
|
|
// ...powerup bottle? |
|
if ( bIsBottle ) |
|
return ApplyUpgrade_Bottle( iUpgrade, pTFPlayer, pView, nCost, bDowngrade ); |
|
|
|
// ...player upgrade? |
|
if ( upgrade.nUIGroup == UIGROUP_UPGRADE_ATTACHED_TO_PLAYER ) |
|
return ApplyUpgrade_Default( upgrade, pTFPlayer, pView, nCost, bDowngrade ); |
|
|
|
Assert( upgrade.nUIGroup == UIGROUP_UPGRADE_ATTACHED_TO_ITEM ); |
|
return ApplyUpgrade_Default( upgrade, pTFPlayer, pView, nCost, bDowngrade ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Given an upgrade index, return it's associated attribute description |
|
//----------------------------------------------------------------------------- |
|
const char *CUpgrades::GetUpgradeAttributeName( int iUpgrade ) const |
|
{ |
|
if ( iUpgrade < 0 || iUpgrade >= g_MannVsMachineUpgrades.m_Upgrades.Count() ) |
|
return NULL; |
|
|
|
return g_MannVsMachineUpgrades.m_Upgrades[ iUpgrade ].szAttrib; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CUpgrades::NotifyItemOnUpgrade( CTFPlayer *pTFPlayer, attrib_definition_index_t nAttrDefIndex, bool bDowngrade /*= false*/ ) |
|
{ |
|
if ( !pTFPlayer ) |
|
return; |
|
|
|
switch( nAttrDefIndex ) |
|
{ |
|
case 286: // "engy building health bonus" |
|
{ |
|
// Tell the wrench we've upgraded our object health (which handles the rest) |
|
CTFWrench *pWrench = dynamic_cast<CTFWrench *>( pTFPlayer->Weapon_OwnsThisID( TF_WEAPON_WRENCH ) ); |
|
if ( pWrench ) |
|
{ |
|
pWrench->ApplyBuildingHealthUpgrade(); |
|
} |
|
} |
|
break; |
|
case 320: // "robot sapper" |
|
{ |
|
// Sets the UI active |
|
CTFWeaponBuilder *pBuilder = dynamic_cast<CTFWeaponBuilder *>( pTFPlayer->Weapon_OwnsThisID( TF_WEAPON_BUILDER ) ); |
|
if ( pBuilder ) |
|
{ |
|
pBuilder->m_bRoboSapper.Set( true ); |
|
} |
|
} |
|
break; |
|
case 351: |
|
if ( bDowngrade ) |
|
{ |
|
// if we're refunding the engy_disposable_sentries attribute we need to destroy the disposable sentry if we have one |
|
if ( pTFPlayer->IsPlayerClass( TF_CLASS_ENGINEER ) ) |
|
{ |
|
for ( int i = pTFPlayer->GetObjectCount() - 1; i >= 0; i-- ) |
|
{ |
|
CBaseObject *pObj = pTFPlayer->GetObject( i ); |
|
if ( pObj ) |
|
{ |
|
if ( ( pObj->GetType() == OBJ_SENTRYGUN ) && ( pObj->IsDisposableBuilding() ) ) |
|
{ |
|
pObj->DetonateObject(); |
|
} |
|
} |
|
} |
|
} |
|
} |
|
break; |
|
case 375: |
|
{ |
|
// Reduce buff duration when a Heavy gets the Rage upgrade in MvM |
|
if ( TFGameRules() && TFGameRules()->GameModeUsesUpgrades() ) |
|
{ |
|
if ( pTFPlayer->IsPlayerClass( TF_CLASS_HEAVYWEAPONS ) ) |
|
{ |
|
const int mod_buff_duration = 319; |
|
const float flMod = 0.5f; |
|
CEconItemAttributeDefinition *pDef = GetItemSchema()->GetAttributeDefinition( mod_buff_duration ); |
|
if ( !pDef ) |
|
return; |
|
|
|
pTFPlayer->GetAttributeList()->SetRuntimeAttributeValue( pDef, flMod ); |
|
} |
|
} |
|
} |
|
break; |
|
case 499: |
|
{ |
|
// Increase buff duration for Medics with projectile shields |
|
if ( TFGameRules() && TFGameRules()->GameModeUsesUpgrades() ) |
|
{ |
|
if ( pTFPlayer->IsPlayerClass( TF_CLASS_MEDIC ) ) |
|
{ |
|
const int mod_buff_duration = 319; |
|
const float flMod = 1.2f; |
|
CEconItemAttributeDefinition *pDef = GetItemSchema()->GetAttributeDefinition( mod_buff_duration ); |
|
if ( !pDef ) |
|
return; |
|
|
|
pTFPlayer->GetAttributeList()->SetRuntimeAttributeValue( pDef, flMod ); |
|
} |
|
} |
|
} |
|
break; |
|
#ifdef STAGING_ONLY |
|
case 555: // medigun specialist |
|
{ |
|
static UpgradeAttribBlock_t upgradeBlock[] = |
|
{ |
|
{ "healing mastery", 4.f, LOADOUT_POSITION_SECONDARY }, |
|
{ "overheal expert", 4.f, LOADOUT_POSITION_SECONDARY }, |
|
{ "uber duration bonus", 4.f, LOADOUT_POSITION_SECONDARY }, |
|
{ "ubercharge rate bonus", 2.f, LOADOUT_POSITION_SECONDARY }, |
|
}; |
|
|
|
ApplyUpgradeAttributeBlock( upgradeBlock, ARRAYSIZE( upgradeBlock ), pTFPlayer, bDowngrade ); |
|
} |
|
break; |
|
case 605: // master sniper |
|
{ |
|
static UpgradeAttribBlock_t upgradeBlock[] = |
|
{ |
|
{ "projectile penetration", 1.f, LOADOUT_POSITION_PRIMARY }, |
|
{ "SRifle Charge rate increased", 1.5f, LOADOUT_POSITION_PRIMARY }, |
|
{ "faster reload rate", 0.6f, LOADOUT_POSITION_PRIMARY }, |
|
}; |
|
|
|
ApplyUpgradeAttributeBlock( upgradeBlock, ARRAYSIZE( upgradeBlock ), pTFPlayer, bDowngrade ); |
|
} |
|
break; |
|
case 611: // airborne infantry |
|
{ |
|
static UpgradeAttribBlock_t upgradeBlock[] = |
|
{ |
|
{ "increased air control", 10.f, LOADOUT_POSITION_PRIMARY }, |
|
{ "rocket launch impulse", 1, LOADOUT_POSITION_PRIMARY }, |
|
{ "cancel falling damage", 1, LOADOUT_POSITION_PRIMARY }, |
|
}; |
|
|
|
ApplyUpgradeAttributeBlock( upgradeBlock, ARRAYSIZE( upgradeBlock ), pTFPlayer, bDowngrade ); |
|
} |
|
break; |
|
case 624: // construction expert |
|
{ |
|
static UpgradeAttribBlock_t upgradeBlock[] = |
|
{ |
|
{ "build rate bonus", 0.7f, LOADOUT_POSITION_MELEE }, |
|
{ "maxammo metal increased", 1.5f, LOADOUT_POSITION_INVALID }, |
|
{ "engy building health penalty", 0.7f, LOADOUT_POSITION_MELEE }, |
|
}; |
|
|
|
ApplyUpgradeAttributeBlock( upgradeBlock, ARRAYSIZE( upgradeBlock ), pTFPlayer, bDowngrade ); |
|
} |
|
break; |
|
case 626: // support engineer |
|
{ |
|
static UpgradeAttribBlock_t upgradeBlock[] = |
|
{ |
|
{ "teleporter recharge rate bonus", 0.5f, LOADOUT_POSITION_INVALID }, |
|
{ "teleporter speed boost", 1, LOADOUT_POSITION_INVALID }, |
|
{ "bidirectional teleport", 1, LOADOUT_POSITION_INVALID }, |
|
{ "dispenser rate bonus", 1.25f, LOADOUT_POSITION_INVALID }, |
|
{ "engy dispenser radius increased", 3.f, LOADOUT_POSITION_INVALID }, |
|
}; |
|
|
|
ApplyUpgradeAttributeBlock( upgradeBlock, ARRAYSIZE( upgradeBlock ), pTFPlayer, bDowngrade ); |
|
} |
|
break; |
|
#endif // STAGING_ONLY |
|
|
|
default: |
|
break; |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Reports the Upgrade to systems that care (clients / ogs) |
|
//----------------------------------------------------------------------------- |
|
void CUpgrades::ReportUpgrade( CTFPlayer *pTFPlayer, int nItemDef, int nAttributeDef, int nQuality, int nCost, bool bDowngrade, bool bIsFresh, bool bIsBottle /*= false*/ ) |
|
{ |
|
if ( !pTFPlayer ) |
|
{ |
|
return; |
|
} |
|
|
|
if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() ) |
|
{ |
|
// Calculate how much money is being used on active class / items |
|
int nSpending = 0; |
|
int iClass = pTFPlayer->GetPlayerClass()->GetClassIndex(); |
|
CUtlVector< CUpgradeInfo > *upgrades = g_pPopulationManager->GetPlayerUpgradeHistory( pTFPlayer ); |
|
if ( upgrades ) |
|
{ |
|
for( int u = 0; u < upgrades->Count(); ++u ) |
|
{ |
|
// Class Match, Check to see if we have this item equipped |
|
if ( iClass == upgrades->Element(u).m_iPlayerClass) |
|
{ |
|
// Player upgrade |
|
if ( upgrades->Element( u ).m_itemDefIndex == INVALID_ITEM_DEF_INDEX ) |
|
{ |
|
nSpending += upgrades->Element(u).m_nCost; |
|
continue; |
|
} |
|
|
|
// Item upgrade, look at equipment only not miscs or bottle |
|
for ( int itemIndex = 0; itemIndex <= LOADOUT_POSITION_PDA2; itemIndex++ ) |
|
{ |
|
CEconItemView *pItem = pTFPlayer->GetLoadoutItem( iClass, itemIndex, true ); |
|
if ( upgrades->Element(u).m_itemDefIndex == pItem->GetItemDefIndex() ) |
|
{ |
|
nSpending += upgrades->Element(u).m_nCost; |
|
break; |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
CMannVsMachineStats *pStats = MannVsMachineStats_GetInstance(); |
|
if ( pStats ) |
|
{ |
|
pStats->NotifyPlayerActiveUpgradeCosts( pTFPlayer, nSpending ); |
|
} |
|
|
|
// Only report fresh upgrades |
|
if ( !bIsFresh ) |
|
return; |
|
|
|
MannVsMachineStats_PlayerEvent_Upgraded( pTFPlayer, nItemDef, nAttributeDef, nQuality, nCost, bIsBottle ); |
|
} |
|
|
|
if ( bDowngrade ) |
|
{ |
|
return; |
|
} |
|
|
|
pTFPlayer->EmitSound( "MVM.PlayerUpgraded" ); |
|
|
|
IGameEvent *event = gameeventmanager->CreateEvent( "player_upgraded" ); |
|
if ( event ) |
|
{ |
|
gameeventmanager->FireEvent( event ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CUpgrades::RestoreItemAttributeToBaseValue( CEconItemAttributeDefinition *pAttrib, CEconItemView *pItem ) |
|
{ |
|
Assert( pAttrib ); |
|
Assert( pItem ); |
|
|
|
CAttributeList *pAttrList = pItem->GetAttributeList(); |
|
Assert( pAttrList ); |
|
|
|
float flCurrentValue; |
|
if ( ::FindAttribute_UnsafeBitwiseCast<attrib_value_t>( pAttrList, pAttrib, &flCurrentValue ) ) |
|
{ |
|
float fDefaultValue = pAttrib->GetDescriptionFormat() == ATTDESCFORM_VALUE_IS_PERCENTAGE || pAttrib->GetDescriptionFormat() == ATTDESCFORM_VALUE_IS_INVERTED_PERCENTAGE ? 1.f : 0.f; |
|
::FindAttribute_UnsafeBitwiseCast<attrib_value_t>( pAttrList, pAttrib, &fDefaultValue ); |
|
|
|
// We don't need the attribute |
|
if ( AlmostEqual( flCurrentValue, fDefaultValue ) ) |
|
{ |
|
pAttrList->RemoveAttribute( pAttrib ); |
|
return; |
|
} |
|
|
|
pAttrList->SetRuntimeAttributeValue( pAttrib, fDefaultValue ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CUpgrades::RestorePlayerAttributeToBaseValue( CEconItemAttributeDefinition *pAttrib, CTFPlayer *pTFPlayer ) |
|
{ |
|
Assert( pAttrib ); |
|
Assert( pTFPlayer ); |
|
|
|
CAttributeList *pAttrList = pTFPlayer->GetAttributeList(); |
|
Assert( pAttrList ); |
|
|
|
float flCurrentValue; |
|
if ( ::FindAttribute_UnsafeBitwiseCast<attrib_value_t>( pAttrList, pAttrib, &flCurrentValue ) ) |
|
{ |
|
float fDefaultValue = pAttrib->GetDescriptionFormat() == ATTDESCFORM_VALUE_IS_PERCENTAGE || pAttrib->GetDescriptionFormat() == ATTDESCFORM_VALUE_IS_INVERTED_PERCENTAGE ? 1.f : 0.f; |
|
::FindAttribute_UnsafeBitwiseCast<attrib_value_t>( pAttrList, pAttrib, &fDefaultValue ); |
|
|
|
// We don't need the attribute |
|
if ( AlmostEqual( flCurrentValue, fDefaultValue ) ) |
|
{ |
|
pAttrList->RemoveAttribute( pAttrib ); |
|
return; |
|
} |
|
|
|
pAttrList->SetRuntimeAttributeValue( pAttrib, fDefaultValue ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CUpgrades::ApplyUpgradeAttributeBlock( UpgradeAttribBlock_t *upgradeBlock, int upgradeCount, CTFPlayer *pPlayer, bool bDowngrade ) |
|
{ |
|
if ( !pPlayer ) |
|
return; |
|
|
|
for ( int i = 0; i < upgradeCount; i++ ) |
|
{ |
|
if ( !upgradeBlock[i].szName || !upgradeBlock[i].szName[0] ) |
|
continue; |
|
|
|
CAttributeList *pAttribList = NULL; |
|
CEconItemView *pItem = NULL; |
|
|
|
// Player or item? |
|
if ( upgradeBlock[i].iSlot == LOADOUT_POSITION_INVALID ) |
|
{ |
|
pAttribList = pPlayer->GetAttributeList(); |
|
} |
|
else |
|
{ |
|
pItem = CTFPlayerSharedUtils::GetEconItemViewByLoadoutSlot( pPlayer, upgradeBlock[i].iSlot ); |
|
if ( !pItem ) |
|
continue; |
|
|
|
pAttribList = pItem->GetAttributeList(); |
|
} |
|
|
|
if ( !pAttribList ) |
|
continue; |
|
|
|
CEconItemAttributeDefinition *pDef = ItemSystem()->GetItemSchema()->GetAttributeDefinitionByName( upgradeBlock[i].szName ); |
|
if ( !pDef ) |
|
continue; |
|
|
|
if ( bDowngrade ) |
|
{ |
|
if ( pItem ) |
|
{ |
|
RestoreItemAttributeToBaseValue( pDef, pItem ); |
|
continue; |
|
} |
|
else |
|
{ |
|
RestorePlayerAttributeToBaseValue( pDef, pPlayer ); |
|
} |
|
} |
|
else |
|
{ |
|
pAttribList->SetRuntimeAttributeValue( pDef, upgradeBlock[i].flValue ); |
|
} |
|
} |
|
|
|
pPlayer->NetworkStateChanged(); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|