This is a sub-page of Development:Team Fortress 2.
Item Attributes#
Several experimental or otherwise unused item attributes exist in the code.
Disabled SMG Headshot Ability#
Located in tf_weapon_smg.cpp, this code would allow the SMG to deal various damage depending on which body part which has been damaged (retail game only uses critical damage on headshots). This feature is disabled because of the g_aWeaponDamageTypes variable not having the DMG_USE_HITLOCATIONS attribute for the SMG and Cleaner's Carbine. If added, headshots work as well as with all Sniper's primaries, shown here.
int CTFSMG::GetDamageType( void ) const
{
if ( CanHeadshot() )
{
int iDamageType = BaseClass::GetDamageType() | DMG_USE_HITLOCATIONS;
return iDamageType;
}
return BaseClass::GetDamageType();
Disguise as Dispenser#
First discovered via the existence of the unused TF_COND_DISGUISED_AS_DISPENSER condition flag, this condition turned out to be for an unused item attribute; disguise_as_dispenser_on_crouch. Within the CTFPlayer::TFPlayerThink() function is a bit of code designed to run only when:
- You are a Spy
- You are crouching
- You have an item with the disguise_as_dispenser_on_crouch attribute on it
If these conditions are met, your player model is swapped to that of a Dispenser.
A major benefit to this disguise is that Sentry Guns won't shoot you.
if ( GetPlayerClass()->GetClassIndex() == TF_CLASS_SPY && ( GetFlags() & FL_DUCKING ) && ( pGroundEntity != NULL ) )
{
int nDisguiseAsDispenserOnCrouch = 0;
CALL_ATTRIB_HOOK_FLOAT( nDisguiseAsDispenserOnCrouch, disguise_as_dispenser_on_crouch );
if ( nDisguiseAsDispenserOnCrouch != 0 )
{
m_Shared.AddCond( TF_COND_DISGUISED_AS_DISPENSER, 0.5f );
}
}
Mann vs. Machine#
Scrapped Canteen Types and Features#
During MvM's development, initially there were up to seven types of canteen charges available for mercenaries to use. Only five of them (crits, Über, upgrade, ammo and teleporting to spawn) arrived to the release version.
The sixth canteen type is internally called RADIUS_STEALTH and included in most lists of canteen types even without the STAGING_ONLY tag (though _DEBUG_tag can be seen once). This canteen would be available only for Spy3. Judging by code comments, this charge would apply the Spy's cloak effect to both its user and any nearby players.
The seventh canteen type is internally called SEE_CASH_THROUGH_WALL. Its mentions are always under the STAGING_ONLY tag. The effect of this canteen is clear from its name - it would allow to see currency piles through walls. It's unclear which mercenaries would be affected by this effect; there are indications in the game's code that the initial ability to see cash through walls was innate for Scout to Spy, and unused sound files of picking up money for Heavy and Medic could be indications that SEE_CASH_THROUGH_WALL was intended to be applied on 'all' players.
Additionally, the Canteen Specialist upgrade would originally increase maximum canteen charges from 3 to 6, which isn't included in the final game.
// Default canteen has 3 charges. Medic canteen specialist allows purchasing 3 more charges.
// If anything else increases max charges, we need to refactor how canteen specialist is handled.
Assert( iMaxNumCharges >= 0 && iMaxNumCharges <= 6 );
iMaxNumCharges = Min( iMaxNumCharges, 6 );
Medi Gun Heal Buildings ability#
Some remnants of code that would allow Medic's Medi Gun to heal buildings, present in medigun.cpp. Based on the code, it's very likely this was exclusive to MvM.
#ifdef STAGING_ONLY
if ( TFGameRules() && TFGameRules()->GameModeUsesUpgrades() )
{
if ( IsAttachedToBuilding() )
{
// Heal building
if ( m_hHealingTarget->GetHealth() < m_hHealingTarget->GetMaxHealth() )
{
CBaseEntity *pEntity = m_hHealingTarget;
CBaseObject *pObject = dynamic_cast<CBaseObject*>( pEntity );
if ( pObject )
{
pObject->SetHealth( m_hHealingTarget->GetHealth() + ( GetHealRate() / 10.f ) );
}
}
}
}
#endif // STAGING_ONLY
Unused Canteen Specialist cost reducing formula#
A formula for Canteen Specialist upgrade that reduces the cost by 10% per upgrade tick.
// iCost *= ( 1.f - ( iCanteenSpec * 0.1f ) );
// iCost = Max( 1, ( iCost - ( iCost % 5 ) ) );
Scrapped MvM Upgrade#
There's commented code that hints of some kind of mass-stealth upgrade for MvM. Nothing similar was added in retail version of the game.
// An upgrade in MvM grants AE stealth where the player can fire
// while in stealth, and for a short period after it drops
if ( enemy->m_Shared.InCond( TF_COND_STEALTHED_USER_BUFF_FADING ) )
Unused MvM Difficulty#
A code chunk that describes a difficulty feature in MvM. According to this code, 5 difficulty levels were supposed to be added, and they would change only two parameters - HP and damage output of robots. Icons labeled "mvm_difficulty" can be found within the game's files. 5
void MVMSkillChangedCallback( IConVar *pVar, const char *pOldString, float flOldValue )
{
ConVarRef cVarRef( pVar );
// Testing the effects of skill setting
float flHealth = 1.f; // Health modifier for bots
float flDamage = 1.f; // Damage modifier in bot vs player
switch ( cVarRef.GetInt() )
{
case 1:
{
flHealth = 0.75f;
flDamage = 0.75f;
break;
}
case 2:
{
flHealth = 0.9f;
flDamage = 0.9f;
break;
}
// "Normal"
case 3:
{
flHealth = 1.f;
flDamage = 1.f;
break;
}
case 4:
{
flHealth = 1.10f;
flDamage = 1.10f;
break;
}
case 5:
{
flHealth = 1.25f;
flDamage = 1.25f;
break;
}
}
tf_populator_health_multiplier.SetValue( flHealth );
tf_populator_damage_multiplier.SetValue( flDamage );
}
ConVar tf_mvm_skill( "tf_mvm_skill", "3", FCVAR_DONTRECORD | FCVAR_REPLICATED | TF_MVM_FCVAR_CHEAT, "Sets the challenge level of the invading bot army. 1 = easiest, 3 = normal, 5 = hardest", true, 1, true, 5, MVMSkillChangedCallback );
There is a possibility that leveled attributes added into TF2 code in July 2010 are linked to these difficulty levels. In that case, these levels would have been initially planned for the earlier Creep Mode, judging by the date of adding leveled attributes (as headers of source code point, the most early MvM-related date is May 2011.
Scrapped Death Sounds#
Sound cache code reveals that during development of Mann vs Machine, every class would emit unique death sounds. The final game plays a standard death sound for everyone.6
void CPopulationManager::Precache( void )
{
PrecacheScriptSound( "music.mvm_end_wave" );
PrecacheScriptSound( "music.mvm_end_tank_wave" );
PrecacheScriptSound( "music.mvm_end_mid_wave" );
PrecacheScriptSound( "music.mvm_end_last_wave" );
PrecacheScriptSound( "MVM.PlayerUpgraded" );
PrecacheScriptSound( "MVM.PlayerBoughtIn" );
PrecacheScriptSound( "MVM.PlayerUsedPowerup" );
PrecacheScriptSound( "MVM.PlayerDied" );
PrecacheScriptSound( "MVM.PlayerDiedScout" );
PrecacheScriptSound( "MVM.PlayerDiedSniper" );
PrecacheScriptSound( "MVM.PlayerDiedSoldier" );
PrecacheScriptSound( "MVM.PlayerDiedDemoman" );
PrecacheScriptSound( "MVM.PlayerDiedMedic" );
PrecacheScriptSound( "MVM.PlayerDiedHeavy" );
PrecacheScriptSound( "MVM.PlayerDiedPyro" );
PrecacheScriptSound( "MVM.PlayerDiedSpy" );
PrecacheScriptSound( "MVM.PlayerDiedEngineer" );
Alternate Buyback#
An alternative buyback method for MvM is mentioned in the code. Buybacks would be limited to three times per round, instead of just costing credits like in the final game. 7
// New system (finite buybacks per-wave, not currency-based)
if ( tf_mvm_buybacks_method.GetBool() )
{
if ( g_pPopulationManager->GetNumBuybackCreditsForPlayer( this ) )
{
bSuccess = true;
iCost = 1;
g_pPopulationManager->RemoveBuybackCreditFromPlayer( this );
There are also several mentions of players losing currency after death, which is absent in the final game.
Unused starting cash bonus feature#
MvM initially had a system to encourage players to play solo-queue lobbies by increasing starting credits by 20% for every unique party in the lobby, starting from second. This would lead to a 100% starting credit bonus to teams with random players only, and no bonus at all to a 6-player party lobby. This feature is under the STAGING_ONLY tag.
#ifdef STAGING_ONLY
// only calculate lobby bonus one time when the lobby first match to the server
CMatchInfo *pMatch = GTFGCClientSystem()->GetMatch();
if ( pMatch && GetWaveNumber() == 0 && m_nLobbyBonusCurrency == 0 )
{
// Count unique parties
CUtlVector<uint64> vecPartyIDs;
int total = pMatch->GetNumTotalMatchPlayers();
for ( int idx = 0; idx < total; idx++ )
{
uint64 uPartyID = pMatch->GetMatchDataForPlayer( idx )->uPartyID;
if ( vecPartyIDs.Find( uPartyID ) == vecPartyIDs.InvalidIndex() )
{
vecPartyIDs.AddToTail( uPartyID );
}
}
int nUniqueParties = vecPartyIDs.Count();
// give some bonus currency for each extra unique party in the lobby
float flBonusScale = tf_mvm_mm_bonus.GetFloat() * ( nUniqueParties - 1 );
m_nLobbyBonusCurrency = flBonusScale * m_nStartingCurrency;
}
#endif // STAGING_ONLY
Unused Upgrades#
There are mentions of several unusual upgrades for MvM under STAGING_ONLY tag 8. Internal effect numbers of these upgrades were removed from the effect number list of the final game. Unlike all upgrades in the release version (except Medi Gun Specialist), they were intended to affect more than one player at a time and seem much more general.
#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;
The Medi Gun Specialist upgrade differs from the release one. They both have the Healing Mastery parameter, which consists of healing rate and self-regeneration rate, but the unused one also has Overheal Expert, Über Duration Bonus and Übercharge Rate Bonus included, which are all used in the final game as separate upgrades.
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;
master sniper seems to be general enhancement of Sniper's primary weapon and uses attributed that are used in final version as separate upgrades.
According to other code snippets from various game files, the "Master Sniper" upgrade would have 2 tiers and give Sniper's primary weapon a penetration ability 9, reload speed boost (0.6 rate multiplier for tier 1 and 0.3 for tier 2) 10 11, as well as a charge speed boost for all Sniper Rifles (1.5 multiplier for tier 1 and 3 for tier 2) 12. Tier 1 stats are the same with the ones from the upgrades.cpp file.
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;
None of Airborne Infantry's three parameters are used in the final as MvM upgrades, and in fact one of them ("rocket launch impulse") was completely scrapped. Judging by its attribute names, it was supposed to help Soldier's air control, similar to the Mantreads.
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;
Construction Expert (clearly intended for Engineer) consists of one parameter that is used in MvM as upgrade (maxammo metal increased), one parameter that is used not as upgrade but rather as attribute for weapons, in particular, the Jag(build rate bonus), and one parameter that seems to have been removed from the final version of the game (engy building health penalty).
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
Another Engineer upgrade includes two parameters that are used in final game as separate upgrades ("bidirectional teleport" and "engy dispenser radius increased") and three other parameters that seem to be removed from the final game. The purpose of them are comprehensive from their names - they would increase teleporter recharge speed (from 3 seconds to 1.5 in the case of level 3 teleporters) and speed of health and ammo dispensing from the dispenser, though it's unclear whether interval between dispensing or amount of health and ammo given would be affected. "teleporter speed boost" would give a speed boost on use (similar to that of the Concheror or the Disciplinary Action). This could allude to the scrapped Speed Pad building, as that had a similar effect on use, although it's highly likely this was just meant for the regular teleporters.
There are also mentions of some scrapped upgrades in gamerules.cpp file13, along with indications which class it belongs to.
case 483: // "medic machinery beam"
Most likely was intended for Medic's scrapped upgrade which would allow him to "heal" buildings.
case 523: // "arrow mastery"
Could be chained with aforementioned Master Sniper upgrade when the Huntsman or Fortified Compound are equipped.
#ifdef STAGING_ONLY
case 601: // "ability spy traps"
case 603: // "phase cloak"
#endif // STAGING_ONLY
{
return ( pPlayer->IsPlayerClass( TF_CLASS_SPY ) && iWeaponID == TF_WEAPON_BUILDER );
}
break;
Spy Traps are a scrapped Spy ability/upgrade for MvM and "phase cloak" was intended for a scrapped cloak watch, though here this attribute is tied to Spy's PDA, which was intended for using Spy Traps.
case 440: // "clip size upgrade atomic"
{
return pWeapon && pWeapon->IsBlastImpactWeapon();
}
break;
case 604: // sniper cloak
case 605: // master sniper
{
return ( pPlayer->IsPlayerClass( TF_CLASS_SNIPER ) && iWeaponSlot == TF_WPN_TYPE_PRIMARY );
case 631: // ability doubletap teleport
{
return ( pPlayer->IsPlayerClass( TF_CLASS_SCOUT ) && iWeaponSlot == TF_WPN_TYPE_PRIMARY );
Unused MvM Tank Event#
There's code for some kind of damage indication ("FireOutput") for the MvM tank, and just like with boss_alpha, every 10% HP lost would create an indication. 14
Buildables#
Mini-Dispenser#
Originally a model submitted to the Steam Workshop, the Mini-Dispenser has 100 health and costs 70 metal. It performs identically to a level one dispenser, albeit the ammo replenish rate being doubled. The total "metal" capacity of the Mini-Dispenser would be halved to compensate.15
Staging-only code shows that the Mini-Dispenser would deal damage when manually detonated with the Destruction PDA, with its damage scaling based on how much "metal" is in the Dispenser. This behaviour is similar to the behaviour in Team Fortress Classic, though it also deals damage when destroyed through external means in TFC. The Mini-Dispenser's explosion would deal a base 50 damage in a 150-unit radius (no metal), and up to 300 damage in a 200-unit radius (full metal).16
Spy Traps#
Spy Traps are buildings that were supposed to be placed by Spy in Mann vs Machine17. Their placeholder model is the standard Engineer teleporter. All of them would only be active for a limited time, and would self-destruct when that time expired.
Three types of Spy Traps are present in game code 18 - RADIUS_STEALTH, MAGNET and REPROGRAMMER, plus a fourth - REPULSOR, that exists as only a single mention in the code. Their names are self-explanitory:
- RADIUS_STEALTH would grant all nearby teammates (within 250 HU radius) 2 seconds of invisibility;
- MAGNET would attract all robots within 700 HU radius and direct visibility to itself with significant force (650);
- REPROGRAM would turn every small robot that touched it to the opposite team. Robots under this condition are supposed to move twice as fast.19
Expiration time would be different for all trap types - 20 seconds for REPROGRAMMED, 10 second for RADIUS_STEALTH and only 5 for MAGNET.
Scrapped Teleporter variants#
Multiple replacement items for the Teleporter slots, similar to the Mini-Sentry and cut Mini-Dispenser, were considered.
Speed Pad#
The Speed Pad, also referred as "Speed Teleporter"20 would grant a temporary speed boost effect21 to any teammate who walked over it. This building would be cheaper than standard Teleporter, costing a tiny 25 metal22, and also being influenced by metal cost modifiers like the Eureka Effect23. Upgrading would always cost 100 metal (ignoring modifiers from weapons)24, affecting the cooldown between each speed boost application25 and the boost duration26:
Building level |
Recharge time |
Effect duration |
|---|---|---|
1 |
1,67 |
4 |
2 |
1,33 |
5 |
3 |
1 |
6 |
This building was referenced in a modification to the code|tf/scripts/game_sounds_weapons.txt file in the August 12, 2014 patch of End of the Line update. The following code was added to the file:
// ------------------------------------------------------------------------------------ //
// SPEEDPAD
// ------------------------------------------------------------------------------------ //
"Building_Speedpad.Boost"
{
"channel" "CHAN_STATIC"
"volume" "1.0"
"soundlevel" "SNDLVL_74dB"
"wave" ")weapons/speed_pad.wav"
}
Catapult#
The Catapult was meant to launch teammates upward.27 A comment mentions that this code was copied from Portal 2's Aerial Faith Plate code and then modified (with prediction errors). The FGD file for TF2 (which contains entity definitions that can be recognised by Hammer) also includes a carbon copy information for the Portal 2 entity.
The build speed for this teleporter type is increased by four times (approximately 5.2 seconds) when constructed and seven times (3 seconds) when redeployed.28
Other#
Unused "bonus_round" event#
There's some code that allows players to spawn MONOCULUS! as "bonus_boss" in "bonus_round".29. It is under the STAGING_ONLY tag.
The exact usage of this is unknown. This code is stored in the population manager file, which is used for MvM purposes, but also contains some Raid Mode remnants. MONOCULUS! was added on October 27, 2011, so there's very little possibility that it was supposed to be for Raid Mode, or specifically, Boss Battle Mode. It's possible it was supposed to be a part of Wave 666 (Caliginous Caper).
Unused Lunchbox restoring ammo behavior#
There's unused code30 that would allow Heavy's lunchbox items to restore ammo.
// Restore ammo if applicable
if ( nLunchBoxType == LUNCHBOX_ADDS_AMMO )
{
int maxPrimary = pPlayer->GetMaxAmmo( TF_AMMO_PRIMARY );
pPlayer->GiveAmmo( maxPrimary * 0.25, TF_AMMO_PRIMARY, true );
}
}
Unused Teleporter Explosion Damage feature#
There's a single string in teleporter.cpp which indicates that the Teleporter was supposed to deal minor damage upon destruction (exploding)31. It is unclear what exact conditions would bring such damage type and to what team, however. It could be damage to teammates that stand on/near the Teleporter when it is destroyed by the enemy team, or damage to the enemy team by manual explosion via destruction PDA, or something else.
#define BUILD_TELEPORTER_DAMAGE 25 // how much damage an exploding teleporter can do
References#
- This string was added in an update on August 2 and exists in the source code. https://github.com/SteamDatabase/GameTracking-TF2/commit/4c568805aeacb94c58e33573e13d7b5a3dac094a#diff-7e964358015c4f4ebb4eced8a6fe3e60092e8878fc2b32996f9bc05fbc68feafR16174↩︎
- The string from the above note is in the source code here: https://github.com/OthmanAba/TeamFortress2/blob/1b81dded673d49adebf4d0958e52236ecc28a956/tf2_src/engine/sv_main.cpp#L2173↩︎
- game/shared/tf/tf_gamerules.cpp, line 20959↩︎
- game/server/tf/bot/tf_bot_vision.cpp, line 279↩︎
- game/server/tf/player_vs_environment/tf_population_manager.cpp, line 106↩︎
- game/server/tf/player_vs_environment/tf_population_manager.cpp, line 518↩︎
- game/server/tf/tf_player.cpp, line 7254↩︎
- game/server/tf/player_vs_environment/tf_upgrades.cpp, line 682↩︎
- game/shared/tf/tf_weaponbase.cpp, line 1398↩︎
- game/shared/tf/tf_weapon_compound_bow.cpp, line 570↩︎
- game/shared/tf/tf_weapon_sniperrifle.cpp, line 647↩︎
- game/shared/tf/tf_weapon_sniperrifle.cpp, line 704↩︎
- game/shared/tf/tf_gamerules.cpp, line 21062↩︎
- game/server/tf/player_vs_environment/tf_base_boss.cpp, line 524↩︎
- game/shared/tf/tf_shareddefs.h, line 1345↩︎
- game/server/tf/tf_obj_dispenser.cpp, line 151↩︎
- game/shared/tf/tf_player_shared.cpp, line 4573↩︎
- game/server/tf/tf_obj_spy_trap.cpp↩︎
- game/shared/tf/tf_player_shared.cpp, line 10558↩︎
- game/server/tf/tf_obj_teleporter.cpp, line 655↩︎
- game/server/tf/tf_obj_teleporter.cpp, line 653↩︎
- game/shared/tf/tf_player_shared.cpp, line 11001↩︎
- game\shared\tf\tf_player_shared.cpp, line 11010↩︎
- game/server/tf/tf_obj_teleporter.cpp, line 334↩︎
- game/server/tf/tf_obj_teleporter.cpp, line 756↩︎
- game/server/tf/tf_obj_teleporter.cpp, line 749↩︎
- game/shared/tf/trigger_catapult_shared.cpp↩︎
- game/server/tf/tf_player.cpp, line 7971↩︎
- game/server/tf/player_vs_environment/tf_population_manager.cpp, line 1093↩︎
- shared/tf/tf_weapon_lunchbox.cpp, line 370↩︎
- game/server/tf/tf_obj_teleporter.cpp, line 71↩︎