summaryrefslogtreecommitdiff
path: root/game/server/tf2
diff options
context:
space:
mode:
Diffstat (limited to 'game/server/tf2')
-rw-r--r--game/server/tf2/basecombatcharacter_tf2.cpp249
-rw-r--r--game/server/tf2/bot_base.cpp424
-rw-r--r--game/server/tf2/bot_base.h19
-rw-r--r--game/server/tf2/c_obj_armor_upgrade.cpp43
-rw-r--r--game/server/tf2/c_obj_armor_upgrade.h32
-rw-r--r--game/server/tf2/controlzone.cpp331
-rw-r--r--game/server/tf2/controlzone.h60
-rw-r--r--game/server/tf2/demo_entities.cpp123
-rw-r--r--game/server/tf2/entity_burn_effect.cpp51
-rw-r--r--game/server/tf2/entity_burn_effect.h41
-rw-r--r--game/server/tf2/env_fallingrocks.cpp201
-rw-r--r--game/server/tf2/env_meteor.cpp618
-rw-r--r--game/server/tf2/env_meteor.h146
-rw-r--r--game/server/tf2/fire_damage_mgr.cpp301
-rw-r--r--game/server/tf2/fire_damage_mgr.h121
-rw-r--r--game/server/tf2/gasoline_blob.cpp279
-rw-r--r--game/server/tf2/gasoline_blob.h93
-rw-r--r--game/server/tf2/info_act.cpp587
-rw-r--r--game/server/tf2/info_act.h138
-rw-r--r--game/server/tf2/info_add_resources.cpp72
-rw-r--r--game/server/tf2/info_buildpoint.cpp217
-rw-r--r--game/server/tf2/info_buildpoint.h76
-rw-r--r--game/server/tf2/info_customtech.cpp107
-rw-r--r--game/server/tf2/info_customtech.h43
-rw-r--r--game/server/tf2/info_input_playsound.cpp222
-rw-r--r--game/server/tf2/info_input_resetbanks.cpp124
-rw-r--r--game/server/tf2/info_input_resetobjects.cpp106
-rw-r--r--game/server/tf2/info_input_respawnplayers.cpp121
-rw-r--r--game/server/tf2/info_minimappulse.cpp116
-rw-r--r--game/server/tf2/info_output_team.cpp71
-rw-r--r--game/server/tf2/info_resourceprocessor.cpp139
-rw-r--r--game/server/tf2/info_vehicle_bay.cpp270
-rw-r--r--game/server/tf2/info_vehicle_bay.h72
-rw-r--r--game/server/tf2/mapdata_server.cpp115
-rw-r--r--game/server/tf2/menu_base.cpp262
-rw-r--r--game/server/tf2/menu_base.h77
-rw-r--r--game/server/tf2/mortar_round.cpp257
-rw-r--r--game/server/tf2/mortar_round.h48
-rw-r--r--game/server/tf2/npc_bug_builder.cpp577
-rw-r--r--game/server/tf2/npc_bug_builder.h69
-rw-r--r--game/server/tf2/npc_bug_hole.cpp392
-rw-r--r--game/server/tf2/npc_bug_hole.h76
-rw-r--r--game/server/tf2/npc_bug_warrior.cpp1039
-rw-r--r--game/server/tf2/npc_bug_warrior.h102
-rw-r--r--game/server/tf2/order_assist.cpp177
-rw-r--r--game/server/tf2/order_assist.h51
-rw-r--r--game/server/tf2/order_buildsentrygun.cpp55
-rw-r--r--game/server/tf2/order_buildsentrygun.h38
-rw-r--r--game/server/tf2/order_buildshieldwall.cpp51
-rw-r--r--game/server/tf2/order_buildshieldwall.h35
-rw-r--r--game/server/tf2/order_events.cpp26
-rw-r--r--game/server/tf2/order_events.h110
-rw-r--r--game/server/tf2/order_heal.cpp111
-rw-r--r--game/server/tf2/order_heal.h39
-rw-r--r--game/server/tf2/order_helpers.cpp345
-rw-r--r--game/server/tf2/order_helpers.h128
-rw-r--r--game/server/tf2/order_killmortarguy.cpp125
-rw-r--r--game/server/tf2/order_killmortarguy.h38
-rw-r--r--game/server/tf2/order_mortar_attack.cpp77
-rw-r--r--game/server/tf2/order_mortar_attack.h29
-rw-r--r--game/server/tf2/order_player.cpp26
-rw-r--r--game/server/tf2/order_player.h32
-rw-r--r--game/server/tf2/order_repair.cpp157
-rw-r--r--game/server/tf2/order_repair.h42
-rw-r--r--game/server/tf2/order_resourcepump.cpp66
-rw-r--r--game/server/tf2/order_resourcepump.h38
-rw-r--r--game/server/tf2/order_resupply.cpp54
-rw-r--r--game/server/tf2/order_resupply.h38
-rw-r--r--game/server/tf2/orders.cpp215
-rw-r--r--game/server/tf2/orders.h90
-rw-r--r--game/server/tf2/ragdoll_shadow.cpp117
-rw-r--r--game/server/tf2/ragdoll_shadow.h43
-rw-r--r--game/server/tf2/resource_chunk.cpp172
-rw-r--r--game/server/tf2/resource_chunk.h52
-rw-r--r--game/server/tf2/sensor_tf_team.cpp139
-rw-r--r--game/server/tf2/team_messages.cpp110
-rw-r--r--game/server/tf2/team_messages.h83
-rw-r--r--game/server/tf2/tf_accuracy.cpp168
-rw-r--r--game/server/tf2/tf_ai_hint.h34
-rw-r--r--game/server/tf2/tf_basecombatweapon.cpp137
-rw-r--r--game/server/tf2/tf_basecombatweapon.h39
-rw-r--r--game/server/tf2/tf_basefourwheelvehicle.cpp762
-rw-r--r--game/server/tf2/tf_basefourwheelvehicle.h138
-rw-r--r--game/server/tf2/tf_carrier.cpp1
-rw-r--r--game/server/tf2/tf_carrier.h1
-rw-r--r--game/server/tf2/tf_class_commando.cpp586
-rw-r--r--game/server/tf2/tf_class_commando.h112
-rw-r--r--game/server/tf2/tf_class_defender.cpp352
-rw-r--r--game/server/tf2/tf_class_defender.h77
-rw-r--r--game/server/tf2/tf_class_escort.cpp274
-rw-r--r--game/server/tf2/tf_class_escort.h64
-rw-r--r--game/server/tf2/tf_class_infiltrator.cpp275
-rw-r--r--game/server/tf2/tf_class_infiltrator.h78
-rw-r--r--game/server/tf2/tf_class_medic.cpp305
-rw-r--r--game/server/tf2/tf_class_medic.h73
-rw-r--r--game/server/tf2/tf_class_pyro.cpp164
-rw-r--r--game/server/tf2/tf_class_pyro.h57
-rw-r--r--game/server/tf2/tf_class_recon.cpp221
-rw-r--r--game/server/tf2/tf_class_recon.h60
-rw-r--r--game/server/tf2/tf_class_sapper.cpp328
-rw-r--r--game/server/tf2/tf_class_sapper.h97
-rw-r--r--game/server/tf2/tf_class_sniper.cpp261
-rw-r--r--game/server/tf2/tf_class_sniper.h69
-rw-r--r--game/server/tf2/tf_class_support.cpp153
-rw-r--r--game/server/tf2/tf_class_support.h49
-rw-r--r--game/server/tf2/tf_client.cpp180
-rw-r--r--game/server/tf2/tf_eventlog.cpp56
-rw-r--r--game/server/tf2/tf_flare.cpp249
-rw-r--r--game/server/tf2/tf_flare.h58
-rw-r--r--game/server/tf2/tf_func_construction_yard.cpp235
-rw-r--r--game/server/tf2/tf_func_construction_yard.h30
-rw-r--r--game/server/tf2/tf_func_mass_teleport.cpp368
-rw-r--r--game/server/tf2/tf_func_mass_teleport.h72
-rw-r--r--game/server/tf2/tf_func_no_build.cpp225
-rw-r--r--game/server/tf2/tf_func_no_build.h30
-rw-r--r--game/server/tf2/tf_func_resource.cpp683
-rw-r--r--game/server/tf2/tf_func_resource.h153
-rw-r--r--game/server/tf2/tf_func_weldable_door.cpp399
-rw-r--r--game/server/tf2/tf_func_weldable_door.h81
-rw-r--r--game/server/tf2/tf_gameinterface.cpp27
-rw-r--r--game/server/tf2/tf_hintmanager.cpp64
-rw-r--r--game/server/tf2/tf_hintmanager.h42
-rw-r--r--game/server/tf2/tf_obj.cpp3243
-rw-r--r--game/server/tf2/tf_obj.h492
-rw-r--r--game/server/tf2/tf_obj_armor_upgrade.cpp31
-rw-r--r--game/server/tf2/tf_obj_armor_upgrade.h33
-rw-r--r--game/server/tf2/tf_obj_barbed_wire.cpp239
-rw-r--r--game/server/tf2/tf_obj_barbed_wire.h43
-rw-r--r--game/server/tf2/tf_obj_buff_station.cpp929
-rw-r--r--game/server/tf2/tf_obj_buff_station.h116
-rw-r--r--game/server/tf2/tf_obj_bunker.cpp174
-rw-r--r--game/server/tf2/tf_obj_bunker.h60
-rw-r--r--game/server/tf2/tf_obj_dragonsteeth.cpp97
-rw-r--r--game/server/tf2/tf_obj_dragonsteeth.h37
-rw-r--r--game/server/tf2/tf_obj_empgenerator.cpp96
-rw-r--r--game/server/tf2/tf_obj_empgenerator.h51
-rw-r--r--game/server/tf2/tf_obj_explosives.cpp119
-rw-r--r--game/server/tf2/tf_obj_manned_missilelauncher.cpp227
-rw-r--r--game/server/tf2/tf_obj_manned_missilelauncher.h57
-rw-r--r--game/server/tf2/tf_obj_manned_shield.cpp240
-rw-r--r--game/server/tf2/tf_obj_mapdefined.cpp140
-rw-r--r--game/server/tf2/tf_obj_mapdefined.h40
-rw-r--r--game/server/tf2/tf_obj_mcv_selection_panel.cpp138
-rw-r--r--game/server/tf2/tf_obj_mcv_selection_panel.h18
-rw-r--r--game/server/tf2/tf_obj_mortar.cpp266
-rw-r--r--game/server/tf2/tf_obj_mortar.h67
-rw-r--r--game/server/tf2/tf_obj_powerpack.cpp366
-rw-r--r--game/server/tf2/tf_obj_powerpack.h64
-rw-r--r--game/server/tf2/tf_obj_rallyflag.cpp108
-rw-r--r--game/server/tf2/tf_obj_rallyflag.h40
-rw-r--r--game/server/tf2/tf_obj_resourcepump.cpp260
-rw-r--r--game/server/tf2/tf_obj_resourcepump.h60
-rw-r--r--game/server/tf2/tf_obj_respawn_station.cpp167
-rw-r--r--game/server/tf2/tf_obj_respawn_station.h70
-rw-r--r--game/server/tf2/tf_obj_resupply.cpp383
-rw-r--r--game/server/tf2/tf_obj_resupply.h60
-rw-r--r--game/server/tf2/tf_obj_sandbag_bunker.cpp73
-rw-r--r--game/server/tf2/tf_obj_sandbag_bunker.h33
-rw-r--r--game/server/tf2/tf_obj_selfheal.cpp112
-rw-r--r--game/server/tf2/tf_obj_selfheal.h37
-rw-r--r--game/server/tf2/tf_obj_sentrygun.cpp1178
-rw-r--r--game/server/tf2/tf_obj_sentrygun.h190
-rw-r--r--game/server/tf2/tf_obj_shieldwall.cpp270
-rw-r--r--game/server/tf2/tf_obj_shieldwall.h13
-rw-r--r--game/server/tf2/tf_obj_tower.cpp175
-rw-r--r--game/server/tf2/tf_obj_tower.h61
-rw-r--r--game/server/tf2/tf_obj_tunnel.cpp571
-rw-r--r--game/server/tf2/tf_obj_vehicleboost.cpp76
-rw-r--r--game/server/tf2/tf_obj_vehicleboost.h37
-rw-r--r--game/server/tf2/tf_player.cpp3720
-rw-r--r--game/server/tf2/tf_player.h596
-rw-r--r--game/server/tf2/tf_player_death.cpp230
-rw-r--r--game/server/tf2/tf_player_resource.cpp35
-rw-r--r--game/server/tf2/tf_player_resource.h28
-rw-r--r--game/server/tf2/tf_playerclass.cpp1172
-rw-r--r--game/server/tf2/tf_playerclass.h214
-rw-r--r--game/server/tf2/tf_playerlocaldata.cpp147
-rw-r--r--game/server/tf2/tf_playerlocaldata.h68
-rw-r--r--game/server/tf2/tf_playermove.cpp291
-rw-r--r--game/server/tf2/tf_rescollector_ground.cpp1
-rw-r--r--game/server/tf2/tf_rescollector_ground.h1
-rw-r--r--game/server/tf2/tf_shared_defines.h28
-rw-r--r--game/server/tf2/tf_shield.cpp432
-rw-r--r--game/server/tf2/tf_shield.h146
-rw-r--r--game/server/tf2/tf_shield_flat.cpp150
-rw-r--r--game/server/tf2/tf_shield_flat.h68
-rw-r--r--game/server/tf2/tf_shieldgrenade.cpp424
-rw-r--r--game/server/tf2/tf_shieldgrenade.h19
-rw-r--r--game/server/tf2/tf_stats.cpp624
-rw-r--r--game/server/tf2/tf_stats.h106
-rw-r--r--game/server/tf2/tf_stressentities.cpp107
-rw-r--r--game/server/tf2/tf_team.cpp1434
-rw-r--r--game/server/tf2/tf_team.h219
-rw-r--r--game/server/tf2/tf_teamspawnpoint.h40
-rw-r--r--game/server/tf2/tf_vehicle_battering_ram.cpp226
-rw-r--r--game/server/tf2/tf_vehicle_battering_ram.h58
-rw-r--r--game/server/tf2/tf_vehicle_flatbed.cpp78
-rw-r--r--game/server/tf2/tf_vehicle_flatbed.h39
-rw-r--r--game/server/tf2/tf_vehicle_mortar.cpp352
-rw-r--r--game/server/tf2/tf_vehicle_mortar.h85
-rw-r--r--game/server/tf2/tf_vehicle_motorcycle.cpp99
-rw-r--r--game/server/tf2/tf_vehicle_siege_tower.cpp304
-rw-r--r--game/server/tf2/tf_vehicle_siege_tower.h109
-rw-r--r--game/server/tf2/tf_vehicle_tank.cpp262
-rw-r--r--game/server/tf2/tf_vehicle_tank.h72
-rw-r--r--game/server/tf2/tf_vehicle_teleport_station.cpp414
-rw-r--r--game/server/tf2/tf_vehicle_teleport_station.h82
-rw-r--r--game/server/tf2/tf_vehicle_wagon.cpp80
-rw-r--r--game/server/tf2/tf_vehicle_wagon.h42
-rw-r--r--game/server/tf2/tf_walker_base.cpp302
-rw-r--r--game/server/tf2/tf_walker_base.h115
-rw-r--r--game/server/tf2/tf_walker_ministrider.cpp515
-rw-r--r--game/server/tf2/tf_walker_ministrider.h117
-rw-r--r--game/server/tf2/tf_walker_strider.cpp395
-rw-r--r--game/server/tf2/tf_walker_strider.h85
-rw-r--r--game/server/tf2/trigger_fall.cpp72
-rw-r--r--game/server/tf2/trigger_skybox.cpp65
217 files changed, 45048 insertions, 0 deletions
diff --git a/game/server/tf2/basecombatcharacter_tf2.cpp b/game/server/tf2/basecombatcharacter_tf2.cpp
new file mode 100644
index 0000000..453856a
--- /dev/null
+++ b/game/server/tf2/basecombatcharacter_tf2.cpp
@@ -0,0 +1,249 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: TF2 specific CBaseCombatCharacter code.
+//
+//=============================================================================//
+#include "cbase.h"
+#include "basecombatcharacter.h"
+#include "engine/IEngineSound.h"
+#include "tf_player.h"
+#include "tf_stats.h"
+
+extern char *g_pszEMPPulseStart;
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CBaseCombatCharacter::HasPowerup( int iPowerup )
+{
+ Assert( iPowerup >= 0 && iPowerup < MAX_POWERUPS );
+ return ( m_iPowerups & (1 << iPowerup) ) != 0;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CBaseCombatCharacter::CanPowerupEver( int iPowerup )
+{
+ Assert( iPowerup >= 0 && iPowerup < MAX_POWERUPS );
+
+ // Only objects use power
+ if ( iPowerup == POWERUP_POWER )
+ return false;
+
+ // Accept everything else
+ return true;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CBaseCombatCharacter::CanPowerupNow( int iPowerup )
+{
+ Assert( iPowerup >= 0 && iPowerup < MAX_POWERUPS );
+
+ if ( !CanPowerupEver(iPowerup) )
+ return false;
+
+ switch( iPowerup )
+ {
+ case POWERUP_BOOST:
+ {
+ // Am I taking EMP damage, or is a technician trying to drain me?
+ if ( HasPowerup( POWERUP_EMP ) || ( (m_flPowerupAttemptTimes[POWERUP_EMP] + 0.5) > gpGlobals->curtime ) )
+ {
+ // Reduce EMP time
+ m_flPowerupEndTimes[POWERUP_EMP] -= 0.05;
+
+ // Don't apply any boost effects
+ return false;
+ }
+ }
+ break;
+
+ case POWERUP_EMP:
+ {
+ // Was I just boosted? If so, I don't take EMP damage for a bit
+ if ( (m_flPowerupAttemptTimes[POWERUP_BOOST] + 0.5) > gpGlobals->curtime )
+ return false;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseCombatCharacter::SetPowerup( int iPowerup, bool bState, float flTime, float flAmount, CBaseEntity *pAttacker, CDamageModifier *pDamageModifier )
+{
+ Assert( iPowerup >= 0 && iPowerup < MAX_POWERUPS );
+
+ // Some powerups trigger their on state continuously, as opposed to turning it on for some time.
+ bool bTriggerStart = ( bState && !HasPowerup( iPowerup ) );
+ if ( bState && iPowerup == POWERUP_BOOST )
+ {
+ // Health boost always triggers
+ bTriggerStart = true;
+ }
+
+ bool bHadPowerup = false;
+ if ( HasPowerup( iPowerup ) && !bState )
+ {
+ bHadPowerup = true;
+ }
+
+ if ( bState )
+ {
+ m_iPowerups |= (1 << iPowerup);
+ }
+ else
+ {
+ m_iPowerups &= ~(1 << iPowerup);
+ }
+
+ // Fire start/end triggers
+ if ( bTriggerStart )
+ {
+ PowerupStart( iPowerup, flAmount, pAttacker, pDamageModifier );
+ }
+ else if ( bHadPowerup )
+ {
+ PowerupEnd( iPowerup );
+ }
+
+ // If we've got an active powerup, keep thinking
+ if ( m_iPowerups )
+ {
+ SetContextThink( PowerupThink, gpGlobals->curtime + 0.1, POWERUP_THINK_CONTEXT );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseCombatCharacter::PowerupThink( void )
+{
+ // If we don't have any powerups, stop thinking
+ if ( !m_iPowerups )
+ return;
+
+ // Check all the powerups
+ for ( int i = 0; i < MAX_POWERUPS; i++ )
+ {
+ // Don't check power, because it never runs out naturally
+ if ( i == POWERUP_POWER )
+ continue;
+
+ if ( m_iPowerups & (1 << i) )
+ {
+ // Should it finish now?
+ if ( m_flPowerupEndTimes[i] < gpGlobals->curtime )
+ {
+ SetPowerup( i, false );
+ }
+ }
+ }
+
+ SetNextThink( gpGlobals->curtime + 0.1, POWERUP_THINK_CONTEXT );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CBaseCombatCharacter::AttemptToPowerup( int iPowerup, float flTime, float flAmount, CBaseEntity *pAttacker, CDamageModifier *pDamageModifier )
+{
+ Assert( iPowerup >= 0 && iPowerup < MAX_POWERUPS );
+
+ // Ignore it if I'm dead
+ if ( !IsAlive() )
+ return false;
+
+ m_flPowerupAttemptTimes[iPowerup] = gpGlobals->curtime;
+
+ // If we can't be powerup this type, abort
+ if ( !CanPowerupNow( iPowerup ) )
+ return false;
+
+ // Get the correct duration
+ flTime = PowerupDuration( iPowerup, flTime );
+ m_flPowerupEndTimes[iPowerup] = MAX( m_flPowerupEndTimes[iPowerup], gpGlobals->curtime + flTime );
+
+ // Turn it on
+ SetPowerup( iPowerup, true, flTime, flAmount, pAttacker, pDamageModifier );
+
+ // Add the damage modifier to the player
+ if ( pDamageModifier )
+ {
+ pDamageModifier->AddModifierToEntity( this );
+ }
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Powerup has just started
+//-----------------------------------------------------------------------------
+void CBaseCombatCharacter::PowerupStart( int iPowerup, float flAmount, CBaseEntity *pAttacker, CDamageModifier *pDamageModifier )
+{
+ Assert( iPowerup >= 0 && iPowerup < MAX_POWERUPS );
+
+ switch( iPowerup )
+ {
+ case POWERUP_BOOST:
+ {
+ // Players can be boosted over their max
+ int iMaxBoostedHealth;
+ if ( IsPlayer() )
+ {
+ iMaxBoostedHealth = GetMaxHealth() + GetMaxHealth() / 2;
+ }
+ else
+ {
+ iMaxBoostedHealth = GetMaxHealth();
+ }
+
+ // Can we boost health further?
+ if ( GetHealth() < iMaxBoostedHealth )
+ {
+ int maxHealthToAdd = iMaxBoostedHealth - GetHealth();
+
+ // It uses floating point in here so it doesn't lose the fractional healing part on small frame times.
+ float flHealthToAdd = flAmount + m_flFractionalBoost;
+ int nHealthToAdd = (int)flHealthToAdd;
+ m_flFractionalBoost = flHealthToAdd - nHealthToAdd;
+ if ( nHealthToAdd )
+ {
+ int nHealthAdded = MIN( nHealthToAdd, maxHealthToAdd );
+ if ( IsPlayer() )
+ {
+ ((CBaseTFPlayer*)this)->TakeHealthBoost( nHealthAdded, GetMaxHealth(), 25 );
+ }
+ else
+ {
+ TakeHealth( nHealthAdded, DMG_GENERIC );
+ }
+
+ TFStats()->IncrementPlayerStat( pAttacker, TF_PLAYER_STAT_HEALTH_GIVEN, nHealthAdded );
+ }
+ }
+ }
+ break;
+
+ case POWERUP_EMP:
+ {
+ // EMP removes adrenalin rush
+ SetPowerup( POWERUP_RUSH, false );
+ }
+ break;
+
+ default:
+ break;
+ }
+}
+
diff --git a/game/server/tf2/bot_base.cpp b/game/server/tf2/bot_base.cpp
new file mode 100644
index 0000000..014c6a0
--- /dev/null
+++ b/game/server/tf2/bot_base.cpp
@@ -0,0 +1,424 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Basic BOT handling.
+//
+// $Workfile: $
+// $Date: $
+//
+//-----------------------------------------------------------------------------
+// $Log: $
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#include "cbase.h"
+#include "player.h"
+#include "tf_player.h"
+#include "menu_base.h"
+#include "in_buttons.h"
+#include "movehelper_server.h"
+#include "weapon_twohandedcontainer.h"
+
+void ParseCommand( CBaseTFPlayer *pPlayer, const char *pcmd, const char *pargs );
+void ClientPutInServer( edict_t *pEdict, const char *playername );
+void Bot_Think( CBaseTFPlayer *pBot );
+
+ConVar bot_forcefireweapon( "bot_forcefireweapon", "", 0, "Force bots with the specified weapon to fire." );
+ConVar bot_forceattack2( "bot_forceattack2", "0", 0, "When firing, use attack2." );
+ConVar bot_forceattackon( "bot_forceattackon", "0", 0, "When firing, don't tap fire, hold it down." );
+ConVar bot_flipout( "bot_flipout", "0", 0, "When on, all bots fire their guns." );
+ConVar bot_defend( "bot_defend", "0", 0, "Set to a team number, and that team will all keep their combat shields raised." );
+ConVar bot_changeclass( "bot_changeclass", "0", 0, "Force all bots to change to the specified class." );
+static ConVar bot_mimic( "bot_mimic", "0", 0, "Bot uses usercmd of player by index." );
+
+static int BotNumber = 1;
+static int g_iNextBotTeam = -1;
+static int g_iNextBotClass = -1;
+
+//-----------------------------------------------------------------------------
+// Purpose: Create a new Bot and put it in the game.
+// Output : Pointer to the new Bot, or NULL if there's no free clients.
+//-----------------------------------------------------------------------------
+CBasePlayer *BotPutInServer( bool bFrozen, int iTeam, int iClass )
+{
+ g_iNextBotTeam = iTeam;
+ g_iNextBotClass = iClass;
+
+ char botname[ 64 ];
+ Q_snprintf( botname, sizeof( botname ), "Bot%02i", BotNumber );
+
+ edict_t *pEdict = NULL;
+ {
+ bool oldLock = engine->LockNetworkStringTables( false );
+ pEdict = engine->CreateFakeClient( botname );
+ engine->LockNetworkStringTables( oldLock );
+ }
+
+ if ( !pEdict )
+ {
+ Msg( "Failed to create Bot.\n");
+ return NULL;
+ }
+
+ // Allocate a CBasePlayer for the bot, and call spawn
+ ClientPutInServer( pEdict, botname );
+ CBaseTFPlayer *pPlayer = ((CBaseTFPlayer *)CBaseEntity::Instance( pEdict ));
+ pPlayer->ClearFlags();
+ pPlayer->AddFlag( FL_CLIENT | FL_FAKECLIENT );
+
+ if ( bFrozen )
+ pPlayer->AddEFlags( EFL_BOT_FROZEN );
+
+ if ( iTeam != -1 )
+ {
+ pPlayer->ChangeTeam( iTeam );
+ }
+ pPlayer->ForceRespawn();
+
+ BotNumber++;
+
+ return pPlayer;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Run through all the Bots in the game and let them think.
+//-----------------------------------------------------------------------------
+void Bot_RunAll( void )
+{
+ for ( int i = 1; i <= gpGlobals->maxClients; i++ )
+ {
+ CBaseTFPlayer *pPlayer = ToBaseTFPlayer( UTIL_PlayerByIndex( i ) );
+
+ if ( pPlayer && (pPlayer->GetFlags() & FL_FAKECLIENT) )
+ {
+ Bot_Think( pPlayer );
+ }
+ }
+}
+
+typedef struct
+{
+ bool backwards;
+
+ float nextturntime;
+ bool lastturntoright;
+
+ float nextstrafetime;
+ float sidemove;
+
+ QAngle forwardAngle;
+ QAngle lastAngles;
+} botdata_t;
+
+static botdata_t g_BotData[ MAX_PLAYERS ];
+
+bool RunMimicCommand( CUserCmd& cmd )
+{
+ if ( bot_mimic.GetInt() <= 0 )
+ return false;
+
+ if ( bot_mimic.GetInt() > gpGlobals->maxClients )
+ return false;
+
+
+ CBasePlayer *pPlayer = UTIL_PlayerByIndex( bot_mimic.GetInt() );
+ if ( !pPlayer )
+ return false;
+
+ if ( !pPlayer->GetLastUserCommand() )
+ return false;
+
+ cmd = *pPlayer->GetLastUserCommand();
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Simulates a single frame of movement for a player
+// Input : *fakeclient -
+// *viewangles -
+// forwardmove -
+// sidemove -
+// upmove -
+// buttons -
+// impulse -
+// msec -
+// Output : virtual void
+//-----------------------------------------------------------------------------
+static void RunPlayerMove( CBaseTFPlayer *fakeclient, const QAngle& viewangles, float forwardmove, float sidemove, float upmove, unsigned short buttons, byte impulse, float frametime )
+{
+ if ( !fakeclient )
+ return;
+
+ CUserCmd cmd;
+
+ // Store off the globals.. they're gonna get whacked
+ float flOldFrametime = gpGlobals->frametime;
+ float flOldCurtime = gpGlobals->curtime;
+
+ float flTimeBase = gpGlobals->curtime + gpGlobals->frametime - frametime;
+ fakeclient->SetTimeBase( flTimeBase );
+
+ Q_memset( &cmd, 0, sizeof( cmd ) );
+
+ if ( !RunMimicCommand( cmd ) )
+ {
+ VectorCopy( viewangles, cmd.viewangles );
+ cmd.forwardmove = forwardmove;
+ cmd.sidemove = sidemove;
+ cmd.upmove = upmove;
+ cmd.buttons = buttons;
+ cmd.impulse = impulse;
+ cmd.random_seed = random->RandomInt( 0, 0x7fffffff );
+ }
+
+ MoveHelperServer()->SetHost( fakeclient );
+ fakeclient->PlayerRunCommand( &cmd, MoveHelperServer() );
+
+ // save off the last good usercmd
+ fakeclient->SetLastUserCommand( cmd );
+
+ // Clear out any fixangle that has been set
+ fakeclient->pl.fixangle = FIXANGLE_NONE;
+
+ // Restore the globals..
+ gpGlobals->frametime = flOldFrametime;
+ gpGlobals->curtime = flOldCurtime;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Run this Bot's AI for one frame.
+//-----------------------------------------------------------------------------
+void Bot_Think( CBaseTFPlayer *pBot )
+{
+ // Hack to make Bots use Menus
+ if ( pBot->m_pCurrentMenu == gMenus[MENU_CLASS] )
+ {
+ int iClass = g_iNextBotClass;
+ if ( iClass == -1 )
+ iClass = random->RandomInt( 1, TFCLASS_CLASS_COUNT );
+
+ pBot->m_pCurrentMenu->Input( pBot, iClass );
+ }
+ else if ( bot_changeclass.GetInt() && bot_changeclass.GetInt() != pBot->PlayerClass() )
+ {
+ pBot->m_pCurrentMenu = gMenus[MENU_CLASS];
+ pBot->m_pCurrentMenu->Input( pBot, bot_changeclass.GetInt() );
+ }
+
+ // Make sure we stay being a bot
+ pBot->AddFlag( FL_FAKECLIENT );
+
+ botdata_t *botdata = &g_BotData[ ENTINDEX( pBot->edict() ) - 1 ];
+
+ QAngle vecViewAngles;
+ float forwardmove = 0.0;
+ float sidemove = botdata->sidemove;
+ float upmove = 0.0;
+ unsigned short buttons = 0;
+ byte impulse = 0;
+ float frametime = gpGlobals->frametime;
+
+ vecViewAngles = pBot->GetLocalAngles();
+
+
+ // Create some random values
+ if ( pBot->IsAlive() && (pBot->GetSolid() == SOLID_BBOX) )
+ {
+ trace_t trace;
+
+ // Stop when shot
+ if ( !pBot->IsEFlagSet(EFL_BOT_FROZEN) )
+ {
+ if ( pBot->m_iHealth == 100 )
+ {
+ forwardmove = 600 * ( botdata->backwards ? -1 : 1 );
+ if ( botdata->sidemove != 0.0f )
+ {
+ forwardmove *= random->RandomFloat( 0.1, 1.0f );
+ }
+ }
+ else
+ {
+ forwardmove = 0;
+ }
+ }
+
+ // Only turn if I haven't been hurt
+ if ( !pBot->IsEFlagSet(EFL_BOT_FROZEN) && pBot->m_iHealth == 100 )
+ {
+ Vector vecEnd;
+ Vector forward;
+
+ QAngle angle;
+ float angledelta = 15.0;
+
+ int maxtries = (int)360.0/angledelta;
+
+ if ( botdata->lastturntoright )
+ {
+ angledelta = -angledelta;
+ }
+
+ angle = pBot->GetLocalAngles();
+
+ Vector vecSrc;
+ while ( --maxtries >= 0 )
+ {
+ AngleVectors( angle, &forward );
+
+ vecSrc = pBot->GetLocalOrigin() + Vector( 0, 0, 36 );
+
+ vecEnd = vecSrc + forward * 10;
+
+ UTIL_TraceHull( vecSrc, vecEnd, VEC_HULL_MIN_SCALED( pBot ), VEC_HULL_MAX_SCALED( pBot ),
+ MASK_PLAYERSOLID, pBot, COLLISION_GROUP_NONE, &trace );
+
+ if ( trace.fraction == 1.0 )
+ {
+ if ( gpGlobals->curtime < botdata->nextturntime )
+ {
+ break;
+ }
+ }
+
+ angle.y += angledelta;
+
+ if ( angle.y > 180 )
+ angle.y -= 360;
+ else if ( angle.y < -180 )
+ angle.y += 360;
+
+ botdata->nextturntime = gpGlobals->curtime + 2.0;
+ botdata->lastturntoright = random->RandomInt( 0, 1 ) == 0 ? true : false;
+
+ botdata->forwardAngle = angle;
+ botdata->lastAngles = angle;
+
+ }
+
+
+ if ( gpGlobals->curtime >= botdata->nextstrafetime )
+ {
+ botdata->nextstrafetime = gpGlobals->curtime + 1.0f;
+
+ if ( random->RandomInt( 0, 5 ) == 0 )
+ {
+ botdata->sidemove = -600.0f + 1200.0f * random->RandomFloat( 0, 2 );
+ }
+ else
+ {
+ botdata->sidemove = 0;
+ }
+ sidemove = botdata->sidemove;
+
+ if ( random->RandomInt( 0, 20 ) == 0 )
+ {
+ botdata->backwards = true;
+ }
+ else
+ {
+ botdata->backwards = false;
+ }
+ }
+
+ pBot->SetLocalAngles( angle );
+ vecViewAngles = angle;
+ }
+
+ // Is my team being forced to defend?
+ if ( bot_defend.GetInt() == pBot->GetTeamNumber() )
+ {
+ buttons |= IN_ATTACK2;
+ }
+ // If bots are being forced to fire a weapon, see if I have it
+ else if ( bot_forcefireweapon.GetString() )
+ {
+ CBaseCombatWeapon *pWeapon = pBot->Weapon_OwnsThisType( bot_forcefireweapon.GetString() );
+ if ( pWeapon )
+ {
+ // Switch to it if we don't have it out
+ CBaseCombatWeapon *pActiveWeapon = pBot->GetActiveWeapon();
+ // Is it a twohandedweapon? If so, get the left weapon
+ CWeaponTwoHandedContainer *pContainer = dynamic_cast< CWeaponTwoHandedContainer * >( pActiveWeapon );
+ if ( pContainer )
+ {
+ pActiveWeapon = pContainer->GetLeftWeapon();
+ }
+
+ // Switch?
+ if ( pActiveWeapon != pWeapon )
+ {
+ pBot->Weapon_Switch( pWeapon );
+ }
+ else
+ {
+ // Start firing
+ // Some weapons require releases, so randomise firing
+ if ( bot_forceattackon.GetBool() || (RandomFloat(0.0,1.0) > 0.5) )
+ {
+ buttons |= bot_forceattack2.GetBool() ? IN_ATTACK2 : IN_ATTACK;
+ }
+ }
+ }
+ }
+
+ if ( bot_flipout.GetInt() )
+ {
+ if ( bot_forceattackon.GetBool() || (RandomFloat(0.0,1.0) > 0.5) )
+ {
+ buttons |= bot_forceattack2.GetBool() ? IN_ATTACK2 : IN_ATTACK;
+ }
+ }
+ }
+ else
+ {
+ // Wait for Reinforcement wave
+ if ( !pBot->IsAlive() )
+ {
+ // Try hitting my buttons occasionally
+ if ( random->RandomInt( 0, 100 ) > 80 )
+ {
+ // Respawn the bot
+ if ( random->RandomInt( 0, 1 ) == 0 )
+ {
+ buttons |= IN_JUMP;
+ }
+ else
+ {
+ buttons = 0;
+ }
+ }
+ }
+ }
+
+ if ( bot_flipout.GetInt() >= 2 )
+ {
+
+ QAngle angOffset = RandomAngle( -1, 1 );
+
+ botdata->lastAngles += angOffset;
+
+ for ( int i = 0 ; i < 2; i++ )
+ {
+ if ( fabs( botdata->lastAngles[ i ] - botdata->forwardAngle[ i ] ) > 15.0f )
+ {
+ if ( botdata->lastAngles[ i ] > botdata->forwardAngle[ i ] )
+ {
+ botdata->lastAngles[ i ] = botdata->forwardAngle[ i ] + 15;
+ }
+ else
+ {
+ botdata->lastAngles[ i ] = botdata->forwardAngle[ i ] - 15;
+ }
+ }
+ }
+
+ botdata->lastAngles[ 2 ] = 0;
+
+ pBot->SetLocalAngles( botdata->lastAngles );
+ }
+
+ RunPlayerMove( pBot, pBot->GetLocalAngles(), forwardmove, sidemove, upmove, buttons, impulse, frametime );
+}
+
+
diff --git a/game/server/tf2/bot_base.h b/game/server/tf2/bot_base.h
new file mode 100644
index 0000000..2036f3d
--- /dev/null
+++ b/game/server/tf2/bot_base.h
@@ -0,0 +1,19 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef BOT_BASE_H
+#define BOT_BASE_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+
+// If iTeam or iClass is -1, then a team or class is randomly chosen.
+CBasePlayer *BotPutInServer( bool bFrozen, int iTeam, int iClass );
+
+
+#endif // BOT_BASE_H
diff --git a/game/server/tf2/c_obj_armor_upgrade.cpp b/game/server/tf2/c_obj_armor_upgrade.cpp
new file mode 100644
index 0000000..158d6c3
--- /dev/null
+++ b/game/server/tf2/c_obj_armor_upgrade.cpp
@@ -0,0 +1,43 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+#include "cbase.h"
+#include "c_obj_armor_upgrade.h"
+
+
+IMPLEMENT_CLIENTCLASS_DT(C_ArmorUpgrade, DT_ArmorUpgrade, CArmorUpgrade)
+END_RECV_TABLE()
+
+
+
+C_ArmorUpgrade::C_ArmorUpgrade()
+{
+}
+
+
+int C_ArmorUpgrade::DrawModel( int flags )
+{
+ C_BaseEntity *pParent = GetMoveParent();
+ if ( pParent )
+ {
+ C_BaseAnimating *pAnimating = dynamic_cast< C_BaseAnimating* >( pParent );
+ if ( pAnimating )
+ {
+ SetModelPointer( pParent->GetModel() );
+ SetSequence( pAnimating->GetSequence() );
+ m_nSkin = pAnimating->m_nSkin;
+ m_nBody = pAnimating->m_nBody;
+
+ SetLocalOrigin( Vector( 0, 0, 50 ) );
+ SetLocalAngles( QAngle( 0, 0, 0 ) );
+ InvalidateBoneCache();
+ }
+ }
+
+ return BaseClass::DrawModel( 0 );
+}
+
+
diff --git a/game/server/tf2/c_obj_armor_upgrade.h b/game/server/tf2/c_obj_armor_upgrade.h
new file mode 100644
index 0000000..91898d1
--- /dev/null
+++ b/game/server/tf2/c_obj_armor_upgrade.h
@@ -0,0 +1,32 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+#ifndef C_OBJ_ARMOR_UPGRADE_H
+#define C_OBJ_ARMOR_UPGRADE_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+
+#include "tf_obj_baseupgrade_shared.h"
+
+
+class C_ArmorUpgrade : public C_BaseObjectUpgrade
+{
+public:
+ DECLARE_CLASS( C_ArmorUpgrade, C_BaseObjectUpgrade );
+ DECLARE_CLIENTCLASS();
+ C_ArmorUpgrade();
+
+ virtual int DrawModel( int flags );
+
+
+private:
+ C_ArmorUpgrade( const C_ArmorUpgrade & ) {}
+};
+
+
+#endif // C_OBJ_ARMOR_UPGRADE_H
diff --git a/game/server/tf2/controlzone.cpp b/game/server/tf2/controlzone.cpp
new file mode 100644
index 0000000..a58a0ba
--- /dev/null
+++ b/game/server/tf2/controlzone.cpp
@@ -0,0 +1,331 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Complete definition of the ControlZone behavioral entity
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#include "tf_shareddefs.h"
+#include "cbase.h"
+#include "EntityOutput.h"
+#include "tf_player.h"
+#include "controlzone.h"
+#include "team.h"
+
+//-----------------------------------------------------------------------------
+// Purpose: Since the control zone is a data only class, force it to always be sent ( shouldn't change often so )
+// bandwidth usage should be small.
+// Input : **ppSendTable -
+// *recipient -
+// *pvs -
+// clientArea -
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+int CControlZone::UpdateTransmitState()
+{
+ if ( IsEffectActive( EF_NODRAW ) )
+ {
+ return SetTransmitState( FL_EDICT_DONTSEND );
+ }
+ else
+ {
+ return SetTransmitState( FL_EDICT_ALWAYS );
+ }
+}
+
+IMPLEMENT_SERVERCLASS_ST(CControlZone, DT_ControlZone)
+ SendPropInt( SENDINFO(m_nZoneNumber), 8, SPROP_UNSIGNED ),
+END_SEND_TABLE()
+
+LINK_ENTITY_TO_CLASS( trigger_controlzone, CControlZone);
+
+BEGIN_DATADESC( CControlZone )
+
+ // outputs
+ DEFINE_OUTPUT( m_ControllingTeam, "ControllingTeam" ),
+
+ // inputs
+ DEFINE_INPUTFUNC( FIELD_VOID, "SetTeam", InputSetTeam ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "LockTeam", InputLockControllingTeam ),
+
+ // keys
+ DEFINE_KEYFIELD_NOT_SAVED( m_iLockAfterChange, FIELD_INTEGER, "LockAfterChange" ),
+ DEFINE_KEYFIELD_NOT_SAVED( m_flTimeTillCaptured, FIELD_FLOAT, "UncontestedTime" ),
+ DEFINE_KEYFIELD_NOT_SAVED( m_flTimeTillContested, FIELD_FLOAT, "ContestedTime" ),
+ DEFINE_KEYFIELD_NOT_SAVED( m_nZoneNumber, FIELD_INTEGER, "ZoneNumber" ),
+
+END_DATADESC()
+
+
+
+// Control Zone Ent Flags
+#define CZF_DONT_USE_TOUCHES 1
+
+//-----------------------------------------------------------------------------
+// Purpose: Initializes the control zone
+// Records who was the original controlling team (for control locking)
+//-----------------------------------------------------------------------------
+void CControlZone::Spawn( void )
+{
+ // set the starting controlling team
+ m_ControllingTeam.Set( GetTeamNumber(), this, this );
+
+ // remember who the original controlling team was (for control locking)
+ m_iDefendingTeam = GetTeamNumber();
+
+ // Solid
+ SetSolid( SOLID_BSP );
+ AddSolidFlags( FSOLID_TRIGGER );
+ SetMoveType( MOVETYPE_NONE );
+ SetModel( STRING( GetModelName() ) ); // set size and link into world
+
+ // TF2 rules
+ m_flTimeTillContested = 10.0; // Go to contested 10 seconds after enemies enter the zone
+ m_flTimeTillCaptured = 5.0; // Go to captured state as soon as only one team holds the zone
+
+ if ( m_nZoneNumber == 0 )
+ {
+ Warning( "Warning, trigger_controlzone without Zone Number set\n" );
+ }
+
+ m_ZonePlayerList.Purge();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Records that a player has entered the zone, and updates it's state
+// according, maybe starting to change team.
+// Input : *pOther - the entity that left the zone
+//-----------------------------------------------------------------------------
+void CControlZone::StartTouch( CBaseEntity *pOther )
+{
+ CBaseTFPlayer *pl = ToBaseTFPlayer( pOther );
+ if ( !pl )
+ return;
+
+ CHandle< CBaseTFPlayer > hHandle;
+ hHandle = pl;
+
+ m_ZonePlayerList.AddToTail( hHandle );
+
+ ReevaluateControllingTeam();
+
+ // Set this player's current zone to this zone
+ pl->SetCurrentZone( this );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Records that a player has left the zone, and updates it's state
+// according, maybe starting to change team.
+// Input : *pOther - the entity that left the zone
+//-----------------------------------------------------------------------------
+void CControlZone::EndTouch( CBaseEntity *pOther )
+{
+ CBaseTFPlayer *pl = ToBaseTFPlayer( pOther );
+ if ( !pl )
+ return;
+
+ CHandle< CBaseTFPlayer > hHandle;
+ hHandle = pl;
+ m_ZonePlayerList.FindAndRemove( hHandle );
+
+ ReevaluateControllingTeam();
+
+ // Unset this player's current zone if it's this one
+ if ( pl->GetCurrentZone() == this )
+ pl->SetCurrentZone( NULL );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Checks to see if it's time to change controllers
+//-----------------------------------------------------------------------------
+void CControlZone::ReevaluateControllingTeam( void )
+{
+ // Count the number of players in each team
+ int i;
+ memset( m_iPlayersInZone, 0, sizeof( m_iPlayersInZone ) );
+ for ( i = 0; i < m_ZonePlayerList.Size(); i++ )
+ {
+ if ( m_ZonePlayerList[i] != NULL && (m_ZonePlayerList[i]->GetTeamNumber() > 0) )
+ {
+ m_iPlayersInZone[ m_ZonePlayerList[i]->GetTeamNumber() ] += 1;
+ }
+ }
+
+ // Abort immediately if we're not using touches to changes teams
+ if ( HasSpawnFlags( CZF_DONT_USE_TOUCHES ) )
+ return;
+
+ // if we're locked in place, no changes can occur to controlling team except through an explicit map ResetTeam
+ if ( m_iLocked )
+ return;
+
+ bool foundAnyTeam = false;
+ int teamFound = 0;
+
+ // check to see if any teams have no players
+ for ( i = 0; i < GetNumberOfTeams(); i++ )
+ {
+ if ( m_iPlayersInZone[i] )
+ {
+ if ( foundAnyTeam )
+ {
+ // we've already found a team, so it's being contested;
+ teamFound = ZONE_CONTESTED;
+ break;
+ }
+
+ foundAnyTeam = true;
+ teamFound = i;
+ }
+ }
+
+ // no one in the area!
+ if ( teamFound == 0 )
+ {
+ // just leave it as it is, let it continue to change team
+ // exception: if the zone state is contested, and there aren't any players in the zone,
+ // just return to the team who used to own the zone.
+ if ( GetTeamNumber() == ZONE_CONTESTED )
+ {
+ ChangeTeam(m_iDefendingTeam);
+ SetControllingTeam( this, m_iDefendingTeam );
+ }
+
+ return;
+ }
+
+ // if it's the same controlling team, don't worry about it
+ if ( teamFound == GetTeamNumber() )
+ {
+ // the right team is in control, don't even think of switching
+ m_iTryingToChangeToTeam = 0;
+ SetNextThink( TICK_NEVER_THINK );
+ return;
+ }
+
+ // Find out if the zone isn't owned by anyone at all (hasn't been touched since the map started, and it started un-owned)
+ bool bHasBeenOwned = true;
+ if ( m_iDefendingTeam == 0 && GetTeamNumber() == 0 )
+ bHasBeenOwned = false;
+
+ // if it's not contested, always go to contested mode
+ if ( GetTeamNumber() != ZONE_CONTESTED && teamFound != GetTeamNumber() )
+ {
+ // Unowned zones are captured immediately (no contesting stage)
+ if ( bHasBeenOwned )
+ teamFound = ZONE_CONTESTED;
+ }
+
+ // if it's the team we're trying to change to, don't worry about it
+ if ( teamFound == m_iTryingToChangeToTeam )
+ return;
+
+ // set up the time to change to the new team soon
+ m_iTryingToChangeToTeam = teamFound;
+
+ // changing from contested->uncontested and visa-versa have different delays
+ if ( m_iTryingToChangeToTeam != ZONE_CONTESTED )
+ {
+ if ( !bHasBeenOwned )
+ {
+ DevMsg( 1, "trigger_controlzone: (%s) changing team to %d NOW\n", GetDebugName(), m_iTryingToChangeToTeam );
+ SetNextThink( gpGlobals->curtime + 0.1f );
+ }
+ else
+ {
+ DevMsg( 1, "trigger_controlzone: (%s) changing team to %d in %.2f seconds\n", GetDebugName(), m_iTryingToChangeToTeam, m_flTimeTillCaptured );
+ SetNextThink( gpGlobals->curtime + m_flTimeTillCaptured );
+ }
+ }
+ else
+ {
+ DevMsg( 1, "trigger_controlzone: (%s) changing to contested in %f seconds\n", GetDebugName(), m_flTimeTillContested );
+ SetNextThink( gpGlobals->curtime + m_flTimeTillContested );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Checks to see if an uncontested territory is ready to change state
+// to the new controlling team.
+//-----------------------------------------------------------------------------
+void CControlZone::Think( void )
+{
+ if ( m_iTryingToChangeToTeam != 0 )
+ {
+ // held zone long enough
+ SetControllingTeam( this, m_iTryingToChangeToTeam );
+
+ // lock against further change if set
+ if ( m_iLockAfterChange )
+ {
+ LockControllingTeam();
+ }
+
+ // Re-evaluate controlling team if we were changing to Contested (enemy may have withdrawn)
+ if ( GetTeamNumber() == ZONE_CONTESTED )
+ {
+ ReevaluateControllingTeam();
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: set it so the team can no longer change, until a set controlling team action occurs
+//-----------------------------------------------------------------------------
+void CControlZone::InputLockControllingTeam( inputdata_t &inputdata )
+{
+ LockControllingTeam();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Input handler that sets the controlling team to the activator's team.
+//-----------------------------------------------------------------------------
+void CControlZone::InputSetTeam( inputdata_t &inputdata )
+{
+ // Abort if it's already the defending team
+ if ( inputdata.pActivator->GetTeamNumber() == GetTeamNumber() )
+ return;
+
+ // set the new team
+ ChangeTeam(inputdata.pActivator->GetTeamNumber());
+ SetControllingTeam( inputdata.pActivator, GetTeamNumber() );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Changes the team controlling this zone
+// Input : newTeam - the new team to change to
+//-----------------------------------------------------------------------------
+void CControlZone::SetControllingTeam( CBaseEntity *pActivator, int newTeam )
+{
+ DevMsg( 1, "trigger_controlzone: (%s) changing team to: %d\n", GetDebugName(), newTeam );
+
+ // remember this team as the defenders of the zone
+ m_iDefendingTeam = GetTeamNumber();
+
+ // reset state, firing the output
+ ChangeTeam(newTeam);
+ m_ControllingTeam.Set( GetTeamNumber(), pActivator, this );
+ m_iLocked = FALSE;
+ m_iTryingToChangeToTeam = 0;
+ SetNextThink( TICK_NEVER_THINK );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CControlZone::LockControllingTeam( void )
+{
+ // never lock a zone in contested mode
+ if ( GetTeamNumber() == ZONE_CONTESTED )
+ return;
+
+ // zones never lock to the defenders
+ if ( GetTeamNumber() == m_iDefendingTeam )
+ return;
+
+ m_iLocked = TRUE;
+}
+
diff --git a/game/server/tf2/controlzone.h b/game/server/tf2/controlzone.h
new file mode 100644
index 0000000..1f950b2
--- /dev/null
+++ b/game/server/tf2/controlzone.h
@@ -0,0 +1,60 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Control Zone entity
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef CONTROLZONE_H
+#define CONTROLZONE_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+//-----------------------------------------------------------------------------
+// Purpose: Defines a team zone of control
+// Usually the parent of many trigger entities
+//-----------------------------------------------------------------------------
+class CControlZone : public CBaseEntity
+{
+public:
+ DECLARE_CLASS( CControlZone, CBaseEntity );
+ DECLARE_SERVERCLASS();
+
+ void Spawn( void );
+ void StartTouch( CBaseEntity * );
+ void EndTouch( CBaseEntity * );
+ void Think( void );
+
+ virtual int UpdateTransmitState();
+
+ // input functions
+ void InputLockControllingTeam( inputdata_t &inputdata );
+ void InputSetTeam( inputdata_t &inputdata );
+
+ // internal methods
+ void LockControllingTeam( void );
+ void ReevaluateControllingTeam( void );
+ void SetControllingTeam( CBaseEntity *pActivator, int newTeam );
+
+ // outputs
+ int GetControllingTeam( void ) { return m_ControllingTeam.Get(); };
+ COutputInt m_ControllingTeam; // outputs the team currently controlling this spot, whenever it changes - this is -1 when contended
+
+public:
+ // Data
+ CNetworkVar( int, m_nZoneNumber );
+ int m_iDefendingTeam; // the original defeind team
+ int m_iLocked; // no more changes, until a reset it called
+ int m_iLockAfterChange; // auto-lock after the control zone changes hands through combat
+ float m_flTimeTillCaptured; // time that the control zone has to be uncontested for it to succesfully change teams
+ float m_flTimeTillContested; // time that the control zone has to be contested for for it to change to Contested mode (no team)
+ int m_iTryingToChangeToTeam; // the team is trying to change to
+
+ CUtlVector< CHandle<CBaseTFPlayer> > m_ZonePlayerList; // List of all players in the zone at the moment
+ int m_iPlayersInZone[MAX_TF_TEAMS+1]; // count of players in the zone divided by team
+
+ DECLARE_DATADESC();
+};
+
+#endif // CONTROLZONE_H
diff --git a/game/server/tf2/demo_entities.cpp b/game/server/tf2/demo_entities.cpp
new file mode 100644
index 0000000..08aa366
--- /dev/null
+++ b/game/server/tf2/demo_entities.cpp
@@ -0,0 +1,123 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+#include "cbase.h"
+#include "ai_basenpc.h"
+#include "animation.h"
+#include "vstdlib/random.h"
+#include "h_cycler.h"
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+class CCycler_TF2Commando : public CCycler
+{
+ DECLARE_CLASS( CCycler_TF2Commando, CCycler );
+public:
+ DECLARE_DATADESC();
+ DECLARE_SERVERCLASS();
+
+ virtual void Spawn( void );
+ virtual void Think( void );
+
+ // Inputs
+ void InputRaiseShield( inputdata_t &inputdata );
+ void InputLowerShield( inputdata_t &inputdata );
+
+private:
+ CNetworkVar( bool, m_bShieldActive );
+ CNetworkVar( float, m_flShieldRaiseTime );
+ CNetworkVar( float, m_flShieldLowerTime );
+};
+
+IMPLEMENT_SERVERCLASS_ST(CCycler_TF2Commando, DT_Cycler_TF2Commando)
+ SendPropInt (SENDINFO(m_bShieldActive), 1, SPROP_UNSIGNED ),
+ SendPropFloat(SENDINFO(m_flShieldRaiseTime), 0, SPROP_NOSCALE ),
+ SendPropFloat(SENDINFO(m_flShieldLowerTime), 0, SPROP_NOSCALE ),
+END_SEND_TABLE();
+
+LINK_ENTITY_TO_CLASS( cycler_tf2commando, CCycler_TF2Commando );
+LINK_ENTITY_TO_CLASS( cycler_aliencommando, CCycler_TF2Commando );
+
+BEGIN_DATADESC( CCycler_TF2Commando )
+
+ // Inputs
+ DEFINE_INPUTFUNC( FIELD_VOID, "RaiseShield", InputRaiseShield ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "LowerShield", InputLowerShield ),
+
+END_DATADESC()
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCycler_TF2Commando::Spawn( void )
+{
+ if (GetTeamNumber() == 1)
+ {
+ GenericCyclerSpawn( "models/player/human_commando.mdl", Vector(-16, -16, 0), Vector(16, 16, 72) );
+ }
+ else
+ {
+ GenericCyclerSpawn( "models/player/alien_commando.mdl", Vector(-16, -16, 0), Vector(16, 16, 72) );
+ }
+
+ m_bShieldActive = false;
+
+ BaseClass::Spawn();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCycler_TF2Commando::Think( void )
+{
+ // Change sequence
+ if ( IsSequenceFinished() )
+ {
+ // Raising our shield?
+ if ( m_bShieldActive )
+ {
+ ResetSequence( LookupSequence( "ShieldUpIdle" ) );
+ }
+ else if ( GetSequence() == LookupSequence( "ShieldDown" ) )
+ {
+ ResetSequence( LookupSequence( "Idle" ) );
+ }
+ }
+
+ SetNextThink( gpGlobals->curtime + 0.1f );
+
+ if (m_animate)
+ {
+ StudioFrameAdvance ( );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Input that raises the cycler's shield
+//-----------------------------------------------------------------------------
+void CCycler_TF2Commando::InputRaiseShield( inputdata_t &inputdata )
+{
+ if (m_animate)
+ {
+ m_bShieldActive = true;
+ ResetSequence( LookupSequence( "ShieldUp" ) );
+ m_flShieldRaiseTime = gpGlobals->curtime;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Input that lowers the cycler's shield
+//-----------------------------------------------------------------------------
+void CCycler_TF2Commando::InputLowerShield( inputdata_t &inputdata )
+{
+ if (m_animate)
+ {
+ m_bShieldActive = false;
+ ResetSequence( LookupSequence( "ShieldDown" ) );
+ m_flShieldLowerTime = gpGlobals->curtime;
+ }
+}
+
diff --git a/game/server/tf2/entity_burn_effect.cpp b/game/server/tf2/entity_burn_effect.cpp
new file mode 100644
index 0000000..3380fc7
--- /dev/null
+++ b/game/server/tf2/entity_burn_effect.cpp
@@ -0,0 +1,51 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#include "cbase.h"
+#include "entity_burn_effect.h"
+
+
+
+IMPLEMENT_SERVERCLASS_ST_NOBASE( CEntityBurnEffect, DT_EntityBurnEffect )
+ SendPropEHandle( SENDINFO( m_hBurningEntity ) )
+END_SEND_TABLE()
+
+
+LINK_ENTITY_TO_CLASS( entity_burn_effect, CEntityBurnEffect );
+
+
+CEntityBurnEffect* CEntityBurnEffect::Create( CBaseEntity *pBurningEntity )
+{
+ CEntityBurnEffect *pEffect = static_cast<CEntityBurnEffect*>(CreateEntityByName( "entity_burn_effect" ));
+ if ( pEffect )
+ {
+ pEffect->m_hBurningEntity = pBurningEntity;
+ return pEffect;
+ }
+ else
+ {
+ return NULL;
+ }
+}
+
+int CEntityBurnEffect::UpdateTransmitState()
+{
+ return SetTransmitState( FL_EDICT_FULLCHECK );
+}
+
+
+int CEntityBurnEffect::ShouldTransmit( const CCheckTransmitInfo *pInfo )
+{
+ CBaseEntity *pEnt = m_hBurningEntity;
+ if ( pEnt )
+ return pEnt->ShouldTransmit( pInfo );
+ else
+ return FL_EDICT_DONTSEND;
+}
+
+
+
diff --git a/game/server/tf2/entity_burn_effect.h b/game/server/tf2/entity_burn_effect.h
new file mode 100644
index 0000000..882abd5
--- /dev/null
+++ b/game/server/tf2/entity_burn_effect.h
@@ -0,0 +1,41 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef ENTITY_BURN_EFFECT_H
+#define ENTITY_BURN_EFFECT_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+
+#include "baseentity.h"
+#include "server_class.h"
+
+
+class CEntityBurnEffect : public CBaseEntity
+{
+public:
+
+ DECLARE_CLASS( CEntityBurnEffect, CBaseEntity );
+ DECLARE_SERVERCLASS();
+
+ static CEntityBurnEffect* Create( CBaseEntity *pBurningEntity );
+
+
+// Overrides.
+public:
+
+ virtual int UpdateTransmitState();
+ virtual int ShouldTransmit( const CCheckTransmitInfo *pInfo );
+
+
+private:
+ CNetworkHandle( CBaseEntity, m_hBurningEntity );
+};
+
+
+#endif // ENTITY_BURN_EFFECT_H
diff --git a/game/server/tf2/env_fallingrocks.cpp b/game/server/tf2/env_fallingrocks.cpp
new file mode 100644
index 0000000..00b1634
--- /dev/null
+++ b/game/server/tf2/env_fallingrocks.cpp
@@ -0,0 +1,201 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+#include "cbase.h"
+
+#define MAX_ROCK_MODELS 6
+
+// Rock models
+char *sRockModels[ MAX_ROCK_MODELS ] =
+{
+ "models/props/cliffside/inhibitor_rocks/inhibitor_rock12.mdl",
+ "models/props/cliffside/inhibitor_rocks/inhibitor_rock13.mdl",
+ "models/props/cliffside/inhibitor_rocks/inhibitor_rock14.mdl",
+ "models/props/cliffside/inhibitor_rocks/inhibitor_rock19.mdl",
+ "models/props/cliffside/inhibitor_rocks/inhibitor_rock20.mdl",
+ "models/props/cliffside/inhibitor_rocks/inhibitor_rock21.mdl",
+};
+
+//-----------------------------------------------------------------------------
+// Purpose: A falling rock entity
+//-----------------------------------------------------------------------------
+class CFallingRock : public CBaseAnimating
+{
+ DECLARE_CLASS( CFallingRock, CBaseAnimating );
+public:
+ DECLARE_DATADESC();
+
+ CFallingRock( void );
+ virtual void Spawn( void );
+ virtual void VPhysicsUpdate( IPhysicsObject *pPhysics );
+ static CFallingRock *Create( const Vector &vecOrigin, const QAngle &vecAngles, const Vector &vecVelocity, const AngularImpulse &vecRotationSpeed );
+
+ void RockTouch( CBaseEntity *pOther );
+public:
+};
+
+BEGIN_DATADESC( CFallingRock )
+
+ // functions
+ DEFINE_FUNCTION( RockTouch ),
+
+END_DATADESC()
+LINK_ENTITY_TO_CLASS( fallingrock, CFallingRock );
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CFallingRock::CFallingRock( void )
+{
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CFallingRock::Spawn( void )
+{
+ SetModel( sRockModels[ random->RandomInt(0,MAX_ROCK_MODELS-1) ] );
+ SetMoveType( MOVETYPE_NONE );
+ m_takedamage = DAMAGE_NO;
+
+ // Create the object in the physics system
+ VPhysicsInitNormal( SOLID_BBOX, 0, false );
+ UTIL_SetSize( this, Vector(-4,-4,-4), Vector(4,4,4) );
+
+ SetTouch( RockTouch );
+ SetThink( SUB_Remove );
+ SetNextThink( gpGlobals->curtime + random->RandomFloat( 20.0, 30.0 ) );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CFallingRock::VPhysicsUpdate( IPhysicsObject *pPhysics )
+{
+ BaseClass::VPhysicsUpdate( pPhysics );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Create a falling rock
+//-----------------------------------------------------------------------------
+CFallingRock *CFallingRock::Create( const Vector &vecOrigin, const QAngle &vecAngles, const Vector &vecVelocity, const AngularImpulse &vecRotationSpeed )
+{
+ CFallingRock *pRock = (CFallingRock*)CreateEntityByName("fallingrock");
+
+ UTIL_SetOrigin( pRock, vecOrigin );
+ pRock->SetLocalAngles( vecAngles );
+ pRock->Spawn();
+
+ IPhysicsObject *pPhysicsObject = pRock->VPhysicsGetObject();
+ if ( pPhysicsObject )
+ {
+ pPhysicsObject->AddVelocity( &vecVelocity, &vecRotationSpeed );
+ }
+
+ return pRock;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CFallingRock::RockTouch( CBaseEntity *pOther )
+{
+}
+
+
+
+
+
+
+//-----------------------------------------------------------------------------
+// Purpose: A falling rock spawner entity
+//-----------------------------------------------------------------------------
+class CEnv_FallingRocks : public CPointEntity
+{
+ DECLARE_CLASS( CEnv_FallingRocks, CPointEntity );
+public:
+ DECLARE_DATADESC();
+
+ virtual void Spawn( void );
+ virtual void Precache( void );
+ void RockThink( void );
+ void InputSpawnRock( inputdata_t &inputdata );
+
+public:
+ float m_flFallStrength;
+ float m_flRotationSpeed;
+ float m_flMinSpawnTime;
+ float m_flMaxSpawnTime;
+ COutputEvent m_pOutputRockSpawned;
+};
+
+BEGIN_DATADESC( CEnv_FallingRocks )
+
+ // Fields
+ DEFINE_KEYFIELD( m_flFallStrength, FIELD_FLOAT, "FallSpeed"),
+ DEFINE_KEYFIELD( m_flRotationSpeed, FIELD_FLOAT, "RotationSpeed"),
+ DEFINE_KEYFIELD( m_flMinSpawnTime, FIELD_FLOAT, "MinSpawnTime"),
+ DEFINE_KEYFIELD( m_flMaxSpawnTime, FIELD_FLOAT, "MaxSpawnTime"),
+
+ // Inputs
+ DEFINE_INPUTFUNC( FIELD_VOID, "SpawnRock", InputSpawnRock ),
+
+ // Outputs
+ DEFINE_OUTPUT( m_pOutputRockSpawned, "OnRockSpawned" ),
+
+ // Functions
+ DEFINE_FUNCTION( RockThink ),
+
+END_DATADESC()
+LINK_ENTITY_TO_CLASS( env_fallingrocks, CEnv_FallingRocks );
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CEnv_FallingRocks::Spawn( void )
+{
+ Precache();
+ SetThink( RockThink );
+ SetNextThink( gpGlobals->curtime + random->RandomFloat( m_flMinSpawnTime, m_flMaxSpawnTime ) );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CEnv_FallingRocks::Precache( void )
+{
+ for (int i = 0; i < MAX_ROCK_MODELS; i++ )
+ {
+ PrecacheModel( sRockModels[i] );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CEnv_FallingRocks::RockThink( void )
+{
+ // Spawn a rock
+ // Make it aim a little around the angle supplied
+ QAngle angFire = GetAbsAngles();
+ angFire.y += random->RandomFloat( -10, 10 );
+ Vector vecForward;
+ AngleVectors( angFire, &vecForward );
+ CFallingRock::Create( GetAbsOrigin(), GetAbsAngles(), (vecForward * m_flFallStrength), AngularImpulse(0,0,m_flRotationSpeed) );
+
+ // Fire our output
+ m_pOutputRockSpawned.FireOutput( NULL,this );
+
+ SetNextThink( gpGlobals->curtime + random->RandomFloat( m_flMinSpawnTime, m_flMaxSpawnTime ) );
+}
+
+//------------------------------------------------------------------------------
+// Purpose:
+//------------------------------------------------------------------------------
+void CEnv_FallingRocks::InputSpawnRock( inputdata_t &inputdata )
+{
+ RockThink();
+} \ No newline at end of file
diff --git a/game/server/tf2/env_meteor.cpp b/game/server/tf2/env_meteor.cpp
new file mode 100644
index 0000000..789df1f
--- /dev/null
+++ b/game/server/tf2/env_meteor.cpp
@@ -0,0 +1,618 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#include "cbase.h"
+
+#include "tf_player.h"
+#include "Env_Meteor.h"
+#include "entitylist.h"
+#include "vphysics_interface.h"
+#include "tier1/strtools.h"
+#include "mapdata_shared.h"
+#include "sharedinterface.h"
+#include "skycamera.h"
+#include "ispatialpartition.h"
+#include "gameinterface.h"
+#include "props.h"
+#include "tf_func_resource.h"
+#include "resource_chunk.h"
+
+
+#include "ndebugoverlay.h"
+
+//=============================================================================
+//
+// Enumerator for swept bbox collision.
+//
+class CCollideList : public IEntityEnumerator
+{
+public:
+ CCollideList( Ray_t *pRay, CBaseEntity* pIgnoreEntity, int nContentsMask ) :
+ m_Entities( 0, 32 ), m_pIgnoreEntity( pIgnoreEntity ),
+ m_nContentsMask( nContentsMask ), m_pRay(pRay) {}
+
+ virtual bool EnumEntity( IHandleEntity *pHandleEntity )
+ {
+ trace_t tr;
+ enginetrace->ClipRayToEntity( *m_pRay, m_nContentsMask, pHandleEntity, &tr );
+ if (( tr.fraction < 1.0f ) || (tr.startsolid) || (tr.allsolid))
+ {
+ CBaseEntity *pEntity = gEntList.GetBaseEntity( pHandleEntity->GetRefEHandle() );
+ m_Entities.AddToTail( pEntity );
+ }
+
+ return true;
+ }
+
+ CUtlVector<CBaseEntity*> m_Entities;
+
+private:
+ CBaseEntity *m_pIgnoreEntity;
+ int m_nContentsMask;
+ Ray_t *m_pRay;
+};
+
+
+//=============================================================================
+//
+// Meteor Factory Functions
+//
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CMeteorFactory::CreateMeteor( int nID, int iType,
+ const Vector &vecPosition, const Vector &vecDirection,
+ float flSpeed, float flStartTime, float flDamageRadius,
+ const Vector &vecTriggerMins, const Vector &vecTriggerMaxs )
+{
+ CEnvMeteor::Create( nID, iType, vecPosition, vecDirection, flSpeed, flStartTime, flDamageRadius,
+ vecTriggerMins, vecTriggerMaxs );
+}
+
+//=============================================================================
+//
+// Meteor Spawner Functions
+//
+
+void SendProxy_MeteorTargetPositions( const SendProp *pProp, const void *pStruct, const void *pData, DVariant *pOut, int iElement, int objectID )
+{
+ CEnvMeteorSpawnerShared *pMeteorSpawner = ( CEnvMeteorSpawnerShared* )pData;
+ pOut->m_Vector[0] = pMeteorSpawner->m_aTargets[iElement].m_vecPosition.x;
+ pOut->m_Vector[1] = pMeteorSpawner->m_aTargets[iElement].m_vecPosition.y;
+ pOut->m_Vector[2] = pMeteorSpawner->m_aTargets[iElement].m_vecPosition.z;
+}
+
+void SendProxy_MeteorTargetRadii( const SendProp *pProp, const void *pStruct, const void *pData, DVariant *pOut, int iElement, int objectID )
+{
+ CEnvMeteorSpawnerShared *pMeteorSpawner = ( CEnvMeteorSpawnerShared* )pData;
+ pOut->m_Float = pMeteorSpawner->m_aTargets[iElement].m_flRadius;
+}
+
+int SendProxyArrayLength_MeteorTargets( const void *pStruct, int objectID )
+{
+ CEnvMeteorSpawnerShared *pMeteorSpawner = ( CEnvMeteorSpawnerShared* )pStruct;
+ return pMeteorSpawner->m_aTargets.Count();
+}
+
+// Link the name "env_meteorspawner" to the CMeteorSpawner class. This
+// links the WC entity with the game code.
+LINK_ENTITY_TO_CLASS( env_meteorspawner, CEnvMeteorSpawner );
+
+BEGIN_DATADESC( CEnvMeteorSpawner )
+
+ // Key Fields.
+ DEFINE_KEYFIELD( m_SpawnerShared.m_iMeteorType, FIELD_INTEGER, "MeteorType" ),
+ DEFINE_KEYFIELD( m_SpawnerShared.m_bSkybox, FIELD_INTEGER, "SpawnInSkybox" ),
+ DEFINE_KEYFIELD( m_SpawnerShared.m_flMinSpawnTime, FIELD_FLOAT, "SpawnIntervalMin" ),
+ DEFINE_KEYFIELD( m_SpawnerShared.m_flMaxSpawnTime, FIELD_FLOAT, "SpawnIntervalMax" ),
+ DEFINE_KEYFIELD( m_SpawnerShared.m_nMinSpawnCount, FIELD_INTEGER, "SpawnCountMin" ),
+ DEFINE_KEYFIELD( m_SpawnerShared.m_nMaxSpawnCount, FIELD_INTEGER, "SpawnCountMax" ),
+ DEFINE_KEYFIELD( m_SpawnerShared.m_flMinSpeed, FIELD_FLOAT, "MeteorSpeedMin" ),
+ DEFINE_KEYFIELD( m_SpawnerShared.m_flMaxSpeed, FIELD_FLOAT, "MeteorSpeedMax" ),
+ DEFINE_KEYFIELD( m_SpawnerShared.m_flMeteorDamageRadius, FIELD_FLOAT, "MeteorDamageRadius" ),
+ DEFINE_KEYFIELD( m_fDisabled, FIELD_BOOLEAN, "StartDisabled" ),
+
+ // Function Pointers.
+ DEFINE_FUNCTION( MeteorSpawnerThink ),
+
+ // Inputs
+ DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputDisable ),
+
+END_DATADESC()
+
+BEGIN_SEND_TABLE_NOBASE( CEnvMeteorSpawnerShared, DT_EnvMeteorSpawnerShared )
+ // Setup (read from) Worldcraft.
+ SendPropInt ( SENDINFO( m_iMeteorType ), 8, SPROP_UNSIGNED ),
+ SendPropInt ( SENDINFO( m_bSkybox ), 4, SPROP_UNSIGNED ),
+ SendPropFloat ( SENDINFO( m_flMinSpawnTime ), 0, SPROP_NOSCALE ),
+ SendPropFloat ( SENDINFO( m_flMaxSpawnTime ), 0, SPROP_NOSCALE ),
+ SendPropInt ( SENDINFO( m_nMinSpawnCount ), 16, SPROP_UNSIGNED ),
+ SendPropInt ( SENDINFO( m_nMaxSpawnCount ), 16, SPROP_UNSIGNED ),
+ SendPropFloat ( SENDINFO( m_flMinSpeed ), 0, SPROP_NOSCALE ),
+ SendPropFloat ( SENDINFO( m_flMaxSpeed ), 0, SPROP_NOSCALE ),
+
+ // Setup through Init.
+ SendPropFloat ( SENDINFO( m_flStartTime ), -1, SPROP_NOSCALE ),
+ SendPropInt ( SENDINFO( m_nRandomSeed ), -1, SPROP_UNSIGNED ),
+ SendPropVector ( SENDINFO( m_vecMinBounds ), -1, SPROP_NOSCALE ),
+ SendPropVector ( SENDINFO( m_vecMaxBounds ), -1, SPROP_NOSCALE ),
+ SendPropVector ( SENDINFO( m_vecTriggerMins ), -1, SPROP_NOSCALE ),
+ SendPropVector ( SENDINFO( m_vecTriggerMaxs ), -1, SPROP_NOSCALE ),
+
+ // Target List
+ SendPropArray2( SendProxyArrayLength_MeteorTargets,
+ SendPropVector( "meteortargetposition_array_element", 0, 0, 0, SPROP_NOSCALE, 0, 0, SendProxy_MeteorTargetPositions ),
+ 16, 0, "meteortargetposition_array" ),
+
+ SendPropArray2( SendProxyArrayLength_MeteorTargets,
+ SendPropFloat( "meteortargetradius_array_element", 0, 0, 0, SPROP_NOSCALE, 0, 0, SendProxy_MeteorTargetRadii ),
+ 16, 0, "meteortargetradius_array" )
+END_SEND_TABLE()
+
+// This table encodes the CBaseEntity data.
+IMPLEMENT_SERVERCLASS_ST_NOBASE( CEnvMeteorSpawner, DT_EnvMeteorSpawner )
+ SendPropDataTable ( SENDINFO_DT( m_SpawnerShared ), &REFERENCE_SEND_TABLE( DT_EnvMeteorSpawnerShared ) ),
+ SendPropInt ( SENDINFO( m_fDisabled ), 1, SPROP_UNSIGNED ),
+END_SEND_TABLE()
+
+// Meteor Models
+char *strResourceMeteorModels[2] =
+{
+ "models/props/common/meteorites/meteor04.mdl",
+ "models/props/common/meteorites/meteor05.mdl",
+};
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+CEnvMeteorSpawner::CEnvMeteorSpawner()
+{
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CEnvMeteorSpawner::Spawn( void )
+{
+ // Pre-cache.
+ Precache();
+
+ // Server-side is not visible -- for collision only.
+ SetSolid( SOLID_NONE );
+ SetMoveType( MOVETYPE_NONE );
+ AddEffects( EF_NODRAW );
+
+ // Set the "brush model" size and link into the world.
+ SetModel( STRING( GetModelName() ) );
+
+ // Set the think function and time.
+ if ( !m_fDisabled )
+ {
+ SetThink( MeteorSpawnerThink );
+ }
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CEnvMeteorSpawner::InputEnable( inputdata_t &inputdata )
+{
+ m_fDisabled = false;
+
+ m_SpawnerShared.m_flStartTime = gpGlobals->curtime;
+ m_SpawnerShared.m_flNextSpawnTime = m_SpawnerShared.m_flStartTime + m_SpawnerShared.m_flMaxSpawnTime;
+
+ // Probably should set this as a message begin, etc..... will get to this later!!
+//
+// CEntityMessageFilter filter( this, "CEnvMeteorSpawner" );
+// MessageBegin( filter, 0 );
+// WRITE_LONG( m_SpawnerShared.m_flStartTime );
+// WRITE_LONG( m_SpawnerShared.m_flNextSpawnTime );
+// MessageEnd();
+
+ // Set the think function and time.
+ SetThink( MeteorSpawnerThink );
+ SetNextThink( gpGlobals->curtime + m_SpawnerShared.m_flNextSpawnTime );
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CEnvMeteorSpawner::InputDisable( inputdata_t &inputdata )
+{
+ m_fDisabled = true;
+ SetThink( NULL );
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CEnvMeteorSpawner::Get3DSkyboxWorldBounds( Vector &vecTriggerMins,
+ Vector &vecTriggerMaxs )
+{
+ CBaseEntity *pEntity = gEntList.FindEntityByClassname( NULL, "trigger_skybox2world" );
+ if ( pEntity && pEntity->edict() )
+ {
+ pEntity->CollisionProp()->WorldSpaceAABB( &vecTriggerMins, &vecTriggerMaxs );
+ }
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CEnvMeteorSpawner::Precache( void )
+{
+ // Precache the meteor models!
+ for ( int iType = 0; iType < 2; iType++ )
+ {
+ PrecacheModel( strResourceMeteorModels[iType] );
+ }
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CEnvMeteorSpawner::MeteorSpawnerThink( void )
+{
+ SetNextThink( gpGlobals->curtime + m_SpawnerShared.MeteorThink( gpGlobals->curtime ) );
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+int CEnvMeteorSpawner::ShouldTransmit( const CCheckTransmitInfo *pInfo )
+{
+ if ( m_SpawnerShared.m_bSkybox )
+ return FL_EDICT_ALWAYS;
+
+ return BaseClass::ShouldTransmit( pInfo );
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CEnvMeteorSpawner::Activate( void )
+{
+ // Parse the entity list looking for targets!
+ int nEntityCount = engine->GetEntityCount();
+ for ( int iEntity = 0; iEntity < nEntityCount; ++iEntity )
+ {
+ edict_t *pEdict = engine->PEntityOfEntIndex( iEntity );
+ if ( !pEdict || pEdict->IsFree() )
+ continue;
+
+ CBaseEntity *pEntity = GetContainingEntity( pEdict );
+ if ( !pEntity )
+ continue;
+
+ if ( pEntity->GetFlags()& FL_STATICPROP )
+ continue;
+
+ if ( !Q_strcmp( pEntity->GetClassname(), "env_meteortarget" ) )
+ {
+ CEnvMeteorTarget *pMeteorTarget = static_cast<CEnvMeteorTarget*>( pEntity );
+ if ( pMeteorTarget && pMeteorTarget->m_target != NULL_STRING )
+ {
+ if ( !Q_strcmp( STRING( pMeteorTarget->m_target ), STRING( GetEntityName() ) ) )
+ {
+ m_SpawnerShared.AddToTargetList( pMeteorTarget->GetLocalOrigin(), pMeteorTarget->m_flRadius );
+ }
+ }
+ }
+ }
+
+ // Get 3d skybox world trigger bounds.
+ Vector vecTriggerMins, vecTriggerMaxs;
+ Get3DSkyboxWorldBounds( vecTriggerMins, vecTriggerMaxs );
+
+ // Initialize the spawner.
+ float flTime = gpGlobals->curtime;
+ m_SpawnerShared.Init( &m_Factory, 0/* seed */, flTime,
+ WorldAlignMins(), WorldAlignMaxs(), vecTriggerMins, vecTriggerMaxs );
+
+ // Setup next think.
+ if ( !m_fDisabled )
+ {
+ SetNextThink( gpGlobals->curtime + m_SpawnerShared.m_flNextSpawnTime );
+ }
+}
+
+//=============================================================================
+//
+// Meteor Target Functions
+//
+
+LINK_ENTITY_TO_CLASS( env_meteortarget, CEnvMeteorTarget );
+
+BEGIN_DATADESC( CEnvMeteorTarget )
+
+ // Key Fields.
+ DEFINE_KEYFIELD( m_flRadius, FIELD_FLOAT, "EffectRadius" ),
+
+END_DATADESC()
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+CEnvMeteorTarget::CEnvMeteorTarget()
+{
+ m_iTargetID = -1;
+ m_flRadius = 1.0f;
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CEnvMeteorTarget::Spawn( void )
+{
+ BaseClass::Spawn();
+}
+
+//=============================================================================
+//
+// Meteor Functions
+//
+
+//
+// NOTE: The server-side meteor code has not really been tested. I do not
+// trust that is works correctly and/or cleans itself up nicely!
+//
+
+LINK_ENTITY_TO_CLASS( env_meteor, CEnvMeteor );
+
+BEGIN_DATADESC( CEnvMeteor )
+
+ // Function Pointers.
+ DEFINE_FUNCTION( MeteorSkyboxThink ),
+ DEFINE_FUNCTION( MeteorWorldThink ),
+
+END_DATADESC()
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+CEnvMeteor::CEnvMeteor()
+{
+ m_vecMin.Init( -10.0f, -10.0f, -10.0f );
+ m_vecMax.Init( 10.0f, 10.0f, 10.0f );
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+CEnvMeteor *CEnvMeteor::Create( int nID, int iMeteorType,
+ const Vector &vecOrigin, const Vector &vecDirection,
+ float flSpeed, float flStartTime, float flDamageRadius,
+ const Vector &vecTriggerMins, const Vector &vecTriggerMaxs )
+{
+ CEnvMeteor *pMeteor = ( CEnvMeteor* )CreateEntityByName( "env_meteor" );
+ if ( pMeteor )
+ {
+ pMeteor->m_Meteor.Init( nID, flStartTime, METEOR_PASSIVE_TIME, vecOrigin, vecDirection, flSpeed,
+ flDamageRadius, vecTriggerMins, vecTriggerMaxs );
+
+ // If the meteor will never enter the world, then don't bother with a server-side version.
+ if ( pMeteor->m_Meteor.m_flWorldEnterTime == METEOR_INVALID_TIME )
+ {
+ UTIL_Remove( pMeteor );
+ }
+
+ // Handle forward simulation.
+ if ( ( pMeteor->m_Meteor.m_flStartTime + METEOR_MAX_LIFETIME ) < gpGlobals->curtime )
+ {
+ UTIL_Remove( pMeteor );
+ }
+
+ pMeteor->Spawn();
+ pMeteor->SetNextThink( gpGlobals->curtime + pMeteor->m_Meteor.m_flWorldEnterTime );
+ }
+
+ return pMeteor;
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CEnvMeteor::Spawn( void )
+{
+ // Pass data.
+ BaseClass::Spawn();
+
+ int iModel = modelinfo->GetModelIndex( "models/props/common/meteorites/meteor04.mdl" );
+ if ( iModel > 0 )
+ {
+ const model_t *pModel = modelinfo->GetModel( iModel );
+ modelinfo->GetModelBounds( pModel, m_vecMin, m_vecMax );
+ }
+
+ // Assumes we start life in a skybox!
+ SetThink( MeteorSkyboxThink );
+
+ m_bPrevInSkybox = true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: This think function should be called at the time when the meteor
+// will be leaving the skybox and entering the world.
+//-----------------------------------------------------------------------------
+void CEnvMeteor::MeteorSkyboxThink( void )
+{
+ SetThink( MeteorWorldThink );
+ SetNextThink( gpGlobals->curtime + 0.2f );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: This think function simulates (moves/collides) the meteor while in
+// the world.
+//-----------------------------------------------------------------------------
+void CEnvMeteor::MeteorWorldThink( void )
+{
+ // Get the current time.
+ float flTime = gpGlobals->curtime;
+
+ // Convert if need be!
+ if ( m_bPrevInSkybox )
+ {
+ m_Meteor.ConvertFromSkyboxToWorld();
+ UTIL_SetOrigin( this, m_Meteor.m_vecStartPosition );
+
+ m_bPrevInSkybox = false;
+ }
+
+ // Update meteor position for swept collision test.
+ Vector vecEndPosition;
+ m_Meteor.GetPositionAtTime( flTime, vecEndPosition );
+
+ // Debugging!!
+// NDebugOverlay::Box( GetAbsOrigin(), m_vecMin * 0.5f, m_vecMax * 0.5f, 255, 255, 0, 0, 5 );
+// NDebugOverlay::Box( vecEndPosition, m_vecMin, m_vecMax, 255, 0, 0, 0, 5 );
+
+ Ray_t ray;
+ ray.Init( GetAbsOrigin(), vecEndPosition, m_vecMin, m_vecMax );
+
+ CCollideList collideList( &ray, this, MASK_SOLID );
+ enginetrace->EnumerateEntities( ray, false, &collideList );
+
+ // Now get each entity and react accordinly!
+ for( int iEntity = collideList.m_Entities.Count(); --iEntity >= 0; )
+ {
+ CBaseEntity *pEntity = collideList.m_Entities[iEntity];
+
+ if ( pEntity )
+ {
+ Vector vecForceDir = m_Meteor.m_vecDirection;
+
+ // Check for a physics object and apply force!
+ IPhysicsObject *pPhysObject = pEntity->VPhysicsGetObject();
+ if ( pPhysObject )
+ {
+// float flMass = pPhysObject->GetMass();
+
+ // Send it flying!!!
+ vecForceDir *= 5000000000000.0f;
+ pPhysObject->ApplyForceCenter( vecForceDir );
+ }
+
+ if ( pEntity->m_takedamage )
+ {
+ CTakeDamageInfo info( this, this, 200.0f, DMG_CLUB );
+ CalculateExplosiveDamageForce( &info, vecForceDir, pEntity->GetAbsOrigin() );
+ pEntity->TakeDamage( info );
+ }
+ }
+ }
+
+ trace_t trace;
+ UTIL_TraceHull( GetAbsOrigin(), vecEndPosition, m_vecMin, m_vecMax,
+ MASK_NPCWORLDSTATIC, this, COLLISION_GROUP_NONE, &trace );
+ if( ( trace.fraction < 1.0f ) && !( trace.surface.flags & SURF_SKY ) )
+ {
+ CBaseEntity *pEntity = trace.m_pEnt;
+ if ( pEntity )
+ {
+ // Hit the world? The meteor is destroyed!
+ if ( pEntity->GetSolid() == SOLID_BSP )
+ {
+#if 0
+ // Suppress resources for now!!
+
+ // Create a random number or resource chunks.
+ int nChunkCount = random->RandomInt( 0, 4 );
+ for( int iChunk = 0; iChunk < nChunkCount; ++iChunk )
+ {
+ // Generate a random velocity vector.
+ Vector vVelocity = Vector( random->RandomFloat( -20,20 ), random->RandomFloat( -20,20 ), random->RandomFloat( 100,150 ) );
+ CResourceChunk::Create( false, GetAbsOrigin(), vVelocity );
+ }
+#endif
+
+ // Splash damage!
+ Vector vecImpactPoint;
+ vecImpactPoint = GetAbsOrigin() + ( ( vecEndPosition - GetAbsOrigin() ) * trace.fraction );
+
+ // Debugging!!
+// NDebugOverlay::Box( vecImpactPoint, m_vecMin, m_vecMax, 0, 255, 0, 0, 5 );
+
+ //Iterate on all entities in the vicinity.
+ for ( CEntitySphereQuery sphere( vecImpactPoint, m_Meteor.GetDamageRadius() ); pEntity = sphere.GetCurrentEntity(); sphere.NextEntity() )
+ {
+ // Get distance to object and use it as a scale value.
+ Vector vecSegment;
+ vecSegment = pEntity->GetAbsOrigin() - vecImpactPoint;
+ float flDistance = vecSegment.Length();
+
+ float flScale = flDistance / ( m_Meteor.GetDamageRadius() * 0.75f );
+ if ( flScale > 1.0f )
+ {
+ flScale = 1.0f;
+ }
+
+ Vector vecForceDir = m_Meteor.m_vecDirection;
+
+ // Check for a physics object and apply force!
+ IPhysicsObject *pPhysObject = pEntity->VPhysicsGetObject();
+ if ( pPhysObject )
+ {
+// float flMass = pPhysObject->GetMass();
+
+ // Send it flying!!!
+ vecForceDir *= 5000000000000.0f * flScale;
+ pPhysObject->ApplyForceCenter( vecForceDir );
+ }
+
+ if ( pEntity->m_takedamage )
+ {
+ CTakeDamageInfo info( this, this, 300.0f * flScale, DMG_CLUB );
+ CalculateExplosiveDamageForce( &info, vecForceDir, pEntity->GetAbsOrigin() );
+ pEntity->TakeDamage( info );
+ }
+ }
+
+ UTIL_Remove( this );
+ return;
+ }
+ }
+ }
+
+ // Always move full movement.
+ UTIL_SetOrigin( this, vecEndPosition );
+ SetNextThink( gpGlobals->curtime + 0.2f );
+
+ // Check for death.
+ if ( flTime >= m_Meteor.m_flWorldExitTime )
+ {
+ UTIL_Remove( this );
+ return;
+ }
+}
+
+//=============================================================================
+//
+// Shooting Star Spawner Functionality.
+//
+
+// Link the name "env_meteorspawner" to the CMeteorSpawner class. This
+// links the WC entity with the game code.
+LINK_ENTITY_TO_CLASS( env_shootingstarspawner, CShootingStarSpawner );
+
+BEGIN_DATADESC( CShootingStarSpawner )
+
+ // keys
+ DEFINE_KEYFIELD_NOT_SAVED( m_flSpawnInterval, FIELD_FLOAT, "SpawnInterval" ),
+ DEFINE_KEYFIELD_NOT_SAVED( m_bSkybox, FIELD_INTEGER, "SpawnInSkybox" ),
+
+END_DATADESC()
+
+IMPLEMENT_SERVERCLASS_ST( CShootingStarSpawner, DT_ShootingStarSpawner )
+ SendPropFloat( SENDINFO( m_flSpawnInterval ), -1, SPROP_NOSCALE ),
+END_SEND_TABLE()
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CShootingStarSpawner::CShootingStarSpawner()
+{
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+int CShootingStarSpawner::ShouldTransmit( const CCheckTransmitInfo *pInfo )
+{
+ // Always send shooting star spawners if they are in the skybox!
+ if ( m_bSkybox )
+ return FL_EDICT_ALWAYS ;
+
+ return BaseClass::ShouldTransmit( pInfo );
+}
diff --git a/game/server/tf2/env_meteor.h b/game/server/tf2/env_meteor.h
new file mode 100644
index 0000000..ddeb2ca
--- /dev/null
+++ b/game/server/tf2/env_meteor.h
@@ -0,0 +1,146 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef ENV_METEOR_H
+#define ENV_METEOR_H
+#pragma once
+
+#include "BaseEntity.h"
+#include "BaseAnimating.h"
+#include "Env_Meteor_Shared.h"
+#include "utlvector.h"
+
+//=============================================================================
+//
+// Server-side Meteor Factory Class
+//
+class CMeteorFactory : public IMeteorFactory
+{
+public:
+
+ void CreateMeteor( int nID, int iType, const Vector &vecPosition,
+ const Vector &vecDirection, float flSpeed, float flStartTime,
+ float flDamageRadius,
+ const Vector &vecTriggerMins, const Vector &vecTriggerMaxs );
+};
+
+//=============================================================================
+//
+// Meteor Spawner Class
+//
+class CEnvMeteorSpawner : public CBaseEntity
+{
+public:
+
+ DECLARE_CLASS( CEnvMeteorSpawner, CBaseEntity );
+
+ DECLARE_DATADESC();
+ DECLARE_SERVERCLASS();
+
+ CEnvMeteorSpawner();
+
+ void Spawn( void );
+ void Precache( void );
+ void MeteorSpawnerThink( void );
+ int UpdateTransmitState() { return SetTransmitState( FL_EDICT_FULLCHECK ); }
+ int ShouldTransmit( const CCheckTransmitInfo *pInfo );
+ void Activate( void );
+
+private:
+
+ // Inputs
+ void InputEnable( inputdata_t &inputdata );
+ void InputDisable( inputdata_t &inputdata );
+
+ void Get3DSkyboxWorldBounds( Vector &vecTriggerMins, Vector &vecTriggerMaxs );
+
+ CMeteorFactory m_Factory;
+ CNetworkVarEmbedded( CEnvMeteorSpawnerShared, m_SpawnerShared );
+
+ CNetworkVar( bool, m_fDisabled ); // Spawner active (trigger). NOTE: uses an f to remain consistent
+ // with entity input system
+};
+
+//=============================================================================
+//
+// Meteor Target Class
+//
+class CEnvMeteorTarget : public CBaseEntity
+{
+public:
+
+ DECLARE_CLASS( CEnvMeteorTarget, CBaseEntity );
+ DECLARE_DATADESC();
+
+ CEnvMeteorTarget();
+ void Spawn( void );
+
+ int m_iTargetID;
+ float m_flRadius;
+};
+
+//=============================================================================
+//
+// Meteor Class
+//
+class CEnvMeteor : public CBaseAnimating
+{
+
+ DECLARE_CLASS( CEnvMeteor, CBaseAnimating );
+
+public:
+
+ DECLARE_DATADESC();
+
+ //-------------------------------------------------------------------------
+ // Initialization
+ //-------------------------------------------------------------------------
+ CEnvMeteor();
+ static CEnvMeteor *Create( int nID, int iMeteorType, const Vector &vecOrigin,
+ const Vector &vecDirection, float flSpeed, float flStartTime,
+ float flDamageRadius,
+ const Vector &vecTriggerMins, const Vector &vecTriggerMaxs );
+ void Spawn( void );
+
+ //-------------------------------------------------------------------------
+ // Think(s)
+ //-------------------------------------------------------------------------
+ void MeteorSkyboxThink( void );
+ void MeteorWorldThink( void );
+
+private:
+
+ CEnvMeteorShared m_Meteor;
+ bool m_bPrevInSkybox;
+ Vector m_vecMin, m_vecMax;
+};
+
+//=============================================================================
+//
+// Shooting Star Spawner Class
+//
+class CShootingStarSpawner : public CBaseEntity
+{
+ DECLARE_CLASS( CShootingStarSpawner, CBaseEntity );
+
+public:
+
+ CShootingStarSpawner();
+
+ DECLARE_DATADESC();
+ DECLARE_SERVERCLASS();
+
+ virtual int UpdateTransmitState() { return SetTransmitState( FL_EDICT_FULLCHECK ); }
+ virtual int ShouldTransmit( const CCheckTransmitInfo *pInfo );
+
+public:
+
+ CNetworkVar( float, m_flSpawnInterval ); // How often do I spawn shooting stars?
+ bool m_bSkybox; // Is the spawner in the skybox?
+};
+
+#endif // ENV_METEOR_H \ No newline at end of file
diff --git a/game/server/tf2/fire_damage_mgr.cpp b/game/server/tf2/fire_damage_mgr.cpp
new file mode 100644
index 0000000..4d15084
--- /dev/null
+++ b/game/server/tf2/fire_damage_mgr.cpp
@@ -0,0 +1,301 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#include "cbase.h"
+#include "fire_damage_mgr.h"
+#include "entity_burn_effect.h"
+#include "gasoline_blob.h"
+#include "tf_obj.h"
+#include "ai_basenpc.h"
+#include "tf_gamerules.h"
+
+
+#define FIRE_DAMAGE_APPLY_INTERVAL 0.5 // Apply the damage at this interval.
+#define FIRE_DECAY_END_VALUE 0.00001
+
+
+// No more damage from fire can be applied to a player per second.
+#define MAX_FIRE_DAMAGE_PER_SECOND 15
+
+// The fire heat uses exponential decay. It goes from MAX_FIRE_DAMAGE_PER_SECOND to
+// FIRE_DECAY_END_VALUE in FIRE_DECAY_SECONDS.
+#define FIRE_DECAY_SECONDS 3
+
+
+ConVar fire_damageall( "fire_damageall", "0", 0, "Enable fire damaging team members." );
+
+
+bool CFireDamageMgr::Init()
+{
+ m_flApplyDamageCountdown = FIRE_DAMAGE_APPLY_INTERVAL;
+
+ // Fire decays exponentially: B = A * e^(-kt)
+ // So we set B=FIRE_DECAY_END_VALUE, A=flMaxDamagePerSecond, and t=flFireDecaySeconds, then solve for K.
+ m_flMaxDamagePerSecond = MAX_FIRE_DAMAGE_PER_SECOND;
+ m_flDecayConstant = -log( FIRE_DECAY_END_VALUE / m_flMaxDamagePerSecond ) / FIRE_DECAY_SECONDS;
+
+ return true;
+}
+
+
+void CFireDamageMgr::AddDamage( CBaseEntity *pTarget, CBaseEntity *pAttacker, float flDamageAccel, bool bMakeBurnEffect )
+{
+ FOR_EACH_LL( m_DamageEnts, iDamageEnt )
+ {
+ CDamageEnt *pEnt = &m_DamageEnts[iDamageEnt];
+
+ if ( pEnt->m_hEnt != pTarget )
+ continue;
+
+ for ( int i=0; i < pEnt->m_nAttackers; i++ )
+ {
+ if ( pEnt->m_Attackers[i].m_hAttacker == pAttacker )
+ {
+ pEnt->m_Attackers[i].m_flVelocity += flDamageAccel * gpGlobals->frametime;
+ return;
+ }
+ }
+
+
+ if ( pEnt->m_nAttackers < CDamageEnt::MAX_ATTACKERS )
+ {
+ // Add a new attacker.
+ pEnt->m_Attackers[pEnt->m_nAttackers].Init( pAttacker, flDamageAccel * gpGlobals->frametime );
+ ++pEnt->m_nAttackers;
+ return;
+ }
+ else
+ {
+ // No room for more attackers.
+ Warning( "CFireDamageMgr: ran out of attackers\n" );
+ return;
+ }
+ }
+
+ // Add a new CDamageEnt.
+ int iNew = m_DamageEnts.AddToTail();
+ CDamageEnt *pEnt = &m_DamageEnts[iNew];
+ pEnt->m_hEnt = pTarget;
+ pEnt->m_bWasAlive = pTarget->IsAlive();
+ pEnt->m_nAttackers = 1;
+ pEnt->m_Attackers[0].Init( pAttacker, flDamageAccel * gpGlobals->frametime );
+ if ( bMakeBurnEffect )
+ pEnt->m_pBurnEffect = CEntityBurnEffect::Create( pTarget );
+ else
+ pEnt->m_pBurnEffect = NULL;
+}
+
+
+void CFireDamageMgr::RemoveDamageEnt( int iEnt )
+{
+ UTIL_Remove( m_DamageEnts[iEnt].m_pBurnEffect );
+ m_DamageEnts.Remove( iEnt );
+}
+
+
+void CFireDamageMgr::FrameUpdatePostEntityThink()
+{
+ VPROF( "CFireDamageMgr::FrameUpdatePostEntityThink" );
+ float frametime = gpGlobals->frametime;
+
+ // Update the damage countdown.
+ m_flApplyDamageCountdown -= gpGlobals->frametime;
+ bool bApplyDamageThisFrame = false;
+ if ( m_flApplyDamageCountdown <= 0 )
+ {
+ bApplyDamageThisFrame = true;
+ m_flApplyDamageCountdown += FIRE_DAMAGE_APPLY_INTERVAL;
+ }
+
+
+ // (-kt)
+ // Figure out how much all the damage decays this frame: e
+ float flFrameDecay = pow( 2.718281828459045235360, -m_flDecayConstant * frametime );
+
+
+ int iNext;
+ for ( int iCur = m_DamageEnts.Head(); iCur != m_DamageEnts.InvalidIndex(); iCur = iNext )
+ {
+ iNext = m_DamageEnts.Next( iCur );
+ CDamageEnt *pEnt = &m_DamageEnts[iCur];
+
+
+ // If the entity was dead and is now alive, stop damage to them so their new body doesn't burn.
+ if ( !pEnt->m_hEnt.Get() || ( !pEnt->m_bWasAlive && pEnt->m_hEnt->IsAlive() ) )
+ {
+ RemoveDamageEnt( iCur );
+ pEnt = NULL;
+ continue;
+ }
+
+ pEnt->m_bWasAlive = pEnt->m_hEnt->IsAlive();
+
+ // Sum up each attacker's velocity.
+ float flTotalVelocity = 0;
+ for ( int i=0; i < pEnt->m_nAttackers; i++ )
+ flTotalVelocity += pEnt->m_Attackers[i].m_flVelocity;
+
+
+ // Figure out each attacker's contribution.
+ float flContributionPercent[CDamageEnt::MAX_ATTACKERS];
+ for ( i=0; i < pEnt->m_nAttackers; i++ )
+ flContributionPercent[i] = pEnt->m_Attackers[i].m_flVelocity / flTotalVelocity;
+
+
+ // Decay each attacker's velocity.
+ flTotalVelocity *= flFrameDecay;
+
+ // Uniformly scale each attacker's velocity down so the sum total doesn't exceed our maximum.
+ float flPercentScale = 1;
+ if ( flTotalVelocity > m_flMaxDamagePerSecond )
+ flPercentScale = m_flMaxDamagePerSecond / flTotalVelocity;
+
+ for ( i=0; i < pEnt->m_nAttackers; i++ )
+ {
+ CDamageAttacker *pAttacker = &pEnt->m_Attackers[i];
+
+ pAttacker->m_flVelocity *= flFrameDecay * flPercentScale;
+
+ bool bEntsValid = (pEnt->m_Attackers[i].m_hAttacker.Get() != NULL);
+ if ( !bEntsValid ||
+ pEnt->m_Attackers[i].m_flVelocity <= 0.001 )
+ {
+ if ( bEntsValid )
+ ApplyCollectedDamage( pEnt, i ); // Apply the last-remaining damage from this guy.
+
+ Q_memmove( &pEnt->m_Attackers[i], &pEnt->m_Attackers[i+1], sizeof( pEnt->m_Attackers[0] ) * (pEnt->m_nAttackers-i-1) );
+ Q_memmove( &flContributionPercent[i], &flContributionPercent[i+1], sizeof( flContributionPercent[0] ) * (pEnt->m_nAttackers-i-1) );
+
+ --pEnt->m_nAttackers;
+ if ( pEnt->m_nAttackers == 0 )
+ {
+ // This ent isn't being damaged anymore.
+ RemoveDamageEnt( iCur );
+ break;
+ }
+
+ --i;
+ }
+
+ // Update their current damage sum and maybe apply the damage.
+ pAttacker->m_flDamageSum += pAttacker->m_flVelocity * frametime;
+ if ( bApplyDamageThisFrame )
+ {
+ ApplyCollectedDamage( pEnt, i );
+ }
+ }
+ }
+}
+
+
+float GetFireDamageScale( CBaseEntity *pEnt )
+{
+ // Objects have a lot more health and we want them to take damage faster.
+ if ( dynamic_cast< CBaseObject* >( pEnt ) )
+ return 4;
+ else
+ return 1;
+}
+
+
+void CFireDamageMgr::ApplyCollectedDamage( CFireDamageMgr::CDamageEnt *pEnt, int iAttacker )
+{
+ CDamageAttacker *pAttacker = &pEnt->m_Attackers[iAttacker];
+
+ CTakeDamageInfo info( NULL, pAttacker->m_hAttacker, pAttacker->m_flDamageSum * GetFireDamageScale( pEnt->m_hEnt ), DMG_BURN );
+ pEnt->m_hEnt->TakeDamage( info );
+
+ pAttacker->m_flDamageSum = 0;
+}
+
+
+// ------------------------------------------------------------------------------------------------ //
+// Global functions.
+// ------------------------------------------------------------------------------------------------ //
+
+bool IsBurnableEnt( CBaseEntity *pEntity, int iIgnoreTeam )
+{
+ if ( pEntity->m_takedamage == DAMAGE_NO )
+ return false;
+
+ CGasolineBlob *pBlob = dynamic_cast< CGasolineBlob* >( pEntity );
+ if ( pBlob )
+ {
+ return !pBlob->IsLit();
+ }
+
+ if ( pEntity->GetTeamNumber() == iIgnoreTeam && !fire_damageall.GetInt() )
+ {
+ // Don't damage anyone on the pyro's team (including the pyro himself).
+ return false;
+ }
+
+ // Now only allow specific types of objects to be damaged.
+ if ( dynamic_cast< CBasePlayer* >( pEntity ) ||
+ dynamic_cast< CAI_BaseNPC* >( pEntity ) ||
+ dynamic_cast< CBaseObject* >( pEntity ) )
+ {
+ return true;
+ }
+
+ return false;
+}
+
+
+int FindBurnableEntsInSphere(
+ CBaseEntity **ents,
+ float *dists,
+ int nMaxEnts,
+ const Vector &vecCenter,
+ float flSearchRadius,
+ CBaseEntity *pOwner )
+{
+ Assert( nMaxEnts > 0 );
+ int nOutEnts = 0;
+
+ CBaseEntity *pEntity;
+ for ( CEntitySphereQuery sphere( vecCenter, flSearchRadius ); ( pEntity = sphere.GetCurrentEntity() ) != NULL; sphere.NextEntity() )
+ {
+ if ( !IsBurnableEnt( pEntity, pOwner->GetTeamNumber() ) )
+ continue;
+
+ // Make sure it's not blocked.
+ trace_t tr;
+ Vector vCenter = pEntity->WorldSpaceCenter();
+
+ UTIL_TraceLine ( vecCenter, vCenter, MASK_SHOT & (~CONTENTS_HITBOX), NULL, COLLISION_GROUP_NONE, &tr );
+ if ( tr.fraction != 1.0 && tr.m_pEnt != pEntity )
+ continue;
+
+ if ( TFGameRules()->IsTraceBlockedByWorldOrShield( vecCenter, vCenter, pOwner, DMG_BURN | DMG_PROBE, &tr ) )
+ continue;
+
+ // Make sure it's in range.
+ const Vector &mins = pEntity->WorldAlignMins();
+ const Vector &maxs = pEntity->WorldAlignMaxs();
+ float approxTargetRadius = ( Vector( maxs.x, maxs.y, 0 ) - Vector( mins.x, mins.y, 0 )).Length() * 0.5f;
+
+ float flDistFromCenter = ( vecCenter - tr.endpos ).Length() - approxTargetRadius;
+
+ ents[nOutEnts] = pEntity;
+ dists[nOutEnts] = flDistFromCenter;
+ nOutEnts++;
+ if ( nOutEnts >= nMaxEnts )
+ return nOutEnts;
+ }
+
+ return nOutEnts;
+}
+
+
+CFireDamageMgr g_FireDamageMgr;
+
+CFireDamageMgr* GetFireDamageMgr()
+{
+ return &g_FireDamageMgr;
+}
+
diff --git a/game/server/tf2/fire_damage_mgr.h b/game/server/tf2/fire_damage_mgr.h
new file mode 100644
index 0000000..0f0df10
--- /dev/null
+++ b/game/server/tf2/fire_damage_mgr.h
@@ -0,0 +1,121 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef FIRE_DAMAGE_MGR_H
+#define FIRE_DAMAGE_MGR_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+
+#include "igamesystem.h"
+#include "utllinkedlist.h"
+#include "ehandle.h"
+
+
+class CEntityBurnEffect;
+
+
+// ------------------------------------------------------------------------------------------ //
+// CFireDamageMgr.
+//
+// This class manages fire damage being applied to entities. It uses velocity, acceleration,
+// and decay to model fire damage building up, and it puts a cap on the maximum amount of damage
+// an entity can take from fire during a given frame.
+// ------------------------------------------------------------------------------------------ //
+
+class CFireDamageMgr : public CAutoGameSystem
+{
+// Overrides.
+public:
+
+ virtual bool Init();
+ virtual void FrameUpdatePostEntityThink();
+
+
+public:
+ // Apply fire damage to an entity. flDamageAccel is in units per second, so in the absence of
+ // decay, it equals velocity increase per second.
+ //
+ // NOTE: the damage acceleration should always be greater than the decay per second, or no damage
+ // will be applied since it will decay faster than
+ void AddDamage( CBaseEntity *pTarget, CBaseEntity *pAttacker, float flDamageAccel, bool bMakeBurnEffect );
+
+
+private:
+ class CDamageAttacker
+ {
+ public:
+ void Init( CBaseEntity *pAttacker, float flVelocity )
+ {
+ m_hAttacker = pAttacker;
+ m_flVelocity = flVelocity;
+ m_flDamageSum = 0;
+ }
+
+ EHANDLE m_hAttacker;
+ float m_flVelocity; // Current damage velocity.
+
+ float m_flDamageSum; // Damage is summed up and applied a couple times per second instead of
+ // each frame since fractional damage is rounded to 1.
+ };
+
+ class CDamageEnt
+ {
+ public:
+ enum
+ {
+ MAX_ATTACKERS = 4
+ };
+
+ bool m_bWasAlive;
+
+ EHANDLE m_hEnt;
+ CHandle<CEntityBurnEffect> m_pBurnEffect;
+
+ // Each attacker gets credit for a portion of
+ CDamageAttacker m_Attackers[MAX_ATTACKERS];
+ int m_nAttackers;
+ };
+
+
+private:
+
+ void ApplyCollectedDamage( CFireDamageMgr::CDamageEnt *pEnt, int iAttacker );
+ void RemoveDamageEnt( int iEnt );
+
+
+private:
+
+ CUtlLinkedList<CDamageEnt,int> m_DamageEnts;
+
+ float m_flMaxDamagePerSecond;
+ float m_flDecayConstant;
+
+ // This counts down to zero so we only apply fire damage every so often.
+ float m_flApplyDamageCountdown;
+};
+
+
+// Returns true if the entity is burnable by the specified team.
+bool IsBurnableEnt( CBaseEntity *pEntity, int iTeam );
+
+
+// This is used by the flamethrower and the burning gasoline blobs to find entities to burn.
+int FindBurnableEntsInSphere(
+ CBaseEntity **ents,
+ float *dists,
+ int nMaxEnts,
+ const Vector &vecCenter,
+ float flSearchRadius,
+ CBaseEntity *pOwner );
+
+
+CFireDamageMgr* GetFireDamageMgr();
+
+
+#endif // FIRE_DAMAGE_MGR_H
diff --git a/game/server/tf2/gasoline_blob.cpp b/game/server/tf2/gasoline_blob.cpp
new file mode 100644
index 0000000..e9c4a54
--- /dev/null
+++ b/game/server/tf2/gasoline_blob.cpp
@@ -0,0 +1,279 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#include "cbase.h"
+#include "gasoline_blob.h"
+#include "gasoline_shared.h"
+#include "utllinkedlist.h"
+#include "fire_damage_mgr.h"
+#include "tf_gamerules.h"
+
+
+// Flamethrower blobs wait a bit before they cause damage so they don't hurt the guy
+// shooting them.
+#define BLOB_DAMAGE_WAIT_TIME 1.0
+
+
+// At what heat level does an unlit blob ignite?
+#define IGNITION_HEAT 0.1
+
+
+#define FIRE_DAMAGE_SEARCH_DISTANCE 200 // It searches within this sphere for entities to damage.
+#define FIRE_DAMAGE_DISTANCE 90 // This is how far fire can damage an entity from.
+
+
+ConVar fire_enable( "fire_enable", "1", 0, "Enable or disable fire." );
+
+
+// ------------------------------------------------------------------------------------------ //
+// CGasolineBlob implementation.
+// ------------------------------------------------------------------------------------------ //
+
+IMPLEMENT_SERVERCLASS_ST_NOBASE( CGasolineBlob, DT_GasolineBlob )
+ SendPropVector( SENDINFO(m_vecOrigin), -1, SPROP_COORD ),
+ SendPropEHandle (SENDINFO_NAME(m_hMoveParent, moveparent)),
+ SendPropFloat( SENDINFO(m_flLitStartTime), -1, SPROP_NOSCALE ),
+
+ SendPropFloat( SENDINFO(m_flCreateTime), -1, SPROP_NOSCALE ),
+ SendPropFloat( SENDINFO(m_flMaxLifetime), -1, SPROP_NOSCALE ),
+
+ SendPropInt( SENDINFO(m_iTeamNum), TEAMNUM_NUM_BITS, 0 ),
+ SendPropInt( SENDINFO( m_BlobFlags ), NUM_BLOB_FLAGS, SPROP_UNSIGNED ),
+ SendPropVector( SENDINFO( m_vSurfaceNormal ), 0, SPROP_NORMAL ),
+END_SEND_TABLE()
+
+
+LINK_ENTITY_TO_CLASS( gasoline_blob, CGasolineBlob );
+
+
+CUtlLinkedList<CGasolineBlob*, int> g_GasolineBlobs;
+
+
+CGasolineBlob* CGasolineBlob::Create(
+ CBaseEntity *pOwner,
+ const Vector &vOrigin,
+ const Vector &vStartVelocity,
+ bool bUseGravity,
+ float flAirLifetime,
+ float flLifetime )
+{
+ CGasolineBlob *pBlob = (CGasolineBlob*)CreateEntityByName( "gasoline_blob" );
+ if ( !pBlob )
+ return NULL;
+
+ // The "constructor".
+ pBlob->SetLocalOrigin( vOrigin );
+ pBlob->SetAbsVelocity( vStartVelocity );
+ pBlob->SetThink( &CGasolineBlob::Think );
+ pBlob->SetNextThink( gpGlobals->curtime );
+ pBlob->SetCollisionBounds( Vector( -GASOLINE_BLOB_RADIUS, -GASOLINE_BLOB_RADIUS, -GASOLINE_BLOB_RADIUS ), Vector( GASOLINE_BLOB_RADIUS, GASOLINE_BLOB_RADIUS, GASOLINE_BLOB_RADIUS ) );
+ pBlob->SetMoveType( MOVETYPE_NONE );
+ pBlob->SetSolid( SOLID_BBOX );
+ pBlob->AddSolidFlags( FSOLID_NOT_SOLID );
+ pBlob->AddEFlags( EFL_FORCE_CHECK_TRANSMIT );
+ pBlob->m_BlobFlags = 0;
+ pBlob->m_HeatLevel = 0;
+ pBlob->m_hOwner = pOwner;
+ pBlob->m_flCreateTime = gpGlobals->curtime;
+ pBlob->m_flMaxLifetime = flLifetime;
+ pBlob->m_takedamage = DAMAGE_YES;
+ pBlob->m_flLitStartTime = 0;
+ pBlob->ChangeTeam( pOwner->GetTeamNumber() );
+
+ if ( bUseGravity )
+ pBlob->m_BlobFlags |= BLOBFLAG_USE_GRAVITY;
+
+ pBlob->m_flAirLifetime = flAirLifetime;
+ pBlob->m_flTimeInAir = 0;
+
+ pBlob->SetNextThink( gpGlobals->curtime );
+ g_GasolineBlobs.AddToTail( pBlob );
+
+ return pBlob;
+}
+
+
+CGasolineBlob::~CGasolineBlob()
+{
+ g_GasolineBlobs.Remove( g_GasolineBlobs.Find( this ) );
+}
+
+
+void CGasolineBlob::AddAutoBurnBlob( CGasolineBlob *pBlob )
+{
+ int index = m_AutoBurnBlobs.AddToTail();
+ m_AutoBurnBlobs[index] = pBlob;
+}
+
+
+int CGasolineBlob::OnTakeDamage( const CTakeDamageInfo &info )
+{
+ m_HeatLevel += info.GetDamage();
+ if ( m_HeatLevel >= IGNITION_HEAT )
+ SetLit( true );
+
+ return 0;
+}
+
+
+void CGasolineBlob::SetLit( bool bLit )
+{
+ if ( bLit != IsLit() )
+ {
+ if ( bLit )
+ {
+ m_BlobFlags |= BLOBFLAG_LIT;
+ m_flLitStartTime = gpGlobals->curtime;
+ }
+ else
+ {
+ m_BlobFlags &= ~BLOBFLAG_LIT;
+ }
+ }
+}
+
+
+bool CGasolineBlob::IsLit() const
+{
+ return (m_BlobFlags & BLOBFLAG_LIT) != 0;
+}
+
+
+bool CGasolineBlob::IsStopped() const
+{
+ return (m_BlobFlags & BLOBFLAG_STOPPED) != 0;
+}
+
+
+void CGasolineBlob::AutoBurn_R( CGasolineBlob *pParent )
+{
+ SetLit( true );
+
+ for ( int i=0; i < m_AutoBurnBlobs.Count(); i++ )
+ {
+ CGasolineBlob *pTestBlob = m_AutoBurnBlobs[i];
+
+ if ( pTestBlob )
+ {
+ if ( pTestBlob != pParent )
+ pTestBlob->AutoBurn_R( this );
+ }
+ else
+ {
+ m_AutoBurnBlobs.Remove( i );
+ --i;
+ }
+ }
+}
+
+
+void CGasolineBlob::Think()
+{
+ if ( !fire_enable.GetInt() )
+ {
+ UTIL_Remove( this );
+ return;
+ }
+
+ // Decay quickly while in the air.
+ if ( !IsStopped() )
+ {
+ m_flTimeInAir += gpGlobals->frametime;
+ if ( m_flTimeInAir >= m_flAirLifetime )
+ {
+ UTIL_Remove( this );
+ return;
+ }
+ }
+
+ float flLifetime = gpGlobals->curtime - m_flCreateTime;
+ if ( flLifetime >= m_flMaxLifetime )
+ {
+ UTIL_Remove( this );
+ return;
+ }
+
+ if ( IsLit() )
+ {
+ // Have we burnt out?
+ float litPercent = 1 - (flLifetime / m_flMaxLifetime);
+ if ( litPercent <= 0 )
+ {
+ UTIL_Remove( this );
+ return;
+ }
+
+ // Look for nearby entities to burn.
+ CBaseEntity *ents[512];
+ float dists[512];
+ int nEnts = FindBurnableEntsInSphere( ents, dists, ARRAYSIZE( ents ), GetAbsOrigin(), FIRE_DAMAGE_SEARCH_DISTANCE, m_hOwner );
+
+ for ( int i=0; i < nEnts; i++ )
+ {
+ float flDistFromBorder = MAX( 0, FIRE_DAMAGE_DISTANCE - dists[i] );
+ if ( flDistFromBorder <= 0 )
+ continue;
+
+ float flDamage = litPercent * flDistFromBorder / FIRE_DAMAGE_DISTANCE * FIRE_DAMAGE_PER_SEC;
+ GetFireDamageMgr()->AddDamage( ents[i], m_hOwner, flDamage, !IsGasolineBlob( ents[i] ) );
+ }
+
+ // Ignite our "auto burn" blobs.
+ AutoBurn_R( NULL );
+ }
+
+ // Figure out where we want to go.
+ if ( !IsStopped() )
+ {
+ // Apply gravity.
+ Vector vecNewVelocity = GetAbsVelocity();
+ if ( m_BlobFlags & BLOBFLAG_USE_GRAVITY )
+ {
+ vecNewVelocity.z -= 800 * gpGlobals->frametime;
+ SetAbsVelocity( vecNewVelocity );
+ }
+
+ Vector vNewPos = GetAbsOrigin() + vecNewVelocity * gpGlobals->frametime;
+
+ // Can we go there?
+ trace_t trace;
+ UTIL_TraceLine( GetAbsOrigin(), vNewPos, CONTENTS_SOLID, NULL, COLLISION_GROUP_NONE, &trace );
+ bool bStopped = (trace.fraction != 1);
+
+ if ( !bStopped )
+ {
+ // Trace against shields.
+ if ( TFGameRules()->IsTraceBlockedByWorldOrShield( GetAbsOrigin(), vNewPos, m_hOwner, DMG_BURN, &trace ) )
+ {
+ // Blobs just fizzle out when they hit a shield.
+ UTIL_Remove( this );
+ }
+ }
+
+ if( bStopped )
+ {
+ SetLocalOrigin( trace.endpos + trace.plane.normal * 2 );
+
+ // Ok, we hit something. Stop moving.
+ m_BlobFlags |= BLOBFLAG_STOPPED;
+ m_vSurfaceNormal = trace.plane.normal;
+ }
+ else
+ {
+ SetLocalOrigin( vNewPos );
+ }
+ }
+
+ SetNextThink( gpGlobals->curtime );
+}
+
+
+bool IsGasolineBlob( CBaseEntity *pEnt )
+{
+ return FClassnameIs( pEnt, "gasoline_blob" );
+}
+
diff --git a/game/server/tf2/gasoline_blob.h b/game/server/tf2/gasoline_blob.h
new file mode 100644
index 0000000..5c3b303
--- /dev/null
+++ b/game/server/tf2/gasoline_blob.h
@@ -0,0 +1,93 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef GASOLINE_BLOB_H
+#define GASOLINE_BLOB_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+
+#include "baseentity.h"
+
+
+class CGasolineBlob : public CBaseEntity
+{
+public:
+ DECLARE_CLASS( CGasolineBlob, CBaseEntity );
+ DECLARE_SERVERCLASS();
+
+
+ // Create a gasoline blob.
+ // flAirLifetime specifies how long it takes to fizzle out in the air.
+ static CGasolineBlob* Create(
+ CBaseEntity *pOwner,
+ const Vector &vOrigin,
+ const Vector &vStartVelocity,
+ bool bUseGravity,
+ float flAirLifetime,
+ float flLifetime );
+
+ virtual ~CGasolineBlob();
+
+ // A lit blob will always apply at least 25% damage to its "auto burn" blob.
+ //
+ // This is used when laying down gasoline blobs in a line. Since it's fairly easy to accidentally
+ // lay down blobs that won't damage each other because they're too far away, the line of blobs
+ // can be linked together using this.
+ void AddAutoBurnBlob( CGasolineBlob *pBlob );
+
+
+// Overrides.
+public:
+
+ virtual int OnTakeDamage( const CTakeDamageInfo &info );
+
+
+
+// Implementation.
+public:
+
+ bool IsLit() const;
+ bool IsStopped() const;
+ void SetLit( bool bLit );
+
+ void Think();
+
+ void AutoBurn_R( CGasolineBlob *pParent );
+
+
+private:
+
+ typedef CHandle<CGasolineBlob> CGasolineBlobHandle;
+ CUtlVector<CGasolineBlobHandle> m_AutoBurnBlobs;
+
+ float m_flTimeInAir; // How long we've been in the air.
+ float m_flAirLifetime; // How long we're allowed to exist in the air.
+
+ CNetworkVar( float, m_flLitStartTime ); // What time did the blob become lit at?
+
+ CNetworkVar( int, m_BlobFlags ); // Combination of BLOBFLAG_ defines.
+
+ // This is set at the start and is used to know the percentage of lifetime left.
+ CNetworkVar( float, m_flMaxLifetime );
+
+ // When the blob was created.
+ CNetworkVar( float, m_flCreateTime );
+
+ CNetworkVector( m_vSurfaceNormal ); // This is sent to the client so it can spread the fire out.
+
+ EHANDLE m_hOwner;
+ float m_HeatLevel; // This rises when other flames are nearby until we ignite.
+};
+
+
+// Returns true if the entity is a gasoline blob.
+bool IsGasolineBlob( CBaseEntity *pEnt );
+
+
+#endif // GASOLINE_BLOB_H
diff --git a/game/server/tf2/info_act.cpp b/game/server/tf2/info_act.cpp
new file mode 100644
index 0000000..2f18cc5
--- /dev/null
+++ b/game/server/tf2/info_act.cpp
@@ -0,0 +1,587 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+#include "cbase.h"
+#include "EntityOutput.h"
+#include "EntityList.h"
+#include "tf_team.h"
+#include "tier1/strtools.h"
+#include "baseentity.h"
+#include "tf_shareddefs.h"
+#include "info_act.h"
+
+// Global pointer to the current act
+CHandle<CInfoAct> g_hCurrentAct;
+
+BEGIN_DATADESC( CInfoAct )
+
+ // inputs
+ DEFINE_INPUTFUNC( FIELD_VOID, "Start", InputStart ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "FinishWinNone", InputFinishWinNone ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "FinishWin1", InputFinishWin1 ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "FinishWin2", InputFinishWin2 ),
+ DEFINE_INPUTFUNC( FIELD_FLOAT, "AddTime", InputAddTime ),
+
+ // outputs
+ DEFINE_OUTPUT( m_OnStarted, "OnStarted" ),
+ DEFINE_OUTPUT( m_OnFinishedTeamNone, "OnFinishedWinNone" ),
+ DEFINE_OUTPUT( m_OnFinishedTeam1, "OnFinishedWin1" ),
+ DEFINE_OUTPUT( m_OnFinishedTeam2, "OnFinishedWin2" ),
+ DEFINE_OUTPUT( m_OnTimerExpired, "OnTimerExpired" ),
+
+ DEFINE_OUTPUT( m_Respawn1Team1Events[CInfoAct::RESPAWN_TIMER_90_REMAINING], "OnRespawn1Team1_90sec" ),
+ DEFINE_OUTPUT( m_Respawn1Team1Events[CInfoAct::RESPAWN_TIMER_60_REMAINING], "OnRespawn1Team1_60sec" ),
+ DEFINE_OUTPUT( m_Respawn1Team1Events[CInfoAct::RESPAWN_TIMER_45_REMAINING], "OnRespawn1Team1_45sec" ),
+ DEFINE_OUTPUT( m_Respawn1Team1Events[CInfoAct::RESPAWN_TIMER_30_REMAINING], "OnRespawn1Team1_30sec" ),
+ DEFINE_OUTPUT( m_Respawn1Team1Events[CInfoAct::RESPAWN_TIMER_10_REMAINING], "OnRespawn1Team1_10sec" ),
+ DEFINE_OUTPUT( m_Respawn1Team1Events[CInfoAct::RESPAWN_TIMER_0_REMAINING], "OnRespawn1Team1" ),
+ DEFINE_OUTPUT( m_Respawn1Team1TimeRemaining, "Respawn1Team1TimeRemaining" ),
+
+ DEFINE_OUTPUT( m_Respawn2Team1Events[CInfoAct::RESPAWN_TIMER_90_REMAINING], "OnRespawn2Team1_90sec" ),
+ DEFINE_OUTPUT( m_Respawn2Team1Events[CInfoAct::RESPAWN_TIMER_60_REMAINING], "OnRespawn2Team1_60sec" ),
+ DEFINE_OUTPUT( m_Respawn2Team1Events[CInfoAct::RESPAWN_TIMER_45_REMAINING], "OnRespawn2Team1_45sec" ),
+ DEFINE_OUTPUT( m_Respawn2Team1Events[CInfoAct::RESPAWN_TIMER_30_REMAINING], "OnRespawn2Team1_30sec" ),
+ DEFINE_OUTPUT( m_Respawn2Team1Events[CInfoAct::RESPAWN_TIMER_10_REMAINING], "OnRespawn2Team1_10sec" ),
+ DEFINE_OUTPUT( m_Respawn2Team1Events[CInfoAct::RESPAWN_TIMER_0_REMAINING], "OnRespawn2Team1" ),
+ DEFINE_OUTPUT( m_Respawn2Team1TimeRemaining, "Respawn2Team1TimeRemaining" ),
+
+ DEFINE_OUTPUT( m_Respawn1Team2Events[CInfoAct::RESPAWN_TIMER_90_REMAINING], "OnRespawn1Team2_90sec" ),
+ DEFINE_OUTPUT( m_Respawn1Team2Events[CInfoAct::RESPAWN_TIMER_60_REMAINING], "OnRespawn1Team2_60sec" ),
+ DEFINE_OUTPUT( m_Respawn1Team2Events[CInfoAct::RESPAWN_TIMER_45_REMAINING], "OnRespawn1Team2_45sec" ),
+ DEFINE_OUTPUT( m_Respawn1Team2Events[CInfoAct::RESPAWN_TIMER_30_REMAINING], "OnRespawn1Team2_30sec" ),
+ DEFINE_OUTPUT( m_Respawn1Team2Events[CInfoAct::RESPAWN_TIMER_10_REMAINING], "OnRespawn1Team2_10sec" ),
+ DEFINE_OUTPUT( m_Respawn1Team2Events[CInfoAct::RESPAWN_TIMER_0_REMAINING], "OnRespawn1Team2" ),
+ DEFINE_OUTPUT( m_Respawn1Team2TimeRemaining, "Respawn1Team2TimeRemaining" ),
+
+ DEFINE_OUTPUT( m_Respawn2Team2Events[CInfoAct::RESPAWN_TIMER_90_REMAINING], "OnRespawn2Team2_90sec" ),
+ DEFINE_OUTPUT( m_Respawn2Team2Events[CInfoAct::RESPAWN_TIMER_60_REMAINING], "OnRespawn2Team2_60sec" ),
+ DEFINE_OUTPUT( m_Respawn2Team2Events[CInfoAct::RESPAWN_TIMER_45_REMAINING], "OnRespawn2Team2_45sec" ),
+ DEFINE_OUTPUT( m_Respawn2Team2Events[CInfoAct::RESPAWN_TIMER_30_REMAINING], "OnRespawn2Team2_30sec" ),
+ DEFINE_OUTPUT( m_Respawn2Team2Events[CInfoAct::RESPAWN_TIMER_10_REMAINING], "OnRespawn2Team2_10sec" ),
+ DEFINE_OUTPUT( m_Respawn2Team2Events[CInfoAct::RESPAWN_TIMER_0_REMAINING], "OnRespawn2Team2" ),
+ DEFINE_OUTPUT( m_Respawn2Team2TimeRemaining, "Respawn2Team2TimeRemaining" ),
+
+ DEFINE_OUTPUT( m_Team1RespawnDelayDone, "OnTeam1RespawnDelayDone" ),
+ DEFINE_OUTPUT( m_Team2RespawnDelayDone, "OnTeam2RespawnDelayDone" ),
+
+ // keys
+ DEFINE_KEYFIELD_NOT_SAVED( m_iActNumber, FIELD_INTEGER, "ActNumber" ),
+ DEFINE_KEYFIELD_NOT_SAVED( m_flActTimeLimit, FIELD_FLOAT, "ActTimeLimit" ),
+ DEFINE_KEYFIELD_NOT_SAVED( m_iszIntermissionCamera, FIELD_STRING, "IntermissionCamera" ),
+ DEFINE_KEYFIELD_NOT_SAVED( m_nRespawn1Team1Time, FIELD_INTEGER, "Respawn1Team1Time" ),
+ DEFINE_KEYFIELD_NOT_SAVED( m_nRespawn2Team1Time, FIELD_INTEGER, "Respawn2Team1Time" ),
+ DEFINE_KEYFIELD_NOT_SAVED( m_nRespawn1Team2Time, FIELD_INTEGER, "Respawn1Team2Time" ),
+ DEFINE_KEYFIELD_NOT_SAVED( m_nRespawn2Team2Time, FIELD_INTEGER, "Respawn2Team2Time" ),
+ DEFINE_KEYFIELD_NOT_SAVED( m_nRespawnTeam1Delay, FIELD_INTEGER, "RespawnTeam1InitialDelay" ),
+ DEFINE_KEYFIELD_NOT_SAVED( m_nRespawnTeam2Delay, FIELD_INTEGER, "RespawnTeam2InitialDelay" ),
+
+ // functions
+ DEFINE_FUNCTION( ActThink ),
+ DEFINE_FUNCTION( ActThinkEndActOverlayTime ),
+ DEFINE_FUNCTION( RespawnTimerThink ),
+ DEFINE_FUNCTION( Team1RespawnDelayThink ),
+ DEFINE_FUNCTION( Team2RespawnDelayThink ),
+
+END_DATADESC()
+
+IMPLEMENT_SERVERCLASS_ST(CInfoAct, DT_InfoAct)
+ SendPropInt(SENDINFO(m_iActNumber), 5 ),
+ SendPropInt(SENDINFO(m_spawnflags), SF_ACT_BITS, SPROP_UNSIGNED ),
+ SendPropFloat(SENDINFO(m_flActTimeLimit), 12 ),
+ SendPropInt(SENDINFO(m_nRespawn1Team1Time), 8 ),
+ SendPropInt(SENDINFO(m_nRespawn2Team1Time), 8 ),
+ SendPropInt(SENDINFO(m_nRespawn1Team2Time), 8 ),
+ SendPropInt(SENDINFO(m_nRespawn2Team2Time), 8 ),
+ SendPropInt(SENDINFO(m_nRespawnTeam1Delay), 8 ),
+ SendPropInt(SENDINFO(m_nRespawnTeam2Delay), 8 ),
+END_SEND_TABLE();
+
+LINK_ENTITY_TO_CLASS( info_act, CInfoAct );
+
+
+#define RESPAWN_TIMER_CONTEXT "RespawnTimerThink"
+#define RESPAWN_TEAM_1_DELAY_CONTEXT "RespawnTeam1DelayThink"
+#define RESPAWN_TEAM_2_DELAY_CONTEXT "RespawnTeam2DelayThink"
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CInfoAct::CInfoAct( void )
+{
+ // No act == -1
+ m_iActNumber = -1;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+int CInfoAct::UpdateTransmitState()
+{
+ return SetTransmitState( FL_EDICT_ALWAYS );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CInfoAct::Spawn( void )
+{
+ m_flActStartedAt = 0;
+ m_iWinners = 0;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Set up respawn timers
+//-----------------------------------------------------------------------------
+void CInfoAct::SetUpRespawnTimers()
+{
+ // NOTE: Need to add the second there so the respawn timers don't immediately trigger
+ SetContextThink( RespawnTimerThink, gpGlobals->curtime + 1.0f, RESPAWN_TIMER_CONTEXT );
+
+ if (m_nRespawnTeam1Delay != 0)
+ {
+ SetContextThink( Team1RespawnDelayThink, gpGlobals->curtime + m_nRespawnTeam1Delay, RESPAWN_TEAM_1_DELAY_CONTEXT );
+ }
+ else
+ {
+ m_Team1RespawnDelayDone.FireOutput( this, this );
+ }
+
+ if (m_nRespawnTeam2Delay != 0)
+ {
+ SetContextThink( Team2RespawnDelayThink, gpGlobals->curtime + m_nRespawnTeam2Delay, RESPAWN_TEAM_2_DELAY_CONTEXT );
+ }
+ else
+ {
+ m_Team2RespawnDelayDone.FireOutput( this, this );
+ }
+}
+
+void CInfoAct::ShutdownRespawnTimers()
+{
+ SetContextThink( NULL, 0, RESPAWN_TIMER_CONTEXT );
+ SetContextThink( NULL, 0, RESPAWN_TEAM_1_DELAY_CONTEXT );
+ SetContextThink( NULL, 0, RESPAWN_TEAM_2_DELAY_CONTEXT );
+}
+
+
+//-----------------------------------------------------------------------------
+// Respawn delay
+//-----------------------------------------------------------------------------
+void CInfoAct::Team1RespawnDelayThink()
+{
+ m_Team1RespawnDelayDone.FireOutput( this, this );
+ SetContextThink( NULL, 0, RESPAWN_TEAM_1_DELAY_CONTEXT );
+}
+
+void CInfoAct::Team2RespawnDelayThink()
+{
+ m_Team2RespawnDelayDone.FireOutput( this, this );
+ SetContextThink( NULL, 0, RESPAWN_TEAM_2_DELAY_CONTEXT );
+}
+
+
+//-----------------------------------------------------------------------------
+// Computes the time remaining
+//-----------------------------------------------------------------------------
+int CInfoAct::ComputeTimeRemaining( int nPeriod, int nDelay )
+{
+ if (nPeriod <= 0)
+ return -1;
+
+ int nTimeDelta = (int)(gpGlobals->curtime - m_flActStartedAt);
+ Assert( nTimeDelta >= 0 );
+ nTimeDelta -= nDelay;
+
+ // This case takes care of the initial spawn delay time...
+ if (nTimeDelta <= 0)
+ {
+ return nPeriod - nTimeDelta;
+ }
+
+ int nFactor = nTimeDelta / nPeriod;
+ int nTimeRemainder = nTimeDelta - nFactor * nPeriod;
+ if (nTimeRemainder == 0)
+ return 0;
+
+ return nPeriod - nTimeRemainder;
+}
+
+
+//-----------------------------------------------------------------------------
+// Fires respawn events
+//-----------------------------------------------------------------------------
+void CInfoAct::FireRespawnEvents( int nTimeRemaining, COutputEvent *pRespawnEvents, COutputInt &respawnTime )
+{
+ if (nTimeRemaining < 0)
+ return;
+
+ switch (nTimeRemaining)
+ {
+ case 90:
+ pRespawnEvents[RESPAWN_TIMER_90_REMAINING].FireOutput( this, this );
+ break;
+ case 60:
+ pRespawnEvents[RESPAWN_TIMER_60_REMAINING].FireOutput( this, this );
+ break;
+ case 45:
+ pRespawnEvents[RESPAWN_TIMER_45_REMAINING].FireOutput( this, this );
+ break;
+ case 30:
+ pRespawnEvents[RESPAWN_TIMER_30_REMAINING].FireOutput( this, this );
+ break;
+ case 10:
+ pRespawnEvents[RESPAWN_TIMER_10_REMAINING].FireOutput( this, this );
+ break;
+ case 0:
+ pRespawnEvents[RESPAWN_TIMER_0_REMAINING].FireOutput( this, this );
+ break;
+ default:
+ break;
+ }
+
+ respawnTime.Set( nTimeRemaining, this, this );
+}
+
+
+//-----------------------------------------------------------------------------
+// Respawn timers
+//-----------------------------------------------------------------------------
+void CInfoAct::RespawnTimerThink()
+{
+ int nTimeRemaining = ComputeTimeRemaining( m_nRespawn1Team1Time, m_nRespawnTeam1Delay );
+ FireRespawnEvents( nTimeRemaining, m_Respawn1Team1Events, m_Respawn1Team1TimeRemaining );
+
+ nTimeRemaining = ComputeTimeRemaining( m_nRespawn2Team1Time, m_nRespawnTeam1Delay );
+ FireRespawnEvents( nTimeRemaining, m_Respawn2Team1Events, m_Respawn2Team1TimeRemaining );
+
+ nTimeRemaining = ComputeTimeRemaining( m_nRespawn1Team2Time, m_nRespawnTeam2Delay );
+ FireRespawnEvents( nTimeRemaining, m_Respawn1Team2Events, m_Respawn1Team2TimeRemaining );
+
+ nTimeRemaining = ComputeTimeRemaining( m_nRespawn2Team2Time, m_nRespawnTeam2Delay );
+ FireRespawnEvents( nTimeRemaining, m_Respawn2Team2Events, m_Respawn2Team2TimeRemaining );
+
+ SetNextThink( gpGlobals->curtime + 1.0f, RESPAWN_TIMER_CONTEXT );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: The act has started
+//-----------------------------------------------------------------------------
+void CInfoAct::StartAct( void )
+{
+ // FIXME: Should this change?
+ // Don't allow two simultaneous acts
+ if (g_hCurrentAct)
+ {
+ g_hCurrentAct->FinishAct( );
+ }
+
+ // Set the global act to this
+ g_hCurrentAct = this;
+
+ m_flActStartedAt = gpGlobals->curtime;
+ m_OnStarted.FireOutput( this, this );
+
+ // Do we have a timelimit?
+ if ( m_flActTimeLimit )
+ {
+ SetNextThink( gpGlobals->curtime + m_flActTimeLimit );
+ SetThink( ActThink );
+ }
+
+ SetUpRespawnTimers();
+
+ // Tell all the clients
+ CReliableBroadcastRecipientFilter filter;
+
+ UserMessageBegin( filter, "ActBegin" );
+ WRITE_BYTE( (byte)m_iActNumber );
+ WRITE_FLOAT( m_flActStartedAt );
+ MessageEnd();
+
+ // If we're not an intermission, clean up
+ if ( !HasSpawnFlags( SF_ACT_INTERMISSION ) )
+ {
+ CleanupOnActStart();
+ }
+
+ // Cycle through all players and start the act
+ for ( int i = 1; i <= gpGlobals->maxClients; i++ )
+ {
+ CBaseTFPlayer *pPlayer = (CBaseTFPlayer*)UTIL_PlayerByIndex( i );
+ if ( pPlayer )
+ {
+ // Am I an intermission?
+ if ( HasSpawnFlags( SF_ACT_INTERMISSION ) )
+ {
+ StartIntermission( pPlayer );
+ }
+ else
+ {
+ StartActOverlayTime( pPlayer );
+ }
+ }
+ }
+
+ // Think again soon, to remove player locks
+ if ( !HasSpawnFlags(SF_ACT_INTERMISSION) )
+ {
+ SetNextThink( gpGlobals->curtime + MIN_ACT_OVERLAY_TIME );
+ SetThink( ActThinkEndActOverlayTime );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Update a client who joined during the middle of an act
+//-----------------------------------------------------------------------------
+void CInfoAct::UpdateClient( CBaseTFPlayer *pPlayer )
+{
+ CSingleUserRecipientFilter user( pPlayer );
+ user.MakeReliable();
+
+ UserMessageBegin( user, "ActBegin" );
+ WRITE_BYTE( (byte)m_iActNumber );
+ WRITE_FLOAT( m_flActStartedAt );
+ MessageEnd();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: The act has finished
+//-----------------------------------------------------------------------------
+void CInfoAct::FinishAct( )
+{
+ if ( g_hCurrentAct.Get() != this )
+ {
+ DevWarning( 2, "Attempted to finish an act which wasn't started!\n" );
+ return;
+ }
+
+ ShutdownRespawnTimers();
+
+ switch( m_iWinners)
+ {
+ case 0:
+ m_OnFinishedTeamNone.FireOutput( this, this );
+ break;
+
+ case 1:
+ m_OnFinishedTeam1.FireOutput( this, this );
+ break;
+
+ case 2:
+ m_OnFinishedTeam2.FireOutput( this, this );
+ break;
+
+ default:
+ Assert(0);
+ break;
+ }
+
+ g_hCurrentAct = NULL;
+
+ // Tell all the clients
+ CReliableBroadcastRecipientFilter filter;
+ UserMessageBegin( filter, "ActEnd" );
+ WRITE_BYTE( m_iWinners );
+ MessageEnd();
+
+ // Am I an intermission?
+ if ( HasSpawnFlags( SF_ACT_INTERMISSION ) )
+ {
+ // Cycle through all players and end the intermission for them
+ for ( int i = 1; i <= gpGlobals->maxClients; i++ )
+ {
+ CBaseTFPlayer *pPlayer = (CBaseTFPlayer*)UTIL_PlayerByIndex( i );
+ if ( pPlayer )
+ {
+ EndIntermission( pPlayer );
+ }
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CInfoAct::ActThink( void )
+{
+ m_OnTimerExpired.FireOutput( this,this );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Force the players not to move to give them time to read the act overlays
+//-----------------------------------------------------------------------------
+void CInfoAct::StartActOverlayTime( CBaseTFPlayer *pPlayer )
+{
+ // Lock the player in place
+ pPlayer->CleanupOnActStart();
+ pPlayer->LockPlayerInPlace();
+
+ if ( pPlayer->GetActiveWeapon() )
+ {
+ pPlayer->GetActiveWeapon()->Holster();
+ }
+
+ pPlayer->m_Local.m_iHideHUD |= (HIDEHUD_WEAPONSELECTION | HIDEHUD_HEALTH);
+ pPlayer->GetLocalData()->m_bForceMapOverview = true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Release the players after overlay time has finished
+//-----------------------------------------------------------------------------
+void CInfoAct::EndActOverlayTime( CBaseTFPlayer *pPlayer )
+{
+ // Release the player
+ pPlayer->UnlockPlayer();
+
+ if ( pPlayer->GetActiveWeapon() )
+ {
+ pPlayer->GetActiveWeapon()->Deploy();
+ }
+
+ pPlayer->m_Local.m_iHideHUD &= ~(HIDEHUD_WEAPONSELECTION | HIDEHUD_HEALTH);
+ pPlayer->GetLocalData()->m_bForceMapOverview = false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Unlock the players after an act has started
+//-----------------------------------------------------------------------------
+void CInfoAct::ActThinkEndActOverlayTime( void )
+{
+ // Cycle through all players and end the intermission for them
+ for ( int i = 1; i <= gpGlobals->maxClients; i++ )
+ {
+ CBaseTFPlayer *pPlayer = (CBaseTFPlayer*)UTIL_PlayerByIndex( i );
+ if ( pPlayer )
+ {
+ EndActOverlayTime( pPlayer );
+ }
+ }
+
+ // Think again when the act ends, if we have a timelimit
+ if ( m_flActTimeLimit )
+ {
+ SetNextThink( gpGlobals->curtime + m_flActTimeLimit - MIN_ACT_OVERLAY_TIME );
+ SetThink( ActThink );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Clean up entities before a new act starts
+//-----------------------------------------------------------------------------
+void CInfoAct::CleanupOnActStart( void )
+{
+ // Remove all resource chunks
+ CBaseEntity *pEntity = NULL;
+ while ((pEntity = gEntList.FindEntityByClassname( pEntity, "resource_chunk" )) != NULL)
+ {
+ UTIL_Remove( pEntity );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Intermission handling
+//-----------------------------------------------------------------------------
+void CInfoAct::StartIntermission( CBaseTFPlayer *pPlayer )
+{
+ // Do we have a camera point?
+ if ( m_iszIntermissionCamera != NULL_STRING )
+ {
+ CBaseEntity *pCamera = gEntList.FindEntityByName( NULL, STRING(m_iszIntermissionCamera) );
+ if ( pCamera )
+ {
+ // Move the player to the camera point
+ pPlayer->SetViewEntity( pCamera );
+ pPlayer->m_Local.m_iHideHUD |= (HIDEHUD_WEAPONSELECTION | HIDEHUD_HEALTH | HIDEHUD_MISCSTATUS);
+ }
+ }
+
+ // Lock the player in place
+ pPlayer->LockPlayerInPlace();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Intermission handling
+//-----------------------------------------------------------------------------
+void CInfoAct::EndIntermission( CBaseTFPlayer *pPlayer )
+{
+ // Force the player to respawn
+ pPlayer->UnlockPlayer();
+ pPlayer->SetViewEntity( pPlayer );
+ pPlayer->ForceRespawn();
+ pPlayer->m_Local.m_iHideHUD &= ~(HIDEHUD_WEAPONSELECTION | HIDEHUD_HEALTH | HIDEHUD_MISCSTATUS);
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Force the act to start
+//-----------------------------------------------------------------------------
+void CInfoAct::InputStart( inputdata_t &inputdata )
+{
+ StartAct();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Force the act to finish, with team 1 as the winners
+//-----------------------------------------------------------------------------
+void CInfoAct::InputFinishWinNone( inputdata_t &inputdata )
+{
+ m_iWinners = 0;
+ FinishAct();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Force the act to finish, with team 1 as the winners
+//-----------------------------------------------------------------------------
+void CInfoAct::InputFinishWin1( inputdata_t &inputdata )
+{
+ m_iWinners = 1;
+ FinishAct();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Force the act to finish, with team 2 as the winners
+//-----------------------------------------------------------------------------
+void CInfoAct::InputFinishWin2( inputdata_t &inputdata )
+{
+ m_iWinners = 2;
+ FinishAct();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Add time to the act's time
+//-----------------------------------------------------------------------------
+void CInfoAct::InputAddTime( inputdata_t &inputdata )
+{
+ float flNewTime = inputdata.value.Float();
+
+ // Think again when the act ends, if we have a timelimit
+ if ( flNewTime )
+ {
+ m_flActTimeLimit += flNewTime;
+ SetNextThink( GetNextThink() + flNewTime );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CInfoAct::IsAWaitingAct( void )
+{
+ return HasSpawnFlags(SF_ACT_WAITINGFORGAMESTART);
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Return true if the current act (if any) is a waiting act.
+//-----------------------------------------------------------------------------
+bool CurrentActIsAWaitingAct( void )
+{
+ if ( g_hCurrentAct )
+ return g_hCurrentAct->IsAWaitingAct();
+
+ return false;
+} \ No newline at end of file
diff --git a/game/server/tf2/info_act.h b/game/server/tf2/info_act.h
new file mode 100644
index 0000000..f946316
--- /dev/null
+++ b/game/server/tf2/info_act.h
@@ -0,0 +1,138 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef INFO_ACT_H
+#define INFO_ACT_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+class CBaseTFPlayer;
+
+//-----------------------------------------------------------------------------
+// Purpose: Map entity that defines an act
+//-----------------------------------------------------------------------------
+class CInfoAct : public CBaseEntity
+{
+ DECLARE_CLASS( CInfoAct, CBaseEntity );
+public:
+ CInfoAct();
+
+ DECLARE_DATADESC();
+ DECLARE_SERVERCLASS();
+
+ int UpdateTransmitState();
+
+ void Spawn( void );
+ void StartAct( void );
+ void UpdateClient( CBaseTFPlayer *pPlayer );
+ void FinishAct( void );
+ void ActThink( void );
+ int ActNumber() const;
+
+ void CleanupOnActStart( void );
+
+ bool IsAWaitingAct( void );
+
+ // Act player locking
+ void StartActOverlayTime( CBaseTFPlayer *pPlayer );
+ void EndActOverlayTime( CBaseTFPlayer *pPlayer );
+ void ActThinkEndActOverlayTime( void );
+
+ // Intermissions
+ void StartIntermission( CBaseTFPlayer *pPlayer );
+ void EndIntermission( CBaseTFPlayer *pPlayer );
+
+ int GetMCVTimer( void ) { return m_nRespawn2Team2Time; }
+
+private:
+ enum
+ {
+ RESPAWN_TIMER_90_REMAINING = 0,
+ RESPAWN_TIMER_60_REMAINING,
+ RESPAWN_TIMER_45_REMAINING,
+ RESPAWN_TIMER_30_REMAINING,
+ RESPAWN_TIMER_10_REMAINING,
+ RESPAWN_TIMER_0_REMAINING,
+
+ RESPAWN_TIMER_EVENT_COUNT
+ };
+
+ void SetUpRespawnTimers();
+ void ShutdownRespawnTimers();
+
+ // Inputs
+ void InputStart( inputdata_t &inputdata );
+ void InputFinishWinNone( inputdata_t &inputdata );
+ void InputFinishWin1( inputdata_t &inputdata );
+ void InputFinishWin2( inputdata_t &inputdata );
+ void InputAddTime( inputdata_t &inputdata );
+
+ // Respawn timers
+ void RespawnTimerThink();
+
+ // Respawn delay
+ void Team1RespawnDelayThink();
+ void Team2RespawnDelayThink();
+
+ // Computes the time remaining
+ int ComputeTimeRemaining( int nPeriod, int nDelay );
+
+ // Fires respawn events
+ void FireRespawnEvents( int nTimeRemaining, COutputEvent *pRespawnEvents, COutputInt &respawnTime );
+
+ // Outputs
+ COutputEvent m_OnStarted;
+ COutputEvent m_OnFinishedTeamNone;
+ COutputEvent m_OnFinishedTeam1;
+ COutputEvent m_OnFinishedTeam2;
+ COutputEvent m_OnTimerExpired;
+
+ COutputEvent m_Team1RespawnDelayDone;
+ COutputEvent m_Team2RespawnDelayDone;
+
+ COutputInt m_Respawn1Team1TimeRemaining;
+ COutputInt m_Respawn2Team1TimeRemaining;
+ COutputInt m_Respawn1Team2TimeRemaining;
+ COutputInt m_Respawn2Team2TimeRemaining;
+
+ // A whole buncha respawn timer events
+ COutputEvent m_Respawn1Team1Events[RESPAWN_TIMER_EVENT_COUNT];
+ COutputEvent m_Respawn2Team1Events[RESPAWN_TIMER_EVENT_COUNT];
+ COutputEvent m_Respawn1Team2Events[RESPAWN_TIMER_EVENT_COUNT];
+ COutputEvent m_Respawn2Team2Events[RESPAWN_TIMER_EVENT_COUNT];
+
+ // Respawn timer periods
+ CNetworkVar( int, m_nRespawn1Team1Time );
+ CNetworkVar( int, m_nRespawn1Team2Time );
+ CNetworkVar( int, m_nRespawn2Team1Time );
+ CNetworkVar( int, m_nRespawn2Team2Time );
+ CNetworkVar( int, m_nRespawnTeam1Delay );
+ CNetworkVar( int, m_nRespawnTeam2Delay );
+
+ // Data
+ CNetworkVar( int, m_iActNumber );
+ CNetworkVar( float, m_flActTimeLimit );
+ int m_iWinners;
+
+ // Acts
+ float m_flActStartedAt;
+
+ // Intermissions
+ string_t m_iszIntermissionCamera;
+};
+
+inline int CInfoAct::ActNumber() const
+{
+ return m_iActNumber;
+}
+
+extern CHandle<CInfoAct> g_hCurrentAct;
+
+bool CurrentActIsAWaitingAct( void );
+
+#endif // INFO_ACT_H
diff --git a/game/server/tf2/info_add_resources.cpp b/game/server/tf2/info_add_resources.cpp
new file mode 100644
index 0000000..1260bb8
--- /dev/null
+++ b/game/server/tf2/info_add_resources.cpp
@@ -0,0 +1,72 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+#include "cbase.h"
+#include "EntityOutput.h"
+#include "EntityList.h"
+#include "tf_team.h"
+#include "baseentity.h"
+#include "tf_stats.h"
+
+//-----------------------------------------------------------------------------
+// Purpose: Map entity that gives resources to players passed into it
+//-----------------------------------------------------------------------------
+class CInfoAddResources : public CBaseEntity
+{
+ DECLARE_CLASS( CInfoAddResources, CBaseEntity );
+public:
+ DECLARE_DATADESC();
+
+ void Spawn( void );
+
+ // Inputs
+ void InputPlayer( inputdata_t &inputdata );
+
+public:
+ // Outputs
+ COutputEvent m_OnAdded;
+
+ // Data
+ int m_iResourceAmount;
+};
+
+BEGIN_DATADESC( CInfoAddResources )
+
+ // inputs
+ DEFINE_INPUTFUNC( FIELD_EHANDLE, "Player", InputPlayer ),
+
+ // outputs
+ DEFINE_OUTPUT( m_OnAdded, "OnAdded" ),
+
+ // keys
+ DEFINE_KEYFIELD_NOT_SAVED( m_iResourceAmount, FIELD_INTEGER, "ResourceAmount" ),
+
+END_DATADESC()
+
+LINK_ENTITY_TO_CLASS( info_add_resources, CInfoAddResources );
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CInfoAddResources::Spawn( void )
+{
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CInfoAddResources::InputPlayer( inputdata_t &inputdata )
+{
+ CBaseEntity *pEntity = (inputdata.value.Entity()).Get();
+ if ( pEntity && pEntity->IsPlayer() )
+ {
+ CBaseTFPlayer *pPlayer = (CBaseTFPlayer *)pEntity;
+ pPlayer->AddBankResources( m_iResourceAmount );
+ TFStats()->IncrementPlayerStat( pPlayer, TF_PLAYER_STAT_RESOURCES_ACQUIRED, m_iResourceAmount );
+ }
+
+ m_OnAdded.FireOutput( inputdata.pActivator, this );
+}
diff --git a/game/server/tf2/info_buildpoint.cpp b/game/server/tf2/info_buildpoint.cpp
new file mode 100644
index 0000000..961b4ff
--- /dev/null
+++ b/game/server/tf2/info_buildpoint.cpp
@@ -0,0 +1,217 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Map entity that allows players to build objects on it
+//
+// $NoKeywords: $
+//=============================================================================//
+#include "cbase.h"
+#include "EntityOutput.h"
+#include "EntityList.h"
+#include "tf_team.h"
+#include "baseentity.h"
+#include "info_buildpoint.h"
+#include "tf_gamerules.h"
+
+// Spawnflags
+const int SF_BUILDPOINT_ALLOW_ALL_GUNS = 0x01; // Allow all manned guns to be built on this point
+const int SF_BUILDPOINT_ALLOW_VEHICLES = 0x02; // Allow all vehicles to be built on this point
+
+BEGIN_DATADESC( CInfoBuildPoint )
+
+ // keys
+ DEFINE_KEYFIELD_NOT_SAVED( m_iszAllowedObject, FIELD_STRING, "AllowedObject" ),
+
+END_DATADESC()
+
+LINK_ENTITY_TO_CLASS( info_buildpoint, CInfoBuildPoint );
+
+// List of buildpoints
+CUtlVector<CInfoBuildPoint*> g_MapDefinedBuildPoints;
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CInfoBuildPoint::Spawn( void )
+{
+ g_MapDefinedBuildPoints.AddToTail( this );
+
+ m_iAllowedObjectType = -1;
+ if ( m_iszAllowedObject != NULL_STRING )
+ {
+ for ( int i = 0; i < OBJ_LAST; i++ )
+ {
+ if ( !Q_strcmp( STRING(m_iszAllowedObject), GetObjectInfo(i)->m_pClassName ) )
+ {
+ m_iAllowedObjectType = i;
+ break;
+ }
+ }
+ }
+
+ BaseClass::Spawn();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CInfoBuildPoint::UpdateOnRemove( void )
+{
+ g_MapDefinedBuildPoints.FindAndRemove( this );
+ BaseClass::UpdateOnRemove();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Tell me how many build points you have
+//-----------------------------------------------------------------------------
+int CInfoBuildPoint::GetNumBuildPoints( void ) const
+{
+ return 1;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Give me the origin & angles of the specified build point
+//-----------------------------------------------------------------------------
+bool CInfoBuildPoint::GetBuildPoint( int iPoint, Vector &vecOrigin, QAngle &vecAngles )
+{
+ ASSERT( iPoint <= GetNumBuildPoints() );
+
+ vecOrigin = GetAbsOrigin();
+ vecAngles = GetAbsAngles();
+ return true;
+}
+
+int CInfoBuildPoint::GetBuildPointAttachmentIndex( int iPoint ) const
+{
+ return 0;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Can I build the specified object on the specified build point?
+//-----------------------------------------------------------------------------
+bool CInfoBuildPoint::CanBuildObjectOnBuildPoint( int iPoint, int iObjectType )
+{
+ ASSERT( iPoint <= GetNumBuildPoints() );
+ if ( m_hObjectBuiltOnMe )
+ return false;
+
+ // Manned guns?
+ if ( m_spawnflags & SF_BUILDPOINT_ALLOW_ALL_GUNS )
+ {
+ if ( (iObjectType == OBJ_MANNED_PLASMAGUN) ||
+ (iObjectType == OBJ_MANNED_MISSILELAUNCHER) ||
+ (iObjectType == OBJ_MANNED_SHIELD) ||
+ (iObjectType == OBJ_SENTRYGUN_PLASMA) )
+ return true;
+ }
+
+ // Vehicles
+ if ( m_spawnflags & SF_BUILDPOINT_ALLOW_VEHICLES )
+ {
+ if ( IsObjectAVehicle(iObjectType) )
+ return true;
+ }
+
+ // Check our unique
+ if ( m_iAllowedObjectType >= 0 && m_iAllowedObjectType == iObjectType )
+ return true;
+
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: I've finished building the specified object on the specified build point
+//-----------------------------------------------------------------------------
+void CInfoBuildPoint::SetObjectOnBuildPoint( int iPoint, CBaseObject *pObject )
+{
+ ASSERT( iPoint <= GetNumBuildPoints() );
+ m_hObjectBuiltOnMe = pObject;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Get the number of objects build on this entity
+//-----------------------------------------------------------------------------
+int CInfoBuildPoint::GetNumObjectsOnMe( void )
+{
+ if ( m_hObjectBuiltOnMe )
+ return 1;
+
+ return 0;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Get the first object that's built on me
+//-----------------------------------------------------------------------------
+CBaseEntity *CInfoBuildPoint::GetFirstObjectOnMe( void )
+{
+ return m_hObjectBuiltOnMe;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Get the first object of type, return NULL if no such type available
+//-----------------------------------------------------------------------------
+CBaseObject *CInfoBuildPoint::GetObjectOfTypeOnMe( int iObjectType )
+{
+ if ( m_hObjectBuiltOnMe )
+ {
+ if ( m_hObjectBuiltOnMe->ObjectType() == iObjectType )
+ return m_hObjectBuiltOnMe;
+ }
+
+ return NULL;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Remove all objects built on me
+//-----------------------------------------------------------------------------
+void CInfoBuildPoint::RemoveAllObjects( void )
+{
+ UTIL_Remove( m_hObjectBuiltOnMe );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Return the maximum distance that this entity's build points can be snapped to
+//-----------------------------------------------------------------------------
+float CInfoBuildPoint::GetMaxSnapDistance( int iPoint )
+{
+ return 64;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Return true if it's possible that build points on this entity may move in local space (i.e. due to animation)
+//-----------------------------------------------------------------------------
+bool CInfoBuildPoint::ShouldCheckForMovement( void )
+{
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: I've finished building the specified object on the specified build point
+//-----------------------------------------------------------------------------
+int CInfoBuildPoint::FindObjectOnBuildPoint( CBaseObject *pObject )
+{
+ return 1;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Returns an exit point for a vehicle built on a build point...
+//-----------------------------------------------------------------------------
+void CInfoBuildPoint::GetExitPoint( CBaseEntity *pPlayer, int iPoint, Vector *pAbsOrigin, QAngle *pAbsAngles )
+{
+ // FIXME: In future, we may well want to use specific exit attachments here...
+ GetBuildPoint( iPoint, *pAbsOrigin, *pAbsAngles );
+
+ // Move back along the forward direction a bit...
+ Vector vecForward;
+ AngleVectors( *pAbsAngles, &vecForward );
+ *pAbsOrigin -= vecForward * 60;
+
+ // Now select a good spot to drop onto
+ Vector vNewPos;
+ if ( !EntityPlacementTest(pPlayer, *pAbsOrigin, vNewPos, true) )
+ {
+ Warning("Can't find valid place to exit object.\n");
+ return;
+ }
+
+ *pAbsOrigin = vNewPos;
+}
diff --git a/game/server/tf2/info_buildpoint.h b/game/server/tf2/info_buildpoint.h
new file mode 100644
index 0000000..294eee4
--- /dev/null
+++ b/game/server/tf2/info_buildpoint.h
@@ -0,0 +1,76 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Map entity that allows players to build objects on it
+//
+//=============================================================================//
+
+#ifndef INFO_BUILDPOINT_H
+#define INFO_BUILDPOINT_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include "ihasbuildpoints.h"
+
+//-----------------------------------------------------------------------------
+// Purpose: Map entity that allows players to build objects on it
+//-----------------------------------------------------------------------------
+class CInfoBuildPoint : public CBaseEntity, public IHasBuildPoints
+{
+ DECLARE_CLASS( CInfoBuildPoint, CBaseEntity );
+public:
+ DECLARE_DATADESC();
+
+ void Spawn( void );
+ void UpdateOnRemove( void );
+
+// IHasBuildPoints
+public:
+ // Tell me how many build points you have
+ virtual int GetNumBuildPoints( void ) const;
+
+ // Give me the origin & angles of the specified build point
+ virtual bool GetBuildPoint( int iPoint, Vector &vecOrigin, QAngle &vecAngles );
+
+ virtual int GetBuildPointAttachmentIndex( int iPoint ) const;
+
+ // Can I build the specified object on the specified build point?
+ virtual bool CanBuildObjectOnBuildPoint( int iPoint, int iObjectType );
+
+ // I've finished building the specified object on the specified build point
+ virtual void SetObjectOnBuildPoint( int iPoint, CBaseObject *pObject );
+
+ // Get the number of objects build on this entity
+ virtual int GetNumObjectsOnMe( void );
+
+ // Get the first object that's built on me
+ virtual CBaseEntity *GetFirstObjectOnMe( void );
+
+ // Get the first object of type, return NULL if no such type available
+ virtual CBaseObject *GetObjectOfTypeOnMe( int iObjectType );
+
+ // Remove all objects built on me
+ virtual void RemoveAllObjects( void );
+
+ // Return the maximum distance that this entity's build points can be snapped to
+ virtual float GetMaxSnapDistance( int iPoint );
+
+ // Return true if it's possible that build points on this entity may move in local space (i.e. due to animation)
+ virtual bool ShouldCheckForMovement( void );
+
+ // I've finished building the specified object on the specified build point
+ virtual int FindObjectOnBuildPoint( CBaseObject *pObject );
+
+ // Returns an exit point for a vehicle built on a build point...
+ virtual void GetExitPoint( CBaseEntity *pPlayer, int iPoint, Vector *pAbsOrigin, QAngle *pAbsAngles );
+
+
+private:
+ string_t m_iszAllowedObject;
+ int m_iAllowedObjectType;
+ CHandle<CBaseObject> m_hObjectBuiltOnMe;
+};
+
+extern CUtlVector<CInfoBuildPoint*> g_MapDefinedBuildPoints;
+
+#endif // INFO_BUILDPOINT_H
diff --git a/game/server/tf2/info_customtech.cpp b/game/server/tf2/info_customtech.cpp
new file mode 100644
index 0000000..d2a2e5a
--- /dev/null
+++ b/game/server/tf2/info_customtech.cpp
@@ -0,0 +1,107 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Map entity that adds a custom technology to the techtree
+//
+// $NoKeywords: $
+//=============================================================================//
+#include "cbase.h"
+#include "EntityOutput.h"
+#include "EntityList.h"
+#include "tf_team.h"
+#include "techtree.h"
+#include "info_customtech.h"
+#include "tier1/strtools.h"
+
+BEGIN_DATADESC( CInfoCustomTechnology )
+
+ // outputs
+ DEFINE_OUTPUT( m_flTechPercentage, "TechPercentage" ),
+
+ // keys
+ DEFINE_KEYFIELD_NOT_SAVED( m_iszTech , FIELD_STRING, "TechToWatch" ),
+ DEFINE_KEYFIELD_NOT_SAVED( m_iszTechTreeFile , FIELD_STRING, "NewTechFile" ),
+
+END_DATADESC()
+
+IMPLEMENT_SERVERCLASS_ST( CInfoCustomTechnology, DT_InfoCustomTechnology )
+ SendPropString( SENDINFO( m_szTechTreeFile ) ),
+END_SEND_TABLE();
+
+LINK_ENTITY_TO_CLASS( info_customtech, CInfoCustomTechnology );
+
+//-----------------------------------------------------------------------------
+// Purpose: Always transmit
+//-----------------------------------------------------------------------------
+int CInfoCustomTechnology::ShouldTransmit( const CCheckTransmitInfo *pInfo )
+{
+ // Don't need to transmit ones that don't add new techs
+ if ( !m_iszTechTreeFile )
+ return FL_EDICT_DONTSEND;
+
+ // Only transmit to members of my team
+ CBaseEntity* pRecipientEntity = CBaseEntity::Instance( pInfo->m_pClientEnt );
+ if ( InSameTeam( pRecipientEntity ) )
+ {
+ //Msg( "SENDING\n" );
+ return FL_EDICT_ALWAYS;
+ }
+
+ return FL_EDICT_DONTSEND;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CInfoCustomTechnology::Spawn( void )
+{
+ m_flTechPercentage.Set( 0, this, this );
+ memset( m_szTechTreeFile.GetForModify(), 0, sizeof(m_szTechTreeFile) );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Add myself to the technology tree
+//-----------------------------------------------------------------------------
+void CInfoCustomTechnology::Activate( void )
+{
+ BaseClass::Activate();
+ if ( !GetTeamNumber() )
+ {
+ Msg( "ERROR: info_customtech without a specified team.\n" );
+ UTIL_Remove( this );
+ return;
+ }
+
+ // Get the Team's Technology Tree
+ CTFTeam *pTeam = (CTFTeam *)GetTeam();
+ if ( pTeam )
+ {
+ CTechnologyTree *pTechTree = pTeam->GetTechnologyTree();
+ if ( pTechTree )
+ {
+ // Am I supposed to add some new technologies to the tech tree?
+ if ( m_iszTechTreeFile != NULL_STRING )
+ {
+ pTechTree->AddTechnologyFile( filesystem, GetTeamNumber(), (char*)STRING(m_iszTechTreeFile ) );
+
+ // Tell our clients about the technology
+ Q_strncpy( m_szTechTreeFile.GetForModify(), STRING(m_iszTechTreeFile), sizeof(m_szTechTreeFile) );
+ }
+
+ // Find the technology in the techtree
+ CBaseTechnology *pTechnology = pTechTree->GetTechnology( STRING(m_iszTech) );
+ // Now hook the technology up to me
+ if ( pTechnology )
+ {
+ pTechnology->RegisterWatcher( this );
+ }
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Update the amount of our technology that's owned
+//-----------------------------------------------------------------------------
+void CInfoCustomTechnology::UpdateTechPercentage( float flPercentage )
+{
+ m_flTechPercentage.Set( flPercentage, this, this );
+} \ No newline at end of file
diff --git a/game/server/tf2/info_customtech.h b/game/server/tf2/info_customtech.h
new file mode 100644
index 0000000..dc8202f
--- /dev/null
+++ b/game/server/tf2/info_customtech.h
@@ -0,0 +1,43 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef INFO_CUSTOMTECH_H
+#define INFO_CUSTOMTECH_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include "entityoutput.h"
+#include "baseentity.h"
+
+//-----------------------------------------------------------------------------
+// Purpose: Map entity that adds a custom technology to the techtree
+//-----------------------------------------------------------------------------
+class CInfoCustomTechnology : public CPointEntity
+{
+ DECLARE_CLASS( CInfoCustomTechnology, CPointEntity );
+public:
+ void Spawn( void );
+ void Activate( void );
+ void UpdateTechPercentage( float flPercentage );
+
+ virtual int UpdateTransmitState() { return SetTransmitState( FL_EDICT_FULLCHECK ); };
+ virtual int ShouldTransmit( const CCheckTransmitInfo *pInfo );
+
+ DECLARE_SERVERCLASS();
+ DECLARE_DATADESC();
+
+public:
+ COutputFloat m_flTechPercentage; // Percentage of the tech that's owned
+ string_t m_iszTech;
+ string_t m_iszTechTreeFile;
+
+ // Sent via datatable
+ CNetworkString( m_szTechTreeFile, 128 );
+};
+
+#endif // INFO_CUSTOMTECH_H
diff --git a/game/server/tf2/info_input_playsound.cpp b/game/server/tf2/info_input_playsound.cpp
new file mode 100644
index 0000000..529e561
--- /dev/null
+++ b/game/server/tf2/info_input_playsound.cpp
@@ -0,0 +1,222 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+#include "cbase.h"
+#include "EntityOutput.h"
+#include "EntityList.h"
+#include "tf_team.h"
+#include "baseentity.h"
+#include "engine/IEngineSound.h"
+#include "triggers.h"
+
+// Spawnflags
+#define SF_PLAYSOUND_USE_THIS_ORIGIN 0x0001
+
+//-----------------------------------------------------------------------------
+// Purpose: Map entity that plays sounds to players
+//-----------------------------------------------------------------------------
+class CInfoInputPlaySound : public CBaseEntity
+{
+ DECLARE_CLASS( CInfoInputPlaySound, CBaseEntity );
+public:
+ DECLARE_DATADESC();
+
+ virtual void Spawn( void );
+ virtual void Precache( void );
+ virtual void Activate( void );
+
+ // Inputs
+ void InputPlaySoundToAll( inputdata_t &inputdata );
+ void InputPlaySoundToTeam1( inputdata_t &inputdata );
+ void InputPlaySoundToTeam2( inputdata_t &inputdata );
+ void InputPlaySoundToPlayer( inputdata_t &inputdata );
+ void InputSetSound( inputdata_t &inputdata );
+
+ // Sound playing
+ void PlaySoundToPlayer( CBaseTFPlayer *pPlayer );
+ void PlaySoundToTeam( CTFTeam *pTeam );
+
+private:
+ string_t m_iszSound;
+ float m_flVolume;
+ float m_flAttenuation;
+ string_t m_iszTestVolumeName;
+ EHANDLE m_hTestVolume;
+};
+
+BEGIN_DATADESC( CInfoInputPlaySound )
+
+ // variables
+ DEFINE_KEYFIELD( m_iszSound, FIELD_SOUNDNAME, "Sound" ),
+ DEFINE_KEYFIELD( m_flVolume, FIELD_FLOAT, "Volume" ),
+ DEFINE_KEYFIELD( m_flAttenuation, FIELD_FLOAT, "Attenuation" ),
+ DEFINE_KEYFIELD( m_iszTestVolumeName, FIELD_STRING, "TestVolume" ),
+
+ // inputs
+ DEFINE_INPUTFUNC( FIELD_STRING, "SetSound", InputSetSound ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "PlaySoundToAll", InputPlaySoundToAll ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "PlaySoundToTeam1", InputPlaySoundToTeam1 ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "PlaySoundToTeam2", InputPlaySoundToTeam2 ),
+ DEFINE_INPUTFUNC( FIELD_EHANDLE, "PlaySoundToPlayer", InputPlaySoundToPlayer ),
+
+END_DATADESC()
+
+LINK_ENTITY_TO_CLASS( info_input_playsound, CInfoInputPlaySound );
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CInfoInputPlaySound::Spawn( void )
+{
+ m_hTestVolume = NULL;
+ Precache();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CInfoInputPlaySound::Precache( void )
+{
+ if ( m_iszSound != NULL_STRING )
+ {
+ PrecacheScriptSound( STRING(m_iszSound) );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CInfoInputPlaySound::Activate( void )
+{
+ BaseClass::Activate();
+
+ // Find our test volume, if we have one
+ if ( m_iszTestVolumeName != NULL_STRING )
+ {
+ m_hTestVolume = gEntList.FindEntityByName( NULL, STRING(m_iszTestVolumeName) );
+ if ( !m_hTestVolume )
+ {
+ Msg("ERROR: Could not find test volume %s for info_input_playsound.\n", STRING(m_iszTestVolumeName) );
+ }
+ else
+ {
+ // Make sure it's a trigger
+ CBaseTrigger *pTrigger = dynamic_cast<CBaseTrigger*>((CBaseEntity*)m_hTestVolume);
+ if ( !pTrigger )
+ {
+ Msg("ERROR: info_input_playsound specifies a volume %s, but it's not a trigger.\n", STRING(m_iszTestVolumeName) );
+ m_hTestVolume = NULL;
+ }
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Play sound to all players
+//-----------------------------------------------------------------------------
+void CInfoInputPlaySound::InputPlaySoundToAll( inputdata_t &inputdata )
+{
+ for ( int i = 1; i <= gpGlobals->maxClients; i++ )
+ {
+ CBaseTFPlayer *pPlayer = ToBaseTFPlayer( UTIL_PlayerByIndex(i) );
+ if ( pPlayer )
+ {
+ PlaySoundToPlayer( pPlayer );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Play sound to all players on team 1
+//-----------------------------------------------------------------------------
+void CInfoInputPlaySound::InputPlaySoundToTeam1( inputdata_t &inputdata )
+{
+ PlaySoundToTeam( GetGlobalTFTeam(1) );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Play sound to all players on team 2
+//-----------------------------------------------------------------------------
+void CInfoInputPlaySound::InputPlaySoundToTeam2( inputdata_t &inputdata )
+{
+ PlaySoundToTeam( GetGlobalTFTeam(2) );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Play sound to a specific player
+//-----------------------------------------------------------------------------
+void CInfoInputPlaySound::InputPlaySoundToPlayer( inputdata_t &inputdata )
+{
+ CBaseEntity *pEntity = (inputdata.value.Entity()).Get();
+ if ( pEntity && pEntity->IsPlayer() )
+ {
+ CBaseTFPlayer *pPlayer = (CBaseTFPlayer *)pEntity;
+ PlaySoundToPlayer( pPlayer );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Set the sound to play
+//-----------------------------------------------------------------------------
+void CInfoInputPlaySound::InputSetSound( inputdata_t &inputdata )
+{
+ m_iszSound = MAKE_STRING( inputdata.value.String() );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Play sound to a team
+//-----------------------------------------------------------------------------
+void CInfoInputPlaySound::PlaySoundToTeam( CTFTeam *pTeam )
+{
+ for ( int i = 0; i < pTeam->GetNumPlayers(); i++ )
+ {
+ PlaySoundToPlayer( (CBaseTFPlayer*)pTeam->GetPlayer(i) );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Play sound to a player
+//-----------------------------------------------------------------------------
+void CInfoInputPlaySound::PlaySoundToPlayer( CBaseTFPlayer *pPlayer )
+{
+ // First, if we have a test volume, make sure the player's within it
+ if ( m_hTestVolume )
+ {
+ CBaseTrigger *pTrigger = (CBaseTrigger *)(CBaseEntity*)m_hTestVolume;
+ if ( !pTrigger->IsTouching( pPlayer ) )
+ return;
+ }
+
+ // Check to see if we're supposed to play it from this entity's location
+ if ( HasSpawnFlags( SF_PLAYSOUND_USE_THIS_ORIGIN ) )
+ {
+ CPASAttenuationFilter filter;
+ filter.AddRecipient( pPlayer );
+ filter.Filter( GetAbsOrigin(), m_flAttenuation );
+
+ EmitSound_t ep;
+ ep.m_nChannel = CHAN_VOICE;
+ ep.m_pSoundName = STRING(m_iszSound);
+ ep.m_flVolume = m_flVolume;
+ ep.m_SoundLevel = ATTN_TO_SNDLVL( m_flAttenuation );
+
+ ep.m_pOrigin = &(GetAbsOrigin());
+
+ EmitSound( filter, entindex(), ep );
+ }
+ else
+ {
+ CSingleUserRecipientFilter filter( pPlayer );
+
+ EmitSound_t ep;
+ ep.m_nChannel = CHAN_VOICE;
+ ep.m_pSoundName = STRING(m_iszSound);
+ ep.m_flVolume = m_flVolume;
+ ep.m_SoundLevel = ATTN_TO_SNDLVL( m_flAttenuation );
+
+ EmitSound( filter, pPlayer->entindex(), ep );
+ }
+}
diff --git a/game/server/tf2/info_input_resetbanks.cpp b/game/server/tf2/info_input_resetbanks.cpp
new file mode 100644
index 0000000..9e44b5c
--- /dev/null
+++ b/game/server/tf2/info_input_resetbanks.cpp
@@ -0,0 +1,124 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+#include "cbase.h"
+#include "EntityOutput.h"
+#include "EntityList.h"
+#include "tf_team.h"
+#include "baseentity.h"
+
+//-----------------------------------------------------------------------------
+// Purpose: Map entity that resets player's banks
+//-----------------------------------------------------------------------------
+class CInfoInputResetBanks : public CBaseEntity
+{
+ DECLARE_CLASS( CInfoInputResetBanks, CBaseEntity );
+public:
+ DECLARE_DATADESC();
+
+ // Inputs
+ void InputResetAll( inputdata_t &inputdata );
+ void InputResetTeam1( inputdata_t &inputdata );
+ void InputResetTeam2( inputdata_t &inputdata );
+ void InputResetPlayer( inputdata_t &inputdata );
+ void InputSetResetAmount( inputdata_t &inputdata );
+
+ // Resetting
+ void ResetPlayersBank( CBaseTFPlayer *pPlayer );
+ void ResetTeamsBanks( CTFTeam *pTeam );
+
+private:
+ int m_iResetAmount;
+};
+
+BEGIN_DATADESC( CInfoInputResetBanks )
+
+ // variables
+ DEFINE_KEYFIELD( m_iResetAmount, FIELD_INTEGER, "ResetAmount" ),
+
+ // inputs
+ DEFINE_INPUTFUNC( FIELD_INTEGER, "SetResetAmount", InputSetResetAmount ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "ResetAll", InputResetAll ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "ResetTeam1", InputResetTeam1 ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "ResetTeam2", InputResetTeam2 ),
+ DEFINE_INPUTFUNC( FIELD_EHANDLE, "ResetPlayer", InputResetPlayer ),
+
+END_DATADESC()
+
+LINK_ENTITY_TO_CLASS( info_input_resetbanks, CInfoInputResetBanks );
+
+//-----------------------------------------------------------------------------
+// Purpose: Reset all the player's resource banks
+//-----------------------------------------------------------------------------
+void CInfoInputResetBanks::InputResetAll( inputdata_t &inputdata )
+{
+ for ( int i = 1; i <= gpGlobals->maxClients; i++ )
+ {
+ CBaseTFPlayer *pPlayer = ToBaseTFPlayer( UTIL_PlayerByIndex(i) );
+ if ( pPlayer )
+ {
+ ResetPlayersBank( pPlayer );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Reset all of team 1's player's resource banks
+//-----------------------------------------------------------------------------
+void CInfoInputResetBanks::InputResetTeam1( inputdata_t &inputdata )
+{
+ ResetTeamsBanks( GetGlobalTFTeam(1) );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Reset all of team 2's player's resource banks
+//-----------------------------------------------------------------------------
+void CInfoInputResetBanks::InputResetTeam2( inputdata_t &inputdata )
+{
+ ResetTeamsBanks( GetGlobalTFTeam(2) );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Reset a specific player's resource banks
+//-----------------------------------------------------------------------------
+void CInfoInputResetBanks::InputResetPlayer( inputdata_t &inputdata )
+{
+ CBaseEntity *pEntity = (inputdata.value.Entity()).Get();
+ if ( pEntity && pEntity->IsPlayer() )
+ {
+ CBaseTFPlayer *pPlayer = (CBaseTFPlayer *)pEntity;
+ ResetPlayersBank( pPlayer );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Set the reset amount
+//-----------------------------------------------------------------------------
+void CInfoInputResetBanks::InputSetResetAmount( inputdata_t &inputdata )
+{
+ m_iResetAmount = inputdata.value.Int();
+ Assert( m_iResetAmount >= 0 );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Reset the team's resource banks
+//-----------------------------------------------------------------------------
+void CInfoInputResetBanks::ResetTeamsBanks( CTFTeam *pTeam )
+{
+ pTeam->SetRecentBankSet( m_iResetAmount );
+ for ( int i = 0; i < pTeam->GetNumPlayers(); i++ )
+ {
+ ResetPlayersBank( (CBaseTFPlayer*)pTeam->GetPlayer(i) );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Reset the player's resource bank
+//-----------------------------------------------------------------------------
+void CInfoInputResetBanks::ResetPlayersBank( CBaseTFPlayer *pPlayer )
+{
+ pPlayer->SetBankResources( m_iResetAmount );
+}
diff --git a/game/server/tf2/info_input_resetobjects.cpp b/game/server/tf2/info_input_resetobjects.cpp
new file mode 100644
index 0000000..e0475a1
--- /dev/null
+++ b/game/server/tf2/info_input_resetobjects.cpp
@@ -0,0 +1,106 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+#include "cbase.h"
+#include "EntityOutput.h"
+#include "EntityList.h"
+#include "tf_team.h"
+#include "baseentity.h"
+
+//-----------------------------------------------------------------------------
+// Purpose: Map entity that resets player's objects
+//-----------------------------------------------------------------------------
+class CInfoInputResetObjects : public CBaseEntity
+{
+ DECLARE_CLASS( CInfoInputResetObjects, CBaseEntity );
+public:
+ DECLARE_DATADESC();
+
+ // Inputs
+ void InputResetAll( inputdata_t &inputdata );
+ void InputResetTeam1( inputdata_t &inputdata );
+ void InputResetTeam2( inputdata_t &inputdata );
+ void InputResetPlayer( inputdata_t &inputdata );
+
+ // Resetting
+ void ResetPlayersObjects( CBaseTFPlayer *pPlayer );
+ void ResetTeamsObjects( CTFTeam *pTeam );
+};
+
+BEGIN_DATADESC( CInfoInputResetObjects )
+
+ // inputs
+ DEFINE_INPUTFUNC( FIELD_VOID, "ResetAll", InputResetAll ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "ResetTeam1", InputResetTeam1 ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "ResetTeam2", InputResetTeam2 ),
+ DEFINE_INPUTFUNC( FIELD_EHANDLE, "ResetPlayer", InputResetPlayer ),
+
+END_DATADESC()
+
+LINK_ENTITY_TO_CLASS( info_input_resetobjects, CInfoInputResetObjects );
+
+//-----------------------------------------------------------------------------
+// Purpose: Reset all the player's objects
+//-----------------------------------------------------------------------------
+void CInfoInputResetObjects::InputResetAll( inputdata_t &inputdata )
+{
+ for ( int i = 1; i <= gpGlobals->maxClients; i++ )
+ {
+ CBaseTFPlayer *pPlayer = ToBaseTFPlayer( UTIL_PlayerByIndex(i) );
+ if ( pPlayer )
+ {
+ ResetPlayersObjects( pPlayer );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Reset all of team 1's player's objects
+//-----------------------------------------------------------------------------
+void CInfoInputResetObjects::InputResetTeam1( inputdata_t &inputdata )
+{
+ ResetTeamsObjects( GetGlobalTFTeam(1) );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Reset all of team 2's player's objects
+//-----------------------------------------------------------------------------
+void CInfoInputResetObjects::InputResetTeam2( inputdata_t &inputdata )
+{
+ ResetTeamsObjects( GetGlobalTFTeam(2) );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Reset a specific player's objects
+//-----------------------------------------------------------------------------
+void CInfoInputResetObjects::InputResetPlayer( inputdata_t &inputdata )
+{
+ CBaseEntity *pEntity = (inputdata.value.Entity()).Get();
+ if ( pEntity && pEntity->IsPlayer() )
+ {
+ CBaseTFPlayer *pPlayer = (CBaseTFPlayer *)pEntity;
+ ResetPlayersObjects( pPlayer );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Reset the team's objects
+//-----------------------------------------------------------------------------
+void CInfoInputResetObjects::ResetTeamsObjects( CTFTeam *pTeam )
+{
+ for ( int i = 0; i < pTeam->GetNumPlayers(); i++ )
+ {
+ ResetPlayersObjects( (CBaseTFPlayer*)pTeam->GetPlayer(i) );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Reset the player's objects
+//-----------------------------------------------------------------------------
+void CInfoInputResetObjects::ResetPlayersObjects( CBaseTFPlayer *pPlayer )
+{
+ pPlayer->RemoveAllObjects( true, 0, true );
+} \ No newline at end of file
diff --git a/game/server/tf2/info_input_respawnplayers.cpp b/game/server/tf2/info_input_respawnplayers.cpp
new file mode 100644
index 0000000..4b9917f
--- /dev/null
+++ b/game/server/tf2/info_input_respawnplayers.cpp
@@ -0,0 +1,121 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+#include "cbase.h"
+#include "EntityOutput.h"
+#include "EntityList.h"
+#include "tf_team.h"
+#include "baseentity.h"
+
+// Spawnflags
+const int SF_RESPAWNPLAYERS_RESETALL = 0x01; // Respawned players have their inventory completely reset
+const int SF_RESPAWNPLAYERS_RESETAMMO = 0x02; // Respawned players have their ammo counts reset
+
+//-----------------------------------------------------------------------------
+// Purpose: Map entity that respawns players
+//-----------------------------------------------------------------------------
+class CInfoInputRespawnPlayers : public CBaseEntity
+{
+ DECLARE_CLASS( CInfoInputRespawnPlayers, CBaseEntity );
+public:
+ DECLARE_DATADESC();
+
+ // Inputs
+ void InputRespawnAll( inputdata_t &inputdata );
+ void InputRespawnTeam1( inputdata_t &inputdata );
+ void InputRespawnTeam2( inputdata_t &inputdata );
+ void InputRespawnPlayer( inputdata_t &inputdata );
+
+ // Respawning
+ void RespawnPlayer( CBaseTFPlayer *pPlayer );
+ void RespawnTeam( CTFTeam *pTeam );
+};
+
+BEGIN_DATADESC( CInfoInputRespawnPlayers )
+
+ // inputs
+ DEFINE_INPUTFUNC( FIELD_VOID, "RespawnAll", InputRespawnAll ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "RespawnTeam1", InputRespawnTeam1 ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "RespawnTeam2", InputRespawnTeam2 ),
+ DEFINE_INPUTFUNC( FIELD_EHANDLE, "RespawnPlayer", InputRespawnPlayer ),
+
+END_DATADESC()
+
+LINK_ENTITY_TO_CLASS( info_input_respawnplayers, CInfoInputRespawnPlayers );
+
+//-----------------------------------------------------------------------------
+// Purpose: Respawn all the players
+//-----------------------------------------------------------------------------
+void CInfoInputRespawnPlayers::InputRespawnAll( inputdata_t &inputdata )
+{
+ for ( int i = 0; i < MAX_TF_TEAMS; i++ )
+ {
+ RespawnTeam( GetGlobalTFTeam(i+1) );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Respawn all of team 1's players
+//-----------------------------------------------------------------------------
+void CInfoInputRespawnPlayers::InputRespawnTeam1( inputdata_t &inputdata )
+{
+ RespawnTeam( GetGlobalTFTeam(1) );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Respawn all of team 2's players
+//-----------------------------------------------------------------------------
+void CInfoInputRespawnPlayers::InputRespawnTeam2( inputdata_t &inputdata )
+{
+ RespawnTeam( GetGlobalTFTeam(2) );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Respawn a specific player
+//-----------------------------------------------------------------------------
+void CInfoInputRespawnPlayers::InputRespawnPlayer( inputdata_t &inputdata )
+{
+ CBaseEntity *pEntity = (inputdata.value.Entity()).Get();
+ if ( pEntity && pEntity->IsPlayer() )
+ {
+ CBaseTFPlayer *pPlayer = (CBaseTFPlayer *)pEntity;
+ RespawnPlayer( pPlayer );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Respawn the team
+//-----------------------------------------------------------------------------
+void CInfoInputRespawnPlayers::RespawnTeam( CTFTeam *pTeam )
+{
+ Assert( pTeam );
+ if ( !pTeam )
+ return;
+
+ // Respawn all the players
+ for ( int i = 0; i < pTeam->GetNumPlayers(); i++ )
+ {
+ RespawnPlayer( (CBaseTFPlayer*)pTeam->GetPlayer(i) );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Respawn the player
+//-----------------------------------------------------------------------------
+void CInfoInputRespawnPlayers::RespawnPlayer( CBaseTFPlayer *pPlayer )
+{
+ // Reset ammo if the spawnflag's set
+ if ( HasSpawnFlags( SF_RESPAWNPLAYERS_RESETALL ) )
+ {
+ pPlayer->ResupplyAmmo( 1.0, RESUPPLY_ALL_FROM_STATION );
+ }
+ else if ( HasSpawnFlags( SF_RESPAWNPLAYERS_RESETAMMO ) )
+ {
+ pPlayer->ResupplyAmmo( 1.0, RESUPPLY_RESPAWN );
+ }
+
+ pPlayer->ForceRespawn();
+} \ No newline at end of file
diff --git a/game/server/tf2/info_minimappulse.cpp b/game/server/tf2/info_minimappulse.cpp
new file mode 100644
index 0000000..fa34a09
--- /dev/null
+++ b/game/server/tf2/info_minimappulse.cpp
@@ -0,0 +1,116 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+#include "cbase.h"
+#include "EntityOutput.h"
+#include "EntityList.h"
+#include "tf_team.h"
+#include "baseentity.h"
+
+//-----------------------------------------------------------------------------
+// Purpose: Map entity that makes a pulse on the minimap
+//-----------------------------------------------------------------------------
+class CInfoMinimapPulse : public CBaseEntity
+{
+ DECLARE_CLASS( CInfoMinimapPulse, CBaseEntity );
+public:
+ DECLARE_DATADESC();
+
+ void Spawn( void );
+
+ // Inputs
+ void InputPulseForAll( inputdata_t &inputdata );
+ void InputPulseForTeam1( inputdata_t &inputdata );
+ void InputPulseForTeam2( inputdata_t &inputdata );
+ void InputPulseForPlayer( inputdata_t &inputdata );
+
+ // Pulsing
+ void PulseForPlayer( CBaseTFPlayer *pPlayer );
+ void PulseForTeam( CTFTeam *pTeam );
+};
+
+BEGIN_DATADESC( CInfoMinimapPulse )
+
+ // inputs
+ DEFINE_INPUTFUNC( FIELD_VOID, "PulseForAll", InputPulseForAll ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "PulseForTeam1", InputPulseForTeam1 ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "PulseForTeam2", InputPulseForTeam2 ),
+ DEFINE_INPUTFUNC( FIELD_EHANDLE, "PulseForPlayer", InputPulseForPlayer ),
+
+END_DATADESC()
+
+LINK_ENTITY_TO_CLASS( info_minimappulse, CInfoMinimapPulse );
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CInfoMinimapPulse::Spawn( void )
+{
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Pulse for all the players
+//-----------------------------------------------------------------------------
+void CInfoMinimapPulse::InputPulseForAll( inputdata_t &inputdata )
+{
+ for ( int i = 0; i < MAX_TF_TEAMS; i++ )
+ {
+ PulseForTeam( GetGlobalTFTeam(i) );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Pulse for all of team 1's players
+//-----------------------------------------------------------------------------
+void CInfoMinimapPulse::InputPulseForTeam1( inputdata_t &inputdata )
+{
+ PulseForTeam( GetGlobalTFTeam(1) );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Pulse for all of team 2's players
+//-----------------------------------------------------------------------------
+void CInfoMinimapPulse::InputPulseForTeam2( inputdata_t &inputdata )
+{
+ PulseForTeam( GetGlobalTFTeam(2) );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Pulse for a specific player
+//-----------------------------------------------------------------------------
+void CInfoMinimapPulse::InputPulseForPlayer( inputdata_t &inputdata )
+{
+ CBaseEntity *pEntity = (inputdata.value.Entity()).Get();
+ if ( pEntity && pEntity->IsPlayer() )
+ {
+ CBaseTFPlayer *pPlayer = (CBaseTFPlayer *)pEntity;
+ PulseForPlayer( pPlayer );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Pulse for the team
+//-----------------------------------------------------------------------------
+void CInfoMinimapPulse::PulseForTeam( CTFTeam *pTeam )
+{
+ // Pulse all the players
+ for ( int i = 0; i < pTeam->GetNumPlayers(); i++ )
+ {
+ PulseForPlayer( (CBaseTFPlayer*)pTeam->GetPlayer(i) );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Make a minimap pulse for the player
+//-----------------------------------------------------------------------------
+void CInfoMinimapPulse::PulseForPlayer( CBaseTFPlayer *pPlayer )
+{
+ CSingleUserRecipientFilter user( pPlayer );
+ user.MakeReliable();
+ UserMessageBegin( user, "MinimapPulse" );
+ WRITE_VEC3COORD( GetAbsOrigin() );
+ MessageEnd();
+} \ No newline at end of file
diff --git a/game/server/tf2/info_output_team.cpp b/game/server/tf2/info_output_team.cpp
new file mode 100644
index 0000000..ffe3f97
--- /dev/null
+++ b/game/server/tf2/info_output_team.cpp
@@ -0,0 +1,71 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+#include "cbase.h"
+#include "EntityOutput.h"
+#include "EntityList.h"
+#include "tf_team.h"
+#include "baseentity.h"
+
+//-----------------------------------------------------------------------------
+// Purpose: Map entity that fires its output with all the players in a team
+//-----------------------------------------------------------------------------
+class CInfoOutputTeam : public CBaseEntity
+{
+ DECLARE_CLASS( CInfoOutputTeam, CBaseEntity );
+public:
+ DECLARE_DATADESC();
+
+ void Spawn( void );
+
+ // Inputs
+ void InputFire( inputdata_t &inputdata );
+
+public:
+ // Outputs
+ COutputEHANDLE m_Player;
+};
+
+BEGIN_DATADESC( CInfoOutputTeam )
+
+ // inputs
+ DEFINE_INPUTFUNC( FIELD_VOID, "Fire", InputFire ),
+
+ // outputs
+ DEFINE_OUTPUT( m_Player, "Player" ),
+
+END_DATADESC()
+
+LINK_ENTITY_TO_CLASS( info_output_team, CInfoOutputTeam );
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CInfoOutputTeam::Spawn( void )
+{
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CInfoOutputTeam::InputFire( inputdata_t &inputdata )
+{
+ // Loop through all the players on the team and fire our output with each of them.
+ for ( int i = 1; i <= gpGlobals->maxClients; i++ )
+ {
+ CBaseTFPlayer *pPlayer = ToBaseTFPlayer( UTIL_PlayerByIndex(i) );
+ if ( pPlayer )
+ {
+ // If we don't belong to a team, loop through all players
+ if ( GetTeamNumber() == 0 || pPlayer->GetTeamNumber() == GetTeamNumber() )
+ {
+ EHANDLE hHandle;
+ hHandle = pPlayer;
+ m_Player.Set( hHandle, inputdata.pActivator, this );
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/game/server/tf2/info_resourceprocessor.cpp b/game/server/tf2/info_resourceprocessor.cpp
new file mode 100644
index 0000000..940202b
--- /dev/null
+++ b/game/server/tf2/info_resourceprocessor.cpp
@@ -0,0 +1,139 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: A team's resource processor back at their base
+//
+// $NoKeywords: $
+//=============================================================================//
+#include "cbase.h"
+#include "tf_team.h"
+#include "info_resourceprocessor.h"
+#include "tf_player.h"
+#include "npc_minicarrier.h"
+#include "tf_gamerules.h"
+
+BEGIN_DATADESC( CResourceProcessor )
+
+ // functions
+ DEFINE_FUNCTION( ProcessorTouch ),
+
+END_DATADESC()
+
+IMPLEMENT_SERVERCLASS_ST(CResourceProcessor, DT_ResourceProcessor)
+END_SEND_TABLE()
+
+LINK_ENTITY_TO_CLASS( info_resourceprocessor, CResourceProcessor);
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CResourceProcessor::Spawn( void )
+{
+ Precache();
+
+ SetSolid( SOLID_SLIDEBOX );
+ SetMoveType( MOVETYPE_NONE );
+ AddFlag( FL_NOTARGET );
+
+ UTIL_SetSize( pev, Vector(-32,-32,0), Vector(32,32, 128) );
+ SetModel( "models/objects/obj_resourceprocessor.mdl" );
+ SetTouch( ProcessorTouch );
+
+ m_flHackSpawnHeight = 256;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CResourceProcessor::Precache( void )
+{
+ PrecacheModel( "models/objects/obj_resourceprocessor.mdl" );
+
+ UTIL_PrecacheOther( "npc_minicarrier" );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CResourceProcessor::Activate( void )
+{
+ BaseClass::Activate();
+ if ( GetTeamNumber() < 0 || GetTeamNumber() >= GetNumberOfTeams() )
+ {
+ Warning( "Warning, info_resourceprocessor with invalid Team Number set.\n" );
+ UTIL_Remove( this );
+ return;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CResourceProcessor::ProcessorTouch( CBaseEntity *pOther )
+{
+ // Players
+ if ( pOther->IsPlayer() )
+ {
+ // Ignore touches from enemy players
+ if ( !InSameTeam( pOther ) )
+ return;
+
+ CBaseTFPlayer *pPlayer = (CBaseTFPlayer *)pOther;
+
+ // Remove all the player's resources and add them to the team's stash
+ for ( int i = 0; i < MAX_RESOURCE_TYPES; i++ )
+ {
+ int iCount = pPlayer->GetResourceChunkCount(i, false);
+ if ( iCount )
+ {
+ pPlayer->RemoveResourceChunks( i, iCount, false );
+ AddResources( i, iCount * CHUNK_RESOURCE_VALUE );
+ }
+
+ // Now remove processed versions too
+ iCount = pPlayer->GetResourceChunkCount(i, true);
+ if ( iCount )
+ {
+ pPlayer->RemoveResourceChunks( i, iCount, true );
+ AddResources( i, iCount * PROCESSED_CHUNK_RESOURCE_VALUE );
+ }
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Add resources to the processor, and hence the team's bank
+//-----------------------------------------------------------------------------
+void CResourceProcessor::AddResources( int iResourceType, float flResources )
+{
+ if ( !GetTeam() )
+ return;
+
+ ((CTFTeam *)GetTeam())->AddResources( iResourceType, flResources );
+ ((CTFTeam *)GetTeam())->ResourceLoadDeposited();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Spawn a minicarrier
+//-----------------------------------------------------------------------------
+void CResourceProcessor::SpawnMiniCarrier( void )
+{
+ CNPC_MiniCarrier *pMiniCarrier = (CNPC_MiniCarrier*)CreateEntityByName( "npc_minicarrier" );
+ pMiniCarrier->Spawn();
+ pMiniCarrier->ChangeTeam( m_iTeamNumber );
+
+ // Find a clear spot near me & spawn in it
+ if ( !EntityPlacementTest( pMiniCarrier, GetAbsOrigin() + Vector(0,0,m_flHackSpawnHeight),
+ pMiniCarrier->GetAbsOrigin(), false ) )
+ {
+ Warning( "Failed to find empty space to spawn a minicarrier.\n" );
+ ( ( CTFTeam * )GetTeam() )->RemoveRobot( pMiniCarrier );
+ UTIL_Remove( pMiniCarrier );
+ return;
+ }
+
+ m_flHackSpawnHeight += 64;
+
+ engine->SetOrigin( pMiniCarrier->pev, pMiniCarrier->GetOrigin() );
+ pMiniCarrier->SetHomeProcessor( this );
+}
diff --git a/game/server/tf2/info_vehicle_bay.cpp b/game/server/tf2/info_vehicle_bay.cpp
new file mode 100644
index 0000000..30737b9
--- /dev/null
+++ b/game/server/tf2/info_vehicle_bay.cpp
@@ -0,0 +1,270 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+#include "cbase.h"
+#include "EntityOutput.h"
+#include "EntityList.h"
+#include "info_vehicle_bay.h"
+#include "tf_obj.h"
+#include "tf_player.h"
+#include "info_act.h"
+
+extern ConVar tf_fastbuild;
+
+BEGIN_DATADESC( CInfoVehicleBay )
+END_DATADESC()
+
+LINK_ENTITY_TO_CLASS( info_vehicle_bay, CInfoVehicleBay );
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CInfoVehicleBay::Spawn( void )
+{
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Vehicle bays have 1 build point
+//-----------------------------------------------------------------------------
+int CInfoVehicleBay::GetNumBuildPoints( void ) const
+{
+ return 1;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Return true if the specified object type can be built on this point
+//-----------------------------------------------------------------------------
+bool CInfoVehicleBay::CanBuildObjectOnBuildPoint( int iPoint, int iObjectType )
+{
+ ASSERT( iPoint <= GetNumBuildPoints() );
+
+ // Don't allow building if there's another vehicle in the way
+
+
+ // Only vehicles can be built here
+ return ( iObjectType >= OBJ_BATTERING_RAM );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CInfoVehicleBay::GetBuildPoint( int iPoint, Vector &vecOrigin, QAngle &vecAngles )
+{
+ ASSERT( iPoint <= GetNumBuildPoints() );
+
+ vecOrigin = GetAbsOrigin();
+ vecAngles = GetAbsAngles();
+ return true;
+}
+
+int CInfoVehicleBay::GetBuildPointAttachmentIndex( int iPoint ) const
+{
+ return 0;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CInfoVehicleBay::SetObjectOnBuildPoint( int iPoint, CBaseObject *pObject )
+{
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+int CInfoVehicleBay::GetNumObjectsOnMe( void )
+{
+ return 0;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CBaseEntity *CInfoVehicleBay::GetFirstObjectOnMe( void )
+{
+ return NULL;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CBaseObject *CInfoVehicleBay::GetObjectOfTypeOnMe( int iObjectType )
+{
+ return NULL;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+int CInfoVehicleBay::FindObjectOnBuildPoint( CBaseObject *pObject )
+{
+ return -1;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CInfoVehicleBay::GetExitPoint( CBaseEntity *pPlayer, int iPoint, Vector *pAbsOrigin, QAngle *pAbsAngles )
+{
+ Assert(0);
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CInfoVehicleBay::RemoveAllObjects( void )
+{
+}
+
+//===================================================================================================================
+// Vehicle Bay VGui Screen
+//===================================================================================================================
+BEGIN_DATADESC( CVGuiScreenVehicleBay )
+ // outputs
+ DEFINE_OUTPUT( m_OnStartedBuild, "OnStartedBuild" ),
+ DEFINE_OUTPUT( m_OnFinishedBuild, "OnFinishedBuild" ),
+ DEFINE_OUTPUT( m_OnReadyToBuildAgain, "OnReadyToBuildAgain" ),
+
+ // functions
+ DEFINE_FUNCTION( BayThink ),
+END_DATADESC()
+
+LINK_ENTITY_TO_CLASS( vgui_screen_vehicle_bay, CVGuiScreenVehicleBay );
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CVGuiScreenVehicleBay::Activate( void )
+{
+ BaseClass::Activate();
+
+ // Make sure we have a buildpoint specified
+ CBaseEntity *pBuildPoint = gEntList.FindEntityByName( NULL, m_target );
+ if ( !pBuildPoint )
+ {
+ Msg("ERROR: vgui_screen_vehicle_bay with no buildpoint as its target.\n" );
+ UTIL_Remove( this );
+ return;
+ }
+
+ Vector vecOrigin = pBuildPoint->GetAbsOrigin();
+ QAngle vecAngles = pBuildPoint->GetAbsAngles();
+ SetBuildPoint( vecOrigin, vecAngles );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CVGuiScreenVehicleBay::SetBuildPoint( Vector &vecOrigin, QAngle &vecAngles )
+{
+ m_vecBuildPointOrigin = vecOrigin;
+ m_vecBuildPointAngles = vecAngles;
+ m_bBayIsClear = false;
+
+ // Start checking to see when I'm clear again
+ SetThink( BayThink );
+ SetNextThink( gpGlobals->curtime + 0.1f );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CVGuiScreenVehicleBay::BuildVehicle( CBaseTFPlayer *pPlayer, int iObjectType )
+{
+ if ( !IsObjectAVehicle(iObjectType) )
+ return;
+ Assert( m_vecBuildPointOrigin != vec3_origin );
+
+ // Can't build if the game hasn't started
+ if ( !tf_fastbuild.GetInt() && CurrentActIsAWaitingAct() )
+ {
+ ClientPrint( pPlayer, HUD_PRINTCENTER, "Can't build until the game's started.\n" );
+ return;
+ }
+
+ // Try and spawn the object
+ CBaseEntity *pEntity = CreateEntityByName( GetObjectInfo(iObjectType)->m_pClassName );
+ if ( !pEntity )
+ return;
+
+ if ( !m_bBayIsClear )
+ {
+ ClientPrint( pPlayer, HUD_PRINTCENTER, "Vehicle bay isn't clear.\n" );
+ return;
+ }
+
+ pEntity->SetAbsOrigin( m_vecBuildPointOrigin );
+ pEntity->SetAbsAngles( m_vecBuildPointAngles );
+
+ CBaseObject *pObject = dynamic_cast<CBaseObject*>(pEntity);
+ if ( pObject )
+ pObject->AdjustInitialBuildAngles();
+
+ pEntity->Spawn();
+
+ // If it's an object, finish setting it up
+ if ( !pObject )
+ return;
+
+ pObject->StartPlacement( pPlayer );
+ pObject->SetVehicleBay( this );
+
+ // StartBuilding will return false if the player couldn't afford the vehicle
+ if ( !pObject->StartBuilding( pPlayer ) )
+ return;
+
+ // Fire our started-building output
+ m_OnStartedBuild.FireOutput( pPlayer, this );
+
+ m_bBayIsClear = false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CVGuiScreenVehicleBay::FinishedBuildVehicle( CBaseObject *pObject )
+{
+ m_OnFinishedBuild.FireOutput( pObject->GetBuilder(), this );
+
+ // Start checking to see when I'm clear again
+ SetThink( BayThink );
+ SetNextThink( gpGlobals->curtime + 0.3 );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Check to see if we're clear enough to allow another vehicle to be built here
+//-----------------------------------------------------------------------------
+void CVGuiScreenVehicleBay::BayThink( void )
+{
+ // Get a list of entities around our buildpoint
+ CBaseEntity *pListOfNearbyEntities[100];
+ int iNumberOfNearbyEntities = UTIL_EntitiesInSphere( pListOfNearbyEntities, 100, m_vecBuildPointOrigin, 128, 0 );
+ for ( int i = 0; i < iNumberOfNearbyEntities; i++ )
+ {
+ CBaseEntity *pEntity = pListOfNearbyEntities[i];
+ if ( pEntity->IsSolid( ) )
+ {
+ // Ignore shields..
+ if ( pEntity->GetCollisionGroup() == TFCOLLISION_GROUP_SHIELD )
+ continue;
+ // Ignore func brushes
+ if ( pEntity->GetMoveType() == MOVETYPE_PUSH )
+ continue;
+
+ //NDebugOverlay::EntityBounds( pEntity, 0,255,0,8, 0.1 );
+
+ // Check again soon
+ SetNextThink( gpGlobals->curtime + 1 );
+ return;
+ }
+ }
+
+ // We're clear
+ m_bBayIsClear = true;
+ SetThink( NULL );
+
+ m_OnReadyToBuildAgain.FireOutput( this, this );
+} \ No newline at end of file
diff --git a/game/server/tf2/info_vehicle_bay.h b/game/server/tf2/info_vehicle_bay.h
new file mode 100644
index 0000000..3692ac9
--- /dev/null
+++ b/game/server/tf2/info_vehicle_bay.h
@@ -0,0 +1,72 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+#ifndef INFO_VEHICLE_BAY_H
+#define INFO_VEHICLE_BAY_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include "ihasbuildpoints.h"
+#include "vguiscreen.h"
+
+//-----------------------------------------------------------------------------
+// Purpose: Entity that provides a place to build a vehicle
+//-----------------------------------------------------------------------------
+class CInfoVehicleBay : public CBaseEntity, public IHasBuildPoints
+{
+ DECLARE_CLASS( CInfoVehicleBay, CBaseEntity );
+public:
+ DECLARE_DATADESC();
+
+ void Spawn( void );
+
+// IHasBuildPoints
+public:
+ virtual int GetNumBuildPoints( void ) const;
+ virtual bool CanBuildObjectOnBuildPoint( int iPoint, int iObjectType );
+ virtual bool GetBuildPoint( int iPoint, Vector &vecOrigin, QAngle &vecAngles );
+ virtual int GetBuildPointAttachmentIndex( int iPoint ) const;
+ virtual void SetObjectOnBuildPoint( int iPoint, CBaseObject *pObject );
+ virtual int GetNumObjectsOnMe( void );
+ virtual CBaseEntity *GetFirstObjectOnMe( void );
+ virtual CBaseObject *GetObjectOfTypeOnMe( int iObjectType );
+ virtual int FindObjectOnBuildPoint( CBaseObject *pObject );
+ virtual void GetExitPoint( CBaseEntity *pPlayer, int iPoint, Vector *pAbsOrigin, QAngle *pAbsAngles );
+ virtual void RemoveAllObjects( void );
+ virtual float GetMaxSnapDistance( int iPoint ) { return 128; }
+ virtual bool ShouldCheckForMovement( void ) { return false; }
+};
+
+//-----------------------------------------------------------------------------
+// Purpose: Vgui screen for vehicle buying
+//-----------------------------------------------------------------------------
+class CVGuiScreenVehicleBay : public CVGuiScreen
+{
+ DECLARE_CLASS( CVGuiScreenVehicleBay, CVGuiScreen );
+public:
+ DECLARE_DATADESC();
+
+ virtual void Activate( void );
+
+ void BuildVehicle( CBaseTFPlayer *pPlayer, int iObjectType );
+ void FinishedBuildVehicle( CBaseObject *pObject );
+ void SetBuildPoint( Vector &vecOrigin, QAngle &vecAngles );
+
+ void BayThink( void );
+
+private:
+ bool m_bBayIsClear;
+ Vector m_vecBuildPointOrigin;
+ QAngle m_vecBuildPointAngles;
+
+ // Outputs
+ COutputEvent m_OnStartedBuild;
+ COutputEvent m_OnFinishedBuild;
+ COutputEvent m_OnReadyToBuildAgain;
+};
+
+#endif // INFO_VEHICLE_BAY_H
diff --git a/game/server/tf2/mapdata_server.cpp b/game/server/tf2/mapdata_server.cpp
new file mode 100644
index 0000000..258f580
--- /dev/null
+++ b/game/server/tf2/mapdata_server.cpp
@@ -0,0 +1,115 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#include "cbase.h"
+
+#include "mapdata_shared.h"
+#include "sharedinterface.h"
+#include "baseentity.h"
+#include "world.h"
+#include "player.h"
+
+class CMapData_Server : public IMapData
+{
+public:
+
+ // World data queries.
+ void GetMapBounds( Vector &vecMins, Vector &vecMaxs );
+ void GetMapOrigin( Vector &vecOrigin );
+ void GetMapSize( Vector &vecSize );
+
+ // 3D Skybox data queries.
+ void Get3DSkyboxOrigin( Vector &vecOrigin );
+ float Get3DSkyboxScale( void );
+};
+
+static CMapData_Server g_MapData;
+IMapData *g_pMapData = &g_MapData;
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CMapData_Server::GetMapBounds( Vector &vecMins, Vector &vecMaxs )
+{
+ CWorld *pWorld = static_cast<CWorld*>( GetWorldEntity() );
+ if ( pWorld )
+ {
+ // Get the world bounds.
+ pWorld->GetWorldBounds( vecMins, vecMaxs );
+
+ // Backward compatability...
+ if ( ( vecMins.LengthSqr() == 0.0f ) && ( vecMaxs.LengthSqr() == 0.0f ) )
+ {
+ vecMins.Init( -6500.0f, -6500.0f, -6500.0f );
+ vecMaxs.Init( 6500.0f, 6500.0f, 6500.0f );
+ }
+ }
+ else
+ {
+ Assert( 0 );
+ vecMins.Init( 0.0f, 0.0f, 0.0f );
+ vecMaxs.Init( 1.0f, 1.0f, 1.0f );
+ }
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CMapData_Server::GetMapOrigin( Vector &vecOrigin )
+{
+ Vector vecMins, vecMaxs;
+ GetMapBounds( vecMins, vecMaxs );
+ VectorAdd( vecMins, vecMaxs, vecOrigin );
+ VectorMultiply( vecOrigin, 0.5f, vecOrigin );
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CMapData_Server::GetMapSize( Vector &vecSize )
+{
+ Vector vecMins, vecMaxs;
+ GetMapBounds( vecMins, vecMaxs );
+ VectorSubtract( vecMaxs, vecMins, vecSize );
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CMapData_Server::Get3DSkyboxOrigin( Vector &vecOrigin )
+{
+ // NOTE: If the player hasn't been created yet -- this doesn't work!!!
+ // We need to pass the data along in the map - requires a tool change.
+ CBasePlayer *pPlayer = UTIL_GetLocalPlayer();
+ if ( pPlayer )
+ {
+ CPlayerLocalData *pLocalData = &pPlayer->m_Local;
+ VectorCopy( pLocalData->m_skybox3d.origin, vecOrigin );
+ }
+ else
+ {
+ // Debugging!
+ Assert( 0 );
+ vecOrigin.Init();
+ }
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+float CMapData_Server::Get3DSkyboxScale( void )
+{
+ // NOTE: If the player hasn't been created yet -- this doesn't work!!!
+ // We need to pass the data along in the map - requires a tool change.
+ CBasePlayer *pPlayer = UTIL_GetLocalPlayer();
+ if ( pPlayer )
+ {
+ CPlayerLocalData *pLocalData = &pPlayer->m_Local;
+ return pLocalData->m_skybox3d.scale;
+ }
+ else
+ {
+ // Debugging!
+ Assert( 0 );
+ return 1.0f;
+ }
+}
diff --git a/game/server/tf2/menu_base.cpp b/game/server/tf2/menu_base.cpp
new file mode 100644
index 0000000..e08adaa
--- /dev/null
+++ b/game/server/tf2/menu_base.cpp
@@ -0,0 +1,262 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Ugly menus for prototyping
+//
+// $NoKeywords: $
+//=============================================================================//
+#include "cbase.h"
+#include "player.h"
+#include "tf_player.h"
+#include "menu_base.h"
+#include "tf_team.h"
+#include "baseviewmodel.h"
+#include "tf_gamerules.h"
+#include "tf_class_infiltrator.h"
+#include "tier1/strtools.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+// Global list of menus
+CMenu *gMenus[MENU_LAST];
+
+//-----------------------------------------------------------------------------
+// Purpose: Initialize the Global Menu structure
+//-----------------------------------------------------------------------------
+void InitializeMenus( void )
+{
+ gMenus[MENU_DEFAULT] = NULL;
+ gMenus[MENU_TEAM] = new CMenuTeam();
+ gMenus[MENU_CLASS] = new CMenuClass();
+}
+
+void DestroyMenus( void )
+{
+ delete gMenus[MENU_DEFAULT];
+ delete gMenus[MENU_TEAM];
+ delete gMenus[MENU_CLASS];
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Base Menu Handling
+//-----------------------------------------------------------------------------
+CMenu::CMenu()
+{
+ memset( m_szMenuString, 0, sizeof(m_szMenuString) );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CMenu::Display( CBaseTFPlayer *pViewer, int allowed, int display_time )
+{
+ RecalculateMenu( pViewer );
+
+ // Only display if the menu's not identical to the one the player's already seeing
+ if ( pViewer->m_MenuUpdateTime > gpGlobals->curtime )
+ {
+ if ( (allowed == pViewer->m_MenuSelectionBuffer) && FStrEq( m_szMenuString, pViewer->m_MenuStringBuffer) )
+ return;
+ }
+ pViewer->m_MenuUpdateTime = gpGlobals->curtime + 10;
+ pViewer->m_MenuSelectionBuffer = allowed;
+
+ const char *msg_portion = m_szMenuString;
+ Q_strncpy( pViewer->m_MenuStringBuffer, m_szMenuString, MENU_STRING_BUFFER_SIZE );
+
+ CSingleUserRecipientFilter user( pViewer );
+ user.MakeReliable();
+
+ while ( strlen(msg_portion) >= MENU_MSG_TEXTCHUNK_SIZE )
+ {
+ // split the string
+ char sbuf[MENU_MSG_TEXTCHUNK_SIZE+1];
+ Q_strncpy( sbuf, msg_portion, MENU_MSG_TEXTCHUNK_SIZE+1 );
+ msg_portion += MENU_MSG_TEXTCHUNK_SIZE;
+
+
+ // send the string portion
+ UserMessageBegin( user, "ShowMenu" );
+ WRITE_WORD( allowed );
+ WRITE_CHAR( display_time ); // display time (-1 means unlimited)
+ WRITE_BYTE( TRUE ); // there is more message to come
+ WRITE_STRING( sbuf );
+ MessageEnd();
+ }
+
+ // send the remaining string
+ UserMessageBegin( user, "ShowMenu" );
+ WRITE_WORD( allowed );
+ WRITE_CHAR( display_time ); // display time (-1 means unlimited)
+ WRITE_BYTE( FALSE ); // there is no more message to come
+ WRITE_STRING( (char*)msg_portion );
+ MessageEnd();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CMenu::RecalculateMenu( CBaseTFPlayer *pViewer )
+{
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CMenu::Input( CBaseTFPlayer *pViewer, int iInput )
+{
+ return false;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Team Menu
+//-----------------------------------------------------------------------------
+CMenuTeam::CMenuTeam()
+{
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CMenuTeam::RecalculateMenu( CBaseTFPlayer *pViewer )
+{
+ Q_strncpy( m_szMenuString, "Pick a Team: \n\n\n->1. Humans\n\n->2. Aliens\n", sizeof(m_szMenuString) );
+
+ // Allow aborting if they have a class
+ if ( pViewer->GetTeam() )
+ {
+ Q_strncat( m_szMenuString, "\n\n->9. Don't change team.\n", sizeof(m_szMenuString), COPY_ALL_CHARACTERS );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Handle input for the Team menu
+//-----------------------------------------------------------------------------
+bool CMenuTeam::Input( CBaseTFPlayer *pViewer, int iInput )
+{
+ // Allow aborting if they have a team
+ if ( pViewer->GetTeam() && iInput == 9 )
+ {
+ pViewer->MenuReset();
+ return true;
+ }
+
+ if (iInput < 0 || iInput >= GetNumberOfTeams())
+ return false;
+
+ // Ignore changeteam requests to their current team
+ if ( pViewer->GetTeam() )
+ {
+ if ( iInput == pViewer->GetTeam()->GetTeamNumber() )
+ {
+ pViewer->MenuReset();
+ return true;
+ }
+ }
+
+ // Add the player to the team and then bring up the Class Menu
+ pViewer->ChangeTeam( iInput );
+
+ // Clear out the class
+ if ( pViewer->GetPlayerClass() )
+ {
+ // Remove all the player's items
+ pViewer->RemoveAllItems( false );
+ pViewer->HideViewModels();
+ pViewer->ClearPlayerClass();
+ }
+
+ pViewer->ForceRespawn();
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Class Menu
+//-----------------------------------------------------------------------------
+CMenuClass::CMenuClass()
+{
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CMenuClass::RecalculateMenu( CBaseTFPlayer *pViewer )
+{
+ if ( !pViewer->GetTeam() )
+ return;
+
+ Q_snprintf( m_szMenuString,sizeof(m_szMenuString), "You are on Team %s\n\n\n\n\n\n\nPick your Class: \n\n\n", pViewer->GetTeam()->GetName() );
+
+ int iClassNum = 1;
+
+ // Check technology for each class
+ for ( int i = 0; i < TFCLASS_CLASS_COUNT; i++ )
+ {
+ char sClass[256];
+
+ if ( !( pViewer->IsClassAvailable( (TFClass)i ) ) )
+ continue;
+
+ int iNumber = pViewer->GetTFTeam()->GetNumOfClass( (TFClass)i );
+ if ( !iNumber )
+ {
+ Q_snprintf( sClass, sizeof(sClass), "->%d. %s\n\n", iClassNum, GetTFClassInfo( i )->m_pClassName );
+ }
+ else
+ {
+ Q_snprintf( sClass, sizeof(sClass), "->%d. %s (%d in your team)\n\n", iClassNum, GetTFClassInfo( i )->m_pClassName, iNumber );
+ }
+
+ Q_strncat( m_szMenuString, sClass,sizeof(m_szMenuString), COPY_ALL_CHARACTERS );
+ iClassNum++;
+ }
+
+ // Allow aborting if they have a class
+ if ( pViewer->GetPlayerClass() )
+ {
+ Q_strncat( m_szMenuString, "\n\n->9. Don't change class.\n", sizeof(m_szMenuString), COPY_ALL_CHARACTERS );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Handle input for the Class menu
+//-----------------------------------------------------------------------------
+bool CMenuClass::Input( CBaseTFPlayer *pViewer, int iInput )
+{
+ int iClassNum = 0;
+
+ // Allow aborting if they have a class
+ if ( pViewer->GetPlayerClass() && iInput == 9 )
+ {
+ pViewer->MenuReset();
+ return true;
+ }
+
+ // Get the class number
+ for ( int i = 1; iInput && i < TFCLASS_CLASS_COUNT; i++ )
+ {
+ if ( !( pViewer->IsClassAvailable( (TFClass)i ) ) )
+ continue;
+ iInput--;
+ iClassNum = i;
+ }
+
+ // Ignore changeclass requests to their current class
+ if ( pViewer->GetPlayerClass() )
+ {
+ if ( (TFClass)iClassNum == pViewer->PlayerClass() )
+ {
+ pViewer->MenuReset();
+ return true;
+ }
+ }
+
+ if ( !pViewer->IsClassAvailable( (TFClass)iClassNum ) )
+ return false;
+
+ pViewer->ChangeClass( (TFClass)iClassNum );
+ pViewer->m_pCurrentMenu = NULL;
+ return true;
+} \ No newline at end of file
diff --git a/game/server/tf2/menu_base.h b/game/server/tf2/menu_base.h
new file mode 100644
index 0000000..4566296
--- /dev/null
+++ b/game/server/tf2/menu_base.h
@@ -0,0 +1,77 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $Workfile: $
+// $Date: $
+//
+//-----------------------------------------------------------------------------
+// $Log: $
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef MENU_BASE_H
+#define MENU_BASE_H
+#pragma once
+
+class CBasePlayer;
+class CMenu;
+
+enum
+{
+ MENU_DEFAULT = 0,
+ MENU_TEAM,
+ MENU_CLASS,
+
+ // Insert new Menus here
+ MENU_LAST, // Total Number of menus
+};
+
+// Global list of menus
+extern CMenu *gMenus[];
+
+//-----------------------------------------------------------------------------
+// Purpose: Base Menu Class
+//-----------------------------------------------------------------------------
+class CMenu
+{
+public:
+ CMenu();
+
+ virtual void RecalculateMenu( CBaseTFPlayer *pViewer );
+ virtual void Display( CBaseTFPlayer *pViewer, int allowed = 0xFFFF, int display_time = -1 );
+ virtual bool Input( CBaseTFPlayer *pViewer, int iInput );
+
+protected:
+ char m_szMenuString[1024];
+};
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Team Menu
+//-----------------------------------------------------------------------------
+class CMenuTeam : public CMenu
+{
+public:
+ CMenuTeam();
+
+ virtual void RecalculateMenu( CBaseTFPlayer *pViewer );
+ virtual bool Input( CBaseTFPlayer *pViewer, int iInput );
+};
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Class Menu
+//-----------------------------------------------------------------------------
+class CMenuClass : public CMenu
+{
+public:
+ CMenuClass();
+
+ virtual void RecalculateMenu( CBaseTFPlayer *pViewer );
+ virtual bool Input( CBaseTFPlayer *pViewer, int iInput );
+};
+
+
+#endif // MENU_BASE_H
diff --git a/game/server/tf2/mortar_round.cpp b/game/server/tf2/mortar_round.cpp
new file mode 100644
index 0000000..38f72b9
--- /dev/null
+++ b/game/server/tf2/mortar_round.cpp
@@ -0,0 +1,257 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#include "cbase.h"
+#include "mortar_round.h"
+#include "engine/IEngineSound.h"
+#include "tf_gamerules.h"
+#include "tf_team.h"
+
+
+
+// Damage CVars
+ConVar weapon_mortar_shell_damage( "weapon_mortar_shell_damage","0", FCVAR_NONE, "Mortar's standard shell maximum damage" );
+ConVar weapon_mortar_shell_radius( "weapon_mortar_shell_radius","0", FCVAR_NONE, "Mortar's standard shell splash radius" );
+ConVar weapon_mortar_starburst_damage( "weapon_mortar_starburst_damage","0", FCVAR_NONE, "Mortar's starburst maximum damage" );
+ConVar weapon_mortar_starburst_radius( "weapon_mortar_starburst_radius","0", FCVAR_NONE, "Mortar's starburst splash radius" );
+ConVar weapon_mortar_cluster_shells( "weapon_mortar_cluster_shells","0", FCVAR_NONE, "Number of shells a mortar cluster round bursts into" );
+
+//=====================================================================================================
+// MORTAR ROUND
+//=====================================================================================================
+BEGIN_DATADESC( CMortarRound )
+
+ // Function Pointers
+ DEFINE_FUNCTION( MissileTouch ),
+ DEFINE_FUNCTION( FallThink ),
+
+END_DATADESC()
+
+LINK_ENTITY_TO_CLASS( mortar_round, CMortarRound );
+PRECACHE_WEAPON_REGISTER(mortar_round);
+
+CMortarRound::CMortarRound()
+{
+ m_pSmokeTrail = NULL;
+ m_pLauncher = NULL;
+ m_iRoundType = MA_SHELL;
+ UseClientSideAnimation();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CMortarRound::Precache( void )
+{
+ PrecacheModel( "models/weapons/w_grenade.mdl" );
+
+ PrecacheScriptSound( "MortarRound.StopSound" );
+ PrecacheScriptSound( "MortarRound.IncomingSound" );
+
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CMortarRound::Spawn( void )
+{
+ Precache();
+
+ SetMoveType( MOVETYPE_FLYGRAVITY );
+ SetSolid( SOLID_BBOX );
+ SetModel( "models/weapons/w_grenade.mdl" );
+ UTIL_SetSize( this, vec3_origin, vec3_origin );
+
+ SetTouch( MissileTouch );
+ SetCollisionGroup( TFCOLLISION_GROUP_WEAPON );
+
+ // Trail smoke
+ m_pSmokeTrail = SmokeTrail::CreateSmokeTrail();
+ if ( m_pSmokeTrail )
+ {
+ m_pSmokeTrail->m_SpawnRate = 90;
+ m_pSmokeTrail->m_ParticleLifetime = 1.5;
+ m_pSmokeTrail->m_StartColor.Init(0.0, 0.0, 0.0);
+ m_pSmokeTrail->m_EndColor.Init( 0.5,0.5,0.5 );
+ m_pSmokeTrail->m_StartSize = 10;
+ m_pSmokeTrail->m_EndSize = 50;
+ m_pSmokeTrail->m_SpawnRadius = 1;
+ m_pSmokeTrail->m_MinSpeed = 15;
+ m_pSmokeTrail->m_MaxSpeed = 25;
+ m_pSmokeTrail->SetLifetime(15);
+ m_pSmokeTrail->FollowEntity( this );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CMortarRound::MissileTouch( CBaseEntity *pOther )
+{
+ CMortarRound *pRound = dynamic_cast< CMortarRound* >(pOther);
+ if ( pRound )
+ return;
+
+ // Stop emitting smoke/sound
+ m_pSmokeTrail->SetEmit(false);
+ EmitSound( "MortarRound.StopSound" );
+
+ // Create an explosion.
+ if ( m_iRoundType == MA_STARBURST )
+ {
+ // Small explosion, blind people in the area
+ float flBlind = 0;
+
+ // Shift it up a bit for the explosion
+ SetLocalOrigin( GetAbsOrigin() + Vector(0,0,32) );
+ CPASFilter filter( GetAbsOrigin() );
+ te->Explosion( filter, 0.0, &GetAbsOrigin(), g_sModelIndexFireball, 3.0, 15, TE_EXPLFLAG_NONE, 512, 100 );
+ RadiusDamage( CTakeDamageInfo( this, GetOwnerEntity(), weapon_mortar_starburst_damage.GetFloat(), DMG_BLAST ), GetAbsOrigin(), weapon_mortar_starburst_radius.GetFloat(), CLASS_NONE, NULL );
+
+ // Blind all players nearby
+ for ( int i = 1; i <= gpGlobals->maxClients; i++ )
+ {
+ CBaseTFPlayer *pPlayer = ToBaseTFPlayer( UTIL_PlayerByIndex(i) );
+ if ( pPlayer )
+ {
+ Vector vecSrc = pPlayer->EyePosition();
+ Vector vecToTarget = (vecSrc - GetAbsOrigin());
+ float flLength = VectorNormalize( vecToTarget );
+ // If the player's looking at the grenade, blind him a lot
+ if ( flLength < 2048 )
+ {
+ Vector forward, right, up;
+ AngleVectors( pPlayer->pl.v_angle, &forward, &right, &up );
+ float flDot = DotProduct( vecToTarget, forward );
+
+// Msg( "Dot: %f\n", flDot );
+
+ // Make sure it's in front of the player
+ if ( flDot < 0.0f )
+ {
+ flDot = fabs( DotProduct(vecToTarget, right ) ) + fabs( DotProduct(vecToTarget, up ) );
+
+ // Open LOS?
+ trace_t tr;
+ UTIL_TraceLine( vecSrc, GetAbsOrigin(), MASK_SHOT, this, COLLISION_GROUP_NONE, &tr );
+ if ( ( tr.fraction == 1.0f ) || ( tr.m_pEnt == pPlayer ) )
+ {
+ flBlind = flDot;
+ }
+ else
+ {
+ // Blocked blind is only half as effective
+ flBlind = flDot * 0.5;
+ }
+ }
+ else if ( flLength < 512 )
+ {
+ // Otherwise, if the player's near the grenade blind him a little
+ flBlind = 0.2;
+ }
+
+
+ // Flash the screen red
+ color32 white = {255,255,255, 255};
+ white.a = MIN( (flBlind * 255), 255 );
+ UTIL_ScreenFade( pPlayer, white, 0.3, 5.0, 0 );
+ }
+ }
+ }
+
+ UTIL_Remove( this );
+ }
+ else
+ {
+ // Large explosion
+
+ // Shift it up a bit for the explosion
+ SetLocalOrigin( GetAbsOrigin() + Vector(0,0,64) );
+ CPASFilter filter( GetAbsOrigin() );
+ te->Explosion( filter, 0.0, &GetAbsOrigin(), g_sModelIndexFireball, 10.0, 15, TE_EXPLFLAG_NONE, 512, 300 );
+ RadiusDamage( CTakeDamageInfo( this, GetOwnerEntity(), weapon_mortar_shell_damage.GetFloat(), DMG_BLAST ), GetAbsOrigin(), weapon_mortar_shell_radius.GetFloat(), CLASS_NONE, NULL );
+ UTIL_Remove( this );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Play a fall sound when the mortar round begins to fall
+//-----------------------------------------------------------------------------
+void CMortarRound::FallThink( void )
+{
+ if ( m_pLauncher )
+ {
+ CRecipientFilter filter;
+ filter.AddAllPlayers();
+ EmitSound( filter, entindex(), "MortarRound.IncomingSound" );
+
+ // Cluster bombs split up in the air
+ if ( m_iRoundType == MA_CLUSTER )
+ {
+ Vector forward, right;
+ QAngle angles;
+ VectorAngles( GetAbsVelocity(), angles );
+ SetLocalAngles( angles );
+ AngleVectors( GetLocalAngles(), &forward, &right, NULL );
+ for ( int i = 0; i < weapon_mortar_cluster_shells.GetInt(); i++ )
+ {
+ Vector vecVelocity = GetAbsVelocity();
+ vecVelocity += forward * random->RandomFloat( -200, 200 );
+ vecVelocity += right * random->RandomFloat( -200, 200 );
+
+ CMortarRound *pRound = CMortarRound::Create( GetAbsOrigin(), vecVelocity, GetOwnerEntity() ? GetOwnerEntity()->edict() : NULL );
+ pRound->SetLauncher( m_pLauncher );
+ pRound->ChangeTeam( GetTeamNumber() );
+ pRound->m_iRoundType = MA_SHELL;
+ }
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Keep a pointer to the Launcher
+//-----------------------------------------------------------------------------
+void CMortarRound::SetLauncher( CVehicleMortar *pLauncher )
+{
+ m_pLauncher = pLauncher;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Set the point at which we should start playing the fall sound
+//-----------------------------------------------------------------------------
+void CMortarRound::SetFallTime( float flFallTime )
+{
+ SetThink( FallThink );
+ SetNextThink( gpGlobals->curtime + flFallTime );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Set the round's type
+//-----------------------------------------------------------------------------
+void CMortarRound::SetRoundType( int iRoundType )
+{
+ m_iRoundType = iRoundType;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Create a missile
+//-----------------------------------------------------------------------------
+CMortarRound* CMortarRound::Create( const Vector &vecOrigin, const Vector &vecVelocity, edict_t *pentOwner = NULL )
+{
+ CMortarRound *pGrenade = (CMortarRound*)CreateEntityByName("mortar_round");
+
+ UTIL_SetOrigin( pGrenade, vecOrigin );
+ pGrenade->SetOwnerEntity( Instance( pentOwner ) );
+ pGrenade->Spawn();
+ pGrenade->SetAbsVelocity( vecVelocity );
+ QAngle angles;
+ VectorAngles( vecVelocity, angles );
+ pGrenade->SetLocalAngles( angles );
+
+ return pGrenade;
+}
+
diff --git a/game/server/tf2/mortar_round.h b/game/server/tf2/mortar_round.h
new file mode 100644
index 0000000..a4ecadb
--- /dev/null
+++ b/game/server/tf2/mortar_round.h
@@ -0,0 +1,48 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef MORTAR_ROUND_H
+#define MORTAR_ROUND_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+
+#include "tf_vehicle_mortar.h"
+#include "smoke_trail.h"
+
+
+class CMortarRound : public CBaseAnimating
+{
+public:
+ DECLARE_CLASS( CMortarRound, CBaseAnimating );
+
+ CMortarRound();
+
+ DECLARE_DATADESC();
+
+public:
+ virtual void Spawn( void );
+ virtual void Precache( void );
+ virtual void MissileTouch( CBaseEntity *pOther );
+ virtual void FallThink( void );
+ virtual void SetLauncher( CVehicleMortar *pLauncher );
+ virtual void SetFallTime( float flFallTime );
+ virtual void SetRoundType( int iRoundType );
+
+ // Damage type accessors
+ virtual int GetDamageType() const { return DMG_BLAST; }
+
+ static CMortarRound* CMortarRound::Create( const Vector &vecOrigin, const Vector &vecVelocity, edict_t *pentOwner );
+
+ SmokeTrail *m_pSmokeTrail;
+ CHandle<CVehicleMortar> m_pLauncher;
+ int m_iRoundType;
+};
+
+
+#endif // MORTAR_ROUND_H
diff --git a/game/server/tf2/npc_bug_builder.cpp b/game/server/tf2/npc_bug_builder.cpp
new file mode 100644
index 0000000..e2bdfe2
--- /dev/null
+++ b/game/server/tf2/npc_bug_builder.cpp
@@ -0,0 +1,577 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: The builder bug
+//
+// $NoKeywords: $
+//=============================================================================//
+#include "cbase.h"
+#include "AI_Task.h"
+#include "AI_Default.h"
+#include "AI_Schedule.h"
+#include "AI_Hull.h"
+#include "AI_Hint.h"
+#include "AI_Navigator.h"
+#include "activitylist.h"
+#include "soundent.h"
+#include "game.h"
+#include "NPCEvent.h"
+#include "tf_player.h"
+#include "EntityList.h"
+#include "ndebugoverlay.h"
+#include "shake.h"
+#include "monstermaker.h"
+#include "decals.h"
+#include "vstdlib/random.h"
+#include "tf_obj.h"
+#include "engine/IEngineSound.h"
+#include "IEffects.h"
+#include "npc_bug_builder.h"
+#include "npc_bug_hole.h"
+
+ConVar npc_bug_builder_health( "npc_bug_builder_health", "100" );
+
+BEGIN_DATADESC( CNPC_Bug_Builder )
+
+ DEFINE_FIELD( m_flIdleDelay, FIELD_FLOAT ),
+
+END_DATADESC()
+
+LINK_ENTITY_TO_CLASS( npc_bug_builder, CNPC_Bug_Builder );
+IMPLEMENT_CUSTOM_AI( npc_bug_builder, CNPC_Bug_Builder );
+
+// Dawdling details
+// Max & Min distances for dawdle forward movement
+#define DAWDLE_MIN_DIST 64
+#define DAWDLE_MAX_DIST 1024
+
+//==================================================
+// Bug Conditions
+//==================================================
+enum BugConditions
+{
+ COND_BBUG_RETURN_TO_BUGHOLE = LAST_SHARED_CONDITION,
+};
+
+//==================================================
+// Bug Schedules
+//==================================================
+
+enum BugSchedules
+{
+ SCHED_BBUG_FLEE_ENEMY = LAST_SHARED_SCHEDULE,
+ SCHED_BBUG_RETURN_TO_BUGHOLE,
+ SCHED_BBUG_RETURN_TO_BUGHOLE_AND_REMOVE,
+ SCHED_BBUG_DAWDLE,
+};
+
+//==================================================
+// Bug Tasks
+//==================================================
+
+enum BugTasks
+{
+ TASK_BBUG_GET_PATH_TO_FLEE = LAST_SHARED_TASK,
+ TASK_BBUG_GET_PATH_TO_BUGHOLE,
+ TASK_BBUG_HOLE_REMOVE,
+ TASK_BBUG_GET_PATH_TO_DAWDLE,
+ TASK_BBUG_FACE_DAWDLE,
+};
+
+//==================================================
+// Bug Activities
+//==================================================
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CNPC_Bug_Builder::CNPC_Bug_Builder( void )
+{
+ m_flFieldOfView = 0.5f;
+ m_flIdleDelay = 0.0f;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Setup our schedules and tasks, etc.
+//-----------------------------------------------------------------------------
+void CNPC_Bug_Builder::InitCustomSchedules( void )
+{
+ INIT_CUSTOM_AI( CNPC_Bug_Builder );
+
+ // Schedules
+ ADD_CUSTOM_SCHEDULE( CNPC_Bug_Builder, SCHED_BBUG_FLEE_ENEMY );
+ ADD_CUSTOM_SCHEDULE( CNPC_Bug_Builder, SCHED_BBUG_RETURN_TO_BUGHOLE );
+ ADD_CUSTOM_SCHEDULE( CNPC_Bug_Builder, SCHED_BBUG_RETURN_TO_BUGHOLE_AND_REMOVE );
+ ADD_CUSTOM_SCHEDULE( CNPC_Bug_Builder, SCHED_BBUG_DAWDLE );
+
+ // Conditions
+ ADD_CUSTOM_CONDITION( CNPC_Bug_Builder, COND_BBUG_RETURN_TO_BUGHOLE );
+
+ // Tasks
+ ADD_CUSTOM_TASK( CNPC_Bug_Builder, TASK_BBUG_GET_PATH_TO_FLEE );
+ ADD_CUSTOM_TASK( CNPC_Bug_Builder, TASK_BBUG_GET_PATH_TO_BUGHOLE );
+ ADD_CUSTOM_TASK( CNPC_Bug_Builder, TASK_BBUG_HOLE_REMOVE );
+ ADD_CUSTOM_TASK( CNPC_Bug_Builder, TASK_BBUG_GET_PATH_TO_DAWDLE );
+ ADD_CUSTOM_TASK( CNPC_Bug_Builder, TASK_BBUG_FACE_DAWDLE );
+
+ // Activities
+ //ADD_CUSTOM_ACTIVITY( CNPC_Bug_Builder, ACT_BUG_WARRIOR_DISTRACT );
+
+ AI_LOAD_SCHEDULE( CNPC_Bug_Builder, SCHED_BBUG_FLEE_ENEMY );
+ AI_LOAD_SCHEDULE( CNPC_Bug_Builder, SCHED_BBUG_RETURN_TO_BUGHOLE );
+ AI_LOAD_SCHEDULE( CNPC_Bug_Builder, SCHED_BBUG_RETURN_TO_BUGHOLE_AND_REMOVE );
+ AI_LOAD_SCHEDULE( CNPC_Bug_Builder, SCHED_BBUG_DAWDLE );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_Bug_Builder::Spawn( void )
+{
+ Precache();
+
+ SetModel( BUG_BUILDER_MODEL );
+
+ SetHullType(HULL_TINY);
+ SetHullSizeNormal();
+ SetDefaultEyeOffset();
+ SetViewOffset( (WorldAlignMins() + WorldAlignMaxs()) * 0.5 ); // See from my center
+ SetDistLook( 1024.0 );
+ m_flNextDawdle = 0;
+
+ SetNavType(NAV_GROUND);
+ m_NPCState = NPC_STATE_NONE;
+ SetBloodColor( BLOOD_COLOR_YELLOW );
+ m_iHealth = npc_bug_builder_health.GetFloat();
+
+ SetSolid( SOLID_BBOX );
+ AddSolidFlags( FSOLID_NOT_STANDABLE );
+ SetMoveType( MOVETYPE_STEP );
+
+ CapabilitiesAdd( bits_CAP_MOVE_GROUND );
+
+ NPCInit();
+
+ BaseClass::Spawn();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_Bug_Builder::Precache( void )
+{
+ PrecacheModel( BUG_BUILDER_MODEL );
+
+ PrecacheScriptSound( "NPC_Bug_Builder.Idle" );
+ PrecacheScriptSound( "NPC_Bug_Builder.Pain" );
+
+ BaseClass::Precache();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Output : int
+//-----------------------------------------------------------------------------
+int CNPC_Bug_Builder::SelectSchedule( void )
+{
+ // If I'm not in idle anymore, don't idle
+ if ( m_NPCState != NPC_STATE_IDLE )
+ {
+ m_flNextDawdle = 0;
+ }
+
+ switch ( m_NPCState )
+ {
+ case NPC_STATE_IDLE:
+ {
+ // BugHole might be requesting help
+ if ( HasCondition( COND_BBUG_RETURN_TO_BUGHOLE ) )
+ return SCHED_BBUG_RETURN_TO_BUGHOLE;
+
+ // Setup to dawdle a bit from now
+ if ( !m_flNextDawdle )
+ {
+ m_flNextDawdle = gpGlobals->curtime + random->RandomFloat( 3.0, 5.0 );
+ }
+ else if ( m_flNextDawdle < gpGlobals->curtime )
+ {
+ m_flNextDawdle = 0;
+ return SCHED_BBUG_DAWDLE;
+ }
+
+ // When I take damage, I flee
+ if ( HasCondition( COND_LIGHT_DAMAGE | COND_HEAVY_DAMAGE ) )
+ return SCHED_BBUG_FLEE_ENEMY;
+
+ // Return to my bughole
+ //return SCHED_BBUG_RETURN_TO_BUGHOLE_AND_REMOVE;
+ break;
+ }
+ case NPC_STATE_ALERT:
+ {
+ // BugHole might be requesting help
+ if ( HasCondition( COND_BBUG_RETURN_TO_BUGHOLE ) )
+ return SCHED_BBUG_RETURN_TO_BUGHOLE;
+
+ // When I take damage, I flee
+ if ( HasCondition( COND_LIGHT_DAMAGE ) || HasCondition( COND_HEAVY_DAMAGE ) )
+ return SCHED_BBUG_FLEE_ENEMY;
+
+ break;
+ }
+ case NPC_STATE_COMBAT:
+ {
+ // Did I lose my enemy?
+ if ( HasCondition ( COND_LOST_ENEMY ) || HasCondition ( COND_ENEMY_UNREACHABLE ) )
+ {
+ SetEnemy( NULL );
+ SetState(NPC_STATE_IDLE);
+ return BaseClass::SelectSchedule();
+ }
+
+ // When I take damage, I flee
+ if ( HasCondition( COND_LIGHT_DAMAGE ) || HasCondition( COND_HEAVY_DAMAGE ) )
+ return SCHED_BBUG_FLEE_ENEMY;
+ }
+ break;
+ }
+
+ return BaseClass::SelectSchedule();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : *pTask -
+//-----------------------------------------------------------------------------
+void CNPC_Bug_Builder::StartTask( const Task_t *pTask )
+{
+ switch ( pTask->iTask )
+ {
+ case TASK_BBUG_GET_PATH_TO_FLEE:
+ {
+ // Always tell our bughole that we're under attack
+ if ( m_hMyBugHole )
+ {
+ m_hMyBugHole->IncomingFleeingBug( this );
+ }
+
+ // If we have no squad, or we couldn't get a path to our squadmate, move to our bughole
+ if ( m_hMyBugHole )
+ {
+ SetTarget( m_hMyBugHole );
+ AI_NavGoal_t goal( GOALTYPE_TARGETENT, vec3_origin, ACT_RUN );
+ if ( GetNavigator()->SetGoal( goal ) )
+ {
+ TaskComplete();
+ return;
+ }
+ }
+
+ TaskComplete();
+ }
+ break;
+
+ case TASK_BBUG_GET_PATH_TO_BUGHOLE:
+ {
+ // Get a path back to my bughole
+ // If we have no squad, or we couldn't get a path to our squadmate, look for a bughole
+ if ( m_hMyBugHole )
+ {
+ SetTarget( m_hMyBugHole );
+ AI_NavGoal_t goal( GOALTYPE_TARGETENT, vec3_origin, ACT_RUN );
+ if ( GetNavigator()->SetGoal( goal ) )
+ {
+ TaskComplete();
+ return;
+ }
+ }
+
+ TaskFail( "Couldn't get to bughole." );
+ }
+ break;
+
+ case TASK_BBUG_HOLE_REMOVE:
+ {
+ TaskComplete();
+
+ // Crawl inside the bughole and remove myself
+ AddEffects( EF_NODRAW );
+ AddSolidFlags( FSOLID_NOT_SOLID );
+ Event_Killed( CTakeDamageInfo( this, this, 200, DMG_CRUSH ) );
+
+ // Tell the bughole
+ if ( m_hMyBugHole )
+ {
+ m_hMyBugHole->BugReturned();
+ }
+ }
+ break;
+
+ case TASK_BBUG_GET_PATH_TO_DAWDLE:
+ {
+ // Get a dawdle point ahead of us
+ Vector vecForward, vecTarget;
+ AngleVectors( GetAbsAngles(), &vecForward );
+ VectorMA( GetAbsOrigin(), random->RandomFloat( DAWDLE_MIN_DIST, DAWDLE_MAX_DIST ), vecForward, vecTarget );
+
+ // See how far we could move ahead
+ trace_t tr;
+ UTIL_TraceEntity( this, GetAbsOrigin(), vecTarget, MASK_SOLID, &tr);
+ float flDistance = tr.fraction * (vecTarget - GetAbsOrigin()).Length();
+ if ( flDistance >= DAWDLE_MIN_DIST )
+ {
+ AI_NavGoal_t goal( tr.endpos );
+ GetNavigator()->SetGoal( goal );
+ }
+
+ TaskComplete();
+ }
+ break;
+
+ case TASK_BBUG_FACE_DAWDLE:
+ {
+ // Turn a random amount to the right
+ float flYaw = GetMotor()->GetIdealYaw();
+ flYaw = flYaw + random->RandomFloat( 45, 135 );
+ GetMotor()->SetIdealYaw( UTIL_AngleMod(flYaw) );
+ SetTurnActivity();
+ break;
+ }
+ break;
+
+ default:
+ BaseClass::StartTask( pTask );
+ break;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : *pTask -
+//-----------------------------------------------------------------------------
+void CNPC_Bug_Builder::RunTask( const Task_t *pTask )
+{
+ switch ( pTask->iTask )
+ {
+ case TASK_BBUG_FACE_DAWDLE:
+ {
+ GetMotor()->UpdateYaw();
+ if ( FacingIdeal() )
+ {
+ TaskComplete();
+ }
+ break;
+ }
+
+ default:
+ BaseClass::RunTask( pTask );
+ break;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CNPC_Bug_Builder::FValidateHintType(CAI_Hint *pHint)
+{
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : *pVictim -
+//-----------------------------------------------------------------------------
+void CNPC_Bug_Builder::Event_Killed( const CTakeDamageInfo &info )
+{
+ BaseClass::Event_Killed( info );
+
+ // Remove myself in a minute
+ if ( !ShouldFadeOnDeath() )
+ {
+ SetThink( SUB_Remove );
+ SetNextThink( gpGlobals->curtime + 20 );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : *pEvent -
+//-----------------------------------------------------------------------------
+void CNPC_Bug_Builder::HandleAnimEvent( animevent_t *pEvent )
+{
+ BaseClass::HandleAnimEvent( pEvent );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+float CNPC_Bug_Builder::MaxYawSpeed( void )
+{
+ return 2.0f;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_Bug_Builder::IdleSound( void )
+{
+ EmitSound( "NPC_Bug_Builder.Idle" );
+ m_flIdleDelay = gpGlobals->curtime + 4.0f;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_Bug_Builder::PainSound( const CTakeDamageInfo &info )
+{
+ EmitSound( "NPC_Bug_Builder.Pain" );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CNPC_Bug_Builder::ShouldPlayIdleSound( void )
+{
+ //Only do idles in the right states
+ if ( ( m_NPCState != NPC_STATE_IDLE && m_NPCState != NPC_STATE_ALERT ) )
+ return false;
+
+ //Gagged monsters don't talk
+ if ( HasSpawnFlags( SF_NPC_GAG ) )
+ return false;
+
+ //Don't cut off another sound or play again too soon
+ if ( m_flIdleDelay > gpGlobals->curtime )
+ return false;
+
+ //Randomize it a bit
+ if ( random->RandomInt( 0, 20 ) )
+ return false;
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_Bug_Builder::SetBugHole( CMaker_BugHole *pBugHole )
+{
+ m_hMyBugHole = pBugHole;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: BugHole is calling me home to defend it
+//-----------------------------------------------------------------------------
+void CNPC_Bug_Builder::ReturnToBugHole( void )
+{
+ SetCondition( COND_BBUG_RETURN_TO_BUGHOLE );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_Bug_Builder::AlertSound( void )
+{
+ if ( GetEnemy() )
+ {
+ //FIXME: We need a better solution for inner-squad alerts!!
+ //SOUND_DANGER is designed to frighten NPC's away. Need a different SOUND_ type.
+ CSoundEnt::InsertSound( SOUND_DANGER, GetEnemy()->GetAbsOrigin(), 1024, 0.5f, this );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Overridden for team handling
+//-----------------------------------------------------------------------------
+Disposition_t CNPC_Bug_Builder::IRelationType( CBaseEntity *pTarget )
+{
+ // Builders ignore everything
+ return D_NU;
+}
+
+
+//-----------------------------------------------------------------------------
+//
+// Schedules
+//
+//-----------------------------------------------------------------------------
+
+//=========================================================
+// Dawdle around
+//=========================================================
+AI_DEFINE_SCHEDULE
+(
+ SCHED_BBUG_DAWDLE,
+
+ " Tasks"
+ " TASK_SET_TOLERANCE_DISTANCE 32"
+ " TASK_BBUG_GET_PATH_TO_DAWDLE 0"
+ " TASK_RUN_PATH 0"
+ " TASK_WAIT_FOR_MOVEMENT 0"
+ " TASK_BBUG_FACE_DAWDLE 0"
+ " "
+ " Interrupts"
+ " COND_LIGHT_DAMAGE"
+ " COND_HEAVY_DAMAGE"
+);
+
+//=========================================================
+// Flee from our enemy
+//=========================================================
+AI_DEFINE_SCHEDULE
+(
+ SCHED_BBUG_FLEE_ENEMY,
+
+ " Tasks"
+ " TASK_SET_TOLERANCE_DISTANCE 128"
+ " TASK_BBUG_GET_PATH_TO_FLEE 0"
+ " TASK_RUN_PATH 0"
+ " TASK_WAIT_FOR_MOVEMENT 0"
+ " TASK_TURN_RIGHT 180"
+ " "
+ " Interrupts"
+ " COND_ENEMY_DEAD"
+ " COND_LOST_ENEMY"
+);
+
+//=========================================================
+// Retreat to a bughole
+//=========================================================
+AI_DEFINE_SCHEDULE
+(
+ SCHED_BBUG_RETURN_TO_BUGHOLE,
+
+ " Tasks"
+ " TASK_SET_TOLERANCE_DISTANCE 128"
+ " TASK_BBUG_GET_PATH_TO_BUGHOLE 0"
+ " TASK_RUN_PATH 0"
+ " TASK_WAIT_FOR_MOVEMENT 0"
+ " "
+ " Interrupts"
+ " COND_NEW_ENEMY"
+ " COND_HEAR_COMBAT"
+ " COND_HEAR_DANGER"
+);
+
+//=========================================================
+// Return to a bughole and remove myself
+//=========================================================
+AI_DEFINE_SCHEDULE
+(
+ SCHED_BBUG_RETURN_TO_BUGHOLE_AND_REMOVE,
+
+ " Tasks"
+ " TASK_WAIT 5" // Wait for 5-10 seconds to see if anything happens
+ " TASK_WAIT_RANDOM 5"
+ " TASK_SET_TOLERANCE_DISTANCE 128"
+ " TASK_BBUG_GET_PATH_TO_BUGHOLE 0"
+ " TASK_RUN_PATH 0"
+ " TASK_WAIT_FOR_MOVEMENT 0"
+ " TASK_BBUG_HOLE_REMOVE 0"
+ " "
+ " Interrupts"
+ " COND_NEW_ENEMY"
+ " COND_HEAR_COMBAT"
+ " COND_HEAR_DANGER"
+);
+
diff --git a/game/server/tf2/npc_bug_builder.h b/game/server/tf2/npc_bug_builder.h
new file mode 100644
index 0000000..cda1ead
--- /dev/null
+++ b/game/server/tf2/npc_bug_builder.h
@@ -0,0 +1,69 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef NPC_BUG_BUILDER_H
+#define NPC_BUG_BUILDER_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include "AI_BaseNPC.h"
+
+#define BUG_BUILDER_MODEL "models/npcs/bugs/bug_builder.mdl"
+
+class CMaker_BugHole;
+
+//-----------------------------------------------------------------------------
+// Purpose: BUILDER BUG
+//-----------------------------------------------------------------------------
+class CNPC_Bug_Builder : public CAI_BaseNPC
+{
+ DECLARE_CLASS( CNPC_Bug_Builder, CAI_BaseNPC );
+public:
+ CNPC_Bug_Builder( void );
+
+ virtual void Spawn( void );
+ virtual void Precache( void );
+
+ virtual int SelectSchedule( void );
+ virtual void StartTask( const Task_t *pTask );
+ virtual void RunTask( const Task_t *pTask );
+
+ virtual float MaxYawSpeed( void );
+ virtual void HandleAnimEvent( animevent_t *pEvent );
+ virtual void IdleSound( void );
+ virtual void PainSound( const CTakeDamageInfo &info );
+ virtual void AlertSound( void );
+ virtual bool FValidateHintType(CAI_Hint *pHint);
+
+ virtual bool ShouldPlayIdleSound( void );
+
+ virtual Class_T Classify( void ) { return CLASS_ANTLION; }
+ virtual int GetSoundInterests( void ) { return 0; }
+
+ DECLARE_DATADESC();
+
+ // BugHole handling
+ void SetBugHole( CMaker_BugHole *pBugHole );
+ void ReturnToBugHole( void );
+
+private:
+ virtual Disposition_t IRelationType( CBaseEntity *pTarget );
+
+ void Event_Killed( const CTakeDamageInfo &info );
+
+ float m_flIdleDelay;
+
+ float m_flNextDawdle;
+
+ // BugHole handling
+ CHandle< CMaker_BugHole > m_hMyBugHole;
+
+ DEFINE_CUSTOM_AI;
+};
+
+#endif // NPC_BUG_BUILDER_H
diff --git a/game/server/tf2/npc_bug_hole.cpp b/game/server/tf2/npc_bug_hole.cpp
new file mode 100644
index 0000000..98923b3
--- /dev/null
+++ b/game/server/tf2/npc_bug_hole.cpp
@@ -0,0 +1,392 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Bug hole
+//
+// $NoKeywords: $
+//=============================================================================//
+#include "cbase.h"
+#include "AI_Task.h"
+#include "AI_Default.h"
+#include "AI_Schedule.h"
+#include "AI_Hull.h"
+#include "AI_Hint.h"
+#include "activitylist.h"
+#include "soundent.h"
+#include "game.h"
+#include "NPCEvent.h"
+#include "tf_player.h"
+#include "EntityList.h"
+#include "ndebugoverlay.h"
+#include "shake.h"
+#include "monstermaker.h"
+#include "decals.h"
+#include "vstdlib/random.h"
+#include "tf_obj.h"
+#include "engine/IEngineSound.h"
+#include "IEffects.h"
+#include "npc_bug_warrior.h"
+#include "npc_bug_builder.h"
+#include "npc_bug_hole.h"
+
+LINK_ENTITY_TO_CLASS( npc_bughole, CMaker_BugHole );
+
+IMPLEMENT_SERVERCLASS_ST(CMaker_BugHole, DT_Maker_BugHole)
+END_SEND_TABLE();
+
+BEGIN_DATADESC( CMaker_BugHole )
+
+ DEFINE_KEYFIELD( m_iMaxPool, FIELD_INTEGER, "PoolSize" ),
+ DEFINE_KEYFIELD( m_flPoolRegenTime, FIELD_FLOAT, "PoolRegen" ),
+ DEFINE_KEYFIELD( m_flPatrolTime, FIELD_FLOAT, "PatrolTime" ),
+ DEFINE_KEYFIELD( m_iszPatrolPathName, FIELD_STRING, "PatrolName" ),
+ DEFINE_KEYFIELD( m_iMaxNumberOfPatrollers, FIELD_INTEGER, "MaxPatrollers" ),
+ DEFINE_KEYFIELD( m_iMaxNumberOfBuilders, FIELD_INTEGER, "MaxBuilders" ),
+
+END_DATADESC()
+
+// Maximum speed at which a bughole thinks. Regen/Spawn times faster than this won't make it work faster.
+#define BUGHOLE_THINK_SPEED 3.0
+
+static ConVar npc_bughole_health( "npc_bughole_health","300", FCVAR_NONE, "Bug hole's health." );
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CMaker_BugHole::CMaker_BugHole( void)
+{
+ m_iszNPCClassname_Warrior = MAKE_STRING( "npc_bug_warrior" );
+ m_iszNPCClassname_Builder = MAKE_STRING( "npc_bug_builder" );
+ m_iszNPCClassname = m_iszNPCClassname_Warrior;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CMaker_BugHole::Spawn( void )
+{
+ // Set these up before calling base spawn
+ m_spawnflags |= SF_NPCMAKER_INF_CHILD;
+ m_bDisabled = true;
+
+ // Start with a full pool
+ m_iPool = m_iMaxPool;
+
+ BaseClass::Spawn();
+
+ // Bug holes are destroyable
+ SetSolid( SOLID_BBOX );
+ SetModel( "models/npcs/bugs/bug_hole.mdl" );
+ m_takedamage = DAMAGE_YES;
+ m_iHealth = npc_bughole_health.GetInt();
+
+ // Setup spawn & regen times
+ m_flNextSpawnTime = 0;
+ m_flNextRegenTime = gpGlobals->curtime + m_flPoolRegenTime;
+ m_flNextPatrolTime = gpGlobals->curtime + m_flPatrolTime;
+
+ // Override the base class think, and think with some random so bugholes don't all think at the same time
+ SetThink ( BugHoleThink );
+ SetNextThink( gpGlobals->curtime + BUGHOLE_THINK_SPEED + random->RandomFloat( -0.5, 0.5 ) );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CMaker_BugHole::Precache( void )
+{
+ BaseClass::Precache();
+
+ PrecacheModel( "models/npcs/bugs/bug_hole.mdl" );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CMaker_BugHole::BugHoleThink( void )
+{
+ // Regenerate our bug pool
+ if ( m_flNextRegenTime < gpGlobals->curtime )
+ {
+ if ( m_iPool < m_iMaxPool )
+ {
+ m_iPool++;
+ }
+ m_flNextRegenTime += m_flPoolRegenTime;
+ }
+
+ // Spawn if we're set to
+ if ( m_flNextSpawnTime && m_flNextSpawnTime < gpGlobals->curtime )
+ {
+ m_flNextSpawnTime = 0;
+ MakeNPC();
+ }
+ else
+ {
+ // If I can see a player, try and spawn a bug
+ CBaseEntity *pList[100];
+ Vector vecDelta( 768, 768, 768 );
+ int count = UTIL_EntitiesInBox( pList, 32, GetAbsOrigin() - vecDelta, GetAbsOrigin() + vecDelta, FL_CLIENT|FL_OBJECT );
+ for ( int i = 0; i < count; i++ )
+ {
+ CBaseEntity *pEnt = pList[i];
+ if ( pEnt->IsAlive() && FVisible(pEnt) )
+ {
+ BugHoleUnderAttack();
+ break;
+ }
+ }
+ }
+
+ // Send out a patrol if we haven't spawned anything for a long time
+ if ( m_flNextPatrolTime < gpGlobals->curtime )
+ {
+ m_flNextPatrolTime = gpGlobals->curtime + m_flPatrolTime;
+ StartPatrol();
+ CheckBuilder();
+ }
+
+ SetNextThink( gpGlobals->curtime + BUGHOLE_THINK_SPEED );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Spawn a bug, if we're not waiting to spawn one already
+//-----------------------------------------------------------------------------
+void CMaker_BugHole::SpawnBug( float flTime )
+{
+ // If no time was passed in, spawn immediately
+ if ( !flTime )
+ {
+ MakeNPC();
+ }
+ else if ( !m_flNextSpawnTime )
+ {
+ m_flNextSpawnTime = gpGlobals->curtime + flTime;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CMaker_BugHole::SpawnWarrior( float flTime )
+{
+ m_iszNPCClassname = m_iszNPCClassname_Warrior;
+ SpawnBug( flTime );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CMaker_BugHole::SpawnBuilder( float flTime )
+{
+ m_iszNPCClassname = m_iszNPCClassname_Builder;
+ SpawnBug( flTime );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: BugHole has spotted a player near it
+//-----------------------------------------------------------------------------
+void CMaker_BugHole::BugHoleUnderAttack( void )
+{
+ // Call any patrollers back to defend the base
+ for ( int i = 0; i < m_aWarriorBugs.Size(); i++ )
+ {
+ if ( m_aWarriorBugs[i]->IsPatrolling() )
+ {
+ m_aWarriorBugs[i]->ReturnToBugHole();
+ }
+ }
+
+ // Try and spawn a warrior
+ SpawnWarrior( random->RandomFloat( 1.0, 3.0 ) );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Send a bug out to patrol
+//-----------------------------------------------------------------------------
+void CMaker_BugHole::StartPatrol( void )
+{
+ // Don't patrol if I don't have a patrol name
+ if ( !m_iszPatrolPathName || !m_iMaxNumberOfPatrollers )
+ return;
+
+ // If I don't have any children, spawn one for to patrol with
+ if ( m_nLiveChildren < m_iMaxNumberOfPatrollers )
+ {
+ SpawnWarrior(0);
+
+ // Think again to use the bug we just created
+ m_flNextPatrolTime = gpGlobals->curtime + 2.0;
+ }
+
+ // We might have failed due to having none in the pool
+ if ( !m_aWarriorBugs.Size() )
+ return;
+
+ // Count number of bugs patrolling
+ int i, iCount;
+ iCount = 0;
+ for ( i = 0; i < m_aWarriorBugs.Size(); i++ )
+ {
+ if ( m_aWarriorBugs[i]->IsPatrolling() )
+ {
+ iCount++;
+ }
+ }
+
+ // Find bugs willing to patrol
+ for ( i = 0; i < m_aWarriorBugs.Size(); i++ )
+ {
+ // Make sure we don't have too many
+ if ( iCount >= m_iMaxNumberOfPatrollers )
+ return;
+
+ if ( m_aWarriorBugs[i]->StartPatrolling( m_iszPatrolPathName ) )
+ {
+ iCount++;
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: See if we should spawn a builder bug
+//-----------------------------------------------------------------------------
+void CMaker_BugHole::CheckBuilder( void )
+{
+ // If my pool is full, and I have spaw builder spots, spawn a builder bug
+ /*
+ if ( m_iPool == m_iMaxPool && m_aBuilderBugs.Size() < m_iMaxNumberOfBuilders )
+ {
+ SpawnBuilder(0);
+ }
+ */
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Bughole makes multiple types of bugs
+//-----------------------------------------------------------------------------
+void CMaker_BugHole::MakeNPC( void )
+{
+ // Don't try and patrol for a while
+ m_flNextPatrolTime = gpGlobals->curtime + m_flPatrolTime;
+
+ // If my pool is empty, don't spawn a bug
+ if ( !m_iPool )
+ return;
+
+ BaseClass::MakeNPC();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Hook to allow bugs to move before they spawn
+//-----------------------------------------------------------------------------
+void CMaker_BugHole::ChildPreSpawn( CAI_BaseNPC *pChild )
+{
+ BaseClass::ChildPreSpawn( pChild );
+
+ // Drop the bug down and remove it's onground flag
+ Vector origin = GetLocalOrigin();
+ origin.z -= 64;
+ pChild->SetLocalOrigin( origin );
+ pChild->SetGroundEntity( NULL );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Hook to allow bugs to pack specific data into their known class fields
+// Input : *pChild - pointer to the spawned entity to work on
+//-----------------------------------------------------------------------------
+void CMaker_BugHole::ChildPostSpawn( CAI_BaseNPC *pChild )
+{
+ BaseClass::ChildPostSpawn( pChild );
+
+ // May be a warrior or a builder
+ CNPC_Bug_Warrior *pWarrior = dynamic_cast<CNPC_Bug_Warrior*>((CAI_BaseNPC*)pChild);
+ if ( pWarrior )
+ {
+ pWarrior->SetBugHole( this );
+
+ // Add him to my bug list
+ WarriorHandle_t hHandle;
+ hHandle = pWarrior;
+ m_aWarriorBugs.AddToTail( hHandle );
+ }
+ else
+ {
+ CNPC_Bug_Builder *pBuilder = dynamic_cast<CNPC_Bug_Builder*>((CAI_BaseNPC*)pChild);
+ ASSERT( pBuilder );
+ pBuilder->SetBugHole( this );
+
+ // Add him to my bug list
+ BuilderHandle_t hHandle;
+ hHandle = pBuilder;
+ m_aBuilderBugs.AddToTail( hHandle );
+ }
+
+ m_iPool--;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Remove the bug from our list of bugs
+//-----------------------------------------------------------------------------
+void CMaker_BugHole::DeathNotice( CBaseEntity *pVictim )
+{
+ BaseClass::DeathNotice( pVictim );
+
+ // May be a warrior or a builder
+ CNPC_Bug_Warrior *pWarrior = dynamic_cast<CNPC_Bug_Warrior*>((CAI_BaseNPC*)pVictim);
+ if ( pWarrior )
+ {
+ // Remove him from my list
+ WarriorHandle_t hHandle;
+ hHandle = pWarrior;
+ m_aWarriorBugs.FindAndRemove( hHandle );
+ }
+ else
+ {
+ CNPC_Bug_Builder *pBuilder = dynamic_cast<CNPC_Bug_Builder*>((CAI_BaseNPC*)pVictim);
+ ASSERT( pBuilder );
+
+ // Remove him from my list
+ BuilderHandle_t hHandle;
+ hHandle = pBuilder;
+ m_aBuilderBugs.FindAndRemove( hHandle );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: On death, fall to the ground and vanish
+//-----------------------------------------------------------------------------
+void CMaker_BugHole::Event_Killed( const CTakeDamageInfo &info )
+{
+ BaseClass::Event_Killed( info );
+
+ SetMoveType( MOVETYPE_FLYGRAVITY );
+ SetGroundEntity( NULL );
+
+ SetThink( SUB_Remove );
+ SetNextThink( gpGlobals->curtime + 5.0 );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: A bug is fleeing to me, see if I want to do anything
+//-----------------------------------------------------------------------------
+void CMaker_BugHole::IncomingFleeingBug( CAI_BaseNPC *pBug )
+{
+ SpawnWarrior( random->RandomFloat( 3.0, 5.0 ) );
+
+ // If I have available warriors, tell them to engage
+ for ( int i = 0; i < m_aWarriorBugs.Size(); i++ )
+ {
+ m_aWarriorBugs[i]->Assist( pBug );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: One of my bugs has returned to me
+//-----------------------------------------------------------------------------
+void CMaker_BugHole::BugReturned( void )
+{
+ if ( m_iPool < m_iMaxPool )
+ {
+ m_iPool++;
+ }
+}
diff --git a/game/server/tf2/npc_bug_hole.h b/game/server/tf2/npc_bug_hole.h
new file mode 100644
index 0000000..10dbeec
--- /dev/null
+++ b/game/server/tf2/npc_bug_hole.h
@@ -0,0 +1,76 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef NPC_BUG_HOLE_H
+#define NPC_BUG_HOLE_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+class CNPC_Bug_Warrior;
+class CNPC_Bug_Builder;
+
+//-----------------------------------------------------------------------------
+// Purpose: BUG HOLE
+//-----------------------------------------------------------------------------
+class CMaker_BugHole : public CNPCMaker
+{
+ DECLARE_CLASS( CMaker_BugHole, CNPCMaker );
+public:
+ CMaker_BugHole( void );
+
+ DECLARE_DATADESC();
+ DECLARE_SERVERCLASS();
+
+ virtual void Spawn( void );
+ virtual void Precache( void );
+ virtual void MakeNPC( void );
+ virtual void ChildPreSpawn( CAI_BaseNPC *pChild );
+ virtual void ChildPostSpawn( CAI_BaseNPC *pChild );
+ virtual void DeathNotice( CBaseEntity *pVictim );
+ virtual void Event_Killed( const CTakeDamageInfo &info );
+
+ // Bug interactions
+ void BugHoleThink( void );
+ void SpawnBug( float flTime );
+ void SpawnWarrior( float flTime );
+ void SpawnBuilder( float flTime );
+ void BugHoleUnderAttack( void );
+ void StartPatrol( void );
+ void CheckBuilder( void );
+ void IncomingFleeingBug( CAI_BaseNPC *pBug );
+ void BugReturned( void );
+
+private:
+ string_t m_iszNPCClassname_Warrior;
+ string_t m_iszNPCClassname_Builder;
+
+ // Bug pool
+ int m_iPool;
+ int m_iMaxPool;
+ float m_flPoolRegenTime;
+
+ float m_flNextSpawnTime;
+ float m_flNextRegenTime;
+
+ // Patrols
+ int m_iMaxNumberOfPatrollers;
+ float m_flPatrolTime;
+ float m_flNextPatrolTime;
+ string_t m_iszPatrolPathName;
+
+ // Builders
+ int m_iMaxNumberOfBuilders;
+
+ // List of bugs I have out there
+ typedef CHandle<CNPC_Bug_Warrior> WarriorHandle_t;
+ CUtlVector<WarriorHandle_t> m_aWarriorBugs;
+ typedef CHandle<CNPC_Bug_Builder> BuilderHandle_t;
+ CUtlVector<BuilderHandle_t> m_aBuilderBugs;
+};
+
+#endif // NPC_BUG_HOLE_H
diff --git a/game/server/tf2/npc_bug_warrior.cpp b/game/server/tf2/npc_bug_warrior.cpp
new file mode 100644
index 0000000..af900d0
--- /dev/null
+++ b/game/server/tf2/npc_bug_warrior.cpp
@@ -0,0 +1,1039 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: The warrior bug
+//
+// $NoKeywords: $
+//=============================================================================//
+#include "cbase.h"
+#include "AI_Task.h"
+#include "AI_Default.h"
+#include "AI_Schedule.h"
+#include "AI_Hull.h"
+#include "AI_Hint.h"
+#include "AI_Navigator.h"
+#include "AI_Memory.h"
+#include "AI_Squad.h"
+#include "activitylist.h"
+#include "soundent.h"
+#include "game.h"
+#include "NPCEvent.h"
+#include "tf_player.h"
+#include "EntityList.h"
+#include "ndebugoverlay.h"
+#include "shake.h"
+#include "monstermaker.h"
+#include "decals.h"
+#include "vstdlib/random.h"
+#include "tf_obj.h"
+#include "engine/IEngineSound.h"
+#include "IEffects.h"
+#include "npc_bug_warrior.h"
+#include "npc_bug_hole.h"
+
+ConVar npc_bug_warrior_health( "npc_bug_warrior_health", "150" );
+ConVar npc_bug_warrior_swipe_damage( "npc_bug_warrior_swipe_damage", "20" );
+
+BEGIN_DATADESC( CNPC_Bug_Warrior )
+
+ DEFINE_FIELD( m_flIdleDelay, FIELD_FLOAT ),
+
+END_DATADESC()
+
+LINK_ENTITY_TO_CLASS( npc_bug_warrior, CNPC_Bug_Warrior );
+IMPLEMENT_CUSTOM_AI( npc_bug_warrior, CNPC_Bug_Warrior );
+
+// Bug interactions
+int g_interactionBugSquadAttacking = 0;
+
+//==================================================
+// Bug Conditions
+//==================================================
+enum BugConditions
+{
+ COND_WBUG_STOP_FLEEING = LAST_SHARED_CONDITION,
+ COND_WBUG_RETURN_TO_BUGHOLE,
+ COND_WBUG_ASSIST_FELLOW_BUG,
+};
+
+//==================================================
+// Bug Schedules
+//==================================================
+
+enum BugSchedules
+{
+ SCHED_WBUG_CHASE_ENEMY = LAST_SHARED_SCHEDULE,
+ SCHED_WBUG_FLEE_ENEMY,
+ SCHED_WBUG_CHASE_ENEMY_FAILED,
+ SCHED_WBUG_PATROL,
+ SCHED_WBUG_RETURN_TO_BUGHOLE,
+ SCHED_WBUG_RETURN_TO_BUGHOLE_AND_REMOVE,
+ SCHED_WBUG_ASSIST_FELLOW_BUG,
+};
+
+//==================================================
+// Bug Tasks
+//==================================================
+
+enum BugTasks
+{
+ TASK_WBUG_GET_PATH_TO_FLEE = LAST_SHARED_TASK,
+ TASK_WBUG_GET_PATH_TO_PATROL,
+ TASK_WBUG_GET_PATH_TO_BUGHOLE,
+ TASK_WBUG_HOLE_REMOVE,
+ TASK_WBUG_GET_PATH_TO_ASSIST,
+};
+
+//==================================================
+// Bug Activities
+//==================================================
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CNPC_Bug_Warrior::CNPC_Bug_Warrior( void )
+{
+ m_flFieldOfView = 0.5f;
+ m_flIdleDelay = 0.0f;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Setup our schedules and tasks, etc.
+//-----------------------------------------------------------------------------
+void CNPC_Bug_Warrior::InitCustomSchedules( void )
+{
+ INIT_CUSTOM_AI( CNPC_Bug_Warrior );
+
+ // Register our interactions
+ ADD_CUSTOM_INTERACTION( g_interactionBugSquadAttacking );
+
+ // Schedules
+ ADD_CUSTOM_SCHEDULE( CNPC_Bug_Warrior, SCHED_WBUG_CHASE_ENEMY );
+ ADD_CUSTOM_SCHEDULE( CNPC_Bug_Warrior, SCHED_WBUG_FLEE_ENEMY );
+ ADD_CUSTOM_SCHEDULE( CNPC_Bug_Warrior, SCHED_WBUG_CHASE_ENEMY_FAILED );
+ ADD_CUSTOM_SCHEDULE( CNPC_Bug_Warrior, SCHED_WBUG_PATROL );
+ ADD_CUSTOM_SCHEDULE( CNPC_Bug_Warrior, SCHED_WBUG_RETURN_TO_BUGHOLE );
+ ADD_CUSTOM_SCHEDULE( CNPC_Bug_Warrior, SCHED_WBUG_RETURN_TO_BUGHOLE_AND_REMOVE );
+ ADD_CUSTOM_SCHEDULE( CNPC_Bug_Warrior, SCHED_WBUG_ASSIST_FELLOW_BUG );
+
+ // Conditions
+ ADD_CUSTOM_CONDITION( CNPC_Bug_Warrior, COND_WBUG_STOP_FLEEING );
+ ADD_CUSTOM_CONDITION( CNPC_Bug_Warrior, COND_WBUG_RETURN_TO_BUGHOLE );
+ ADD_CUSTOM_CONDITION( CNPC_Bug_Warrior, COND_WBUG_ASSIST_FELLOW_BUG );
+
+ // Tasks
+ ADD_CUSTOM_TASK( CNPC_Bug_Warrior, TASK_WBUG_GET_PATH_TO_FLEE );
+ ADD_CUSTOM_TASK( CNPC_Bug_Warrior, TASK_WBUG_GET_PATH_TO_PATROL );
+ ADD_CUSTOM_TASK( CNPC_Bug_Warrior, TASK_WBUG_GET_PATH_TO_BUGHOLE );
+ ADD_CUSTOM_TASK( CNPC_Bug_Warrior, TASK_WBUG_HOLE_REMOVE );
+ ADD_CUSTOM_TASK( CNPC_Bug_Warrior, TASK_WBUG_GET_PATH_TO_ASSIST );
+
+ // Activities
+ //ADD_CUSTOM_ACTIVITY( CNPC_Bug_Warrior, ACT_BUG_WARRIOR_DISTRACT );
+
+ AI_LOAD_SCHEDULE( CNPC_Bug_Warrior, SCHED_WBUG_CHASE_ENEMY );
+ AI_LOAD_SCHEDULE( CNPC_Bug_Warrior, SCHED_WBUG_FLEE_ENEMY );
+ AI_LOAD_SCHEDULE( CNPC_Bug_Warrior, SCHED_WBUG_CHASE_ENEMY_FAILED );
+ AI_LOAD_SCHEDULE( CNPC_Bug_Warrior, SCHED_WBUG_PATROL );
+ AI_LOAD_SCHEDULE( CNPC_Bug_Warrior, SCHED_WBUG_RETURN_TO_BUGHOLE );
+ AI_LOAD_SCHEDULE( CNPC_Bug_Warrior, SCHED_WBUG_RETURN_TO_BUGHOLE_AND_REMOVE );
+ AI_LOAD_SCHEDULE( CNPC_Bug_Warrior, SCHED_WBUG_ASSIST_FELLOW_BUG );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_Bug_Warrior::Spawn( void )
+{
+ Precache();
+
+ SetModel( BUG_WARRIOR_MODEL );
+
+ SetHullType(HULL_WIDE_HUMAN);//HULL_WIDE_SHORT;
+ SetHullSizeNormal();
+ SetDefaultEyeOffset();
+ SetViewOffset( (WorldAlignMins() + WorldAlignMaxs()) * 0.5 ); // See from my center
+ SetDistLook( 1024.0 );
+
+ SetNavType(NAV_GROUND);
+ m_NPCState = NPC_STATE_NONE;
+ SetBloodColor( BLOOD_COLOR_YELLOW );
+ m_iHealth = npc_bug_warrior_health.GetFloat();
+
+ SetSolid( SOLID_BBOX );
+ AddSolidFlags( FSOLID_NOT_STANDABLE );
+ SetMoveType( MOVETYPE_STEP );
+
+ m_iszPatrolPathName = NULL_STRING;
+
+ // Only do this if a squadname appears in the entity
+ if ( m_SquadName != NULL_STRING )
+ {
+ CapabilitiesAdd( bits_CAP_SQUAD );
+ }
+
+ CapabilitiesAdd( bits_CAP_MOVE_GROUND | bits_CAP_INNATE_MELEE_ATTACK1 );
+
+ NPCInit();
+
+ BaseClass::Spawn();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_Bug_Warrior::Precache( void )
+{
+ PrecacheModel( BUG_WARRIOR_MODEL );
+
+ PrecacheScriptSound( "NPC_Bug_Warrior.AttackHit" );
+ PrecacheScriptSound( "NPC_Bug_Warrior.AttackSingle" );
+ PrecacheScriptSound( "NPC_Bug_Warrior.Idle" );
+ PrecacheScriptSound( "NPC_Bug_Warrior.Pain" );
+
+ BaseClass::Precache();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Output : int
+//-----------------------------------------------------------------------------
+int CNPC_Bug_Warrior::SelectSchedule( void )
+{
+ ClearCondition( COND_WBUG_STOP_FLEEING );
+
+ // Turn towards sounds
+ if ( HasCondition(COND_HEAR_DANGER) || HasCondition(COND_HEAR_COMBAT) )
+ {
+ CSound *pSound = GetBestSound();
+ if ( pSound)
+ {
+ if ( !HasCondition( COND_SEE_ENEMY ) && ( pSound->m_iType & (SOUND_PLAYER | SOUND_COMBAT) ) )
+ {
+ GetMotor()->SetIdealYawToTarget( pSound->GetSoundReactOrigin() );
+ }
+ }
+ }
+
+ switch ( m_NPCState )
+ {
+ case NPC_STATE_IDLE:
+ {
+ // If I have an enemy, but I don't have any nearby friends, flee
+ if ( HasCondition( COND_SEE_ENEMY ) && ShouldFlee() )
+ {
+ SetState( NPC_STATE_ALERT );
+ return SCHED_WBUG_FLEE_ENEMY;
+ }
+
+ // Fellow bug might be requesting assistance
+ if ( HasCondition( COND_WBUG_ASSIST_FELLOW_BUG ) )
+ return SCHED_WBUG_ASSIST_FELLOW_BUG;
+
+ // BugHole might be requesting help
+ if ( HasCondition( COND_WBUG_RETURN_TO_BUGHOLE ) )
+ return SCHED_WBUG_RETURN_TO_BUGHOLE;
+
+ // Return to my bughole
+ return SCHED_WBUG_RETURN_TO_BUGHOLE_AND_REMOVE;
+ break;
+ }
+ case NPC_STATE_ALERT:
+ {
+ // If I have an enemy, but I don't have any nearby friends, flee
+ if ( HasCondition( COND_SEE_ENEMY ) && ShouldFlee() )
+ {
+ SetState( NPC_STATE_ALERT );
+ return SCHED_WBUG_FLEE_ENEMY;
+ }
+
+ // Fellow bug might be requesting assistance
+ if ( HasCondition( COND_WBUG_ASSIST_FELLOW_BUG ) )
+ return SCHED_WBUG_ASSIST_FELLOW_BUG;
+
+ // BugHole might be requesting help
+ if ( HasCondition( COND_WBUG_RETURN_TO_BUGHOLE ) )
+ return SCHED_WBUG_RETURN_TO_BUGHOLE;
+
+ // If I have a patrol path, walk it
+ if ( m_iszPatrolPathName != NULL_STRING )
+ return SCHED_WBUG_PATROL;
+
+ break;
+ }
+ case NPC_STATE_COMBAT:
+ {
+ // Did I lose my enemy?
+ if ( HasCondition ( COND_LOST_ENEMY ) || HasCondition ( COND_ENEMY_UNREACHABLE ) )
+ {
+ SetEnemy( NULL );
+ m_bFightingToDeath = false;
+ SetState(NPC_STATE_IDLE);
+ return BaseClass::SelectSchedule();
+ }
+
+ // If I have an enemy, but I don't have any nearby friends, flee
+ if ( HasCondition( COND_SEE_ENEMY ) && ShouldFlee() )
+ return SCHED_WBUG_FLEE_ENEMY;
+
+ // If we're able to melee, do so
+ if ( HasCondition( COND_CAN_MELEE_ATTACK1 ) )
+ return BaseClass::SelectSchedule();
+ }
+ break;
+ }
+
+ return BaseClass::SelectSchedule();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: override/translate a schedule by type
+// Input : Type - schedule type
+// Output : int - translated type
+//-----------------------------------------------------------------------------
+int CNPC_Bug_Warrior::TranslateSchedule( int scheduleType )
+{
+ if ( scheduleType == SCHED_CHASE_ENEMY )
+ {
+ // Tell my squad that I'm attacking this guy
+ if ( m_pSquad )
+ {
+ m_pSquad->BroadcastInteraction( g_interactionBugSquadAttacking, (void *)GetEnemy(), this );
+ }
+ return SCHED_WBUG_CHASE_ENEMY;
+ }
+
+ return BaseClass::TranslateSchedule(scheduleType);
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : *pTask -
+//-----------------------------------------------------------------------------
+void CNPC_Bug_Warrior::StartTask( const Task_t *pTask )
+{
+ switch ( pTask->iTask )
+ {
+ case TASK_WBUG_GET_PATH_TO_FLEE:
+ {
+ // Always tell our bughole that we're under attack
+ if ( m_hMyBugHole )
+ {
+ m_hMyBugHole->IncomingFleeingBug( this );
+ }
+
+ // We're fleeing from an enemy.
+ // If I have a squadmate, run to him.
+ CAI_BaseNPC *pSquadMate;
+ if ( m_pSquad && (pSquadMate = m_pSquad->NearestSquadMember(this)) != false )
+ {
+ SetTarget( pSquadMate );
+ AI_NavGoal_t goal( GOALTYPE_TARGETENT, vec3_origin, ACT_RUN );
+ if ( GetNavigator()->SetGoal( goal ) )
+ {
+ TaskComplete();
+ return;
+ }
+ }
+
+ // If we have no squad, or we couldn't get a path to our squadmate, look for a bughole
+ if ( m_hMyBugHole )
+ {
+ SetTarget( m_hMyBugHole );
+ AI_NavGoal_t goal( GOALTYPE_TARGETENT, vec3_origin, ACT_RUN );
+ if ( GetNavigator()->SetGoal( goal ) )
+ {
+ TaskComplete();
+ return;
+ }
+ }
+
+ // Give up, and fight to the death
+ m_bFightingToDeath = true;
+ TaskComplete();
+ }
+ break;
+
+ case TASK_WBUG_GET_PATH_TO_PATROL:
+ {
+ // Get a path to the next point in the patrol.
+ // Abort if we have no patrol path
+ if ( !m_iszPatrolPathName )
+ {
+ TaskFail( "Attempting to patrol without patrol path." );
+ return;
+ }
+
+ SetHintNode( CAI_HintManager::FindHint( this, HINT_BUG_PATROL_POINT, bits_HINT_NODE_RANDOM, 4096 ) );
+ if( !GetHintNode() )
+ {
+ TaskFail("Couldn't find patrol node");
+ return;
+ }
+
+ Vector vHintPos;
+ GetHintNode()->GetPosition( this, &vHintPos );
+ AI_NavGoal_t goal( vHintPos, ACT_RUN );
+ GetNavigator()->SetGoal( goal );
+ TaskComplete();
+ }
+ break;
+
+ case TASK_WBUG_GET_PATH_TO_BUGHOLE:
+ {
+ // Get a path back to my bughole
+ // If we have no squad, or we couldn't get a path to our squadmate, look for a bughole
+ if ( m_hMyBugHole )
+ {
+ SetTarget( m_hMyBugHole );
+ AI_NavGoal_t goal( GOALTYPE_TARGETENT, vec3_origin, ACT_RUN );
+ if ( GetNavigator()->SetGoal( goal ) )
+ {
+ TaskComplete();
+ return;
+ }
+ }
+
+ TaskFail( "Couldn't get to bughole." );
+ }
+ break;
+
+ case TASK_WBUG_HOLE_REMOVE:
+ {
+ // Crawl inside the bughole and remove myself
+ AddEffects( EF_NODRAW );
+ AddSolidFlags( FSOLID_NOT_SOLID );
+ Event_Killed( CTakeDamageInfo( this, this, 200, DMG_CRUSH ) );
+
+ // Tell the bughole
+ if ( m_hMyBugHole )
+ {
+ m_hMyBugHole->BugReturned();
+ }
+
+ TaskComplete();
+ }
+ break;
+
+ case TASK_WBUG_GET_PATH_TO_ASSIST:
+ {
+ if ( m_hAssistTarget )
+ {
+ SetTarget( m_hAssistTarget );
+ AI_NavGoal_t goal( GOALTYPE_TARGETENT, vec3_origin, ACT_RUN );
+ if ( GetNavigator()->SetGoal( goal ) )
+ {
+ TaskComplete();
+ return;
+ }
+ }
+
+ TaskFail( "Couldn't get to assist target." );
+ }
+ break;
+
+ default:
+ BaseClass::StartTask( pTask );
+ break;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : *pTask -
+//-----------------------------------------------------------------------------
+void CNPC_Bug_Warrior::RunTask( const Task_t *pTask )
+{
+ BaseClass::RunTask( pTask );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CNPC_Bug_Warrior::FValidateHintType(CAI_Hint *pHint)
+{
+ if ( pHint->HintType() == HINT_BUG_PATROL_POINT )
+ {
+ // Make sure the name matches the patrol path I'm on
+ if ( pHint->GetEntityName() == m_iszPatrolPathName )
+ return true;
+ }
+
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Handle specific interactions with other NPCs
+//-----------------------------------------------------------------------------
+bool CNPC_Bug_Warrior::HandleInteraction( int interactionType, void *data, CBaseCombatCharacter *sender )
+{
+ // Check for a target found while burrowed
+ if ( interactionType == g_interactionBugSquadAttacking )
+ {
+ // Ignore ones sent by me
+ if ( sender == this )
+ return false;
+
+ // Interrupt me if I'm fleeing
+ if ( IsCurSchedule(SCHED_WBUG_FLEE_ENEMY) ||
+ IsCurSchedule(SCHED_WBUG_CHASE_ENEMY_FAILED) )
+ {
+ SetCondition( COND_WBUG_STOP_FLEEING );
+ }
+
+ return true;
+ }
+
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : *pVictim -
+//-----------------------------------------------------------------------------
+void CNPC_Bug_Warrior::Event_Killed( const CTakeDamageInfo &info )
+{
+ BaseClass::Event_Killed( info );
+
+ // Remove myself in a minute
+ if ( !ShouldFadeOnDeath() )
+ {
+ SetThink( SUB_Remove );
+ SetNextThink( gpGlobals->curtime + 20 );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_Bug_Warrior::MeleeAttack( float distance, float damage, QAngle& viewPunch, Vector& shove )
+{
+ if ( GetEnemy() == NULL )
+ return;
+
+ // Trace directly at my target
+ Vector vStart = GetAbsOrigin();
+ vStart.z += WorldAlignSize().z * 0.5;
+ Vector vecForward = (GetEnemy()->EyePosition() - vStart);
+ VectorNormalize( vecForward );
+ Vector vEnd = vStart + (vecForward * distance );
+
+ // Use half the size of my target for the box
+ Vector vecHalfTraceBox = (GetEnemy()->WorldAlignMaxs() - GetEnemy()->WorldAlignMins()) * 0.25;
+ //NDebugOverlay::Box( vStart, -Vector(10,10,10), Vector(10,10,10), 0,255,0,20,1.0);
+ //NDebugOverlay::Box( GetEnemy()->EyePosition(), -Vector(10,10,10), Vector(10,10,10), 255,255,255,20,1.0);
+ CBaseEntity *pHurt = CheckTraceHullAttack( vStart, vEnd, -vecHalfTraceBox, vecHalfTraceBox, damage, DMG_SLASH );
+
+ if ( pHurt )
+ {
+ CBasePlayer *pPlayer = ToBasePlayer( pHurt );
+
+ if ( pPlayer )
+ {
+ //Kick the player angles
+ pPlayer->ViewPunch( viewPunch );
+
+ Vector dir = pHurt->GetAbsOrigin() - GetAbsOrigin();
+ VectorNormalize(dir);
+
+ QAngle angles;
+ VectorAngles( dir, angles );
+ Vector forward, right;
+ AngleVectors( angles, &forward, &right, NULL );
+
+ // Push the target back
+ Vector vecImpulse;
+ VectorMultiply( right, -shove[1], vecImpulse );
+ VectorMA( vecImpulse, -shove[0], forward, vecImpulse );
+ pHurt->ApplyAbsVelocityImpulse( vecImpulse );
+ }
+
+ // Play a random attack hit sound
+ EmitSound( "NPC_Bug_Warrior.AttackHit" );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : *pEvent -
+//-----------------------------------------------------------------------------
+void CNPC_Bug_Warrior::HandleAnimEvent( animevent_t *pEvent )
+{
+ switch ( pEvent->event )
+ {
+ case BUG_WARRIOR_AE_MELEE_HIT1:
+ MeleeAttack( BUG_WARRIOR_MELEE1_RANGE, npc_bug_warrior_swipe_damage.GetFloat(), QAngle( 20.0f, 0.0f, 0.0f ), Vector( -350.0f, 1.0f, 1.0f ) );
+ return;
+ break;
+
+ case BUG_WARRIOR_AE_MELEE_SOUND1:
+ {
+ EmitSound( "NPC_Bug_Warrior.AttackSingle" );
+ return;
+ }
+ break;
+ }
+
+ BaseClass::HandleAnimEvent( pEvent );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : *pInflictor -
+// *pAttacker -
+// flDamage -
+// bitsDamageType -
+// Output : int
+//-----------------------------------------------------------------------------
+int CNPC_Bug_Warrior::OnTakeDamage_Alive( const CTakeDamageInfo &info )
+{
+ Vector faceDir;
+
+ return BaseClass::OnTakeDamage_Alive( info );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_Bug_Warrior::IdleSound( void )
+{
+ EmitSound( "NPC_Bug_Warrior.Idle" );
+ m_flIdleDelay = gpGlobals->curtime + 4.0f;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_Bug_Warrior::PainSound( const CTakeDamageInfo &info )
+{
+ EmitSound( "NPC_Bug_Warrior.Pain" );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : &vecTarget -
+// Output : float
+//-----------------------------------------------------------------------------
+float CNPC_Bug_Warrior::CalcIdealYaw( const Vector &vecTarget )
+{
+ //If we can see our enemy but not reach them, face them always
+ if ( ( GetEnemy() != NULL ) && ( HasCondition( COND_SEE_ENEMY ) && HasCondition( COND_ENEMY_UNREACHABLE ) ) )
+ {
+ return UTIL_VecToYaw ( GetEnemy()->GetAbsOrigin() - GetAbsOrigin() );
+ }
+
+ return BaseClass::CalcIdealYaw( vecTarget );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Output : float
+//-----------------------------------------------------------------------------
+float CNPC_Bug_Warrior::MaxYawSpeed( void )
+{
+ switch ( GetActivity() )
+ {
+ case ACT_IDLE:
+ return 15.0f;
+ break;
+
+ case ACT_WALK:
+ return 15.0f;
+ break;
+
+ default:
+ case ACT_RUN:
+ return 30.0f;
+ break;
+ }
+
+ return 30.0f;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CNPC_Bug_Warrior::ShouldPlayIdleSound( void )
+{
+ //Only do idles in the right states
+ if ( ( m_NPCState != NPC_STATE_IDLE && m_NPCState != NPC_STATE_ALERT ) )
+ return false;
+
+ //Gagged monsters don't talk
+ if ( HasSpawnFlags( SF_NPC_GAG ) )
+ return false;
+
+ //Don't cut off another sound or play again too soon
+ if ( m_flIdleDelay > gpGlobals->curtime )
+ return false;
+
+ //Randomize it a bit
+ if ( random->RandomInt( 0, 20 ) )
+ return false;
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : flDot -
+// flDist -
+// Output : int
+//-----------------------------------------------------------------------------
+int CNPC_Bug_Warrior::MeleeAttack1Conditions( float flDot, float flDist )
+{
+ if ( ( gpGlobals->curtime - m_flLastAttackTime ) < 1.0f )
+ return 0;
+
+#if 0
+
+ float flPrDist, flPrDot;
+ Vector vecPrPos;
+ Vector2D vec2DPrDir;
+
+ //Get our likely position in one second
+ UTIL_GetPredictedPosition( GetEnemy(), 0.5f, &vecPrPos );
+
+ flPrDist = ( vecPrPos - GetAbsOrigin() ).Length();
+
+ vec2DPrDir = ( vecPrPos - GetAbsOrigin() ).AsVector2D();
+
+ Vector vecBodyDir;
+
+ BodyDirection2D( &vecBodyDir );
+
+ Vector2D vec2DBodyDir = vecBodyDir.AsVector2D();
+
+ flPrDot = DotProduct2D (vec2DPrDir, vec2DBodyDir );
+
+ if ( flPrDist > BUG_WARRIOR_MELEE1_RANGE )
+ return COND_TOO_FAR_TO_ATTACK;
+
+ if ( flPrDot < 0.5f )
+ return COND_NOT_FACING_ATTACK;
+
+#else
+
+ if ( flDist > BUG_WARRIOR_MELEE1_RANGE )
+ return COND_TOO_FAR_TO_ATTACK;
+
+ if ( flDot < 0.5f )
+ return COND_NOT_FACING_ATTACK;
+
+#endif
+
+ return COND_CAN_MELEE_ATTACK1;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Return true if this bug should flee
+//-----------------------------------------------------------------------------
+bool CNPC_Bug_Warrior::ShouldFlee( void )
+{
+ // I don't flee if I'm fighting to the death
+ if ( m_bFightingToDeath )
+ return false;
+
+ // I don't flee if I'm not alone
+ if ( !IsAlone() )
+ return false;
+
+ // I don't flee if I'm within sight of a bughole
+ if ( m_hMyBugHole.Get() && FVisible( m_hMyBugHole ) )
+ return false;
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CNPC_Bug_Warrior::IsAlone( void )
+{
+ // If I'm not in a squad, make me just fight
+ if ( !m_pSquad )
+ return false;
+
+ // If there aren't any visible squad members, I'm alone
+ if ( m_pSquad->GetVisibleSquadMembers( this ) == 0 )
+ return true;
+
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_Bug_Warrior::SetBugHole( CMaker_BugHole *pBugHole )
+{
+ m_hMyBugHole = pBugHole;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: BugHole is calling me home to defend it
+//-----------------------------------------------------------------------------
+void CNPC_Bug_Warrior::ReturnToBugHole( void )
+{
+ SetCondition( COND_WBUG_RETURN_TO_BUGHOLE );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Assist a bug that's under attack and making it's way back to the bughole
+//-----------------------------------------------------------------------------
+void CNPC_Bug_Warrior::Assist( CAI_BaseNPC *pBug )
+{
+ // If I'm not idle, I can't assist
+ if ( m_NPCState != NPC_STATE_IDLE && m_NPCState != NPC_STATE_ALERT && m_NPCState != NPC_STATE_NONE )
+ return;
+
+ m_hAssistTarget = pBug;
+ SetCondition( COND_WBUG_ASSIST_FELLOW_BUG );
+ SetCondition( COND_PROVOKED );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CNPC_Bug_Warrior::StartPatrolling( string_t iszPatrolPathName )
+{
+ // If I'm not idle, I can't patrol
+ if ( m_NPCState != NPC_STATE_IDLE && m_NPCState != NPC_STATE_ALERT && m_NPCState != NPC_STATE_NONE )
+ return false;
+
+ // If I'm patrolling already, I can't patrol
+ if ( IsPatrolling() )
+ return false;
+
+ SetState( NPC_STATE_ALERT );
+ SetCondition( COND_PROVOKED );
+
+ // Store off my patrol name
+ m_iszPatrolPathName = iszPatrolPathName;
+ m_iPatrolPoint = 0;
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Return true if this bug is currently patrolling
+//-----------------------------------------------------------------------------
+bool CNPC_Bug_Warrior::IsPatrolling( void )
+{
+ return ( IsCurSchedule(SCHED_WBUG_PATROL) );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_Bug_Warrior::AlertSound( void )
+{
+ if ( GetEnemy() )
+ {
+ //FIXME: We need a better solution for inner-squad alerts!!
+ //SOUND_DANGER is designed to frighten NPC's away. Need a different SOUND_ type.
+ CSoundEnt::InsertSound( SOUND_DANGER, GetEnemy()->GetAbsOrigin(), 1024, 0.5f, this );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Overridden for team handling
+//-----------------------------------------------------------------------------
+Disposition_t CNPC_Bug_Warrior::IRelationType( CBaseEntity *pTarget )
+{
+ if ( pTarget->GetFlags() & FL_NOTARGET )
+ return D_NU;
+
+ if ( pTarget->Classify() == CLASS_PLAYER )
+ {
+ // Ignore stealthed enemies
+ CBaseTFPlayer *pPlayer = (CBaseTFPlayer *)pTarget;
+ if ( pPlayer->IsCamouflaged() )
+ return D_NU;
+
+ return D_HT;
+ }
+
+ // Attack objects
+ if ( pTarget->Classify() == CLASS_MILITARY )
+ return D_HT;
+
+ return D_NU;
+}
+
+//------------------------------------------------------------------------------
+// Purpose : Hate sentryguns more than other types of objects
+//------------------------------------------------------------------------------
+int CNPC_Bug_Warrior::IRelationPriority( CBaseEntity *pTarget )
+{
+ if ( pTarget->Classify() == CLASS_MILITARY )
+ {
+ CBaseObject* pBaseObject = dynamic_cast<CBaseObject*>(pTarget);
+ if ( pBaseObject && pBaseObject->IsSentrygun() )
+ return ( BaseClass::IRelationPriority ( pTarget ) + 1 );
+ }
+
+ return BaseClass::IRelationPriority ( pTarget );
+}
+
+
+//------------------------------------------------------------------------------
+//
+// Schedules
+//
+//------------------------------------------------------------------------------
+
+//=========================================================
+// Chase Enemy
+//=========================================================
+AI_DEFINE_SCHEDULE
+(
+ SCHED_WBUG_CHASE_ENEMY,
+
+ " Tasks"
+ //" TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_WBUG_CHASE_ENEMY_FAILED"
+ " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_CHASE_ENEMY_FAILED"
+ " TASK_SET_TOLERANCE_DISTANCE 24"
+ " TASK_GET_PATH_TO_ENEMY 0"
+ " TASK_RUN_PATH 0"
+ " TASK_WAIT_FOR_MOVEMENT 0"
+ ""
+ " Interrupts"
+ " COND_NEW_ENEMY"
+ " COND_ENEMY_DEAD"
+ " COND_ENEMY_UNREACHABLE"
+ " COND_CAN_MELEE_ATTACK1"
+ " COND_CAN_MELEE_ATTACK2"
+ " COND_GIVE_WAY"
+ " COND_LOST_ENEMY"
+);
+
+//=========================================================
+// Failed to chase my enemy
+//=========================================================
+AI_DEFINE_SCHEDULE
+(
+ SCHED_WBUG_CHASE_ENEMY_FAILED,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE"
+ " TASK_WAIT_FACE_ENEMY 0.5"
+ ""
+ " Interrupts"
+ " COND_NEW_ENEMY"
+ " COND_ENEMY_DEAD"
+ " COND_HEAR_COMBAT"
+ " COND_HEAR_DANGER"
+ " COND_CAN_RANGE_ATTACK1"
+ " COND_CAN_MELEE_ATTACK1"
+ " COND_CAN_RANGE_ATTACK2"
+ " COND_CAN_MELEE_ATTACK2"
+ " COND_WBUG_STOP_FLEEING"
+);
+
+//=========================================================
+// Flee from our enemy
+//=========================================================
+AI_DEFINE_SCHEDULE
+(
+ SCHED_WBUG_FLEE_ENEMY,
+
+ " Tasks"
+ " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_WBUG_CHASE_ENEMY"
+ " TASK_SET_TOLERANCE_DISTANCE 128"
+ " TASK_WBUG_GET_PATH_TO_FLEE 0"
+ " TASK_RUN_PATH 0"
+ " TASK_WAIT_FOR_MOVEMENT 0"
+ " TASK_TURN_RIGHT 180"
+ ""
+ " Interrupts"
+ " COND_ENEMY_DEAD"
+ " COND_WBUG_STOP_FLEEING"
+ " COND_LOST_ENEMY"
+);
+
+//=========================================================
+// Walk a set of patrol points
+//=========================================================
+AI_DEFINE_SCHEDULE
+(
+ SCHED_WBUG_PATROL,
+
+ " Tasks"
+ //" TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_WBUG_CHASE_ENEMY"
+ " TASK_SET_TOLERANCE_DISTANCE 256"
+ " TASK_WBUG_GET_PATH_TO_PATROL 0"
+ " TASK_RUN_PATH 0"
+ " TASK_WAIT_FOR_MOVEMENT 0"
+ " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE" // Use look around
+ " TASK_WAIT 2"
+ " TASK_WAIT_RANDOM 4"
+ ""
+ " Interrupts"
+ " COND_NEW_ENEMY"
+ " COND_HEAR_COMBAT"
+ " COND_HEAR_DANGER"
+ " COND_WBUG_RETURN_TO_BUGHOLE"
+);
+
+//=========================================================
+// Return to defend a bughole
+//=========================================================
+AI_DEFINE_SCHEDULE
+(
+ SCHED_WBUG_RETURN_TO_BUGHOLE,
+
+ " Tasks"
+ " TASK_SET_TOLERANCE_DISTANCE 128"
+ " TASK_WBUG_GET_PATH_TO_BUGHOLE 0"
+ " TASK_RUN_PATH 0"
+ " TASK_WAIT_FOR_MOVEMENT 0"
+ ""
+ " Interrupts"
+ " COND_NEW_ENEMY"
+ " COND_HEAR_COMBAT"
+ " COND_HEAR_DANGER"
+);
+
+//=========================================================
+// Return to a bughole and remove myself
+//=========================================================
+AI_DEFINE_SCHEDULE
+(
+ SCHED_WBUG_RETURN_TO_BUGHOLE_AND_REMOVE,
+
+ " Tasks"
+ " TASK_WAIT 5" // Wait for 5-10 seconds to see if anything happens
+ " TASK_WAIT_RANDOM 5"
+ " TASK_SET_TOLERANCE_DISTANCE 128"
+ " TASK_WBUG_GET_PATH_TO_BUGHOLE 0"
+ " TASK_RUN_PATH 0"
+ " TASK_WAIT_FOR_MOVEMENT 0"
+ " TASK_WBUG_HOLE_REMOVE 0"
+ ""
+ " Interrupts"
+ " COND_NEW_ENEMY"
+ " COND_HEAR_COMBAT"
+ " COND_HEAR_DANGER"
+);
+
+//=========================================================
+// Move to assist a fellow bug
+//=========================================================
+AI_DEFINE_SCHEDULE
+(
+ SCHED_WBUG_ASSIST_FELLOW_BUG,
+
+ " Tasks"
+ " TASK_SET_TOLERANCE_DISTANCE 128"
+ " TASK_WBUG_GET_PATH_TO_ASSIST 0"
+ " TASK_RUN_PATH 0"
+ " TASK_WAIT_FOR_MOVEMENT 0"
+ ""
+ " Interrupts"
+ " COND_NEW_ENEMY"
+ " COND_CAN_MELEE_ATTACK1"
+ " COND_CAN_MELEE_ATTACK2"
+ " COND_GIVE_WAY"
+); \ No newline at end of file
diff --git a/game/server/tf2/npc_bug_warrior.h b/game/server/tf2/npc_bug_warrior.h
new file mode 100644
index 0000000..aef1922
--- /dev/null
+++ b/game/server/tf2/npc_bug_warrior.h
@@ -0,0 +1,102 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef NPC_BUG_WARRIOR_H
+#define NPC_BUG_WARRIOR_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include "AI_BaseNPC.h"
+
+// Animation events
+#define BUG_WARRIOR_AE_MELEE_SOUND1 11 // Start attack sound
+#define BUG_WARRIOR_AE_MELEE_HIT1 15 // Melee hit, one arm
+
+// Attack range definitions
+#define BUG_WARRIOR_MELEE1_RANGE 128.0f
+#define BUG_WARRIOR_MELEE1_RANGE_MIN 128.0f
+
+#define BUG_WARRIOR_MODEL "models/npcs/bugs/bug_warrior.mdl"
+
+class CMaker_BugHole;
+
+//-----------------------------------------------------------------------------
+// Purpose: WARRIOR BUG
+//-----------------------------------------------------------------------------
+class CNPC_Bug_Warrior : public CAI_BaseNPC
+{
+ DECLARE_CLASS( CNPC_Bug_Warrior, CAI_BaseNPC );
+public:
+ CNPC_Bug_Warrior( void );
+
+ virtual void Spawn( void );
+ virtual void Precache( void );
+
+ virtual int SelectSchedule( void );
+ virtual int TranslateSchedule( int scheduleType );
+ virtual void StartTask( const Task_t *pTask );
+ virtual void RunTask( const Task_t *pTask );
+ virtual bool FValidateHintType(CAI_Hint *pHint);
+
+ virtual void HandleAnimEvent( animevent_t *pEvent );
+ virtual void IdleSound( void );
+ virtual void PainSound( const CTakeDamageInfo &info );
+ virtual void AlertSound( void );
+ virtual bool HandleInteraction( int interactionType, void *data, CBaseCombatCharacter *sender );
+
+ virtual bool ShouldPlayIdleSound( void );
+
+ virtual int MeleeAttack1Conditions( float flDot, float flDist );
+ virtual int OnTakeDamage_Alive( const CTakeDamageInfo &info );
+
+ virtual Class_T Classify( void ) { return CLASS_ANTLION; }
+
+ virtual float MaxYawSpeed ( void );
+ virtual float CalcIdealYaw( const Vector &vecTarget );
+
+ DECLARE_DATADESC();
+
+ // Returns true if the bug should flee
+ bool ShouldFlee( void );
+
+ // Squad handling
+ bool IsAlone( void );
+
+ // BugHole handling
+ void SetBugHole( CMaker_BugHole *pBugHole );
+ void ReturnToBugHole( void );
+ void Assist( CAI_BaseNPC *pBug );
+
+ // Patrolling
+ bool StartPatrolling( string_t iszPatrolPathName );
+ bool IsPatrolling( void );
+
+private:
+ virtual Disposition_t IRelationType( CBaseEntity *pTarget );
+ virtual int IRelationPriority( CBaseEntity *pTarget );
+
+ void Event_Killed( const CTakeDamageInfo &info );
+ void MeleeAttack( float distance, float damage, QAngle& viewPunch, Vector& shove );
+
+ float m_flIdleDelay;
+
+ // BugHole handling
+ CHandle< CMaker_BugHole > m_hMyBugHole;
+ CHandle< CAI_BaseNPC > m_hAssistTarget;
+
+ // Patrolling
+ string_t m_iszPatrolPathName;
+ int m_iPatrolPoint;
+
+ // Bug's decided he's not fleeing anymore
+ bool m_bFightingToDeath;
+
+ DEFINE_CUSTOM_AI;
+};
+
+#endif // NPC_BUG_WARRIOR_H
diff --git a/game/server/tf2/order_assist.cpp b/game/server/tf2/order_assist.cpp
new file mode 100644
index 0000000..0a9f12e
--- /dev/null
+++ b/game/server/tf2/order_assist.cpp
@@ -0,0 +1,177 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#include "cbase.h"
+#include "order_assist.h"
+#include "tf_team.h"
+#include "order_helpers.h"
+
+
+// If a player has been shot within this time delta, commandos will get orders to assist.
+#define COMMANDO_ASSIST_SHOT_DELAY 3.0f
+
+// How close a commando has to be to a teammate to get an assist order.
+#define COMMAND_ASSIST_DISTANCE 1200
+#define COMMAND_ASSIST_DISTANCE_SQR (COMMAND_ASSIST_DISTANCE*COMMAND_ASSIST_DISTANCE)
+
+
+
+IMPLEMENT_SERVERCLASS_ST( COrderAssist, DT_OrderAssist )
+END_SEND_TABLE()
+
+
+static bool IsValidFn_OnEnemyTeam( void *pUserData, int a )
+{
+ edict_t *pEdict = engine->PEntityOfEntIndex( a+1 );
+ if ( !pEdict )
+ return false;
+
+ CBaseEntity *pBaseEntity = CBaseEntity::Instance( pEdict );
+ if ( !pBaseEntity )
+ return false;
+
+ CSortBase *p = (CSortBase*)pUserData;
+ return pBaseEntity->GetTeam() != p->m_pPlayer->GetTeam();
+}
+
+
+static bool IsValidFn_PlayersWantingAssist( void *pUserData, int a )
+{
+ CSortBase *pSortBase = (CSortBase*)pUserData;
+ CBaseTFPlayer *pPlayer = (CBaseTFPlayer*)pSortBase->m_pPlayer->GetTeam()->GetPlayer( a );
+
+ if ( !pPlayer->IsAlive() )
+ {
+ // This guy sure could have used an assist but YOU'RE TOO SLOW!!!
+ return false;
+ }
+
+ // Don't try to assist yourself...
+ if ( pPlayer == pSortBase->m_pPlayer )
+ return false;
+
+ // Make sure this guy was shot recently.
+ if ( (gpGlobals->curtime - pPlayer->LastTimeDamagedByEnemy()) > COMMANDO_ASSIST_SHOT_DELAY )
+ return false;
+
+ // Is the guy close enough?
+ if ( pSortBase->m_pPlayer->GetAbsOrigin().DistToSqr( pPlayer->GetAbsOrigin() ) > COMMAND_ASSIST_DISTANCE_SQR )
+ return false;
+
+ return true;
+}
+
+
+bool COrderAssist::CreateOrder( CPlayerClass *pClass )
+{
+ // Search for a (live) nearby player who's just been shot.
+ CSortBase info;
+ info.m_pPlayer = pClass->GetPlayer();
+
+ int sorted[512];
+ int nSorted = BuildSortedActiveList(
+ sorted,
+ ARRAYSIZE( sorted ),
+ SortFn_TeamPlayersByDistance,
+ IsValidFn_PlayersWantingAssist,
+ &info,
+ pClass->GetTeam()->GetNumPlayers()
+ );
+
+ if ( nSorted )
+ {
+ COrderAssist *pOrder = new COrderAssist;
+
+ CBaseTFPlayer *pPlayerToAssist = (CBaseTFPlayer*)pClass->GetTeam()->GetPlayer( sorted[0] );
+
+ pClass->GetTeam()->AddOrder(
+ ORDER_ASSIST,
+ pPlayerToAssist,
+ info.m_pPlayer,
+ COMMAND_ASSIST_DISTANCE,
+ 25,
+ pOrder );
+
+ // Add the closest enemies.
+ CSortBase enemySortInfo;
+ enemySortInfo.m_pPlayer = pPlayerToAssist;
+
+ int sortedEnemies[256];
+ int nSortedEnemies = BuildSortedActiveList(
+ sortedEnemies,
+ ARRAYSIZE( sortedEnemies ),
+ SortFn_PlayerEntitiesByDistance,
+ IsValidFn_OnEnemyTeam,
+ &info,
+ gpGlobals->maxClients
+ );
+
+ nSortedEnemies = MIN( nSortedEnemies, NUM_ASSIST_ENEMIES );
+ for ( int i=0; i < nSortedEnemies; i++ )
+ {
+ CBaseEntity *pEnt = CBaseEntity::Instance( engine->PEntityOfEntIndex( sortedEnemies[i] + 1 ) );
+ Assert( dynamic_cast<CBasePlayer*>( pEnt ) );
+ pOrder->m_Enemies[i] = pEnt;
+ }
+ }
+
+ return false;
+}
+
+
+bool COrderAssist::Update()
+{
+ if ( !GetTargetEntity() || !GetTargetEntity()->IsAlive() )
+ return true;
+
+ return BaseClass::Update();
+}
+
+
+bool COrderAssist::UpdateOnEvent( COrderEvent_Base *pEvent )
+{
+ if ( !GetTargetEntity() )
+ return true;
+
+ switch( pEvent->GetType() )
+ {
+ // If our boy dies, then he doesn't care about assistance anymore.
+ case ORDER_EVENT_PLAYER_KILLED:
+ {
+ COrderEvent_PlayerKilled *pKilled = (COrderEvent_PlayerKilled*)pEvent;
+ if ( pKilled->m_pPlayer == GetTargetEntity() )
+ return true;
+ }
+ break;
+
+ // Did we damage one of the enemies?
+ case ORDER_EVENT_PLAYER_DAMAGED:
+ {
+ COrderEvent_PlayerDamaged *pPlayerDamaged = (COrderEvent_PlayerDamaged*)pEvent;
+ if ( pPlayerDamaged->m_TakeDamageInfo.GetInflictor() == GetOwner() )
+ {
+ for ( int i=0; i < NUM_ASSIST_ENEMIES; i++ )
+ {
+ if ( pPlayerDamaged->m_pPlayerDamaged == m_Enemies[i].Get() )
+ {
+ // Reset the timer on the guy we're defending, in case we killed his
+ // attacker really quickly.
+// CBaseTFPlayer *pPlayer = (CBaseTFPlayer*)GetTargetEntity();
+// pPlayer->m_flLastTimeDamagedByEnemy = 0;
+
+ return true;
+ }
+ }
+ }
+ }
+ break;
+ }
+
+ return BaseClass::UpdateOnEvent( pEvent );
+}
+
+
diff --git a/game/server/tf2/order_assist.h b/game/server/tf2/order_assist.h
new file mode 100644
index 0000000..821fab7
--- /dev/null
+++ b/game/server/tf2/order_assist.h
@@ -0,0 +1,51 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef TF_ORDER_ASSIST_H
+#define TF_ORDER_ASSIST_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+
+#include "order_player.h"
+
+
+class CPlayerClass;
+
+
+class COrderAssist : public COrderPlayer
+{
+public:
+ DECLARE_CLASS( COrderAssist, COrderPlayer );
+ DECLARE_SERVERCLASS();
+
+ // Create an order for the player.
+ static bool CreateOrder( CPlayerClass *pClass );
+
+
+// COrder overrides.
+public:
+
+ virtual bool Update();
+ virtual bool UpdateOnEvent( COrderEvent_Base *pEvent );
+
+
+private:
+
+ enum
+ {
+ NUM_ASSIST_ENEMIES = 2
+ };
+
+ // The order goes away when the player who has the assist order shoots
+ // one of these enemies.
+ CHandle<CBaseEntity> m_Enemies[NUM_ASSIST_ENEMIES];
+};
+
+
+#endif // TF_ORDER_ASSIST_H
diff --git a/game/server/tf2/order_buildsentrygun.cpp b/game/server/tf2/order_buildsentrygun.cpp
new file mode 100644
index 0000000..241947c
--- /dev/null
+++ b/game/server/tf2/order_buildsentrygun.cpp
@@ -0,0 +1,55 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#include "cbase.h"
+#include "order_buildsentrygun.h"
+#include "tf_class_defender.h"
+#include "tf_team.h"
+#include "order_helpers.h"
+
+
+// The defender will get orders to cover objects with sentry guns this far away.
+#define SENTRYGUN_ORDER_DIST 3500
+
+
+IMPLEMENT_SERVERCLASS_ST( COrderBuildSentryGun, DT_OrderBuildSentryGun )
+END_SEND_TABLE()
+
+
+bool COrderBuildSentryGun::CreateOrder( CPlayerClassDefender *pClass )
+{
+ if ( !pClass->CanBuildSentryGun() )
+ return false;
+
+ COrderBuildSentryGun *pOrder = new COrderBuildSentryGun;
+ if ( OrderCreator_GenericObject( pClass, OBJ_SENTRYGUN_PLASMA, SENTRYGUN_ORDER_DIST, pOrder ) )
+ {
+ return true;
+ }
+ else
+ {
+ UTIL_RemoveImmediate( pOrder );
+ return false;
+ }
+}
+
+
+bool COrderBuildSentryGun::Update()
+{
+ // If the entity we were supposed to cover with the sentry gun is covered now,
+ // then the order is done.
+ CBaseEntity *pEnt = GetTargetEntity();
+ if( !pEnt || !m_hOwningPlayer.Get() )
+ return true;
+
+ CTFTeam *pTeam = m_hOwningPlayer->GetTFTeam();
+ if( pTeam->IsCoveredBySentryGun( pEnt->GetAbsOrigin() ) )
+ return true;
+
+ return false;
+}
+
diff --git a/game/server/tf2/order_buildsentrygun.h b/game/server/tf2/order_buildsentrygun.h
new file mode 100644
index 0000000..86307cc
--- /dev/null
+++ b/game/server/tf2/order_buildsentrygun.h
@@ -0,0 +1,38 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef ORDER_BUILDSENTRYGUN_H
+#define ORDER_BUILDSENTRYGUN_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+
+#include "orders.h"
+
+
+class CPlayerClassDefender;
+
+
+class COrderBuildSentryGun : public COrder
+{
+public:
+ DECLARE_CLASS( COrderBuildSentryGun, COrder );
+ DECLARE_SERVERCLASS();
+
+ // Create an order for the player.
+ static bool CreateOrder( CPlayerClassDefender *pClass );
+
+
+// COrder overrides.
+public:
+
+ virtual bool Update( void );
+};
+
+
+#endif // ORDER_BUILDSENTRYGUN_H
diff --git a/game/server/tf2/order_buildshieldwall.cpp b/game/server/tf2/order_buildshieldwall.cpp
new file mode 100644
index 0000000..f561f5b
--- /dev/null
+++ b/game/server/tf2/order_buildshieldwall.cpp
@@ -0,0 +1,51 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#include "cbase.h"
+#include "order_buildshieldwall.h"
+#include "tf_team.h"
+#include "order_helpers.h"
+
+
+// The defender will get orders to cover objects with sentry guns this far away.
+#define SANDBAG_ORDER_DIST 3500
+
+
+IMPLEMENT_SERVERCLASS_ST( COrderBuildShieldWall, DT_OrderBuildShieldWall )
+END_SEND_TABLE()
+
+
+bool COrderBuildShieldWall::CreateOrder( CPlayerClass *pClass )
+{
+ COrderBuildShieldWall *pOrder = new COrderBuildShieldWall;
+ if ( OrderCreator_GenericObject( pClass, OBJ_SHIELDWALL, 2000, pOrder ) )
+ {
+ return true;
+ }
+ else
+ {
+ UTIL_RemoveImmediate( pOrder );
+ return false;
+ }
+}
+
+
+bool COrderBuildShieldWall::Update()
+{
+ CBaseEntity *pEnt = GetTargetEntity();
+ if( !pEnt )
+ return true;
+
+ if ( !m_hOwningPlayer.Get() || m_hOwningPlayer->CanBuild( OBJ_SHIELDWALL ) != CB_CAN_BUILD )
+ return true;
+
+ CTFTeam *pTeam = m_hOwningPlayer->GetTFTeam();
+ if ( pTeam->GetNumShieldWallsCoveringPosition( pEnt->GetAbsOrigin() ) )
+ return true;
+
+ return false;
+}
diff --git a/game/server/tf2/order_buildshieldwall.h b/game/server/tf2/order_buildshieldwall.h
new file mode 100644
index 0000000..5a8bd7b
--- /dev/null
+++ b/game/server/tf2/order_buildshieldwall.h
@@ -0,0 +1,35 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef ORDER_BUILDSHIELDWALL_H
+#define ORDER_BUILDSHIELDWALL_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+
+#include "orders.h"
+
+
+class COrderBuildShieldWall : public COrder
+{
+public:
+ DECLARE_CLASS( COrderBuildShieldWall, COrder );
+ DECLARE_SERVERCLASS();
+
+ // Create an order for the player.
+ static bool CreateOrder( CPlayerClass *pClass );
+
+
+// COrder overrides.
+public:
+
+ virtual bool Update( void );
+};
+
+
+#endif // ORDER_BUILDSHIELDWALL_H
diff --git a/game/server/tf2/order_events.cpp b/game/server/tf2/order_events.cpp
new file mode 100644
index 0000000..617126a
--- /dev/null
+++ b/game/server/tf2/order_events.cpp
@@ -0,0 +1,26 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//
+//=============================================================================//
+
+
+#include "cbase.h"
+
+#include "order_events.h"
+#include "tf_team.h"
+
+//-----------------------------------------------------------------------------
+// Purpose: Fire an event for all teams telling them to update their orders
+//-----------------------------------------------------------------------------
+void GlobalOrderEvent( COrderEvent_Base *pOrder )
+{
+ // Loop through the teams
+ for ( int i = 0; i < GetNumberOfTeams(); i++ )
+ {
+ CTFTeam *pTeam = GetGlobalTFTeam( i );
+ pTeam->UpdateOrdersOnEvent( pOrder );
+ }
+}
diff --git a/game/server/tf2/order_events.h b/game/server/tf2/order_events.h
new file mode 100644
index 0000000..20d0864
--- /dev/null
+++ b/game/server/tf2/order_events.h
@@ -0,0 +1,110 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef TF_ORDER_EVENTS_H
+#define TF_ORDER_EVENTS_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+
+#include "tf_player.h"
+
+
+//-----------------------------------------------------------------------------
+// ORDER EVENTS
+//-----------------------------------------------------------------------------
+
+typedef enum
+{
+ ORDER_EVENT_PLAYER_DISCONNECTED, // COrderEvent_PlayerDisconnected
+ ORDER_EVENT_PLAYER_KILLED, // CorderEvent_PlayerKilled
+ ORDER_EVENT_PLAYER_RESPAWNED, // COrderEvent_PlayerRespawned
+ ORDER_EVENT_OBJECT_DESTROYED, // COrderEvent_ObjectDestroyed
+ ORDER_EVENT_PLAYER_DAMAGED // COrderEvent_PlayerDamaged
+} OrderEventType;
+
+
+abstract_class COrderEvent_Base
+{
+public:
+ virtual OrderEventType GetType() = 0;
+};
+
+
+// Fire a global order event. It goes to all orders so they can determine if
+// they want to react.
+void GlobalOrderEvent( COrderEvent_Base *pOrder );
+
+
+class COrderEvent_PlayerDisconnected : public COrderEvent_Base
+{
+public:
+ COrderEvent_PlayerDisconnected( CBaseEntity *pPlayer )
+ {
+ m_pPlayer = pPlayer;
+ }
+
+ virtual OrderEventType GetType() { return ORDER_EVENT_PLAYER_DISCONNECTED; }
+
+ CBaseEntity *m_pPlayer;
+};
+
+
+class COrderEvent_PlayerKilled : public COrderEvent_Base
+{
+public:
+ COrderEvent_PlayerKilled( CBaseEntity *pPlayer )
+ {
+ m_pPlayer = pPlayer;
+ }
+
+ virtual OrderEventType GetType() { return ORDER_EVENT_PLAYER_KILLED; }
+
+ CBaseEntity *m_pPlayer;
+};
+
+
+class COrderEvent_PlayerRespawned : public COrderEvent_Base
+{
+public:
+ COrderEvent_PlayerRespawned( CBaseEntity *pPlayer )
+ {
+ m_pPlayer = pPlayer;
+ }
+
+ virtual OrderEventType GetType() { return ORDER_EVENT_PLAYER_RESPAWNED; }
+
+ CBaseEntity *m_pPlayer;
+};
+
+
+class COrderEvent_ObjectDestroyed : public COrderEvent_Base
+{
+public:
+ COrderEvent_ObjectDestroyed( CBaseEntity *pObj )
+ {
+ m_pObject = pObj;
+ }
+
+ virtual OrderEventType GetType() { return ORDER_EVENT_OBJECT_DESTROYED; }
+
+ CBaseEntity *m_pObject;
+};
+
+
+class COrderEvent_PlayerDamaged : public COrderEvent_Base
+{
+public:
+ virtual OrderEventType GetType() { return ORDER_EVENT_PLAYER_DAMAGED; }
+
+ CBaseEntity *m_pPlayerDamaged;
+ CTakeDamageInfo m_TakeDamageInfo;
+};
+
+
+#endif // TF_ORDER_EVENTS_H
diff --git a/game/server/tf2/order_heal.cpp b/game/server/tf2/order_heal.cpp
new file mode 100644
index 0000000..2e6631b
--- /dev/null
+++ b/game/server/tf2/order_heal.cpp
@@ -0,0 +1,111 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#include "cbase.h"
+#include "order_heal.h"
+#include "tf_team.h"
+#include "tf_playerclass.h"
+#include "order_helpers.h"
+
+
+#define MAX_HEAL_DIST 1500
+
+IMPLEMENT_SERVERCLASS_ST( COrderHeal, DT_OrderHeal )
+END_SEND_TABLE()
+
+
+bool IsValidFn_Heal( void *pUserData, int a )
+{
+ // Can't heal dead players.
+ CSortBase *p = (CSortBase*)pUserData;
+ CBasePlayer *pPlayer = p->m_pPlayer->GetTeam()->GetPlayer( a );
+
+ // Can't heal yourself...
+ if (p->m_pPlayer == pPlayer)
+ return false;
+
+ // Don't heal players that are too far away...
+ const Vector &vPlayer = p->m_pPlayer->GetAbsOrigin();
+ if (vPlayer.DistToSqr(pPlayer->GetAbsOrigin()) > MAX_HEAL_DIST * MAX_HEAL_DIST )
+ return false;
+
+ return pPlayer->IsAlive() && pPlayer->m_iHealth < pPlayer->m_iMaxHealth;
+}
+
+
+int SortFn_Heal( void *pUserData, int a, int b )
+{
+ CSortBase *p = (CSortBase*)pUserData;
+
+ const Vector &vPlayer = p->m_pPlayer->GetAbsOrigin();
+ const Vector &va = p->m_pPlayer->GetTeam()->GetPlayer( a )->GetAbsOrigin();
+ const Vector &vb = p->m_pPlayer->GetTeam()->GetPlayer( b )->GetAbsOrigin();
+
+ return vPlayer.DistToSqr( va ) < vPlayer.DistToSqr( vb );
+}
+
+
+bool COrderHeal::CreateOrder( CPlayerClass *pClass )
+{
+ CTFTeam *pTeam = pClass->GetTeam();
+
+ CSortBase info;
+ info.m_pPlayer = pClass->GetPlayer();
+
+ int sorted[MAX_PLAYERS];
+ int nSorted = BuildSortedActiveList(
+ sorted,
+ MAX_PLAYERS,
+ SortFn_Heal,
+ IsValidFn_Heal,
+ &info,
+ pTeam->GetNumPlayers()
+ );
+
+ if ( nSorted )
+ {
+ COrderHeal *pOrder = new COrderHeal;
+
+ pClass->GetTeam()->AddOrder(
+ ORDER_HEAL,
+ pTeam->GetPlayer( sorted[0] ),
+ pClass->GetPlayer(),
+ 1e24,
+ 60,
+ pOrder );
+
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+}
+
+
+bool COrderHeal::Update()
+{
+ CBaseEntity *pTarget = GetTargetEntity();
+ if ( !pTarget || pTarget->m_iHealth >= pTarget->m_iMaxHealth )
+ return true;
+
+ return false;
+}
+
+
+bool COrderHeal::UpdateOnEvent( COrderEvent_Base *pEvent )
+{
+ if ( pEvent->GetType() == ORDER_EVENT_PLAYER_KILLED )
+ {
+ COrderEvent_PlayerKilled *pKilled = (COrderEvent_PlayerKilled*)pEvent;
+ if ( pKilled->m_pPlayer == GetTargetEntity() )
+ return true;
+ }
+
+ return BaseClass::UpdateOnEvent( pEvent );
+}
+
diff --git a/game/server/tf2/order_heal.h b/game/server/tf2/order_heal.h
new file mode 100644
index 0000000..28271b2
--- /dev/null
+++ b/game/server/tf2/order_heal.h
@@ -0,0 +1,39 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef ORDER_HEAL_H
+#define ORDER_HEAL_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+
+#include "order_player.h"
+
+
+class CPlayerClass;
+
+
+class COrderHeal : public COrderPlayer
+{
+public:
+ DECLARE_CLASS( COrderHeal, COrderPlayer );
+ DECLARE_SERVERCLASS();
+
+ // Create an order for the player.
+ static bool CreateOrder( CPlayerClass *pClass );
+
+
+// COrder overrides.
+public:
+
+ virtual bool Update();
+ virtual bool UpdateOnEvent( COrderEvent_Base *pEvent );
+};
+
+
+#endif // ORDER_HEAL_H
diff --git a/game/server/tf2/order_helpers.cpp b/game/server/tf2/order_helpers.cpp
new file mode 100644
index 0000000..56274ce
--- /dev/null
+++ b/game/server/tf2/order_helpers.cpp
@@ -0,0 +1,345 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#include "cbase.h"
+
+#include "order_helpers.h"
+#include "tf_team.h"
+#include "tf_func_resource.h"
+#include "tf_obj.h"
+
+
+// ------------------------------------------------------------------------ //
+// CSortBase implementation.
+// ------------------------------------------------------------------------ //
+
+CSortBase::CSortBase()
+{
+ m_pPlayer = 0;
+ m_pTeam = 0;
+}
+
+
+CTFTeam* CSortBase::GetTeam()
+{
+ if ( m_pTeam )
+ return m_pTeam;
+ else
+ return m_pPlayer->GetTFTeam();
+}
+
+
+
+// ------------------------------------------------------------------------ //
+// Global functions.
+// ------------------------------------------------------------------------ //
+
+int SortFn_TeamPlayersByDistance( void *pUserData, int a, int b )
+{
+ CSortBase *p = (CSortBase*)pUserData;
+
+ const Vector &vPlayer = p->m_pPlayer->GetAbsOrigin();
+ const Vector &va = p->m_pPlayer->GetTeam()->GetPlayer( a )->GetAbsOrigin();
+ const Vector &vb = p->m_pPlayer->GetTeam()->GetPlayer( b )->GetAbsOrigin();
+
+ return vPlayer.DistTo( va ) < vPlayer.DistTo( vb );
+}
+
+
+// This is a generic function that takes a number of items and builds a sorted
+// list of the valid items.
+int BuildSortedActiveList(
+ int *pList, // This is the list where the final data is placed.
+ int nMaxItems,
+ sortFn pSortFn, // Callbacks.
+ isValidFn pIsValidFn, // This can be null, in which case all items are valid.
+ void *pUserData, // Passed into the function pointers.
+ int nItems // Number of items in the list to sort.
+ )
+{
+ // First build the list of active items.
+ if( nItems > nMaxItems )
+ nItems = nMaxItems;
+
+ int nActive = 0;
+ for( int i=0; i < nItems; i++ )
+ {
+ if( pIsValidFn )
+ {
+ if( !pIsValidFn( pUserData, i ) )
+ continue;
+ }
+
+ int j;
+ for( j=0; j < nActive; j++ )
+ {
+ Assert( pList[j] < nItems );
+ if( pSortFn( pUserData, i, pList[j] ) > 0 )
+ {
+ break;
+ }
+ }
+
+ // Slide everything up.
+ if( nActive )
+ {
+ Q_memmove( &pList[j+1], &pList[j], (nActive-j) * sizeof(int) );
+ }
+
+ // Add the new item to the list.
+ pList[j] = i;
+ ++nActive;
+
+ for (int l = 0; l < nActive ; ++l )
+ {
+ Assert( pList[l] < nItems );
+ }
+
+ }
+
+ return nActive;
+}
+
+
+// Finds the closest resource zone without the specified object on it and
+// gives an order to the player to build the object.
+bool OrderCreator_ResourceZoneObject(
+ CBaseTFPlayer *pPlayer,
+ int objType,
+ COrder *pOrder
+ )
+{
+ // Can we even build a resource box?
+ if ( pPlayer->CanBuild( objType ) != CB_CAN_BUILD )
+ return false;
+
+ CTFTeam *pTeam = pPlayer->GetTFTeam();
+ if( !pTeam )
+ return false;
+
+ // Let's have one near each resource zone that we own.
+ CResourceZone *pClosest = 0;
+ float flClosestDist = 100000000;
+
+ CBaseEntity *pEntity = NULL;
+ while( (pEntity = gEntList.FindEntityByClassname( pEntity, "trigger_resourcezone" )) != NULL )
+ {
+ CResourceZone *pZone = (CResourceZone*)pEntity;
+
+ // Ignore empty zones and zones not captured by this team.
+ if ( pZone->IsEmpty() || !pZone->GetActive() )
+ continue;
+
+ Vector vZoneCenter = pZone->WorldSpaceCenter();
+
+ // Look for a resource pump on this zone.
+ bool bPump = objType == OBJ_RESOURCEPUMP && pPlayer->NumPumpsOnResourceZone( pZone ) == 0;
+ if ( bPump )
+ {
+ // Make sure it's their preferred tech.
+ float flTestDist = pPlayer->GetAbsOrigin().DistTo( vZoneCenter );
+ if ( flTestDist < flClosestDist )
+ {
+ pClosest = pZone;
+ flClosestDist = flTestDist;
+ }
+ }
+ }
+
+ if ( pClosest )
+ {
+ // No pump here. Build one!
+ pPlayer->GetTFTeam()->AddOrder(
+ ORDER_BUILD,
+ pClosest,
+ pPlayer,
+ 1e24,
+ 60,
+ pOrder
+ );
+
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+}
+
+
+int SortFn_PlayerObjectsByDistance( void *pUserData, int a, int b )
+{
+ CSortBase *pSortBase = (CSortBase*)pUserData;
+
+ CBaseObject* pObjA = pSortBase->m_pPlayer->GetObject(a);
+ CBaseObject* pObjB = pSortBase->m_pPlayer->GetObject(b);
+ if (!pObjA)
+ return false;
+ if (!pObjB)
+ return true;
+
+ const Vector &v = pSortBase->m_pPlayer->GetAbsOrigin();
+
+ return v.DistTo( pObjA->GetAbsOrigin() ) < v.DistTo( pObjB->GetAbsOrigin() );
+}
+
+
+int SortFn_TeamObjectsByDistance( void *pUserData, int a, int b )
+{
+ CSortBase *pSortBase = (CSortBase*)pUserData;
+
+ CBaseObject *pObj1 = pSortBase->m_pPlayer->GetTFTeam()->GetObject( a );
+ CBaseObject *pObj2 = pSortBase->m_pPlayer->GetTFTeam()->GetObject( b );
+ const Vector &v = pSortBase->m_pPlayer->GetAbsOrigin();
+
+ return v.DistTo( pObj1->GetAbsOrigin() ) < v.DistTo( pObj2->GetAbsOrigin() );
+}
+
+
+int SortFn_PlayerEntitiesByDistance( void *pUserData, int a, int b )
+{
+ CSortBase *pSortBase = (CSortBase*)pUserData;
+
+ CBaseEntity *pObj1 = CBaseEntity::Instance( engine->PEntityOfEntIndex( a+1 ) );
+ CBaseEntity *pObj2 = CBaseEntity::Instance( engine->PEntityOfEntIndex( b+1 ) );
+ const Vector &v = pSortBase->m_pPlayer->GetAbsOrigin();
+
+ return v.DistTo( pObj1->GetAbsOrigin() ) < v.DistTo( pObj2->GetAbsOrigin() );
+}
+
+
+int SortFn_DistanceAndConcentration( void *pUserData, int a, int b )
+{
+ CSortBase *p = (CSortBase*)pUserData;
+
+ // Compare distances. Each rope attachment to another ent
+ // subtracts 200 inches, so the order is biased towards covering
+ // groups of objects together.
+ CBaseObject *pObjectA = p->GetTeam()->GetObject( a );
+ CBaseObject *pObjectB = p->GetTeam()->GetObject( b );
+
+ const Vector &vOrigin1 = pObjectA->GetAbsOrigin();
+ const Vector &vOrigin2 = p->GetTeam()->GetObject( b )->GetAbsOrigin();
+
+ float flScore1 = -p->m_pPlayer->GetAbsOrigin().DistTo( vOrigin1 );
+ float flScore2 = -p->m_pPlayer->GetAbsOrigin().DistTo( vOrigin2 );
+
+ flScore1 += pObjectA->RopeCount() * 200;
+ flScore2 += pObjectB->RopeCount() * 200;
+
+ return flScore1 > flScore2;
+}
+
+
+bool IsValidFn_NearAndNotCovered( void *pUserData, int a )
+{
+ CSortBase *p = (CSortBase*)pUserData;
+ CBaseObject *pObj = p->m_pPlayer->GetTFTeam()->GetObject( a );
+
+ // Is the object too far away to be covered?
+ if ( p->m_pPlayer->GetAbsOrigin().DistTo( pObj->GetAbsOrigin() ) > p->m_flMaxDist )
+ return false;
+
+ // Don't cover certain entities (like sentry guns, sand bags, etc).
+ switch( p->m_ObjectType )
+ {
+ case OBJ_SENTRYGUN_PLASMA:
+ {
+ if ( !pObj->WantsCoverFromSentryGun() )
+ return false;
+
+ if ( p->m_pPlayer->GetTFTeam()->IsCoveredBySentryGun( pObj->GetAbsOrigin() ) )
+ return false;
+ }
+ break;
+
+ case OBJ_SHIELDWALL:
+ {
+ if ( !pObj->WantsCover() )
+ return false;
+
+ if ( p->m_pPlayer->GetTFTeam()->GetNumShieldWallsCoveringPosition( pObj->GetAbsOrigin() ) )
+ return false;
+ }
+ break;
+
+ case OBJ_RESUPPLY:
+ {
+ if ( p->m_pPlayer->GetTFTeam()->GetNumResuppliesCoveringPosition( pObj->GetAbsOrigin() ) )
+ return false;
+ }
+ break;
+
+ case OBJ_RESPAWN_STATION:
+ {
+ if ( p->m_pPlayer->GetTFTeam()->GetNumRespawnStationsCoveringPosition( pObj->GetAbsOrigin() ) )
+ return false;
+ }
+ break;
+
+ default:
+ {
+ Assert( !"Unsupported object type" );
+ }
+ break;
+ }
+
+ return true;
+}
+
+
+bool OrderCreator_GenericObject(
+ CPlayerClass *pClass,
+ int objectType,
+ float flMaxDist,
+ COrder *pOrder
+ )
+{
+ // Can we build one?
+ if ( pClass->CanBuild( objectType ) != CB_CAN_BUILD )
+ return false;
+
+ CBaseTFPlayer *pPlayer = pClass->GetPlayer();
+ CTFTeam *pTeam = pClass->GetTeam();
+
+ // Sort nearby objects.
+ CSortBase info;
+ info.m_pPlayer = pPlayer;
+ info.m_flMaxDist = flMaxDist;
+ info.m_ObjectType = objectType;
+
+ int sorted[MAX_TEAM_OBJECTS];
+ int nSorted = BuildSortedActiveList(
+ sorted, // the sorted list of objects
+ MAX_TEAM_OBJECTS,
+ SortFn_DistanceAndConcentration, // sort on distance and entity concentration
+ IsValidFn_NearAndNotCovered, // filter function
+ &info, // user data
+ pTeam->GetNumObjects() // number of objects to check
+ );
+
+ if( nSorted )
+ {
+ // Ok, make an order to cover the closest object with a sentry gun.
+ CBaseEntity *pEnt = pTeam->GetObject( sorted[0] );
+
+ pTeam->AddOrder(
+ ORDER_BUILD,
+ pEnt,
+ pPlayer,
+ flMaxDist,
+ 60,
+ pOrder
+ );
+
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+}
diff --git a/game/server/tf2/order_helpers.h b/game/server/tf2/order_helpers.h
new file mode 100644
index 0000000..cfea580
--- /dev/null
+++ b/game/server/tf2/order_helpers.h
@@ -0,0 +1,128 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef ORDER_HELPERS_H
+#define ORDER_HELPERS_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+
+#include "orders.h"
+
+
+class CTFTeam;
+
+
+class CSortBase
+{
+public:
+
+ CSortBase();
+
+ // Returns m_pTeam, and if that doesn't exist, returns m_pPlayer->GetTFTeam().
+ CTFTeam* GetTeam();
+
+
+public:
+
+ CBaseTFPlayer *m_pPlayer;
+
+ // If this is left at null, then GetTeam() returns m_pPlayer->GetTFTeam().
+ CTFTeam *m_pTeam;
+
+ // One of the OBJ_ defines telling what type of object it's thinking of building.
+ int m_ObjectType;
+
+ // If the object is further from m_vPlayerOrigin than this, then an order
+ // won't be generated to cover it.
+ float m_flMaxDist;
+};
+
+
+// Return positive if iItem1 > iItem2.
+// Return negative if iItem1 < iItem2.
+// Return zero if they're equal.
+typedef int (*sortFn)( void *pUserData, int iItem1, int iItem2 );
+typedef bool (*isValidFn)( void *pUserData, int iItem );
+
+
+
+// Index engine->PEntityOfIndex(a+1) and b+1.
+int SortFn_PlayerEntitiesByDistance( void *pUserData, int a, int b );
+
+// Helper sort function. Sorts CSortBase::m_pPlayer's objects by distance.
+// pUserData must be a CSortBase.
+int SortFn_PlayerObjectsByDistance( void *pUserData, int a, int b );
+
+// Helper sort function. Sorts CSortBase::m_pPlayer->GetTeam()'s objects by distance.
+// pUserData must be a CSortBase.
+int SortFn_TeamObjectsByDistance( void *pUserData, int a, int b );
+
+// Sort by distance and concentation. pUserData must point at something
+// derived from CSortBase.
+int SortFn_DistanceAndConcentration( void *pUserData, int a, int b );
+
+// pUserData is a CSortBase
+// a and b index CSortBase::m_pPlayer->GetTeam()->GetPlayer()
+// Sort players on distance.
+int SortFn_TeamPlayersByDistance( void *pUserData, int a, int b );
+
+
+// pUserdata is a CSortBase.
+//
+// Rejects the object if:
+// - it's already covered by CSortBase::m_ObjectType
+// - it's being ferried
+// - it's further from the player than CSortBase::m_flMaxDist;
+//
+// This function currently supports:
+// - OBJ_SENTRYGUN_PLASMA
+// - OBJ_SANDBAG
+// - OBJ_AUTOREPAIR
+// - OBJ_SHIELDWALL
+// - OBJ_RESUPPLY
+bool IsValidFn_NearAndNotCovered( void *pUserData, int a );
+
+
+
+// This is a generic function that takes a number of items and builds a sorted
+// list of the valid items.
+int BuildSortedActiveList(
+ int *pList, // This is the list where the final data is placed.
+ int nMaxItems,
+ sortFn pSortFn, // Callbacks.
+ isValidFn pIsValidFn, // This can be null, in which case all items are valid.
+ void *pUserData, // Passed into the function pointers.
+ int nItems // Number of items in the list to sort.
+ );
+
+// Finds the closest resource zone without the specified object on it and
+// gives an order to the player to build the object.
+// This function supports OBJ_RESOURCEBOX, OBJ_RESOURCEPUMP, and OBJ_ZONE_INCREASER.
+bool OrderCreator_ResourceZoneObject(
+ CBaseTFPlayer *pPlayer,
+ int objType,
+ COrder *pOrder
+ );
+
+// This function is shared by lots of the order creation functions.
+// It makes an order to create a specific type of object by looking for nearby
+// concentrations of team objects that aren't "covered" by objectType.
+//
+// It uses IsValidFn_NearAndNotCovered, so any object type you specify in here
+// must be supported in IsValidFn_NearAndNotCovered.
+bool OrderCreator_GenericObject(
+ CPlayerClass *pClass,
+ int objectType,
+ float flMaxDist,
+ COrder *pOrder
+ );
+
+
+
+#endif // ORDER_HELPERS_H
diff --git a/game/server/tf2/order_killmortarguy.cpp b/game/server/tf2/order_killmortarguy.cpp
new file mode 100644
index 0000000..ff2bdff
--- /dev/null
+++ b/game/server/tf2/order_killmortarguy.cpp
@@ -0,0 +1,125 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#include "cbase.h"
+#include "order_helpers.h"
+#include "order_killmortarguy.h"
+#include "tf_team.h"
+
+
+#define KILLMORTARGUY_DIST 3500
+
+
+IMPLEMENT_SERVERCLASS_ST( COrderKillMortarGuy, DT_OrderKillMortarGuy )
+END_SEND_TABLE()
+
+
+static bool IsValidFn_DeployedBrianJacobsons( void *pUserData, int iClient )
+{
+ CBaseTFPlayer *pPlayer = (CBaseTFPlayer*)UTIL_PlayerByIndex( iClient+1 );
+ if ( !pPlayer || pPlayer->IsClass( TFCLASS_UNDECIDED ) || !pPlayer->GetTeam() )
+ return false;
+
+ // Is this person on an enemy team?
+ CSortBase *pSortBase = (CSortBase*)pUserData;
+ CBaseTFPlayer *pMyPlayer = pSortBase->m_pPlayer;
+
+ if( pPlayer->GetTeam()->GetTeamNumber() == pMyPlayer->GetTeam()->GetTeamNumber() )
+ return false;
+
+ // Is he alive?
+ if( !pPlayer->IsAlive() )
+ return false;
+
+ // ROBIN: Removed mortar object. This needs to handle mortar vehicles instead.
+ // Is he looking surly?
+ //if( pPlayer->GetNumObjects( OBJ_MORTAR ) == 0 )
+ //return false;
+
+ // Is he close enough?
+ if( pMyPlayer->GetAbsOrigin().DistTo( pPlayer->GetAbsOrigin() ) > KILLMORTARGUY_DIST )
+ return false;
+
+ // Is he visible to the tactical?
+// if( !pMyPlayer->GetTFTeam()->IsEntityVisibleToTactical( pPlayer ) )
+// return false;
+
+ // KILL HIM!!!
+ return true;
+}
+
+
+static int SortFn_PlayerEntsByDistance( void *pUserData, int a, int b )
+{
+ CBaseEntity *pEdictA = CBaseEntity::Instance( engine->PEntityOfEntIndex( a+1 ) );
+ CBaseEntity *pEdictB = CBaseEntity::Instance( engine->PEntityOfEntIndex( b+1 ) );
+
+ if ( !pEdictA || !pEdictB )
+ return 1;
+
+ CSortBase *pSortBase = (CSortBase*)pUserData;
+ const Vector &v = pSortBase->m_pPlayer->GetAbsOrigin();
+
+ return v.DistTo( pEdictA->GetAbsOrigin() ) < v.DistTo( pEdictB->GetAbsOrigin() );
+}
+
+
+bool COrderKillMortarGuy::CreateOrder( CPlayerClass *pClass )
+{
+ CSortBase info;
+ info.m_pPlayer = pClass->GetPlayer();
+
+ // Look for an enemy sniper visible to the
+ int supports[MAX_PLAYERS];
+ int nSupports = BuildSortedActiveList(
+ supports, // the sorted list
+ MAX_PLAYERS,
+ SortFn_PlayerEntsByDistance, // sort on distance
+ IsValidFn_DeployedBrianJacobsons, // only get deployed support guys
+ &info, // pUserData
+ gpGlobals->maxClients // how many players to look through
+ );
+
+ // Kill the closest punk.
+ if( nSupports )
+ {
+ CBaseTFPlayer *pBrian = (CBaseTFPlayer*)UTIL_PlayerByIndex( supports[0]+1 );
+ Assert( pBrian );
+
+ COrderKillMortarGuy *pOrder = new COrderKillMortarGuy;
+ pClass->GetTeam()->AddOrder(
+ ORDER_KILL,
+ pBrian,
+ pClass->GetPlayer(),
+ 1e24,
+ 60,
+ pOrder
+ );
+
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+}
+
+
+bool COrderKillMortarGuy::UpdateOnEvent( COrderEvent_Base *pEvent )
+{
+ if ( pEvent->GetType() == ORDER_EVENT_PLAYER_KILLED )
+ {
+ COrderEvent_PlayerKilled *pKilled = (COrderEvent_PlayerKilled*)pEvent;
+ if ( pKilled->m_pPlayer == GetTargetEntity() )
+ return true;
+ }
+
+ return BaseClass::UpdateOnEvent( pEvent );
+}
+
+
+
diff --git a/game/server/tf2/order_killmortarguy.h b/game/server/tf2/order_killmortarguy.h
new file mode 100644
index 0000000..c80f562
--- /dev/null
+++ b/game/server/tf2/order_killmortarguy.h
@@ -0,0 +1,38 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef ORDER_KILLMORTARGUY_H
+#define ORDER_KILLMORTARGUY_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+
+#include "order_player.h"
+
+
+class CPlayerClass;
+
+
+class COrderKillMortarGuy : public COrderPlayer
+{
+public:
+ DECLARE_CLASS( COrderKillMortarGuy, COrderPlayer );
+ DECLARE_SERVERCLASS();
+
+ // Create an order for the player.
+ static bool CreateOrder( CPlayerClass *pClass );
+
+
+// COrder overrides.
+public:
+
+ virtual bool UpdateOnEvent( COrderEvent_Base *pEvent );
+};
+
+
+#endif // ORDER_KILLMORTARGUY_H
diff --git a/game/server/tf2/order_mortar_attack.cpp b/game/server/tf2/order_mortar_attack.cpp
new file mode 100644
index 0000000..1f98da3
--- /dev/null
+++ b/game/server/tf2/order_mortar_attack.cpp
@@ -0,0 +1,77 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#include "cbase.h"
+#include "order_mortar_attack.h"
+#include "tf_team.h"
+#include "order_helpers.h"
+#include "tf_obj.h"
+
+
+// How far away the escort guy will get orders to shell enemy objects.
+#define ENEMYOBJ_MORTAR_DIST 3000
+
+
+IMPLEMENT_SERVERCLASS_ST( COrderMortarAttack, DT_OrderMortarAttack )
+END_SEND_TABLE()
+
+
+static bool IsValidFn_WithinMortarRange( void *pUserData, int a )
+{
+ CSortBase *p = (CSortBase*)pUserData;
+ CBaseObject *pObj = p->GetTeam()->GetObject( a );
+ return pObj->GetAbsOrigin().DistTo( p->m_pPlayer->GetAbsOrigin() ) < ENEMYOBJ_MORTAR_DIST;
+}
+
+
+bool COrderMortarAttack::CreateOrder( CPlayerClass *pClass )
+{
+ // Look for some nearby enemy objects that would be fun to destroy.
+ CTFTeam *pEnemyTeam;
+ if ( !pClass->GetTeam() || (pEnemyTeam = pClass->GetTeam()->GetEnemyTeam()) == NULL )
+ return false;
+
+ CBaseTFPlayer *pPlayer = pClass->GetPlayer();
+
+ CSortBase info;
+ info.m_pPlayer = pPlayer;
+ info.m_pTeam = pEnemyTeam;
+
+ int sorted[MAX_TEAM_OBJECTS];
+ int nSorted = BuildSortedActiveList(
+ sorted, // the sorted list of objects
+ MAX_TEAM_OBJECTS,
+ SortFn_DistanceAndConcentration, // sort on distance and entity concentration
+ IsValidFn_WithinMortarRange, // filter function
+ &info, // user data
+ pEnemyTeam->GetNumObjects() // number of objects to check
+ );
+
+ if( nSorted > 0 )
+ {
+ CBaseEntity *pEnt = pEnemyTeam->GetObject( sorted[0] );
+
+ COrderMortarAttack *pOrder = new COrderMortarAttack;
+
+ pClass->GetTeam()->AddOrder(
+ ORDER_MORTAR_ATTACK,
+ pEnt,
+ pPlayer,
+ 1e24,
+ 40,
+ pOrder
+ );
+
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+}
+
+
diff --git a/game/server/tf2/order_mortar_attack.h b/game/server/tf2/order_mortar_attack.h
new file mode 100644
index 0000000..5162fdc
--- /dev/null
+++ b/game/server/tf2/order_mortar_attack.h
@@ -0,0 +1,29 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef ORDER_BUILDMORTAR_H
+#define ORDER_BUILDMORTAR_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+
+#include "orders.h"
+
+
+class COrderMortarAttack : public COrder
+{
+public:
+ DECLARE_CLASS( COrderMortarAttack, COrder );
+ DECLARE_SERVERCLASS();
+
+ // Create an order for the player.
+ static bool CreateOrder( CPlayerClass *pClass );
+};
+
+
+#endif // ORDER_BUILDMORTAR_H
diff --git a/game/server/tf2/order_player.cpp b/game/server/tf2/order_player.cpp
new file mode 100644
index 0000000..3cd9043
--- /dev/null
+++ b/game/server/tf2/order_player.cpp
@@ -0,0 +1,26 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#include "cbase.h"
+
+#include "order_player.h"
+
+
+IMPLEMENT_SERVERCLASS_ST( COrderPlayer, DT_OrderPlayer )
+END_SEND_TABLE()
+
+
+
+bool COrderPlayer::UpdateOnEvent( COrderEvent_Base *pEvent )
+{
+ // All player orders give up if the player disconnects
+ if ( pEvent->GetType() == ORDER_EVENT_PLAYER_DISCONNECTED )
+ return true;
+
+ return BaseClass::UpdateOnEvent( pEvent );
+}
+
diff --git a/game/server/tf2/order_player.h b/game/server/tf2/order_player.h
new file mode 100644
index 0000000..d757eed
--- /dev/null
+++ b/game/server/tf2/order_player.h
@@ -0,0 +1,32 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef ORDER_PLAYER_H
+#define ORDER_PLAYER_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+
+#include "orders.h"
+
+
+class COrderPlayer : public COrder
+{
+public:
+ DECLARE_CLASS( COrderPlayer, COrder );
+ DECLARE_SERVERCLASS();
+
+
+// COrder overrides.
+public:
+
+ virtual bool UpdateOnEvent( COrderEvent_Base *pEvent );
+};
+
+
+#endif // ORDER_PLAYER_H
diff --git a/game/server/tf2/order_repair.cpp b/game/server/tf2/order_repair.cpp
new file mode 100644
index 0000000..a75f75d
--- /dev/null
+++ b/game/server/tf2/order_repair.cpp
@@ -0,0 +1,157 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#include "cbase.h"
+#include "order_repair.h"
+#include "tf_team.h"
+#include "tf_class_defender.h"
+#include "order_helpers.h"
+#include "tf_obj.h"
+
+
+IMPLEMENT_SERVERCLASS_ST( COrderRepair, DT_OrderRepair )
+END_SEND_TABLE()
+
+
+static int SortFn_Defender( void *pUserData, int a, int b )
+{
+ CSortBase *p = (CSortBase*)pUserData;
+
+ const Vector &vOrigin1 = p->m_pPlayer->GetTFTeam()->GetObject( a )->GetAbsOrigin();
+ const Vector &vOrigin2 = p->m_pPlayer->GetTFTeam()->GetObject( b )->GetAbsOrigin();
+
+ return p->m_pPlayer->GetAbsOrigin().DistTo( vOrigin1 ) < p->m_pPlayer->GetAbsOrigin().DistTo( vOrigin2 );
+}
+
+
+static bool IsValidFn_RepairFriendlyObjects( void *pUserData, int a )
+{
+ // Only pick objects that are damaged.
+ CSortBase *p = (CSortBase*)pUserData;
+ CBaseObject *pObj = p->m_pPlayer->GetTFTeam()->GetObject( a );
+
+ // Skip objects under construction
+ if ( pObj->IsBuilding() )
+ return false;
+
+ return ( pObj->m_iHealth < pObj->m_iMaxHealth );
+}
+
+
+static bool IsValidFn_RepairOwnObjects( void *pUserData, int a )
+{
+ // Only pick objects that are damaged.
+ CSortBase *pSortBase = (CSortBase*)pUserData;
+ CBaseObject *pObj = pSortBase->m_pPlayer->GetObject(a);
+
+ // Skip objects under construction
+ if ( !pObj || pObj->IsBuilding() )
+ return false;
+
+ return pObj->m_iHealth < pObj->m_iMaxHealth;
+}
+
+
+bool COrderRepair::CreateOrder_RepairFriendlyObjects( CPlayerClassDefender *pClass )
+{
+ if( !pClass->CanBuildSentryGun() )
+ return false;
+
+ CBaseTFPlayer *pPlayer = pClass->GetPlayer();
+ CTFTeam *pTeam = pClass->GetTeam();
+
+ // Sort the list and filter out fully healed objects..
+ CSortBase info;
+ info.m_pPlayer = pPlayer;
+
+ int sorted[MAX_TEAM_OBJECTS];
+ int nSorted = BuildSortedActiveList(
+ sorted,
+ MAX_TEAM_OBJECTS,
+ SortFn_Defender,
+ IsValidFn_RepairFriendlyObjects,
+ &info,
+ pTeam->GetNumObjects() );
+
+ // If the player is close enough to the closest damaged object, issue an order.
+ if( nSorted )
+ {
+ CBaseObject *pObjToHeal = pTeam->GetObject( sorted[0] );
+
+ static float flClosestDist = 1024;
+ if( pPlayer->GetAbsOrigin().DistTo( pObjToHeal->GetAbsOrigin() ) < flClosestDist )
+ {
+ COrder *pOrder = new COrderRepair;
+
+ pTeam->AddOrder(
+ ORDER_REPAIR,
+ pObjToHeal,
+ pPlayer,
+ 1e24,
+ 60,
+ pOrder
+ );
+
+ return true;
+ }
+ }
+
+ return false;
+}
+
+
+bool COrderRepair::CreateOrder_RepairOwnObjects( CPlayerClass *pClass )
+{
+ CSortBase info;
+ info.m_pPlayer = pClass->GetPlayer();
+
+ int sorted[16];
+ int nSorted = BuildSortedActiveList(
+ sorted,
+ sizeof( sorted ) / sizeof( sorted[0] ),
+ SortFn_PlayerObjectsByDistance,
+ IsValidFn_RepairOwnObjects,
+ &info,
+ info.m_pPlayer->GetObjectCount() );
+
+ if( nSorted )
+ {
+ // Make an order to repair the closest damaged object.
+ CBaseObject *pObj = info.m_pPlayer->GetObject( sorted[0] );
+ if (!pObj)
+ return false;
+
+ COrderRepair *pOrder = new COrderRepair;
+ info.m_pPlayer->GetTFTeam()->AddOrder(
+ ORDER_REPAIR,
+ pObj,
+ info.m_pPlayer,
+ 1e24,
+ 60,
+ pOrder
+ );
+
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+}
+
+
+bool COrderRepair::Update()
+{
+ CBaseEntity *pEnt = GetTargetEntity();
+ if( !pEnt )
+ return true;
+
+ // Kill the order when the object is repaired.
+ return pEnt->m_iHealth >= pEnt->m_iMaxHealth;
+}
+
+
diff --git a/game/server/tf2/order_repair.h b/game/server/tf2/order_repair.h
new file mode 100644
index 0000000..fd94a0b
--- /dev/null
+++ b/game/server/tf2/order_repair.h
@@ -0,0 +1,42 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef ORDER_REPAIR_H
+#define ORDER_REPAIR_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+
+#include "orders.h"
+
+
+class CPlayerClass;
+class CPlayerClassDefender;
+
+
+class COrderRepair : public COrder
+{
+public:
+ DECLARE_CLASS( COrderRepair, COrder );
+ DECLARE_SERVERCLASS();
+
+ // Create an order for the defender to fix friendly objects.
+ static bool CreateOrder_RepairFriendlyObjects( CPlayerClassDefender *pClass );
+
+ // Create an order for anyone to repair their own objects.
+ static bool CreateOrder_RepairOwnObjects( CPlayerClass *pClass );
+
+
+// COrder overrides.
+public:
+
+ virtual bool Update( void );
+};
+
+
+#endif // ORDER_REPAIR_H
diff --git a/game/server/tf2/order_resourcepump.cpp b/game/server/tf2/order_resourcepump.cpp
new file mode 100644
index 0000000..bd24706
--- /dev/null
+++ b/game/server/tf2/order_resourcepump.cpp
@@ -0,0 +1,66 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#include "cbase.h"
+#include "order_resourcepump.h"
+#include "tf_team.h"
+#include "tf_obj_resourcepump.h"
+#include "tf_func_resource.h"
+#include "order_helpers.h"
+
+
+IMPLEMENT_SERVERCLASS_ST( COrderResourcePump, DT_OrderResourcePump )
+END_SEND_TABLE()
+
+
+bool COrderResourcePump::CreateOrder( CPlayerClass *pClass )
+{
+ COrderResourcePump *pOrder = new COrderResourcePump;
+
+ if ( OrderCreator_ResourceZoneObject( pClass->GetPlayer(), OBJ_RESOURCEPUMP, pOrder ) )
+ {
+ return true;
+ }
+ else
+ {
+ UTIL_RemoveImmediate( pOrder );
+ return false;
+ }
+}
+
+
+bool COrderResourcePump::Update()
+{
+ // Can they still build resource pumps?
+ if ( !m_hOwningPlayer.Get() || m_hOwningPlayer->CanBuild( OBJ_RESOURCEPUMP ) != CB_CAN_BUILD )
+ return true;
+
+ // Lost our resource zone?
+ if ( !GetTargetEntity() )
+ return true;
+ // Is our target zone now empty?
+ if ( ((CResourceZone*)GetTargetEntity())->IsEmpty() )
+ return true;
+
+ // Have they built a pump on this zone?
+ for( int i=0; i < m_hOwningPlayer->GetObjectCount(); i++ )
+ {
+ CBaseObject *pObj = m_hOwningPlayer->GetObject(i);
+
+ if( pObj && pObj->GetType() == OBJ_RESOURCEPUMP )
+ {
+ CObjectResourcePump *pPump = (CObjectResourcePump*)pObj;
+ CResourceZone *pZone = pPump->GetResourceZone();
+ if( pZone && pZone->entindex() == m_iTargetEntIndex && !pZone->IsEmpty() )
+ return true;
+ }
+ }
+
+ return BaseClass::Update();
+}
+
+
diff --git a/game/server/tf2/order_resourcepump.h b/game/server/tf2/order_resourcepump.h
new file mode 100644
index 0000000..19cf2cf
--- /dev/null
+++ b/game/server/tf2/order_resourcepump.h
@@ -0,0 +1,38 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef ORDER_RESOURCEPUMP_H
+#define ORDER_RESOURCEPUMP_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+
+#include "orders.h"
+
+
+class CPlayerClass;
+
+
+class COrderResourcePump : public COrder
+{
+public:
+ DECLARE_CLASS( COrderResourcePump, COrder );
+ DECLARE_SERVERCLASS();
+
+ // Create an order for the player.
+ static bool CreateOrder( CPlayerClass *pClass );
+
+
+// COrder overrides.
+public:
+
+ virtual bool Update( void );
+};
+
+
+#endif // ORDER_RESOURCEPUMP_H
diff --git a/game/server/tf2/order_resupply.cpp b/game/server/tf2/order_resupply.cpp
new file mode 100644
index 0000000..54ddfc7
--- /dev/null
+++ b/game/server/tf2/order_resupply.cpp
@@ -0,0 +1,54 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#include "cbase.h"
+#include "order_resupply.h"
+#include "tf_team.h"
+#include "tf_playerclass.h"
+#include "order_helpers.h"
+
+
+// Orders to build resupplies near objects come in within this range.
+#define RESUPPLY_ORDER_MAXDIST 2000
+
+
+IMPLEMENT_SERVERCLASS_ST( COrderResupply, DT_OrderResupply )
+END_SEND_TABLE()
+
+
+bool COrderResupply::CreateOrder( CPlayerClass *pClass )
+{
+ COrderResupply *pOrder = new COrderResupply;
+ if ( OrderCreator_GenericObject( pClass, OBJ_RESUPPLY, RESUPPLY_ORDER_MAXDIST, pOrder ) )
+ {
+ return true;
+ }
+ else
+ {
+ UTIL_RemoveImmediate( pOrder );
+ return false;
+ }
+}
+
+
+bool COrderResupply::Update()
+{
+ CBaseEntity *pEnt = GetTargetEntity();
+ if( !pEnt )
+ return true;
+
+ if ( !m_hOwningPlayer.Get() || m_hOwningPlayer->CanBuild( OBJ_RESUPPLY ) != CB_CAN_BUILD )
+ return true;
+
+ CTFTeam *pTeam = m_hOwningPlayer->GetTFTeam();
+ if ( pTeam->GetNumResuppliesCoveringPosition( pEnt->GetAbsOrigin() ) )
+ return true;
+
+ return BaseClass::Update();
+}
+
+
diff --git a/game/server/tf2/order_resupply.h b/game/server/tf2/order_resupply.h
new file mode 100644
index 0000000..8aa516e
--- /dev/null
+++ b/game/server/tf2/order_resupply.h
@@ -0,0 +1,38 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef ORDER_RESUPPLY_H
+#define ORDER_RESUPPLY_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+
+#include "orders.h"
+
+
+class CPlayerClass;
+
+
+class COrderResupply : public COrder
+{
+public:
+ DECLARE_CLASS( COrderResupply, COrder );
+ DECLARE_SERVERCLASS();
+
+ // Create an order for the player.
+ static bool CreateOrder( CPlayerClass *pClass );
+
+
+// COrder overrides.
+public:
+
+ virtual bool Update();
+};
+
+
+#endif // ORDER_RESUPPLY_H
diff --git a/game/server/tf2/orders.cpp b/game/server/tf2/orders.cpp
new file mode 100644
index 0000000..3d3c906
--- /dev/null
+++ b/game/server/tf2/orders.cpp
@@ -0,0 +1,215 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Order handling
+//
+// $NoKeywords: $
+//=============================================================================//
+#include "cbase.h"
+#include "orders.h"
+#include "tf_player.h"
+#include "tf_func_resource.h"
+#include "tf_team.h"
+#include "tf_obj_resourcepump.h"
+
+
+IMPLEMENT_SERVERCLASS_ST(COrder, DT_Order)
+ SendPropInt( SENDINFO(m_iOrderType), 4, SPROP_UNSIGNED ),
+ SendPropInt( SENDINFO(m_iTargetEntIndex), 16, SPROP_UNSIGNED ),
+END_SEND_TABLE()
+
+LINK_ENTITY_TO_CLASS( tf_order, COrder );
+
+
+COrder::COrder()
+{
+ m_iOrderType = 0;
+ m_iTargetEntIndex = 0;
+ m_hTarget = NULL;
+ m_flDistanceToRemove = 0;
+ m_hOwningPlayer = NULL;
+ m_flDieTime = 0;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void COrder::UpdateOnRemove( void )
+{
+ DetachFromPlayer();
+ // Chain at end to mimic destructor unwind order
+ BaseClass::UpdateOnRemove();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Transmit weapon data
+//-----------------------------------------------------------------------------
+int COrder::ShouldTransmit( const CCheckTransmitInfo *pInfo )
+{
+ CBaseEntity* pRecipientEntity = CBaseEntity::Instance( pInfo->m_pClientEnt );
+
+ // If this is a personal order, only send to it's owner
+ if ( GetOwner() )
+ {
+ if ( GetOwner() == pRecipientEntity )
+ return FL_EDICT_ALWAYS;
+
+ return FL_EDICT_DONTSEND;
+ }
+
+ // Otherwise, only send to players on our team
+ if ( InSameTeam( pRecipientEntity ) )
+ return FL_EDICT_ALWAYS;
+
+ return FL_EDICT_DONTSEND;
+}
+
+
+void COrder::DetachFromPlayer()
+{
+ // Detach from our owner.
+ if ( m_hOwningPlayer )
+ {
+ m_hOwningPlayer->SetOrder( NULL );
+ m_hOwningPlayer = NULL;
+
+ if ( GetTeam() )
+ {
+ ((CTFTeam*)GetTeam())->RemoveOrder( this );
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+int COrder::GetType( void )
+{
+ return m_iOrderType;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CBaseEntity *COrder::GetTargetEntity( void )
+{
+ return m_hTarget;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void COrder::SetType( int iOrderType )
+{
+ m_iOrderType = iOrderType;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void COrder::SetTarget( CBaseEntity *pTarget )
+{
+ m_hTarget = pTarget;
+ if ( m_hTarget )
+ {
+ m_iTargetEntIndex = m_hTarget->entindex();
+ }
+ else
+ {
+ m_iTargetEntIndex = 0;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void COrder::SetDistance( float flDistance )
+{
+ m_flDistanceToRemove = flDistance;
+}
+
+
+void COrder::SetLifetime( float flLifetime )
+{
+ m_flDieTime = gpGlobals->curtime + flLifetime;
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+// Purpose: for updates on the order. Return true if the order should be removed.
+//-----------------------------------------------------------------------------
+bool COrder::Update( void )
+{
+ // Orders with no targets & no owners don't go away on their own
+ if ( !GetOwner() )
+ return false;
+
+ // Has it timed out?
+ if( gpGlobals->curtime > m_flDieTime )
+ return true;
+
+ // Check to make sure we're still within the correct distance
+ if ( m_flDistanceToRemove )
+ {
+ CBaseEntity *pTarget = GetTargetEntity();
+ if ( pTarget )
+ {
+ // Have the player and the target moved away from each other?
+ if ( (m_hOwningPlayer->GetAbsOrigin() - pTarget->GetAbsOrigin()).Length() > (m_flDistanceToRemove * 1.25) )
+ return true;
+ }
+ }
+
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: An event for this order's target has arrived. Return true if this order should be removed.
+//-----------------------------------------------------------------------------
+bool COrder::UpdateOnEvent( COrderEvent_Base *pEvent )
+{
+ // Default behavior is to get rid of the order if the object we're referencing
+ // gets destroyed.
+ if ( pEvent->GetType() == ORDER_EVENT_OBJECT_DESTROYED )
+ {
+ COrderEvent_ObjectDestroyed *pObjDestroyed = (COrderEvent_ObjectDestroyed*)pEvent;
+ if ( pObjDestroyed->m_pObject == GetTargetEntity() )
+ return true;
+ }
+
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CBaseTFPlayer *COrder::GetOwner( void )
+{
+ return m_hOwningPlayer;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void COrder::SetOwner( CBaseTFPlayer *pPlayer )
+{
+ // Null out our m_hOwningPlayer so we don't recurse infinitely.
+ CHandle<CBaseTFPlayer> hPlayer = m_hOwningPlayer;
+ m_hOwningPlayer = 0;
+
+ if ( hPlayer.Get() && (hPlayer != pPlayer) )
+ {
+ Assert( hPlayer->GetOrder() == this );
+ hPlayer->SetOrder( NULL );
+ }
+
+ m_hOwningPlayer = pPlayer;
+}
+
+
+
+
+
+
diff --git a/game/server/tf2/orders.h b/game/server/tf2/orders.h
new file mode 100644
index 0000000..0aa68e2
--- /dev/null
+++ b/game/server/tf2/orders.h
@@ -0,0 +1,90 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Order handling
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef ORDERS_H
+#define ORDERS_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+
+class CTFTeam;
+class CBaseTFPlayer;
+
+
+#include "order_events.h"
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Datatable container class for orders
+//-----------------------------------------------------------------------------
+class COrder : public CBaseEntity
+{
+ DECLARE_CLASS( COrder, CBaseEntity );
+public:
+ DECLARE_SERVERCLASS();
+
+ COrder();
+ virtual void UpdateOnRemove( void );
+
+ virtual int UpdateTransmitState() { return SetTransmitState( FL_EDICT_FULLCHECK ); }
+ virtual int ShouldTransmit( const CCheckTransmitInfo *pInfo );
+
+ // This is called when removing the order.
+ void DetachFromPlayer();
+
+
+// Overridables.
+public:
+
+ // Purpose: for updates on the order. Return true if the order should be removed.
+ virtual bool Update( void );
+ virtual bool UpdateOnEvent( COrderEvent_Base *pEvent );
+
+
+
+public:
+
+ CBaseTFPlayer *GetOwner( void );
+ CBaseEntity *GetTargetEntity( void );
+ int GetType( void );
+
+ void SetOwner( CBaseTFPlayer *pPlayer );
+ void SetType( int iOrderType );
+ void SetTarget( CBaseEntity *pTarget );
+ void SetDistance( float flDistance );
+ void SetLifetime( float flLifetime );
+
+public:
+ // Sent via datatable
+ CNetworkVar( int, m_iOrderType );
+ float m_flDistanceToRemove;
+
+ // When the order goes away.
+ double m_flDieTime;
+
+ // Personal order owner
+ CHandle< CBaseTFPlayer > m_hOwningPlayer;
+ EHANDLE m_hTarget;
+ CNetworkVar( int, m_iTargetEntIndex );
+};
+
+
+
+//-----------------------------------------------------------------------------
+// ORDER CREATION DATA
+//-----------------------------------------------------------------------------
+// Time between personal order updates
+#define PERSONAL_ORDER_UPDATE_TIME 2.0
+
+// KILL orders
+#define ORDER_KILL_ENEMY_DISTANCE 2048 // Distance the enemy must be within for this player to receive this order
+
+// HEAL orders
+#define ORDER_HEAL_FRIENDLY_DISTANCE 2048 // Distance the friendly must be within this player to receive this order
+
+#endif // ORDERS_H
diff --git a/game/server/tf2/ragdoll_shadow.cpp b/game/server/tf2/ragdoll_shadow.cpp
new file mode 100644
index 0000000..5c35c10
--- /dev/null
+++ b/game/server/tf2/ragdoll_shadow.cpp
@@ -0,0 +1,117 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Resource chunks
+//
+// $NoKeywords: $
+//=============================================================================//
+#include "cbase.h"
+#include "ragdoll_shadow.h"
+#include "tf_player.h"
+#include "sendproxy.h"
+
+// FIXME Hook up a real player standin
+static char *sRagdollShadowModel = "models/player/human_commando.mdl";
+
+
+IMPLEMENT_SERVERCLASS_ST( CRagdollShadow, DT_RagdollShadow )
+ SendPropInt( SENDINFO( m_nPlayer ), 10, SPROP_UNSIGNED ),
+
+ SendPropExclude( "DT_BaseEntity", "m_angAbsRotation[0]" ),
+ SendPropExclude( "DT_BaseEntity", "m_angAbsRotation[1]" ),
+ SendPropExclude( "DT_BaseEntity", "m_angAbsRotation[2]" ),
+
+END_SEND_TABLE()
+
+LINK_ENTITY_TO_CLASS( ragdoll_shadow, CRagdollShadow );
+PRECACHE_REGISTER( ragdoll_shadow );
+
+CRagdollShadow::CRagdollShadow( void )
+{
+ m_pPlayer = NULL;
+ m_nPlayer = 0;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CRagdollShadow::Spawn( )
+{
+ // Init value & model
+ if ( m_pPlayer )
+ {
+ SetModelName( m_pPlayer->GetModelName() );
+ }
+ else
+ {
+ SetModelName( AllocPooledString( sRagdollShadowModel ) );
+ }
+
+ BaseClass::Spawn();
+
+ // Create the object in the physics system
+ IPhysicsObject *pPhysics = VPhysicsInitNormal( SOLID_VPHYSICS, FSOLID_NOT_SOLID, false );
+// IPhysicsObject *pPhysics = VPhysicsInitNormal( SOLID_VPHYSICS, 0, false );
+
+ // disable physics sounds on this object
+ pPhysics->SetMaterialIndex( physprops->GetSurfaceIndex("default_silent") );
+
+ UTIL_SetSize( this, Vector(-36,-36, 0), Vector(36,36,72) );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : **ppSendTable -
+// *recipient -
+// *pvs -
+// clientArea -
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+int CRagdollShadow::ShouldTransmit( const CCheckTransmitInfo *pInfo )
+{
+ // Always send to local player
+ if ( Instance( pInfo->m_pClientEnt ) == GetOwnerEntity() )
+ return FL_EDICT_ALWAYS;
+
+ return BaseClass::ShouldTransmit( pInfo );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CRagdollShadow::Precache( void )
+{
+ PrecacheModel( sRagdollShadowModel );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Create a resource chunk
+//-----------------------------------------------------------------------------
+CRagdollShadow *CRagdollShadow::Create( CBaseTFPlayer *player, const Vector& force )
+{
+ CRagdollShadow *pRagdollShadow = (CRagdollShadow*)CreateEntityByName("ragdoll_shadow");
+
+ UTIL_SetOrigin( pRagdollShadow, player->GetAbsOrigin() );
+
+ pRagdollShadow->m_pPlayer = player;
+ pRagdollShadow->m_nPlayer = player->entindex();
+
+ pRagdollShadow->Spawn();
+ pRagdollShadow->SetAbsVelocity( force );
+ pRagdollShadow->SetLocalAngles( vec3_angle );
+ pRagdollShadow->SetLocalAngularVelocity( RandomAngle( -100, 100 ) );
+
+ //pRagdollShadow->AddEffects( EF_NODRAW );
+ pRagdollShadow->AddEffects( EF_NOSHADOW );
+
+ pRagdollShadow->m_lifeState = LIFE_DYING;
+
+ IPhysicsObject *pPhysicsObject = pRagdollShadow->VPhysicsGetObject();
+ if ( pPhysicsObject )
+ {
+ AngularImpulse tmp;
+ QAngleToAngularImpulse( pRagdollShadow->GetLocalAngularVelocity(), tmp );
+ pPhysicsObject->AddVelocity( &pRagdollShadow->GetAbsVelocity(), &tmp );
+ }
+
+ return pRagdollShadow;
+} \ No newline at end of file
diff --git a/game/server/tf2/ragdoll_shadow.h b/game/server/tf2/ragdoll_shadow.h
new file mode 100644
index 0000000..93a027c
--- /dev/null
+++ b/game/server/tf2/ragdoll_shadow.h
@@ -0,0 +1,43 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef RAGDOLL_SHADOW_H
+#define RAGDOLL_SHADOW_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include "props.h"
+
+class CBaseTFPlayer;
+class IPhysicsObject;
+
+//-----------------------------------------------------------------------------
+// Purpose: A shadow object used to bound the position of a player ragdoll
+//-----------------------------------------------------------------------------
+class CRagdollShadow : public CBaseProp
+{
+ DECLARE_CLASS( CRagdollShadow, CBaseProp );
+public:
+ DECLARE_SERVERCLASS();
+
+ CRagdollShadow( void ) ;
+
+ virtual void Spawn( void );
+ virtual void Precache( void );
+
+ virtual int UpdateTransmitState() { return SetTransmitState( FL_EDICT_FULLCHECK); }
+ virtual int ShouldTransmit( const CCheckTransmitInfo *pInfo );
+
+ static CRagdollShadow *Create( CBaseTFPlayer *player, const Vector& force );
+
+public:
+ CBaseTFPlayer *m_pPlayer;
+ CNetworkVar( int, m_nPlayer );
+};
+
+#endif // RAGDOLL_SHADOW_H
diff --git a/game/server/tf2/resource_chunk.cpp b/game/server/tf2/resource_chunk.cpp
new file mode 100644
index 0000000..a4a00d4
--- /dev/null
+++ b/game/server/tf2/resource_chunk.cpp
@@ -0,0 +1,172 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Resource chunks
+//
+// $NoKeywords: $
+//=============================================================================//
+#include "cbase.h"
+#include "tf_func_resource.h"
+#include "tf_team.h"
+#include "tf_basecombatweapon.h"
+#include "tf_obj.h"
+#include "resource_chunk.h"
+#include "vstdlib/random.h"
+#include "tf_stats.h"
+#include "engine/IEngineSound.h"
+
+ConVar resource_chunk_value( "resource_chunk_value","20", FCVAR_NONE, "Resource value of a single resource chunk." );
+ConVar resource_chunk_processed_value( "resource_chunk_processed_value","80", FCVAR_NONE, "Resource value of a single processed resource chunk." );
+
+// Resource Chunk Models
+char *sResourceChunkModel = "models/resources/resource_chunk_B.mdl";
+char *sProcessedResourceChunkModel = "models/resources/processed_resource_chunk_B.mdl";
+
+BEGIN_DATADESC( CResourceChunk )
+
+ // functions
+ DEFINE_FUNCTION( ChunkTouch ),
+ DEFINE_FUNCTION( ChunkRemove ),
+
+END_DATADESC()
+
+IMPLEMENT_SERVERCLASS_ST( CResourceChunk, DT_ResourceChunk )
+END_SEND_TABLE()
+
+LINK_ENTITY_TO_CLASS( resource_chunk, CResourceChunk );
+PRECACHE_REGISTER( resource_chunk );
+
+//-----------------------------------------------------------------------------
+// Purpose: Remove me from any lists I'm in when I'm deleted
+//-----------------------------------------------------------------------------
+void CResourceChunk::UpdateOnRemove( void )
+{
+ if ( m_hZone )
+ {
+ m_hZone->RemoveChunk( this, false );
+ m_hZone = NULL;
+ }
+
+ // Chain at end to mimic destructor unwind order
+ BaseClass::UpdateOnRemove();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CResourceChunk::Spawn( )
+{
+ // Init model
+ if ( IsProcessed() )
+ {
+ SetModelName( AllocPooledString( sProcessedResourceChunkModel ) );
+ }
+ else
+ {
+ SetModelName( AllocPooledString( sResourceChunkModel ) );
+ }
+
+ BaseClass::Spawn();
+
+ UTIL_SetSize( this, Vector(-4,-4,-4), Vector(4,4,4) );
+ SetMoveType( MOVETYPE_FLYGRAVITY, MOVECOLLIDE_FLY_BOUNCE );
+ SetSolid( SOLID_BBOX );
+ AddSolidFlags( FSOLID_TRIGGER );
+ CollisionProp()->UseTriggerBounds( true, 24 );
+ SetCollisionGroup( TFCOLLISION_GROUP_RESOURCE_CHUNK );
+ SetGravity( 1.0 );
+ SetFriction( 1 );
+ SetTouch( ChunkTouch );
+ SetThink( ChunkRemove );
+ SetNextThink( gpGlobals->curtime + random->RandomFloat( 50.0, 80.0 ) ); // Remove myself the
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CResourceChunk::Precache( void )
+{
+ PrecacheModel( sResourceChunkModel );
+ PrecacheModel( sProcessedResourceChunkModel );
+ PrecacheModel( "sprites/redglow1.vmt" );
+
+ PrecacheScriptSound( "ResourceChunk.Pickup" );
+
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Create a resource chunk
+//-----------------------------------------------------------------------------
+CResourceChunk *CResourceChunk::Create( bool bProcessed, const Vector &vecOrigin, const Vector &vecVelocity )
+{
+ CResourceChunk *pChunk = (CResourceChunk*)CreateEntityByName("resource_chunk");
+
+ UTIL_SetOrigin( pChunk, vecOrigin );
+ pChunk->m_bIsProcessed = bProcessed;
+ pChunk->m_bBeingCollected = false;
+ pChunk->Spawn();
+ pChunk->SetAbsVelocity( vecVelocity );
+ pChunk->SetLocalAngularVelocity( RandomAngle( -100, 100 ) );
+ pChunk->SetLocalAngles( vec3_angle );
+
+ return pChunk;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: If we're picked up by another pla`yer, give resources to that team
+//-----------------------------------------------------------------------------
+void CResourceChunk::ChunkTouch( CBaseEntity *pOther )
+{
+ if ( pOther->IsPlayer() || pOther->GetServerVehicle() )
+ {
+ // Give the team the resources
+ int iAmountPerPlayer = ((CTFTeam *)pOther->GetTeam())->AddTeamResources( GetResourceValue(), TF_PLAYER_STAT_RESOURCES_ACQUIRED_FROM_CHUNKS );
+ TFStats()->IncrementTeamStat( pOther->GetTeamNumber(), TF_TEAM_STAT_RESOURCE_CHUNKS_COLLECTED, GetResourceValue() );
+
+ pOther->EmitSound( "ResourceChunk.Pickup" );
+
+ // Tell the player
+ CSingleUserRecipientFilter user( (CBasePlayer*)pOther );
+ UserMessageBegin( user, "PickupRes" );
+ WRITE_BYTE( iAmountPerPlayer );
+ MessageEnd();
+
+ // Tell our zone to remove this chunk from it's list
+ if ( m_hZone )
+ {
+ m_hZone->RemoveChunk( this, false );
+ m_hZone = NULL;
+ }
+
+ // Remove this chunk
+ SetTouch( NULL );
+ UTIL_Remove( this );
+ return;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Remove myself if I'm not being harvested
+//-----------------------------------------------------------------------------
+void CResourceChunk::ChunkRemove( void )
+{
+ // Remove this chunk
+ if ( m_hZone )
+ {
+ m_hZone->RemoveChunk( this, true );
+ m_hZone = NULL;
+ }
+ UTIL_Remove( this );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Return the resource value of this chunk
+//-----------------------------------------------------------------------------
+float CResourceChunk::GetResourceValue( void )
+{
+ // Init value & model
+ if ( IsProcessed() )
+ return resource_chunk_processed_value.GetFloat();
+
+ return resource_chunk_value.GetFloat();
+} \ No newline at end of file
diff --git a/game/server/tf2/resource_chunk.h b/game/server/tf2/resource_chunk.h
new file mode 100644
index 0000000..7f03278
--- /dev/null
+++ b/game/server/tf2/resource_chunk.h
@@ -0,0 +1,52 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef RESOURCE_CHUNK_H
+#define RESOURCE_CHUNK_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+
+#include "props.h"
+
+class CResourceZone;
+
+extern ConVar resource_chunk_value;
+extern ConVar resource_chunk_processed_value;
+
+//-----------------------------------------------------------------------------
+// Purpose: A resource chunk that's harvestable by a player
+//-----------------------------------------------------------------------------
+class CResourceChunk : public CBaseProp
+{
+ DECLARE_CLASS( CResourceChunk, CBaseProp );
+public:
+ DECLARE_DATADESC();
+ DECLARE_SERVERCLASS();
+
+ virtual void Spawn( void );
+ virtual void Precache( void );
+ virtual void UpdateOnRemove( void );
+
+ void ChunkTouch( CBaseEntity *pOther );
+ void ChunkRemove( void );
+
+ virtual bool IsStandable( const CBaseEntity *pStander ) { return true; } // can pStander stand on this entity?
+ virtual bool IsProcessed( void ) { return m_bIsProcessed; };
+
+ float GetResourceValue( void );
+
+ static CResourceChunk *Create( bool bProcessed, const Vector &vecOrigin, const Vector &vecVelocity );
+
+public:
+ CHandle<CResourceZone> m_hZone;
+ bool m_bIsProcessed;
+ bool m_bBeingCollected;
+};
+
+#endif // RESOURCE_CHUNK_H
diff --git a/game/server/tf2/sensor_tf_team.cpp b/game/server/tf2/sensor_tf_team.cpp
new file mode 100644
index 0000000..083d2d4
--- /dev/null
+++ b/game/server/tf2/sensor_tf_team.cpp
@@ -0,0 +1,139 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Definitions of all the entities that control logic flow within a map
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#include "cbase.h"
+#include "EntityInput.h"
+#include "EntityOutput.h"
+#include "tf_team.h"
+#include "tf_obj.h"
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Detects a bunch of tf team state
+//-----------------------------------------------------------------------------
+class CSensorTFTeam : public CLogicalEntity
+{
+ DECLARE_CLASS( CSensorTFTeam, CLogicalEntity );
+
+public:
+ void Spawn( void );
+ void Think( void );
+
+private:
+ DECLARE_DATADESC();
+
+ // Computes the number of respawns stations on the sensed team
+ int ComputeRespawnCount();
+
+ // outputs
+ COutputInt m_OnRespawnCountChanged;
+ COutputInt m_OnResourceCountChanged;
+ COutputInt m_OnMemberCountChanged;
+ COutputInt m_OnRespawnCountChangedDelta;
+ COutputInt m_OnResourceCountChangedDelta;
+ COutputInt m_OnMemberCountChangedDelta;
+
+ // What team am I sensing?
+ int m_nTeam;
+ CTFTeam *m_pTeam;
+
+ // So we can know when state changes...
+ int m_nRespawnCount;
+ int m_nResourceCount;
+ int m_nMemberCount;
+};
+
+
+LINK_ENTITY_TO_CLASS( sensor_tf_team, CSensorTFTeam );
+
+
+BEGIN_DATADESC( CSensorTFTeam )
+
+ DEFINE_OUTPUT( m_OnRespawnCountChanged, "OnRespawnCountChanged" ),
+ DEFINE_OUTPUT( m_OnResourceCountChanged, "OnResourceCountChanged" ),
+ DEFINE_OUTPUT( m_OnMemberCountChanged, "OnMemberCountChanged" ),
+ DEFINE_OUTPUT( m_OnRespawnCountChangedDelta, "OnRespawnCountChangedDelta" ),
+ DEFINE_OUTPUT( m_OnResourceCountChangedDelta, "OnResourceCountChangedDelta" ),
+ DEFINE_OUTPUT( m_OnMemberCountChangedDelta, "OnMemberCountChangedDelta" ),
+ DEFINE_KEYFIELD( m_nTeam, FIELD_INTEGER, "team"),
+
+END_DATADESC()
+
+
+
+
+//-----------------------------------------------------------------------------
+// Spawn!
+//-----------------------------------------------------------------------------
+void CSensorTFTeam::Spawn( void )
+{
+ // Hook us up to a team...
+ m_pTeam = GetGlobalTFTeam( m_nTeam );
+
+ // Gets us thinkin!
+ SetNextThink( gpGlobals->curtime + 0.1f );
+
+ // Force an output message on our first think
+ m_nRespawnCount = -1;
+ m_nResourceCount = -1;
+}
+
+
+//-----------------------------------------------------------------------------
+// Compute the number of respawn stations on this team
+//-----------------------------------------------------------------------------
+int CSensorTFTeam::ComputeRespawnCount()
+{
+ int nCount = 0;
+ for (int i = m_pTeam->GetNumObjects(); --i >= 0; )
+ {
+ CBaseObject *pObject = m_pTeam->GetObject(i);
+ if ( pObject && (pObject->GetType() == OBJ_RESPAWN_STATION) )
+ {
+ ++nCount;
+ }
+ }
+ return nCount;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Forces a recompare
+//-----------------------------------------------------------------------------
+void CSensorTFTeam::Think( )
+{
+ if (!m_pTeam)
+ return;
+
+ // Check for a difference in the number of respawn stations
+ int nRespawnCount = ComputeRespawnCount();
+ if ( nRespawnCount != m_nRespawnCount )
+ {
+ m_OnRespawnCountChangedDelta.Set( nRespawnCount - m_nRespawnCount, this, this );
+ m_nRespawnCount = nRespawnCount;
+ m_OnRespawnCountChanged.Set( m_nRespawnCount, this, this );
+ }
+
+ // Check for a difference in the number of resources harvested
+ if ( m_nResourceCount != m_pTeam->m_flTotalResourcesSoFar )
+ {
+ m_OnResourceCountChangedDelta.Set( m_pTeam->m_flTotalResourcesSoFar - m_nResourceCount, this, this );
+ m_nResourceCount = m_pTeam->m_flTotalResourcesSoFar;
+ m_OnResourceCountChanged.Set( m_nResourceCount, this, this );
+ }
+
+ // Check for a difference in the number of team members
+ if ( m_nMemberCount != m_pTeam->GetNumPlayers() )
+ {
+ m_OnMemberCountChangedDelta.Set( m_pTeam->GetNumPlayers() - m_nMemberCount, this, this );
+ m_nMemberCount = m_pTeam->GetNumPlayers();
+ m_OnMemberCountChanged.Set( m_nMemberCount, this, this );
+ }
+
+ SetNextThink( gpGlobals->curtime + 0.1f );
+}
+
diff --git a/game/server/tf2/team_messages.cpp b/game/server/tf2/team_messages.cpp
new file mode 100644
index 0000000..e41a028
--- /dev/null
+++ b/game/server/tf2/team_messages.cpp
@@ -0,0 +1,110 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+#include "cbase.h"
+#include "player.h"
+#include "tf_team.h"
+#include "team_messages.h"
+#include "engine/IEngineSound.h"
+
+//-----------------------------------------------------------------------------
+// Purpose: Create the right class of message based upon the type
+//-----------------------------------------------------------------------------
+CTeamMessage *CTeamMessage::Create( CTFTeam *pTeam, int iMessageID, CBaseEntity *pEntity )
+{
+ CTeamMessage *pMessage = NULL;
+
+ // Create the right type
+ switch ( iMessageID )
+ {
+ // Sound orders
+ case TEAMMSG_REINFORCEMENTS_ARRIVED:
+ pMessage = new CTeamMessage_Sound( pTeam, iMessageID, pEntity, 5.0 );
+ ((CTeamMessage_Sound*)pMessage)->SetSound( "TeamMessage.Reinforcements" );
+ break;
+ case TEAMMSG_CARRIER_UNDER_ATTACK:
+ pMessage = new CTeamMessage_Sound( pTeam, iMessageID, pEntity, 10.0 );
+ ((CTeamMessage_Sound*)pMessage)->SetSound( "TeamMessage.CarrierAttacked" );
+ break;
+ case TEAMMSG_CARRIER_DESTROYED:
+ pMessage = new CTeamMessage_Sound( pTeam, iMessageID, pEntity, 10.0 );
+ ((CTeamMessage_Sound*)pMessage)->SetSound( "TeamMessage.CarrierDestroyed" );
+ break;
+ case TEAMMSG_HARVESTER_UNDER_ATTACK:
+ pMessage = new CTeamMessage_Sound( pTeam, iMessageID, pEntity, 10.0 );
+ ((CTeamMessage_Sound*)pMessage)->SetSound( "TeamMessage.HarvesterAttacked" );
+ break;
+ case TEAMMSG_HARVESTER_DESTROYED:
+ pMessage = new CTeamMessage_Sound( pTeam, iMessageID, pEntity, 10.0 );
+ ((CTeamMessage_Sound*)pMessage)->SetSound( "TeamMessage.HarvesterDestroyed" );
+ break;
+ case TEAMMSG_NEW_TECH_LEVEL_OPEN:
+ pMessage = new CTeamMessage_Sound( pTeam, iMessageID, pEntity, 10.0 );
+ ((CTeamMessage_Sound*)pMessage)->SetSound( "TeamMessage.NewTechLevel" );
+ break;
+ case TEAMMSG_RESOURCE_ZONE_EMPTIED:
+ pMessage = new CTeamMessage_Sound( pTeam, iMessageID, pEntity, 10.0 );
+ ((CTeamMessage_Sound*)pMessage)->SetSound( "TeamMessage.ResourceZoneEmpty" );
+ break;
+ case TEAMMSG_CUSTOM_SOUND:
+ pMessage = new CTeamMessage_Sound( pTeam, iMessageID, pEntity, 10.0 );
+ break;
+
+ default:
+ break;
+ };
+
+ return pMessage;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CTeamMessage::CTeamMessage( CTFTeam *pTeam, int iMessageID, CBaseEntity *pEntity, float flTTL )
+{
+ m_pTeam = pTeam;
+ m_iMessageID = iMessageID;
+ m_hEntity = pEntity;
+ m_flTTL = gpGlobals->curtime + flTTL;
+}
+
+
+//===============================================================================================================
+// TEAM MESSAGE SOUND
+//===============================================================================================================
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CTeamMessage_Sound::CTeamMessage_Sound( CTFTeam *pTeam, int iMessageID, CBaseEntity *pEntity, float flTTL ) :
+ CTeamMessage( pTeam, iMessageID, pEntity, flTTL )
+{
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTeamMessage_Sound::SetSound( char *sSound )
+{
+ CBaseEntity::PrecacheScriptSound( sSound );
+ m_SoundName = sSound;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Called when the team manager wants me to fire myself
+//-----------------------------------------------------------------------------
+void CTeamMessage_Sound::FireMessage( void )
+{
+ Assert( m_SoundName.String() );
+
+ // Play my sound to all the team's members
+ for ( int i = 0; i < m_pTeam->GetNumPlayers(); i++ )
+ {
+ CBasePlayer *pPlayer = m_pTeam->GetPlayer(i);
+
+ CSingleUserRecipientFilter filter( pPlayer );
+ CBaseEntity::EmitSound( filter, pPlayer->entindex(), m_SoundName.String() );
+ }
+} \ No newline at end of file
diff --git a/game/server/tf2/team_messages.h b/game/server/tf2/team_messages.h
new file mode 100644
index 0000000..536e836
--- /dev/null
+++ b/game/server/tf2/team_messages.h
@@ -0,0 +1,83 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef TEAM_MESSAGES_H
+#define TEAM_MESSAGES_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include "utlsymbol.h"
+
+// Message IDs
+enum
+{
+ // Reinforcements
+ TEAMMSG_REINFORCEMENTS_ARRIVED,
+
+ // Carriers / Harvesters
+ TEAMMSG_CARRIER_UNDER_ATTACK,
+ TEAMMSG_CARRIER_DESTROYED,
+ TEAMMSG_HARVESTER_UNDER_ATTACK,
+ TEAMMSG_HARVESTER_DESTROYED,
+
+ // Resources
+ TEAMMSG_RESOURCE_ZONE_EMPTIED,
+
+ // Techtree
+ TEAMMSG_NEW_TECH_LEVEL_OPEN,
+
+ // Custom sounds
+ TEAMMSG_CUSTOM_SOUND,
+};
+
+//-----------------------------------------------------------------------------
+// Purpose: Message sent to a team for the purpose of updating its members about some event
+//-----------------------------------------------------------------------------
+abstract_class CTeamMessage
+{
+public:
+ CTeamMessage( CTFTeam *pTeam, int iMessageID, CBaseEntity *pEntity, float flTTL );
+
+ static CTeamMessage *Create( CTFTeam *pTeam, int iMessageID, CBaseEntity *pEntity );
+
+ // Called when the team manager wants me to fire myself
+ virtual void FireMessage( void ) = 0;
+
+ // Accessors
+ virtual int GetID( void ) { return m_iMessageID; };
+ virtual float GetTTL( void ) { return m_flTTL; };
+ virtual CBaseEntity *GetEntity( void ) { return m_hEntity; };
+ virtual CTFTeam *GetTeam( void ) { return m_pTeam; };
+
+ virtual void SetData( char *pszData ) { return; }
+
+protected:
+ int m_iMessageID;
+ float m_flTTL;
+ EHANDLE m_hEntity;
+ CTFTeam *m_pTeam;
+ CUtlSymbol m_SoundName;
+};
+
+//-----------------------------------------------------------------------------
+// Purpose: Team message that plays a sound to the members of the team
+//-----------------------------------------------------------------------------
+class CTeamMessage_Sound : public CTeamMessage
+{
+public:
+ CTeamMessage_Sound( CTFTeam *pTeam, int iMessageID, CBaseEntity *pEntity, float flTTL );
+
+ // Set my sound
+ virtual void SetSound( char *sSound );
+ // Called when the team manager wants me to fire myself
+ virtual void FireMessage( void );
+
+ virtual void SetData( char *pszData ) { SetSound( pszData ); }
+};
+
+#endif // TEAM_MESSAGES_H
diff --git a/game/server/tf2/tf_accuracy.cpp b/game/server/tf2/tf_accuracy.cpp
new file mode 100644
index 0000000..db2b342
--- /dev/null
+++ b/game/server/tf2/tf_accuracy.cpp
@@ -0,0 +1,168 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: TF2 Accuracy system
+//
+// $Workfile: $
+// $Date: $
+// $NoKeywords: $
+//=============================================================================//
+#include "cbase.h"
+#include "player.h"
+#include "tf_player.h"
+#include "basecombatweapon.h"
+#include "vstdlib/random.h"
+
+
+
+
+// THIS ISN'T USED ANYMORE. NO REASON TO MAKE OUR HITSCAN WPNS THIS COMPLEX
+
+
+
+
+// Accuracy is measured as the weapons spread in inches at 1024 units (~85 feet)
+// Accuracy is sent to the client, where it's used to generate the size of the accuracy representation.
+// Accuracy is a "floating" value, in that it's always moving towards a target accuracy, and takes some time to change
+
+// Accuracy Multipliers
+// < 1 increases accuracy, > 1 decreases
+#define ACCMULT_DUCKING 0.75 // Player is ducking
+#define ACCMULT_RUNNING 1.25 // Player is moving >50% of his max speed
+
+// Ricochet Multiplier
+// This works differently to other acc multipliers.
+#define ACCMULT_RICOCHET 1.0 // Player is being suppressed by bullet fire
+#define ACC_RICOCHET_TIME 1.0 // Amount of time accuracy is affected by a ricochet near the player
+#define ACC_RICOCHET_MULTIPLE 0.25 // The effect of ricochets on accuracy is multiplied by this by the number of ricochets nearby
+#define ACC_RICOCHET_CAP 10 // Maximum number of bullets to register for suppression fire
+
+#define ACCURACY_CHANGE_SPEED 5
+
+//-----------------------------------------------------------------------------
+// Purpose: Calculates the players "accuracy" level
+//-----------------------------------------------------------------------------
+void CBaseTFPlayer::CalculateAccuracy( void )
+{
+ static flLastTime = 0;
+
+ // Get the time since the last calculation
+ float flTimeSlice = (gpGlobals->curtime - flLastTime);
+ m_flTargetAccuracy = 0;
+
+ if ( !GetPlayerClass() )
+ return;
+
+ // Get the base accuracy from the current weapon
+ if ( m_hActiveWeapon )
+ {
+ m_flTargetAccuracy = m_hActiveWeapon->GetAccuracy();
+
+ // Accuracy is increased if the player's crouching
+ if ( GetFlags() & FL_DUCKING )
+ m_flTargetAccuracy *= m_hActiveWeapon->GetDuckingMultiplier();
+
+ // Accuracy is decreased if the player's moving
+ if ( m_vecVelocity.Length2D() > ( GetPlayerClass()->GetMaxSpeed() * 0.5 ) )
+ m_flTargetAccuracy *= m_hActiveWeapon->GetRunningMultiplier();
+ }
+
+ // Accuracy is decreased if the player's arms are injured
+
+ // Accuracy is increased if there's an Officer nearby
+
+ // Accuracy is decreased if this player's being supressed (bullets/explosions impacting nearby)
+ float flFarTime = (m_flLastRicochetNearby + ACC_RICOCHET_TIME);
+ if ( gpGlobals->curtime <= flFarTime )
+ m_flTargetAccuracy *= 1 + (m_flNumberOfRicochets * ACC_RICOCHET_MULTIPLE) * (ACCMULT_RICOCHET * ((flFarTime - gpGlobals->curtime) / ACC_RICOCHET_TIME));
+
+ // Accuracy is decreased if the player's just been hit by a bullet/explosion
+
+ // Now float towards the target accuracy
+ if ( m_bSnapAccuracy )
+ {
+ m_bSnapAccuracy = false;
+ m_flAccuracy = m_flTargetAccuracy;
+ }
+ else
+ {
+ if ( m_flAccuracy < m_flTargetAccuracy )
+ {
+ m_flAccuracy += (flTimeSlice * ACCURACY_CHANGE_SPEED);
+ if ( m_flAccuracy > m_flTargetAccuracy )
+ m_flAccuracy = m_flTargetAccuracy ;
+ }
+ else if ( m_flAccuracy > m_flTargetAccuracy )
+ {
+ m_flAccuracy -= (flTimeSlice * ACCURACY_CHANGE_SPEED);
+ if ( m_flAccuracy < m_flTargetAccuracy )
+ m_flAccuracy = m_flTargetAccuracy ;
+ }
+ }
+
+ // Clip to prevent silly accuracies
+ if ( m_flAccuracy > 1024 )
+ m_flAccuracy = 1024;
+
+ flLastTime = gpGlobals->curtime;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Snap the players accuracy immediately
+//-----------------------------------------------------------------------------
+void CBaseTFPlayer::SnapAccuracy( void )
+{
+ m_bSnapAccuracy = true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Return the player's current accuracy
+//-----------------------------------------------------------------------------
+float CBaseTFPlayer::GetAccuracy( void )
+{
+ return m_flAccuracy;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Bullets / Explosions are hitting near the player. Reduce his/her accuracy.
+//-----------------------------------------------------------------------------
+void CBaseTFPlayer::Supress( void )
+{
+ if ( gpGlobals->curtime <= (m_flLastRicochetNearby + ACC_RICOCHET_TIME) )
+ {
+ m_flNumberOfRicochets = MIN( ACC_RICOCHET_CAP, m_flNumberOfRicochets + 1 );
+ }
+ else
+ {
+ m_flNumberOfRicochets = 1;
+ }
+
+ m_flLastRicochetNearby = gpGlobals->curtime;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+Vector CBaseTFPlayer::GenerateFireVector( Vector *viewVector )
+{
+ // Calculate the weapon spread from the player's accuracy
+ float flAcc = (GetAccuracy() * 0.5) / ACCURACY_DISTANCE;
+ float flAccuracyAngle = RAD2DEG( atan( flAcc ) );
+ // If the user passed in a viewVector, use it, otherwise use player's v_angle
+ Vector angShootAngles = viewVector ? *viewVector : pl->v_angle;
+ if ( flAccuracyAngle )
+ {
+ float x, y, z;
+ do {
+ x = random->RandomFloat(-0.5,0.5) + random->RandomFloat(-0.5,0.5);
+ y = random->RandomFloat(-0.5,0.5) + random->RandomFloat(-0.5,0.5);
+ z = x*x+y*y;
+ } while (z > 1);
+
+ angShootAngles.x = UTIL_AngleMod( angShootAngles.x + (x * flAccuracyAngle) );
+ angShootAngles.y = UTIL_AngleMod( angShootAngles.y + (y * flAccuracyAngle) );
+ }
+
+ Vector forward;
+ AngleVectors( angShootAngles, &forward );
+ return forward;
+}
diff --git a/game/server/tf2/tf_ai_hint.h b/game/server/tf2/tf_ai_hint.h
new file mode 100644
index 0000000..8e305fa
--- /dev/null
+++ b/game/server/tf2/tf_ai_hint.h
@@ -0,0 +1,34 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef TF_AI_HINT_H
+#define TF_AI_HINT_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+//=========================================================
+// hints - these MUST coincide with the HINTS listed under
+// info_node in the FGD file!
+//=========================================================
+enum TF_Hint_e
+{
+ HINT_RESOURCE_ZONE_AREA = 2000,
+
+ // The carrier starts at base_area land spot, goes first to
+ // the hover spot, then goes to the dropoff hover spot and
+ // finally to the dropoff landspot
+ HINT_AIR_CARRIER_DROPOFF_POINT_LANDSPOT,
+ HINT_AIR_CARRIER_DROPOFF_POINT_HOVERSPOT,
+ HINT_AIR_CARRIER_BASE_AREA_LANDSPOT,
+ HINT_AIR_CARRIER_BASE_AREA_HOVERSPOT,
+
+ // The ground collector needs hints to drive itself
+ HINT_GROUNDCOLLECTOR_ZONE_ENTRANCE,
+};
+
+#endif // TF_AI_HINT_H
diff --git a/game/server/tf2/tf_basecombatweapon.cpp b/game/server/tf2/tf_basecombatweapon.cpp
new file mode 100644
index 0000000..b4c374f
--- /dev/null
+++ b/game/server/tf2/tf_basecombatweapon.cpp
@@ -0,0 +1,137 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Base TF Combat weapon
+//
+// $NoKeywords: $
+//=============================================================================//
+#include "cbase.h"
+#include "animation.h"
+#include "tf_player.h"
+#include "tf_basecombatweapon.h"
+#include "soundent.h"
+#include "weapon_twohandedcontainer.h"
+#include "tf_gamerules.h"
+#include "tf_obj.h"
+
+//====================================================================================================
+// BASE TF MACHINEGUN
+//====================================================================================================
+IMPLEMENT_SERVERCLASS_ST(CTFMachineGun, DT_TFMachineGun )
+END_SEND_TABLE()
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFMachineGun::PrimaryAttack( void )
+{
+ // Only the player fires this way so we can cast
+ CBasePlayer *pPlayer = ToBasePlayer( GetOwner() );
+ if (!pPlayer)
+ return;
+
+ // Abort here to handle burst and auto fire modes
+ if ( (GetMaxClip1() != -1 && m_iClip1 == 0) || (GetMaxClip1() == -1 && !pPlayer->GetAmmoCount(m_iPrimaryAmmoType) ) )
+ return;
+
+ pPlayer->DoMuzzleFlash();
+
+
+ // To make the firing framerate independent, we may have to fire more than one bullet here on low-framerate systems,
+ // especially if the weapon we're firing has a really fast rate of fire.
+ if ( GetSequence() != SelectWeightedSequence( ACT_VM_PRIMARYATTACK ))
+ {
+ m_flNextPrimaryAttack = gpGlobals->curtime;
+ }
+ int iBulletsToFire = 0;
+ float fireRate = GetFireRate();
+
+ while ( m_flNextPrimaryAttack <= gpGlobals->curtime )
+ {
+ // MUST call sound before removing a round from the clip of a CMachineGun
+ WeaponSound(SINGLE, m_flNextPrimaryAttack);
+ m_flNextPrimaryAttack = m_flNextPrimaryAttack + fireRate;
+ iBulletsToFire++;
+ }
+
+ // Make sure we don't fire more than the amount in the clip, if this weapon uses clips
+ if ( GetMaxClip1() != -1 )
+ {
+ if ( iBulletsToFire > m_iClip1 )
+ iBulletsToFire = m_iClip1;
+ m_iClip1 -= iBulletsToFire;
+ }
+ else
+ {
+ if ( iBulletsToFire > pPlayer->GetAmmoCount(m_iPrimaryAmmoType) )
+ iBulletsToFire = pPlayer->GetAmmoCount(m_iPrimaryAmmoType);
+ pPlayer->RemoveAmmo( iBulletsToFire, m_iPrimaryAmmoType );
+ }
+
+ // Not time to fire any bullets yet?
+ if ( !iBulletsToFire )
+ return;
+
+ // Fire the bullets
+ Vector vecSrc = pPlayer->Weapon_ShootPosition( );
+ Vector vecAiming = pPlayer->GetAutoaimVector( AUTOAIM_5DEGREES );
+
+ // Factor in the view kick
+ AddViewKick();
+
+ float range = m_pRangeCVar ? m_pRangeCVar->GetFloat() : 1024.0f;
+
+ if ( !m_pRangeCVar )
+ {
+ Msg( "Weapon missing m_pRangeCVar!!!\n" );
+ }
+
+ FireBullets( this, iBulletsToFire, vecSrc, vecAiming, GetBulletSpread(), range, m_iPrimaryAmmoType, 2 );
+
+ if (!m_iClip1 && pPlayer->GetAmmoCount(m_iPrimaryAmmoType) <= 0)
+ {
+ // HEV suit - indicate out of ammo condition
+ pPlayer->SetSuitUpdate("!HEV_AMO0", FALSE, 0);
+ }
+
+ PlayAttackAnimation( GetPrimaryAttackActivity() );
+
+ // Register a muzzleflash for the AI
+ pPlayer->SetMuzzleFlashTime( gpGlobals->curtime + 0.5 );
+
+ CheckRemoveDisguise();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+const Vector& CTFMachineGun::GetBulletSpread( void )
+{
+ static Vector cone = VECTOR_CONE_3DEGREES;
+ return cone;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFMachineGun::FireBullets( CBaseTFCombatWeapon *pWeapon, int cShots, const Vector &vecSrc, const Vector &vecDirShooting, const Vector &vecSpread, float flDistance, int iBulletType, int iTracerFreq)
+{
+ if ( CBaseTFPlayer *pPlayer = (CBaseTFPlayer*)GetOwner() )
+ {
+ float damage = m_pDamageCVar ? m_pDamageCVar->GetFloat() : 1;
+
+ if ( !m_pDamageCVar )
+ {
+ Msg( "Weapon missing m_pDamageCVar!!!!\n" );
+ }
+
+ TFGameRules()->FireBullets( CTakeDamageInfo( this, pPlayer, damage, DMG_BULLET ), cShots, vecSrc, vecDirShooting, vecSpread, flDistance, iBulletType, 4, entindex(), 0 );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+float CTFMachineGun::GetFireRate( void )
+{
+ return 1.0;
+}
diff --git a/game/server/tf2/tf_basecombatweapon.h b/game/server/tf2/tf_basecombatweapon.h
new file mode 100644
index 0000000..a35f7db
--- /dev/null
+++ b/game/server/tf2/tf_basecombatweapon.h
@@ -0,0 +1,39 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: TF's derived BaseCombatWeapon
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef TF_BASECOMBATWEAPON_H
+#define TF_BASECOMBATWEAPON_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include "baseplayer_shared.h"
+#include "basetfplayer_shared.h"
+#include "basetfcombatweapon_shared.h"
+
+class CBaseObject;
+class CBaseTechnology;
+class CBaseTFPlayer;
+
+//-----------------------------------------------------------------------------
+// Purpose: Base TF Machinegun
+//-----------------------------------------------------------------------------
+class CTFMachineGun : public CBaseTFCombatWeapon
+{
+ DECLARE_CLASS( CTFMachineGun, CBaseTFCombatWeapon );
+public:
+
+ DECLARE_SERVERCLASS();
+
+ virtual void PrimaryAttack( void );
+ virtual void FireBullets( CBaseTFCombatWeapon *pWeapon, int cShots, const Vector &vecSrc, const Vector &vecDirShooting, const Vector &vecSpread, float flDistance, int iBulletType, int iTracerFreq);
+ virtual const Vector& GetBulletSpread( void );
+ virtual float GetFireRate( void );
+};
+
+#endif // TF_BASECOMBATWEAPON_H
+
diff --git a/game/server/tf2/tf_basefourwheelvehicle.cpp b/game/server/tf2/tf_basefourwheelvehicle.cpp
new file mode 100644
index 0000000..f2b0808
--- /dev/null
+++ b/game/server/tf2/tf_basefourwheelvehicle.cpp
@@ -0,0 +1,762 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: A moving vehicle that is used as a battering ram
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#include "cbase.h"
+#include "tf_basefourwheelvehicle.h"
+#include "engine/IEngineSound.h"
+#include "soundenvelope.h"
+#include "vcollide_parse.h"
+#include "in_buttons.h"
+#include "tf_movedata.h"
+
+#define BASEFOURWHEELEDVEHICLE_THINK_CONTEXT "BaseFourWheeledThink"
+#define BASEFOURWHEELEDVEHICLE_DEPLOYTHINK_CONTEXT "BaseFourWheeledDeployThink"
+// HACK HAC
+#define BASEFOURWHEELEDVEHICLE_STOPTHERODEO_CONTEXT "BaseFourWheeledVehicleStopTheRodeoMadnessThink"
+
+ConVar road_feel( "road_feel", "0.1", FCVAR_NOTIFY | FCVAR_REPLICATED );
+extern ConVar tf_fastbuild;
+
+BEGIN_DATADESC( CBaseTFFourWheelVehicle )
+
+ DEFINE_EMBEDDED( m_VehiclePhysics ),
+
+ DEFINE_INPUTFUNC( FIELD_FLOAT, "Throttle", InputThrottle ),
+ DEFINE_INPUTFUNC( FIELD_FLOAT, "Steer", InputSteering ),
+ DEFINE_INPUTFUNC( FIELD_FLOAT, "Action", InputAction ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "TurnOn", InputTurnOn ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "TurnOff", InputTurnOff ),
+
+END_DATADESC()
+
+
+// Used for debugging to make vehicle deploying go really fast.
+ConVar tf_fastdeploy( "tf_fastdeploy", "0", FCVAR_CHEAT );
+
+
+IMPLEMENT_SERVERCLASS_ST(CBaseTFFourWheelVehicle, DT_BaseTFFourWheelVehicle)
+ SendPropFloat( SENDINFO( m_flDeployFinishTime ), 0, SPROP_NOSCALE ),
+ SendPropInt( SENDINFO( m_eDeployMode ), NUM_VEHICLE_DEPLOYMODE_BITS, SPROP_UNSIGNED ),
+ SendPropInt( SENDINFO( m_bBoostUpgrade ), 1, SPROP_UNSIGNED ),
+ SendPropInt( SENDINFO( m_nBoostTimeLeft ), 8, SPROP_UNSIGNED ),
+END_SEND_TABLE()
+
+ConVar fourwheelvehicle_hit_damage( "fourwheelvehicle_hit_damage","40", FCVAR_NONE, "Four-wheel vehicle hit damage" );
+ConVar fourwheelvehicle_hit_damage_boostmod( "fourwheelvehicle_hit_damage_boostmod", "1.5", FCVAR_NONE, "Four-wheel vehicle boosted hit damage modifier" );
+ConVar fourwheelvehicle_hit_mindamagevel( "fourwheelvehicle_hit_mindamagevel","100", FCVAR_NONE, "Four-wheel vehciel hit velocity for min damage" );
+ConVar fourwheelvehicle_hit_maxdamagevel( "fourwheelvehicle_hit_maxdamagevel","230", FCVAR_NONE, "Four-wheel vehicle hit velocity for max damage" );
+ConVar fourwheelvehicle_impact_time( "fourwheelvehicle_impact_time", "1.0", FCVAR_NONE, "Four-wheel vehicle impact wait time." );
+ConVar fourwheelvehicle_hit_damage_player( "fourwheelvehicle_hit_damage_player", "30.0f", FCVAR_NONE, "Four-wheel vehicle hit player damage." );
+
+//-----------------------------------------------------------------------------
+// Constructor
+//-----------------------------------------------------------------------------
+#pragma warning( disable: 4355 )
+CBaseTFFourWheelVehicle::CBaseTFFourWheelVehicle() : m_VehiclePhysics(this)
+{
+ m_flDeployFinishTime = -1;
+}
+#pragma warning( default: 4355 )
+
+//-----------------------------------------------------------------------------
+// Destructor
+//-----------------------------------------------------------------------------
+CBaseTFFourWheelVehicle::~CBaseTFFourWheelVehicle ()
+{
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Put a vehicle in a deploy state. Turn off the engine and run a
+// deploy animation.
+//-----------------------------------------------------------------------------
+bool CBaseTFFourWheelVehicle::Deploy( void )
+{
+ // Make sure we're allowed to deploy here
+ if ( !IsReadyToDrive() )
+ return false;
+
+ // Check to see if we are already in a deploy mode.
+ if ( m_eDeployMode != VEHICLE_MODE_NORMAL )
+ return false;
+
+ // Disable the vehicle's motion.
+ DisableMotion();
+
+ // Save pre-deploy activity.
+ m_PreDeployActivity = GetActivity();
+
+ // Set the deploying activity - ACT_DEPLOY
+ SetActivity( ACT_DEPLOY );
+
+ // Get the deployment time.
+ float flDeployTime = SequenceDuration();
+ if ( tf_fastdeploy.GetBool() )
+ flDeployTime = 1;
+
+ m_flDeployFinishTime = gpGlobals->curtime + flDeployTime;
+
+ SetContextThink( BaseFourWheeledVehicleDeployThink, gpGlobals->curtime + flDeployTime,
+ BASEFOURWHEELEDVEHICLE_DEPLOYTHINK_CONTEXT );
+
+ // Set the deploy mode.
+ m_eDeployMode = VEHICLE_MODE_DEPLOYING;
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Release a vehicle from a deployed state. Run a de-coupling
+// animation and turn on the vehicle.
+//-----------------------------------------------------------------------------
+void CBaseTFFourWheelVehicle::UnDeploy( void )
+{
+ // Make sure we are deployed.
+ if ( !IsDeployed() )
+ return;
+
+ // Enable motion and turn on the vehicle.
+ EnableMotion();
+
+ // Set the undeploying activity - ACT_UNDEPLOY
+ SetActivity( ACT_UNDEPLOY );
+
+ // Get the undeployment time.
+ float flUnDeployTime = SequenceDuration();
+ if ( tf_fastdeploy.GetBool() )
+ flUnDeployTime = 1;
+
+ m_flDeployFinishTime = gpGlobals->curtime + flUnDeployTime;
+
+ SetContextThink( BaseFourWheeledVehicleDeployThink, gpGlobals->curtime + flUnDeployTime,
+ BASEFOURWHEELEDVEHICLE_DEPLOYTHINK_CONTEXT );
+
+ // Set the deploy mode.
+ m_eDeployMode = VEHICLE_MODE_UNDEPLOYING;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseTFFourWheelVehicle::CancelDeploy( void )
+{
+ // Check for the deploying state.
+ if ( !IsDeploying() )
+ return;
+
+ // Re-enable the motion.
+ EnableMotion();
+
+ // Reset the activity to the previous activity.
+ SetActivity( m_PreDeployActivity );
+
+ // Reset the deploy mode.
+ m_eDeployMode = VEHICLE_MODE_NORMAL;
+
+ m_flDeployFinishTime = -1;
+
+ // Turn off the context think.
+ SetContextThink( NULL, 0, BASEFOURWHEELEDVEHICLE_DEPLOYTHINK_CONTEXT );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseTFFourWheelVehicle::BaseFourWheeledVehicleDeployThink( void )
+{
+ // Called from deploy.
+ if ( IsDeploying() )
+ {
+ OnFinishedDeploy();
+ m_flDeployFinishTime = -1;
+ }
+ // Called from undeploy.
+ else if ( IsUndeploying() )
+ {
+ OnFinishedUnDeploy();
+ m_flDeployFinishTime = -1;
+ }
+
+ // Turn off the context think.
+ SetContextThink( NULL, 0, BASEFOURWHEELEDVEHICLE_DEPLOYTHINK_CONTEXT );
+}
+
+void CBaseTFFourWheelVehicle::BaseFourWheeledVehicleStopTheRodeoMadnessThink( void )
+{
+ // HACK HACK: See note above at FinishBuilding call
+ // This resets the handbrake, so the newly placed object doesn't roll down any hills.
+ m_VehiclePhysics.ResetControls();
+
+ // Turn off the context think.
+ SetContextThink( NULL, 0, BASEFOURWHEELEDVEHICLE_STOPTHERODEO_CONTEXT );
+
+ // Start our base think
+ SetContextThink( BaseFourWheeledVehicleThink, gpGlobals->curtime + 0.1, BASEFOURWHEELEDVEHICLE_THINK_CONTEXT );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseTFFourWheelVehicle::OnFinishedDeploy( void )
+{
+ SetActivity( ACT_DEPLOY_IDLE );
+ m_eDeployMode = VEHICLE_MODE_DEPLOYED;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseTFFourWheelVehicle::OnFinishedUnDeploy( void )
+{
+ m_eDeployMode = VEHICLE_MODE_NORMAL;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Allow the vehicle to move.
+//-----------------------------------------------------------------------------
+void CBaseTFFourWheelVehicle::EnableMotion( void )
+{
+ // Enable vehicle chasis motion.
+ IPhysicsObject *pVehicleObject = VPhysicsGetObject();
+ if ( pVehicleObject )
+ {
+ pVehicleObject->EnableMotion( true );
+ }
+
+ // Enable motion on the tires.
+ m_VehiclePhysics.EnableMotion();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Dis-allow the vehicle to move.
+//-----------------------------------------------------------------------------
+void CBaseTFFourWheelVehicle::DisableMotion( void )
+{
+ // Disable vehicle chasis motion.
+ IPhysicsObject *pVehicleObject = VPhysicsGetObject();
+ if ( pVehicleObject )
+ {
+ pVehicleObject->EnableMotion( false );
+ }
+
+ // Disable motion on the tires.
+ m_VehiclePhysics.DisableMotion();
+}
+
+//-----------------------------------------------------------------------------
+// Precache
+//-----------------------------------------------------------------------------
+void CBaseTFFourWheelVehicle::Precache()
+{
+ BaseClass::Precache();
+
+ PrecacheScriptSound( "BaseTFFourWheelVehicle.EMP" );
+ PrecacheScriptSound( "BaseTFFourWheelVehicle.RamSound" );
+}
+
+//-----------------------------------------------------------------------------
+// Spawn
+//-----------------------------------------------------------------------------
+void CBaseTFFourWheelVehicle::Spawn( )
+{
+ SetModel( STRING( GetModelName() ) );
+// CFourWheelServerVehicle *pServerVehicle = dynamic_cast<CFourWheelServerVehicle*>(GetServerVehicle());
+// m_VehiclePhysics.SetOuter( this, pServerVehicle );
+ m_VehiclePhysics.Spawn();
+ BaseClass::Spawn();
+ // The base class spawn sets a default collision group, so this needs to
+ // be called post.
+ SetCollisionGroup( COLLISION_GROUP_VEHICLE );
+
+ m_eDeployMode = VEHICLE_MODE_NORMAL;
+
+ SetBoostUpgrade( false );
+
+ m_flNextHitTime = 0.0f;
+}
+
+//-----------------------------------------------------------------------------
+// Teleport
+//-----------------------------------------------------------------------------
+void CBaseTFFourWheelVehicle::Teleport( const Vector *newPosition, const QAngle *newAngles, const Vector *newVelocity )
+{
+ // We basically just have to make sure the wheels are in the right place
+ // after teleportation occurs
+ matrix3x4_t startMatrixInv;
+ MatrixInvert( EntityToWorldTransform(), startMatrixInv );
+
+ BaseClass::Teleport( newPosition, newAngles, newVelocity );
+
+ // Teleport the vehicle physics from the starting position to the ending one
+ matrix3x4_t relativeTransform;
+ ConcatTransforms( EntityToWorldTransform(), startMatrixInv, relativeTransform );
+ m_VehiclePhysics.Teleport( relativeTransform );
+}
+
+
+//-----------------------------------------------------------------------------
+// Debugging methods
+//-----------------------------------------------------------------------------
+void CBaseTFFourWheelVehicle::DrawDebugGeometryOverlays()
+{
+ if (m_debugOverlays & OVERLAY_BBOX_BIT)
+ {
+ m_VehiclePhysics.DrawDebugGeometryOverlays();
+ }
+ BaseClass::DrawDebugGeometryOverlays();
+}
+
+int CBaseTFFourWheelVehicle::DrawDebugTextOverlays()
+{
+ int nOffset = BaseClass::DrawDebugTextOverlays();
+ if (m_debugOverlays & OVERLAY_TEXT_BIT)
+ {
+ nOffset = m_VehiclePhysics.DrawDebugTextOverlays( nOffset );
+ }
+ return nOffset;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CBaseTFFourWheelVehicle::StartBuilding( CBaseEntity *pPlayer )
+{
+ if (!BaseClass::StartBuilding(pPlayer))
+ return false;
+
+ // Until we're finished building, turn off vphysics-based motion
+ SetSolid( SOLID_VPHYSICS );
+ VPhysicsInitStatic();
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseTFFourWheelVehicle::FinishedBuilding( void )
+{
+ BaseClass::FinishedBuilding();
+
+ char pScriptName[128];
+ Q_snprintf( pScriptName, sizeof( pScriptName ), "scripts/vehicles/%s.txt", GetClassname() );
+ m_VehiclePhysics.Initialize( pScriptName, true );
+
+ // HACK HACK: This is a hack to avoid physics spazzing out on a newly created vehicle with the handbrake
+ // set. We create and activate it, but then release the handbrake for a single Think function call and
+ // then zero out the controls right then. This seems to stabilize something in the physics simulator.
+ m_VehiclePhysics.ReleaseHandbrake();
+ SetContextThink( BaseFourWheeledVehicleStopTheRodeoMadnessThink, gpGlobals->curtime, BASEFOURWHEELEDVEHICLE_STOPTHERODEO_CONTEXT );
+
+ ResetDeteriorationTime();
+}
+
+//-----------------------------------------------------------------------------
+// Input methods
+//-----------------------------------------------------------------------------
+void CBaseTFFourWheelVehicle::InputThrottle( inputdata_t &inputdata )
+{
+ m_VehiclePhysics.SetThrottle( inputdata.value.Float() );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseTFFourWheelVehicle::InputSteering( inputdata_t &inputdata )
+{
+ m_VehiclePhysics.SetSteering( inputdata.value.Float(), 2*gpGlobals->frametime );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseTFFourWheelVehicle::InputAction( inputdata_t &inputdata )
+{
+ m_VehiclePhysics.SetAction( inputdata.value.Float() );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseTFFourWheelVehicle::InputTurnOn( inputdata_t &inputdata )
+{
+ if (!m_VehiclePhysics.IsOn())
+ {
+ SetContextThink( BaseFourWheeledVehicleThink, gpGlobals->curtime + 0.1, BASEFOURWHEELEDVEHICLE_THINK_CONTEXT );
+ m_VehiclePhysics.TurnOn( );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseTFFourWheelVehicle::InputTurnOff( inputdata_t &inputdata )
+{
+ if ( m_VehiclePhysics.IsOn() )
+ {
+ m_VehiclePhysics.TurnOff( );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Input methods
+//-----------------------------------------------------------------------------
+void CBaseTFFourWheelVehicle::BaseFourWheeledVehicleThink()
+{
+ if (m_VehiclePhysics.Think())
+ {
+ SetNextThink( gpGlobals->curtime, BASEFOURWHEELEDVEHICLE_THINK_CONTEXT );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseTFFourWheelVehicle::VPhysicsUpdate( IPhysicsObject *pPhysics )
+{
+ // must be a wheel
+ if (!m_VehiclePhysics.VPhysicsUpdate(pPhysics))
+ return;
+
+ BaseClass::VPhysicsUpdate( pPhysics );
+}
+
+
+//-----------------------------------------------------------------------------
+// Methods related to getting in and out of the vehicle
+//-----------------------------------------------------------------------------
+void CBaseTFFourWheelVehicle::SetPassenger( int nRole, CBasePlayer *pEnt )
+{
+ if ( nRole == VEHICLE_ROLE_DRIVER )
+ {
+ if (pEnt)
+ {
+ PlayerControlInit( ToBasePlayer(pEnt) );
+ }
+ else
+ {
+ PlayerControlShutdown( );
+ }
+ }
+ BaseClass::SetPassenger( nRole, pEnt );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseTFFourWheelVehicle::PlayerControlInit( CBasePlayer *pPlayer )
+{
+ // Blat out the view offset
+ m_savedViewOffset = pPlayer->GetViewOffset();
+ pPlayer->SetViewOffset( vec3_origin );
+
+ m_playerOn.FireOutput( pPlayer, this, 0 );
+ InputTurnOn( inputdata_t() );
+
+ // Release the handbrake.
+ if ( !IsDeployed() )
+ {
+ m_VehiclePhysics.ReleaseHandbrake();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseTFFourWheelVehicle::ResetUseKey( CBasePlayer *pPlayer )
+{
+ pPlayer->m_afButtonPressed &= ~IN_USE;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseTFFourWheelVehicle::PlayerControlShutdown()
+{
+ CBasePlayer *pPlayer = GetDriverPlayer();
+ if ( !pPlayer )
+ return;
+
+ ResetUseKey( pPlayer );
+ pPlayer->SetViewOffset( m_savedViewOffset );
+
+ m_playerOff.FireOutput( pPlayer, this, 0 );
+ // clear out the fire buttons
+ m_attackaxis.Set( 0, pPlayer, this );
+ m_attack2axis.Set( 0, pPlayer, this );
+
+ InputTurnOff( inputdata_t() );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Powerup has just started
+//-----------------------------------------------------------------------------
+void CBaseTFFourWheelVehicle::PowerupStart( int iPowerup, float flAmount, CBaseEntity *pAttacker, CDamageModifier *pDamageModifier )
+{
+ switch( iPowerup )
+ {
+ case POWERUP_EMP:
+ m_VehiclePhysics.SetMaxThrottle( 0.1 );
+ break;
+
+ default:
+ break;
+ }
+
+ BaseClass::PowerupStart( iPowerup, flAmount, pAttacker, pDamageModifier );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Powerup has just started
+//-----------------------------------------------------------------------------
+void CBaseTFFourWheelVehicle::PowerupEnd( int iPowerup )
+{
+ switch ( iPowerup )
+ {
+ case POWERUP_EMP:
+ m_VehiclePhysics.SetMaxThrottle( 1.0 );
+ break;
+
+ default:
+ break;
+ }
+
+ BaseClass::PowerupEnd( iPowerup );
+}
+
+//-----------------------------------------------------------------------------
+// Methods related to actually driving the vehicle
+//-----------------------------------------------------------------------------
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseTFFourWheelVehicle::DriveVehicle( CBasePlayer *pPlayer, CUserCmd *ucmd )
+{
+ // Lose control when the player dies
+ if ( pPlayer->IsAlive() == false )
+ return;
+
+ // Only the driver gets to drive.
+ int nRole = GetPassengerRole( pPlayer );
+ if ( nRole != VEHICLE_ROLE_DRIVER )
+ return;
+
+ // No driving in the mothership, kids
+ if ( !tf_fastbuild.GetInt() && !IsReadyToDrive() )
+ {
+ m_VehiclePhysics.SetHandbrake( true );
+ m_VehiclePhysics.SetThrottle( 0 );
+ m_VehiclePhysics.SetSteering( 0, 0 );
+ m_attackaxis.Set( 0, pPlayer, this );
+ m_attack2axis.Set( 0, pPlayer, this );
+ return;
+ }
+
+ // Update the boost time.
+ m_nBoostTimeLeft = m_VehiclePhysics.BoostTimeLeft();
+
+ // If the vehicle's emped, it can't drive
+ if ( HasPowerup( POWERUP_EMP ) )
+ {
+ // Play sounds if they're trying to drive
+ if ( ucmd->buttons & (IN_MOVELEFT | IN_MOVERIGHT | IN_FORWARD | IN_BACK) )
+ {
+ if ( m_flNextEmpSound < gpGlobals->curtime )
+ {
+ EmitSound( "BaseTFFourWheelVehicle.EMP" );
+ m_flNextEmpSound = gpGlobals->curtime + 2.0;
+ }
+ }
+ }
+
+ ResetDeteriorationTime();
+
+ m_VehiclePhysics.UpdateDriverControls( ucmd, TICK_INTERVAL );
+
+ float attack = 0, attack2 = 0;
+
+ if ( pPlayer->m_afButtonPressed & IN_ATTACK )
+ {
+ m_pressedAttack.FireOutput( pPlayer, this, 0 );
+ }
+ if ( pPlayer->m_afButtonPressed & IN_ATTACK2 )
+ {
+ m_pressedAttack2.FireOutput( pPlayer, this, 0 );
+ }
+
+ if ( ucmd->buttons & IN_ATTACK )
+ {
+ attack = 1;
+ }
+ if ( ucmd->buttons & IN_ATTACK2 )
+ {
+ attack2 = 1;
+ }
+
+ m_attackaxis.Set( attack, pPlayer, this );
+ m_attack2axis.Set( attack2, pPlayer, this );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseTFFourWheelVehicle::SetupMove( CBasePlayer *pPlayer, CUserCmd *ucmd, IMoveHelper *pHelper, CMoveData *move )
+{
+ BaseClass::SetupMove( pPlayer, ucmd, pHelper, move );
+
+ if ( IsDeployed() )
+ return;
+
+ DriveVehicle( pPlayer, ucmd );
+ m_nMovementRole = GetPassengerRole( pPlayer );
+ Assert( m_nMovementRole >= 0 );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseTFFourWheelVehicle::ProcessMovement( CBasePlayer *pPlayer, CMoveData *pMoveData )
+{
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseTFFourWheelVehicle::SetBoostUpgrade( bool bBoostUpgrade )
+{
+ m_bBoostUpgrade = bBoostUpgrade;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CBaseTFFourWheelVehicle::IsBoostable( void )
+{
+ return m_bBoostUpgrade;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseTFFourWheelVehicle::StartBoost( void )
+{
+ if ( IsBoostable() )
+ {
+ m_VehiclePhysics.SetBoost( 1.0f );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CBaseTFFourWheelVehicle::IsBoosting( void )
+{
+ return m_VehiclePhysics.IsBoosting();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Vehicle damage!
+//-----------------------------------------------------------------------------
+void CBaseTFFourWheelVehicle::VPhysicsCollision( int index, gamevcollisionevent_t *pEvent )
+{
+ BaseClass::VPhysicsCollision( index, pEvent );
+
+ int otherIndex = !index;
+ CBaseEntity *pEntity = pEvent->pEntities[otherIndex];
+
+ // We only damage objects...
+ // And only if we're travelling fast enough...
+ Assert( pEntity );
+ if ( !pEntity->IsSolid() )
+ return;
+
+ // Ignore shields..
+ if ( pEntity->GetCollisionGroup() == TFCOLLISION_GROUP_SHIELD )
+ return;
+
+ // Ignore anything that's not an object
+ if ( pEntity->Classify() != CLASS_MILITARY && !pEntity->IsPlayer() )
+ return;
+
+ // Ignore teammates
+ if ( InSameTeam( pEntity ))
+ return;
+
+ // Ignore invulnerable stuff
+ if ( pEntity->m_takedamage == DAMAGE_NO )
+ return;
+
+ // See if we can damage again? (Time-based)
+ if ( m_flNextHitTime > gpGlobals->curtime )
+ return;
+
+ // Do damage based on velocity.
+ Vector vecVelocity = pEvent->preVelocity[index];
+
+ CTakeDamageInfo info;
+ info.SetInflictor( this );
+ info.SetAttacker( GetDriverPlayer() );
+ info.SetDamageType( DMG_CLUB );
+
+ float flMaxDamage = fourwheelvehicle_hit_damage.GetFloat();
+ float flMaxDamageVel = fourwheelvehicle_hit_maxdamagevel.GetFloat();
+ float flMinDamageVel = fourwheelvehicle_hit_mindamagevel.GetFloat();
+
+ float flVel = vecVelocity.Length();
+ if ( flVel < flMinDamageVel )
+ return;
+
+ EmitSound( "BaseTFFourWheelVehicle.RamSound" );
+
+ float flDamageFactor = flMaxDamage;
+ // Special damage for players.
+ if ( pEntity->IsPlayer() )
+ {
+ flDamageFactor = fourwheelvehicle_hit_damage_player.GetFloat();
+
+ if ( IsBoosting() )
+ {
+ flDamageFactor *= 4.0f;
+ }
+
+ // Knock the player up into the air
+ float flForceScale = (flVel*0.5) * 75 * 4;
+ Vector vecForce = vecVelocity;
+ VectorNormalize( vecForce );
+ vecForce.z += 0.7;
+ vecForce *= flForceScale;
+ info.SetDamageForce( vecForce );
+ }
+ // Damage to objects.
+ else
+ {
+ if ( IsBoosting() )
+ {
+ flDamageFactor *= fourwheelvehicle_hit_damage_boostmod.GetFloat();
+ }
+
+ if ( ( flMaxDamageVel > flMinDamageVel ) && ( flVel < flMaxDamageVel ) )
+ {
+ // Use less damage when we're not moving fast enough
+ float flVelocityFactor = ( flVel - flMinDamageVel ) / ( flMaxDamageVel - flMinDamageVel );
+ flVelocityFactor *= flVelocityFactor;
+ flDamageFactor *= flVelocityFactor;
+ }
+ }
+
+ info.SetDamage( flDamageFactor );
+ Vector damagePos;
+ pEvent->pInternalData->GetContactPoint( damagePos );
+ Vector damageForce = pEvent->postVelocity[index] * pEvent->pObjects[index]->GetMass();
+ info.SetDamageForce( damageForce );
+ info.SetDamagePosition( damagePos );
+ PhysCallbackDamage( pEntity, info, *pEvent, index );
+
+ // Set next time hit time
+ m_flNextHitTime = gpGlobals->curtime + fourwheelvehicle_impact_time.GetFloat();
+}
diff --git a/game/server/tf2/tf_basefourwheelvehicle.h b/game/server/tf2/tf_basefourwheelvehicle.h
new file mode 100644
index 0000000..f9c6205
--- /dev/null
+++ b/game/server/tf2/tf_basefourwheelvehicle.h
@@ -0,0 +1,138 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: A base class that deals with four-wheel vehicles
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef TF_BASE_FOUR_WHEEL_VEHICLE_H
+#define TF_BASE_FOUR_WHEEL_VEHICLE_H
+
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include "basetfvehicle.h"
+#include "vphysics/vehicles.h"
+#include "fourwheelvehiclephysics.h"
+#include "tf_vehicleshared.h"
+
+class CMoveData;
+
+class CBaseTFFourWheelVehicle : public CBaseTFVehicle
+{
+public:
+ DECLARE_CLASS( CBaseTFFourWheelVehicle, CBaseTFVehicle );
+ DECLARE_SERVERCLASS();
+
+public:
+ CBaseTFFourWheelVehicle();
+ ~CBaseTFFourWheelVehicle ();
+
+ // CBaseEntity
+ void Spawn();
+ void Precache();
+ void VPhysicsUpdate( IPhysicsObject *pPhysics );
+ void DrawDebugGeometryOverlays();
+ int DrawDebugTextOverlays();
+ void Teleport( const Vector *newPosition, const QAngle *newAngles, const Vector *newVelocity );
+ void BaseFourWheeledVehicleThink();
+ void BaseFourWheeledVehicleDeployThink( void );
+
+ // HACK HACK: This is a hack to avoid physics spazzing out on a newly created vehicle with the handbrake
+ // set. We create and activate it, but then release the handbrake for a single Think function call and
+ // then zero out the controls right then. This seems to stabilize something in the physics simulator.
+ void BaseFourWheeledVehicleStopTheRodeoMadnessThink( void );
+
+ virtual bool StartBuilding( CBaseEntity *pPlayer );
+ virtual void FinishedBuilding( void );
+
+ virtual void SetupMove( CBasePlayer *player, CUserCmd *ucmd, IMoveHelper *pHelper, CMoveData *move );
+ virtual void ProcessMovement( CBasePlayer *pPlayer, CMoveData *pMoveData );
+ virtual void SetPassenger( int nRole, CBasePlayer *pEnt );
+
+ // Powerup handling
+ virtual void PowerupStart( int iPowerup, float flAmount, CBaseEntity *pAttacker, CDamageModifier *pDamageModifier );
+ virtual void PowerupEnd( int iPowerup );
+
+ // Collision
+ virtual void VPhysicsCollision( int index, gamevcollisionevent_t *pEvent );
+
+ // Inputs
+ void InputThrottle( inputdata_t &inputdata );
+ void InputSteering( inputdata_t &inputdata );
+ void InputAction( inputdata_t &inputdata );
+ void InputTurnOn( inputdata_t &inputdata );
+ void InputTurnOff( inputdata_t &inputdata );
+
+ // Boost
+ void SetBoostUpgrade( bool bBoostUpgrade );
+ bool IsBoostable( void );
+ bool IsBoosting( void );
+ void StartBoost( void );
+
+ bool IsDeployed( void ) { return ( m_eDeployMode == VEHICLE_MODE_DEPLOYED ); }
+ bool IsDeploying( void ) { return ( m_eDeployMode == VEHICLE_MODE_DEPLOYING ); }
+ bool IsUndeploying( void ) { return ( m_eDeployMode == VEHICLE_MODE_UNDEPLOYING ); }
+ bool InDeployMode( void ) { return ( m_eDeployMode != VEHICLE_MODE_NORMAL ); }
+
+ DECLARE_DATADESC();
+
+ // locals
+protected:
+ // engine sounds
+ void SoundInit();
+ void SoundShutdown();
+ void SoundUpdate( const vehicle_operatingparams_t &params, const vehicleparams_t &vehicle );
+ void CalcWheelData( vehicleparams_t &vehicle );
+ void ResetControls();
+
+ // Deploy
+ bool Deploy( void );
+ void UnDeploy( void );
+ void CancelDeploy( void );
+ virtual void OnFinishedDeploy( void );
+ virtual void OnFinishedUnDeploy( void );
+
+private:
+ void DriveVehicle( CBasePlayer *pPlayer, CUserCmd *ucmd );
+ void PlayerControlInit( CBasePlayer *pPlayer );
+ void PlayerControlShutdown();
+ void ResetUseKey( CBasePlayer *pPlayer );
+ void InitializePoseParameters();
+ bool ParseVehicleScript( solid_t &solid, vehicleparams_t &vehicle );
+
+ void EnableMotion( void );
+ void DisableMotion( void );
+
+private:
+ CFourWheelVehiclePhysics m_VehiclePhysics;
+ COutputEvent m_playerOn;
+ COutputEvent m_playerOff;
+
+ COutputEvent m_pressedAttack;
+ COutputEvent m_pressedAttack2;
+
+ COutputFloat m_attackaxis;
+ COutputFloat m_attack2axis;
+
+ int m_nMovementRole;
+ Vector m_savedViewOffset; //[MAX_PASSENGERS];
+
+ float m_flNextEmpSound;
+
+ // Deploy
+ CNetworkVar( VehicleModeDeploy_e, m_eDeployMode );
+ Activity m_PreDeployActivity;
+
+ // Used for vgui screens on the client.
+ CNetworkVar( float, m_flDeployFinishTime );
+ CNetworkVar( bool, m_bBoostUpgrade );
+ CNetworkVar( int, m_nBoostTimeLeft );
+
+ float m_flNextHitTime;
+};
+
+
+
+#endif // TF_BASE_FOUR_WHEEL_VEHICLE_H
diff --git a/game/server/tf2/tf_carrier.cpp b/game/server/tf2/tf_carrier.cpp
new file mode 100644
index 0000000..db4f992
--- /dev/null
+++ b/game/server/tf2/tf_carrier.cpp
@@ -0,0 +1 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
diff --git a/game/server/tf2/tf_carrier.h b/game/server/tf2/tf_carrier.h
new file mode 100644
index 0000000..db4f992
--- /dev/null
+++ b/game/server/tf2/tf_carrier.h
@@ -0,0 +1 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
diff --git a/game/server/tf2/tf_class_commando.cpp b/game/server/tf2/tf_class_commando.cpp
new file mode 100644
index 0000000..79a4943
--- /dev/null
+++ b/game/server/tf2/tf_class_commando.cpp
@@ -0,0 +1,586 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: The Commando Player Class
+//
+// $Workfile: $
+// $NoKeywords: $
+//=============================================================================//
+#include "cbase.h"
+#include "tf_player.h"
+#include "tf_class_commando.h"
+#include "tf_vehicle_teleport_station.h"
+#include "EntityList.h"
+#include "basecombatweapon.h"
+#include "weapon_builder.h"
+#include "tf_obj.h"
+#include "tf_obj_rallyflag.h"
+#include "tf_team.h"
+#include "order_assist.h"
+#include "engine/IEngineSound.h"
+#include "weapon_twohandedcontainer.h"
+#include "weapon_combatshield.h"
+
+ConVar tf_knockdowntime( "tf_knockdowntime", "3", FCVAR_NONE, "Length of time knocked-down players remain on the ground." );
+
+// Adrenalin
+ConVar class_commando_speed( "class_commando_speed","200", FCVAR_NONE, "Commando movement speed." );
+ConVar class_commando_rush_length( "class_commando_rush_length","10", FCVAR_NONE, "Commando's adrenalin rush length in seconds." );
+ConVar class_commando_rush_recharge( "class_commando_rush_recharge","60", FCVAR_NONE, "Commando's adrenalin rush recharge time in seconds." );
+
+ConVar class_commando_battlecry_radius( "class_commando_battlecry_radius","512", FCVAR_NONE, "Commando's battlecry radius." );
+ConVar class_commando_battlecry_length( "class_commando_battlecry_length","10", FCVAR_NONE, "Length of adrenalin rush given by the Commando's battlecry in seconds." );
+
+//=============================================================================
+//
+// Commando Data Table
+//
+BEGIN_SEND_TABLE_NOBASE( CPlayerClassCommando, DT_PlayerClassCommandoData )
+ SendPropInt ( SENDINFO_STRUCTELEM( m_ClassData.m_bCanBullRush ), 1, SPROP_UNSIGNED ),
+ SendPropInt ( SENDINFO_STRUCTELEM( m_ClassData.m_bBullRush ), 1, SPROP_UNSIGNED ),
+ SendPropVector ( SENDINFO_STRUCTELEM( m_ClassData.m_vecBullRushDir ), -1, SPROP_COORD ),
+ SendPropVector ( SENDINFO_STRUCTELEM( m_ClassData.m_vecBullRushViewDir ), -1, SPROP_COORD ),
+ SendPropVector ( SENDINFO_STRUCTELEM( m_ClassData.m_vecBullRushViewGoalDir ), -1, SPROP_COORD ),
+ SendPropFloat ( SENDINFO_STRUCTELEM( m_ClassData.m_flBullRushTime ), 32, SPROP_NOSCALE ),
+ SendPropFloat ( SENDINFO_STRUCTELEM( m_ClassData.m_flDoubleTapForwardTime ), 32, SPROP_NOSCALE ),
+END_SEND_TABLE()
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Output : const char
+//-----------------------------------------------------------------------------
+const char *CPlayerClassCommando::GetClassModelString( int nTeam )
+{
+ if (nTeam == TEAM_HUMANS)
+ return "models/player/human_commando.mdl";
+ else
+ return "models/player/alien_commando.mdl";
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CPlayerClassCommando::CPlayerClassCommando( CBaseTFPlayer *pPlayer, TFClass iClass ) : CPlayerClass( pPlayer, iClass )
+{
+ for (int i = 0; i < MAX_TF_TEAMS; ++i)
+ {
+ SetClassModel( MAKE_STRING(GetClassModelString(i)), i );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CPlayerClassCommando::~CPlayerClassCommando()
+{
+ m_aHitPlayers.RemoveAll();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPlayerClassCommando::ClassActivate( void )
+{
+ BaseClass::ClassActivate();
+
+ // Setup movement data.
+ SetupMoveData();
+
+ // Initialize the shared class data.
+ m_ClassData.m_bCanBullRush = false;
+ m_ClassData.m_bBullRush = false;
+ m_ClassData.m_vecBullRushDir.Init();
+ m_ClassData.m_vecBullRushViewDir.Init();
+ m_ClassData.m_vecBullRushViewGoalDir.Init();
+ m_ClassData.m_flBullRushTime = COMMANDO_TIME_INVALID;
+ m_ClassData.m_flDoubleTapForwardTime = COMMANDO_TIME_INVALID;
+
+ m_bCanRush = false;
+ m_bPersonalRush = false;
+ m_bHasBattlecry = false;
+ m_bCanBoot = false;
+ m_flNextBootCheck = 0.0f; // Time at which to recheck for the automatic melee attack
+ m_bOldBullRush = 0.0f;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPlayerClassCommando::ClassDeactivate( void )
+{
+ m_hWpnShield = NULL;
+ m_hWpnPlasma = NULL;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPlayerClassCommando::CreateClass( void )
+{
+ BaseClass::CreateClass();
+
+ // Create our two handed weapon layout
+ m_hWpnShield = m_pPlayer->GetCombatShield();
+
+ CWeaponTwoHandedContainer *p = ( CWeaponTwoHandedContainer * )m_pPlayer->Weapon_OwnsThisType( "weapon_twohandedcontainer" );
+ if ( !p )
+ {
+ p = static_cast< CWeaponTwoHandedContainer * >( m_pPlayer->GiveNamedItem( "weapon_twohandedcontainer" ) );
+ }
+
+ if ( p && m_hWpnShield.Get() )
+ {
+ m_hWpnShield->SetReflectViewModelAnimations( true );
+ p->SetWeapons( NULL, m_hWpnShield );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPlayerClassCommando::RespawnClass( void )
+{
+ BaseClass::RespawnClass();
+
+ m_flNextBootCheck = 0;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Supply the player with Ammo. Return true if some ammo was given.
+//-----------------------------------------------------------------------------
+bool CPlayerClassCommando::ResupplyAmmo( float flFraction, ResupplyReason_t reason )
+{
+ bool bGiven = false;
+
+ if ((reason == RESUPPLY_ALL_FROM_STATION) || (reason == RESUPPLY_GRENADES_FROM_STATION))
+ {
+ if (ResupplyAmmoType( 3 * flFraction, "Grenades" ))
+ bGiven = true;
+ if (ResupplyAmmoType( 1, "RallyFlags" ))
+ bGiven = true;
+ if (ResupplyAmmoType( 3, "Rockets" ))
+ bGiven = true;
+ }
+
+ if ((reason == RESUPPLY_ALL_FROM_STATION) || (reason == RESUPPLY_AMMO_FROM_STATION))
+ {
+ if (ResupplyAmmoType( 3, "Rockets" ))
+ bGiven = true;
+ }
+
+ // On respawn, resupply base weapon ammo
+ if ( reason == RESUPPLY_RESPAWN )
+ {
+ }
+
+ if ( BaseClass::ResupplyAmmo(flFraction, reason) )
+ bGiven = true;
+
+ return bGiven;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Set commando class specific movement data here.
+//-----------------------------------------------------------------------------
+void CPlayerClassCommando::SetupMoveData( void )
+{
+ // Setup Class statistics
+ m_flMaxWalkingSpeed = class_commando_speed.GetFloat();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPlayerClassCommando::SetupSizeData( void )
+{
+ // Initially set the player to the base player class standing hull size.
+ m_pPlayer->SetCollisionBounds( COMMANDOCLASS_HULL_STAND_MIN, COMMANDOCLASS_HULL_STAND_MAX );
+ m_pPlayer->SetViewOffset( COMMANDOCLASS_VIEWOFFSET_STAND );
+ m_pPlayer->m_Local.m_flStepSize = COMMANDOCLASS_STEPSIZE;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Return true if this player's allowed to build another one of the specified objects
+//-----------------------------------------------------------------------------
+int CPlayerClassCommando::CanBuild( int iObjectType )
+{
+ if ( iObjectType == OBJ_RALLYFLAG )
+ {
+ if ( !m_pPlayer->HasNamedTechnology( "com_obj_rallyflag" ) )
+ return CB_NOT_RESEARCHED;
+ }
+
+ return BaseClass::CanBuild( iObjectType );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Object has been built by this player
+//-----------------------------------------------------------------------------
+void CPlayerClassCommando::FinishedObject( CBaseObject *pObject )
+{
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Calculate the Commando's Adrenalin Rush from available technologies
+//-----------------------------------------------------------------------------
+void CPlayerClassCommando::CalculateRush( void )
+{
+ // Adrenalin Rush
+ if ( m_pPlayer->HasNamedTechnology( "com_adrenalin_rush" ) )
+ {
+ m_bCanRush = true;
+ }
+ else
+ {
+ m_bCanRush = false;
+ }
+
+ // Battlecry
+ m_bHasBattlecry = m_pPlayer->HasNamedTechnology( "com_adrenalin_battlecry" );
+
+ // Boot
+ // ROBIN: Removed for now
+ m_bCanBoot = false;//m_pPlayer->HasNamedTechnology( "com_automatic_boot" );
+
+ // Killing Rush
+ m_pPlayer->SetRampage( m_pPlayer->HasNamedTechnology( "com_adrenalin_rampage" ) );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Return true if the player is bullrushing.
+//-----------------------------------------------------------------------------
+bool CPlayerClassCommando::InBullRush( void )
+{
+ return m_ClassData.m_bBullRush;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Return true if the player's able to bull rush
+//-----------------------------------------------------------------------------
+bool CPlayerClassCommando::CanBullRush( void )
+{
+ return m_ClassData.m_bCanBullRush;
+}
+
+
+//-----------------------------------------------------------------------------
+// Should we take damage-based force?
+//-----------------------------------------------------------------------------
+bool CPlayerClassCommando::ShouldApplyDamageForce( const CTakeDamageInfo &info )
+{
+ return !InBullRush();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPlayerClassCommando::BullRushTouch( CBaseEntity *pTouched )
+{
+ if ( pTouched->IsPlayer() && !pTouched->InSameTeam( m_pPlayer ) )
+ {
+ // Get the player.
+ CBaseTFPlayer *pTFPlayer = ( CBaseTFPlayer* )pTouched;
+
+ // Check to see if we have "touched" this player already this bullrush cycle.
+ if ( m_aHitPlayers.Find( pTFPlayer ) != -1 )
+ return;
+
+ // Hitting the player now.
+ m_aHitPlayers.AddToTail( pTFPlayer );
+
+ // ROBIN: Bullrush now instantly kills again
+ float flDamage = 200;
+ // Calculate the damage a player takes based on distance(time).
+ //float flDamage = 1.0f - ( ( COMMANDO_BULLRUSH_TIME - m_ClassData.m_flBullRushTime ) * ( 1.0f / COMMANDO_BULLRUSH_TIME ) );
+ //flDamage *= 115.0f; // max bullrush damage
+
+ const trace_t &tr = m_pPlayer->GetTouchTrace();
+ CTakeDamageInfo info( m_pPlayer, m_pPlayer, flDamage, DMG_CLUB, DMG_KILL_BULLRUSH );
+ CalculateMeleeDamageForce( &info, (tr.endpos - tr.startpos), tr.endpos );
+ pTFPlayer->TakeDamage( info );
+
+ CPASAttenuationFilter filter( m_pPlayer, "Commando.BullRushFlesh" );
+ CBaseEntity::EmitSound( filter, m_pPlayer->entindex(), "Commando.BullRushFlesh" );
+
+ pTFPlayer->Touch( m_pPlayer );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: New technology has been gained. Recalculate any class specific technology dependencies.
+//-----------------------------------------------------------------------------
+void CPlayerClassCommando::GainedNewTechnology( CBaseTechnology *pTechnology )
+{
+ // Technology handling
+ CalculateRush();
+
+ BaseClass::GainedNewTechnology( pTechnology );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Called when we are about to bullrush.
+//-----------------------------------------------------------------------------
+void CPlayerClassCommando::PreBullRush( void )
+{
+ // Set the touch function to look for collisions!
+ SetClassTouch( m_pPlayer, BullRushTouch );
+
+ // Clear the player hit list.
+ m_aHitPlayers.RemoveAll();
+
+ // Start the bull rush sound.
+ CPASAttenuationFilter filter( m_pPlayer, "Commando.BullRushScream" );
+ filter.MakeReliable();
+ CBaseEntity::EmitSound( filter, m_pPlayer->entindex(), "Commando.BullRushScream" );
+
+ // Force the shield down, if it is up.
+ CWeaponTwoHandedContainer *pContainer = dynamic_cast<CWeaponTwoHandedContainer*>( m_pPlayer->GetActiveWeapon() );
+ if ( pContainer )
+ {
+ CWeaponCombatShield *pShield = dynamic_cast<CWeaponCombatShield*>( pContainer->GetRightWeapon() );
+ if ( pShield )
+ {
+ pShield->SetShieldUsable( false );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Called when we finish bullrushing.
+//-----------------------------------------------------------------------------
+void CPlayerClassCommando::PostBullRush( void )
+{
+ SetClassTouch( m_pPlayer, NULL );
+
+ // Force the shield down, if it is up.
+ CWeaponTwoHandedContainer *pContainer = dynamic_cast<CWeaponTwoHandedContainer*>( m_pPlayer->GetActiveWeapon() );
+ if ( pContainer )
+ {
+ CWeaponCombatShield *pShield = dynamic_cast<CWeaponCombatShield*>( pContainer->GetRightWeapon() );
+ if ( pShield )
+ {
+ pShield->SetShieldUsable( true );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Called every frame by postthink
+//-----------------------------------------------------------------------------
+void CPlayerClassCommando::ClassThink( void )
+{
+ // Check bullrush
+ m_ClassData.m_bCanBullRush = true;
+
+ // Do the init thing here!
+ if ( m_bOldBullRush != m_ClassData.m_bBullRush )
+ {
+ if ( m_ClassData.m_bBullRush )
+ {
+ PreBullRush();
+ }
+ else
+ {
+ PostBullRush();
+ }
+
+ m_bOldBullRush = (bool)m_ClassData.m_bBullRush;
+ }
+
+ // Check for melee attack
+ if ( m_bCanBoot && m_pPlayer->IsAlive() && m_flNextBootCheck < gpGlobals->curtime )
+ {
+ m_flNextBootCheck = gpGlobals->curtime + 0.2;
+
+ CBaseEntity *pEntity = NULL;
+ Vector vecSrc = m_pPlayer->Weapon_ShootPosition( );
+ Vector vecDir = m_pPlayer->BodyDirection2D( );
+ Vector vecTarget = vecSrc + (vecDir * 48);
+ for ( CEntitySphereQuery sphere( vecTarget, 16 ); pEntity = sphere.GetCurrentEntity(); sphere.NextEntity() )
+ {
+ if ( pEntity->IsPlayer() && (pEntity != m_pPlayer) )
+ {
+ CBaseTFPlayer *pPlayer = (CBaseTFPlayer *)pEntity;
+ // Target needs to be on the enemy team
+ if ( !pPlayer->IsClass( TFCLASS_UNDECIDED ) && pPlayer->IsAlive() && pPlayer->InSameTeam( m_pPlayer ) == false )
+ {
+ Boot( pPlayer );
+ m_flNextBootCheck = gpGlobals->curtime + 1.5;
+ }
+ }
+ }
+ }
+
+ BaseClass::ClassThink();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPlayerClassCommando::StartAdrenalinRush( void )
+{
+ // Am I actually alive?
+ if ( !m_pPlayer->IsAlive() )
+ return;
+
+ // Do I have rush capability?
+ if ( !m_bCanRush )
+ return;
+
+ m_bPersonalRush = true;
+
+ // Start adrenalin rushing
+ m_pPlayer->AttemptToPowerup( POWERUP_RUSH, class_commando_rush_length.GetFloat() );
+
+ // If I have battlecry, adrenalin up all my nearby teammates
+ if ( m_bHasBattlecry )
+ {
+ // Find nearby teammates
+ for ( int i = 0; i < m_pPlayer->GetTFTeam()->GetNumPlayers(); i++ )
+ {
+ CBaseTFPlayer *pPlayer = (CBaseTFPlayer *)m_pPlayer->GetTFTeam()->GetPlayer(i);
+ assert(pPlayer);
+
+ // Is it within range?
+ if ( pPlayer != m_pPlayer && (pPlayer->GetAbsOrigin() - m_pPlayer->GetAbsOrigin()).Length() < class_commando_battlecry_radius.GetFloat() )
+ {
+ // Can I see it?
+ trace_t tr;
+ UTIL_TraceLine( m_pPlayer->EyePosition(), pPlayer->EyePosition(), MASK_SOLID_BRUSHONLY, m_pPlayer, COLLISION_GROUP_NONE, &tr);
+ CBaseEntity *pEntity = tr.m_pEnt;
+ if ( (tr.fraction == 1.0) || ( pEntity == pPlayer ) )
+ {
+ pPlayer->AttemptToPowerup( POWERUP_RUSH, class_commando_battlecry_length.GetFloat() );
+ }
+ }
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Automatic Melee Attack
+//-----------------------------------------------------------------------------
+void CPlayerClassCommando::Boot( CBaseTFPlayer *pTarget )
+{
+ CPASAttenuationFilter filter( m_pPlayer, "Commando.BootSwing" );
+
+ CBaseEntity::EmitSound( filter, m_pPlayer->entindex(), "Commando.BootSwing" );
+ CBaseEntity::EmitSound( filter, m_pPlayer->entindex(), "Commando.BootHit" );
+
+ // Damage the target
+ CTakeDamageInfo info( m_pPlayer, m_pPlayer, 25, DMG_CLUB );
+ CalculateMeleeDamageForce( &info, (pTarget->GetAbsOrigin() - m_pPlayer->GetAbsOrigin()), pTarget->GetAbsOrigin() );
+ pTarget->TakeDamage( info );
+
+ Vector vecForward;
+ AngleVectors( m_pPlayer->GetLocalAngles(), &vecForward );
+ // Give it a lot of "in the air"
+ vecForward.z = MAX( 0.8, vecForward.z );
+ VectorNormalize( vecForward );
+
+ // Knock the target to the ground for a few seconds (use default duration)
+ pTarget->KnockDownPlayer( vecForward, 500.0f, tf_knockdowntime.GetFloat() );
+
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Handle custom commands for this playerclass
+//-----------------------------------------------------------------------------
+bool CPlayerClassCommando::ClientCommand( const char *pcmd )
+{
+ return BaseClass::ClientCommand( pcmd );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPlayerClassCommando::SetPlayerHull( void )
+{
+ if ( m_pPlayer->GetFlags() & FL_DUCKING )
+ {
+ m_pPlayer->SetCollisionBounds( COMMANDOCLASS_HULL_DUCK_MIN, COMMANDOCLASS_HULL_DUCK_MAX );
+ }
+ else
+ {
+ m_pPlayer->SetCollisionBounds( COMMANDOCLASS_HULL_STAND_MIN, COMMANDOCLASS_HULL_STAND_MAX );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPlayerClassCommando::GetPlayerHull( bool bDucking, Vector &vecMin, Vector &vecMax )
+{
+ if ( bDucking )
+ {
+ VectorCopy( COMMANDOCLASS_HULL_DUCK_MIN, vecMin );
+ VectorCopy( COMMANDOCLASS_HULL_DUCK_MAX, vecMax );
+ }
+ else
+ {
+ VectorCopy( COMMANDOCLASS_HULL_STAND_MIN, vecMin );
+ VectorCopy( COMMANDOCLASS_HULL_STAND_MAX, vecMax );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPlayerClassCommando::CreatePersonalOrder( void )
+{
+ if ( CreateInitialOrder() )
+ return;
+
+ if ( COrderAssist::CreateOrder( this ) )
+ return;
+
+ BaseClass::CreatePersonalOrder();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPlayerClassCommando::ResetViewOffset( void )
+{
+ if ( m_pPlayer )
+ {
+ m_pPlayer->SetViewOffset( COMMANDOCLASS_VIEWOFFSET_STAND );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPlayerClassCommando::InitVCollision( void )
+{
+ CPhysCollide *pStandModel = PhysCreateBbox( COMMANDOCLASS_HULL_STAND_MIN, COMMANDOCLASS_HULL_STAND_MAX );
+ CPhysCollide *pCrouchModel = PhysCreateBbox( COMMANDOCLASS_HULL_DUCK_MIN, COMMANDOCLASS_HULL_DUCK_MAX );
+ m_pPlayer->SetupVPhysicsShadow( pStandModel, "tfplayer_commando_stand", pCrouchModel, "tfplayer_commando_crouch" );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CPlayerClassCommando::CanGetInVehicle( void )
+{
+ if ( InBullRush() )
+ return false;
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+int CPlayerClassCommando::ClassCostAdjustment( ResupplyBuyType_t nType )
+{
+ int nCost = 0;
+ if ( nType != RESUPPLY_BUY_HEALTH )
+ {
+ nCost = RESUPPLY_ROCKET_COST;
+ }
+
+ return nCost;
+}
diff --git a/game/server/tf2/tf_class_commando.h b/game/server/tf2/tf_class_commando.h
new file mode 100644
index 0000000..54ca5b1
--- /dev/null
+++ b/game/server/tf2/tf_class_commando.h
@@ -0,0 +1,112 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef TF_CLASS_COMMANDO_H
+#define TF_CLASS_COMMANDO_H
+
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include "tf_playerclass.h"
+#include "TFClassData_Shared.h"
+#include "basetfcombatweapon_shared.h"
+
+//=====================================================================
+// Commando
+class CPlayerClassCommando : public CPlayerClass
+{
+ DECLARE_CLASS( CPlayerClassCommando, CPlayerClass );
+public:
+ CPlayerClassCommando( CBaseTFPlayer *pPlayer, TFClass iClass );
+ virtual ~CPlayerClassCommando();
+
+ virtual void ClassActivate( void );
+ virtual void ClassDeactivate( void );
+
+ virtual const char* GetClassModelString( int nTeam );
+
+ // Class Initialization
+ virtual void CreateClass( void ); // Create the class upon initial spawn
+ virtual void RespawnClass( void ); // Called upon all respawns
+ virtual bool ResupplyAmmo( float flPercentage, ResupplyReason_t reason );
+ virtual void SetupMoveData( void ); // Override class specific movement data here.
+ virtual void SetupSizeData( void ); // Override class specific size data here.
+ virtual void ResetViewOffset( void );
+
+ // Should we take damage-based force?
+ virtual bool ShouldApplyDamageForce( const CTakeDamageInfo &info );
+
+ PlayerClassCommandoData_t *GetClassData( void ) { return &m_ClassData; }
+
+ // Class Abilities
+ virtual void ClassThink( void );
+
+ // Resources
+ int ClassCostAdjustment( ResupplyBuyType_t nType );
+
+ // Objects
+ virtual int CanBuild( int iObjectType );
+ virtual void FinishedObject( CBaseObject *pObject );
+
+ virtual bool ClientCommand( const CCommand &args );
+ virtual void GainedNewTechnology( CBaseTechnology *pTechnology );
+
+ // Adrenalin Rush
+ virtual void CalculateRush( void );
+ virtual void StartAdrenalinRush( void );
+
+ // Automatic Melee Attack
+ virtual void Boot( CBaseTFPlayer *pTarget );
+
+ // Hooks
+ virtual void SetPlayerHull( void );
+ virtual void GetPlayerHull( bool bDucking, Vector &vecMin, Vector &vecMax );
+
+ // Orders.
+ virtual void CreatePersonalOrder( void );
+
+ // Bull Rush.
+ bool InBullRush( void );
+ bool CanBullRush( void );
+ void BullRushTouch( CBaseEntity *pTouched );
+
+ CNetworkVarEmbedded( PlayerClassCommandoData_t, m_ClassData );
+
+ // Player physics shadow.
+ void InitVCollision( void );
+
+ // Vehicle
+ bool CanGetInVehicle( void );
+
+protected:
+ // BullRush
+ void PreBullRush( void );
+ void PostBullRush( void );
+
+protected:
+ // Adrenalin Rush
+ bool m_bCanRush; // True if he has the ability to rush
+ bool m_bPersonalRush; // True if this he started his current rush, or outside effect
+ bool m_bHasBattlecry; // True if he has the ability to battlecry
+
+ // Weapons
+ CHandle<CBaseTFCombatWeapon> m_hWpnPlasma;
+// CHandle<CBaseTFCombatWeapon> m_hWpnGrenade;
+
+ // Automatic Melee Attack
+ bool m_bCanBoot; // True if he has the ability to boot
+ float m_flNextBootCheck; // Time at which to recheck for the automatic melee attack
+
+ bool m_bOldBullRush;
+
+ CUtlVector<CBaseTFPlayer*> m_aHitPlayers; // Player I have hit during this bullrush.
+};
+
+EXTERN_SEND_TABLE( DT_PlayerClassCommandoData )
+
+#endif // TF_CLASS_COMMANDO_H
diff --git a/game/server/tf2/tf_class_defender.cpp b/game/server/tf2/tf_class_defender.cpp
new file mode 100644
index 0000000..0de2034
--- /dev/null
+++ b/game/server/tf2/tf_class_defender.cpp
@@ -0,0 +1,352 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: The Defender Player Class
+//
+// $Workfile: $
+// $Date: $
+// $NoKeywords: $
+//=============================================================================//
+#include "cbase.h"
+#include "tf_player.h"
+#include "tf_class_defender.h"
+#include "tf_obj.h"
+#include "tf_obj_sentrygun.h"
+#include "basecombatweapon.h"
+#include "weapon_builder.h"
+#include "weapon_limpetmine.h"
+#include "tf_team.h"
+#include "orders.h"
+#include "order_repair.h"
+#include "order_buildsentrygun.h"
+#include "weapon_twohandedcontainer.h"
+#include "weapon_combatshield.h"
+#include "tf_vehicle_teleport_station.h"
+
+ConVar class_defender_speed( "class_defender_speed","200", FCVAR_NONE, "Defender movement speed" );
+
+
+// An object must be this close to a sentry gun to be considered covered by it.
+#define DEFENDER_SENTRY_COVERED_DIST 1000
+
+
+//=============================================================================
+//
+// Defender Data Table
+//
+BEGIN_SEND_TABLE_NOBASE( CPlayerClassDefender, DT_PlayerClassDefenderData )
+END_SEND_TABLE()
+
+
+bool OrderCreator_BuildSentryGun( CPlayerClassDefender *pClass )
+{
+ return COrderBuildSentryGun::CreateOrder( pClass );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Output : const char
+//-----------------------------------------------------------------------------
+const char *CPlayerClassDefender::GetClassModelString( int nTeam )
+{
+ if (nTeam == TEAM_HUMANS)
+ return "models/player/human_defender.mdl";
+ else
+ return "models/player/defender.mdl";
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Defender
+//-----------------------------------------------------------------------------
+CPlayerClassDefender::CPlayerClassDefender( CBaseTFPlayer *pPlayer, TFClass iClass ) : CPlayerClass( pPlayer, iClass )
+{
+ for (int i = 0; i < MAX_TF_TEAMS; ++i)
+ {
+ SetClassModel( MAKE_STRING(GetClassModelString(i)), i );
+ }
+}
+
+CPlayerClassDefender::~CPlayerClassDefender()
+{
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPlayerClassDefender::ClassActivate( void )
+{
+ BaseClass::ClassActivate();
+
+ // Setup movement data.
+ SetupMoveData();
+
+ m_iNumberOfSentriesAllowed = 0;
+ m_bHasSmarterSentryguns = false;
+ m_bHasSensorSentryguns = false;
+ m_bHasMachinegun = false;
+ m_bHasRocketlauncher = false;
+ m_bHasAntiair = false;
+
+ m_hWpnShield = NULL;
+ m_hWpnPlasma = NULL;
+ memset( &m_ClassData, 0, sizeof( m_ClassData ) );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPlayerClassDefender::ClassDeactivate( void )
+{
+ BaseClass::ClassDeactivate();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPlayerClassDefender::CreateClass( void )
+{
+ BaseClass::CreateClass();
+
+ // Create our two handed weapon layout
+ m_hWpnPlasma = static_cast< CBaseTFCombatWeapon * >( m_pPlayer->GiveNamedItem( "weapon_combat_burstrifle" ) );
+ m_hWpnShield = m_pPlayer->GetCombatShield();
+ CWeaponTwoHandedContainer *p = ( CWeaponTwoHandedContainer * )m_pPlayer->Weapon_OwnsThisType( "weapon_twohandedcontainer" );
+ if ( !p )
+ {
+ p = static_cast< CWeaponTwoHandedContainer * >( m_pPlayer->GiveNamedItem( "weapon_twohandedcontainer" ) );
+ }
+ if ( p && m_hWpnShield.Get() && m_hWpnPlasma.Get() )
+ {
+ m_hWpnShield->SetReflectViewModelAnimations( true );
+ p->SetWeapons( m_hWpnPlasma, m_hWpnShield );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CPlayerClassDefender::ResupplyAmmo( float flFraction, ResupplyReason_t reason )
+{
+ bool bGiven = false;
+ if ((reason == RESUPPLY_ALL_FROM_STATION) || (reason == RESUPPLY_AMMO_FROM_STATION))
+ {
+ if (ResupplyAmmoType( 20 * flFraction, "Limpets" ))
+ bGiven = true;
+
+ // Defender doesn't use rockets, but his sentryguns do
+ if (ResupplyAmmoType( 50 * flFraction, "Rockets" ))
+ bGiven = true;
+ }
+
+ if ((reason == RESUPPLY_ALL_FROM_STATION) || (reason == RESUPPLY_GRENADES_FROM_STATION))
+ {
+ }
+
+ // On respawn, resupply base weapon ammo
+ if ( reason == RESUPPLY_RESPAWN )
+ {
+ }
+
+ if ( BaseClass::ResupplyAmmo(flFraction, reason) )
+ bGiven = true;
+
+ return bGiven;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Set defender class specific movement data here.
+//-----------------------------------------------------------------------------
+void CPlayerClassDefender::SetupMoveData( void )
+{
+ // Setup Class statistics
+ m_flMaxWalkingSpeed = class_defender_speed.GetFloat();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPlayerClassDefender::SetupSizeData( void )
+{
+ // Initially set the player to the base player class standing hull size.
+ m_pPlayer->SetCollisionBounds( DEFENDERCLASS_HULL_STAND_MIN, DEFENDERCLASS_HULL_STAND_MAX );
+ m_pPlayer->SetViewOffset( DEFENDERCLASS_VIEWOFFSET_STAND );
+ m_pPlayer->m_Local.m_flStepSize = DEFENDERCLASS_STEPSIZE;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: New technology has been gained. Recalculate any class specific technology dependencies.
+//-----------------------------------------------------------------------------
+void CPlayerClassDefender::GainedNewTechnology( CBaseTechnology *pTechnology )
+{
+ // Calculate the number of sentryguns allowed
+ if ( m_pPlayer->HasNamedTechnology( "sentrygun_three" ) )
+ {
+ m_iNumberOfSentriesAllowed = 3;
+ }
+ else if ( m_pPlayer->HasNamedTechnology( "sentrygun_two" ) )
+ {
+ m_iNumberOfSentriesAllowed = 2;
+ }
+ else
+ {
+ m_iNumberOfSentriesAllowed = 1;
+ }
+
+ m_bHasSmarterSentryguns = false;
+ m_bHasSensorSentryguns = false;
+ m_bHasRocketlauncher = false;
+
+ // Sentrygun levels
+ if ( m_pPlayer->HasNamedTechnology( "sentrygun_ai" ) )
+ {
+ m_bHasSmarterSentryguns = true;
+ }
+ if ( m_pPlayer->HasNamedTechnology( "sentrygun_sensors" ) )
+ {
+ m_bHasSensorSentryguns = true;
+ }
+
+ // Sentrygun types
+ if ( m_pPlayer->HasNamedTechnology( "sentrygun_rocket" ) )
+ {
+ m_bHasRocketlauncher = true;
+ }
+
+ UpdateSentrygunTechnology();
+ BaseClass::GainedNewTechnology( pTechnology );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Tell all sentryguns what level of technology the Defender has
+//-----------------------------------------------------------------------------
+void CPlayerClassDefender::UpdateSentrygunTechnology( void )
+{
+ for (int i = 0; i < m_pPlayer->GetObjectCount(); i++)
+ {
+ CBaseObject *pObj = m_pPlayer->GetObject(i);
+ if ( pObj && pObj->IsSentrygun() )
+ {
+ CObjectSentrygun *pSentry = static_cast<CObjectSentrygun *>(pObj);
+ pSentry->SetTechnology( m_bHasSmarterSentryguns, m_bHasSensorSentryguns );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Return true if this player's allowed to build another one of the specified objects
+//-----------------------------------------------------------------------------
+int CPlayerClassDefender::CanBuild( int iObjectType )
+{
+ // First, check to see if we've got the technology
+ if ( iObjectType == OBJ_SENTRYGUN_ROCKET_LAUNCHER )
+ {
+ if ( !m_bHasRocketlauncher )
+ return CB_NOT_RESEARCHED;
+ }
+
+ return BaseClass::CanBuild( iObjectType );
+}
+
+
+int CPlayerClassDefender::CanBuildSentryGun()
+{
+ return
+ CanBuild( OBJ_SENTRYGUN_ROCKET_LAUNCHER ) == CB_CAN_BUILD ||
+ CanBuild( OBJ_SENTRYGUN_PLASMA ) == CB_CAN_BUILD;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Object has been built by this player
+//-----------------------------------------------------------------------------
+void CPlayerClassDefender::FinishedObject( CBaseObject *pObject )
+{
+ UpdateSentrygunTechnology();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPlayerClassDefender::PlayerDied( CBaseEntity *pAttacker )
+{
+ CWeaponLimpetmine *weapon = (CWeaponLimpetmine*)m_pPlayer->Weapon_OwnsThisType( "weapon_limpetmine" );
+ if ( weapon )
+ {
+ weapon->RemoveDeployedLimpets();
+ }
+
+ BaseClass::PlayerDied( pAttacker );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPlayerClassDefender::SetPlayerHull( void )
+{
+ if ( m_pPlayer->GetFlags() & FL_DUCKING )
+ {
+ m_pPlayer->SetCollisionBounds( DEFENDERCLASS_HULL_DUCK_MIN, DEFENDERCLASS_HULL_DUCK_MAX );
+ }
+ else
+ {
+ m_pPlayer->SetCollisionBounds( DEFENDERCLASS_HULL_STAND_MIN, DEFENDERCLASS_HULL_STAND_MAX );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPlayerClassDefender::GetPlayerHull( bool bDucking, Vector &vecMin, Vector &vecMax )
+{
+ if ( bDucking )
+ {
+ VectorCopy( DEFENDERCLASS_HULL_DUCK_MIN, vecMin );
+ VectorCopy( DEFENDERCLASS_HULL_DUCK_MAX, vecMax );
+ }
+ else
+ {
+ VectorCopy( DEFENDERCLASS_HULL_STAND_MIN, vecMin );
+ VectorCopy( DEFENDERCLASS_HULL_STAND_MAX, vecMax );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPlayerClassDefender::ResetViewOffset( void )
+{
+ if ( m_pPlayer )
+ {
+ m_pPlayer->SetViewOffset( DEFENDERCLASS_VIEWOFFSET_STAND );
+ }
+}
+
+void CPlayerClassDefender::CreatePersonalOrder()
+{
+ if ( CreateInitialOrder() )
+ return;
+
+ if( COrderRepair::CreateOrder_RepairFriendlyObjects( this ) )
+ return;
+
+ // Alternate between sentrygun and sandbag orders.
+ if ( OrderCreator_BuildSentryGun( this ) )
+ {
+ return;
+ }
+
+ BaseClass::CreatePersonalOrder();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPlayerClassDefender::InitVCollision( void )
+{
+ CPhysCollide *pStandModel = PhysCreateBbox( DEFENDERCLASS_HULL_STAND_MIN, DEFENDERCLASS_HULL_STAND_MAX );
+ CPhysCollide *pCrouchModel = PhysCreateBbox( DEFENDERCLASS_HULL_DUCK_MIN, DEFENDERCLASS_HULL_DUCK_MAX );
+ m_pPlayer->SetupVPhysicsShadow( pStandModel, "tfplayer_defender_stand", pCrouchModel, "tfplayer_defender_crouch" );
+}
diff --git a/game/server/tf2/tf_class_defender.h b/game/server/tf2/tf_class_defender.h
new file mode 100644
index 0000000..7493797
--- /dev/null
+++ b/game/server/tf2/tf_class_defender.h
@@ -0,0 +1,77 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Defender player class
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef TF_CLASS_DEFENDER_H
+#define TF_CLASS_DEFENDER_H
+#pragma once
+
+#include "TFClassData_Shared.h"
+
+//=====================================================================
+// Defender
+class CPlayerClassDefender : public CPlayerClass
+{
+public:
+ DECLARE_CLASS( CPlayerClassDefender, CPlayerClass );
+
+ CPlayerClassDefender( CBaseTFPlayer *pPlayer, TFClass iClass );
+ ~CPlayerClassDefender();
+
+ virtual void ClassActivate( void );
+ virtual void ClassDeactivate( void );
+
+ virtual const char* GetClassModelString( int nTeam );
+
+ // Class Initialization
+ virtual void CreateClass( void ); // Create the class upon initial spawn
+ virtual bool ResupplyAmmo( float flPercentage, ResupplyReason_t reason );
+ virtual void SetupMoveData( void ); // Override class specific movement data here.
+ virtual void SetupSizeData( void ); // Override class specific size data here.
+ virtual void ResetViewOffset( void );
+
+ PlayerClassDefenderData_t *GetClassData( void ) { return &m_ClassData; }
+
+ virtual void PlayerDied( CBaseEntity *pAttacker );
+
+ virtual void GainedNewTechnology( CBaseTechnology *pTechnology );
+ virtual void UpdateSentrygunTechnology( void );
+
+ // Sentrygun building
+ virtual int CanBuild( int iObjectType );
+ int CanBuildSentryGun();
+ virtual void FinishedObject( CBaseObject *pObject );
+
+ // Orders.
+ virtual void CreatePersonalOrder();
+
+ // Hooks
+ virtual void SetPlayerHull( void );
+ virtual void GetPlayerHull( bool bDucking, Vector &vecMin, Vector &vecMax );
+
+ // Player physics shadow.
+ void InitVCollision( void );
+
+public:
+ // Sentrygun building
+ int m_iNumberOfSentriesAllowed;
+ bool m_bHasSmarterSentryguns;
+ bool m_bHasSensorSentryguns;
+ // Sentrygun types
+ bool m_bHasMachinegun;
+ bool m_bHasRocketlauncher;
+ bool m_bHasAntiair;
+
+ // Weapons
+ CHandle<CBaseTFCombatWeapon> m_hWpnPlasma;
+
+private:
+ PlayerClassDefenderData_t m_ClassData;
+};
+
+EXTERN_SEND_TABLE( DT_PlayerClassDefenderData )
+
+#endif // TF_CLASS_DEFENDER_H
diff --git a/game/server/tf2/tf_class_escort.cpp b/game/server/tf2/tf_class_escort.cpp
new file mode 100644
index 0000000..de03f59
--- /dev/null
+++ b/game/server/tf2/tf_class_escort.cpp
@@ -0,0 +1,274 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: The Escort Player Class
+//
+// $Workfile: $
+// $Date: $
+// $NoKeywords: $
+//=============================================================================//
+
+#include "cbase.h"
+#include "tf_player.h"
+#include "tf_class_escort.h"
+#include "basecombatweapon.h"
+#include "tf_obj_shieldwall.h"
+#include "weapon_builder.h"
+#include "tf_shareddefs.h"
+#include "tf_team.h"
+#include "orders.h"
+#include "order_buildshieldwall.h"
+#include "order_mortar_attack.h"
+#include "weapon_twohandedcontainer.h"
+#include "tf_shield.h"
+#include "iservervehicle.h"
+#include "weapon_combatshield.h"
+#include "in_buttons.h"
+#include "tf_vehicle_teleport_station.h"
+#include "weapon_shield.h"
+
+
+ConVar class_escort_speed( "class_escort_speed","200", FCVAR_NONE, "Escort movement speed" );
+
+
+//=============================================================================
+//
+// Escort Data Table
+//
+BEGIN_SEND_TABLE_NOBASE( CPlayerClassEscort, DT_PlayerClassEscortData )
+END_SEND_TABLE()
+
+
+
+bool OrderCreator_Mortar( CPlayerClassEscort *pClass )
+{
+ return COrderMortarAttack::CreateOrder( pClass );
+}
+
+
+bool OrderCreator_ShieldWall( CPlayerClassEscort *pClass )
+{
+ return COrderBuildShieldWall::CreateOrder( pClass );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Output : const char
+//-----------------------------------------------------------------------------
+const char *CPlayerClassEscort::GetClassModelString( int nTeam )
+{
+ static const char *string = "models/player/alien_escort.mdl";
+ return string;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CPlayerClassEscort::CPlayerClassEscort( CBaseTFPlayer *pPlayer, TFClass iClass ) : CPlayerClass( pPlayer, iClass )
+{
+ for (int i = 0; i < MAX_TF_TEAMS; ++i)
+ {
+ SetClassModel( MAKE_STRING(GetClassModelString(i)), i );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPlayerClassEscort::SetupSizeData( void )
+{
+ // Initially set the player to the base player class standing hull size.
+ m_pPlayer->SetCollisionBounds( ESCORTCLASS_HULL_STAND_MIN, ESCORTCLASS_HULL_STAND_MAX );
+ m_pPlayer->SetViewOffset( ESCORTCLASS_VIEWOFFSET_STAND );
+ m_pPlayer->m_Local.m_flStepSize = ESCORTCLASS_STEPSIZE;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CPlayerClassEscort::~CPlayerClassEscort()
+{
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPlayerClassEscort::ClassActivate( void )
+{
+ BaseClass::ClassActivate();
+
+ // Setup movement data.
+ m_hWeaponProjectedShield = NULL;
+ SetupMoveData();
+ memset( &m_ClassData, 0, sizeof( m_ClassData ) );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CWeaponShield *CPlayerClassEscort::GetProjectedShield( void )
+{
+ if ( !m_hWeaponProjectedShield )
+ {
+ m_hWeaponProjectedShield = static_cast< CWeaponShield * >( m_pPlayer->Weapon_OwnsThisType( "weapon_shield" ) );
+ if ( !m_hWeaponProjectedShield )
+ {
+ m_hWeaponProjectedShield = static_cast< CWeaponShield * >( m_pPlayer->GiveNamedItem( "weapon_shield" ) );
+ }
+ }
+
+ if ( m_hWeaponProjectedShield )
+ {
+ m_hWeaponProjectedShield->SetReflectViewModelAnimations( true );
+ }
+
+ return m_hWeaponProjectedShield;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPlayerClassEscort::CreateClass( void )
+{
+ BaseClass::CreateClass();
+
+ CWeaponTwoHandedContainer *p = ( CWeaponTwoHandedContainer * )m_pPlayer->Weapon_OwnsThisType( "weapon_twohandedcontainer" );
+ if ( !p )
+ {
+ p = static_cast< CWeaponTwoHandedContainer * >( m_pPlayer->GiveNamedItem( "weapon_twohandedcontainer" ) );
+ }
+
+ CWeaponShield *pShield = GetProjectedShield();
+ if ( p && pShield )
+ {
+ p->SetWeapons( NULL, pShield );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CPlayerClassEscort::ResupplyAmmo( float flFraction, ResupplyReason_t reason )
+{
+ bool bGiven = false;
+ if ((reason == RESUPPLY_ALL_FROM_STATION) || (reason == RESUPPLY_AMMO_FROM_STATION))
+ {
+ if ( ResupplyAmmoType( 200 * flFraction, "Bullets" ) )
+ bGiven = true;
+ }
+
+ if ((reason == RESUPPLY_ALL_FROM_STATION) || (reason == RESUPPLY_GRENADES_FROM_STATION))
+ {
+ if (ResupplyAmmoType( 5 * flFraction, "ShieldGrenades" ))
+ bGiven = true;
+ }
+
+ // On respawn, resupply base weapon ammo
+ if ( reason == RESUPPLY_RESPAWN )
+ {
+ if ( ResupplyAmmoType( 200, "Bullets" ) )
+ bGiven = true;
+ }
+
+ if ( BaseClass::ResupplyAmmo(flFraction, reason) )
+ bGiven = true;
+
+ return bGiven;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Set escort class specific movement data here.
+//-----------------------------------------------------------------------------
+void CPlayerClassEscort::SetupMoveData( void )
+{
+ // Setup Class statistics
+ m_flMaxWalkingSpeed = class_escort_speed.GetFloat();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPlayerClassEscort::SetPlayerHull( void )
+{
+ if ( m_pPlayer->GetFlags() & FL_DUCKING )
+ {
+ m_pPlayer->SetCollisionBounds( ESCORTCLASS_HULL_DUCK_MIN, ESCORTCLASS_HULL_DUCK_MAX );
+ }
+ else
+ {
+ m_pPlayer->SetCollisionBounds( ESCORTCLASS_HULL_STAND_MIN, ESCORTCLASS_HULL_STAND_MAX );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPlayerClassEscort::ResetViewOffset( void )
+{
+ if ( m_pPlayer )
+ {
+ m_pPlayer->SetViewOffset( ESCORTCLASS_VIEWOFFSET_STAND );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPlayerClassEscort::PowerupStart( int iPowerup, float flAmount, CBaseEntity *pAttacker, CDamageModifier *pDamageModifier )
+{
+ /*
+ switch( iPowerup )
+ {
+ case POWERUP_BOOST:
+ // Increase our shield's energy
+ if ( m_hDeployedShield )
+ {
+ m_hDeployedShield->SetPower( m_hDeployedShield->GetPower() + (flAmount * 10) );
+ }
+ break;
+
+ case POWERUP_EMP:
+ if ( m_hDeployedShield )
+ {
+ m_hDeployedShield->SetEMPed( true );
+ }
+ break;
+
+ default:
+ break;
+ }
+ */
+
+ BaseClass::PowerupStart( iPowerup, flAmount, pAttacker, pDamageModifier );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Powerup has just started
+//-----------------------------------------------------------------------------
+void CPlayerClassEscort::PowerupEnd( int iPowerup )
+{
+ /*
+ switch( iPowerup )
+ {
+ case POWERUP_EMP:
+ if ( m_hDeployedShield )
+ {
+ m_hDeployedShield->SetEMPed( false );
+ }
+ break;
+
+ default:
+ break;
+ }
+ */
+
+ BaseClass::PowerupEnd( iPowerup );
+}
diff --git a/game/server/tf2/tf_class_escort.h b/game/server/tf2/tf_class_escort.h
new file mode 100644
index 0000000..414b578
--- /dev/null
+++ b/game/server/tf2/tf_class_escort.h
@@ -0,0 +1,64 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef TF_CLASS_ESCORT_H
+#define TF_CLASS_ESCORT_H
+
+#ifdef _WIN32
+#pragma once
+#endif
+
+
+#include "tf_playerclass.h"
+#include "TFClassData_Shared.h"
+#include "tf_shieldshared.h"
+
+class CShield;
+class CWeaponShield;
+
+//=====================================================================
+// Indirect
+class CPlayerClassEscort : public CPlayerClass
+{
+public:
+ DECLARE_CLASS( CPlayerClassEscort, CPlayerClass );
+
+ CPlayerClassEscort( CBaseTFPlayer *pPlayer, TFClass iClass );
+ ~CPlayerClassEscort();
+
+ virtual void ClassActivate( void );
+
+ virtual const char* GetClassModelString( int nTeam );
+
+ // Class Initialization
+ virtual void CreateClass( void ); // Create the class upon initial spawn
+ virtual bool ResupplyAmmo( float flPercentage, ResupplyReason_t reason );
+ virtual void SetupMoveData( void ); // Override class specific movement data here.
+ virtual void SetupSizeData( void ); // Override class specific size data here.
+ virtual void ResetViewOffset( void );
+
+ PlayerClassEscortData_t *GetClassData( void ) { return &m_ClassData; }
+
+ // Hooks
+ virtual void SetPlayerHull( void );
+
+ // Powerups
+ virtual void PowerupStart( int iPowerup, float flAmount, CBaseEntity *pAttacker, CDamageModifier *pDamageModifier );
+ virtual void PowerupEnd( int iPowerup );
+
+private:
+ // Purpose:
+ CWeaponShield *GetProjectedShield( void );
+
+protected:
+ PlayerClassEscortData_t m_ClassData;
+ CHandle<CWeaponShield> m_hWeaponProjectedShield;
+};
+
+EXTERN_SEND_TABLE( DT_PlayerClassEscortData )
+
+#endif // TF_CLASS_ESCORT_H
diff --git a/game/server/tf2/tf_class_infiltrator.cpp b/game/server/tf2/tf_class_infiltrator.cpp
new file mode 100644
index 0000000..bc59b18
--- /dev/null
+++ b/game/server/tf2/tf_class_infiltrator.cpp
@@ -0,0 +1,275 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: The Infiltrator Player Class
+//
+// $Workfile: $
+// $Date: $
+// $NoKeywords: $
+//=============================================================================//
+#include "cbase.h"
+#include "orders.h"
+#include "tf_player.h"
+#include "tf_class_infiltrator.h"
+#include "EntityList.h"
+#include "basecombatweapon.h"
+#include "menu_base.h"
+#include "tf_obj.h"
+#include "tf_team.h"
+#include "weapon_builder.h"
+
+ConVar class_infiltrator_speed( "class_infiltrator_speed","200", FCVAR_NONE, "Infiltrator movement speed" );
+
+//=============================================================================
+//
+// Infiltrator Data Table
+//
+BEGIN_SEND_TABLE_NOBASE( CPlayerClassInfiltrator, DT_PlayerClassInfiltratorData )
+END_SEND_TABLE()
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Output : const char
+//-----------------------------------------------------------------------------
+const char *CPlayerClassInfiltrator::GetClassModelString( int nTeam )
+{
+ static const char *string = "models/player/spy.mdl";
+ return string;
+}
+
+// Infiltrator
+CPlayerClassInfiltrator::CPlayerClassInfiltrator( CBaseTFPlayer *pPlayer, TFClass iClass ) : CPlayerClass( pPlayer, iClass )
+{
+ for (int i = 0; i < MAX_TF_TEAMS; ++i)
+ {
+ SetClassModel( MAKE_STRING(GetClassModelString(i)), i );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CPlayerClassInfiltrator::~CPlayerClassInfiltrator()
+{
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPlayerClassInfiltrator::ClassActivate( void )
+{
+ BaseClass::ClassActivate();
+
+ // Setup movement data.
+ SetupMoveData();
+
+ //m_hAssassinationWeapon = NULL;
+ m_hSwappedWeapon = NULL;
+
+ m_bCanConsumeCorpses = false;
+ m_flStartCamoAt = 0.0f;
+
+ memset( &m_ClassData, 0, sizeof( m_ClassData ) );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Register for precaching.
+//-----------------------------------------------------------------------------
+void PrecacheInfiltrator(void *pUser)
+{
+}
+PRECACHE_REGISTER_FN(PrecacheInfiltrator);
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPlayerClassInfiltrator::RespawnClass( void )
+{
+ BaseClass::RespawnClass();
+
+ m_flStartCamoAt = gpGlobals->curtime + INFILTRATOR_CAMOTIME_AFTER_SPAWN;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CPlayerClassInfiltrator::ResupplyAmmo( float flFraction, ResupplyReason_t reason )
+{
+ bool bGiven = false;
+ if ((reason == RESUPPLY_ALL_FROM_STATION) || (reason == RESUPPLY_AMMO_FROM_STATION))
+ {
+ if (ResupplyAmmoType( 200 * flFraction, "Bullets" ))
+ bGiven = true;
+ if (ResupplyAmmoType( 20 * flFraction, "Limpets" ))
+ bGiven = true;
+ }
+
+ if ( BaseClass::ResupplyAmmo(flFraction, reason) )
+ bGiven = true;
+ return bGiven;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Set infiltrator class specific movement data here.
+//-----------------------------------------------------------------------------
+void CPlayerClassInfiltrator::SetupMoveData( void )
+{
+ // Setup Class statistics
+ m_flMaxWalkingSpeed = class_infiltrator_speed.GetFloat();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPlayerClassInfiltrator::SetupSizeData( void )
+{
+ // Initially set the player to the base player class standing hull size.
+ m_pPlayer->SetCollisionBounds( INFILTRATORCLASS_HULL_STAND_MIN, INFILTRATORCLASS_HULL_STAND_MAX );
+ m_pPlayer->SetViewOffset( INFILTRATORCLASS_VIEWOFFSET_STAND );
+ m_pPlayer->m_Local.m_flStepSize = INFILTRATORCLASS_STEPSIZE;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: New technology has been gained. Recalculate any class specific technology dependencies.
+//-----------------------------------------------------------------------------
+void CPlayerClassInfiltrator::GainedNewTechnology( CBaseTechnology *pTechnology )
+{
+ // Consume corpse technology?
+ m_bCanConsumeCorpses = m_pPlayer->HasNamedTechnology( "inf_consume_corpse" );
+
+ BaseClass::GainedNewTechnology( pTechnology );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Return true if this player's allowed to build another one of the specified objects
+//-----------------------------------------------------------------------------
+int CPlayerClassInfiltrator::CanBuild( int iObjectType )
+{
+ return BaseClass::CanBuild( iObjectType );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Called every frame by postthink
+//-----------------------------------------------------------------------------
+void CPlayerClassInfiltrator::ClassThink( void )
+{
+ BaseClass::ClassThink();
+
+ CheckForAssassination();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: The player's just had his camo cleared
+//-----------------------------------------------------------------------------
+void CPlayerClassInfiltrator::ClearCamouflage( void )
+{
+ float flNewTime = gpGlobals->curtime + INFILTRATOR_RECAMO_TIME;
+ if ( flNewTime > m_flStartCamoAt )
+ {
+ m_flStartCamoAt = flNewTime;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Player's finished disguising.
+//-----------------------------------------------------------------------------
+void CPlayerClassInfiltrator::FinishedDisguising( void )
+{
+ // Remove my camo
+ m_pPlayer->ClearCamouflage();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Player's lost his disguise.
+//-----------------------------------------------------------------------------
+void CPlayerClassInfiltrator::StopDisguising( void )
+{
+ // Remove & restart my camo
+ m_pPlayer->ClearCamouflage();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Handle custom commands for this playerclass
+//-----------------------------------------------------------------------------
+bool CPlayerClassInfiltrator::ClientCommand( const char *pcmd )
+{
+ // Toggle thermal vision
+ if ( FStrEq( pcmd, "special" ) )
+ {
+ Assert( m_pPlayer );
+ // Toggle
+ m_pPlayer->SetUsingThermalVision( !m_pPlayer->IsUsingThermalVision() );
+ return true;
+ }
+
+ return BaseClass::ClientCommand( pcmd );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Check to see if I can assassinate anyone
+//-----------------------------------------------------------------------------
+void CPlayerClassInfiltrator::CheckForAssassination( void )
+{
+ /*
+ // Find my assassination weapon if I haven't already
+ if ( m_hAssassinationWeapon == NULL )
+ {
+ m_hAssassinationWeapon = (CWeaponInfiltrator*)m_pPlayer->Weapon_OwnsThisType( "weapon_infiltrator" );
+ }
+
+ // No Assasination weapon?
+ if ( m_hAssassinationWeapon == NULL )
+ return;
+
+ // If we have a target, bring the assassination weapon up.
+ if ( m_hAssassinationWeapon->GetAssassinationTarget() )
+ {
+ if ( m_pPlayer->GetActiveWeapon() != m_hAssassinationWeapon.Get() )
+ {
+ m_hSwappedWeapon = m_pPlayer->GetActiveWeapon();
+ m_pPlayer->Weapon_Switch( m_hAssassinationWeapon );
+ }
+ }
+ else
+ {
+ // We have no target. If the assassination weapon's up, put it away.
+ if ( m_pPlayer->GetActiveWeapon() == m_hAssassinationWeapon.Get() )
+ {
+ // Don't switch until the weapon's finished killing people
+ if ( m_pPlayer->GetActiveWeapon()->m_flNextPrimaryAttack <= gpGlobals->curtime )
+ {
+ m_pPlayer->Weapon_Switch( m_hSwappedWeapon );
+ m_hSwappedWeapon = NULL;
+ }
+ }
+ }
+ */
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPlayerClassInfiltrator::SetPlayerHull( void )
+{
+ if ( m_pPlayer->GetFlags() & FL_DUCKING )
+ {
+ m_pPlayer->SetCollisionBounds( INFILTRATORCLASS_HULL_DUCK_MIN, INFILTRATORCLASS_HULL_DUCK_MAX );
+ }
+ else
+ {
+ m_pPlayer->SetCollisionBounds( INFILTRATORCLASS_HULL_STAND_MIN, INFILTRATORCLASS_HULL_STAND_MAX );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPlayerClassInfiltrator::ResetViewOffset( void )
+{
+ if ( m_pPlayer )
+ {
+ m_pPlayer->SetViewOffset( INFILTRATORCLASS_VIEWOFFSET_STAND );
+ }
+}
diff --git a/game/server/tf2/tf_class_infiltrator.h b/game/server/tf2/tf_class_infiltrator.h
new file mode 100644
index 0000000..f026bee
--- /dev/null
+++ b/game/server/tf2/tf_class_infiltrator.h
@@ -0,0 +1,78 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Infiltrator
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef TF_CLASS_INFILTRATOR_H
+#define TF_CLASS_INFILTRATOR_H
+
+#ifdef _WIN32
+#pragma once
+#endif
+
+#define INFILTRATOR_EAVESDROP_RADIUS 256.0f
+#define INFILTRATOR_DISGUISE_TIME 3.0f
+
+// Time after losing camo before the infiltrator can re-camo
+#define INFILTRATOR_RECAMO_TIME 5.0
+// Time after spawning that the infilitrator's camo kicks in
+#define INFILTRATOR_CAMOTIME_AFTER_SPAWN 3.0
+
+#include "TFClassData_Shared.h"
+
+class CLootableCorpse;
+
+//=====================================================================
+// Infiltrator
+class CPlayerClassInfiltrator : public CPlayerClass
+{
+ DECLARE_CLASS( CPlayerClassInfiltrator, CPlayerClass );
+public:
+ CPlayerClassInfiltrator( CBaseTFPlayer *pPlayer, TFClass iClass );
+ ~CPlayerClassInfiltrator();
+
+ virtual void ClassActivate( void );
+
+ virtual const char* GetClassModelString( int nTeam );
+
+ // Class Initialization
+ virtual void RespawnClass( void ); // Called upon all respawns
+ virtual bool ResupplyAmmo( float flPercentage, ResupplyReason_t reason );
+ virtual void SetupMoveData( void ); // Override class specific movement data here.
+ virtual void SetupSizeData( void ); // Override class specific size data here.
+ virtual void ResetViewOffset( void );
+
+ PlayerClassInfiltratorData_t *GetClassData( void ) { return &m_ClassData; }
+
+ virtual void ClassThink( void );
+ virtual void ClearCamouflage( void );
+
+ virtual int CanBuild( int iObjectType );
+ virtual bool ClientCommand( const CCommand &args );
+ virtual void GainedNewTechnology( CBaseTechnology *pTechnology );
+
+ void CheckForAssassination( void );
+
+ // Disguise
+ virtual void FinishedDisguising( void );
+ virtual void StopDisguising( void );
+
+ // Hooks
+ virtual void SetPlayerHull( void );
+
+protected:
+ bool m_bCanConsumeCorpses;
+ float m_flStartCamoAt;
+
+ // Assassination weapon
+ //CHandle<CWeaponInfiltrator> m_hAssassinationWeapon;
+ CHandle<CBaseCombatWeapon> m_hSwappedWeapon;
+
+ PlayerClassInfiltratorData_t m_ClassData;
+};
+
+EXTERN_SEND_TABLE( DT_PlayerClassInfiltratorData )
+
+#endif // TF_CLASS_INFILTRATOR_H
diff --git a/game/server/tf2/tf_class_medic.cpp b/game/server/tf2/tf_class_medic.cpp
new file mode 100644
index 0000000..be2a094
--- /dev/null
+++ b/game/server/tf2/tf_class_medic.cpp
@@ -0,0 +1,305 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: The Medic Player Class
+//
+// $Workfile: $
+// $Date: $
+// $NoKeywords: $
+//=============================================================================//
+#include "cbase.h"
+#include "basecombatweapon.h"
+#include "tf_player.h"
+#include "tf_class_medic.h"
+#include "tf_obj.h"
+#include "tf_obj_resupply.h"
+#include "tf_obj_resourcepump.h"
+#include "weapon_builder.h"
+#include "tf_team.h"
+#include "orders.h"
+#include "entitylist.h"
+#include "tf_func_resource.h"
+#include "order_resupply.h"
+#include "order_resourcepump.h"
+#include "order_heal.h"
+#include "weapon_twohandedcontainer.h"
+#include "weapon_combatshield.h"
+
+// Radius buff defines
+#define RADIUS_BUFF_RANGE 512.0
+
+#define MEDIC_HEAL_TIME_AFTER_DAMAGE 10.0
+
+// Medic
+ConVar class_medic_speed( "class_medic_speed","200", FCVAR_NONE, "Medic movement speed" );
+
+//=============================================================================
+//
+// Medic Data Table
+//
+BEGIN_SEND_TABLE_NOBASE( CPlayerClassMedic, DT_PlayerClassMedicData )
+END_SEND_TABLE()
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Output : const char
+//-----------------------------------------------------------------------------
+const char *CPlayerClassMedic::GetClassModelString( int nTeam )
+{
+ if (nTeam == TEAM_HUMANS)
+ return "models/player/human_medic.mdl";
+ else
+ return "models/player/recon.mdl";
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CPlayerClassMedic::CPlayerClassMedic( CBaseTFPlayer *pPlayer, TFClass iClass ) : CPlayerClass( pPlayer, iClass )
+{
+ for (int i = 0; i < MAX_TF_TEAMS; ++i)
+ {
+ SetClassModel( MAKE_STRING(GetClassModelString(i)), i );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPlayerClassMedic::SetupSizeData( void )
+{
+ // Initially set the player to the base player class standing hull size.
+ m_pPlayer->SetCollisionBounds( MEDICCLASS_HULL_STAND_MIN, MEDICCLASS_HULL_STAND_MAX );
+ m_pPlayer->SetViewOffset( MEDICCLASS_VIEWOFFSET_STAND );
+ m_pPlayer->m_Local.m_flStepSize = MEDICCLASS_STEPSIZE;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CPlayerClassMedic::~CPlayerClassMedic()
+{
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPlayerClassMedic::ClassActivate( void )
+{
+ BaseClass::ClassActivate();
+
+ // Setup movement data.
+ SetupMoveData();
+
+ m_hWpnShield = NULL;
+ m_hWpnPlasma = NULL;
+
+ m_bHasAutoRepair = false;
+
+ m_flNextHealTime = 0;
+
+ memset( &m_ClassData, 0, sizeof( m_ClassData ) );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPlayerClassMedic::ClassDeactivate( void )
+{
+ BaseClass::ClassDeactivate();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPlayerClassMedic::CreateClass( void )
+{
+ BaseClass::CreateClass();
+
+ // Create our two handed weapon layout
+ m_hWpnPlasma = static_cast< CBaseTFCombatWeapon * >( m_pPlayer->GiveNamedItem( "weapon_combat_burstrifle" ) );
+ m_hWpnShield = m_pPlayer->GetCombatShield();
+ CWeaponTwoHandedContainer *p = ( CWeaponTwoHandedContainer * )m_pPlayer->Weapon_OwnsThisType( "weapon_twohandedcontainer" );
+ if ( !p )
+ {
+ p = static_cast< CWeaponTwoHandedContainer * >( m_pPlayer->GiveNamedItem( "weapon_twohandedcontainer" ) );
+ }
+ if ( p && m_hWpnShield.Get() && m_hWpnPlasma.Get() )
+ {
+ m_hWpnShield->SetReflectViewModelAnimations( true );
+ p->SetWeapons( m_hWpnPlasma, m_hWpnShield );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPlayerClassMedic::RespawnClass( void )
+{
+ BaseClass::RespawnClass();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CPlayerClassMedic::ResupplyAmmo( float flFraction, ResupplyReason_t reason )
+{
+ bool bGiven = false;
+
+ if ((reason == RESUPPLY_ALL_FROM_STATION) || (reason == RESUPPLY_GRENADES_FROM_STATION))
+ {
+ if (ResupplyAmmoType( 3 * flFraction, "Grenades" ))
+ bGiven = true;
+ }
+
+ if ((reason == RESUPPLY_ALL_FROM_STATION) || (reason == RESUPPLY_AMMO_FROM_STATION))
+ {
+ }
+
+ if ( reason == RESUPPLY_RESPAWN )
+ {
+ }
+
+ if ( BaseClass::ResupplyAmmo(flFraction, reason) )
+ bGiven = true;
+ return bGiven;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Set medic class specific movement data here.
+//-----------------------------------------------------------------------------
+void CPlayerClassMedic::SetupMoveData( void )
+{
+ // Setup Class statistics
+ m_flMaxWalkingSpeed = class_medic_speed.GetFloat();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Called every frame by postthink
+//-----------------------------------------------------------------------------
+void CPlayerClassMedic::ClassThink( void )
+{
+ // Heal me if it's time (and I'm not dead!)
+ if ( m_pPlayer->IsAlive() && m_flNextHealTime && m_flNextHealTime < gpGlobals->curtime )
+ {
+ if ( m_pPlayer->GetHealth() < m_pPlayer->GetMaxHealth() )
+ {
+ // Heal the player
+ m_pPlayer->TakeHealth( 1.0, DMG_GENERIC );
+ m_flNextHealTime = gpGlobals->curtime + 0.1;
+ }
+ else
+ {
+ m_flNextHealTime = 0;
+ }
+ }
+
+ BaseClass::ClassThink();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Return true if this player's allowed to build another one of the specified objects
+//-----------------------------------------------------------------------------
+int CPlayerClassMedic::CanBuild( int iObjectType )
+{
+ return BaseClass::CanBuild( iObjectType );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Update abilities based on new technologies gained
+//-----------------------------------------------------------------------------
+void CPlayerClassMedic::GainedNewTechnology( CBaseTechnology *pTechnology )
+{
+ // Autorepair
+ m_bHasAutoRepair = m_pPlayer->HasNamedTechnology( "obj_autorepair" );
+
+ BaseClass::GainedNewTechnology( pTechnology );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Create a personal order for this player
+//-----------------------------------------------------------------------------
+void CPlayerClassMedic::CreatePersonalOrder( void )
+{
+ if ( CreateInitialOrder() )
+ return;
+
+ // Check for healing orders.
+ if ( COrderHeal::CreateOrder( this ) )
+ return;
+
+ // Should they build a resource pump?
+ if ( COrderResourcePump::CreateOrder( this ) )
+ return;
+
+ // Build a resupply station?
+ if ( COrderResupply::CreateOrder( this ) )
+ return;
+
+ BaseClass::CreatePersonalOrder();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPlayerClassMedic::SetPlayerHull( void )
+{
+ if ( m_pPlayer->GetFlags() & FL_DUCKING )
+ {
+ m_pPlayer->SetCollisionBounds( MEDICCLASS_HULL_DUCK_MIN, MEDICCLASS_HULL_DUCK_MAX );
+ }
+ else
+ {
+ m_pPlayer->SetCollisionBounds( MEDICCLASS_HULL_STAND_MIN, MEDICCLASS_HULL_STAND_MAX );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPlayerClassMedic::GetPlayerHull( bool bDucking, Vector &vecMin, Vector &vecMax )
+{
+ if ( bDucking )
+ {
+ VectorCopy( MEDICCLASS_HULL_DUCK_MIN, vecMin );
+ VectorCopy( MEDICCLASS_HULL_DUCK_MAX, vecMax );
+ }
+ else
+ {
+ VectorCopy( MEDICCLASS_HULL_STAND_MIN, vecMin );
+ VectorCopy( MEDICCLASS_HULL_STAND_MAX, vecMax );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPlayerClassMedic::ResetViewOffset( void )
+{
+ if ( m_pPlayer )
+ {
+ m_pPlayer->SetViewOffset( MEDICCLASS_VIEWOFFSET_STAND );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: The player has taken damage. Return the damage done.
+//-----------------------------------------------------------------------------
+float CPlayerClassMedic::OnTakeDamage( const CTakeDamageInfo &info )
+{
+ // Stop healing when taking damage
+ m_flNextHealTime = gpGlobals->curtime + MEDIC_HEAL_TIME_AFTER_DAMAGE;
+
+ return BaseClass::OnTakeDamage( info );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPlayerClassMedic::InitVCollision( void )
+{
+ CPhysCollide *pStandModel = PhysCreateBbox( MEDICCLASS_HULL_STAND_MIN, MEDICCLASS_HULL_STAND_MAX );
+ CPhysCollide *pCrouchModel = PhysCreateBbox( MEDICCLASS_HULL_DUCK_MIN, MEDICCLASS_HULL_DUCK_MAX );
+ m_pPlayer->SetupVPhysicsShadow( pStandModel, "tfplayer_medic_stand", pCrouchModel, "tfplayer_medic_crouch" );
+}
diff --git a/game/server/tf2/tf_class_medic.h b/game/server/tf2/tf_class_medic.h
new file mode 100644
index 0000000..714575e
--- /dev/null
+++ b/game/server/tf2/tf_class_medic.h
@@ -0,0 +1,73 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Medic playerclass
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef TF_CLASS_MEDIC_H
+#define TF_CLASS_MEDIC_H
+
+#ifdef _WIN32
+#pragma once
+#endif
+
+
+#include "TFClassData_Shared.h"
+
+//=====================================================================
+// Medic
+class CPlayerClassMedic : public CPlayerClass
+{
+ DECLARE_CLASS( CPlayerClassMedic, CPlayerClass );
+public:
+ CPlayerClassMedic( CBaseTFPlayer *pPlayer, TFClass iClass );
+ virtual ~CPlayerClassMedic();
+
+ virtual void ClassActivate( void );
+ virtual void ClassDeactivate( void );
+
+ virtual const char* GetClassModelString( int nTeam );
+
+ // Class Initialization
+ virtual void CreateClass( void ); // Create the class upon initial spawn
+ virtual void RespawnClass( void ); // Called upon all respawns
+ virtual bool ResupplyAmmo( float flPercentage, ResupplyReason_t reason );
+ virtual void SetupMoveData( void ); // Override class specific movement data here.
+ virtual void SetupSizeData( void ); // Override class specific size data here.
+ virtual void ResetViewOffset( void );
+
+ PlayerClassMedicData_t *GetClassData( void ) { return &m_ClassData; }
+
+ virtual void ClassThink( void );
+ virtual void GainedNewTechnology( CBaseTechnology *pTechnology );
+
+ // Objects
+ int CanBuild( int iObjectType );
+
+ // Orders
+ virtual void CreatePersonalOrder( void );
+
+ // Hooks
+ virtual void SetPlayerHull( void );
+ virtual void GetPlayerHull( bool bDucking, Vector &vecMin, Vector &vecMax );
+
+ virtual float OnTakeDamage( const CTakeDamageInfo &info );
+
+ // Player physics shadow.
+ void InitVCollision( void );
+
+protected:
+
+ // Weapons
+ CHandle<CBaseTFCombatWeapon> m_hWpnPlasma;
+
+ bool m_bHasAutoRepair;
+ float m_flNextHealTime;
+
+ PlayerClassMedicData_t m_ClassData;
+};
+
+EXTERN_SEND_TABLE( DT_PlayerClassMedicData )
+
+#endif // TF_CLASS_MEDIC_H
diff --git a/game/server/tf2/tf_class_pyro.cpp b/game/server/tf2/tf_class_pyro.cpp
new file mode 100644
index 0000000..a3dd5fd
--- /dev/null
+++ b/game/server/tf2/tf_class_pyro.cpp
@@ -0,0 +1,164 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#include "cbase.h"
+#include "basecombatweapon.h"
+#include "tf_player.h"
+#include "tf_class_pyro.h"
+#include "weapon_twohandedcontainer.h"
+#include "weapon_gas_can.h"
+#include "weapon_flame_thrower.h"
+#include "gasoline_shared.h"
+#include "weapon_combatshield.h"
+
+BEGIN_SEND_TABLE_NOBASE( CPlayerClassPyro, DT_PlayerClassPyroData )
+END_SEND_TABLE()
+
+
+CPlayerClassPyro::CPlayerClassPyro( CBaseTFPlayer *pPlayer, TFClass iClass ) : CPlayerClass( pPlayer, iClass )
+{
+ for (int i = 0; i < MAX_TF_TEAMS; ++i)
+ {
+ SetClassModel( MAKE_STRING(GetClassModelString(i)), i );
+ }
+}
+
+
+CPlayerClassPyro::~CPlayerClassPyro()
+{
+}
+
+
+void CPlayerClassPyro::SetupSizeData()
+{
+ // Initially set the player to the base player class standing hull size.
+ m_pPlayer->SetCollisionBounds( PYROCLASS_HULL_STAND_MIN, PYROCLASS_HULL_STAND_MAX );
+ m_pPlayer->SetViewOffset( PYROCLASS_VIEWOFFSET_STAND );
+ m_pPlayer->m_Local.m_flStepSize = PYROCLASS_STEPSIZE;
+}
+
+
+void CPlayerClassPyro::ClassActivate()
+{
+ BaseClass::ClassActivate();
+
+ // Setup movement data.
+ SetupMoveData();
+
+ memset( &m_ClassData, 0, sizeof( m_ClassData ) );
+}
+
+
+const char *CPlayerClassPyro::GetClassModelString( int nTeam )
+{
+ if (nTeam == TEAM_HUMANS)
+ return "models/player/medic.mdl";
+ else
+ return "models/player/recon.mdl";
+}
+
+
+void CPlayerClassPyro::CreateClass()
+{
+ BaseClass::CreateClass();
+
+ // Create our two handed weapon layout
+ m_hWpnFlameThrower = static_cast< CWeaponFlameThrower * >( m_pPlayer->GiveNamedItem( "weapon_flame_thrower" ) );
+ m_hWpnGasCan = static_cast< CWeaponGasCan * >( m_pPlayer->GiveNamedItem( "weapon_gas_can" ) );
+ m_hWpnShield = m_pPlayer->GetCombatShield();
+ CWeaponTwoHandedContainer *p = ( CWeaponTwoHandedContainer * )m_pPlayer->Weapon_OwnsThisType( "weapon_twohandedcontainer" );
+ if ( !p )
+ {
+ p = static_cast< CWeaponTwoHandedContainer * >( m_pPlayer->GiveNamedItem( "weapon_twohandedcontainer" ) );
+ }
+ if ( p && m_hWpnShield.Get() && m_hWpnGasCan.Get() )
+ {
+ m_hWpnShield->SetReflectViewModelAnimations( true );
+ p->SetWeapons( m_hWpnGasCan, m_hWpnShield );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPlayerClassPyro::SetPlayerHull()
+{
+ if ( m_pPlayer->GetFlags() & FL_DUCKING )
+ {
+ m_pPlayer->SetCollisionBounds( PYROCLASS_HULL_DUCK_MIN, PYROCLASS_HULL_DUCK_MAX );
+ }
+ else
+ {
+ m_pPlayer->SetCollisionBounds( PYROCLASS_HULL_STAND_MIN, PYROCLASS_HULL_STAND_MAX );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPlayerClassPyro::GetPlayerHull( bool bDucking, Vector &vecMin, Vector &vecMax )
+{
+ if ( bDucking )
+ {
+ VectorCopy( PYROCLASS_HULL_DUCK_MIN, vecMin );
+ VectorCopy( PYROCLASS_HULL_DUCK_MAX, vecMax );
+ }
+ else
+ {
+ VectorCopy( PYROCLASS_HULL_STAND_MIN, vecMin );
+ VectorCopy( PYROCLASS_HULL_STAND_MAX, vecMax );
+ }
+}
+
+
+void CPlayerClassPyro::ResetViewOffset()
+{
+ if ( m_pPlayer )
+ {
+ m_pPlayer->SetViewOffset( PYROCLASS_VIEWOFFSET_STAND );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPlayerClassPyro::InitVCollision()
+{
+ CPhysCollide *pStandModel = PhysCreateBbox( PYROCLASS_HULL_STAND_MIN, PYROCLASS_HULL_STAND_MAX );
+ CPhysCollide *pCrouchModel = PhysCreateBbox( PYROCLASS_HULL_DUCK_MIN, PYROCLASS_HULL_DUCK_MAX );
+ m_pPlayer->SetupVPhysicsShadow( pStandModel, "tfplayer_medic_stand", pCrouchModel, "tfplayer_medic_crouch" );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CPlayerClassPyro::ResupplyAmmo( float flFraction, ResupplyReason_t reason )
+{
+ bool bGiven = false;
+
+ if ((reason == RESUPPLY_ALL_FROM_STATION) || (reason == RESUPPLY_AMMO_FROM_STATION))
+ {
+ if (ResupplyAmmoType( 80 * flFraction, PYRO_AMMO_TYPE ))
+ bGiven = true;
+ }
+
+ // On respawn, resupply base weapon ammo
+ if ( reason == RESUPPLY_RESPAWN )
+ {
+ if ( ResupplyAmmoType( 30, PYRO_AMMO_TYPE ) )
+ bGiven = true;
+ }
+
+ if ( BaseClass::ResupplyAmmo(flFraction, reason) )
+ bGiven = true;
+
+ return bGiven;
+}
+
diff --git a/game/server/tf2/tf_class_pyro.h b/game/server/tf2/tf_class_pyro.h
new file mode 100644
index 0000000..600b768
--- /dev/null
+++ b/game/server/tf2/tf_class_pyro.h
@@ -0,0 +1,57 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef TF_CLASS_PYRO_H
+#define TF_CLASS_PYRO_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+
+#include "TFClassData_Shared.h"
+
+
+class CWeaponFlameThrower;
+class CWeaponGasCan;
+
+
+//=====================================================================
+// Medic
+class CPlayerClassPyro : public CPlayerClass
+{
+ DECLARE_CLASS( CPlayerClassPyro, CPlayerClass );
+public:
+ CPlayerClassPyro( CBaseTFPlayer *pPlayer, TFClass iClass );
+ virtual ~CPlayerClassPyro();
+
+
+// Overrides.
+public:
+
+ virtual void SetupSizeData();
+ virtual void CreateClass();
+ virtual void SetPlayerHull();
+ virtual void GetPlayerHull( bool bDucking, Vector &vecMin, Vector &vecMax );
+ virtual void ResetViewOffset();
+ virtual void InitVCollision();
+ virtual bool ResupplyAmmo( float flFraction, ResupplyReason_t reason );
+ virtual void ClassActivate();
+
+ virtual const char* GetClassModelString( int nTeam );
+
+
+private:
+
+ PlayerClassPyroData_t m_ClassData;
+
+ CHandle<CWeaponFlameThrower> m_hWpnFlameThrower;
+ CHandle<CWeaponGasCan> m_hWpnGasCan;
+};
+
+
+
+#endif // TF_CLASS_PYRO_H
diff --git a/game/server/tf2/tf_class_recon.cpp b/game/server/tf2/tf_class_recon.cpp
new file mode 100644
index 0000000..c5bc1fe
--- /dev/null
+++ b/game/server/tf2/tf_class_recon.cpp
@@ -0,0 +1,221 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: The Recon Player Class
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#include "cbase.h"
+#include "tf_player.h"
+#include "tf_class_recon.h"
+#include "tf_reconvars.h"
+#include "in_buttons.h"
+#include "basecombatweapon.h"
+#include "tf_obj.h"
+#include "weapon_builder.h"
+#include "tf_team.h"
+#include "tf_func_resource.h"
+#include "orders.h"
+#include "engine/IEngineSound.h"
+
+
+extern short g_sModelIndexFireball;
+
+ConVar class_recon_speed( "class_recon_speed","250", FCVAR_NONE, "Recon movement speed" );
+
+// ------------------------------------------------------------------------ //
+// Globals.
+// ------------------------------------------------------------------------ //
+
+//=============================================================================
+//
+// Recon Data Table
+//
+BEGIN_SEND_TABLE_NOBASE( CPlayerClassRecon, DT_PlayerClassReconData )
+ SendPropInt ( SENDINFO_STRUCTELEM( m_ClassData.m_nJumpCount ), 3, SPROP_UNSIGNED ),
+ SendPropFloat ( SENDINFO_STRUCTELEM( m_ClassData.m_flSuppressionJumpTime ), 32, SPROP_NOSCALE ),
+ SendPropFloat ( SENDINFO_STRUCTELEM( m_ClassData.m_flSuppressionImpactTime ), 32, SPROP_NOSCALE ),
+ SendPropFloat ( SENDINFO_STRUCTELEM( m_ClassData.m_flStickTime ), 32, SPROP_NOSCALE ),
+ SendPropFloat ( SENDINFO_STRUCTELEM( m_ClassData.m_flActiveJumpTime ), 32, SPROP_NOSCALE ),
+ SendPropFloat ( SENDINFO_STRUCTELEM( m_ClassData.m_flImpactDist ), 32, SPROP_NOSCALE ),
+ SendPropVector ( SENDINFO_STRUCTELEM( m_ClassData.m_vecImpactNormal ), -1, SPROP_NORMAL ),
+ SendPropVector ( SENDINFO_STRUCTELEM( m_ClassData.m_vecUnstickVelocity ), -1, SPROP_COORD ),
+ SendPropInt ( SENDINFO_STRUCTELEM( m_ClassData.m_bTrailParticles ), 1, SPROP_UNSIGNED ),
+END_SEND_TABLE()
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Output : const char
+//-----------------------------------------------------------------------------
+const char *CPlayerClassRecon::GetClassModelString( int nTeam )
+{
+ static const char *string = "models/player/recon.mdl";
+ return string;
+}
+
+// ------------------------------------------------------------------------ //
+// CPlayerClassRecon
+// ------------------------------------------------------------------------ //
+CPlayerClassRecon::CPlayerClassRecon( CBaseTFPlayer *pPlayer, TFClass iClass ) : CPlayerClass( pPlayer, iClass )
+{
+ for (int i = 0; i < MAX_TF_TEAMS; ++i)
+ {
+ SetClassModel( MAKE_STRING(GetClassModelString(i)), i );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CPlayerClassRecon::~CPlayerClassRecon()
+{
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPlayerClassRecon::ClassActivate( void )
+{
+ BaseClass::ClassActivate();
+
+ // Setup movement data.
+ SetupMoveData();
+
+ m_bHasRadarScanner = false;
+
+ memset( &m_ClassData, 0, sizeof( m_ClassData ) );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPlayerClassRecon::ClassDeactivate( void )
+{
+ BaseClass::ClassDeactivate();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CPlayerClassRecon::ResupplyAmmo( float flFraction, ResupplyReason_t reason )
+{
+ bool bGiven = false;
+
+ if ((reason == RESUPPLY_ALL_FROM_STATION) || (reason == RESUPPLY_GRENADES_FROM_STATION))
+ {
+ }
+
+ if ((reason == RESUPPLY_ALL_FROM_STATION) || (reason == RESUPPLY_AMMO_FROM_STATION))
+ {
+ }
+
+ if ( BaseClass::ResupplyAmmo(flFraction, reason) )
+ bGiven = true;
+ return bGiven;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Set recon class specific movement data here.
+//-----------------------------------------------------------------------------
+void CPlayerClassRecon::SetupMoveData( void )
+{
+ // Setup Class statistics
+ m_flMaxWalkingSpeed = class_recon_speed.GetFloat();
+
+ m_ClassData.m_nJumpCount = 0;
+ m_ClassData.m_flSuppressionJumpTime = -99999;
+ m_ClassData.m_flSuppressionImpactTime = -99999;
+ m_ClassData.m_flActiveJumpTime = -99999;
+ m_ClassData.m_flStickTime = -99999;
+ m_ClassData.m_flImpactDist = -99999;
+ m_ClassData.m_vecImpactNormal.Init();
+ m_ClassData.m_vecUnstickVelocity.Init();
+ m_ClassData.m_bTrailParticles = false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPlayerClassRecon::SetupSizeData( void )
+{
+ // Initially set the player to the base player class standing hull size.
+ m_pPlayer->SetCollisionBounds( RECONCLASS_HULL_STAND_MIN, RECONCLASS_HULL_STAND_MAX );
+ m_pPlayer->SetViewOffset( RECONCLASS_VIEWOFFSET_STAND );
+ m_pPlayer->m_Local.m_flStepSize = RECONCLASS_STEPSIZE;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Return true if this player's allowed to build another one of the specified objects
+//-----------------------------------------------------------------------------
+int CPlayerClassRecon::CanBuild( int iObjectType )
+{
+ return BaseClass::CanBuild( iObjectType );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPlayerClassRecon::ClassThink()
+{
+ BaseClass::ClassThink();
+
+ m_ClassData.m_bTrailParticles = (m_pPlayer->IsAlive() && !(m_pPlayer->GetFlags() & FL_ONGROUND));
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPlayerClassRecon::CreatePersonalOrder()
+{
+ if ( CreateInitialOrder() )
+ return;
+
+ BaseClass::CreatePersonalOrder();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: New technology has been gained. Recalculate any class specific technology dependencies.
+//-----------------------------------------------------------------------------
+void CPlayerClassRecon::GainedNewTechnology( CBaseTechnology *pTechnology )
+{
+ // Radar Scanner
+ if ( m_pPlayer->HasNamedTechnology( "rec_b_radar_scanners" ) )
+ {
+ m_bHasRadarScanner = true;
+ }
+ else
+ {
+ m_bHasRadarScanner = false;
+ }
+
+ BaseClass::GainedNewTechnology( pTechnology );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPlayerClassRecon::SetPlayerHull( void )
+{
+ if ( m_pPlayer->GetFlags() & FL_DUCKING )
+ {
+ m_pPlayer->SetCollisionBounds( RECONCLASS_HULL_DUCK_MIN, RECONCLASS_HULL_DUCK_MAX );
+ }
+ else
+ {
+ m_pPlayer->SetCollisionBounds( RECONCLASS_HULL_STAND_MIN, RECONCLASS_HULL_STAND_MAX );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPlayerClassRecon::ResetViewOffset( void )
+{
+ if ( m_pPlayer )
+ {
+ m_pPlayer->SetViewOffset( RECONCLASS_VIEWOFFSET_STAND );
+ }
+}
+
+
diff --git a/game/server/tf2/tf_class_recon.h b/game/server/tf2/tf_class_recon.h
new file mode 100644
index 0000000..ef61a04
--- /dev/null
+++ b/game/server/tf2/tf_class_recon.h
@@ -0,0 +1,60 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef TF_CLASS_RECON_H
+#define TF_CLASS_RECON_H
+#pragma once
+
+#include "TFClassData_Shared.h"
+#include "tf_playerclass.h"
+
+class CTFJetpackSteam;
+class CReconJetpackLevel;
+
+
+//=====================================================================
+// Recon
+class CPlayerClassRecon : public CPlayerClass
+{
+public:
+ DECLARE_CLASS( CPlayerClassRecon, CPlayerClass );
+
+ CPlayerClassRecon( CBaseTFPlayer *pPlayer, TFClass iClass );
+ ~CPlayerClassRecon();
+
+ virtual void ClassActivate( void );
+ virtual void ClassDeactivate( void );
+
+ virtual const char* GetClassModelString( int nTeam );
+
+ // Class Initialization
+ virtual bool ResupplyAmmo( float flPercentage, ResupplyReason_t reason );
+ virtual void SetupMoveData( void ); // Override class specific movement data here.
+ virtual void SetupSizeData( void ); // Override class specific size data here.
+ virtual void ResetViewOffset( void );
+
+ PlayerClassReconData_t *GetClassData( void ) { return &m_ClassData; }
+
+ virtual int CanBuild( int iObjectType );
+
+ virtual void ClassThink( void );
+ virtual void GainedNewTechnology( CBaseTechnology *pTechnology );
+
+ virtual void CreatePersonalOrder();
+
+ // Hooks
+ virtual void SetPlayerHull( void );
+
+ CNetworkVarEmbedded( PlayerClassReconData_t, m_ClassData );
+
+protected:
+ bool m_bHasRadarScanner;
+};
+
+EXTERN_SEND_TABLE( DT_PlayerClassReconData )
+
+#endif // TF_CLASS_RECON_H
diff --git a/game/server/tf2/tf_class_sapper.cpp b/game/server/tf2/tf_class_sapper.cpp
new file mode 100644
index 0000000..63338c8
--- /dev/null
+++ b/game/server/tf2/tf_class_sapper.cpp
@@ -0,0 +1,328 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: The Sapper Player Class
+//
+// $Workfile: $
+// $NoKeywords: $
+//=============================================================================//
+#include "cbase.h"
+#include "tf_player.h"
+#include "tf_class_sapper.h"
+#include "basecombatweapon.h"
+#include "weapon_builder.h"
+#include "tf_team.h"
+#include "entitylist.h"
+#include "tf_obj.h"
+#include "weapon_mortar.h"
+#include "orders.h"
+#include "engine/IEngineSound.h"
+#include "weapon_twohandedcontainer.h"
+#include "weapon_combatshield.h"
+#include "tf_vehicle_teleport_station.h"
+#define EMP_RADIUS_NORMAL ( 512.0f )
+#define EMP_RADIUS_MEDIUM ( 1024.0f )
+#define EMP_RADIUS_HUGE ( 4096.0f )
+
+#define EMP_RECHARGETIME_NORMAL ( 20.0f )
+#define EMP_RECHARGETIME_FAST ( 15.0f )
+
+#define EMP_EFFECTTIME_NORMAL ( 10.0f )
+#define EMP_EFFECTTIME_LONG ( 15.0f )
+
+ConVar class_sapper_speed( "class_sapper_speed","225", FCVAR_NONE, "Sapper movement speed" );
+ConVar class_sapper_boost_amount( "class_sapper_boost_amount","35", FCVAR_NONE, "Amount of energy drained for the Sapper to get a boost." );
+ConVar class_sapper_boost_time( "class_sapper_boost_time","15", FCVAR_NONE, "Sapper's boost time after draining a full charge of boost." );
+ConVar class_sapper_boost_rate( "class_sapper_boost_rate","1", FCVAR_NONE, "Sapper's boost rate on his self-boost." );
+ConVar class_sapper_damage_modifier( "class_sapper_damage_modifier", "1.5", 0, "Scales the damage a Sapper does while he's self-boosted." );
+
+BEGIN_SEND_TABLE_NOBASE( CPlayerClassSapper, DT_PlayerClassSapperData )
+ SendPropFloat( SENDINFO( m_flDrainedEnergy ), 8, SPROP_UNSIGNED, 0.0f, 1.0f ),
+END_SEND_TABLE()
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Output : const char
+//-----------------------------------------------------------------------------
+const char *CPlayerClassSapper::GetClassModelString( int nTeam )
+{
+ static const char *string = "models/player/technician.mdl";
+ return string;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Sapper
+//-----------------------------------------------------------------------------
+CPlayerClassSapper::CPlayerClassSapper( CBaseTFPlayer *pPlayer, TFClass iClass ) : CPlayerClass( pPlayer, iClass )
+{
+ for (int i = 0; i < MAX_TF_TEAMS; ++i)
+ {
+ SetClassModel( MAKE_STRING(GetClassModelString(i)), i );
+ }
+
+ // Setup movement data.
+ SetupMoveData();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CPlayerClassSapper::~CPlayerClassSapper()
+{
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Any objects created/owned by class should be allocated and destroyed here
+//-----------------------------------------------------------------------------
+void CPlayerClassSapper::ClassActivate( void )
+{
+ BaseClass::ClassActivate();
+
+ m_bHasMediumRangeEMP = false;
+ m_bHasFasterRechargingEMP = false;
+ m_bHasHugeRadiusEMP = false;
+ m_bHasLongerLastingEMPEffect = false;
+
+ m_vecLastOrigin.Init();
+ m_flLastMoveTime = 0.0f;
+
+ memset( &m_ClassData, 0, sizeof( m_ClassData ) );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPlayerClassSapper::ClassDeactivate( void )
+{
+ m_DamageModifier.RemoveModifier();
+ BaseClass::ClassDeactivate();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPlayerClassSapper::CreateClass( void )
+{
+ BaseClass::CreateClass();
+
+ // Create our two handed weapon layout
+ m_hWpnPlasma = static_cast< CBaseTFCombatWeapon * >( m_pPlayer->GiveNamedItem( "weapon_combat_shotgun" ) );
+ m_hWpnShield = m_pPlayer->GetCombatShield();
+ CWeaponTwoHandedContainer *p = ( CWeaponTwoHandedContainer * )m_pPlayer->Weapon_OwnsThisType( "weapon_twohandedcontainer" );
+ if ( !p )
+ {
+ p = static_cast< CWeaponTwoHandedContainer * >( m_pPlayer->GiveNamedItem( "weapon_twohandedcontainer" ) );
+ }
+ if ( p && m_hWpnShield.Get() && m_hWpnPlasma.Get() )
+ {
+ m_hWpnShield->SetReflectViewModelAnimations( true );
+ p->SetWeapons( m_hWpnPlasma, m_hWpnShield );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPlayerClassSapper::RespawnClass( void )
+{
+ BaseClass::RespawnClass();
+
+ // Reset time of last move
+ m_vecLastOrigin.Init();
+ m_flLastMoveTime = 0.0f;
+ m_flDrainedEnergy = 0;
+ m_flBoostEndTime = 0;
+ m_DamageModifier.SetModifier( class_sapper_damage_modifier.GetFloat() );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CPlayerClassSapper::ResupplyAmmo( float flFraction, ResupplyReason_t reason )
+{
+ bool bGiven = false;
+ if ((reason == RESUPPLY_ALL_FROM_STATION) || (reason == RESUPPLY_GRENADES_FROM_STATION))
+ {
+ if (ResupplyAmmoType( 3 * flFraction, "Grenades" ))
+ bGiven = true;
+ }
+
+ if ((reason == RESUPPLY_ALL_FROM_STATION) || (reason == RESUPPLY_AMMO_FROM_STATION))
+ {
+ }
+
+ // On respawn, resupply base weapon ammo
+ if ( reason == RESUPPLY_RESPAWN )
+ {
+ }
+
+ if ( BaseClass::ResupplyAmmo(flFraction,reason) )
+ bGiven = true;
+ return bGiven;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Set sapper class specific movement data here.
+//-----------------------------------------------------------------------------
+void CPlayerClassSapper::SetupMoveData( void )
+{
+ // Setup Class statistics
+ m_flMaxWalkingSpeed = class_sapper_speed.GetFloat();
+ m_vecLastOrigin.Init();
+ m_flLastMoveTime = 0.0f;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPlayerClassSapper::SetupSizeData( void )
+{
+ // Initially set the player to the base player class standing hull size.
+ m_pPlayer->SetCollisionBounds( SAPPERCLASS_HULL_STAND_MIN, SAPPERCLASS_HULL_STAND_MAX );
+ m_pPlayer->SetViewOffset( SAPPERCLASS_VIEWOFFSET_STAND );
+ m_pPlayer->m_Local.m_flStepSize = SAPPERCLASS_STEPSIZE;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: New technology has been gained. Recalculate any class specific technology dependencies.
+//-----------------------------------------------------------------------------
+void CPlayerClassSapper::GainedNewTechnology( CBaseTechnology *pTechnology )
+{
+ m_bHasMediumRangeEMP = false;
+ m_bHasFasterRechargingEMP = false;
+ m_bHasHugeRadiusEMP = false;
+ m_bHasLongerLastingEMPEffect = false;
+
+ if ( m_pPlayer->HasNamedTechnology( "emp1" ) )
+ {
+ m_bHasFasterRechargingEMP = true;
+ }
+
+ if ( m_pPlayer->HasNamedTechnology( "emp3" ) )
+ {
+ m_bHasMediumRangeEMP = true;
+ }
+
+ if ( m_pPlayer->HasNamedTechnology( "emp4" ) )
+ {
+ m_bHasLongerLastingEMPEffect = true;
+ }
+
+ if ( m_pPlayer->HasNamedTechnology( "emp5" ) )
+ {
+ m_bHasHugeRadiusEMP = true;
+ }
+
+ BaseClass::GainedNewTechnology( pTechnology );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPlayerClassSapper::ClassThink( void )
+{
+ if ( m_pPlayer->GetLocalOrigin() != m_vecLastOrigin )
+ {
+ m_vecLastOrigin = m_pPlayer->GetLocalOrigin();
+ m_flLastMoveTime = gpGlobals->curtime;
+ }
+
+ // Am I boosting myself?
+ if ( m_pPlayer->IsAlive() && (m_flBoostEndTime > gpGlobals->curtime) )
+ {
+ m_pPlayer->AttemptToPowerup( POWERUP_BOOST, class_sapper_boost_time.GetFloat(), class_sapper_boost_rate.GetFloat() * 0.1, m_pPlayer, &m_DamageModifier );
+ }
+ else
+ {
+ m_DamageModifier.RemoveModifier();
+ }
+
+ BaseClass::ClassThink();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CPlayerClassSapper::CheckStationaryTime( float time_required )
+{
+ // Has enough time passed?
+ if ( gpGlobals->curtime >= m_flLastMoveTime + time_required )
+ {
+ return true;
+ }
+
+ // Not enough time yet
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPlayerClassSapper::CreatePersonalOrder( void )
+{
+ if ( CreateInitialOrder() )
+ return;
+
+ BaseClass::CreatePersonalOrder();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPlayerClassSapper::SetPlayerHull( void )
+{
+ if ( m_pPlayer->GetFlags() & FL_DUCKING )
+ {
+ m_pPlayer->SetCollisionBounds( SAPPERCLASS_HULL_DUCK_MIN, SAPPERCLASS_HULL_DUCK_MAX );
+ }
+ else
+ {
+ m_pPlayer->SetCollisionBounds( SAPPERCLASS_HULL_STAND_MIN, SAPPERCLASS_HULL_STAND_MAX );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPlayerClassSapper::ResetViewOffset( void )
+{
+ if ( m_pPlayer )
+ {
+ m_pPlayer->SetViewOffset( SAPPERCLASS_VIEWOFFSET_STAND );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPlayerClassSapper::AddDrainedEnergy( float flEnergy )
+{
+ // Convert to 0->1
+ flEnergy = ( flEnergy / class_sapper_boost_amount.GetFloat() );
+ m_flDrainedEnergy = MIN( 1.0, m_flDrainedEnergy + flEnergy );
+
+ // Did we hit max?
+ if ( m_flDrainedEnergy >= 1.0 )
+ {
+ m_flDrainedEnergy = 0;
+
+ // Boost the player
+ m_flBoostEndTime = gpGlobals->curtime + class_sapper_boost_time.GetFloat();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+float CPlayerClassSapper::GetDrainedEnergy( void )
+{
+ return m_flDrainedEnergy;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPlayerClassSapper::DeductDrainedEnergy( float flEnergy )
+{
+ m_flDrainedEnergy = MAX( 0, m_flDrainedEnergy - flEnergy );
+}
diff --git a/game/server/tf2/tf_class_sapper.h b/game/server/tf2/tf_class_sapper.h
new file mode 100644
index 0000000..e003008
--- /dev/null
+++ b/game/server/tf2/tf_class_sapper.h
@@ -0,0 +1,97 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Sapper player class
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef TF_CLASS_SAPPER_H
+#define TF_CLASS_SAPPER_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include "TFClassData_Shared.h"
+#include "damagemodifier.h"
+
+class CEMPPulseStatus;
+class CBaseObject;
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Sapper
+//-----------------------------------------------------------------------------
+class CPlayerClassSapper : public CPlayerClass
+{
+public:
+ DECLARE_CLASS( CPlayerClassSapper, CPlayerClass );
+
+ CPlayerClassSapper( CBaseTFPlayer *pPlayer, TFClass iClass );
+ ~CPlayerClassSapper();
+
+ // Any objects created/owned by class should be allocated and destroyed here
+ virtual void ClassActivate( void );
+ virtual void ClassDeactivate( void );
+
+ virtual const char* GetClassModelString( int nTeam );
+
+ // Class Initialization
+ virtual void CreateClass( void ); // Create the class upon initial spawn
+ virtual void RespawnClass( void ); // Called upon all respawns
+ virtual bool ResupplyAmmo( float flPercentage, ResupplyReason_t reason );
+ virtual void SetupMoveData( void ); // Override class specific movement data here.
+ virtual void SetupSizeData( void ); // Override class specific size data here.
+ virtual void ResetViewOffset( void );
+
+ PlayerClassSapperData_t *GetClassData( void ) { return &m_ClassData; }
+
+ virtual void GainedNewTechnology( CBaseTechnology *pTechnology );
+
+ virtual void ClassThink( void );
+
+ // Energy draining
+ void AddDrainedEnergy( float flEnergy );
+
+ // See if the player has not moved in time_required seconds
+ bool CheckStationaryTime( float time_required );
+
+ // Orders
+ virtual void CreatePersonalOrder( void );
+
+ // Hooks
+ virtual void SetPlayerHull( void );
+
+ float GetDrainedEnergy( void );
+ void DeductDrainedEnergy( float flEnergy );
+
+public:
+ CNetworkVar( float, m_flDrainedEnergy );
+
+private:
+ bool m_bHasMediumRangeEMP;
+ bool m_bHasFasterRechargingEMP;
+ bool m_bHasHugeRadiusEMP;
+ bool m_bHasLongerLastingEMPEffect;
+
+ // Energy drained with the drainbeam
+ float m_flBoostEndTime;
+ CDamageModifier m_DamageModifier;
+
+ static int m_nEffectIndex;
+ CEMPPulseStatus *m_pEMPPulseStatus;
+
+ Vector m_vecLastOrigin;
+ float m_flLastMoveTime;
+
+ PlayerClassSapperData_t m_ClassData;
+
+ // Weapons
+ CHandle<CBaseTFCombatWeapon> m_hWpnPlasma;
+};
+
+EXTERN_SEND_TABLE( DT_PlayerClassSapperData )
+
+
+extern char *g_pszEMPPulseStart;
+
+#endif // TF_CLASS_SAPPER_H
diff --git a/game/server/tf2/tf_class_sniper.cpp b/game/server/tf2/tf_class_sniper.cpp
new file mode 100644
index 0000000..f724ede
--- /dev/null
+++ b/game/server/tf2/tf_class_sniper.cpp
@@ -0,0 +1,261 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: The Sniper Player Class
+//
+// $Workfile: $
+// $NoKeywords: $
+//=============================================================================//
+#include "cbase.h"
+#include "tf_player.h"
+#include "tf_class_sniper.h"
+#include "in_buttons.h"
+#include "tf_team.h"
+#include "tf_class_support.h"
+#include "order_killmortarguy.h"
+
+
+bool IsEntityVisibleToTactical( int iLocalTeamNumber, int iLocalTeamPlayers,
+ int iLocalTeamScanners, int iEntIndex, const char *pEntName, int pEntTeamNumber, const Vector &pEntOrigin );
+
+ConVar class_sniper_speed( "class_sniper_speed","200", FCVAR_NONE, "Sniper movement speed" );
+
+// Stationary Camo
+#define SNIPER_MAX_HIDE_LEVEL 60
+#define SNIPER_HIDE_INCREASE_PER_SEC 15
+#define SNIPER_FIRE_UNHIDE_TIME 5.0
+
+//=============================================================================
+//
+// Sniper Data Table
+//
+BEGIN_SEND_TABLE_NOBASE( CPlayerClassSniper, DT_PlayerClassSniperData )
+END_SEND_TABLE()
+
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Output : const char
+//-----------------------------------------------------------------------------
+const char *CPlayerClassSniper::GetClassModelString( int nTeam )
+{
+ static const char *string = "models/player/sniper.mdl";
+ return string;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CPlayerClassSniper::CPlayerClassSniper( CBaseTFPlayer *pPlayer, TFClass iClass ) : CPlayerClass( pPlayer, iClass )
+{
+ for (int i = 0; i < MAX_TF_TEAMS; ++i)
+ {
+ SetClassModel( MAKE_STRING(GetClassModelString(i)), i );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CPlayerClassSniper::~CPlayerClassSniper()
+{
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPlayerClassSniper::ClassActivate( void )
+{
+ BaseClass::ClassActivate();
+
+ // Setup movement data.
+ SetupMoveData();
+
+ m_bHiding = false;
+ m_flHideTransparency = 0.0f;
+ m_flLastHideUpdate = 0.0f;
+
+ m_bCanHide = false;
+
+ memset( &m_ClassData, 0, sizeof( m_ClassData ) );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPlayerClassSniper::ClassDeactivate( void )
+{
+ BaseClass::ClassDeactivate();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPlayerClassSniper::RespawnClass( void )
+{
+ BaseClass::RespawnClass();
+
+ // Hiding values
+ m_bHiding = false;
+ m_flHideTransparency = m_flLastHideUpdate = 0;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CPlayerClassSniper::ResupplyAmmo( float flFraction, ResupplyReason_t reason )
+{
+ bool bGiven = false;
+
+ if ((reason == RESUPPLY_ALL_FROM_STATION) || (reason == RESUPPLY_AMMO_FROM_STATION))
+ {
+ if (ResupplyAmmoType( 100 * flFraction, "Bullets" ))
+ bGiven = true;
+ }
+
+ if ( BaseClass::ResupplyAmmo(flFraction, reason) )
+ bGiven = true;
+
+ return bGiven;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Set sniper class specific movement data here.
+//-----------------------------------------------------------------------------
+void CPlayerClassSniper::SetupMoveData( void )
+{
+ // Setup Class statistics
+ m_flMaxWalkingSpeed = class_sniper_speed.GetFloat();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPlayerClassSniper::SetupSizeData( void )
+{
+ // Initially set the player to the base player class standing hull size.
+ m_pPlayer->SetCollisionBounds( SNIPERCLASS_HULL_STAND_MIN, SNIPERCLASS_HULL_STAND_MAX );
+ m_pPlayer->SetViewOffset( SNIPERCLASS_VIEWOFFSET_STAND );
+ m_pPlayer->m_Local.m_flStepSize = SNIPERCLASS_STEPSIZE;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+float CPlayerClassSniper::GetDeployTime( void )
+{
+ return 2.8;
+};
+
+//-----------------------------------------------------------------------------
+// Purpose: Called every frame by the player PostThink()
+//-----------------------------------------------------------------------------
+void CPlayerClassSniper::ClassThink( void )
+{
+ // Only hide if we have the technology
+ if ( m_bCanHide )
+ {
+ CheckHiding();
+ }
+
+ BaseClass::ClassThink();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: New technology has been gained. Recalculate any class specific technology dependencies.
+//-----------------------------------------------------------------------------
+void CPlayerClassSniper::GainedNewTechnology( CBaseTechnology *pTechnology )
+{
+ // Stealthed on deploy
+ if ( m_pPlayer->HasNamedTechnology( "sniper_deploy_stealth" ) )
+ {
+ m_bCanHide = true;
+ }
+ else
+ {
+ m_bCanHide = false;
+ }
+
+ BaseClass::GainedNewTechnology( pTechnology );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: If the player's prone, slowly make him transparent
+//-----------------------------------------------------------------------------
+void CPlayerClassSniper::CheckHiding( void )
+{
+ // Can't hide or stay hidden if taking EMP damage
+ if ( m_pPlayer->HasPowerup(POWERUP_EMP) )
+ {
+ m_pPlayer->ClearCamouflage();
+ return;
+ }
+
+ // See if the player's just fired
+ if ( m_pPlayer->m_afButtonReleased & IN_ATTACK )
+ {
+ // Immediately mostly unhide if so
+ m_pPlayer->ClearCamouflage();
+ }
+ else if ( !m_pPlayer->IsDeployed() )
+ {
+ // Not deployed? Immediately unhide if so
+ m_pPlayer->ClearCamouflage();
+ }
+ else
+ {
+ // We're deployed, hide if we haven't fired for a bit.
+ if ( m_pPlayer->LastAttackTime() + SNIPER_FIRE_UNHIDE_TIME < gpGlobals->curtime )
+ {
+ m_pPlayer->SetCamouflaged( SNIPER_MAX_HIDE_LEVEL, SNIPER_HIDE_INCREASE_PER_SEC );
+ }
+ else
+ {
+ m_pPlayer->ClearCamouflage();
+ }
+ }
+}
+
+
+void CPlayerClassSniper::CreatePersonalOrder( void )
+{
+ if ( CreateInitialOrder() )
+ return;
+
+ // Kill a support guy?
+ if ( COrderKillMortarGuy::CreateOrder( this ) )
+ return;
+
+ BaseClass::CreatePersonalOrder();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPlayerClassSniper::SetPlayerHull( void )
+{
+ if ( m_pPlayer->GetFlags() & FL_DUCKING )
+ {
+ m_pPlayer->SetCollisionBounds( SNIPERCLASS_HULL_DUCK_MIN, SNIPERCLASS_HULL_DUCK_MAX );
+ }
+ else
+ {
+ m_pPlayer->SetCollisionBounds( SNIPERCLASS_HULL_STAND_MIN, SNIPERCLASS_HULL_STAND_MAX );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPlayerClassSniper::ResetViewOffset( void )
+{
+ if ( m_pPlayer )
+ {
+ m_pPlayer->SetViewOffset( SNIPERCLASS_VIEWOFFSET_STAND );
+ }
+}
+
diff --git a/game/server/tf2/tf_class_sniper.h b/game/server/tf2/tf_class_sniper.h
new file mode 100644
index 0000000..7e504bf
--- /dev/null
+++ b/game/server/tf2/tf_class_sniper.h
@@ -0,0 +1,69 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef TF_CLASS_SNIPER_H
+#define TF_CLASS_SNIPER_H
+#pragma once
+
+#include "TFClassData_Shared.h"
+
+
+//=====================================================================
+// Sniper
+class CPlayerClassSniper : public CPlayerClass
+{
+public:
+ DECLARE_CLASS( CPlayerClassSniper, CPlayerClass );
+
+ CPlayerClassSniper( CBaseTFPlayer *pPlayer, TFClass iClass );
+ ~CPlayerClassSniper();
+
+ virtual void ClassActivate( void );
+ virtual void ClassDeactivate( void );
+
+ virtual const char* GetClassModelString( int nTeam );
+
+ virtual void GainedNewTechnology( CBaseTechnology *pTechnology );
+
+ // Class Initialization
+ virtual void RespawnClass( void ); // Called upon all respawns
+ virtual bool ResupplyAmmo( float flPercentage, ResupplyReason_t reason );
+ virtual void SetupMoveData( void ); // Override class specific movement data here.
+ virtual void SetupSizeData( void ); // Override class specific size data here.
+ virtual void ResetViewOffset( void );
+
+ PlayerClassSniperData_t *GetClassData( void ) { return &m_ClassData; }
+
+ // Deployment
+ virtual float GetDeployTime( void );
+
+ // Class abilities
+ virtual void ClassThink();
+ // Hiding
+ void CheckHiding( void );
+
+ // Orders
+ virtual void CreatePersonalOrder( void );
+
+ // Hooks
+ virtual void SetPlayerHull( void );
+
+protected:
+ // Hiding
+ bool m_bHiding;
+ float m_flHideTransparency;
+ float m_flLastHideUpdate;
+
+ PlayerClassSniperData_t m_ClassData;
+
+private:
+ bool m_bCanHide;
+};
+
+EXTERN_SEND_TABLE( DT_PlayerClassSniperData )
+
+#endif // TF_CLASS_SNIPER_H
diff --git a/game/server/tf2/tf_class_support.cpp b/game/server/tf2/tf_class_support.cpp
new file mode 100644
index 0000000..9406f3c
--- /dev/null
+++ b/game/server/tf2/tf_class_support.cpp
@@ -0,0 +1,153 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: The Support/Suppression Player Class
+//
+// $Workfile: $
+// $Date: $
+// $NoKeywords: $
+//=============================================================================//
+#include "cbase.h"
+#include "tf_player.h"
+#include "tf_class_support.h"
+#include "basecombatweapon.h"
+#include "tf_obj.h"
+#include "in_buttons.h"
+#include "menu_base.h"
+#include "tf_team.h"
+#include "weapon_builder.h"
+
+ConVar class_support_speed( "class_support_speed","200", FCVAR_NONE, "Support movement speed" );
+
+//=============================================================================
+//
+// Support Data Table
+//
+BEGIN_SEND_TABLE_NOBASE( CPlayerClassSupport, DT_PlayerClassSupportData )
+END_SEND_TABLE()
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Output : const char
+//-----------------------------------------------------------------------------
+const char *CPlayerClassSupport::GetClassModelString( int nTeam )
+{
+ static const char *string = "models/player/alien_support.mdl";
+ return string;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CPlayerClassSupport::CPlayerClassSupport( CBaseTFPlayer *pPlayer, TFClass iClass ) : CPlayerClass( pPlayer, iClass )
+{
+ for (int i = 1; i <= MAX_TF_TEAMS; ++i)
+ {
+ SetClassModel( MAKE_STRING(GetClassModelString(i)), i );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CPlayerClassSupport::~CPlayerClassSupport()
+{
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPlayerClassSupport::ClassActivate( void )
+{
+ BaseClass::ClassActivate();
+
+// Any objects created/owned by class should be allocated and destroyed here
+ // Setup movement data.
+ SetupMoveData();
+
+ m_pPlayer->SetCollisionBounds( SUPPORTCLASS_HULL_STAND_MIN, SUPPORTCLASS_HULL_STAND_MAX );
+
+ memset( &m_ClassData, 0, sizeof( m_ClassData ) );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPlayerClassSupport::ClassDeactivate( void )
+{
+ BaseClass::ClassDeactivate();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPlayerClassSupport::CreateClass( void )
+{
+ BaseClass::CreateClass();
+
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CPlayerClassSupport::ResupplyAmmo( float flFraction, ResupplyReason_t reason )
+{
+ bool bGiven = false;
+
+ // On respawn, resupply base weapon ammo
+ if ( reason == RESUPPLY_RESPAWN )
+ {
+ }
+
+ if ( BaseClass::ResupplyAmmo(flFraction, reason) )
+ bGiven = true;
+ return bGiven;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Set support class specific movement data here.
+//-----------------------------------------------------------------------------
+void CPlayerClassSupport::SetupMoveData( void )
+{
+ // Setup Class statistics
+ m_flMaxWalkingSpeed = class_support_speed.GetFloat();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPlayerClassSupport::SetupSizeData( void )
+{
+ // Initially set the player to the base player class standing hull size.
+ m_pPlayer->SetCollisionBounds( SUPPORTCLASS_HULL_STAND_MIN, SUPPORTCLASS_HULL_STAND_MAX );
+ m_pPlayer->SetViewOffset( SUPPORTCLASS_VIEWOFFSET_STAND );
+ m_pPlayer->m_Local.m_flStepSize = SUPPORTCLASS_STEPSIZE;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPlayerClassSupport::SetPlayerHull( void )
+{
+ if ( m_pPlayer->GetFlags() & FL_DUCKING )
+ {
+ m_pPlayer->SetCollisionBounds( SUPPORTCLASS_HULL_DUCK_MIN, SUPPORTCLASS_HULL_DUCK_MAX );
+ }
+ else
+ {
+ m_pPlayer->SetCollisionBounds( SUPPORTCLASS_HULL_STAND_MIN, SUPPORTCLASS_HULL_STAND_MAX );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPlayerClassSupport::ResetViewOffset( void )
+{
+ if ( m_pPlayer )
+ {
+ m_pPlayer->SetViewOffset( SUPPORTCLASS_VIEWOFFSET_STAND );
+ }
+}
+
+
diff --git a/game/server/tf2/tf_class_support.h b/game/server/tf2/tf_class_support.h
new file mode 100644
index 0000000..2271d39
--- /dev/null
+++ b/game/server/tf2/tf_class_support.h
@@ -0,0 +1,49 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: The support/suppression player class
+//
+// $NoKeywords: $
+//=============================================================================//
+#ifndef TF_CLASS_SUPPORT_H
+#define TF_CLASS_SUPPORT_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include "tf_basecombatweapon.h"
+#include "TFClassData_Shared.h"
+
+//=====================================================================
+// Support
+class CPlayerClassSupport : public CPlayerClass
+{
+public:
+ DECLARE_CLASS( CPlayerClassSupport, CPlayerClass );
+
+ CPlayerClassSupport( CBaseTFPlayer *pPlayer, TFClass iClass );
+ ~CPlayerClassSupport();
+
+ virtual void ClassActivate( void );
+ virtual void ClassDeactivate( void );
+
+ virtual const char* GetClassModelString( int nTeam );
+
+ // Class Initialization
+ virtual void CreateClass( void ); // Create the class upon initial spawn
+ virtual bool ResupplyAmmo( float flPercentage, ResupplyReason_t reason );
+ virtual void SetupMoveData( void ); // Override class specific movement data here.
+ virtual void SetupSizeData( void ); // Override class specific size data here.
+ virtual void ResetViewOffset( void );
+
+ PlayerClassSupportData_t *GetClassData( void ) { return &m_ClassData; }
+
+ // Hooks
+ virtual void SetPlayerHull( void );
+
+protected:
+ PlayerClassSupportData_t m_ClassData;
+};
+
+EXTERN_SEND_TABLE( DT_PlayerClassSupportData )
+
+#endif // TF_CLASS_SUPPORT_H
diff --git a/game/server/tf2/tf_client.cpp b/game/server/tf2/tf_client.cpp
new file mode 100644
index 0000000..c39dc05
--- /dev/null
+++ b/game/server/tf2/tf_client.cpp
@@ -0,0 +1,180 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//
+//=============================================================================//
+/*
+
+===== tf_client.cpp ========================================================
+
+ HL2 client/server game specific stuff
+
+*/
+
+#include "cbase.h"
+#include "player.h"
+#include "gamerules.h"
+#include "teamplay_gamerules.h"
+#include "tf_gamerules.h"
+#include "EntityList.h"
+#include "physics.h"
+#include "game.h"
+#include "ai_network.h"
+#include "ai_node.h"
+#include "ai_hull.h"
+#include "tf_player.h"
+#include "menu_base.h"
+#include "shake.h"
+#include "player_resource.h"
+#include "engine/IEngineSound.h"
+
+#include "tier0/vprof.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+
+void Host_Say( edict_t *pEdict, bool teamonly );
+extern CBaseEntity *FindPickerEntity( CBasePlayer *pPlayer );
+void InitializeMenus( void );
+void DestroyMenus( void );
+void Bot_RunAll( void );
+
+extern bool g_fGameOver;
+
+/*
+===========
+ClientPutInServer
+
+called each time a player is spawned into the game
+============
+*/
+void ClientPutInServer( edict_t *pEdict, const char *playername )
+{
+ // Allocate a CBaseTFPlayer for pev, and call spawn
+ CBaseTFPlayer *pPlayer = CBaseTFPlayer::CreatePlayer( "player", pEdict );
+ pPlayer->InitialSpawn();
+ pPlayer->SetPlayerName( playername );
+ pPlayer->Spawn();
+}
+
+/*
+===============
+const char *GetGameDescription()
+
+Returns the descriptive name of this .dll. E.g., Half-Life, or Team Fortress 2
+===============
+*/
+const char *GetGameDescription()
+{
+ if ( g_pGameRules ) // this function may be called before the world has spawned, and the game rules initialized
+ return g_pGameRules->GetGameDescription();
+ else
+ return "TeamFortress 2";
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Precache game-specific models & sounds
+//-----------------------------------------------------------------------------
+void ClientGamePrecache( void )
+{
+ // Materials used by the client effects
+ CBaseEntity::PrecacheModel( "sprites/white.vmt" );
+ CBaseEntity::PrecacheModel( "sprites/physbeam.vmt" );
+ CBaseEntity::PrecacheModel( "effects/human_object_glow.vmt" );
+
+ // Precache player models
+ CBaseEntity::PrecacheModel( "models/player/alien_commando.mdl" );
+ CBaseEntity::PrecacheModel( "models/player/human_commando.mdl" );
+ CBaseEntity::PrecacheModel( "models/player/human_defender.mdl");
+ CBaseEntity::PrecacheModel( "models/player/medic.mdl" );
+ CBaseEntity::PrecacheModel( "models/player/recon.mdl" );
+ CBaseEntity::PrecacheModel( "models/player/human_medic.mdl" );
+ CBaseEntity::PrecacheModel( "models/player/alien_escort.mdl" );
+ CBaseEntity::PrecacheModel( "models/player/defender.mdl" );
+ CBaseEntity::PrecacheModel( "models/player/technician.mdl" );
+}
+
+
+// called by ClientKill and DeadThink
+void respawn( CBaseEntity *pEdict, bool fCopyCorpse )
+{
+ if (gpGlobals->coop || gpGlobals->deathmatch)
+ {
+ if ( fCopyCorpse )
+ {
+ // make a copy of the dead body for appearances sake
+ ((CBaseTFPlayer *)pEdict)->CreateCorpse();
+ }
+
+ // respawn player
+ pEdict->Spawn();
+ }
+ else
+ { // restart the entire server
+ engine->ServerCommand("reload\n");
+ }
+}
+
+void GameStartFrame( void )
+{
+ VPROF("GameStartFrame()");
+
+ if ( g_pGameRules )
+ g_pGameRules->Think();
+
+ if ( g_fGameOver )
+ return;
+
+ gpGlobals->teamplay = teamplay.GetInt() ? true : false;
+
+ Bot_RunAll();
+}
+
+//=========================================================
+// instantiate the proper game rules object
+//=========================================================
+void InstallGameRules()
+{
+ InitializeMenus();
+
+ // teamplay
+ CreateGameRulesObject( "CTeamFortress" );
+}
+
+void FinishClientPutInServer( CBaseTFPlayer *pPlayer )
+{
+ pPlayer->InitialSpawn();
+ pPlayer->Spawn();
+
+ // When the player first joins the server, they
+ pPlayer->m_lifeState = LIFE_DEAD;
+ pPlayer->AddEffects( EF_NODRAW );
+ pPlayer->ChangeTeam( TEAM_UNASSIGNED );
+ pPlayer->SetThink( NULL );
+
+ char sName[128];
+ Q_strncpy( sName, pPlayer->GetPlayerName(), sizeof( sName ) );
+
+ // First parse the name and remove any %'s
+ for ( char *pApersand = sName; pApersand != NULL && *pApersand != 0; pApersand++ )
+ {
+ // Replace it with a space
+ if ( *pApersand == '%' )
+ *pApersand = ' ';
+ }
+
+ // notify other clients of player joining the game
+ UTIL_ClientPrintAll( HUD_PRINTNOTIFY, "#Game_connected", sName[0] != 0 ? sName : "<unconnected>" );
+}
+
+void ClientActive( edict_t *pEdict, bool bLoadGame )
+{
+ // Can't load games in CS!
+ Assert( !bLoadGame );
+
+ CBaseTFPlayer *pPlayer = static_cast<CBaseTFPlayer*>( CBaseEntity::Instance( pEdict ) );
+ FinishClientPutInServer( pPlayer );
+}
diff --git a/game/server/tf2/tf_eventlog.cpp b/game/server/tf2/tf_eventlog.cpp
new file mode 100644
index 0000000..9fb99ee
--- /dev/null
+++ b/game/server/tf2/tf_eventlog.cpp
@@ -0,0 +1,56 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//
+//=============================================================================//
+#include "cbase.h"
+#include "../eventlog.h"
+#include <KeyValues.h>
+
+class CTF2EventLog : public CEventLog
+{
+private:
+ typedef CEventLog BaseClass;
+
+public:
+ virtual ~CTF2EventLog() {};
+
+public:
+ bool PrintEvent( KeyValues * event ) // override virtual function
+ {
+ if ( BaseClass::PrintEvent( event ) )
+ {
+ return true;
+ }
+
+ if ( Q_strcmp(event->GetName(), "tf2_") == 0 )
+ {
+ return PrintTF2Event( event );
+ }
+
+ return false;
+ }
+
+protected:
+
+ bool PrintTF2Event( KeyValues * event ) // print Mod specific logs
+ {
+ // const char * name = event->GetName() + Q_strlen("tf2_"); // remove prefix
+
+ return false;
+ }
+
+};
+
+CTF2EventLog g_TF2EventLog;
+
+//-----------------------------------------------------------------------------
+// Singleton access
+//-----------------------------------------------------------------------------
+IGameSystem* GameLogSystem()
+{
+ return &g_TF2EventLog;
+}
+
diff --git a/game/server/tf2/tf_flare.cpp b/game/server/tf2/tf_flare.cpp
new file mode 100644
index 0000000..d6961c0
--- /dev/null
+++ b/game/server/tf2/tf_flare.cpp
@@ -0,0 +1,249 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Auto Repair
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#include "cbase.h"
+#include "tf_flare.h"
+#include "tf_player.h"
+#include "tf_team.h"
+#include "IEffects.h"
+#include "engine/IEngineSound.h"
+
+// Data Description
+BEGIN_DATADESC( CSignalFlare )
+
+ // Function Pointers
+ DEFINE_FUNCTION( FlareTouch ),
+ DEFINE_FUNCTION( FlareThink ),
+
+END_DATADESC()
+
+//Data Table
+IMPLEMENT_SERVERCLASS_ST( CSignalFlare, DT_SignalFlare )
+ SendPropFloat( SENDINFO( m_flDuration ), 0, SPROP_NOSCALE ),
+ SendPropFloat( SENDINFO( m_flScale ), 0, SPROP_NOSCALE ),
+ SendPropInt( SENDINFO( m_bLight ), 1, SPROP_UNSIGNED ),
+ SendPropInt( SENDINFO( m_bSmoke ), 1, SPROP_UNSIGNED ),
+END_SEND_TABLE()
+
+LINK_ENTITY_TO_CLASS( env_flare, CSignalFlare );
+PRECACHE_REGISTER( env_flare );
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CSignalFlare::CSignalFlare( void )
+{
+ m_nBounces = 0;
+ m_flScale = 1.0f;
+
+ m_bFading = false;
+ m_bLight = true;
+ m_bSmoke = true;
+
+ m_flNextDamage = gpGlobals->curtime;
+ m_lifeState = LIFE_ALIVE;
+ m_iHealth = 100;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CSignalFlare::Precache( void )
+{
+ // Precache model(s).
+ // bug: this model no longer exists:
+ //PrecacheModel("models/flare.mdl" );
+
+ PrecacheScriptSound( "SignalFlare.Impact" );
+ PrecacheScriptSound( "SignalFlare.BurnLower" );
+ PrecacheScriptSound( "SignalFlare.Burn" );
+
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CSignalFlare::Spawn( void )
+{
+ Precache();
+ SetModel( "models/flare.mdl" );
+
+ UTIL_SetSize( this, Vector( -2, -2, -2 ), Vector( 2, 2, 2 ) );
+
+ SetSolid( SOLID_BBOX );
+ AddSolidFlags( FSOLID_NOT_SOLID );
+ SetMoveType( MOVETYPE_NONE );
+ SetFriction( 0.6f );
+ SetGravity( UTIL_ScaleForGravity( 400 ) );
+ m_flDuration = gpGlobals->curtime + 10;
+
+ if ( HasSpawnFlags( SIGNALFLARE_NO_DLIGHT ) )
+ {
+ m_bLight = false;
+ }
+
+ if ( HasSpawnFlags( SIGNALFLARE_NO_SMOKE ) )
+ {
+ m_bSmoke = false;
+ }
+
+ if ( HasSpawnFlags( SIGNALFLARE_INFINITE ) )
+ {
+ m_flDuration = -1.0f;
+ }
+
+ AddFlag( FL_OBJECT | FL_NOTARGET );
+
+ EmitSound( "SignalFlare.Burn" );
+
+ SetThink( FlareThink );
+ SetNextThink( gpGlobals->curtime + 0.1f );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : vecOrigin -
+// vecAngles -
+// *pOwner -
+// Output : CSignalFlare
+//-----------------------------------------------------------------------------
+CSignalFlare *CSignalFlare::Create( Vector vecOrigin, QAngle vecAngles, CBaseEntity *pOwner, float lifetime )
+{
+ // Create an instance of a flare.
+ CSignalFlare *pFlare = ( CSignalFlare* )CreateEntityByName( "env_flare" );
+ if ( !pFlare )
+ return NULL;
+
+ // Set the initial origin and angle vectors.
+ UTIL_SetOrigin( pFlare, vecOrigin );
+ pFlare->SetLocalAngles( vecAngles );
+
+ // Spawn the flare.
+ pFlare->Spawn();
+
+ // Set the flare's touch and think functions.
+ pFlare->SetTouch( FlareTouch );
+ pFlare->SetThink( FlareThink );
+
+ // Don't start sparking immediately.
+ pFlare->SetNextThink( gpGlobals->curtime + 0.5f );
+
+ // Set the burn out time.
+ pFlare->m_flDuration = gpGlobals->curtime + lifetime;
+
+ pFlare->AddSolidFlags( FSOLID_NOT_STANDABLE );
+ pFlare->RemoveSolidFlags( FSOLID_NOT_SOLID );
+
+ pFlare->SetMoveType( MOVETYPE_FLYGRAVITY, MOVECOLLIDE_FLY_BOUNCE );
+
+ pFlare->SetOwnerEntity( pOwner );
+ pFlare->m_pOwner = pOwner;
+
+ return pFlare;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CSignalFlare::FlareThink( void )
+{
+ float deltaTime = ( m_flDuration - gpGlobals->curtime );
+
+ if ( m_flDuration != -1.0f )
+ {
+ //Fading away
+ if ( ( deltaTime <= 2.0f ) && ( m_bFading == false ) )
+ {
+ m_bFading = true;
+
+ // Reduce sound!
+ EmitSound( "SignalFlare.BurnLower" );
+ }
+
+ //Burned out
+ if ( m_flDuration < gpGlobals->curtime )
+ {
+ StopSound( "SignalFlare.Burn" );
+ UTIL_Remove( this );
+ return;
+ }
+ }
+
+ //Act differently underwater
+ if ( GetWaterLevel() > 1 )
+ {
+ UTIL_Bubbles( GetAbsOrigin() + Vector( -2, -2, -2 ), GetAbsOrigin() + Vector( 2, 2, 2 ), 1 );
+ m_bSmoke = false;
+ }
+ else
+ {
+ //Shoot sparks
+ if ( random->RandomInt( 0, 8 ) == 1 )
+ {
+ g_pEffects->Sparks( GetAbsOrigin() );
+ }
+ }
+
+ //Next update
+ SetNextThink( gpGlobals->curtime + 0.1f );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : *pOther -
+//-----------------------------------------------------------------------------
+void CSignalFlare::FlareTouch( CBaseEntity *pOther )
+{
+ Assert( pOther );
+ if ( !pOther->IsSolid() )
+ return;
+
+ if ( ( m_nBounces < 10 ) && ( GetWaterLevel() < 1 ) )
+ {
+ //Throw some real chunks here
+ g_pEffects->Sparks( GetAbsOrigin() );
+ }
+
+ // hit the world, check the material type here, see if the flare should stick.
+// const trace_t &tr = CBaseEntity::GetTouchTrace();
+
+ if ( m_nBounces == 0 )
+ {
+ // Emit an impact sound.
+ EmitSound( "SignalFlare.Impact" );
+ }
+
+ // Change our flight characteristics
+ SetMoveType( MOVETYPE_FLYGRAVITY, MOVECOLLIDE_FLY_BOUNCE );
+ SetGravity( UTIL_ScaleForGravity( 640 ) );
+
+ m_nBounces++;
+
+ // After the first bounce, smacking into whoever fired the flare is fair game
+ SetOwnerEntity( this );
+
+ //Slow down
+ Vector newVelocity = GetAbsVelocity();
+ newVelocity.x *= 0.8f;
+ newVelocity.y *= 0.8f;
+
+ // Stopped?
+ if ( newVelocity.Length() < 64.0f )
+ {
+ newVelocity.Init();
+ SetMoveType( MOVETYPE_NONE );
+ RemoveSolidFlags( FSOLID_NOT_SOLID );
+ AddSolidFlags( FSOLID_TRIGGER );
+ }
+
+ SetAbsVelocity( newVelocity );
+}
diff --git a/game/server/tf2/tf_flare.h b/game/server/tf2/tf_flare.h
new file mode 100644
index 0000000..8aea8a1
--- /dev/null
+++ b/game/server/tf2/tf_flare.h
@@ -0,0 +1,58 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef TF_FLARE_H
+#define TF_FLARE_H
+#pragma once
+
+#define SIGNALFLARE_NO_DLIGHT 0x01
+#define SIGNALFLARE_NO_SMOKE 0x02
+#define SIGNALFLARE_INFINITE 0x04
+#define SIGNALFLARE_DURATION 5.0f
+
+//=============================================================================
+//
+// Signal Flare Class
+//
+class CSignalFlare : public CBaseAnimating
+{
+
+ DECLARE_CLASS( CSignalFlare, CBaseAnimating );
+
+public:
+
+ DECLARE_DATADESC();
+ DECLARE_SERVERCLASS();
+
+ CSignalFlare( void );
+
+ static CSignalFlare *Create( Vector vecOrigin, QAngle vecAngles, CBaseEntity *pOwner, float lifetime );
+
+ // Initialization
+ void Spawn( void );
+ void Precache( void );
+
+ // Think and Touch
+ void FlareThink( void );
+ void FlareTouch( CBaseEntity *pOther );
+
+public:
+
+ CBaseEntity *m_pOwner;
+ int m_nBounces; // how many times has this flare bounced?
+ CNetworkVar( float, m_flDuration ); // when will the flare burn out?
+ CNetworkVar( float, m_flScale );
+ float m_flNextDamage;
+
+ bool m_bFading;
+ CNetworkVar( bool, m_bLight );
+ CNetworkVar( bool, m_bSmoke );
+};
+
+EXTERN_SEND_TABLE( DT_SignalFlare );
+
+#endif // TF_FLARE_H \ No newline at end of file
diff --git a/game/server/tf2/tf_func_construction_yard.cpp b/game/server/tf2/tf_func_construction_yard.cpp
new file mode 100644
index 0000000..c2312e2
--- /dev/null
+++ b/game/server/tf2/tf_func_construction_yard.cpp
@@ -0,0 +1,235 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//
+//=============================================================================//
+#include "cbase.h"
+#include "tf_func_construction_yard.h"
+#include "tf_team.h"
+
+//-----------------------------------------------------------------------------
+// Purpose: Defines an area from which resources can be collected
+//-----------------------------------------------------------------------------
+class CFuncConstructionYard : public CBaseEntity
+{
+ DECLARE_CLASS( CFuncConstructionYard, CBaseEntity );
+public:
+ CFuncConstructionYard();
+
+ DECLARE_DATADESC();
+ DECLARE_SERVERCLASS();
+
+ virtual void Spawn( void );
+ virtual void UpdateOnRemove( void );
+ virtual void Precache( void );
+ virtual void Activate( void );
+
+ // Inputs
+ void InputSetActive( inputdata_t &inputdata );
+ void InputSetInactive( inputdata_t &inputdata );
+ void InputToggleActive( inputdata_t &inputdata );
+
+ void SetActive( bool bActive );
+ bool GetActive() const;
+
+ bool IsEmpty( void );
+ bool PointIsWithin( const Vector &vecPoint );
+
+ // need to transmit to players who are in commander mode
+ int UpdateTransmitState() { return SetTransmitState( FL_EDICT_FULLCHECK); }
+ int ShouldTransmit( const CCheckTransmitInfo *pInfo );
+
+private:
+ bool m_bActive;
+};
+
+LINK_ENTITY_TO_CLASS( func_construction_yard, CFuncConstructionYard);
+
+BEGIN_DATADESC( CFuncConstructionYard )
+
+ DEFINE_INPUTFUNC( FIELD_VOID, "SetActive", InputSetActive ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "SetInactive", InputSetInactive ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "ToggleActive", InputToggleActive ),
+
+END_DATADESC()
+
+
+IMPLEMENT_SERVERCLASS_ST(CFuncConstructionYard, DT_FuncConstructionYard)
+END_SEND_TABLE();
+
+PRECACHE_REGISTER( func_construction_yard );
+
+// List of construction yards for fast lookup
+typedef CHandle<CFuncConstructionYard> YardHandle;
+CUtlVector< YardHandle > g_hConstructionYards;
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CFuncConstructionYard::CFuncConstructionYard()
+{
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Initializes the resource zone
+//-----------------------------------------------------------------------------
+void CFuncConstructionYard::Spawn( void )
+{
+ SetSolid( SOLID_BSP );
+ AddSolidFlags( FSOLID_TRIGGER );
+ SetMoveType( MOVETYPE_NONE );
+ AddEffects( EF_NODRAW );
+ SetModel( STRING( GetModelName() ) );
+ m_bActive = false;
+
+ YardHandle hYard;
+ hYard = this;
+ g_hConstructionYards.AddToTail( hYard );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CFuncConstructionYard::UpdateOnRemove( void )
+{
+ YardHandle hYard;
+ hYard = this;
+ g_hConstructionYards.FindAndRemove( hYard );
+
+ BaseClass::UpdateOnRemove();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CFuncConstructionYard::Precache( void )
+{
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: See if we've got a gather point specified
+//-----------------------------------------------------------------------------
+void CFuncConstructionYard::Activate( void )
+{
+ BaseClass::Activate();
+ SetActive( true );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CFuncConstructionYard::InputSetActive( inputdata_t &inputdata )
+{
+ SetActive( true );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CFuncConstructionYard::InputSetInactive( inputdata_t &inputdata )
+{
+ SetActive( false );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CFuncConstructionYard::InputToggleActive( inputdata_t &inputdata )
+{
+ if ( m_bActive )
+ {
+ SetActive( false );
+ }
+ else
+ {
+ SetActive( true );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CFuncConstructionYard::SetActive( bool bActive )
+{
+ m_bActive = bActive;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CFuncConstructionYard::GetActive() const
+{
+ return m_bActive;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Return true if the specified point is within this zone
+//-----------------------------------------------------------------------------
+bool CFuncConstructionYard::PointIsWithin( const Vector &vecPoint )
+{
+ return CollisionProp()->IsPointInBounds( vecPoint );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Transmit this to all players who are in commander mode
+//-----------------------------------------------------------------------------
+int CFuncConstructionYard::ShouldTransmit( const CCheckTransmitInfo *pInfo )
+{
+ // Team rules may tell us that we should
+ CBaseEntity* pRecipientEntity = CBaseEntity::Instance( pInfo->m_pClientEnt );
+ Assert( pRecipientEntity->IsPlayer() );
+
+ CBasePlayer *pPlayer = (CBasePlayer*)pRecipientEntity;
+ if ( pPlayer->GetTeam() )
+ {
+ if (pPlayer->GetTeam()->ShouldTransmitToPlayer( pPlayer, this ))
+ return FL_EDICT_ALWAYS;
+ }
+
+ return FL_EDICT_DONTSEND;
+}
+
+
+//-----------------------------------------------------------------------------
+// Does a construction yard (or lack of one) prevent us from building?
+//-----------------------------------------------------------------------------
+bool PointInConstructionYard( const Vector &vecBuildOrigin )
+{
+ // Find out whether we're in a construction yard or not
+ for ( int i = 0; i < g_hConstructionYards.Count(); i++ )
+ {
+ CFuncConstructionYard *pYard = g_hConstructionYards[i];
+ if ( pYard && pYard->PointIsWithin( vecBuildOrigin ) )
+ return true;
+ }
+
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool ConstructionYardPreventsBuild( CBaseObject *pObject, const Vector &vecBuildOrigin )
+{
+ bool bMustBuildInYard = pObject->MustBeBuiltInConstructionYard();
+
+ // Find out whether we're in a resource zone or not
+ if( PointInConstructionYard( vecBuildOrigin ) )
+ {
+ if( !pObject->MustNotBeBuiltInConstructionYard() )
+ return false; // An object that must be built in a yard is in a yard.
+ else
+ return true; // An object that can't be built in a yard is in a yard.
+ }
+
+ // If we have to be built in a construction yard, we're not in one
+ if ( bMustBuildInYard )
+ return true;
+
+ return false;
+}
diff --git a/game/server/tf2/tf_func_construction_yard.h b/game/server/tf2/tf_func_construction_yard.h
new file mode 100644
index 0000000..1dc6c72
--- /dev/null
+++ b/game/server/tf2/tf_func_construction_yard.h
@@ -0,0 +1,30 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Place where we can build vehicles
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef TF_FUNC_CONSTRUCTION_YARD_H
+#define TF_FUNC_CONSTRUCTION_YARD_H
+
+#ifdef _WIN32
+#pragma once
+#endif
+
+class CBaseObject;
+class Vector;
+
+//-----------------------------------------------------------------------------
+// Is a given point contained within any construction yard?
+//-----------------------------------------------------------------------------
+bool PointInConstructionYard( const Vector &vecBuildOrigin );
+
+//-----------------------------------------------------------------------------
+// Does a construction yard (or lack of one) prevent us from building?
+//-----------------------------------------------------------------------------
+
+bool ConstructionYardPreventsBuild( CBaseObject *pObject, const Vector &vecBuildOrigin );
+
+
+#endif // TF_FUNC_CONSTRUCTION_YARD_H
diff --git a/game/server/tf2/tf_func_mass_teleport.cpp b/game/server/tf2/tf_func_mass_teleport.cpp
new file mode 100644
index 0000000..2571841
--- /dev/null
+++ b/game/server/tf2/tf_func_mass_teleport.cpp
@@ -0,0 +1,368 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//
+//=============================================================================//
+#include "cbase.h"
+#include "tf_func_mass_teleport.h"
+#include "tf_team.h"
+#include "basetfvehicle.h"
+#include "team_spawnpoint.h"
+#include "NDebugOverlay.h"
+#include "tf_vehicle_teleport_station.h"
+#include "tf_gamerules.h"
+
+LINK_ENTITY_TO_CLASS( func_mass_teleport, CFuncMassTeleport);
+
+BEGIN_DATADESC( CFuncMassTeleport )
+
+ // Data
+ DEFINE_FIELD(m_hTargetEntity, FIELD_EHANDLE),
+
+ // Keys
+ DEFINE_KEYFIELD_NOT_SAVED( m_bMCV, FIELD_BOOLEAN, "MCV" ),
+
+ // Inputs
+ DEFINE_INPUTFUNC( FIELD_VOID, "DoTeleport", InputDoTeleport ),
+
+ // Outputs
+ DEFINE_OUTPUT( m_OnTeleport, "OnTeleport" ),
+ DEFINE_OUTPUT( m_OnActive, "OnActive" ),
+ DEFINE_OUTPUT( m_OnInactive, "OnInactive" ),
+
+END_DATADESC()
+
+PRECACHE_REGISTER( func_mass_teleport );
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CFuncMassTeleport::CFuncMassTeleport()
+{
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Initializes the resource zone
+//-----------------------------------------------------------------------------
+void CFuncMassTeleport::Spawn( void )
+{
+ SetSolid( SOLID_BSP );
+ AddSolidFlags( FSOLID_TRIGGER );
+ SetMoveType( MOVETYPE_NONE );
+ AddEffects( EF_NODRAW );
+ SetModel( STRING( GetModelName() ) );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CFuncMassTeleport::Precache( void )
+{
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CFuncMassTeleport::Activate( void )
+{
+ m_hTargetEntity = GetNextTarget();
+ BaseClass::Activate();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CFuncMassTeleport::MassTeleport(
+ CTFTeam *pTeam,
+ Vector vSourceMins,
+ Vector vSourceMaxs,
+ Vector vDest,
+ bool bSwap,
+ bool bPlayersOnly,
+ EHANDLE hMCV,
+ bool bTelefragDestination )
+{
+ bSwap = false;
+
+ Vector vSource = vSourceMins + ( ( vSourceMaxs - vSourceMins ) / 2.f );
+ vSource.z = vSourceMins.z;
+/*
+ if( bTelefragDestination )
+ {
+ Vector vDestMins = vDest - ( ( vSourceMaxs - vSourceMins ) / 2.f );
+ Vector vDestMaxs = vDest + ( ( vSourceMaxs - vSourceMins ) / 2.f );
+ MassTelefrag(vDestMins,vDestMaxs);
+ }
+*/
+ CBaseEntity *pList[1024];
+ int OriginalSolidFlags[1024];
+
+ int count = UTIL_EntitiesInBox( pList, 1024, vSourceMins, vSourceMaxs, FL_CLIENT|FL_NPC|FL_OBJECT );
+
+ for( int i = 0; i < count; i++ )
+ {
+ CBaseEntity* pEnt = pList[i];
+
+ if ( !pEnt )
+ continue;
+
+ if ( bPlayersOnly )
+ {
+ CBaseTFPlayer *pTestPlayer = dynamic_cast< CBaseTFPlayer* >( pEnt );
+ if ( pTestPlayer )
+ {
+ // If it's players only, then only teleport players attached to the specified MCV.
+ if ( pTestPlayer->GetSelectedMCV() != hMCV.Get() )
+ {
+ pList[i] = NULL;
+ continue;
+ }
+ }
+ else
+ {
+ pList[i] = NULL;
+ continue;
+ }
+ }
+
+
+ if( pEnt->GetSolidFlags() & FSOLID_NOT_SOLID )
+ {
+ pList[i] = NULL;
+ continue;
+ }
+
+ // Only teleport team members
+ if ( pTeam && ((CTFTeam*)pEnt->GetTeam()) != pTeam )
+ {
+ pList[i] = NULL;
+ continue;
+ }
+
+ // Only teleport items at the root of hierarchies.
+ if( pEnt->GetMoveParent() )
+ {
+ pList[i] = NULL;
+ continue;
+ }
+
+ CBaseObject* pObj = dynamic_cast<CBaseObject*>(pEnt);
+
+ // NJS: dont teleport map placed objects.
+ if( pObj )
+ {
+ if ( pObj->WasMapPlaced() )
+ {
+ pList[i] = NULL;
+ continue;
+ }
+ }
+
+ OriginalSolidFlags[i] = pEnt->GetSolidFlags();
+ pEnt->AddSolidFlags( FSOLID_NOT_SOLID);
+
+ Vector vEntOrigin = pEnt->GetAbsOrigin();
+ Vector vDelta = vEntOrigin - vSource;
+ Vector vEntTarget = vDest + vDelta + Vector(0,0,3); // Teleport them a little above the ground, to allow for the suspension drop.
+ QAngle aEntAngles= pEnt->GetAbsAngles();
+ Vector vEntVelocity;
+ pEnt->GetVelocity(&vEntVelocity);
+
+ // If it's a player, trace down from the top of the box to find a safe place
+ if ( bPlayersOnly && pEnt->IsPlayer() )
+ {
+ // Start the target up the top of the dest box
+ Vector vecTop = vEntTarget;
+ vecTop.z = (vDest.z + (vSourceMaxs.z - vSourceMins.z));
+ // Trace a hull down
+ trace_t tr;
+ UTIL_TraceEntity( pEnt, vecTop, vEntTarget, MASK_SOLID, NULL, COLLISION_GROUP_PLAYER_MOVEMENT, &tr );
+ vEntTarget = tr.endpos + Vector(0,0,3);
+ }
+
+ /*
+ if( ! bSwap )
+ {
+ Vector origin(0,0,0);
+ if ( !EntityPlacementTest( pEnt, vEntTarget, origin, true ) )
+ {
+ UTIL_Remove( pEnt );
+ return;
+ }
+
+ vEntTarget = origin;
+ }
+ */
+
+ pEnt->Teleport( &vEntTarget, &aEntAngles, &vEntVelocity );
+ }
+
+ // If I am to swap source and destination, do it while the current entites from this teleport are incorperal.
+ if( bSwap )
+ {
+ MassTeleport( pTeam, vSourceMins - vSource + vDest, vSourceMaxs - vSource + vDest, vSource, false, bPlayersOnly, hMCV, false );
+ }
+
+ for( i = 0; i < count; i++ )
+ {
+ CBaseEntity* pEnt = pList[i];
+
+ if( !pEnt )
+ continue;
+
+ pEnt->SetSolidFlags(OriginalSolidFlags[i]);
+ }
+}
+
+void CFuncMassTeleport::MassTelefrag( Vector vMins, Vector vMaxs )
+{
+ CBaseEntity *pList[4096];
+
+ int count = UTIL_EntitiesInBox( pList, 4096, vMins, vMaxs, FL_CLIENT|FL_NPC|FL_OBJECT );
+
+ for( int i = 0; i < count; i++ )
+ {
+ CBaseEntity* pEnt = pList[i];
+
+ if ( !pEnt )
+ continue;
+
+
+ if( pEnt->GetSolidFlags() & FSOLID_NOT_SOLID )
+ {
+ pList[i] = NULL;
+ continue;
+ }
+
+ // Only teleport items at the root of hierarchies.
+ if( pEnt->GetMoveParent() )
+ {
+ pList[i] = NULL;
+ continue;
+ }
+
+ CBaseObject* pObj = dynamic_cast<CBaseObject*>(pEnt);
+
+ // dont frag map placed objects.
+ if( !pObj )
+ continue;
+
+ if ( pObj->WasMapPlaced() )
+ {
+ pList[i] = NULL;
+ continue;
+ }
+
+ // Do a massive amount of damage (DMG_GENERIC so there's no physics force)
+ CTakeDamageInfo info( this, this, 99999, DMG_GENERIC );
+
+ pObj->TakeDamage(info);
+ }
+
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CFuncMassTeleport::DoTeleport(
+ CTFTeam *pTeam,
+ Vector vSourceMins,
+ Vector vSourceMaxs,
+ Vector vDest,
+ bool bSwap,
+ bool bPlayersOnly,
+ EHANDLE hMCV )
+{
+ bool bTelefragDestination = hMCV ? false : true;
+ MassTeleport( pTeam, vSourceMins, vSourceMaxs, vDest, bSwap, bPlayersOnly, hMCV, bTelefragDestination );
+
+ // Fire our output
+ m_OnTeleport.FireOutput( this, this );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CFuncMassTeleport::InputDoTeleport( inputdata_t &inputdata )
+{
+ // If I have MCVs attached, tell them to teleport.
+ for ( int i = 0; i < m_MCVs.Count(); i++ )
+ {
+ m_MCVs[i]->DoTeleport();
+ }
+
+ // If we have a target entity, teleport to it
+ if ( m_hTargetEntity )
+ {
+ Vector vecAbsMins, vecAbsMaxs;
+ CollisionProp()->WorldSpaceAABB( &vecAbsMins, &vecAbsMaxs );
+ DoTeleport( ((CTFTeam*)GetTeam()), vecAbsMins, vecAbsMaxs, m_hTargetEntity->GetAbsOrigin(), true, false, EHANDLE() );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Return true if the specified point is within this zone
+//-----------------------------------------------------------------------------
+bool CFuncMassTeleport::PointIsWithin( const Vector &vecPoint )
+{
+ return CollisionProp()->IsPointInBounds( vecPoint );
+}
+
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Transmit this to all players who are in commander mode
+//-----------------------------------------------------------------------------
+int CFuncMassTeleport::ShouldTransmit( const CCheckTransmitInfo *pInfo )
+{
+ // Team rules may tell us that we should
+ CBaseEntity* pRecipientEntity = CBaseEntity::Instance( pInfo->m_pClientEnt );
+
+ Assert( pRecipientEntity->IsPlayer() );
+
+ CBasePlayer *pPlayer = (CBasePlayer*)pRecipientEntity;
+ if ( pPlayer->GetTeam() )
+ {
+ if (pPlayer->GetTeam()->ShouldTransmitToPlayer( pPlayer, this ))
+ return FL_EDICT_ALWAYS;
+ }
+
+ return FL_EDICT_DONTSEND;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CFuncMassTeleport::AddMCV( CBaseEntity *pMCV )
+{
+ // Are we going active?
+ if ( !m_MCVs.Count() )
+ {
+ // Fire our output
+ m_OnActive.FireOutput( this, this );
+ }
+
+ EHANDLE hMCV;
+ hMCV = pMCV;
+ m_MCVs.AddToTail( hMCV );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CFuncMassTeleport::RemoveMCV( CBaseEntity *pMCV )
+{
+ EHANDLE hMCV;
+ hMCV = pMCV;
+ m_MCVs.FindAndRemove( hMCV );
+
+ // Have we just gone inactive?
+ if ( !m_MCVs.Count() )
+ {
+ // Fire our output
+ m_OnInactive.FireOutput( this, this );
+ }
+} \ No newline at end of file
diff --git a/game/server/tf2/tf_func_mass_teleport.h b/game/server/tf2/tf_func_mass_teleport.h
new file mode 100644
index 0000000..c9d178c
--- /dev/null
+++ b/game/server/tf2/tf_func_mass_teleport.h
@@ -0,0 +1,72 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: An trigger that teleports its contents to a target when triggered.
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef TF_FUNC_MASS_TELEPORT_H
+#define TF_FUNC_MASS_TELEPORT_H
+
+#ifdef _WIN32
+#pragma once
+#endif
+
+class CBaseObject;
+class Vector;
+class CTFTeam;
+class CVehicleTeleportStation;
+
+//-----------------------------------------------------------------------------
+// Purpose: Defines an area from which resources can be collected
+//-----------------------------------------------------------------------------
+class CFuncMassTeleport : public CBaseEntity
+{
+ DECLARE_CLASS( CFuncMassTeleport, CBaseEntity );
+public:
+ CFuncMassTeleport();
+
+ DECLARE_DATADESC();
+
+ virtual void Spawn( void );
+ virtual void Precache( void );
+ virtual void Activate(void);
+
+ void DoTeleport( CTFTeam *pTeam, Vector vSourceMins, Vector vSourceMaxs, Vector vDest, bool bSwap, bool bPlayersOnly, EHANDLE hMCV );
+
+ // Inputs
+ void InputDoTeleport( inputdata_t &inputdata );
+
+ bool PointIsWithin( const Vector &vecPoint );
+
+ // need to transmit to players who are in commander mode
+ int UpdateTransmitState() { return SetTransmitState( FL_EDICT_FULLCHECK); }
+ int ShouldTransmit( const CCheckTransmitInfo *pInfo );
+
+ // Return true if this teleport's for use by MCVs in the field
+ bool IsMCVTeleport( void ) { return m_bMCV; }
+ void AddMCV( CBaseEntity *pMCV );
+ void RemoveMCV( CBaseEntity *pMCV );
+
+private:
+
+ void MassTeleport( CTFTeam *pTeam, Vector vSourceMins, Vector vSourceMaxs, Vector vDest, bool bSwap, bool bPlayersOnly, EHANDLE hMCV, bool bTelefragDestination );
+ void MassTelefrag( Vector vMins, Vector vMaxs );
+
+
+ // Entity to teleport to.
+ EHANDLE m_hTargetEntity;
+
+ // If true, this teleport's designed to teleport players to MCVs in the field
+ bool m_bMCV;
+ typedef CHandle<CVehicleTeleportStation> hMCVHandle;
+ CUtlVector<hMCVHandle> m_MCVs;
+
+ // Outputs
+ COutputEvent m_OnTeleport;
+ COutputEvent m_OnActive;
+ COutputEvent m_OnInactive;
+};
+
+
+#endif // TF_FUNC_MASS_TELEPORT_H
diff --git a/game/server/tf2/tf_func_no_build.cpp b/game/server/tf2/tf_func_no_build.cpp
new file mode 100644
index 0000000..db6f8b2
--- /dev/null
+++ b/game/server/tf2/tf_func_no_build.cpp
@@ -0,0 +1,225 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//
+//=============================================================================//
+#include "cbase.h"
+#include "tf_func_no_build.h"
+#include "tf_team.h"
+#include "NDebugOverlay.h"
+
+//-----------------------------------------------------------------------------
+// Purpose: Defines an area from which resources can be collected
+//-----------------------------------------------------------------------------
+class CFuncNoBuild : public CBaseEntity
+{
+ DECLARE_CLASS( CFuncNoBuild, CBaseEntity );
+public:
+ CFuncNoBuild();
+
+ DECLARE_DATADESC();
+
+ virtual void Spawn( void );
+ virtual void Precache( void );
+ virtual void Activate( void );
+
+ // Inputs
+ void InputSetActive( inputdata_t &inputdata );
+ void InputSetInactive( inputdata_t &inputdata );
+ void InputToggleActive( inputdata_t &inputdata );
+
+ void SetActive( bool bActive );
+ bool GetActive() const;
+
+ bool IsEmpty( void );
+ bool PointIsWithin( const Vector &vecPoint );
+
+ bool PreventsBuildOf( int iObjectType );
+
+ // need to transmit to players who are in commander mode
+ int UpdateTransmitState() { return SetTransmitState( FL_EDICT_FULLCHECK ); }
+ int ShouldTransmit( const CCheckTransmitInfo *pInfo );
+
+private:
+ bool m_bActive;
+ bool m_bOnlyPreventTowers;
+};
+
+LINK_ENTITY_TO_CLASS( func_no_build, CFuncNoBuild);
+
+BEGIN_DATADESC( CFuncNoBuild )
+
+ // keys
+ DEFINE_KEYFIELD_NOT_SAVED( m_bOnlyPreventTowers, FIELD_BOOLEAN, "OnlyPreventTowers" ),
+
+ // inputs
+ DEFINE_INPUTFUNC( FIELD_VOID, "SetActive", InputSetActive ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "SetInactive", InputSetInactive ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "ToggleActive", InputToggleActive ),
+
+END_DATADESC()
+
+
+PRECACHE_REGISTER( func_no_build );
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CFuncNoBuild::CFuncNoBuild()
+{
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Initializes the resource zone
+//-----------------------------------------------------------------------------
+void CFuncNoBuild::Spawn( void )
+{
+ SetSolid( SOLID_BSP );
+ AddSolidFlags( FSOLID_TRIGGER );
+ SetMoveType( MOVETYPE_NONE );
+ AddEffects( EF_NODRAW );
+ SetModel( STRING( GetModelName() ) );
+ m_bActive = true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CFuncNoBuild::Precache( void )
+{
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: See if we've got a gather point specified
+//-----------------------------------------------------------------------------
+void CFuncNoBuild::Activate( void )
+{
+ BaseClass::Activate();
+ SetActive( true );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CFuncNoBuild::InputSetActive( inputdata_t &inputdata )
+{
+ SetActive( true );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CFuncNoBuild::InputSetInactive( inputdata_t &inputdata )
+{
+ SetActive( false );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CFuncNoBuild::InputToggleActive( inputdata_t &inputdata )
+{
+ if ( m_bActive )
+ {
+ SetActive( false );
+ }
+ else
+ {
+ SetActive( true );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CFuncNoBuild::SetActive( bool bActive )
+{
+ m_bActive = bActive;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CFuncNoBuild::GetActive() const
+{
+ return m_bActive;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Return true if the specified point is within this zone
+//-----------------------------------------------------------------------------
+bool CFuncNoBuild::PointIsWithin( const Vector &vecPoint )
+{
+ return CollisionProp()->IsPointInBounds( vecPoint );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Return true if this zone prevents building of the specified type
+//-----------------------------------------------------------------------------
+bool CFuncNoBuild::PreventsBuildOf( int iObjectType )
+{
+ if ( m_bOnlyPreventTowers )
+ return ( iObjectType == OBJ_TOWER );
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Transmit this to all players who are in commander mode
+//-----------------------------------------------------------------------------
+int CFuncNoBuild::ShouldTransmit( const CCheckTransmitInfo *pInfo )
+{
+ // Team rules may tell us that we should
+ CBaseEntity* pRecipientEntity = CBaseEntity::Instance( pInfo->m_pClientEnt );
+ Assert( pRecipientEntity->IsPlayer() );
+
+ CBasePlayer *pPlayer = (CBasePlayer*)pRecipientEntity;
+ if ( pPlayer->GetTeam() )
+ {
+ if (pPlayer->GetTeam()->ShouldTransmitToPlayer( pPlayer, this ))
+ return FL_EDICT_ALWAYS;
+ }
+
+ return FL_EDICT_DONTSEND;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Does a nobuild zone prevent us from building?
+//-----------------------------------------------------------------------------
+bool PointInNoBuild( CBaseObject *pObject, const Vector &vecBuildOrigin )
+{
+ // Find out whether we're in a resource zone or not
+ CBaseEntity *pEntity = NULL;
+ while ((pEntity = gEntList.FindEntityByClassname( pEntity, "func_no_build" )) != NULL)
+ {
+ CFuncNoBuild *pNoBuild = (CFuncNoBuild *)pEntity;
+
+ // Are we within this no build?
+ if ( pNoBuild->GetActive() && pNoBuild->PointIsWithin( vecBuildOrigin ) )
+ {
+ if ( pNoBuild->PreventsBuildOf( pObject->GetType() ) )
+ return true; // prevent building.
+ }
+ }
+
+ return false; // Building should be ok.
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Return true if a nobuild zone prevents building this object at the specified origin
+//-----------------------------------------------------------------------------
+bool NoBuildPreventsBuild( CBaseObject *pObject, const Vector &vecBuildOrigin )
+{
+ // Find out whether we're in a no build zone or not
+ if ( PointInNoBuild( pObject, vecBuildOrigin ) )
+ {
+ return true;
+ }
+
+ return false;
+}
diff --git a/game/server/tf2/tf_func_no_build.h b/game/server/tf2/tf_func_no_build.h
new file mode 100644
index 0000000..071e0ca
--- /dev/null
+++ b/game/server/tf2/tf_func_no_build.h
@@ -0,0 +1,30 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Place where we can't build things.
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef TF_FUNC_NO_BUILD_H
+#define TF_FUNC_NO_BUILD_H
+
+#ifdef _WIN32
+#pragma once
+#endif
+
+class CBaseObject;
+class Vector;
+
+//-----------------------------------------------------------------------------
+// Is a given point contained within any construction yard?
+//-----------------------------------------------------------------------------
+bool PointInNoBuild( const Vector &vecBuildOrigin );
+
+//-----------------------------------------------------------------------------
+// Does a construction yard (or lack of one) prevent us from building?
+//-----------------------------------------------------------------------------
+
+bool NoBuildPreventsBuild( CBaseObject *pObject, const Vector &vecBuildOrigin );
+
+
+#endif // TF_FUNC_NO_BUILD_H
diff --git a/game/server/tf2/tf_func_resource.cpp b/game/server/tf2/tf_func_resource.cpp
new file mode 100644
index 0000000..cfb0d74
--- /dev/null
+++ b/game/server/tf2/tf_func_resource.cpp
@@ -0,0 +1,683 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//
+//=============================================================================//
+#include "cbase.h"
+#include "EntityOutput.h"
+#include "EntityList.h"
+#include "tf_player.h"
+#include "tf_func_resource.h"
+#include "tf_team.h"
+#include "tf_basecombatweapon.h"
+#include "gamerules.h"
+#include "ammodef.h"
+#include "tf_obj.h"
+#include "resource_chunk.h"
+#include "vstdlib/random.h"
+#include "engine/IEngineSound.h"
+#include "team_messages.h"
+#include "tf_stats.h"
+
+LINK_ENTITY_TO_CLASS( trigger_resourcezone, CResourceZone);
+
+BEGIN_DATADESC( CResourceZone )
+
+ // keys
+ DEFINE_KEYFIELD_NOT_SAVED( m_nResourcesLeft, FIELD_INTEGER, "ResourceAmount" ),
+ DEFINE_KEYFIELD_NOT_SAVED( m_iMaxChunks, FIELD_INTEGER, "ResourceChunks" ),
+ DEFINE_KEYFIELD_NOT_SAVED( m_flResourceRate, FIELD_FLOAT, "ResourceRate" ),
+ DEFINE_KEYFIELD_NOT_SAVED( m_flChunkValueMin, FIELD_FLOAT, "ChunkValueMin" ),
+ DEFINE_KEYFIELD_NOT_SAVED( m_flChunkValueMax, FIELD_FLOAT, "ChunkValueMax" ),
+
+ // inputs
+ DEFINE_INPUTFUNC( FIELD_INTEGER, "SetAmount", InputSetAmount ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "ResetAmount", InputResetAmount ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "SetActive", InputSetActive ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "SetInactive", InputSetInactive ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "ToggleActive", InputToggleActive ),
+
+ // outputs
+ DEFINE_OUTPUT( m_OnEmpty, "OnEmpty" ),
+
+END_DATADESC()
+
+
+IMPLEMENT_SERVERCLASS_ST(CResourceZone, DT_ResourceZone)
+ SendPropFloat( SENDINFO( m_flClientResources ), 8, SPROP_UNSIGNED, 0.0f, 1.0f ),
+ SendPropInt( SENDINFO( m_nResourcesLeft ), 20, SPROP_UNSIGNED ),
+END_SEND_TABLE();
+
+PRECACHE_REGISTER( trigger_resourcezone );
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CResourceZone::CResourceZone()
+{
+#ifdef _DEBUG
+ m_vecGatherPoint.Init();
+ m_angGatherPoint.Init();
+#endif
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Initializes the resource zone
+//-----------------------------------------------------------------------------
+void CResourceZone::Spawn( void )
+{
+ SetSolid( SOLID_BSP );
+ AddSolidFlags( FSOLID_TRIGGER );
+ SetMoveType( MOVETYPE_NONE );
+ AddEffects( EF_NODRAW );
+ SetModel( STRING( GetModelName() ) );
+
+ if ( !m_nResourcesLeft )
+ {
+ m_nResourcesLeft = 10000;
+ }
+
+ m_nMaxResources = m_nResourcesLeft;
+ m_flClientResources = 1;
+
+ SetActive( false );
+ m_vecGatherPoint = WorldSpaceCenter();
+ m_angGatherPoint = vec3_angle;
+ m_iTeamGathering = -1;
+ m_aSpawners.Purge();
+ m_hResourcePump = NULL;
+
+ if ( !m_iMaxChunks )
+ m_iMaxChunks = 5;
+ if ( !m_flChunkValueMin )
+ m_flChunkValueMin = 20;
+ if ( !m_flChunkValueMax )
+ m_flChunkValueMax = 60;
+
+ m_flBaseResourceRate = m_flResourceRate;
+
+ m_flRespawnTimeModifier = 1.0f;
+
+ m_flTestTime = 0;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CResourceZone::Precache( void )
+{
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: See if we've got a gather point specified
+//-----------------------------------------------------------------------------
+void CResourceZone::Activate( void )
+{
+ BaseClass::Activate();
+
+ if (m_target != NULL_STRING)
+ {
+ CBaseEntity *pEnt = gEntList.FindEntityByName( NULL, m_target );
+ if ( pEnt )
+ {
+ m_vecGatherPoint = pEnt->GetLocalOrigin();
+ m_angGatherPoint = pEnt->GetLocalAngles();
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CResourceZone::InputSetAmount( inputdata_t &inputdata )
+{
+ m_nMaxResources = m_nResourcesLeft = inputdata.value.Int();
+ RecomputeClientResources();
+
+ // We may have just been reactivated
+ if ( m_nResourcesLeft )
+ {
+ SetActive( true );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CResourceZone::InputResetAmount( inputdata_t &inputdata )
+{
+ m_nResourcesLeft = m_nMaxResources;
+ m_flClientResources = 1;
+
+ // We may have just been reactivated
+ if ( m_nResourcesLeft )
+ {
+ SetActive( true );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CResourceZone::InputSetActive( inputdata_t &inputdata )
+{
+ SetActive( true );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CResourceZone::InputSetInactive( inputdata_t &inputdata )
+{
+ SetActive( false );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CResourceZone::InputToggleActive( inputdata_t &inputdata )
+{
+ if ( GetActive() )
+ {
+ SetActive( false );
+ }
+ else
+ {
+ SetActive( true );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CResourceZone::SetActive( bool bActive )
+{
+ // Going active?
+ if ( !m_bActive && bActive )
+ {
+ // Start our ambient sound
+ //EmitAmbientSound( this, Center(), "ResourceZone.AmbientActiveSound" );
+ }
+ else if ( m_bActive && !bActive )
+ {
+ // Going inactive
+
+ // Stop our sound loop
+ //StopSound( "ResourceZone.AmbientActiveSound" );
+ }
+
+ m_bActive = bActive;
+
+ // Tell all my spawners
+ for ( int i = 0; i < m_aSpawners.Size(); i++ )
+ {
+ m_aSpawners[i]->SetActive( bActive );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CResourceZone::GetActive() const
+{
+ return m_bActive;
+}
+
+//-----------------------------------------------------------------------------
+// Zone increasing....
+//-----------------------------------------------------------------------------
+void CResourceZone::AddZoneIncreaser( float rate )
+{
+ Assert( rate != 0.0f );
+
+ m_flRespawnTimeModifier *= rate;
+ m_flResourceRate = m_flBaseResourceRate / m_flRespawnTimeModifier;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CResourceZone::RemoveZoneIncreaser( float rate )
+{
+ Assert( rate != 0.0f );
+
+ m_flRespawnTimeModifier /= rate;
+ m_flResourceRate = m_flBaseResourceRate / m_flRespawnTimeModifier;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Removes resources from the zone, and returns true if it's now empty
+//-----------------------------------------------------------------------------
+bool CResourceZone::RemoveResources( int nResourcesRemoved )
+{
+ if ( IsEmpty() )
+ return true;
+
+ m_nResourcesLeft = MAX(0, m_nResourcesLeft - nResourcesRemoved);
+ RecomputeClientResources();
+
+ // If I'm out of resources, destroy my resource spawners
+ if ( IsEmpty() )
+ {
+ // Tell all existing chunks to stay forever
+ int i;
+ for ( i = 0; i < m_aChunks.Size(); i++ )
+ {
+ // Clear them removal think
+ m_aChunks[i]->SetThink( NULL );
+ }
+
+ SetActive( false );
+
+ // Tell teams about it
+ for ( i = 0; i < GetNumberOfTeams(); i++ )
+ {
+ CTFTeam *pTeam = GetGlobalTFTeam( i );
+ pTeam->PostMessage( TEAMMSG_RESOURCE_ZONE_EMPTIED );
+ }
+
+ // Fire my output
+ m_OnEmpty.FireOutput( NULL,this );
+ return true;
+ }
+
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Return true if this zone is empty
+//-----------------------------------------------------------------------------
+bool CResourceZone::IsEmpty( void )
+{
+ // Inactive zones pretend to be empty, so nothing tries to do anything with them
+ if ( !GetActive() )
+ return true;
+
+ return (m_nResourcesLeft <= 0);
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Return true if the specified point is within this zone
+//-----------------------------------------------------------------------------
+bool CResourceZone::PointIsWithin( const Vector &vecPoint )
+{
+ return CollisionProp()->IsPointInBounds( vecPoint );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Resource zones have 1 build point
+//-----------------------------------------------------------------------------
+int CResourceZone::GetNumBuildPoints( void ) const
+{
+ return 1;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Return true if the specified object type can be built on this point
+//-----------------------------------------------------------------------------
+bool CResourceZone::CanBuildObjectOnBuildPoint( int iPoint, int iObjectType )
+{
+ ASSERT( iPoint <= GetNumBuildPoints() );
+
+ // Don't allow more than one pump
+ if ( m_hResourcePump.Get() )
+ return false;
+
+ // Only pumps can be built on zones
+ return ( iObjectType == OBJ_RESOURCEPUMP );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CResourceZone::GetBuildPoint( int iPoint, Vector &vecOrigin, QAngle &vecAngles )
+{
+ ASSERT( iPoint <= GetNumBuildPoints() );
+
+ // If we have a gather point, return it
+ if ( m_vecGatherPoint != WorldSpaceCenter() )
+ {
+ vecOrigin = m_vecGatherPoint;
+ }
+ else if ( m_aSpawners.Size() )
+ {
+ // Return the first resource spawner
+ vecOrigin = m_aSpawners[0]->GetAbsOrigin();
+ }
+ else
+ {
+ vecOrigin = GetAbsOrigin();
+ }
+
+ vecAngles = QAngle(0,0,0);
+ return true;
+}
+
+int CResourceZone::GetBuildPointAttachmentIndex( int iPoint ) const
+{
+ return 0;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CResourceZone::SetObjectOnBuildPoint( int iPoint, CBaseObject *pObject )
+{
+ m_hResourcePump = pObject;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+int CResourceZone::GetNumObjectsOnMe( void )
+{
+ if ( m_hResourcePump.Get() )
+ return 1;
+
+ return 0;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CBaseEntity *CResourceZone::GetFirstObjectOnMe( void )
+{
+ return m_hResourcePump;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CBaseObject *CResourceZone::GetObjectOfTypeOnMe( int iObjectType )
+{
+ if ( GetNumObjectsOnMe() == 1 )
+ {
+ CBaseObject *pObject = dynamic_cast<CBaseObject*>( m_hResourcePump.Get() );
+ if ( pObject )
+ {
+ if ( pObject->GetType() == iObjectType )
+ return pObject;
+ }
+ }
+
+ return NULL;
+}
+
+int CResourceZone::FindObjectOnBuildPoint( CBaseObject *pObject )
+{
+ if (m_hResourcePump == pObject)
+ return 0;
+ return -1;
+}
+
+void CResourceZone::GetExitPoint( CBaseEntity *pPlayer, int iPoint, Vector *pAbsOrigin, QAngle *pAbsAngles )
+{
+ Assert(0);
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CResourceZone::RemoveAllObjects( void )
+{
+ UTIL_Remove( m_hResourcePump );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Get the team that's gathering from this point
+//-----------------------------------------------------------------------------
+CTFTeam *CResourceZone::GetOwningTeam( void )
+{
+ if ( m_iTeamGathering == -1 )
+ return NULL;
+
+ return (CTFTeam*)GetGlobalTeam(m_iTeamGathering);
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CResourceZone::SetOwningTeam( int iTeamNumber )
+{
+ m_iTeamGathering = iTeamNumber;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Transmit this to all players who are in commander mode
+//-----------------------------------------------------------------------------
+int CResourceZone::ShouldTransmit( const CCheckTransmitInfo *pInfo )
+{
+ // Team rules may tell us that we should
+ CBaseEntity* pRecipientEntity = CBaseEntity::Instance( pInfo->m_pClientEnt );
+ Assert( pRecipientEntity->IsPlayer() );
+
+ CBasePlayer *pPlayer = (CBasePlayer*)pRecipientEntity;
+ if ( pPlayer->GetTeam() )
+ {
+ if (pPlayer->GetTeam()->ShouldTransmitToPlayer( pPlayer, this ))
+ return FL_EDICT_ALWAYS;
+ }
+
+ return FL_EDICT_DONTSEND;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Check to see if we should create any more resource chunks
+//-----------------------------------------------------------------------------
+bool CResourceZone::ShouldSpawnChunk( void )
+{
+ // Don't spawn chunks if we're outta resources
+ if ( IsEmpty() )
+ return false;
+
+ // Create a chunk if we're below our max
+ if ( m_aChunks.Size() >= m_iMaxChunks )
+ return false;
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CResourceZone::SpawnChunk( const Vector &vecOrigin )
+{
+ // ROBIN: Disabled for now
+ return;
+
+ TFStats()->IncrementStat( TF_STAT_RESOURCE_CHUNKS_SPAWNED, 1 );
+
+ // Create a resource chunk and add it to our list
+ Vector vecVelocity = Vector( random->RandomFloat( -100,100 ), random->RandomFloat( -100,100 ), random->RandomFloat( 300,600 ));
+ CResourceChunk *pChunk = CResourceChunk::Create( false, vecOrigin, vecVelocity );
+ pChunk->m_hZone = this;
+
+ // Add it to our list
+ m_aChunks.AddToTail( pChunk );
+
+ // Remove it's value from the zone
+ RemoveResources( pChunk->GetResourceValue() );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CResourceZone::RecomputeClientResources( )
+{
+ m_flClientResources = clamp( (float)m_nResourcesLeft / (float)m_nMaxResources, 0.0f, 1.0f );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CResourceZone::RemoveChunk( CResourceChunk *pChunk, bool bReturn )
+{
+ if (bReturn)
+ {
+ TFStats()->IncrementStat( TF_STAT_RESOURCE_CHUNKS_RETIRED, 1 );
+ }
+
+ m_aChunks.FindAndRemove( pChunk );
+
+ // If I'm being returned, re-add my value to the resource level of the zone
+ if ( bReturn )
+ {
+ m_nResourcesLeft += pChunk->GetResourceValue();
+ RecomputeClientResources();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CResourceZone::AddSpawner( CResourceSpawner *pSpawner )
+{
+ m_aSpawners.AddToTail( pSpawner );
+ pSpawner->SetActive( GetActive() );
+}
+
+//========================================================================================================================
+// RESOURCE CHUNK SPAWNER
+//========================================================================================================================
+LINK_ENTITY_TO_CLASS( env_resourcespawner, CResourceSpawner );
+PRECACHE_REGISTER( env_resourcespawner );
+
+BEGIN_DATADESC( CResourceSpawner )
+
+ // functions
+ DEFINE_FUNCTION( SpawnChunkThink ),
+
+END_DATADESC()
+
+
+IMPLEMENT_SERVERCLASS_ST(CResourceSpawner, DT_ResourceSpawner)
+ SendPropInt( SENDINFO( m_bActive ), 1, SPROP_UNSIGNED ),
+END_SEND_TABLE();
+
+// Resource Spawner Models
+char *sResourceSpawnerModel = "models/resources/resource_spawner_B.mdl";
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CResourceSpawner::Spawn( void )
+{
+ m_hZone = NULL;
+ m_bActive = false;
+ SetModel( sResourceSpawnerModel );
+
+ // Create the object in the physics system
+ /*
+ VPhysicsInitStatic();
+ */
+ SetMoveType( MOVETYPE_NONE );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CResourceSpawner::Precache( void )
+{
+ PrecacheModel( sResourceSpawnerModel );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Find my resource point
+//-----------------------------------------------------------------------------
+void CResourceSpawner::Activate( void )
+{
+ if ( m_target != NULL_STRING )
+ {
+ // Find my resource zone
+ CResourceZone *pZone = (CResourceZone*)gEntList.FindEntityByName( NULL, m_target );
+ if ( pZone )
+ {
+ m_hZone = pZone;
+ SetModel( sResourceSpawnerModel );
+ m_hZone->AddSpawner( this );
+ return;
+ }
+ }
+
+ Warning( "ERROR: Resource Spawner without a target resource zone specified.\n" );
+ UTIL_Remove( this );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CResourceSpawner::SetActive( bool bActive )
+{
+ // Going active?
+ if ( !m_bActive && bActive )
+ {
+ // Randomize the thinks a little to reduce network usage ong chunk spawning
+ SetNextThink( gpGlobals->curtime + m_hZone->GetResourceRate() + random->RandomFloat( 0.0, 1.0 ) );
+ SetThink( SpawnChunkThink );
+ RemoveEffects( EF_NODRAW );
+ }
+ else if ( m_bActive && !bActive )
+ {
+ // Going inactive
+ SetThink( NULL );
+ AddEffects( EF_NODRAW );
+ }
+
+ m_bActive = bActive;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Spawn a chunk from this spawner
+//-----------------------------------------------------------------------------
+void CResourceSpawner::SpawnChunkThink( void )
+{
+ // Lost our zone?
+ if ( !m_hZone )
+ {
+ SetActive( false );
+ return;
+ }
+
+ if ( m_hZone->ShouldSpawnChunk() )
+ {
+ // Start spawning events
+ EntityMessageBegin( this );
+ MessageEnd();
+
+ m_hZone->SpawnChunk( GetAbsOrigin() + Vector(0,0,64) );
+ }
+
+ // Randomize the thinks a little to reduce network usage on chunk spawning
+ SetNextThink( gpGlobals->curtime + m_hZone->GetResourceRate() + random->RandomFloat( 0.0, 1.0 ) );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Convert an amount of resources into a number of processed & unprocessed resource chunks
+//-----------------------------------------------------------------------------
+void ConvertResourceValueToChunks( int iResources, int *iNumProcessed, int *iNumNormal )
+{
+ *iNumProcessed = *iNumNormal = 0;
+
+ while ( iResources >= resource_chunk_processed_value.GetFloat() )
+ {
+ iResources -= resource_chunk_processed_value.GetFloat();
+ *iNumProcessed += 1;
+ }
+
+ while ( iResources >= resource_chunk_value.GetFloat() )
+ {
+ iResources -= resource_chunk_value.GetFloat();
+ *iNumNormal += 1;
+ }
+
+ // Round up
+ if ( iResources )
+ {
+ *iNumNormal++;
+ }
+} \ No newline at end of file
diff --git a/game/server/tf2/tf_func_resource.h b/game/server/tf2/tf_func_resource.h
new file mode 100644
index 0000000..c22410a
--- /dev/null
+++ b/game/server/tf2/tf_func_resource.h
@@ -0,0 +1,153 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Resource collection entity
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef TF_FUNC_RESOURCE_H
+#define TF_FUNC_RESOURCE_H
+#pragma once
+
+#include "utlvector.h"
+#include "props.h"
+#include "techtree.h"
+#include "entityoutput.h"
+#include "ihasbuildpoints.h"
+
+class CBaseTFPlayer;
+class CTFTeam;
+class CResourceChunk;
+class CResourceSpawner;
+
+//-----------------------------------------------------------------------------
+// Purpose: Defines an area from which resources can be collected
+//-----------------------------------------------------------------------------
+class CResourceZone : public CBaseEntity, public IHasBuildPoints
+{
+ DECLARE_CLASS( CResourceZone, CBaseEntity );
+public:
+ CResourceZone();
+
+ DECLARE_DATADESC();
+ DECLARE_SERVERCLASS();
+
+ virtual void Spawn( void );
+ virtual void Precache( void );
+ virtual void Activate( void );
+
+ // Inputs
+ void InputSetAmount( inputdata_t &inputdata );
+ void InputResetAmount( inputdata_t &inputdata );
+ void InputSetActive( inputdata_t &inputdata );
+ void InputSetInactive( inputdata_t &inputdata );
+ void InputToggleActive( inputdata_t &inputdata );
+
+ void SetActive( bool bActive );
+ bool GetActive() const;
+
+ bool IsEmpty( void );
+ bool PointIsWithin( const Vector &vecPoint );
+
+ // need to transmit to players who are in commander mode
+ int ShouldTransmit( const CCheckTransmitInfo *pInfo );
+
+ // Team handling
+ void SetOwningTeam( int iTeamNumber );
+ CTFTeam *GetOwningTeam( void );
+
+ // Resource handling
+ bool RemoveResources( int nResourcesRemoved );
+ int GetResources( void ) const { return m_nResourcesLeft; }
+
+ // Resource Chunks
+ bool ShouldSpawnChunk( void );
+ void SpawnChunk( const Vector &vecOrigin );
+ void RemoveChunk( CResourceChunk *pChunk, bool bReturn );
+
+ // Resource Spawners
+ void AddSpawner( CResourceSpawner *pSpawner );
+
+ // Zone increasing....
+ void AddZoneIncreaser( float rate );
+ void RemoveZoneIncreaser( float rate );
+
+ CNetworkVar( float, m_flClientResources ); // Amount sent to clients (0->1 range)
+
+ float GetResourceRate() const { return m_flResourceRate; }
+
+// IHasBuildPoints
+public:
+ virtual int GetNumBuildPoints( void ) const;
+ virtual bool CanBuildObjectOnBuildPoint( int iPoint, int iObjectType );
+ virtual bool GetBuildPoint( int iPoint, Vector &vecOrigin, QAngle &vecAngles );
+ virtual int GetBuildPointAttachmentIndex( int iPoint ) const;
+ virtual void SetObjectOnBuildPoint( int iPoint, CBaseObject *pObject );
+ virtual float GetMaxSnapDistance( int iPoint ) { return 128; }
+ virtual int GetNumObjectsOnMe( void );
+ virtual CBaseEntity *GetFirstObjectOnMe( void );
+ virtual CBaseObject *GetObjectOfTypeOnMe( int iObjectType );
+ virtual void RemoveAllObjects( void );
+ virtual bool ShouldCheckForMovement( void ) { return false; }
+ virtual int FindObjectOnBuildPoint( CBaseObject *pObject );
+ virtual void GetExitPoint( CBaseEntity *pPlayer, int iPoint, Vector *pAbsOrigin, QAngle *pAbsAngles );
+
+private:
+ void RecomputeClientResources( );
+
+ // Team handling
+ float m_flTestTime; // Time to next check to see if we've been captured.
+
+ bool m_bActive;
+
+ // Resource handling
+ int m_iTeamGathering; // Team that's gathering from this resource
+ Vector m_vecGatherPoint; // Position for the resource collector to be to gather from this point
+ QAngle m_angGatherPoint; // Angles for the collector to be at on the point
+
+ // Resources
+ CNetworkVar( int, m_nResourcesLeft ); // Amount of the resource that's left
+ int m_nMaxResources; // Max resources at this zone
+ float m_flResourceRate; // Time between each suck from this zone by resource pumps
+ float m_flBaseResourceRate;
+
+ // Resource chunks in this zone
+ int m_iMaxChunks;
+ float m_flRespawnTimeModifier; // speed deltas imposed by zone increasers
+ float m_flChunkValueMin;
+ float m_flChunkValueMax;
+ CUtlVector< CResourceChunk* > m_aChunks;
+
+ COutputEvent m_OnEmpty;
+
+ EHANDLE m_hResourcePump;
+
+ // Resource spawners in this zone
+ CUtlVector< CResourceSpawner* > m_aSpawners;
+};
+
+//-----------------------------------------------------------------------------
+// Purpose: A resource chunk spawning point
+//-----------------------------------------------------------------------------
+class CResourceSpawner : public CBaseAnimating
+{
+ DECLARE_CLASS( CResourceSpawner, CBaseAnimating );
+public:
+ DECLARE_DATADESC();
+ DECLARE_SERVERCLASS();
+
+ void Spawn( void );
+ void Precache( void );
+ void Activate( void );
+ void SetActive( bool bActive );
+
+ void SpawnChunkThink( void );
+
+public:
+ CHandle<CResourceZone> m_hZone;
+ CNetworkVar( bool, m_bActive );
+};
+
+void ConvertResourceValueToChunks( int iResources, int *iNumProcessed, int *iNumNormal );
+
+#endif // TF_FUNC_RESOURCE_H
diff --git a/game/server/tf2/tf_func_weldable_door.cpp b/game/server/tf2/tf_func_weldable_door.cpp
new file mode 100644
index 0000000..3e278e4
--- /dev/null
+++ b/game/server/tf2/tf_func_weldable_door.cpp
@@ -0,0 +1,399 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Func door that's weldable shut
+//
+// $NoKeywords: $
+//=============================================================================//
+#include "cbase.h"
+#include "beam_shared.h"
+#include "tf_player.h"
+#include "tf_team.h"
+#include "tf_basecombatweapon.h"
+#include "tf_func_weldable_door.h"
+#include "IEffects.h"
+
+
+LINK_ENTITY_TO_CLASS( func_door_weldable, CWeldableDoor );
+
+BEGIN_DATADESC( CWeldableDoor )
+
+ // keys
+ DEFINE_KEYFIELD( m_iszWeldPoints, FIELD_STRING, "weldpoints" ),
+
+END_DATADESC()
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CWeldableDoor::Spawn( void )
+{
+ BaseClass::Spawn();
+
+ m_hWeldingPlayer = NULL;
+ m_flMaxWeldedPercentage = 0.0;
+ m_iWeldLeader = WL_UNASSIGNED;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CWeldableDoor::Activate( void )
+{
+ BaseClass::Activate();
+
+ // Find my weld points
+ if ( !m_iszWeldPoints )
+ {
+ Msg( "func_weldable_door without weldpoints specified.\n" );
+ UTIL_Remove( this );
+ return;
+ }
+
+ // Find the weld points. Doors will almost always have multiple weld point pairs.
+ CWeldPoint *pWeldStartPoint = NULL;
+ while( (pWeldStartPoint = (CWeldPoint*)gEntList.FindEntityByName( pWeldStartPoint, m_iszWeldPoints ) ) != NULL )
+ {
+ // Does it have an endpoint specified?
+ if ( !pWeldStartPoint->m_target )
+ {
+ Msg( "func_weldable_door weldpoint '%s' didn't have an endpoint specified.\n", STRING(m_iszWeldPoints) );
+ continue;
+ }
+
+ // Find the endpoint for this startpoint
+ CWeldPoint *pWeldEndPoint = (CWeldPoint*)gEntList.FindEntityByName( NULL, pWeldStartPoint->m_target );
+ if ( pWeldEndPoint )
+ {
+ // Connect the start point to the endpoint
+ pWeldStartPoint->SetEndPoint( pWeldEndPoint->GetLocalOrigin() );
+
+ // Add it to the list
+ m_aWeldPoints.AddToTail( pWeldStartPoint );
+ }
+ else
+ {
+ Msg( "func_weldable_door weldpoint couldn't find it's endpoint of '%s'.\n", STRING(pWeldStartPoint->m_target) );
+ continue;
+ }
+ }
+
+ // Did we find any weldpoint pairs?
+ if ( m_aWeldPoints.Size() == 0 )
+ {
+ Msg( "func_weldable_door couldn't find any weldpoints for '%s'.\n", STRING(m_iszWeldPoints) );
+ UTIL_Remove( this );
+ return;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Returns true if someone's allowed to start welding on this door
+//-----------------------------------------------------------------------------
+bool CWeldableDoor::IsWeldable( CBaseTFPlayer *pWeldee )
+{
+ // Can't be welded if I'm open
+ if ( m_toggle_state != TS_AT_BOTTOM )
+ return false;
+
+ // Can't be welded if I'm already being welded by someone else
+ if ( m_hWeldingPlayer != NULL && (((CBaseEntity*)m_hWeldingPlayer) != pWeldee) )
+ return false;
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Return the weld leader.
+// Doors can be made up of multiple door entities, so one of them needs to volunteer to control the welding.
+//-----------------------------------------------------------------------------
+CWeldableDoor *CWeldableDoor::GetWeldLeader( void )
+{
+ // Am I the leader?
+ if ( m_iWeldLeader == WL_WELD_LEADER )
+ return this;
+
+ // If this guy's unassigned, he's volunteering
+ if ( m_iWeldLeader == WL_UNASSIGNED )
+ {
+ m_iWeldLeader = WL_WELD_LEADER;
+
+ // Tell all other friends they're children
+ CBaseEntity *pTarget = NULL;
+ if ( GetEntityName() != NULL_STRING )
+ {
+ while ( (pTarget = gEntList.FindEntityByName( pTarget, GetEntityName() )) != NULL )
+ {
+ if ( pTarget != this )
+ {
+ CWeldableDoor *pWeldableDoor = dynamic_cast< CWeldableDoor * >( pTarget );
+ if ( pWeldableDoor )
+ {
+ pWeldableDoor->m_iWeldLeader = WL_WELD_CHILD;
+ }
+ }
+ }
+ }
+
+ return this;
+ }
+
+ // We're not the leader. so find him
+ CBaseEntity *pTarget = NULL;
+ if ( GetEntityName() != NULL_STRING )
+ {
+ while ( (pTarget = gEntList.FindEntityByName( pTarget, GetEntityName() )) != NULL )
+ {
+ if ( pTarget != this )
+ {
+ CWeldableDoor *pWeldableDoor = dynamic_cast< CWeldableDoor * >( pTarget );
+ if ( pWeldableDoor && pWeldableDoor->m_iWeldLeader == WL_WELD_LEADER )
+ return pWeldableDoor;
+ }
+ }
+ }
+
+ return NULL;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: The Player's going to start welding this door.
+//-----------------------------------------------------------------------------
+void CWeldableDoor::StartWelding( CBaseTFPlayer *pWeldee )
+{
+ m_hWeldingPlayer = pWeldee;
+
+ // If this is the weld leader, tell all the door pieces that the player's welding them
+ if ( m_iWeldLeader == WL_WELD_LEADER )
+ {
+ CBaseEntity *pTarget = NULL;
+ if ( GetEntityName() != NULL_STRING )
+ {
+ while ( (pTarget = gEntList.FindEntityByName( pTarget, GetEntityName() )) != NULL )
+ {
+ if ( pTarget != this )
+ {
+ CWeldableDoor *pWeldableDoor = dynamic_cast< CWeldableDoor * >( pTarget );
+ if ( pWeldableDoor )
+ {
+ pWeldableDoor->StartWelding( pWeldee );
+ }
+ }
+ }
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: The player's stopped welding this door
+//-----------------------------------------------------------------------------
+void CWeldableDoor::StopWelding( void )
+{
+ m_hWeldingPlayer = NULL;
+
+ // If this is the weld leader, tell all the door pieces that the player's stopped welding them
+ if ( m_iWeldLeader == WL_WELD_LEADER )
+ {
+ CBaseEntity *pTarget = NULL;
+ if ( GetEntityName() != NULL_STRING )
+ {
+ while ( (pTarget = gEntList.FindEntityByName( pTarget, GetEntityName() )) != NULL )
+ {
+ if ( pTarget != this )
+ {
+ CWeldableDoor *pWeldableDoor = dynamic_cast< CWeldableDoor * >( pTarget );
+ if ( pWeldableDoor )
+ {
+ pWeldableDoor->StopWelding();
+ }
+ }
+ }
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Return the amount the door's been welded
+//-----------------------------------------------------------------------------
+float CWeldableDoor::GetWeldPercentage( void )
+{
+ return m_flWeldedPercentage;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Update the amount the door's been welded
+//-----------------------------------------------------------------------------
+void CWeldableDoor::UpdateWeld( bool bCutting, float flWeldPercentage )
+{
+ if ( m_flMaxWeldedPercentage < flWeldPercentage )
+ m_flMaxWeldedPercentage = flWeldPercentage;
+ m_flWeldedPercentage = flWeldPercentage;
+
+ // Did we cut away the entire weld?
+ if ( m_flWeldedPercentage <= 0.0 )
+ {
+ // Clear welded
+ m_flMaxWeldedPercentage = 0.0;
+
+ if ( m_bLocked )
+ {
+ // Unlock all the pieces of this door
+ m_bLocked = false;
+
+ CBaseEntity *pTarget = NULL;
+ if ( GetEntityName() != NULL_STRING )
+ {
+ while ( (pTarget = gEntList.FindEntityByName( pTarget, GetEntityName() )) != NULL )
+ {
+ if ( pTarget != this )
+ {
+ CWeldableDoor *pWeldableDoor = dynamic_cast< CWeldableDoor * >( pTarget );
+ if ( pWeldableDoor )
+ {
+ pWeldableDoor->Unlock();
+ }
+ }
+ }
+ }
+ }
+ }
+ else
+ {
+ if ( !m_bLocked )
+ {
+ // Lock all the pieces of this door
+ m_bLocked = true;
+
+ CBaseEntity *pTarget = NULL;
+ if ( GetEntityName() != NULL_STRING )
+ {
+ while ( (pTarget = gEntList.FindEntityByName( pTarget, GetEntityName() )) != NULL )
+ {
+ if ( pTarget != this )
+ {
+ CWeldableDoor *pWeldableDoor = dynamic_cast< CWeldableDoor * >( pTarget );
+ if ( pWeldableDoor )
+ {
+ pWeldableDoor->Lock();
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // Update the beams
+ for ( int i = 0; i < m_aWeldPoints.Size(); i++ )
+ {
+ // First time we've updated?
+ if ( m_aWeldBeams.Size() <= i )
+ {
+ CBeam *pBeam = CBeam::BeamCreate( "sprites/physbeam.vmt", 4.0 );
+ pBeam->SetColor( 128, 128, 128 );
+ pBeam->SetBrightness( 128 );
+ pBeam->PointsInit( m_aWeldPoints[i]->GetStartPoint(), m_aWeldPoints[i]->GetEndPoint() );
+ m_aWeldBeams.AddToTail( pBeam );
+ }
+ if ( m_aCutBeams.Size() <= i )
+ {
+ CBeam *pBeam = CBeam::BeamCreate( "sprites/physbeam.vmt", 4.0 );
+ pBeam->SetColor( 255, 255, 255 );
+ pBeam->SetBrightness( 255 );
+ pBeam->PointsInit( m_aWeldPoints[i]->GetStartPoint(), m_aWeldPoints[i]->GetEndPoint() );
+ m_aCutBeams.AddToTail( pBeam );
+ }
+
+ // Figure out how far we've welded
+ Vector vecLine = ( m_aWeldPoints[i]->GetEndPoint() - m_aWeldPoints[i]->GetStartPoint());
+ float flLength = vecLine.Length();
+ VectorNormalize(vecLine);
+ vecLine = vecLine * (flLength * flWeldPercentage);
+ Vector vecWeldPoint = m_aWeldPoints[i]->GetStartPoint() + vecLine;
+
+ // Update the beams
+ m_aWeldBeams[i]->SetStartPos( m_aWeldPoints[i]->GetStartPoint() );
+ m_aWeldBeams[i]->SetEndPos( vecWeldPoint );
+ m_aWeldBeams[i]->RelinkBeam();
+ if ( flWeldPercentage <= m_flMaxWeldedPercentage )
+ {
+ // Get the cut point
+ VectorNormalize(vecLine);
+ vecLine = vecLine * (flLength * m_flMaxWeldedPercentage);
+ Vector vecCutPoint = m_aWeldPoints[i]->GetStartPoint() + vecLine;
+
+ m_aCutBeams[i]->SetEndPos( vecWeldPoint );
+ m_aCutBeams[i]->SetStartPos( vecCutPoint );
+ m_aCutBeams[i]->RelinkBeam();
+ }
+
+ // Some sparks
+ if ( random->RandomInt(0,2) == 0 )
+ {
+ g_pEffects->Sparks( vecWeldPoint );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Find a weld point to watch for this player
+//-----------------------------------------------------------------------------
+Vector CWeldableDoor::GetPlayerWeldPoint( void )
+{
+ CBaseTFPlayer *pPlayer = (CBaseTFPlayer *)(CBaseEntity*)m_hWeldingPlayer;
+ Vector vecSrc = pPlayer->Weapon_ShootPosition( );
+ trace_t tr;
+
+ for ( int i = 0; i < m_aWeldPoints.Size(); i++ )
+ {
+ // Figure out where the weldpoint is
+ Vector vecLine = ( m_aWeldPoints[i]->GetEndPoint() - m_aWeldPoints[i]->GetStartPoint());
+ float flLength = vecLine.Length();
+ VectorNormalize(vecLine);
+ vecLine = vecLine * (flLength * m_flWeldedPercentage);
+ Vector vecWeldPoint = m_aWeldPoints[i]->GetStartPoint() + vecLine;
+
+ // Is the weld point visible to our player?
+ UTIL_TraceLine( vecSrc, vecWeldPoint, MASK_SOLID, pPlayer, TFCOLLISION_GROUP_WEAPON, &tr );
+ if ( tr.fraction == 1.0 )
+ return vecWeldPoint;
+ }
+
+ return vec3_origin;
+}
+
+
+//============================================================================================================
+// WELD POINTS
+//============================================================================================================
+
+LINK_ENTITY_TO_CLASS( info_weldpoint, CWeldPoint );
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CWeldPoint::Spawn( void )
+{
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CWeldPoint::SetEndPoint( const Vector &vecEndPoint )
+{
+ m_vecEndPoint = vecEndPoint;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+const Vector &CWeldPoint::GetStartPoint( void ) const
+{
+ return GetLocalOrigin();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+const Vector &CWeldPoint::GetEndPoint( void ) const
+{
+ return m_vecEndPoint;
+}
diff --git a/game/server/tf2/tf_func_weldable_door.h b/game/server/tf2/tf_func_weldable_door.h
new file mode 100644
index 0000000..b887ba7
--- /dev/null
+++ b/game/server/tf2/tf_func_weldable_door.h
@@ -0,0 +1,81 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef TF_FUNC_WELDABLE_DOOR_H
+#define TF_FUNC_WELDABLE_DOOR_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include "doors.h"
+
+class CBeam;
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Weld Point for a weldable door
+//-----------------------------------------------------------------------------
+class CWeldPoint : public CPointEntity
+{
+ DECLARE_CLASS( CWeldPoint, CPointEntity );
+public:
+ void Spawn( void );
+ void SetEndPoint( const Vector &vecEndPoint );
+ const Vector &GetStartPoint( void ) const;
+ const Vector &GetEndPoint( void ) const;
+
+private:
+ Vector m_vecEndPoint;
+};
+
+// Weld Leader
+enum
+{
+ WL_UNASSIGNED,
+ WL_WELD_LEADER,
+ WL_WELD_CHILD,
+};
+
+//-----------------------------------------------------------------------------
+// Purpose: A weldable door
+//-----------------------------------------------------------------------------
+class CWeldableDoor : public CBaseDoor
+{
+public:
+ DECLARE_CLASS( CWeldableDoor, CBaseDoor );
+
+ DECLARE_DATADESC();
+
+ virtual void Spawn( void );
+ virtual void Activate( void );
+
+ // Welding
+ virtual bool IsWeldable( CBaseTFPlayer *pWeldee );
+ virtual void StartWelding( CBaseTFPlayer *pWeldee );
+ virtual void StopWelding( void );
+ virtual float GetWeldPercentage( void );
+ virtual Vector GetPlayerWeldPoint( void );
+ virtual void UpdateWeld( bool bCutting, float flWeldPercentage );
+ CWeldableDoor *GetWeldLeader( void );
+
+ // Welding
+ int m_iWeldLeader;
+ EHANDLE m_hWeldingPlayer;
+
+ // Weld points
+ string_t m_iszWeldPoints;
+ CUtlVector < CWeldPoint * > m_aWeldPoints;
+
+ // Weld beams
+ float m_flMaxWeldedPercentage;
+ float m_flWeldedPercentage;
+ CUtlVector < CBeam * > m_aWeldBeams;
+ CUtlVector < CBeam * > m_aCutBeams;
+};
+
+
+#endif // TF_FUNC_WELDABLE_DOOR_H
diff --git a/game/server/tf2/tf_gameinterface.cpp b/game/server/tf2/tf_gameinterface.cpp
new file mode 100644
index 0000000..d10728b
--- /dev/null
+++ b/game/server/tf2/tf_gameinterface.cpp
@@ -0,0 +1,27 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+#include "cbase.h"
+#include "gameinterface.h"
+#include "mapentities.h"
+
+
+void CServerGameClients::GetPlayerLimits( int& minplayers, int& maxplayers, int &defaultMaxPlayers ) const
+{
+ minplayers = 2; // Force multiplayer.
+ maxplayers = MAX_PLAYERS;
+ defaultMaxPlayers = 32;
+}
+
+
+// -------------------------------------------------------------------------------------------- //
+// Mod-specific CServerGameDLL implementation.
+// -------------------------------------------------------------------------------------------- //
+
+void CServerGameDLL::LevelInit_ParseAllEntities( const char *pMapEntities )
+{
+ MapEntity_ParseAllEntities( pMapEntities, NULL );
+}
diff --git a/game/server/tf2/tf_hintmanager.cpp b/game/server/tf2/tf_hintmanager.cpp
new file mode 100644
index 0000000..b5af27e
--- /dev/null
+++ b/game/server/tf2/tf_hintmanager.cpp
@@ -0,0 +1,64 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//
+//=============================================================================//
+#include "cbase.h"
+
+#include "tf_hintmanager.h"
+
+#define TFHINTMANAGER_THINK_INTERVAL 1.0f
+
+IMPLEMENT_SERVERCLASS_ST_NOBASE(CTFHintManager, DT_TFHintManager)
+END_SEND_TABLE()
+
+LINK_ENTITY_TO_CLASS( tf_hintmanager, CTFHintManager );
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CTFHintManager::CTFHintManager( void )
+{
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFHintManager::Spawn( void )
+{
+ Precache();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFHintManager::Think( void )
+{
+ SetNextThink( gpGlobals->curtime + TFHINTMANAGER_THINK_INTERVAL );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : *player -
+// hintID -
+//-----------------------------------------------------------------------------
+void CTFHintManager::AddHint( CBaseTFPlayer *player, int hintID, int priority, int entityIndex /*=0*/ )
+{
+ // Send a message to the client side entity
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Always send
+// Input : **ppSendTable -
+// *recipient -
+// *pvs -
+// clientArea -
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+
+int CTFHintManager::UpdateTransmitState()
+{
+ return SetTransmitState( FL_EDICT_ALWAYS );
+} \ No newline at end of file
diff --git a/game/server/tf2/tf_hintmanager.h b/game/server/tf2/tf_hintmanager.h
new file mode 100644
index 0000000..70295aa
--- /dev/null
+++ b/game/server/tf2/tf_hintmanager.h
@@ -0,0 +1,42 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef TF_HINTMANAGER_H
+#define TF_HINTMANAGER_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include "cbase.h"
+
+class CBaseTFPlayer;
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+class CTFHintManager : public CBaseEntity
+{
+ DECLARE_CLASS( CTFHintManager, CBaseEntity );
+
+public:
+
+ DECLARE_SERVERCLASS();
+
+ CTFHintManager( void );
+
+ virtual void Spawn( void );
+
+ virtual void Think( void );
+
+ virtual int UpdateTransmitState();
+
+private:
+
+ void AddHint( CBaseTFPlayer *player, int hintID, int priority, int entityIndex = 0 );
+};
+
+#endif // TF_HINTMANAGER_H
diff --git a/game/server/tf2/tf_obj.cpp b/game/server/tf2/tf_obj.cpp
new file mode 100644
index 0000000..cd07213
--- /dev/null
+++ b/game/server/tf2/tf_obj.cpp
@@ -0,0 +1,3243 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Base Object built by players
+//
+// $NoKeywords: $
+//=============================================================================//
+#include "cbase.h"
+#include "tf_player.h"
+#include "tf_team.h"
+#include "tf_obj.h"
+#include "tf_basecombatweapon.h"
+#include "rope.h"
+#include "rope_shared.h"
+#include "bone_setup.h"
+#include "tf_func_resource.h"
+#include "ndebugoverlay.h"
+#include "rope_helpers.h"
+#include "IEffects.h"
+#include "vstdlib/random.h"
+#include "tier1/strtools.h"
+#include "basegrenade_shared.h"
+#include "grenade_objectsapper.h"
+#include "tf_stats.h"
+#include "tf_gamerules.h"
+#include "engine/IEngineSound.h"
+#include "tf_obj_sentrygun.h"
+#include "tf_obj_powerpack.h"
+#include "tf_shareddefs.h"
+#include "VGuiScreen.h"
+#include "resource_chunk.h"
+#include "hierarchy.h"
+#include "tf_func_construction_yard.h"
+#include "tf_func_no_build.h"
+#include <KeyValues.h>
+#include "team_messages.h"
+#include "info_act.h"
+#include "info_vehicle_bay.h"
+#include "ihasbuildpoints.h"
+#include "tf_obj_buff_station.h"
+#include "info_buildpoint.h"
+#include "utldict.h"
+#include "filesystem.h"
+#include "npcevent.h"
+#include "tf_shareddefs.h"
+#include "animation.h"
+
+// Control panels
+#define SCREEN_OVERLAY_MATERIAL "vgui/screens/vgui_overlay"
+
+#define ROPE_HANG_DIST 150
+
+
+ConVar object_verbose( "object_verbose", "0", 0, "Debug object system." );
+ConVar obj_damage_factor( "obj_damage_factor","0", FCVAR_NONE, "Factor applied to all damage done to objects" );
+ConVar obj_child_damage_factor( "obj_child_damage_factor","0.25", FCVAR_NONE, "Factor applied to damage done to objects that are built on a buildpoint" );
+ConVar tf_fastbuild("tf_fastbuild", "0", FCVAR_CHEAT);
+ConVar tf_obj_ground_clearance( "tf_obj_ground_clearance", "60", 0, "Object corners can be this high above the ground" );
+
+extern short g_sModelIndexFireball;
+
+// Minimum distance between 2 objects to ensure player movement between them
+#define MINIMUM_OBJECT_SAFE_DISTANCE 100
+
+// Maximum number of a type of objects on a single resource zone
+#define MAX_OBJECTS_PER_ZONE 1
+
+// Time it takes a fully healed object to deteriorate
+ConVar object_deterioration_time( "object_deterioration_time", "30", 0, "Time it takes for a fully-healed object to deteriorate." );
+
+// Time after taking damage that an object will still drop resources on death
+#define MAX_DROP_TIME_AFTER_DAMAGE 5
+
+#define OBJ_BASE_THINK_CONTEXT "BaseObjectThink"
+#define OBJ_LOSTPOWER_THINK_CONTEXT "LostPowerThink"
+
+BEGIN_DATADESC( CBaseObject )
+ // keys
+ DEFINE_KEYFIELD_NOT_SAVED( m_bInvulnerable, FIELD_BOOLEAN, "Invulnerable" ),
+ DEFINE_KEYFIELD_NOT_SAVED( m_flRepairMultiplier, FIELD_FLOAT, "RepairMult" ),
+ DEFINE_KEYFIELD_NOT_SAVED( m_iszUnderAttackSound, FIELD_STRING, "AttackNotify" ),
+ DEFINE_KEYFIELD_NOT_SAVED( m_flMinDisableHealth, FIELD_FLOAT, "MinDisabledHealth" ),
+ DEFINE_KEYFIELD_NOT_SAVED( m_SolidToPlayers, FIELD_INTEGER, "SolidToPlayer" ),
+ DEFINE_KEYFIELD_NOT_SAVED( m_iszDisabledModel, FIELD_STRING, "DisabledModel" ),
+ DEFINE_KEYFIELD_NOT_SAVED( m_bCantDie, FIELD_BOOLEAN, "CantDie" ),
+
+ // Inputs
+ DEFINE_INPUTFUNC( FIELD_INTEGER, "SetHealth", InputSetHealth ),
+ DEFINE_INPUTFUNC( FIELD_INTEGER, "AddHealth", InputAddHealth ),
+ DEFINE_INPUTFUNC( FIELD_INTEGER, "RemoveHealth", InputRemoveHealth ),
+ DEFINE_INPUTFUNC( FIELD_INTEGER, "SetMinDisabledHealth", InputSetMinDisabledHealth ),
+ DEFINE_INPUTFUNC( FIELD_INTEGER, "SetSolidToPlayer", InputSetSolidToPlayer ),
+
+ // Outputs
+ DEFINE_OUTPUT( m_OnDestroyed, "OnDestroyed" ),
+ DEFINE_OUTPUT( m_OnDamaged, "OnDamaged" ),
+ DEFINE_OUTPUT( m_OnRepaired, "OnRepaired" ),
+ DEFINE_OUTPUT( m_OnBecomingDisabled, "OnDisabled" ),
+ DEFINE_OUTPUT( m_OnBecomingReenabled, "OnReenabled" ),
+ DEFINE_OUTPUT( m_OnObjectHealthChanged, "OnObjectHealthChanged" )
+END_DATADESC()
+
+
+IMPLEMENT_SERVERCLASS_ST(CBaseObject, DT_BaseObject)
+ SendPropInt(SENDINFO(m_iHealth), 13 ),
+ SendPropInt(SENDINFO(m_iMaxHealth), 13 ),
+ SendPropInt(SENDINFO(m_bHasSapper), 1, SPROP_UNSIGNED ),
+ SendPropInt(SENDINFO(m_iObjectType), 6, SPROP_UNSIGNED ),
+ SendPropInt(SENDINFO(m_bBuilding), 1, SPROP_UNSIGNED ),
+ SendPropInt(SENDINFO(m_bPlacing), 1, SPROP_UNSIGNED ),
+ SendPropFloat(SENDINFO(m_flPercentageConstructed), 8, 0, 0.0, 1.0f ),
+ SendPropInt(SENDINFO(m_fObjectFlags), OF_BIT_COUNT, SPROP_UNSIGNED ),
+ SendPropInt(SENDINFO(m_bDeteriorating), 1, SPROP_UNSIGNED ),
+ SendPropEHandle(SENDINFO(m_hBuiltOnEntity)),
+ SendPropInt( SENDINFO( m_takedamage ), 2, SPROP_UNSIGNED ),
+ SendPropInt( SENDINFO( m_bDisabled ), 1, SPROP_UNSIGNED ),
+ SendPropEHandle( SENDINFO( m_hBuilder ) ),
+END_SEND_TABLE();
+
+
+// This controls whether ropes attached to objects are transmitted or not. It's important that
+// ropes aren't transmitted to guys who don't own them.
+class CObjectRopeTransmitProxy : public CBaseTransmitProxy
+{
+public:
+ CObjectRopeTransmitProxy( CBaseEntity *pRope ) : CBaseTransmitProxy( pRope )
+ {
+ }
+
+ virtual int ShouldTransmit( const CCheckTransmitInfo *pInfo, int nPrevShouldTransmitResult )
+ {
+ // Don't transmit the rope if it's not even visible.
+ if ( !nPrevShouldTransmitResult )
+ return FL_EDICT_DONTSEND;
+
+ // This proxy only wants to be active while one of the two objects is being placed.
+ // When they're done being placed, the proxy goes away and the rope draws like normal.
+ bool bAnyObjectPlacing = (m_hObj1 && m_hObj1->IsPlacing()) || (m_hObj2 && m_hObj2->IsPlacing());
+ if ( !bAnyObjectPlacing )
+ {
+ Release();
+ return nPrevShouldTransmitResult;
+ }
+
+ // Give control to whichever object is being placed.
+ if ( m_hObj1 && m_hObj1->IsPlacing() )
+ return m_hObj1->ShouldTransmit( pInfo );
+
+ else if ( m_hObj2 && m_hObj2->IsPlacing() )
+ return m_hObj2->ShouldTransmit( pInfo );
+
+ else
+ return FL_EDICT_ALWAYS;
+ }
+
+
+ CHandle<CBaseObject> m_hObj1;
+ CHandle<CBaseObject> m_hObj2;
+};
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CBaseObject::CBaseObject()
+{
+ m_hPowerPack = NULL;
+ m_iHealth = m_iMaxHealth = m_flHealth = 0;
+ m_aRopes.Purge();
+ m_flPercentageConstructed = 0;
+ m_bPlacing = false;
+ m_bBuilding = false;
+ m_bInvulnerable = false;
+ m_bCantDie = false;
+ m_bDeteriorating = false;
+ m_flRepairMultiplier = 1;
+ m_hBuffStation = NULL;
+ m_bBuffActivated = false;
+ m_Activity = ACT_OBJ_IDLE;
+ m_bDisabled = false;
+ m_hVehicleBay = NULL;
+ m_flLastRepairTime = 0;
+ m_flNextRepairMultiplier = 0;
+ m_flRepairedSinceLastTime = 0;
+ m_iszUnderAttackSound = NULL_STRING;
+ m_SolidToPlayers = SOLID_TO_PLAYER_USE_DEFAULT;
+ m_iszDisabledModel = NULL_STRING;
+ m_iszEnabledModel = NULL_STRING;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseObject::UpdateOnRemove( void )
+{
+ m_bDying = true;
+
+ // Remove anything left on me
+ IHasBuildPoints *pBPInterface = dynamic_cast<IHasBuildPoints*>(this);
+ if ( pBPInterface && pBPInterface->GetNumObjectsOnMe() )
+ {
+ pBPInterface->RemoveAllObjects();
+ }
+
+ DestroyObject();
+
+ if ( GetTeam() )
+ {
+ ((CTFTeam*)GetTeam())->RemoveObject( this );
+ }
+
+ // Make sure the object isn't in either team's list of objects...
+ Assert( !GetGlobalTFTeam(1)->IsObjectOnTeam( this ) );
+ Assert( !GetGlobalTFTeam(2)->IsObjectOnTeam( this ) );
+
+ // Chain at end to mimic destructor unwind order
+ BaseClass::UpdateOnRemove();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+int CBaseObject::ShouldTransmit( const CCheckTransmitInfo *pInfo )
+{
+ // Always transmit to owner
+ if ( GetBuilder() && pInfo->m_pClientEnt == GetBuilder()->edict() )
+ return FL_EDICT_ALWAYS;
+
+ // Placement models only transmit to owners
+ if ( IsPlacing() )
+ return FL_EDICT_DONTSEND;
+
+ return BaseClass::ShouldTransmit( pInfo );
+}
+
+
+void CBaseObject::SetTransmit( CCheckTransmitInfo *pInfo, bool bAlways )
+{
+ // Are we already marked for transmission?
+ if ( pInfo->m_pTransmitEdict->Get( entindex() ) )
+ return;
+
+ BaseClass::SetTransmit( pInfo, bAlways );
+
+ // Force our screens to be sent too.
+ int nTeam = CBaseEntity::Instance( pInfo->m_pClientEnt )->GetTeamNumber();
+ for ( int i=0; i < m_hScreens.Count(); i++ )
+ {
+ CVGuiScreen *pScreen = m_hScreens[i].Get();
+ if ( pScreen && pScreen->IsVisibleToTeam( nTeam ) )
+ pScreen->SetTransmit( pInfo, bAlways );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseObject::Precache()
+{
+ PrecacheVGuiScreen( "screen_basic_with_disable" );
+
+ if ( m_iszUnderAttackSound != NULL_STRING )
+ {
+ PrecacheScriptSound( STRING(m_iszUnderAttackSound) );
+ }
+ PrecacheMaterial( SCREEN_OVERLAY_MATERIAL );
+
+ if ( m_iszDisabledModel != NULL_STRING )
+ {
+ PrecacheModel( STRING( m_iszDisabledModel ) );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseObject::Spawn( void )
+{
+ Precache();
+
+ CollisionProp()->SetSurroundingBoundsType( USE_BEST_COLLISION_BOUNDS );
+ SetSolidToPlayers( m_SolidToPlayers, true );
+
+ m_bWasMapPlaced = false;
+ m_bHasSapper = false;
+ m_takedamage = DAMAGE_YES;
+ m_flHealth = m_iMaxHealth = m_iHealth;
+ m_iAmountPlayerPaidForMe = CalculateObjectCost( GetType(), 0, GetTeamNumber() );
+
+ SetContextThink( BaseObjectThink, gpGlobals->curtime + 0.1, OBJ_BASE_THINK_CONTEXT );
+ m_szAmmoName = NULL;
+
+ AddFlag( FL_OBJECT ); // So NPCs will notice it
+ SetViewOffset( WorldSpaceCenter() - GetAbsOrigin() );
+
+ // Don't take damage if we're invulnerable, and don't require power either
+ if ( m_bInvulnerable )
+ {
+ m_takedamage = DAMAGE_NO;
+ m_fObjectFlags |= OF_DOESNT_NEED_POWER;
+ AddFlag( FL_NOTARGET );
+ }
+
+ // Cache off the normal model name
+ m_iszEnabledModel = GetModelName();
+}
+
+
+//-----------------------------------------------------------------------------
+// Returns information about the various control panels
+//-----------------------------------------------------------------------------
+void CBaseObject::GetControlPanelInfo( int nPanelIndex, const char *&pPanelName )
+{
+ pPanelName = NULL;
+}
+
+//-----------------------------------------------------------------------------
+// Returns information about the various control panels
+//-----------------------------------------------------------------------------
+void CBaseObject::GetControlPanelClassName( int nPanelIndex, const char *&pPanelName )
+{
+ pPanelName = "vgui_screen";
+}
+
+//-----------------------------------------------------------------------------
+// This is called by the base object when it's time to spawn the control panels
+//-----------------------------------------------------------------------------
+void CBaseObject::SpawnControlPanels()
+{
+ char buf[64];
+
+ // FIXME: Deal with dynamically resizing control panels?
+
+ // If we're attached to an entity, spawn control panels on it instead of use
+ CBaseAnimating *pEntityToSpawnOn = this;
+ char *pOrgLL = "controlpanel%d_ll";
+ char *pOrgUR = "controlpanel%d_ur";
+ char *pAttachmentNameLL = pOrgLL;
+ char *pAttachmentNameUR = pOrgUR;
+ if ( IsBuiltOnAttachment() )
+ {
+ pEntityToSpawnOn = dynamic_cast<CBaseAnimating*>((CBaseEntity*)m_hBuiltOnEntity.Get());
+ if ( pEntityToSpawnOn )
+ {
+ char sBuildPointLL[64];
+ char sBuildPointUR[64];
+ Q_snprintf( sBuildPointLL, sizeof( sBuildPointLL ), "bp%d_controlpanel%%d_ll", m_iBuiltOnPoint );
+ Q_snprintf( sBuildPointUR, sizeof( sBuildPointUR ), "bp%d_controlpanel%%d_ur", m_iBuiltOnPoint );
+ pAttachmentNameLL = sBuildPointLL;
+ pAttachmentNameUR = sBuildPointUR;
+ }
+ else
+ {
+ pEntityToSpawnOn = this;
+ }
+ }
+
+ Assert( pEntityToSpawnOn );
+
+ // Lookup the attachment point...
+ int nPanel;
+ for ( nPanel = 0; true; ++nPanel )
+ {
+ Q_snprintf( buf, sizeof( buf ), pAttachmentNameLL, nPanel );
+ int nLLAttachmentIndex = pEntityToSpawnOn->LookupAttachment(buf);
+ if (nLLAttachmentIndex <= 0)
+ {
+ // Try and use my panels then
+ pEntityToSpawnOn = this;
+ Q_snprintf( buf, sizeof( buf ), pOrgLL, nPanel );
+ nLLAttachmentIndex = pEntityToSpawnOn->LookupAttachment(buf);
+ if (nLLAttachmentIndex <= 0)
+ return;
+ }
+
+ Q_snprintf( buf, sizeof( buf ), pAttachmentNameUR, nPanel );
+ int nURAttachmentIndex = pEntityToSpawnOn->LookupAttachment(buf);
+ if (nURAttachmentIndex <= 0)
+ {
+ // Try and use my panels then
+ Q_snprintf( buf, sizeof( buf ), pOrgUR, nPanel );
+ nURAttachmentIndex = pEntityToSpawnOn->LookupAttachment(buf);
+ if (nURAttachmentIndex <= 0)
+ return;
+ }
+
+ const char *pScreenName;
+ GetControlPanelInfo( nPanel, pScreenName );
+ if (!pScreenName)
+ continue;
+
+ const char *pScreenClassname;
+ GetControlPanelClassName( nPanel, pScreenClassname );
+ if ( !pScreenClassname )
+ continue;
+
+ // Compute the screen size from the attachment points...
+ matrix3x4_t panelToWorld;
+ pEntityToSpawnOn->GetAttachment( nLLAttachmentIndex, panelToWorld );
+
+ matrix3x4_t worldToPanel;
+ MatrixInvert( panelToWorld, worldToPanel );
+
+ // Now get the lower right position + transform into panel space
+ Vector lr, lrlocal;
+ pEntityToSpawnOn->GetAttachment( nURAttachmentIndex, panelToWorld );
+ MatrixGetColumn( panelToWorld, 3, lr );
+ VectorTransform( lr, worldToPanel, lrlocal );
+
+ float flWidth = lrlocal.x;
+ float flHeight = lrlocal.y;
+
+ CVGuiScreen *pScreen = CreateVGuiScreen( pScreenClassname, pScreenName, pEntityToSpawnOn, this, nLLAttachmentIndex );
+ pScreen->ChangeTeam( GetTeamNumber() );
+ pScreen->SetActualSize( flWidth, flHeight );
+ pScreen->SetActive( false );
+ pScreen->MakeVisibleOnlyToTeammates( true );
+ pScreen->SetOverlayMaterial( SCREEN_OVERLAY_MATERIAL );
+ int nScreen = m_hScreens.AddToTail( );
+ m_hScreens[nScreen].Set( pScreen );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Various commands sent by control panels
+//-----------------------------------------------------------------------------
+void CBaseObject::DismantleCommand( CBaseTFPlayer *pSender )
+{
+ if (CanBeRemovedBy( pSender ))
+ {
+ PickupObject();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseObject::YawCommand( CBaseTFPlayer *pSender, float flYaw )
+{
+ if ( CanBeRotatedBy(pSender) )
+ {
+ QAngle angles = GetAbsAngles();
+
+ angles.y = anglemod( flYaw );
+ SetLocalAngles( ConvertAbsAnglesToLocal( angles ) );
+ Teleport( NULL, &GetLocalAngles(), NULL );
+
+ // Notify the object that it moved
+ ObjectMoved();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseObject::TakeControlCommand( CBaseTFPlayer *pSender )
+{
+ // Deteriorating objects can be bought
+ if ( InSameTeam( pSender ) && IsDeteriorating() )
+ {
+ if ( ClassCanBuild( pSender->PlayerClass(), GetType() ) )
+ {
+ // Make sure he has the resources
+ int iCost = CalculateObjectCost( GetType(), pSender->GetNumObjects( GetType() ), GetTeamNumber() );
+ if ( pSender->GetBankResources() >= iCost )
+ {
+ pSender->RemoveBankResources( iCost );
+ SetBuilder( pSender );
+ pSender->AddObject( this );
+ }
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Handle commands sent from vgui panels on the client
+//-----------------------------------------------------------------------------
+bool CBaseObject::ClientCommand( CBaseTFPlayer *pSender, const char *pCmd, ICommandArguments *pArg )
+{
+ if ( FStrEq( pCmd, "dismantle" ) )
+ {
+ DismantleCommand( pSender );
+ return true;
+ }
+
+ if ( FStrEq( pCmd, "yaw" ) )
+ {
+ if ( pArg->Argc() == 2 )
+ {
+ float flYaw = atof( pArg->Argv(1) );
+ YawCommand( pSender, flYaw );
+ }
+ return true;
+ }
+
+ if ( FStrEq( pCmd, "takecontrol" ) )
+ {
+ TakeControlCommand( pSender );
+ return true;
+ }
+
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseObject::BaseObjectThink( void )
+{
+ SetNextThink( gpGlobals->curtime + 0.1, OBJ_BASE_THINK_CONTEXT );
+
+ // Make sure animation is up to date
+ DetermineAnimation();
+
+ // Can't animate without a model
+ if ( !(m_fObjectFlags & OF_DOESNT_HAVE_A_MODEL) )
+ {
+ StudioFrameAdvance();
+ }
+
+ /*
+ ROBIN: Hierarchy should do this for us
+
+ // If we were built on an attachment that's moved, update our position
+ if ( !IsPlacing() && IsBuiltOnAttachment() )
+ {
+ IHasBuildPoints *pBPInterface = dynamic_cast<IHasBuildPoints*>((CBaseEntity*)m_hBuiltOnEntity);
+ Assert( pBPInterface );
+
+ if ( pBPInterface->ShouldCheckForMovement() )
+ {
+ Vector vecOrigin;
+ QAngle vecAngles;
+ pBPInterface->GetBuildPoint( m_iBuiltOnPoint, vecOrigin, vecAngles );
+
+ EntityMatrix vehicleToWorld, childMatrix;
+ vehicleToWorld.InitFromEntity( GetMoveParent() ); // parent->world
+ vecOrigin = vehicleToWorld.WorldToLocal( vecOrigin );
+
+ if ( vecOrigin != GetLocalOrigin() )
+ {
+ Teleport( &vecOrigin, NULL, NULL );
+ }
+ }
+ }
+ */
+
+ // Do nothing while we're being placed
+ if ( IsPlacing() )
+ {
+ for ( int i=0; i < m_aRopes.Count(); i++ )
+ {
+ if ( m_aRopes[i].Get() )
+ m_aRopes[i]->SetupHangDistance( ROPE_HANG_DIST );
+ }
+
+ return;
+ }
+
+ // If we're deteriorating, keep going
+ if ( IsDeteriorating() )
+ {
+ DeterioratingThink();
+ }
+
+ // If we're building, keep going
+ if ( IsBuilding() )
+ {
+ BuildingThink();
+ return;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CBaseTFPlayer *CBaseObject::GetOwner()
+{
+ return m_hBuilder;
+}
+
+
+//-----------------------------------------------------------------------------
+// Do we have to be built in a resource zone?
+//-----------------------------------------------------------------------------
+bool CBaseObject::MustBeBuiltInResourceZone( void ) const
+{
+ return (m_fObjectFlags & OF_MUST_BE_BUILT_IN_RESOURCE_ZONE) != 0;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CBaseObject::MustBeBuiltInConstructionYard( ) const
+{
+ return (m_fObjectFlags & OF_MUST_BE_BUILT_IN_CONSTRUCTION_YARD) != 0;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CBaseObject::MustNotBeBuiltInConstructionYard( void ) const
+{
+ return !MustBeBuiltInConstructionYard();
+}
+
+//-----------------------------------------------------------------------------
+// Do we have to be built on an attachment point
+//-----------------------------------------------------------------------------
+bool CBaseObject::MustBeBuiltOnAttachmentPoint( void ) const
+{
+ return (m_fObjectFlags & OF_MUST_BE_BUILT_ON_ATTACHMENT) != 0;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Map placed objects need to setup here.
+//-----------------------------------------------------------------------------
+void CBaseObject::InitializeMapPlacedObject( void )
+{
+ m_bWasMapPlaced = true;
+ m_fObjectFlags |= OF_CANNOT_BE_DISMANTLED;
+
+ // If a map-placed object spawns child objects with their own control
+ // panels, all of this lovely code will already have been run
+ if (m_hBuiltOnEntity.Get())
+ return;
+
+ SetBuilder( NULL );
+
+ // NOTE: We must spawn the control panels now, instead of during
+ // Spawn, because until placement is started, we don't actually know
+ // the position of the control panel because we don't know what it's
+ // been attached to (could be a vehicle which supplies a different
+ // place for the control panel)
+
+ if ( !(m_fObjectFlags & OF_DOESNT_HAVE_A_MODEL) )
+ {
+ SpawnControlPanels();
+ }
+
+ SetHealth( GetMaxHealth() );
+
+ AlignToGround( GetAbsOrigin() );
+ FinishedBuilding();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseObject::Activate( void )
+{
+ BaseClass::Activate();
+
+ // Add myself to the team
+ InitializeMapPlacedObject();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseObject::SetBuilder( CBaseTFPlayer *pBuilder, bool moveobjects )
+{
+ TRACE_OBJECT( UTIL_VarArgs( "%0.2f CBaseObject::SetBuilder builder %s, moveobjects == %s\n", gpGlobals->curtime,
+ pBuilder ? pBuilder->GetPlayerName() : "NULL",
+ moveobjects ? "true" : "false" ) );
+
+ ChangeBuilder( pBuilder, moveobjects );
+}
+
+
+//-----------------------------------------------------------------------------
+// Called when the builder rotates this object...
+//-----------------------------------------------------------------------------
+void CBaseObject::ObjectMoved( )
+{
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+int CBaseObject::ObjectType( ) const
+{
+ return m_iObjectType;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Remove this object from it's team and mark for deletion
+//-----------------------------------------------------------------------------
+void CBaseObject::DestroyObject( void )
+{
+ TRACE_OBJECT( UTIL_VarArgs( "%0.2f CBaseObject::DestroyObject %p:%s\n", gpGlobals->curtime, this, GetClassname() ) );
+
+ COrderEvent_ObjectDestroyed order( this );
+ GlobalOrderEvent( &order );
+
+ if ( GetBuilder() )
+ {
+ GetBuilder()->OwnedObjectDestroyed( this );
+ }
+
+ // Tell my powerpack that I'm gone
+ if ( m_hPowerPack != NULL )
+ {
+ m_hPowerPack->UnPowerObject( this );
+ }
+
+ // Tell my power up source that I have been destroyed.
+ if ( GetBuffStation() )
+ {
+ GetBuffStation()->DeBuffObject( this );
+ }
+
+ // Detach all my ropes
+ int i;
+ for ( i = 0; i < m_aRopes.Size(); i++ )
+ {
+ if ( m_aRopes[i] )
+ {
+ m_aRopes[i]->DieAtNextRest();
+ }
+ }
+
+ UTIL_Remove( this );
+
+ // Kill the control panels
+ for ( i = m_hScreens.Count(); --i >= 0; )
+ {
+ DestroyVGuiScreen( m_hScreens[i].Get() );
+ }
+ m_hScreens.RemoveAll();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: My builder's switched class/team, so start deteriorating.
+//-----------------------------------------------------------------------------
+void CBaseObject::StartDeteriorating( void )
+{
+ if ( tf_fastbuild.GetInt() )
+ return;
+
+ m_bDeteriorating = true;
+ m_flStartedDeterioratingAt = gpGlobals->curtime;
+ SetBuilder( NULL, true );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseObject::StopDeteriorating( void )
+{
+ m_bDeteriorating = false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Continue deterioration of this object
+//-----------------------------------------------------------------------------
+void CBaseObject::DeterioratingThink( void )
+{
+ // Calculate damage. The longer we've lasted, the faster we should go.
+ float flDamage;
+ float flDeteriorationTime = (gpGlobals->curtime - m_flStartedDeterioratingAt);
+ // If we've lasted less than the base time, we want to take the base time to die
+ flDamage = 0.1 * ( GetMaxHealth() / object_deterioration_time.GetFloat() ) * ceil(flDeteriorationTime / object_deterioration_time.GetFloat());
+ // Hax0r the damage to get around the object damage reduction
+ if ( obj_damage_factor.GetFloat() )
+ {
+ flDamage *= 1 / obj_damage_factor.GetFloat();
+ }
+
+ // Apply the damage
+ OnTakeDamage( CTakeDamageInfo( GetContainingEntity(INDEXENT(0)), GetContainingEntity(INDEXENT(0)), flDamage, DMG_GENERIC ) );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Get the total time it will take to build this object
+//-----------------------------------------------------------------------------
+float CBaseObject::GetTotalTime( void )
+{
+ if (tf_fastbuild.GetInt())
+ return 2.f;
+
+ // If it's in a construction yard, don't take more than 5 seconds to build
+ if ( PointInConstructionYard( GetAbsOrigin() ) )
+ {
+ if ( GetObjectInfo( ObjectType() )->m_flBuildTime > 5.0 )
+ return 5.0;
+ }
+
+ return GetObjectInfo( ObjectType() )->m_flBuildTime;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Start placing the object
+//-----------------------------------------------------------------------------
+void CBaseObject::StartPlacement( CBaseTFPlayer *pPlayer )
+{
+ AddSolidFlags( FSOLID_NOT_SOLID );
+
+ m_bPlacing = true;
+ m_bBuilding = false;
+ if ( pPlayer )
+ {
+ SetBuilder( pPlayer );
+ ChangeTeam( pPlayer->GetTeamNumber() );
+ }
+
+ // Make it semi-transparent
+ m_nRenderMode = kRenderTransAlpha;
+ SetRenderColorA( 128 );
+
+ // Set my build size
+ CollisionProp()->WorldSpaceAABB( &m_vecBuildMins, &m_vecBuildMaxs );
+ m_vecBuildMins -= Vector( 4,4,0 );
+ m_vecBuildMaxs += Vector( 4,4,0 );
+ m_vecBuildMins -= GetAbsOrigin();
+ m_vecBuildMaxs -= GetAbsOrigin();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Stop placing the object
+//-----------------------------------------------------------------------------
+void CBaseObject::StopPlacement( void )
+{
+ UTIL_Remove( this );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Find the nearest buildpoint on the specified entity
+//-----------------------------------------------------------------------------
+bool CBaseObject::FindNearestBuildPoint( CBaseEntity *pEntity, Vector vecBuildOrigin, float &flNearestPoint, Vector &vecNearestBuildPoint )
+{
+ bool bFoundPoint = false;
+
+ IHasBuildPoints *pBPInterface = dynamic_cast<IHasBuildPoints*>(pEntity);
+ Assert( pBPInterface );
+
+ // Any empty buildpoints?
+ for ( int i = 0; i < pBPInterface->GetNumBuildPoints(); i++ )
+ {
+ // Can this object build on this point?
+ if ( pBPInterface->CanBuildObjectOnBuildPoint( i, GetType() ) )
+ {
+ // Close to this point?
+ Vector vecBPOrigin;
+ QAngle vecBPAngles;
+ if ( pBPInterface->GetBuildPoint(i, vecBPOrigin, vecBPAngles) )
+ {
+ float flDist = (vecBPOrigin - vecBuildOrigin).Length();
+ if ( flDist < MIN(flNearestPoint, pBPInterface->GetMaxSnapDistance( i )) )
+ {
+ flNearestPoint = flDist;
+ vecNearestBuildPoint = vecBPOrigin;
+ m_hBuiltOnEntity = pEntity;
+ m_iBuiltOnPoint = i;
+
+ // Set our angles to the buildpoint's angles
+ SetAbsAngles( vecBPAngles );
+
+ bFoundPoint = true;
+ }
+ }
+ }
+ }
+
+ return bFoundPoint;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Calculate the placement model's position
+//-----------------------------------------------------------------------------
+bool CBaseObject::CalculatePlacement( CBaseTFPlayer *pPlayer )
+{
+ // Calculate build position
+ Vector forward;
+ QAngle vecAngles = vec3_angle;
+ vecAngles.y = pPlayer->EyeAngles().y;
+ SetLocalAngles( vecAngles );
+ AngleVectors(vecAngles, &forward );
+
+ // Adjust build distance based upon object size
+ Vector2D xyDims;
+ xyDims.x = MAX( fabs( m_vecBuildMins.x ), fabs( m_vecBuildMaxs.x ) );
+ xyDims.y = MAX( fabs( m_vecBuildMins.y ), fabs( m_vecBuildMaxs.y ) );
+ float flDistance = xyDims.Length() + 16; // small safety buffer
+ Vector vecBuildOrigin = pPlayer->WorldSpaceCenter() + forward * flDistance;
+
+ bool bSnappedToPoint = false;
+ bool bShouldAttachToParent = false;
+
+ // See if there are any nearby build positions to snap to
+ Vector vecNearestBuildPoint = vec3_origin;
+ float flNearestPoint = 9999;
+ // First, look for nearby buildpoints on other objects
+ for ( int i = 0; i < GetTFTeam()->GetNumObjects(); i++ )
+ {
+ CBaseObject *pObject = GetTFTeam()->GetObject(i);
+ if ( pObject && !pObject->IsPlacing() )
+ {
+ if ( FindNearestBuildPoint( pObject, vecBuildOrigin, flNearestPoint, vecNearestBuildPoint ) )
+ {
+ bSnappedToPoint = true;
+
+ // If I'm a vehicle, I'm being built on an MCV. Don't attach to the parent.
+ if ( ShouldAttachToParent() )
+ {
+ bShouldAttachToParent = true;
+ }
+ }
+ }
+ }
+
+ // If we're a vehicle, look for vehicle build points
+ if ( IsAVehicle() )
+ {
+ CBaseEntity *pEntity = NULL;
+ while ((pEntity = gEntList.FindEntityByClassname( pEntity, "info_vehicle_bay" )) != NULL)
+ {
+ if ( FindNearestBuildPoint( pEntity, vecBuildOrigin, flNearestPoint, vecNearestBuildPoint ) )
+ {
+ bSnappedToPoint = true;
+ }
+ }
+ }
+
+ // Check for resource zones for resource pumps
+ if ( GetType() == OBJ_RESOURCEPUMP )
+ {
+ CBaseEntity *pEntity = NULL;
+ while ((pEntity = gEntList.FindEntityByClassname( pEntity, "trigger_resourcezone" )) != NULL)
+ {
+ if ( FindNearestBuildPoint( pEntity, vecBuildOrigin, flNearestPoint, vecNearestBuildPoint ) )
+ {
+ bSnappedToPoint = true;
+ }
+ }
+ }
+
+ // See if there's any mapdefined build points near me
+ int iCount = g_MapDefinedBuildPoints.Count();
+ for ( i = 0; i < iCount; i++ )
+ {
+ if ( !InSameTeam(g_MapDefinedBuildPoints[i]) )
+ continue;
+
+ if ( FindNearestBuildPoint( g_MapDefinedBuildPoints[i], vecBuildOrigin, flNearestPoint, vecNearestBuildPoint ) )
+ {
+ bSnappedToPoint = true;
+ }
+ }
+
+ // Upgrades become invisible if the player's not attaching them to a snap pint
+ if ( IsAnUpgrade() )
+ {
+ if ( MustBeBuiltOnAttachmentPoint() && !bSnappedToPoint )
+ {
+ AddEffects( EF_NODRAW );
+ return false;
+ }
+ else
+ {
+ RemoveEffects( EF_NODRAW );
+ }
+ }
+
+ // Did we find a snap point?
+ if ( bSnappedToPoint )
+ {
+ if ( bShouldAttachToParent )
+ {
+ AttachObjectToObject( m_hBuiltOnEntity.Get(), m_iBuiltOnPoint, vecNearestBuildPoint );
+ }
+
+ return CheckBuildOrigin( pPlayer, vecNearestBuildPoint, true );
+ }
+
+ // Clear out previous parent
+ if ( m_hBuiltOnEntity.Get() )
+ {
+ m_hBuiltOnEntity = NULL;
+ m_iBuiltOnPoint = 0;
+ SetParent( NULL );
+
+ SetupUnattachedVersion();
+ }
+
+ // Check the build position
+ return CheckBuildOrigin( pPlayer, vecBuildOrigin, false );
+}
+
+
+bool CBaseObject::VerifyCorner( const Vector &vBottomCenter, float xOffset, float yOffset )
+{
+ Vector vStart( vBottomCenter.x + xOffset, vBottomCenter.y + yOffset, vBottomCenter.z );
+
+ trace_t tr;
+ UTIL_TraceLine(
+ vStart,
+ vStart - Vector( 0, 0, tf_obj_ground_clearance.GetFloat() ),
+ MASK_SOLID, this, COLLISION_GROUP_PLAYER_MOVEMENT, &tr );
+
+ return !tr.startsolid && tr.fraction < 1;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Check under a build point to ensure it's buildable on
+//-----------------------------------------------------------------------------
+bool CBaseObject::CheckBuildPoint( Vector vecPoint, Vector &vecTrace, Vector *vecOutPoint )
+{
+ trace_t tr;
+
+ bool bClear = true;
+ Vector vecEnd;
+
+ // Ensure that this point isn't in a no-build zone:
+ if( !tf_fastbuild.GetInt() && NoBuildPreventsBuild(this, vecPoint ) )
+ bClear = false;
+
+ // If the point isn't in solid, trace down until we find the ground
+ if ( enginetrace->GetPointContents( vecPoint ) == CONTENTS_EMPTY )
+ {
+ vecEnd = vecPoint - vecTrace;
+ UTIL_TraceLine( vecPoint, vecEnd, MASK_SOLID, this, COLLISION_GROUP_PLAYER_MOVEMENT, &tr);
+
+ // Can't find ground to build on?
+ if ( tr.fraction == 1.0 )
+ {
+ bClear = false;
+ }
+ }
+ else
+ {
+ // If the point's solid, trace up until we find empty air
+ vecEnd = vecPoint + vecTrace;
+ UTIL_TraceLine( vecPoint, vecEnd, MASK_SOLID, this, COLLISION_GROUP_PLAYER_MOVEMENT, &tr);
+
+ // Can't find ground to build on?
+ if ( tr.allsolid )
+ {
+ bClear = false;
+ }
+ }
+
+ // FIXME: HACK! This is a test to try to make mud non-buildable!!
+ const surfacedata_t *pSurfaceProp = physprops->GetSurfaceData( tr.surface.surfaceProps );
+ if (pSurfaceProp->game.maxSpeedFactor < 1.0f)
+ bClear = false;
+
+ if ( vecOutPoint )
+ {
+ *vecOutPoint = tr.endpos;
+ }
+
+ return bClear;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+
+bool CBaseObject::CheckBuildOrigin( CBaseTFPlayer *pPlayer, const Vector &vecInitialBuildOrigin, bool bSnappedToPoint )
+{
+ // By default, use the vecBuildOrigin..
+ bool bResult = true;
+ m_vecBuildOrigin = vecInitialBuildOrigin;
+ Vector vErrorOrigin = vecInitialBuildOrigin - (m_vecBuildMaxs - m_vecBuildMins) * 0.5f - m_vecBuildMins;
+
+ // If we're snapping to a build point, don't bother performing area checks
+ if ( !bSnappedToPoint )
+ {
+ Vector vBuildDims = m_vecBuildMaxs - m_vecBuildMins;
+ Vector vHalfBuildDims = vBuildDims * 0.5;
+ Vector vHalfBuildDimsXY( vHalfBuildDims.x, vHalfBuildDims.y, 0 );
+
+ // Here, we start at the highest Z we'll allow for the top of the object. Then
+ // we sweep an XY cross section downwards until it hits the ground.
+ //
+ // The rule is that the top of to box can't go lower than the player's feet, and the bottom of the
+ // box can't go higher than the player's head.
+ //
+ // To simplify things in here, we treat the box as though it's symmetrical about all axes
+ // (so mins = -maxs), then reoffset the box at the very end.
+ Vector vHalfPlayerDims = (pPlayer->WorldAlignMaxs() - pPlayer->WorldAlignMins()) * 0.5f;
+ float flBoxTopZ = pPlayer->WorldSpaceCenter().z + vHalfPlayerDims.z + vBuildDims.z;
+ float flBoxBottomZ = pPlayer->WorldSpaceCenter().z - vHalfPlayerDims.z - vBuildDims.z;
+
+ // First, find the ground (ie: where the bottom of the box goes).
+ trace_t tr;
+ float bottomZ = 0;
+ int nIterations = 6;
+ float topZ = flBoxTopZ;
+ float topZInc = (flBoxBottomZ - flBoxTopZ) / (nIterations-1);
+ for ( int iIteration = 0; iIteration < nIterations; iIteration++ )
+ {
+ UTIL_TraceHull(
+ Vector( m_vecBuildOrigin.x, m_vecBuildOrigin.y, topZ ),
+ Vector( m_vecBuildOrigin.x, m_vecBuildOrigin.y, flBoxBottomZ ),
+ -vHalfBuildDimsXY, vHalfBuildDimsXY, MASK_SOLID, this, COLLISION_GROUP_PLAYER_MOVEMENT, &tr );
+ bottomZ = tr.endpos.z;
+
+ // If there is no ground, then we can't place here.
+ if ( tr.fraction == 1 )
+ {
+ m_vecBuildOrigin = vErrorOrigin;
+ return false;
+ }
+
+ // If it started in solid, keep moving down.
+ // Note that a working CGameTrace::fractionleftsolid would make this trivial, but it isn't
+ // working now so we must resort to rubitry.
+ if ( !tr.startsolid )
+ break;
+
+ topZ += topZInc;
+ }
+
+ if ( iIteration == nIterations )
+ {
+ m_vecBuildOrigin = vErrorOrigin;
+ return false;
+ }
+
+
+ // Now see if the range we've got leaves us room for our box.
+ if ( topZ - bottomZ < vBuildDims.z )
+ {
+ m_vecBuildOrigin = vErrorOrigin;
+ return false;
+ }
+
+ // Verify that it's not on too much of a slope by seeing how far the corners are from the ground.
+ Vector vBottomCenter( m_vecBuildOrigin.x, m_vecBuildOrigin.y, bottomZ );
+ if ( !VerifyCorner( vBottomCenter, -vHalfBuildDims.x, -vHalfBuildDims.y ) ||
+ !VerifyCorner( vBottomCenter, +vHalfBuildDims.x, +vHalfBuildDims.y ) ||
+ !VerifyCorner( vBottomCenter, +vHalfBuildDims.x, -vHalfBuildDims.y ) ||
+ !VerifyCorner( vBottomCenter, -vHalfBuildDims.x, +vHalfBuildDims.y ) )
+ {
+ m_vecBuildOrigin = vErrorOrigin;
+ return false;
+ }
+
+ // Ok, now we know the Z range where this box can fit.
+ Vector vBottomLeft = m_vecBuildOrigin - vHalfBuildDims;
+ vBottomLeft.z = bottomZ;
+ m_vecBuildOrigin = vBottomLeft - m_vecBuildMins;
+ }
+
+ Vector vecForward, vecRight, vecUp;
+ AngleVectors( GetLocalAngles(), &vecForward, &vecRight, &vecUp );
+ AttemptToFindPower();
+ AttemptToFindBuffStation();
+
+ // Make sure construction yards don't screw us up (tf_fastbuild allows builds anywhere)
+ if ( !tf_fastbuild.GetInt() && ConstructionYardPreventsBuild( this, m_vecBuildOrigin ))
+ return false;
+
+ // If we have to be attached to something, and we're not, abort
+ if ( !tf_fastbuild.GetInt() && MustBeBuiltOnAttachmentPoint() && !bSnappedToPoint )
+ return false;
+
+ // Make sure there aren't any solid objects in the area
+ if ( !bSnappedToPoint || IsAVehicle() )
+ {
+ if ( !(m_fObjectFlags & OF_DONT_PREVENT_BUILD_NEAR_OBJ) )
+ {
+ // Get a list of nearby entities
+ CBaseEntity *pListOfNearbyEntities[100];
+ int iNumberOfNearbyEntities = UTIL_EntitiesInSphere( pListOfNearbyEntities, 100, m_vecBuildOrigin, GetNearbyObjectCheckRadius(), 0 );
+ for ( int i = 0; i < iNumberOfNearbyEntities; i++ )
+ {
+ CBaseEntity *pEntity = pListOfNearbyEntities[i];
+ if ( pEntity->IsSolid( ) )
+ {
+ // Ignore shields..
+ if ( pEntity->GetCollisionGroup() == TFCOLLISION_GROUP_SHIELD )
+ continue;
+
+ // Ignore func brushes
+ // BUGBUG: Shouldn't this test against MOVETYPE_PUSH instead of SOLID_BSP?
+ if ( pEntity->GetSolid() == SOLID_BSP )
+ continue;
+
+ // Ignore the player who's building
+ if ( pEntity == GetBuilder() )
+ continue;
+
+ // YWB: Ignore other players
+ if ( pEntity->IsPlayer() )
+ continue;
+
+ // Ignore map placed objects
+ if ( pEntity->GetTeamNumber() == 0 )
+ continue;
+
+ //NDebugOverlay::EntityBounds( pEntity, 0,255,0,8, 0.1 );
+ return false;
+ }
+
+ // Sentryguns may be turtled, and non-solid
+ if ( pEntity->Classify() == CLASS_MILITARY )
+ {
+ CObjectSentrygun *pSentry = dynamic_cast<CObjectSentrygun*>(pEntity);
+ if ( pSentry && pSentry->IsTurtled() )
+ return false;
+ }
+ }
+ }
+ }
+
+ if ( !bSnappedToPoint )
+ {
+ AlignToGround( m_vecBuildOrigin );
+ }
+
+ return bResult;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Align myself to the ground below the specified point
+//-----------------------------------------------------------------------------
+void CBaseObject::AlignToGround( Vector vecOrigin )
+{
+ if ( !(m_fObjectFlags & OF_ALIGN_TO_GROUND) )
+ return;
+
+ trace_t tr;
+ Vector vecWorldMins, vecWorldMaxs;
+ CollisionProp()->WorldSpaceAABB( &vecWorldMins, &vecWorldMaxs );
+ float flHeight = MAX( vecWorldMaxs.z - vecWorldMins.z, 60 );
+ UTIL_TraceLine( vecOrigin, vecOrigin + Vector(0,0,-flHeight), MASK_SOLID, this, COLLISION_GROUP_PLAYER_MOVEMENT, &tr );
+ if ( tr.fraction != 1.0 )
+ {
+ // Orient the *up* axis to be along the plane normal
+ Vector perp( 1, 0, 0 );
+ Vector forward, right;
+ CrossProduct( perp, tr.plane.normal, forward );
+ if (forward.LengthSqr() < 0.1f)
+ {
+ perp.Init( 0, 1, 0 );
+ CrossProduct( perp, tr.plane.normal, forward );
+ }
+ VectorNormalize( forward );
+ CrossProduct( tr.plane.normal, forward, right );
+
+ VMatrix orientation( forward, right, tr.plane.normal );
+
+ QAngle angles;
+ MatrixToAngles( orientation, angles );
+ SetAbsAngles( angles );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Exit points for mounted vehicles....
+//-----------------------------------------------------------------------------
+void CBaseObject::GetExitPoint( CBaseEntity *pPlayer, int nBuildPoint, Vector *pAbsPosition, QAngle *pAbsAngles )
+{
+ // Deal with hierarchy...
+ IHasBuildPoints *pMount = dynamic_cast<IHasBuildPoints*>(GetMoveParent());
+ if (pMount)
+ {
+ int nBuildPoint = pMount->FindObjectOnBuildPoint( this );
+ if (nBuildPoint >= 0)
+ {
+ pMount->GetExitPoint( pPlayer, nBuildPoint, pAbsPosition, pAbsAngles );
+ return;
+ }
+ }
+
+ // FIXME: In future, we may well want to use specific exit attachments here...
+ GetBuildPoint( nBuildPoint, *pAbsPosition, *pAbsAngles );
+
+ // Move back along the forward direction a bit...
+ Vector vecForward, vecUp;
+ AngleVectors( *pAbsAngles, &vecForward, NULL, &vecUp );
+ *pAbsPosition -= vecForward * 60;
+ *pAbsPosition += vecUp * 30;
+
+ // Now select a good spot to drop onto
+ Vector vNewPos;
+ if ( !EntityPlacementTest(pPlayer, *pAbsPosition, vNewPos, true) )
+ {
+ Warning("Can't find valid place to exit object.\n");
+ return;
+ }
+
+ *pAbsPosition = vNewPos;
+}
+
+
+void CBaseObject::AdjustInitialBuildAngles()
+{
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Try and find power for this object during placement
+//-----------------------------------------------------------------------------
+void CBaseObject::AttemptToFindPower( void )
+{
+ // Human objects need power, so show the player if the current position will have power, but don't prevent building.
+ if ( !CanPowerupEver( POWERUP_POWER ) )
+ return;
+
+ // If I have a powerpack, see if I'm unable to keep power, or not needed.
+ // This is done before checking to see if the object needs power, because it may
+ // have once needed power, but doesn't anymore (i.e. snapped to an attachment point)
+ if ( m_hPowerPack )
+ {
+ m_hPowerPack->EnsureObjectPower( this );
+ }
+
+ // If I don't have a powerpack, or I just moved too far from it, look for a powerpack
+ if ( !m_hPowerPack )
+ {
+ GetTFTeam()->UpdatePowerpacks( NULL, this );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseObject::AttemptToFindBuffStation( void )
+{
+ // Check to see if this object can be connected to a buff station.
+ if ( !CanBeHookedToBuffStation() )
+ return;
+
+ // We have already found a buff station, we want to use - check distances.
+ if ( GetBuffStation() )
+ {
+ GetBuffStation()->CheckBuffConnection( this );
+ }
+ // Look for a buff station to use.
+ else
+ {
+ GetTFTeam()->UpdateBuffStations( NULL, this, true );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Move the placement model to the current position. Return false if it's an invalid position
+//-----------------------------------------------------------------------------
+bool CBaseObject::UpdatePlacement( CBaseTFPlayer *pPlayer )
+{
+ bool placementOk = CalculatePlacement( pPlayer );
+ if ( placementOk )
+ {
+ SetRenderColor( 255, 255, 255, GetRenderColor().a );
+ }
+ else
+ {
+ SetRenderColor( 255, 0, 0, GetRenderColor().a );
+ }
+
+ Teleport( &m_vecBuildOrigin, &GetLocalAngles(), NULL );
+
+ return placementOk;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CBaseObject::PreStartBuilding()
+{
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Start building the object
+//-----------------------------------------------------------------------------
+bool CBaseObject::StartBuilding( CBaseEntity *pBuilder )
+{
+ // Need to add the object to the team now...
+ CTFTeam *pTFTeam = ( CTFTeam * )GetGlobalTeam( GetTeamNumber() );
+
+ // Deduct the cost from the player
+ if ( pBuilder && pBuilder->IsPlayer() )
+ {
+ m_iAmountPlayerPaidForMe = ((CBaseTFPlayer*)pBuilder)->StartedBuildingObject( m_iObjectType );
+ if ( !m_iAmountPlayerPaidForMe )
+ {
+ // Player couldn't afford to pay for me, so abort
+ ClientPrint( (CBasePlayer*)pBuilder, HUD_PRINTCENTER, "Not enough resources.\n" );
+ StopPlacement();
+ return false;
+ }
+ }
+
+ // Add this object to the team's list (because we couldn't add it during
+ // placement mode)
+ if ( pTFTeam && !pTFTeam->IsObjectOnTeam( this ) )
+ {
+ pTFTeam->AddObject( this );
+ }
+
+ m_bPlacing = false;
+ m_bBuilding = true;
+ SetHealth( OBJECT_CONSTRUCTION_STARTINGHEALTH );
+ m_flPercentageConstructed = 0;
+
+ // Compute a good fitting AABB since we know where this thing belongs
+ if ( VPhysicsGetObject() && !IsBuiltOnAttachment() )
+ {
+ Vector absmins, absmaxs;
+ physcollision->CollideGetAABB( &absmins, &absmaxs, VPhysicsGetObject()->GetCollide(), GetAbsOrigin(), GetAbsAngles() );
+
+ // This is required to get the client + server looking the same
+ // since the client uses the mins to compute absmins + absmaxs
+ SetCollisionBounds( absmins - GetAbsOrigin(), absmaxs - GetAbsOrigin() );
+ }
+
+ m_nRenderMode = kRenderNormal;
+ RemoveSolidFlags( FSOLID_NOT_SOLID );
+
+ // NOTE: We must spawn the control panels now, instead of during
+ // Spawn, because until placement is started, we don't actually know
+ // the position of the control panel because we don't know what it's
+ // been attached to (could be a vehicle which supplies a different
+ // place for the control panel)
+ // NOTE: We must also spawn it before FinishedBuilding can be called
+ SpawnControlPanels();
+
+ // Tell the object we've been built on that we exist
+ if ( IsBuiltOnAttachment() && ShouldAttachToParent() )
+ {
+ IHasBuildPoints *pBPInterface = dynamic_cast<IHasBuildPoints*>((CBaseEntity*)m_hBuiltOnEntity.Get());
+ Assert( pBPInterface );
+ pBPInterface->SetObjectOnBuildPoint( m_iBuiltOnPoint, this );
+ }
+
+ // Start the build animations
+ m_flTotalConstructionTime = m_flConstructionTimeLeft = GetTotalTime();
+
+ if ( pBuilder && pBuilder->IsPlayer() )
+ {
+ ((CBaseTFPlayer*)pBuilder)->FinishedObject( this );
+ }
+
+ m_vecBuildOrigin = GetAbsOrigin();
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Continue construction of this object
+//-----------------------------------------------------------------------------
+void CBaseObject::BuildingThink( void )
+{
+ // Continue construction
+ Repair( (GetMaxHealth() - OBJECT_CONSTRUCTION_STARTINGHEALTH) / m_flTotalConstructionTime * OBJECT_CONSTRUCTION_INTERVAL );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseObject::AttemptToActivateBuffStation( void )
+{
+ if ( !GetBuffStation() )
+ return;
+
+ if ( GetBuffStation()->IsPlacing() || GetBuffStation()->IsBuilding() ||
+ !GetBuffStation()->IsPowered() )
+ return;
+
+ if ( m_bBuffActivated )
+ return;
+
+ BuffStationActivate();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseObject::SetControlPanelsActive( bool bState )
+{
+ // Activate control panel screens
+ for ( int i = m_hScreens.Count(); --i >= 0; )
+ {
+ if (m_hScreens[i].Get())
+ {
+ m_hScreens[i]->SetActive( bState );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseObject::FinishedBuilding( void )
+{
+ SetControlPanelsActive( true );
+
+ // Only make a shadow if the object doesn't use vphysics
+ if (!VPhysicsGetObject())
+ {
+ VPhysicsInitStatic();
+ }
+
+ m_bBuilding = false;
+
+ AttemptToGoActive();
+ AttemptToActivateBuffStation();
+
+ // We're done building, add in the stat...
+ TFStats()->IncrementStat( (TFStatId_t)(TF_STAT_FIRST_OBJECT_BUILT + ObjectType()), 1 );
+
+ // Spawn any objects on this one
+ SpawnObjectPoints();
+
+ // Let our vehicle bay know, if we have one
+ if ( m_hVehicleBay )
+ {
+ m_hVehicleBay->FinishedBuildVehicle( this );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Objects store health in hacky ways
+//-----------------------------------------------------------------------------
+void CBaseObject::SetHealth( float flHealth )
+{
+ if ( IsDisabled() )
+ {
+ if ( ( m_flMinDisableHealth != 0.0f && flHealth > m_flMinDisableHealth ) ||
+ ( flHealth > 1 ) )
+ {
+ // Reenable and fire output
+ SetDisabled( false );
+
+ m_OnBecomingReenabled.FireOutput( this, this );
+ }
+ }
+
+ bool changed = m_flHealth != flHealth;
+
+ m_flHealth = flHealth;
+ m_iHealth = ceil(m_flHealth);
+
+ // If we have a model, and a pose parameter, set the pose parameter to reflect our health
+ if ( !(m_fObjectFlags & OF_DOESNT_HAVE_A_MODEL) )
+ {
+ if (LookupPoseParameter( "object_health") >= 0 && GetMaxHealth() > 0 )
+ {
+ SetPoseParameter( "object_health", 100 * ( GetHealth() / (float)GetMaxHealth() ) );
+ }
+ }
+
+ if ( changed )
+ {
+ // Set value and fire output
+ m_OnObjectHealthChanged.Set( m_flHealth, this, this );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Powerup has just started
+//-----------------------------------------------------------------------------
+void CBaseObject::PowerupStart( int iPowerup, float flAmount, CBaseEntity *pAttacker, CDamageModifier *pDamageModifier )
+{
+ switch( iPowerup )
+ {
+ case POWERUP_BOOST:
+ {
+ // Can we boost health further?
+ if ( GetHealth() < GetMaxHealth() )
+ {
+ /*
+ if ( (gpGlobals->curtime - m_flLastRepairTime) > 0.01 )
+ {
+ Msg("TOTAL REPAIR: %.2f in %.2f\n\n", m_flRepairedSinceLastTime, (gpGlobals->curtime - m_flLastRepairTime) );
+ }
+
+ if ( pAttacker->IsPlayer() )
+ {
+ CBaseTFPlayer *pPlayer = (CBaseTFPlayer *)pAttacker;
+ Msg("(%.2f) %s repaired %s for %.2f health.\n", gpGlobals->curtime, pPlayer->GetPlayerName(), GetClassname(), flAmount );
+ }
+ */
+ // Is this repair happening at the same time as other repairing on me?
+ if ( (gpGlobals->curtime - m_flLastRepairTime) < 0.01 )
+ {
+ //Msg(" ->Reducing repair by %.2f\n", m_flNextRepairMultiplier );
+ flAmount *= m_flNextRepairMultiplier;
+ m_flNextRepairMultiplier *= 0.5;
+ }
+ else
+ {
+ m_flLastRepairTime = gpGlobals->curtime;
+ m_flNextRepairMultiplier = 0.5;
+ m_flRepairedSinceLastTime = 0;
+ }
+
+ //Msg(" REPAIRED: %.2f\n", flAmount );
+
+ Repair( flAmount );
+
+ m_flRepairedSinceLastTime += flAmount;
+ }
+
+ // Prevent callback to base class, since we handled it here
+ return;
+ }
+ break;
+
+ case POWERUP_POWER:
+ {
+ Assert( m_hPowerPack );
+ AttemptToGoActive();
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ BaseClass::PowerupStart( iPowerup, flAmount, pAttacker, pDamageModifier );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Powerup has just started
+//-----------------------------------------------------------------------------
+void CBaseObject::PowerupEnd( int iPowerup )
+{
+ switch( iPowerup )
+ {
+ case POWERUP_POWER:
+ {
+ OnGoInactive();
+ m_hPowerPack = NULL;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ BaseClass::PowerupEnd( iPowerup );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Override base traceattack to prevent visible effects from team members shooting me
+//-----------------------------------------------------------------------------
+void CBaseObject::TraceAttack( const CTakeDamageInfo &inputInfo, const Vector &vecDir, trace_t *ptr )
+{
+ // Prevent team damage here so blood doesn't appear
+ if ( inputInfo.GetAttacker() )
+ {
+ if ( InSameTeam(inputInfo.GetAttacker()) )
+ return;
+ }
+
+ float fVulnerableMultiplier = FindVulnerablePointMultiplier( ptr->hitgroup, ptr->hitbox );
+
+ CTakeDamageInfo info = inputInfo;
+ info.ScaleDamage( fVulnerableMultiplier );
+
+ SpawnBlood( ptr->endpos, vecDir, BloodColor(), info.GetDamage() );
+ AddMultiDamage( info, this );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Prevent Team Damage
+//-----------------------------------------------------------------------------
+ConVar object_show_damage( "obj_show_damage", "0", 0, "Show all damage taken by objects." );
+ConVar object_capture_damage( "obj_capture_damage", "0", 0, "Captures all damage taken by objects for dumping later." );
+
+CUtlDict<int,int> g_DamageMap;
+
+void Cmd_DamageDump_f(void)
+{
+ CUtlDict<bool,int> g_UniqueColumns;
+
+ // Build the unique columns:
+ for( int idx = g_DamageMap.First(); idx != g_DamageMap.InvalidIndex(); idx = g_DamageMap.Next(idx) )
+ {
+ char* szColumnName = strchr(g_DamageMap.GetElementName(idx),',') + 1;
+
+ int ColumnIdx = g_UniqueColumns.Find( szColumnName );
+
+ if( ColumnIdx == g_UniqueColumns.InvalidIndex() )
+ {
+ g_UniqueColumns.Insert( szColumnName, false );
+ }
+ }
+
+ // Dump the column names:
+ FileHandle_t f = filesystem->Open("damage.txt","wt+");
+
+ for( idx = g_UniqueColumns.First(); idx != g_UniqueColumns.InvalidIndex(); idx = g_UniqueColumns.Next(idx) )
+ {
+ filesystem->FPrintf(f,"\t%s",g_UniqueColumns.GetElementName(idx));
+ }
+
+ filesystem->FPrintf(f,"\n");
+
+
+ CUtlDict<bool,int> g_CompletedRows;
+
+ // Dump each row:
+ bool bDidRow;
+
+ do
+ {
+ bDidRow = false;
+
+ for( idx = g_DamageMap.First(); idx != g_DamageMap.InvalidIndex(); idx = g_DamageMap.Next(idx) )
+ {
+ char szRowName[256];
+
+ // Check the Row name of each entry to see if I've done this row yet.
+ Q_strncpy(szRowName, g_DamageMap.GetElementName(idx), sizeof( szRowName ) );
+ *strchr(szRowName,',') = '\0';
+
+ char szRowNameComma[256];
+ Q_snprintf( szRowNameComma, sizeof( szRowNameComma ), "%s,", szRowName );
+
+ if( g_CompletedRows.Find(szRowName) == g_CompletedRows.InvalidIndex() )
+ {
+ bDidRow = true;
+ g_CompletedRows.Insert(szRowName,false);
+
+
+ // Output the row name:
+ filesystem->FPrintf(f,szRowName);
+
+ for( int ColumnIdx = g_UniqueColumns.First(); ColumnIdx != g_UniqueColumns.InvalidIndex(); ColumnIdx = g_UniqueColumns.Next( ColumnIdx ) )
+ {
+ char szRowNameCommaColumn[256];
+ Q_strncpy( szRowNameCommaColumn, szRowNameComma, sizeof( szRowNameCommaColumn ) );
+ Q_strncat( szRowNameCommaColumn, g_UniqueColumns.GetElementName( ColumnIdx ), sizeof( szRowNameCommaColumn ), COPY_ALL_CHARACTERS );
+
+ int nDamageAmount = 0;
+ // Fine to reuse idx since we are going to break anyways.
+ for( idx = g_DamageMap.First(); idx != g_DamageMap.InvalidIndex(); idx = g_DamageMap.Next(idx) )
+ {
+ if( !stricmp( g_DamageMap.GetElementName(idx), szRowNameCommaColumn ) )
+ {
+ nDamageAmount = g_DamageMap[idx];
+ break;
+ }
+ }
+
+ filesystem->FPrintf(f,"\t%i",nDamageAmount);
+
+ }
+
+ filesystem->FPrintf(f,"\n");
+ break;
+ }
+ }
+ // Grab the row name:
+
+ } while(bDidRow);
+
+ // close the file:
+ filesystem->Close(f);
+}
+
+static ConCommand obj_dump_damage( "obj_dump_damage", Cmd_DamageDump_f );
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void ReportDamage( const char* szInflictor, const char* szVictim, float fAmount, int nCurrent, int nMax )
+{
+ int iAmount = (int)fAmount;
+
+ if( object_show_damage.GetBool() && iAmount )
+ {
+ Msg( "ShowDamage: Object %s taking %0.1f damage from %s ( %i / %i )\n", szVictim, fAmount, szInflictor, nCurrent, nMax );
+ }
+
+ if( object_capture_damage.GetBool() )
+ {
+ char szMangledKey[256];
+
+ Q_snprintf(szMangledKey,sizeof(szMangledKey)/sizeof(szMangledKey[0]),"%s,%s",szInflictor,szVictim);
+ int idx = g_DamageMap.Find( szMangledKey );
+
+ if( idx == g_DamageMap.InvalidIndex() )
+ {
+ g_DamageMap.Insert( szMangledKey, iAmount );
+
+ } else
+ {
+ g_DamageMap[idx] += iAmount;
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Pass the specified amount of damage through to any objects I have built on me
+//-----------------------------------------------------------------------------
+bool CBaseObject::PassDamageOntoChildren( const CTakeDamageInfo &info, float *flDamageLeftOver )
+{
+ IHasBuildPoints *pBPInterface = dynamic_cast<IHasBuildPoints*>(this);
+ Assert( pBPInterface );
+
+ float flDamage = info.GetDamage();
+
+ // Double the amount of damage done (and get around the child damage modifier)
+ flDamage *= 2;
+ if ( obj_child_damage_factor.GetFloat() )
+ {
+ flDamage *= (1 / obj_child_damage_factor.GetFloat());
+ }
+
+ // Remove blast damage because child objects (well specifically upgrades)
+ // want to ignore direct blast damage but still take damage from parent
+ CTakeDamageInfo childInfo = info;
+ childInfo.SetDamage( flDamage );
+ childInfo.SetDamageType( info.GetDamageType() & (~DMG_BLAST) );
+
+ CBaseEntity *pEntity = pBPInterface->GetFirstObjectOnMe();
+ while ( pEntity )
+ {
+ Assert( pEntity->m_takedamage != DAMAGE_NO );
+ // Do damage to the next object
+ float flDamageTaken = pEntity->OnTakeDamage( childInfo );
+ // If we didn't kill it, abort
+ CBaseObject *pObject = dynamic_cast<CBaseObject*>(pEntity);
+ if ( !pObject || !pObject->IsDying() )
+ {
+ char* szInflictor = "unknown";
+ if( info.GetInflictor() )
+ szInflictor = (char*)info.GetInflictor()->GetClassname();
+
+ ReportDamage( szInflictor, GetClassname(), flDamageTaken, GetHealth(), GetMaxHealth() );
+
+ *flDamageLeftOver = flDamage;
+ return true;
+ }
+ // Reduce the damage and move on to the next
+ flDamage -= flDamageTaken;
+ pEntity = pBPInterface->GetFirstObjectOnMe();
+ }
+
+ *flDamageLeftOver = flDamage;
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+int CBaseObject::OnTakeDamage( const CTakeDamageInfo &info )
+{
+ // Prevent damage if the game hasn't started yet
+ if ( CurrentActIsAWaitingAct() )
+ return 0;
+ if ( !IsAlive() )
+ return info.GetDamage();
+ if (m_bInvulnerable)
+ return 0;
+ if ( m_takedamage == DAMAGE_NO )
+ return 0;
+ if ( IsPlacing() )
+ return 0;
+
+ // Check teams
+ if ( info.GetAttacker() )
+ {
+ if ( InSameTeam(info.GetAttacker()) )
+ return 0;
+ }
+
+ IHasBuildPoints *pBPInterface = dynamic_cast<IHasBuildPoints*>(this);
+
+ float flDamage = info.GetDamage();
+
+ // Objects take half damage from bullets
+ if ( info.GetDamageType() & DMG_BULLET )
+ {
+ flDamage *= 0.5;
+ }
+
+ // Objects build on other objects take less damage
+ if ( !IsAnUpgrade() && GetParentObject() )
+ {
+ flDamage *= obj_child_damage_factor.GetFloat();
+ }
+
+ if (obj_damage_factor.GetFloat())
+ {
+ flDamage *= obj_damage_factor.GetFloat();
+ }
+
+ // Constructing objects take extra damage
+ if ( IsBuilding() )
+ {
+ flDamage *= 3;
+ }
+
+ // If has min health, and damage would put it below min health disable it if not already disabled
+ bool bShouldBeDisabled = false;
+ if ( m_flMinDisableHealth != 0 && ( m_flHealth - flDamage ) < m_flMinDisableHealth )
+ {
+ bShouldBeDisabled = true;
+ }
+ else if ( pBPInterface && pBPInterface->GetNumObjectsOnMe() && (( m_flHealth - flDamage ) < 1) )
+ {
+ bShouldBeDisabled = true;
+ }
+
+ // Make sure we're disabled if we're supposed to be
+ if ( bShouldBeDisabled )
+ {
+ // Remove any sappers on me
+ if ( m_bCantDie )
+ {
+ RemoveAllSappers( this );
+ }
+
+ // Make sure this only fires first time we cross the threshold and go disabled
+ if ( !IsDisabled() )
+ {
+ SetDisabled( true );
+ m_OnBecomingDisabled.FireOutput( info.GetAttacker(), this );
+
+ // Special case: If we have a min disabled health, and we're set to not die, immediately fall to 1 health
+ if ( m_bCantDie )
+ {
+ SetHealth( 1 );
+ }
+ }
+ }
+
+ // If I have objects on me, I can't be destroyed until they're gone. Ditto if I can't be killed.
+ bool bWillDieButCant = (m_bCantDie || pBPInterface->GetNumObjectsOnMe()) && (( m_flHealth - flDamage ) < 1);
+ if ( bWillDieButCant )
+ {
+ // Soak up the damage it would take to drop us to 1 health
+ flDamage = flDamage - m_flHealth;
+ SetHealth( 1 );
+
+ // Pass leftover damage
+ if ( flDamage )
+ {
+ if ( PassDamageOntoChildren( info, &flDamage ) )
+ return flDamage;
+ }
+ }
+
+ if ( flDamage )
+ {
+ // Recheck our death possibility, because our objects may have all been blown off us by now
+ bWillDieButCant = (m_bCantDie || pBPInterface->GetNumObjectsOnMe()) && (( m_flHealth - flDamage ) < 1);
+ if ( !bWillDieButCant )
+ {
+ // Reduce health
+ SetHealth( m_flHealth - flDamage );
+ }
+ }
+
+ m_OnDamaged.FireOutput(info.GetAttacker(), this);
+
+ // Hurt by an enemy?
+ if ( info.GetAttacker() && info.GetAttacker()->entindex() > 0 )
+ {
+ m_flLastRealDamage = gpGlobals->curtime;
+ }
+
+ if ( GetHealth() <= 0 )
+ {
+ if ( info.GetAttacker() )
+ {
+ TFStats()->IncrementTeamStat( info.GetAttacker()->GetTeamNumber(), TF_TEAM_STAT_DESTROYED_OBJECT_COUNT, 1 );
+ TFStats()->IncrementPlayerStat( info.GetAttacker(), TF_PLAYER_STAT_DESTROYED_OBJECT_COUNT, 1 );
+ }
+
+ m_lifeState = LIFE_DEAD;
+ m_OnDestroyed.FireOutput( info.GetAttacker(), this);
+ Killed();
+ }
+ else
+ {
+ // Notify team about interesting stuff going on with this object
+ if ( !(m_fObjectFlags & OF_SUPPRESS_NOTIFY_UNDER_ATTACK) && ( m_iszUnderAttackSound != NULL_STRING ) )
+ {
+ CTFTeam *pTeam = GetTFTeam();
+ if ( pTeam )
+ {
+ Vector vecPosition = GetAbsOrigin();
+
+ // Tell everyone on the team that this object's underattack
+ CRecipientFilter myteam;
+ myteam.MakeReliable();
+ myteam.AddRecipientsByTeam( pTeam );
+ UserMessageBegin( myteam, "MinimapPulse" );
+ WRITE_VEC3COORD( vecPosition );
+ MessageEnd();
+
+ GetTFTeam()->PostMessage( TEAMMSG_CUSTOM_SOUND, NULL, (char*)STRING(m_iszUnderAttackSound) );
+ }
+ }
+ }
+
+ {
+ char* szInflictor = "unknown";
+ if( info.GetInflictor() )
+ szInflictor = (char*)info.GetInflictor()->GetClassname();
+
+ ReportDamage( szInflictor, GetClassname(), flDamage, GetHealth(), GetMaxHealth() );
+ }
+
+ return flDamage;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Get the time it will take to repair this object
+//-----------------------------------------------------------------------------
+float CBaseObject::GetRepairTime( void )
+{
+ // Can't be repaired while being constructed
+ if ( IsBuilding() )
+ return 0;
+
+ int iRepairHealth = GetMaxHealth() - GetHealth();
+ if ( iRepairHealth )
+ {
+ return ((float)iRepairHealth / OBJECT_REPAIR_RATE);
+ }
+
+ return 0;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Repair myself for the time period passed in. Return true if I'm fully repaired.
+//-----------------------------------------------------------------------------
+bool CBaseObject::UpdateRepair( float flRepairTime )
+{
+ return Repair( (flRepairTime * OBJECT_REPAIR_RATE) );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Repair / Help-Construct this object the specified amount
+//-----------------------------------------------------------------------------
+bool CBaseObject::Repair( float flHealth )
+{
+ // Multiply it by the repair rate
+ flHealth *= m_flRepairMultiplier;
+ if ( !flHealth )
+ return false;
+
+ if ( IsBuilding() )
+ {
+ if ( HasPowerup(POWERUP_EMP) )
+ return false;
+
+ // Reduce the construction time by the correct amount for the health passed in
+ float flConstructionTime = flHealth / ((GetMaxHealth() - OBJECT_CONSTRUCTION_STARTINGHEALTH) / m_flTotalConstructionTime);
+ m_flConstructionTimeLeft = MAX( 0, m_flConstructionTimeLeft - flConstructionTime);
+ m_flConstructionTimeLeft = clamp( m_flConstructionTimeLeft, 0.0f, m_flTotalConstructionTime );
+ m_flPercentageConstructed = 1 - (m_flConstructionTimeLeft / m_flTotalConstructionTime);
+ m_flPercentageConstructed = clamp( m_flPercentageConstructed, 0.0f, 1.0f );
+
+ // Increase health.
+ SetHealth( MIN( GetMaxHealth(), m_flHealth + flHealth ) );
+
+ // Return true if we're constructed now
+ if ( m_flConstructionTimeLeft <= 0.0f )
+ {
+ FinishedBuilding();
+ return true;
+ }
+ }
+ else
+ {
+ // Return true if we're already fully healed
+ if ( GetHealth() >= GetMaxHealth() )
+ return true;
+
+ // Increase health.
+ SetHealth( MIN( GetMaxHealth(), m_flHealth + flHealth ) );
+
+ m_OnRepaired.FireOutput( this, this);
+
+ // Return true if we're fully healed now
+ if ( GetHealth() == GetMaxHealth() )
+ return true;
+ }
+
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Object has been blown up. Drop resource chunks upto the value of my max health.
+//-----------------------------------------------------------------------------
+void CBaseObject::Killed( void )
+{
+ m_bDying = true;
+
+ // Do an explosion.
+ CPASFilter filter( GetAbsOrigin() );
+ te->Explosion(
+ filter,
+ 0.0,
+ &GetAbsOrigin(),
+ g_sModelIndexFireball,
+ 5.4, // radius
+ 15,
+ TE_EXPLFLAG_NODLIGHTS,
+ 256,
+ 200);
+
+ Vector vecOrigin = WorldSpaceCenter() + Vector(0,0,32);
+
+ bool bDropResources = true;
+
+ // Don't drop resources if I'm built out of brushes, or I'm an upgrade
+ if ( m_fObjectFlags & OF_DOESNT_HAVE_A_MODEL || IsAnUpgrade() )
+ {
+ bDropResources = false;
+ }
+
+ // Don't drop resources if I haven't taken damage from an enemy for a while (i.e. I've deteriorated instead)
+ if ( gpGlobals->curtime > (m_flLastRealDamage + MAX_DROP_TIME_AFTER_DAMAGE) )
+ {
+ bDropResources = false;
+ }
+
+ // Drop resources based upon our base cost
+ int iCost = CalculateObjectCost( GetType(), 0, GetTeamNumber() );
+ iCost *= 0.5;
+ if ( bDropResources && iCost )
+ {
+ // Convert value to chunks.
+ int nProcessedChunks = 0;
+ int nNormalChunks = 0;
+ ConvertResourceValueToChunks( iCost, &nProcessedChunks, &nNormalChunks );
+
+ // Make everything drop at least 1 chunk
+ if ( !nProcessedChunks && !nNormalChunks )
+ {
+ nNormalChunks++;
+ }
+
+ // Drop processed chunks.
+ int iChunk;
+ for ( iChunk = 0; iChunk < nProcessedChunks; iChunk++ )
+ {
+ // Generate a random velocity vector.
+ Vector vecVelocity = Vector( random->RandomFloat( -250,250 ), random->RandomFloat( -250,250 ), random->RandomFloat( 200,450 ) );
+
+ // Create a processed chunk.
+ CResourceChunk *pChunk = CResourceChunk::Create( true, vecOrigin, vecVelocity );
+ pChunk->ChangeTeam( GetTeamNumber() );
+ }
+
+ // Drop normal chunks
+ for ( iChunk = 0; iChunk < nNormalChunks; iChunk++ )
+ {
+ // Generate a random velocity vector.
+ Vector vecVelocity = Vector( random->RandomFloat( -250,250 ), random->RandomFloat( -250,250 ), random->RandomFloat( 200,450 ) );
+
+ // Create a processed chunk.
+ CResourceChunk *pChunk = CResourceChunk::Create( false, vecOrigin, vecVelocity );
+ pChunk->ChangeTeam( GetTeamNumber() );
+ }
+
+ TFStats()->IncrementTeamStat( GetTeamNumber(), TF_TEAM_STAT_RESOURCE_CHUNKS_DROPPED, (resource_chunk_processed_value.GetFloat() * nProcessedChunks) + (resource_chunk_value.GetFloat() * nNormalChunks) );
+ }
+
+ DetachObjectFromObject();
+
+ UTIL_Remove( this );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Indicates this NPC's place in the relationship table.
+//-----------------------------------------------------------------------------
+Class_T CBaseObject::Classify( void )
+{
+ return (CLASS_MILITARY);
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Get the type of this object
+//-----------------------------------------------------------------------------
+int CBaseObject::GetType()
+{
+ return m_iObjectType;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Get the builder of this object
+//-----------------------------------------------------------------------------
+CBaseTFPlayer *CBaseObject::GetBuilder( void )
+{
+ return m_hBuilder;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Get the original builder of this object
+// Used to get the builder of a deteriorating object
+//-----------------------------------------------------------------------------
+CBaseTFPlayer *CBaseObject::GetOriginalBuilder( void )
+{
+ return m_hOriginalBuilder;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Return true if the Owning CTeam should clean this object up automatically
+//-----------------------------------------------------------------------------
+bool CBaseObject::ShouldAutoRemove( void )
+{
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: If the object's still being built, it's not usable
+//-----------------------------------------------------------------------------
+int CBaseObject::ObjectCaps( void )
+{
+ if ( IsPlacing() )
+ return 0;
+
+ // If I'm being built, only allow +use if I don't have a sapper on me and I haven't been disabled by a plasma weapon
+ if ( IsBuilding() && !HasSapper() && !IsPlasmaDisabled() )
+ return 0;
+
+ return FCAP_ONOFF_USE;
+};
+
+//-----------------------------------------------------------------------------
+// Clean off the object of offensive material...
+//-----------------------------------------------------------------------------
+bool CBaseObject::RemoveEnemyAttachments( CBaseEntity *pActivator )
+{
+ bool bRemoved = false;
+
+ // Sapper removal
+ if ( pActivator->IsPlayer() )
+ {
+ if ( HasSapper() )
+ {
+ RemoveAllSappers( pActivator );
+ bRemoved = true;
+ }
+ }
+
+ return bRemoved;
+}
+
+
+//-----------------------------------------------------------------------------
+// Object using!
+//-----------------------------------------------------------------------------
+void CBaseObject::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
+{
+ // If we're friendly, pickup / remove sappers
+ // If we're an enemy, plant a sapper
+ if ( pActivator->IsPlayer() )
+ {
+ if ( InSameTeam( pActivator ) )
+ {
+ if ( useType == USE_ON )
+ {
+ // Some combat objects can be picked up
+ if ( m_fObjectFlags & OF_CAN_BE_PICKED_UP )
+ {
+ if ( GetBuilder() == pActivator )
+ {
+ if ( GetBuilder()->GetPlayerClass()->ResupplyAmmoType( 1, m_szAmmoName ) )
+ {
+ PickupObject();
+ }
+ return;
+ }
+ }
+
+ // Sapper removal
+ if ( RemoveEnemyAttachments( pActivator ) )
+ return;
+ }
+ }
+ else
+ {
+ CBaseTFPlayer *pPlayer = (CBaseTFPlayer *)pActivator;
+
+ // If we're already planting a sapper, abort
+ if ( useType == USE_OFF || pPlayer->IsAttachingSapper() )
+ {
+ // Don't abort if we just started placing it. This is to catch people who'd like to +use toggle instead of hold down
+ if ( pPlayer->GetSapperAttachmentTime() > 0.2 && pPlayer->IsAttachingSapper() )
+ {
+ pPlayer->StopAttaching();
+ }
+ }
+ else if ( useType == USE_ON )
+ {
+ // Don't allow sappers to be planted on invulnerable objects
+ if ( m_bInvulnerable )
+ return;
+
+ // If the object's already got a sapper from me on it, I can't put another
+ if ( HasSapperFromPlayer( ((CBaseTFPlayer*)pActivator ) ) )
+ return;
+
+ Vector vecAiming;
+ pPlayer->EyeVectors( &vecAiming );
+ // Trace from the player to the object to find an attachment position
+ trace_t tr;
+ Vector vecStart = pPlayer->EyePosition();
+ UTIL_TraceLine( vecStart, vecStart + (vecAiming * 256), MASK_SOLID, pPlayer, COLLISION_GROUP_NONE, &tr );
+ if ( tr.fraction < 1.0 && tr.m_pEnt == this )
+ {
+ CGrenadeObjectSapper *sapper = CGrenadeObjectSapper::Create( tr.endpos, vecAiming, pPlayer, this );
+ pPlayer->StartAttachingSapper( this, sapper );
+ }
+ }
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Builder has picked up the object
+//-----------------------------------------------------------------------------
+void CBaseObject::PickupObject( void )
+{
+ // Tell the playerclass
+ if ( GetBuilder() && GetBuilder()->GetPlayerClass() )
+ {
+ GetBuilder()->GetPlayerClass()->PickupObject( this );
+ }
+
+ UTIL_Remove( this );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Return true if the specified player's allowed to remove this object
+//-----------------------------------------------------------------------------
+bool CBaseObject::CanBeRemovedBy( CBaseTFPlayer *pPlayer )
+{
+ if ( m_fObjectFlags & OF_CANNOT_BE_DISMANTLED )
+ return false;
+
+ // If I'm a map-defined object, I'm not removable by anyone
+ if ( WasMapPlaced() )
+ return false;
+
+ // If I have an owner, only he can remove me
+ if ( GetBuilder() != pPlayer )
+ return false;
+
+ return true;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Return true if the specified player's allowed to rotate this object
+//-----------------------------------------------------------------------------
+bool CBaseObject::CanBeRotatedBy( CBaseTFPlayer *pPlayer )
+{
+ // If I'm a map-defined object, I'm not removable by anyone
+ if ( WasMapPlaced() )
+ return false;
+
+ // If I have an owner, only he can remove me
+ if ( GetBuilder() != pPlayer )
+ return false;
+
+ return true;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : iTeamNum -
+//-----------------------------------------------------------------------------
+void CBaseObject::ChangeTeam( int iTeamNum )
+{
+ CTFTeam *pTeam = ( CTFTeam * )GetGlobalTeam( iTeamNum );
+ CTFTeam *pExisting = ( CTFTeam * )GetTeam();
+
+ TRACE_OBJECT( UTIL_VarArgs( "%0.2f CBaseObject::ChangeTeam old %s new %s\n", gpGlobals->curtime,
+ pExisting ? pExisting->GetName() : "NULL",
+ pTeam ? pTeam->GetName() : "NULL" ) );
+
+ // Already on this team
+ if ( GetTeamNumber() == iTeamNum )
+ return;
+
+ if ( pExisting )
+ {
+ // Remove it from current team ( if it's in one ) and give it to new team
+ pExisting->RemoveObject( this );
+ }
+
+ // Change to new team
+ BaseClass::ChangeTeam( iTeamNum );
+
+ // Add this object to the team's list
+ // But only if we're not placing it
+ if ( pTeam && (!m_bPlacing) )
+ {
+ pTeam->AddObject( this );
+ }
+
+ // Setup for our new team's model
+ SetupTeamModel();
+ CreateBuildPoints();
+ CreateVulnerablePoints();
+
+ // Alien buildings never need power
+ if ( GetTeamNumber() == TEAM_ALIENS )
+ {
+ m_fObjectFlags |= OF_DOESNT_NEED_POWER;
+ }
+
+ GainedNewTechnology( NULL );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Output : const char
+//-----------------------------------------------------------------------------
+const char *CBaseObject::GetWeaponClassnameForObject( void )
+{
+ return NULL;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : *pNewOwner -
+//-----------------------------------------------------------------------------
+void CBaseObject::AddItemsNeededForObject( CBaseTFPlayer *pNewOwner )
+{
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Derived classes might want to give the new builder the appropriate
+// items needed to own this object and move the objects owned over as well
+// Input : *pNewOwner -
+//-----------------------------------------------------------------------------
+void CBaseObject::ChangeBuilder( CBaseTFPlayer *pNewBuilder, bool moveobjects )
+{
+ CBaseTFPlayer *oldBuilder = GetOwner();
+
+ TRACE_OBJECT( UTIL_VarArgs( "%0.2f CBaseObject::ChangeBuilder old %s, new %s, moveobjects %s\n", gpGlobals->curtime,
+ oldBuilder ? oldBuilder->GetPlayerName() : "NULL",
+ pNewBuilder ? pNewBuilder->GetPlayerName() : "NULL",
+ moveobjects ? "true" : "false" ) );
+
+ // Store off original builder
+ if ( GetOwner() )
+ {
+ m_hOriginalBuilder = GetOwner();
+ }
+
+ m_hBuilder = pNewBuilder;
+
+ if ( !moveobjects )
+ return;
+
+ if ( oldBuilder )
+ {
+ oldBuilder->OwnedObjectChangeTeam( this, pNewBuilder );
+ }
+
+ // For instance, if this is a mortar being added to a technician via subversion, then
+ // the "weapon_mortar" will be added to the player if the player doesn't have it.
+ AddItemsNeededForObject( pNewBuilder );
+
+ const char *classname = GetWeaponClassnameForObject();
+ if ( !classname )
+ return;
+
+ TRACE_OBJECT( UTIL_VarArgs( "%0.2f CBaseObject::ChangeBuilder moving associated objects %s\n", gpGlobals->curtime,
+ classname ) );
+
+ // Find the old player who owned a weapon that owned this object type and remove it
+ // Then add to current player under the approrpriate weapon ( same classname ) which
+ // should have been added in ChangeBuilder
+ for ( int i = 1; i <= gpGlobals->maxClients; i++ )
+ {
+ CBaseTFPlayer *player = static_cast< CBaseTFPlayer *>( UTIL_PlayerByIndex( i ) );
+ if ( !player )
+ continue;
+
+ // Cycle through weapons
+ for ( int j = 0; j < player->WeaponCount(); j++ )
+ {
+ if ( !player->GetWeapon( j ) )
+ continue;
+
+ if ( !FClassnameIs( player->GetWeapon( j ), classname ) )
+ continue;
+
+ // Add to this player
+ if ( player == pNewBuilder )
+ {
+ ( ( CBaseTFCombatWeapon * )player->GetWeapon( j ))->AddAssociatedObject( this );
+ }
+ // Remove from any other
+ else
+ {
+ ( ( CBaseTFCombatWeapon * )player->GetWeapon( j ))->RemoveAssociatedObject( this );
+ }
+ }
+
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Return true if I have at least 1 sapper on me
+//-----------------------------------------------------------------------------
+bool CBaseObject::HasSapper( void )
+{
+ return ( m_hSappers.Size() > 0 );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Return true if the specified player has attached a sapper to me
+//-----------------------------------------------------------------------------
+bool CBaseObject::HasSapperFromPlayer( CBaseTFPlayer *pPlayer )
+{
+ for ( int i = 0; i < m_hSappers.Size(); i++ )
+ {
+ if ( m_hSappers[i] == NULL )
+ continue;
+
+ if ( m_hSappers[i]->GetThrower() == pPlayer )
+ return true;
+ }
+
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Add a sapper to this object
+//-----------------------------------------------------------------------------
+void CBaseObject::AddSapper( CGrenadeObjectSapper *pSapper )
+{
+ SapperHandle hSapper;
+ hSapper = pSapper;
+ m_hSappers.AddToTail( hSapper );
+ m_bHasSapper = true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Tell all sappers on this object to remove themselves
+//-----------------------------------------------------------------------------
+void CBaseObject::RemoveAllSappers( CBaseEntity *pRemovingEntity )
+{
+ // Loop through all the sappers and fire a +use on them (backwards because list will change)
+ int iSize = m_hSappers.Size();
+ for (int i = iSize-1; i >= 0; i--)
+ {
+ m_hSappers[i]->Use( pRemovingEntity, pRemovingEntity, USE_TOGGLE, 0 );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Remove a sapper from this object
+//-----------------------------------------------------------------------------
+void CBaseObject::RemoveSapper( CGrenadeObjectSapper *pSapper )
+{
+ SapperHandle hSapper;
+ hSapper = pSapper;
+ m_hSappers.FindAndRemove( hSapper );
+ m_bHasSapper = HasSapper();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: My owner's just received a new technology, see if it affects me
+//-----------------------------------------------------------------------------
+void CBaseObject::GainedNewTechnology( CBaseTechnology *pTechnology )
+{
+ // Base object doesn't respond to tech
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : *pRecipient -
+// *techname -
+//-----------------------------------------------------------------------------
+void CBaseObject::GiveNamedTechnology( CBaseTFPlayer *pRecipient, const char *techname )
+{
+ CTFTeam *team = static_cast< CTFTeam * >( pRecipient->GetTeam() );
+ if ( !team )
+ return;
+
+ CBaseTechnology *tech = team->m_pTechnologyTree->GetTechnology( techname );
+ if ( tech )
+ {
+ team->EnableTechnology( tech, true );
+ }
+}
+
+
+bool CBaseObject::ShowVGUIScreen( int panelIndex, bool bShow )
+{
+ Assert( panelIndex >= 0 && panelIndex < m_hScreens.Count() );
+ if ( m_hScreens[panelIndex].Get() )
+ {
+ m_hScreens[panelIndex]->SetActive( bShow );
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Returns true if this object was placed in the map, not built by a player
+//-----------------------------------------------------------------------------
+bool CBaseObject::WasMapPlaced( void )
+{
+ return m_bWasMapPlaced;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Find nearby objects on my team and connect to them
+//-----------------------------------------------------------------------------
+CRopeKeyframe *CBaseObject::ConnectCableTo( CBaseObject *pObject, int iLocalAttachment, int iTargetAttachment )
+{
+ // Connect to it
+ CRopeKeyframe *pRope = CRopeKeyframe::Create( this, pObject, iLocalAttachment, iTargetAttachment );
+ if ( pRope )
+ {
+ pRope->m_Width = 3;
+ pRope->m_nSegments = ROPE_MAX_SEGMENTS;
+ //pRope->m_RopeFlags |= (ROPE_RESIZE | ROPE_COLLIDE);
+ pRope->EnableCollision();
+ pRope->EnableWind( false );
+ pRope->SetupHangDistance( ROPE_HANG_DIST );
+ pRope->ActivateStartDirectionConstraints( true );
+ pRope->ActivateEndDirectionConstraints( true );
+ }
+
+ // Add the rope to both Object's lists
+ CHandle< CRopeKeyframe > hHandle;
+ hHandle = pRope;
+ m_aRopes.AddToTail( hHandle );
+ pObject->m_aRopes.AddToTail( hHandle );
+
+ // During placement, the rules for whether the rope is transmitted or not are
+ // tricky, so we make a proxy here to control it.
+ if ( IsPlacing() || pObject->IsPlacing() )
+ {
+ CObjectRopeTransmitProxy *pProxy = new CObjectRopeTransmitProxy( pRope );
+ pProxy->m_hObj1 = this;
+ pProxy->m_hObj2 = pObject;
+ // pRope->NetworkProp()->SetTransmitProxy( pProxy ); TODO
+ }
+
+ return pRope;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Return true if I have a cable to the specified object
+//-----------------------------------------------------------------------------
+bool CBaseObject::HasCableTo( CBaseObject *pObject )
+{
+ for (int i = 0; i < m_aRopes.Size(); i++)
+ {
+ CHandle< CRopeKeyframe > hHandle;
+ hHandle = m_aRopes[i];
+ if ( hHandle )
+ {
+ if ( m_aRopes[i]->GetEndPoint() == pObject )
+ return true;
+ }
+ }
+
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Return an attachment point for a cable
+//-----------------------------------------------------------------------------
+int CBaseObject::GetCableAttachment( void )
+{
+ Vector vecOrigin, vecAngles;
+ // If I already have a rope attached, try and use a different attachment point
+ if ( m_aRopes.Size() )
+ {
+ // First, check to see if we've lost any ropes (this can happen because
+ // the other object it was attached to has been destroyed.
+ int iSize = m_aRopes.Size();
+ for (int i = iSize-1; i >= 0; i--)
+ {
+ CHandle< CRopeKeyframe > hHandle;
+ hHandle = m_aRopes[i];
+ if ( hHandle == NULL )
+ {
+ m_aRopes.Remove(i);
+ }
+ }
+
+ // If I have enough connections, tell 'em I don't want no more
+ if ( m_aRopes.Size() >= MAX_CABLE_CONNECTIONS )
+ return -1;
+
+ char sAttachment[32];
+ Q_snprintf( sAttachment,sizeof(sAttachment), "cablepoint%d", m_aRopes.Size() + 1 );
+ int iPoint = LookupAttachment( sAttachment );
+ if ( iPoint > 0 )
+ return iPoint;
+ }
+
+
+ return LookupAttachment( "cablepoint1" );
+}
+
+
+//====================================================================================================================
+// POWER PACKS
+//====================================================================================================================
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseObject::SetPowerPack( CObjectPowerPack *pPack )
+{
+ bool bHadPower = HasPowerup( POWERUP_POWER );
+ CObjectPowerPack *pOldPack = m_hPowerPack;
+
+ m_hPowerPack = pPack;
+
+ // If it's placing, I don't get power yet
+ if ( m_hPowerPack && !m_hPowerPack->IsPlacing() )
+ {
+ SetPowerup( POWERUP_POWER, true );
+ }
+ else
+ {
+ // Lose power in a second, to give any nearby powerpacks time to connect to me and replace the power
+ if ( bHadPower )
+ {
+ SetContextThink( LostPowerThink, gpGlobals->curtime + 1.0, OBJ_LOSTPOWER_THINK_CONTEXT );
+ if ( GetTFTeam() )
+ {
+ // Dirty hack to make powerpack think I need power
+ m_iPowerups &= ~(1 << POWERUP_POWER);
+ GetTFTeam()->UpdatePowerpacks( pOldPack, this );
+ }
+ }
+ else
+ {
+ SetPowerup( POWERUP_POWER, false );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: We've lost power fully
+//-----------------------------------------------------------------------------
+void CBaseObject::LostPowerThink( void )
+{
+ // We may have found another powerpack
+ if ( !m_hPowerPack )
+ {
+ // Dirty hack to get our powerup removed properly
+ m_iPowerups |= (1 << POWERUP_POWER);
+ SetPowerup( POWERUP_POWER, false );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Set the health of the object
+//-----------------------------------------------------------------------------
+void CBaseObject::InputSetHealth( inputdata_t &inputdata )
+{
+ m_iMaxHealth = inputdata.value.Int();
+ SetHealth( m_iMaxHealth );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Add health to the object
+//-----------------------------------------------------------------------------
+void CBaseObject::InputAddHealth( inputdata_t &inputdata )
+{
+ int iHealth = inputdata.value.Int();
+ SetHealth( MIN( GetMaxHealth(), m_flHealth + iHealth ) );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Remove health from the object
+//-----------------------------------------------------------------------------
+void CBaseObject::InputRemoveHealth( inputdata_t &inputdata )
+{
+ int iDamage = inputdata.value.Int();
+
+ SetHealth( m_flHealth - iDamage );
+ if ( GetHealth() <= 0 )
+ {
+ m_lifeState = LIFE_DEAD;
+ m_OnDestroyed.FireOutput(this, this);
+ Killed();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : &inputdata -
+//-----------------------------------------------------------------------------
+void CBaseObject::InputSetMinDisabledHealth( inputdata_t &inputdata )
+{
+ float minhealth = inputdata.value.Float();
+
+ bool wasdisabled = IsDisabled();
+
+ if ( m_flHealth < minhealth )
+ {
+ SetDisabled( true );
+ // NOTE: This could theoretically add health, sigh.
+ SetHealth( minhealth );
+
+ // Disable it if not already disabled
+ if ( !wasdisabled )
+ {
+ m_OnBecomingDisabled.FireOutput( inputdata.pActivator, this );
+ }
+ }
+ else if ( wasdisabled && ( m_flHealth > minhealth ) )
+ {
+ SetDisabled( false );
+
+ m_OnBecomingReenabled.FireOutput( inputdata.pActivator, this );
+ }
+
+ m_flMinDisableHealth = minhealth;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : &inputdata -
+//-----------------------------------------------------------------------------
+void CBaseObject::InputSetSolidToPlayer( inputdata_t &inputdata )
+{
+ int ival = inputdata.value.Int();
+ ival = clamp( ival, (int)SOLID_TO_PLAYER_USE_DEFAULT, (int)SOLID_TO_PLAYER_NO );
+ OBJSOLIDTYPE stp = (OBJSOLIDTYPE)ival;
+ SetSolidToPlayers( stp );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseObject::PlayStartupAnimation( void )
+{
+ SetActivity( ACT_OBJ_STARTUP );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseObject::DetermineAnimation( void )
+{
+ Activity desiredActivity = m_Activity;
+
+ switch ( m_Activity )
+ {
+ default:
+ {
+ if ( IsPlacing() )
+ {
+ desiredActivity = ACT_OBJ_PLACING;
+ }
+ else if ( IsBuilding() )
+ {
+ desiredActivity = ACT_OBJ_ASSEMBLING;
+ }
+ /*
+ TODO:
+ ACT_OBJ_DISMANTLING;
+ ACT_OBJ_DETERIORATING;
+ */
+ else
+ {
+ desiredActivity = ACT_OBJ_RUNNING;
+ }
+ }
+ break;
+ case ACT_OBJ_STARTUP:
+ {
+ if ( IsActivityFinished() )
+ {
+ desiredActivity = ACT_OBJ_RUNNING;
+ }
+ }
+ break;
+ }
+
+ if ( desiredActivity == m_Activity )
+ return;
+
+ SetActivity( desiredActivity );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Attach this object to the specified object
+//-----------------------------------------------------------------------------
+
+void CBaseObject::AttachObjectToObject( const CBaseEntity *pEntity, int iPoint, Vector &vecOrigin )
+{
+ SetupAttachedVersion();
+
+ m_hBuiltOnEntity = pEntity;
+ m_iBuiltOnPoint = iPoint;
+
+ if ( m_hBuiltOnEntity.Get() )
+ {
+ // Parent ourselves to the object
+ int iAttachment = 0;
+ const IHasBuildPoints *pBPInterface = dynamic_cast<const IHasBuildPoints*>( pEntity );
+ if ( pBPInterface )
+ iAttachment = pBPInterface->GetBuildPointAttachmentIndex( iPoint );
+
+ SetParent( m_hBuiltOnEntity.Get(), iAttachment );
+
+ if ( iAttachment >= 1 )
+ {
+ // Stick right onto the attachment point.
+ vecOrigin.Init();
+ SetLocalOrigin( vecOrigin );
+ SetLocalAngles( QAngle(0,0,0) );
+ }
+ else
+ {
+ SetAbsOrigin( vecOrigin );
+ vecOrigin = GetLocalOrigin();
+ }
+ }
+
+ Assert( m_hBuiltOnEntity == GetMoveParent() );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Detach this object from its parent, if it has one
+//-----------------------------------------------------------------------------
+void CBaseObject::DetachObjectFromObject( void )
+{
+ if ( !GetParentObject() )
+ return;
+
+ // Clear the build point
+ IHasBuildPoints *pBPInterface = dynamic_cast<IHasBuildPoints*>(GetParentObject() );
+ Assert( pBPInterface );
+ pBPInterface->SetObjectOnBuildPoint( m_iBuiltOnPoint, NULL );
+
+ SetParent( NULL );
+ m_hBuiltOnEntity = NULL;
+ m_iBuiltOnPoint = 0;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Spawn any objects specified inside the mdl
+//-----------------------------------------------------------------------------
+void CBaseObject::SpawnEntityOnBuildPoint( const char *pEntityName, int iAttachmentNumber )
+{
+ // Try and spawn the object
+ CBaseEntity *pEntity = CreateEntityByName( pEntityName );
+ if ( !pEntity )
+ return;
+
+ Vector vecOrigin;
+ QAngle vecAngles;
+ GetAttachment( iAttachmentNumber, vecOrigin, vecAngles );
+ pEntity->SetAbsOrigin( vecOrigin );
+ pEntity->SetAbsAngles( vecAngles );
+ pEntity->Spawn();
+
+ // If it's an object, finish setting it up
+ CBaseObject *pObject = dynamic_cast<CBaseObject*>(pEntity);
+ if ( !pObject )
+ return;
+
+ // Add a buildpoint here
+ int iPoint = AddBuildPoint( iAttachmentNumber );
+ AddValidObjectToBuildPoint( iPoint, pObject->GetType() );
+ pObject->SetBuilder( GetBuilder() );
+ pObject->ChangeTeam( GetTeamNumber() );
+ pObject->SpawnControlPanels();
+ pObject->SetHealth( pObject->GetMaxHealth() );
+ pObject->FinishedBuilding();
+ pObject->AttachObjectToObject( this, iPoint, vecOrigin );
+ pObject->m_fObjectFlags |= OF_CANNOT_BE_DISMANTLED;
+ pObject->AttemptToFindPower();
+
+ IHasBuildPoints *pBPInterface = dynamic_cast<IHasBuildPoints*>(this);
+ Assert( pBPInterface );
+ pBPInterface->SetObjectOnBuildPoint( iPoint, pObject );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Spawn any objects specified inside the mdl
+//-----------------------------------------------------------------------------
+void CBaseObject::SpawnObjectPoints( void )
+{
+ KeyValues *modelKeyValues = new KeyValues("");
+ if ( !modelKeyValues->LoadFromBuffer( modelinfo->GetModelName( GetModel() ), modelinfo->GetModelKeyValueText( GetModel() ) ) )
+ {
+ modelKeyValues->deleteThis();
+ return;
+ }
+
+ // Do we have a build point section?
+ KeyValues *pkvAllObjectPoints = modelKeyValues->FindKey("object_points");
+ if ( !pkvAllObjectPoints )
+ {
+ modelKeyValues->deleteThis();
+ return;
+ }
+
+ // Start grabbing the sounds and slotting them in
+ KeyValues *pkvObjectPoint;
+ for ( pkvObjectPoint = pkvAllObjectPoints->GetFirstSubKey(); pkvObjectPoint; pkvObjectPoint = pkvObjectPoint->GetNextKey() )
+ {
+ // Find the attachment first
+ const char *sAttachment = pkvObjectPoint->GetName();
+ int iAttachmentNumber = LookupAttachment( sAttachment );
+ if ( iAttachmentNumber == 0 )
+ {
+ Msg( "ERROR: Model %s specifies object point %s, but has no attachment named %s.\n", STRING(GetModelName()), pkvObjectPoint->GetString(), pkvObjectPoint->GetString() );
+ continue;
+ }
+
+ // Now see what we're supposed to spawn there
+ // The count check is because it seems wrong to emit multiple entities on the same point
+ int nCount = 0;
+ KeyValues *pkvObject;
+ for ( pkvObject = pkvObjectPoint->GetFirstSubKey(); pkvObject; pkvObject = pkvObject->GetNextKey() )
+ {
+ SpawnEntityOnBuildPoint( pkvObject->GetName(), iAttachmentNumber );
+ ++nCount;
+ Assert( nCount <= 1 );
+ }
+ }
+
+ modelKeyValues->deleteThis();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseObject::CreateVulnerablePoints()
+{
+ KeyValues *modelKeyValues = new KeyValues("");
+ if ( !modelKeyValues->LoadFromBuffer( modelinfo->GetModelName( GetModel() ), modelinfo->GetModelKeyValueText( GetModel() ) ) )
+ return;
+
+ // Do we have a build point section?
+ KeyValues *pkvAllVulnerablePoints = modelKeyValues->FindKey("vulnerable_points");
+ if ( !pkvAllVulnerablePoints )
+ return;
+
+ // Start grabbing the sounds and slotting them in
+ KeyValues *pkvVulnerablePoint = pkvAllVulnerablePoints->GetFirstSubKey();
+ while ( pkvVulnerablePoint )
+ {
+ AddVulnerablePoint( pkvVulnerablePoint->GetName(), pkvVulnerablePoint->GetFloat() );
+ pkvVulnerablePoint = pkvVulnerablePoint->GetNextKey();
+ }
+
+}
+
+ConVar obj_debug_vulnerable( "obj_debug_vulnerable","0", FCVAR_NONE, "Show vulnerable points" );
+
+void CBaseObject::AddVulnerablePoint( const char* szName, float fMultiplier )
+{
+ // Make a new vulnerable point
+ VulnerablePoint_t v;
+ Q_memset(&v,0,sizeof(v));
+ v.m_fDamageMultiplier = fMultiplier;
+
+ int nSet, nBox;
+
+ if( !LookupHitbox(szName, nSet, nBox) )
+ {
+ Msg( "Error: Vulnerable point on model %s unable to locate hitbox %s\n", STRING(GetModelName()), szName);
+ return;
+ }
+
+ v.m_nSet = nSet;
+ v.m_nBox = nBox;
+
+ // Insert it into our list
+ if( obj_debug_vulnerable.GetBool() )
+ {
+ Msg( "Vulnerable point %s on model %s added with a damage multiplier of %f. (%i, %i)\n", szName, STRING(GetModelName()), fMultiplier, nSet, nBox);
+ }
+
+ m_VulnerablePoints.AddToTail( v );
+}
+
+
+float CBaseObject::FindVulnerablePointMultiplier( int nGroup, int nBox )
+{
+ for( int i=0; i < m_VulnerablePoints.Count(); i++ )
+ {
+ VulnerablePoint_t& v = m_VulnerablePoints[i];
+
+ if( v.m_nBox == nBox /* && v.m_nSet == nGroup */)
+ {
+ if( obj_debug_vulnerable.GetBool() )
+ {
+ Msg("VulnerablePoint: %f\n",v.m_fDamageMultiplier);
+ }
+ return v.m_fDamageMultiplier;
+ }
+ }
+
+ if( obj_debug_vulnerable.GetBool() )
+ {
+ Msg("Couldn't find vulnerable point: %i %i\n",nGroup,nBox);
+ }
+ return 1.f;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+QAngle CBaseObject::ConvertAbsAnglesToLocal( QAngle vecLocalAngles )
+{
+ if ( !GetMoveParent() )
+ return vecLocalAngles;
+
+ EntityMatrix vehicleToWorld, childMatrix;
+ vehicleToWorld.InitFromEntity( GetMoveParent() ); // parent->world
+
+ // Calculate the build point angles in vehicle space
+ VMatrix attachmentToWorld;
+ MatrixFromAngles( vecLocalAngles, attachmentToWorld );
+
+ VMatrix worldToVehicle = vehicleToWorld.Transpose();
+ VMatrix attachmentToVehicle;
+ MatrixMultiply( worldToVehicle, attachmentToWorld, attachmentToVehicle );
+ QAngle vecAbsAngles;
+ MatrixToAngles( attachmentToVehicle, vecAbsAngles );
+
+ return vecAbsAngles;
+}
+
+bool CBaseObject::IsSolidToPlayers( void ) const
+{
+ switch ( m_SolidToPlayers )
+ {
+ default:
+ break;
+ case SOLID_TO_PLAYER_USE_DEFAULT:
+ {
+ if ( GetObjectInfo( ObjectType() ) )
+ {
+ return GetObjectInfo( ObjectType() )->m_bSolidToPlayerMovement;
+ }
+ }
+ break;
+ case SOLID_TO_PLAYER_YES:
+ return true;
+ case SOLID_TO_PLAYER_NO:
+ return false;
+ }
+
+ return false;
+}
+
+void CBaseObject::SetSolidToPlayers( OBJSOLIDTYPE stp, bool force )
+{
+ bool changed = stp != m_SolidToPlayers;
+ m_SolidToPlayers = stp;
+
+ if ( changed || force )
+ {
+ SetCollisionGroup(
+ IsSolidToPlayers() ?
+ TFCOLLISION_GROUP_OBJECT_SOLIDTOPLAYERMOVEMENT :
+ TFCOLLISION_GROUP_OBJECT );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Hooks to support swapping out model if the object is damaged
+// Input : bDisabled -
+//-----------------------------------------------------------------------------
+void CBaseObject::SetDisabled( bool bDisabled )
+{
+ bool changed = m_bDisabled != bDisabled;
+ m_bDisabled = bDisabled;
+
+ // value changed and mapper specified a "disabled"/damaged model
+ if ( changed && NULL_STRING != m_iszDisabledModel )
+ {
+ // Change model
+ SetModel( STRING( m_bDisabled ? m_iszDisabledModel : m_iszEnabledModel ) );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseObject::SetBuffStation( CObjectBuffStation *pBuffStation, bool bPlacing )
+{
+ // Activate
+ if ( pBuffStation && !GetBuffStation() && !bPlacing && !IsBuilding() && IsPowered() )
+ {
+ BuffStationActivate();
+ }
+
+ if ( GetBuffStation() && ( pBuffStation == GetBuffStation() ) && !m_bBuffActivated && !bPlacing && !IsBuilding() && IsPowered() )
+ {
+ BuffStationActivate();
+ }
+
+ // Deactivate
+ if ( !pBuffStation && GetBuffStation() )
+ {
+ BuffStationDeactivate();
+ }
+
+ m_hBuffStation = pBuffStation;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CBaseObject::IsHookedAndBuffed( void )
+{
+ if ( GetBuffStation() && HasPowerup( POWERUP_BOOST ) )
+ {
+ if ( GetBuffStation()->IsPowered() && !GetBuffStation()->IsPlacing() &&
+ !GetBuffStation()->IsBuilding() )
+ return true;
+ }
+
+ return false;
+}
diff --git a/game/server/tf2/tf_obj.h b/game/server/tf2/tf_obj.h
new file mode 100644
index 0000000..b53002d
--- /dev/null
+++ b/game/server/tf2/tf_obj.h
@@ -0,0 +1,492 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Base Object built by a player
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef TF_OBJ_H
+#define TF_OBJ_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include "baseentity.h"
+#include "tf_func_resource.h"
+#include "ihasbuildpoints.h"
+#include "baseobject_shared.h"
+#include "info_vehicle_bay.h"
+
+class CBaseTFPlayer;
+class CTFTeam;
+class CRopeKeyframe;
+class CBaseTechnology;
+class CGrenadeObjectSapper;
+class CObjectPowerPack;
+class CVGuiScreen;
+class CResourceZone;
+class KeyValues;
+class CObjectBuffStation;
+struct animevent_t;
+
+#define OBJECT_REPAIR_RATE 10 // Health healed per second while repairing
+
+// Construction
+#define OBJECT_CONSTRUCTION_INTERVAL 0.1
+#define OBJECT_CONSTRUCTION_STARTINGHEALTH 0.1
+
+
+extern ConVar object_verbose;
+extern ConVar obj_child_range_factor;
+
+#if defined( _DEBUG )
+#define TRACE_OBJECT( str ) \
+if ( object_verbose.GetInt() ) \
+{ \
+ Msg( "%s", str ); \
+}
+#else
+#define TRACE_OBJECT( string )
+#endif
+
+
+// ------------------------------------------------------------------------ //
+// Resupply object that's built by the player
+// ------------------------------------------------------------------------ //
+class CBaseObject : public CBaseCombatCharacter, public IHasBuildPoints
+{
+ DECLARE_CLASS( CBaseObject, CBaseCombatCharacter );
+public:
+ CBaseObject();
+
+ virtual void UpdateOnRemove( void );
+
+ DECLARE_SERVERCLASS();
+ DECLARE_DATADESC();
+
+ virtual bool IsBaseObject( void ) const { return true; }
+
+ virtual void BaseObjectThink( void );
+ virtual void LostPowerThink( void );
+ virtual CBaseTFPlayer *GetOwner( void );
+
+ // Creation
+ virtual void Precache();
+ virtual void Spawn( void );
+ virtual void Activate( void );
+ void InitializeMapPlacedObject( void );
+
+ virtual void SetBuilder( CBaseTFPlayer *pBuilder, bool moveobjects = false );
+ virtual void SetupTeamModel( void ) { return; }
+ virtual void SetType( int iObjectType );
+ int ObjectType( ) const;
+
+ virtual int BloodColor( void ) { return BLOOD_COLOR_MECH; }
+
+ // Building
+ virtual float GetTotalTime( void );
+ virtual void StartPlacement( CBaseTFPlayer *pPlayer );
+ void StopPlacement( void );
+ bool FindNearestBuildPoint( CBaseEntity *pEntity, Vector vecBuildOrigin, float &flNearestPoint, Vector &vecNearestBuildPoint );
+ virtual bool CalculatePlacement( CBaseTFPlayer *pPlayer );
+ bool CheckBuildPoint( Vector vecPoint, Vector &vecTrace, Vector *vecOutPoint=NULL );
+ bool VerifyCorner( const Vector &vBottomCenter, float xOffset, float yOffset );
+ virtual bool CheckBuildOrigin( CBaseTFPlayer *pPlayer, const Vector &vecBuildOrigin, bool bSnappedToPoint = false );
+ void AttemptToFindPower( void );
+ void AttemptToFindBuffStation( void );
+ void AttemptToActivateBuffStation( void );
+ virtual float GetNearbyObjectCheckRadius( void ) { return 30.0; }
+ bool UpdatePlacement( CBaseTFPlayer *pPlayer );
+ virtual bool ShouldAttachToParent( void ) { return true; }
+ void SetVehicleBay( CVGuiScreenVehicleBay *pBay ) { m_hVehicleBay = pBay; }
+
+ // Sort of a hack for walkers - vehicles are pre-rotated by 90 degrees and walkers need to undo this.
+ virtual void AdjustInitialBuildAngles();
+
+ // Exit points for mounted vehicles....
+ virtual void GetExitPoint( CBaseEntity *pPlayer, int nBuildPoint, Vector *pExitPoint, QAngle *pAngles );
+
+ // I've finished building the specified object on the specified build point
+ virtual int FindObjectOnBuildPoint( CBaseObject *pObject );
+
+ // This gives an object a chance to prevent itself from being built when the user clicks the
+ // attack button during placement. Barbed wire uses this to change which object the barbed wire
+ // is attached to.
+ virtual bool PreStartBuilding();
+
+ virtual bool StartBuilding( CBaseEntity *pPlayer );
+ void BuildingThink( void );
+ void SetControlPanelsActive( bool bState );
+ virtual void FinishedBuilding( void );
+ bool IsBuilding( void ) { return m_bBuilding; };
+ bool IsPlacing( void ) { return m_bPlacing; };
+ bool MustBeBuiltInResourceZone( void ) const;
+ bool MustBeBuiltInConstructionYard( void ) const;
+ virtual bool MustNotBeBuiltInConstructionYard( void ) const;
+ bool MustBeBuiltOnAttachmentPoint( void ) const;
+
+ void AlignToGround( Vector vecOrigin );
+
+ // Returns information about the various control panels
+ virtual void GetControlPanelInfo( int nPanelIndex, const char *&pPanelName );
+ virtual void GetControlPanelClassName( int nPanelIndex, const char *&pPanelName );
+
+ // Client commands sent by clicking on various panels....
+ // NOTE: pArg->Argv(0) == pCmd, pArg->Argv(1) == the first argument
+ virtual bool ClientCommand( CBaseTFPlayer *pSender, const CCommand &args );
+
+ // Damage
+ void SetHealth( float flHealth );
+ virtual void TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr );
+ virtual int OnTakeDamage( const CTakeDamageInfo &info );
+ bool PassDamageOntoChildren( const CTakeDamageInfo &info, float *flDamageLeftOver );
+ virtual float GetRepairTime( void );
+ virtual bool UpdateRepair( float flRepairTime );
+ virtual bool Repair( float flHealth );
+
+ // Powerups
+ virtual bool CanPowerupEver( int iPowerup );
+ virtual bool CanPowerupNow( int iPowerup );
+ virtual void PowerupStart( int iPowerup, float flAmount, CBaseEntity *pAttacker, CDamageModifier *pDamageModifier );
+ virtual void PowerupEnd( int iPowerup );
+
+ // Destruction
+ virtual bool ShouldAutoRemove( void );
+ virtual void Killed( void );
+ virtual void DestroyObject( void ); // Silent cleanup
+ virtual bool IsDying( void ) { return m_bDying; }
+
+ // Data
+ virtual Class_T Classify( void );
+ virtual int GetType( void );
+ virtual CBaseTFPlayer *GetBuilder( void );
+ virtual CBaseTFPlayer *GetOriginalBuilder( void );
+ CTFTeam *GetTFTeam( void ) { return (CTFTeam*)GetTeam(); };
+
+ // ID functions
+ virtual bool IsAnUpgrade( void ) { return false; }
+ virtual bool IsAVehicle( void ) { return false; }
+ virtual bool IsSentrygun() { return false; }
+ virtual bool WantsCoverFromSentryGun() { return false; }
+ virtual bool WantsCover() { return false; }
+
+ // Inputs
+ void InputSetHealth( inputdata_t &inputdata );
+ void InputAddHealth( inputdata_t &inputdata );
+ void InputRemoveHealth( inputdata_t &inputdata );
+ void InputSetMinDisabledHealth( inputdata_t &inputdata );
+ void InputSetSolidToPlayer( inputdata_t &inputdata );
+
+ // Pickup
+ virtual int ObjectCaps( void );
+ virtual void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value );
+ virtual void PickupObject( void );
+ virtual bool CanBeRemovedBy( CBaseTFPlayer *pPlayer );
+ virtual bool CanBeRotatedBy( CBaseTFPlayer *pPlayer );
+
+ virtual void ChangeTeam( int iTeamNum ) OVERRIDE; // Assign this entity to a team.
+
+ virtual void ChangeBuilder( CBaseTFPlayer *pNewBuilder, bool moveobjects );
+ virtual const char *GetWeaponClassnameForObject( void );
+ virtual void AddItemsNeededForObject( CBaseTFPlayer *pNewOwner );
+
+ // Objects that are damaged/disabled can return false here when checked for being available
+ virtual bool ComputeEMPDamageState( void ) { return true; }
+
+ // Handling object inactive
+ virtual bool ShouldBeActive( void );
+
+ // Technology
+ virtual void GainedNewTechnology( CBaseTechnology *pTechnology );
+
+ // Sappers
+ bool HasSapper( void );
+ bool HasSapperFromPlayer( CBaseTFPlayer *pPlayer );
+ void AddSapper( CGrenadeObjectSapper *pSapper );
+ void RemoveSapper( CGrenadeObjectSapper *pSapper );
+
+ // Called when the builder rotates this object...
+ virtual void ObjectMoved( );
+
+ // Minibase stuff
+ virtual bool WasMapPlaced( void );
+ virtual CRopeKeyframe *ConnectCableTo( CBaseObject *pObject, int iLocalAttachment, int iTargetAttachment );
+ virtual bool HasCableTo( CBaseObject *pObject );
+ virtual int GetCableAttachment( void );
+
+ // Returns the object flags
+ int GetObjectFlags() const { return m_fObjectFlags; }
+ void SetObjectFlags( int flags ) { m_fObjectFlags = flags; }
+
+ CResourceZone *GetResourceZone() { return m_hResourceZone.Get(); }
+ int RopeCount() const { return m_aRopes.Count(); }
+
+ // Power handling (Human objects need power to operate)
+ bool IsPowered( void );
+ void SetPowerPack( CObjectPowerPack *pPack );
+ CObjectPowerPack *GetPowerPack( void ) { return m_hPowerPack; };
+
+ void AttemptToGoActive( void );
+ virtual void OnGoActive( void );
+ virtual void OnGoInactive( void );
+
+ // Buffed objects (objects that connect to a medic's buff station)
+ bool IsHookedAndBuffed( void );
+ virtual bool CanBeHookedToBuffStation( void );
+ void SetBuffStation( CObjectBuffStation *pBuffStation, bool bPlacing );
+ CObjectBuffStation *GetBuffStation( void );
+
+ virtual void BuffStationActivate( void );
+ virtual void BuffStationDeactivate( void );
+
+ // Deterioration
+ void StartDeteriorating( void );
+ void StopDeteriorating( void );
+ bool IsDeteriorating( void ) { return m_bDeteriorating; };
+ void DeterioratingThink( void );
+
+ // Disabling
+ void SetDisabled( bool bDisabled );
+ bool IsDisabled( void ) { return m_bDisabled; }
+
+ // Animation
+ virtual void PlayStartupAnimation( void );
+
+ Activity GetActivity( ) const;
+ void SetActivity( Activity act );
+ void SetObjectSequence( int sequence );
+
+ virtual void OnActivityChanged( Activity act );
+
+ // Object points
+ void SpawnObjectPoints( void );
+
+ // Derive to customize an object's attached version
+ virtual void SetupAttachedVersion( void ) { return; }
+ virtual void SetupUnattachedVersion( void ) { return; }
+
+ QAngle ConvertAbsAnglesToLocal( QAngle vecLocalAngles );
+
+public:
+ // VulnerablePoints
+ void CreateVulnerablePoints( void );
+ void AddVulnerablePoint( const char* szHitboxName, float Multiplier );
+ float FindVulnerablePointMultiplier( int nGroup, int nBox );
+
+ // Build points
+ CUtlVector<VulnerablePoint_t> m_VulnerablePoints;
+
+ virtual bool TestHitboxes( const Ray_t &ray, unsigned int fContentsMask, trace_t& tr );
+
+public:
+ // Client/Server shared build point code
+ void CreateBuildPoints( void );
+ void AddAndParseBuildPoint( int iAttachmentNumber, KeyValues *pkvBuildPoint );
+ virtual int AddBuildPoint( int iAttachmentNum );
+ virtual void AddValidObjectToBuildPoint( int iPoint, int iObjectType );
+ virtual CBaseObject *GetBuildPointObject( int iPoint );
+ bool IsBuiltOnAttachment( void ) { return (m_hBuiltOnEntity.Get() != NULL); }
+ void AttachObjectToObject( const CBaseEntity *pEntity, int iPoint, Vector &vecOrigin );
+ void DetachObjectFromObject( void );
+ CBaseObject *GetParentObject( void );
+ void SetBuildPointPassenger( int iPoint, int iPassenger );
+ int GetBuildPointPassenger( int iPoint ) const;
+
+ virtual float GetSapperAttachTime( void );
+
+// IHasBuildPoints
+public:
+ virtual int GetNumBuildPoints( void ) const;
+ virtual bool GetBuildPoint( int iPoint, Vector &vecOrigin, QAngle &vecAngles );
+ virtual int GetBuildPointAttachmentIndex( int iPoint ) const;
+ virtual bool CanBuildObjectOnBuildPoint( int iPoint, int iObjectType );
+ virtual void SetObjectOnBuildPoint( int iPoint, CBaseObject *pObject );
+ virtual float GetMaxSnapDistance( int iBuildPoint );
+ virtual bool ShouldCheckForMovement( void ) { return true; }
+ virtual int GetNumObjectsOnMe( void );
+ virtual CBaseEntity *GetFirstObjectOnMe( void );
+ virtual CBaseObject *GetObjectOfTypeOnMe( int iObjectType );
+ virtual void RemoveAllObjects( void );
+
+
+// IServerNetworkable.
+public:
+
+ virtual int ShouldTransmit( const CCheckTransmitInfo *pInfo );
+ virtual void SetTransmit( CCheckTransmitInfo *pInfo, bool bAlways );
+
+
+protected:
+ // Clean off the object of offensive material, returns true if it found anything
+ bool RemoveEnemyAttachments( CBaseEntity *pActivator );
+ void RemoveAnalyzer( CBaseEntity *pRemovingEntity );
+ void RemoveAllSappers( CBaseEntity *pRemovingEntity );
+
+ void GiveNamedTechnology( CBaseTFPlayer *pRecipient, const char *techname );
+
+ // Show/hide vgui screens.
+ bool ShowVGUIScreen( int panelIndex, bool bShow );
+
+private:
+ void DetermineAnimation( void );
+
+ // Spawns the various control panels
+ void SpawnControlPanels();
+
+ // Purpose: Spawn any objects specified inside the mdl
+ void SpawnEntityOnBuildPoint( const char *pEntityName, int iAttachmentNumber );
+
+ // Various commands sent by control panels
+ void DismantleCommand( CBaseTFPlayer *pSender );
+ void YawCommand( CBaseTFPlayer *pSender, float flYaw );
+ void TakeControlCommand( CBaseTFPlayer *pSender );
+
+protected:
+ enum OBJSOLIDTYPE
+ {
+ SOLID_TO_PLAYER_USE_DEFAULT = 0,
+ SOLID_TO_PLAYER_YES,
+ SOLID_TO_PLAYER_NO,
+ };
+
+ bool IsSolidToPlayers( void ) const;
+
+ // object flags....
+ CNetworkVar( int, m_fObjectFlags );
+ CNetworkHandle( CBaseTFPlayer, m_hBuilder );
+
+ // Zone we're in (valid only for objects that sit in zones)
+ CNetworkHandle( CResourceZone, m_hResourceZone );
+
+ // Combat Objects
+ char *m_szAmmoName; // Ammo used by players to build me
+
+ // Cables
+ CUtlVector< CHandle<CRopeKeyframe> > m_aRopes;
+
+ // Placement
+ Vector m_vecBuildOrigin;
+ Vector m_vecBuildMins;
+ Vector m_vecBuildMaxs;
+ CNetworkHandle( CBaseEntity, m_hBuiltOnEntity );
+ int m_iBuiltOnPoint;
+
+ bool m_bInvulnerable;
+ bool m_bCantDie;
+ float m_flRepairMultiplier;
+ bool m_bDying;
+
+ // Outputs
+ COutputEvent m_OnDestroyed;
+ COutputEvent m_OnDamaged;
+ COutputEvent m_OnRepaired;
+
+ COutputEvent m_OnBecomingDisabled;
+ COutputEvent m_OnBecomingReenabled;
+
+ COutputFloat m_OnObjectHealthChanged;
+
+ // Control panel
+ typedef CHandle<CVGuiScreen> ScreenHandle_t;
+ CUtlVector<ScreenHandle_t> m_hScreens;
+
+private:
+ // Make sure we pick up changes to these.
+ IMPLEMENT_NETWORK_VAR_FOR_DERIVED( m_iHealth );
+ IMPLEMENT_NETWORK_VAR_FOR_DERIVED( m_takedamage );
+
+ Activity m_Activity;
+
+ CNetworkVar( int, m_iObjectType );
+
+
+ // True if players shouldn't do collision avoidance, but should just collide exactly with the object.
+ OBJSOLIDTYPE m_SolidToPlayers;
+ void SetSolidToPlayers( OBJSOLIDTYPE stp, bool force = false );
+
+ // True if this was a map placed object, not a player built one
+ bool m_bWasMapPlaced;
+
+ // Disabled
+ CNetworkVar( bool, m_bDisabled );
+
+ // Vehicle bays
+ CHandle<CVGuiScreenVehicleBay> m_hVehicleBay;
+
+ // Building
+ CNetworkVar( bool, m_bPlacing ); // True while the object's being placed
+ CNetworkVar( bool, m_bBuilding ); // True while the object's still constructing itself
+ float m_flConstructionTimeLeft; // Current time left in construction
+ float m_flTotalConstructionTime; // Total construction time (the value of GetTotalTime() at the time construction
+ // started, ie, incase you teleport out of a construction yard)
+
+ CNetworkVar( float, m_flPercentageConstructed ); // Used to send to client
+ float m_flHealth; // Health during construction. Needed a float due to small increases in health.
+
+ // Repair capping
+ float m_flLastRepairTime;
+ float m_flNextRepairMultiplier;
+ float m_flRepairedSinceLastTime;
+
+ // Sappers on me
+ CNetworkVar( bool, m_bHasSapper );
+ typedef CHandle<CGrenadeObjectSapper> SapperHandle;
+ CUtlVector< SapperHandle > m_hSappers;
+
+ // Power handling (Human objects need power to operate)
+ CHandle< CObjectPowerPack > m_hPowerPack;
+
+ // Buff Station
+ CHandle< CObjectBuffStation > m_hBuffStation;
+ bool m_bBuffActivated;
+
+ // Deterioration
+ CNetworkVar( bool, m_bDeteriorating );
+ float m_flStartedDeterioratingAt;
+ CHandle<CBaseTFPlayer> m_hOriginalBuilder;
+
+ // Build points
+ CUtlVector<BuildPoint_t> m_BuildPoints;
+
+ // Store the last time I took damage from an enemy. Use this to know whether to drop resources
+ // when I die, because I only want to drop resources if I was "destroyed" by an enemy, not if I deteriorated.
+ float m_flLastRealDamage;
+
+ // Amount of resources the player who built me paid for me
+ int m_iAmountPlayerPaidForMe;
+
+ // Attack notification sounds
+ string_t m_iszUnderAttackSound;
+
+ // If non-zero then if health gets below this amount, the object becomes disabled
+ float m_flMinDisableHealth;
+
+ // If not NULL, then when going disabled, swith to this model
+ string_t m_iszDisabledModel;
+ string_t m_iszEnabledModel;
+};
+
+inline bool CBaseObject::CanBeHookedToBuffStation( void )
+{
+ return false;
+}
+
+inline CObjectBuffStation *CBaseObject::GetBuffStation( void )
+{
+ return m_hBuffStation.Get();
+}
+
+inline void CBaseObject::BuffStationActivate( void )
+{
+ m_bBuffActivated = true;
+}
+
+inline void CBaseObject::BuffStationDeactivate( void )
+{
+ m_bBuffActivated = false;
+}
+
+extern short g_sModelIndexFireball; // holds the index for the fireball
+
+
+#endif // TF_OBJ_H
diff --git a/game/server/tf2/tf_obj_armor_upgrade.cpp b/game/server/tf2/tf_obj_armor_upgrade.cpp
new file mode 100644
index 0000000..01b05f3
--- /dev/null
+++ b/game/server/tf2/tf_obj_armor_upgrade.cpp
@@ -0,0 +1,31 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+#include "cbase.h"
+#include "tf_obj_armor_upgrade.h"
+
+
+#define AERIAL_SENTRY_STATION_MODEL "models/objects/obj_aerial_sentry_station.mdl"
+
+
+LINK_ENTITY_TO_CLASS( obj_armor_upgrade, CArmorUpgrade );
+
+
+IMPLEMENT_SERVERCLASS_ST( CArmorUpgrade, DT_ArmorUpgrade )
+END_SEND_TABLE()
+
+
+CArmorUpgrade::CArmorUpgrade()
+{
+ UseClientSideAnimation();
+}
+
+
+void CArmorUpgrade::Spawn()
+{
+ SetModel( AERIAL_SENTRY_STATION_MODEL );
+ SetType( OBJ_ARMOR_UPGRADE );
+}
diff --git a/game/server/tf2/tf_obj_armor_upgrade.h b/game/server/tf2/tf_obj_armor_upgrade.h
new file mode 100644
index 0000000..386982a
--- /dev/null
+++ b/game/server/tf2/tf_obj_armor_upgrade.h
@@ -0,0 +1,33 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+#ifndef TF_OBJ_ARMOR_UPGRADE_H
+#define TF_OBJ_ARMOR_UPGRADE_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+
+#include "tf_obj_baseupgrade_shared.h"
+
+
+// This class shows a temporary model until you get to an object it can upgrade.
+// Then it draws a shell over the model you want to upgrade.
+class CArmorUpgrade : public CBaseObjectUpgrade
+{
+public:
+ DECLARE_CLASS( CArmorUpgrade, CBaseObjectUpgrade );
+ DECLARE_SERVERCLASS();
+
+
+ CArmorUpgrade();
+
+
+ virtual void Spawn();
+};
+
+
+#endif // TF_OBJ_ARMOR_UPGRADE_H
diff --git a/game/server/tf2/tf_obj_barbed_wire.cpp b/game/server/tf2/tf_obj_barbed_wire.cpp
new file mode 100644
index 0000000..5666d36
--- /dev/null
+++ b/game/server/tf2/tf_obj_barbed_wire.cpp
@@ -0,0 +1,239 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+#include "cbase.h"
+#include "tf_obj_barbed_wire.h"
+#include "tf_player.h"
+#include "basetfvehicle.h"
+#include "engine/IEngineSound.h"
+#include "te_effect_dispatch.h"
+#include "ndebugoverlay.h"
+
+#define BARBED_WIRE_MINS Vector(-5, -5, 0)
+#define BARBED_WIRE_MAXS Vector( 5, 5, 40)
+#define BARBED_WIRE_MODEL "models/objects/obj_barbed_wire.mdl"
+
+#define MAX_BARBED_WIRE_DISTANCE 768
+#define BARBED_WIRE_THINK_CONTEXT "BarbedWireThinkContext"
+
+#define BARBED_WIRE_THINK_INTERVAL 0.2
+
+ConVar obj_barbed_wire_damage( "obj_barbed_wire_damage", "80" );
+ConVar obj_barbed_wire_health( "obj_barbed_wire_health", "100" );
+
+
+IMPLEMENT_SERVERCLASS_ST( CObjectBarbedWire, DT_ObjectBarbedWire )
+ SendPropEHandle( SENDINFO( m_hConnectedTo ) )
+END_SEND_TABLE()
+
+
+LINK_ENTITY_TO_CLASS( obj_barbed_wire, CObjectBarbedWire );
+PRECACHE_REGISTER( obj_barbed_wire );
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CObjectBarbedWire::CObjectBarbedWire()
+{
+ m_iHealth = obj_barbed_wire_health.GetInt();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CObjectBarbedWire::Precache()
+{
+ PrecacheModel( BARBED_WIRE_MODEL );
+ BaseClass::Precache();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CObjectBarbedWire::Spawn()
+{
+ Precache();
+
+ SetModel( BARBED_WIRE_MODEL );
+ SetSolid( SOLID_BBOX );
+ SetType( OBJ_BARBED_WIRE );
+ UTIL_SetSize(this, BARBED_WIRE_MINS, BARBED_WIRE_MAXS );
+
+ // Set our flags.
+ m_fObjectFlags |= OF_DOESNT_NEED_POWER | OF_SUPPRESS_APPEAR_ON_MINIMAP | OF_SUPPRESS_NOTIFY_UNDER_ATTACK | OF_ALLOW_REPEAT_PLACEMENT;
+
+ // Get the ball rolling here.
+ BarbedWireThink();
+
+ BaseClass::Spawn();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Enumerator
+//-----------------------------------------------------------------------------
+class CBarbedWireEnumerator : public IEntityEnumerator
+{
+public:
+ CBarbedWireEnumerator( CObjectBarbedWire *pWire, Ray_t *pRay, int contentsMask )
+ {
+ m_pWire = pWire;
+ m_pRay = pRay;
+ m_ContentsMask = contentsMask;
+ m_aDamagedEntities.Purge();
+ }
+
+ virtual bool EnumEntity( IHandleEntity *pHandleEntity )
+ {
+ CBaseEntity *pEntity = gEntList.GetBaseEntity( pHandleEntity->GetRefEHandle() );
+ if ( pEntity && !m_pWire->InSameTeam( pEntity ) )
+ {
+ trace_t tr;
+ enginetrace->ClipRayToEntity( *m_pRay, m_ContentsMask, pHandleEntity, &tr );
+ if ( tr.fraction < 1.0f )
+ {
+ // Add them to the list & damage them later.
+ // Done this way so entities don't remove themselves from leaves while we're tracing
+ if ( m_aDamagedEntities.Find( pEntity ) == m_aDamagedEntities.InvalidIndex() )
+ {
+ m_aDamagedEntities.AddToTail( pEntity );
+ }
+ }
+ }
+
+ return true;
+ }
+
+ void DamageEntities( void )
+ {
+ int iSize = m_aDamagedEntities.Count();
+ for ( int i = iSize-1; i >= 0; i-- )
+ {
+ CBaseEntity *pEntity = m_aDamagedEntities[i].Get();
+ if ( pEntity )
+ {
+ // DMG_CRUSH added so there's no physics force generated
+ CTakeDamageInfo info( m_pWire, m_pWire->GetBuilder(), obj_barbed_wire_damage.GetFloat() * BARBED_WIRE_THINK_INTERVAL, DMG_SLASH | DMG_CRUSH );
+ pEntity->TakeDamage( info );
+
+ // Bloodspray
+ CEffectData data;
+ data.m_vOrigin = pEntity->WorldSpaceCenter();
+ data.m_vNormal = Vector(0,0,1);
+ data.m_flScale = 4;
+ data.m_fFlags = FX_BLOODSPRAY_ALL;
+ data.m_nEntIndex = pEntity->entindex();
+ DispatchEffect( "tf2blood", data );
+ }
+ }
+ }
+
+private:
+ CObjectBarbedWire *m_pWire;
+ int m_ContentsMask;
+ Ray_t
+ *m_pRay;
+ CUtlVector< EHANDLE > m_aDamagedEntities;
+};
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CObjectBarbedWire::BarbedWireThink( void )
+{
+ // Cut the rope if we're too far from the entity it's attached to.
+ if ( m_hConnectedTo.Get() )
+ {
+ if ( (WorldSpaceCenter() - m_hConnectedTo->WorldSpaceCenter()).Length() > MAX_BARBED_WIRE_DISTANCE )
+ {
+ m_hConnectedTo = NULL;
+ }
+
+ if ( m_hConnectedTo )
+ {
+ Ray_t ray;
+ ray.Init( WorldSpaceCenter(), m_hConnectedTo->WorldSpaceCenter() );
+
+ //NDebugOverlay::Line( WorldSpaceCenter(), m_hConnectedTo->WorldSpaceCenter(), 255,255,255, false, 0.1 );
+ //NDebugOverlay::Box( WorldSpaceCenter(), -Vector(5,5,5), Vector(5,5,5), 0,255,0,8, 0.1 );
+ //NDebugOverlay::Box( m_hConnectedTo->WorldSpaceCenter(), -Vector(6,6,6), Vector(6,6,6), 255,255,255,8, 0.1 );
+
+ CBarbedWireEnumerator bwEnum( this, &ray, MASK_SHOT_HULL );
+ enginetrace->EnumerateEntities( ray, false, &bwEnum );
+ bwEnum.DamageEntities();
+ }
+ }
+
+ SetContextThink( BarbedWireThink, gpGlobals->curtime + BARBED_WIRE_THINK_INTERVAL, BARBED_WIRE_THINK_CONTEXT );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CObjectBarbedWire::StartPlacement( CBaseTFPlayer *pPlayer )
+{
+ if ( pPlayer && !m_hConnectedTo )
+ {
+ // Automatically connect to the nearest barbed wire on our team.
+ float flClosest = 1e24;
+ CObjectBarbedWire *pClosest = NULL;
+
+ CBaseEntity *pCur = gEntList.FirstEnt();
+ while ( pCur )
+ {
+ CObjectBarbedWire *pWire = dynamic_cast< CObjectBarbedWire* >( pCur );
+ if ( pWire )
+ {
+ if ( pWire->GetTeamNumber() == pPlayer->GetTeamNumber() )
+ {
+ float flDist = (pWire->WorldSpaceCenter() - pPlayer->WorldSpaceCenter()).Length();
+ if ( flDist < flClosest )
+ {
+ flClosest = flDist;
+ pClosest = pWire;
+ }
+ }
+ }
+
+ pCur = gEntList.NextEnt( pCur );
+ }
+
+ if ( pClosest )
+ {
+ m_hConnectedTo = pClosest;
+ }
+ }
+
+ BaseClass::StartPlacement( pPlayer );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CObjectBarbedWire::PreStartBuilding()
+{
+ const CObjectBarbedWire *pWire = dynamic_cast< const CObjectBarbedWire* >( m_hBuiltOnEntity.Get() );
+ if ( pWire )
+ {
+ // Reconnect the wire to this entity and don't build yet.
+ m_hConnectedTo = pWire;
+
+ return false;
+ }
+ else
+ {
+ // Go ahead and build.
+ return true;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CObjectBarbedWire::FinishedBuilding()
+{
+ BaseClass::FinishedBuilding();
+}
+
diff --git a/game/server/tf2/tf_obj_barbed_wire.h b/game/server/tf2/tf_obj_barbed_wire.h
new file mode 100644
index 0000000..c897d22
--- /dev/null
+++ b/game/server/tf2/tf_obj_barbed_wire.h
@@ -0,0 +1,43 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+#ifndef TF_OBJ_BARBED_WIRE_H
+#define TF_OBJ_BARBED_WIRE_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+
+#include "tf_obj.h"
+
+
+class CObjectBarbedWire : public CBaseObject
+{
+public:
+ DECLARE_CLASS( CObjectBarbedWire, CBaseObject );
+ DECLARE_SERVERCLASS();
+
+
+ CObjectBarbedWire();
+
+
+ void BarbedWireThink();
+
+ virtual void Precache();
+ virtual void Spawn();
+
+ virtual void StartPlacement( CBaseTFPlayer *pPlayer );
+
+ virtual bool PreStartBuilding();
+ virtual void FinishedBuilding();
+
+private:
+ CNetworkHandle( CObjectBarbedWire, m_hConnectedTo );
+
+};
+
+
+#endif // TF_OBJ_BARBED_WIRE_H
diff --git a/game/server/tf2/tf_obj_buff_station.cpp b/game/server/tf2/tf_obj_buff_station.cpp
new file mode 100644
index 0000000..0dc6791
--- /dev/null
+++ b/game/server/tf2/tf_obj_buff_station.cpp
@@ -0,0 +1,929 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Medic's portable power generator
+//
+//=============================================================================//
+
+#include "cbase.h"
+#include "tf_obj_buff_station.h"
+#include "tf_player.h"
+#include "rope.h"
+#include "rope_shared.h"
+#include "entitylist.h"
+#include "VGuiScreen.h"
+#include "engine/IEngineSound.h"
+#include "tf_team.h"
+
+//=============================================================================
+//
+// Console Variables
+//
+static ConVar obj_buff_station_damage_modifier( "obj_buff_station_damage_modifier", "1.5", 0, "Scales the damage a player does while connected to the buff station." );
+static ConVar obj_buff_station_heal_rate( "obj_buff_station_heal_rate", "10" );
+static ConVar obj_buff_station_range( "obj_buff_station_range", "300" );
+static ConVar obj_buff_station_obj_range( "obj_buff_station_obj_range", "800" );
+static ConVar obj_buff_station_health( "obj_buff_station_health","100", FCVAR_NONE, "Buff Station health" );
+
+//-----------------------------------------------------------------------------
+// Buff Station defines
+//-----------------------------------------------------------------------------
+#define BUFF_STATION_MINS Vector( -30, -30, 0 )
+#define BUFF_STATION_MAXS Vector( 30, 30, 50 )
+
+#define BUFF_STATION_HUMAN_MODEL "models/objects/human_obj_buffstation.mdl"
+#define BUFF_STATION_HUMAN_ASSEMBLING_MODEL "models/objects/human_obj_buffstation_build.mdl"
+#define BUFF_STATION_ALIEN_MODEL "models/objects/alien_obj_buffstation.mdl"
+#define BUFF_STATION_ALIEN_ASSEMBLING_MODEL "models/objects/alien_obj_buffstation_build.mdl"
+
+#define BUFF_STATION_VGUI_SCREEN "screen_obj_buffstation"
+
+#define BUFF_STATION_BOOST_PLAYER_THINK_CONTEXT "BoostPlayerThink"
+#define BUFF_STATION_BOOST_OBJECT_THINK_CONTEXT "BoostObjectThink"
+#define BUFF_STATION_BOOST_PLAYER_THINK_INTERVAL 0.1f
+#define BUFF_STATION_BOOST_OBJECT_THINK_INTERVAL 2.0f
+
+#define BUFF_STATION_BUFF_RANGE ( 600 * 600 )
+
+//=============================================================================
+//
+// Data Description
+//
+BEGIN_DATADESC( CObjectBuffStation )
+ DEFINE_INPUTFUNC( FIELD_VOID, "PlayerSpawned", InputPlayerSpawned ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "PlayerAttachedToGenerator", InputPlayerAttachedToGenerator ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "PlayerEnteredVehicle", InputPlayerSpawned ), // NJS: Detach player from buff pack.
+END_DATADESC()
+
+//=============================================================================
+//
+// Server Class
+//
+IMPLEMENT_SERVERCLASS_ST( CObjectBuffStation, DT_ObjectBuffStation )
+ SendPropInt( SENDINFO( m_nPlayerCount ), BUFF_STATION_MAX_PLAYER_BITS, SPROP_UNSIGNED ),
+ SendPropArray( SendPropEHandle( SENDINFO_ARRAY( m_hPlayers ) ), m_hPlayers ),
+ SendPropInt( SENDINFO( m_nObjectCount ), BUFF_STATION_MAX_OBJECT_BITS, SPROP_UNSIGNED ),
+ SendPropArray( SendPropEHandle( SENDINFO_ARRAY( m_hObjects ) ), m_hObjects ),
+END_SEND_TABLE()
+
+//=============================================================================
+//
+// Linking and Precache
+//
+LINK_ENTITY_TO_CLASS( obj_buff_station, CObjectBuffStation );
+PRECACHE_REGISTER( obj_buff_station );
+
+// Backwards compatability...
+LINK_ENTITY_TO_CLASS( obj_portable_power_generator, CObjectBuffStation );
+
+//-----------------------------------------------------------------------------
+// Purpose: Constructor
+//-----------------------------------------------------------------------------
+CObjectBuffStation::CObjectBuffStation()
+{
+ // Verify networking data.
+ COMPILE_TIME_ASSERT( BUFF_STATION_MAX_PLAYERS < ( 1 << BUFF_STATION_MAX_PLAYER_BITS ) );
+ COMPILE_TIME_ASSERT( BUFF_STATION_MAX_PLAYERS >= ( 1 << ( BUFF_STATION_MAX_PLAYER_BITS - 1 ) ) );
+
+ COMPILE_TIME_ASSERT( BUFF_STATION_MAX_OBJECTS < ( 1 << BUFF_STATION_MAX_OBJECT_BITS ) );
+ COMPILE_TIME_ASSERT( BUFF_STATION_MAX_OBJECTS >= ( 1 << ( BUFF_STATION_MAX_OBJECT_BITS - 1 ) ) );
+
+ // Uses the client-side animation system.
+ UseClientSideAnimation();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Spawn
+//-----------------------------------------------------------------------------
+void CObjectBuffStation::Spawn()
+{
+ // This must be set before calling the base class spawn.
+ m_iHealth = obj_buff_station_health.GetInt();
+
+ BaseClass::Spawn();
+
+ SetModel( BUFF_STATION_HUMAN_MODEL );
+ SetSolid( SOLID_BBOX );
+
+ SetType( OBJ_BUFF_STATION );
+ UTIL_SetSize( this, BUFF_STATION_MINS, BUFF_STATION_MAXS );
+
+ m_takedamage = DAMAGE_YES;
+
+ // Initialize buff station attachment data.
+ InitAttachmentData();
+
+ // Thinking
+ SetContextThink( BoostPlayerThink, 1.0f, BUFF_STATION_BOOST_PLAYER_THINK_CONTEXT );
+ SetContextThink( BoostObjectThink, 2.0f, BUFF_STATION_BOOST_OBJECT_THINK_CONTEXT );
+
+ m_bBuilding = false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Precache model, vgui elements, and sound.
+//-----------------------------------------------------------------------------
+void CObjectBuffStation::Precache()
+{
+ // Models
+ PrecacheModel( BUFF_STATION_HUMAN_MODEL );
+ PrecacheModel( BUFF_STATION_ALIEN_MODEL );
+
+ // Build models
+ PrecacheModel( BUFF_STATION_HUMAN_ASSEMBLING_MODEL );
+ PrecacheModel( BUFF_STATION_ALIEN_ASSEMBLING_MODEL );
+
+ // VGUI Screen
+ PrecacheVGuiScreen( BUFF_STATION_VGUI_SCREEN );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CObjectBuffStation::SetupTeamModel( void )
+{
+ if ( GetTeamNumber() == TEAM_HUMANS )
+ {
+ if ( m_bBuilding )
+ {
+ SetModel( BUFF_STATION_HUMAN_ASSEMBLING_MODEL );
+ }
+ else
+ {
+ SetModel( BUFF_STATION_HUMAN_MODEL );
+ }
+ }
+ else
+ {
+ if ( m_bBuilding )
+ {
+ SetModel( BUFF_STATION_ALIEN_ASSEMBLING_MODEL );
+ }
+ else
+ {
+ SetModel( BUFF_STATION_ALIEN_MODEL );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Gets info about the control panels
+//-----------------------------------------------------------------------------
+void CObjectBuffStation::GetControlPanelInfo( int nControlPanelIndex, const char *&pPanelName )
+{
+ pPanelName = BUFF_STATION_VGUI_SCREEN;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Remove this object from it's team and mark it for deletion.
+//-----------------------------------------------------------------------------
+void CObjectBuffStation::DestroyObject( void )
+{
+ // Detach all players.
+ for ( int iPlayer = 0; iPlayer < BUFF_STATION_MAX_PLAYERS; iPlayer++ )
+ {
+ DetachPlayerByIndex( iPlayer );
+ }
+
+ // Detach all objects.
+ for( int iObject = m_nObjectCount - 1; iObject >= 0; --iObject )
+ {
+ DetachObjectByIndex( iObject );
+ }
+
+ // Inform all other buff stations on this team to attempt to power object (cover for this one).
+ if ( GetTFTeam() )
+ {
+ GetTFTeam()->UpdateBuffStations( this, NULL, false );
+ }
+
+ // We shouldn't get any more messages
+ g_pNotify->ClearEntity( this );
+ BaseClass::DestroyObject();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CObjectBuffStation::OnGoInactive( void )
+{
+ BaseClass::OnGoInactive();
+
+ // Detach all players.
+ for ( int iPlayer = 0; iPlayer < BUFF_STATION_MAX_PLAYERS; iPlayer++ )
+ {
+ CBaseTFPlayer *pPlayer = m_hPlayers[iPlayer].Get();
+ if ( pPlayer )
+ {
+ ClientPrint( pPlayer, HUD_PRINTCENTER, "Lost power to Buff Station!" );
+ }
+
+ DetachPlayerByIndex( iPlayer );
+ }
+
+ // Detach all objects.
+ for ( int iObject = m_nObjectCount - 1; iObject >= 0; --iObject )
+ {
+ DetachObjectByIndex( iObject );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Attach to players who touch me
+//-----------------------------------------------------------------------------
+void CObjectBuffStation::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
+{
+ if ( useType == USE_ON )
+ {
+ // See if the activator is a player
+ if ( !pActivator->IsPlayer() || !InSameTeam( pActivator ) || !pActivator->CanBePoweredUp() )
+ return;
+
+ CBaseTFPlayer *pPlayer = static_cast<CBaseTFPlayer*>(pActivator);
+ if ( pPlayer )
+ {
+ UpdatePlayerAttachment( pPlayer );
+ }
+ }
+
+ BaseClass::Use( pActivator, pCaller, useType, value );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Handle commands sent from vgui panels on the client
+//-----------------------------------------------------------------------------
+bool CObjectBuffStation::ClientCommand( CBaseTFPlayer *pPlayer, const char *pCmd, ICommandArguments *pArg )
+{
+ if ( FStrEq( pCmd, "toggle_connect" ) )
+ {
+ UpdatePlayerAttachment( pPlayer );
+ return true;
+ }
+
+ return BaseClass::ClientCommand( pPlayer, pCmd, pArg );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CObjectBuffStation::InitAttachmentData( void )
+{
+ // Initialize the attachment data.
+ char szAttachName[13];
+
+ m_nPlayerCount = 0;
+ Q_strncpy( szAttachName, "playercable1", 13 );
+ for ( int iPlayer = 0; iPlayer < BUFF_STATION_MAX_PLAYERS; ++iPlayer )
+ {
+ m_hPlayers.Set( iPlayer, NULL );
+
+ szAttachName[11] = '1' + iPlayer;
+ m_aPlayerAttachInfo[iPlayer].m_iAttachPoint = LookupAttachment( szAttachName );
+ }
+
+ m_nObjectCount = 0;
+ Q_strncpy( szAttachName, "objectcable1", 13 );
+ for ( int iObject = 0; iObject < BUFF_STATION_MAX_OBJECTS; ++iObject )
+ {
+ m_hObjects.Set( iObject, NULL );
+
+ szAttachName[11] = '1' + iObject;
+ m_aObjectAttachInfo[iObject].m_iAttachPoint = LookupAttachment( szAttachName );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Create "Buff Station" cable (rope).
+//-----------------------------------------------------------------------------
+CRopeKeyframe *CObjectBuffStation::CreateRope( CBaseTFPlayer *pPlayer, int iAttachPoint )
+{
+ CRopeKeyframe *pRope = CRopeKeyframe::Create( this, pPlayer, iAttachPoint, 0 );
+ if ( pRope )
+ {
+ pRope->m_RopeLength = obj_buff_station_range.GetFloat();
+ pRope->m_Slack = 0.0f;
+ pRope->m_Width = 2;
+ pRope->m_nSegments = ROPE_MAX_SEGMENTS;
+ pRope->m_RopeFlags |= ROPE_COLLIDE;
+ pRope->EnablePlayerWeaponAttach( true );
+ pRope->ActivateStartDirectionConstraints( true );
+ if ( GetTeamNumber() == TEAM_HUMANS )
+ {
+ pRope->SetMaterial( "cable/human_buffcable.vmt" );
+ }
+ else
+ {
+ pRope->SetMaterial( "cable/alien_buffcable.vmt" );
+ }
+ }
+
+ return pRope;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Create "Buff Station" cable (rope).
+//-----------------------------------------------------------------------------
+CRopeKeyframe *CObjectBuffStation::CreateRope( CBaseObject *pObject, int iAttachPoint, int iObjectAttachPoint )
+{
+ CRopeKeyframe *pRope = CRopeKeyframe::Create( this, pObject, iAttachPoint, iObjectAttachPoint );
+ if ( pRope )
+ {
+ pRope->m_RopeLength = obj_buff_station_obj_range.GetFloat();
+ pRope->m_Slack = 0.0f;
+ pRope->m_Width = 2;
+ pRope->m_nSegments = ROPE_MAX_SEGMENTS;
+ pRope->m_RopeFlags |= ROPE_COLLIDE;
+ pRope->EnablePlayerWeaponAttach( true );
+ pRope->ActivateStartDirectionConstraints( true );
+ if ( GetTeamNumber() == TEAM_HUMANS )
+ {
+ pRope->SetMaterial( "cable/human_buffcable.vmt" );
+ }
+ else
+ {
+ pRope->SetMaterial( "cable/alien_buffcable.vmt" );
+ }
+ }
+
+ return pRope;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Is a particular player attached?
+//-----------------------------------------------------------------------------
+bool CObjectBuffStation::IsPlayerAttached( CBaseTFPlayer *pPlayer )
+{
+ for ( int iPlayer = 0; iPlayer < BUFF_STATION_MAX_PLAYERS; iPlayer++ )
+ {
+ if ( m_hPlayers[iPlayer].Get() == pPlayer )
+ return true;
+ }
+
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Is a particular object attached?
+//-----------------------------------------------------------------------------
+bool CObjectBuffStation::IsObjectAttached( CBaseObject *pObject )
+{
+ for ( int iObject = 0; iObject < m_nObjectCount; ++iObject )
+ {
+ if ( m_hObjects[iObject].Get() == pObject )
+ return true;
+ }
+
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Attach the player to the "Buff Station."
+//-----------------------------------------------------------------------------
+void CObjectBuffStation::AttachPlayer( CBaseTFPlayer *pPlayer )
+{
+ // Player shouldn't already be attached.
+ Assert( !IsPlayerAttached( pPlayer ) );
+
+ // Check to see if the player is alive and on the correct team.
+ if ( !pPlayer->IsAlive() || !pPlayer->InSameTeam( this ) )
+ return;
+
+ // Check attachment availability.
+ if ( m_nPlayerCount == BUFF_STATION_MAX_PLAYERS )
+ {
+ // Unless the player is the owner he cannot connect.
+ if ( pPlayer != GetOwner() )
+ return;
+
+ // Kick a non-owning player off.
+ DetachPlayerByIndex( BUFF_STATION_MAX_PLAYERS - 1 );
+ }
+
+ // This will disconnect the player from other Buff Stations, and keep track of important player events.
+ g_pNotify->ReportNamedEvent( pPlayer, "PlayerAttachedToGenerator" );
+ g_pNotify->AddEntity( this, pPlayer );
+
+ // Connect player.
+ // Find the nearest empty slot
+ int iNearest = BUFF_STATION_MAX_PLAYERS;
+ float flNearestDist = 9999*9999;
+ for ( int iPlayer = 0; iPlayer < BUFF_STATION_MAX_PLAYERS; iPlayer++ )
+ {
+ if ( !m_hPlayers[iPlayer] )
+ {
+ Vector vecPoint;
+ QAngle angPoint;
+ GetAttachment( m_aPlayerAttachInfo[iPlayer].m_iAttachPoint, vecPoint, angPoint );
+ float flDistance = ( vecPoint - pPlayer->GetAbsOrigin() ).LengthSqr();
+ if ( flDistance < flNearestDist )
+ {
+ flNearestDist = flDistance;
+ iNearest = iPlayer;
+ }
+ }
+ }
+ Assert( iNearest != BUFF_STATION_MAX_PLAYERS );
+
+ m_hPlayers.Set( iNearest, pPlayer );
+ m_aPlayerAttachInfo[iNearest].m_DamageModifier.SetModifier( obj_buff_station_damage_modifier.GetFloat() );
+ m_aPlayerAttachInfo[iNearest].m_hRope = CreateRope( pPlayer, m_aPlayerAttachInfo[iNearest].m_iAttachPoint );
+ m_nPlayerCount++;
+
+ // Tell the player to constrain his movement.
+ pPlayer->ActivateMovementConstraint( this, GetAbsOrigin(), obj_buff_station_range.GetFloat(), 75.0f, 0.15f );
+
+ // Update think.
+ if ( GetNextThink(BUFF_STATION_BOOST_PLAYER_THINK_CONTEXT) > gpGlobals->curtime + BUFF_STATION_BOOST_PLAYER_THINK_INTERVAL )
+ {
+ SetNextThink( gpGlobals->curtime + BUFF_STATION_BOOST_PLAYER_THINK_INTERVAL, BUFF_STATION_BOOST_PLAYER_THINK_CONTEXT );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Detach the player from the "Buff Station."
+//-----------------------------------------------------------------------------
+void CObjectBuffStation::DetachPlayer( CBaseTFPlayer *pPlayer )
+{
+ for ( int iPlayer = 0; iPlayer < BUFF_STATION_MAX_PLAYERS; iPlayer++ )
+ {
+ if ( m_hPlayers[iPlayer].Get() == pPlayer )
+ {
+ DetachPlayerByIndex( iPlayer );
+ return;
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Detach the player from the "Buff Station."
+//-----------------------------------------------------------------------------
+void CObjectBuffStation::DetachPlayerByIndex( int nIndex )
+{
+ // Valid index?
+ Assert( nIndex < BUFF_STATION_MAX_PLAYERS );
+
+ // Get the player.
+ CBaseTFPlayer *pPlayer = m_hPlayers[nIndex].Get();
+ if ( !pPlayer )
+ {
+ m_hPlayers.Set( nIndex, NULL );
+ return;
+ }
+
+ // Remove the damage modifier.
+ m_aPlayerAttachInfo[nIndex].m_DamageModifier.RemoveModifier();
+
+ // Remove the rope (cable).
+ if ( m_aPlayerAttachInfo[nIndex].m_hRope.Get() )
+ {
+ m_aPlayerAttachInfo[nIndex].m_hRope->DetachPoint( 1 );
+ m_aPlayerAttachInfo[nIndex].m_hRope->DieAtNextRest();
+ }
+
+ // Unconstrain the player movement.
+ pPlayer->DeactivateMovementConstraint();
+
+ // Keep track of player events.
+ g_pNotify->RemoveEntity( this, pPlayer );
+
+ // Reduce player count.
+ m_nPlayerCount--;
+ m_hPlayers.Set( nIndex, NULL );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Attach the object to the "Buff Station."
+//-----------------------------------------------------------------------------
+void CObjectBuffStation::AttachObject( CBaseObject *pObject, bool bPlacing )
+{
+ // Check to see if the object is already attached.
+ if ( IsObjectAttached( pObject ) )
+ return;
+
+ // Check to see if the object is on the correct team.
+ if ( !pObject->InSameTeam( this ) )
+ return;
+
+ // Check to see if the object is already being buffed by another station.
+ if ( pObject->IsHookedAndBuffed() )
+ return;
+
+ // Check attachment availability.
+ if ( m_nObjectCount == BUFF_STATION_MAX_OBJECTS )
+ return;
+
+ // Attach cable to object - get the attachment point.
+ int nObjectAttachPoint = pObject->LookupAttachment( "boostpoint" );
+ if ( nObjectAttachPoint <= 0 )
+ nObjectAttachPoint = 1;
+
+ // Connect object.
+ m_hObjects.Set( m_nObjectCount, pObject );
+ m_aObjectAttachInfo[m_nObjectCount].m_DamageModifier.SetModifier( obj_buff_station_damage_modifier.GetFloat() );
+ m_aObjectAttachInfo[m_nObjectCount].m_hRope = CreateRope( pObject, m_aObjectAttachInfo[m_nObjectCount].m_iAttachPoint, nObjectAttachPoint );
+ m_nObjectCount += 1;
+
+ // If we're placing, we're pretending to buff objects, but not really powering them
+ pObject->SetBuffStation( this, bPlacing );
+
+ // Update think.
+ if ( GetNextThink(BUFF_STATION_BOOST_OBJECT_THINK_CONTEXT) > gpGlobals->curtime + BUFF_STATION_BOOST_OBJECT_THINK_INTERVAL )
+ {
+ SetNextThink( gpGlobals->curtime + BUFF_STATION_BOOST_OBJECT_THINK_INTERVAL, BUFF_STATION_BOOST_OBJECT_THINK_CONTEXT );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Detach the object from the "Buff Station."
+//-----------------------------------------------------------------------------
+void CObjectBuffStation::DetachObject( CBaseObject *pObject )
+{
+ for ( int iObject = 0; iObject < m_nObjectCount; ++iObject )
+ {
+ if ( m_hObjects[iObject].Get() == pObject )
+ {
+ DetachObjectByIndex( iObject );
+ return;
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Detach the object from the "Buff Station."
+//-----------------------------------------------------------------------------
+void CObjectBuffStation::DetachObjectByIndex( int nIndex )
+{
+ // Valid index?
+ Assert( nIndex >= 0 );
+ Assert( nIndex < m_nObjectCount );
+
+ // Get the object.
+ CBaseObject *pObject = m_hObjects[nIndex].Get();
+ if ( !pObject )
+ return;
+
+ // Remove the damage modifier.
+ m_aObjectAttachInfo[nIndex].m_DamageModifier.RemoveModifier();
+
+ // Remove the rope (cable).
+ if ( m_aObjectAttachInfo[nIndex].m_hRope.Get() )
+ {
+ m_aObjectAttachInfo[nIndex].m_hRope->DetachPoint( 1 );
+ m_aObjectAttachInfo[nIndex].m_hRope->DieAtNextRest();
+ }
+
+ // Reduce object count.
+ m_nObjectCount -= 1;
+
+ // Set the object as unbuffed.
+ pObject->SetBuffStation( NULL, false );
+
+ // If the detached object wasn't the last object in the list, swap placement.
+ if ( nIndex != m_nObjectCount )
+ {
+ SwapObjectAttachment( nIndex );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CObjectBuffStation::UpdatePlayerAttachment( CBaseTFPlayer *pPlayer )
+{
+ // Valid player?
+ if ( !pPlayer )
+ return;
+
+ // Attach/Detach (toggle).
+ if ( IsPlayerAttached( pPlayer ) )
+ {
+ DetachPlayer( pPlayer );
+ }
+ else
+ {
+ // Check for power, do not attach to unpowered generator.
+ if ( !IsPowered() )
+ {
+ ClientPrint( pPlayer, HUD_PRINTCENTER, "No power source for the Buff Station!" );
+ }
+ else
+ {
+ AttachPlayer( pPlayer );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CObjectBuffStation::SwapObjectAttachment( int nIndex )
+{
+ bool bModifierActive = m_aObjectAttachInfo[m_nObjectCount].m_DamageModifier.GetCharacter() != NULL;
+ m_aObjectAttachInfo[m_nObjectCount].m_DamageModifier.RemoveModifier();
+
+ m_hObjects.Set( nIndex, m_hObjects[m_nObjectCount] );
+ m_aObjectAttachInfo[nIndex] = m_aObjectAttachInfo[m_nObjectCount];
+
+ if ( bModifierActive )
+ {
+ m_aObjectAttachInfo[nIndex].m_DamageModifier.AddModifierToEntity( m_hObjects[nIndex].Get() );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Input handler
+//-----------------------------------------------------------------------------
+void CObjectBuffStation::InputPlayerSpawned( inputdata_t &inputdata )
+{
+ if ( inputdata.pActivator->IsPlayer() )
+ {
+ CBaseTFPlayer *pPlayer = static_cast<CBaseTFPlayer*>( inputdata.pActivator );
+ if ( IsPlayerAttached( pPlayer ) )
+ {
+ DetachPlayer( pPlayer );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Input handler
+//-----------------------------------------------------------------------------
+void CObjectBuffStation::InputPlayerAttachedToGenerator( inputdata_t &inputdata )
+{
+ if ( inputdata.pActivator->IsPlayer() )
+ {
+ CBaseTFPlayer *pPlayer = static_cast<CBaseTFPlayer*>( inputdata.pActivator );
+ if ( IsPlayerAttached( pPlayer ) )
+ {
+ DetachPlayer( pPlayer );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Boost those attached to me as long as I'm not EMPed
+//-----------------------------------------------------------------------------
+void CObjectBuffStation::BoostPlayerThink( void )
+{
+ // Are we emped?
+ bool bIsEmped = HasPowerup( POWERUP_EMP );
+
+ // Get range (squared = faster test).
+ float flMaxRangeSq = obj_buff_station_range.GetFloat();
+ flMaxRangeSq *= flMaxRangeSq;
+
+ // Boost all attached players and objects.
+ for ( int iPlayer = 0; iPlayer < BUFF_STATION_MAX_PLAYERS; iPlayer++ )
+ {
+ // Clean up dangling pointers + dead players, subversion, disconnection
+ CBaseTFPlayer *pPlayer = m_hPlayers[iPlayer].Get();
+ if ( !pPlayer || !pPlayer->IsAlive() || !InSameTeam( pPlayer ) || !pPlayer->PlayerClass() )
+ {
+ DetachPlayerByIndex( iPlayer );
+ continue;
+ }
+
+ // Check for out of range.
+ float flDistSq = GetAbsOrigin().DistToSqr( pPlayer->GetAbsOrigin() );
+ if ( flDistSq > flMaxRangeSq )
+ {
+ DetachPlayerByIndex( iPlayer );
+ continue;
+ }
+
+ bool bBoosted = false;
+ if ( !bIsEmped )
+ {
+ float flHealAmount = obj_buff_station_heal_rate.GetFloat() * BUFF_STATION_BOOST_PLAYER_THINK_INTERVAL;
+ bBoosted = pPlayer->AttemptToPowerup( POWERUP_BOOST, 0, flHealAmount, this, &m_aPlayerAttachInfo[iPlayer].m_DamageModifier );
+ }
+
+ if ( !bBoosted )
+ {
+ m_aPlayerAttachInfo[iPlayer].m_DamageModifier.RemoveModifier();
+ }
+ }
+
+ // Set next think time.
+ if ( m_nPlayerCount > 0 )
+ {
+ SetNextThink( gpGlobals->curtime + BUFF_STATION_BOOST_PLAYER_THINK_INTERVAL,
+ BUFF_STATION_BOOST_PLAYER_THINK_CONTEXT );
+ }
+ else
+ {
+ SetNextThink( gpGlobals->curtime + 1.0f, BUFF_STATION_BOOST_PLAYER_THINK_CONTEXT );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CObjectBuffStation::BoostObjectThink( void )
+{
+ // Set next boost object think time.
+ SetNextThink( gpGlobals->curtime + BUFF_STATION_BOOST_OBJECT_THINK_INTERVAL,
+ BUFF_STATION_BOOST_OBJECT_THINK_CONTEXT );
+
+ // If we're emped, placing, or building, we're not ready to powerup
+ if ( IsPlacing() || IsBuilding() || HasPowerup( POWERUP_EMP ) )
+ return;
+
+ float flMaxRangeSq = obj_buff_station_obj_range.GetFloat();
+ flMaxRangeSq *= flMaxRangeSq;
+
+ // Boost objects.
+ for ( int iObject = m_nObjectCount; --iObject >= 0; )
+ {
+ CBaseObject *pObject = m_hObjects[iObject].Get();
+ if ( !pObject || !InSameTeam( pObject ) )
+ {
+ DetachObjectByIndex( iObject );
+ continue;
+ }
+
+ // Check for out of range.
+ float flDistSq = GetAbsOrigin().DistToSqr( pObject->GetAbsOrigin() );
+ if ( flDistSq > flMaxRangeSq )
+ {
+ DetachObjectByIndex( iObject );
+ continue;
+ }
+
+ // Don't powerup it until it's finished building
+ if ( pObject->IsPlacing() || pObject->IsBuilding() )
+ continue;
+
+ // Boost it
+ if ( !pObject->AttemptToPowerup( POWERUP_BOOST, BUFF_STATION_BOOST_OBJECT_THINK_INTERVAL, 0,
+ this, &m_aObjectAttachInfo[iObject].m_DamageModifier ) )
+ {
+ m_aObjectAttachInfo[iObject].m_DamageModifier.RemoveModifier();
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CObjectBuffStation::DeBuffObject( CBaseObject *pObject )
+{
+ DetachObject( pObject );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Find nearby objects and buff them.
+//-----------------------------------------------------------------------------
+void CObjectBuffStation::BuffNearbyObjects( CBaseObject *pObjectToTarget, bool bPlacing )
+{
+ // ROBIN: Disabled object buffing for now
+ return;
+
+ // Check for a team.
+ if ( !GetTFTeam() )
+ return;
+
+ // Am I ready to power anything?
+ if ( IsBuilding() || ( !bPlacing && IsPlacing() ) )
+ return;
+
+ // Am I already full?
+ if ( m_nObjectCount >= BUFF_STATION_MAX_OBJECTS )
+ return;
+
+ // Do we have a specific target?
+ if ( pObjectToTarget )
+ {
+ if( !pObjectToTarget->CanBeHookedToBuffStation() || pObjectToTarget->GetBuffStation() )
+ return;
+
+ if ( IsWithinBuffRange( pObjectToTarget ) )
+ {
+ AttachObject( pObjectToTarget, bPlacing );
+ }
+ }
+ else
+ {
+ // Find nearby objects
+ for ( int iObject = 0; iObject < GetTFTeam()->GetNumObjects(); iObject++ )
+ {
+ CBaseObject *pObject = GetTFTeam()->GetObject( iObject );
+ assert(pObject);
+
+ if ( pObject == this || !pObject->CanBeHookedToBuffStation() || pObject->GetBuffStation() )
+ continue;
+
+ // Make sure it's within range
+ if ( IsWithinBuffRange( pObject ) )
+ {
+ AttachObject( pObject, bPlacing );
+
+ // Am I now full?
+ if ( m_nObjectCount >= BUFF_STATION_MAX_OBJECTS )
+ break;
+ }
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Update buff connections on the fly while placing
+//-----------------------------------------------------------------------------
+bool CObjectBuffStation::CalculatePlacement( CBaseTFPlayer *pPlayer )
+{
+ bool bReturn = BaseClass::CalculatePlacement( pPlayer );
+
+ // First, disconnect any connections that should break (too far away).
+ for ( int iObject = m_nObjectCount - 1; iObject >= 0; --iObject )
+ {
+ if ( GetBuffedObject( iObject ) )
+ {
+ CheckBuffConnection( GetBuffedObject( iObject ) );
+ }
+ }
+
+ // If we have any spare connections, look for nearby objects to buff
+ if ( m_nObjectCount < BUFF_STATION_MAX_OBJECTS )
+ {
+ BuffNearbyObjects( NULL, true );
+ }
+
+ return bReturn;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CObjectBuffStation::FinishedBuilding( void )
+{
+ BaseClass::FinishedBuilding();
+
+ for( int iObject = 0; iObject < m_nObjectCount; ++iObject )
+ {
+ if ( GetBuffedObject( iObject ) )
+ {
+ GetBuffedObject( iObject )->SetBuffStation( this, false );
+ }
+ }
+
+ BuffNearbyObjects( NULL, false );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CObjectBuffStation::CheckBuffConnection( CBaseObject *pObject )
+{
+ if ( !pObject->CanBeHookedToBuffStation() )
+ return;
+
+ // Check to see if the object is within the buff range.
+ if ( IsWithinBuffRange( pObject ) )
+ return;
+
+ // It's obscured, or out of range. Remove it.
+ DetachObject( pObject );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Return true if this object is powerable
+//-----------------------------------------------------------------------------
+bool CObjectBuffStation::IsWithinBuffRange( CBaseObject *pObject )
+{
+ if ( ( pObject->GetAbsOrigin() - GetAbsOrigin() ).LengthSqr() < BUFF_STATION_BUFF_RANGE )
+ {
+ // Can I see it?
+ // Ignore things we're attached to
+ trace_t tr;
+ CTraceFilterWorldAndPropsOnly buffFilter;
+ UTIL_TraceLine( WorldSpaceCenter(), pObject->WorldSpaceCenter(), MASK_SOLID_BRUSHONLY, &buffFilter, &tr );
+ CBaseEntity *pEntity = tr.m_pEnt;
+ if ( ( tr.fraction == 1.0 ) || ( pEntity == pObject ) )
+ return true;
+ }
+
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : act -
+//-----------------------------------------------------------------------------
+void CObjectBuffStation::OnActivityChanged( Activity act )
+{
+ BaseClass::OnActivityChanged( act );
+
+ switch ( act )
+ {
+ case ACT_OBJ_ASSEMBLING:
+ m_bBuilding = true;
+ break;
+ default:
+ m_bBuilding = false;
+ break;
+ }
+
+ SetupTeamModel();
+}
+
+
diff --git a/game/server/tf2/tf_obj_buff_station.h b/game/server/tf2/tf_obj_buff_station.h
new file mode 100644
index 0000000..089dbf9
--- /dev/null
+++ b/game/server/tf2/tf_obj_buff_station.h
@@ -0,0 +1,116 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Medic's portable power generator (Buff Station)
+//
+//=============================================================================//
+
+#include "tf_obj.h"
+
+//=============================================================================
+//
+// Portable Power Generator Class (Buff Station)
+//
+class CObjectBuffStation : public CBaseObject
+{
+
+DECLARE_CLASS( CObjectBuffStation, CBaseObject );
+
+public:
+
+ DECLARE_DATADESC();
+ DECLARE_SERVERCLASS();
+
+ CObjectBuffStation();
+
+ static CObjectBuffStation *Create(const Vector &vOrigin, const QAngle &vAngles);
+
+ void Spawn();
+ void Precache();
+ void SetupTeamModel( void );
+ void GetControlPanelInfo( int nPanelIndex, const char *&pPanelName );
+ void DestroyObject( void );
+ void OnGoInactive( void );
+ bool CalculatePlacement( CBaseTFPlayer *pPlayer );
+ void FinishedBuilding( void );
+
+ // Attach/Detach
+ void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value );
+ bool ClientCommand( CBaseTFPlayer *pPlayer, const CCommand &args );
+
+ // EMP
+ bool CanTakeEMPDamage( void ) { return true; }
+
+ // Buff
+ void DeBuffObject( CBaseObject *pObject );
+ void BuffNearbyObjects( CBaseObject *pObjectToTarget, bool bPlacing );
+ void CheckBuffConnection( CBaseObject *pObject );
+
+ virtual void OnActivityChanged( Activity act );
+private:
+
+ void InitAttachmentData( void );
+ CRopeKeyframe *CreateRope( CBaseTFPlayer *pPlayer, int iAttachPoint );
+ CRopeKeyframe *CreateRope( CBaseObject *pObject, int iAttachPoint, int iObjectAttachPoint );
+
+ // Attach/Detach
+ bool IsPlayerAttached( CBaseTFPlayer *pPlayer );
+ bool IsObjectAttached( CBaseObject *pObject );
+
+ void AttachPlayer( CBaseTFPlayer *pPlayer );
+ void DetachPlayer( CBaseTFPlayer *pPlayer );
+ void DetachPlayerByIndex( int nIndex );
+ void AttachObject( CBaseObject *pObject, bool bPlacing );
+ void DetachObject( CBaseObject *pObject );
+ void DetachObjectByIndex( int nIndex );
+
+ void UpdatePlayerAttachment( CBaseTFPlayer *pPlayer );
+
+ void SwapObjectAttachment( int nIndex );
+
+ // Input handlers
+ void InputPlayerSpawned( inputdata_t &inputdata );
+ void InputPlayerAttachedToGenerator( inputdata_t &inputdata );
+
+ // Buff Helpers
+ bool IsWithinBuffRange( CBaseObject *pObject );
+ CBaseObject *GetBuffedObject( int iIndex );
+
+ // Think
+ void BoostPlayerThink( void );
+ void BoostObjectThink( void );
+
+private:
+
+ struct AttachInfo_t
+ {
+ CDamageModifier m_DamageModifier;
+ CHandle<CRopeKeyframe> m_hRope;
+ int m_iAttachPoint;
+ };
+
+ typedef CHandle<CBaseTFPlayer> CPlayerHandle;
+ CNetworkArray( CPlayerHandle, m_hPlayers, BUFF_STATION_MAX_PLAYERS );
+
+ CNetworkVar( int, m_nPlayerCount );
+ AttachInfo_t m_aPlayerAttachInfo[BUFF_STATION_MAX_PLAYERS];
+
+ typedef CHandle<CBaseObject> CObjectHandle;
+ CNetworkArray( CObjectHandle, m_hObjects, BUFF_STATION_MAX_OBJECTS );
+
+ CNetworkVar( int, m_nObjectCount );
+ AttachInfo_t m_aObjectAttachInfo[BUFF_STATION_MAX_OBJECTS];
+
+ bool m_bBuilding;
+};
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+inline CBaseObject *CObjectBuffStation::GetBuffedObject( int iIndex )
+{
+ if ( ( iIndex >= 0 ) && ( iIndex < m_nObjectCount ) )
+ {
+ return m_hObjects[iIndex].Get();
+ }
+
+ return NULL;
+} \ No newline at end of file
diff --git a/game/server/tf2/tf_obj_bunker.cpp b/game/server/tf2/tf_obj_bunker.cpp
new file mode 100644
index 0000000..9382aa6
--- /dev/null
+++ b/game/server/tf2/tf_obj_bunker.cpp
@@ -0,0 +1,174 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: A stationary bunker that players can take cover in.
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#include "cbase.h"
+#include "tf_obj.h"
+#include "tf_team.h"
+#include "tf_obj_bunker.h"
+#include "engine/IEngineSound.h"
+
+#define BUNKER_MINS Vector(-170, -170, 0)
+#define BUNKER_MAXS Vector( 170, 170, 150)
+#define BUNKER_MODEL "models/objects/obj_bunker.mdl"
+#define BUNKER_LADDER_MODEL "models/objects/obj_bunker_ladder.mdl"
+
+IMPLEMENT_SERVERCLASS_ST(CObjectBunker, DT_ObjectBunker)
+END_SEND_TABLE();
+
+LINK_ENTITY_TO_CLASS(obj_bunker, CObjectBunker);
+PRECACHE_REGISTER(obj_bunker);
+
+IMPLEMENT_SERVERCLASS_ST( CObjectBunkerLadder, DT_ObjectBunkerLadder )
+END_SEND_TABLE();
+
+LINK_ENTITY_TO_CLASS( obj_bunker_ladder, CObjectBunkerLadder );
+PRECACHE_REGISTER( obj_bunker_ladder );
+
+// CVars
+ConVar obj_bunker_health( "obj_bunker_health","100", FCVAR_NONE, "Bunker health" );
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CObjectBunker::CObjectBunker( void )
+{
+ m_hLadder = NULL;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CObjectBunker::Spawn( void )
+{
+ Precache();
+ SetModel( BUNKER_MODEL );
+
+ UTIL_SetSize(this, BUNKER_MINS, BUNKER_MAXS);
+ m_takedamage = DAMAGE_YES;
+ m_iHealth = obj_bunker_health.GetInt();
+
+ m_fObjectFlags |= OF_DOESNT_NEED_POWER;
+ SetType( OBJ_BUNKER );
+
+ SetSolid( SOLID_VPHYSICS );
+ VPhysicsInitStatic();
+
+ BaseClass::Spawn();
+
+ SetCollisionGroup( COLLISION_GROUP_VEHICLE );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CObjectBunker::Precache( void )
+{
+ PrecacheModel( BUNKER_MODEL );
+ PrecacheModel( BUNKER_LADDER_MODEL );
+}
+
+
+//------------------------------------------------------------------------------
+// Purpose:
+//------------------------------------------------------------------------------
+void CObjectBunker::UpdateOnRemove( void )
+{
+ if ( m_hLadder.Get() )
+ {
+ UTIL_Remove( m_hLadder );
+ m_hLadder = NULL;
+ }
+
+ // Chain at end to mimic destructor unwind order
+ BaseClass::UpdateOnRemove();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CObjectBunker::FinishedBuilding( void )
+{
+ BaseClass::FinishedBuilding();
+
+ // Create the ladder.
+ Vector vecOrigin;
+ QAngle vecAngles;
+ GetAttachment( "ladder", vecOrigin, vecAngles );
+ m_hLadder = CObjectBunkerLadder::Create( vecOrigin, vecAngles, this );
+ m_hLadder->ChangeTeam( GetTeamNumber() );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Gets info about the control panels
+//-----------------------------------------------------------------------------
+void CObjectBunker::GetControlPanelInfo( int nPanelIndex, const char *&pPanelName )
+{
+ pPanelName = "screen_basic_with_disable";
+}
+
+//==============================================================================
+// Bunker Ladder
+//==============================================================================
+
+//------------------------------------------------------------------------------
+// Purpose:
+//------------------------------------------------------------------------------
+CObjectBunkerLadder::CObjectBunkerLadder()
+{
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CObjectBunkerLadder *CObjectBunkerLadder::Create( const Vector &vOrigin, const QAngle &vAngles, CBaseEntity *pParent )
+{
+ CObjectBunkerLadder *pLadder = static_cast<CObjectBunkerLadder*>( CBaseObject::Create( "obj_bunker_ladder", vOrigin, vAngles ) );
+ if ( pLadder )
+ {
+ pLadder->m_hBunker = pParent;
+ }
+
+ return pLadder;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CObjectBunkerLadder::Spawn()
+{
+ Precache();
+ SetModel( BUNKER_LADDER_MODEL );
+ SetSolid( SOLID_VPHYSICS );
+ m_takedamage = DAMAGE_NO;
+
+ BaseClass::Spawn();
+
+ CollisionProp()->SetSurroundingBoundsType( USE_BEST_COLLISION_BOUNDS );
+ IPhysicsObject *pPhysics = VPhysicsInitStatic();
+ if ( pPhysics )
+ {
+ pPhysics->EnableMotion( false );
+ }
+ SetCollisionGroup( COLLISION_GROUP_VEHICLE );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CObjectBunkerLadder::Precache()
+{
+ PrecacheModel( BUNKER_LADDER_MODEL );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Pass all damage back to the bunker
+//-----------------------------------------------------------------------------
+int CObjectBunkerLadder::OnTakeDamage( const CTakeDamageInfo &info )
+{
+ return m_hBunker->OnTakeDamage( info );
+} \ No newline at end of file
diff --git a/game/server/tf2/tf_obj_bunker.h b/game/server/tf2/tf_obj_bunker.h
new file mode 100644
index 0000000..cdfb23e
--- /dev/null
+++ b/game/server/tf2/tf_obj_bunker.h
@@ -0,0 +1,60 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: A stationary bunker that players can take cover in.
+//
+//=============================================================================//
+
+#ifndef TF_OBJ_BUNKER_H
+#define TF_OBJ_BUNKER_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+class CObjectBunkerLadder;
+
+// ------------------------------------------------------------------------ //
+// Purpose: A stationary bunker that players can take cover in.
+// ------------------------------------------------------------------------ //
+class CObjectBunker : public CBaseObject
+{
+ DECLARE_CLASS( CObjectBunker, CBaseObject );
+
+public:
+ DECLARE_SERVERCLASS();
+
+ CObjectBunker( void );
+
+ virtual void Spawn( void );
+ virtual void Precache( void );
+ virtual void GetControlPanelInfo( int nPanelIndex, const char *&pPanelName );
+ virtual void UpdateOnRemove( void );
+ virtual void FinishedBuilding( void );
+
+private:
+ CHandle<CObjectBunkerLadder> m_hLadder;
+};
+
+//-----------------------------------------------------------------------------
+// Purpose: Bunker ladder
+//-----------------------------------------------------------------------------
+class CObjectBunkerLadder : public CBaseAnimating
+{
+ DECLARE_CLASS( CObjectBunkerLadder, CBaseAnimating );
+
+public:
+
+ DECLARE_SERVERCLASS();
+
+ static CObjectBunkerLadder* Create( const Vector &vOrigin, const QAngle &vAngles, CBaseEntity *pParent );
+
+ CObjectBunkerLadder();
+
+ void Spawn();
+ void Precache();
+ virtual int OnTakeDamage( const CTakeDamageInfo &info );
+
+public:
+ EHANDLE m_hBunker;
+};
+
+#endif // TF_OBJ_BUNKER_H
diff --git a/game/server/tf2/tf_obj_dragonsteeth.cpp b/game/server/tf2/tf_obj_dragonsteeth.cpp
new file mode 100644
index 0000000..07b01d0
--- /dev/null
+++ b/game/server/tf2/tf_obj_dragonsteeth.cpp
@@ -0,0 +1,97 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Vehicle blockers
+//
+// $NoKeywords: $
+//=============================================================================//
+#include "cbase.h"
+#include "tf_player.h"
+#include "tf_team.h"
+#include "tf_obj.h"
+#include "tf_obj_dragonsteeth.h"
+
+// ------------------------------------------------------------------------ //
+// Dragon's teeth defines
+#define DRAGONSTEETH_MINS Vector(-20, -20, 0)
+#define DRAGONSTEETH_MAXS Vector( 20, 20, 35)
+#define DRAGONSTEETH_MODEL "models/objects/human_obj_dragonsteeth.mdl"
+#define DRAGONSTEETH_ASSEMBLING_MODEL "models/objects/human_obj_dragonsteeth_build.mdl"
+
+IMPLEMENT_SERVERCLASS_ST( CObjectDragonsTeeth, DT_ObjectDragonsTeeth )
+END_SEND_TABLE();
+
+LINK_ENTITY_TO_CLASS(obj_dragonsteeth, CObjectDragonsTeeth);
+PRECACHE_REGISTER(obj_dragonsteeth);
+
+ConVar obj_dragonsteeth_health( "obj_dragonsteeth_health","200", FCVAR_NONE, "Dragon's Teeth health" );
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CObjectDragonsTeeth::CObjectDragonsTeeth()
+{
+ UseClientSideAnimation();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CObjectDragonsTeeth::Spawn( void )
+{
+ SetModel( DRAGONSTEETH_MODEL );
+ SetSolid( SOLID_BBOX );
+ UTIL_SetSize(this, DRAGONSTEETH_MINS, DRAGONSTEETH_MAXS);
+
+ m_iHealth = obj_dragonsteeth_health.GetInt();
+ m_fObjectFlags |= OF_DOESNT_NEED_POWER | OF_SUPPRESS_APPEAR_ON_MINIMAP | OF_ALLOW_REPEAT_PLACEMENT | OF_ALIGN_TO_GROUND;
+ SetType( OBJ_DRAGONSTEETH );
+
+ BaseClass::Spawn();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CObjectDragonsTeeth::Precache()
+{
+ BaseClass::Precache();
+ PrecacheModel( DRAGONSTEETH_MODEL );
+ PrecacheModel( DRAGONSTEETH_ASSEMBLING_MODEL );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Start building the object
+//-----------------------------------------------------------------------------
+bool CObjectDragonsTeeth::StartBuilding( CBaseEntity *pBuilder )
+{
+ if ( BaseClass::StartBuilding( pBuilder ) )
+ {
+ // Dragonsteeth randomise their Y before building
+ QAngle vecAngles = GetAbsAngles();
+ vecAngles[YAW] = RandomFloat(0,360);
+ SetAbsAngles( vecAngles );
+
+ return true;
+ }
+
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : act -
+//-----------------------------------------------------------------------------
+void CObjectDragonsTeeth::OnActivityChanged( Activity act )
+{
+ BaseClass::OnActivityChanged( act );
+
+ switch ( act )
+ {
+ case ACT_OBJ_ASSEMBLING:
+ SetModel( DRAGONSTEETH_ASSEMBLING_MODEL );
+ break;
+ default:
+ SetModel( DRAGONSTEETH_MODEL );
+ break;
+ }
+}
diff --git a/game/server/tf2/tf_obj_dragonsteeth.h b/game/server/tf2/tf_obj_dragonsteeth.h
new file mode 100644
index 0000000..3c4dfd1
--- /dev/null
+++ b/game/server/tf2/tf_obj_dragonsteeth.h
@@ -0,0 +1,37 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+#ifndef TF_OBJ_DRAGONSTEETH_H
+#define TF_OBJ_DRAGONSTEETH_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include "tf_obj.h"
+
+// ------------------------------------------------------------------------ //
+// Purpose: Object built to block vehicle movement
+// ------------------------------------------------------------------------ //
+class CObjectDragonsTeeth : public CBaseObject
+{
+DECLARE_CLASS( CObjectDragonsTeeth, CBaseObject );
+
+public:
+ DECLARE_SERVERCLASS();
+
+ CObjectDragonsTeeth();
+
+ virtual void Spawn();
+ virtual void Precache();
+ virtual float GetNearbyObjectCheckRadius( void ) { return 10.0; }
+ virtual bool StartBuilding( CBaseEntity *pBuilder );
+ virtual void OnActivityChanged( Activity act );
+
+public:
+ CHandle< CBaseObject > m_hOwningObject; // Object I was created for
+};
+
+#endif // TF_OBJ_DRAGONSTEETH_H
diff --git a/game/server/tf2/tf_obj_empgenerator.cpp b/game/server/tf2/tf_obj_empgenerator.cpp
new file mode 100644
index 0000000..45a0ba2
--- /dev/null
+++ b/game/server/tf2/tf_obj_empgenerator.cpp
@@ -0,0 +1,96 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+#include "cbase.h"
+#include "tf_player.h"
+#include "tf_team.h"
+#include "tf_gamerules.h"
+#include "tf_obj.h"
+#include "tf_obj_empgenerator.h"
+#include "ndebugoverlay.h"
+
+BEGIN_DATADESC( CObjectEMPGenerator )
+
+ DEFINE_THINKFUNC( EMPThink ),
+
+END_DATADESC()
+
+IMPLEMENT_SERVERCLASS_ST(CObjectEMPGenerator, DT_ObjectEMPGenerator)
+END_SEND_TABLE();
+
+LINK_ENTITY_TO_CLASS(obj_empgenerator, CObjectEMPGenerator);
+PRECACHE_REGISTER(obj_empgenerator);
+
+ConVar obj_empgenerator_health( "obj_empgenerator_health","100", FCVAR_NONE, "EMP Generator health" );
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CObjectEMPGenerator::CObjectEMPGenerator()
+{
+ UseClientSideAnimation();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CObjectEMPGenerator::Spawn()
+{
+ BaseClass::Spawn();
+
+ Precache();
+ SetModel( EMPGENERATOR_MODEL );
+ SetSolid( SOLID_BBOX );
+ SetCollisionGroup( TFCOLLISION_GROUP_COMBATOBJECT );
+
+ UTIL_SetSize(this, EMPGENERATOR_MINS, EMPGENERATOR_MAXS);
+ m_takedamage = DAMAGE_YES;
+ m_iHealth = obj_empgenerator_health.GetInt();
+
+ SetThink( EMPThink );
+ SetNextThink( gpGlobals->curtime + EMPGENERATOR_RATE );
+ m_flExpiresAt = gpGlobals->curtime + EMPGENERATOR_LIFETIME;
+
+ SetType( OBJ_EMPGENERATOR );
+ m_fObjectFlags |= OF_SUPPRESS_NOTIFY_UNDER_ATTACK | OF_SUPPRESS_TECH_ANALYZER |
+ OF_DONT_AUTO_REPAIR | OF_DONT_PREVENT_BUILD_NEAR_OBJ | OF_DOESNT_NEED_POWER;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CObjectEMPGenerator::Precache()
+{
+ PrecacheModel( EMPGENERATOR_MODEL );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Look for enemies to EMP
+//-----------------------------------------------------------------------------
+void CObjectEMPGenerator::EMPThink( void )
+{
+ if ( !GetTeam() )
+ return;
+
+ // Time to die?
+ if ( gpGlobals->curtime > m_flExpiresAt )
+ {
+ UTIL_Remove( this );
+ return;
+ }
+
+ // Look for nearby enemies to EMP
+ CBaseEntity *pEntity = NULL;
+ for ( CEntitySphereQuery sphere( GetAbsOrigin(), EMPGENERATOR_RADIUS ); ( pEntity = sphere.GetCurrentEntity() ) != NULL; sphere.NextEntity() )
+ {
+ if ( InSameTeam( pEntity ) )
+ continue;
+ if ( !pEntity->CanBePoweredUp() )
+ continue;
+
+ pEntity->AttemptToPowerup( POWERUP_EMP, EMPGENERATOR_EMP_TIME );
+ }
+}
diff --git a/game/server/tf2/tf_obj_empgenerator.h b/game/server/tf2/tf_obj_empgenerator.h
new file mode 100644
index 0000000..ffa5274
--- /dev/null
+++ b/game/server/tf2/tf_obj_empgenerator.h
@@ -0,0 +1,51 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef TF_OBJ_EMPGENERATOR_H
+#define TF_OBJ_EMPGENERATOR_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+// ------------------------------------------------------------------------ //
+// EMP Generator defines
+#define EMPGENERATOR_MINS Vector(-20, -20, 0)
+#define EMPGENERATOR_MAXS Vector( 20, 20, 90)
+#define EMPGENERATOR_RADIUS 400
+#define EMPGENERATOR_LIFETIME 15
+#define EMPGENERATOR_RATE 3 // Rate at which it looks for enemies to EMP
+#define EMPGENERATOR_EMP_TIME 5
+#define EMPGENERATOR_MODEL "models/objects/obj_antimortar.mdl"
+
+// ------------------------------------------------------------------------ //
+// EMP Generator Combat Object
+// ------------------------------------------------------------------------ //
+class CObjectEMPGenerator : public CBaseObject
+{
+DECLARE_CLASS( CObjectEMPGenerator, CBaseObject );
+
+public:
+ DECLARE_DATADESC();
+ DECLARE_SERVERCLASS();
+
+ static CObjectEMPGenerator* Create(const Vector &vOrigin, const QAngle &vAngles);
+
+ CObjectEMPGenerator();
+
+ virtual void Spawn();
+ virtual void Precache();
+ virtual bool CanTakeEMPDamage( void ) { return true; }
+
+ // EMP
+ virtual void EMPThink( void );
+
+private:
+ float m_flExpiresAt;
+};
+
+
+#endif // TF_OBJ_EMPGENERATOR_H
diff --git a/game/server/tf2/tf_obj_explosives.cpp b/game/server/tf2/tf_obj_explosives.cpp
new file mode 100644
index 0000000..eea913a
--- /dev/null
+++ b/game/server/tf2/tf_obj_explosives.cpp
@@ -0,0 +1,119 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Upgrade that explodes when it's object dies, injuring nearby enemies
+//
+// $NoKeywords: $
+//=============================================================================//
+#include "cbase.h"
+#include "tf_player.h"
+#include "tf_team.h"
+#include "tf_gamerules.h"
+#include "tf_obj.h"
+#include "ndebugoverlay.h"
+#include "tf_obj_baseupgrade_shared.h"
+#include "hierarchy.h"
+
+// ------------------------------------------------------------------------ //
+// Explosives defines
+#define EXPLOSIVE_MINS Vector(-10, -10, 0)
+#define EXPLOSIVE_MAXS Vector( 10, 10, 10)
+#define EXPLOSIVE_MODEL "models/objects/obj_explosives.mdl"
+
+// ------------------------------------------------------------------------ //
+// Explosives upgrade
+// ------------------------------------------------------------------------ //
+class CObjectExplosives : public CBaseObjectUpgrade
+{
+ DECLARE_CLASS( CObjectExplosives, CBaseObjectUpgrade );
+public:
+ DECLARE_DATADESC();
+ DECLARE_SERVERCLASS();
+
+ CObjectExplosives();
+
+ virtual void Spawn();
+ virtual void Precache();
+ virtual void Killed( void );
+
+ // Explosivo
+ void ExplodeThink( void );
+};
+
+BEGIN_DATADESC( CObjectExplosives )
+
+ DEFINE_THINKFUNC( ExplodeThink ),
+
+END_DATADESC()
+
+IMPLEMENT_SERVERCLASS_ST(CObjectExplosives, DT_ObjectExplosives)
+END_SEND_TABLE();
+
+LINK_ENTITY_TO_CLASS(obj_explosives, CObjectExplosives);
+PRECACHE_REGISTER(obj_explosives);
+
+ConVar obj_explosives_health( "obj_explosives_health","1", FCVAR_NONE, "Explosives health" );
+ConVar obj_explosives_damage( "obj_explosives_damage","100", FCVAR_NONE, "Explosives damage" );
+ConVar obj_explosives_radius( "obj_explosives_radius","256", FCVAR_NONE, "Explosives damage" );
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CObjectExplosives::CObjectExplosives()
+{
+ UseClientSideAnimation();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CObjectExplosives::Spawn()
+{
+ Precache();
+ SetModel( EXPLOSIVE_MODEL );
+ SetCollisionGroup( TFCOLLISION_GROUP_COMBATOBJECT );
+
+ UTIL_SetSize(this, EXPLOSIVE_MINS, EXPLOSIVE_MAXS);
+ m_takedamage = DAMAGE_YES;
+ m_iHealth = obj_explosives_health.GetInt();
+
+ SetType( OBJ_EXPLOSIVES );
+ m_fObjectFlags |= OF_SUPPRESS_NOTIFY_UNDER_ATTACK | OF_SUPPRESS_TECH_ANALYZER | OF_DONT_AUTO_REPAIR | OF_MUST_BE_BUILT_ON_ATTACHMENT;
+
+ BaseClass::Spawn();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CObjectExplosives::Precache()
+{
+ PrecacheModel( EXPLOSIVE_MODEL );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Explosivo!
+//-----------------------------------------------------------------------------
+void CObjectExplosives::Killed( void )
+{
+ // Tell 'em I'm dying now
+ m_bDying = true;
+
+ // Remove myself from the entity I was built on so it can die
+ DetachObjectFromObject();
+
+ // Delay the explosion & death so that it's not blocked by the entity we were built on
+ SetThink( ExplodeThink );
+ SetNextThink( gpGlobals->curtime + 0.3 );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CObjectExplosives::ExplodeThink( void )
+{
+ // Do radius damage
+ RadiusDamage( CTakeDamageInfo( this, GetBuilder(), obj_explosives_damage.GetFloat(), DMG_BLAST ), GetAbsOrigin(), obj_explosives_radius.GetFloat(), CLASS_NONE, NULL );
+
+ // Kill myself
+ BaseClass::Killed();
+} \ No newline at end of file
diff --git a/game/server/tf2/tf_obj_manned_missilelauncher.cpp b/game/server/tf2/tf_obj_manned_missilelauncher.cpp
new file mode 100644
index 0000000..631911b
--- /dev/null
+++ b/game/server/tf2/tf_obj_manned_missilelauncher.cpp
@@ -0,0 +1,227 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: A stationary gun that players can man
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#include "cbase.h"
+#include "tf_obj_manned_plasmagun.h"
+#include "tf_obj_manned_plasmagun_shared.h"
+#include "tf_team.h"
+#include "tf_obj.h"
+#include "sendproxy.h"
+#include "in_buttons.h"
+#include "tf_player.h"
+#include "ammodef.h"
+#include "engine/IEngineSound.h"
+#include "tf_gamerules.h"
+#include "plasmaprojectile.h"
+#include "tf_movedata.h"
+#include "VGuiScreen.h"
+#include "weapon_grenade_rocket.h"
+#include "tf_obj_manned_missilelauncher.h"
+
+#define MANNED_MISSILELAUNCHER_CLIP_COUNT 3
+
+#define MANNED_MISSILELAUNCHER_MINS Vector(-20, -20, 0)
+#define MANNED_MISSILELAUNCHER_MAXS Vector( 20, 20, 55)
+#define MANNED_MISSILELAUNCHER_ALIEN_MODEL "models/objects/obj_manned_plasmagun.mdl"
+#define MANNED_MISSILELAUNCHER_HUMAN_MODEL "models/objects/human_obj_manned_rocketlauncher.mdl"
+
+#define MANNED_MISSILELAUNCHER_RECHARGE_TIME 1.5
+
+#define MANNED_MISSILELAUNCHER_REFIRE_TIME 1.25
+
+BEGIN_DATADESC( CObjectMannedMissileLauncher )
+ DEFINE_THINKFUNC( MissileRechargeThink ),
+END_DATADESC()
+
+IMPLEMENT_SERVERCLASS_ST(CObjectMannedMissileLauncher, DT_ObjectMannedMissileLauncher)
+END_SEND_TABLE();
+
+LINK_ENTITY_TO_CLASS(obj_manned_missilelauncher, CObjectMannedMissileLauncher);
+PRECACHE_REGISTER(obj_manned_missilelauncher);
+
+// CVars
+ConVar obj_manned_missilelauncher_health( "obj_manned_missilelauncher_health","100", FCVAR_NONE, "Manned Missile Launcher health" );
+ConVar obj_manned_missilelauncher_range_def( "obj_manned_missilelauncher_range_def","1100", FCVAR_NONE, "Defensive Manned Missile Launcher range" );
+ConVar obj_manned_missilelauncher_range_off( "obj_manned_missilelauncher_range_off","900", FCVAR_NONE, "Offensive Manned Missile Launcher range" );
+ConVar obj_manned_missilelauncher_damage( "obj_manned_missilelauncher_damage","150", FCVAR_NONE, "Manned Missile Launcher damage" );
+ConVar obj_manned_missilelauncher_radius( "obj_manned_missilelauncher_radius","128", FCVAR_NONE, "Manned Missile Launcher explosive radius" );
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CObjectMannedMissileLauncher::CObjectMannedMissileLauncher()
+{
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CObjectMannedMissileLauncher::Precache()
+{
+ PrecacheModel( MANNED_MISSILELAUNCHER_ALIEN_MODEL );
+ PrecacheModel( MANNED_MISSILELAUNCHER_HUMAN_MODEL );
+
+ PrecacheScriptSound( "ObjectMannedMissileLauncher.Fire" );
+ PrecacheScriptSound( "ObjectMannedMissileLauncher.Reload" );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CObjectMannedMissileLauncher::Spawn()
+{
+ m_iHealth = obj_manned_missilelauncher_health.GetInt();
+ BaseClass::Spawn();
+
+ SetSolid( SOLID_BBOX );
+ UTIL_SetSize(this, MANNED_MISSILELAUNCHER_MINS, MANNED_MISSILELAUNCHER_MAXS);
+
+ SetThink( MissileRechargeThink );
+ SetNextThink( gpGlobals->curtime + MANNED_MISSILELAUNCHER_RECHARGE_TIME );
+
+ SetType( OBJ_MANNED_MISSILELAUNCHER );
+ m_nAmmoType = GetAmmoDef()->Index( "Rockets" );
+ m_nAmmoCount = m_nMaxAmmoCount = MANNED_MISSILELAUNCHER_CLIP_COUNT;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Finished the build
+//-----------------------------------------------------------------------------
+void CObjectMannedMissileLauncher::FinishedBuilding( void )
+{
+ BaseClass::FinishedBuilding();
+
+ CalculateMaxRange( obj_manned_missilelauncher_range_def.GetFloat(), obj_manned_missilelauncher_range_off.GetFloat() );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CObjectMannedMissileLauncher::SetupTeamModel( void )
+{
+ // FIXME: When adding in build animations here, make sure C_ObjectBaseMannedGun::OnDataChanged
+ // does the right thing on the client!!
+ if ( GetTeamNumber() == TEAM_HUMANS )
+ {
+ SetMovementStyle( MOVEMENT_STYLE_BARREL_PIVOT );
+ SetModel( MANNED_MISSILELAUNCHER_HUMAN_MODEL );
+ }
+ else
+ {
+ SetMovementStyle( MOVEMENT_STYLE_STANDARD );
+ SetModel( MANNED_MISSILELAUNCHER_ALIEN_MODEL );
+ }
+
+ // Call this to get all the attachment points happy
+ OnModelSelected();
+}
+
+//-----------------------------------------------------------------------------
+// Recharge think...
+//-----------------------------------------------------------------------------
+void CObjectMannedMissileLauncher::MissileRechargeThink( void )
+{
+ // Prevent manned guns from deteriorating
+ ResetDeteriorationTime();
+ SetNextThink( gpGlobals->curtime + (HasPowerup(POWERUP_EMP) ? (MANNED_MISSILELAUNCHER_RECHARGE_TIME * 1.5) : MANNED_MISSILELAUNCHER_RECHARGE_TIME ) );
+
+ // I can't do anything if I'm not active
+ if ( !ShouldBeActive() )
+ return;
+
+ if (m_nAmmoCount < m_nMaxAmmoCount)
+ {
+ m_nAmmoCount += 1;
+
+ EmitSound( "ObjectMannedMissileLauncher.Reload" );
+
+ // Push fire out
+ m_flNextAttack = gpGlobals->curtime + 0.3;
+ }
+ else
+ {
+ // No need to think when it's full
+ SetNextThink( gpGlobals->curtime + 5.0f );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Missile Launcher fire
+//-----------------------------------------------------------------------------
+void CObjectMannedMissileLauncher::Fire( )
+{
+ if ( m_flNextAttack > gpGlobals->curtime )
+ return;
+ if ( !m_nAmmoCount )
+ return;
+
+ // Push recharge out
+ SetNextThink( gpGlobals->curtime + (HasPowerup(POWERUP_EMP) ? (MANNED_MISSILELAUNCHER_RECHARGE_TIME * 1.5) : MANNED_MISSILELAUNCHER_RECHARGE_TIME ) );
+
+ // We have to flush the bone cache because it's possible that only the bone controllers
+ // have changed since the bonecache was generated, and bone controllers aren't checked.
+ InvalidateBoneCache();
+
+ QAngle vecAng;
+ Vector vecSrc, vecAim;
+ GetAttachment( m_nBarrelAttachment, vecSrc, vecAng );
+
+ // Get the distance to the target
+ AngleVectors( vecAng, &vecAim );
+
+ // Create the rocket.
+ CWeaponGrenadeRocket *pRocket = CWeaponGrenadeRocket::Create( vecSrc, vecAim, m_flMaxRange, this );
+ if ( pRocket )
+ {
+ pRocket->SetRealOwner( GetDriverPlayer() );
+ pRocket->SetDamage( obj_manned_missilelauncher_damage.GetFloat() );
+ pRocket->SetDamageRadius( obj_manned_missilelauncher_radius.GetFloat() );
+ }
+
+ SetActivity( ACT_VM_PRIMARYATTACK );
+
+ EmitSound( "ObjectMannedMissileLauncher.Fire" );
+ // SetSentryAnim( TFTURRET_ANIM_FIRE );
+ DoMuzzleFlash();
+
+ m_nAmmoCount -= 1;
+
+ // Slow fire rate while EMPed
+ m_flNextAttack = gpGlobals->curtime + ( HasPowerup(POWERUP_EMP) ? MANNED_MISSILELAUNCHER_REFIRE_TIME * 2 : MANNED_MISSILELAUNCHER_REFIRE_TIME );
+}
+
+#if defined( CLIENT_DLL )
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : updateType -
+//-----------------------------------------------------------------------------
+void CObjectMannedMissileLauncher::PostDataUpdate( DataUpdateType_t updateType )
+{
+ BaseClass::PostDataUpdate( updateType );
+
+ bool teamchanged = GetTeamNumber() != m_nPreviousTeam;
+
+ if ( teamchanged ||
+ updateType == DATA_UPDATE_CREATED )
+ {
+ C_BaseAnimating::AllowBoneAccess( true, false );
+ SetupTeamModel();
+ C_BaseAnimating::AllowBoneAccess( false, false );
+ }
+}
+
+void CObjectMannedMissileLauncher::PreDataUpdate( DataUpdateType_t updateType )
+{
+ BaseClass::PreDataUpdate( updateType );
+
+ m_nPreviousTeam = GetTeamNumber();
+}
+
+#endif
diff --git a/game/server/tf2/tf_obj_manned_missilelauncher.h b/game/server/tf2/tf_obj_manned_missilelauncher.h
new file mode 100644
index 0000000..ac03216
--- /dev/null
+++ b/game/server/tf2/tf_obj_manned_missilelauncher.h
@@ -0,0 +1,57 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+#ifndef TF_OBJ_MANNED_MISSILELAUNCHER_H
+#define TF_OBJ_MANNED_MISSILELAUNCHER_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include "tf_obj_base_manned_gun.h"
+
+// ------------------------------------------------------------------------ //
+// A stationary gun that players can man that's built by the player
+// ------------------------------------------------------------------------ //
+class CObjectMannedMissileLauncher : public CObjectBaseMannedGun
+{
+ DECLARE_CLASS( CObjectMannedMissileLauncher, CObjectBaseMannedGun );
+
+public:
+ DECLARE_SERVERCLASS();
+ DECLARE_DATADESC();
+
+ static CObjectMannedMissileLauncher* Create(const Vector &vOrigin, const QAngle &vAngles);
+
+ CObjectMannedMissileLauncher();
+
+ virtual void Spawn();
+ virtual void Precache();
+ virtual void FinishedBuilding( void );
+ virtual void SetupTeamModel( void );
+ void MissileRechargeThink( void );
+
+#if defined( CLIENT_DLL )
+ virtual bool ShouldPredict( void )
+ {
+ if ( GetOwner() == C_BasePlayer::GetLocalPlayer() )
+ return true;
+
+ return BaseClass::ShouldPredict();
+ }
+
+ virtual void PreDataUpdate( DataUpdateType_t updateType );
+ virtual void PostDataUpdate( DataUpdateType_t updateType );
+#endif
+
+protected:
+ // Fire the weapon
+ virtual void Fire( void );
+
+ int m_nPreviousTeam;
+ int m_nMaxAmmoCount;
+};
+
+#endif // TF_OBJ_MANNED_MISSILELAUNCHER_H
diff --git a/game/server/tf2/tf_obj_manned_shield.cpp b/game/server/tf2/tf_obj_manned_shield.cpp
new file mode 100644
index 0000000..878c302
--- /dev/null
+++ b/game/server/tf2/tf_obj_manned_shield.cpp
@@ -0,0 +1,240 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: A stationary shield that players can man
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#include "cbase.h"
+#include "tf_obj_base_manned_gun.h"
+#include "tf_shield.h"
+#include "in_buttons.h"
+
+
+// ------------------------------------------------------------------------ //
+// A stationary gun that players can man that's built by the player
+// ------------------------------------------------------------------------ //
+class CObjectMannedShield : public CObjectBaseMannedGun
+{
+ DECLARE_CLASS( CObjectMannedShield, CObjectBaseMannedGun );
+
+public:
+ DECLARE_SERVERCLASS();
+ DECLARE_DATADESC();
+
+ CObjectMannedShield();
+ virtual ~CObjectMannedShield();
+
+ static CObjectMannedShield* Create(const Vector &vOrigin, const QAngle &vAngles);
+
+ virtual void Spawn();
+ virtual void Precache();
+ virtual void SetPassenger( int nRole, CBasePlayer *pEnt );
+
+ virtual void PowerupStart( int iPowerup, float flAmount, CBaseEntity *pAttacker, CDamageModifier *pDamageModifier );
+ virtual void PowerupEnd( int iPowerup );
+
+protected:
+
+ virtual void OnItemPostFrame( CBaseTFPlayer *pDriver );
+
+
+private:
+
+ void ShieldRotationThink();
+
+ CHandle<CShield> m_hDeployedShield;
+};
+
+#define MANNED_SHIELD_CLIP_COUNT 3
+
+#define MANNED_SHIELD_MINS Vector(-20, -20, 0)
+#define MANNED_SHIELD_MAXS Vector( 20, 20, 55)
+#define MANNED_SHIELD_MODEL "models/objects/obj_manned_plasmagun.mdl"
+
+
+BEGIN_DATADESC( CObjectMannedShield )
+END_DATADESC()
+
+IMPLEMENT_SERVERCLASS_ST(CObjectMannedShield, DT_ObjectMannedShield)
+END_SEND_TABLE()
+
+LINK_ENTITY_TO_CLASS(obj_manned_shield, CObjectMannedShield);
+PRECACHE_REGISTER(obj_manned_shield);
+
+// CVars
+ConVar obj_manned_shield_health( "obj_manned_shield_health","100", FCVAR_NONE, "Manned Missile Launcher health" );
+
+const char *g_pMannedShieldThinkContextName = "MannedShieldThinkContext";
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CObjectMannedShield::CObjectMannedShield()
+{
+}
+
+CObjectMannedShield::~CObjectMannedShield()
+{
+ UTIL_Remove( m_hDeployedShield );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CObjectMannedShield::Spawn()
+{
+ m_iHealth = obj_manned_shield_health.GetInt();
+ BaseClass::Spawn();
+
+ SetMovementStyle( MOVEMENT_STYLE_SIMPLE );
+ SetModel( MANNED_SHIELD_MODEL );
+ OnModelSelected();
+ SetSolid( SOLID_BBOX );
+ UTIL_SetSize(this, MANNED_SHIELD_MINS, MANNED_SHIELD_MAXS);
+
+ SetMaxPassengerCount( 1 );
+
+ SetType( OBJ_MANNED_SHIELD );
+
+ SetContextThink( ShieldRotationThink, gpGlobals->curtime + 0.1, g_pMannedShieldThinkContextName );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Powerup has just started
+//-----------------------------------------------------------------------------
+void CObjectMannedShield::PowerupStart( int iPowerup, float flAmount, CBaseEntity *pAttacker, CDamageModifier *pDamageModifier )
+{
+ switch( iPowerup )
+ {
+ case POWERUP_BOOST:
+ // Increase our shield's energy
+ if ( m_hDeployedShield )
+ {
+ m_hDeployedShield->SetPower( m_hDeployedShield->GetPower() + (flAmount * 10) );
+ }
+ break;
+
+ case POWERUP_EMP:
+ if (m_hDeployedShield)
+ {
+ m_hDeployedShield->SetEMPed(true);
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ BaseClass::PowerupStart( iPowerup, flAmount, pAttacker, pDamageModifier );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Powerup has just started
+//-----------------------------------------------------------------------------
+void CObjectMannedShield::PowerupEnd( int iPowerup )
+{
+ switch ( iPowerup )
+ {
+ case POWERUP_EMP:
+ if (m_hDeployedShield)
+ {
+ m_hDeployedShield->SetEMPed(false);
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ BaseClass::PowerupEnd( iPowerup );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CObjectMannedShield::ShieldRotationThink( void )
+{
+ if ( m_hDeployedShield )
+ {
+ QAngle vAngles;
+ vAngles[YAW] = GetAbsAngles()[YAW] + GetGunYaw();
+ vAngles[PITCH] = GetAbsAngles()[PITCH] + GetGunPitch();
+ vAngles[ROLL] = 0;
+ m_hDeployedShield->SetCenterAngles( vAngles );
+ }
+
+ SetContextThink( ShieldRotationThink, gpGlobals->curtime + 0.1, g_pMannedShieldThinkContextName );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CObjectMannedShield::Precache( void )
+{
+ PrecacheModel( MANNED_SHIELD_MODEL );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Get and set the current driver.
+//-----------------------------------------------------------------------------
+void CObjectMannedShield::SetPassenger( int nRole, CBasePlayer *pEnt )
+{
+ BaseClass::SetPassenger( nRole, pEnt );
+
+ // If we just got a player, create the shield. Otherwise, remove it
+ if ( pEnt )
+ {
+ if ( !m_hDeployedShield )
+ {
+ // Hack our angles so the mobile shield appears directly in front of the gun
+ QAngle vecOldAngles = GetAbsAngles();
+ QAngle vAngles;
+ vAngles[YAW] = GetAbsAngles()[YAW] + GetGunYaw();
+ vAngles[PITCH] = GetAbsAngles()[PITCH] + GetGunPitch();
+ vAngles[ROLL] = 0;
+ SetAbsAngles( vAngles );
+
+ int nAttachmentIndex = LookupAttachment( "barrel" );
+ m_hDeployedShield = CreateMobileShield( this, 0 );
+ if ( m_hDeployedShield )
+ {
+ m_hDeployedShield->SetThetaPhi( 60, 40 );
+ m_hDeployedShield->SetAngularSpringConstant( 15 );
+ m_hDeployedShield->SetAlwaysOrient( false );
+ m_hDeployedShield->SetAttachmentIndex( nAttachmentIndex );
+ }
+
+ SetAbsAngles( vecOldAngles );
+ }
+ }
+ else
+ {
+ if ( m_hDeployedShield )
+ {
+ UTIL_Remove( m_hDeployedShield );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CObjectMannedShield::OnItemPostFrame( CBaseTFPlayer *pDriver )
+{
+ int iOldButtons = pDriver->m_nButtons;
+
+ // Manned shields have no gun, so map both attack buttons to laser designate
+ if ( pDriver->m_nButtons & IN_ATTACK )
+ {
+ pDriver->m_nButtons &= ~IN_ATTACK;
+ pDriver->m_nButtons |= IN_ATTACK2;
+ }
+
+ BaseClass::OnItemPostFrame( pDriver );
+
+ pDriver->m_nButtons = iOldButtons;
+}
diff --git a/game/server/tf2/tf_obj_mapdefined.cpp b/game/server/tf2/tf_obj_mapdefined.cpp
new file mode 100644
index 0000000..56b1efa
--- /dev/null
+++ b/game/server/tf2/tf_obj_mapdefined.cpp
@@ -0,0 +1,140 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+#include "cbase.h"
+#include "tf_player.h"
+#include "tf_team.h"
+#include "tf_gamerules.h"
+#include "tf_obj.h"
+#include "tf_obj_mapdefined.h"
+#include "ndebugoverlay.h"
+
+extern ConVar obj_damage_factor;
+
+// Map defined object spawnflags
+#define SF_MAPDEFOBJ_SUPPRESS_MINIMAP 0x0001
+#define SF_MAPDEFOBJ_SUPPRESS_ATTACKNOTIFY 0x0002
+#define SF_MAPDEFOBJ_DOESNT_NEED_POWER 0x0004
+
+BEGIN_DATADESC( CObjectMapDefined )
+ // keys
+ DEFINE_KEYFIELD_NOT_SAVED( m_iszCustomName , FIELD_STRING, "CustomName" ),
+END_DATADESC()
+
+IMPLEMENT_SERVERCLASS_ST(CObjectMapDefined, DT_ObjectMapDefined)
+ SendPropString( SENDINFO( m_szCustomName ) ),
+END_SEND_TABLE();
+
+LINK_ENTITY_TO_CLASS(obj_mapdefined, CObjectMapDefined);
+LINK_ENTITY_TO_CLASS(func_obj_mapdefined, CObjectMapDefined);
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CObjectMapDefined::CObjectMapDefined()
+{
+ UseClientSideAnimation();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CObjectMapDefined::Spawn()
+{
+ // Get the model from the map
+ char *szModel = (char *)STRING( GetModelName() );
+ if (!szModel || !*szModel)
+ {
+ Warning( "obj_mapdefined at %.0f %.0f %0.f missing modelname\n", GetAbsOrigin().x, GetAbsOrigin().y, GetAbsOrigin().z );
+ UTIL_Remove( this );
+ return;
+ }
+
+ memset( m_szCustomName.GetForModify(), 0, sizeof(m_szCustomName) );
+ if ( m_iszCustomName != NULL_STRING )
+ {
+ Q_strncpy( m_szCustomName.GetForModify(), STRING(m_iszCustomName), sizeof(m_szCustomName) );
+ }
+
+ Precache();
+
+ // Get the bounding box from the model
+ if ( szModel[0] != '*' )
+ {
+ SetModel( szModel );
+ Vector vecMins, vecMaxs;
+ const model_t *pModel = GetModel();
+ modelinfo->GetModelBounds( pModel, vecMins, vecMaxs );
+ UTIL_SetSize(this, vecMins, vecMaxs );
+ }
+ else
+ {
+ // Don't call our internal setmodel to avoid the error
+ UTIL_SetModel( this, szModel );
+
+ // No control panels / power on map geometry objects
+ m_fObjectFlags |= OF_DOESNT_HAVE_A_MODEL;
+ m_fObjectFlags |= OF_DOESNT_NEED_POWER;
+ }
+
+ SetSolid( SOLID_VPHYSICS );
+ SetType( OBJ_MAPDEFINED );
+
+ // Setup object flags
+ if ( HasSpawnFlags( SF_MAPDEFOBJ_SUPPRESS_MINIMAP ) )
+ {
+ m_fObjectFlags |= OF_SUPPRESS_APPEAR_ON_MINIMAP;
+ }
+ if ( HasSpawnFlags( SF_MAPDEFOBJ_SUPPRESS_ATTACKNOTIFY ) )
+ {
+ m_fObjectFlags |= OF_SUPPRESS_NOTIFY_UNDER_ATTACK;
+ }
+ if ( HasSpawnFlags( SF_MAPDEFOBJ_DOESNT_NEED_POWER ) )
+ {
+ m_fObjectFlags |= OF_DOESNT_NEED_POWER;
+ }
+
+ // If I don't have health, make me invulnerable
+ if ( !m_iHealth )
+ {
+ m_bInvulnerable = true;
+ }
+
+ BaseClass::Spawn();
+
+ // Override base object settings
+ SetCollisionGroup( COLLISION_GROUP_NONE );
+
+ FinishedBuilding();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CObjectMapDefined::Precache()
+{
+ PrecacheModel( STRING( GetModelName() ) );
+ BaseClass::Precache();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+int CObjectMapDefined::OnTakeDamage( const CTakeDamageInfo &info )
+{
+ CTakeDamageInfo childInfo = info;
+
+ // Hack around the damage factor applied to objects
+ if ( obj_damage_factor.GetFloat() )
+ {
+ float flDamage = info.GetDamage() * (1 / obj_damage_factor.GetFloat());
+ childInfo.SetDamage( flDamage );
+ }
+
+ return BaseClass::OnTakeDamage( childInfo );
+} \ No newline at end of file
diff --git a/game/server/tf2/tf_obj_mapdefined.h b/game/server/tf2/tf_obj_mapdefined.h
new file mode 100644
index 0000000..2b1d837
--- /dev/null
+++ b/game/server/tf2/tf_obj_mapdefined.h
@@ -0,0 +1,40 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef TF_OBJ_MAPDEFINED_H
+#define TF_OBJ_MAPDEFINED_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+// ------------------------------------------------------------------------ //
+// Purpose: Map Defined object placed by mapmakers
+// ------------------------------------------------------------------------ //
+class CObjectMapDefined : public CBaseObject
+{
+DECLARE_CLASS( CObjectMapDefined, CBaseObject );
+
+public:
+ DECLARE_DATADESC();
+ DECLARE_SERVERCLASS();
+
+ static CObjectMapDefined* Create(const Vector &vOrigin, const QAngle &vAngles);
+
+ CObjectMapDefined();
+
+ virtual void Spawn();
+ virtual void Precache();
+ virtual bool CanTakeEMPDamage( void ) { return true; }
+ virtual int OnTakeDamage( const CTakeDamageInfo &info );
+
+private:
+ // Custom names for ID strings
+ string_t m_iszCustomName;
+ CNetworkString( m_szCustomName, MAX_OBJ_CUSTOMNAME_SIZE );
+};
+
+#endif // TF_OBJ_MAPDEFINED_H
diff --git a/game/server/tf2/tf_obj_mcv_selection_panel.cpp b/game/server/tf2/tf_obj_mcv_selection_panel.cpp
new file mode 100644
index 0000000..c293132
--- /dev/null
+++ b/game/server/tf2/tf_obj_mcv_selection_panel.cpp
@@ -0,0 +1,138 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+#include "cbase.h"
+#include "tf_obj.h"
+#include "tf_shareddefs.h"
+#include "vguiscreen.h"
+#include "tf_vehicle_teleport_station.h"
+
+
+#define MCV_SELECTION_MODEL "models/objects/obj_resupply.mdl"
+#define MCV_SELECTION_SCREEN_NAME "screen_mcv_selection_panel"
+
+
+class CObjMCVSelectionPanel : public CBaseObject
+{
+public:
+
+ DECLARE_CLASS( CObjMCVSelectionPanel, CBaseObject );
+ DECLARE_SERVERCLASS();
+
+ CObjMCVSelectionPanel();
+ ~CObjMCVSelectionPanel();
+
+
+public:
+
+ virtual void Spawn();
+ virtual void Precache();
+ virtual void GetControlPanelInfo( int nPanelIndex, const char *&pPanelName );
+ virtual bool ClientCommand( CBaseTFPlayer *pPlayer, const char *pCmd, ICommandArguments *pArg );
+
+ virtual void SetTransmit( CCheckTransmitInfo *pInfo, bool bAlways );
+};
+
+
+// This holds all the allocated CObjMCVSelectionPanels.
+CUtlLinkedList<CObjMCVSelectionPanel*,int> g_MCVSelectionPanels;
+
+
+LINK_ENTITY_TO_CLASS( obj_mcv_selection_panel, CObjMCVSelectionPanel );
+
+
+int SendProxy_TeleportStationCount( const void *pStruct, int objectID )
+{
+ return CVehicleTeleportStation::GetNumDeployedTeleportStations();
+}
+
+
+void SendProxy_TeleportStationElement( const SendProp *pProp, const void *pStructBase, const void *pData, DVariant *pOut, int iElement, int objectID )
+{
+ // Get the EHANDLE.
+ EHANDLE hEnt;
+ hEnt = CVehicleTeleportStation::GetDeployedTeleportStation( iElement );
+
+ // Use the standard ehandle-encoding SendProxy to encode it.
+ SendProxy_EHandleToInt( pProp, pStructBase, &hEnt, pOut, iElement, objectID );
+}
+
+
+void SignalChangeInMCVSelectionPanels()
+{
+}
+
+
+IMPLEMENT_SERVERCLASS_ST( CObjMCVSelectionPanel, DT_MCVSelectionPanel )
+ SendPropVirtualArray(
+ SendProxy_TeleportStationCount,
+ 32, // max # elements we'd ever send
+ SendPropEHandle( "teleport_station_element", 0, 0, 0, SendProxy_TeleportStationElement ),
+ "teleport_stations" )
+END_SEND_TABLE()
+
+
+CObjMCVSelectionPanel::CObjMCVSelectionPanel()
+{
+ g_MCVSelectionPanels.AddToTail( this );
+}
+
+
+CObjMCVSelectionPanel::~CObjMCVSelectionPanel()
+{
+ g_MCVSelectionPanels.FindAndRemove( this );
+}
+
+
+void CObjMCVSelectionPanel::Spawn()
+{
+ SetModel( MCV_SELECTION_MODEL );
+ m_takedamage = DAMAGE_NO;
+ SetType( OBJ_MCV_SELECTION_PANEL );
+
+ BaseClass::Spawn();
+}
+
+
+void CObjMCVSelectionPanel::Precache()
+{
+ PrecacheModel( MCV_SELECTION_MODEL );
+ PrecacheVGuiScreen( MCV_SELECTION_SCREEN_NAME );
+
+ BaseClass::Precache();
+}
+
+
+void CObjMCVSelectionPanel::GetControlPanelInfo( int nPanelIndex, const char *&pPanelName )
+{
+ pPanelName = MCV_SELECTION_SCREEN_NAME;
+}
+
+
+bool CObjMCVSelectionPanel::ClientCommand( CBaseTFPlayer *pPlayer, const char *pCmd, ICommandArguments *pArg )
+{
+ if ( stricmp( pCmd, "SelectMCV" ) == 0 )
+ {
+ int mcvID = atoi( pArg->Argv( 1 ) );
+ pPlayer->SetSelectedMCV( dynamic_cast< CVehicleTeleportStation* >( CBaseEntity::Instance( mcvID ) ) );
+ }
+
+ return BaseClass::ClientCommand( pPlayer, pCmd, pArg );
+}
+
+
+void CObjMCVSelectionPanel::SetTransmit( CCheckTransmitInfo *pInfo, bool bAlways )
+{
+ BaseClass::SetTransmit( pInfo, bAlways );
+
+ // Force deployed MCVs to be sent to the client too so the client can draw their position on its vgui screen.
+ int count = CVehicleTeleportStation::GetNumDeployedTeleportStations();
+ for ( int i=0; i < count; i++ )
+ {
+ CVehicleTeleportStation::GetDeployedTeleportStation( i )->SetTransmit( pInfo, bAlways );
+ }
+}
+
diff --git a/game/server/tf2/tf_obj_mcv_selection_panel.h b/game/server/tf2/tf_obj_mcv_selection_panel.h
new file mode 100644
index 0000000..47e6984
--- /dev/null
+++ b/game/server/tf2/tf_obj_mcv_selection_panel.h
@@ -0,0 +1,18 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+#ifndef TF_OBJ_MCV_SELECTION_PANEL_H
+#define TF_OBJ_MCV_SELECTION_PANEL_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+
+// Tells the MCV selection panels to detect network state changes.
+void SignalChangeInMCVSelectionPanels();
+
+
+#endif // TF_OBJ_MCV_SELECTION_PANEL_H
diff --git a/game/server/tf2/tf_obj_mortar.cpp b/game/server/tf2/tf_obj_mortar.cpp
new file mode 100644
index 0000000..2473519
--- /dev/null
+++ b/game/server/tf2/tf_obj_mortar.cpp
@@ -0,0 +1,266 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Indirect's mortar object
+//
+// $NoKeywords: $
+//=============================================================================//
+#include "cbase.h"
+#include "tf_player.h"
+#include "tf_team.h"
+#include "basecombatweapon.h"
+#include "tf_obj.h"
+#include "tf_obj_mortar.h"
+#include "techtree.h"
+#include "tf_shareddefs.h"
+#include "weapon_mortar.h"
+#include "vstdlib/random.h"
+#include "movevars_shared.h"
+#include "mortar_round.h"
+
+
+LINK_ENTITY_TO_CLASS(obj_mortar, CObjectMortar);
+PRECACHE_REGISTER(obj_mortar);
+
+IMPLEMENT_SERVERCLASS_ST(CObjectMortar, DT_ObjectMortar)
+ SendPropInt( SENDINFO( m_iRoundType ), 8, SPROP_UNSIGNED, 0 ),
+ SendPropArray( SendPropInt( SENDINFO_ARRAY(m_iMortarRounds), 7, 0 ), m_iMortarRounds ),
+END_SEND_TABLE();
+
+BEGIN_DATADESC( CObjectMortar )
+
+ DEFINE_THINKFUNC( ReloadingThink ),
+
+END_DATADESC()
+
+// Mortar size
+#define MORTAR_MINS Vector(-16, -16, 0)
+#define MORTAR_MAXS Vector( 16, 16, 64)
+
+ConVar obj_mortar_health( "obj_mortar_health","200", FCVAR_NONE, "Mortar object health" );
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CObjectMortar::CObjectMortar()
+{
+ for ( int i=0; i < m_iMortarRounds.Count(); i++ )
+ m_iMortarRounds.Set( i, 0 );
+ m_flLastBlastTime = -1;
+ UseClientSideAnimation();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CObjectMortar::Spawn()
+{
+ SetModel( "models/objects/obj_mortar.mdl" );
+
+ SetSolid( SOLID_BBOX );
+ UTIL_SetSize(this, MORTAR_MINS, MORTAR_MAXS);
+ m_takedamage = DAMAGE_YES;
+ m_iHealth = obj_mortar_health.GetInt();
+ m_iRoundType = MA_SHELL;
+ m_iSalvoLeft = MORTAR_SALVO_SIZE;
+ m_fObjectFlags |= OF_DONT_PREVENT_BUILD_NEAR_OBJ;
+
+ SetType( OBJ_MORTAR );
+
+ // Fill out the ammo levels
+ for ( int i = 0; i < MA_LASTAMMOTYPE; i++ )
+ {
+ m_iMortarRounds.Set( i, MortarAmmoMax[i] );
+ }
+
+ BaseClass::Spawn();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CObjectMortar::Precache()
+{
+ PrecacheModel( "models/objects/obj_mortar.mdl" );
+ PrecacheVGuiScreen( "screen_obj_mortar" );
+}
+
+//-----------------------------------------------------------------------------
+// Gets info about the control panels
+//-----------------------------------------------------------------------------
+void CObjectMortar::GetControlPanelInfo( int nPanelIndex, const char *&pPanelName )
+{
+ pPanelName = "screen_obj_mortar";
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CObjectMortar::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
+{
+ // Sapper removal
+ if ( RemoveEnemyAttachments( pActivator ) )
+ return;
+
+ if ( pActivator == GetOwner() )
+ {
+ int iOldType = m_iRoundType;
+ m_iRoundType += 1;
+
+ // Cycle to the next ammo type
+ while ( m_iRoundType != iOldType )
+ {
+ // Hit the end of the round types?
+ if ( m_iRoundType == MA_LASTAMMOTYPE )
+ {
+ m_iRoundType = MA_SHELL;
+ break;
+ }
+
+ // Does this round type need a technology?
+ if ( MortarAmmoTechs[ m_iRoundType ] && MortarAmmoTechs[ m_iRoundType ][0] )
+ {
+ // Does the player have the technology?
+ if ( GetOwner() && GetOwner()->HasNamedTechnology( MortarAmmoTechs[ m_iRoundType ] ) )
+ {
+ // Do we have ammo?
+ if ( m_iMortarRounds[ m_iRoundType ] > 0 )
+ break;
+ }
+ }
+
+ // Go to the next round type
+ m_iRoundType += 1;
+ }
+ }
+ else
+ {
+ // Let other team's technician try to subvert it
+ BaseClass::Use( pActivator, pCaller, useType, value );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Fire a round from the mortar
+//-----------------------------------------------------------------------------
+bool CObjectMortar::FireMortar( float flFiringPower, float flFiringAccuracy, bool bRangeUpgraded, bool bAccuracyUpgraded )
+{
+ // Are we reloading?
+ if ( IsReloading() )
+ return false;
+ // Do we have any ammo of this type left?
+ if ( m_iMortarRounds[ m_iRoundType ] != -1 && m_iMortarRounds[ m_iRoundType ] == 0 )
+ return false;
+
+ // Get target distance
+ float flDistance;
+ if ( bRangeUpgraded )
+ {
+ flDistance = MORTAR_RANGE_MIN + (flFiringPower * (MORTAR_RANGE_MAX_UPGRADED - MORTAR_RANGE_MIN));
+ }
+ else
+ {
+ flDistance = MORTAR_RANGE_MIN + (flFiringPower * (MORTAR_RANGE_MAX_INITIAL - MORTAR_RANGE_MIN));
+ }
+
+ // Factor in inaccuracy
+ float flInaccuracy;
+ if ( bAccuracyUpgraded )
+ {
+ flInaccuracy = MORTAR_INACCURACY_MAX_UPGRADED * (flFiringAccuracy * 4); // flFiringAccuracy is a range from -0.25 to 0.25
+ }
+ else
+ {
+ flInaccuracy = MORTAR_INACCURACY_MAX_INITIAL * (flFiringAccuracy * 4); // flFiringAccuracy is a range from -0.25 to 0.25
+ }
+ flDistance += (flDistance * MORTAR_DIST_INACCURACY) * random->RandomFloat( -flInaccuracy, flInaccuracy );
+
+ Vector forward, right;
+ AngleVectors( GetAbsAngles(), &forward, &right, NULL );
+ Vector vecTargetOrg = GetAbsOrigin() + (forward * flDistance);
+ // Add in sideways inaccuracy
+ vecTargetOrg += (right * (flDistance * flInaccuracy) );
+
+ // Trace down from the sky and find the point we're actually going to hit
+ trace_t tr;
+ Vector vecSky = vecTargetOrg + Vector(0,0,1024);
+ UTIL_TraceLine( vecSky, vecTargetOrg, MASK_ALL, this, COLLISION_GROUP_NONE, &tr );
+ vecTargetOrg = tr.endpos;
+
+ Vector vecMidPoint = vec3_origin;
+ // Start with a low arc, and keep aiming higher until we've got a roughly clear shot
+ for (int i = 2048; i <= 4096; i += 1024)
+ {
+ trace_t tr1;
+ trace_t tr2;
+
+ vecMidPoint = Vector(0,0,i) + GetAbsOrigin() + (vecTargetOrg - GetAbsOrigin()) * 0.5;
+ UTIL_TraceLine(GetAbsOrigin(), vecMidPoint, MASK_ALL, this, COLLISION_GROUP_NONE, &tr1);
+ UTIL_TraceLine(vecMidPoint, vecTargetOrg, MASK_ALL, this, COLLISION_GROUP_NONE, &tr2);
+
+ // Clear shot?
+ // We want a clear shot for the first half, and a fairly clear shot on the fall
+ if ( tr1.fraction == 1 && tr2.fraction > 0.5 )
+ break;
+ }
+
+ // How high should we travel to reach the apex
+ float distance1 = (vecMidPoint.z - GetAbsOrigin().z);
+ float distance2 = (vecMidPoint.z - vecTargetOrg.z);
+
+ // How long will it take to travel this distance
+ float flGravity = GetCurrentGravity();
+ float time1 = sqrt( distance1 / (0.5 * flGravity) );
+ float time2 = sqrt( distance2 / (0.5 * flGravity) );
+ if (time1 < 0.1)
+ return false;
+
+ // how hard to launch to get there in time.
+ Vector vecTargetVel = (vecTargetOrg - GetLocalOrigin()) / (time1 + time2);
+ vecTargetVel.z = flGravity * time1;
+
+ // Create the round
+ CMortarRound *pRound = CMortarRound::Create( GetLocalOrigin(), vecTargetVel, edict() );
+ pRound->ChangeTeam( GetTeamNumber() );
+ pRound->SetFallTime( time1 * 0.5 ); // Start a falling sound just a bit before we begin to fall
+ pRound->SetRoundType( m_iRoundType );
+
+ // Decrease ammo count
+ if ( m_iMortarRounds[ m_iRoundType ] > 0 )
+ {
+ m_iMortarRounds.Set( m_iRoundType, m_iMortarRounds[m_iRoundType]-1 );
+ }
+
+ // Decrease salvo count
+ if ( m_iSalvoLeft )
+ {
+ m_iSalvoLeft--;
+ if ( m_iSalvoLeft <= 0 )
+ {
+ // Time to reload
+ StartReloading();
+ }
+ }
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Start reloading our next salvo
+//-----------------------------------------------------------------------------
+void CObjectMortar::StartReloading( void )
+{
+ SetThink( ReloadingThink );
+ SetNextThink( gpGlobals->curtime + MORTAR_RELOAD_TIME );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Finish reloading our salvo
+//-----------------------------------------------------------------------------
+void CObjectMortar::ReloadingThink( void )
+{
+ SetThink( NULL );
+
+ m_iSalvoLeft = MORTAR_SALVO_SIZE;
+}
+
diff --git a/game/server/tf2/tf_obj_mortar.h b/game/server/tf2/tf_obj_mortar.h
new file mode 100644
index 0000000..da44da8
--- /dev/null
+++ b/game/server/tf2/tf_obj_mortar.h
@@ -0,0 +1,67 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef TF_OBJ_MORTAR_H
+#define TF_OBJ_MORTAR_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include "tf_obj.h"
+#include "utllinkedlist.h"
+
+#define MAX_DEPLOYED_MORTARS 1
+
+class CWeaponMortar;
+
+// ------------------------------------------------------------------------ //
+// Mortar object that's built by the player
+// ------------------------------------------------------------------------ //
+class CObjectMortar : public CBaseObject
+{
+ DECLARE_CLASS( CObjectMortar, CBaseObject );
+public:
+ static CObjectMortar* Create(const Vector &vOrigin, const QAngle &vAngles, int team);
+
+ DECLARE_SERVERCLASS();
+ DECLARE_DATADESC();
+
+ CObjectMortar();
+
+ virtual int ObjectCaps( void ) { return FCAP_IMPULSE_USE; };
+ virtual void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value );
+ virtual void Spawn();
+ virtual void Precache();
+ virtual void GetControlPanelInfo( int nPanelIndex, const char *&pPanelName );
+
+ // Firing called by the mortar "weapon"
+ bool FireMortar( float flFiringPower, float flFiringAccuracy, bool bRangeUpgraded, bool bAccuracyUpgraded );
+
+ // Salvo reloading
+ void StartReloading( void );
+ void ReloadingThink( void );
+ bool IsReloading( void ) { return (m_iSalvoLeft <= 0); };
+
+ float LastBlastTime() { return m_flLastBlastTime; }
+ void SetBlastTime( float time ) { m_flLastBlastTime = time; }
+
+ const Vector &LastBlastPosition() { return m_vLastBlastPos; }
+ void SetBlastPosition( const Vector &pos ) { m_vLastBlastPos = pos; }
+
+private:
+ CNetworkVar( int, m_iRoundType );
+ CNetworkArray( int, m_iMortarRounds, MA_LASTAMMOTYPE );
+
+ int m_iSalvoLeft; // Decremented every shot. Once depleted, the mortar must reload.
+
+ // Stored for the global mortar list for anti-mortar orders.
+ unsigned short m_MortarListIndex;
+ Vector m_vLastBlastPos;
+ double m_flLastBlastTime; // -1 if no shots have hit anything yet.
+};
+
+#endif // TF_OBJ_MORTAR_H
diff --git a/game/server/tf2/tf_obj_powerpack.cpp b/game/server/tf2/tf_obj_powerpack.cpp
new file mode 100644
index 0000000..5ab64d6
--- /dev/null
+++ b/game/server/tf2/tf_obj_powerpack.cpp
@@ -0,0 +1,366 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Human's power pack
+//
+// $NoKeywords: $
+//=============================================================================//
+#include "cbase.h"
+#include "tf_player.h"
+#include "tf_team.h"
+#include "tf_obj.h"
+#include "tf_obj_powerpack.h"
+#include "tf_func_resource.h"
+#include "resource_chunk.h"
+#include "techtree.h"
+#include "sendproxy.h"
+#include "vstdlib/random.h"
+#include "tf_stats.h"
+#include "rope.h"
+#include "tf_shareddefs.h"
+#include "VGuiScreen.h"
+#include "hierarchy.h"
+
+#define POWERPACK_MODEL "models/objects/human_obj_powerpack.mdl"
+#define POWERPACK_ASSEMBLING_MODEL "models/objects/human_obj_powerpack_build.mdl"
+
+IMPLEMENT_SERVERCLASS_ST( CObjectPowerPack, DT_ObjectPowerPack )
+ SendPropInt( SENDINFO(m_iObjectsAttached), 3, SPROP_UNSIGNED ),
+END_SEND_TABLE();
+
+LINK_ENTITY_TO_CLASS(obj_powerpack, CObjectPowerPack);
+PRECACHE_REGISTER(obj_powerpack);
+
+ConVar obj_powerpack_health( "obj_powerpack_health","100", FCVAR_NONE, "Human powerpack health" );
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CObjectPowerPack::CObjectPowerPack()
+{
+ UseClientSideAnimation();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CObjectPowerPack::Spawn( void )
+{
+ SetModel( POWERPACK_MODEL );
+ SetSolid( SOLID_BBOX );
+ UTIL_SetSize(this, POWERPACK_MINS, POWERPACK_MAXS);
+
+ m_iHealth = obj_powerpack_health.GetInt();
+ m_fObjectFlags |= OF_DOESNT_NEED_POWER;
+ SetType( OBJ_POWERPACK );
+ m_hPoweredObjects.Purge();
+ m_iFreeAttachments = 0;
+ m_iObjectsAttached = 0;
+
+ BaseClass::Spawn();
+}
+
+
+//-----------------------------------------------------------------------------
+// Gets info about the control panels
+//-----------------------------------------------------------------------------
+void CObjectPowerPack::GetControlPanelInfo( int nPanelIndex, const char *&pPanelName )
+{
+ pPanelName = "screen_obj_power_pack";
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CObjectPowerPack::FinishedBuilding( void )
+{
+ BaseClass::FinishedBuilding();
+
+ // Now tell all our objects we connected to, during placement, that they're really getting power
+ // Walk backwards, because we might remove objects from our list that have somehow gained power
+ // inbetween the time we placed and the time we finished building.
+ int iSize = m_hPoweredObjects.Count();
+ for (int i = iSize-1; i >= 0; i--)
+ {
+ if ( m_hPoweredObjects[i] )
+ {
+ if ( m_hPoweredObjects[i]->IsPowered() )
+ {
+ UnPowerObject( m_hPoweredObjects[i] );
+ }
+ else
+ {
+ m_hPoweredObjects[i]->SetPowerPack( this );
+ }
+ }
+ }
+
+ PowerNearbyObjects();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CObjectPowerPack::Precache()
+{
+ BaseClass::Precache();
+ PrecacheModel( POWERPACK_MODEL );
+ PrecacheModel( POWERPACK_ASSEMBLING_MODEL );
+ PrecacheVGuiScreen( "screen_obj_power_pack" );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CObjectPowerPack::DestroyObject( void )
+{
+ // Remove power from all my objects (backwards because list will change)
+ int iSize = m_hPoweredObjects.Count();
+ for (int i = iSize-1; i >= 0; i--)
+ {
+ if ( m_hPoweredObjects[i] )
+ {
+ UnPowerObject( m_hPoweredObjects[i] );
+ }
+ }
+
+ // Now tell all other powerpacks on this team to power nearby objects, in case they can cover for this one.
+ if ( GetTFTeam() )
+ {
+ GetTFTeam()->UpdatePowerpacks( this, NULL );
+ }
+
+ BaseClass::DestroyObject();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Update power connections on the fly while placing
+//-----------------------------------------------------------------------------
+bool CObjectPowerPack::CalculatePlacement( CBaseTFPlayer *pPlayer )
+{
+ bool bReturn = BaseClass::CalculatePlacement( pPlayer );
+
+ // First, disconnect any connections that should break
+ int iSize = m_hPoweredObjects.Count();
+ for (int i = iSize-1; i >= 0; i--)
+ {
+ if ( m_hPoweredObjects[i] )
+ {
+ EnsureObjectPower( m_hPoweredObjects[i] );
+ }
+ }
+
+ // If we have any spare connections, look for nearby objects to power
+ if ( m_hPoweredObjects.Count() < MAX_OBJECTS_PER_PACK )
+ {
+ PowerNearbyObjects( NULL, true );
+ }
+
+ return bReturn;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Find nearby objects and provide them with power
+//-----------------------------------------------------------------------------
+void CObjectPowerPack::PowerNearbyObjects( CBaseObject *pObjectToTarget, bool bPlacing )
+{
+ if ( !GetTFTeam() )
+ return;
+ // Am I ready to power anything?
+ if ( IsBuilding() || (!bPlacing && IsPlacing()) )
+ return;
+
+ // Am I already full?
+ if ( m_hPoweredObjects.Count() >= MAX_OBJECTS_PER_PACK )
+ return;
+
+ // Do we have a specific target?
+ if ( pObjectToTarget )
+ {
+ if ( !pObjectToTarget->CanPowerupNow(POWERUP_POWER) )
+ return;
+
+ if ( IsWithinPowerRange( pObjectToTarget ) )
+ {
+ PowerObject( pObjectToTarget );
+ }
+ }
+ else
+ {
+ // Find nearby objects
+ for ( int i = 0; i < GetTFTeam()->GetNumObjects(); i++ )
+ {
+ CBaseObject *pObject = GetTFTeam()->GetObject(i);
+ assert(pObject);
+ if ( pObject == this || !pObject->CanPowerupNow(POWERUP_POWER) )
+ continue;
+ // We might be rechecking our power because one of our own objects is dying.
+ // Make sure we don't re-attach the sucker.
+ if ( pObject->IsDying() )
+ continue;
+
+ // Make sure it's within range
+ if ( IsWithinPowerRange( pObject ) )
+ {
+ PowerObject( pObject, bPlacing );
+ }
+
+ // Am I now full?
+ if ( m_hPoweredObjects.Count() >= MAX_OBJECTS_PER_PACK )
+ break;
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Provide power to the specified object
+//-----------------------------------------------------------------------------
+void CObjectPowerPack::PowerObject( CBaseObject *pObject, bool bPlacing )
+{
+ // Make sure we're not already powering it
+ ObjectHandle hObject;
+ hObject = pObject;
+ if ( m_hPoweredObjects.Find( hObject ) != m_hPoweredObjects.InvalidIndex() )
+ return;
+
+ // Add it to our list
+ m_hPoweredObjects.AddToTail( hObject );
+ m_iObjectsAttached = m_hPoweredObjects.Count();
+
+ // Find a free attachment point
+ int iPoint = 1;
+ for ( int i = 0; i < MAX_OBJECTS_PER_PACK; i++ )
+ {
+ if ( !(m_iFreeAttachments & (1<<i)) )
+ {
+ m_iFreeAttachments |= (1<<i);
+ iPoint = i+1;
+ break;
+ }
+ }
+
+ // Lookup the attachment point...
+ int nAttachmentIndex = pObject->LookupAttachment("powerpoint");
+ if (nAttachmentIndex < 0)
+ nAttachmentIndex = 1;
+
+ // FIXME: Cache these off
+ char sAttachment[32];
+ Q_snprintf( sAttachment,sizeof(sAttachment), "cablepoint%d", iPoint );
+ int nLocalAttachment = LookupAttachment( sAttachment );
+ if ( nLocalAttachment > 0 )
+ {
+ // Throw a cable to it
+ CRopeKeyframe *pRope = ConnectCableTo( pObject, nLocalAttachment, nAttachmentIndex );
+ if ( pRope )
+ {
+ pRope->SetMaterial( "cable/human_powercable.vmt" );
+ }
+ }
+
+ // If we're placing only, don't tell it we're supplying power yet
+ if ( IsPlacing() )
+ return;
+
+ pObject->SetPowerPack( this );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Remove power to the specified object
+//-----------------------------------------------------------------------------
+void CObjectPowerPack::UnPowerObject( CBaseObject *pObject )
+{
+ // Make sure it's in our list
+ ObjectHandle hObject;
+ hObject = pObject;
+ if ( m_hPoweredObjects.Find( hObject ) == m_hPoweredObjects.InvalidIndex() )
+ return;
+
+ // Remove it from our list
+ m_hPoweredObjects.FindAndRemove( hObject );
+ m_iObjectsAttached = m_hPoweredObjects.Count();
+
+ // Remove our cable to it
+ for ( int i = 0; i < m_aRopes.Count(); i++ )
+ {
+ if ( (m_aRopes[i] != NULL) && (m_aRopes[i]->GetEndPoint() == pObject) )
+ {
+ // Free up the attachment point
+ m_iFreeAttachments &= ~(1 << (m_aRopes[i]->GetEndAttachment()-1));
+ UTIL_Remove( m_aRopes[i] );
+ m_aRopes.Remove(i);
+ break;
+ }
+ }
+
+ // Tell the object that it has lost power
+ if ( pObject->GetPowerPack() == this )
+ {
+ pObject->SetPowerPack( NULL );
+ }
+
+ // If I'm not dying, immediately look for other things to power
+ if ( !IsDying() )
+ {
+ PowerNearbyObjects();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Make sure the specified object is still within powering range
+//-----------------------------------------------------------------------------
+void CObjectPowerPack::EnsureObjectPower( CBaseObject *pObject )
+{
+ if ( IsWithinPowerRange( pObject ) )
+ return;
+
+ // It's obscured, or out of range. Remove it.
+ UnPowerObject( pObject );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Return true if this object is powerable
+//-----------------------------------------------------------------------------
+bool CObjectPowerPack::IsWithinPowerRange( CBaseObject *pObject )
+{
+ // If this powerpack is built on an attachment, it'll only power objects in the same hierarchy
+ if ( GetParentObject() )
+ {
+ if ( GetRootMoveParent() != pObject->GetRootMoveParent() )
+ return false;
+ }
+
+ if ( (pObject->GetAbsOrigin() - GetAbsOrigin()).LengthSqr() < POWERPACK_RANGE )
+ {
+ // Can I see it?
+ // Ignore things we're attached to
+ trace_t tr;
+ CTraceFilterWorldAndPropsOnly powerFilter;
+ UTIL_TraceLine( WorldSpaceCenter(), pObject->WorldSpaceCenter(), MASK_SOLID_BRUSHONLY, &powerFilter, &tr );
+ CBaseEntity *pEntity = tr.m_pEnt;
+ if ( (tr.fraction == 1.0) || ( pEntity == pObject ) )
+ return true;
+ }
+
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : act -
+//-----------------------------------------------------------------------------
+void CObjectPowerPack::OnActivityChanged( Activity act )
+{
+ BaseClass::OnActivityChanged( act );
+
+ switch ( act )
+ {
+ case ACT_OBJ_ASSEMBLING:
+ SetModel( POWERPACK_ASSEMBLING_MODEL );
+ break;
+ default:
+ SetModel( POWERPACK_MODEL );
+ break;
+ }
+}
diff --git a/game/server/tf2/tf_obj_powerpack.h b/game/server/tf2/tf_obj_powerpack.h
new file mode 100644
index 0000000..4d78b27
--- /dev/null
+++ b/game/server/tf2/tf_obj_powerpack.h
@@ -0,0 +1,64 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Human's power pack
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef TF_OBJ_POWERPACK_H
+#define TF_OBJ_POWERPACK_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include "tf_obj.h"
+
+// ------------------------------------------------------------------------ //
+// Pack defines
+#define POWERPACK_MINS Vector(-20, -20, 0)
+#define POWERPACK_MAXS Vector( 20, 20, 80)
+#define POWERPACK_RANGE (600 * 600)
+
+// ------------------------------------------------------------------------ //
+// Resupply object that's built by the player
+// ------------------------------------------------------------------------ //
+class CObjectPowerPack : public CBaseObject
+{
+ DECLARE_CLASS( CObjectPowerPack, CBaseObject );
+public:
+ DECLARE_SERVERCLASS();
+
+ CObjectPowerPack();
+ static CObjectPowerPack *Create(const Vector &vOrigin, const QAngle &vAngles);
+
+ virtual void Spawn();
+ virtual void FinishedBuilding( void );
+ virtual void Precache();
+ virtual bool CanTakeEMPDamage( void ) { return true; }
+ virtual void DestroyObject( void );
+ virtual bool CalculatePlacement( CBaseTFPlayer *pPlayer );
+
+ // This is called by the base object when it's time to spawn the control panels
+ virtual void GetControlPanelInfo( int nPanelIndex, const char *&pPanelName );
+
+ // Find nearby objects and provide them with power
+ void PowerNearbyObjects( CBaseObject *pObjectToTarget = NULL, bool bPlacing = false );
+ void UnPowerAllObjects( void );
+ void UnPowerObject( CBaseObject *pObject );
+ void EnsureObjectPower( CBaseObject *pObject );
+
+ // Powerpack switches models after assembly
+ virtual void OnActivityChanged( Activity act );
+
+private:
+ void PowerObject( CBaseObject *pObject, bool bPlacing = false );
+ bool IsWithinPowerRange( CBaseObject *pObject );
+
+ // Objects powered from this pack
+ typedef CHandle<CBaseObject> ObjectHandle;
+ CUtlVector< ObjectHandle > m_hPoweredObjects;
+ int m_iFreeAttachments;
+ CNetworkVar( int, m_iObjectsAttached );
+};
+
+#endif // TF_OBJ_POWERPACK_H
diff --git a/game/server/tf2/tf_obj_rallyflag.cpp b/game/server/tf2/tf_obj_rallyflag.cpp
new file mode 100644
index 0000000..8cc9bf9
--- /dev/null
+++ b/game/server/tf2/tf_obj_rallyflag.cpp
@@ -0,0 +1,108 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+#include "cbase.h"
+#include "tf_player.h"
+#include "tf_team.h"
+#include "tf_gamerules.h"
+#include "tf_obj.h"
+#include "tf_obj_rallyflag.h"
+#include "ndebugoverlay.h"
+
+BEGIN_DATADESC( CObjectRallyFlag )
+
+ DEFINE_THINKFUNC( RallyThink ),
+
+END_DATADESC()
+
+IMPLEMENT_SERVERCLASS_ST(CObjectRallyFlag, DT_ObjectRallyFlag)
+END_SEND_TABLE();
+
+LINK_ENTITY_TO_CLASS(obj_rallyflag, CObjectRallyFlag);
+PRECACHE_REGISTER(obj_rallyflag);
+
+ConVar obj_rallyflag_health( "obj_rallyflag_health","100", FCVAR_NONE, "Rally Flag health" );
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CObjectRallyFlag::CObjectRallyFlag()
+{
+ UseClientSideAnimation();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CObjectRallyFlag::Spawn()
+{
+ Precache();
+ SetModel( RALLYFLAG_MODEL );
+ SetSolid( SOLID_BBOX );
+ SetCollisionGroup( TFCOLLISION_GROUP_COMBATOBJECT );
+
+ UTIL_SetSize(this, RALLYFLAG_MINS, RALLYFLAG_MAXS);
+ m_takedamage = DAMAGE_YES;
+ m_iHealth = obj_rallyflag_health.GetInt();
+
+ SetThink( RallyThink );
+ SetNextThink( gpGlobals->curtime + 0.1f );
+ m_flExpiresAt = gpGlobals->curtime + RALLYFLAG_LIFETIME;
+
+ SetType( OBJ_RALLYFLAG );
+ m_fObjectFlags |= OF_SUPPRESS_NOTIFY_UNDER_ATTACK | OF_SUPPRESS_TECH_ANALYZER |
+ OF_DONT_AUTO_REPAIR | OF_DONT_PREVENT_BUILD_NEAR_OBJ | OF_DOESNT_NEED_POWER;
+
+ BaseClass::Spawn();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CObjectRallyFlag::Precache()
+{
+ PrecacheModel( RALLYFLAG_MODEL );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Look for friendlies to rally
+//-----------------------------------------------------------------------------
+void CObjectRallyFlag::RallyThink( void )
+{
+ if ( !GetTeam() )
+ return;
+
+ // Time to die?
+ if ( gpGlobals->curtime > m_flExpiresAt )
+ {
+ UTIL_Remove( this );
+ return;
+ }
+
+ // Look for nearby players to rally
+ for ( int i = 0; i < GetTFTeam()->GetNumPlayers(); i++ )
+ {
+ CBaseTFPlayer *pPlayer = (CBaseTFPlayer *)GetTFTeam()->GetPlayer(i);
+ assert(pPlayer);
+
+ // Is it within range?
+ if ( ((pPlayer->GetAbsOrigin() - GetAbsOrigin()).Length() < RALLYFLAG_RADIUS ) && pPlayer->IsAlive() )
+ {
+ // Can I see it?
+ trace_t tr;
+ UTIL_TraceLine( EyePosition(), pPlayer->EyePosition(), MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr);
+ CBaseEntity *pEntity = tr.m_pEnt;
+ if ( (tr.fraction == 1.0) || ( pEntity == pPlayer ) )
+ {
+ pPlayer->AttemptToPowerup( POWERUP_RUSH, RALLYFLAG_ADRENALIN_TIME );
+ }
+ }
+ }
+
+ SetNextThink( gpGlobals->curtime + RALLYFLAG_RATE );
+}
+
diff --git a/game/server/tf2/tf_obj_rallyflag.h b/game/server/tf2/tf_obj_rallyflag.h
new file mode 100644
index 0000000..cd86e77
--- /dev/null
+++ b/game/server/tf2/tf_obj_rallyflag.h
@@ -0,0 +1,40 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef TF_OBJ_RALLYFLAG_H
+#define TF_OBJ_RALLYFLAG_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+// ------------------------------------------------------------------------ //
+// Rally flag that's built by players
+// ------------------------------------------------------------------------ //
+class CObjectRallyFlag : public CBaseObject
+{
+DECLARE_CLASS( CObjectRallyFlag, CBaseObject );
+
+public:
+ DECLARE_DATADESC();
+ DECLARE_SERVERCLASS();
+
+ static CObjectRallyFlag* Create(const Vector &vOrigin, const QAngle &vAngles);
+
+ CObjectRallyFlag();
+
+ virtual void Spawn();
+ virtual void Precache();
+ virtual bool CanTakeEMPDamage( void ) { return true; }
+
+ // Rally
+ virtual void RallyThink( void );
+
+private:
+ float m_flExpiresAt;
+};
+
+#endif // TF_OBJ_RALLYFLAG_H
diff --git a/game/server/tf2/tf_obj_resourcepump.cpp b/game/server/tf2/tf_obj_resourcepump.cpp
new file mode 100644
index 0000000..390bdfd
--- /dev/null
+++ b/game/server/tf2/tf_obj_resourcepump.cpp
@@ -0,0 +1,260 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Resource pump
+//
+// $NoKeywords: $
+//=============================================================================//
+#include "cbase.h"
+#include "tf_player.h"
+#include "tf_team.h"
+#include "tf_obj.h"
+#include "tf_obj_resourcepump.h"
+#include "tf_func_resource.h"
+#include "resource_chunk.h"
+#include "techtree.h"
+#include "sendproxy.h"
+#include "vstdlib/random.h"
+#include "tf_stats.h"
+#include "VGuiScreen.h"
+#include "engine/IEngineSound.h"
+
+BEGIN_DATADESC( CObjectResourcePump )
+
+ DEFINE_THINKFUNC( ResourcePumpThink ),
+
+END_DATADESC()
+
+
+// Resource pump team-only vars.
+BEGIN_SEND_TABLE_NOBASE( CObjectResourcePump, DT_ResourcePumpTeamOnlyVars )
+ SendPropInt( SENDINFO(m_iPumpLevel), 4 ),
+ SendPropEHandle( SENDINFO(m_hResourceZone) ),
+END_SEND_TABLE()
+
+
+IMPLEMENT_SERVERCLASS_ST(CObjectResourcePump, DT_ResourcePump)
+ SendPropDataTable( "teamonly", 0, &REFERENCE_SEND_TABLE( DT_ResourcePumpTeamOnlyVars ), SendProxy_OnlyToTeam ),
+END_SEND_TABLE()
+
+LINK_ENTITY_TO_CLASS(obj_resourcepump, CObjectResourcePump);
+PRECACHE_REGISTER(obj_resourcepump);
+
+ConVar obj_resourcepump_health( "obj_resourcepump_health","100", FCVAR_NONE, "Resource pump health" );
+ConVar obj_resourcepump_rate( "obj_resourcepump_rate","0", FCVAR_NONE, "Base rate at which resource pumps give resources to their team." );
+ConVar obj_resourcepump_amount( "obj_resourcepump_amount","0", FCVAR_NONE, "Base amount of resources that resource pumps give to their team." );
+
+#define RESOURCE_PUMP_CONTEXT "ResourcePumpThink"
+
+#define HUMAN_RESOURCEPUMP_MODEL "models/objects/obj_resourcepump.mdl"
+#define ALIEN_RESOURCEPUMP_MODEL "models/objects/alien_obj_resourcepump.mdl"
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CObjectResourcePump::CObjectResourcePump()
+{
+ UseClientSideAnimation();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CObjectResourcePump::SetupTeamModel( void )
+{
+ if ( GetTeamNumber() == TEAM_HUMANS )
+ {
+ SetModel( HUMAN_RESOURCEPUMP_MODEL );
+ }
+ else
+ {
+ SetModel( ALIEN_RESOURCEPUMP_MODEL );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CObjectResourcePump::Spawn( void )
+{
+ SetSolid( SOLID_BBOX );
+ UTIL_SetSize(this, RESOURCEPUMP_MINS, RESOURCEPUMP_MAXS);
+
+ m_iHealth = obj_resourcepump_health.GetInt();
+
+ m_hResourceZone = NULL;
+ m_flPumpSpeed = obj_resourcepump_rate.GetFloat();
+ m_fObjectFlags |= OF_DONT_PREVENT_BUILD_NEAR_OBJ | OF_DOESNT_NEED_POWER | OF_MUST_BE_BUILT_IN_RESOURCE_ZONE | OF_MUST_BE_BUILT_ON_ATTACHMENT;
+ m_iPumpLevel = 1;
+
+ SetType( OBJ_RESOURCEPUMP );
+
+ BaseClass::Spawn();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CObjectResourcePump::Activate( void )
+{
+ BaseClass::Activate();
+
+ SetupPump();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Gets info about the control panels
+//-----------------------------------------------------------------------------
+void CObjectResourcePump::GetControlPanelInfo( int nPanelIndex, const char *&pPanelName )
+{
+ pPanelName = "screen_obj_resourcepump";
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CObjectResourcePump::FinishedBuilding( void )
+{
+ BaseClass::FinishedBuilding();
+
+ SetupPump();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CObjectResourcePump::SetupPump( void )
+{
+ // Find the resource zone I've been planted in
+ m_hResourceZone = NULL;
+ CBaseEntity *pEntity = NULL;
+ while ((pEntity = gEntList.FindEntityByClassname( pEntity, "trigger_resourcezone" )) != NULL)
+ {
+ CResourceZone *pZone = (CResourceZone *)pEntity;
+
+ // Are we within this zone?
+ if ( pZone->CollisionProp()->IsPointInBounds( GetAbsOrigin() ) )
+ {
+ m_hResourceZone = pZone;
+ break;
+ }
+ }
+
+ if ( m_hResourceZone == NULL )
+ {
+ Msg( "Resource Pump (entindex %d) at (%.2f, %.2f, %.2f) can't find it's zone.\n",
+ entindex(), GetAbsOrigin().x, GetAbsOrigin().y, GetAbsOrigin().z );
+ UTIL_Remove( this );
+ return;
+ }
+
+ // Tell the zone
+ if ( m_hResourceZone->GetNumBuildPoints() )
+ {
+ m_hResourceZone->SetObjectOnBuildPoint( 0, this );
+ }
+
+ SetContextThink( ResourcePumpThink, gpGlobals->curtime + m_flPumpSpeed, RESOURCE_PUMP_CONTEXT );
+
+ // Start the pump animation
+ ResetSequence( SelectWeightedSequence( ACT_IDLE ) );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CObjectResourcePump::Precache()
+{
+ PrecacheModel( HUMAN_RESOURCEPUMP_MODEL );
+ PrecacheModel( ALIEN_RESOURCEPUMP_MODEL );
+ PrecacheVGuiScreen( "screen_obj_resourcepump" );
+
+ PrecacheScriptSound( "ObjectResourcePump.UpgradeFailed" );
+
+}
+
+//-----------------------------------------------------------------------------
+// Gets the resource zone (may be NULL!)
+//-----------------------------------------------------------------------------
+CResourceZone* CObjectResourcePump::GetResourceZone()
+{
+ return m_hResourceZone;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Pump resources out of the zone I'm sitting on
+//-----------------------------------------------------------------------------
+void CObjectResourcePump::ResourcePumpThink( void )
+{
+ Assert( m_hResourceZone != NULL );
+ if ( !GetTeam() )
+ return;
+
+ float flSpeed = m_hResourceZone->GetResourceRate() ? m_hResourceZone->GetResourceRate() : m_flPumpSpeed;
+
+ SetNextThink( gpGlobals->curtime + flSpeed, RESOURCE_PUMP_CONTEXT );
+
+ // I can't do anything if I'm not active
+ if ( !ShouldBeActive() )
+ return;
+
+ // If the zone's not active, don't do anything
+ if ( !m_hResourceZone->GetActive() )
+ return;
+
+ // Suck resources from the zone I'm in
+ int iResourcesPerPlayer = 0;
+ for ( int i = 0; i < m_iPumpLevel; i++ )
+ {
+ // Decreasing value for each level
+ float flLevelBonus = 1.0 - (i / (float)GetObjectInfo( GetType() )->m_MaxUpgradeLevel);
+ iResourcesPerPlayer += (obj_resourcepump_amount.GetFloat() * flLevelBonus);
+ }
+ int iPumpedResources = MIN( m_hResourceZone->GetResources(), iResourcesPerPlayer );
+ if ( iPumpedResources )
+ {
+ m_hResourceZone->RemoveResources( iPumpedResources );
+
+ // Give out resources to the team
+ GetTFTeam()->AddTeamResources( iPumpedResources * GetTFTeam()->GetNumPlayers() );
+ TFStats()->IncrementTeamStat( GetTFTeam()->GetTeamNumber(), TF_TEAM_STAT_RESOURCES_HARVESTED, iPumpedResources );
+ }
+
+ // If we've just run out of resources in the zone, shut down
+ if ( m_hResourceZone->GetResources() <= 0 )
+ {
+ UTIL_Remove( this );
+ return;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Handle commands sent from vgui panels on the client
+//-----------------------------------------------------------------------------
+bool CObjectResourcePump::ClientCommand( CBaseTFPlayer *pPlayer, const char *pCmd, ICommandArguments *pArg )
+{
+ if ( FStrEq( pCmd, "upgrade" ) )
+ {
+ int iCost = CalculateObjectUpgrade( GetType(), m_iPumpLevel );
+
+ // Do we have enough resources to activate it?
+ if ( !iCost || pPlayer->GetBankResources() < iCost )
+ {
+ // Play a sound indicating it didn't work...
+ CSingleUserRecipientFilter filter( pPlayer );
+ EmitSound( filter, pPlayer->entindex(), "ObjectResourcePump.UpgradeFailed" );
+ return true;
+ }
+
+ pPlayer->RemoveBankResources( iCost );
+
+ // Upgrade myself
+ m_iPumpLevel += 1;
+ return true;
+ }
+
+ return BaseClass::ClientCommand( pPlayer, pCmd, pArg );
+}
diff --git a/game/server/tf2/tf_obj_resourcepump.h b/game/server/tf2/tf_obj_resourcepump.h
new file mode 100644
index 0000000..83229fc
--- /dev/null
+++ b/game/server/tf2/tf_obj_resourcepump.h
@@ -0,0 +1,60 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Resource pump
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef TF_OBJ_RESOURCEPUMP_H
+#define TF_OBJ_RESOURCEPUMP_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+
+#include "tf_obj.h"
+
+
+class CResourceZone;
+
+// ------------------------------------------------------------------------ //
+// Pump defines
+#define RESOURCEPUMP_MINS Vector(-20, -20, 0)
+#define RESOURCEPUMP_MAXS Vector( 20, 20, 140)
+
+// ------------------------------------------------------------------------ //
+// Resupply object that's built by the player
+// ------------------------------------------------------------------------ //
+class CObjectResourcePump : public CBaseObject
+{
+DECLARE_CLASS( CObjectResourcePump, CBaseObject );
+
+public:
+ DECLARE_DATADESC();
+ DECLARE_SERVERCLASS();
+
+ static CObjectResourcePump* Create(const Vector &vOrigin, const QAngle &vAngles);
+
+ CObjectResourcePump();
+
+ virtual void Spawn();
+ virtual void Activate( void );
+ virtual void GetControlPanelInfo( int nPanelIndex, const char *&pPanelName );
+ virtual void FinishedBuilding( void );
+ void SetupPump( void );
+ virtual void Precache();
+ virtual bool CanTakeEMPDamage( void ) { return true; }
+
+ virtual void ResourcePumpThink( void );
+ virtual bool ClientCommand( CBaseTFPlayer *pPlayer, const CCommand &args );
+ virtual void SetupTeamModel( void );
+
+ // Gets the resource zone (may be NULL!)
+ CResourceZone* GetResourceZone();
+
+private:
+ float m_flPumpSpeed;
+ CNetworkVar( int, m_iPumpLevel );
+};
+
+#endif // TF_OBJ_RESOURCEPUMP_H
diff --git a/game/server/tf2/tf_obj_respawn_station.cpp b/game/server/tf2/tf_obj_respawn_station.cpp
new file mode 100644
index 0000000..2888679
--- /dev/null
+++ b/game/server/tf2/tf_obj_respawn_station.cpp
@@ -0,0 +1,167 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Medic's resupply beacon
+//
+// $NoKeywords: $
+//=============================================================================//
+#include "cbase.h"
+#include "tf_obj.h"
+#include "tf_player.h"
+#include "tf_team.h"
+#include "techtree.h"
+#include "tf_shield.h"
+#include "tf_obj_respawn_station.h"
+
+IMPLEMENT_SERVERCLASS_ST(CObjectRespawnStation, DT_ObjectRespawnStation)
+END_SEND_TABLE();
+
+BEGIN_DATADESC( CObjectRespawnStation )
+
+ // keys
+ DEFINE_KEYFIELD_NOT_SAVED( m_bIsInitialSpawnPoint, FIELD_BOOLEAN, "InitialSpawn" ),
+
+END_DATADESC()
+
+LINK_ENTITY_TO_CLASS(obj_respawn_station, CObjectRespawnStation);
+PRECACHE_REGISTER(obj_respawn_station);
+
+ConVar obj_respawnstation_health( "obj_respawnstation_health","300", FCVAR_NONE, "Respawn Station health" );
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CObjectRespawnStation::CObjectRespawnStation()
+{
+ m_bIsInitialSpawnPoint = false;
+ UseClientSideAnimation();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CObjectRespawnStation::Precache()
+{
+ PrecacheModel( "models/objects/obj_respawn_station.mdl" );
+ m_iSpriteTexture = PrecacheModel( "sprites/laserbeam.vmt" );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CObjectRespawnStation::Spawn()
+{
+ Precache();
+ SetMoveType( MOVETYPE_NONE );
+ SetSolid( SOLID_BBOX );
+
+ SetModel( "models/objects/obj_respawn_station.mdl" );
+
+ UTIL_SetSize(this, RESPAWN_STATION_MINS, RESPAWN_STATION_MAXS);
+
+ m_iHealth = m_iMaxHealth = obj_respawnstation_health.GetInt();
+ m_takedamage = DAMAGE_YES;
+ m_fLastRespawnTime = -99999;
+
+ SetType( OBJ_RESPAWN_STATION );
+
+ BaseClass::Spawn();
+}
+
+
+//-----------------------------------------------------------------------------
+// Object using!
+//-----------------------------------------------------------------------------
+void CObjectRespawnStation::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
+{
+ // Sapper removal
+ if ( RemoveEnemyAttachments( pActivator ) )
+ return;
+
+ // See if the activator is a player
+ if ( pActivator->IsPlayer() )
+ {
+ CBaseTFPlayer *player = static_cast< CBaseTFPlayer * >( pActivator );
+ player->SetRespawnStation( this );
+ }
+
+ BaseClass::Use( pActivator, pCaller, useType, value );
+}
+
+
+
+//-----------------------------------------------------------------------------
+// Gets called when someone respawns on this station
+//-----------------------------------------------------------------------------
+void CObjectRespawnStation::PerformRespawnEffect()
+{
+ if (gpGlobals->curtime - m_fLastRespawnTime > RESPAWN_EFFECT_TIME)
+ {
+ Vector vecEnd;
+ VectorAdd( GetAbsOrigin(), Vector( 0, 0, RESPAWN_BEAM_HEIGHT ), vecEnd );
+
+ CBroadcastRecipientFilter filter;
+ te->BeamPoints( filter, 0.0,
+ &GetAbsOrigin(),
+ &vecEnd,
+ m_iSpriteTexture,
+ 0, // Halo index
+ 0, // Start frame
+ 15, // Frame rate
+ 3.0, // Life
+ 50, // Width
+ 50, // EndWidth
+ 0, // FadeLength
+ 0, // Amplitude
+ 100, // r
+ 200, // g
+ 255, // b
+ 255, // a
+ 20 ); // speed
+
+ m_fLastRespawnTime = gpGlobals->curtime;
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CObjectRespawnStation* CObjectRespawnStation::Create(const Vector &vOrigin, const QAngle &vAngles )
+{
+ CObjectRespawnStation *pRet = (CObjectRespawnStation*)CreateEntityByName("obj_respawn_station");
+ if(pRet)
+ {
+ pRet->SetLocalOrigin( vOrigin );
+ pRet->SetLocalAngles( vAngles );
+ pRet->Spawn();
+ }
+
+ return pRet;
+}
+
+
+//-----------------------------------------------------------------------------
+// Plays a respawn effect on a respawn station...
+//-----------------------------------------------------------------------------
+void PlayRespawnEffect(CBaseEntity *pRespawnStation)
+{
+ // ROBIN: Removed this for now
+ return;
+
+ // Check last respawn time; wait a couple seconds
+ if (!FClassnameIs(pRespawnStation, "obj_respawn_station"))
+ return;
+
+ CObjectRespawnStation* pStation = static_cast<CObjectRespawnStation*>(pRespawnStation);
+ pStation->PerformRespawnEffect();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Returns true if this spawnpoint is the map specified initial spawnpoint for its team
+//-----------------------------------------------------------------------------
+bool CObjectRespawnStation::IsInitialSpawnPoint( void )
+{
+ return m_bIsInitialSpawnPoint;
+}
+
+
diff --git a/game/server/tf2/tf_obj_respawn_station.h b/game/server/tf2/tf_obj_respawn_station.h
new file mode 100644
index 0000000..6290f17
--- /dev/null
+++ b/game/server/tf2/tf_obj_respawn_station.h
@@ -0,0 +1,70 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Portable respawn station
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef TF_OBJ_RESPAWN_STATION_H
+#define TF_OBJ_RESPAWN_STATION_H
+
+//-----------------------------------------------------------------------------
+// forward declarations
+//-----------------------------------------------------------------------------
+class CBaseEntity;
+
+//-----------------------------------------------------------------------------
+// Respawn station defines
+//-----------------------------------------------------------------------------
+#define RESPAWN_STATION_MINS Vector(-60, -45, 0)
+#define RESPAWN_STATION_MAXS Vector( 60, 45, 140)
+
+#define RESPAWN_STATION_BUILD_MINS Vector(-60, -45, 0)
+#define RESPAWN_STATION_BUILD_MAXS Vector( 60, 40, 140)
+
+#define RESPAWN_EFFECT_TIME 5.0f
+#define RESPAWN_BEAM_HEIGHT 800.0f
+
+//-----------------------------------------------------------------------------
+// Portable respawn station
+//-----------------------------------------------------------------------------
+class CObjectRespawnStation : public CBaseObject
+{
+DECLARE_CLASS( CObjectRespawnStation, CBaseObject );
+
+public:
+ DECLARE_SERVERCLASS();
+ DECLARE_DATADESC();
+
+ CObjectRespawnStation();
+
+ // Gets called when someone respawns on this station
+ void PerformRespawnEffect();
+
+ static CObjectRespawnStation* Create(const Vector &vOrigin, const QAngle &vAngles);
+
+ virtual void Precache();
+ virtual void Spawn();
+
+ virtual bool WantsCover() { return true; }
+
+ // Object using!
+ virtual void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value );
+
+ virtual bool CanTakeEMPDamage( void ) { return false; }
+
+ // Map specified as the initial spawnpoint for a team
+ bool IsInitialSpawnPoint( void );
+
+protected:
+ float m_fLastRespawnTime;
+ int m_iSpriteTexture;
+ bool m_bIsInitialSpawnPoint;
+};
+
+//-----------------------------------------------------------------------------
+// Plays a respawn effect on a respawn station...
+//-----------------------------------------------------------------------------
+void PlayRespawnEffect(CBaseEntity *pRespawnStation);
+
+#endif // TF_OBJ_RESPAWN_STATION_H
diff --git a/game/server/tf2/tf_obj_resupply.cpp b/game/server/tf2/tf_obj_resupply.cpp
new file mode 100644
index 0000000..31fbbb2
--- /dev/null
+++ b/game/server/tf2/tf_obj_resupply.cpp
@@ -0,0 +1,383 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Medic's resupply beacon
+//
+// $NoKeywords: $
+//=============================================================================//
+#include "cbase.h"
+
+#include "tf_obj_resupply.h"
+#include "engine/IEngineSound.h"
+#include "tf_player.h"
+#include "tf_team.h"
+#include "VGuiScreen.h"
+#include "world.h"
+
+#define RESUPPLY_HEAL_AMT 100
+#define RESUPPLY_AMMO_AMT 0.25f
+
+// Wall mounted version
+#define RESUPPLY_WALL_MODEL "models/objects/obj_resupply.mdl"
+#define RESUPPLY_WALL_MODEL_ALIEN "models/objects/alien_obj_resupply.mdl"
+#define RESUPPLY_WALL_MINS Vector(-10, -10, -40)
+#define RESUPPLY_WALL_MAXS Vector( 10, 10, 40)
+
+// Ground placed version
+#define RESUPPLY_GROUND_MODEL "models/objects/obj_resupply_ground.mdl"
+#define RESUPPLY_GROUND_MODEL_HUMAN "models/objects/human_obj_resupply_ground.mdl"
+#define RESUPPLY_GROUND_MINS Vector(-20, -20, 0)
+#define RESUPPLY_GROUND_MAXS Vector( 20, 20, 55)
+
+IMPLEMENT_SERVERCLASS_ST( CObjectResupply, DT_ObjectResupply )
+END_SEND_TABLE()
+
+LINK_ENTITY_TO_CLASS(obj_resupply, CObjectResupply);
+PRECACHE_REGISTER(obj_resupply);
+
+ConVar obj_resupply_health( "obj_resupply_health","100", FCVAR_NONE, "Resupply Station health" );
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CObjectResupply::CObjectResupply()
+{
+ m_iHealth = obj_resupply_health.GetInt();
+ UseClientSideAnimation();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CObjectResupply::Spawn()
+{
+ SetModel( RESUPPLY_WALL_MODEL );
+ SetSolid( SOLID_BBOX );
+
+ UTIL_SetSize(this, RESUPPLY_WALL_MINS, RESUPPLY_WALL_MAXS);
+ m_takedamage = DAMAGE_YES;
+
+ SetType( OBJ_RESUPPLY );
+ m_fObjectFlags |= OF_DONT_PREVENT_BUILD_NEAR_OBJ;
+
+ BaseClass::Spawn();
+}
+
+
+//-----------------------------------------------------------------------------
+// Spawn the vgui control screens on the object
+//-----------------------------------------------------------------------------
+void CObjectResupply::GetControlPanelInfo( int nPanelIndex, const char *&pPanelName )
+{
+ pPanelName = "screen_obj_resupply";
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CObjectResupply::Precache()
+{
+ BaseClass::Precache();
+ PrecacheModel( RESUPPLY_WALL_MODEL );
+ PrecacheModel( RESUPPLY_WALL_MODEL_ALIEN );
+ PrecacheModel( RESUPPLY_GROUND_MODEL );
+ PrecacheModel( RESUPPLY_GROUND_MODEL_HUMAN );
+ PrecacheVGuiScreen( "screen_obj_resupply" );
+
+ PrecacheScriptSound( "ObjectResupply.InsufficientFunds" );
+ PrecacheScriptSound( "BaseCombatCharacter.AmmoPickup" );
+}
+
+
+//-----------------------------------------------------------------------------
+// Resupply Health
+//-----------------------------------------------------------------------------
+bool CObjectResupply::ResupplyHealth( CBaseTFPlayer *pPlayer, float flFraction )
+{
+ // Calculate the amount to heal
+ float flAmountToHeal = flFraction * RESUPPLY_HEAL_AMT;
+ if (flAmountToHeal > (pPlayer->m_iMaxHealth - pPlayer->m_iHealth))
+ {
+ flAmountToHeal = (pPlayer->m_iMaxHealth - pPlayer->m_iHealth);
+ }
+
+ if ( flAmountToHeal > 0 )
+ {
+ pPlayer->TakeHealth( flAmountToHeal, 0 );
+ return true;
+ }
+
+ return false;
+}
+
+
+//-----------------------------------------------------------------------------
+// Handle commands sent from vgui panels on the client
+//-----------------------------------------------------------------------------
+bool CObjectResupply::ClientCommand( CBaseTFPlayer *pPlayer, const char *pCmd, ICommandArguments *pArg )
+{
+ // NOTE: Must match ResupplyBuyType_t
+ static float s_Costs[] =
+ {
+ RESUPPLY_AMMO_COST,
+ RESUPPLY_HEALTH_COST,
+ RESUPPLY_GRENADES_COST,
+ RESUPPLY_ALL_COST
+ };
+
+ COMPILE_TIME_ASSERT( RESUPPLY_BUY_TYPE_COUNT == 4 );
+
+ if ( FStrEq( pCmd, "buy" ) )
+ {
+ if ( pArg->Argc() < 2 )
+ return true;
+
+ // I can't do anything if I'm not active
+ if ( !ShouldBeActive() )
+ return true;
+
+ // Do we have enough resources to activate it?
+ if (pPlayer->GetBankResources() <= 0)
+ {
+ // Play a sound indicating it didn't work...
+ CSingleUserRecipientFilter filter( pPlayer );
+ EmitSound( filter, pPlayer->entindex(), "ObjectResupply.InsufficientFunds" );
+ return true;
+ }
+
+ bool bUsedResupply = false;
+ ResupplyBuyType_t type = (ResupplyBuyType_t)atoi( pArg->Argv(1) );
+ if (type >= RESUPPLY_BUY_TYPE_COUNT)
+ return true;
+
+ // Get the potential cost.
+ float flCost = s_Costs[type];
+// flCost += pPlayer->ClassCostAdjustment( type );
+
+ float flFraction = pPlayer->GetBankResources() / flCost;
+ if (flFraction > 1.0f)
+ flFraction = 1.0f;
+
+ switch( type )
+ {
+ case RESUPPLY_BUY_HEALTH:
+ // Calculate the amount to heal
+ if (ResupplyHealth(pPlayer, flFraction))
+ {
+ bUsedResupply = true;
+ }
+ break;
+
+ case RESUPPLY_BUY_AMMO:
+ // Refill the player's ammo too
+ if (pPlayer->ResupplyAmmo( flFraction * RESUPPLY_AMMO_AMT, RESUPPLY_AMMO_FROM_STATION ))
+ {
+ bUsedResupply = true;
+ }
+ break;
+
+ case RESUPPLY_BUY_GRENADES:
+ // Refill the player's ammo too
+ if (pPlayer->ResupplyAmmo( flFraction * RESUPPLY_AMMO_AMT, RESUPPLY_GRENADES_FROM_STATION ))
+ {
+ bUsedResupply = true;
+ }
+ break;
+
+ case RESUPPLY_BUY_ALL:
+ // Calculate the amount to heal
+ if (ResupplyHealth(pPlayer, flFraction))
+ {
+ bUsedResupply = true;
+ }
+
+ // Refill the player's ammo too
+ if (pPlayer->ResupplyAmmo( flFraction * RESUPPLY_AMMO_AMT, RESUPPLY_ALL_FROM_STATION ))
+ {
+ bUsedResupply = true;
+ }
+ break;
+ }
+
+ if (bUsedResupply)
+ {
+ // Play an ammo pickup just to this player
+ CSingleUserRecipientFilter filter( pPlayer );
+ pPlayer->EmitSound( filter, pPlayer->entindex(), "BaseCombatCharacter.AmmoPickup" );
+
+ pPlayer->RemoveBankResources( flFraction * flCost );
+ }
+
+ return true;
+ }
+
+ return BaseClass::ClientCommand( pPlayer, pCmd, pArg );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CObjectResupply::DestroyObject( void )
+{
+ if ( GetTeam() )
+ {
+ ((CTFTeam*)GetTeam())->RemoveResupply( this );
+ }
+ BaseClass::DestroyObject();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : *pTeam -
+//-----------------------------------------------------------------------------
+void CObjectResupply::ChangeTeam( int iTeamNum )
+{
+ CTFTeam *pExisting = (CTFTeam*)GetTeam();
+ CTFTeam *pTeam = (CTFTeam*)GetGlobalTeam( iTeamNum );
+
+ // Already on this team
+ if ( GetTeamNumber() == iTeamNum )
+ return;
+
+ if ( pExisting )
+ {
+ // Remove it from current team ( if it's in one ) and give it to new team
+ pExisting->RemoveResupply( this );
+ }
+
+ // Change to new team
+ BaseClass::ChangeTeam( iTeamNum );
+
+ // Add this object to the team's list
+ if (pTeam)
+ {
+ pTeam->AddResupply( this );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Resupply always wants to use the wall mount for attachment points
+//-----------------------------------------------------------------------------
+void CObjectResupply::SetupAttachedVersion( void )
+{
+ BaseClass::SetupAttachedVersion();
+
+ if ( GetTeamNumber() == TEAM_ALIENS )
+ {
+ SetModel( RESUPPLY_WALL_MODEL_ALIEN );
+ }
+ else
+ {
+ SetModel( RESUPPLY_WALL_MODEL );
+ }
+
+ UTIL_SetSize(this, RESUPPLY_WALL_MINS, RESUPPLY_WALL_MAXS);
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CObjectResupply::CalculatePlacement( CBaseTFPlayer *pPlayer )
+{
+ trace_t tr;
+ Vector vecAiming;
+ // Get an aim vector. Don't use GetAimVector() because we don't want autoaiming.
+ Vector vecSrc = pPlayer->Weapon_ShootPosition( );
+ pPlayer->EyeVectors( &vecAiming );
+ Vector vecTarget;
+ VectorMA( vecSrc, 90, vecAiming, vecTarget );
+ m_vecBuildOrigin = vecTarget;
+
+ // Angle it towards me
+ Vector vecForward = pPlayer->WorldSpaceCenter() - m_vecBuildOrigin;
+ SetLocalAngles( QAngle( 0, UTIL_VecToYaw( vecForward ), 0 ) );
+
+ // Is there something to attach to?
+ // Use my bounding box, not the build box, so I fit to the wall
+ UTIL_TraceLine( vecSrc, vecTarget, MASK_SOLID, pPlayer, COLLISION_GROUP_PLAYER_MOVEMENT, &tr);
+ //UTIL_TraceHull( vecSrc, vecTarget, WorldAlignMins(), WorldAlignMaxs(), MASK_SOLID, pPlayer, TFCOLLISION_GROUP_OBJECT, &tr );
+ m_vecBuildOrigin = tr.endpos;
+ bool bTryToPlaceGroundVersion = false;
+ if ( tr.allsolid || (tr.fraction == 1.0) )
+ {
+ bTryToPlaceGroundVersion = true;
+ }
+ else
+ {
+ // Make sure we're planting on the world
+ CBaseEntity *pEntity = tr.m_pEnt;
+ if ( pEntity != GetWorldEntity() )
+ {
+ bTryToPlaceGroundVersion = true;
+ }
+ }
+
+ // Make sure the wall we've touched is vertical
+ if ( !bTryToPlaceGroundVersion && fabs(tr.plane.normal.z) > 0.3 )
+ {
+ bTryToPlaceGroundVersion = true;
+ }
+
+ // Aborting?
+ if ( bTryToPlaceGroundVersion )
+ {
+ // We couldn't find a wall, so try and place a ground version instead
+ if ( GetTeamNumber() == TEAM_HUMANS )
+ {
+ SetModel( RESUPPLY_GROUND_MODEL_HUMAN );
+ }
+ else
+ {
+ SetModel( RESUPPLY_GROUND_MODEL );
+ }
+ UTIL_SetSize(this, RESUPPLY_GROUND_MINS, RESUPPLY_GROUND_MAXS);
+ m_vecBuildMins = WorldAlignMins() - Vector( 4,4,0 );
+ m_vecBuildMaxs = WorldAlignMaxs() + Vector( 4,4,0 );
+ return BaseClass::CalculatePlacement( pPlayer );
+ }
+
+ SetupAttachedVersion();
+ m_vecBuildMins = WorldAlignMins() - Vector( 4,4,0 );
+ m_vecBuildMaxs = WorldAlignMaxs() + Vector( 4,4,0 );
+
+ // Set the angles
+ vecForward = tr.plane.normal;
+ SetLocalAngles( QAngle( 0, UTIL_VecToYaw( vecForward ), 0 ) );
+
+ // Trace back from the corners
+ Vector vecMins, vecMaxs, vecModelMins, vecModelMaxs;
+ const model_t *pModel = GetModel();
+ modelinfo->GetModelBounds( pModel, vecModelMins, vecModelMaxs );
+
+ // Check the four build points
+ Vector vecPointCheck = (vecForward * 32);
+ Vector vecUp = Vector(0,0,1);
+ Vector vecRight;
+ CrossProduct( vecUp, vecForward, vecRight );
+ float flWidth = fabs(vecModelMaxs.y - vecModelMins.y) * 0.5;
+ float flHeight = fabs(vecModelMaxs.z - vecModelMins.z) * 0.5;
+
+ bool bResult = true;
+ if ( bResult )
+ {
+ bResult = CheckBuildPoint( m_vecBuildOrigin + (vecRight * flWidth) + (vecUp * flHeight), vecPointCheck );
+ }
+ if ( bResult )
+ {
+ bResult = CheckBuildPoint( m_vecBuildOrigin + (vecRight * flWidth) - (vecUp * flHeight), vecPointCheck );
+ }
+ if ( bResult )
+ {
+ bResult = CheckBuildPoint( m_vecBuildOrigin - (vecRight * flWidth) + (vecUp * flHeight), vecPointCheck );
+ }
+ if ( bResult )
+ {
+ bResult = CheckBuildPoint( m_vecBuildOrigin - (vecRight * flWidth) - (vecUp * flHeight), vecPointCheck );
+ }
+
+ AttemptToFindPower();
+
+ return bResult;
+}
diff --git a/game/server/tf2/tf_obj_resupply.h b/game/server/tf2/tf_obj_resupply.h
new file mode 100644
index 0000000..373a32c
--- /dev/null
+++ b/game/server/tf2/tf_obj_resupply.h
@@ -0,0 +1,60 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Medic's resupply beacon
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef TF_OBJ_RESUPPLY_H
+#define TF_OBJ_RESUPPLY_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include "tf_obj.h"
+
+// ------------------------------------------------------------------------ //
+// Resupply defines
+#define RESUPPLY_NUM_PLAYERS_REFILLED 5 // Number of full resupplies before refill need
+
+// An object is considered covered by a resupply station (for purposes of order creation)
+// if it is within this distance of the station.
+#define RESUPPLY_COVER_DIST 1500
+
+
+
+// ------------------------------------------------------------------------ //
+// Resupply object that's built by the player
+// ------------------------------------------------------------------------ //
+class CObjectResupply : public CBaseObject
+{
+DECLARE_CLASS( CObjectResupply, CBaseObject );
+
+public:
+ DECLARE_SERVERCLASS();
+
+ CObjectResupply();
+
+ static CObjectResupply* Create(const Vector &vOrigin, const QAngle &vAngles);
+
+ virtual void Spawn();
+ virtual void GetControlPanelInfo( int nPanelIndex, const char *&pPanelName );
+ virtual void Precache();
+ virtual void DestroyObject( void );
+
+ virtual bool CalculatePlacement( CBaseTFPlayer *pPlayer );
+ virtual void SetupAttachedVersion( void );
+
+ // Resupply
+ virtual bool ClientCommand( CBaseTFPlayer *pPlayer, const CCommand &args );
+
+ virtual void ChangeTeam( int iTeamNum ) OVERRIDE;
+
+ virtual bool CanTakeEMPDamage( void ) { return true; }
+
+private:
+ // Resupply Health
+ bool ResupplyHealth( CBaseTFPlayer *pPlayer, float flFraction );
+};
+
+#endif // TF_OBJ_RESUPPLY_H
diff --git a/game/server/tf2/tf_obj_sandbag_bunker.cpp b/game/server/tf2/tf_obj_sandbag_bunker.cpp
new file mode 100644
index 0000000..d3d012f
--- /dev/null
+++ b/game/server/tf2/tf_obj_sandbag_bunker.cpp
@@ -0,0 +1,73 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: A stationary sandbag bunker that players can take cover in.
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#include "cbase.h"
+#include "tf_obj.h"
+#include "tf_team.h"
+#include "engine/IEngineSound.h"
+#include "tf_obj_sandbag_bunker.h"
+
+#define SANDBAGBUNKER_MINS Vector(-60, -60, 0)
+#define SANDBAGBUNKER_MAXS Vector( 60, 60, 50)
+#define SANDBAGBUNKER_MODEL "models/objects/obj_sandbag_bunker.mdl"
+
+
+IMPLEMENT_SERVERCLASS_ST(CObjectSandbagBunker, DT_ObjectSandbagBunker)
+END_SEND_TABLE();
+
+LINK_ENTITY_TO_CLASS(obj_sandbag_bunker, CObjectSandbagBunker);
+PRECACHE_REGISTER(obj_sandbag_bunker);
+
+// CVars
+ConVar obj_sandbag_bunker_health( "obj_sandbag_bunker_health","100", FCVAR_NONE, "Sandbag bunker health" );
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CObjectSandbagBunker::CObjectSandbagBunker( void )
+{
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CObjectSandbagBunker::Spawn( void )
+{
+ Precache();
+ SetModel( SANDBAGBUNKER_MODEL );
+ SetSolid( SOLID_BBOX );
+
+ UTIL_SetSize(this, SANDBAGBUNKER_MINS, SANDBAGBUNKER_MAXS);
+ m_takedamage = DAMAGE_YES;
+ m_iHealth = obj_sandbag_bunker_health.GetInt();
+
+ m_fObjectFlags |= OF_DOESNT_NEED_POWER;
+ SetType( OBJ_SANDBAG_BUNKER );
+
+ SetSolid( SOLID_VPHYSICS );
+ VPhysicsInitStatic();
+
+ BaseClass::Spawn();
+
+ SetCollisionGroup( COLLISION_GROUP_VEHICLE );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CObjectSandbagBunker::Precache( void )
+{
+ PrecacheModel( SANDBAGBUNKER_MODEL );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Gets info about the control panels
+//-----------------------------------------------------------------------------
+void CObjectSandbagBunker::GetControlPanelInfo( int nPanelIndex, const char *&pPanelName )
+{
+ pPanelName = "screen_basic_with_disable";
+} \ No newline at end of file
diff --git a/game/server/tf2/tf_obj_sandbag_bunker.h b/game/server/tf2/tf_obj_sandbag_bunker.h
new file mode 100644
index 0000000..3dd91c9
--- /dev/null
+++ b/game/server/tf2/tf_obj_sandbag_bunker.h
@@ -0,0 +1,33 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: A stationary sandbag bunker that players can take cover in.
+//
+//=============================================================================//
+
+#ifndef TF_OBJ_SANDBAG_BUNKER_H
+#define TF_OBJ_SANDBAG_BUNKER_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+// ------------------------------------------------------------------------ //
+// Purpose: A stationary sandbag bunker that players can take cover in.
+// ------------------------------------------------------------------------ //
+class CObjectSandbagBunker : public CBaseObject
+{
+ DECLARE_CLASS( CObjectSandbagBunker, CBaseObject );
+
+public:
+ DECLARE_SERVERCLASS();
+
+ CObjectSandbagBunker( void );
+
+ virtual void Spawn( void );
+ virtual void Precache( void );
+ virtual void GetControlPanelInfo( int nPanelIndex, const char *&pPanelName );
+
+private:
+};
+
+
+#endif // TF_OBJ_TOWER_H
diff --git a/game/server/tf2/tf_obj_selfheal.cpp b/game/server/tf2/tf_obj_selfheal.cpp
new file mode 100644
index 0000000..f4bcc7e
--- /dev/null
+++ b/game/server/tf2/tf_obj_selfheal.cpp
@@ -0,0 +1,112 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Upgrade that heals the object over time
+//
+// $NoKeywords: $
+//=============================================================================//
+#include "cbase.h"
+#include "tf_player.h"
+#include "tf_team.h"
+#include "tf_gamerules.h"
+#include "tf_obj.h"
+#include "tf_obj_selfheal.h"
+#include "ndebugoverlay.h"
+
+// ------------------------------------------------------------------------ //
+// Self Heal defines
+#define SELFHEAL_MINS Vector(-10, -10, 0)
+#define SELFHEAL_MAXS Vector( 10, 10, 10)
+#define SELFHEAL_MODEL "models/objects/obj_selfheal.mdl"
+
+BEGIN_DATADESC( CObjectSelfHeal )
+
+ DEFINE_THINKFUNC( SelfHealThink ),
+
+END_DATADESC()
+
+IMPLEMENT_SERVERCLASS_ST(CObjectSelfHeal, DT_ObjectSelfHeal)
+END_SEND_TABLE();
+
+LINK_ENTITY_TO_CLASS(obj_selfheal, CObjectSelfHeal);
+PRECACHE_REGISTER(obj_selfheal);
+
+ConVar obj_selfheal_health( "obj_selfheal_health","100", FCVAR_NONE, "Self-Heal health" );
+ConVar obj_selfheal_rate( "obj_selfheal_rate","1.0", FCVAR_NONE, "Rate at which the Self-Heal object repairs it's parent" );
+ConVar obj_selfheal_amount( "obj_selfheal_amount","3", FCVAR_NONE, "Amount of health healed by a Self-Heal object per tick" );
+
+#define SELFHEAL_THINK_CONTEXT "SelfHealThink"
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CObjectSelfHeal::CObjectSelfHeal()
+{
+ UseClientSideAnimation();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CObjectSelfHeal::Spawn()
+{
+ Precache();
+ SetModel( SELFHEAL_MODEL );
+ SetCollisionGroup( TFCOLLISION_GROUP_COMBATOBJECT );
+
+ UTIL_SetSize(this, SELFHEAL_MINS, SELFHEAL_MAXS);
+ m_takedamage = DAMAGE_YES;
+ m_iHealth = obj_selfheal_health.GetInt();
+
+ SetType( OBJ_SELFHEAL );
+ m_fObjectFlags |= OF_SUPPRESS_NOTIFY_UNDER_ATTACK | OF_SUPPRESS_TECH_ANALYZER |
+ OF_DONT_AUTO_REPAIR | OF_MUST_BE_BUILT_ON_ATTACHMENT;
+
+ BaseClass::Spawn();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CObjectSelfHeal::Precache()
+{
+ PrecacheModel( SELFHEAL_MODEL );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CObjectSelfHeal::FinishedBuilding( void )
+{
+ BaseClass::FinishedBuilding();
+
+ SetContextThink( SelfHealThink, gpGlobals->curtime + obj_selfheal_rate.GetFloat(), SELFHEAL_THINK_CONTEXT );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Heal the object I'm attached to
+//-----------------------------------------------------------------------------
+void CObjectSelfHeal::SelfHealThink( void )
+{
+ if ( !GetTeam() )
+ return;
+
+ CBaseObject *pObject = GetParentObject();
+ if ( !pObject )
+ {
+ Killed();
+ return;
+ }
+
+ SetNextThink( gpGlobals->curtime + obj_selfheal_rate.GetFloat(), SELFHEAL_THINK_CONTEXT );
+
+ // Don't heal if we've been EMPed
+ if ( HasPowerup( POWERUP_EMP ) )
+ return;
+
+ // Don't bring objects back from the dead
+ if ( !pObject->IsAlive() || pObject->IsDying() )
+ return;
+
+ // Repair our parent if it's hurt
+ pObject->Repair( obj_selfheal_amount.GetFloat() );
+}
diff --git a/game/server/tf2/tf_obj_selfheal.h b/game/server/tf2/tf_obj_selfheal.h
new file mode 100644
index 0000000..d015ab9
--- /dev/null
+++ b/game/server/tf2/tf_obj_selfheal.h
@@ -0,0 +1,37 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Upgrade that heals the object over time
+//
+//=============================================================================//
+
+#ifndef TF_OBJ_SELFHEAL_H
+#define TF_OBJ_SELFHEAL_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include "tf_obj_baseupgrade_shared.h"
+
+// ------------------------------------------------------------------------ //
+// Self-Heal upgrade
+// ------------------------------------------------------------------------ //
+class CObjectSelfHeal : public CBaseObjectUpgrade
+{
+DECLARE_CLASS( CObjectSelfHeal, CBaseObjectUpgrade );
+
+public:
+ DECLARE_DATADESC();
+ DECLARE_SERVERCLASS();
+
+ CObjectSelfHeal();
+
+ virtual void Spawn();
+ virtual void Precache();
+ virtual bool CanTakeEMPDamage( void ) { return true; }
+ virtual void FinishedBuilding( void );
+
+ // Repairing
+ virtual void SelfHealThink( void );
+};
+
+#endif // TF_OBJ_SELFHEAL_H
diff --git a/game/server/tf2/tf_obj_sentrygun.cpp b/game/server/tf2/tf_obj_sentrygun.cpp
new file mode 100644
index 0000000..2ea32dc
--- /dev/null
+++ b/game/server/tf2/tf_obj_sentrygun.cpp
@@ -0,0 +1,1178 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Defender's sentrygun objects
+//
+// $NoKeywords: $
+//=============================================================================//
+#include "cbase.h"
+#include "tf_player.h"
+#include "tf_team.h"
+#include "tf_obj.h"
+#include "tf_obj_sentrygun.h"
+#include "tf_obj_dragonsteeth.h"
+#include "tf_obj_tower.h"
+#include "tf_obj_sandbag_bunker.h"
+#include "tf_obj_bunker.h"
+#include "tf_obj_mapdefined.h"
+#include "tf_gamerules.h"
+#include "gamerules.h"
+#include "ammodef.h"
+#include "plasmaprojectile.h"
+#include "tf_class_recon.h"
+#include "sendproxy.h"
+#include "vstdlib/random.h"
+#include "engine/IEngineSound.h"
+#include "grenade_rocket.h"
+#include "VGuiScreen.h"
+
+extern short g_sModelIndexFireball;
+
+#define MAX_SUPPRESSION_TIME 5.0 // Max amount of time to supress for
+
+// Sentrygun size
+#define SENTRYGUN_MINS Vector(-16, -16, 0)
+#define SENTRYGUN_MAXS Vector( 16, 16, 65)
+
+//=============================================================================
+// Link and precache all the sentrygun types
+LINK_ENTITY_TO_CLASS(obj_sentrygun_plasma, CObjectSentrygunPlasma);
+LINK_ENTITY_TO_CLASS(obj_sentrygun_rocketlauncher, CObjectSentrygunRocketlauncher);
+PRECACHE_REGISTER(obj_sentrygun_plasma);
+PRECACHE_REGISTER(obj_sentrygun_rocketlauncher);
+
+//=============================================================================
+// Data description
+BEGIN_DATADESC( CObjectSentrygun )
+
+ DEFINE_THINKFUNC( SentryRotate ),
+ DEFINE_THINKFUNC( Attack ),
+
+END_DATADESC()
+
+// Sentrygun team-only vars.
+BEGIN_SEND_TABLE_NOBASE( CObjectSentrygun, DT_SentrygunTeamOnlyVars )
+ SendPropInt( SENDINFO(m_iAmmo), 9 ),
+END_SEND_TABLE()
+
+#define SENTRY_ANIMATION_PARITY_BITS 2
+
+IMPLEMENT_SERVERCLASS_ST(CObjectSentrygun, DT_ObjectSentrygun)
+ SendPropInt( SENDINFO( m_iBaseTurnRate ), 3, SPROP_UNSIGNED ),
+ SendPropEHandle( SENDINFO( m_hEnemy ) ),
+ SendPropDataTable( "teamonly", 0, &REFERENCE_SEND_TABLE( DT_SentrygunTeamOnlyVars ), SendProxy_OnlyToTeam ),
+ SendPropInt( SENDINFO(m_bTurtled), 1, SPROP_UNSIGNED ),
+ SendPropInt( SENDINFO( m_nAnimationParity ), (1<<SENTRY_ANIMATION_PARITY_BITS), SPROP_UNSIGNED ),
+ SendPropInt( SENDINFO( m_nOrientationParity ), 1, SPROP_UNSIGNED ),
+END_SEND_TABLE();
+
+ConVar obj_sentrygun_plasma_health( "obj_sentrygun_plasma_health","200", FCVAR_NONE, "Plasma sentrygun health" );
+ConVar obj_sentrygun_plasma_range( "obj_sentrygun_plasma_range","1500", FCVAR_NONE, "Plasma sentrygun's shot range" );
+ConVar obj_sentrygun_rocketlauncher_health( "obj_sentrygun_rocketlauncher_health","250", FCVAR_NONE, "Rocket Launcher sentrygun health" );
+ConVar obj_sentrygun_range_mid( "obj_sentrygun_range_mid","768", FCVAR_NONE, "Sentrygun's mid targeting range. Targets beyond this need to be in the viewcone to be seen." );
+ConVar obj_sentrygun_range_max( "obj_sentrygun_range_max","1600", FCVAR_NONE, "Sentrygun's max targeting range." );
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CObjectSentrygun::CObjectSentrygun( void )
+{
+ UseClientSideAnimation();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CObjectSentrygun::Spawn( void )
+{
+ m_bSmarter = false;
+ m_bSensors = false;
+ m_bSuppressing = false;
+ m_bTurtled = false;
+ m_bTurtling = false;
+ m_flTurtlingFinishedAt = 0;
+
+ SetViewOffset( Vector(0,0,22) );
+
+ // Setup
+ SetSolid( SOLID_BBOX );
+ AddSolidFlags( FSOLID_NOT_STANDABLE );
+ UTIL_SetSize(this, SENTRYGUN_MINS, SENTRYGUN_MAXS);
+
+ BaseClass::Spawn();
+
+ // Start searching for enemies
+ m_hEnemy = NULL;
+ m_hDesignatedEnemy = NULL;
+ SetThink( SentryRotate );
+ SetNextThink( gpGlobals->curtime + 0.5f );
+ m_flNextLook = gpGlobals->curtime;
+
+ SetTechnology( false, false );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CObjectSentrygun::Precache()
+{
+ PrecacheModel( SG_PLASMA_MODEL );
+ PrecacheModel( SG_ROCKETLAUNCHER_MODEL );
+ PrecacheVGuiScreen( "screen_obj_sentrygun" );
+
+ PrecacheScriptSound( "ObjectSentrygun.ResupplyAmmo" );
+ PrecacheScriptSound( "ObjectSentrygun.Idle" );
+ PrecacheScriptSound( "ObjectSentrygun.FoundTarget" );
+ PrecacheScriptSound( "ObjectSentrygun.Turtle" );
+ PrecacheScriptSound( "ObjectSentrygun.UnTurtle" );
+ PrecacheScriptSound( "ObjectSentrygun.Fire" );
+ PrecacheScriptSound( "ObjectSentrygunRocketlauncher.Fire" );
+
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Gets info about the control panels
+//-----------------------------------------------------------------------------
+void CObjectSentrygun::GetControlPanelInfo( int nPanelIndex, const char *&pPanelName )
+{
+ pPanelName = "screen_obj_sentrygun";
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Hide the base of the gun if it's on an attachment
+//-----------------------------------------------------------------------------
+void CObjectSentrygun::SetupAttachedVersion( void )
+{
+ BaseClass::SetupAttachedVersion();
+
+ SetBodygroup( 1, true );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CObjectSentrygun::SetupUnattachedVersion( void )
+{
+ BaseClass::SetupUnattachedVersion();
+
+ SetBodygroup( 1, false );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CObjectSentrygun::FinishedBuilding( void )
+{
+ BaseClass::FinishedBuilding();
+
+ // Orient it
+ m_vecCurAngles.y = UTIL_AngleMod( GetLocalAngles().y );
+ RecomputeOrientation();
+}
+
+//-----------------------------------------------------------------------------
+// Called when a rotation happens
+//-----------------------------------------------------------------------------
+void CObjectSentrygun::RecomputeOrientation( )
+{
+ ResetOrientation();
+
+ m_iRightBound = UTIL_AngleMod( m_vecCurAngles.y - 50);
+ m_iLeftBound = UTIL_AngleMod( m_vecCurAngles.y + 50);
+ if ( m_iRightBound > m_iLeftBound )
+ {
+ m_iRightBound = m_iLeftBound;
+ m_iLeftBound = UTIL_AngleMod( m_vecCurAngles.y - 50);
+ }
+
+ // Start it rotating
+ m_vecGoalAngles.y = m_iRightBound;
+ m_vecGoalAngles.x = m_vecCurAngles.x = 0;
+ m_bTurningRight = true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Handle commands sent from vgui panels on the client
+//-----------------------------------------------------------------------------
+bool CObjectSentrygun::ClientCommand( CBaseTFPlayer *pPlayer, const char *pCmd, ICommandArguments *pArg )
+{
+ if ( FStrEq( pCmd, "addammo" ) )
+ {
+ if ( TakeAmmoFrom( pPlayer ) )
+ {
+ // We got some ammo, so make a sound
+ CPASAttenuationFilter filter( pPlayer, "ObjectSentrygun.ResupplyAmmo" );
+ EmitSound( filter, pPlayer->entindex(), "ObjectSentrygun.ResupplyAmmo" );
+ }
+ return true;
+ }
+
+ return BaseClass::ClientCommand( pPlayer, pCmd, pArg );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Return true if the player gave the sentrygun some ammo
+//-----------------------------------------------------------------------------
+bool CObjectSentrygun::TakeAmmoFrom( CBaseTFPlayer *pPlayer )
+{
+ // Do I need ammo?
+ if ( m_iAmmo >= m_iMaxAmmo )
+ return false;
+
+ // Try to fill the sentry up a bit at a time
+ int iRoundsToGive = 10;
+ iRoundsToGive = MIN( iRoundsToGive, (m_iMaxAmmo - m_iAmmo) );
+ iRoundsToGive = MIN( iRoundsToGive, pPlayer->GetAmmoCount( m_iAmmoType ) );
+ if ( !iRoundsToGive )
+ return false;
+
+ // Give me the ammo
+ pPlayer->RemoveAmmo( iRoundsToGive, m_iAmmoType );
+ m_iAmmo += iRoundsToGive;
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Resupply has taken damage
+//-----------------------------------------------------------------------------
+int CObjectSentrygun::OnTakeDamage( const CTakeDamageInfo &info )
+{
+ int iDamage = BaseClass::OnTakeDamage( info );
+
+ return iDamage;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Object has been blown up
+//-----------------------------------------------------------------------------
+void CObjectSentrygun::Killed( void )
+{
+ // Tell the player he's lost this resupply beacon
+ if ( GetOwner() )
+ {
+ GetOwner()->OwnedObjectDestroyed( this );
+ }
+
+ BaseClass::Killed();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CObjectSentrygun::RestartAnimation( void )
+{
+ // Increment and mask parity counter
+ m_nAnimationParity += 1;
+ m_nAnimationParity &= ( (1<<SENTRY_ANIMATION_PARITY_BITS) - 1 );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CObjectSentrygun::ResetOrientation()
+{
+ m_nOrientationParity = !m_nOrientationParity;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CObjectSentrygun::SetSentryAnim( TFTURRET_ANIM anim )
+{
+ if ( GetSequence() != anim )
+ {
+ switch(anim)
+ {
+ case TFTURRET_ANIM_FIRE:
+ case TFTURRET_ANIM_SPIN:
+ if ( GetSequence() != TFTURRET_ANIM_FIRE && GetSequence() != TFTURRET_ANIM_SPIN )
+ {
+ RestartAnimation();
+ }
+ break;
+ default:
+ RestartAnimation();
+ break;
+ }
+
+ ResetSequence( anim );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Handle movement of the turret
+//-----------------------------------------------------------------------------
+bool CObjectSentrygun::MoveTurret(void)
+{
+ bool bMoved = 0;
+
+ // any x movement?
+ if ( m_vecCurAngles.x != m_vecGoalAngles.x )
+ {
+ float flDir = m_vecGoalAngles.x > m_vecCurAngles.x ? 1 : -1 ;
+
+ m_vecCurAngles.x += 0.1 * (m_iBaseTurnRate * 5) * flDir;
+
+ // if we started below the goal, and now we're past, peg to goal
+ if (flDir == 1)
+ {
+ if (m_vecCurAngles.x > m_vecGoalAngles.x)
+ m_vecCurAngles.x = m_vecGoalAngles.x;
+ }
+ else
+ {
+ if (m_vecCurAngles.x < m_vecGoalAngles.x)
+ m_vecCurAngles.x = m_vecGoalAngles.x;
+ }
+
+ m_fBoneYRotator = m_vecCurAngles.x;
+
+ bMoved = 1;
+ }
+
+ if ( m_vecCurAngles.y != m_vecGoalAngles.y )
+ {
+ float flDir = m_vecGoalAngles.y > m_vecCurAngles.y ? 1 : -1 ;
+ float flDist = fabs(m_vecGoalAngles.y - m_vecCurAngles.y);
+ bool bReversed = false;
+
+ if (flDist > 180)
+ {
+ flDist = 360 - flDist;
+ flDir = -flDir;
+ bReversed = true;
+ }
+
+ if (m_hEnemy == NULL && !m_bSuppressing)
+ {
+ if (flDist > 30)
+ {
+ if (m_fTurnRate < m_iBaseTurnRate * 20)
+ {
+ m_fTurnRate += m_iBaseTurnRate;
+ }
+ }
+ else
+ {
+ // Slow down
+ if ( m_fTurnRate > (m_iBaseTurnRate * 5) )
+ m_fTurnRate -= m_iBaseTurnRate;
+ }
+ }
+ else
+ {
+ // When tracking enemies, move faster and don't slow
+ if (flDist > 30)
+ {
+ if (m_fTurnRate < m_iBaseTurnRate * 30)
+ {
+ m_fTurnRate += m_iBaseTurnRate * 3;
+ }
+ }
+ }
+
+ m_vecCurAngles.y += 0.1 * m_fTurnRate * flDir;
+
+ // if we passed over the goal, peg right to it now
+ if (flDir == -1)
+ {
+ if ( (bReversed == false && m_vecGoalAngles.y > m_vecCurAngles.y) || (bReversed == true && m_vecGoalAngles.y < m_vecCurAngles.y) )
+ m_vecCurAngles.y = m_vecGoalAngles.y;
+ }
+ else
+ {
+ if ( (bReversed == false && m_vecGoalAngles.y < m_vecCurAngles.y) || (bReversed == true && m_vecGoalAngles.y > m_vecCurAngles.y) )
+ m_vecCurAngles.y = m_vecGoalAngles.y;
+ }
+
+ if (m_vecCurAngles.y < 0)
+ m_vecCurAngles.y += 360;
+ else if (m_vecCurAngles.y >= 360)
+ m_vecCurAngles.y -= 360;
+
+ if (flDist < (0.05 * m_iBaseTurnRate))
+ m_vecCurAngles.y = m_vecGoalAngles.y;
+
+ m_fBoneXRotator = m_vecCurAngles.y - GetLocalAngles().y;
+
+ bMoved = 1;
+ }
+
+ if ( !bMoved || !m_fTurnRate )
+ {
+ m_fTurnRate = m_iBaseTurnRate;
+ }
+
+ return bMoved;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Returns true is the passed ent is in the caller's forward view cone.
+// The dot product is performed in 2d, making the view cone infinitely tall.
+//-----------------------------------------------------------------------------
+bool CObjectSentrygun::FInViewCone( CBaseEntity *pEntity )
+{
+ float flDot;
+
+ Vector vecFacingDir;
+ AngleVectors( m_vecCurAngles, &vecFacingDir );
+ Vector vecLOS = ( pEntity->GetAbsOrigin() - GetAbsOrigin() );
+ flDot = DotProduct( vecLOS , vecFacingDir );
+
+ if ( flDot > VIEW_FIELD_NARROW )
+ return true;
+
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Check the shield's values
+//-----------------------------------------------------------------------------
+void CObjectSentrygun::CheckShield( void )
+{
+ if ( m_nRenderFX == kRenderFxNone )
+ return;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Rotate and scan for targets
+//-----------------------------------------------------------------------------
+void CObjectSentrygun::SentryRotate( void )
+{
+ SetNextThink( gpGlobals->curtime + 0.1f );
+
+ CheckShield();
+
+ // I can't do anything if I'm not active
+ if ( !ShouldBeActive() )
+ return;
+
+ // If we're turtling, see if we're finished yet
+ if ( IsTurtling() )
+ {
+ if ( m_flTurtlingFinishedAt <= gpGlobals->curtime )
+ {
+ m_bTurtling = false;
+ if ( m_bTurtled )
+ {
+ AddSolidFlags( FSOLID_NOT_SOLID );
+ m_takedamage = DAMAGE_NO;
+ }
+ }
+ return;
+ }
+
+ // Turtling sentryguns don't think
+ if ( IsTurtled() )
+ return;
+
+ // animate
+ SetSentryAnim( TFTURRET_ANIM_SPIN );
+
+ // Abort if it's not time to search for enemies
+ if ( m_flNextLook < gpGlobals->curtime )
+ {
+ m_flNextLook = gpGlobals->curtime + 1.0;
+
+ // Look for a target
+ m_hEnemy = FindTarget();
+
+ if ( m_hEnemy != NULL )
+ {
+ FoundTarget();
+ return;
+ }
+ }
+
+ // Rotate
+ if ( !MoveTurret() )
+ {
+ // Play a sound occasionally
+ if ( random->RandomFloat(0, 1) < 0.02 )
+ {
+ EmitSound( "ObjectSentrygun.Idle" );
+ }
+
+ // Switch rotation direction
+ if (m_bTurningRight)
+ {
+ m_bTurningRight = false;
+ m_vecGoalAngles.y = m_iLeftBound;
+ }
+ else
+ {
+ m_bTurningRight = true;
+ m_vecGoalAngles.y = m_iRightBound;
+ }
+
+ // Randomly look up and down a bit
+ if ( random->RandomFloat(0, 1) < 0.3 )
+ {
+ m_vecGoalAngles.x = (int)random->RandomFloat(-10,10);
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Check to see if there's a valid target in sight
+//-----------------------------------------------------------------------------
+CBaseEntity *CObjectSentrygun::FindTarget( void )
+{
+ CBaseEntity *pHighestPriorityTarget = NULL;
+ float fHighestPriority = 0;
+
+ // If I have a designated enemy, and it's valid, assume it will be the target, unless something higher priority shows up.
+ if ( m_hDesignatedEnemy.Get() && ValidTarget( m_hDesignatedEnemy) )
+ {
+ fHighestPriority = GetPriority( m_hDesignatedEnemy );
+ pHighestPriorityTarget = m_hDesignatedEnemy;
+ }
+
+ // Find a target.
+ CBaseEntity *pList[1024];
+ Vector delta( 2048, 2048, 2048 );
+ int count = UTIL_EntitiesInBox( pList, 1024, GetAbsOrigin() - delta, GetAbsOrigin() + delta, FL_CLIENT|FL_NPC|FL_OBJECT );
+
+ for ( int i = 0; i < count; i++ )
+ {
+ if( !pList[i] )
+ continue;
+
+ if ( pList[i] == this )
+ continue;
+
+ float fPriority = GetPriority( pList[i] );
+
+ if( !pHighestPriorityTarget || (fPriority > fHighestPriority) )
+ {
+
+ if ( ValidTarget( pList[i] ) )
+ {
+ pHighestPriorityTarget = pList[i];
+ fHighestPriority = fPriority;
+ }
+ }
+ }
+
+ return pHighestPriorityTarget;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Get the priority of the target
+//-----------------------------------------------------------------------------
+float CObjectSentrygun::GetPriority( CBaseEntity *pTarget )
+{
+ // Players
+ if ( pTarget->IsPlayer() )
+ {
+ return 20;
+ }
+
+ // NPCs
+ if ( pTarget->GetFlags() & FL_NPC )
+ return 10;
+
+ // Objects
+ if ( pTarget->Classify() == CLASS_MILITARY )
+ {
+ // Sentryguns are highest priority
+ CBaseObject *pObject = (CBaseObject *)pTarget;
+ if ( pObject->IsSentrygun() )
+ return 5;
+ return 2;
+ }
+
+ return 1;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Returns the sentry targeting range the target is in
+//-----------------------------------------------------------------------------
+int CObjectSentrygun::Range( CBaseEntity *pTarget )
+{
+ Vector vecOrg = EyePosition();
+ Vector vecTargetOrg = pTarget->EyePosition();
+
+ int iDist = ( vecTargetOrg - vecOrg ).Length();
+
+ // Sensors increase targeting range
+ if ( m_bSensors )
+ {
+ iDist *= 0.75;
+ }
+
+ if (iDist < obj_sentrygun_range_mid.GetFloat() )
+ return RANGE_NEAR;
+ if (iDist < obj_sentrygun_range_max.GetFloat() )
+ return RANGE_MID;
+ return RANGE_FAR;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Check to see if a target's valid
+//-----------------------------------------------------------------------------
+bool CObjectSentrygun::ValidTarget( CBaseEntity *pTarget )
+{
+ // Make sure we aren't borked:
+ if ( !pTarget )
+ return false;
+
+ // Don't attack things that have already died
+ if ( !pTarget->IsAlive() )
+ return false;
+
+ // Don't attack things that cant be hurt
+ if ( pTarget->m_takedamage != DAMAGE_YES )
+ return false;
+
+ // Don't shoot at objects on the neutral team.
+ if( !pTarget->IsInAnyTeam() )
+ return false;
+
+
+
+ // Ignore camoed players
+ if ( pTarget->IsPlayer() )
+ {
+ CBaseTFPlayer *pPlayer = (CBaseTFPlayer*)pTarget;
+
+ if ( InSameTeam( pPlayer ) )
+ return false;
+ if ( pPlayer->IsClass( TFCLASS_UNDECIDED ) )
+ return false;
+ if ( pPlayer->GetCamouflageAmount() >= 30.0f )
+ return false;
+
+ }
+ else
+ {
+ // Only attack enemies.
+ if ( InSameTeam( pTarget) )
+ return false;
+ }
+
+ if ( pTarget->GetFlags() & FL_NOTARGET )
+ return false;
+
+ if ( !FVisible(pTarget) )
+ return false;
+
+ // Ignore certain enemy infrastructure type objects:
+ CBaseObject *pObject = dynamic_cast< CBaseObject* >(pTarget);
+ if ( pObject )
+ {
+ // Make sure it's not placing
+ if ( pObject->IsPlacing() )
+ return false;
+
+ // Ignore upgrades
+ if ( pObject->IsAnUpgrade() )
+ return false;
+
+ // Ignore defensive structures
+ if ( IsObjectADefensiveBuilding( pObject->GetType() ) )
+ return false;
+
+ // Ignore mapdefined objects
+ if ( pObject->GetType() == OBJ_MAPDEFINED )
+ return false;
+ }
+
+ // Make sure there's nothing inbetween us
+ Vector vecSrc = EyePosition();
+
+ // Now make sure there isn't something other than team players in the way.
+ trace_t tr;
+ CTraceFilterSimpleList sentryFilter( COLLISION_GROUP_NONE );
+ sentryFilter.AddEntityToIgnore( GetOwner() );
+ sentryFilter.AddEntityToIgnore( this );
+ sentryFilter.AddEntityToIgnore( GetMoveParent() );
+ UTIL_TraceLine( vecSrc, pTarget->WorldSpaceCenter(), MASK_SHOT, &sentryFilter, &tr );
+ CBaseEntity *pEntity = tr.m_pEnt;
+ if ( (tr.fraction < 1.0) && ( pEntity != pTarget ) )
+ return false;
+
+ int iRange = Range(pTarget);
+ if ( iRange == RANGE_FAR )
+ return false;
+
+ // Better sensors allow them to track irrespective of facing
+ if ( iRange == RANGE_MID && (!FInViewCone(pTarget) && !m_bSensors) )
+ return false;
+
+ // Don't shoot at turtled sentry guns.
+ CObjectSentrygun *pSentry = dynamic_cast< CObjectSentrygun* >( pTarget );
+ if ( pSentry && pSentry->IsTurtled() )
+ return false;
+
+ // Don't shoot at targets blocked by enemy shields
+ bool bBlocked = TFGameRules()->IsBlockedByEnemyShields( GetAbsOrigin(), pTarget->GetAbsOrigin(), GetTeamNumber() );
+ if( bBlocked )
+ return false;
+
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Return true if the sentry has some ammo to fire
+//-----------------------------------------------------------------------------
+bool CObjectSentrygun::HasAmmo( void )
+{
+ return (m_iAmmo > 0);
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: We've found a valid target
+//-----------------------------------------------------------------------------
+void CObjectSentrygun::FoundTarget()
+{
+ if ( HasAmmo() )
+ {
+ EmitSound( "ObjectSentrygun.FoundTarget" );
+ }
+
+ SetThink( Attack );
+ SetNextThink( gpGlobals->curtime + 0.1 );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Make sure our target is still valid, and if so, fire at it
+//-----------------------------------------------------------------------------
+void CObjectSentrygun::Attack( void )
+{
+ SetNextThink( gpGlobals->curtime + 0.1f );
+
+ CheckShield();
+
+ // Turtling sentryguns don't attack
+ if ( IsTurtled() )
+ {
+ SetThink( SentryRotate );
+ return;
+ }
+
+ // I can't do anything if I'm not active
+ if ( !ShouldBeActive() )
+ return;
+
+ // Make sure our target is still valid and that we've still got ammo
+ if ( m_bSuppressing && HasAmmo() )
+ {
+ if ( gpGlobals->curtime > (m_flStartedSuppressing + MAX_SUPPRESSION_TIME) )
+ {
+ m_bSuppressing = false;
+ SetThink( SentryRotate );
+ return;
+ }
+
+ // Check to see if we can find a valid target to switch to
+ m_hEnemy = FindTarget();
+ if ( m_hEnemy )
+ {
+ // Stop supressing and target this new enemy
+ m_bSuppressing = false;
+ m_flStartedSuppressing = 0;
+ FoundTarget();
+ }
+ }
+ else if ( !ValidTarget(m_hEnemy) || HasAmmo() == false )
+ {
+ m_hEnemy = NULL;
+
+ // Smarter sentryguns will suppression fire for a few seconds
+ if ( m_bSmarter && WillSuppress() && HasAmmo() )
+ {
+ m_bSuppressing = true;
+ m_flStartedSuppressing = gpGlobals->curtime;
+ }
+ else
+ {
+ RecomputeOrientation();
+ SetThink( SentryRotate );
+ return;
+ }
+ }
+
+ // If I have a designated enemy, and it's valid, target it instead of my current enemy
+ if ( m_hDesignatedEnemy.Get() && (m_hEnemy.Get() != m_hDesignatedEnemy.Get()) )
+ {
+ if ( ValidTarget( m_hDesignatedEnemy ) )
+ {
+ m_hEnemy = m_hDesignatedEnemy;
+ FoundTarget();
+ }
+ }
+
+ // Figure out where we're firing at
+ Vector vecMid = EyePosition();
+ if ( m_bSuppressing )
+ {
+ // Suppression fire should just shoot at it's last known position
+ m_vecFireTarget = m_vecLastKnownPosition;
+ }
+ else
+ {
+ m_vecFireTarget = m_hEnemy->BodyTarget( vecMid );
+ m_vecLastKnownPosition = m_vecFireTarget;
+ }
+ Vector vecDirToEnemy = m_vecFireTarget - vecMid;
+ QAngle angToTarget;
+ VectorAngles(vecDirToEnemy, angToTarget);
+
+ angToTarget.y = UTIL_AngleMod( angToTarget.y );
+ if (angToTarget.x < -180)
+ angToTarget.x += 360;
+ if (angToTarget.x > 180)
+ angToTarget.x -= 360;
+
+ // now all numbers should be in [1...360]
+ // pin to turret limitations to [-50...50]
+ if (angToTarget.x > 50)
+ angToTarget.x = 50;
+ else if (angToTarget.x < -50)
+ angToTarget.x = -50;
+ m_vecGoalAngles.y = angToTarget.y;
+ m_vecGoalAngles.x = angToTarget.x;
+
+ MoveTurret();
+
+ // Fire on the target if it's within 10 units of being aimed right at it
+ if ( m_flNextAttack <= gpGlobals->curtime && (m_vecGoalAngles - m_vecCurAngles).Length() <= 15 )
+ {
+ // Suppressing turrets fire randomly
+ if ( m_bSuppressing )
+ {
+ if ( random->RandomInt( 0,1 ) != 0 )
+ {
+ m_flNextAttack = gpGlobals->curtime + 0.5;
+ return;
+ }
+ }
+
+ // See if the object or its owner is taking emp damage, if so, don't fire
+ if ( ShouldBeActive() )
+ {
+ Fire();
+ }
+ }
+ else
+ {
+ SetSentryAnim( TFTURRET_ANIM_SPIN );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Called when a rotation happens
+//-----------------------------------------------------------------------------
+void CObjectSentrygun::ObjectMoved( void )
+{
+ m_vecCurAngles.y = UTIL_AngleMod( GetLocalAngles().y );
+ RecomputeOrientation();
+ m_fBoneXRotator = 0;
+ BaseClass::ObjectMoved();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Fire at our target
+//-----------------------------------------------------------------------------
+bool CObjectSentrygun::Fire( void )
+{
+ // Base sentry doesn't know how to fire
+ return true;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Tell this sentrygun to attack the following target, if it can
+//-----------------------------------------------------------------------------
+void CObjectSentrygun::DesignateTarget( CBaseEntity *pTarget )
+{
+ m_hDesignatedEnemy = pTarget;
+
+ if ( m_hEnemy.Get() != m_hDesignatedEnemy.Get() )
+ {
+ m_hEnemy = NULL;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CObjectSentrygun::IsTurtled( void )
+{
+ return m_bTurtled;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CObjectSentrygun::IsTurtling( void )
+{
+ return m_bTurtling;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CObjectSentrygun::ToggleTurtle( void )
+{
+ // Don't turtle while building
+ if ( IsPlacing() || IsBuilding() || IsTurtling() )
+ return;
+
+ // Don't turtle if I'm built on anything
+ if ( GetMoveParent() )
+ return;
+
+ // Swap turtle state
+ if ( IsTurtled() )
+ {
+ UnTurtle();
+ }
+ else
+ {
+ Turtle();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CObjectSentrygun::Turtle( void )
+{
+ m_bTurtled = true;
+ m_bTurtling = true;
+
+ m_flTurtlingFinishedAt = gpGlobals->curtime + SENTRY_TURTLE_TIME;
+ EmitSound( "ObjectSentrygun.Turtle" );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CObjectSentrygun::UnTurtle( void )
+{
+ // Make sure there's enough room to unturtle
+ // NJS: this seems a bit hacky and returns false positives sometimes, for now we're just assuming that if it can turtle, it can also unturtle.
+ //trace_t tr;
+ //UTIL_TraceHull( GetAbsOrigin(), GetAbsOrigin(), WorldAlignMins() - Vector( 4,4,4 ), WorldAlignMaxs() + Vector( 4,4,4 ), MASK_SOLID, this, COLLISION_GROUP_NONE, &tr );
+ //if ( tr.startsolid || tr.allsolid )
+ // return;
+
+ m_bTurtled = false;
+ m_bTurtling = true;
+ m_flTurtlingFinishedAt = gpGlobals->curtime + SENTRY_TURTLE_TIME;
+ EmitSound( "ObjectSentrygun.UnTurtle" );
+
+ RemoveSolidFlags( FSOLID_NOT_SOLID );
+ m_takedamage = DAMAGE_YES;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Set the technology levels of the sentrygun
+//-----------------------------------------------------------------------------
+void CObjectSentrygun::SetTechnology( bool bSmarter, bool bSensors )
+{
+ m_bSmarter = bSmarter;
+ m_bSensors = bSensors;
+
+ // Smarter sentryguns turn faster
+ if ( m_bSmarter )
+ {
+ m_iBaseTurnRate = 6;
+ }
+ else
+ {
+ m_iBaseTurnRate = 4;
+ }
+}
+
+//========================================================================================================
+// SENTRYGUN TYPES
+//========================================================================================================
+IMPLEMENT_SERVERCLASS_ST(CObjectSentrygunPlasma, DT_ObjectSentrygunPlasma)
+END_SEND_TABLE();
+
+IMPLEMENT_SERVERCLASS_ST(CObjectSentrygunRocketlauncher, DT_ObjectSentrygunRocketlauncher)
+END_SEND_TABLE();
+
+void CObjectSentrygunPlasma::Spawn( void )
+{
+ m_iHealth = obj_sentrygun_plasma_health.GetInt();
+
+ SetModel( SG_PLASMA_MODEL );
+ BaseClass::Spawn();
+
+ SetType( OBJ_SENTRYGUN_PLASMA );
+
+ m_flNextAmmoRecharge = gpGlobals->curtime + PLASMA_SENTRYGUN_RECHARGE_TIME;
+ m_nBurstCount = PLASMA_SENTRY_BURST_COUNT;
+
+ m_iAmmo = m_iMaxAmmo = 50;
+ m_iAmmoType = GetAmmoDef()->Index( "ShotgunEnergy" );
+}
+
+void CObjectSentrygunRocketlauncher::Spawn( void )
+{
+ m_iHealth = obj_sentrygun_rocketlauncher_health.GetInt();
+
+ SetModel( SG_ROCKETLAUNCHER_MODEL );
+ BaseClass::Spawn();
+
+ SetType( OBJ_SENTRYGUN_ROCKET_LAUNCHER );
+
+ m_iAmmo = m_iMaxAmmo = 50;
+ m_iAmmoType = GetAmmoDef()->Index( "Rockets" );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Plasma sentrygun's fire
+//-----------------------------------------------------------------------------
+bool CObjectSentrygunPlasma::Fire( void )
+{
+ Vector vecSrc = EyePosition();
+ Vector vecTarget = m_vecFireTarget;
+ Vector vecAim;
+ QAngle vecAng;
+
+ // Because the plasma sentrygun always thinks it has ammo (see below)
+ // we might not have ammo here, in which case we should just abort.
+ if ( !m_iAmmo )
+ return true;
+
+ GetAttachment( "muzzle", vecSrc, vecAng );
+
+ // Get the distance to the target
+ float targetDist = (vecTarget - vecSrc).Length();
+ float targetTime = targetDist / PLASMA_VELOCITY;
+
+ // If we're not suppressing, calculate where the target's going to be in that time
+ if ( !m_bSuppressing )
+ {
+ Vector vecVelocity = m_hEnemy->GetSmoothedVelocity();
+ // Dampen Z velocity to prevent jumping people screwing the aim
+ vecVelocity.z *= 0.25;
+ // Get the target point to aim for
+ Vector vecEnd = vecTarget + ( vecVelocity * targetTime );
+ vecAim = (vecEnd - vecSrc);
+ }
+ else
+ {
+ vecAim = (vecTarget - vecSrc);
+ }
+ VectorNormalize( vecAim );
+
+ int damageType = GetAmmoDef()->DamageType( m_iAmmoType );
+ CBasePlasmaProjectile *pPlasma = CBasePlasmaProjectile::Create( vecSrc + (vecAim * 32), vecAim, damageType, this );
+ pPlasma->SetDamage( 15 );
+ pPlasma->SetMaxRange( obj_sentrygun_plasma_range.GetFloat() );
+ pPlasma->m_hOwner = GetBuilder();
+
+ EmitSound( "ObjectSentrygun.Fire" );
+ SetSentryAnim( TFTURRET_ANIM_FIRE );
+ DoMuzzleFlash();
+
+ m_iAmmo -= 1;
+
+ float flAttackTime;
+ if (--m_nBurstCount > 0)
+ {
+ flAttackTime = 0.2f;
+ }
+ else
+ {
+ flAttackTime = random->RandomFloat( 1.0f, 2.0f );
+ m_nBurstCount = PLASMA_SENTRY_BURST_COUNT + random->RandomInt( 0, PLASMA_SENTRY_BURST_COUNT );
+ }
+
+ // If I'm EMPed, slow the firing rate down
+ if ( HasPowerup(POWERUP_EMP) )
+ {
+ flAttackTime *= 3;
+ }
+
+ m_flNextAttack = gpGlobals->curtime + flAttackTime;
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Plasma sentry regenerates ammo, so always assume it has ammo left.
+// This is to prevent it from continually unlocking & relocking when it's
+// ammo is flickering between 0 and 1.
+//-----------------------------------------------------------------------------
+bool CObjectSentrygunPlasma::HasAmmo( void )
+{
+ return true;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Plasma sentrygun recharges it's ammo
+//-----------------------------------------------------------------------------
+void CObjectSentrygunPlasma::CheckShield( void )
+{
+ // ROBIN: Disabled recharging for now
+ /*
+ if ( m_flNextAmmoRecharge < gpGlobals->curtime )
+ {
+ if ( m_iAmmo < m_iMaxAmmo )
+ {
+ m_iAmmo++;
+ }
+
+ m_flNextAmmoRecharge = gpGlobals->curtime + PLASMA_SENTRYGUN_RECHARGE_TIME;
+ }
+ */
+
+ BaseClass::CheckShield();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Rocket launcher sentrygun's fire
+//-----------------------------------------------------------------------------
+bool CObjectSentrygunRocketlauncher::Fire()
+{
+ Vector vecSrc = EyePosition();
+ Vector vecTarget = m_vecFireTarget;
+ Vector vecAim;
+
+ // Get the distance to the target
+ float targetDist = (vecTarget - vecSrc).Length();
+ float targetTime = targetDist / ROCKET_VELOCITY;
+
+ // If we're not suppressing, calculate where the target's going to be in that time
+ if ( !m_bSuppressing )
+ {
+ Vector vecVelocity = m_hEnemy->GetSmoothedVelocity();
+ // Dampen velocity to prevent people rapidly switching strafe
+ vecVelocity *= 0.5;
+ // Dampen Z velocity to prevent jumping people screwing the aim
+ vecVelocity.z *= 0.5;
+ // Get the target point to aim for
+ Vector vecEnd = vecTarget + ( vecVelocity * targetTime );
+ vecAim = (vecEnd - vecSrc);
+ }
+ else
+ {
+ vecAim = (vecTarget - vecSrc);
+ }
+
+ CGrenadeRocket::Create( vecSrc, vecAim, edict(), GetOwner() );
+
+ EmitSound( "ObjectSentrygunRocketlauncher.Fire" );
+
+ m_iAmmo -= 1;
+
+ m_flNextAttack = gpGlobals->curtime + 1.5f;
+ return true;
+}
+
+
+void CObjectSentrygunRocketlauncher::SetTechnology( bool bSmarter, bool bSensors )
+{
+ BaseClass::SetTechnology( bSmarter, bSensors );
+ m_iBaseTurnRate = 2;
+}
diff --git a/game/server/tf2/tf_obj_sentrygun.h b/game/server/tf2/tf_obj_sentrygun.h
new file mode 100644
index 0000000..aabe44b
--- /dev/null
+++ b/game/server/tf2/tf_obj_sentrygun.h
@@ -0,0 +1,190 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Defender's sentrygun
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef TF_OBJ_SENTRYGUN_H
+#define TF_OBJ_SENTRYGUN_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include "tf_obj.h"
+
+enum TFTURRET_ANIM
+{
+ TFTURRET_ANIM_NONE = 0,
+ TFTURRET_ANIM_FIRE,
+ TFTURRET_ANIM_SPIN,
+};
+
+enum target_ranges
+{
+ RANGE_NEAR,
+ RANGE_MID,
+ RANGE_FAR,
+};
+
+// Sentrygun damages
+#define SG_MACHINEGUN_DAMAGE 5
+
+// ------------------------------------------------------------------------ //
+// The Base Sentrygun
+// ------------------------------------------------------------------------ //
+class CObjectSentrygun : public CBaseObject
+{
+ DECLARE_CLASS( CObjectSentrygun, CBaseObject );
+public:
+ DECLARE_DATADESC();
+ DECLARE_SERVERCLASS();
+
+ CObjectSentrygun();
+
+ static CObjectSentrygun* Create(const Vector &vOrigin, const QAngle &vAngles, int iType);
+
+ virtual void Spawn();
+ virtual void Precache();
+ virtual void SetupAttachedVersion( void );
+ virtual void SetupUnattachedVersion( void );
+ virtual void GetControlPanelInfo( int nPanelIndex, const char *&pPanelName );
+ virtual void FinishedBuilding( void );
+ virtual bool IsSentrygun( void ) { return true; };
+ virtual bool WantsCoverFromSentryGun() { return false; }
+ virtual void SetTechnology( bool bSmarter, bool bSensors );
+ virtual int OnTakeDamage( const CTakeDamageInfo &info );
+ virtual void Killed( void );
+
+ // Ammo filling
+ virtual bool ClientCommand( CBaseTFPlayer *pPlayer, const CCommand &args );
+ virtual bool TakeAmmoFrom( CBaseTFPlayer *pPlayer );
+
+ // Think functions
+ void SentryRotate(void);
+ void Attack(void);
+ void Sentry_Explode( void );
+ bool FInViewCone( CBaseEntity *pEntity );
+ int BloodColor( void ) { return DONT_BLEED; }
+
+ virtual void CheckShield( void );
+ void RestartAnimation();
+ void ResetOrientation();
+
+ virtual void SetSentryAnim( TFTURRET_ANIM anim );
+ virtual CBaseEntity *FindTarget( void );
+ virtual float GetPriority( CBaseEntity *pTarget );
+ virtual void FoundTarget();
+ virtual bool ValidTarget( CBaseEntity *pTarget );
+ virtual int Range( CBaseEntity *pTarget );
+
+ // Combat functions
+ virtual bool HasAmmo( void );
+ virtual bool Fire( void );
+ virtual bool WillSuppress( void ) { return true; };
+
+ virtual bool CanTakeEMPDamage( void ) { return true; }
+
+ // Turret Functions
+ bool MoveTurret( void );
+
+ // Object functions
+ void ObjectMoved( void );
+
+ // Designator interactions
+ void DesignateTarget( CBaseEntity *pTarget );
+ // Turtle mode
+ bool IsTurtled( void );
+ bool IsTurtling( void ); // Return true if we're in the process of turtling / unturtling
+ void ToggleTurtle( void );
+ void Turtle( void );
+ void UnTurtle( void );
+
+private:
+ // Recompute sentrygun orientation...
+ void RecomputeOrientation();
+
+public:
+ // Variables
+ int m_iRightBound;
+ int m_iLeftBound;
+ bool m_bTurningRight;
+ int m_iShardIndex;
+ int m_iAmmoType;
+ bool m_bSmarter;
+ bool m_bSensors;
+ float m_flNextLook;
+
+ // Attacking
+ float m_flNextAttack;
+ CNetworkVar( int, m_iAmmo );
+ int m_iMaxAmmo;
+ Vector m_vecFireTarget;
+ Vector m_vecLastKnownPosition;
+ bool m_bSuppressing;
+ float m_flStartedSuppressing;
+
+ // Movement
+ CNetworkVar( int, m_iBaseTurnRate );
+ float m_fTurnRate;
+ QAngle m_vecCurAngles;
+ QAngle m_vecGoalAngles;
+ Vector m_vecCurDishAngles;
+
+ // Turtling
+ CNetworkVar( bool, m_bTurtled );
+ bool m_bTurtling;
+ float m_flTurtlingFinishedAt;
+
+ // Data sent to clients
+ // Bone controllers
+ float m_fBoneXRotator;
+ float m_fBoneYRotator;
+
+ // Target data
+ CNetworkHandle( CBaseEntity, m_hEnemy );
+ EHANDLE m_hDesignatedEnemy;
+
+ CNetworkVar( int, m_nAnimationParity );
+ CNetworkVar( int, m_nOrientationParity );
+};
+
+//-----------------------------------------------------------------------------
+// Purpose: Plasma Sentrygun
+//-----------------------------------------------------------------------------
+class CObjectSentrygunPlasma : public CObjectSentrygun
+{
+ DECLARE_CLASS( CObjectSentrygunPlasma, CObjectSentrygun );
+public:
+ DECLARE_SERVERCLASS();
+ virtual void Spawn();
+ virtual bool Fire( void );
+ virtual void CheckShield( void );
+ virtual bool HasAmmo( void );
+
+private:
+ float m_flNextAmmoRecharge;
+ int m_nBurstCount;
+};
+
+#define SG_PLASMA_MODEL "models/sentry2.mdl"
+#define PLASMA_SENTRYGUN_RECHARGE_TIME 1.25 // Time it takes to recharge 1 round of ammo
+#define PLASMA_SENTRY_BURST_COUNT 4
+
+//-----------------------------------------------------------------------------
+// Purpose: Rocket launcher Sentrygun
+//-----------------------------------------------------------------------------
+class CObjectSentrygunRocketlauncher : public CObjectSentrygun
+{
+ DECLARE_CLASS( CObjectSentrygunRocketlauncher, CObjectSentrygun );
+public:
+ DECLARE_SERVERCLASS();
+ virtual void Spawn();
+ virtual bool Fire( void );
+
+ virtual void SetTechnology( bool bSmarter, bool bSensors );
+};
+
+#define SG_ROCKETLAUNCHER_MODEL "models/sentry3.mdl"
+
+#endif // TF_OBJ_SENTRYGUN_H
diff --git a/game/server/tf2/tf_obj_shieldwall.cpp b/game/server/tf2/tf_obj_shieldwall.cpp
new file mode 100644
index 0000000..37ccc47
--- /dev/null
+++ b/game/server/tf2/tf_obj_shieldwall.cpp
@@ -0,0 +1,270 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Medic's resupply beacon
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#include "cbase.h"
+#include "tf_obj.h"
+#include "tf_player.h"
+#include "tf_team.h"
+#include "techtree.h"
+#include "tf_shield.h"
+#include "VGuiScreen.h"
+
+//-----------------------------------------------------------------------------
+// Shield wall defines
+//-----------------------------------------------------------------------------
+
+#define SHIELDWALL_MINS Vector(-20, -20, 0)
+#define SHIELDWALL_MAXS Vector( 20, 20, 100)
+
+#define SHIELD_WALL_PITCH -10.0f
+
+
+ConVar obj_shieldwall_health( "obj_shieldwall_health","200", FCVAR_NONE, "Shield wall health" );
+
+
+//-----------------------------------------------------------------------------
+// Shield wall object that's built by the player
+//-----------------------------------------------------------------------------
+class CObjectShieldWallBase : public CBaseObject
+{
+DECLARE_CLASS( CObjectShieldWallBase, CBaseObject );
+
+public:
+ CObjectShieldWallBase();
+
+ virtual void UpdateOnRemove( void );
+
+ virtual void Spawn();
+
+ virtual void PowerupStart( int iPowerup, float flAmount, CBaseEntity *pAttacker, CDamageModifier *pDamageModifier );
+ virtual void PowerupEnd( int iPowerup );
+
+ // Team change
+ virtual void ChangeTeam( int nTeamNumber ) OVERRIDE;
+
+public:
+ CNetworkHandle( CShield, m_hDeployedShield );
+};
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CObjectShieldWallBase::CObjectShieldWallBase()
+{
+ m_hDeployedShield.Set(0);
+ AddEFlags( EFL_FORCE_CHECK_TRANSMIT );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CObjectShieldWallBase::UpdateOnRemove( void )
+{
+ if ( m_hDeployedShield.Get() )
+ {
+ UTIL_Remove( m_hDeployedShield );
+ m_hDeployedShield.Set( NULL );
+ }
+
+ // Chain at end to mimic destructor unwind order
+ BaseClass::UpdateOnRemove();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CObjectShieldWallBase::Spawn()
+{
+ m_takedamage = DAMAGE_YES;
+
+ SetType( OBJ_SHIELDWALL );
+
+ BaseClass::Spawn();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Powerup has just started
+//-----------------------------------------------------------------------------
+void CObjectShieldWallBase::PowerupStart( int iPowerup, float flAmount, CBaseEntity *pAttacker, CDamageModifier *pDamageModifier )
+{
+ switch( iPowerup )
+ {
+ case POWERUP_BOOST:
+ // Increase our shield's energy
+ if ( m_hDeployedShield )
+ {
+ m_hDeployedShield->SetPower( m_hDeployedShield->GetPower() + (flAmount * 3) );
+ }
+ break;
+
+ case POWERUP_EMP:
+ if (m_hDeployedShield)
+ {
+ m_hDeployedShield->SetEMPed(true);
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ BaseClass::PowerupStart( iPowerup, flAmount, pAttacker, pDamageModifier );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Powerup has just started
+//-----------------------------------------------------------------------------
+void CObjectShieldWallBase::PowerupEnd( int iPowerup )
+{
+ switch ( iPowerup )
+ {
+ case POWERUP_EMP:
+ if (m_hDeployedShield)
+ {
+ m_hDeployedShield->SetEMPed(false);
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ BaseClass::PowerupEnd( iPowerup );
+}
+
+
+//-----------------------------------------------------------------------------
+// Team change
+//-----------------------------------------------------------------------------
+void CObjectShieldWallBase::ChangeTeam( int nTeamNumber )
+{
+ BaseClass::ChangeTeam( nTeamNumber );
+ if ( m_hDeployedShield )
+ {
+ m_hDeployedShield->ChangeTeam( nTeamNumber );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Shield wall object that's built by the player
+//-----------------------------------------------------------------------------
+class CObjectShieldWall : public CObjectShieldWallBase
+{
+DECLARE_CLASS( CObjectShieldWall, CObjectShieldWallBase );
+
+public:
+ DECLARE_SERVERCLASS();
+
+ CObjectShieldWall();
+
+ virtual void Spawn();
+ virtual void Precache();
+ virtual void GetControlPanelInfo( int nPanelIndex, const char *&pPanelName );
+ virtual void ObjectMoved( );
+ virtual void FinishedBuilding( void );
+};
+
+IMPLEMENT_SERVERCLASS_ST(CObjectShieldWall, DT_ObjectShieldWall)
+ SendPropEHandle(SENDINFO(m_hDeployedShield)),
+END_SEND_TABLE();
+
+LINK_ENTITY_TO_CLASS(obj_shieldwall, CObjectShieldWall);
+PRECACHE_REGISTER(obj_shieldwall);
+
+CObjectShieldWall::CObjectShieldWall()
+{
+ UseClientSideAnimation();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CObjectShieldWall::Spawn()
+{
+ SetModel( "models/objects/obj_shieldwall.mdl" );
+ SetSolid( SOLID_BBOX );
+ UTIL_SetSize(this, SHIELDWALL_MINS, SHIELDWALL_MAXS);
+ m_iHealth = obj_shieldwall_health.GetInt();
+ m_hDeployedShield = NULL;
+
+ SetType( OBJ_SHIELDWALL );
+
+ BaseClass::Spawn();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CObjectShieldWall::Precache()
+{
+ PrecacheModel( "models/objects/obj_shieldwall.mdl" );
+ PrecacheVGuiScreen( "screen_obj_shieldwall" );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Gets info about the control panels
+//-----------------------------------------------------------------------------
+void CObjectShieldWall::GetControlPanelInfo( int nPanelIndex, const char *&pPanelName )
+{
+ pPanelName = "screen_obj_shieldwall";
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CObjectShieldWall::FinishedBuilding( void )
+{
+ BaseClass::FinishedBuilding();
+
+ int nAttachmentIndex = LookupAttachment( "projectionpoint" );
+
+ m_hDeployedShield = CreateMobileShield( this );
+ m_hDeployedShield->SetAlwaysOrient( false );
+ m_hDeployedShield->SetAttachmentIndex( nAttachmentIndex );
+ m_hDeployedShield->SetAngularSpringConstant( 20 );
+ ObjectMoved();
+}
+
+
+//-----------------------------------------------------------------------------
+// Called when the builder rotates this object...
+//-----------------------------------------------------------------------------
+void CObjectShieldWall::ObjectMoved( )
+{
+ if (m_hDeployedShield)
+ {
+ VMatrix matangles;
+ VMatrix matoffset;
+ VMatrix matfinal;
+
+ // This represents how much to pitch the shield up from the attachment point
+ QAngle angleOffset( SHIELD_WALL_PITCH, 0, 0 );
+
+ // Get the location and angles of the attachment point
+ // Attachment point position is the origin of the shield
+ QAngle angles;
+
+ // Rotate the angles of the attachment point by the angle offset
+ MatrixFromAngles( GetAbsAngles(), matangles );
+ MatrixFromAngles( angleOffset, matoffset );
+ MatrixMultiply( matangles, matoffset, matfinal );
+ MatrixToAngles( matfinal, angles );
+ m_hDeployedShield->SetCenterAngles( angles );
+
+ m_hDeployedShield->ShieldMoved();
+ }
+ BaseClass::ObjectMoved();
+}
+
+
+
+
diff --git a/game/server/tf2/tf_obj_shieldwall.h b/game/server/tf2/tf_obj_shieldwall.h
new file mode 100644
index 0000000..f78f242
--- /dev/null
+++ b/game/server/tf2/tf_obj_shieldwall.h
@@ -0,0 +1,13 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Medic's resupply beacon
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef TF_OBJ_SHIELDWALL_H
+#define TF_OBJ_SHIELDWALL_H
+
+#define SHIELDWALL_RANGE 300.0f
+
+#endif // TF_OBJ_SHIELDWALL_H
diff --git a/game/server/tf2/tf_obj_tower.cpp b/game/server/tf2/tf_obj_tower.cpp
new file mode 100644
index 0000000..6ea8a23
--- /dev/null
+++ b/game/server/tf2/tf_obj_tower.cpp
@@ -0,0 +1,175 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: A stationary tower that players can take cover in.
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#include "cbase.h"
+#include "tf_obj.h"
+#include "tf_obj_tower.h"
+#include "tf_team.h"
+#include "tf_obj.h"
+#include "engine/IEngineSound.h"
+
+#define TOWER_MINS Vector(-100, -100, 0)
+#define TOWER_MAXS Vector( 100, 100, 200)
+#define TOWER_MODEL "models/objects/obj_tower.mdl"
+#define TOWER_LADDER_MODEL "models/objects/obj_tower_ladder.mdl"
+
+IMPLEMENT_SERVERCLASS_ST(CObjectTower, DT_ObjectTower)
+END_SEND_TABLE();
+
+LINK_ENTITY_TO_CLASS(obj_tower, CObjectTower);
+PRECACHE_REGISTER(obj_tower);
+
+IMPLEMENT_SERVERCLASS_ST( CObjectTowerLadder, DT_ObjectTowerLadder )
+END_SEND_TABLE();
+
+LINK_ENTITY_TO_CLASS( obj_tower_ladder, CObjectTowerLadder );
+PRECACHE_REGISTER( obj_tower_ladder );
+
+// CVars
+ConVar obj_tower_health( "obj_tower_health","100", FCVAR_NONE, "Tower health" );
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CObjectTower::CObjectTower( void )
+{
+ m_hLadder = NULL;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CObjectTower::Spawn( void )
+{
+ Precache();
+ SetModel( TOWER_MODEL );
+ SetSolid( SOLID_BBOX );
+
+ UTIL_SetSize(this, TOWER_MINS, TOWER_MAXS);
+ m_takedamage = DAMAGE_YES;
+ m_iHealth = obj_tower_health.GetInt();
+
+ m_fObjectFlags |= OF_DONT_PREVENT_BUILD_NEAR_OBJ | OF_DOESNT_NEED_POWER;
+ SetType( OBJ_TOWER );
+
+ SetSolid( SOLID_VPHYSICS );
+ VPhysicsInitStatic();
+
+ BaseClass::Spawn();
+
+ SetCollisionGroup( COLLISION_GROUP_VEHICLE );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CObjectTower::Precache( void )
+{
+ PrecacheModel( TOWER_MODEL );
+ PrecacheModel( TOWER_LADDER_MODEL );
+}
+
+//------------------------------------------------------------------------------
+// Purpose:
+//------------------------------------------------------------------------------
+void CObjectTower::UpdateOnRemove( void )
+{
+ if ( m_hLadder.Get() )
+ {
+ UTIL_Remove( m_hLadder );
+ m_hLadder = NULL;
+ }
+
+ // Chain at end to mimic destructor unwind order
+ BaseClass::UpdateOnRemove();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CObjectTower::FinishedBuilding( void )
+{
+ BaseClass::FinishedBuilding();
+
+ // Create the ladder.
+ Vector vecOrigin;
+ QAngle vecAngles;
+ GetAttachment( "ladder", vecOrigin, vecAngles );
+ m_hLadder = CObjectTowerLadder::Create( vecOrigin, vecAngles, this );
+ m_hLadder->ChangeTeam( GetTeamNumber() );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Gets info about the control panels
+//-----------------------------------------------------------------------------
+void CObjectTower::GetControlPanelInfo( int nPanelIndex, const char *&pPanelName )
+{
+ pPanelName = "screen_basic_with_disable";
+}
+
+
+//==============================================================================
+// Tower Ladder
+//==============================================================================
+
+//------------------------------------------------------------------------------
+// Purpose:
+//------------------------------------------------------------------------------
+CObjectTowerLadder::CObjectTowerLadder()
+{
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CObjectTowerLadder *CObjectTowerLadder::Create( const Vector &vOrigin, const QAngle &vAngles, CBaseEntity *pParent )
+{
+ CObjectTowerLadder *pLadder = static_cast<CObjectTowerLadder*>( CBaseObject::Create( "obj_tower_ladder", vOrigin, vAngles ) );
+ if ( pLadder )
+ {
+ pLadder->m_hTower = pParent;
+ }
+
+ return pLadder;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CObjectTowerLadder::Spawn()
+{
+ Precache();
+ SetModel( TOWER_LADDER_MODEL );
+ SetSolid( SOLID_VPHYSICS );
+ m_takedamage = DAMAGE_NO;
+
+ BaseClass::Spawn();
+
+ CollisionProp()->SetSurroundingBoundsType( USE_BEST_COLLISION_BOUNDS );
+ IPhysicsObject *pPhysics = VPhysicsInitStatic();
+ if ( pPhysics )
+ {
+ pPhysics->EnableMotion( false );
+ }
+ SetCollisionGroup( COLLISION_GROUP_VEHICLE );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CObjectTowerLadder::Precache()
+{
+ PrecacheModel( TOWER_LADDER_MODEL );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Pass all damage back to the tower
+//-----------------------------------------------------------------------------
+int CObjectTowerLadder::OnTakeDamage( const CTakeDamageInfo &info )
+{
+ return m_hTower->OnTakeDamage( info );
+} \ No newline at end of file
diff --git a/game/server/tf2/tf_obj_tower.h b/game/server/tf2/tf_obj_tower.h
new file mode 100644
index 0000000..79eb503
--- /dev/null
+++ b/game/server/tf2/tf_obj_tower.h
@@ -0,0 +1,61 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: A stationary tower that players can take cover in.
+//
+//=============================================================================//
+
+#ifndef TF_OBJ_TOWER_H
+#define TF_OBJ_TOWER_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+class CObjectTowerLadder;
+
+// ------------------------------------------------------------------------ //
+// Purpose: A stationary tower that players can take cover in.
+// ------------------------------------------------------------------------ //
+class CObjectTower : public CBaseObject
+{
+ DECLARE_CLASS( CObjectTower, CBaseObject );
+
+public:
+ DECLARE_SERVERCLASS();
+
+ CObjectTower( void );
+
+ virtual void Spawn( void );
+ virtual void Precache( void );
+ virtual void UpdateOnRemove( void );
+ virtual void GetControlPanelInfo( int nPanelIndex, const char *&pPanelName );
+ virtual void FinishedBuilding( void );
+
+private:
+ CHandle<CObjectTowerLadder> m_hLadder;
+};
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Ladder tower
+//-----------------------------------------------------------------------------
+class CObjectTowerLadder : public CBaseAnimating
+{
+ DECLARE_CLASS( CObjectTowerLadder, CBaseAnimating );
+
+public:
+
+ DECLARE_SERVERCLASS();
+
+ static CObjectTowerLadder* Create( const Vector &vOrigin, const QAngle &vAngles, CBaseEntity *pParent );
+
+ CObjectTowerLadder();
+
+ void Spawn();
+ void Precache();
+ virtual int OnTakeDamage( const CTakeDamageInfo &info );
+
+public:
+ EHANDLE m_hTower;
+};
+
+#endif // TF_OBJ_TOWER_H
diff --git a/game/server/tf2/tf_obj_tunnel.cpp b/game/server/tf2/tf_obj_tunnel.cpp
new file mode 100644
index 0000000..c00261e
--- /dev/null
+++ b/game/server/tf2/tf_obj_tunnel.cpp
@@ -0,0 +1,571 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+#include "cbase.h"
+#include "tf_obj.h"
+#include "tf_obj_mapdefined.h"
+#include "engine/IEngineSound.h"
+#include "entityoutput.h"
+#include "tf_shareddefs.h"
+#include "triggers.h"
+#include "shake.h"
+#include "tf_player.h"
+#include "tf_gamerules.h"
+
+#define TUNNEL_THINK_INTERVAL 0.1f
+#define TUNNEL_FADE_TIME 1.0f
+#define MAX_TUNNEL_DURATION 30.0f
+// If tunneling takes longer than this, use a countdown
+#define TUNNEL_DURATION_MESSAGE_NEEDED 3.0f
+
+// It takes this long to tunnel
+static ConVar tf_tunnel_time( "tf_tunnel_time", "2", 0, "Takes this long to traverse a tunnel." );
+
+class CObjectTunnel : public CObjectMapDefined
+{
+ DECLARE_CLASS( CObjectTunnel, CObjectMapDefined );
+public:
+
+ DECLARE_SERVERCLASS();
+
+ virtual void Spawn( void );
+ virtual void Killed( void );
+
+ int UpdateTransmitState();
+
+private:
+};
+
+IMPLEMENT_SERVERCLASS_ST(CObjectTunnel, DT_ObjectTunnel)
+END_SEND_TABLE();
+
+int CObjectTunnel::UpdateTransmitState()
+{
+ return SetTransmitState( FL_EDICT_ALWAYS );
+}
+
+void CObjectTunnel::Spawn( void )
+{
+ BaseClass::Spawn();
+
+ AddFlag( FL_NOTARGET );
+ SetType( OBJ_TUNNEL );
+}
+
+LINK_ENTITY_TO_CLASS(obj_tunnel,CObjectTunnel);
+LINK_ENTITY_TO_CLASS(obj_tunnel_prop,CObjectTunnel);
+
+//-----------------------------------------------------------------------------
+// Purpose: Object has been blown up. Tunnels are never fully destroyed, so they stay on the minimap.
+//-----------------------------------------------------------------------------
+void CObjectTunnel::Killed( void )
+{
+ m_bDying = true;
+
+ RemoveAllSappers( this );
+
+ // Do an explosion.
+ CPASFilter filter( GetAbsOrigin() );
+ te->Explosion(
+ filter,
+ 0.0,
+ &GetAbsOrigin(),
+ g_sModelIndexFireball,
+ 5.4, // radius
+ 15,
+ TE_EXPLFLAG_NODLIGHTS,
+ 256,
+ 200);
+
+ // Become non-solid and invisible
+ VPhysicsDestroyObject();
+ AddSolidFlags( FSOLID_NOT_SOLID );
+ m_takedamage = DAMAGE_NO;
+ AddEffects( EF_NODRAW );
+}
+
+class CInfoTunnelExit : public CPointEntity
+{
+public:
+ DECLARE_CLASS( CInfoTunnelExit, CPointEntity );
+private:
+};
+
+LINK_ENTITY_TO_CLASS(info_tunnel_exit,CInfoTunnelExit);
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+class CObjectTunnelTrigger : public CBaseTrigger
+{
+ DECLARE_CLASS( CObjectTunnelTrigger, CBaseTrigger );
+public:
+ CObjectTunnelTrigger();
+
+ DECLARE_DATADESC();
+
+ virtual void Precache();
+ virtual void Spawn();
+ virtual void Activate();
+
+ virtual void StartTouch( CBaseEntity *pOther );
+
+ void SetActive( bool active );
+ bool GetActive( void ) const;
+
+ void InputSetActive( inputdata_t &inputdata );
+ void InputSetInactive( inputdata_t &inputdata );
+ void InputToggleActive( inputdata_t &inputdata );
+
+ void InputSetTarget( inputdata_t &inputdata );
+ void InputSetTeleportDuration( inputdata_t &inputdata );
+ void InputSetTeleportVelocity( inputdata_t &inputdata );
+
+ virtual void TunnelThink();
+private:
+ float GetTeleportDuration( void );
+
+ bool m_bActive;
+ CHandle< CInfoTunnelExit > m_hTunnelExit;
+
+ COutputEvent OnTunnelTriggerStart;
+ COutputEvent OnTunnelTriggerEnd;
+
+ struct TunnelPlayer
+ {
+ CHandle< CBaseTFPlayer > player;
+ Vector startpos;
+ float tunnelstarted;
+ float duration;
+ float teleporttime;
+ float fadeintime;
+ bool exitstarted;
+ float fadetime;
+ int iremaining;
+ int ilastremaining;
+ bool needremainigcounter;
+ };
+
+ CUtlVector< TunnelPlayer > m_Tunneling;
+
+ void StartTunneling( CBaseTFPlayer *player );
+ bool KeepTunneling( TunnelPlayer *tunnel );
+
+
+ float m_flTeleportDuration;
+ float m_flTeleportVelocity;
+};
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CObjectTunnelTrigger::CObjectTunnelTrigger()
+{
+ m_flTeleportDuration = -1.0f;
+ m_flTeleportVelocity = 0.0f;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : *player -
+//-----------------------------------------------------------------------------
+void CObjectTunnelTrigger::StartTunneling( CBaseTFPlayer *player )
+{
+ if ( !player )
+ return;
+
+ // Ignore if it's already in the list
+ int c = m_Tunneling.Count();
+ for ( int i = 0 ; i < c; i++ )
+ {
+ TunnelPlayer *tp = &m_Tunneling[ i ];
+ if ( tp->player == player )
+ {
+ return;
+ }
+ }
+
+ TunnelPlayer tunnel;
+ tunnel.player = player;
+ tunnel.tunnelstarted = gpGlobals->curtime;
+ tunnel.duration = GetTeleportDuration();
+ tunnel.teleporttime = tunnel.tunnelstarted + tunnel.duration;
+ tunnel.exitstarted = false;
+ tunnel.startpos = player->GetAbsOrigin();
+ tunnel.iremaining = (int)tunnel.duration;
+ tunnel.ilastremaining = tunnel.iremaining;
+ tunnel.needremainigcounter = ( tunnel.iremaining > TUNNEL_DURATION_MESSAGE_NEEDED ) ? true : false;
+
+ // Fade user screen to black
+ color32 black = {0,0,0,255};
+
+ float duration = tunnel.duration;
+ float fadeouttime = TUNNEL_FADE_TIME;
+ float holdtime = 0.0f;
+ if ( duration < 2 * TUNNEL_FADE_TIME )
+ {
+ fadeouttime = duration * 0.5f;
+ }
+ else
+ {
+ fadeouttime = TUNNEL_FADE_TIME;
+ holdtime = duration - 2 * fadeouttime;
+ }
+
+ tunnel.fadetime = fadeouttime;
+ tunnel.fadeintime = tunnel.tunnelstarted + fadeouttime + holdtime;
+
+ UTIL_ScreenFade( player, black, fadeouttime, holdtime, FFADE_OUT | FFADE_STAYOUT | FFADE_PURGE );
+
+ m_Tunneling.AddToTail( tunnel );
+
+ player->SetMoveType( MOVETYPE_NONE );
+ player->EnableControl( false );
+ player->AddEffects( EF_NODRAW );
+ player->AddSolidFlags( FSOLID_NOT_SOLID );
+
+ CPASAttenuationFilter filter( player, "ObjectTunnelTrigger.TeleportSound" );
+ EmitSound( filter, player->entindex(), "ObjectTunnelTrigger.TeleportSound" );
+
+ OnTunnelTriggerStart.FireOutput( player, this );
+}
+
+float CObjectTunnelTrigger::GetTeleportDuration( void )
+{
+ float duration = m_flTeleportDuration;
+
+ if ( m_flTeleportVelocity > 0.0f && m_hTunnelExit != NULL )
+ {
+ Vector delta = m_hTunnelExit->GetAbsOrigin() - GetAbsOrigin();
+ float dist = delta.Length();
+ duration = dist / m_flTeleportVelocity;
+ }
+ else if ( m_flTeleportDuration == -1.0f )
+ {
+ Msg( "obj_tunnel_trigger: must set TeleportVelocity or TeleportDuration" );
+ m_flTeleportDuration = tf_tunnel_time.GetFloat();
+ }
+
+ duration = MIN( duration, MAX_TUNNEL_DURATION );
+ return duration;
+}
+
+bool CObjectTunnelTrigger::KeepTunneling( TunnelPlayer *tunnel )
+{
+ if ( !tunnel || ( tunnel->player == NULL ) )
+ {
+ return false;
+ }
+
+ float remaining = tunnel->teleporttime - gpGlobals->curtime + 0.5f;
+ remaining = MAX( 0.0f, remaining );
+
+ tunnel->iremaining = (int)( remaining );
+
+ if ( !tunnel->exitstarted )
+ {
+ if ( gpGlobals->curtime > tunnel->fadeintime )
+ {
+ tunnel->exitstarted = true;
+ // Fade user screen to black
+ color32 black = {0,0,0,255};
+ UTIL_ScreenFade( tunnel->player, black, tunnel->fadetime, 0.0, FFADE_IN | FFADE_PURGE );
+
+ // Move to tunnel exit spot now that we're half-way through teleport
+ if ( m_hTunnelExit != NULL )
+ {
+ tunnel->player->EnableControl( true );
+ tunnel->player->RemoveEffects( EF_NODRAW );
+
+ // Change the player to non-solid before the teleport, so the physics system doesn't think he
+ // actually moved this distance:
+ int OriginalSolidFlags = tunnel->player->GetSolidFlags();
+ tunnel->player->AddSolidFlags( FSOLID_NOT_SOLID);
+
+ // Do a placement test to prevent the player from teleporting inside another player, the ground, or just to help
+ // prevent badly placed tunnels from causing stuck situations.
+ Vector vTarget = m_hTunnelExit->GetAbsOrigin();
+ Vector vOriginal = vTarget;
+
+ if ( !EntityPlacementTest( tunnel->player, vOriginal, vTarget, true ) )
+ {
+ Warning("Couldn't place entity after tunnel teleport.\n");
+ }
+
+
+ tunnel->player->Teleport( &vTarget /*m_hTunnelExit->GetAbsOrigin()*/, &m_hTunnelExit->GetAbsAngles(), NULL );
+ tunnel->player->SnapEyeAngles( m_hTunnelExit->GetAbsAngles() );
+ tunnel->player->SetAbsVelocity( vec3_origin );
+
+ // Restore the player's solid flags.
+ tunnel->player->SetSolidFlags(OriginalSolidFlags);
+
+ }
+ }
+// Can't quite do this because the player's weapons are still visible flying across the map even if
+// he is hidden
+#if 0
+ else if ( gpGlobals->curtime > tunnel->tunnelstarted + tunnel->fadetime )
+ {
+ float travel_time = tunnel->duration - 2 * tunnel->fadetime;
+ if ( travel_time > 0.0f )
+ {
+ float f = ( gpGlobals->curtime - tunnel->tunnelstarted - tunnel->fadetime ) / travel_time;
+ f = clamp( f, 0.0f, 1.0f );
+ if ( m_hTunnelExit != NULL )
+ {
+ Vector delta = m_hTunnelExit->GetAbsOrigin() - tunnel->startpos;
+ Vector currentPos;
+ VectorMA( tunnel->startpos, f, delta, currentPos );
+
+ tunnel->player->Teleport( &currentPos, NULL, NULL );
+ }
+ }
+ }
+#endif
+ }
+
+ if ( tunnel->ilastremaining != tunnel->iremaining &&
+ tunnel->needremainigcounter &&
+ tunnel->iremaining >= 1 &&
+ tunnel->player != NULL )
+ {
+ // Counter
+ ClientPrint( tunnel->player, HUD_PRINTCENTER, UTIL_VarArgs("\nExiting tunnel in %d %s\n", tunnel->iremaining, tunnel->iremaining > 1 ? "seconds" : "second" ) );
+ }
+
+ tunnel->ilastremaining = tunnel->iremaining;
+
+ // TODO: Play footstep or some other teleport sounds occasionaly to this player?
+
+ bool done = ( gpGlobals->curtime > tunnel->teleporttime ) ? true : false;
+ if ( done )
+ {
+ color32 black = {0,0,0,255};
+ UTIL_ScreenFade( tunnel->player, black, 0.0f, 0.0f, FFADE_IN | FFADE_PURGE );
+
+ tunnel->player->SetMoveType( MOVETYPE_WALK );
+ tunnel->player->EnableControl( true );
+ tunnel->player->RemoveEffects( EF_NODRAW );
+ tunnel->player->RemoveSolidFlags( FSOLID_NOT_SOLID );
+
+ // TODO: Play an exit sound??
+ OnTunnelTriggerEnd.FireOutput( tunnel->player, this );
+ }
+
+ return !done;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CObjectTunnelTrigger::TunnelThink()
+{
+ // Make sure it's not already in the list
+ int c = m_Tunneling.Count();
+ for ( int i = c - 1; i >= 0; i-- )
+ {
+ TunnelPlayer *tp = &m_Tunneling[ i ];
+
+ if ( !KeepTunneling( tp ) )
+ {
+ m_Tunneling.Remove( i );
+ }
+ }
+
+ SetNextThink( gpGlobals->curtime + TUNNEL_THINK_INTERVAL );
+}
+
+void CObjectTunnelTrigger::Precache()
+{
+ BaseClass::Precache();
+
+ PrecacheScriptSound( "ObjectTunnelTrigger.TeleportSound" );
+ PrecacheScriptSound( "ObjectTunnelTrigger.DisabledSound" );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CObjectTunnelTrigger::Spawn()
+{
+ Precache();
+
+ SetSolid( SOLID_BSP );
+ AddSolidFlags( FSOLID_TRIGGER );
+ SetMoveType( MOVETYPE_NONE );
+ AddEffects( EF_NODRAW );
+ SetModel( STRING( GetModelName() ) );
+ AddFlag( FL_NOTARGET );
+
+ m_bActive = false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: See if we've got a gather point specified
+//-----------------------------------------------------------------------------
+void CObjectTunnelTrigger::Activate( void )
+{
+ BaseClass::Activate();
+
+ if (m_target != NULL_STRING)
+ {
+ CBaseEntity *pEnt = gEntList.FindEntityByName( NULL, m_target );
+ if ( pEnt && FClassnameIs( pEnt, "info_tunnel_exit" ) )
+ {
+ m_hTunnelExit = static_cast< CInfoTunnelExit * >( pEnt );
+ }
+ else
+ {
+ Msg( "CObjectTunnelTrigger::Activate, unable to connect tunnel to target %s\n",
+ STRING( m_target ) );
+ }
+ }
+ else
+ {
+ Msg( "CObjectTunnelTrigger::Activate, missing target\n" );
+ }
+
+ SetActive( true );
+
+ SetThink( TunnelThink );
+ SetNextThink( gpGlobals->curtime + TUNNEL_THINK_INTERVAL );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : active -
+//-----------------------------------------------------------------------------
+void CObjectTunnelTrigger::SetActive( bool active )
+{
+ m_bActive = active;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CObjectTunnelTrigger::GetActive( void ) const
+{
+ return m_bActive;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CObjectTunnelTrigger::InputSetActive( inputdata_t &inputdata )
+{
+ SetActive( true );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CObjectTunnelTrigger::InputSetInactive( inputdata_t &inputdata )
+{
+ SetActive( false );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CObjectTunnelTrigger::InputToggleActive( inputdata_t &inputdata )
+{
+ if ( m_bActive )
+ {
+ SetActive( false );
+ }
+ else
+ {
+ SetActive( true );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : &inputdata -
+//-----------------------------------------------------------------------------
+void CObjectTunnelTrigger::InputSetTarget( inputdata_t &inputdata )
+{
+ CBaseEntity *pEnt = gEntList.FindEntityByName( NULL, inputdata.value.String() );
+ if ( pEnt && FClassnameIs( pEnt, "info_tunnel_exit" ) )
+ {
+ m_hTunnelExit = static_cast< CInfoTunnelExit * >( pEnt );
+ }
+ else
+ {
+ Msg( "CObjectTunnelTrigger::InputSetTarget: Couldn't find info_tunnel_exit named %s\n",
+ inputdata.value.String() );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : &inputdata -
+//-----------------------------------------------------------------------------
+void CObjectTunnelTrigger::InputSetTeleportDuration( inputdata_t &inputdata )
+{
+ m_flTeleportDuration = inputdata.value.Float();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : &inputdata -
+//-----------------------------------------------------------------------------
+void CObjectTunnelTrigger::InputSetTeleportVelocity( inputdata_t &inputdata )
+{
+ m_flTeleportVelocity = inputdata.value.Float();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : *pOther -
+//-----------------------------------------------------------------------------
+void CObjectTunnelTrigger::StartTouch( CBaseEntity *pOther )
+{
+ if ( !pOther || !pOther->IsPlayer() )
+ return;
+
+ // Only works for my team, of course
+ if ( !pOther->InSameTeam( this ) )
+ return;
+
+ if ( m_hTunnelExit == NULL )
+ return;
+
+ // It's been damaged to the point of being disabled
+ if ( !GetActive() )
+ {
+ // Play a deny sound
+ CPASAttenuationFilter filter( pOther, "ObjectTunnelTrigger.DisabledSound" );
+ EmitSound( filter, pOther->entindex(), "ObjectTunnelTrigger.DisabledSound" );
+ return;
+ }
+
+ StartTunneling( (CBaseTFPlayer *)pOther );
+}
+
+LINK_ENTITY_TO_CLASS(obj_tunnel_trigger,CObjectTunnelTrigger);
+
+BEGIN_DATADESC( CObjectTunnelTrigger )
+ // inputs
+ DEFINE_INPUTFUNC( FIELD_VOID, "SetActive", InputSetActive ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "SetInactive", InputSetInactive ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "ToggleActive", InputToggleActive ),
+
+ DEFINE_INPUTFUNC( FIELD_STRING, "SetTarget", InputSetTarget ),
+ DEFINE_INPUTFUNC( FIELD_FLOAT, "SetTeleportDuration", InputSetTeleportDuration ),
+ DEFINE_INPUTFUNC( FIELD_FLOAT, "SetTeleportVelocity", InputSetTeleportVelocity ),
+
+ // outputs
+ DEFINE_OUTPUT( OnTunnelTriggerStart, "OnTunnelTriggerStart" ),
+ DEFINE_OUTPUT( OnTunnelTriggerEnd, "OnTunnelTriggerEnd" ),
+
+ // keyvalues
+ DEFINE_KEYFIELD_NOT_SAVED( m_flTeleportDuration, FIELD_FLOAT, "TeleportDuration" ),
+ DEFINE_KEYFIELD_NOT_SAVED( m_flTeleportVelocity, FIELD_FLOAT, "TeleportVelocity" ),
+END_DATADESC()
diff --git a/game/server/tf2/tf_obj_vehicleboost.cpp b/game/server/tf2/tf_obj_vehicleboost.cpp
new file mode 100644
index 0000000..c08bf88
--- /dev/null
+++ b/game/server/tf2/tf_obj_vehicleboost.cpp
@@ -0,0 +1,76 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Upgrade that boosts vehicle speeds for short periods of time.
+//
+//=============================================================================//
+
+#include "cbase.h"
+#include "tf_player.h"
+#include "tf_obj.h"
+#include "tf_obj_vehicleboost.h"
+#include "tf_basefourwheelvehicle.h"
+
+#define VEHICLE_BOOST_MINS Vector( -10, -10, 0 )
+#define VEHICLE_BOOST_MAXS Vector( 10, 10, 10 )
+#define VEHICLE_BOOST_MODEL "models/objects/obj_vehicle_boost.mdl"
+
+BEGIN_DATADESC( CObjectVehicleBoost )
+END_DATADESC()
+
+IMPLEMENT_SERVERCLASS_ST( CObjectVehicleBoost, DT_ObjectVehicleBoost )
+END_SEND_TABLE();
+
+LINK_ENTITY_TO_CLASS( obj_vehicle_boost, CObjectVehicleBoost );
+PRECACHE_REGISTER( obj_vehicle_boost );
+
+ConVar obj_vehicle_boost_health( "obj_vehicle_boost_health","100", FCVAR_NONE, "Vehicle Boost Health" );
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CObjectVehicleBoost::CObjectVehicleBoost()
+{
+ UseClientSideAnimation();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CObjectVehicleBoost::Spawn( void )
+{
+ Precache();
+ SetModel( VEHICLE_BOOST_MODEL );
+ SetCollisionGroup( TFCOLLISION_GROUP_COMBATOBJECT );
+
+ UTIL_SetSize(this, VEHICLE_BOOST_MINS, VEHICLE_BOOST_MAXS );
+ m_takedamage = DAMAGE_YES;
+ m_iHealth = obj_vehicle_boost_health.GetInt();
+
+ SetType( OBJ_VEHICLE_BOOST );
+ m_fObjectFlags |= OF_SUPPRESS_NOTIFY_UNDER_ATTACK | OF_SUPPRESS_TECH_ANALYZER |
+ OF_DONT_AUTO_REPAIR | OF_MUST_BE_BUILT_ON_ATTACHMENT;
+
+ BaseClass::Spawn();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CObjectVehicleBoost::Precache( void )
+{
+ PrecacheModel( VEHICLE_BOOST_MODEL );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CObjectVehicleBoost::FinishedBuilding( void )
+{
+ BaseClass::FinishedBuilding();
+
+ CBaseTFFourWheelVehicle *pVehicle = dynamic_cast<CBaseTFFourWheelVehicle*>( GetParent() );
+ if ( pVehicle )
+ {
+ pVehicle->SetBoostUpgrade( true );
+ }
+}
diff --git a/game/server/tf2/tf_obj_vehicleboost.h b/game/server/tf2/tf_obj_vehicleboost.h
new file mode 100644
index 0000000..3caed65
--- /dev/null
+++ b/game/server/tf2/tf_obj_vehicleboost.h
@@ -0,0 +1,37 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Upgrade that boosts vehicle speeds for short periods of time.
+//
+//=============================================================================//
+
+#ifndef TF_OBJ_VEHICLEBOOST_H
+#define TF_OBJ_VEHICLEBOOST_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include "tf_obj_baseupgrade_shared.h"
+
+//=============================================================================
+//
+// Vehicle Boost Upgrade
+//
+class CObjectVehicleBoost : public CBaseObjectUpgrade
+{
+
+ DECLARE_CLASS( CObjectVehicleBoost, CBaseObjectUpgrade );
+
+public:
+
+ DECLARE_DATADESC();
+ DECLARE_SERVERCLASS();
+
+ CObjectVehicleBoost();
+
+ void Spawn( void );
+ void Precache( void );
+ bool CanTakeEMPDamage( void ) { return true; }
+ void FinishedBuilding( void );
+};
+
+#endif // TF_OBJ_VEHICLEBOOST_H
diff --git a/game/server/tf2/tf_player.cpp b/game/server/tf2/tf_player.cpp
new file mode 100644
index 0000000..6631454
--- /dev/null
+++ b/game/server/tf2/tf_player.cpp
@@ -0,0 +1,3720 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: TF2's player object.
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#include "cbase.h"
+#include <stdarg.h>
+#include "player.h"
+#include "tf_player.h"
+#include "gamerules.h"
+#include "trains.h"
+#include "entitylist.h"
+#include "menu_base.h"
+#include "basecombatweapon.h"
+#include "controlzone.h"
+#include "tf_shareddefs.h"
+#include "AmmoDef.h"
+#include "techtree.h"
+#include "in_buttons.h"
+#include "tf_team.h"
+#include "client.h"
+#include "baseviewmodel.h"
+#include "tf_gamerules.h"
+#include "tf_obj.h"
+#include "weapon_builder.h"
+#include "orders.h"
+#include "decals.h"
+#include "tf_func_resource.h"
+#include "resource_chunk.h"
+#include "team_messages.h"
+#include "tier0/dbg.h"
+#include "tf_obj_respawn_station.h"
+#include "tf_obj_resourcepump.h"
+#include "tf_class_commando.h"
+#include "tf_class_defender.h"
+#include "tf_class_escort.h"
+#include "tf_class_infiltrator.h"
+#include "tf_class_medic.h"
+#include "tf_class_recon.h"
+#include "tf_class_sniper.h"
+#include "tf_class_support.h"
+#include "tf_class_sapper.h"
+#include "sendproxy.h"
+#include "ragdoll_shadow.h"
+#include "vstdlib/random.h"
+#include "engine/IEngineSound.h"
+#include "bone_setup.h"
+#include "weapon_combatshield.h"
+#include "weapon_twohandedcontainer.h"
+#include "NDebugOverlay.h"
+#include "tier1/strtools.h"
+#include "IEffects.h"
+#include "info_act.h"
+#include "ai_basehumanoid.h"
+#include "tf_stats.h"
+#include "iservervehicle.h"
+#include "tf_vehicle_teleport_station.h"
+#include "globals.h"
+
+#define MAX_EXPLOSIVE_VELOCITY 600.0f
+
+extern ConVar tf_knockdowntime;
+
+extern ConVar inv_demo;
+
+ConVar tf_autoteam( "tf_autoteam", "1", 0, "Automatically place players on the team with the least players." );
+ConVar tf_destroyobjects( "tf_destroyobjects", "1", FCVAR_CHEAT, "Destroy objects when players change class or team." );
+
+IMPLEMENT_SERVERCLASS_ST(CBaseTFPlayer, DT_BaseTFPlayer)
+ SendPropDataTable(SENDINFO_DT(m_TFLocal), &REFERENCE_SEND_TABLE(DT_TFLocal), SendProxy_SendLocalDataTable),
+
+ SendPropInt(SENDINFO(m_iPlayerClass), 4, SPROP_UNSIGNED),
+
+ // Class Data Tables
+ SendPropDataTable( SENDINFO_DT( m_PlayerClasses ), &REFERENCE_SEND_TABLE( DT_AllPlayerClasses ), SendProxy_SendLocalDataTable ),
+
+ SendPropEHandle( SENDINFO( m_hSelectedMCV ) ),
+ SendPropInt( SENDINFO(m_iCurrentZoneState ), 3 ),
+ SendPropInt( SENDINFO(m_iMaxHealth ), 8, SPROP_UNSIGNED ),
+ SendPropInt( SENDINFO(m_TFPlayerFlags), TF_PLAYER_NUMFLAGS, SPROP_UNSIGNED ),
+ SendPropInt( SENDINFO( m_bUnderAttack ), 1, SPROP_UNSIGNED ),
+ SendPropInt( SENDINFO( m_bIsBlocking ), 1, SPROP_UNSIGNED ),
+
+ // Sniper - will get moved to a class data table
+ SendPropVector( SENDINFO(m_vecDeployedAngles), -1, SPROP_COORD ),
+ SendPropInt( SENDINFO( m_bDeployed ), 1, SPROP_UNSIGNED ),
+ SendPropInt( SENDINFO( m_bDeploying ), 1, SPROP_UNSIGNED ),
+ SendPropInt( SENDINFO( m_bUnDeploying ), 1, SPROP_UNSIGNED ),
+
+ // Infiltrator - will get moved to a class data table
+ SendPropFloat( SENDINFO( m_flCamouflageAmount ), 7, SPROP_ROUNDDOWN, 0.0f, 100.0f ),
+
+ SendPropEHandle(SENDINFO(m_hSpawnPoint)),
+
+ SendPropExclude( "DT_BaseAnimating" , "m_flPoseParameter" ),
+ SendPropExclude( "DT_BaseAnimating" , "m_flPlaybackRate" ),
+
+END_SEND_TABLE()
+
+LINK_ENTITY_TO_CLASS( player, CBaseTFPlayer );
+PRECACHE_REGISTER(player);
+
+BEGIN_DATADESC( CBaseTFPlayer )
+
+ DEFINE_INPUTFUNC( FIELD_VOID, "Respawn", InputRespawn ),
+
+ // Function Pointers
+ DEFINE_THINKFUNC( TFPlayerDeathThink ),
+
+END_DATADESC()
+
+
+BEGIN_PREDICTION_DATA_NO_BASE( CTFPlayerLocalData )
+END_PREDICTION_DATA()
+
+BEGIN_PREDICTION_DATA( CBaseTFPlayer )
+END_PREDICTION_DATA()
+
+
+bool IsSpawnPointValid( CBaseEntity *pPlayer, CBaseEntity *pSpot );
+void respawn( CBaseEntity *pEdict, bool fCopyCorpse );
+int TrainSpeed(int iSpeed, int iMax);
+void BulletWizz( Vector vecSrc, Vector vecEndPos, edict_t *pShooter, bool isTracer );
+
+extern float g_flNextReinforcementTime;
+extern short g_sModelIndexFireball;
+extern CBaseEntity *g_pLastSpawn;
+
+//-----------------------------------------------------------------------------
+// Purpose: Don't do anything for now
+// Input : *pFormat -
+// ... -
+// Output : static void
+//-----------------------------------------------------------------------------
+void StatusPrintf( bool clear, int destination, char *pFormat, ... )
+{
+ return;
+
+ /*
+ va_list marker;
+ char msg[8192];
+
+ va_start(marker, pFormat);
+ Q_vsnprintf(msg, sizeof( msg ), pFormat, marker);
+ va_end(marker);
+
+ Msg( msg );
+ */
+}
+
+#pragma warning( disable : 4355 )
+
+//=====================================================================
+// PLAYER HANDLING
+//=====================================================================
+CBaseTFPlayer::CBaseTFPlayer() :
+ m_PlayerClasses( this ), m_PlayerAnimState( this )
+{
+ // HACK because player's have pev set in baseclass constructor
+ // which triggers an assert that we want to keep.
+ {
+ edict_t *savepev = edict();
+ NetworkProp()->SetEdict( NULL );
+ UseClientSideAnimation();
+ NetworkProp()->SetEdict( savepev );
+ }
+
+ m_bWasMoving = false;
+
+ m_iLastSecondsToGo = -1;
+ m_TFLocal.m_nInTacticalView = 0;
+ m_TFLocal.m_pPlayer = this;
+ m_bSwitchingView = false;
+ ClearActiveWeapon();
+
+ m_iPlayerClass = TFCLASS_UNDECIDED;
+ SetPlayerClass( TFCLASS_UNDECIDED );
+ m_pCurrentMenu = NULL;
+ m_TFPlayerFlags = 0;
+ m_bDeploying = false;
+ m_bDeployed = false;
+ m_bUnDeploying = false;
+ m_flFinishedDeploying = 0;
+ SetOrder( NULL );
+
+ m_nPreferredTechnology = -1;
+ m_nMedicDamageBoosts = 0;
+
+ m_hSpawnPoint = NULL;
+ m_flLastTimeDamagedByEnemy = -1000;
+
+ int i;
+ for ( i = 0; i < MOMENTUM_MAXSIZE; i++ )
+ {
+ m_aMomentum[ i ] = 1.0f;
+ }
+}
+
+void CBaseTFPlayer::UpdateOnRemove( void )
+{
+ if ( m_hSelectedOrder )
+ {
+ GetTFTeam()->RemoveOrdersToPlayer( this );
+ Assert( !m_hSelectedOrder.Get() );
+ }
+
+ ClearPlayerClass();
+
+ ClearClientRagdoll( false );
+
+ // Chain at end to mimic destructor unwind order
+ BaseClass::UpdateOnRemove();
+}
+
+CBaseTFPlayer::~CBaseTFPlayer()
+{
+ SetPlayerClass( (TFClass)-1 );
+}
+
+bool CBaseTFPlayer::IsHidden() const
+{
+ return (m_TFPlayerFlags & TF_PLAYER_HIDDEN) != 0;
+}
+
+void CBaseTFPlayer::SetHidden( bool bHidden )
+{
+ if ( bHidden )
+ m_TFPlayerFlags |= TF_PLAYER_HIDDEN;
+ else
+ m_TFPlayerFlags &= ~TF_PLAYER_HIDDEN;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Called everytime the player's respawned
+//-----------------------------------------------------------------------------
+void CBaseTFPlayer::Spawn( void )
+{
+ m_bUnderAttack = false;
+ m_pCurrentZone = NULL;
+ ClearClientRagdoll( false );
+
+ g_pNotify->ReportNamedEvent( this, "PlayerSpawned" );
+
+ DeactivateMovementConstraint();
+
+ if ( IsInAVehicle() )
+ {
+ LeaveVehicle();
+ }
+
+ // If the player doesn't have a spawn station set, find one
+ if ( m_hSpawnPoint == NULL || !InSameTeam( m_hSpawnPoint ) )
+ {
+ m_hSpawnPoint = GetInitialSpawnPoint();
+ }
+
+ if ( inv_demo.GetBool() )
+ {
+ if ( !GetPlayerClass() )
+ {
+ ChangeClass( TFCLASS_MEDIC );
+ m_Local.m_iHideHUD |= HIDEHUD_MISCSTATUS;
+ engine->ServerCommand("r_DispEnableLOD 0\n");
+ }
+ }
+
+ // Must be done before baseclass spawn, so it's correct for when we find a spawnpoint
+ if ( GetPlayerClass() )
+ {
+ GetPlayerClass()->SetPlayerHull();
+ }
+
+ // Use human commando model until we know our class
+ SetModel( "models/player/human_commando.mdl" );
+
+ BaseClass::Spawn();
+
+ m_flFractionalBoost = 0.0f;
+
+ // Create second view model ( for support/commando, etc )
+ CreateViewModel( 1 );
+
+ // Tell the PlayerClass that this player's just respawned
+ if ( GetPlayerClass() )
+ {
+ RemoveFlag( FL_NOTARGET );
+ RemoveSolidFlags( FSOLID_NOT_SOLID );
+
+ GetPlayerClass()->RespawnClass();
+ if ( GetActiveWeapon() )
+ {
+ // Holster weapon immediately, to allow it to cleanup
+// GetActiveWeapon()->Holster( ); // NJS: test
+
+ if (GetActiveWeapon()->HasAnyAmmo())
+ {
+ Weapon_Switch( GetActiveWeapon() );
+ }
+ else
+ {
+ SwitchToNextBestWeapon( GetActiveWeapon() );
+ }
+ }
+ else
+ {
+ SwitchToNextBestWeapon( NULL );
+ }
+
+ SetPlayerModel();
+
+ // Make sure they're not deployed
+ FinishUnDeploying();
+
+ // Remove my personal orders
+ if ( GetTFTeam() )
+ {
+ GetTFTeam()->RemoveOrdersToPlayer( this );
+ }
+
+ RemoveAllDecals();
+ }
+ else
+ {
+ // No class? can't target this dude
+ AddFlag( FL_NOTARGET );
+
+ // Remove everything
+ RemoveAllItems( false );
+
+ // Set/unset m_bHidden instead to hide the tf player
+ SetHidden( true );
+
+ AddSolidFlags( FSOLID_NOT_SOLID );
+ SetMoveType( MOVETYPE_NONE );
+
+ SetModel( "models/player/human_commando.mdl" );
+
+ // If they're not in a team, bring up the Team Menu
+ if ( !IsInAnyTeam() )
+ {
+ if ( tf_autoteam.GetFloat() )
+ {
+ // Autoteam the player
+ PlacePlayerInTeam();
+ ForceRespawn();
+ }
+ else
+ {
+ // Let players choose their team
+ m_pCurrentMenu = gMenus[MENU_TEAM];
+ }
+ }
+ else // Bring up the Class Menu
+ {
+ m_pCurrentMenu = gMenus[MENU_CLASS];
+ }
+
+ m_MenuRefreshTime = gpGlobals->curtime;
+
+ m_nPreferredTechnology = -1;
+ }
+
+ SetCantMove( false );
+
+
+ m_TFLocal.m_nInTacticalView = 0;
+ m_flLastTimeDamagedByEnemy = -1000;
+
+ // Purge resource chunks
+ for ( int i=0; i < m_TFLocal.m_iResourceAmmo.Count(); i++ )
+ m_TFLocal.m_iResourceAmmo.Set( i, 0 );
+
+ ResetKnockdown();
+ SetGagged( false );
+ SetUsingThermalVision( false );
+ ClearCamouflage();
+ SetIDEnt( NULL );
+ m_iPowerups = 0;
+
+ // MUST set the right player hull before placing the player somewhere.
+ if ( GetPlayerClass() )
+ GetPlayerClass()->SetPlayerHull();
+
+ g_pGameRules->GetPlayerSpawnSpot( this );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseTFPlayer::CleanupOnActStart( void )
+{
+ // Tell all our weapons
+ for ( int i = 0; i < WeaponCount(); i++ )
+ {
+ if ( GetWeapon(i) )
+ {
+ ((CBaseTFCombatWeapon*)GetWeapon(i))->CleanupOnActStart();
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseTFPlayer::RecalculateSpeed( void )
+{
+ if ( GetPlayerClass() )
+ {
+ GetPlayerClass()->SetMaxSpeed( GetPlayerClass()->GetMaxSpeed() );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: I just killed another player
+//-----------------------------------------------------------------------------
+void CBaseTFPlayer::KilledPlayer( CBaseTFPlayer *pVictim )
+{
+ TFStats()->IncrementPlayerStat( this, TF_PLAYER_STAT_KILL_COUNT, 1 );
+ TFStats()->IncrementTeamStat( GetTeamNumber(), TF_TEAM_STAT_KILL_COUNT, 1 );
+
+ // Am I in a rampage?
+ if ( HasPowerup( POWERUP_RUSH ) && IsInRampage() )
+ {
+ // Extend my rush
+ AttemptToPowerup( POWERUP_RUSH, ADRENALIN_RAMPAGE_EXTEND );
+
+ // Let 'em know
+ EmitSound( "BaseTFPlayer.BloodSportKiller" );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Called only the first time a player's placed in the map
+//-----------------------------------------------------------------------------
+void CBaseTFPlayer::InitialSpawn( void )
+{
+ BaseClass::InitialSpawn();
+ SetWeaponBuilder( NULL );
+
+ m_bFirstTeamSpawn = true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseTFPlayer::Precache( void )
+{
+ //!! hack for radar
+ BaseClass::Precache();
+
+ PrecacheScriptSound( "BaseTFPlayer.BloodSportKiller" );
+ PrecacheScriptSound( "Humans.Death" );
+ PrecacheScriptSound( "AlienCommando.Death" );
+ PrecacheScriptSound( "AlienMedic.Death" );
+ PrecacheScriptSound( "AlienDefender.Death" );
+ PrecacheScriptSound( "AlienEscort.Death" );
+ PrecacheScriptSound( "BaseTFPlayer.StartDeploying" );
+ PrecacheScriptSound( "BaseTFPlayer.StartUnDeploying" );
+ PrecacheScriptSound( "BaseTFPlayer.KnockedDown" );
+ PrecacheScriptSound( "BaseTFPlayer.ThermalOn" );
+ PrecacheScriptSound( "BaseTFPlayer.ThermalOff" );
+ PrecacheScriptSound( "BaseTFPlayer.PickupResources" );
+ PrecacheScriptSound( "BaseTFPlayer.DonateResources" );
+
+ // Class specific sounds
+ PrecacheScriptSound( "Commando.BootHit" );
+ PrecacheScriptSound( "Commando.BootSwing" );
+ PrecacheScriptSound( "Commando.BullRushScream" );
+ PrecacheScriptSound( "Commando.BullRushFlesh" );
+
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseTFPlayer::UpdateClientData( void )
+{
+ CTeam *pTeam = GetTeam();
+ if ( pTeam )
+ pTeam->UpdateClientData( this );
+
+ BaseClass::UpdateClientData();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseTFPlayer::ForceClientDllUpdate( void )
+{
+ BaseClass::ForceClientDllUpdate();
+
+ // Force any active menu to be reset
+ m_MenuRefreshTime = 0;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Forces an immediate respawn of the player
+//-----------------------------------------------------------------------------
+void CBaseTFPlayer::ForceRespawn( void )
+{
+ Spawn();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Input handler that forces a respawn of the player.
+//-----------------------------------------------------------------------------
+void CBaseTFPlayer::InputRespawn( inputdata_t &inputdata )
+{
+ ForceRespawn();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseTFPlayer::InitHUD( void )
+{
+ CSingleUserRecipientFilter user( this );
+ user.MakeReliable();
+
+ // If we're in an act, tell it to update the client
+ if ( g_hCurrentAct )
+ {
+ g_hCurrentAct->UpdateClient( this );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Player has just tried to switch to a new weapon
+//-----------------------------------------------------------------------------
+void CBaseTFPlayer::SelectItem( const char *pstr, int iSubType )
+{
+ // can't change weapon while deployed
+ if ( IsPlayerLockedInPlace() || IsDeployed() || IsDeploying() )
+ return;
+
+ // Pass through to CBaseCombatWeapon code
+ BaseClass::SelectItem( pstr, iSubType );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Put the player in the specified team
+//-----------------------------------------------------------------------------
+void CBaseTFPlayer::ChangeTeam( int iTeamNum )
+{
+ // If we're changing team, clear my order
+ if ( iTeamNum != GetTeamNumber() )
+ {
+ SetOrder(NULL);
+ if ( tf_destroyobjects.GetFloat() )
+ {
+ RemoveAllObjects( false );
+ }
+ }
+
+ // Force full tech tree update
+ for ( int i = 0 ; i < MAX_TECHNOLOGIES; i++ )
+ {
+ m_rgClientTechAvail[ i ].m_nAvailable = -1;
+ }
+
+ BaseClass::ChangeTeam( iTeamNum );
+
+ // Now handle resources:
+ // - If it's the first spawn ever, give the player the team's currently calculated resource amount
+ // - If the player has more resources than the team's joining amount, drop his resources to that amount. Otherwise, he can keep his current.
+ if ( GetGlobalTFTeam( iTeamNum ) )
+ {
+ float flJoiningResources = GetGlobalTFTeam( iTeamNum )->GetJoiningPlayerResources();
+ if ( m_bFirstTeamSpawn )
+ {
+ m_bFirstTeamSpawn = false;
+ SetBankResources( flJoiningResources );
+ }
+ else
+ {
+ if ( flJoiningResources < GetBankResources() )
+ {
+ SetBankResources( flJoiningResources );
+ }
+ }
+ }
+
+ // Clear the client ragdoll, when changing teams.
+ ClearClientRagdoll( false );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Automatically place the player in the most appropriate team
+//-----------------------------------------------------------------------------
+void CBaseTFPlayer::PlacePlayerInTeam( void )
+{
+ CTFTeam *pTargetTeam = NULL;
+
+ // Find the team with the least players in it
+ for ( int i = 0; i < MAX_TF_TEAMS; i++ )
+ {
+ CTFTeam *pTeam = GetGlobalTFTeam(i);
+
+ if ( pTargetTeam )
+ {
+ if ( pTeam->GetNumPlayers() < pTargetTeam->GetNumPlayers() )
+ pTargetTeam = pTeam;
+ }
+ else
+ {
+ pTargetTeam = pTeam;
+ }
+ }
+
+ ChangeTeam( pTargetTeam->GetTeamNumber() );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Return true if the specified class is available to this player
+//-----------------------------------------------------------------------------
+bool CBaseTFPlayer::IsClassAvailable( TFClass iClass )
+{
+ char str[128];
+ Q_snprintf( str, sizeof( str ), "class_%s", GetTFClassInfo( iClass )->m_pClassName );
+ return HasNamedTechnology( str );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseTFPlayer::ChangeClass( TFClass iClass )
+{
+ // If they've got a playerclass, kill it
+ if ( GetPlayerClass() )
+ {
+ if ( tf_destroyobjects.GetFloat() )
+ {
+ RemoveAllObjects( false, iClass );
+ }
+
+ ClearPlayerClass();
+ }
+
+ // can't change class if we have no team
+ if ( !IsInAnyTeam() )
+ return;
+
+ // Make sure client .dll can find out about it.
+ SetPlayerClass( iClass );
+
+ // Clear out current vote....
+ CTFTeam *pTFTeam = GetTFTeam();
+ SetPreferredTechnology( pTFTeam->m_pTechnologyTree, -1 );
+
+ // Force a respawn if they're alive
+ if ( IsAlive() )
+ {
+ ForceRespawn();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Reset player class
+//-----------------------------------------------------------------------------
+void CBaseTFPlayer::ClearPlayerClass( void )
+{
+ // Remove all weapons & items
+ if ( GetPlayerClass() )
+ {
+ RemoveAllItems( false );
+ m_hWeaponCombatShield = NULL;
+ }
+
+ m_iPowerups = 0;
+ SetPlayerClass( TFCLASS_UNDECIDED );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Set the player's model to the correct one, taking into account
+// class, gender, team, and disguise.
+//-----------------------------------------------------------------------------
+void CBaseTFPlayer::SetPlayerModel( void )
+{
+ if (!GetPlayerClass())
+ {
+ SetHidden( true );
+ return;
+ }
+
+ string_t sModel = GetPlayerClass()->GetClassModel( GetTeamNumber() );
+
+ // If they don't have a model, make the player invisible
+ if ( !sModel )
+ {
+ SetHidden( true );
+ return;
+ }
+
+ // Make the player visible
+ SetHidden( false );
+
+ // Set the model
+ SetModel( STRING( sModel ) );
+
+ if ( GetFlags() & FL_DUCKING )
+ UTIL_SetSize(this, VEC_DUCK_HULL_MIN, VEC_DUCK_HULL_MAX);
+ else
+ UTIL_SetSize(this, VEC_HULL_MIN, VEC_HULL_MAX);
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseTFPlayer::PlayerRespawn( void )
+{
+ m_nButtons = 0;
+ m_iRespawnFrames = 0;
+
+ // don't copy a corpse if we're in deathcam.
+ respawn( this, !IsObserver() );
+ SetThink( NULL );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Play a sound when we die
+//-----------------------------------------------------------------------------
+void CBaseTFPlayer::DeathSound( const CTakeDamageInfo &info )
+{
+ if ( GetTeamNumber() == TEAM_HUMANS )
+ {
+ EmitSound( "Humans.Death" );
+ }
+ else if ( GetTeamNumber() == TEAM_ALIENS )
+ {
+ switch( PlayerClass() )
+ {
+ case TFCLASS_COMMANDO:
+ EmitSound( "AlienCommando.Death" );
+ break;
+
+ case TFCLASS_MEDIC:
+ EmitSound( "AlienMedic.Death" );
+ break;
+
+ case TFCLASS_DEFENDER:
+ EmitSound( "AlienDefender.Death" );
+ break;
+
+ case TFCLASS_ESCORT:
+ EmitSound( "AlienEscort.Death" );
+ break;
+
+ default:
+ break;
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseTFPlayer::ItemPostFrame()
+{
+ // Don't process items while in a vehicle.
+ if ( IsInAVehicle() )
+ {
+ IServerVehicle *pVehicle = GetVehicle();
+ Assert( pVehicle );
+
+ // NOTE: We *have* to do this before ItemPostFrame because ItemPostFrame
+ // may dump us out of the vehicle
+ int nRole = pVehicle->GetPassengerRole( this );
+ bool bUsingStandardWeapons = pVehicle->IsPassengerUsingStandardWeapons( nRole );
+
+ pVehicle->ItemPostFrame( this );
+
+ // Fall through and check weapons, etc. if we're using them
+ if (!bUsingStandardWeapons || !IsInAVehicle())
+ return;
+ }
+
+ // If we're attaching a sapper, handle player use only
+ if ( m_TFLocal.m_bAttachingSapper )
+ {
+ PlayerUse();
+ return;
+ }
+
+ BaseClass::ItemPostFrame();
+
+ if ( GetPlayerClass() )
+ {
+ GetPlayerClass()->ItemPostFrame(); // Let the player class handle it.
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseTFPlayer::Jump( void )
+{
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseTFPlayer::PreThink(void)
+{
+ CheckBuffs();
+
+ // Riding a vehicle?
+ if ( IsInAVehicle() )
+ {
+ BaseClass::PreThink();
+ return;
+ }
+
+ CheckDeployFinish();
+ CheckKnockdown();
+ CheckCamouflage();
+ CheckSapperAttaching();
+
+ // Update reinforcement state
+ if (m_lifeState >= LIFE_DYING)
+ {
+ // After 3 seconds, move them to the Tactical Map
+ if ( (gpGlobals->curtime - m_flTimeOfDeath) > 3.0 )
+ {
+ if ( m_TFLocal.m_nInTacticalView == false )
+ {
+ ShowTacticalView( 1 );
+ }
+ }
+
+ // ROBIN: Maps will define whether or not teams reinforce
+ /*
+ // Aliens respawn in waves
+ if ( GetTeamNumber() == TEAM_ALIENS )
+ {
+ int iSecondsToGo = (int)(g_flNextReinforcementTime - gpGlobals->curtime);
+ if ( iSecondsToGo != m_iLastSecondsToGo && iSecondsToGo >= 1 )
+ {
+ m_iLastSecondsToGo = iSecondsToGo;
+ ClientPrint( this, HUD_PRINTCENTER, UTIL_VarArgs("\nReinforcing in %d %s\n", iSecondsToGo, iSecondsToGo > 1 ? "seconds" : "second" ) );
+ }
+ }
+ */
+
+ TFPlayerDeathThink();
+ }
+
+ // Update zone state
+ if ( m_pCurrentZone )
+ {
+ m_iCurrentZoneState = m_pCurrentZone->GetControllingTeam();
+ if ( m_iCurrentZoneState != ZONE_CONTESTED )
+ {
+ // Set the Zone state to the correct one
+ if ( m_iCurrentZoneState == GetTeamNumber() )
+ m_iCurrentZoneState = ZONE_FRIENDLY;
+ else
+ m_iCurrentZoneState = ZONE_ENEMY;
+ }
+ }
+ else
+ {
+ m_iCurrentZoneState = 0;
+ }
+
+ BaseClass::PreThink();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseTFPlayer::PostThink()
+{
+ BaseClass::PostThink();
+
+ // Make sure we have a valid MCV id.
+ CVehicleTeleportStation *pMCV = GetSelectedMCV();
+ if ( !pMCV || !pMCV->IsDeployed() )
+ {
+ m_hSelectedMCV = CVehicleTeleportStation::GetFirstDeployedMCV( GetTeamNumber() );
+ }
+
+ // Tell the client if our damage is boosted so it can do a smurfy effect on the weapon.
+ if ( GetAttackDamageScale( NULL ) == 1 )
+ m_TFPlayerFlags &= ~TF_PLAYER_DAMAGE_BOOST;
+ else
+ m_TFPlayerFlags |= TF_PLAYER_DAMAGE_BOOST;
+
+ m_PlayerAnimState.Update();
+// SetLocalAngles( m_PlayerAnimState.GetRenderAngles() );
+
+ float flTimeSinceAttacked = gpGlobals->curtime - LastTimeDamagedByEnemy();
+ m_bUnderAttack = ((flTimeSinceAttacked >= 0.0f) && (flTimeSinceAttacked < 1.0f));
+
+ // TODO: This collision hull is set in the base class PostThink (so this is
+ // redundant), but I don't wanna re-write the whole thing at this point.
+ // We will just have to deal with a little redundancy for now.
+ if ( GetPlayerClass() )
+ {
+ GetPlayerClass()->SetPlayerHull();
+ }
+
+ // Menus
+ MenuDisplay();
+
+ // Player class Think
+ if (GetPlayerClass())
+ {
+ GetPlayerClass()->ClassThink();
+ }
+
+ if ( m_bSwitchingView )
+ {
+ m_bSwitchingView = false;
+ SetMoveType( m_TFLocal.m_nInTacticalView ? MOVETYPE_ISOMETRIC : MOVETYPE_WALK );
+ }
+
+ FollowClientRagdoll();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: selects a valid point that the player can spawn at
+// Output : edict_t - the point in the world to spawn at
+//-----------------------------------------------------------------------------
+CBaseEntity *CBaseTFPlayer::EntSelectSpawnPoint( void )
+{
+ // If we're in a team, ask the team for a spawnpoint
+ if ( GetTeam() )
+ {
+ CBaseEntity *entity = NULL;
+ if ( GetPlayerClass() )
+ {
+ // Let individual player classes override the respawn point
+ entity = GetPlayerClass()->SelectSpawnPoint();
+ if ( entity )
+ {
+ return entity;
+ }
+
+ // Do we have a selected spawn point (from a respawn station)?
+ entity = m_hSpawnPoint;
+ if (entity && (entity->GetTeam() == GetTeam()))
+ {
+ PlayRespawnEffect( entity );
+ return entity;
+ }
+ }
+
+ entity = GetTeam()->SpawnPlayer( this );
+ if ( entity )
+ return entity;
+ }
+
+ // If we're not in a team, or the team didn't have a spawnpoint for us,
+ // fall back to the basic spawnpoint code.
+ return BaseClass::EntSelectSpawnPoint();
+}
+
+void CBaseTFPlayer::RemoveShieldOverlays( void )
+{
+ RemoveGesture( ACT_OVERLAY_SHIELD_UP );
+ RemoveGesture( ACT_OVERLAY_SHIELD_DOWN );
+ RemoveGesture( ACT_OVERLAY_SHIELD_UP_IDLE );
+ RemoveGesture( ACT_OVERLAY_SHIELD_ATTACK );
+ RemoveGesture( ACT_OVERLAY_SHIELD_KNOCKBACK );
+}
+
+static bool IsShieldOverlay( Activity activity )
+{
+ switch ( activity )
+ {
+ default:
+ return false;
+ case ACT_OVERLAY_SHIELD_UP:
+ case ACT_OVERLAY_SHIELD_DOWN:
+ case ACT_OVERLAY_SHIELD_UP_IDLE:
+ case ACT_OVERLAY_SHIELD_ATTACK:
+ case ACT_OVERLAY_SHIELD_KNOCKBACK:
+ return true;
+ }
+ return false;
+}
+
+int CBaseTFPlayer::RemoveShieldOverlaysExcept( Activity activity, bool addifnotpresent /*= true */ )
+{
+ int skip = FindGestureLayer( activity );
+
+ int i;
+ for ( i = 0; i < CBaseAnimatingOverlay::MAX_OVERLAYS; i++ )
+ {
+ if ( i == skip )
+ continue;
+
+ if ( IsShieldOverlay( GetLayerActivity( i ) ) )
+ {
+ RemoveLayer( i, 0.0, 0.0f );
+ }
+ }
+
+ // Add it in if it's not present already
+ if ( addifnotpresent && ( skip == -1 ) )
+ {
+ return AddGesture( activity );
+ }
+ else
+ {
+ return skip;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : activity - i/o : can be changed to a new activity
+// overlayindex - o: if an overlay is picked, this gets changed
+// Rest of paramters are input only
+// moving - is player moving
+// ducked - is player ducking
+// overlay - animation choices for this state (either full body crouch/stand, or overlay on top of base crouch/stand )
+// crouch -
+// normal -
+// Overlay parameters
+// autokill - if false, overlay will loop indefinitely
+// blendin - amount of time over which to blend in (0.0f for snap)
+// blendout - same but for blending out instead
+//-----------------------------------------------------------------------------
+void CBaseTFPlayer::PickShieldAnimation( Activity& activity, int& overlayindex, bool moving, bool ducked,
+ Activity overlay, Activity crouch, Activity normal,
+ bool autokill /*=true*/, float blendin /*=0.0f*/, float blendout /*=0.0f*/ )
+{
+ if ( moving )
+ {
+ overlayindex = RemoveShieldOverlaysExcept( overlay );
+ if ( overlayindex != -1 )
+ {
+ if ( blendin > 0.0f )
+ {
+ SetLayerBlendIn( overlayindex, blendin );
+ }
+
+ if ( blendout > 0.0f )
+ {
+ SetLayerBlendOut( overlayindex, blendout );
+ }
+
+ if ( !autokill )
+ {
+ SetLayerAutokill( overlayindex, false );
+ }
+ }
+ }
+ else
+ {
+ activity = ducked ? crouch : normal;
+ }
+}
+
+Activity CBaseTFPlayer::ShieldTranslateActivity( Activity activity )
+{
+ CWeaponTwoHandedContainer *container = dynamic_cast< CWeaponTwoHandedContainer * >( GetActiveWeapon() );
+ if ( !container )
+ return activity;
+
+ CWeaponCombatShield *pShield = dynamic_cast< CWeaponCombatShield * >( container->GetLeftWeapon() );
+ if ( !pShield )
+ {
+ pShield = dynamic_cast< CWeaponCombatShield * >( container->GetRightWeapon() );
+ if ( !pShield )
+ {
+ return activity;
+ }
+ }
+
+ float speed = GetAbsVelocity().Length2D();
+ bool isMoving = speed != 0 ? true : false;
+ //bool isRunning = speed > 75 ? true : false;
+ bool isDucked = ( GetFlags() & FL_DUCKING ) ? true : false;
+
+ int shieldState = pShield->GetShieldState();
+
+ float startframe = 0.0f;
+
+ bool movechanged = isMoving ^ m_bWasMoving;
+ if ( movechanged)
+ {
+ // Grab frame from overlay
+ if ( !isMoving )
+ {
+ for ( int i = 0; i < MAX_OVERLAYS; i++ )
+ {
+ if ( IsShieldOverlay( GetLayerActivity( i ) ) )
+ {
+ startframe = GetLayerCycle( i );
+ }
+ }
+
+ RemoveShieldOverlays();
+ }
+ else
+ {
+ switch ( GetActivity() )
+ {
+ case ACT_SHIELD_UP:
+ case ACT_SHIELD_DOWN:
+ case ACT_SHIELD_UP_IDLE:
+ case ACT_SHIELD_ATTACK:
+ //case ACT_SHIELD_KNOCKBACK:
+ case ACT_CROUCHING_SHIELD_UP:
+ case ACT_CROUCHING_SHIELD_DOWN:
+ case ACT_CROUCHING_SHIELD_UP_IDLE:
+ case ACT_CROUCHING_SHIELD_ATTACK:
+ //case ACT_CROUCHING_SHIELD_KNOCKBACK:
+ startframe = GetCycle();
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ // Asume we should fix up animation based on move/stationary state change
+ bool fixup = true;
+ // Assume no overlay
+ int idx = -1;
+
+ switch ( shieldState )
+ {
+ default:
+ case SS_DOWN:
+ case SS_UNAVAILABLE:
+ RemoveShieldOverlays();
+ // By default, remove shield overlays and don't do fixup
+ fixup = false;
+ break;
+ case SS_LOWERING:
+ {
+ PickShieldAnimation( activity, idx, isMoving, isDucked,
+ ACT_OVERLAY_SHIELD_DOWN, ACT_CROUCHING_SHIELD_DOWN, ACT_SHIELD_DOWN,
+ true, 0.0f, 0.2f );
+ }
+ break;
+ case SS_RAISING:
+ {
+ PickShieldAnimation( activity, idx, isMoving, isDucked,
+ ACT_OVERLAY_SHIELD_UP, ACT_CROUCHING_SHIELD_UP, ACT_SHIELD_UP,
+ true, 0.2f, 0.0f );
+ }
+ break;
+ case SS_UP:
+ {
+ PickShieldAnimation( activity, idx, isMoving, isDucked,
+ ACT_OVERLAY_SHIELD_UP_IDLE, ACT_CROUCHING_SHIELD_UP_IDLE, ACT_SHIELD_UP_IDLE,
+ false );
+ }
+ break;
+ case SS_PARRYING:
+ {
+ PickShieldAnimation( activity, idx, isMoving, isDucked,
+ ACT_OVERLAY_SHIELD_ATTACK, ACT_CROUCHING_SHIELD_ATTACK, ACT_SHIELD_ATTACK,
+ true, 0.1f, 0.1f );
+ }
+ break;
+ }
+
+ // If started or stopped moving and still using shield, match the cycle to/from the overlay/base animation
+ // being used beforehand
+ if ( movechanged && fixup )
+ {
+ // Fixup overlay frame
+ if ( idx != -1 )
+ {
+ SetLayerCycle( idx, startframe );
+ }
+ else
+ {
+ // Force animation blend
+ ResetSequenceInfo();
+
+ // Match start frame
+ SetCycle( startframe );
+ }
+ }
+
+ // Remember previous state
+ m_bWasMoving = isMoving;
+
+ // Return translated activity
+ return activity;
+}
+
+void CBaseTFPlayer::StoreCycle( void )
+{
+ m_flStoredCycle = GetCycle(); // !!!!!
+}
+
+float CBaseTFPlayer::RetrieveCycle( void )
+{
+ return m_flStoredCycle;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Certain activities have matched cycles
+// Input : newActivity -
+// currentActivity -
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CBaseTFPlayer::ShouldMatchCycles( Activity newActivity, Activity currentActivity )
+{
+ if ( ( newActivity == ACT_WALK || newActivity == ACT_RUN ) &&
+ ( currentActivity == ACT_WALK || currentActivity == ACT_RUN ) )
+ {
+ // Don't blend either
+ IncrementInterpolationFrame();
+ return true;
+ }
+ return false;
+}
+
+#define ARBITRARY_RUN_SPEED 75.0f
+
+//-----------------------------------------------------------------------------
+// Purpose: Set the activity based on an event or current state
+//-----------------------------------------------------------------------------
+void CBaseTFPlayer::SetAnimation( PLAYER_ANIM playerAnim )
+{
+ // Assume no change
+ Activity idealActivity = GetActivity();
+ int animDesired = GetSequence();
+
+ float speed = GetAbsVelocity().Length2D();
+
+ bool isMoving = ( speed != 0.0f ) ? true : false;
+ bool isRunning = false;
+
+ if ( GetPlayerClass() )
+ {
+// FIXME: TF2 makes no distinction between walking and running for now,
+// use the run animation always
+ if ( speed > 10.0f )
+ {
+ isRunning = true;
+ }
+ }
+ else
+ {
+ if ( speed > ARBITRARY_RUN_SPEED )
+ {
+ isRunning = true;
+ }
+ }
+
+ bool isDucked = ( GetFlags() & FL_DUCKING ) ? true : false;
+ bool isStillJumping = !( GetFlags() & FL_ONGROUND ) && ( GetActivity() == ACT_HOP );
+
+ StoreCycle();
+
+ // Decide upon an animation activity based upon the desired Player animation
+ switch ( playerAnim )
+ {
+ default:
+ case PLAYER_RELOAD:
+ case PLAYER_ATTACK1:
+ case PLAYER_IDLE:
+ case PLAYER_WALK:
+ // Are we still jumping?
+ // If so, keep playing the jump animation.
+ if ( !isStillJumping )
+ {
+ idealActivity = ACT_WALK;
+
+ if ( isDucked )
+ {
+ idealActivity = !isMoving ? ACT_CROUCHIDLE : ACT_CROUCH;
+ }
+ else
+ {
+
+ if ( isRunning )
+ {
+ idealActivity = ACT_RUN;
+ }
+ else
+ {
+ idealActivity = isMoving ? ACT_WALK : ACT_IDLE;
+ }
+ }
+
+ // Allow shield to override
+ idealActivity = ShieldTranslateActivity( idealActivity );
+ // Allow body yaw to override for standing and turning in place
+ idealActivity = m_PlayerAnimState.BodyYawTranslateActivity( idealActivity );
+ }
+ break;
+
+ case PLAYER_IN_VEHICLE:
+ // For now, use manned gun pose for all vehicles
+ idealActivity = ACT_RIDE_MANNED_GUN;
+ break;
+
+ case PLAYER_JUMP:
+ idealActivity = ACT_HOP;
+ break;
+
+ case PLAYER_DIE:
+ // Uses Ragdoll now???
+ idealActivity = ACT_DIESIMPLE;
+ break;
+
+ // FIXME: Use overlays for reload, start/leave aiming, attacking
+ case PLAYER_START_AIMING:
+ case PLAYER_LEAVE_AIMING:
+ idealActivity = ACT_WALK;
+ break;
+ }
+
+ // No change requested?
+ if ( ( GetActivity() == idealActivity ) && ( GetSequence() != -1 ) )
+ return;
+
+ bool useStoredCycle = ShouldMatchCycles( idealActivity, GetActivity() );
+
+ animDesired = SelectWeightedSequence( idealActivity );
+
+ SetActivity( idealActivity );
+
+ // Already using the desired animation?
+ if ( GetSequence() == animDesired )
+ return;
+
+ ResetSequence( animDesired );
+
+ // Reset to first frame of desired animation or match previous animation if activities are
+ // meant to synchronize
+ SetCycle( useStoredCycle ? RetrieveCycle() : 0 );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseTFPlayer::CheatImpulseCommands( int iImpulse )
+{
+ switch(iImpulse)
+ {
+ case 101:
+ if ( GetPlayerClass() )
+ {
+ GetPlayerClass()->ResupplyAmmo( 1.0f, RESUPPLY_ALL_FROM_STATION );
+ }
+ break;
+
+ case 150:
+ if ( GetTFTeam() )
+ GetTFTeam()->PostMessage( TEAMMSG_REINFORCEMENTS_ARRIVED );
+ break;
+
+ default:
+ BaseClass::CheatImpulseCommands(iImpulse);
+ break;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseTFPlayer::SetRespawnStation( CBaseEntity* pRespawnStation )
+{
+ // This can happen because the object may get killed and its index reused
+ // between time the message was sent and the
+ if( !pRespawnStation || !FClassnameIs( pRespawnStation, "obj_respawn_station" ) )
+ return;
+
+ // Team could have changed (stolen object)
+ if ( GetTeam() != pRespawnStation->GetTeam() )
+ return;
+
+ // If the respawn station is the same one, then unselect!
+ if ( pRespawnStation != m_hSpawnPoint )
+ {
+ // Make sure the respawn station is a respawn station; it could be some
+ m_hSpawnPoint = pRespawnStation;
+ }
+ else
+ {
+ m_hSpawnPoint = 0;
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Find a starting respawn station
+//-----------------------------------------------------------------------------
+CBaseEntity *CBaseTFPlayer::GetInitialSpawnPoint( void )
+{
+ if ( !GetTFTeam() )
+ return NULL;
+
+ CBaseEntity *pFirstStation = NULL;
+
+ // Cycle through all the respawn stations on my team
+ for ( int i = 0; i < GetTFTeam()->GetNumObjects(); i++ )
+ {
+ CBaseObject *pObject = GetTFTeam()->GetObject(i);
+ if ( pObject->GetType() == OBJ_RESPAWN_STATION )
+ {
+ // Store off the first station we find
+ if ( !pFirstStation )
+ {
+ pFirstStation = pObject;
+ }
+
+ // Map specified initial spawnpoint?
+ if ( ((CObjectRespawnStation*)pObject)->IsInitialSpawnPoint() )
+ return pObject;
+ }
+ }
+
+ return pFirstStation;
+}
+
+
+
+CBaseEntity *FindEntityForward( CBasePlayer *pMe, bool fHull );
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CBaseTFPlayer::ClientCommand( const CCommand &args )
+{
+ if( HasClass() )
+ {
+ if ( GetPlayerClass()->ClientCommand( args ) )
+ return true;
+
+ const char *cmd = args[0];
+ if ( FStrEq( cmd, "emp" ) )
+ {
+ Msg( "Self-inflicted EMP: testing\n" );
+ float flTime = 10;
+ if ( args.ArgC() == 2 )
+ {
+ flTime = atof( args[ 1 ] );
+ }
+
+ AttemptToPowerup( POWERUP_EMP, flTime );
+ return true;
+ }
+
+ if ( FStrEq( cmd, "emp_target" ) )
+ {
+ CBaseEntity *pEntity = FindEntityForward( this, true );
+ if ( pEntity && pEntity->CanBePoweredUp() )
+ {
+ float flTime = 10;
+ if ( args.ArgC() == 2 )
+ {
+ flTime = atof( args[ 1 ] );
+ }
+ pEntity->AttemptToPowerup( POWERUP_EMP, flTime );
+ }
+ return true;
+ }
+
+ if ( FStrEq( cmd, "dmg_target" ) )
+ {
+ CBaseEntity *pEntity = FindEntityForward( this, true );
+ if ( pEntity && pEntity->m_takedamage )
+ {
+ float flDamage = 1;
+ if ( args.ArgC() == 2 )
+ {
+ flDamage = atof( args[ 1 ] );
+ }
+ CBaseEntity *world = CBaseEntity::Instance( engine->PEntityOfEntIndex( 0 ) );
+ if ( world )
+ {
+ pEntity->OnTakeDamage( CTakeDamageInfo( world, world, flDamage, DMG_GENERIC ) );
+ }
+ }
+ return true;
+ }
+ }
+
+ if ( !stricmp( cmd, "kd" ) )
+ {
+ Vector force( 0, 0, 0 );
+ if ( args.ArgC() == 1 )
+ {
+ force.x = random->RandomFloat( 0.5, 1.0 );
+ force.y = random->RandomFloat( 0.5, 1.0 );
+
+ if ( random->RandomFloat( 0, 1 ) > 0.5 )
+ {
+ force.x *= -1.0f;
+ }
+ if ( random->RandomFloat( 0, 1 ) > 0.5 )
+ {
+ force.y *= -1.0f;
+ }
+ force.z = random->RandomFloat( 0.5, 1.0 );
+ }
+ else
+ {
+ Vector fwd;
+ Vector right;
+ AngleVectors( GetAbsAngles(), &fwd, &right, NULL );
+
+ if ( !stricmp( args[ 1 ], "f" ) )
+ {
+ force = fwd * -1.0f;
+ }
+ else if ( !stricmp( args[ 1 ], "b" ) )
+ {
+ force = fwd;
+ }
+ else if ( !stricmp( args[ 1 ], "r" ) )
+ {
+ force = right * -1.0f;
+ }
+ else if ( !stricmp( args[ 1 ], "l" ) )
+ {
+ force = right;
+ }
+ else if ( !stricmp( args[ 1 ], "fr" ) )
+ {
+ force = fwd * -1.0f;
+ force += right * -1.0f;
+ }
+ else if ( !stricmp( args[ 1 ], "br" ) )
+ {
+ force = fwd;
+ force += right * -1.0f;
+ }
+ else if ( !stricmp( args[ 1 ], "fl" ) )
+ {
+ force = fwd * -1.0f;
+ force += right;
+ }
+ else if ( !stricmp( args[ 1 ], "bl" ) )
+ {
+ force = fwd;
+ force += right;
+ }
+
+ force.z = 0.8f;
+ VectorNormalize( force );
+ }
+
+ KnockDownPlayer( force, 500.0f, 3.0f );
+ return true;
+ }
+
+ if ( FStrEq( cmd, "veryweak" ) )
+ {
+ int ouch = m_iHealth - 1;
+
+ CBaseEntity *world = CBaseEntity::Instance( engine->PEntityOfEntIndex( 0 ) );
+ if ( world )
+ {
+ OnTakeDamage( CTakeDamageInfo( world, world, (float)ouch, DMG_GENERIC ) );
+ }
+
+ return true;
+ }
+
+ if ( FStrEq( cmd, "ragdoll" ) )
+ {
+ bool on = true;
+
+ if ( args.ArgC() >= 2 )
+ {
+ on = atoi( args[ 1 ] ) ? true : false;
+ }
+
+ if ( on )
+ {
+ Vector force = RandomVector( -500, 500 );
+ force.z = fabs( force.z );
+ force.z = MIN( 200.0f, force.z );
+
+ BecomeRagdollOnClient( force );
+ }
+ else
+ {
+ ClearClientRagdoll( true );
+ }
+ return true;
+ }
+
+ if ( FStrEq( cmd, "hbset" ) )
+ {
+ if ( args.ArgC() >= 2 )
+ {
+ SetHitboxSet( atoi( args[ 1 ] ) );
+ Msg( "Hitboxset forced to %i %s\n", GetHitboxSet(), GetHitboxSetName() );
+ }
+
+ return true;
+ }
+
+ return BaseClass::ClientCommand( args );
+}
+
+
+//=========================================================
+// Purpose: Override base TraceAttack
+//=========================================================
+void CBaseTFPlayer::TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr )
+{
+ if ( m_takedamage )
+ {
+ // Prevent team damage here so blood doesn't appear
+ if ( info.GetAttacker() )
+ {
+ // Take damage from myself
+ if ( InSameTeam( info.GetAttacker() ) && info.GetAttacker() != this )
+ return;
+ }
+
+ // If we hit our shield, ignore the damage
+ float flDamage = info.GetDamage();
+ if ( IsHittingShield( vecDir, &flDamage ))
+ return;
+
+ // Shield may have blocked some
+ CTakeDamageInfo subInfo = info;
+ subInfo.SetDamage( flDamage );
+
+ SetLastHitGroup( ptr->hitgroup );
+
+ // Hit groups aren't evaluated here, like base TraceAttack.
+ // Weapons factor hit location into flDamage before it gets here
+/* //SpawnBlood( ptr->endpos - (vecDir * 5), BloodColor(), subInfo.GetDamage() );
+ //TraceBleed( subInfo.GetDamage(), vecDir, ptr, subInfo.GetDamageType() );
+
+ // Show the personal shield effect.
+ // What we do here is collide the trace line with an ellipse that is slightly larger
+ // than the player and put the effect there.
+
+ // Translate the line so the player's (and the ellipse's) center is at the origin.
+ Vector vCenter = Center();
+ Vector vStart = ptr->startpos - vCenter;
+ Vector vEnd = ptr->endpos - vCenter;
+
+ // Figure out the ellipse dimensions.
+ Vector vDims = (WorldAlignMaxs() - WorldAlignMins()) * 0.5f;
+ Vector vEllipse = vDims * 1.5;
+
+ // Squash the line we're testing so we're testing against a sphere of radius 1 at the origin.
+ vStart /= vEllipse;
+ vEnd /= vEllipse;
+
+ // See where the line hits the sphere.
+ Vector vLineDir = vEnd - vStart;
+ float f1, f2;
+ if ( IntersectInfiniteRayWithSphere( vStart, vLineDir, vec3_origin, 1, &f1, &f2 ) )
+ {
+ // Use the closest hit point on the sphere.
+ float fMin = MIN( f1, f2 );
+ Vector vPos = vStart + vLineDir * fMin;
+
+ // Unsquash back to the ellipse's dimensions.
+ vPos *= vEllipse;
+
+ ShowPersonalShieldEffect( vPos, vecDir, subInfo.GetDamage() );
+ }
+*/
+
+ AddMultiDamage( subInfo, this );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Applies a force on the player when he takes damage
+//-----------------------------------------------------------------------------
+void CBaseTFPlayer::ApplyDamageForce( const CTakeDamageInfo &info, int nDamageToDo )
+{
+ if (nDamageToDo <= 0)
+ return;
+
+ if ( (info.GetDamageType() & (DMG_ENERGYBEAM | DMG_BLAST)) == 0 )
+ return;
+
+ if ( !info.GetInflictor() || (info.GetInflictor() == this) || info.GetAttacker()->IsSolidFlagSet(FSOLID_TRIGGER) )
+ return;
+
+ // Don't blow ragdolls around
+ if ( IsClientRagdoll() )
+ return;
+
+ // Don't bother with crouched players, or classes that have other rules about it
+ if ( GetFlags() & FL_DUCKING )
+ return;
+
+ if (!GetPlayerClass() || !GetPlayerClass()->ShouldApplyDamageForce( info ))
+ return;
+
+ Vector vecDir;
+ // If the inflictor isn't moving, use the delta between it & me. If it's moving, use it's velocity.
+ Vector vecInflictorVelocity;
+ info.GetInflictor()->GetVelocity( &vecInflictorVelocity, NULL );
+
+ // Explosives never use the velocity of the inflictor
+ if ( !(info.GetDamageType() & DMG_BLAST) && vecInflictorVelocity != vec3_origin )
+ {
+ vecDir = vecInflictorVelocity;
+ }
+ else
+ {
+ vecDir = WorldSpaceCenter( );
+ vecDir -= info.GetInflictor()->WorldSpaceCenter( );
+ }
+ VectorNormalize( vecDir );
+
+ float flForce = (nDamageToDo * 2) + 20;
+ if (flForce > 1000.0)
+ flForce = 1000.0;
+
+ // Escorts get knocked half as far
+ if ( PlayerClass() == TFCLASS_ESCORT )
+ {
+ flForce *= 0.5;
+ }
+
+ vecDir *= flForce;
+
+ if ( (GetMoveType() != MOVETYPE_FLY) && (GetMoveType() != MOVETYPE_FLYGRAVITY) && ((GetFlags() & FL_ONGROUND) != 0) )
+ {
+ // Need large x-y component to overcome walking friction
+ vecDir.x *= 3;
+ vecDir.y *= 3;
+ }
+
+ Vector vecNewVelocity = GetAbsVelocity();
+ vecNewVelocity += vecDir;
+
+ Vector vecTestVel = vecNewVelocity;
+ float flLen = VectorNormalize( vecTestVel );
+ if (flLen > MAX_EXPLOSIVE_VELOCITY)
+ VectorMultiply( vecTestVel, MAX_EXPLOSIVE_VELOCITY, vecNewVelocity );
+
+ SetAbsVelocity( vecNewVelocity );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Deal damage to the player
+//-----------------------------------------------------------------------------
+int CBaseTFPlayer::OnTakeDamage( const CTakeDamageInfo &info )
+{
+ if ( !IsAlive() )
+ return 0;
+
+ //if ( GetFlags() & FL_GODMODE )
+ //return 0;
+
+ // Generate a global order event.
+ COrderEvent_PlayerDamaged event;
+ event.m_pPlayerDamaged = this;
+ event.m_TakeDamageInfo = info;
+ GlobalOrderEvent( &event );
+
+ // Don't do damage if the player's in a vehicle, in a non-damagable spot.
+ if ( IsInAVehicle() && m_hVehicle.Get() )
+ {
+ IServerVehicle* pVehicle = m_hVehicle.Get()->GetServerVehicle();
+ Assert( pVehicle );
+ int nRole = pVehicle->GetPassengerRole(this);
+
+ if( ( nRole < 0 )
+ || !pVehicle->IsPassengerVisible(nRole)
+ || !pVehicle->IsPassengerDamagable(nRole) )
+ {
+ return 0;
+ }
+ }
+
+ // Check teams
+ CBaseTFPlayer *pPlayer = (CBaseTFPlayer*)info.GetAttacker();
+ if ( pPlayer )
+ {
+ // Take damage from myself
+ if ( pPlayer != this )
+ {
+ if ( InSameTeam(pPlayer) )
+ {
+ return 0;
+ }
+ else
+ {
+ // Store off the last time we were damaged by an enemy so commandos can
+ // get orders to assist.
+ m_flLastTimeDamagedByEnemy = gpGlobals->curtime;
+ }
+ }
+ }
+
+ CTakeDamageInfo subInfo = info;
+
+ // Let the playerclass at it
+ if ( GetPlayerClass() )
+ {
+ subInfo.SetDamage( GetPlayerClass()->OnTakeDamage( subInfo ) );
+ }
+
+ if ( !subInfo.GetDamage() )
+ return 0;
+
+ //Msg( "Weapon did: %f\n", flDamage );
+
+ int iDamageToDo = Ceil2Int( subInfo.GetDamage() );
+
+ if ( !(GetFlags() & FL_GODMODE) )
+ {
+ // Only certain damage types knock players around
+ ApplyDamageForce( info, iDamageToDo );
+
+ m_iHealth = MAX(0, m_iHealth - iDamageToDo);
+ }
+
+ //Msg( "m_iHealth: %d\n\n", m_iHealth );
+
+ // Dead?
+ if ( m_iHealth < 1 )
+ {
+ Event_Killed( subInfo );
+ }
+
+ // Let the client know
+ // Try and figure out where the damage is coming from
+ Vector vecDamageOrigin = info.GetReportedPosition();
+ // If we didn't get an origin to use, try using the attacker's origin
+ if ( vecDamageOrigin == vec3_origin && info.GetAttacker() )
+ {
+ vecDamageOrigin = info.GetAttacker()->GetAbsOrigin();
+ }
+
+ CSingleUserRecipientFilter user( this );
+ UserMessageBegin( user, "Damage" );
+ WRITE_BYTE( clamp( iDamageToDo, 0, 255 ) );
+ WRITE_FLOAT( vecDamageOrigin.x ); // BUG: Should be fixed point (to hud) not floats
+ WRITE_FLOAT( vecDamageOrigin.y ); // BUG: However, the HUD does _not_ implement bitfield messages (yet)
+ WRITE_FLOAT( vecDamageOrigin.z ); // BUG: We use WRITE_VEC3COORD for everything else
+ MessageEnd();
+
+ // Do special explosion damage effect
+ if ( info.GetDamageType() & DMG_BLAST )
+ {
+ OnDamagedByExplosion( info );
+ }
+
+ return iDamageToDo;
+}
+
+
+void CBaseTFPlayer::ShowPersonalShieldEffect(
+ const Vector &vOffsetFromEnt,
+ const Vector &vIncomingDirection,
+ float flDamage )
+{
+ Vector vNormalized = vIncomingDirection;
+ VectorNormalize( vNormalized );
+
+ EntityMessageBegin( this );
+ WRITE_BYTE( PLAYER_MSG_PERSONAL_SHIELD );
+ WRITE_VEC3COORD( vOffsetFromEnt );
+ WRITE_VEC3NORMAL( vNormalized );
+ WRITE_SHORT( (short)flDamage );
+ MessageEnd();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Player is being healed
+//-----------------------------------------------------------------------------
+int CBaseTFPlayer::TakeHealth( float flHealth, int bitsDamageType )
+{
+ if ( m_iHealth == m_iMaxHealth )
+ return 0;
+
+ // Heal the location
+ float flAmountToHeal = flHealth;
+ if ( flAmountToHeal > (m_iMaxHealth - m_iHealth) )
+ flAmountToHeal = (m_iMaxHealth - m_iHealth);
+ m_iHealth += flAmountToHeal;
+
+ //Msg( "Health: %d\n", m_iHealth );
+
+ return flAmountToHeal;
+}
+
+
+//=====================================================================
+// MENU HANDLING
+//=====================================================================
+void CBaseTFPlayer::MenuDisplay( void )
+{
+ if ( !m_pCurrentMenu )
+ {
+ m_MenuRefreshTime = 0;
+ return;
+ }
+
+ if ( m_MenuRefreshTime > gpGlobals->curtime )
+ {
+ // guard against sudden clock changes
+ m_MenuRefreshTime = MIN( m_MenuRefreshTime, gpGlobals->curtime + MENU_UPDATETIME );
+ return;
+ }
+
+ m_MenuRefreshTime = gpGlobals->curtime + MENU_UPDATETIME;
+
+ if ( m_pCurrentMenu )
+ m_pCurrentMenu->Display( this );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CBaseTFPlayer::MenuInput( int iInput )
+{
+ if ( m_pCurrentMenu )
+ {
+ return m_pCurrentMenu->Input( this, iInput );
+ }
+
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseTFPlayer::MenuReset( void )
+{
+ CSingleUserRecipientFilter user( this );
+ user.MakeReliable();
+
+ UserMessageBegin( user, "ShowMenu" );
+ WRITE_SHORT( 0 );
+ WRITE_CHAR( 0 ); // display time (-1 means unlimited)
+ WRITE_BYTE( false ); // is there more message to come? no
+ WRITE_STRING( "" );
+ MessageEnd();
+
+ Q_strncpy( m_MenuStringBuffer, "" , sizeof(m_MenuStringBuffer) );
+ m_MenuRefreshTime = m_MenuDisplayTime = 0;
+
+ m_pCurrentMenu = NULL;
+};
+
+//-----------------------------------------------------------------------------
+// Purpose: Enables/disables tactical/map view for the player
+// Input : bTactical - true == enable it
+//-----------------------------------------------------------------------------
+void CBaseTFPlayer::ShowTacticalView( bool bTactical )
+{
+ // TODO: Decide if we are going to keep the tactical view in TF2
+ if ( !inv_demo.GetBool() )
+ return;
+
+ m_bSwitchingView = true;
+ m_TFLocal.m_nInTacticalView = bTactical ? 1 : 0;
+}
+
+//-----------------------------------------------------------------------------
+// returns true if we're in tactical view
+//-----------------------------------------------------------------------------
+bool CBaseTFPlayer::IsInTacticalView( void ) const
+{
+ return m_TFLocal.m_nInTacticalView;
+}
+
+int CBaseTFPlayer::UpdateTransmitState()
+{
+ return SetTransmitState( FL_EDICT_FULLCHECK );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Note, an entity can override the send table ( e.g., to send less data or to send minimal data for
+// objects ( prob. players ) that are not in the pvs.
+// Input : **ppSendTable -
+// *recipient -
+// *pvs -
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+int CBaseTFPlayer::ShouldTransmit( const CCheckTransmitInfo *pInfo )
+{
+ // Don't transmit if we have no team or class
+ if ((PlayerClass() == TFCLASS_UNDECIDED) || (GetTeamNumber() == 0))
+ return FL_EDICT_DONTSEND;
+
+ // Thermal vision in effect, if so, cull some players who are too far away
+ CBaseTFPlayer *pPlayer = ( ( CBaseTFPlayer * )CBaseEntity::Instance( pInfo->m_pClientEnt ) );
+ if ( pPlayer )
+ {
+ if ( pPlayer->IsUsingThermalVision() )
+ {
+ // Do a radius check, and force sending of guys nearby (so we can see them through walls)
+ Vector dist = GetAbsOrigin() - pPlayer->GetAbsOrigin();
+ if ( dist.Length() < THERMAL_VISION_RADIUS )
+ return FL_EDICT_ALWAYS;
+ }
+
+ // If the player we might see is camouflaged and not on our team, we can preclude based
+ // on distance
+ if ( IsCamouflaged() && !InSameTeam( pPlayer ) )
+ {
+ Vector dist = GetAbsOrigin() - pPlayer->GetAbsOrigin();
+ if ( dist.Length() > CAMO_OUTER_RADIUS )
+ {
+ return FL_EDICT_ALWAYS;
+ }
+ }
+ }
+
+ // Use default pvs etc. rules
+ return BaseClass::ShouldTransmit( pInfo );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CTechnologyTree *CBaseTFPlayer::GetTechTree( void )
+{
+ CTFTeam *pTeam = GetTFTeam();
+ if ( pTeam )
+ return pTeam->m_pTechnologyTree;
+
+ return NULL;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+int CBaseTFPlayer::PlayerClass( void )
+{
+ return m_iPlayerClass;
+}
+
+CPlayerClass *CBaseTFPlayer::GetPlayerClass()
+{
+ return m_PlayerClasses.GetPlayerClass( PlayerClass() );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : *name -
+//-----------------------------------------------------------------------------
+void CBaseTFPlayer::SetPreferredTechnology( CTechnologyTree *pTechnologyTree, int iTechIndex )
+{
+ Assert( pTechnologyTree );
+
+ // Have to be on a team to vote for tech
+ CTFTeam *pTeam = GetTFTeam();
+ if ( !pTeam )
+ return;
+
+ if ( iTechIndex == -1 )
+ {
+ m_nPreferredTechnology = -1;
+ }
+ else
+ {
+ if ( iTechIndex < 0 || iTechIndex >= pTechnologyTree->GetNumberTechnologies() )
+ {
+ Msg( "%s tried to set voting preference to unknown technology index : %d\n",
+ GetPlayerName(), iTechIndex );
+ return;
+ }
+ CBaseTechnology *tech = pTechnologyTree->GetTechnology( iTechIndex );
+ if ( !tech )
+ return;
+
+ // Has the tech got incomplete dependancies?
+ if ( tech->HasInactiveDependencies() )
+ return;
+ // Already have it?
+ if ( tech->GetAvailable() )
+ return;
+ // Can't prefer a hidden tech
+ if ( tech->IsHidden() )
+ return;
+
+ m_nPreferredTechnology = iTechIndex;
+ }
+
+ pTeam->RecomputePreferences();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Output : int
+//-----------------------------------------------------------------------------
+int CBaseTFPlayer::GetPreferredTechnology( void )
+{
+ return m_nPreferredTechnology;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : *name -
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CBaseTFPlayer::HasNamedTechnology( const char *name )
+{
+ if ( GetTFTeam() == NULL )
+ return false;
+
+ return GetTFTeam()->HasNamedTechnology( name );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Networking is about to update this player, let it override and specify it's own pvs
+// Input : **pvs -
+// **pas -
+//-----------------------------------------------------------------------------
+void CBaseTFPlayer::SetupVisibility( CBaseEntity *pViewEntity, unsigned char *pvs, int pvssize )
+{
+ // Normal PVS
+ BaseClass::SetupVisibility( pViewEntity, pvs, pvssize );
+
+ // PVS has an additional origin
+ if ( m_vecAdditionalPVSOrigin != vec3_origin )
+ {
+ // Add an additional origin to the pvs
+ engine->AddOriginToPVS( m_vecAdditionalPVSOrigin );
+ }
+
+ if ( m_vecCameraPVSOrigin != vec3_origin )
+ {
+ engine->AddOriginToPVS( m_vecCameraPVSOrigin );
+ }
+
+ // If in tactical mode, merge in pvs from all of our teammates, too
+ // send all the others team info
+ if ( m_TFLocal.m_nInTacticalView )
+ {
+ int i;
+ for ( i = 1; i <= gpGlobals->maxClients; i++ )
+ {
+ CBaseTFPlayer *plr = ToBaseTFPlayer( UTIL_PlayerByIndex(i) );
+ if ( plr &&
+ ( plr != this ) &&
+ ( plr->TeamID() == TeamID() ) )
+ {
+ Vector org;
+ org = plr->EyePosition();
+
+ engine->AddOriginToPVS( org );
+ }
+ }
+ }
+}
+
+//--------------------------------------------------------------------------------------------------------------
+// ORDERS
+//-----------------------------------------------------------------------------
+// Purpose: Assign the player to the specified order
+//-----------------------------------------------------------------------------
+void CBaseTFPlayer::SetOrder( COrder *pOrder )
+{
+ if ( m_hSelectedOrder.Get() && m_hSelectedOrder != pOrder )
+ {
+ m_hSelectedOrder->SetOwner( NULL );
+ }
+ m_hSelectedOrder = pOrder;
+}
+
+
+int CBaseTFPlayer::GetNumResourceZoneOrders()
+{
+ return GetTFTeam()->CountOrders( COUNTORDERS_TYPE | COUNTORDERS_OWNER, ORDER_ATTACK, 0, this ) +
+ GetTFTeam()->CountOrders( COUNTORDERS_TYPE | COUNTORDERS_OWNER, ORDER_DEFEND, 0, this ) +
+ GetTFTeam()->CountOrders( COUNTORDERS_TYPE | COUNTORDERS_OWNER, ORDER_CAPTURE, 0, this );
+}
+
+
+void CBaseTFPlayer::KillResourceZoneOrders()
+{
+ if( GetNumResourceZoneOrders() )
+ GetTFTeam()->RemoveOrdersToPlayer( this );
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+// DEPLOYMENT
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseTFPlayer::StartDeploying( void )
+{
+ if ( !GetPlayerClass() )
+ return;
+
+ m_bDeploying = true;
+ m_vecDeployedAngles = GetLocalAngles();
+
+ // No pitch or roll, though
+ m_vecDeployedAngles.SetX( 0 );
+ m_vecDeployedAngles.SetZ( 0 );
+
+ SetCantMove( true );
+ m_flFinishedDeploying = gpGlobals->curtime + GetPlayerClass()->GetDeployTime();
+
+ SetAnimation( PLAYER_START_AIMING );
+
+ EmitSound( "BaseTFPlayer.StartDeploying" );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseTFPlayer::StartUnDeploying( void )
+{
+ if ( !GetPlayerClass() )
+ return;
+
+ m_bUnDeploying = true;
+ SetCantMove( true );
+ m_flFinishedDeploying = gpGlobals->curtime + GetPlayerClass()->GetDeployTime();
+ SetAnimation( PLAYER_LEAVE_AIMING );
+
+ EmitSound( "BaseTFPlayer.StartUnDeploying" );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseTFPlayer::CheckDeployFinish( void )
+{
+ // Check to see if deployment has finished
+ if ( m_bDeploying )
+ {
+ if ( gpGlobals->curtime > m_flFinishedDeploying )
+ {
+ FinishDeploying();
+ }
+ return;
+ }
+
+ // Check to see if un-deployment has finished
+ if ( m_bUnDeploying )
+ {
+ if ( gpGlobals->curtime > m_flFinishedDeploying )
+ {
+ FinishUnDeploying();
+ }
+ return;
+ }
+
+ // Check to see if the player's trying to move while deployed
+ if ( IsAlive() && m_bDeployed )
+ {
+ if ( m_nButtons & (IN_FORWARD | IN_BACK | IN_MOVELEFT | IN_MOVERIGHT ) )
+ {
+ StartUnDeploying();
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseTFPlayer::FinishDeploying( void )
+{
+ m_bDeploying = false;
+ m_bDeployed = true;
+ SetCantMove( true );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseTFPlayer::FinishUnDeploying( void )
+{
+ m_bUnDeploying = false;
+ m_bDeployed = false;
+ SetCantMove( false );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CBaseTFPlayer::IsDeployed( void )
+{
+ return m_bDeployed;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CBaseTFPlayer::IsDeploying( void )
+{
+ return (m_bDeploying || m_bUnDeploying);
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CBaseTFPlayer::IsUnDeploying( void )
+{
+ return m_bUnDeploying;
+}
+
+void CBaseTFPlayer::OnVehicleStart()
+{
+ // Do any class-specific stuff
+ if (GetPlayerClass())
+ {
+ GetPlayerClass()->OnVehicleStart();
+ }
+
+ IServerVehicle *pVehicle = GetVehicle();
+ CBaseCombatWeapon *weapon = GetActiveWeapon();
+ if ( pVehicle && weapon )
+ {
+ // Get Role for this player
+ int role = pVehicle->GetPassengerRole( this );
+ bool allowweapons = pVehicle->IsPassengerUsingStandardWeapons( role );
+ if ( !allowweapons )
+ {
+ weapon->Holster();
+ }
+ }
+}
+
+void CBaseTFPlayer::OnVehicleEnd( Vector &playerDestPosition )
+{
+ // Do any class-specific stuff
+ if (GetPlayerClass())
+ {
+ GetPlayerClass()->OnVehicleEnd();
+ }
+
+ Vector vNewPos;
+ if ( !EntityPlacementTest( this, playerDestPosition, vNewPos, true) )
+ {
+ Warning("Can't find valid place to exit vehicle.\n");
+ return;
+ }
+
+ // Move the player up a bit to be safe
+ playerDestPosition = vNewPos + Vector(0,0,16);
+
+ CBaseCombatWeapon *weapon = GetActiveWeapon();
+ if ( weapon )
+ {
+ weapon->Deploy();
+ }
+}
+
+//--------------------------------------------------------------------------------------------------------------
+// Purpose:
+//--------------------------------------------------------------------------------------------------------------
+bool CBaseTFPlayer::CanGetInVehicle( void )
+{
+ // Class-specific?
+ if ( GetPlayerClass() )
+ {
+ return GetPlayerClass()->CanGetInVehicle();
+ }
+
+ return true;
+}
+
+
+CVehicleTeleportStation* CBaseTFPlayer::GetSelectedMCV() const
+{
+ return dynamic_cast< CVehicleTeleportStation* >( m_hSelectedMCV.Get() );
+}
+
+
+void CBaseTFPlayer::SetSelectedMCV( CVehicleTeleportStation *pMCV )
+{
+ m_hSelectedMCV = pMCV;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Restore this player's ammo count to it's starting state
+//-----------------------------------------------------------------------------
+bool CBaseTFPlayer::ResupplyAmmo( float flPercentage, ResupplyReason_t reason )
+{
+ if ( !GetPlayerClass() )
+ return false;
+
+ return GetPlayerClass()->ResupplyAmmo(flPercentage, reason);
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Medic has provided this player with a health boost
+//-----------------------------------------------------------------------------
+void CBaseTFPlayer::TakeHealthBoost( int iHealthBoost, int iTarget, int iDuration )
+{
+ m_iHealth += iHealthBoost;
+ m_iHealthBoostTarget = iTarget;
+ m_flHealthBoostDecrement = ceil((m_iHealth - iTarget) / (float)iDuration);
+
+ // Start the health ticking down
+ m_flHealthBoostTime = gpGlobals->curtime + 1.0;
+
+ if ( iTarget >= m_iMaxHealth )
+ {
+ m_bBuffHealthBoost = true;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Remove health buffs
+//-----------------------------------------------------------------------------
+void CBaseTFPlayer::RemoveHealthBoost( void )
+{
+ m_bBuffHealthBoost = false;
+ m_iHealthBoostTarget = 0;
+ m_flHealthBoostTime = 0;
+
+ if ( m_iHealth > m_iMaxHealth )
+ {
+ m_iHealth = m_iMaxHealth;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Check the state of all buffs on this player
+//-----------------------------------------------------------------------------
+void CBaseTFPlayer::CheckBuffs( void )
+{
+ // Health boost?
+ if ( m_bBuffHealthBoost )
+ {
+ // Dropped below normal max health?
+ if ( m_iHealth <= m_iHealthBoostTarget )
+ {
+ RemoveHealthBoost();
+ }
+ else
+ {
+ if ( m_flHealthBoostTime < gpGlobals->curtime )
+ {
+ // Ticking down from a boost? or suffering poison damage?
+ if ( m_iHealth > m_iMaxHealth )
+ {
+ // Drop back to normal health in 20 seconds
+ m_iHealth -= m_flHealthBoostDecrement;
+ }
+
+ m_flHealthBoostTime = gpGlobals->curtime + 1.0;
+ }
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Powerup has just started
+//-----------------------------------------------------------------------------
+void CBaseTFPlayer::PowerupStart( int iPowerup, float flAmount, CBaseEntity *pAttacker, CDamageModifier *pDamageModifier )
+{
+ Assert( iPowerup >= 0 && iPowerup < MAX_POWERUPS );
+
+ switch( iPowerup )
+ {
+ case POWERUP_BOOST:
+ {
+ m_hLastBoostEntity = pAttacker;
+
+ // Power up their shield
+ if ( GetCombatShield() )
+ {
+ GetCombatShield()->AddShieldHealth( 0.06 );
+ }
+
+ // Let their playerclass know
+ GetPlayerClass()->PowerupStart( iPowerup, flAmount, pAttacker, pDamageModifier );
+ }
+ break;
+
+ case POWERUP_EMP:
+ {
+ // Let the playerclass know about it
+ GetPlayerClass()->PowerupStart( iPowerup, flAmount, pAttacker, pDamageModifier );
+ }
+ break;
+
+ case POWERUP_RUSH:
+ {
+ // Speed up
+ // We need to set this here so RecalculateSpeed() can check HasPowerup(POWERUP_RUSH)
+ m_iPowerups |= (1 << iPowerup);
+ RecalculateSpeed();
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ BaseClass::PowerupStart( iPowerup, flAmount, pAttacker, pDamageModifier );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Powerup has just started
+//-----------------------------------------------------------------------------
+void CBaseTFPlayer::PowerupEnd( int iPowerup )
+{
+ switch( iPowerup )
+ {
+ case POWERUP_EMP:
+ {
+ GetPlayerClass()->PowerupEnd(iPowerup);
+ }
+ break;
+
+ case POWERUP_RUSH:
+ {
+ // Slow down
+ RecalculateSpeed();
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ BaseClass::PowerupEnd( iPowerup );
+}
+
+//--------------------------------------------------------------------------------------------------------------
+// OBJECTS
+//-----------------------------------------------------------------------------
+// Purpose: Return true if this player's allowed to build another one of the specified object
+//-----------------------------------------------------------------------------
+int CBaseTFPlayer::CanBuild( int iObjectType )
+{
+ if ( iObjectType < 0 || iObjectType >= OBJ_LAST )
+ return CB_NOT_RESEARCHED;
+
+ if ( GetPlayerClass() )
+ {
+ return GetPlayerClass()->CanBuild( iObjectType );
+ }
+
+ return CB_NOT_RESEARCHED;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Get the number of objects of the specified type that this player has
+//-----------------------------------------------------------------------------
+int CBaseTFPlayer::GetNumObjects( int iObjectType )
+{
+ int iCount = 0;
+ for (int i = 0; i < GetObjectCount(); i++)
+ {
+ if ( !GetObject(i) )
+ continue;
+
+ if ( GetObject(i)->GetType() == iObjectType )
+ {
+ iCount++;
+ }
+ }
+
+ return iCount;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+int CBaseTFPlayer::GetObjectCount( void )
+{
+ return m_TFLocal.m_aObjects.Count();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CBaseObject *CBaseTFPlayer::GetObject( int index )
+{
+ return m_TFLocal.m_aObjects[index].Get();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Returns true if this player is building something
+//-----------------------------------------------------------------------------
+bool CBaseTFPlayer::IsBuilding( void )
+{
+ CWeaponBuilder *pBuilder = GetWeaponBuilder();
+ if ( pBuilder )
+ return pBuilder->IsBuilding();
+
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Object built by this player has been destroyed
+//-----------------------------------------------------------------------------
+void CBaseTFPlayer::OwnedObjectDestroyed( CBaseObject *pObject )
+{
+ TRACE_OBJECT( UTIL_VarArgs( "%0.2f CBaseTFPlayer::OwnedObjectDestroyed player %s object %p:%s\n", gpGlobals->curtime,
+ GetPlayerName(),
+ pObject,
+ pObject->GetClassname() ) );
+
+ if ( GetPlayerClass() )
+ {
+ GetPlayerClass()->OwnedObjectDestroyed( pObject );
+ }
+
+ RemoveObject( pObject );
+
+ // Tell our builder weapon so it recalculates the state of the build icons
+ CWeaponBuilder *pBuilder = GetWeaponBuilder();
+ if ( pBuilder )
+ {
+ pBuilder->GainedNewTechnology( NULL );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Removes an object from the player
+//-----------------------------------------------------------------------------
+void CBaseTFPlayer::RemoveObject( CBaseObject *pObject )
+{
+ TRACE_OBJECT( UTIL_VarArgs( "%0.2f CBaseTFPlayer::RemoveObject %p:%s from player %s\n", gpGlobals->curtime,
+ pObject,
+ pObject->GetClassname(),
+ GetPlayerName() ) );
+
+ Assert( pObject );
+ for (int i = m_TFLocal.m_aObjects.Count(); --i >= 0; )
+ {
+ // Also, while we're at it, remove all other bogus ones too...
+ if ((!m_TFLocal.m_aObjects[i].Get()) || (m_TFLocal.m_aObjects[i] == pObject))
+ {
+ m_TFLocal.m_aObjects.FastRemove(i);
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : *pObject -
+// *pNewOwner -
+//-----------------------------------------------------------------------------
+void CBaseTFPlayer::OwnedObjectChangeTeam( CBaseObject *pObject, CBaseTFPlayer *pNewOwner )
+{
+ TRACE_OBJECT( UTIL_VarArgs( "%0.2f CBaseTFPlayer::OwnedObjectChangeTeam player %s object %p:%s new player %s\n", gpGlobals->curtime,
+ GetPlayerName(),
+ pObject,
+ pObject->GetClassname(),
+ pNewOwner->GetPlayerName() ) );
+
+ if ( pNewOwner && pNewOwner->GetPlayerClass() )
+ {
+ pNewOwner->GetPlayerClass()->OwnedObjectChangeFromTeam( pObject, this );
+ }
+
+ // Remove from my list of objects
+ RemoveObject( pObject );
+
+ // Add to new team
+ if ( pNewOwner )
+ {
+ pNewOwner->AddObject( pObject );
+ }
+
+ if ( GetPlayerClass() )
+ {
+ GetPlayerClass()->OwnedObjectChangeToTeam( pObject, pNewOwner );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : *pZone -
+// Output : int
+//-----------------------------------------------------------------------------
+int CBaseTFPlayer::NumPumpsOnResourceZone( CResourceZone *pZone )
+{
+ int ret = 0;
+
+ for( int iObj=0; iObj < GetObjectCount(); iObj++ )
+ {
+ CBaseObject *pObj = GetObject(iObj);
+
+ if( pObj->GetType() == OBJ_RESOURCEPUMP )
+ {
+ CObjectResourcePump *pPump = (CObjectResourcePump*)pObj;
+
+ // Ok, this guy already has a pump here.
+ if( pPump->GetResourceZone() == pZone )
+ ++ret;
+ }
+ }
+
+ return ret;
+}
+
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Remove all the player's objects
+// If bForceAll is set, remove all of them immediately.
+// Otherwise, make them all deteriorate over time.
+// If iClass is passed in, don't remove any objects that can be built
+// by that class. If bReturnResources is set, the cost of any destroyed
+// objects will be returned to the player.
+//-----------------------------------------------------------------------------
+void CBaseTFPlayer::RemoveAllObjects( bool bForceAll, int iClass, bool bReturnResources )
+{
+ // Remove all the player's objects
+ int iSize = GetObjectCount();
+ for (int i = iSize-1; i >= 0; i--)
+ {
+ CBaseObject *obj = GetObject(i);
+ Assert( obj );
+ if ( !obj )
+ {
+ continue;
+ }
+
+ if ( !bForceAll )
+ {
+ if ( iClass )
+ {
+ // Can our new class build this object?
+ if ( ClassCanBuild( iClass, obj->GetType() ) )
+ continue;
+ }
+
+ // Vehicles don't deteriorate when their owner changes teams/leaves.
+ // They'll deteriorate naturally if they're unused for a while.
+ if ( IsObjectAVehicle(obj->GetType()) )
+ {
+ RemoveObject( obj );
+
+ // Just remove it from my list
+ obj->SetBuilder( NULL );
+ continue;
+ }
+ }
+
+ // Return the cost of the object?
+ if ( bReturnResources )
+ {
+ GetPlayerClass()->PickupObject( obj );
+ }
+
+ // Remove or deteriorate?
+ if ( bForceAll )
+ {
+ UTIL_Remove( obj );
+ }
+ else
+ {
+ OwnedObjectDestroyed( obj );
+ obj->StartDeteriorating();
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseTFPlayer::StopPlacement( void )
+{
+ // Tell our builder weapon
+ CWeaponBuilder *pBuilder = GetWeaponBuilder();
+ if ( pBuilder )
+ {
+ pBuilder->StopPlacement();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Player has started building an object
+//-----------------------------------------------------------------------------
+int CBaseTFPlayer::StartedBuildingObject( int iObjectType )
+{
+ // Tell our playerclass
+ if ( GetPlayerClass() )
+ return GetPlayerClass()->StartedBuildingObject( iObjectType );
+
+ return 0;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Player has aborted building something
+//-----------------------------------------------------------------------------
+void CBaseTFPlayer::StoppedBuilding( int iObjectType )
+{
+ // Tell our playerclass
+ if ( GetPlayerClass() )
+ {
+ GetPlayerClass()->StoppedBuilding( iObjectType );
+ }
+
+ // Tell our builder weapon
+ CWeaponBuilder *pBuilder = GetWeaponBuilder();
+ if ( pBuilder )
+ {
+ pBuilder->StoppedBuilding( iObjectType );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Object has been built by this player
+//-----------------------------------------------------------------------------
+void CBaseTFPlayer::FinishedObject( CBaseObject *pObject )
+{
+ AddObject( pObject );
+
+ // Tell our playerclass
+ if ( GetPlayerClass() )
+ {
+ GetPlayerClass()->FinishedObject( pObject );
+ }
+
+ // Tell our builder weapon
+ CWeaponBuilder *pBuilder = GetWeaponBuilder();
+ if ( pBuilder )
+ {
+ pBuilder->FinishedObject();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Add the specified object to this player's object list.
+//-----------------------------------------------------------------------------
+void CBaseTFPlayer::AddObject( CBaseObject *pObject )
+{
+ TRACE_OBJECT( UTIL_VarArgs( "%0.2f CBaseTFPlayer::AddObject adding object %p:%s to player %s\n", gpGlobals->curtime, pObject, pObject->GetClassname(), GetPlayerName() ) );
+
+ // Make a handle out of it
+ CHandle<CBaseObject> hObject;
+ hObject = pObject;
+
+ // Make sure it's not in the list already
+ bool alreadyInList = (m_TFLocal.m_aObjects.Find( hObject ) != -1);
+ Assert( !alreadyInList );
+ if ( !alreadyInList )
+ {
+ m_TFLocal.m_aObjects.AddToTail( hObject );
+ }
+
+ // Stop it deterioating, if it is
+ pObject->StopDeteriorating();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseTFPlayer::SetWeaponBuilder( CWeaponBuilder *pBuilder )
+{
+ m_hWeaponBuilder = pBuilder;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CWeaponBuilder *CBaseTFPlayer::GetWeaponBuilder( void )
+{
+ return m_hWeaponBuilder;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : *attacker -
+// sourceDir -
+// duration -
+//-----------------------------------------------------------------------------
+void CBaseTFPlayer::KnockDownPlayer( const Vector& sourceDir, float magnitude, float duration )
+{
+ // Already knocked down
+ if ( m_TFLocal.m_bKnockedDown )
+ return;
+ // In a vehicle
+ if ( IsInAVehicle() )
+ return;
+
+ m_TFLocal.m_bKnockedDown = true;
+
+ // Randomize it a bit
+ Vector jitter( 0, 0, 0 );
+ //jitter.Random( -0.1, 0.1 );
+
+ Vector dir = sourceDir + jitter;
+
+ VectorNormalize( dir );
+
+ Vector force = dir * magnitude;
+ ApplyAbsVelocityImpulse( force );
+
+ VectorAngles( dir, m_TFLocal.m_vecKnockDownDir.GetForModify() );
+
+ QAngle ang = GetAbsAngles();
+ Vector forward, right;
+ AngleVectors( ang, &forward, &right, NULL );
+
+ float dotFwd = dir.Dot( forward );
+ float dotRight = dir.Dot( right );
+
+ if ( dotFwd >= 0)
+ {
+ // if get hit from behind, pitch down a bit
+ m_TFLocal.m_vecKnockDownDir.SetX( dotFwd * 20.0f );
+ // look in the direction you fell
+ m_TFLocal.m_vecKnockDownDir.SetZ( dotRight * 80.0f );
+ }
+ else
+ {
+ //Invert knock yaw if hit from front, so you are looking straight up at the direction
+ // the hit cam efrom
+ m_TFLocal.m_vecKnockDownDir += QAngle( 0, 180, 0 );
+ // Look up in the air
+ m_TFLocal.m_vecKnockDownDir.SetX( fabs( dotFwd ) * -60.0f );
+ // And a bit to the side the hit came from
+ m_TFLocal.m_vecKnockDownDir.SetZ( dotRight * 20.0f );
+ }
+
+ m_flKnockdownEndTime = gpGlobals->curtime + duration;
+
+ // Play some kind of knockdown sound
+ EmitSound( "BaseTFPlayer.KnockedDown" );
+
+ if ( BecomeRagdollOnClient( force ) )
+ {
+ // We we are using ragdoll flight, then don't change underlying player
+ // velocity
+ ApplyAbsVelocityImpulse( -force );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseTFPlayer::ResetKnockdown( void )
+{
+ // Don't get up if I'm dead
+ if ( IsAlive() )
+ {
+ if ( !ClearClientRagdoll( true ) )
+ return;
+ }
+
+ m_TFLocal.m_bKnockedDown = false;
+ m_TFLocal.m_vecKnockDownDir.Init();
+ m_flKnockdownEndTime = 0.0f;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CBaseTFPlayer::IsKnockedDown( void )
+{
+ return m_TFLocal.m_bKnockedDown;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseTFPlayer::CheckKnockdown( void )
+{
+ if ( !m_TFLocal.m_bKnockedDown )
+ return;
+
+ if ( gpGlobals->curtime < m_flKnockdownEndTime )
+ return;
+
+ // Remove knockdown
+ ResetKnockdown();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CBaseTFPlayer::IsGagged( void )
+{
+ return m_bGagged;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : gag -
+//-----------------------------------------------------------------------------
+void CBaseTFPlayer::SetGagged( bool gag )
+{
+ m_bGagged = gag;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CBaseTFPlayer::CanSpeak( void )
+{
+ return !IsGagged();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CBaseTFPlayer::IsUsingThermalVision( void )
+{
+ return m_TFLocal.m_bThermalVision;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseTFPlayer::SetIDEnt( CBaseEntity *pEntity )
+{
+ if ( pEntity )
+ m_TFLocal.m_iIDEntIndex = pEntity->entindex();
+ else
+ m_TFLocal.m_iIDEntIndex = 0;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : thermal -
+//-----------------------------------------------------------------------------
+void CBaseTFPlayer::SetUsingThermalVision( bool thermal )
+{
+ // Play sounds if we're changing
+ if ( m_TFLocal.m_bThermalVision != thermal )
+ {
+ if ( thermal )
+ {
+ EmitSound( "BaseTFPlayer.ThermalOn" );
+ }
+ else
+ {
+ EmitSound( "BaseTFPlayer.ThermalOff" );
+ }
+ }
+
+ m_TFLocal.m_bThermalVision = thermal;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Add the specified number of resource chunks to the player. Return true if he can carry it all.
+//-----------------------------------------------------------------------------
+bool CBaseTFPlayer::AddResourceChunks( int iChunks, bool bProcessed )
+{
+ // Am I allowed to carry any more chunks?
+ int iCurrentCount = GetTotalResourceChunks();
+ // Somewhat hacky
+ int iIndex = GetAmmoDef()->Index("ResourceChunks");
+ int iMax = GetAmmoDef()->MaxCarry( iIndex );
+ if ( iCurrentCount >= iMax )
+ {
+ bool bSwapped = false;
+
+ // If this is a processed chunk, see if we can swap it for an unprocessed chunk
+ if ( bProcessed )
+ {
+ if ( m_TFLocal.m_iResourceAmmo[ NORMAL_RESOURCES ] )
+ {
+ // Drop this unprocessed chunk
+ Vector vecVelocity = Vector( random->RandomFloat( -250,250 ), random->RandomFloat( -250,250 ), random->RandomFloat( 200,450 ) );
+ CResourceChunk::Create( false, GetAbsOrigin() + Vector(0,0,32), vecVelocity );
+ RemoveResourceChunks( 1, false );
+ bSwapped = true;
+ }
+ }
+
+ if ( !bSwapped )
+ return false;
+ }
+
+ m_TFLocal.m_iResourceAmmo.Set( bProcessed, MIN( iMax, m_TFLocal.m_iResourceAmmo[ bProcessed ] + iChunks ) );
+ SetAmmoCount( GetTotalResourceChunks(), iIndex );
+ CPASAttenuationFilter filter( this,"BaseTFPlayer.PickupResources" );
+ EmitSound( filter, entindex(),"BaseTFPlayer.PickupResources" );
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Remove the specified number of resources chunks from the player.
+//-----------------------------------------------------------------------------
+void CBaseTFPlayer::RemoveResourceChunks( int iChunks, bool bProcessed )
+{
+ // Remove the amount
+ m_TFLocal.m_iResourceAmmo.Set( bProcessed, MAX( 0, m_TFLocal.m_iResourceAmmo[ bProcessed ] - iChunks ) );
+ int iIndex = GetAmmoDef()->Index("ResourceChunks");
+ SetAmmoCount( GetTotalResourceChunks(), iIndex );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Get the number of resource chunks of this type the player's carrying
+//-----------------------------------------------------------------------------
+int CBaseTFPlayer::GetResourceChunkCount( bool bProcessed )
+{
+ return m_TFLocal.m_iResourceAmmo[ bProcessed ];
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Get the total number of resource chunks being carried by the player
+//-----------------------------------------------------------------------------
+int CBaseTFPlayer::GetTotalResourceChunks( void )
+{
+ int iCurrentCount = 0;
+ for ( int i = 0; i < RESOURCE_TYPES; i++ )
+ {
+ iCurrentCount += m_TFLocal.m_iResourceAmmo[i];
+ }
+
+ return iCurrentCount;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Drop some resource chunks
+//-----------------------------------------------------------------------------
+void CBaseTFPlayer::DropAllResourceChunks( void )
+{
+ Vector vecOrigin = GetAbsOrigin() + Vector(0,0,32);
+
+ TFStats()->IncrementTeamStat( GetTeamNumber(), TF_TEAM_STAT_RESOURCE_CHUNKS_DROPPED, resource_chunk_value.GetFloat() );
+
+ // Drop a resource chunk
+ Vector vecVelocity = Vector( random->RandomFloat( -250,250 ), random->RandomFloat( -250,250 ), random->RandomFloat( 200,450 ) );
+ CResourceChunk *pChunk = CResourceChunk::Create( FALSE, vecOrigin, vecVelocity );
+ pChunk->ChangeTeam( GetTeamNumber() );
+}
+
+
+//------------------------------------------------------------------------------------------------------------------
+// RESOURCE BANK
+//-----------------------------------------------------------------------------
+// Purpose: Get the amount of a resource in this player's bank
+//-----------------------------------------------------------------------------
+int CBaseTFPlayer::GetBankResources( void )
+{
+ return m_TFLocal.ResourceCount();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseTFPlayer::SetBankResources( int iAmount )
+{
+ int nOldAmount = m_TFLocal.ResourceCount();
+
+ TFStats()->IncrementPlayerStat( this, TF_PLAYER_STAT_RESOURCES_ACQUIRED, iAmount - nOldAmount );
+
+ m_TFLocal.SetResources( iAmount );
+
+ // Tell the player's builder weapon to update
+ CWeaponBuilder *pBuilder = GetWeaponBuilder();
+ if ( pBuilder )
+ {
+ pBuilder->GainedNewTechnology( NULL );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Add resources to this player's Bank
+//-----------------------------------------------------------------------------
+void CBaseTFPlayer::AddBankResources( int iAmount )
+{
+ m_TFLocal.AddResources( iAmount );
+
+ // Tell the player's builder weapon to update
+ CWeaponBuilder *pBuilder = GetWeaponBuilder();
+ if ( pBuilder )
+ {
+ pBuilder->GainedNewTechnology( NULL );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Remove resources to this player's Bank
+//-----------------------------------------------------------------------------
+void CBaseTFPlayer::RemoveBankResources( int iAmount, bool bSpent )
+{
+ m_TFLocal.RemoveResources( iAmount );
+
+ // Tell the player's builder weapon to update
+ CWeaponBuilder *pBuilder = GetWeaponBuilder();
+ if ( pBuilder )
+ {
+ pBuilder->GainedNewTechnology( NULL );
+ }
+
+ if (bSpent)
+ {
+ TFStats()->IncrementPlayerStat( this, TF_PLAYER_STAT_RESOURCES_SPENT, iAmount );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CBaseTFPlayer::IsCamouflaged( void )
+{
+ return ( m_flCamouflageAmount > 0.0f ) ? true : false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Change state over time
+//-----------------------------------------------------------------------------
+void CBaseTFPlayer::CheckCamouflage( void )
+{
+ if ( m_flCamouflageAmount == m_flGoalCamouflageAmount )
+ return;
+
+ float remaining = m_flGoalCamouflageAmount - m_flCamouflageAmount;
+ float maxstep = m_flGoalCamouflageChangeRate * gpGlobals->frametime;
+
+ if ( remaining > 0.0f )
+ {
+ m_flCamouflageAmount += MIN( remaining, maxstep );
+ }
+ else
+ {
+ remaining = -remaining;
+ m_flCamouflageAmount -= MIN( remaining, maxstep );
+ }
+
+ m_flCamouflageAmount = MAX( 0.0f, m_flCamouflageAmount );
+ m_flCamouflageAmount = MIN( 100.0f, m_flCamouflageAmount );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Goal % and rate in percent/second to achieve the goal
+// Input : percentage -
+// changerate -
+//-----------------------------------------------------------------------------
+void CBaseTFPlayer::SetCamouflaged( int percentage, float changerate )
+{
+ m_flGoalCamouflageAmount = (float)percentage;
+ m_flGoalCamouflageChangeRate = changerate;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Remove the player's camo
+//-----------------------------------------------------------------------------
+void CBaseTFPlayer::ClearCamouflage( void )
+{
+ SetCamouflaged( 0, 1000 );
+
+ // Tell the playerclass
+ if ( GetPlayerClass() )
+ {
+ GetPlayerClass()->ClearCamouflage();
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Confirm powerup durations
+//-----------------------------------------------------------------------------
+float CBaseTFPlayer::PowerupDuration( int iPowerup, float flTime )
+{
+ // Medics are never EMPed for long
+ if ( PlayerClass() == TFCLASS_MEDIC && iPowerup == POWERUP_EMP )
+ return 0.2;
+
+ return BaseClass::PowerupDuration( iPowerup, flTime );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Return the player's anim speed multiplier. Used for speeding up viewmodels while rushed.
+//-----------------------------------------------------------------------------
+float CBaseTFPlayer::GetDefaultAnimSpeed( void )
+{
+ if ( HasPowerup( POWERUP_RUSH ) )
+ return ADRENALIN_ANIM_SPEED;
+
+ // Weapons may modify animation times
+ if ( GetActiveWeapon() )
+ return GetActiveWeapon()->GetDefaultAnimSpeed();
+
+ return 1.0;
+}
+
+
+//-----------------------------------------------------------------------------
+// Donate resources to a teammate
+//-----------------------------------------------------------------------------
+void CBaseTFPlayer::DonateResources( CBaseTFPlayer *pTarget, int pCount )
+{
+ Assert( pTarget );
+
+ int nTotalCountDonated = 0;
+ int nDonationCount = GetBankResources();
+ if ( pCount < nDonationCount )
+ nDonationCount = pCount;
+
+ if (nDonationCount)
+ {
+ RemoveBankResources( nDonationCount, false );
+ pTarget->AddBankResources( nDonationCount );
+ nTotalCountDonated += nDonationCount;
+ }
+
+ if (nTotalCountDonated > 0)
+ {
+ char buf[1024];
+ Q_snprintf( buf, sizeof( buf ), "%s has donated %d resources to you\n",
+ GetPlayerName(), nTotalCountDonated );
+ ClientPrint( pTarget, HUD_PRINTCENTER, buf );
+
+ CSingleUserRecipientFilter filter( this );
+ EmitSound( filter, entindex(), "BaseTFPlayer.DonateResources" );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Infilitrator's can +use a corpse to consume it
+//-----------------------------------------------------------------------------
+void CBaseTFPlayer::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
+{
+ if ( pActivator->IsPlayer() )
+ {
+ CBaseTFPlayer *pPlayer = static_cast<CBaseTFPlayer*>(pActivator);
+
+ if ( InSameTeam( pActivator ))
+ {
+ // Resource donation
+ pPlayer->DonateResources( this, 25 );
+ }
+ }
+
+ BaseClass::Use( pActivator, pCaller, useType, value );
+}
+
+
+//-----------------------------------------------------------------------------
+// The player's usable...
+//-----------------------------------------------------------------------------
+int CBaseTFPlayer::ObjectCaps( void )
+{
+ return ( (BaseClass::ObjectCaps() & ~FCAP_ACROSS_TRANSITION) | FCAP_IMPULSE_USE );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CBaseTFPlayer::CantMove( void )
+{
+ return m_bCantMove;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseTFPlayer::SetCantMove( bool bCantMove )
+{
+ m_bCantMove = bCantMove;
+ RecalculateSpeed();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseTFPlayer::ResetViewOffset( void )
+{
+ if ( GetPlayerClass() )
+ {
+ GetPlayerClass()->ResetViewOffset();
+ }
+ else
+ {
+ SetViewOffset( VEC_VIEW_SCALED( this ) );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: If ragdolling, move the player along the path that the ragdoll takes
+//-----------------------------------------------------------------------------
+void CBaseTFPlayer::FollowClientRagdoll( void )
+{
+ if (( m_hRagdollShadow == NULL ) || ( GetPlayerClass() == NULL ))
+ return;
+
+ Vector vecMin, vecMax;
+ GetPlayerClass()->GetPlayerHull( ( ( GetFlags() & FL_DUCKING ) == 1 ), vecMin, vecMax );
+
+ // Follow shadow object
+ trace_t tr;
+
+ UTIL_TraceHull(
+ m_hRagdollShadow->GetAbsOrigin() + Vector(0,0,18),
+ m_hRagdollShadow->GetAbsOrigin(),
+ vecMin,
+ vecMax,
+ MASK_PLAYERSOLID,
+ m_hRagdollShadow,
+ COLLISION_GROUP_NONE,
+ &tr );
+
+ // Only move if we can find a valid spot under where shadow rolled
+ if ( !tr.allsolid )
+ {
+ UTIL_SetOrigin( this, tr.endpos );
+ VectorCopy( tr.endpos, m_vecLastGoodRagdollPos );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Stop being a ragdoll
+// Input : moveplayertofinalspot -
+// Output : return whether or not the ragdoll was cleared
+//-----------------------------------------------------------------------------
+bool CBaseTFPlayer::ClearClientRagdoll( bool moveplayertofinalspot )
+{
+ if ( m_hRagdollShadow )
+ {
+ if ( GetContainingEntity( edict() ) )
+ {
+ if ( moveplayertofinalspot )
+ {
+ // Move player to resting spot of shadow object
+ FollowClientRagdoll();
+
+ // Check for a valid standing position. If an entity is blocking impart some
+ // velocity to them and check again.
+ trace_t trace;
+ if ( CheckRagdollToStand( trace ) )
+ {
+ // Switch back to normal movement and kill off ragdoll bone setup on client
+ SetMoveType( MOVETYPE_WALK );
+ m_nRenderFX = kRenderFxNone;
+ //RemoveSolidFlags( FSOLID_NOTSOLID );
+ Assert( GetPlayerClass() != NULL );
+ Vector vecMin, vecMax;
+ GetPlayerClass()->GetPlayerHull( ( ( GetFlags() & FL_DUCKING ) == 1 ), vecMin, vecMax );
+ UTIL_SetSize( this, vecMin, vecMax );
+ }
+ else
+ {
+ CBaseEntity *pEntity = trace.m_pEnt;
+ if ( pEntity != GetContainingEntity( INDEXENT( 0 ) ) )
+ {
+ // Check for a physics object and apply force!
+ IPhysicsObject *pPhysObject = pEntity->VPhysicsGetObject();
+ if ( pPhysObject )
+ {
+ Vector vecDirection( random->RandomFloat( 0.0f, 1.0f ),
+ random->RandomFloat( 0.0f, 1.0f ),
+ random->RandomFloat( 0.0f, 1.0f ) );
+ vecDirection *= 40000.0f;
+ pPhysObject->ApplyForceCenter( vecDirection );
+ }
+
+ return false;
+ }
+ else
+ {
+ UTIL_SetOrigin( this, Vector( m_vecLastGoodRagdollPos.x, m_vecLastGoodRagdollPos.y, m_vecLastGoodRagdollPos.z + 18.0f ) );
+ return false;
+ }
+ }
+ }
+ }
+ // Kill the shadow object
+ UTIL_Remove( m_hRagdollShadow );
+ m_hRagdollShadow = NULL;
+ }
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CBaseTFPlayer::CheckRagdollToStand( trace_t &trace )
+{
+ Assert( GetPlayerClass() != NULL );
+ Vector vecMin, vecMax;
+ GetPlayerClass()->GetPlayerHull( ( ( GetFlags() & FL_DUCKING ) == 1 ), vecMin, vecMax );
+
+ // Write this better -- this is just a test to get things started.
+ UTIL_TraceHull(
+ m_vecLastGoodRagdollPos + Vector( 0, 0, 18 ),
+ m_vecLastGoodRagdollPos,
+ vecMin,
+ vecMax,
+ MASK_PLAYERSOLID,
+ m_hRagdollShadow,
+ COLLISION_GROUP_NONE,
+ &trace );
+
+ if ( !trace.allsolid )
+ return true;
+
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Start being a ragdoll, creates client ragdoll object and server
+// physics shadow object
+// Input : &force -
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CBaseTFPlayer::BecomeRagdollOnClient( const Vector &force )
+{
+ // Defender doesn't support it yet
+ if ( PlayerClass() == TFCLASS_INFILTRATOR )
+ return false;
+
+ // Initialize the good ragdoll position.
+ VectorCopy( GetAbsOrigin(), m_vecLastGoodRagdollPos );
+
+ bool bret = BaseClass::BecomeRagdollOnClient( force );
+
+ // ROBIN: Disabled ragdoll shadows for now.
+ // We'll re-enable them if we need to know the end position again
+ // If we re-enable them, we need to fix the ragdoll shadow not having the correct mass
+ return bret;
+
+ AddSolidFlags( FSOLID_NOT_SOLID );
+
+ // Clear any old shadow object ( should never occur )
+ ClearClientRagdoll( false );
+
+ // Create new shadow object
+ m_hRagdollShadow = CRagdollShadow::Create( this, force );
+
+ return bret;
+}
+
+//=========================================================
+// AddGesture - add a gesture into the animation queue
+//=========================================================
+int CBaseTFPlayer::AddGesture( Activity activity, bool autokill /*= true*/ )
+{
+ int layer = BaseClass::AddGesture( activity, autokill );
+ SetLayerBlendIn( layer, 0.0 );
+ SetLayerBlendOut( layer, 0.0 );
+ return layer;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Class specific touch functionality!
+//-----------------------------------------------------------------------------
+void CBaseTFPlayer::ClassTouch( CBaseEntity *pTouched )
+{
+ if ( m_pfnClassTouch && HasClass() )
+ {
+ (GetPlayerClass()->*m_pfnClassTouch)( pTouched );
+ }
+}
+
+const char* CBaseTFPlayer::GetClassModelString( int iClass, int iTeam )
+{
+ return m_PlayerClasses.GetPlayerClass( iClass )->GetClassModelString( iTeam );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : bRampage -
+//-----------------------------------------------------------------------------
+void CBaseTFPlayer::SetRampage( bool bRampage )
+{
+ m_bRampage = bRampage;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CBaseTFPlayer::IsInRampage( void )
+{
+ return m_bRampage;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseTFPlayer::SetPlayerClass( TFClass iClass )
+{
+ if ( m_iPlayerClass != iClass )
+ {
+ m_Timer.End();
+
+ if ( m_iPlayerClass >= 0 && m_iPlayerClass < TFCLASS_CLASS_COUNT )
+ {
+ void AddPlayerClassTime( int classnum, float seconds );
+
+ AddPlayerClassTime( m_iPlayerClass, m_Timer.GetDuration().GetSeconds() );
+ }
+ }
+
+ if ( m_iPlayerClass >= 0 )
+ {
+ if ( GetPlayerClass() )
+ {
+ GetPlayerClass()->ClassDeactivate();
+ }
+ }
+
+ m_iPlayerClass = iClass;
+
+ if ( m_iPlayerClass >= 0 )
+ {
+ SetPlayerModel();
+
+ m_Timer.Start();
+
+ if ( GetPlayerClass() )
+ {
+ GetPlayerClass()->ClassActivate();
+ // Setup the class on initial spawn
+ GetPlayerClass()->CreateClass();
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+int CBaseTFPlayer::ClassCostAdjustment( ResupplyBuyType_t nType )
+{
+ if ( m_iPlayerClass >= 0 )
+ {
+ return GetPlayerClass()->ClassCostAdjustment( nType );
+ }
+
+ return 0;
+}
+
+//=============================================================================
+//
+// Player Physics Shadow Code
+//
+class CPhysicsTFPlayerCallback : public IPhysicsPlayerControllerEvent
+{
+public:
+ int ShouldMoveTo( IPhysicsObject *pObject, const Vector &position )
+ {
+ CBaseTFPlayer *pPlayer = ( CBaseTFPlayer* )pObject->GetGameData();
+ if ( pPlayer->TouchedPhysics() )
+ {
+ return 0;
+ }
+ return 1;
+ }
+};
+
+static CPhysicsTFPlayerCallback TFPlayerCallback;
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseTFPlayer::InitVCollision( void )
+{
+ if ( GetPlayerClass() )
+ {
+ GetPlayerClass()->InitVCollision();
+ }
+
+ // Setup the HL2 specific callback.
+ if ( GetPhysicsController() )
+ {
+ GetPhysicsController()->SetEventHandler( &TFPlayerCallback );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Return the entity that should receive the score
+//-----------------------------------------------------------------------------
+CBasePlayer *CBaseTFPlayer::GetScorer( void )
+{
+ return this;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Return the entity that should get assistance credit
+//-----------------------------------------------------------------------------
+CBasePlayer *CBaseTFPlayer::GetAssistant( void )
+{
+ // If I'm in a vehicle, the builder gets credit
+ if ( IsInAVehicle() )
+ {
+ CBaseObject *pObject = dynamic_cast<CBaseObject*>( GetVehicle() );
+ if ( pObject )
+ {
+ CBasePlayer *pBuilder = pObject->GetBuilder();
+ if ( pBuilder && pBuilder != this )
+ return pBuilder;
+ }
+ }
+
+ // If I'm boosted, someone's getting the assist
+ if ( HasPowerup( POWERUP_BOOST ) && m_hLastBoostEntity.Get() )
+ {
+ // I may have boosted myself
+ if ( m_hLastBoostEntity.Get() != this )
+ {
+ if ( m_hLastBoostEntity->IsPlayer() )
+ return (CBasePlayer*)m_hLastBoostEntity.Get();
+
+ // If it's an object, give the builder the assist (i.e. buff station)
+ CBaseObject *pObject = dynamic_cast<CBaseObject*>( m_hLastBoostEntity.Get() );
+ if ( pObject )
+ {
+ CBasePlayer *pBuilder = pObject->GetBuilder();
+ if ( pBuilder && pBuilder != this )
+ return pBuilder;
+ }
+ }
+ }
+
+ return NULL;
+}
diff --git a/game/server/tf2/tf_player.h b/game/server/tf2/tf_player.h
new file mode 100644
index 0000000..5986872
--- /dev/null
+++ b/game/server/tf2/tf_player.h
@@ -0,0 +1,596 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef TF_PLAYER_H
+#define TF_PLAYER_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include "player.h"
+#include "tf_shareddefs.h"
+#include "tf_playerlocaldata.h"
+#include "tf_playerclass.h"
+#include "iscorer.h"
+#include "tf_playeranimstate.h"
+#include "vphysics/player_controller.h"
+
+#define MENU_STRING_BUFFER_SIZE 512
+#define MENU_MSG_TEXTCHUNK_SIZE 50
+#define MENU_UPDATETIME 2.0
+
+class CVehicleTeleportStation;
+class CPlayerClass;
+class CThrownGrenade;
+class CFlybyPoint;
+class CMenu;
+class CControlZone;
+class CTechnologyTree;
+class CTFTeam;
+class CWeaponBuilder;
+class CWeaponCombatShield;
+class COrder;
+class CBaseTFCombatWeapon;
+class CResourceZone;
+class CRagdollShadow;
+class CBaseObject;
+class CBasePredictedWeapon;
+class IVehicle;
+enum ResupplyReason_t;
+
+// If disguised player fires weapon, suppress disguise for this long
+#define DISGUISE_FIRE_SUPPRESSTIME 4.0f
+// If there are enemies within this radius, then
+// firing your weapon will cause you to lose your
+// disguise for DISGUISE_FIRE_SUPPRESSTIME
+#define DISGUISE_SUPPRESSION_RADIUS 1024.0f
+// Can only initiate disguise if no enemies within this radius
+#define DISGUISE_START_RADIUS 512.0f
+
+// Adrenalin
+#define ADRENALIN_DAMAGE_REDUCTION 0.5 // Damage reduction during adrenalin
+#define ADRENALIN_SPEED_INCREASE 1.2 // Movement speed increase during adrenalin
+#define ADRENALIN_RAMPAGE_EXTEND 5.0 // Time gained from killing an enemy during rampage
+
+// ID
+#define MAX_ID_RANGE 2048
+
+// Setup for class specific touch functions.
+#define SetClassTouch( _player, a ) _player->SetTouch( CBaseTFPlayer::ClassTouch ); _player->m_pfnClassTouch = static_cast<void (CPlayerClass::*)(CBaseEntity*)>(a)
+
+//=====================================================================
+// TF PLAYER
+//=====================================================================
+class CBaseTFPlayer : public CBasePlayer, public IScorer
+{
+ typedef CBasePlayer BaseClass;
+public:
+ DECLARE_CLASS( CBaseTFPlayer, CBasePlayer );
+ DECLARE_SERVERCLASS();
+ DECLARE_DATADESC();
+ DECLARE_PREDICTABLE();
+
+ CBaseTFPlayer();
+ ~CBaseTFPlayer();
+
+ // For updating hud tech tree
+ struct tfplayertech_t
+ {
+ int m_nAvailable;
+ int m_nUserCount;
+ int m_nResourceLevel;
+ };
+
+ // Helper to get a CBaseTFPlayer by its entity index.
+ static CBaseTFPlayer* Instance( int iEnt )
+ {
+ return dynamic_cast< CBaseTFPlayer* >( CBaseEntity::Instance( INDEXENT( iEnt ) ) );
+ }
+
+ bool IsHidden() const;
+ void SetHidden( bool bHidden );
+
+ // Class specific touch functions.
+ void ClassTouch( CBaseEntity *pTouched );
+
+ // This normally wouldn't go in here but we have to access CAllPlayerClasses and that is private.
+ const char* GetClassModelString( int iClass, int iTeam );
+
+ // The player class touch function
+ void (CPlayerClass ::*m_pfnClassTouch)( CBaseEntity *pOther );
+
+ // Override CBaseAnimatingOverlay to zero out m_dweights by default
+ virtual int AddGesture( Activity activity, bool autokill = true );
+
+ // Hacky shield stuff for now
+ void RemoveShieldOverlays( void );
+ int RemoveShieldOverlaysExcept( Activity activity, bool addifnotpresent = true );
+ Activity ShieldTranslateActivity( Activity activity );
+ void PickShieldAnimation( Activity& activity, int& overlayindex,
+ bool moving, bool ducked,
+ Activity overlay, Activity crouch, Activity normal,
+ bool autokill = true, float blendin = 0.0f, float blendout = 0.0f );
+
+ // True if player was moving last time checked (used to switch between shield full body and overlay anims )
+ bool m_bWasMoving;
+
+ virtual void UpdateOnRemove( void );
+
+ static CBaseTFPlayer *CreatePlayer( const char *className, edict_t *ed )
+ {
+ CBaseTFPlayer::s_PlayerEdict = ed;
+ return (CBaseTFPlayer*)CreateEntityByName( className );
+ }
+
+ virtual int UpdateTransmitState();
+ virtual int ShouldTransmit( const CCheckTransmitInfo *pInfo );
+ // Networking is about to update this player, let it override and specify it's own pvs
+ virtual void SetupVisibility( CBaseEntity *pViewEntity, unsigned char *pvs, int pvssize );
+
+ virtual void Spawn( void );
+ virtual void InitialSpawn( void );
+ virtual void ForceRespawn( void );
+ virtual void UpdateClientData( void );
+
+ virtual void ForceClientDllUpdate( void ); // Forces all client .dll specific data to be resent to client.
+
+ virtual void Precache( void );
+
+ virtual void InitHUD( void );
+ virtual void SelectItem( const char *pstr, int iSubType = 0 );
+
+ virtual void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value );
+ virtual int ObjectCaps( void );
+
+ // Override
+ virtual bool CanSpeak( void );
+
+ void ResetViewOffset( void );
+
+ // Input handlers
+ void InputRespawn( inputdata_t &inputdata );
+
+ // Team Handling
+ CTFTeam *GetTFTeam( void ) { return (CTFTeam*)GetTeam(); };
+ CTechnologyTree *GetTechTree( void );
+ virtual void ChangeTeam( int iTeamNum ) OVERRIDE;
+ void PlacePlayerInTeam( void );
+
+ // Class Handling
+ int PlayerClass( void );
+ bool IsClass( TFClass iClass );
+ bool IsSameClass( CBaseTFPlayer* pPlayer ) { Assert(pPlayer); return IsClass( (TFClass)pPlayer->PlayerClass() ); }
+
+ void ChangeClass( TFClass iClass );
+ bool IsClassAvailable( TFClass iClass );
+ void SetPlayerModel( void );
+ CPlayerClass *GetPlayerClass();
+ void ClearPlayerClass( void );
+ void SetPlayerClass( TFClass iClass );
+
+ int ClassCostAdjustment( ResupplyBuyType_t nType );
+
+ // Menu Handling
+ void MenuDisplay( void );
+ bool MenuInput( int iInput );
+ void MenuReset( void );
+
+ // Standard functions
+ virtual void ItemPostFrame();
+
+ virtual void Jump( void );
+
+ void PostThink( void );
+ void PreThink( void );
+ void PlayerRespawn( void );
+ CBaseEntity *EntSelectSpawnPoint( void );
+
+ // Death
+ virtual void DeathSound( const CTakeDamageInfo &info );
+ virtual void PainSound( const CTakeDamageInfo &info );
+ virtual void Event_Killed( const CTakeDamageInfo &info );
+ void TFPlayerDeathThink( void );
+
+ virtual void CheatImpulseCommands( int iImpulse );
+ virtual bool ClientCommand( const CCommand &args );
+ virtual void SetAnimation( PLAYER_ANIM playerAnim );
+
+ // Combat
+ void RecalculateSpeed( void );
+ void KilledPlayer( CBaseTFPlayer *pVictim );
+
+ int TakeHealth( float flHealth, int bitsDamageType );
+
+ // Combat
+ virtual int OnTakeDamage( const CTakeDamageInfo &info );
+ virtual void TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr );
+ void ShowPersonalShieldEffect( const Vector &vOffsetFromEnt, const Vector &vIncomingDirection, float flDamage );
+
+ // Objects
+ void AddObject( CBaseObject *pObject );
+ void RemoveObject( CBaseObject *pObject );
+ int CanBuild( int iObjectType );
+ int GetNumObjects( int iObjectType );
+ int GetObjectCount( );
+ CBaseObject *GetObject( int iObjectIndex );
+ bool IsBuilding( void );
+ void OwnedObjectDestroyed( CBaseObject *pObject );
+ void StopPlacement( void );
+ int StartedBuildingObject( int iObjectType );
+ void StoppedBuilding( int iObjectType );
+ void FinishedObject( CBaseObject *pObject );
+ void SetWeaponBuilder( CWeaponBuilder *pBuilder );
+ CWeaponBuilder *GetWeaponBuilder( void );
+ CWeaponCombatShield *GetCombatShield( void );
+ void OwnedObjectChangeTeam( CBaseObject *pObject, CBaseTFPlayer *pNewOwner );
+ void RemoveAllObjects( bool bForceAll = false, int iClass = 0, bool bReturnResources = false );
+
+ int NumPumpsOnResourceZone( CResourceZone *pZone );
+
+ // Deployment
+ void StartDeploying( void );
+ void StartUnDeploying( void );
+ void CheckDeployFinish( void );
+ void FinishDeploying( void );
+ void FinishUnDeploying( void );
+ bool IsDeployed( void );
+ bool IsDeploying( void );
+ bool IsUnDeploying( void );
+
+ // Movement prevention
+ bool CantMove( void );
+ void SetCantMove( bool bCantMove );
+
+ bool HasNamedTechnology( const char *name );
+ void SetPreferredTechnology( CTechnologyTree *pTechnologyTree, int iTechIndex );
+ int GetPreferredTechnology( void );
+ tfplayertech_t &AvailableTech( int i ) { return m_rgClientTechAvail[i]; }
+
+ // Powerups
+ virtual void PowerupStart( int iPowerup, float flAmount, CBaseEntity *pAttacker, CDamageModifier *pDamageModifier );
+ virtual void PowerupEnd( int iPowerup );
+ virtual float PowerupDuration( int iPowerup, float flTime );
+
+ void SetRampage( bool bRampage );
+ float GetDefaultAnimSpeed( void );
+
+ // Knockdown
+ void KnockDownPlayer( const Vector& sourceDir, float velocity, float duration );
+ bool IsKnockedDown( void );
+
+ // Resources
+ void AddBankResources( int iAmount );
+ void RemoveBankResources( int iAmount, bool bSpent = true );
+ void DonateResources( CBaseTFPlayer *pTarget, int pCount );
+ int GetBankResources( void );
+ void SetBankResources( int iAmount );
+
+ // Check for a collision....
+ bool IsHittingShield( const Vector &vecVelocity, float *flDamage );
+
+ // Combat prototyping
+ bool IsBlocking( void ) const { return m_bIsBlocking; }
+ bool IsParrying( void ) const { return m_bIsParrying; }
+ void SetBlocking( bool bBlocking ) { m_bIsBlocking = bBlocking; }
+ void SetParrying( bool bParrying ) { m_bIsParrying = bParrying; }
+
+ // Thermal Vision
+ bool IsUsingThermalVision( void );
+ void SetUsingThermalVision( bool thermal );
+
+ CTFPlayerLocalData *GetLocalData() { return &m_TFLocal; }
+
+ // Acts
+ void CleanupOnActStart( void );
+
+ // Resource chunks
+ bool AddResourceChunks( int iChunks, bool bProcessed );
+ void RemoveResourceChunks( int iChunks, bool bProcessed );
+ int GetTotalResourceChunks( void );
+ int GetResourceChunkCount( bool bProcessed );
+
+ void SetGagged( bool gag );
+
+ // Control zone
+ CControlZone* GetCurrentZone( ) { return m_pCurrentZone; }
+ void SetCurrentZone( CControlZone* pZone ) { m_pCurrentZone = pZone; }
+
+ // Camouflage
+ float GetCamouflageAmount( void ) { return m_flCamouflageAmount; };
+ bool IsCamouflaged( void );
+ void SetCamouflaged( int percentage, float changerate );
+ void ClearCamouflage( void );
+
+ float LastAttackTime() const { return m_flLastAttackTime; }
+ void SetLastAttackTime( float flTime ) { m_flLastAttackTime = flTime; }
+
+ bool ResupplyAmmo( float flFraction, ResupplyReason_t reason );
+
+ // The last time we were damaged by an enemy.
+ double LastTimeDamagedByEnemy() const { return m_flLastTimeDamagedByEnemy; }
+
+ // Orders
+ COrder* GetOrder( void ) { return m_hSelectedOrder; }
+ void SetOrder( COrder *pOrder );
+
+ // Count resource zone orders.
+ int GetNumResourceZoneOrders();
+
+ // Reinforcement
+ bool IsReadyToReinforce( void );
+ void Reinforce( void );
+
+ void ShowTacticalView( bool bTactical );
+
+ void SetRespawnStation( CBaseEntity *pStation );
+
+ // ID
+ void SetIDEnt( CBaseEntity *pEntity );
+
+ // Health boosts
+ void TakeHealthBoost( int iHealthBoost, int iTarget, int iDuration );
+
+ // Vehicle
+ bool CanGetInVehicle( void );
+
+ CVehicleTeleportStation* GetSelectedMCV() const;
+ void SetSelectedMCV( CVehicleTeleportStation *pMCV );
+
+ // Weapon handling
+ virtual bool Weapon_ShouldSetLast( CBaseCombatWeapon *pOldWeapon, CBaseCombatWeapon *pNewWeapon );
+ virtual bool Weapon_ShouldSelectItem( CBaseCombatWeapon *pWeapon );
+ virtual CBaseCombatWeapon *GetLastWeaponBeforeObject( void ) { return m_hLastWeaponBeforeObject; }
+
+ // Sapper attaching
+ bool IsAttachingSapper( void );
+ float GetSapperAttachmentTime( void );
+ void StartAttachingSapper( CBaseObject *pObject, CGrenadeObjectSapper *pSapper );
+ void CleanupAfterAttaching( void );
+ void StopAttaching( void );
+ void FinishAttaching( void );
+ void CheckSapperAttaching( void );
+
+// IScorer
+public:
+ // Return the entity that should receive the score
+ virtual CBasePlayer *GetScorer( void );
+ // Return the entity that should get assistance credit
+ virtual CBasePlayer *GetAssistant( void );
+
+public:
+ // FIXME: Make these private
+
+ // Menu-related goodies
+ CMenu *m_pCurrentMenu;
+ char m_MenuStringBuffer[MENU_STRING_BUFFER_SIZE];
+ int m_MenuSelectionBuffer;
+ float m_MenuRefreshTime;
+ float m_MenuUpdateTime;
+
+ bool m_bBuffHealthBoost;
+
+ // Object sapper placement handling
+ float m_flSapperAttachmentFinishTime;
+ float m_flSapperAttachmentStartTime;
+ CHandle< CGrenadeObjectSapper > m_hSapper;
+ CHandle< CBaseObject > m_hSappedObject;
+
+private:
+ // Medic Buffs
+ void CheckBuffs( void );
+ void RemoveHealthBoost( void );
+
+ // vehicles
+ void OnVehicleStart();
+ void OnVehicleEnd( Vector & );
+
+ bool IsInTacticalView( void ) const;
+
+ // Get rid of resource zone orders.
+ void KillResourceZoneOrders();
+
+ // Knockdowns
+ void ResetKnockdown( void );
+ void CheckKnockdown( void );
+
+ // Gagging
+ bool IsGagged( void );
+
+ void DropAllResourceChunks( void );
+
+ // Thinking
+ void CheckCamouflage( void );
+
+ // Rampage
+ bool IsInRampage( void );
+
+ // Vertification
+ inline bool HasClass( void ) { return GetPlayerClass() != NULL; }
+
+ // Respawn stations
+ CBaseEntity *GetInitialSpawnPoint( void );
+
+ // Go ragdoll, create shadow object, etc.
+ bool BecomeRagdollOnClient( const Vector &force );
+ // Remove ragdoll
+ bool ClearClientRagdoll( bool moveplayertofinalspot );
+ // Move origin in sync with shadow object if ragdolling
+ void FollowClientRagdoll( void );
+ bool CheckRagdollToStand( trace_t &trace );
+ bool IsClientRagdoll() const { return m_hRagdollShadow.Get() != 0; }
+
+ // Deployed?
+ bool IsDeployed() const { return m_bDeployed; }
+
+
+ void StoreCycle( void );
+ float RetrieveCycle( void );
+
+ bool ShouldMatchCycles( Activity newActivity, Activity currentActivity );
+
+ // TF Player Physics Shadow
+ void InitVCollision( void );
+
+ void ApplyDamageForce( const CTakeDamageInfo &info, int nDamageToDo );
+
+ // Movement.
+ Vector m_vecPosDelta;
+
+ enum { MOMENTUM_MAXSIZE = 10 };
+ float m_aMomentum[MOMENTUM_MAXSIZE];
+ int m_iMomentumHead;
+
+ // Spawn spot
+ CNetworkHandle( CBaseEntity, m_hSpawnPoint );
+
+ // This player's TF2 specific data that should only be replicated to
+ // the player and not to other players.
+ CNetworkVarEmbedded( CTFPlayerLocalData, m_TFLocal );
+ IMPLEMENT_NETWORK_VAR_FOR_DERIVED( m_iMaxHealth ); // Make sure this ent is marked as changed when m_iMaxHealth changes.
+
+ CHandle< CWeaponBuilder > m_hWeaponBuilder;
+ CHandle< CWeaponCombatShield > m_hWeaponCombatShield;
+ float m_flKnockdownEndTime;
+
+ // Weapon used before switching to an object placement
+ CHandle<CBaseCombatWeapon> m_hLastWeaponBeforeObject;
+
+ CNetworkQAngle( m_vecDeployedAngles );
+
+ // Tracks when medics are boosting the damage this guy applies.
+ int m_nMedicDamageBoosts;
+
+ CNetworkVar( int, m_TFPlayerFlags ); // Combination of TF_PLAYER_ flags.
+
+ bool m_bCantMove;
+
+ bool m_bGagged;
+
+ bool m_bFirstTeamSpawn; // When true, the player's joined the server but not picked a team for the first time
+
+ float m_flLastAttackTime;
+
+ // Camouflage
+ CNetworkVar( float, m_flCamouflageAmount );
+
+ // Camouflage state machine
+ float m_flGoalCamouflageAmount;
+ float m_flGoalCamouflageChangeRate;
+
+ // State information
+ bool m_bSwitchingView;
+
+ // Zone the player's currently in
+ CControlZone *m_pCurrentZone;
+ CNetworkVar( int, m_iCurrentZoneState );
+
+ // Accuracy
+ bool m_bSnapAccuracy;
+ float m_flAccuracy;
+ float m_flTargetAccuracy;
+ float m_flLastRicochetNearby;
+ float m_flNumberOfRicochets;
+ float m_flLastExplosionNearby;
+
+ // Buff states
+ int m_iHealthBoostTarget;
+ float m_flHealthBoostDecrement;
+ float m_flHealthBoostTime;
+
+ EHANDLE m_hNextPlayerToUpdateOnRadar;
+
+ CNetworkHandle( CBaseEntity, m_hSelectedMCV );
+
+ // Orders
+ CHandle< COrder > m_hSelectedOrder;
+
+ // Tech spending vote preference
+ int m_nPreferredTechnology;
+
+ // The last time we were damaged by an enemy.
+ double m_flLastTimeDamagedByEnemy;
+
+ // Menu Handling
+ float m_MenuDisplayTime;
+
+ // Rampage
+ bool m_bRampage; // Do I get adrenalin extension for killing enemies?
+
+ // Handheld shield
+ CNetworkVar( bool, m_bIsBlocking );
+ bool m_bIsParrying;
+
+ float m_flTimeOfDeath;
+
+ EHANDLE m_hLastBoostEntity; // Last entity that gave me a power boost
+
+ // Deployment
+ CNetworkVar( bool, m_bDeployed );
+ CNetworkVar( bool, m_bDeploying );
+ CNetworkVar( bool, m_bUnDeploying );
+ float m_flFinishedDeploying;
+
+ CNetworkVar( int, m_iPlayerClass );
+ CAllPlayerClasses m_PlayerClasses;
+
+ // This times how long each class is active.
+ CFastTimer m_Timer;
+
+ tfplayertech_t m_rgClientTechAvail[ MAX_TECHNOLOGIES ];
+
+ CHandle< CRagdollShadow > m_hRagdollShadow;
+ Vector m_vecLastGoodRagdollPos;
+
+ // Last reinforcment second counter for player ( so we don't spam the player
+ // with text every frame )
+ int m_iLastSecondsToGo;
+
+ CPlayerAnimState m_PlayerAnimState;
+
+ // Fractional boost amount
+ float m_flFractionalBoost;
+
+ float m_flStoredCycle;
+
+
+ // Are we under attack?
+ CNetworkVar( bool, m_bUnderAttack );
+
+ friend void* SendProxy_RideVehicle( const void *pStruct, const void *pData, CSendProxyRecipients *pRecipients, int objectID );
+ friend class CTFPlayerMove;
+};
+
+inline CBaseTFPlayer *ToBaseTFPlayer( CBaseEntity *pEntity )
+{
+ if ( !pEntity || !pEntity->IsPlayer() )
+ return NULL;
+
+ return dynamic_cast<CBaseTFPlayer *>( pEntity );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+class CPhysicsPlayerCallback : public IPhysicsPlayerControllerEvent
+{
+public:
+
+ int ShouldMoveTo( IPhysicsObject *pObject, const Vector &position )
+ {
+ CBaseTFPlayer *pPlayer = ( CBaseTFPlayer* )pObject->GetGameData();
+ if ( pPlayer->TouchedPhysics() )
+ {
+ return 0;
+ }
+ return 1;
+ }
+};
+
+extern CPhysicsPlayerCallback playerCallback;
+
+#endif // TF_PLAYER_H
diff --git a/game/server/tf2/tf_player_death.cpp b/game/server/tf2/tf_player_death.cpp
new file mode 100644
index 0000000..da90f9e
--- /dev/null
+++ b/game/server/tf2/tf_player_death.cpp
@@ -0,0 +1,230 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: CBaseTFPlayer functions dealing with death and reinforcement
+//
+// $Workfile: $
+// $Date: $
+// $NoKeywords: $
+//=============================================================================//
+#include "cbase.h"
+#include "player.h"
+#include "tf_player.h"
+#include "gamerules.h"
+#include "basecombatweapon.h"
+#include "EntityList.h"
+#include "tf_shareddefs.h"
+#include "tf_team.h"
+#include "baseviewmodel.h"
+#include "tf_class_infiltrator.h"
+#include "in_buttons.h"
+#include "globals.h"
+
+int g_iNumberOfCorpses;
+
+//-----------------------------------------------------------------------------
+// Purpose: The player was just killed
+//-----------------------------------------------------------------------------
+void CBaseTFPlayer::Event_Killed( const CTakeDamageInfo &info )
+{
+ // TODO don't use temp entities to transmit messages
+ CPASFilter filter( GetLocalOrigin() );
+ te->KillPlayerAttachments( filter, 0.0, entindex() );
+
+ // Remove the player from any vehicle they're in
+ if ( IsInAVehicle() )
+ {
+ LeaveVehicle();
+ }
+
+ // Holster weapon immediately, to allow it to cleanup
+ if (GetActiveWeapon())
+ {
+ GetActiveWeapon()->Holster( );
+ }
+
+ // Stop attaching sappers
+ if ( IsAttachingSapper() )
+ {
+ StopAttaching();
+ }
+
+ // stop them touching anything
+ AddFlag( FL_DONTTOUCH );
+
+ g_pGameRules->PlayerKilled( this, info );
+
+ ClearUseEntity();
+
+ // If I'm ragdolling due to a knockdown, don't play any animations
+ if ( m_hRagdollShadow == NULL )
+ {
+ if ( PlayerClass() != TFCLASS_INFILTRATOR )
+ {
+ // Calculate death force
+ Vector forceVector = CalcDamageForceVector( info );
+
+ BecomeRagdollOnClient( forceVector );
+ }
+ else
+ {
+ SetAnimation( PLAYER_DIE );
+ }
+ }
+
+ DeathSound( info );
+
+ SetViewOffset( VEC_DEAD_VIEWHEIGHT_SCALED( this ) );
+ m_lifeState = LIFE_DYING;
+ pl.deadflag = true;
+
+ // Enter dying state
+ AddSolidFlags( FSOLID_NOT_SOLID );
+ SetMoveType( MOVETYPE_NONE );
+ QAngle angles = GetLocalAngles();
+ angles.x = angles.z = 0;
+ SetLocalAngles( angles );
+ m_takedamage = DAMAGE_NO;
+
+ // clear out the suit message cache so we don't keep chattering
+ SetSuitUpdate(NULL, false, 0);
+
+ // reset FOV
+ SetFOV( this, 0 );
+
+ // Setup for respawn
+ m_flTimeOfDeath = gpGlobals->curtime;
+
+ SetThink(TFPlayerDeathThink);
+ SetNextThink( gpGlobals->curtime + 0.1f );
+
+ SetPowerup(POWERUP_EMP,false);
+
+ // Tell the playerclass that the player died
+ if ( GetPlayerClass() )
+ {
+ GetPlayerClass()->PlayerDied( info.GetAttacker() );
+ }
+
+ // Tell the attacker's playerclass that he killed someone
+ if ( info.GetAttacker() && info.GetAttacker()->IsPlayer() )
+ {
+ CBaseTFPlayer *pPlayerAttacker = (CBaseTFPlayer*)info.GetAttacker();
+ pPlayerAttacker->KilledPlayer( this );
+ }
+
+ DropAllResourceChunks();
+
+ // Tell all teams to update their orders
+ COrderEvent_PlayerKilled order( this );
+ GlobalOrderEvent( &order );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Think function for dead/dying players.
+// Play their death animation, then set up for reinforcement.
+//-----------------------------------------------------------------------------
+void CBaseTFPlayer::TFPlayerDeathThink(void)
+{
+ float flForward;
+
+ SetNextThink( gpGlobals->curtime + 0.1f );
+
+ if ( GetFlags() & FL_ONGROUND )
+ {
+ flForward = GetAbsVelocity().Length() - 20;
+ if (flForward <= 0)
+ {
+ SetAbsVelocity( vec3_origin );
+ }
+ else
+ {
+ Vector vecNewVelocity = GetAbsVelocity();
+ VectorNormalize( vecNewVelocity );
+ vecNewVelocity *= flForward;
+ SetAbsVelocity( vecNewVelocity );
+ }
+ }
+
+ StudioFrameAdvance();
+
+ if (GetModelIndex() && (!IsSequenceFinished()) && (m_lifeState == LIFE_DYING))
+ {
+ m_iRespawnFrames++;
+ if ( m_iRespawnFrames < 60 ) // animations should be no longer than this
+ return;
+ }
+
+ // Start looping dying state
+ SetAnimation( PLAYER_DIE );
+
+ // ROBIN: Everyone respawns immediately now. Maps will define respawns in the future.
+
+ if ( (gpGlobals->curtime - m_flTimeOfDeath) < 3 )
+ return;
+
+ m_lifeState = LIFE_RESPAWNABLE;
+
+ // Respawn on button press, but not if they're checking the scores
+ // Also respawn if they're not looking at scores, and they've been dead for over 5 seconds
+ bool bButtonDown = (m_nButtons & ~IN_SCORE) > 0;
+ if ( (bButtonDown || (gpGlobals->curtime - m_flTimeOfDeath) > 5 ) )
+ {
+ PlayerRespawn();
+ }
+
+ /*
+ // Aliens become respawnable immediately, because they're waiting for a reinforcement wave.
+ // Humans have to wait a short time.
+ if ( (GetTeamNumber() == TEAM_HUMANS) && (gpGlobals->curtime - m_flTimeOfDeath) < 3 )
+ return;
+ if ( (GetTeamNumber() == TEAM_ALIENS) && (gpGlobals->curtime - m_flTimeOfDeath) < 1 )
+ return;
+
+ // Humans can respawn, Aliens can't (reinforcement wave for their kind)
+ // Aliens stop thinking now and wait for the reinforcement wave
+ if ( GetTeamNumber() == TEAM_ALIENS )
+ {
+ m_lifeState = LIFE_RESPAWNABLE;
+ SetThink( NULL );
+ }
+ else
+ {
+ m_lifeState = LIFE_RESPAWNABLE;
+
+ // Respawn on button press
+ if ( m_nButtons & ~IN_SCORE )
+ {
+ PlayerRespawn();
+ }
+ }
+ */
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Return true if this player is ready to reinforce
+//-----------------------------------------------------------------------------
+bool CBaseTFPlayer::IsReadyToReinforce( void )
+{
+ // Only Aliens reinforce in waves, humans respawn normally
+ if ( (GetTeamNumber() == TEAM_ALIENS) && (m_lifeState == LIFE_RESPAWNABLE) )
+ return true;
+
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Bring the player back to life in a reinforcement wave
+//-----------------------------------------------------------------------------
+void CBaseTFPlayer::Reinforce( void )
+{
+ // Tell all teams to update their orders
+ COrderEvent_PlayerRespawned order( this );
+ GlobalOrderEvent( &order );
+
+ StopAnimation();
+ IncrementInterpolationFrame();
+ m_flPlaybackRate = 0.0;
+
+ PlayerRespawn();
+}
+
diff --git a/game/server/tf2/tf_player_resource.cpp b/game/server/tf2/tf_player_resource.cpp
new file mode 100644
index 0000000..ece062a
--- /dev/null
+++ b/game/server/tf2/tf_player_resource.cpp
@@ -0,0 +1,35 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: TF's custom CPlayerResource
+//
+// $NoKeywords: $
+//=============================================================================//
+#include "cbase.h"
+#include "player.h"
+#include "player_resource.h"
+#include "tf_player_resource.h"
+
+// Datatable
+IMPLEMENT_SERVERCLASS_ST(CTFPlayerResource, DT_TFPlayerResource)
+END_SEND_TABLE()
+
+LINK_ENTITY_TO_CLASS( tf_player_manager, CTFPlayerResource );
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerResource::Spawn( void )
+{
+ BaseClass::Spawn();
+
+ // Use autodetect, but only once every second.
+ NetworkProp()->SetUpdateInterval( 2.0f );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerResource::UpdatePlayerData( void )
+{
+ BaseClass::UpdatePlayerData();
+}
diff --git a/game/server/tf2/tf_player_resource.h b/game/server/tf2/tf_player_resource.h
new file mode 100644
index 0000000..0ccbe83
--- /dev/null
+++ b/game/server/tf2/tf_player_resource.h
@@ -0,0 +1,28 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: TF's custom CPlayerResource
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef TF_PLAYER_RESOURCE_H
+#define TF_PLAYER_RESOURCE_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include "player_resource.h"
+
+class CTFPlayerResource : public CPlayerResource
+{
+ DECLARE_CLASS( CTFPlayerResource, CPlayerResource );
+public:
+ DECLARE_SERVERCLASS();
+
+ virtual void Spawn( void );
+ virtual void UpdatePlayerData( void );
+
+public:
+};
+
+#endif // TF_PLAYER_RESOURCE_H
diff --git a/game/server/tf2/tf_playerclass.cpp b/game/server/tf2/tf_playerclass.cpp
new file mode 100644
index 0000000..5113435
--- /dev/null
+++ b/game/server/tf2/tf_playerclass.cpp
@@ -0,0 +1,1172 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//
+//=============================================================================//
+/*
+
+===== tf_playerclass.cpp ========================================================
+
+ functions dealing with the TF playerclasses
+
+*/
+#include "cbase.h"
+#include "player.h"
+#include "basecombatweapon.h"
+#include "tf_player.h"
+#include "tf_obj.h"
+#include "weapon_builder.h"
+#include "tf_team.h"
+#include "tf_func_resource.h"
+#include "orders.h"
+#include "order_repair.h"
+#include "engine/IEngineSound.h"
+#include "tier1/strtools.h"
+#include "textstatsmgr.h"
+#include "ammodef.h"
+#include "weapon_objectselection.h"
+#include "vcollide_parse.h"
+#include "weapon_combatshield.h"
+#include "tf_vehicle_tank.h"
+#include "tf_obj_manned_plasmagun.h"
+#include "tf_obj_manned_missilelauncher.h"
+
+extern char *g_pszEMPPulseStart;
+extern ConVar tf_fastbuild;
+
+
+// Stat groupings
+enum
+{
+ STATS_TANK = TFCLASS_CLASS_COUNT,
+ STATS_MANNEDGUN_PLASMA,
+ STATS_MANNEDGUN_ROCKET,
+
+ STATS_NUM_GROUPS,
+};
+
+char *sNonClassStatNames[] =
+{
+ "Tanks",
+ "Manned Plasmaguns",
+ "Manned Rocket launchers",
+};
+
+// Stats between player classes (like how many snipers killed recons).
+class CInterClassStats
+{
+public:
+
+ CInterClassStats()
+ {
+ m_flTotalDamageInflicted = 0;
+ m_nKills = 0;
+
+ m_flTotalEngagementDist = 0;
+ m_nEngagements = 0;
+
+ m_flTotalNormalizedEngagementDist = 0;
+ m_nNormalizedEngagements = 0;
+ }
+
+public:
+ //
+ // Note: "containing class" refers to the class in the CPlayerClassStats that owns this CInterClassStats.
+ // So if there's a CPlayerClassStats for a recon, and it has a CInterClassStats for a sniper,
+ // then "containing class" refers to the recon. In this example, m_flTotalDamageInflicted representss
+ // how much damage the recon players inflicted on the snipers.
+ //
+ double m_flTotalDamageInflicted; // Total damage inflicted on this class by the containing class.
+ double m_flTotalEngagementDist; // Used to get the average engagement distance.
+ int m_nEngagements;
+
+ double m_flTotalNormalizedEngagementDist;
+ int m_nNormalizedEngagements;
+
+ int m_nKills; // Now many times the containing class killed this one.
+};
+
+class CPlayerClassStats
+{
+public:
+ CPlayerClassStats()
+ {
+ m_flPlayerTime = 0;
+ }
+
+public:
+
+ CInterClassStats m_InterClassStats[STATS_NUM_GROUPS];
+ double m_flPlayerTime; // How much player time was spent in this class.
+};
+
+
+ConVar tf2_object_hard_limits( "tf2_object_hard_limits","0", FCVAR_NONE, "If true, use hard object limits instead of resource costs" );
+
+CPlayerClassStats g_PlayerClassStats[STATS_NUM_GROUPS];
+
+void AddPlayerClassTime( int classnum, float seconds )
+{
+ g_PlayerClassStats[ classnum ].m_flPlayerTime += seconds;
+}
+
+int GetStatGroupFor( CBaseTFPlayer *pPlayer )
+{
+ // In a vehicle?
+ if ( pPlayer->IsInAVehicle() )
+ {
+ if ( dynamic_cast<CVehicleTank*>( pPlayer->GetVehicle() ) )
+ return STATS_TANK;
+ if ( dynamic_cast<CObjectMannedMissileLauncher*>( pPlayer->GetVehicle() ) )
+ return STATS_MANNEDGUN_ROCKET;
+ if ( dynamic_cast<CObjectMannedPlasmagun*>( pPlayer->GetVehicle() ) )
+ return STATS_MANNEDGUN_PLASMA;
+ }
+
+ // Otherwise, use the playerclass
+ return pPlayer->GetPlayerClass()->GetTFClass();
+}
+
+const char* GetGroupNameFor( int iStatGroup )
+{
+ Assert( iStatGroup > TFCLASS_UNDECIDED && iStatGroup < STATS_NUM_GROUPS );
+ if ( iStatGroup < TFCLASS_CLASS_COUNT )
+ return GetTFClassInfo( iStatGroup )->m_pClassName;
+ else
+ return sNonClassStatNames[ iStatGroup - TFCLASS_CLASS_COUNT ];
+}
+
+
+void PrintPlayerClassStats()
+{
+ IFileSystem *pFileSys = filesystem;
+
+ FileHandle_t hFile = pFileSys->Open( "class_stats.txt", "wt", "LOGDIR" );
+ if ( hFile == FILESYSTEM_INVALID_HANDLE )
+ return;
+
+
+ pFileSys->FPrintf( hFile, "Class\tPlayer Time (minutes)\tAvg Engagement Dist\t(OLD) Engagement Dist\n" );
+ for ( int i=TFCLASS_UNDECIDED+1; i < STATS_NUM_GROUPS; i++ )
+ {
+ CPlayerClassStats *pStats = &g_PlayerClassStats[i];
+
+
+ // Figure out the average engagement distance across all classes.
+ int j;
+ double flAvgEngagementDist = 0;
+ int nTotalEngagements = 0;
+ double flTotalEngagementDist = 0;
+
+ double flTotalNormalizedEngagementDist = 0;
+ int nTotalNormalizedEngagements = 0;
+
+ for ( j=TFCLASS_UNDECIDED+1; j < STATS_NUM_GROUPS; j++ )
+ {
+ CInterClassStats *pInter = &g_PlayerClassStats[i].m_InterClassStats[j];
+
+ nTotalEngagements += pInter->m_nEngagements;
+ flTotalEngagementDist += pInter->m_flTotalEngagementDist;
+
+ nTotalNormalizedEngagements += pInter->m_nNormalizedEngagements;
+ flTotalNormalizedEngagementDist += pInter->m_flTotalNormalizedEngagementDist;
+ }
+ flAvgEngagementDist = nTotalEngagements ? ( flTotalEngagementDist / nTotalEngagements ) : 0;
+ double flAvgNormalizedEngagementDist = nTotalNormalizedEngagements ? (flTotalNormalizedEngagementDist / nTotalNormalizedEngagements) : 0;
+
+
+ pFileSys->FPrintf( hFile, "%s", GetGroupNameFor( i ) );
+ pFileSys->FPrintf( hFile, "\t%.1f", (pStats->m_flPlayerTime / 60.0f) );
+ pFileSys->FPrintf( hFile, "\t%d", (int)flAvgNormalizedEngagementDist );
+ pFileSys->FPrintf( hFile, "\t%d", (int)flAvgEngagementDist );
+ pFileSys->FPrintf( hFile, "\n" );
+ }
+
+ pFileSys->FPrintf( hFile, "\n" );
+ pFileSys->FPrintf( hFile, "\n" );
+
+
+ pFileSys->FPrintf( hFile, "Class\tTarget Class\tTotal Damage\tKills\tAvg Engagement Dist\t(OLD) Engagement Dist\n" );
+
+ for ( i=TFCLASS_UNDECIDED+1; i < STATS_NUM_GROUPS; i++ )
+ {
+ CPlayerClassStats *pStats = &g_PlayerClassStats[i];
+
+ // Print the inter-class stats.
+ for ( int j=TFCLASS_UNDECIDED+1; j < STATS_NUM_GROUPS; j++ )
+ {
+ CInterClassStats *pInter = &pStats->m_InterClassStats[j];
+
+ pFileSys->FPrintf( hFile, "%s", GetGroupNameFor( i ) );
+ pFileSys->FPrintf( hFile, "\t%s", GetGroupNameFor( j ) );
+ pFileSys->FPrintf( hFile, "\t%d", (int)pInter->m_flTotalDamageInflicted );
+ pFileSys->FPrintf( hFile, "\t%d", pInter->m_nKills );
+ pFileSys->FPrintf( hFile, "\t%d", (int)(pInter->m_nNormalizedEngagements ? (pInter->m_flTotalNormalizedEngagementDist / pInter->m_nNormalizedEngagements) : 0) );
+ pFileSys->FPrintf( hFile, "\t%d", (int)(pInter->m_nEngagements ? (pInter->m_flTotalEngagementDist / pInter->m_nEngagements) : 0) );
+ pFileSys->FPrintf( hFile, "\n" );
+ }
+ }
+
+ pFileSys->Close( hFile );
+}
+
+CTextStatFile g_PlayerClassStatsOutput( PrintPlayerClassStats );
+
+
+// ---------------------------------------------------------------------------------------------------------------- //
+// Detailed stats output.
+// ---------------------------------------------------------------------------------------------------------------- //
+
+ConVar tf_DetailedStats( "tf_DetailedStats", "1", 0, "Prints extensive detailed gameplay stats into detailed_stats.txt" );
+
+class CShotInfo
+{
+public:
+ float m_flDistance;
+ int m_nDamage;
+};
+
+CUtlLinkedList<CShotInfo,int> g_ClassShotInfos[STATS_NUM_GROUPS];
+
+void PrintDetailedPlayerClassStats()
+{
+ if ( !tf_DetailedStats.GetInt() )
+ return;
+
+ IFileSystem *pFileSys = filesystem;
+
+ FileHandle_t hFile = pFileSys->Open( "class_stats_detailed.txt", "wt", "LOGDIR" );
+ if ( hFile != FILESYSTEM_INVALID_HANDLE )
+ {
+ // Print the header.
+ for ( int i=TFCLASS_UNDECIDED+1; i < STATS_NUM_GROUPS; i++ )
+ {
+ pFileSys->FPrintf( hFile, "%s dist\t%s dmg\t", GetGroupNameFor( i ), GetGroupNameFor( i ) );
+ }
+ pFileSys->FPrintf( hFile, "\n" );
+
+ // Write out each column.
+ int iterators[STATS_NUM_GROUPS];
+ for ( i=TFCLASS_UNDECIDED+1; i < STATS_NUM_GROUPS; i++ )
+ iterators[i] = g_ClassShotInfos[i].Head();
+
+ bool bWroteAnything;
+ do
+ {
+ bWroteAnything = false;
+ for ( int i=TFCLASS_UNDECIDED+1; i < STATS_NUM_GROUPS; i++ )
+ {
+ if ( iterators[i] == g_ClassShotInfos[i].InvalidIndex() )
+ {
+ pFileSys->FPrintf( hFile, "\t\t" );
+ }
+ else
+ {
+ CShotInfo *pInfo = &g_ClassShotInfos[i][iterators[i]];
+ iterators[i] = g_ClassShotInfos[i].Next( iterators[i] );
+
+ pFileSys->FPrintf( hFile, "%.2f\t%d\t", pInfo->m_flDistance, pInfo->m_nDamage );
+ bWroteAnything = true;
+ }
+ }
+ pFileSys->FPrintf( hFile, "\n" );
+
+ } while ( bWroteAnything );
+
+
+ pFileSys->Close( hFile );
+ }
+}
+
+CTextStatFile g_PlayerClassStatsDetailedOutput( PrintDetailedPlayerClassStats );
+
+
+
+//=====================================================================
+// PLAYER CLASS HANDLING
+//=====================================================================
+// Base PlayerClass
+CPlayerClass::CPlayerClass( CBaseTFPlayer *pPlayer, TFClass iClass )
+: m_TFClass( iClass )
+{
+ m_pPlayer = pPlayer;
+
+ for (int i = 0; i <= MAX_TF_TEAMS; ++i)
+ {
+ m_sClassModel[i] = NULL_STRING;
+ }
+ m_iNumWeaponTechAssociations = 0;
+
+ m_bTechAssociationsSet = false;
+
+ m_flNormalizedEngagementNextTime = -1;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CPlayerClass::~CPlayerClass()
+{
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPlayerClass::ClassActivate( void )
+{
+ // Setup the default player movement variables.
+ SetupMoveData();
+
+ AddWeaponTechAssociations();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPlayerClass::ClassDeactivate( void )
+{
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPlayerClass::AddWeaponTechAssociations( void )
+{
+ ClearAllWeaponTechAssoc();
+
+ // Iterate tech tree for this class and find
+ Assert( m_pPlayer );
+ CTechnologyTree *tree = m_pPlayer->GetTechTree();
+ Assert( tree );
+
+ // Loop through all of the techs to see if any of them say yes to being applicable to
+ // this class specifically
+ for ( int i = 0 ; i < tree->GetNumberTechnologies(); i++ )
+ {
+ CBaseTechnology *tech = tree->GetTechnology( i );
+ if ( !tech )
+ continue;
+
+ if ( !tech->GetAssociateWeaponsForClass( GetTFClass() ) )
+ continue;
+
+ // Associate weapon tech name with class
+ AddWeaponTechAssoc( (char *)tech->GetName() );
+ }
+}
+
+
+void CPlayerClass::NetworkStateChanged()
+{
+ if ( m_pPlayer )
+ m_pPlayer->NetworkStateChanged();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Setup the default movement parameters, quite a few of these will be
+// overridden with class specific values.
+//-----------------------------------------------------------------------------
+void CPlayerClass::SetupMoveData( void )
+{
+ // Set the default walking and sprinting speeds.
+ m_flMaxWalkingSpeed = 120;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPlayerClass::SetupSizeData( void )
+{
+ // Initially set the player to the base player class standing hull size.
+ m_pPlayer->SetCollisionBounds( PLAYERCLASS_HULL_STAND_MIN, PLAYERCLASS_HULL_STAND_MAX );
+ m_pPlayer->SetViewOffset( PLAYERCLASS_VIEWOFFSET_STAND );
+ m_pPlayer->m_Local.m_flStepSize = PLAYERCLASS_STEPSIZE;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPlayerClass::CreateClass( void )
+{
+ // Give them a full loadout on the initial spawn
+ ResupplyAmmo( 100.0f, RESUPPLY_ALL_FROM_STATION );
+
+ // Create the builder weapon & all objects in it (Helpers are automatically deleted when the weapon's deleted)
+ // Make sure they can build at least 1 object
+ if ( GetTFClassInfo( m_TFClass )->m_pClassObjects[0] != OBJ_LAST )
+ {
+ m_pPlayer->GiveNamedItem( "weapon_builder" );
+
+ Assert( m_pPlayer->GetWeaponBuilder() );
+
+ // Do we have a construction yard?
+ bool bHaveYard = false;
+ CBaseEntity *pEntity = NULL;
+ while ((pEntity = gEntList.FindEntityByClassname( pEntity, "func_construction_yard" )) != NULL)
+ {
+ if ( m_pPlayer->InSameTeam( pEntity ) )
+ {
+ bHaveYard = true;
+ break;
+ }
+ }
+
+ for ( int i = 0; i < OBJ_LAST; i++ )
+ {
+ int iClassObject = GetTFClassInfo( m_TFClass )->m_pClassObjects[i];
+
+ // Hit the end?
+ if ( iClassObject == OBJ_LAST )
+ break;
+
+ // Only Humans can build the powerpacks & mortars
+ if ( iClassObject == OBJ_POWERPACK || iClassObject == OBJ_MORTAR)
+ {
+ if ( m_pPlayer->GetTeamNumber() != TEAM_HUMANS )
+ continue;
+ }
+
+ // Only Aliens can build shields
+ if ( iClassObject == OBJ_SHIELDWALL )
+ {
+ if ( m_pPlayer->GetTeamNumber() != TEAM_ALIENS )
+ continue;
+ }
+
+ // If my team doesn't have a construction yard, don't allow me to build vehicles
+ if ( !tf_fastbuild.GetBool() && !bHaveYard )
+ {
+ if ( IsObjectAVehicle(iClassObject) )
+ continue;
+
+ // Don't allow them to build vehicle upgrades either
+ if ( iClassObject == OBJ_DRIVER_MACHINEGUN )
+ continue;
+ }
+
+ // If my team has a construction yard, don't allow me to build defensive buildings
+ if ( !tf_fastbuild.GetBool() && bHaveYard && IsObjectADefensiveBuilding(iClassObject) )
+ continue;
+
+ m_pPlayer->GetWeaponBuilder()->AddBuildableObject( iClassObject );
+
+ // Give the player a fake weapon to select this object with
+ CWeaponObjectSelection *pSelection = (CWeaponObjectSelection *)m_pPlayer->GiveNamedItem( "weapon_objectselection", iClassObject );
+ if ( pSelection )
+ {
+ pSelection->SetType( iClassObject );
+ }
+ }
+ }
+
+ // Give the player all the weapons from tech associations
+ CTechnologyTree *pTechTree = m_pPlayer->GetTechTree();
+ if ( pTechTree )
+ {
+ // Give the player any weapons s/he might have just received the tech for
+ for ( int i = 0; i < m_iNumWeaponTechAssociations; i++ )
+ {
+ if ( m_pPlayer->HasNamedTechnology( m_WeaponTechAssociations[i].pWeaponTech ) )
+ {
+ CBaseTechnology *tech = pTechTree->GetTechnology( m_WeaponTechAssociations[i].pWeaponTech );
+ if ( tech )
+ {
+ for ( int j = 0; j < tech->GetNumWeaponAssociations(); j++ )
+ {
+ const char *weaponname = tech->GetAssociatedWeapon( j );
+ Assert( weaponname );
+
+ m_pPlayer->GiveNamedItem( weaponname );
+ }
+ }
+ }
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Called on each respawn
+//-----------------------------------------------------------------------------
+void CPlayerClass::RespawnClass( void )
+{
+ ResupplyAmmo( 100.0f, RESUPPLY_ALL_FROM_STATION );
+ GainedNewTechnology( NULL );
+ SetMaxHealth( GetMaxHealthCVarValue() );
+ SetMaxSpeed( GetMaxSpeed() );
+ CheckDeterioratingObjects();
+
+ SetupSizeData();
+
+ // Refill the clips of all my weapons
+ for (int i = 0; i < MAX_WEAPONS; i++)
+ {
+ CBaseCombatWeapon *pWeapon = m_pPlayer->GetWeapon(i);
+ if ( pWeapon )
+ {
+ if ( pWeapon->UsesClipsForAmmo1() )
+ {
+ pWeapon->m_iClip1 = pWeapon->GetDefaultClip1();
+ }
+ if ( pWeapon->UsesClipsForAmmo2() )
+ {
+ pWeapon->m_iClip2 = pWeapon->GetDefaultClip2();
+ }
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Supply the player with Ammo. Return true if some ammo was given.
+//-----------------------------------------------------------------------------
+bool CPlayerClass::ResupplyAmmoType( float flAmount, const char *pAmmoType )
+{
+ if (flAmount <= 0)
+ return false;
+
+ // Make sure at least 1 if given...
+ int nAmount;
+ if (flAmount <= 1)
+ nAmount = 1;
+ else
+ nAmount = (int)flAmount;
+
+ bool bGiven = false;
+ if ( g_pGameRules->CanHaveAmmo( m_pPlayer, pAmmoType ) )
+ {
+ if ( m_pPlayer->GiveAmmo( nAmount, pAmmoType, true ) > 0 )
+ bGiven = true;
+ }
+
+ return bGiven;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Supply the player with Ammo. Return true if some ammo was given.
+//-----------------------------------------------------------------------------
+bool CPlayerClass::ResupplyAmmo( float flPercentage, ResupplyReason_t reason )
+{
+ bool bGiven = false;
+
+ // Fully resupply shield energy everytime
+ if ( m_pPlayer->GetCombatShield() )
+ {
+ m_pPlayer->GetCombatShield()->AddShieldHealth( 1.0 );
+ }
+
+ if ((reason == RESUPPLY_RESPAWN) || (reason == RESUPPLY_ALL_FROM_STATION) || (reason == RESUPPLY_AMMO_FROM_STATION))
+ {
+ if (ResupplyAmmoType( 1, "Sappers" ))
+ {
+ bGiven = true;
+ }
+ }
+
+ return bGiven;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Calculate the player's Speed
+//-----------------------------------------------------------------------------
+float CPlayerClass::GetMaxSpeed( void )
+{
+ float flMaxSpeed = m_flMaxWalkingSpeed;
+
+ // Is the player in adrenalin mode?
+ if ( m_pPlayer->HasPowerup( POWERUP_RUSH ) )
+ {
+ flMaxSpeed *= ADRENALIN_SPEED_INCREASE;
+ }
+
+ // Is the player unable to move right now?
+ if ( m_pPlayer->CantMove() )
+ {
+ flMaxSpeed = 1;
+ }
+
+ return flMaxSpeed;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Get the player's maximum walking speed
+//-----------------------------------------------------------------------------
+float CPlayerClass::GetMaxWalkSpeed( void )
+{
+ return m_flMaxWalkingSpeed;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Set the player's speed
+//-----------------------------------------------------------------------------
+void CPlayerClass::SetMaxSpeed( float flMaxSpeed )
+{
+ if ( m_pPlayer )
+ {
+ m_pPlayer->SetMaxSpeed( flMaxSpeed );
+
+ float curspeed = m_pPlayer->GetAbsVelocity().Length();
+
+ if ( curspeed != 0.0f && curspeed > flMaxSpeed )
+ {
+ float crop = flMaxSpeed / curspeed;
+
+ Vector vecNewVelocity;
+ VectorScale( m_pPlayer->GetAbsVelocity(), crop, vecNewVelocity );
+ m_pPlayer->SetAbsVelocity( vecNewVelocity );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Return the player's max health
+//-----------------------------------------------------------------------------
+int CPlayerClass::GetMaxHealthCVarValue()
+{
+ int val = GetTFClassInfo( GetTFClass() )->m_pMaxHealthCVar->GetInt();
+ Assert( val > 0 ); // If you hit this assert, then you probably didn't add an entry to skill?.cfg
+ return val;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Set the player's health
+//-----------------------------------------------------------------------------
+void CPlayerClass::SetMaxHealth( float flMaxHealth )
+{
+ if ( m_pPlayer )
+ {
+ m_pPlayer->m_iMaxHealth = m_pPlayer->m_iHealth = flMaxHealth;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+string_t CPlayerClass::GetClassModel( int nTeam )
+{
+ return m_sClassModel[nTeam];
+}
+
+
+const char* CPlayerClass::GetClassModelString( int nTeam )
+{
+ // Each derived class should implement this.
+ Assert( false );
+ return "INVALID";
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Called to see if another player should be displayed on the players radar
+//-----------------------------------------------------------------------------
+bool CPlayerClass::CanSeePlayerOnRadar( CBaseTFPlayer *pl )
+{
+// if ( pl->InSameTeam(m_pPlayer) )
+// return false;
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPlayerClass::ItemPostFrame()
+{
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Output : TFClass
+//-----------------------------------------------------------------------------
+TFClass CPlayerClass::GetTFClass( void )
+{
+ return m_TFClass;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Handle custom commands for this playerclass
+//-----------------------------------------------------------------------------
+bool CPlayerClass::ClientCommand( const CCommand& args )
+{
+ if ( FStrEq( args[0], "builder_select_obj" ) )
+ {
+ if ( args.ArgC() < 2 )
+ return true;
+
+ // This is a total hack. Eventually this should come in via usercmds.
+
+ // Get our builder weapon
+ if ( !m_pPlayer->GetWeaponBuilder() )
+ return true;
+
+ // Select a state for the builder weapon
+ m_pPlayer->GetWeaponBuilder()->SetCurrentObject( atoi( args[1] ) );
+ m_pPlayer->GetWeaponBuilder()->SetCurrentState( BS_PLACING );
+ m_pPlayer->GetWeaponBuilder()->StartPlacement();
+ m_pPlayer->GetWeaponBuilder()->m_flNextPrimaryAttack = gpGlobals->curtime + 0.35f;
+ return true;
+ }
+ else if ( FStrEq( args[0], "builder_select_mode" ) )
+ {
+ if ( args.ArgC() < 2 )
+ return true;
+
+ // Get our builder weapon
+ if ( !m_pPlayer->GetWeaponBuilder() )
+ return true;
+
+ // Select a state for the builder weapon
+ m_pPlayer->GetWeaponBuilder()->SetCurrentState( atoi( args[1] ) );
+ m_pPlayer->GetWeaponBuilder()->m_flNextPrimaryAttack = gpGlobals->curtime + 0.35f;
+ return true;
+ }
+
+ return false;
+}
+
+
+//-----------------------------------------------------------------------------
+// Should we take damage-based force?
+//-----------------------------------------------------------------------------
+bool CPlayerClass::ShouldApplyDamageForce( const CTakeDamageInfo &info )
+{
+ return true;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: The player has taken damage. Return the damage done.
+//-----------------------------------------------------------------------------
+float CPlayerClass::OnTakeDamage( const CTakeDamageInfo &info )
+{
+ if ( info.GetAttacker() )
+ {
+ CBaseTFPlayer *pPlayer = dynamic_cast< CBaseTFPlayer* >( info.GetAttacker() );
+ if ( pPlayer && pPlayer->GetPlayerClass() )
+ {
+ int iStatGroup = GetStatGroupFor( pPlayer );
+ CInterClassStats *pInter = &g_PlayerClassStats[iStatGroup].m_InterClassStats[GetTFClass()];
+
+ pInter->m_flTotalDamageInflicted += info.GetDamage();
+
+ float flDistToAttacker = pPlayer->GetAbsOrigin().DistTo( GetPlayer()->GetAbsOrigin() );
+ pInter->m_flTotalEngagementDist += flDistToAttacker;
+ pInter->m_nEngagements++;
+
+ if ( gpGlobals->curtime >= m_flNormalizedEngagementNextTime )
+ {
+ pInter->m_flTotalNormalizedEngagementDist += flDistToAttacker;
+ pInter->m_nNormalizedEngagements++;
+
+ m_flNormalizedEngagementNextTime = gpGlobals->curtime + 3;
+ }
+
+ // Store detailed stats for the shot?
+ if ( tf_DetailedStats.GetInt() )
+ {
+ CShotInfo shotInfo;
+
+ shotInfo.m_flDistance = flDistToAttacker;
+ shotInfo.m_nDamage = (int)info.GetDamage();
+
+ g_ClassShotInfos[iStatGroup].AddToTail( shotInfo );
+ }
+ }
+ }
+
+ return info.GetDamage();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: New technology has been gained. Recalculate any class specific technology dependencies.
+//-----------------------------------------------------------------------------
+void CPlayerClass::GainedNewTechnology( CBaseTechnology *pTechnology )
+{
+ int i;
+
+ // Tell the player's weapons that this player's gained new technology
+ for ( i = 0; i < m_pPlayer->WeaponCount(); i++ )
+ {
+ if ( m_pPlayer->GetWeapon(i) )
+ {
+ ((CBaseTFCombatWeapon*)m_pPlayer->GetWeapon(i))->GainedNewTechnology( pTechnology );
+ }
+ }
+
+ // Tell the player's objects that this player's gained new technology
+ for ( i = 0; i < m_pPlayer->GetObjectCount(); i++ )
+ {
+ if (m_pPlayer->GetObject(i))
+ {
+ m_pPlayer->GetObject(i)->GainedNewTechnology( pTechnology );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Return true if this player's allowed to build another one of the specified objects
+//-----------------------------------------------------------------------------
+int CPlayerClass::CanBuild( int iObjectType )
+{
+ int iObjectCount = GetNumObjects( iObjectType );
+
+ // Make sure we haven't hit maximum number
+ if ( tf2_object_hard_limits.GetBool() )
+ {
+ if ( iObjectCount >= GetObjectInfo( iObjectType )->m_nMaxObjects )
+ return CB_LIMIT_REACHED;
+ }
+ else
+ {
+ // Find out how much the next object should cost
+ int iCost = CalculateObjectCost( iObjectType, GetNumObjects( iObjectType ), m_pPlayer->GetTeamNumber() );
+
+ // Make sure we have enough resources
+ if ( m_pPlayer->GetBankResources() < iCost )
+ return CB_NEED_RESOURCES;
+ }
+
+ return CB_CAN_BUILD;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Object built by this player has been destroyed
+//-----------------------------------------------------------------------------
+void CPlayerClass::OwnedObjectDestroyed( CBaseObject *pObject )
+{
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Get the number of the specified objects built by the player
+//-----------------------------------------------------------------------------
+int CPlayerClass::GetNumObjects( int iObjectType )
+{
+ // For the purposes of costs/limits, Sentryguns tally up all sentrygun types
+ if ( iObjectType == OBJ_SENTRYGUN_PLASMA || iObjectType == OBJ_SENTRYGUN_ROCKET_LAUNCHER )
+ {
+ return ( m_pPlayer->GetNumObjects( OBJ_SENTRYGUN_PLASMA ) + m_pPlayer->GetNumObjects( OBJ_SENTRYGUN_ROCKET_LAUNCHER ) );
+ }
+
+ return m_pPlayer->GetNumObjects( iObjectType );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Player has started building an object
+//-----------------------------------------------------------------------------
+int CPlayerClass::StartedBuildingObject( int iObjectType )
+{
+ // Deduct the cost of the object
+ if ( !tf2_object_hard_limits.GetBool() )
+ {
+ int iCost = CalculateObjectCost( iObjectType, GetNumObjects( iObjectType ), m_pPlayer->GetTeamNumber() );
+ if ( iCost > m_pPlayer->GetBankResources() )
+ {
+ // Player must have lost resources since he started placing
+ return 0;
+ }
+ m_pPlayer->RemoveBankResources( iCost );
+
+ // If the object costs 0, we need to return non-0 to mean success
+ if ( !iCost )
+ return 1;
+
+ return iCost;
+ }
+
+ return 1;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Player has aborted a build
+//-----------------------------------------------------------------------------
+void CPlayerClass::StoppedBuilding( int iObjectType )
+{
+ // Return the cost of the object
+ if ( !tf2_object_hard_limits.GetBool() )
+ {
+ int iCost = CalculateObjectCost( iObjectType, GetNumObjects( iObjectType ), m_pPlayer->GetTeamNumber() );
+ m_pPlayer->AddBankResources( iCost );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Object has been built by this player
+//-----------------------------------------------------------------------------
+void CPlayerClass::FinishedObject( CBaseObject *pObject )
+{
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Object has been picked up by this player
+//-----------------------------------------------------------------------------
+void CPlayerClass::PickupObject( CBaseObject *pObject )
+{
+ // Return the cost of the object
+ if ( !tf2_object_hard_limits.GetBool() )
+ {
+ int iCost = CalculateObjectCost( pObject->GetType(), GetNumObjects( pObject->GetType() ), m_pPlayer->GetTeamNumber(), true );
+ m_pPlayer->AddBankResources( iCost );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Create a personal order for this player
+//-----------------------------------------------------------------------------
+bool CPlayerClass::CreateInitialOrder()
+{
+ if( AnyNonResourceZoneOrders() )
+ return true;
+
+ return false;
+}
+
+
+void CPlayerClass::CreatePersonalOrder()
+{
+ if( CreateInitialOrder() )
+ return;
+
+ // Make an order to fix any objects we own.
+ if ( COrderRepair::CreateOrder_RepairOwnObjects( this ) )
+ return;
+}
+
+
+bool CPlayerClass::AnyResourceZoneOrders()
+{
+ return !!m_pPlayer->GetNumResourceZoneOrders();
+}
+
+
+bool CPlayerClass::AnyNonResourceZoneOrders()
+{
+ return
+ m_pPlayer->GetNumResourceZoneOrders() == 0 &&
+ m_pPlayer->GetOrder();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPlayerClass::ClassThink( void )
+{
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPlayerClass::PowerupStart( int iPowerup, float flAmount, CBaseEntity *pAttacker, CDamageModifier *pDamageModifier )
+{
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPlayerClass::PowerupEnd( int iPowerup )
+{
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: My player has just died
+//-----------------------------------------------------------------------------
+void CPlayerClass::PlayerDied( CBaseEntity *pAttacker )
+{
+ if ( pAttacker )
+ {
+ CBaseTFPlayer *pAttackerPlayer = dynamic_cast< CBaseTFPlayer* >( pAttacker );
+ if ( pAttackerPlayer )
+ {
+ CPlayerClass *pAttackerClass = pAttackerPlayer->GetPlayerClass();
+ if ( pAttackerClass )
+ {
+ int iStatGroup = GetStatGroupFor( pAttackerPlayer );
+ g_PlayerClassStats[ iStatGroup ].m_InterClassStats[GetTFClass()].m_nKills++;
+ }
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: My player just killed another player
+//-----------------------------------------------------------------------------
+void CPlayerClass::PlayerKilledPlayer( CBaseTFPlayer *pVictim )
+{
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPlayerClass::SetPlayerHull( void )
+{
+ // Use the generic player hull if the class doesn't override this function.
+ if ( m_pPlayer->GetFlags() & FL_DUCKING )
+ {
+ m_pPlayer->SetCollisionBounds( PLAYERCLASS_HULL_DUCK_MIN, PLAYERCLASS_HULL_DUCK_MAX );
+ }
+ else
+ {
+ m_pPlayer->SetCollisionBounds( PLAYERCLASS_HULL_STAND_MIN, PLAYERCLASS_HULL_STAND_MAX );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPlayerClass::GetPlayerHull( bool bDucking, Vector &vecMin, Vector &vecMax )
+{
+ // Use the generic player hull if the class doesn't override this function.
+ if ( bDucking )
+ {
+ VectorCopy( PLAYERCLASS_HULL_DUCK_MIN, vecMin );
+ VectorCopy( PLAYERCLASS_HULL_DUCK_MAX, vecMax );
+ }
+ else
+ {
+ VectorCopy( PLAYERCLASS_HULL_STAND_MIN, vecMin );
+ VectorCopy( PLAYERCLASS_HULL_STAND_MAX, vecMax );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPlayerClass::InitVCollision( void )
+{
+ CPhysCollide *pStandModel = PhysCreateBbox( PLAYERCLASS_HULL_STAND_MIN, PLAYERCLASS_HULL_STAND_MAX );
+ CPhysCollide *pCrouchModel = PhysCreateBbox( PLAYERCLASS_HULL_DUCK_MIN, PLAYERCLASS_HULL_DUCK_MAX );
+
+ solid_t solid;
+ solid.params = g_PhysDefaultObjectParams;
+ solid.params.mass = 85.0f;
+ solid.params.inertia = 1e24f;
+ solid.params.enableCollisions = false;
+ //disable drag
+ solid.params.dragCoefficient = 0;
+
+ // create standing hull
+ m_pPlayer->m_pShadowStand = PhysModelCreateCustom( m_pPlayer, pStandModel, m_pPlayer->GetLocalOrigin(), m_pPlayer->GetLocalAngles(), "tfplayer_generic", false, &solid );
+ m_pPlayer->m_pShadowStand->SetCallbackFlags( CALLBACK_SHADOW_COLLISION );
+
+ // create crouchig hull
+ m_pPlayer->m_pShadowCrouch = PhysModelCreateCustom( m_pPlayer, pCrouchModel, m_pPlayer->GetLocalOrigin(), m_pPlayer->GetLocalAngles(), "tfplayer_generic", false, &solid );
+ m_pPlayer->m_pShadowCrouch->SetCallbackFlags( CALLBACK_SHADOW_COLLISION );
+
+ // default to stand
+ m_pPlayer->VPhysicsSetObject( m_pPlayer->m_pShadowStand );
+
+ //disable drag
+ m_pPlayer->m_pShadowStand->EnableDrag( false );
+ m_pPlayer->m_pShadowCrouch->EnableDrag( false );
+
+ // tell physics lists I'm a shadow controller object
+ PhysAddShadow( m_pPlayer );
+ m_pPlayer->m_pPhysicsController = physenv->CreatePlayerController( m_pPlayer->m_pShadowStand );
+
+ // init state
+ if ( m_pPlayer->GetFlags() & FL_DUCKING )
+ {
+ m_pPlayer->SetVCollisionState( VPHYS_CROUCH );
+ }
+ else
+ {
+ m_pPlayer->SetVCollisionState( VPHYS_WALK );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : *pObject -
+// *pNewOwner -
+//-----------------------------------------------------------------------------
+void CPlayerClass::OwnedObjectChangeToTeam( CBaseObject *pObject, CBaseTFPlayer *pNewOwner )
+{
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : *pObject -
+// *pOldOwner -
+//-----------------------------------------------------------------------------
+void CPlayerClass::OwnedObjectChangeFromTeam( CBaseObject *pObject, CBaseTFPlayer *pOldOwner )
+{
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Check to see if there are any deteriorating objects I once owned that
+// I can now re-own (i.e. I switched teams back to my original team).
+// TODO: Make this use Steam IDs, so disconnecting & reconnecting players
+// get their objects back.
+//-----------------------------------------------------------------------------
+void CPlayerClass::CheckDeterioratingObjects( void )
+{
+ if ( !GetTeam() )
+ return;
+
+ // Cycle through the team's objects looking for any deteriorating objects once owned by me
+ for ( int i = 0; i < GetTeam()->GetNumObjects(); i++ )
+ {
+ CBaseObject *pObject = GetTeam()->GetObject(i);
+ if ( pObject->IsDeteriorating() && pObject->GetOriginalBuilder() == m_pPlayer )
+ {
+ // Can this class build this object?
+ if ( ClassCanBuild( GetTFClass(), pObject->GetType() ) )
+ {
+ // Give the object back to this player
+ pObject->SetBuilder( m_pPlayer );
+ m_pPlayer->AddObject( pObject );
+ }
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Output : edict_t
+//-----------------------------------------------------------------------------
+CBaseEntity *CPlayerClass::SelectSpawnPoint( void )
+{
+ return NULL;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Add a new weapon/tech association. Weapons that the player has the associated tech for
+// will automatically be given out if the player has the tech.
+//-----------------------------------------------------------------------------
+void CPlayerClass::AddWeaponTechAssoc( char *pWeaponTech )
+{
+ Assert( m_iNumWeaponTechAssociations < MAX_WEAPONS );
+
+ m_WeaponTechAssociations[m_iNumWeaponTechAssociations].pWeaponTech = pWeaponTech;
+ m_iNumWeaponTechAssociations++;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPlayerClass::ClearAllWeaponTechAssoc( )
+{
+ m_iNumWeaponTechAssociations = 0;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CTFTeam *CPlayerClass::GetTeam()
+{
+ return (CTFTeam*)GetPlayer()->GetTeam();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPlayerClass::ResetViewOffset( void )
+{
+ if ( m_pPlayer )
+ {
+ m_pPlayer->SetViewOffset( PLAYERCLASS_VIEWOFFSET_STAND );
+ }
+}
+
diff --git a/game/server/tf2/tf_playerclass.h b/game/server/tf2/tf_playerclass.h
new file mode 100644
index 0000000..873cb8c
--- /dev/null
+++ b/game/server/tf2/tf_playerclass.h
@@ -0,0 +1,214 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//
+//=============================================================================//
+#ifndef TF_PLAYERCLASS_H
+#define TF_PLAYERCLASS_H
+
+#ifdef _WIN32
+#pragma once
+#endif
+
+
+#include "tier0/fasttimer.h"
+#include <crtdbg.h>
+
+class CPlayerClassData;
+class CPlayerClass;
+class CBaseTFPlayer;
+class COrder;
+class CBaseObject;
+class CBaseTechnology;
+class CWeaponCombatShield;
+
+
+enum ResupplyReason_t
+{
+ RESUPPLY_RESPAWN = 0,
+ RESUPPLY_ALL_FROM_STATION,
+ RESUPPLY_AMMO_FROM_STATION,
+ RESUPPLY_GRENADES_FROM_STATION,
+};
+
+
+//==============================================================================
+// PLAYER CLASSES
+//==============================================================================
+
+class CBaseTFPlayer;
+class CTFTeam;
+
+// Base PlayerClass
+// The PlayerClass classes handle all class specific weaponry / abilities / etc
+class CPlayerClass
+{
+public:
+ DECLARE_CLASS_NOBASE( CPlayerClass );
+ CPlayerClass( CBaseTFPlayer *pPlayer, TFClass iClass );
+ virtual ~CPlayerClass();
+
+ // Any objects created/owned by class should be allocated and destroyed here
+ virtual void ClassActivate( void );
+ virtual void ClassDeactivate( void );
+
+ // Class initialization
+ virtual void CreateClass( void ); // Create the class upon initial spawn
+ virtual void RespawnClass( void ); // Called upon all respawns
+ virtual bool ResupplyAmmo( float flPercentage, ResupplyReason_t reason ); // Reset the ammo counts
+ virtual bool ResupplyAmmoType( float flAmount, const char *pAmmoType ); // Purpose: Supply the player with Ammo. Return true if some ammo was given.
+
+ virtual void SetMaxHealth( float flMaxHealth ); // Set the player's max health
+ int GetMaxHealthCVarValue(); // Return the player class's max health cvar
+
+ virtual float GetMaxSpeed( void ); // Calculate and return the player's max speed
+ virtual float GetMaxWalkSpeed( void ); // Calculate and return the player's max walking speed
+ virtual void SetMaxSpeed( float flMaxSpeed ); // Set the player's max speed
+
+ virtual string_t GetClassModel( int nTeam ); // Return a string containing this class's model
+ virtual const char* GetClassModelString( int nTeam );
+
+ virtual void SetupMoveData( void ); // Setup the default player movement data.
+ virtual void SetupSizeData( void );
+
+ virtual bool CanSeePlayerOnRadar( CBaseTFPlayer *pl );
+ virtual void ItemPostFrame( void );
+ virtual bool ClientCommand( const CCommand &args );
+
+ // Class abilities
+ virtual void ClassThink( void );
+ virtual void GainedNewTechnology( CBaseTechnology *pTechnology ); // New technology has been gained
+
+ // Deployment
+ virtual float GetDeployTime( void ) { return 0.0; };
+
+ // Resources
+ virtual int ClassCostAdjustment( ResupplyBuyType_t nType ) { return 0; }
+
+ // Objects
+ int GetNumObjects( int iObjectType );
+ virtual int CanBuild( int iObjectType );
+ virtual int StartedBuildingObject( int iObjectType );
+ virtual void StoppedBuilding( int iObjectType );
+ virtual void FinishedObject( CBaseObject *pObject );
+ virtual void PickupObject( CBaseObject *pObject );
+ virtual void OwnedObjectDestroyed( CBaseObject *pObject );
+ virtual void OwnedObjectChangeToTeam( CBaseObject *pObject, CBaseTFPlayer *pNewOwner );
+ virtual void OwnedObjectChangeFromTeam( CBaseObject *pObject, CBaseTFPlayer *pOldOwner );
+ virtual void CheckDeterioratingObjects( void );
+
+ // Hooks
+ virtual float OnTakeDamage( const CTakeDamageInfo &info );
+ virtual bool ShouldApplyDamageForce( const CTakeDamageInfo &info );
+
+ // Vehicles
+ virtual void OnVehicleStart() {}
+ virtual void OnVehicleEnd() {}
+ virtual bool CanGetInVehicle( void ) { return true; }
+
+ virtual void PlayerDied( CBaseEntity *pAttacker );
+ virtual void PlayerKilledPlayer( CBaseTFPlayer *pVictim );
+
+ virtual void SetPlayerHull( void );
+ virtual void GetPlayerHull( bool bDucking, Vector &vecMin, Vector &vecMax );
+
+ // Player Physics Shadow
+ virtual void InitVCollision( void );
+
+ // Powerups
+ virtual void PowerupStart( int iPowerup, float flAmount, CBaseEntity *pAttacker, CDamageModifier *pDamageModifier );
+ virtual void PowerupEnd( int iPowerup );
+
+ // Camo
+ virtual void ClearCamouflage( void ) { return; };
+
+ // Disguise
+ virtual void FinishedDisguising( void ) { return; };
+ virtual void StopDisguising( void ) { return; };
+
+ // Orders
+ virtual void CreatePersonalOrder( void );
+
+ // Create a high-priority order. This should be called by all player classes before
+ // they try to create class-specific orders. This function returns true if an order is
+ // created.
+ bool CreateInitialOrder();
+
+ bool AnyResourceZoneOrders();
+ bool AnyNonResourceZoneOrders(); // Returns true if there are any non-resource-zone orders.
+ // If there are, then no class should make any overriding orders.
+
+ // Respawn ( allow classes to override spawn points )
+ virtual CBaseEntity *SelectSpawnPoint( void );
+
+ void *operator new( size_t stAllocateBlock )
+ {
+ Assert( stAllocateBlock != 0 );
+ void *pMem = malloc( stAllocateBlock );
+ memset( pMem, 0, stAllocateBlock );
+ return pMem;
+ }
+
+ void* operator new( size_t stAllocateBlock, int nBlockUse, const char *pFileName, int nLine )
+ {
+ Assert( stAllocateBlock != 0 );
+ void *pMem = _malloc_dbg( stAllocateBlock, nBlockUse, pFileName, nLine );
+ memset( pMem, 0, stAllocateBlock );
+ return pMem;
+ }
+
+ void operator delete( void *pMem )
+ {
+ free( pMem );
+ }
+
+ void SetClassModel( string_t sModelName, int nTeam ) { m_sClassModel[nTeam] = sModelName; }
+
+ // Weapon & Tech Associations
+ void AddWeaponTechAssoc( char *pWeaponTech );
+
+
+ // Accessors.
+ inline CBaseTFPlayer* GetPlayer() { return m_pPlayer; }
+ CTFTeam* GetTeam();
+
+ virtual void ResetViewOffset( void );
+
+ virtual TFClass GetTFClass( void );
+
+ void AddWeaponTechAssociations( void );
+
+ // For CNetworkVar support. Chain to the player entity.
+ void NetworkStateChanged();
+
+ TFClass m_TFClass;
+
+protected:
+ double m_flNormalizedEngagementNextTime;
+
+ CBaseTFPlayer *m_pPlayer; // Reference to the player
+
+ float m_flMaxWalkingSpeed;
+
+ string_t m_sClassModel[ MAX_TF_TEAMS + 1 ];
+
+ // Weapon & Tech associations
+ // Used to give out all weapons the player currently has the technologies for.
+ struct WeaponTechAssociation_t
+ {
+ char *pWeaponTech;
+ };
+ WeaponTechAssociation_t m_WeaponTechAssociations[ MAX_WEAPONS ];
+ int m_iNumWeaponTechAssociations;
+
+ CHandle<CWeaponCombatShield> m_hWpnShield;
+private:
+ void ClearAllWeaponTechAssoc( void );
+
+private:
+ bool m_bTechAssociationsSet;
+};
+
+#endif // TF_PLAYERCLASS_H \ No newline at end of file
diff --git a/game/server/tf2/tf_playerlocaldata.cpp b/game/server/tf2/tf_playerlocaldata.cpp
new file mode 100644
index 0000000..1b82242
--- /dev/null
+++ b/game/server/tf2/tf_playerlocaldata.cpp
@@ -0,0 +1,147 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#include "cbase.h"
+#include "tf_playerlocaldata.h"
+#include "tf_player.h"
+#include "mathlib/mathlib.h"
+#include "entitylist.h"
+
+extern ConVar tf_fastbuild;
+ConVar tf_maxbankresources( "tf_maxbankresources", "2000", 0, "Max resources a single player can have." );
+
+#define BANK_RESOURCE_BITS 20
+#define MAX_PLAYER_RESOURCES ( (1 <<BANK_RESOURCE_BITS) - 1 )
+
+//-----------------------------------------------------------------------------
+// Purpose: SendProxy that converts the UtlVector list of objects to entindexes, where it's reassembled on the client
+//-----------------------------------------------------------------------------
+void SendProxy_PlayerObjectList( const SendProp *pProp, const void *pStruct, const void *pData, DVariant *pOut, int iElement, int objectID )
+{
+ CTFPlayerLocalData *pLocalData = (CTFPlayerLocalData*)pStruct;
+ Assert( pLocalData->m_pPlayer );
+
+ // If this fails, then SendProxyArrayLength_PlayerObjects didn't work.
+ Assert( iElement < pLocalData->m_pPlayer->GetObjectCount() );
+
+ CBaseObject *pObject = pLocalData->m_pPlayer->GetObject(iElement);
+
+ EHANDLE hObject;
+ hObject = pObject;
+
+ SendProxy_EHandleToInt( pProp, pStruct, &hObject, pOut, iElement, objectID );
+}
+
+
+int SendProxyArrayLength_PlayerObjects( const void *pStruct, int objectID )
+{
+ CTFPlayerLocalData *pLocalData = (CTFPlayerLocalData*)pStruct;
+ Assert( pLocalData->m_pPlayer );
+ int iObjects = pLocalData->m_pPlayer->GetObjectCount();
+ Assert( iObjects < MAX_OBJECTS_PER_PLAYER );
+ return iObjects;
+}
+
+BEGIN_SEND_TABLE_NOBASE( CTFPlayerLocalData, DT_TFLocal )
+ SendPropInt( SENDINFO(m_nInTacticalView), 1, SPROP_UNSIGNED ),
+ SendPropInt( SENDINFO( m_bKnockedDown ), 1, SPROP_UNSIGNED ),
+ SendPropVector( SENDINFO( m_vecKnockDownDir ), -1, SPROP_COORD ),
+ SendPropInt( SENDINFO( m_bThermalVision ), 1, SPROP_UNSIGNED ),
+ SendPropInt( SENDINFO( m_iIDEntIndex ), 10, SPROP_UNSIGNED ),
+ SendPropArray( SendPropInt( SENDINFO_ARRAY(m_iResourceAmmo), 4, SPROP_UNSIGNED ), m_iResourceAmmo ),
+ SendPropInt( SENDINFO(m_iBankResources), BANK_RESOURCE_BITS, SPROP_UNSIGNED ),
+ SendPropArray2(
+ SendProxyArrayLength_PlayerObjects,
+ SendPropInt("player_object_array_element", 0, SIZEOF_IGNORE, NUM_NETWORKED_EHANDLE_BITS, SPROP_UNSIGNED, SendProxy_PlayerObjectList),
+ MAX_OBJECTS_PER_PLAYER,
+ 0,
+ "player_object_array"
+ ),
+ SendPropInt( SENDINFO( m_bAttachingSapper ), 1, SPROP_UNSIGNED ),
+ SendPropFloat( SENDINFO( m_flSapperAttachmentFrac ), 7, SPROP_ROUNDDOWN, 0.0f, 1.0f ),
+ SendPropInt( SENDINFO( m_bForceMapOverview ), 1, SPROP_UNSIGNED ),
+END_SEND_TABLE()
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CTFPlayerLocalData::CTFPlayerLocalData()
+{
+ m_nInTacticalView = 0;
+ m_pPlayer = NULL;
+
+ m_bKnockedDown = false;
+ m_vecKnockDownDir.Init();
+
+ m_bThermalVision = false;
+ m_iIDEntIndex = 0;
+
+ // Resource carrying
+ for ( int i = 0; i < RESOURCE_TYPES; i++ )
+ {
+ m_iResourceAmmo.Set( i, 0 );
+ }
+
+ if( inv_demo.GetInt() )
+ {
+ m_iBankResources = 5000;
+ }
+ else
+ {
+ m_iBankResources = 0;
+ }
+ m_aObjects.Purge();
+
+ m_bAttachingSapper = false;
+ m_flSapperAttachmentFrac = 0;
+ m_bForceMapOverview = false;
+}
+
+//-----------------------------------------------------------------------------
+// Add, remove, count resources
+//-----------------------------------------------------------------------------
+void CTFPlayerLocalData::AddResources( int iAmount )
+{
+ m_iBankResources += iAmount;
+
+ if ( !tf_fastbuild.GetBool() && (m_iBankResources > tf_maxbankresources.GetFloat()) )
+ {
+ m_iBankResources = tf_maxbankresources.GetFloat();
+ }
+
+ // Clamp for overflow...
+ if ( m_iBankResources > MAX_PLAYER_RESOURCES )
+ {
+ Msg("Warning: Player overflowed resource count!\n");
+ m_iBankResources = MAX_PLAYER_RESOURCES;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerLocalData::RemoveResources( int iAmount )
+{
+ Assert( iAmount <= m_iBankResources );
+ m_iBankResources = MAX(0, m_iBankResources - iAmount);
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+int CTFPlayerLocalData::ResourceCount( void ) const
+{
+ return m_iBankResources;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerLocalData::SetResources( int iAmount )
+{
+ m_iBankResources = iAmount;
+} \ No newline at end of file
diff --git a/game/server/tf2/tf_playerlocaldata.h b/game/server/tf2/tf_playerlocaldata.h
new file mode 100644
index 0000000..408f54d
--- /dev/null
+++ b/game/server/tf2/tf_playerlocaldata.h
@@ -0,0 +1,68 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef TF_PLAYERLOCALDATA_H
+#define TF_PLAYERLOCALDATA_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include "techtree.h"
+#include "predictable_entity.h"
+#include "tf_obj.h"
+
+//-----------------------------------------------------------------------------
+// Purpose: Player specific data for TF2 ( sent only to local player, too )
+//-----------------------------------------------------------------------------
+class CTFPlayerLocalData
+{
+public:
+ DECLARE_PREDICTABLE();
+ DECLARE_CLASS_NOBASE( CTFPlayerLocalData );
+ DECLARE_EMBEDDED_NETWORKVAR();
+
+ CTFPlayerLocalData();
+
+ CBaseTFPlayer *m_pPlayer;
+
+ CNetworkVar( bool, m_nInTacticalView );
+
+ // Player has been knocked down
+ CNetworkVar( bool, m_bKnockedDown );
+ CNetworkQAngle( m_vecKnockDownDir );
+
+ // Player is using thermal vision
+ CNetworkVar( bool, m_bThermalVision );
+
+ // ID
+ CNetworkVar( int, m_iIDEntIndex );
+
+ // Resource chunk carrying counts
+ CNetworkArray( int, m_iResourceAmmo, RESOURCE_TYPES ); // 0 = Normal resources, 1 = Processed resources
+
+ // Resource manipulation
+ void AddResources( int iAmount );
+ void RemoveResources( int iAmount );
+ int ResourceCount( void ) const;
+ void SetResources( int iAmount );
+
+ // Resource bank
+ CNetworkVar( int, m_iBankResources ); // Current amounts of resources in my bank
+
+ // Objects
+ CUtlVector< CHandle<CBaseObject> > m_aObjects;
+
+ // Object sapper placement handling
+ CNetworkVar( bool, m_bAttachingSapper );
+ CNetworkVar( float, m_flSapperAttachmentFrac );
+
+ CNetworkVar( bool, m_bForceMapOverview );
+};
+
+EXTERN_SEND_TABLE(DT_TFLocal);
+
+#endif // TF_PLAYERLOCALDATA_H
diff --git a/game/server/tf2/tf_playermove.cpp b/game/server/tf2/tf_playermove.cpp
new file mode 100644
index 0000000..7f4e2b2
--- /dev/null
+++ b/game/server/tf2/tf_playermove.cpp
@@ -0,0 +1,291 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#include "cbase.h"
+#include "player_command.h"
+#include "tf_player.h"
+#include "igamemovement.h"
+#include "tf_shareddefs.h"
+#include "in_buttons.h"
+#include "tf_movedata.h"
+#include "tf_class_recon.h"
+#include "tf_reconvars.h"
+#include "IserverVehicle.h"
+#include "tf_class_commando.h"
+#include "ipredictionsystem.h"
+
+ConVar jetpack_infinite("jetpack_infinite", "0");
+extern float g_JetpackDepleteRate; // How fast the jetpack depletes.
+extern float g_JetpackNegSnap; // When the jetpack is fully depleted, it snaps to this so the pilot sputters.
+
+static CTFMoveData g_TFMoveData;
+CMoveData *g_pMoveData = &g_TFMoveData;
+
+IPredictionSystem *IPredictionSystem::g_pPredictionSystems = NULL;
+
+
+//-----------------------------------------------------------------------------
+// Sets up the move data for TF2
+//-----------------------------------------------------------------------------
+class CTFPlayerMove : public CPlayerMove
+{
+DECLARE_CLASS( CTFPlayerMove, CPlayerMove );
+
+public:
+ virtual void SetupMove( CBasePlayer *player, CUserCmd *ucmd, IMoveHelper *pHelper, CMoveData *move );
+ virtual void FinishMove( CBasePlayer *player, CUserCmd *ucmd, CMoveData *move );
+
+private:
+
+ void SetupMoveCommando( CBaseTFPlayer *pTFPlayer, CUserCmd *pUcmd, IMoveHelper *pHelper, CTFMoveData *pTFMove );
+ void SetupMoveRecon( CBaseTFPlayer *pTFPlayer, CUserCmd *pUcmd, IMoveHelper *pHelper, CTFMoveData *pTFMove );
+
+ void FinishMoveCommando( CBaseTFPlayer *pTFPlayer, CTFMoveData *pTFMove, CUserCmd *ucmd );
+ void FinishMoveRecon( CBaseTFPlayer *pTFPlayer, CTFMoveData *pTFMove, CUserCmd *ucmd );
+};
+
+// PlayerMove Interface
+static CTFPlayerMove g_PlayerMove;
+
+//-----------------------------------------------------------------------------
+// Singleton accessor
+//-----------------------------------------------------------------------------
+CPlayerMove *PlayerMove()
+{
+ return &g_PlayerMove;
+}
+
+//-----------------------------------------------------------------------------
+// Main setup, finish
+//-----------------------------------------------------------------------------
+
+//-----------------------------------------------------------------------------
+// Purpose: This is called pre player movement and copies all the data necessary
+// from the player for movement. (Server-side, the client-side version
+// of this code can be found in prediction.cpp.)
+//-----------------------------------------------------------------------------
+void CTFPlayerMove::SetupMove( CBasePlayer *player, CUserCmd *ucmd, IMoveHelper *pHelper, CMoveData *move )
+{
+ // Call the default SetupMove code.
+ BaseClass::SetupMove( player, ucmd, pHelper, move );
+
+ //
+ // Convert to TF2 data.
+ //
+ CBaseTFPlayer *pTFPlayer = static_cast<CBaseTFPlayer*>( player );
+ Assert( pTFPlayer );
+
+ CTFMoveData *pTFMove = static_cast<CTFMoveData*>( move );
+ Assert( pTFMove );
+
+ //
+ // Player movement data.
+ //
+
+ // Copy the position delta.
+ pTFMove->m_vecPosDelta = pTFPlayer->m_vecPosDelta;
+
+ // Copy the momentum data.
+ pTFMove->m_iMomentumHead = pTFPlayer->m_iMomentumHead;
+ for ( int iMomentum = 0; iMomentum < CTFMoveData::MOMENTUM_MAXSIZE; iMomentum++ )
+ {
+ pTFMove->m_aMomentum[iMomentum] = pTFPlayer->m_aMomentum[iMomentum];
+ }
+
+ pTFMove->m_nClassID = pTFPlayer->PlayerClass();
+
+ IVehicle *pVehicle = player->GetVehicle();
+ if (!pVehicle)
+ {
+ // Handle player class specific setup.
+ switch( pTFPlayer->PlayerClass() )
+ {
+ case TFCLASS_RECON:
+ {
+ SetupMoveRecon( pTFPlayer, ucmd, pHelper, pTFMove );
+ break;
+ }
+ case TFCLASS_COMMANDO:
+ {
+ SetupMoveCommando( pTFPlayer, ucmd, pHelper, pTFMove );
+ break;
+ }
+ default:
+ {
+ // pTFMove->m_nClassID = TFCLASS_UNDECIDED;
+ break;
+ }
+ }
+ }
+ else
+ {
+ pVehicle->SetupMove( player, ucmd, pHelper, move );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerMove::SetupMoveRecon( CBaseTFPlayer *pTFPlayer, CUserCmd *pUcmd, IMoveHelper *pHelper,
+ CTFMoveData *pTFMove )
+{
+ CPlayerClassRecon *pRecon = static_cast<CPlayerClassRecon*>( pTFPlayer->GetPlayerClass() );
+ if ( pRecon )
+ {
+ PlayerClassReconData_t *pReconData = pRecon->GetClassData();
+ if ( pReconData )
+ {
+ pTFMove->ReconData().m_nJumpCount = pReconData->m_nJumpCount;
+ pTFMove->ReconData().m_flSuppressionJumpTime = pReconData->m_flSuppressionJumpTime;
+ pTFMove->ReconData().m_flSuppressionImpactTime = pReconData->m_flSuppressionImpactTime;
+ pTFMove->ReconData().m_flActiveJumpTime = pReconData->m_flActiveJumpTime;
+ pTFMove->ReconData().m_flStickTime = pReconData->m_flStickTime;
+ pTFMove->ReconData().m_flImpactDist = pReconData->m_flImpactDist;
+ pTFMove->ReconData().m_vecImpactNormal = pReconData->m_vecImpactNormal;
+ pTFMove->ReconData().m_vecUnstickVelocity = pReconData->m_vecUnstickVelocity;
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerMove::SetupMoveCommando( CBaseTFPlayer *pTFPlayer, CUserCmd *pUcmd, IMoveHelper *pHelper,
+ CTFMoveData *pTFMove )
+{
+ CPlayerClassCommando *pCommando = static_cast<CPlayerClassCommando*>( pTFPlayer->GetPlayerClass() );
+ if ( pCommando )
+ {
+ PlayerClassCommandoData_t *pCommandoData = pCommando->GetClassData();
+ if ( pCommandoData )
+ {
+ pTFMove->CommandoData().m_bCanBullRush = pCommandoData->m_bCanBullRush;
+ pTFMove->CommandoData().m_bBullRush = pCommandoData->m_bBullRush;
+ pTFMove->CommandoData().m_vecBullRushDir = pCommandoData->m_vecBullRushDir;
+ pTFMove->CommandoData().m_vecBullRushViewDir = pCommandoData->m_vecBullRushViewDir;
+ pTFMove->CommandoData().m_vecBullRushViewGoalDir = pCommandoData->m_vecBullRushViewGoalDir;
+ pTFMove->CommandoData().m_flBullRushTime = pCommandoData->m_flBullRushTime;
+ pTFMove->CommandoData().m_flDoubleTapForwardTime = pCommandoData->m_flDoubleTapForwardTime;
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: This is called post player movement to copy back all data that
+// movement could have modified and that is necessary for future
+// movement. (Server-side, the client-side version of this code can
+// be found in prediction.cpp.)
+//-----------------------------------------------------------------------------
+void CTFPlayerMove::FinishMove( CBasePlayer *player, CUserCmd *ucmd, CMoveData *move )
+{
+ // Call the default FinishMove code.
+ BaseClass::FinishMove( player, ucmd, move );
+
+ //
+ // Convert to TF2 data.
+ //
+ CBaseTFPlayer *pTFPlayer = static_cast<CBaseTFPlayer*>( player );
+ Assert( pTFPlayer );
+
+ CTFMoveData *pTFMove = static_cast<CTFMoveData*>( move );
+ Assert( pTFMove );
+
+ // The class had better not have changed during the move!!
+ Assert( pTFMove->m_nClassID == pTFPlayer->PlayerClass() );
+
+ IVehicle *pVehicle = player->GetVehicle();
+ if (!pVehicle)
+ {
+ // Handle player class specific setup.
+ switch( pTFPlayer->PlayerClass() )
+ {
+ case TFCLASS_RECON:
+ {
+ FinishMoveRecon( pTFPlayer, pTFMove, ucmd );
+ break;
+ }
+ case TFCLASS_COMMANDO:
+ {
+ FinishMoveCommando( pTFPlayer, pTFMove, ucmd );
+ break;
+ }
+ default:
+ {
+ break;
+ }
+ }
+ }
+ else
+ {
+ // Similarly, the vehicle had better not have changed during the move!
+ Assert( pTFPlayer->IsInAVehicle() );
+ pVehicle->FinishMove( pTFPlayer, ucmd, pTFMove );
+ }
+
+ //
+ // Player movement data.
+ //
+
+ // Copy the position delta.
+ pTFPlayer->m_vecPosDelta = pTFMove->m_vecPosDelta;
+
+ COMPILE_TIME_ASSERT( CBaseTFPlayer::MOMENTUM_MAXSIZE == CTFMoveData::MOMENTUM_MAXSIZE );
+
+ // Copy the momentum data back (the movement may have updated it!).
+ pTFPlayer->m_iMomentumHead = pTFMove->m_iMomentumHead;
+ for ( int iMomentum = 0; iMomentum < CTFMoveData::MOMENTUM_MAXSIZE; iMomentum++ )
+ {
+ pTFPlayer->m_aMomentum[iMomentum] = pTFMove->m_aMomentum[iMomentum];
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerMove::FinishMoveRecon( CBaseTFPlayer *pTFPlayer, CTFMoveData *pTFMove,
+ CUserCmd *ucmd )
+{
+ CPlayerClassRecon *pRecon = static_cast<CPlayerClassRecon*>( pTFPlayer->GetPlayerClass() );
+ if ( pRecon )
+ {
+ PlayerClassReconData_t *pReconData = pRecon->GetClassData();
+ if ( pReconData )
+ {
+ pReconData->m_nJumpCount = pTFMove->ReconData().m_nJumpCount;
+ pReconData->m_flSuppressionJumpTime = pTFMove->ReconData().m_flSuppressionJumpTime;
+ pReconData->m_flSuppressionImpactTime = pTFMove->ReconData().m_flSuppressionImpactTime;
+ pReconData->m_flActiveJumpTime = pTFMove->ReconData().m_flActiveJumpTime;
+ pReconData->m_flStickTime = pTFMove->ReconData().m_flStickTime;
+ pReconData->m_flImpactDist = pTFMove->ReconData().m_flImpactDist;
+ pReconData->m_vecImpactNormal = pTFMove->ReconData().m_vecImpactNormal;
+ pReconData->m_vecUnstickVelocity = pTFMove->ReconData().m_vecUnstickVelocity;
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerMove::FinishMoveCommando( CBaseTFPlayer *pTFPlayer, CTFMoveData *pTFMove,
+ CUserCmd *ucmd )
+{
+ CPlayerClassCommando *pCommando = static_cast<CPlayerClassCommando*>( pTFPlayer->GetPlayerClass() );
+ if ( pCommando )
+ {
+ PlayerClassCommandoData_t *pCommandoData = pCommando->GetClassData();
+ if ( pCommandoData )
+ {
+ pCommandoData->m_bCanBullRush = pTFMove->CommandoData().m_bCanBullRush;
+ pCommandoData->m_bBullRush = pTFMove->CommandoData().m_bBullRush;
+ pCommandoData->m_vecBullRushDir = pTFMove->CommandoData().m_vecBullRushDir;
+ pCommandoData->m_vecBullRushViewDir = pTFMove->CommandoData().m_vecBullRushViewDir;
+ pCommandoData->m_vecBullRushViewGoalDir = pTFMove->CommandoData().m_vecBullRushViewGoalDir;
+ pCommandoData->m_flBullRushTime = pTFMove->CommandoData().m_flBullRushTime;
+ pCommandoData->m_flDoubleTapForwardTime = pTFMove->CommandoData().m_flDoubleTapForwardTime;
+ }
+ }
+}
diff --git a/game/server/tf2/tf_rescollector_ground.cpp b/game/server/tf2/tf_rescollector_ground.cpp
new file mode 100644
index 0000000..db4f992
--- /dev/null
+++ b/game/server/tf2/tf_rescollector_ground.cpp
@@ -0,0 +1 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
diff --git a/game/server/tf2/tf_rescollector_ground.h b/game/server/tf2/tf_rescollector_ground.h
new file mode 100644
index 0000000..db4f992
--- /dev/null
+++ b/game/server/tf2/tf_rescollector_ground.h
@@ -0,0 +1 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
diff --git a/game/server/tf2/tf_shared_defines.h b/game/server/tf2/tf_shared_defines.h
new file mode 100644
index 0000000..7a82c42
--- /dev/null
+++ b/game/server/tf2/tf_shared_defines.h
@@ -0,0 +1,28 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Shared defines between the game and client DLLs for TF
+//
+// $Workfile: $
+// $Date: $
+//
+//-----------------------------------------------------------------------------
+// $Log: $
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef TF_DEFINES_H
+#define TF_DEFINES_H
+#pragma once
+
+// Zone states
+#define ZONE_FRIENDLY 1
+#define ZONE_ENEMY 2
+#define ZONE_CONTESTED 3
+
+// Loot state
+#define LOOT_NOT 0
+#define LOOT_CAPABLE 1
+#define LOOT_LOOTING 2
+
+#endif // TF_DEFINES_H
diff --git a/game/server/tf2/tf_shield.cpp b/game/server/tf2/tf_shield.cpp
new file mode 100644
index 0000000..c73c092
--- /dev/null
+++ b/game/server/tf2/tf_shield.cpp
@@ -0,0 +1,432 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: The Escort's Shield weapon
+//
+// $Revision: $
+// $NoKeywords: $
+//=============================================================================//
+
+#include "cbase.h"
+#include "tf_shield.h"
+#include "tf_shareddefs.h"
+#include "collisionutils.h"
+#include <float.h>
+#include "sendproxy.h"
+#include "mathlib/mathlib.h"
+
+#define PROBE_EFFECT_TIME 0.15f
+
+ConVar shield_explosive_damage( "shield_explosive_damage","10", FCVAR_REPLICATED, "Shield power damage from explosions" );
+
+// Percentage of total health that the shield's allowed to go below 0
+#define SHIELD_MIN_HEALTH_FACTOR (-0.5)
+
+//-----------------------------------------------------------------------------
+// Stores a list of all active shields
+//-----------------------------------------------------------------------------
+CUtlVector< CShield* > CShield::s_Shields;
+
+
+//-----------------------------------------------------------------------------
+// Returns true if the entity is a shield
+//-----------------------------------------------------------------------------
+bool IsShield( CBaseEntity *pEnt )
+{
+ // This is a faster way...
+ return pEnt && (pEnt->GetCollisionGroup() == TFCOLLISION_GROUP_SHIELD);
+// return pEnt && FClassnameIs( pEnt, "shield" )
+}
+
+
+//=============================================================================
+// Shield effect
+//=============================================================================
+EXTERN_SEND_TABLE(DT_BaseEntity)
+
+IMPLEMENT_SERVERCLASS_ST(CShield, DT_Shield)
+ SendPropInt(SENDINFO(m_nOwningPlayerIndex), MAX_EDICT_BITS, SPROP_UNSIGNED ),
+ SendPropFloat(SENDINFO(m_flPowerLevel), 9, SPROP_ROUNDUP, SHIELD_MIN_HEALTH_FACTOR, 1.0f ),
+ SendPropInt(SENDINFO(m_bIsEMPed), 1, SPROP_UNSIGNED ),
+END_SEND_TABLE()
+
+//-----------------------------------------------------------------------------
+// constructor
+//-----------------------------------------------------------------------------
+CShield::CShield()
+{
+ s_Shields.AddToTail(this);
+ AddEFlags( EFL_FORCE_CHECK_TRANSMIT );
+ SetupRecharge( 0,0,0,0 );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CShield::~CShield()
+{
+ int i = s_Shields.Find(this);
+ if (i >= 0)
+ s_Shields.FastRemove(i);
+}
+
+//-----------------------------------------------------------------------------
+// Precache !!
+//-----------------------------------------------------------------------------
+void CShield::Precache()
+{
+ SetClassname( "shield" );
+}
+
+//-----------------------------------------------------------------------------
+// Spawn !!
+//-----------------------------------------------------------------------------
+void CShield::Spawn( void )
+{
+ m_bIsEMPed = 0;
+ SetCollisionGroup( TFCOLLISION_GROUP_SHIELD );
+
+ // Make it translucent
+ m_nRenderFX = kRenderTransAlpha;
+ SetRenderColorA( 255 );
+
+ m_flNextRechargeTime = gpGlobals->curtime;
+
+ CollisionProp()->SetSurroundingBoundsType( USE_GAME_CODE );
+ SetSolid( SOLID_CUSTOM );
+
+ // Stuff can't come to a rest on shields!
+ AddSolidFlags( FSOLID_NOT_STANDABLE );
+ UTIL_SetSize( this, vec3_origin, vec3_origin );
+}
+
+
+//-----------------------------------------------------------------------------
+// Owner
+//-----------------------------------------------------------------------------
+void CShield::SetOwnerEntity( CBaseEntity *pOwner )
+{
+ BaseClass::SetOwnerEntity( pOwner );
+
+ if (pOwner->IsPlayer())
+ {
+ m_nOwningPlayerIndex = pOwner->entindex();
+ }
+ else
+ {
+ m_nOwningPlayerIndex = 0;
+ }
+ ChangeTeam( pOwner->GetTeamNumber() );
+}
+
+
+
+//-----------------------------------------------------------------------------
+// Does this shield protect from a particular damage type?
+//-----------------------------------------------------------------------------
+float CShield::ProtectionAmount( int damageType ) const
+{
+ // As a test, we're trying to make shields impervious to everything
+ return 1.0f;
+}
+
+
+//-----------------------------------------------------------------------------
+// Activates/deactivates a shield for collision purposes
+//-----------------------------------------------------------------------------
+void CShield::ActivateCollisions( bool activate )
+{
+ if ( activate )
+ {
+ RemoveSolidFlags( FSOLID_NOT_SOLID );
+ }
+ else
+ {
+ AddSolidFlags( FSOLID_NOT_SOLID );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Activates all shields
+//-----------------------------------------------------------------------------
+void CShield::ActivateShields( bool activate, int team )
+{
+ for (int i = s_Shields.Count(); --i >= 0; )
+ {
+ // Activate all shields on the same team
+ if ( (team == -1) || (team == s_Shields[i]->GetTeamNumber()) )
+ {
+ s_Shields[i]->ActivateCollisions( activate );
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Checks a ray against shields only
+//-----------------------------------------------------------------------------
+bool CShield::IsBlockedByShields( const Vector& src, const Vector& end )
+{
+ trace_t tr;
+ Ray_t ray;
+ ray.Init( src, end );
+
+ for (int i = s_Shields.Count(); --i >= 0; )
+ {
+ if (!s_Shields[i]->ShouldCollide( TFCOLLISION_GROUP_WEAPON, MASK_ALL ))
+ continue;
+
+ // Coarse bbox test first
+ Vector vecAbsMins, vecAbsMaxs;
+ s_Shields[i]->CollisionProp()->WorldSpaceAABB( &vecAbsMins, &vecAbsMaxs );
+ if (!IsBoxIntersectingRay( vecAbsMins, vecAbsMaxs, ray.m_Start, ray.m_Delta ))
+ continue;
+
+ if (s_Shields[i]->TestCollision( ray, MASK_ALL, tr ))
+ return true;
+ }
+
+ return false;
+}
+
+
+//-----------------------------------------------------------------------------
+// Called when we hit something that we deflect...
+//-----------------------------------------------------------------------------
+void CShield::RegisterDeflection(const Vector& vecDir, int bitsDamageType, trace_t *ptr)
+{
+ // Probes don't hurt shields
+ if (bitsDamageType & DMG_PROBE)
+ return;
+
+ Vector normalDir;
+ VectorCopy( vecDir, normalDir );
+ VectorNormalize( normalDir );
+
+ // Uncomment this line when the client predicts the shield deflections
+ //filter.UsePredictionRules();
+ EntityMessageBegin( this );
+ WRITE_LONG( ptr->hitgroup );
+ WRITE_VEC3NORMAL( normalDir );
+ WRITE_BYTE( false ); // This was not a partial block
+ MessageEnd();
+
+ // If this is a buckshot round, don't pay the power cost to stop it once we've gone over our max buckshot hits this frame
+ if ( bitsDamageType & DMG_BUCKSHOT )
+ {
+ m_iBuckshotHitsThisFrame++;
+ if ( m_iBuckshotHitsThisFrame > 4 )
+ return;
+ }
+
+ // If this is an explosion, it counts for extra
+ if ( bitsDamageType & DMG_BLAST )
+ {
+ SetPower( m_flPower - shield_explosive_damage.GetFloat() );
+ }
+ else
+ {
+ // Reduce our power level by a hit
+ SetPower( m_flPower - 1 );
+ }
+
+ // If we've lost power fully, don't recharge for a bit
+ if ( m_flPower <= 0 )
+ {
+ m_flNextRechargeTime = gpGlobals->curtime + 1.0;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Called when we hit something that we let through...
+//-----------------------------------------------------------------------------
+void CShield::RegisterPassThru(const Vector& vecDir, int bitsDamageType, trace_t *ptr)
+{
+ // Probes don't hurt shields
+ if (bitsDamageType & DMG_PROBE)
+ return;
+
+ Vector normalDir;
+ VectorCopy( vecDir, normalDir );
+ VectorNormalize( normalDir );
+
+ EntityMessageBegin( this );
+ WRITE_LONG( ptr->hitgroup );
+ WRITE_VEC3NORMAL( normalDir );
+ WRITE_BYTE( true ); // This was a partial block
+ MessageEnd();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CShield::SetPower( float flPower )
+{
+ m_flPower = MAX( (SHIELD_MIN_HEALTH_FACTOR * m_flMaxPower), flPower );
+ if ( m_flPower > m_flMaxPower )
+ {
+ m_flPower = m_flMaxPower;
+ }
+ m_flPowerLevel = ( m_flPower / m_flMaxPower );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Setup shield recharging parameters
+//-----------------------------------------------------------------------------
+void CShield::SetupRecharge( float flPower, float flDelay, float flAmount, float flTickTime )
+{
+ m_flMaxPower = flPower;
+ SetPower( flPower );
+ m_flPowerLevel = 1.0;
+ m_flRechargeDelay = flDelay;
+ m_flRechargeAmount = flAmount;
+ m_flRechargeTime = flTickTime;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Recharge the shield if we haven't taken damage for a while
+//-----------------------------------------------------------------------------
+void CShield::Think( void )
+{
+ // Clear out buckshot count
+ m_iBuckshotHitsThisFrame = 0;
+
+ if ( m_flNextRechargeTime < gpGlobals->curtime )
+ {
+ if ( m_flPower < m_flMaxPower )
+ {
+ SetPower( m_flPower + m_flRechargeAmount );
+ }
+
+ m_flNextRechargeTime = gpGlobals->curtime + m_flRechargeTime;
+ }
+
+ // Let derived objects think if they want to
+ if ( m_pfnThink )
+ {
+ (this->*m_pfnThink)();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Indicates the shield has been EMPed (or not)
+//-----------------------------------------------------------------------------
+void CShield::SetEMPed( bool isEmped )
+{
+ m_bIsEMPed = isEmped;
+}
+
+//-----------------------------------------------------------------------------
+// Helper method for collision testing
+//-----------------------------------------------------------------------------
+#pragma warning ( disable : 4701 )
+
+bool CShield::TestCollision( const Ray_t& ray, unsigned int mask, trace_t& trace )
+{
+ // Can't block anything if we're EMPed, or we've got no power left to block
+ if ( IsEMPed() )
+ return false;
+ if ( m_flPower <= 0 )
+ return false;
+
+ // Here, we're gonna test for collision.
+ // If we don't stop this kind of bullet, we'll generate an effect here
+ // but we won't change the trace to indicate a collision.
+
+ // It's just polygon soup...
+ int hitgroup;
+ bool firstTri;
+ int v1[2], v2[2], v3[2];
+ float ihit, jhit;
+ float mint = FLT_MAX;
+ float t;
+
+ int h = Height();
+ int w = Width();
+
+ for (int i = 0; i < h - 1; ++i)
+ {
+ for (int j = 0; j < w - 1; ++j)
+ {
+ // Don't test if this panel ain't active...
+ if (!IsPanelActive( j, i ))
+ continue;
+
+ // NOTE: Structure order of points so that our barycentric
+ // axes for each triangle are along the (u,v) directions of the mesh
+ // The barycentric coords we'll need below
+
+ // Two triangles per quad...
+ t = IntersectRayWithTriangle( ray,
+ GetPoint( j, i + 1 ),
+ GetPoint( j + 1, i + 1 ),
+ GetPoint( j, i ), true );
+ if ((t >= 0.0f) && (t < mint))
+ {
+ mint = t;
+ v1[0] = j; v1[1] = i + 1;
+ v2[0] = j + 1; v2[1] = i + 1;
+ v3[0] = j; v3[1] = i;
+ ihit = i; jhit = j;
+ firstTri = true;
+ }
+
+ t = IntersectRayWithTriangle( ray,
+ GetPoint( j + 1, i ),
+ GetPoint( j, i ),
+ GetPoint( j + 1, i + 1 ), true );
+ if ((t >= 0.0f) && (t < mint))
+ {
+ mint = t;
+ v1[0] = j + 1; v1[1] = i;
+ v2[0] = j; v2[1] = i;
+ v3[0] = j + 1; v3[1] = i + 1;
+ ihit = i; jhit = j;
+ firstTri = false;
+ }
+ }
+ }
+
+ if (mint == FLT_MAX)
+ return false;
+
+ // Stuff the barycentric coordinates of the triangle hit into the hit group
+ // For the first triangle, the first edge goes along u, the second edge goes
+ // along -v. For the second triangle, the first edge goes along -u,
+ // the second edge goes along v.
+ const Vector& v1vec = GetPoint(v1[0], v1[1]);
+ const Vector& v2vec = GetPoint(v2[0], v2[1]);
+ const Vector& v3vec = GetPoint(v3[0], v3[1]);
+ float u, v;
+ bool ok = ComputeIntersectionBarycentricCoordinates( ray,
+ v1vec, v2vec, v3vec, u, v );
+ Assert( ok );
+ if ( !ok )
+ {
+ return false;
+ }
+
+ if (firstTri)
+ v = 1.0 - v;
+ else
+ u = 1.0 - u;
+ v += ihit; u += jhit;
+ v /= (h - 1);
+ u /= (w - 1);
+
+ // Compress (u,v) into 1 dot 15, v in top bits
+ hitgroup = (((int)(v * (1 << 15))) << 16) + (int)(u * (1 << 15));
+
+ Vector normal;
+ float intercept;
+ ComputeTrianglePlane( v1vec, v2vec, v3vec, normal, intercept );
+
+ UTIL_SetTrace( trace, ray, edict(), mint, hitgroup, CONTENTS_SOLID, normal, intercept );
+ VectorAdd( trace.startpos, ray.m_StartOffset, trace.startpos );
+ VectorAdd( trace.endpos, ray.m_StartOffset, trace.endpos );
+
+ return true;
+}
+
+#pragma warning ( default : 4701 )
+
+
diff --git a/game/server/tf2/tf_shield.h b/game/server/tf2/tf_shield.h
new file mode 100644
index 0000000..14bc7fc
--- /dev/null
+++ b/game/server/tf2/tf_shield.h
@@ -0,0 +1,146 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef TF_SHIELD_H
+#define TF_SHIELD_H
+
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include "baseentity.h"
+#include "utlvector.h"
+
+
+//-----------------------------------------------------------------------------
+// forward declarations
+//-----------------------------------------------------------------------------
+class Vector;
+class CGameTrace;
+typedef CGameTrace trace_t;
+struct edict_t;
+
+
+//-----------------------------------------------------------------------------
+// Base class for shield entities
+//-----------------------------------------------------------------------------
+class CShield : public CBaseEntity
+{
+public:
+ DECLARE_CLASS( CShield, CBaseEntity );
+ DECLARE_SERVERCLASS();
+
+ CShield();
+ ~CShield();
+
+public:
+ void Precache();
+ void Spawn( void );
+
+ virtual void Think( void );
+
+ // Sets the desired center direction
+ virtual void SetCenterAngles( const QAngle &angles ) {}
+ virtual void SetAlwaysOrient( bool bOrient ) {}
+ virtual bool IsAlwaysOrienting( ) { return false; }
+
+ // Used by the mobile shield.
+
+ // Change the angular spring constant. This affects how fast the shield rotates to face the angles
+ // given in SetAngles. Higher numbers are more responsive, but if you go too high (around 40), it will
+ // jump past the specified angles and wiggle a little bit.
+ virtual void SetAngularSpringConstant( float flConstant ) {}
+
+ // Move the shield out a certain amount.
+ virtual void SetFrontDistance( float flDistance ) {}
+
+ // Called when we hit something that we deflect...
+ void RegisterDeflection(const Vector& vecDir, int bitsDamageType, trace_t *ptr);
+
+ // Called when we hit something that we let through...
+ void RegisterPassThru(const Vector& vecDir, int bitsDamageType, trace_t *ptr);
+
+ // Activates/deactivates a shield for collision purposes
+ void ActivateCollisions( bool activate );
+
+ // Does this shield protect from a particular damage type?
+ float ProtectionAmount( int weaponType ) const;
+
+ // Deactivates all shields of players on a particular team
+ // If you don't specify a team, it'll affect all shields
+ static void ActivateShields( bool activate, int team = -1 );
+
+ // For collision testing
+ bool TestCollision( const Ray_t& ray, unsigned int mask, trace_t& trace );
+
+ // Called when the shield has moved
+ virtual void ShieldMoved() {}
+
+ // Called when the shield is EMPed (or de-EMPed)
+ virtual void SetEMPed( bool isEmped );
+
+ // Indicates the visual center of the shape, may not be the actual center
+ // (best example is the dome: it projects from a point which is not
+ // at the center of the hemisphere).
+ virtual void SetGeometryOffset( const Vector& vector ) {};
+
+ // Shield power & recharging
+ void SetupRecharge( float flPower, float flDelay, float flAmount, float flTickTime );
+ float GetPower( void ) { return m_flPower; }
+ void SetPower( float flPower );
+ // Make the shield recharge it's health
+ void ShieldRechargeThink( void );
+
+ // Is this ray blocked by any shields?
+ static bool IsBlockedByShields( const Vector& src, const Vector& end );
+
+ virtual void SetOwnerEntity( CBaseEntity *pOwner );
+
+ virtual void SetThetaPhi( float flTheta, float flPhi ) { }
+ virtual void SetAttachmentIndex( int nAttachmentIndex ) {}
+
+protected:
+ //
+ // derived classes must implement these
+ //
+ virtual int Width() { return 0; }
+ virtual int Height() { return 0; }
+ virtual bool IsPanelActive( int x, int y ) { return true; }
+ virtual const Vector& GetPoint( int x, int y ) { return vec3_origin; }
+ bool IsEMPed() const { return m_bIsEMPed; }
+
+ float m_flPower;
+ float m_flMaxPower;
+ CNetworkVar( float, m_flPowerLevel ); // m_flPower mapped to 0->1 range for networking
+ float m_flRechargeDelay;
+ float m_flRechargeAmount;
+ float m_flRechargeTime;
+ float m_flNextRechargeTime;
+
+private:
+ int m_iBuckshotHitsThisFrame;
+ float m_flLastProbeTime;
+ CNetworkVar( bool, m_bIsEMPed );
+ CNetworkVar( int, m_nOwningPlayerIndex );
+
+ // List of all active shields
+ static CUtlVector< CShield* > s_Shields;
+};
+
+
+//-----------------------------------------------------------------------------
+// Class factory methods to create the various versions of the shield
+//-----------------------------------------------------------------------------
+CShield* CreateMobileShield( CBaseEntity *owner, float flFrontDistance = 0 );
+
+
+//-----------------------------------------------------------------------------
+// Returns true if the entity is a shield
+//-----------------------------------------------------------------------------
+bool IsShield( CBaseEntity *pEnt );
+
+#endif // TF_SHIELD_H
diff --git a/game/server/tf2/tf_shield_flat.cpp b/game/server/tf2/tf_shield_flat.cpp
new file mode 100644
index 0000000..cbe187d
--- /dev/null
+++ b/game/server/tf2/tf_shield_flat.cpp
@@ -0,0 +1,150 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#include "cbase.h"
+#include "tf_shield_flat.h"
+#include "tf_shieldshared.h"
+
+
+//-----------------------------------------------------------------------------
+// Data tables
+//-----------------------------------------------------------------------------
+LINK_ENTITY_TO_CLASS( shield_flat, CShieldFlat );
+
+IMPLEMENT_SERVERCLASS_ST(CShieldFlat, DT_Shield_Flat)
+
+ SendPropInt (SENDINFO(m_ShieldState), 2, SPROP_UNSIGNED ),
+ SendPropFloat (SENDINFO(m_Width), 8, 0, 0, 256 ),
+ SendPropFloat (SENDINFO(m_Height), 8, 0, 0, 256 ),
+
+END_SEND_TABLE()
+
+
+//-----------------------------------------------------------------------------
+// Spawn
+//-----------------------------------------------------------------------------
+void CShieldFlat::Spawn( )
+{
+ BaseClass::Spawn();
+ m_ShieldState = 0;
+ ShieldMoved();
+}
+
+
+//-----------------------------------------------------------------------------
+// Computes the shield bounding box
+//-----------------------------------------------------------------------------
+void CShieldFlat::ShieldMoved( void )
+{
+ Vector forward, right, up;
+ AngleVectors( GetAbsAngles(), &forward, &right, &up );
+
+ VectorMA( GetAbsOrigin(), -m_Width * 0.5, right, m_Pos[0] );
+ VectorMA( m_Pos[0], -m_Height * 0.5, up, m_Pos[0] );
+ VectorMA( m_Pos[0], m_Width, right, m_Pos[1] );
+ VectorMA( m_Pos[0], m_Height, up, m_Pos[2] );
+ VectorMA( m_Pos[2], m_Width, right, m_Pos[3] );
+
+ m_LastAngles = GetAbsAngles();
+ m_LastPosition = GetAbsOrigin();
+}
+
+
+//-----------------------------------------------------------------------------
+// Shield size
+//-----------------------------------------------------------------------------
+void CShieldFlat::SetSize( float w, float h )
+{
+ m_Width = w;
+ m_Height = h;
+ Vector mins( -1.0/16.0f, -w * 0.5f, -h * 0.5f );
+ Vector maxs( 1.0/16.0f, w * 0.5f, h * 0.5f );
+ UTIL_SetSize( this, mins, maxs );
+ ShieldMoved();
+}
+
+
+//-----------------------------------------------------------------------------
+// Compute world axis-aligned bounding box
+//-----------------------------------------------------------------------------
+void CShieldFlat::ComputeWorldSpaceSurroundingBox( Vector *pWorldMins, Vector *pWorldMaxs )
+{
+ TransformAABB( CollisionProp()->CollisionToWorldTransform(),
+ CollisionProp()->OBBMins(), CollisionProp()->OBBMaxs(), *pWorldMins, *pWorldMaxs );
+}
+
+
+//-----------------------------------------------------------------------------
+// Shield points
+//-----------------------------------------------------------------------------
+const Vector& CShieldFlat::GetPoint( int x, int y )
+{
+ if ((m_LastAngles != GetAbsAngles()) || (m_LastPosition != GetAbsOrigin() ))
+ {
+ ShieldMoved();
+ }
+
+ int i = (x >= 1);
+ i += (y >= 1) * 2;
+ return m_Pos[i];
+}
+
+
+//-----------------------------------------------------------------------------
+// Called when the shield is EMPed
+//-----------------------------------------------------------------------------
+void CShieldFlat::SetEMPed( bool isEmped )
+{
+ CShield::SetEMPed(isEmped);
+ if (IsEMPed())
+ {
+ m_ShieldState |= SHIELD_FLAT_EMP;
+ }
+ else
+ {
+ m_ShieldState &= ~SHIELD_FLAT_EMP;
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Shield is killed here
+//-----------------------------------------------------------------------------
+void CShieldFlat::Activate( bool active )
+{
+ if (active)
+ {
+ m_ShieldState &= ~SHIELD_FLAT_INACTIVE;
+ }
+ else
+ {
+ m_ShieldState |= SHIELD_FLAT_INACTIVE;
+ }
+ ActivateCollisions( active );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Create a mobile version of the shield
+//-----------------------------------------------------------------------------
+CShieldFlat* CreateFlatShield( CBaseEntity *pOwner, float w, float h, const Vector& relOrigin, const QAngle &relAngles )
+{
+ CShieldFlat *pShield = (CShieldFlat*)CreateEntityByName("shield_flat");
+
+ pShield->SetParent( pOwner );
+ pShield->SetOwnerEntity( pOwner );
+ UTIL_SetOrigin( pShield, relOrigin );
+
+ // Compute relative angles....
+ pShield->SetLocalAngles( relAngles );
+ pShield->ChangeTeam( pOwner->GetTeamNumber() );
+ pShield->Spawn();
+ pShield->SetSize( w , h );
+
+ return pShield;
+}
+
diff --git a/game/server/tf2/tf_shield_flat.h b/game/server/tf2/tf_shield_flat.h
new file mode 100644
index 0000000..c107db4
--- /dev/null
+++ b/game/server/tf2/tf_shield_flat.h
@@ -0,0 +1,68 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef TF_SHIELD_FLAT_H
+#define TF_SHIELD_FLAT_H
+
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include "tf_shield.h"
+#include "mathlib/vector.h"
+
+//-----------------------------------------------------------------------------
+//
+// This is the shield projected by the grenade
+//
+//-----------------------------------------------------------------------------
+
+class CShieldFlat : public CShield
+{
+ DECLARE_CLASS( CShieldFlat, CShield );
+ DECLARE_SERVERCLASS();
+
+public:
+ void SetSize( float w, float h );
+
+ virtual void Spawn( void );
+
+ virtual void SetEMPed( bool isEmped );
+
+ void Activate( bool active );
+
+ virtual int Width() { return 2; }
+ virtual int Height() { return 2; }
+ virtual bool IsPanelActive( int x, int y ) { return true; }
+ virtual const Vector& GetPoint( int x, int y );
+ virtual void ComputeWorldSpaceSurroundingBox( Vector *pWorldMins, Vector *pWorldMaxs );
+
+public:
+ // Think methods
+ void ShieldMoved();
+
+public:
+
+ // networked data
+ CNetworkVar( unsigned char, m_ShieldState );
+ CNetworkVar( float, m_Width );
+ CNetworkVar( float, m_Height );
+
+private:
+ QAngle m_LastAngles;
+ Vector m_LastPosition;
+ Vector m_Pos[4];
+};
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Create a mobile version of the shield
+//-----------------------------------------------------------------------------
+
+CShieldFlat* CreateFlatShield( CBaseEntity *pOwner, float w, float h, const Vector& relOrigin, const QAngle &relAngles );
+
+#endif TF_SHIELD_FLAT_H \ No newline at end of file
diff --git a/game/server/tf2/tf_shieldgrenade.cpp b/game/server/tf2/tf_shieldgrenade.cpp
new file mode 100644
index 0000000..157395f
--- /dev/null
+++ b/game/server/tf2/tf_shieldgrenade.cpp
@@ -0,0 +1,424 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#include "cbase.h"
+#include "BaseAnimating.h"
+#include "tf_shieldgrenade.h"
+#include "tf_shieldshared.h"
+#include "tf_shield_flat.h"
+#include "tf_player.h"
+#include "engine/IEngineSound.h"
+#include "Sprite.h"
+
+#define SHIELD_GRENADE_FUSE_TIME 2.25f
+
+//-----------------------------------------------------------------------------
+//
+// The shield grenade class
+//
+//-----------------------------------------------------------------------------
+
+class CShieldGrenade : public CBaseAnimating
+{
+ DECLARE_CLASS( CShieldGrenade, CBaseAnimating );
+public:
+ DECLARE_DATADESC();
+
+ CShieldGrenade();
+
+ virtual void Spawn( void );
+ virtual void Precache( void );
+ virtual void UpdateOnRemove( void );
+ void SetLifetime( float timer );
+
+ int GetDamageType() const { return DMG_ENERGYBEAM; }
+
+ void StickyTouch( CBaseEntity *pOther );
+ void BeepThink( void );
+ void ShieldActiveThink( void );
+ void DeathThink( void );
+
+ virtual bool CanTakeEMPDamage() { return true; }
+ virtual bool TakeEMPDamage( float duration );
+
+private:
+ // Check when we're done with EMP
+ void CheckEMPDamageFinish( );
+ void CreateShield( );
+ void ComputeActiveThinkTime( );
+ void BounceSound( );
+
+private:
+ // Make sure all grenades explode
+ Vector m_LastCollision;
+
+ // Time when EMP runs out
+ float m_flEMPDamageEndTime;
+ float m_ShieldLifetime;
+ float m_flDetonateTime;
+
+ // Are we EMPed?
+ bool m_IsEMPed;
+ bool m_IsDeployed;
+
+ // The deployed shield
+ CHandle<CShieldFlat> m_hDeployedShield;
+
+ CSprite *m_pLiveSprite;
+};
+
+//-----------------------------------------------------------------------------
+// Data table
+//-----------------------------------------------------------------------------
+
+// Global Savedata for friction modifier
+BEGIN_DATADESC( CShieldGrenade )
+
+ // Function Pointers
+ DEFINE_FUNCTION( StickyTouch ),
+ DEFINE_FUNCTION( BeepThink ),
+ DEFINE_FUNCTION( ShieldActiveThink ),
+ DEFINE_FUNCTION( DeathThink ),
+
+END_DATADESC()
+
+LINK_ENTITY_TO_CLASS( grenade_shield, CShieldGrenade );
+PRECACHE_REGISTER( grenade_shield );
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CShieldGrenade::CShieldGrenade()
+{
+ UseClientSideAnimation();
+ m_hDeployedShield.Set(0);
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CShieldGrenade::Precache( void )
+{
+ PrecacheModel( "models/weapons/w_grenade.mdl" );
+
+ PrecacheScriptSound( "ShieldGrenade.Bounce" );
+ PrecacheScriptSound( "ShieldGrenade.StickBeep" );
+
+ PrecacheModel( "sprites/redglow1.vmt" );
+
+ BaseClass::Precache();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CShieldGrenade::Spawn( void )
+{
+ BaseClass::Spawn();
+ m_LastCollision.Init( 0, 0, 0 );
+ SetMoveType( MOVETYPE_FLYGRAVITY, MOVECOLLIDE_FLY_BOUNCE );
+ SetSolid( SOLID_BBOX );
+ SetGravity( 1.0 );
+ SetFriction( 0.9 );
+ SetModel( "models/weapons/w_grenade.mdl");
+ UTIL_SetSize(this, Vector( -4, -4, -4), Vector(4, 4, 4));
+ m_IsEMPed = false;
+ m_IsDeployed = false;
+
+ m_flEMPDamageEndTime = 0.0f;
+
+ SetTouch( StickyTouch );
+ SetCollisionGroup( TFCOLLISION_GROUP_GRENADE );
+
+ // Create a green light
+ m_pLiveSprite = CSprite::SpriteCreate( "sprites/redglow1.vmt", GetLocalOrigin() + Vector(0,0,1), false );
+ m_pLiveSprite->SetTransparency( kRenderGlow, 0, 0, 255, 128, kRenderFxNoDissipation );
+ m_pLiveSprite->SetScale( 1 );
+ m_pLiveSprite->SetAttachment( this, 0 );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CShieldGrenade::UpdateOnRemove( void )
+{
+ if ( m_pLiveSprite )
+ {
+ UTIL_Remove( m_pLiveSprite );
+ m_pLiveSprite = NULL;
+ }
+
+ BaseClass::UpdateOnRemove();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CShieldGrenade::SetLifetime( float timer )
+{
+ m_ShieldLifetime = timer;
+}
+
+
+//-----------------------------------------------------------------------------
+// EMP Related methods
+//-----------------------------------------------------------------------------
+bool CShieldGrenade::TakeEMPDamage( float duration )
+{
+ m_flEMPDamageEndTime = gpGlobals->curtime + duration;
+ m_IsEMPed = true;
+ if (m_hDeployedShield)
+ {
+ m_hDeployedShield->SetEMPed(true);
+
+ // Recompute the next think time
+ ComputeActiveThinkTime();
+ }
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: See if EMP impairment time has elapsed
+//-----------------------------------------------------------------------------
+void CShieldGrenade::CheckEMPDamageFinish( void )
+{
+ if ( !m_flEMPDamageEndTime || gpGlobals->curtime < m_flEMPDamageEndTime )
+ return;
+
+ m_flEMPDamageEndTime = 0.0f;
+ m_IsEMPed = false;
+ if (m_hDeployedShield)
+ {
+ m_hDeployedShield->SetEMPed(false);
+
+ // Recompute the next think time
+ ComputeActiveThinkTime();
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Plays a random bounce sound
+//-----------------------------------------------------------------------------
+void CShieldGrenade ::BounceSound( void )
+{
+ EmitSound( "ShieldGrenade.Bounce" );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Make the grenade stick to whatever it touches
+//-----------------------------------------------------------------------------
+void CShieldGrenade::StickyTouch( CBaseEntity *pOther )
+{
+ if (m_IsDeployed)
+ return;
+
+ // The touch can get called multiple times - create an ignore case if we
+ // have already stuck.
+ if ( m_LastCollision == GetLocalOrigin() )
+ return;
+
+ // Only stick to floors...
+ Vector up( 0, 0, 1 );
+ if ( DotProduct( GetTouchTrace().plane.normal, up ) < 0.5f )
+ return;
+
+ // Only stick to BSP models
+ if ( pOther->IsBSPModel() == false )
+ return;
+
+ BounceSound();
+ SetAbsVelocity( vec3_origin );
+ SetMoveType( MOVETYPE_NONE );
+
+ // Beep
+ EmitSound( "ShieldGrenade.StickBeep" );
+
+ // Start ticking...
+ SetThink( BeepThink );
+ m_IsDeployed = true;
+ SetNextThink( gpGlobals->curtime + 0.01f );
+ m_flDetonateTime = gpGlobals->curtime + SHIELD_GRENADE_FUSE_TIME;
+
+ m_LastCollision = GetLocalOrigin();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Play beeping sounds until the charge explode
+//-----------------------------------------------------------------------------
+void CShieldGrenade::CreateShield( void )
+{
+ /*
+ // Set the orientation of the shield based on
+ // the closest teammate's relative position...
+ float mindist = FLT_MAX;
+ Vector dir;
+
+ for ( int i = 1; i <= gpGlobals->maxClients; i++ )
+ {
+ CBaseTFPlayer *pPlayer = ToBaseTFPlayer( UTIL_PlayerByIndex(i) );
+ if ( pPlayer )
+ {
+ if (pPlayer->GetTeamNumber() == GetTeamNumber())
+ {
+ Vector tempdir;
+ VectorSubtract( pPlayer->Center(), Center(), tempdir );
+ float dist = VectorNormalize( tempdir );
+ if (dist < mindist)
+ {
+ mindist = dist;
+ VectorCopy( tempdir, dir );
+ }
+ }
+ }
+ }
+
+ if( mindist == FLT_MAX )
+ {
+ AngleVectors( GetAngles(), &dir );
+ }
+
+ // Never pitch the shield
+ dir.z = 0.0f;
+ VectorNormalize(dir);
+
+ QAngle relAngles;
+ VMatrix parentMatrix;
+ VMatrix worldShieldMatrix;
+ VMatrix relativeMatrix;
+ VMatrix parentInvMatrix;
+
+ // Construct a transform from shield to grenade (parent)
+ MatrixFromAngles( GetAngles(), parentMatrix );
+
+#ifdef _DEBUG
+ bool ok =
+#endif
+ MatrixInverseGeneral( parentMatrix, parentInvMatrix );
+ Assert( ok );
+
+ Vector up( 0, 0, 1 );
+ Vector left;
+ CrossProduct( up, dir, left );
+ MatrixSetIdentity( worldShieldMatrix );
+ worldShieldMatrix.SetUp( up );
+ worldShieldMatrix.SetLeft( left );
+ worldShieldMatrix.SetForward( dir );
+
+ MatrixMultiply( parentInvMatrix, worldShieldMatrix, relativeMatrix );
+ MatrixToAngles( relativeMatrix, relAngles );
+ */
+
+ Vector offset( 0, 0, SHIELD_GRENADE_HEIGHT * 0.5f );
+ m_hDeployedShield = CreateFlatShield( this, SHIELD_GRENADE_WIDTH,
+ SHIELD_GRENADE_HEIGHT, offset, vec3_angle );
+
+ // Notify it about EMP state
+ if (m_IsEMPed)
+ m_hDeployedShield->SetEMPed(true);
+
+ // Play a sound
+// WeaponSound( SPECIAL1 );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Play beeping sounds until the charge explode
+//-----------------------------------------------------------------------------
+void CShieldGrenade::BeepThink( void )
+{
+ if (!IsInWorld())
+ {
+ UTIL_Remove( this );
+ return;
+ }
+
+ if (m_flDetonateTime <= gpGlobals->curtime)
+ {
+ // Here we must project the shield
+ CreateShield();
+ SetThink( ShieldActiveThink );
+ m_flDetonateTime = gpGlobals->curtime + m_ShieldLifetime;
+
+ // Get the EMP state correct
+ CheckEMPDamageFinish();
+ ComputeActiveThinkTime();
+ }
+ else
+ {
+ SetNextThink( gpGlobals->curtime + 1.0f );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Compute next think time while active
+//-----------------------------------------------------------------------------
+void CShieldGrenade::ComputeActiveThinkTime( void )
+{
+ // Next think should be when we detonate, unless we un-EMP before then
+ SetNextThink( gpGlobals->curtime + m_flDetonateTime );
+ if (m_IsEMPed)
+ {
+ Assert( m_flEMPDamageEndTime != 0.0f );
+ if ( m_flEMPDamageEndTime < GetNextThink() )
+ SetNextThink( gpGlobals->curtime + m_flEMPDamageEndTime );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Here's where the grenade "detonates"
+//-----------------------------------------------------------------------------
+void CShieldGrenade::ShieldActiveThink( void )
+{
+ if (m_flDetonateTime > gpGlobals->curtime)
+ {
+ // If it's not time to die, check EMP state
+ CheckEMPDamageFinish();
+ }
+ else
+ {
+ if (m_hDeployedShield)
+ {
+ m_hDeployedShield->Activate( false );
+ }
+ SetNextThink( gpGlobals->curtime + SHIELD_FLAT_SHUTDOWN_TIME + 0.2f );
+ SetThink( DeathThink );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CShieldGrenade::DeathThink( void )
+{
+ // kill the grenade
+ if (m_hDeployedShield)
+ {
+ UTIL_Remove( m_hDeployedShield );
+ m_hDeployedShield.Set(0);
+ }
+ UTIL_Remove( this );
+}
+
+//-----------------------------------------------------------------------------
+// Creates a shield grenade
+//-----------------------------------------------------------------------------
+CBaseEntity *CreateShieldGrenade( const Vector &position, const QAngle &angles, const Vector &velocity, const QAngle &angVelocity, CBaseEntity *pOwner, float timer )
+{
+ CShieldGrenade *pGrenade = (CShieldGrenade *)CBaseEntity::Create( "grenade_shield", position, angles, pOwner );
+ pGrenade->SetLifetime( timer );
+ pGrenade->SetAbsVelocity( velocity );
+
+ if (pOwner)
+ {
+ pGrenade->ChangeTeam( pOwner->GetTeamNumber() );
+ }
+
+ return pGrenade;
+} \ No newline at end of file
diff --git a/game/server/tf2/tf_shieldgrenade.h b/game/server/tf2/tf_shieldgrenade.h
new file mode 100644
index 0000000..c97b35e
--- /dev/null
+++ b/game/server/tf2/tf_shieldgrenade.h
@@ -0,0 +1,19 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef TF_SHIELDGRENADE_H
+#define TF_SHIELDGRENADE_H
+#pragma once
+
+
+class CBaseEntity;
+struct edict_t;
+
+CBaseEntity *CreateShieldGrenade( const Vector& position, const QAngle &angles,
+ const Vector& velocity, const QAngle &angVelocity, CBaseEntity *pOwner, float timer );
+
+#endif // TF_SHIELDGRENADE_H
diff --git a/game/server/tf2/tf_stats.cpp b/game/server/tf2/tf_stats.cpp
new file mode 100644
index 0000000..f2e3f02
--- /dev/null
+++ b/game/server/tf2/tf_stats.cpp
@@ -0,0 +1,624 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: game stat gathering
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#include "cbase.h"
+#include "tf_stats.h"
+#include "tf_shareddefs.h"
+#include "tf_team.h"
+#include "tf_player.h"
+#include "utlbuffer.h"
+#include "filesystem.h"
+#include "igamesystem.h"
+#include "textstatsmgr.h"
+#include "info_act.h"
+
+static ConVar tf_stats( "tf_stats", "0", 0, "Enable stat gathering for TF2." );
+
+//-----------------------------------------------------------------------------
+// Collect stats every N seconds
+//-----------------------------------------------------------------------------
+#define TF_STATS_COLLECTION_TIME 1
+
+#define TF_STAT_FILE "tf_stat_total"
+#define TF_TEAM_STAT_FILE "tf_stat_team"
+#define TF_PLAYER_STAT_FILE "tf_stat_class"
+
+static char s_pStatFile[MAX_PATH];
+static char s_pTeamStatFile[MAX_PATH];
+static char s_pPlayerStatFile[MAX_PATH];
+
+//-----------------------------------------------------------------------------
+// Strings assocaited with the stats
+//-----------------------------------------------------------------------------
+static const char *s_pStatStrings[TF_STAT_COUNT] =
+{
+ "Ferry Control", // TF_STAT_FERRY_CONTROL
+ "Resource Chunks Spawned", // TF_STAT_RESOURCE_CHUNKS_SPAWNED,
+ "Resource Gems Spawned", // TF_STAT_RESOURCE_PROCESSED_CHUNKS_SPAWNED,
+ "Resource Chunks Retired", // TF_STAT_RESOURCE_CHUNKS_RETIRED,
+};
+
+// These are the strings for the team stats above TFCLASS_CLASS_COUNT.
+static const char *s_pNonClassTeamStatStrings[TF_TEAM_STAT_COUNT-TFCLASS_CLASS_COUNT] =
+{
+ "Player Count", // TF_TEAM_STAT_PLAYER_COUNT,
+ "Resources Collected", // TF_TEAM_STAT_RESOURCES_COLLECTED,
+ "Resources Harvested", // TF_TEAM_STAT_RESOURCES_HARVESTED,
+ "Chunks Dropped", // TF_TEAM_STAT_RESOURCE_CHUNKS_DROPPED,
+ "Chunks Collected", // TF_TEAM_STAT_RESOURCE_CHUNKS_COLLECTED,
+ "Kill Count", // TF_TEAM_STAT_KILL_COUNT,
+ "Destroyed Objects", // TF_TEAM_STAT_DESTROYED_OBJECT_COUNT,
+ "Ferry Control Time", // TF_TEAM_STAT_FERRY_CONTROL_TIME,
+};
+
+// These are initialized in the first call to GetTeamStatString().
+static const char *s_pTeamStatStrings[TF_TEAM_STAT_COUNT];
+static bool s_bTeamStatStringsInitted = false;
+
+static const char *s_pPlayerStatStrings[TF_PLAYER_STAT_COUNT] =
+{
+ "Player Count", // TF_PLAYER_STAT_PLAYER_COUNT
+ "Player Seconds", // TF_PLAYER_STAT_PLAYER_SECONDS
+ "Seconds At Least One Existed", // TF_PLAYER_STAT_EXISTING_SECONDS
+
+ "Resources Acquired", // TF_PLAYER_STAT_RESOURCES_ACQUIRED
+ "Resources Acquired From Chunks", // TF_PLAYER_STAT_RESOURCES_ACQUIRED_FROM_CHUNKS
+ "Resources Carried", // TF_PLAYER_STAT_RESOURCES_CARRIED,
+ "Resources Spent", // TF_PLAYER_STAT_RESOURCES_SPENT,
+ "Object Value", // TF_PLAYER_STAT_CURRENT_OBJECT_VALUE
+ "Objects Owned", // TF_PLAYER_STAT_OBJECT_COUNT,
+
+ "Kill Count", // TF_PLAYER_STAT_KILL_COUNT,
+ "Objects Destroyed", // TF_PLAYER_STAT_DESTROYED_OBJECT_COUNT,
+ "Health Given", // TF_PLAYER_STAT_HEALTH_GIVEN,
+
+ "Animation Idle Time", // TF_PLAYER_STAT_ANIMATION_IDLE,
+ "Animation Walk Time", // TF_PLAYER_STAT_ANIMATION_WALKING,
+ "Animation Run Time", // TF_PLAYER_STAT_ANIMATION_RUNNING,
+ "Animation Crouch Time",// TF_PLAYER_STAT_ANIMATION_CROUCHING,
+ "Animation Jump Time", // TF_PLAYER_STAT_ANIMATION_JUMPING,
+ "Animation Other Time", // TF_PLAYER_STAT_ANIMATION_OTHER,
+};
+
+
+static const char *GetStatString( int stat )
+{
+ if (stat < TF_STAT_FIRST_OBJECT_BUILT)
+ return s_pStatStrings[stat];
+
+ static char s_TempBuf[256];
+ Q_snprintf( s_TempBuf, sizeof( s_TempBuf ), "%s Count Built", GetObjectInfo( stat - TF_STAT_FIRST_OBJECT_BUILT )->m_pClassName );
+ return s_TempBuf;
+}
+
+static const char *GetTeamStatString( int stat )
+{
+ if ( !s_bTeamStatStringsInitted )
+ {
+ s_bTeamStatStringsInitted = true;
+
+ // Go through and fill in the strings.
+ for ( int i=0; i < TFCLASS_CLASS_COUNT; i++ )
+ s_pTeamStatStrings[i] = GetTFClassInfo( i )->m_pClassName;
+
+ for ( i=TFCLASS_CLASS_COUNT; i < TF_TEAM_STAT_COUNT; i++ )
+ s_pTeamStatStrings[i] = s_pNonClassTeamStatStrings[i - TFCLASS_CLASS_COUNT];
+ }
+
+ return s_pTeamStatStrings[stat];
+}
+
+static const char *GetPlayerStatString( int stat )
+{
+ return s_pPlayerStatStrings[stat];
+}
+
+
+//-----------------------------------------------------------------------------
+// Implementation of the TF stats class
+//-----------------------------------------------------------------------------
+class CTFStats : public CAutoGameSystem, public ITFStats
+{
+public:
+ CTFStats();
+
+ // Inherited from IAutoServerSystem
+ virtual void LevelInitPreEntity();
+ virtual void FrameUpdatePostEntityThink( );
+
+ // Clear out the stats + their history
+ void ResetStats();
+
+ void IncrementStat( TFStatId_t stat, int nIncrement );
+ void SetStat( TFStatId_t stat, int nAmount );
+
+ void IncrementTeamStat( int nTeam, TFTeamStatId_t stat, int nIncrement );
+ void SetTeamStat( int nTeam, TFTeamStatId_t stat, int nAmount );
+
+ void IncrementPlayerStat( CBaseEntity *pPlayer, TFPlayerStatId_t stat, int nIncrement );
+ void ClearPlayerStat( int nTeam, TFPlayerStatId_t stat );
+
+ // We need to be ticked once a frame
+ void FrameUpdate( );
+
+private:
+ struct Stat_t
+ {
+ int m_nCount;
+ };
+
+ typedef const char * (*StatNameFunc_t)( int stat );
+
+ // Collects frame-based stats
+ void CollectFrameStats( );
+ void CollectStats( );
+
+ int GetStat( TFStatId_t stat ) const { return m_Stats[stat].m_nCount; }
+ int GetTeamStat( int nTeam, TFTeamStatId_t stat ) const { return m_TeamStats[nTeam][stat].m_nCount; }
+ void WriteHeader( CUtlBuffer &buf, const char *pPrefix, int nCount, StatNameFunc_t func, bool bTerminate = true );
+ void WriteStatLine( CUtlBuffer &buf, int nCount, Stat_t *pStats, bool bTerminate = true );
+ void WriteAvgStatLine( CUtlBuffer &buf );
+ void WriteStats();
+ void WriteTeamStats();
+ void WritePlayerStats( );
+ void EraseFile( const char *pFileName );
+ void AppendToFile( const char *pFileName, CUtlBuffer &buf );
+ void ComputeFileNames();
+ void ClearStats();
+
+ // Compute class-based stats from the player stats
+ int ComputeClassStats( int nTeam, TFClass classType, Stat_t *pStats );
+
+ bool m_bWrittenHeader;
+ int m_nLastWriteTime;
+ Stat_t m_Stats[TF_STAT_COUNT];
+ Stat_t m_TeamStats[MAX_TF_TEAMS][TF_TEAM_STAT_COUNT];
+ Stat_t m_ClassStats[MAX_TF_TEAMS][TFCLASS_CLASS_COUNT][TF_PLAYER_STAT_COUNT];
+};
+
+
+//-----------------------------------------------------------------------------
+// Accessor method
+//-----------------------------------------------------------------------------
+static CTFStats s_TFStats;
+ITFStats *TFStats()
+{
+ return &s_TFStats;
+}
+
+
+//-----------------------------------------------------------------------------
+// Constructor, destructor
+//-----------------------------------------------------------------------------
+CTFStats::CTFStats()
+{
+ ResetStats();
+}
+
+//-----------------------------------------------------------------------------
+// Clear out the stats + their history
+//-----------------------------------------------------------------------------
+void CTFStats::ClearStats()
+{
+ int i;
+ for (i = 0; i < TF_STAT_COUNT; ++i)
+ {
+ SetStat( (TFStatId_t)i, 0 );
+ }
+
+ for (i = 0; i < MAX_TF_TEAMS; ++i)
+ {
+ for (int j = 0; j < TF_TEAM_STAT_COUNT; ++j)
+ {
+ SetTeamStat(i, (TFTeamStatId_t)j, 0);
+ }
+ }
+
+ for (int nTeam = 0; nTeam < MAX_TF_TEAMS; ++nTeam)
+ {
+ for (i = 0; i < TFCLASS_CLASS_COUNT; ++i)
+ {
+ for (int j = 0; j < TF_PLAYER_STAT_COUNT; ++j)
+ {
+ m_ClassStats[nTeam][i][(TFPlayerStatId_t)j].m_nCount = 0;
+ }
+ }
+ }
+
+}
+
+//-----------------------------------------------------------------------------
+// Clear out the stats + their history
+//-----------------------------------------------------------------------------
+void CTFStats::ResetStats()
+{
+ ClearStats();
+ m_bWrittenHeader = false;
+ m_nLastWriteTime = -9999;
+}
+
+
+
+//-----------------------------------------------------------------------------
+// Inherited from IAutoServerSystem
+//-----------------------------------------------------------------------------
+void CTFStats::LevelInitPreEntity()
+{
+ ResetStats();
+}
+
+
+//-----------------------------------------------------------------------------
+// Update stats...
+//-----------------------------------------------------------------------------
+void CTFStats::IncrementStat( TFStatId_t stat, int nIncrement )
+{
+ m_Stats[stat].m_nCount += nIncrement;
+}
+
+void CTFStats::SetStat( TFStatId_t stat, int nAmount )
+{
+ m_Stats[stat].m_nCount = nAmount;
+}
+
+void CTFStats::IncrementTeamStat( int nTeam, TFTeamStatId_t stat, int nIncrement )
+{
+ m_TeamStats[nTeam][stat].m_nCount += nIncrement;
+}
+
+void CTFStats::SetTeamStat( int nTeam, TFTeamStatId_t stat, int nAmount )
+{
+ m_TeamStats[nTeam][stat].m_nCount = nAmount;
+}
+
+void CTFStats::IncrementPlayerStat( CBaseEntity *pEntity, TFPlayerStatId_t stat, int nIncrement )
+{
+ if (!pEntity->IsPlayer())
+ return;
+
+ CBaseTFPlayer *pTFPlayer = static_cast<CBaseTFPlayer*>(pEntity);
+ int nTeam = pTFPlayer->GetTeamNumber();
+ CPlayerClass *pPlayerClass = pTFPlayer->GetPlayerClass();
+ int nClass = pPlayerClass ? pPlayerClass->GetTFClass() : TFCLASS_UNDECIDED;
+
+ m_ClassStats[nTeam][nClass][stat].m_nCount += nIncrement;
+}
+
+void CTFStats::ClearPlayerStat( int nTeam, TFPlayerStatId_t stat )
+{
+ for (int i = 0; i < TFCLASS_CLASS_COUNT; ++i)
+ {
+ m_ClassStats[nTeam][i][stat].m_nCount = 0;
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// We need to be ticked once a frame
+//-----------------------------------------------------------------------------
+void CTFStats::CollectFrameStats( )
+{
+ // This collects a bunch of polled stats so we don't have to pollute
+ // a bunch of code
+ for (int i = 0; i < MAX_TF_TEAMS; ++i)
+ {
+ CTFTeam *pTeam = GetGlobalTFTeam(i);
+ for (int j = pTeam->GetNumPlayers(); --j >= 0; )
+ {
+ CBaseTFPlayer *pPlayer = static_cast<CBaseTFPlayer*>(pTeam->GetPlayer(j));
+ if (!pPlayer)
+ continue;
+
+ int nTimeMS = 1000 * gpGlobals->frametime;
+
+ switch( pPlayer->GetActivity() )
+ {
+ case ACT_IDLE:
+ IncrementPlayerStat( pPlayer, TF_PLAYER_STAT_ANIMATION_IDLE, nTimeMS );
+ break;
+
+ case ACT_WALK:
+ IncrementPlayerStat( pPlayer, TF_PLAYER_STAT_ANIMATION_WALKING, nTimeMS );
+ break;
+
+ case ACT_RUN:
+ IncrementPlayerStat( pPlayer, TF_PLAYER_STAT_ANIMATION_RUNNING, nTimeMS );
+ break;
+
+ case ACT_JUMP:
+ IncrementPlayerStat( pPlayer, TF_PLAYER_STAT_ANIMATION_JUMPING, nTimeMS );
+ break;
+
+ case ACT_CROUCHIDLE:
+ IncrementPlayerStat( pPlayer, TF_PLAYER_STAT_ANIMATION_CROUCHING, nTimeMS );
+ break;
+
+ default:
+ IncrementPlayerStat( pPlayer, TF_PLAYER_STAT_ANIMATION_OTHER, nTimeMS );
+ break;
+ }
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// We need to be ticked once a frame
+//-----------------------------------------------------------------------------
+void CTFStats::CollectStats( )
+{
+ // This collects a bunch of polled stats so we don't have to pollute
+ // a bunch of code
+ bool bAtLeastOnePlayer = false;
+ for (int i = 0; i < MAX_TF_TEAMS; ++i)
+ {
+ CTFTeam *pTeam = GetGlobalTFTeam(i);
+
+ for ( int iClass=0; iClass < TFCLASS_CLASS_COUNT; iClass++ )
+ {
+ SetTeamStat( i, (TFTeamStatId_t)iClass, pTeam->GetNumOfClass( (TFClass)iClass ) );
+ }
+
+ SetTeamStat( i, TF_TEAM_STAT_PLAYER_COUNT, pTeam->GetNumPlayers() );
+
+ if ( GetStat( TF_STAT_FERRY_CONTROL ) == i )
+ {
+ IncrementTeamStat( i, TF_TEAM_STAT_FERRY_CONTROL_TIME, TF_STATS_COLLECTION_TIME );
+ }
+
+ ClearPlayerStat( i, TF_PLAYER_STAT_OBJECT_COUNT );
+ ClearPlayerStat( i, TF_PLAYER_STAT_RESOURCES_CARRIED );
+ ClearPlayerStat( i, TF_PLAYER_STAT_CURRENT_OBJECT_VALUE );
+ ClearPlayerStat( i, TF_PLAYER_STAT_PLAYER_COUNT );
+
+ bool bClassEncountered[TFCLASS_CLASS_COUNT];
+ memset( bClassEncountered, 0, TFCLASS_CLASS_COUNT * sizeof(bool) );
+ for (int j = pTeam->GetNumPlayers(); --j >= 0; )
+ {
+ bAtLeastOnePlayer = true;
+
+ CBaseTFPlayer *pPlayer = static_cast<CBaseTFPlayer*>(pTeam->GetPlayer(j));
+ IncrementPlayerStat( pPlayer, TF_PLAYER_STAT_OBJECT_COUNT, pPlayer->GetObjectCount() );
+ IncrementPlayerStat( pPlayer, TF_PLAYER_STAT_RESOURCES_CARRIED, pPlayer->GetBankResources() );
+ IncrementPlayerStat( pPlayer, TF_PLAYER_STAT_PLAYER_COUNT, 1 );
+ IncrementPlayerStat( pPlayer, TF_PLAYER_STAT_PLAYER_SECONDS, 1 );
+
+ CPlayerClass *pPlayerClass = pPlayer->GetPlayerClass();
+ int nClass = pPlayerClass ? pPlayerClass->GetTFClass() : TFCLASS_UNDECIDED;
+
+ if (!bClassEncountered[nClass])
+ {
+ IncrementPlayerStat( pPlayer, TF_PLAYER_STAT_EXISTING_SECONDS, 1 );
+ bClassEncountered[nClass] = true;
+ }
+
+ // Count up the cost of all current objects..
+ int nCost = 0;
+ int pObjectCount[OBJ_LAST];
+ memset( pObjectCount, 0, OBJ_LAST * sizeof(int) );
+ for (int k = pPlayer->GetObjectCount(); --k >= 0; )
+ {
+ CBaseObject *pObject = pPlayer->GetObject(k);
+ if (pObject)
+ {
+ int nType = pObject->GetType();
+ nCost += CalculateObjectCost( nType, pObjectCount[nType], pPlayer->GetTeamNumber(), false );
+ ++pObjectCount[nType];
+ }
+ }
+ IncrementPlayerStat( pPlayer, TF_PLAYER_STAT_CURRENT_OBJECT_VALUE, nCost );
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Output : char const
+//-----------------------------------------------------------------------------
+void CTFStats::ComputeFileNames()
+{
+ Q_snprintf( s_pStatFile, sizeof( s_pStatFile ), "%s.txt", TF_STAT_FILE );
+ Q_snprintf( s_pTeamStatFile, sizeof( s_pTeamStatFile ), "%s.txt", TF_TEAM_STAT_FILE );
+ Q_snprintf( s_pPlayerStatFile, sizeof( s_pPlayerStatFile ), "%s.txt", TF_PLAYER_STAT_FILE );
+}
+
+
+//-----------------------------------------------------------------------------
+// File access.
+//-----------------------------------------------------------------------------
+void CTFStats::EraseFile( const char *pFileName )
+{
+ filesystem->RemoveFile( pFileName, "GAME" );
+}
+
+void CTFStats::AppendToFile( const char *pFileName, CUtlBuffer &buf )
+{
+ FileHandle_t fh = filesystem->Open( pFileName, "a", "LOGDIR" );
+ if (fh != FILESYSTEM_INVALID_HANDLE)
+ {
+ filesystem->Write( buf.Base(), buf.TellPut(), fh );
+ filesystem->Close( fh );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Write out a header.
+//-----------------------------------------------------------------------------
+void CTFStats::WriteHeader( CUtlBuffer &buf, const char *pPrefix, int nCount, StatNameFunc_t func, bool bTerminate )
+{
+ for (int i = 0; i < nCount-1; ++i)
+ {
+ buf.Printf("%s %s\t", pPrefix, func(i) );
+ }
+
+ buf.Printf( bTerminate ? "%s %s\n" : "%s %s\t", pPrefix, func(nCount-1) );
+}
+
+void CTFStats::WriteStatLine( CUtlBuffer &buf, int nCount, Stat_t *pStats, bool bTerminate )
+{
+ for (int i = 0; i < nCount-1; ++i)
+ {
+ buf.Printf("%d\t", pStats[i].m_nCount );
+ }
+
+ buf.Printf( bTerminate ? "%d\n" : "%d\t", pStats[nCount-1].m_nCount );
+}
+
+void CTFStats::WriteAvgStatLine( CUtlBuffer &buf )
+{
+ int nTeam, i, j;
+ for ( nTeam = 0; nTeam < MAX_TF_TEAMS; ++nTeam )
+ {
+ for ( i = 0; i < TF_PLAYER_STAT_COUNT; ++i )
+ {
+ for ( j = 1; j < TFCLASS_CLASS_COUNT; ++j )
+ {
+ if (!GetTFClassInfo(j)->m_pCurrentlyActive)
+ continue;
+
+ buf.Printf("%d\t", m_ClassStats[nTeam][j][i].m_nCount);
+ }
+ }
+ }
+
+ // Blat out that last tab and replace with a \n
+ buf.SeekPut( CUtlBuffer::SEEK_CURRENT, -1 );
+ buf.Printf("\n");
+}
+
+//-----------------------------------------------------------------------------
+// Write out total stats...
+//-----------------------------------------------------------------------------
+void CTFStats::WriteStats()
+{
+ CUtlBuffer buf( 0, 1024, CUtlBuffer::TEXT_BUFFER );
+
+ if (!m_bWrittenHeader)
+ {
+ EraseFile( s_pStatFile );
+ WriteHeader( buf, "", TF_STAT_COUNT, GetStatString );
+ }
+
+ WriteStatLine( buf, TF_STAT_COUNT, m_Stats );
+ AppendToFile( s_pStatFile, buf );
+}
+
+
+//-----------------------------------------------------------------------------
+// Write out total stats...
+//-----------------------------------------------------------------------------
+void CTFStats::WriteTeamStats()
+{
+ CUtlBuffer buf( 0, 1024, CUtlBuffer::TEXT_BUFFER );
+
+ int i,j;
+ if (!m_bWrittenHeader)
+ {
+ EraseFile( s_pTeamStatFile );
+ for ( i = 0; i < TF_TEAM_STAT_COUNT; ++i )
+ {
+ for ( j = 0; j < MAX_TF_TEAMS; ++j )
+ {
+ buf.Printf("Team %d %s\t", j, GetTeamStatString(i) );
+ }
+ }
+ // Blat out that last tab and replace with a \n
+ buf.SeekPut( CUtlBuffer::SEEK_CURRENT, -1 );
+ buf.Printf("\n");
+ }
+
+ for ( i = 0; i < TF_TEAM_STAT_COUNT; ++i )
+ {
+ for ( j = 0; j < MAX_TF_TEAMS; ++j )
+ {
+ buf.Printf("%d\t", m_TeamStats[j][i].m_nCount );
+ }
+ }
+
+ // Blat out that last tab and replace with a \n
+ buf.SeekPut( CUtlBuffer::SEEK_CURRENT, -1 );
+ buf.Printf("\n");
+
+ AppendToFile( s_pTeamStatFile, buf );
+}
+
+
+//-----------------------------------------------------------------------------
+// Write out total stats...
+//-----------------------------------------------------------------------------
+void CTFStats::WritePlayerStats( )
+{
+ CUtlBuffer buf( 0, 1024, CUtlBuffer::TEXT_BUFFER );
+
+ int i, j, nTeam;
+ if (!m_bWrittenHeader)
+ {
+ EraseFile( s_pPlayerStatFile );
+ for ( nTeam = 0; nTeam < MAX_TF_TEAMS; ++nTeam )
+ {
+ for ( i = 0; i < TF_PLAYER_STAT_COUNT; ++i )
+ {
+ for ( j = 1; j < TFCLASS_CLASS_COUNT; ++j )
+ {
+ if (!GetTFClassInfo(j)->m_pCurrentlyActive)
+ continue;
+
+ buf.Printf("Team %d %s %s\t", nTeam, GetTFClassInfo( j )->m_pClassName, GetPlayerStatString(i) );
+ }
+ }
+ }
+
+ // Blat out that last tab and replace with a \n
+ buf.SeekPut( CUtlBuffer::SEEK_CURRENT, -1 );
+ buf.Printf("\n");
+ }
+
+ WriteAvgStatLine( buf );
+
+ AppendToFile( s_pPlayerStatFile, buf );
+}
+
+
+//-----------------------------------------------------------------------------
+// We need to be ticked once a frame
+//-----------------------------------------------------------------------------
+void CTFStats::FrameUpdatePostEntityThink( )
+{
+ if (!tf_stats.GetBool())
+ return;
+
+ // Don't stat gather during waiting acts
+ if ( CurrentActIsAWaitingAct() )
+ return;
+
+ CollectFrameStats();
+
+ // NOTE: We could keep track of the history here if we wanted for later
+ // display when the map ends
+
+ // Record the history every so often
+ if (gpGlobals->curtime - m_nLastWriteTime < TF_STATS_COLLECTION_TIME)
+ return;
+
+ if (!m_bWrittenHeader)
+ {
+ ComputeFileNames();
+ }
+
+ CollectStats();
+ WriteStats();
+ WriteTeamStats();
+ WritePlayerStats();
+ ClearStats();
+
+ // By this point, we've written the header for each file
+ m_bWrittenHeader = true;
+
+ m_nLastWriteTime = gpGlobals->curtime;
+}
diff --git a/game/server/tf2/tf_stats.h b/game/server/tf2/tf_stats.h
new file mode 100644
index 0000000..c5e68e5
--- /dev/null
+++ b/game/server/tf2/tf_stats.h
@@ -0,0 +1,106 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: game stat gathering
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef TF_STATS_H
+#define TF_STATS_H
+
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include "tf_shareddefs.h"
+
+// NOTE: If you change these enums, change the string lists in tf_stats.cpp!
+enum TFStatId_t
+{
+ TF_STAT_FERRY_CONTROL = 0,
+ TF_STAT_RESOURCE_CHUNKS_SPAWNED,
+ TF_STAT_RESOURCE_PROCESSED_CHUNKS_SPAWNED,
+ TF_STAT_RESOURCE_CHUNKS_RETIRED,
+
+ // NOTE: These should go last
+ TF_STAT_FIRST_OBJECT_BUILT,
+ TF_STAT_LAST_OBJECT_BUILT = TF_STAT_FIRST_OBJECT_BUILT + OBJ_LAST - 1,
+
+ TF_STAT_COUNT,
+};
+
+enum TFTeamStatId_t
+{
+ // First TFCLASS_CLASS_COUNT entries are the # of players of each class.
+
+ TF_TEAM_STAT_PLAYER_COUNT = TFCLASS_CLASS_COUNT,
+
+ TF_TEAM_STAT_RESOURCES_COLLECTED,
+ TF_TEAM_STAT_RESOURCES_HARVESTED,
+ TF_TEAM_STAT_RESOURCE_CHUNKS_DROPPED,
+ TF_TEAM_STAT_RESOURCE_CHUNKS_COLLECTED,
+
+ TF_TEAM_STAT_KILL_COUNT,
+ TF_TEAM_STAT_DESTROYED_OBJECT_COUNT,
+
+ TF_TEAM_STAT_FERRY_CONTROL_TIME,
+
+ TF_TEAM_STAT_COUNT,
+};
+
+enum TFPlayerStatId_t
+{
+ // Player count
+ TF_PLAYER_STAT_PLAYER_COUNT = 0,
+ TF_PLAYER_STAT_PLAYER_SECONDS,
+ TF_PLAYER_STAT_EXISTING_SECONDS,
+ TF_PLAYER_STAT_FIRST_AVERAGE_STAT,
+
+ // Economy-based stats
+ TF_PLAYER_STAT_RESOURCES_ACQUIRED = TF_PLAYER_STAT_FIRST_AVERAGE_STAT,
+ TF_PLAYER_STAT_RESOURCES_ACQUIRED_FROM_CHUNKS,
+ TF_PLAYER_STAT_RESOURCES_CARRIED,
+ TF_PLAYER_STAT_RESOURCES_SPENT,
+ TF_PLAYER_STAT_CURRENT_OBJECT_VALUE,
+ TF_PLAYER_STAT_OBJECT_COUNT,
+
+ // Combat based stats
+ TF_PLAYER_STAT_KILL_COUNT,
+ TF_PLAYER_STAT_DESTROYED_OBJECT_COUNT,
+ TF_PLAYER_STAT_HEALTH_GIVEN,
+
+ // Animation-based stats
+ TF_PLAYER_STAT_ANIMATION_IDLE,
+ TF_PLAYER_STAT_ANIMATION_WALKING,
+ TF_PLAYER_STAT_ANIMATION_RUNNING,
+ TF_PLAYER_STAT_ANIMATION_CROUCHING,
+ TF_PLAYER_STAT_ANIMATION_JUMPING,
+ TF_PLAYER_STAT_ANIMATION_OTHER,
+
+ TF_PLAYER_STAT_COUNT,
+};
+
+
+class ITFStats
+{
+public:
+ // Clear out the stats + their history
+ virtual void ResetStats() = 0;
+
+ virtual void IncrementStat( TFStatId_t stat, int nIncrement ) = 0;
+ virtual void SetStat( TFStatId_t stat, int nAmount ) = 0;
+
+ virtual void IncrementTeamStat( int nTeam, TFTeamStatId_t stat, int nIncrement ) = 0;
+ virtual void SetTeamStat( int nTeam, TFTeamStatId_t stat, int nAmount ) = 0;
+
+ virtual void IncrementPlayerStat( CBaseEntity *pPlayer, TFPlayerStatId_t stat, int nIncrement ) = 0;
+};
+
+
+//-----------------------------------------------------------------------------
+// Accessor method
+//-----------------------------------------------------------------------------
+ITFStats *TFStats();
+
+
+#endif // TF_STATS_H
diff --git a/game/server/tf2/tf_stressentities.cpp b/game/server/tf2/tf_stressentities.cpp
new file mode 100644
index 0000000..27e5116
--- /dev/null
+++ b/game/server/tf2/tf_stressentities.cpp
@@ -0,0 +1,107 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#include "cbase.h"
+#include "test_stressentities.h"
+#include "tf_flare.h"
+#include "tf_shareddefs.h"
+#include "plasmaprojectile.h"
+
+
+// ------------------------------------------------------------------------------------ //
+// Functions to create random entities.
+// ------------------------------------------------------------------------------------ //
+
+CBaseEntity* CreateSignalFlare()
+{
+ CBaseEntity *pLocalPlayer = UTIL_GetLocalPlayer();
+ if ( pLocalPlayer )
+ {
+ return CSignalFlare::Create( GetRandomSpot(), QAngle( 0, 0, 0 ), pLocalPlayer, 3 );
+ }
+ else
+ {
+ return NULL;
+ }
+}
+
+
+CBaseEntity* CreateResourceChunk()
+{
+ CBaseEntity *pChunk = MoveToRandomSpot( CreateEntityByName( "resource_chunk" ) );
+ if ( pChunk )
+ {
+ pChunk->Spawn();
+ }
+
+ return pChunk;
+}
+
+
+CBaseEntity* CreateResourceBox()
+{
+ CBaseEntity *pRet = MoveToRandomSpot( CreateEntityByName( "obj_box" ) );
+ if ( pRet )
+ {
+ pRet->Spawn();
+ }
+
+ return pRet;
+}
+
+CBaseEntity* CreatePlasmaProjectile()
+{
+ CBaseEntity *pLocalPlayer = UTIL_GetLocalPlayer();
+ if ( pLocalPlayer )
+ {
+ Vector vForward;
+ vForward.Random(-1,1);
+ VectorNormalize( vForward );
+
+ CBasePlasmaProjectile *pRet = CBasePlasmaProjectile::Create(
+ Vector(0,0,0),
+ vForward,
+ 0,
+ pLocalPlayer );
+
+ if ( pRet )
+ MoveToRandomSpot( pRet );
+
+ return pRet;
+ }
+
+ return NULL;
+}
+
+CBaseEntity* CreatePlasmaShot()
+{
+ CBaseEntity *pEnt = MoveToRandomSpot( CreateEntityByName( "base_plasmaprojectile" ) );
+ if ( pEnt )
+ {
+ CBasePlasmaProjectile *pRet = dynamic_cast< CBasePlasmaProjectile* >( pEnt );
+ if ( pRet )
+ {
+ return pRet;
+ }
+ else
+ {
+ UTIL_Remove( pEnt );
+ }
+ }
+
+ return NULL;
+}
+
+
+REGISTER_STRESS_ENTITY( CreateResourceChunk );
+REGISTER_STRESS_ENTITY( CreateResourceBox );
+REGISTER_STRESS_ENTITY( CreatePlasmaProjectile );
+REGISTER_STRESS_ENTITY( CreatePlasmaShot );
+REGISTER_STRESS_ENTITY( CreateSignalFlare );
+
+
+
diff --git a/game/server/tf2/tf_team.cpp b/game/server/tf2/tf_team.cpp
new file mode 100644
index 0000000..0bec380
--- /dev/null
+++ b/game/server/tf2/tf_team.cpp
@@ -0,0 +1,1434 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Team management class. Contains all the details for a specific team
+//
+// $NoKeywords: $
+//=============================================================================//
+#include "cbase.h"
+#include "team.h"
+#include "tf_team.h"
+#include "tf_func_resource.h"
+#include "tf_player.h"
+#include "techtree.h"
+#include "tf_obj.h"
+#include "tf_obj_resupply.h"
+#include "orders.h"
+#include "entitylist.h"
+#include "team_spawnpoint.h"
+#include "team_messages.h"
+#include "tf_obj_powerpack.h"
+#include "tf_gamerules.h"
+#include "engine/IEngineSound.h"
+#include "tier1/strtools.h"
+#include "tf_stats.h"
+#include "tf_obj_buff_station.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+#define OBJECT_COVERED_DIST 1000
+#define RESOURCE_GIVE_TIME 30
+#define RESOURCE_GIVE_AMOUNT 150
+#define RESOURCE_DONATION_AMT_PER_PLAYER 10
+
+bool IsEntityVisibleToTactical( int iLocalTeamNumber, int iLocalTeamPlayers,
+ int iLocalTeamObjects, int iEntIndex, const char *pEntName, int pEntTeamNumber, const Vector &pEntOrigin );
+extern ConVar tf_destroyobjects;
+
+//-----------------------------------------------------------------------------
+// Purpose: SendProxy that converts the UtlVector list of radar scanners to entindexes, where it's reassembled on the client
+//-----------------------------------------------------------------------------
+void SendProxy_ObjectList( const SendProp *pProp, const void *pStruct, const void *pData, DVariant *pOut, int iElement, int objectID )
+{
+ CTFTeam *pTeam = (CTFTeam*)pData;
+
+ // If this fails, then SendProxyArrayLength_TeamObjects didn't work.
+ Assert( iElement < pTeam->GetNumObjects() );
+
+ CBaseObject *pObject = pTeam->GetObject(iElement);
+ EHANDLE hObject;
+ hObject = pObject;
+
+ SendProxy_EHandleToInt( pProp, pStruct, &hObject, pOut, iElement, objectID );
+}
+
+
+int SendProxyArrayLength_TeamObjects( const void *pStruct, int objectID )
+{
+ CTFTeam *pTeam = (CTFTeam*)pStruct;
+ int iObjects = pTeam->GetNumObjects();
+ Assert( iObjects < MAX_OBJECTS_PER_TEAM );
+ return iObjects;
+}
+
+
+// Datatable
+IMPLEMENT_SERVERCLASS_ST(CTFTeam, DT_TFTeam)
+ SendPropFloat( SENDINFO(m_fResources), 16, SPROP_NOSCALE ),
+ SendPropFloat( SENDINFO(m_fPotentialResources), 16, SPROP_NOSCALE ),
+ SendPropInt( SENDINFO(m_bHaveZone), 1, SPROP_UNSIGNED ),
+
+ SendPropArray2(
+ SendProxyArrayLength_TeamObjects,
+ SendPropInt("object_array_element", 0, SIZEOF_IGNORE, NUM_NETWORKED_EHANDLE_BITS, SPROP_UNSIGNED, SendProxy_ObjectList),
+ MAX_OBJECTS_PER_TEAM,
+ 0,
+ "object_array"
+ )
+END_SEND_TABLE()
+
+LINK_ENTITY_TO_CLASS( tf_team_manager, CTFTeam );
+
+//-----------------------------------------------------------------------------
+// Purpose: Get a pointer to the specified TF team manager
+//-----------------------------------------------------------------------------
+CTFTeam *GetGlobalTFTeam( int iIndex )
+{
+ return (CTFTeam*)GetGlobalTeam( iIndex );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Needed because this is an entity, but should never be used
+//-----------------------------------------------------------------------------
+void CTFTeam::Init( const char *pName, int iNumber )
+{
+ BaseClass::Init( pName, iNumber );
+
+ InitializeTeamResources();
+ InitializeTechTree();
+ InitializeOrders();
+ ClearMessages();
+
+ m_flNextResourceTime = 0;
+
+ // Only detect changes every half-second.
+ NetworkProp()->SetUpdateInterval( 0.75f );
+
+ m_flTotalResourcesSoFar = m_iLastUpdateSentAt = 0;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CTFTeam::~CTFTeam( void )
+{
+ m_aResourcesBeingCollected.Purge();
+ m_aResupplyBeacons.Purge();
+ m_aObjects.Purge();
+ m_aOrders.Purge();
+
+ delete m_pTechnologyTree;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFTeam::Precache( void )
+{
+ // Precache all the technologies in the techtree
+ for ( int i = 0; i < m_pTechnologyTree->GetNumberTechnologies(); i++ )
+ {
+ PrecacheTechnology( m_pTechnologyTree->GetTechnology(i) );
+ }
+
+ PrecacheScriptSound( "TFTeam.CapturedZone" );
+ PrecacheScriptSound( "TFTeam.LostZone" );
+ PrecacheScriptSound( "TFTeam.ObtainStolenTechnology" );
+ PrecacheScriptSound( "TFTeam.BoughtPreferredTechnology" );
+ PrecacheScriptSound( "TFTeam.AddOrder" );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Precache a technology's files
+//-----------------------------------------------------------------------------
+void CTFTeam::PrecacheTechnology( CBaseTechnology *pTech )
+{
+ // Precache sounds for every class result
+ for (int i = 0; i < TFCLASS_CLASS_COUNT; i++ )
+ {
+ if ( pTech->GetSoundFile(i) && (pTech->GetSoundFile(i)[0] != 0) )
+ {
+ PrecacheScriptSound( pTech->GetSoundFile(i) );
+ pTech->SetClassResultSound( i, 0 );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Called every frame
+//-----------------------------------------------------------------------------
+void CTFTeam::Think( void )
+{
+ UpdateOrders();
+ UpdateMessages();
+
+ // FIXME: Try this out?
+ /*
+ // Give resources to the team at regular intervals
+ if (gpGlobals->curtime >= m_flNextResourceTime)
+ {
+ AddTeamResources( RESOURCE_GIVE_AMOUNT );
+ m_flNextResourceTime = gpGlobals->curtime + RESOURCE_GIVE_TIME;
+ }
+ */
+
+ UpdateTechnologies();
+
+ /* FIXME: Re-enable once we figure out what the correct orders should be
+ // Create new personal orders
+ if ( m_flPersonalOrderUpdateTime < gpGlobals->curtime )
+ {
+ CreatePersonalOrders();
+ m_flPersonalOrderUpdateTime = gpGlobals->curtime + PERSONAL_ORDER_UPDATE_TIME;
+ }
+ */
+}
+
+//-----------------------------------------------------------------------------
+// DATA HANDLING
+//-----------------------------------------------------------------------------
+// Purpose: Check to see if we should resend the entire tech tree to a player on Hud reinitialisation, which happens every player respawn
+//-----------------------------------------------------------------------------
+void CTFTeam::UpdateClientData( CBasePlayer *pPlayer )
+{
+ CBaseTFPlayer *pTFPlayer = (CBaseTFPlayer *)pPlayer;
+ // If we're initialising the hud, update all technologies
+ if ( pTFPlayer->HUDNeedsRestart() )
+ {
+ // Check all the technologies and resend any that differ from this client's representation
+ for ( int i = 0; i < m_pTechnologyTree->GetNumberTechnologies(); i++ )
+ {
+ // Update all technologies
+ CBaseTechnology *technology = m_pTechnologyTree->GetTechnology(i);
+ if ( technology )
+ {
+ // Check to see if any resource levels have changed
+ if ( pTFPlayer->AvailableTech(i).m_nResourceLevel != technology->GetResourceLevel() )
+ {
+ UpdateClientTechnology( i, pTFPlayer );
+ continue;
+ }
+
+ if ( technology->GetAvailable() != pTFPlayer->AvailableTech(i).m_nAvailable )
+ {
+ UpdateClientTechnology( i, pTFPlayer );
+ continue;
+ }
+
+ byte pcount = technology->GetPreferenceCount();
+ if ( pTFPlayer->GetPreferredTechnology() == i )
+ {
+ pcount |= 0x80;
+ }
+
+ if ( pcount != pTFPlayer->AvailableTech(i).m_nUserCount )
+ {
+ UpdateClientTechnology( i, pTFPlayer );
+ }
+ }
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Update a technology for a player
+//-----------------------------------------------------------------------------
+void CTFTeam::UpdateClientTechnology( int iTechID, CBaseTFPlayer *pPlayer )
+{
+ CBaseTechnology *pTechnology = m_pTechnologyTree->GetTechnology( iTechID );
+ if ( !pTechnology )
+ return;
+
+ byte pcount = pTechnology->GetPreferenceCount();
+
+ if ( pPlayer->GetPreferredTechnology() == iTechID )
+ {
+ pcount |= 0x80;
+ }
+
+ CSingleUserRecipientFilter user( pPlayer );
+ user.MakeReliable();
+
+ // Update this technology
+ UserMessageBegin( user, "Technology" );
+ WRITE_BYTE( iTechID );
+ WRITE_BYTE( pTechnology->GetAvailable() );
+ WRITE_BYTE( pcount );
+ WRITE_SHORT( (short)pTechnology->GetResourceLevel() );
+ MessageEnd();
+
+ // Update the player's client tech representation
+ pPlayer->AvailableTech(iTechID).m_nAvailable = pTechnology->GetAvailable();
+ pPlayer->AvailableTech(iTechID).m_nUserCount = pcount;
+ pPlayer->AvailableTech(iTechID).m_nResourceLevel = pTechnology->GetResourceLevel();
+
+ /*
+ Msg( "Sent %s(%d) to %s:\n", pTechnology->GetName(), iTechID, pPlayer->GetPlayerName() );
+ Msg( " Available: %d\n", pTechnology->GetAvailable() );
+ Msg( " PrefCount: %d\n", pTechnology->GetPreferenceCount() );
+ Msg( " Level : %0.2f\n", pTechnology->GetResourceLevel() );
+ */
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Check to see if any technology has changed, and resend it to players if it has
+//-----------------------------------------------------------------------------
+void CTFTeam::UpdateTechnologyData( void )
+{
+ for ( int i = 0; i < m_pTechnologyTree->GetNumberTechnologies(); i++ )
+ {
+ // Update all technologies
+ CBaseTechnology *pTechnology = m_pTechnologyTree->GetTechnology(i);
+ if ( pTechnology && pTechnology->IsDirty() )
+ {
+ // Send it to all our clients
+ for ( int iPlayer = 0; iPlayer < m_aPlayers.Count(); iPlayer++ )
+ {
+ CBaseTFPlayer *pPlayer = (CBaseTFPlayer *)m_aPlayers[iPlayer];
+ UpdateClientTechnology( i, pPlayer );
+ }
+
+ pTechnology->SetDirty( false );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CTFTeam::ShouldTransmitToPlayer( CBasePlayer* pRecipient, CBaseEntity* pEntity )
+{
+ return IsEntityVisibleToTactical( pEntity );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Is the specified entity visible on this team's tactical view?
+//-----------------------------------------------------------------------------
+bool CTFTeam::IsEntityVisibleToTactical( CBaseEntity *pEntity )
+{
+ return ::IsEntityVisibleToTactical( GetTeamNumber(), GetNumPlayers(),
+ GetNumObjects(), pEntity->entindex(), (char*)STRING(pEntity->m_iClassname),
+ pEntity->GetTeamNumber(), pEntity->GetAbsOrigin() );
+}
+
+//-----------------------------------------------------------------------------
+// RESOURCES
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+// Purpose: Add a resource zone to the list of zones to collect from
+//-----------------------------------------------------------------------------
+void CTFTeam::AddResourceZone( CResourceZone *pResource )
+{
+ TRACE_OBJECT( UTIL_VarArgs( "%0.2f CTFTeam::AddResourceZone adding res zone %p to team %s\n", gpGlobals->curtime,
+ pResource, GetName() ) );
+
+ // If this resource is already owned by another team, remove it from them
+ CTFTeam *pOwners = pResource->GetOwningTeam();
+ if ( pOwners )
+ {
+ pOwners->RemoveResourceZone( pResource );
+ }
+
+ pResource->SetOwningTeam( GetTeamNumber() );
+
+ m_aResourcesBeingCollected.AddToTail( pResource );
+ m_bHaveZone = true;
+
+ // Tell all the team's members
+ for ( int i = 0; i < m_aPlayers.Count(); i++ )
+ {
+ CBaseTFPlayer *pPlayer = (CBaseTFPlayer *)m_aPlayers[i];
+ CSingleUserRecipientFilter filter( pPlayer );
+ filter.MakeReliable();
+ CBaseEntity::EmitSound( filter, pPlayer->entindex(), "TFTeam.CapturedZone" );
+ }
+
+ // Recalculate team's orders
+ RecalcOrders();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Remove a resource zone from the list of zones being collected from
+//-----------------------------------------------------------------------------
+void CTFTeam::RemoveResourceZone( CResourceZone *pResource )
+{
+ TRACE_OBJECT( UTIL_VarArgs( "%0.2f CTFTeam::RemoveResourceZone removing res zone %p from team %s\n", gpGlobals->curtime,
+ pResource, GetName() ) );
+
+ // Now remove the zone from our list
+ m_aResourcesBeingCollected.FindAndRemove( pResource );
+
+ // Still have a zone if there are other zones in the list
+ m_bHaveZone = ( m_aResourcesBeingCollected.Count() > 0 );
+
+ // Tell all the team's members
+ for ( int i = 0; i < m_aPlayers.Count(); i++ )
+ {
+ CBaseTFPlayer *pPlayer = (CBaseTFPlayer *)m_aPlayers[i];
+ CSingleUserRecipientFilter filter( pPlayer );
+ filter.MakeReliable();
+ CBaseEntity::EmitSound( filter, pPlayer->entindex(),"TFTeam.LostZone" );
+ }
+
+ // Recalculate team's orders
+ RecalcOrders();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Recalculate the potential resources
+//-----------------------------------------------------------------------------
+void CTFTeam::UpdatePotentialResources( void )
+{
+ // Set potential to current amount
+ m_fPotentialResources = GetTeamResources();
+
+ // This used to be used for collectors.
+ // It could be updated to count all incoming resources in en-route resource boxes.
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Return the amount of resources a player should get when joining this team
+//-----------------------------------------------------------------------------
+float CTFTeam::GetJoiningPlayerResources( void )
+{
+ // If we had our banks set recently, use that amount
+ if ( gpGlobals->curtime < (m_flLastBankSetTime + 30.0) )
+ return m_flLastBankSetAmount;
+
+ if ( !GetNumPlayers() )
+ return 0;
+
+ // Otherwise, take the average of all the players on the team
+ RecomputeTeamResources();
+ return ( GetTeamResources() / GetNumPlayers() );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFTeam::SetRecentBankSet( float flResources )
+{
+ m_flLastBankSetAmount = flResources;
+ m_flLastBankSetTime = gpGlobals->curtime;
+}
+
+//------------------------------------------------------------------------------------------------------------------
+// TECHNOLOGY TREE
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFTeam::InitializeTechTree( void )
+{
+ m_pTechnologyTree = new CTechnologyTree( filesystem, GetTeamNumber() );
+
+ // Now iterate through added techs and automatically make level 0 techs available
+ for (int i = 0; i < m_pTechnologyTree->GetNumberTechnologies(); i++ )
+ {
+ CBaseTechnology *tech = m_pTechnologyTree->GetTechnology(i);
+ if ( !tech )
+ continue;
+
+ if ( tech->GetLevel() == 0 )
+ {
+ EnableTechnology( tech );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CTechnologyTree *CTFTeam::GetTechnologyTree( void )
+{
+ return m_pTechnologyTree;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: A new technology has been attained by this team. Give it to every player.
+//-----------------------------------------------------------------------------
+void CTFTeam::EnableTechnology( CBaseTechnology *technology, bool bStolen )
+{
+ CTeamFortress *rules = TFGameRules();
+ if ( rules )
+ {
+ // Disable autoswitching if we are getting a weapon
+ rules->SetAllowWeaponSwitch( false );
+ }
+
+ // Set the technology's resources to the costs
+ // Needed because technologies can be enabled through other means than resource spending
+ technology->ForceComplete();
+
+ // Apply technology to team first.
+ technology->AddTechnologyToTeam( this );
+
+ // Iterate though players
+ for (int i = 0; i < m_aPlayers.Count(); i++ )
+ {
+ CBaseTFPlayer *pPlayer = (CBaseTFPlayer *)m_aPlayers[i];
+ technology->AddTechnologyToPlayer( pPlayer );
+
+ CSingleUserRecipientFilter filter( pPlayer );
+ filter.MakeReliable();
+
+ // Play the sound
+ if (bStolen)
+ {
+ CBaseEntity::EmitSound( filter, pPlayer->entindex(), "TFTeam.ObtainStolenTechnology" );
+ }
+ else
+ {
+ if ( technology->GetSoundFile(0) && technology->GetSoundFile(0)[0] )
+ {
+ EmitSound_t ep;
+ ep.m_nChannel = CHAN_STATIC;
+ ep.m_pSoundName = technology->GetSoundFile(0);
+
+ CBaseEntity::EmitSound( filter, pPlayer->entindex(), ep );
+ }
+ }
+
+ // Remove all the player's votes on this technology
+ CBaseTechnology *pPreferredTech = m_pTechnologyTree->GetTechnology( pPlayer->GetPreferredTechnology() );
+ if ( pPreferredTech && pPreferredTech == technology )
+ {
+ // Tell the player his preferred tech has been bought
+ if (!bStolen)
+ {
+ CBaseEntity::EmitSound( filter, pPlayer->entindex(), "TFTeam.BoughtPreferredTechnology" );
+ }
+
+ pPlayer->SetPreferredTechnology( m_pTechnologyTree, -1 );
+ }
+ }
+
+ // Let the team see if it wants to do anything with this specific technology
+ GainedNewTechnology( technology );
+
+ // Reenable autoswitching
+ if ( rules )
+ {
+ rules->SetAllowWeaponSwitch( true );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: For debugging..
+//-----------------------------------------------------------------------------
+void CTFTeam::EnableAllTechnologies()
+{
+ for ( int i = 0; i < m_pTechnologyTree->GetNumberTechnologies(); i++ )
+ {
+ CBaseTechnology *technology = m_pTechnologyTree->GetTechnology(i);
+ if ( !technology || technology->IsHidden() )
+ continue;
+
+ EnableTechnology( technology );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Called any time a player votes/changes preferences on the client
+//-----------------------------------------------------------------------------
+void CTFTeam::RecomputePreferences( void )
+{
+ // Zero total counters
+ m_pTechnologyTree->ClearPreferenceCount();
+
+ // Zero out all preferences and iterate through active players
+ int i;
+ for ( i = 0; i < m_pTechnologyTree->GetNumberTechnologies(); i++ )
+ {
+ CBaseTechnology *technology = m_pTechnologyTree->GetTechnology(i);
+ if ( technology )
+ {
+ // Zero internal counters
+ technology->ZeroPreferences();
+ }
+ }
+
+ // Now loop through players and see what's preferred
+ for ( i = 0; i < m_aPlayers.Count(); i++ )
+ {
+ CBaseTFPlayer *pPlayer = (CBaseTFPlayer *)m_aPlayers[i];
+ if ( !pPlayer )
+ continue;
+
+ int preferred = pPlayer->GetPreferredTechnology();
+ // No preference set, don't worry about this player
+ if ( preferred == -1 )
+ continue;
+
+ if ( preferred < 0 || preferred >= MAX_TECHNOLOGIES )
+ {
+ Msg( "Player %s tried to set preference to out of range tech %i\n", preferred );
+ continue;
+ }
+
+ // Reference technology
+ CBaseTechnology *technology = m_pTechnologyTree->GetTechnology(preferred);
+ Assert( technology );
+ if ( !technology )
+ continue;
+
+ // Msg( "player %s prefers %s\n", pPlayer->GetPlayerName(), technology->GetPrintName() );
+
+ // Add one vote
+ technology->IncrementPreferences();
+ // Add one vote to totals
+ m_pTechnologyTree->IncrementPreferences();
+ }
+
+ // Any time preferences are changed/set, see if we should make any purchases immediately.
+ RecomputePurchases();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Figure our how many resources we've got in the team
+//-----------------------------------------------------------------------------
+void CTFTeam::RecomputeTeamResources( void )
+{
+ // Recalculate the total amount of resources the team has
+ m_fResources = 0.0f;
+ for ( int i = 0; i < GetNumPlayers(); i++ )
+ {
+ m_fResources += ((CBaseTFPlayer*)GetPlayer(i))->GetBankResources();
+ }
+
+ UpdatePotentialResources();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Attempt to spend resources according to player's preferences
+//-----------------------------------------------------------------------------
+void CTFTeam::RecomputePurchases( void )
+{
+ RecomputeTeamResources();
+
+ // Cycle through all players, and spend their resources on the technologies they're voting for
+ for ( int iPlayer = 0; iPlayer < m_aPlayers.Count(); iPlayer++ )
+ {
+ CBaseTFPlayer *pPlayer = (CBaseTFPlayer *)GetPlayer( iPlayer );
+
+ // See if he has any resources to spend on a tech
+ if ( pPlayer->GetBankResources() <= 0 )
+ continue;
+
+ // Has he got a preffered tech?
+ if ( pPlayer->GetPreferredTechnology() != -1 )
+ {
+ // Get the player's voted-for technology
+ CBaseTechnology *pPreferredTech = m_pTechnologyTree->GetTechnology( pPlayer->GetPreferredTechnology() );
+ if ( pPreferredTech && pPreferredTech->GetAvailable() == false )
+ {
+ if ( !pPreferredTech->GetResourceCost() )
+ continue;
+
+ // Try to spend resources on the tech
+ int iResourcesSpent = MIN( pPlayer->GetBankResources(), pPreferredTech->GetResourceCost() - pPreferredTech->GetResourceLevel() );
+ if ( pPreferredTech->IncreaseResourceLevel( iResourcesSpent ) )
+ {
+ // The technology's had enough resources spent to buy it, so enable it
+ EnableTechnology( pPreferredTech );
+ Msg( "%s bought %s\n", GetName(), pPreferredTech->GetPrintName() );
+ }
+
+ // Reduce the player's bank
+ if ( iResourcesSpent )
+ {
+ pPlayer->RemoveBankResources( iResourcesSpent );
+ }
+ }
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Return true if the team owns the specified technology
+//-----------------------------------------------------------------------------
+bool CTFTeam::HasNamedTechnology( const char *name )
+{
+ // Look it up
+ // FIXME: This could be too slow, consider using #define'd/indexed names?
+ CBaseTechnology *tech = m_pTechnologyTree->GetTechnology( name );
+ if ( !tech )
+ return false;
+ if ( !tech->GetAvailable() )
+ return false;
+
+ return true;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: A new technology has been received by the team. Do anything specific to this technology here.
+//-----------------------------------------------------------------------------
+void CTFTeam::GainedNewTechnology( CBaseTechnology *pTechnology )
+{
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Called by the team's Think function
+//-----------------------------------------------------------------------------
+void CTFTeam::UpdateTechnologies( void )
+{
+ // Update clients
+ UpdateTechnologyData();
+}
+
+
+//------------------------------------------------------------------------------------------------------------------
+// PLAYERS
+//-----------------------------------------------------------------------------
+// Purpose: Add the specified player to this team. Remove them from their current team, if any.
+//-----------------------------------------------------------------------------
+void CTFTeam::AddPlayer( CBasePlayer *pPlayer )
+{
+ BaseClass::AddPlayer( pPlayer );
+
+ // Give the player this team's technology
+ for ( int i = 0; i < m_pTechnologyTree->GetNumberTechnologies(); i++ )
+ {
+ CBaseTechnology *technology = m_pTechnologyTree->GetTechnology(i);
+ if ( !technology )
+ continue;
+
+ if ( technology->IsHidden() )
+ continue;
+
+ // Not yet available to team, skip
+ if ( !technology->GetAvailable() )
+ continue;
+
+ // Add it.
+ technology->AddTechnologyToPlayer( (CBaseTFPlayer*)pPlayer );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Clean up the player's objects when they leave
+//-----------------------------------------------------------------------------
+void CTFTeam::RemovePlayer( CBasePlayer *pPlayer )
+{
+ BaseClass::RemovePlayer( pPlayer );
+
+ // Destroy all objects belonging to this player
+ if ( tf_destroyobjects.GetFloat() )
+ {
+ // Work backwards through the list because objects remove themselves
+ int iSize = m_aObjects.Count();
+ for (int i = iSize-1; i >= 0; i--)
+ {
+ if ( (m_aObjects[i]->GetBuilder() == pPlayer) && (m_aObjects[i]->ShouldAutoRemove()) )
+ {
+ UTIL_Remove( m_aObjects[i] );
+ }
+ }
+ }
+
+ RecomputePreferences();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Return the number of team members of the specified class
+//-----------------------------------------------------------------------------
+int CTFTeam::GetNumOfClass( TFClass iClass )
+{
+ int iNumber = 0;
+ for ( int i = 0; i < GetNumPlayers(); i++ )
+ {
+ if ( ((CBaseTFPlayer*)GetPlayer(i))->IsClass(iClass) )
+ {
+ iNumber++;
+ }
+ }
+ return iNumber;
+}
+
+//------------------------------------------------------------------------------------------------------------------
+// RESOURCE BANK
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFTeam::InitializeTeamResources( void )
+{
+ m_fResources = 0.0f;
+ m_fPotentialResources = 0.0f;
+ m_bHaveZone = false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+float CTFTeam::GetTeamResources( void )
+{
+ return m_fResources;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Add resources to this team
+//-----------------------------------------------------------------------------
+int CTFTeam::AddTeamResources( float fAmount, int nStat )
+{
+ fAmount = clamp(fAmount, 0, 9999.f);
+
+ m_flTotalResourcesSoFar += fAmount;
+
+ TFStats()->IncrementTeamStat( GetTeamNumber(), TF_TEAM_STAT_RESOURCES_COLLECTED, fAmount );
+
+ // Divvy the resources out to the players
+ int iAmountPerPlayer = Ceil2Int( fAmount / GetNumPlayers() ); // Yes, this does create some resources in the roundoff.
+ for ( int i = 0; i < GetNumPlayers(); i++ )
+ {
+ CBaseTFPlayer *pPlayer = (CBaseTFPlayer*)GetPlayer(i);
+ pPlayer->AddBankResources( iAmountPerPlayer );
+ TFStats()->IncrementPlayerStat( pPlayer, TF_PLAYER_STAT_RESOURCES_ACQUIRED, fAmount );
+
+ if (nStat >= 0)
+ {
+ TFStats()->IncrementPlayerStat( pPlayer, (TFPlayerStatId_t)nStat, fAmount );
+ }
+ }
+
+ ResourceLoadDeposited();
+
+ return iAmountPerPlayer;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Give resources to the player
+//-----------------------------------------------------------------------------
+void CTFTeam::DonateResources( CBaseTFPlayer *pPlayer )
+{
+ int nPlayerCount = GetNumPlayers();
+ if (nPlayerCount <= 1)
+ return;
+
+ int pResourceCount;
+ int pResourcePerPlayer;
+ int pDonationCount;
+
+ bool bDonating = false;
+ pResourceCount = pPlayer->GetBankResources();
+
+ // Figure out how many resources per player to donate
+ pResourcePerPlayer = pResourceCount / (nPlayerCount - 1);
+ if (pResourceCount % (nPlayerCount - 1) != 0)
+ ++pResourcePerPlayer;
+
+ // Clamp to max amt per teammate for each hit...
+ if (pResourcePerPlayer > RESOURCE_DONATION_AMT_PER_PLAYER)
+ pResourcePerPlayer = RESOURCE_DONATION_AMT_PER_PLAYER;
+
+ // Figure out if we are donating anything at all
+ if (pResourceCount > 0)
+ bDonating = true;
+ if (!bDonating)
+ return;
+
+ // Now that we've figured how much to donate, do it!
+ for ( int i = 0; i < nPlayerCount; i++ )
+ {
+ CBaseTFPlayer *pDest = (CBaseTFPlayer*)GetPlayer(i);
+ if (pDest == pPlayer)
+ continue;
+
+ // The last guy(s) gets the scraps... too bad.
+ int nCountToDonate = pResourceCount;
+ if (nCountToDonate > pResourcePerPlayer)
+ nCountToDonate = pResourcePerPlayer;
+ pResourceCount -= nCountToDonate;
+ pDonationCount = nCountToDonate;
+
+ pPlayer->DonateResources( pDest, pDonationCount );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: New resources have just been dumped in the bank
+//-----------------------------------------------------------------------------
+void CTFTeam::ResourceLoadDeposited( void )
+{
+ // HACK TEST CODE
+ // Remove after Resource Experiment!
+ static int iIncrements = 250;
+ if ( m_flTotalResourcesSoFar >= (m_iLastUpdateSentAt + iIncrements) )
+ {
+ while ( m_flTotalResourcesSoFar >= (m_iLastUpdateSentAt + iIncrements) )
+ {
+ m_iLastUpdateSentAt += iIncrements;
+ }
+
+ EntityMessageBegin( (CBaseEntity*)this );
+ WRITE_LONG( m_iLastUpdateSentAt );
+ MessageEnd();
+ }
+
+ // Now see if we should buy anything
+ RecomputePurchases();
+}
+
+//------------------------------------------------------------------------------------------------------------------
+// RESUPPLY BEACONS
+//-----------------------------------------------------------------------------
+// Purpose: Add the specified resupply beacon to this team.
+//-----------------------------------------------------------------------------
+void CTFTeam::AddResupply( CObjectResupply *pResupply )
+{
+ TRACE_OBJECT( UTIL_VarArgs( "%0.2f CTFTeam::AddResupply adding resupply %p to team %s\n", gpGlobals->curtime,
+ pResupply, GetName() ) );
+
+ m_aResupplyBeacons.AddToTail( pResupply );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Remove this resupply beacon from the team
+//-----------------------------------------------------------------------------
+void CTFTeam::RemoveResupply( CObjectResupply *pResupply )
+{
+ TRACE_OBJECT( UTIL_VarArgs( "%0.2f CTFTeam::RemoveResupply remove resupply %p from team %s\n", gpGlobals->curtime,
+ pResupply, GetName() ) );
+
+ // Now remove the beacon from our list
+ m_aResupplyBeacons.FindAndRemove( pResupply );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+int CTFTeam::GetNumObjects( int iObjectType )
+{
+ // Asking for a count of a specific object type?
+ if ( iObjectType > 0 )
+ {
+ int iCount = 0;
+ for ( int i = 0; i < GetNumObjects(); i++ )
+ {
+ CBaseObject *pObject = GetObject(i);
+ if ( pObject && pObject->GetType() == iObjectType )
+ {
+ iCount++;
+ }
+ }
+ return iCount;
+ }
+
+ return m_aObjects.Count();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CBaseObject *CTFTeam::GetObject( int num )
+{
+ Assert( num >= 0 && num < m_aObjects.Count() );
+ return m_aObjects[ num ];
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+int CTFTeam::GetNumResupplies( void )
+{
+ return m_aResupplyBeacons.Count();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CObjectResupply *CTFTeam::GetResupply( int num )
+{
+ Assert( num >= 0 && num < m_aResupplyBeacons.Count() );
+ return m_aResupplyBeacons[ num ];
+}
+
+
+bool CTFTeam::IsCoveredBySentryGun( const Vector &vPos )
+{
+ for( int i=0; i < m_aObjects.Count(); i++ )
+ {
+ CBaseObject *pObj = m_aObjects[i];
+
+ if ( pObj->IsSentrygun() && vPos.DistTo( pObj->GetAbsOrigin() ) < OBJECT_COVERED_DIST )
+ return true;
+
+ }
+
+ return false;
+}
+
+
+int CTFTeam::GetNumShieldWallsCoveringPosition( const Vector &vPos )
+{
+ int count = 0;
+
+ for ( int i=0; i < m_aObjects.Count(); i++ )
+ {
+ CBaseObject *pObj = m_aObjects[i];
+
+ if ( pObj->GetType() == OBJ_SHIELDWALL )
+ {
+ if ( vPos.DistTo( pObj->GetAbsOrigin() ) < OBJECT_COVERED_DIST )
+ ++count;
+ }
+ }
+
+ return count;
+}
+
+
+int CTFTeam::GetNumResuppliesCoveringPosition( const Vector &vPos )
+{
+ int count = 0;
+
+ for ( int i=0; i < m_aResupplyBeacons.Count(); i++ )
+ {
+ CBaseObject *pObj = m_aResupplyBeacons[i];
+ if ( vPos.DistTo( pObj->GetAbsOrigin() ) < RESUPPLY_COVER_DIST )
+ ++count;
+ }
+
+ return count;
+}
+
+
+int CTFTeam::GetNumRespawnStationsCoveringPosition( const Vector &vPos )
+{
+ int count = 0;
+
+ for ( int i=0; i < m_aObjects.Count(); i++ )
+ {
+ CBaseObject *pObj = m_aObjects[i];
+
+ if ( pObj->GetType() == OBJ_RESPAWN_STATION )
+ {
+ if ( vPos.DistTo( pObj->GetAbsOrigin() ) < OBJECT_COVERED_DIST )
+ {
+ ++count;
+ }
+ }
+ }
+
+ return count;
+}
+
+
+//------------------------------------------------------------------------------------------------------------------
+// OBJECTS
+//-----------------------------------------------------------------------------
+// Purpose: Add the specified object to this team.
+//-----------------------------------------------------------------------------
+void CTFTeam::AddObject( CBaseObject *pObject )
+{
+ TRACE_OBJECT( UTIL_VarArgs( "%0.2f CTFTeam::AddObject adding object %p:%s to team %s\n", gpGlobals->curtime,
+ pObject, pObject->GetClassname(), GetName() ) );
+
+ bool alreadyInList = IsObjectOnTeam( pObject );
+ Assert( !alreadyInList );
+ if ( !alreadyInList )
+ {
+ m_aObjects.AddToTail( pObject );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Returns true if the object is in the team's list of objects
+//-----------------------------------------------------------------------------
+bool CTFTeam::IsObjectOnTeam( CBaseObject *pObject ) const
+{
+ return ( m_aObjects.Find( pObject ) != -1 );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Remove this object from the team
+// Removes all references from all sublists as well
+//-----------------------------------------------------------------------------
+void CTFTeam::RemoveObject( CBaseObject *pObject )
+{
+ if ( m_aObjects.Count() <= 0 )
+ return;
+
+ if ( m_aObjects.Find( pObject ) != -1 )
+ {
+ TRACE_OBJECT( UTIL_VarArgs( "%0.2f CTFTeam::RemoveObject removing %p:%s from %s\n", gpGlobals->curtime,
+ pObject, pObject->GetClassname(), GetName() ) );
+
+ m_aObjects.FindAndRemove( pObject );
+ }
+ else
+ {
+ TRACE_OBJECT( UTIL_VarArgs( "%0.2f CTFTeam::RemoveObject couldn't remove %p:%s from %s\n", gpGlobals->curtime,
+ pObject, pObject->GetClassname(), GetName() ) );
+ }
+}
+
+
+//------------------------------------------------------------------------------------------------------------------
+// ORDERS
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFTeam::InitializeOrders( void )
+{
+ m_flPersonalOrderUpdateTime = 0;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Add a new order to our list. If it already exists, bump it's priority to the new priority.
+//-----------------------------------------------------------------------------
+COrder* CTFTeam::AddOrder(
+ int iOrderType,
+ CBaseEntity *pTarget,
+ CBaseTFPlayer *pPlayer,
+ float flDistanceToRemove,
+ float flLifetime,
+ COrder *pNewOrder
+ )
+{
+ // Remove any orders to the player.
+ RemoveOrdersToPlayer( pPlayer );
+
+ // The new system requires order class to be passed in.
+ Assert( pNewOrder );
+
+ // All the order create functions should just use new to create the order class,
+ // then we'll attach the edict in here. There's no reason to use LINK_ENTITY_TO_CLASS
+ // and CreateEntityByName.
+ Assert( !pNewOrder->edict() );
+ pNewOrder->NetworkProp()->AttachEdict();
+
+ pNewOrder->ChangeTeam( GetTeamNumber() );
+ OrderHandle hOrder;
+ hOrder = pNewOrder;
+ m_aOrders.AddToTail( hOrder );
+
+ // Update target
+ pNewOrder->SetTarget( pTarget );
+ pNewOrder->SetDistance( flDistanceToRemove );
+
+ // Update lifetime.
+ pNewOrder->SetLifetime( flLifetime );
+
+ Assert( pPlayer->GetOrder() == NULL );
+
+ pNewOrder->SetOwner( pPlayer );
+ pPlayer->SetOrder( pNewOrder );
+
+ // "New Order Received!"
+ CSingleUserRecipientFilter filter( pPlayer );
+ filter.MakeReliable();
+ CBaseEntity::EmitSound( filter, pPlayer->entindex(),"TFTeam.AddOrder" );
+
+ // Debug check.. it should never create an order with its termination conditions
+ // already met.
+ Assert( !pNewOrder->Update() );
+
+ return pNewOrder;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFTeam::RemoveOrder( COrder *pOrder )
+{
+ OrderHandle hOrder;
+ hOrder = pOrder;
+
+ m_aOrders.FindAndRemove( hOrder );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Recalculate the team's orders & their priorities
+//-----------------------------------------------------------------------------
+void CTFTeam::RecalcOrders( void )
+{
+ // Update all existing orders
+ UpdateOrders();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFTeam::UpdateOrders( void )
+{
+ // Tell all our current orders to update themselves. Walk backwards because we may remove them.
+ int iSize = m_aOrders.Count();
+ for (int i = iSize-1; i >= 0; i--)
+ {
+ // Orders without owners should be removed
+ bool bShouldRemove = (
+ !m_aOrders[i] ||
+ !m_aOrders[i]->GetOwner() ||
+ m_aOrders[i]->Update() );
+ if ( bShouldRemove )
+ {
+ COrder *pOrder = m_aOrders[i];
+ m_aOrders.Remove( i );
+ UTIL_Remove( pOrder );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: An event has just occurred that affects orders. Tell all our orders that
+// have the specified entity as a target.
+//-----------------------------------------------------------------------------
+void CTFTeam::UpdateOrdersOnEvent( COrderEvent_Base *pOrder )
+{
+ // Tell all our current orders to update themselves. Walk backwards because we may remove them.
+ int iSize = m_aOrders.Count();
+ for (int i = iSize-1; i >= 0; i--)
+ {
+ bool bShouldRemove = m_aOrders[i]->UpdateOnEvent( pOrder );
+ if ( bShouldRemove )
+ {
+ COrder *pOrder = m_aOrders[i];
+ m_aOrders.Remove( i );
+ UTIL_Remove( pOrder );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Create personal orders for all the team's members
+//-----------------------------------------------------------------------------
+void CTFTeam::CreatePersonalOrders( void )
+{
+ // Create personal orders for each player
+ for ( int i = 0; i < m_aPlayers.Count(); i++ )
+ {
+ CBaseTFPlayer *pPlayer = (CBaseTFPlayer *)m_aPlayers[i];
+
+ // Don't create orders for bots, undefined or dead people
+ if ( !(pPlayer->GetFlags() & FL_FAKECLIENT) && pPlayer->IsAlive() && !pPlayer->IsClass( TFCLASS_UNDECIDED ) )
+ {
+ CreatePersonalOrder( pPlayer );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Create personal orders for specified player
+//-----------------------------------------------------------------------------
+void CTFTeam::CreatePersonalOrder( CBaseTFPlayer *pPlayer )
+{
+ // We still haven't made a personal order, so ask the class if it wants to
+ if ( pPlayer->GetPlayerClass() )
+ {
+ pPlayer->GetPlayerClass()->CreatePersonalOrder();
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFTeam::RemoveOrdersToPlayer( CBaseTFPlayer *pPlayer )
+{
+ // Walk backwards because we're removing them.
+ int iSize = m_aOrders.Count();
+ for (int i = iSize-1; i >= 0; i--)
+ {
+ // Orders without owners should be removed
+ if ( m_aOrders[i].Get() )
+ {
+ if( m_aOrders[i]->GetOwner() == pPlayer )
+ {
+ COrder *pOrder = m_aOrders[i];
+ m_aOrders.Remove( i );
+
+ pOrder->DetachFromPlayer();
+ UTIL_Remove( pOrder );
+ }
+ }
+ else
+ {
+ m_aOrders.Remove( i );
+ }
+ }
+
+ pPlayer->SetOrder( NULL );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Count and return the number of orders of the type with the specified target
+//-----------------------------------------------------------------------------
+int CTFTeam::CountOrders( int flags, int iOrderType, CBaseEntity *pTarget, CBaseTFPlayer *pOwner )
+{
+ int iOrderCount = 0;
+
+ // Count the number of global orders
+ for ( int i = 0; i < m_aOrders.Count(); i++ )
+ {
+ COrder *pOrder = m_aOrders[i];
+
+ if( flags & COUNTORDERS_TYPE )
+ if( pOrder->GetType() != iOrderType )
+ continue;
+
+ if( flags & COUNTORDERS_TARGET )
+ if( pOrder->GetTargetEntity() != pTarget )
+ continue;
+
+ if( flags & COUNTORDERS_OWNER )
+ if( pOrder->GetOwner() != pOwner )
+ continue;
+
+ // Ok, this order matches the criteria.
+ iOrderCount++;
+ }
+
+ return iOrderCount;
+}
+
+
+int CTFTeam::CountOrdersOwnedByPlayer( CBaseTFPlayer *pPlayer )
+{
+ return CountOrders( COUNTORDERS_OWNER, 0, 0, pPlayer );
+}
+
+
+//------------------------------------------------------------------------------------------------------------------
+// MESSAGES
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFTeam::ClearMessages( void )
+{
+ int iSize = m_aMessages.Count();
+ for (int i = iSize-1; i >= 0; i--)
+ {
+ CTeamMessage *pMessage = m_aMessages[i];
+ m_aMessages.Remove( i );
+ delete pMessage;
+ }
+
+ m_aMessages.Purge();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Post a message of the specified type
+//-----------------------------------------------------------------------------
+void CTFTeam::PostMessage( int iMessageID, CBaseEntity *pEntity, char *sData )
+{
+ // First see if we've got this message in the queue already
+ for ( int i = 0; i < m_aMessages.Count(); i++ )
+ {
+ CTeamMessage *pMessage = m_aMessages[i];
+ if ( (pMessage->GetID() == iMessageID) && (pMessage->GetEntity() == pEntity) )
+ {
+ // Already in the queue, abort.
+ return;
+ }
+ }
+
+ // Create a new message and add it to my tail
+ CTeamMessage *pMessage = CTeamMessage::Create( this, iMessageID, pEntity );
+ if ( sData && sData[0] )
+ {
+ pMessage->SetData( sData );
+ }
+ m_aMessages.AddToTail( pMessage );
+
+ // Tell the message to fire
+ pMessage->FireMessage();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFTeam::UpdateMessages( void )
+{
+ // Go through my messages and kill any that have reached their TTL
+ int iSize = m_aMessages.Count();
+ for (int i = iSize-1; i >= 0; i--)
+ {
+ CTeamMessage *pMessage = m_aMessages[i];
+ if ( gpGlobals->curtime > pMessage->GetTTL() )
+ {
+ m_aMessages.Remove( i );
+ delete pMessage;
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Tell all our powerpacks to update their powered objects.
+// pPackToIgnore: Pack to ignore, because it's dying.
+// pObjectToTarget: An object looking for power, because it's being placed
+//-----------------------------------------------------------------------------
+void CTFTeam::UpdatePowerpacks( CObjectPowerPack *pPackToIgnore, CBaseObject *pObjectToTarget )
+{
+ for ( int i = 0; i < GetNumObjects(); i++ )
+ {
+ CBaseObject *pObject = GetObject(i);
+ assert(pObject);
+ if ( pObject == pPackToIgnore || pObject->GetType() != OBJ_POWERPACK )
+ continue;
+
+ ((CObjectPowerPack*)pObject)->PowerNearbyObjects( pObjectToTarget );
+
+ // Quit as soon as we've powered the specified one, if there is one
+ if ( pObjectToTarget && pObjectToTarget->IsPowered() )
+ break;
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Tell all our buff stations to update and look for objects.
+// Input: pBuffStationToIgnore: Buff station to ignore, because it is dying
+// pObjectToTarget: an object looking for a buff station, because it is being placed
+//-----------------------------------------------------------------------------
+void CTFTeam::UpdateBuffStations( CObjectBuffStation *pBuffStationToIgnore, CBaseObject *pObjectToTarget, bool bPlacing )
+{
+ for ( int iObject = 0; iObject < GetNumObjects(); ++iObject )
+ {
+ CBaseObject *pObject = GetObject( iObject );
+ assert( pObject );
+
+ if ( pObject->GetType() != OBJ_BUFF_STATION )
+ continue;
+
+ CObjectBuffStation *pBuffStation = static_cast<CObjectBuffStation*>( pObject );
+ if ( pBuffStation == pBuffStationToIgnore )
+ continue;
+
+ pBuffStation->BuffNearbyObjects( pObjectToTarget, bPlacing );
+
+ // Quit as soon as we've powered the specified one, if there is one.
+ if ( pObjectToTarget && pObjectToTarget->IsHookedAndBuffed() )
+ break;
+ }
+}
+
+//------------------------------------------------------------------------------------------------------------------
+// UTILITY FUNCS
+//-----------------------------------------------------------------------------
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CTFTeam* CTFTeam::GetEnemyTeam()
+{
+ // Look for nearby enemy objects we can capture.
+ int iMyTeam = GetTeamNumber();
+ if( iMyTeam == 0 )
+ return NULL;
+
+ int iEnemyTeam = !(iMyTeam - 1) + 1;
+ return (CTFTeam*)GetGlobalTeam( iEnemyTeam );
+}
+
+
diff --git a/game/server/tf2/tf_team.h b/game/server/tf2/tf_team.h
new file mode 100644
index 0000000..1f90b65
--- /dev/null
+++ b/game/server/tf2/tf_team.h
@@ -0,0 +1,219 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Team management class. Contains all the details for a specific team
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef TF_TEAM_H
+#define TF_TEAM_H
+
+#ifdef _WIN32
+#pragma once
+#endif
+
+
+#include "utlvector.h"
+#include "tf_shareddefs.h"
+#include "techtree.h"
+#include "team.h"
+#include "order_events.h"
+
+class CBaseTFPlayer;
+class CResourceZone;
+class CTeamSpawnPoint;
+class CBaseTFPlayer;
+class CResourceDrop;
+class CTechnologyTree;
+class CBaseTechnology;
+class CObjectResupply;
+class CBaseObject;
+class COrder;
+class CTeamMessage;
+class CObjectPowerPack;
+class CObjectBuffStation;
+
+
+enum
+{
+ COUNTORDERS_TYPE = (1<<0),
+ COUNTORDERS_TARGET = (1<<1),
+ COUNTORDERS_OWNER = (1<<2)
+};
+
+
+// Maximum total number of objects a team can have.
+#define MAX_TEAM_OBJECTS 1024
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Team Manager
+//-----------------------------------------------------------------------------
+class CTFTeam : public CTeam
+{
+ DECLARE_CLASS( CTFTeam, CTeam );
+public:
+ virtual ~CTFTeam( void );
+
+ DECLARE_SERVERCLASS();
+
+ // Initialization
+ virtual void Init( const char *pName, int iNumber );
+
+ virtual void Precache( void );
+ virtual void PrecacheTechnology( CBaseTechnology *pTech );
+ virtual void Think( void );
+
+ //-----------------------------------------------------------------------------
+ // Data Handling
+ //-----------------------------------------------------------------------------
+ virtual void UpdateClientData( CBasePlayer *pPlayer );
+ virtual void UpdateClientTechnology( int iTechID, CBaseTFPlayer *pPlayer );
+ virtual void UpdateTechnologyData( void );
+ virtual bool ShouldTransmitToPlayer( CBasePlayer *pRecipient, CBaseEntity* pEntity );
+ virtual bool IsEntityVisibleToTactical( CBaseEntity *pEntity );
+
+ //-----------------------------------------------------------------------------
+ // Resources
+ //-----------------------------------------------------------------------------
+ virtual void UpdatePotentialResources( void );
+
+ virtual void AddResourceZone( CResourceZone *pResource );
+ virtual void RemoveResourceZone( CResourceZone *pResource );
+
+ // Handling for players joining the team during the game
+ float GetJoiningPlayerResources( void );
+ void SetRecentBankSet( float flResources );
+
+ //-----------------------------------------------------------------------------
+ // Technology Tree
+ //-----------------------------------------------------------------------------
+ virtual void InitializeTechTree( void );
+ virtual CTechnologyTree *GetTechnologyTree( void );
+ virtual void EnableTechnology( CBaseTechnology *technology, bool bStolen = false ); // Give this team a technology
+ virtual void EnableAllTechnologies( void );
+ virtual void RecomputeTeamResources( void );
+ virtual void RecomputePreferences( void );
+ virtual void RecomputePurchases( void );
+ virtual bool HasNamedTechnology( const char *name );
+ virtual void GainedNewTechnology( CBaseTechnology *pTechnology );
+ virtual void UpdateTechnologies( void );
+
+ //-----------------------------------------------------------------------------
+ // Players
+ //-----------------------------------------------------------------------------
+ virtual void AddPlayer( CBasePlayer *pPlayer );
+ virtual void RemovePlayer( CBasePlayer *pPlayer );
+ int GetNumOfClass( TFClass iClass );
+
+ //-----------------------------------------------------------------------------
+ // Resource Bank
+ //-----------------------------------------------------------------------------
+ void InitializeTeamResources( void );
+ float GetTeamResources( void );
+ int AddTeamResources( float fAmount, int nStat = -1 );
+ void ResourceLoadDeposited( void );
+ void DonateResources( CBaseTFPlayer *pPlayer );
+
+ //-----------------------------------------------------------------------------
+ // Objects
+ //-----------------------------------------------------------------------------
+ void AddObject( CBaseObject *pObject );
+ void RemoveObject( CBaseObject *pObject );
+ bool IsObjectOnTeam( CBaseObject *pObject ) const;
+ void AddResupply( CObjectResupply *pResupply );
+ void RemoveResupply( CObjectResupply *pResupply );
+ int GetNumObjects( int iObjectType = -1 );
+ CBaseObject *GetObject( int num );
+ int GetNumResupplies( void );
+ CObjectResupply *GetResupply( int num );
+
+ // Returns true if the position is covered by a sentry gun.
+ bool IsCoveredBySentryGun( const Vector &vPos );
+
+ int GetNumShieldWallsCoveringPosition( const Vector &vPos );
+ int GetNumResuppliesCoveringPosition( const Vector &vPos );
+ int GetNumRespawnStationsCoveringPosition( const Vector &vPos );
+
+
+ //-----------------------------------------------------------------------------
+ // Orders
+ //-----------------------------------------------------------------------------
+ void InitializeOrders( void );
+
+ COrder* AddOrder(
+ int iOrderType,
+ CBaseEntity *pTarget,
+ CBaseTFPlayer *pPlayer = NULL,
+ float flDistanceToRemove = 1e24,
+ float flLifetime = 60,
+ COrder *pDefaultOrder = NULL // If this is specified, then it is used instead of
+ // asking COrder to allocate an order.
+ );
+ void RemoveOrder( COrder *pOrder );
+ void RecalcOrders( void );
+ void UpdateOrders( void );
+ void UpdateOrdersOnEvent( COrderEvent_Base *pEvent );
+
+ // Flags is a combination of COUNTORDERS_ flags telling which fields to check.
+ int CountOrders( int flags, int iOrderType, CBaseEntity *pTarget=0, CBaseTFPlayer *pOwner=0 );
+ int CountOrdersOwnedByPlayer( CBaseTFPlayer *pPlayer );
+
+ void CreatePersonalOrders( void );
+ void CreatePersonalOrder( CBaseTFPlayer *pPlayer );
+ void RemoveOrdersToPlayer( CBaseTFPlayer *pPlayer );
+
+ //-----------------------------------------------------------------------------
+ // Messages
+ //-----------------------------------------------------------------------------
+ void ClearMessages( void );
+ void PostMessage( int iMessageID, CBaseEntity *pEntity = NULL, char *sData = NULL );
+ void UpdateMessages( void );
+
+ void UpdatePowerpacks( CObjectPowerPack *pPackToIgnore, CBaseObject *pObjectToTarget );
+ void UpdateBuffStations( CObjectBuffStation *pBuffStationToIgnore, CBaseObject *pObjectToTarget, bool bPlacing );
+
+ //-----------------------------------------------------------------------------
+ // Utility funcs
+ //-----------------------------------------------------------------------------
+ CTFTeam* GetEnemyTeam();
+
+ // Technology Tree
+ CTechnologyTree *m_pTechnologyTree;
+
+ // TEST CODE
+ // Remove after Resource Experiment!
+ float m_flTotalResourcesSoFar;
+
+private:
+ typedef CHandle<COrder> OrderHandle;
+
+ // Resource UI data
+ CNetworkVar( bool, m_bHaveZone );
+
+ // Resources
+ CNetworkVar( float, m_fResources ); // Current amounts of resource
+ CNetworkVar( float, m_fPotentialResources ); // Amounts of resource when all collectors have returned
+ float m_flLastBankSetAmount; // Most recent amount of resources our players had their banks set to
+ float m_flLastBankSetTime; // Time at which our players last had their banks set
+
+ // Orders
+ float m_flPersonalOrderUpdateTime;
+
+ // Used to distribute resources to a team
+ float m_flNextResourceTime;
+
+ int m_iLastUpdateSentAt;
+
+ CUtlVector< CResourceZone * > m_aResourcesBeingCollected;
+ CUtlVector< CObjectResupply * > m_aResupplyBeacons;
+ CUtlVector< OrderHandle > m_aOrders; // Stored in order of priority
+ CUtlVector< CTeamMessage* > m_aMessages;
+ CUtlVector< CBaseObject* > m_aObjects;
+};
+
+
+extern CTFTeam *GetGlobalTFTeam( int iIndex );
+
+
+#endif // TF_TEAM_H
diff --git a/game/server/tf2/tf_teamspawnpoint.h b/game/server/tf2/tf_teamspawnpoint.h
new file mode 100644
index 0000000..0c566e6
--- /dev/null
+++ b/game/server/tf2/tf_teamspawnpoint.h
@@ -0,0 +1,40 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Team spawnpoint entity
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef TF_TEAMSPAWNPOINT_H
+#define TF_TEAMSPAWNPOINT_H
+#pragma once
+
+#include "baseentity.h"
+#include "EntityOutput.h"
+
+class CTeam;
+
+//-----------------------------------------------------------------------------
+// Purpose: points at which the player can spawn, restricted by team
+//-----------------------------------------------------------------------------
+class CTeamSpawnPoint : public CPointEntity
+{
+public:
+ DECLARE_CLASS( CTeamSpawnPoint, CPointEntity );
+
+ void Activate( void );
+ bool IsValid( CBasePlayer *pPlayer );
+
+ COutputEvent m_OnPlayerSpawn;
+
+protected:
+ int m_iDisabled;
+
+ void EnableSpawn( void );
+ void DisableSpawn( void );
+
+ DECLARE_DATADESC();
+};
+
+
+#endif // TF_TEAMSPAWNPOINT_H
diff --git a/game/server/tf2/tf_vehicle_battering_ram.cpp b/game/server/tf2/tf_vehicle_battering_ram.cpp
new file mode 100644
index 0000000..122cfba
--- /dev/null
+++ b/game/server/tf2/tf_vehicle_battering_ram.cpp
@@ -0,0 +1,226 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: A moving vehicle that is used as a battering ram
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#include "cbase.h"
+#include "tf_vehicle_battering_ram.h"
+#include "engine/IEngineSound.h"
+#include "VGuiScreen.h"
+#include "ammodef.h"
+#include "in_buttons.h"
+#include "shake.h"
+
+#define BATTERING_RAM_MINS Vector(-30, -50, -10)
+#define BATTERING_RAM_MAXS Vector( 30, 50, 55)
+#define BATTERING_RAM_MODEL "models/objects/vehicle_battering_ram.mdl"
+
+IMPLEMENT_SERVERCLASS_ST(CVehicleBatteringRam, DT_VehicleBatteringRam)
+END_SEND_TABLE();
+
+LINK_ENTITY_TO_CLASS(vehicle_battering_ram, CVehicleBatteringRam);
+PRECACHE_REGISTER(vehicle_battering_ram);
+
+// CVars
+ConVar vehicle_battering_ram_health( "vehicle_battering_ram_health","800", FCVAR_NONE, "Battering ram health" );
+ConVar vehicle_battering_ram_damage( "vehicle_battering_ram_damage","300", FCVAR_NONE, "Battering ram damage" );
+ConVar vehicle_battering_ram_damage_boostmod( "vehicle_battering_ram_damage_boostmod", "1.5", FCVAR_NONE, "Battering ram boosted damage modifier" );
+ConVar vehicle_battering_ram_mindamagevel( "vehicle_battering_ram_mindamagevel","100", FCVAR_NONE, "Battering ram velocity for min damage" );
+ConVar vehicle_battering_ram_maxdamagevel( "vehicle_battering_ram_maxdamagevel","260", FCVAR_NONE, "Battering ram velocity for max damage" );
+ConVar vehicle_battering_ram_impact_time( "vehicle_battering_ram_impact_time", "1.0", FCVAR_NONE, "Battering ram impact wait time." );
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CVehicleBatteringRam::CVehicleBatteringRam()
+{
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CVehicleBatteringRam::Precache()
+{
+ PrecacheModel( BATTERING_RAM_MODEL );
+
+ PrecacheVGuiScreen( "screen_vehicle_battering_ram" );
+ PrecacheVGuiScreen( "screen_vulnerable_point");
+
+ PrecacheScriptSound( "VehicleBatteringRam.BashSound" );
+
+ BaseClass::Precache();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CVehicleBatteringRam::Spawn()
+{
+ SetModel( BATTERING_RAM_MODEL );
+
+ // This size is used for placement only...
+ UTIL_SetSize(this, BATTERING_RAM_MINS, BATTERING_RAM_MAXS);
+ m_takedamage = DAMAGE_YES;
+ m_iHealth = vehicle_battering_ram_health.GetInt();
+ m_nBarrelAttachment = LookupAttachment( "barrel" );
+
+ SetType( OBJ_BATTERING_RAM );
+ SetMaxPassengerCount( 4 );
+// SetTouch( BashTouch );
+
+ m_flNextBashTime = 0.0f;
+
+ BaseClass::Spawn();
+}
+
+
+//-----------------------------------------------------------------------------
+// Does the player use his normal weapons while in this mode?
+//-----------------------------------------------------------------------------
+bool CVehicleBatteringRam::IsPassengerUsingStandardWeapons( int nRole )
+{
+ return (nRole > 1);
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Gets info about the control panels
+//-----------------------------------------------------------------------------
+void CVehicleBatteringRam::GetControlPanelInfo( int nPanelIndex, const char *&pPanelName )
+{
+ pPanelName = "screen_vulnerable_point";
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Ram!!!
+//-----------------------------------------------------------------------------
+void CVehicleBatteringRam::VPhysicsCollision( int index, gamevcollisionevent_t *pEvent )
+{
+ int otherIndex = !index;
+ CBaseEntity *pEntity = pEvent->pEntities[otherIndex];
+
+ // We only damage objects...
+ // And only if we're travelling fast enough...
+ if ( !pEntity->IsSolid( ) )
+ return;
+
+ // Ignore shields..
+ if ( pEntity->GetCollisionGroup() == TFCOLLISION_GROUP_SHIELD )
+ return;
+
+ // Ignore anything that's not an object
+ if ( pEntity->Classify() != CLASS_MILITARY && !pEntity->IsPlayer() )
+ return;
+
+ // Ignore teammates
+ if ( InSameTeam( pEntity ))
+ return;
+
+ // Ignore invulnerable stuff
+ if ( pEntity->m_takedamage == DAMAGE_NO )
+ return;
+
+ // See if we can damage again? (Time-based)
+ if ( m_flNextBashTime > gpGlobals->curtime )
+ return;
+
+ // Use the attachment point to make sure we damage stuff that hit our front
+ // FIXME: Should we be using hitboxes here?
+ // And damage them all
+ // We only damage objects...
+ QAngle vecAng;
+ Vector vecSrc, vecAim;
+ GetAttachment( m_nBarrelAttachment, vecSrc, vecAng );
+
+ // Check the forward direction
+ Vector forward;
+ AngleVectors( vecAng, &forward );
+
+ // Did we hit it in front of the vehicle?
+ Vector vecDelta;
+ VectorSubtract( pEntity->WorldSpaceCenter(), vecSrc, vecDelta );
+ if ( ( DotProduct( vecDelta, forward ) <= 0 ) || pEntity->IsPlayer() )
+ {
+ BaseClass::VPhysicsCollision( index, pEvent );
+ return;
+ }
+
+ // Call our parents base class.
+ BaseClass::BaseClass::VPhysicsCollision( index, pEvent );
+
+ // Do damage based on velocity (and only if we were heading forward)
+ Vector vecVelocity = pEvent->preVelocity[index];
+ if (DotProduct( vecVelocity, forward ) <= 0)
+ return;
+
+ CTakeDamageInfo info;
+ info.SetInflictor( this );
+ info.SetAttacker( GetDriverPlayer() );
+ info.SetDamageType( DMG_CLUB );
+
+ float flMaxDamage = vehicle_battering_ram_damage.GetFloat();
+ float flMaxDamageVel = vehicle_battering_ram_maxdamagevel.GetFloat();
+ float flMinDamageVel = vehicle_battering_ram_mindamagevel.GetFloat();
+
+ float flVel = vecVelocity.Length();
+ if (flVel < flMinDamageVel)
+ return;
+
+ // FIXME: Play a sound here...
+ EmitSound( "VehicleBatteringRam.BashSound" );
+
+ // Apply the damage
+ float flDamageFactor = flMaxDamage;
+ if ( IsBoosting() )
+ {
+ flDamageFactor *= vehicle_battering_ram_damage_boostmod.GetFloat();
+ }
+
+ if ((flMaxDamageVel > flMinDamageVel) && (flVel < flMaxDamageVel))
+ {
+ // Use less damage when we're not moving fast enough
+ float flVelocityFactor = (flVel - flMinDamageVel) / (flMaxDamageVel - flMinDamageVel);
+ flVelocityFactor *= flVelocityFactor;
+ flDamageFactor *= flVelocityFactor;
+ }
+
+ UTIL_ScreenShakeObject( pEntity, vecSrc, 45.0f * flDamageFactor / flMaxDamage, 30.0f, 0.5f, 250.0f, SHAKE_START );
+
+ info.SetDamage( flDamageFactor );
+
+ Vector damagePos;
+ pEvent->pInternalData->GetContactPoint( damagePos );
+ Vector damageForce = pEvent->postVelocity[index] * pEvent->pObjects[index]->GetMass();
+ info.SetDamageForce( damageForce );
+ info.SetDamagePosition( damagePos );
+
+ PhysCallbackDamage( pEntity, info, *pEvent, index );
+
+ // Set next time ram time
+ m_flNextBashTime = gpGlobals->curtime + vehicle_battering_ram_impact_time.GetFloat();
+}
+
+//-----------------------------------------------------------------------------
+// Here's where we deal with weapons, ladders, etc.
+//-----------------------------------------------------------------------------
+void CVehicleBatteringRam::OnItemPostFrame( CBaseTFPlayer *pDriver )
+{
+ // I can't do anything if I'm not active
+ if ( !ShouldBeActive() )
+ return;
+
+ if ( GetPassengerRole( pDriver ) != VEHICLE_ROLE_DRIVER )
+ return;
+
+ if ( pDriver->m_nButtons & (IN_ATTACK2 | IN_SPEED) )
+ {
+ StartBoost();
+ }
+
+ BaseClass::OnItemPostFrame( pDriver );
+}
diff --git a/game/server/tf2/tf_vehicle_battering_ram.h b/game/server/tf2/tf_vehicle_battering_ram.h
new file mode 100644
index 0000000..d4c403a
--- /dev/null
+++ b/game/server/tf2/tf_vehicle_battering_ram.h
@@ -0,0 +1,58 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: A stationary gun that players can man
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef TF_VEHICLE_BATTERING_RAM_H
+#define TF_VEHICLE_BATTERING_RAM_H
+
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include "tf_basefourwheelvehicle.h"
+#include "vphysics/vehicles.h"
+
+
+// ------------------------------------------------------------------------ //
+// A stationary gun that players can man that's built by the player
+// ------------------------------------------------------------------------ //
+class CVehicleBatteringRam : public CBaseTFFourWheelVehicle
+{
+ DECLARE_CLASS( CVehicleBatteringRam, CBaseTFFourWheelVehicle );
+
+public:
+ DECLARE_SERVERCLASS();
+
+ static CVehicleBatteringRam* Create(const Vector &vOrigin, const QAngle &vAngles);
+
+ CVehicleBatteringRam();
+
+ virtual void Spawn();
+ virtual void Precache();
+ virtual void GetControlPanelInfo( int nPanelIndex, const char *&pPanelName );
+ virtual bool CanTakeEMPDamage( void ) { return true; }
+
+ void OnItemPostFrame( CBaseTFPlayer *pDriver );
+
+ // Can a given passenger take damage?
+ virtual bool IsPassengerDamagable( int nRole ) { return (nRole > 1); }
+
+ // Vehicle overrides
+ virtual bool IsPassengerVisible( int nRole ) { return true; }
+
+ // Does the player use his normal weapons while in this mode?
+ virtual bool IsPassengerUsingStandardWeapons( int nRole );
+
+ // Used to bash things it touches
+ virtual void VPhysicsCollision( int index, gamevcollisionevent_t *pEvent );
+
+private:
+
+ int m_nBarrelAttachment;
+ float m_flNextBashTime;
+};
+
+#endif // TF_VEHICLE_BATTERING_RAM_H
diff --git a/game/server/tf2/tf_vehicle_flatbed.cpp b/game/server/tf2/tf_vehicle_flatbed.cpp
new file mode 100644
index 0000000..a86064c
--- /dev/null
+++ b/game/server/tf2/tf_vehicle_flatbed.cpp
@@ -0,0 +1,78 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Flatbed truck that can carry multiple stationary objects
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#include "cbase.h"
+#include "tf_vehicle_flatbed.h"
+#include "engine/IEngineSound.h"
+#include "VGuiScreen.h"
+#include "ammodef.h"
+#include "in_buttons.h"
+
+#define FLATBED_MINS Vector(-30, -50, -10)
+#define FLATBED_MAXS Vector( 30, 50, 55)
+#define FLATBED_MODEL "models/objects/obj_flatbed.mdl"
+
+IMPLEMENT_SERVERCLASS_ST(CVehicleFlatbed, DT_VehicleFlatbed)
+END_SEND_TABLE();
+
+LINK_ENTITY_TO_CLASS(vehicle_flatbed, CVehicleFlatbed);
+PRECACHE_REGISTER(vehicle_flatbed);
+
+// CVars
+ConVar vehicle_flatbed_health( "vehicle_flatbed_health","800", FCVAR_NONE, "Flatbed health" );
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CVehicleFlatbed::CVehicleFlatbed()
+{
+
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CVehicleFlatbed::Precache()
+{
+ PrecacheModel( FLATBED_MODEL );
+
+ PrecacheVGuiScreen( "screen_vehicle_battering_ram" );
+
+ BaseClass::Precache();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CVehicleFlatbed::Spawn()
+{
+ SetModel( FLATBED_MODEL );
+
+ // This size is used for placement only...
+ UTIL_SetSize(this, FLATBED_MINS, FLATBED_MAXS);
+ m_takedamage = DAMAGE_YES;
+ m_iHealth = vehicle_flatbed_health.GetInt();
+
+ SetType( OBJ_FLATBED );
+ SetMaxPassengerCount( 1 );
+
+ BaseClass::Spawn();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Gets info about the control panels
+//-----------------------------------------------------------------------------
+void CVehicleFlatbed::GetControlPanelInfo( int nPanelIndex, const char *&pPanelName )
+{
+ pPanelName = "screen_vehicle_battering_ram";
+}
+
+
diff --git a/game/server/tf2/tf_vehicle_flatbed.h b/game/server/tf2/tf_vehicle_flatbed.h
new file mode 100644
index 0000000..133c141
--- /dev/null
+++ b/game/server/tf2/tf_vehicle_flatbed.h
@@ -0,0 +1,39 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+#ifndef TF_VEHICLE_FLATBED_H
+#define TF_VEHICLE_FLATBED_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include "tf_basefourwheelvehicle.h"
+#include "vphysics/vehicles.h"
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Flatbed truck that can carry multiple stationary objects
+//-----------------------------------------------------------------------------
+class CVehicleFlatbed: public CBaseTFFourWheelVehicle
+{
+ DECLARE_CLASS( CVehicleFlatbed, CBaseTFFourWheelVehicle );
+
+public:
+ DECLARE_SERVERCLASS();
+
+ static CVehicleFlatbed *Create(const Vector &vOrigin, const QAngle &vAngles);
+
+ CVehicleFlatbed();
+
+ virtual void Spawn();
+ virtual void Precache();
+ virtual void GetControlPanelInfo( int nPanelIndex, const char *&pPanelName );
+
+ // Vehicle overrides
+ virtual bool IsPassengerVisible( int nRole ) { return true; }
+};
+
+#endif // TF_VEHICLE_FLATBED_H
diff --git a/game/server/tf2/tf_vehicle_mortar.cpp b/game/server/tf2/tf_vehicle_mortar.cpp
new file mode 100644
index 0000000..d365e5c
--- /dev/null
+++ b/game/server/tf2/tf_vehicle_mortar.cpp
@@ -0,0 +1,352 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: A moving vehicle that is used as a battering ram
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#include "cbase.h"
+#include "tf_vehicle_mortar.h"
+#include "engine/IEngineSound.h"
+#include "VGuiScreen.h"
+#include "ammodef.h"
+#include "in_buttons.h"
+#include "vehicle_mortar_shared.h"
+#include "movevars_shared.h"
+#include "mortar_round.h"
+
+
+// Waits this long after each shot before they can fire.
+#define MORTAR_FIRE_DELAY 2
+
+
+#define MORTAR_MINS Vector(-30, -50, -10)
+#define MORTAR_MAXS Vector( 30, 50, 55)
+#define MORTAR_MODEL "models/objects/vehicle_mortar.mdl"
+#define MORTAR_SCREEN_NAME "screen_vehicle_mortar"
+
+#define ELEVATION_INTERVAL 0.3
+
+const char *g_pMortarThinkContextName = "MortarThinkContext";
+const char *g_pMortarNextFireContextName = "MortarNextFireContext";
+
+
+
+IMPLEMENT_SERVERCLASS_ST(CVehicleMortar, DT_VehicleMortar)
+ SendPropFloat( SENDINFO( m_flMortarYaw ), 0, SPROP_NOSCALE ),
+ SendPropFloat( SENDINFO( m_flMortarPitch ), 0, SPROP_NOSCALE ),
+ SendPropBool( SENDINFO( m_bAllowedToFire ) )
+END_SEND_TABLE();
+
+
+LINK_ENTITY_TO_CLASS(vehicle_mortar, CVehicleMortar);
+PRECACHE_REGISTER(vehicle_mortar);
+
+
+// CVars
+ConVar vehicle_mortar_health( "vehicle_mortar_health","800", FCVAR_NONE, "Mortar vehicle health" );
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CVehicleMortar::CVehicleMortar()
+{
+ m_flMortarYaw = 0;
+ m_flMortarPitch = 0;
+ m_bAllowedToFire = true;
+
+ UseClientSideAnimation();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CVehicleMortar::Precache()
+{
+ PrecacheModel( MORTAR_MODEL );
+
+ PrecacheVGuiScreen( MORTAR_SCREEN_NAME );
+
+ PrecacheScriptSound( "VehicleMortar.FireSound" );
+
+ BaseClass::Precache();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CVehicleMortar::Spawn()
+{
+ SetModel( MORTAR_MODEL );
+
+ // This size is used for placement only...
+ SetSize(MORTAR_MINS, MORTAR_MAXS);
+ m_takedamage = DAMAGE_YES;
+ m_iHealth = vehicle_mortar_health.GetInt();
+
+ SetType( OBJ_VEHICLE_MORTAR );
+ SetMaxPassengerCount( 1 );
+
+ BaseClass::Spawn();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Gets info about the control panels
+//-----------------------------------------------------------------------------
+void CVehicleMortar::GetControlPanelInfo( int nPanelIndex, const char *&pPanelName )
+{
+ pPanelName = MORTAR_SCREEN_NAME;
+}
+
+
+bool CVehicleMortar::CanGetInVehicle( CBaseTFPlayer *pPlayer )
+{
+ return ( !InDeployMode() && BaseClass::CanGetInVehicle( pPlayer ) );
+}
+
+
+//-----------------------------------------------------------------------------
+// Here's where we deal with weapons
+//-----------------------------------------------------------------------------
+void CVehicleMortar::OnItemPostFrame( CBaseTFPlayer *pDriver )
+{
+ // I can't do anything if I'm not active
+ if ( !ShouldBeActive() )
+ return;
+
+ if ( GetPassengerRole( pDriver ) != VEHICLE_ROLE_DRIVER )
+ return;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CVehicleMortar::OnFinishedDeploy( void )
+{
+ BaseClass::OnFinishedDeploy();
+
+ EntityMessageBegin( this, true );
+ WRITE_STRING( "OnDeployed" );
+ MessageEnd();
+
+ m_flMortarYaw = 0;
+ m_flMortarPitch = 45;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CVehicleMortar::OnFinishedUnDeploy( void )
+{
+ BaseClass::OnFinishedUnDeploy();
+
+ // Called when we are deployed.
+ EntityMessageBegin( this, true );
+ WRITE_STRING( "OnUndeployed" );
+ MessageEnd();
+
+ m_flMortarYaw = 0;
+ m_flMortarPitch = 0;
+}
+
+
+void CVehicleMortar::SetPassenger( int nRole, CBasePlayer *pEnt )
+{
+ if ( pEnt )
+ ShowVGUIScreen( 0, false );
+ else
+ ShowVGUIScreen( 0, true );
+
+ BaseClass::SetPassenger( nRole, pEnt );
+}
+
+void CVehicleMortar::UpdateElevation( const Vector &vecTargetVel )
+{
+ QAngle angles;
+ VectorAngles( vecTargetVel, angles );
+ m_flMortarPitch = anglemod( -angles[PITCH] );
+
+ SetBoneController( 0, m_flMortarYaw );
+ SetBoneController( 1, m_flMortarPitch );
+}
+
+
+bool CVehicleMortar::ClientCommand( CBaseTFPlayer *pPlayer, const char *pCmd, ICommandArguments *pArg )
+{
+ ResetDeteriorationTime();
+
+ if ( !Q_stricmp( pCmd, "Deploy" ) )
+ {
+ Deploy();
+ return true;
+ }
+ else if ( !Q_stricmp( pCmd, "Undeploy" ) )
+ {
+ UnDeploy();
+ }
+ else if ( !Q_stricmp( pCmd, "CancelDeploy" ) )
+ {
+ CancelDeploy();
+ return true;
+ }
+ else if ( !Q_stricmp( pCmd, "FireMortar" ) )
+ {
+ if ( pArg->Argc() == 3 )
+ {
+ FireMortar( atof( pArg->Argv(1) ), atof( pArg->Argv(2) ), false, false );
+ }
+ return true;
+ }
+ else if ( !Q_stricmp( pCmd, "MortarYaw" ) )
+ {
+ if ( pArg->Argc() == 2 )
+ {
+ m_flMortarYaw = atof( pArg->Argv(1) );
+ }
+ return true;
+ }
+
+ return BaseClass::ClientCommand( pPlayer, pCmd, pArg );
+}
+
+
+bool CVehicleMortar::CalcFireInfo(
+ float flFiringPower,
+ float flFiringAccuracy,
+ bool bRangeUpgraded,
+ bool bAccuracyUpgraded,
+ Vector &vStartPt,
+ Vector &vecTargetVel,
+ float &fallTime
+ )
+{
+ QAngle dummy;
+ if ( !GetAttachment( "barrel", vStartPt, dummy ) )
+ vStartPt = WorldSpaceCenter();
+
+ // Get target distance
+ float flDistance;
+ if ( bRangeUpgraded )
+ {
+ flDistance = MORTAR_RANGE_MIN + (flFiringPower * (MORTAR_RANGE_MAX_UPGRADED - MORTAR_RANGE_MIN));
+ }
+ else
+ {
+ flDistance = MORTAR_RANGE_MIN + (flFiringPower * (MORTAR_RANGE_MAX_INITIAL - MORTAR_RANGE_MIN));
+ }
+
+ // Factor in inaccuracy
+ float flInaccuracy;
+ if ( bAccuracyUpgraded )
+ {
+ flInaccuracy = MORTAR_INACCURACY_MAX_UPGRADED * (flFiringAccuracy * 4); // flFiringAccuracy is a range from -0.25 to 0.25
+ }
+ else
+ {
+ flInaccuracy = MORTAR_INACCURACY_MAX_INITIAL * (flFiringAccuracy * 4); // flFiringAccuracy is a range from -0.25 to 0.25
+ }
+ flDistance += (flDistance * MORTAR_DIST_INACCURACY) * random->RandomFloat( -flInaccuracy, flInaccuracy );
+
+ float flAngle = GetAbsAngles()[YAW] + m_flMortarYaw;
+ Vector forward( -sin( DEG2RAD( flAngle ) ), cos( DEG2RAD( flAngle ) ), 0 );
+ Vector right( forward.y, -forward.x, 0 );
+
+ Vector vecTargetOrg = vStartPt + (forward * flDistance);
+ // Add in sideways inaccuracy
+ vecTargetOrg += (right * (flDistance * random->RandomFloat( -flInaccuracy, flInaccuracy )) );
+
+ // Trace down from the sky and find the point we're actually going to hit
+ trace_t tr;
+ Vector vecSky = vecTargetOrg + Vector(0,0,1024);
+ UTIL_TraceLine( vecSky, vecTargetOrg, MASK_ALL, this, COLLISION_GROUP_NONE, &tr );
+ vecTargetOrg = tr.endpos;
+
+ Vector vecMidPoint = vec3_origin;
+ // Start with a low arc, and keep aiming higher until we've got a roughly clear shot
+ for (int i = 512; i <= 4096; i += 512)
+ {
+ trace_t tr1;
+ trace_t tr2;
+
+ vecMidPoint = Vector(0,0,i) + vStartPt + (vecTargetOrg - vStartPt) * 0.5;
+ UTIL_TraceLine(vStartPt, vecMidPoint, MASK_ALL, this, COLLISION_GROUP_NONE, &tr1);
+ UTIL_TraceLine(vecMidPoint, vecTargetOrg, MASK_ALL, this, COLLISION_GROUP_NONE, &tr2);
+
+ // Clear shot?
+ // We want a clear shot for the first half, and a fairly clear shot on the fall
+ if ( tr1.fraction == 1 && tr2.fraction > 0.5 )
+ break;
+ }
+
+ // How high should we travel to reach the apex
+ float distance1 = (vecMidPoint.z - vStartPt.z);
+ float distance2 = (vecMidPoint.z - vecTargetOrg.z);
+
+ // How long will it take to travel this distance
+ float flGravity = GetCurrentGravity();
+ float time1 = sqrt( distance1 / (0.5 * flGravity) );
+ float time2 = sqrt( distance2 / (0.5 * flGravity) );
+ if (time1 < 0.1)
+ return false;
+
+ // how hard to launch to get there in time.
+ vecTargetVel = (vecTargetOrg - vStartPt) / (time1 + time2);
+ vecTargetVel.z = flGravity * time1;
+
+ fallTime = time1 * 0.5;
+ return true;
+}
+
+
+void CVehicleMortar::NextFireThink()
+{
+ // Ok, we can fire again now.
+ m_bAllowedToFire = true;
+}
+
+
+bool CVehicleMortar::FireMortar( float flFiringPower, float flFiringAccuracy, bool bRangeUpgraded, bool bAccuracyUpgraded )
+{
+ SetActivity( ACT_RANGE_ATTACK1 );
+
+ // Calculate the shot.
+ Vector vStartPt;
+ Vector vecTargetVel;
+ float fallTime;
+
+ if ( !CalcFireInfo(
+ flFiringPower,
+ flFiringAccuracy,
+ bRangeUpgraded,
+ bAccuracyUpgraded,
+ vStartPt,
+ vecTargetVel,
+ fallTime ) )
+ {
+ return false;
+ }
+
+ UpdateElevation( vecTargetVel );
+
+ // Create the round
+ CMortarRound *pRound = CMortarRound::Create( vStartPt, vecTargetVel, GetOwner()->edict() );
+ pRound->SetLauncher( this );
+ pRound->ChangeTeam( GetTeamNumber() );
+ pRound->SetFallTime( fallTime ); // Start a falling sound just a bit before we begin to fall
+ pRound->SetRoundType( MA_SHELL );
+
+ // BOOM!
+ EmitSound( "VehicleMortar.FireSound" );
+
+ // Put in a delay before thinking again.
+ m_bAllowedToFire = false;
+ SetContextThink( NextFireThink, gpGlobals->curtime + MORTAR_FIRE_DELAY, g_pMortarNextFireContextName );
+
+ return true;
+}
+
+
diff --git a/game/server/tf2/tf_vehicle_mortar.h b/game/server/tf2/tf_vehicle_mortar.h
new file mode 100644
index 0000000..5103390
--- /dev/null
+++ b/game/server/tf2/tf_vehicle_mortar.h
@@ -0,0 +1,85 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: A stationary gun that players can man
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef TF_VEHICLE_MORTAR_H
+#define TF_VEHICLE_MORTAR_H
+
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include "tf_basefourwheelvehicle.h"
+#include "vphysics/vehicles.h"
+
+
+// ------------------------------------------------------------------------ //
+// A wagon that players can build one object on the back of
+// ------------------------------------------------------------------------ //
+class CVehicleMortar : public CBaseTFFourWheelVehicle
+{
+ DECLARE_CLASS( CVehicleMortar, CBaseTFFourWheelVehicle );
+
+public:
+ DECLARE_SERVERCLASS();
+
+ static CVehicleMortar* Create(const Vector &vOrigin, const QAngle &vAngles);
+
+ CVehicleMortar();
+
+ virtual void Spawn();
+ virtual void Precache();
+ virtual void GetControlPanelInfo( int nPanelIndex, const char *&pPanelName );
+ virtual bool CanTakeEMPDamage( void ) { return true; }
+ virtual bool ClientCommand( CBaseTFPlayer *pPlayer, const CCommand &args );
+
+ // Vehicle overrides
+ virtual bool IsPassengerVisible( int nRole ) { return true; }
+ virtual void OnFinishedDeploy( void );
+ virtual void OnFinishedUnDeploy( void );
+ virtual void SetPassenger( int nRole, CBasePlayer *pEnt );
+
+protected:
+ virtual bool CanGetInVehicle( CBaseTFPlayer *pPlayer );
+
+ // Here's where we deal with weapons
+ virtual void OnItemPostFrame( CBaseTFPlayer *pDriver );
+
+ // Called when we are deployed.
+ void ElevationThink();
+
+ void UpdateElevation( const Vector &vecTargetVel );
+
+ bool CalcFireInfo(
+ float flFiringPower,
+ float flFiringAccuracy,
+ bool bRangeUpgraded,
+ bool bAccuracyUpgraded,
+ Vector &vStartPt,
+ Vector &vecTargetVel,
+ float &fallTime
+ );
+
+ bool FireMortar( float flFiringPower, float flFiringAccuracy, bool bRangeUpgraded, bool bAccuracyUpgraded );
+
+private:
+ void NextFireThink();
+
+ // Start/end the attack
+ void StartAttack();
+ void FinishAttack();
+
+ void AttackThink();
+
+ int m_nBuildPoint;
+
+ CNetworkVar( float, m_flMortarYaw );
+ CNetworkVar( float, m_flMortarPitch ); // Elevation angle, recalculated periodically.
+
+ CNetworkVar( bool, m_bAllowedToFire );
+};
+
+#endif // TF_VEHICLE_MORTAR_H
diff --git a/game/server/tf2/tf_vehicle_motorcycle.cpp b/game/server/tf2/tf_vehicle_motorcycle.cpp
new file mode 100644
index 0000000..c5cbf60
--- /dev/null
+++ b/game/server/tf2/tf_vehicle_motorcycle.cpp
@@ -0,0 +1,99 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+#include "cbase.h"
+#include "engine/IEngineSound.h"
+#include "VGuiScreen.h"
+#include "tf_basefourwheelvehicle.h"
+#include "vphysics/vehicles.h"
+
+#define MOTORCYCLE_MINS Vector(-30, -50, -10)
+#define MOTORCYCLE_MAXS Vector( 30, 50, 55)
+#define MOTORCYCLE_MODEL "models/objects/vehicle_motorcycle.mdl"
+
+// ------------------------------------------------------------------------ //
+// Purpose:
+// ------------------------------------------------------------------------ //
+class CVehicleMotorcycle : public CBaseTFFourWheelVehicle
+{
+ DECLARE_CLASS( CVehicleMotorcycle, CBaseTFFourWheelVehicle );
+
+public:
+ DECLARE_SERVERCLASS();
+
+ static CVehicleMotorcycle* Create(const Vector &vOrigin, const QAngle &vAngles);
+
+ CVehicleMotorcycle();
+
+ virtual void Spawn();
+ virtual void Precache();
+ virtual void GetControlPanelInfo( int nPanelIndex, const char *&pPanelName );
+ virtual bool CanTakeEMPDamage( void ) { return true; }
+
+ // Vehicle overrides
+ virtual bool IsPassengerVisible( int nRole ) { return true; }
+};
+
+IMPLEMENT_SERVERCLASS_ST(CVehicleMotorcycle, DT_VehicleMotorcycle)
+END_SEND_TABLE();
+
+LINK_ENTITY_TO_CLASS(vehicle_motorcycle, CVehicleMotorcycle);
+PRECACHE_REGISTER(vehicle_motorcycle);
+
+// CVars
+ConVar vehicle_motorcycle_health( "vehicle_motorcycle_health","200", FCVAR_NONE, "Motorcycle health" );
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CVehicleMotorcycle::CVehicleMotorcycle()
+{
+
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CVehicleMotorcycle::Precache()
+{
+ PrecacheModel( MOTORCYCLE_MODEL );
+
+ PrecacheVGuiScreen( "screen_vulnerable_point");
+
+ BaseClass::Precache();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CVehicleMotorcycle::Spawn()
+{
+ SetModel( MOTORCYCLE_MODEL );
+
+ // This size is used for placement only...
+ UTIL_SetSize(this, MOTORCYCLE_MINS, MOTORCYCLE_MAXS);
+ m_takedamage = DAMAGE_YES;
+ m_iHealth = vehicle_motorcycle_health.GetInt();
+
+ SetType( OBJ_VEHICLE_MOTORCYCLE );
+ SetMaxPassengerCount( 4 );
+
+ BaseClass::Spawn();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Gets info about the control panels
+//-----------------------------------------------------------------------------
+void CVehicleMotorcycle::GetControlPanelInfo( int nPanelIndex, const char *&pPanelName )
+{
+ pPanelName = "screen_vulnerable_point";
+}
+
+
diff --git a/game/server/tf2/tf_vehicle_siege_tower.cpp b/game/server/tf2/tf_vehicle_siege_tower.cpp
new file mode 100644
index 0000000..78d16f3
--- /dev/null
+++ b/game/server/tf2/tf_vehicle_siege_tower.cpp
@@ -0,0 +1,304 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Siege Tower Vehicle
+//
+//=============================================================================//
+
+#include "cbase.h"
+#include "tf_vehicle_siege_tower.h"
+#include "engine/IEngineSound.h"
+#include "VGuiScreen.h"
+#include "ammodef.h"
+#include "in_buttons.h"
+
+#define SIEGE_TOWER_MINS Vector(-30, -50, -10)
+#define SIEGE_TOWER_MAXS Vector( 30, 50, 55)
+#define SIEGE_TOWER_MODEL "models/objects/vehicle_siege_tower.mdl"
+#define SIEGE_TOWER_LADDER_MODEL "models/objects/vehicle_siege_ladder.mdl"
+#define SIEGE_TOWER_PLATFORM_MODEL "models/objects/vehicle_siege_plat.mdl"
+
+IMPLEMENT_SERVERCLASS_ST( CVehicleSiegeTower, DT_VehicleSiegeTower )
+END_SEND_TABLE();
+
+LINK_ENTITY_TO_CLASS( vehicle_siege_tower, CVehicleSiegeTower );
+PRECACHE_REGISTER( vehicle_siege_tower );
+
+IMPLEMENT_SERVERCLASS_ST( CObjectSiegeLadder, DT_ObjectSiegeLadder )
+END_SEND_TABLE();
+
+LINK_ENTITY_TO_CLASS( obj_siege_ladder, CObjectSiegeLadder );
+PRECACHE_REGISTER( obj_siege_ladder );
+
+IMPLEMENT_SERVERCLASS_ST( CObjectSiegePlatform, DT_ObjectSiegePlatform )
+END_SEND_TABLE();
+
+LINK_ENTITY_TO_CLASS( obj_siege_platform, CObjectSiegePlatform );
+PRECACHE_REGISTER( obj_siege_platform );
+
+// CVars
+ConVar vehicle_siege_tower_health( "vehicle_siege_tower_health","600", FCVAR_NONE, "Siege tower health" );
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CVehicleSiegeTower::CVehicleSiegeTower()
+{
+ m_hLadder = NULL;
+ m_hPlatform = NULL;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CVehicleSiegeTower::Precache()
+{
+ PrecacheModel( SIEGE_TOWER_MODEL );
+
+ PrecacheVGuiScreen( "screen_vehicle_siege_tower" );
+ PrecacheVGuiScreen( "screen_vulnerable_point" );
+
+ BaseClass::Precache();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CVehicleSiegeTower::Spawn()
+{
+ SetModel( SIEGE_TOWER_MODEL );
+
+ // This size is used for placement only...
+ UTIL_SetSize( this, SIEGE_TOWER_MINS, SIEGE_TOWER_MAXS );
+ m_takedamage = DAMAGE_YES;
+ m_iHealth = vehicle_siege_tower_health.GetInt();
+
+ SetType( OBJ_SIEGE_TOWER );
+ SetMaxPassengerCount( 4 );
+
+ BaseClass::Spawn();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Gets info about the control panels
+//-----------------------------------------------------------------------------
+void CVehicleSiegeTower::GetControlPanelInfo( int nPanelIndex, const char *&pPanelName )
+{
+ //pPanelName = "screen_vehicle_siege_tower";
+ pPanelName = "screen_vulnerable_point";
+}
+
+//-----------------------------------------------------------------------------
+// Here's where we deal with weapons, ladders, etc.
+//-----------------------------------------------------------------------------
+void CVehicleSiegeTower::OnItemPostFrame( CBaseTFPlayer *pDriver )
+{
+ // I can't do anything if I'm not active
+ if ( !ShouldBeActive() )
+ return;
+
+ if ( GetPassengerRole( pDriver ) != VEHICLE_ROLE_DRIVER )
+ return;
+
+ if ( !IsDeployed() && ( pDriver->m_afButtonPressed & IN_ATTACK ) )
+ {
+ InternalDeploy();
+ }
+ else if ( IsDeployed() && ( pDriver->m_afButtonPressed & IN_ATTACK ) )
+ {
+ InternalUnDeploy();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CVehicleSiegeTower::InternalDeploy( void )
+{
+ // Deploy
+ if ( !Deploy() )
+ return;
+
+ InputTurnOff( inputdata_t() );
+
+ // Create the ladder.
+ Vector vecOrigin;
+ QAngle vecAngle;
+ GetAttachment( "ladder", vecOrigin, vecAngle );
+ CreateLadder( vecOrigin, vecAngle );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CVehicleSiegeTower::InternalUnDeploy( void )
+{
+ // Undeploy
+ UnDeploy();
+ InputTurnOn( inputdata_t() );
+
+ // Destory the ladder.
+ DestroyLadder();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CVehicleSiegeTower::CreateLadder( const Vector &vecOrigin, const QAngle &vecAngles )
+{
+ // NOTE: This ladder and platform code is a total hack to test vehicles with. This
+ // is not the correct way to handle this problem at all.
+ m_hLadder = CObjectSiegeLadder::Create( vecOrigin, vecAngles, this );
+ m_hPlatform = CObjectSiegePlatform::Create( vecOrigin, vecAngles, this );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CVehicleSiegeTower::DestroyLadder( void )
+{
+ if ( m_hLadder.Get() )
+ {
+ UTIL_Remove( m_hLadder );
+ m_hLadder = NULL;
+ }
+
+ if ( m_hPlatform.Get() )
+ {
+ UTIL_Remove( m_hPlatform );
+ m_hPlatform = NULL;
+ }
+}
+
+//------------------------------------------------------------------------------
+// Purpose:
+//------------------------------------------------------------------------------
+void CVehicleSiegeTower::Killed( void )
+{
+ DestroyLadder();
+ BaseClass::Killed();
+}
+
+//==============================================================================
+//
+// Object Siege Ladder
+//
+
+//------------------------------------------------------------------------------
+// Purpose:
+//------------------------------------------------------------------------------
+CObjectSiegeLadder::CObjectSiegeLadder()
+{
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CObjectSiegeLadder *CObjectSiegeLadder::Create( const Vector &vOrigin, const QAngle &vAngles, CBaseEntity *pParent )
+{
+ CObjectSiegeLadder *pLadder = static_cast<CObjectSiegeLadder*>( CBaseObject::Create( "obj_siege_ladder", vOrigin, vAngles ) );
+ if ( pLadder )
+ {
+ pLadder->m_hTower = pParent;
+ }
+
+ return pLadder;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CObjectSiegeLadder::Spawn()
+{
+ Precache();
+ SetModel( SIEGE_TOWER_LADDER_MODEL );
+ SetSolid( SOLID_VPHYSICS );
+ m_takedamage = DAMAGE_NO;
+
+ BaseClass::Spawn();
+
+ CollisionProp()->SetSurroundingBoundsType( USE_BEST_COLLISION_BOUNDS );
+ IPhysicsObject *pPhysics = VPhysicsInitStatic();
+ if ( pPhysics )
+ {
+ pPhysics->EnableMotion( false );
+ }
+ SetCollisionGroup( TFCOLLISION_GROUP_OBJECT_SOLIDTOPLAYERMOVEMENT );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CObjectSiegeLadder::Precache()
+{
+ PrecacheModel( SIEGE_TOWER_LADDER_MODEL );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Pass all damage back to the siege tower
+//-----------------------------------------------------------------------------
+int CObjectSiegeLadder::OnTakeDamage( const CTakeDamageInfo &info )
+{
+ return m_hTower->OnTakeDamage( info );
+}
+
+//==============================================================================
+//
+// Object Siege Platform
+//
+
+//------------------------------------------------------------------------------
+// Purpose:
+//------------------------------------------------------------------------------
+CObjectSiegePlatform::CObjectSiegePlatform()
+{
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CObjectSiegePlatform *CObjectSiegePlatform::Create( const Vector &vOrigin, const QAngle &vAngles, CBaseEntity *pParent )
+{
+ CObjectSiegePlatform *pPlatform = static_cast<CObjectSiegePlatform*>( CBaseObject::Create( "obj_siege_platform", vOrigin, vAngles ) );
+ if ( pPlatform )
+ {
+ pPlatform->m_hTower = pParent;
+ }
+
+ return pPlatform;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CObjectSiegePlatform::Spawn()
+{
+ Precache();
+ SetModel( SIEGE_TOWER_PLATFORM_MODEL );
+ SetSolid( SOLID_VPHYSICS );
+ m_takedamage = DAMAGE_NO;
+
+ BaseClass::Spawn();
+ CollisionProp()->SetSurroundingBoundsType( USE_BEST_COLLISION_BOUNDS );
+ IPhysicsObject *pPhysics = VPhysicsInitStatic();
+ if ( pPhysics )
+ {
+ pPhysics->EnableMotion( false );
+ }
+ SetCollisionGroup( TFCOLLISION_GROUP_OBJECT_SOLIDTOPLAYERMOVEMENT );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CObjectSiegePlatform::Precache()
+{
+ PrecacheModel( SIEGE_TOWER_PLATFORM_MODEL );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Pass all damage back to the siege tower
+//-----------------------------------------------------------------------------
+int CObjectSiegePlatform::OnTakeDamage( const CTakeDamageInfo &info )
+{
+ return m_hTower->OnTakeDamage( info );
+} \ No newline at end of file
diff --git a/game/server/tf2/tf_vehicle_siege_tower.h b/game/server/tf2/tf_vehicle_siege_tower.h
new file mode 100644
index 0000000..6832adc
--- /dev/null
+++ b/game/server/tf2/tf_vehicle_siege_tower.h
@@ -0,0 +1,109 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Siege Tower Vehicle
+//
+//=============================================================================//
+
+#ifndef TF_VEHICLE_SIEGE_TOWER_H
+#define TF_VEHICLE_SIEGE_TOWER_H
+
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include "tf_basefourwheelvehicle.h"
+#include "vphysics/vehicles.h"
+
+//=============================================================================
+//
+// Siege Ladder (Object)
+//
+class CObjectSiegeLadder : public CBaseAnimating
+{
+ DECLARE_CLASS( CObjectSiegeLadder, CBaseAnimating );
+
+public:
+
+ DECLARE_SERVERCLASS();
+
+ static CObjectSiegeLadder* Create( const Vector &vOrigin, const QAngle &vAngles, CBaseEntity *pParent );
+
+ CObjectSiegeLadder();
+
+ void Spawn();
+ void Precache();
+ virtual int OnTakeDamage( const CTakeDamageInfo &info );
+
+public:
+ EHANDLE m_hTower;
+};
+
+//=============================================================================
+//
+// Siege Platform (Object)
+//
+class CObjectSiegePlatform : public CBaseAnimating
+{
+ DECLARE_CLASS( CObjectSiegePlatform, CBaseAnimating );
+
+public:
+
+ DECLARE_SERVERCLASS();
+
+ static CObjectSiegePlatform* Create( const Vector &vOrigin, const QAngle &vAngles, CBaseEntity *pParent );
+
+ CObjectSiegePlatform();
+
+ void Spawn();
+ void Precache();
+ virtual int OnTakeDamage( const CTakeDamageInfo &info );
+
+public:
+ EHANDLE m_hTower;
+};
+
+//=============================================================================
+//
+// Siege Tower Vehicle
+//
+class CVehicleSiegeTower : public CBaseTFFourWheelVehicle
+{
+ DECLARE_CLASS( CVehicleSiegeTower, CBaseTFFourWheelVehicle );
+
+public:
+
+ DECLARE_SERVERCLASS();
+
+ static CVehicleSiegeTower* Create( const Vector &vOrigin, const QAngle &vAngles );
+
+ CVehicleSiegeTower();
+
+ void Spawn();
+ void Precache();
+ void Killed(void );
+ void GetControlPanelInfo( int nPanelIndex, const char *&pPanelName );
+ bool CanTakeEMPDamage( void ) { return true; }
+
+ // Vehicle overrides
+ bool IsPassengerVisible( int nRole ) { return true; }
+
+protected:
+
+ // Here's where we deal with weapons
+ void OnItemPostFrame( CBaseTFPlayer *pDriver );
+
+private:
+
+ // Deploy.
+ void InternalDeploy( void );
+ void InternalUnDeploy( void );
+ void CreateLadder( const Vector &vecOrigin, const QAngle &vecAngles );
+ void DestroyLadder( void );
+
+private:
+
+ CHandle<CObjectSiegeLadder> m_hLadder;
+ CHandle<CObjectSiegePlatform> m_hPlatform;
+};
+
+#endif // TF_VEHICLE_SIEGE_TOWER_H
diff --git a/game/server/tf2/tf_vehicle_tank.cpp b/game/server/tf2/tf_vehicle_tank.cpp
new file mode 100644
index 0000000..96fea85
--- /dev/null
+++ b/game/server/tf2/tf_vehicle_tank.cpp
@@ -0,0 +1,262 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: A moving vehicle that is used as a battering ram
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#include "cbase.h"
+#include "tf_vehicle_tank.h"
+#include "engine/IEngineSound.h"
+#include "VGuiScreen.h"
+#include "ammodef.h"
+#include "in_buttons.h"
+#include "vehicle_mortar_shared.h"
+#include "movevars_shared.h"
+#include "mortar_round.h"
+#include "explode.h"
+#include "tf_gamerules.h"
+#include "shake.h"
+#include "basetempentity.h"
+#include "weapon_grenade_rocket.h"
+
+
+#define TANK_MINS Vector(-30, -50, -10)
+#define TANK_MAXS Vector( 30, 50, 55)
+#define TANK_MODEL "models/objects/vehicle_tank.mdl"
+
+// N seconds between tank shots.
+#define TANK_FIRE_INTERVAL 2
+
+#define TANK_MAX_RANGE 1800
+
+
+const char *g_pTurretThinkContextName = "TurretThinkContext";
+
+ConVar vehicle_tank_damage( "vehicle_tank_damage","150", FCVAR_NONE, "Tank Rocket damage" );
+ConVar vehicle_tank_range( "vehicle_tank_range","1000", FCVAR_NONE, "Tank Rocket range" );
+ConVar vehicle_tank_radius( "vehicle_tank_radius","128", FCVAR_NONE, "Tank rocket explosive radius" );
+
+
+// How fast the turret rotates to where the driver is facing.
+#define TURRET_DEGREES_PER_SEC 30
+
+
+IMPLEMENT_SERVERCLASS_ST( CVehicleTank, DT_VehicleTank )
+ SendPropAngle( SENDINFO( m_flTurretYaw ), 11 ),
+ SendPropAngle( SENDINFO( m_flTurretPitch ), 11 ),
+END_SEND_TABLE()
+
+
+LINK_ENTITY_TO_CLASS( vehicle_tank, CVehicleTank );
+PRECACHE_REGISTER( vehicle_tank );
+
+
+// CVars
+ConVar vehicle_tank_health( "vehicle_tank_health","800", FCVAR_NONE, "Tank health" );
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CVehicleTank::CVehicleTank()
+{
+ m_flClientYaw = 0;
+ m_flClientPitch = 0;
+
+ m_flTurretYaw = 0;
+ m_flTurretPitch = 0;
+
+ m_flNextFireTime = 0;
+
+ UseClientSideAnimation();
+}
+
+
+CVehicleTank::~CVehicleTank()
+{
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CVehicleTank::Precache()
+{
+ PrecacheModel( TANK_MODEL );
+ PrecacheVGuiScreen( "screen_vulnerable_point" );
+
+ PrecacheScriptSound( "VehicleTank.FireSound" );
+
+
+ BaseClass::Precache();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CVehicleTank::Spawn()
+{
+ SetModel( TANK_MODEL );
+
+ // This size is used for placement only...
+ SetSize( TANK_MINS, TANK_MAXS );
+ m_takedamage = DAMAGE_YES;
+ m_iHealth = vehicle_tank_health.GetInt();
+
+ SetType( OBJ_VEHICLE_TANK );
+ SetMaxPassengerCount( 3 );
+
+ // This can go away when the tank is using a real tank model.
+ SetActivity( ACT_DEPLOY );
+
+ SetContextThink( TurretThink, gpGlobals->curtime, g_pTurretThinkContextName );
+
+ BaseClass::Spawn();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Gets info about the control panels
+//-----------------------------------------------------------------------------
+void CVehicleTank::GetControlPanelInfo( int nPanelIndex, const char *&pPanelName )
+{
+ //pPanelName = "screen_vehicle_siege_tower";
+ pPanelName = "screen_vulnerable_point";
+}
+
+float MoveAngleTowards( float flAngle, float flTowards, float flRate )
+{
+ // Translate so flTowards is 0, then interpolate towards 0 or 360.
+ flAngle -= flTowards;
+
+ // Move yaw to the client yaw using the shortest path.
+ flAngle = anglemod( flAngle );
+ if ( flAngle > 180 )
+ {
+ flAngle = MIN( 360, flAngle + flRate );
+ }
+ else
+ {
+ flAngle = MAX( 0, flAngle - flRate );
+ }
+
+ // Translate back.
+ flAngle += flTowards;
+ return flAngle;
+}
+
+
+void CVehicleTank::TurretThink()
+{
+ float flStartYaw = m_flTurretYaw;
+ float flStartPitch = m_flTurretPitch;
+
+ m_flTurretYaw = MoveAngleTowards( m_flTurretYaw, m_flClientYaw, gpGlobals->frametime * TURRET_DEGREES_PER_SEC );
+ m_flTurretPitch = MoveAngleTowards( m_flTurretPitch, m_flClientPitch, gpGlobals->frametime * TURRET_DEGREES_PER_SEC );
+
+ SetBoneController( 0, m_flTurretYaw );
+ SetBoneController( 1, m_flTurretPitch );
+
+ SetContextThink( TurretThink, gpGlobals->curtime, g_pTurretThinkContextName );
+}
+
+
+//-----------------------------------------------------------------------------
+// Here's where we deal with weapons
+//-----------------------------------------------------------------------------
+void CVehicleTank::OnItemPostFrame( CBaseTFPlayer *pDriver )
+{
+ // I can't do anything if I'm not active
+ if ( !ShouldBeActive() )
+ return;
+
+ // Make sure we're allowed to fire here
+ if ( !IsReadyToDrive() )
+ return;
+
+ if ( GetPassengerRole( pDriver ) != VEHICLE_ROLE_DRIVER )
+ return;
+
+ if ( pDriver->m_nButtons & IN_ATTACK )
+ {
+ // Time to fire?
+ if ( gpGlobals->curtime >= m_flNextFireTime )
+ {
+ Fire();
+ }
+ }
+ else if ( pDriver->m_nButtons & (IN_ATTACK2 | IN_SPEED) )
+ {
+ //StartBoost();
+ BaseClass::OnItemPostFrame( pDriver );
+ }
+}
+
+
+void CVehicleTank::Fire()
+{
+ // NOTE: this code reverses the steps taken in C_VehicleTank::ClientThink to
+ // reconstruct the fire direction.
+
+ // Build local angles.
+ QAngle angles;
+ angles[YAW] = m_flTurretYaw + 90;
+ angles[PITCH] = -m_flTurretPitch;
+ angles[ROLL] = 0;
+
+ // Convert to a forward vector.
+ Vector vForward, vLocalForward;
+ AngleVectors( angles, &vLocalForward );
+
+ // Move the vector to world space.
+ matrix3x4_t tankToWorld;
+ AngleMatrix( GetAbsAngles(), tankToWorld );
+ VectorTransform( vLocalForward, tankToWorld, vForward );
+
+
+ Vector vFireOrigin;
+ QAngle dummy;
+ GetAttachment( LookupAttachment( "muzzle" ), vFireOrigin, dummy );
+
+ // Create the rocket.
+ CWeaponGrenadeRocket *pRocket = CWeaponGrenadeRocket::Create( vFireOrigin, vForward, vehicle_tank_range.GetFloat(), this );
+ if ( pRocket )
+ {
+ pRocket->SetRealOwner( GetDriverPlayer() );
+ pRocket->SetDamage( vehicle_tank_damage.GetFloat() );
+ pRocket->SetDamageRadius( vehicle_tank_radius.GetFloat() );
+
+ // Apply a screen shake.
+ UTIL_ScreenShake( vFireOrigin, 12, 60, 1.5, 512, SHAKE_START, true );
+
+ // BOOM!
+ CPASAttenuationFilter pasAttenuation( this, "VehicleTank.FireSound" );
+ EmitSound( pasAttenuation, entindex(), "VehicleTank.FireSound" );
+ }
+
+ m_flNextFireTime = gpGlobals->curtime + TANK_FIRE_INTERVAL;
+}
+
+
+bool CVehicleTank::ClientCommand( CBaseTFPlayer *pPlayer, const char *pCmd, ICommandArguments *pArg )
+{
+ ResetDeteriorationTime();
+
+ if ( FStrEq( pCmd, "TurretAngles" ) )
+ {
+ m_flClientYaw = atof( pArg->Argv(1) );
+ m_flClientPitch = atof( pArg->Argv(2) );
+ return true;
+ }
+
+ return BaseClass::ClientCommand( pPlayer, pCmd, pArg );
+}
+
+
+bool CVehicleTank::IsPassengerUsingStandardWeapons( int nRole )
+{
+ return ( nRole != VEHICLE_ROLE_DRIVER );
+}
+
+
diff --git a/game/server/tf2/tf_vehicle_tank.h b/game/server/tf2/tf_vehicle_tank.h
new file mode 100644
index 0000000..3d3792c
--- /dev/null
+++ b/game/server/tf2/tf_vehicle_tank.h
@@ -0,0 +1,72 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+#ifndef TF_VEHICLE_TANK_H
+#define TF_VEHICLE_TANK_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+
+#include "tf_basefourwheelvehicle.h"
+#include "vphysics/vehicles.h"
+
+
+// ------------------------------------------------------------------------ //
+// A wagon that players can build one object on the back of
+// ------------------------------------------------------------------------ //
+class CVehicleTank : public CBaseTFFourWheelVehicle
+{
+ DECLARE_CLASS( CVehicleTank, CBaseTFFourWheelVehicle );
+
+public:
+ DECLARE_SERVERCLASS();
+
+ static CVehicleTank* Create(const Vector &vOrigin, const QAngle &vAngles);
+
+ CVehicleTank();
+ virtual ~CVehicleTank();
+
+ virtual void Spawn();
+ virtual void Precache();
+ virtual void GetControlPanelInfo( int nPanelIndex, const char *&pPanelName );
+ virtual bool CanTakeEMPDamage( void ) { return true; }
+ virtual bool ClientCommand( CBaseTFPlayer *pPlayer, const CCommand &args );
+
+ // Vehicle overrides
+ virtual bool IsPassengerVisible( int nRole ) { return true; }
+ virtual bool IsPassengerUsingStandardWeapons( int nRole );
+
+protected:
+ // Here's where we deal with weapons
+ virtual void OnItemPostFrame( CBaseTFPlayer *pDriver );
+
+
+private:
+
+ void TurretThink();
+ void Fire();
+
+
+ // These are the angles where the client currently wants the tank to look.
+ // The server smoothly interpolates to these.
+ // Pitch is 0 when facing straight ahead and 90 when facing straight up.
+ // Yaw is 0 when facing straight ahead and 90 when facing to the tank's left.
+ float m_flClientYaw;
+
+ float m_flClientPitch; // This is the pitch that the client sends - we use it directly.
+
+ // This is the real yaw (which smoothly interpolates towards m_flClientYaw).
+ CNetworkVar( float, m_flTurretYaw );
+ CNetworkVar( float, m_flTurretPitch );
+
+ // Tracks when we can fire the cannon.
+ float m_flNextFireTime;
+};
+
+
+
+#endif // TF_VEHICLE_TANK_H
diff --git a/game/server/tf2/tf_vehicle_teleport_station.cpp b/game/server/tf2/tf_vehicle_teleport_station.cpp
new file mode 100644
index 0000000..2ecdd70
--- /dev/null
+++ b/game/server/tf2/tf_vehicle_teleport_station.cpp
@@ -0,0 +1,414 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Teleport Station vehicle
+//
+//=============================================================================//
+
+#include "cbase.h"
+#include "tf_vehicle_teleport_station.h"
+#include "engine/IEngineSound.h"
+#include "VGuiScreen.h"
+#include "ammodef.h"
+#include "in_buttons.h"
+#include "ndebugoverlay.h"
+#include "IEffects.h"
+#include "info_act.h"
+#include "tf_obj_mcv_selection_panel.h"
+#include "info_vehicle_bay.h"
+
+#define TELEPORT_STATION_MINS Vector(-30, -50, -10)
+#define TELEPORT_STATION_MAXS Vector( 30, 50, 55)
+#define TELEPORT_STATION_MODEL "models/objects/vehicle_teleport_station.mdl"
+#define TELEPORT_STATION_ZONE_HEIGHT 200
+#define TELEPORT_STATION_THINK_CONTEXT "TeleportThink"
+
+BEGIN_DATADESC( CVehicleTeleportStation )
+
+ DEFINE_THINKFUNC( PostTeleportThink ),
+
+END_DATADESC()
+
+IMPLEMENT_SERVERCLASS_ST( CVehicleTeleportStation, DT_VehicleTeleportStation )
+END_SEND_TABLE()
+
+LINK_ENTITY_TO_CLASS( vehicle_teleport_station, CVehicleTeleportStation );
+PRECACHE_REGISTER( vehicle_teleport_station );
+
+// CVars
+ConVar vehicle_teleport_station_health( "vehicle_teleport_station_health","600", FCVAR_NONE, "Siege tower health" );
+
+
+CUtlVector<CVehicleTeleportStation*> g_AllTeleportStations;
+CUtlVector<CVehicleTeleportStation*> CVehicleTeleportStation::s_DeployedTeleportStations;
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CVehicleTeleportStation::CVehicleTeleportStation()
+{
+ g_AllTeleportStations.AddToTail( this );
+
+ //FIXME: we should be able to use clientside animation on this vehicle, but prediction is messing it
+ //up right now.
+ //UseClientSideAnimation();
+}
+
+
+CVehicleTeleportStation::~CVehicleTeleportStation()
+{
+ g_AllTeleportStations.FindAndRemove( this );
+ s_DeployedTeleportStations.FindAndRemove( this );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CVehicleTeleportStation::Spawn()
+{
+ SetModel( TELEPORT_STATION_MODEL );
+
+ // This size is used for placement only...
+ UTIL_SetSize( this, TELEPORT_STATION_MINS, TELEPORT_STATION_MAXS );
+ m_takedamage = DAMAGE_YES;
+ m_iHealth = vehicle_teleport_station_health.GetInt();
+
+ SetType( OBJ_VEHICLE_TELEPORT_STATION );
+ SetMaxPassengerCount( 4 );
+ SetBodygroup( 1, true );
+
+ BaseClass::Spawn();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CVehicleTeleportStation::Precache()
+{
+ PrecacheModel( TELEPORT_STATION_MODEL );
+
+ PrecacheVGuiScreen( "screen_vehicle_bay" );
+
+ PrecacheModel( "sprites/redglow1.vmt" );
+
+ BaseClass::Precache();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CVehicleTeleportStation::UpdateOnRemove( void )
+{
+ RemoveCornerSprites();
+
+ // Chain at end to mimic destructor unwind order
+ BaseClass::UpdateOnRemove();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Gets info about the control panels
+//-----------------------------------------------------------------------------
+void CVehicleTeleportStation::GetControlPanelInfo( int nPanelIndex, const char *&pPanelName )
+{
+ pPanelName = "screen_vehicle_bay";
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CVehicleTeleportStation::GetControlPanelClassName( int nPanelIndex, const char *&pPanelName )
+{
+ pPanelName = "vgui_screen_vehicle_bay";
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Screens aren't active until deployed
+//-----------------------------------------------------------------------------
+void CVehicleTeleportStation::FinishedBuilding( void )
+{
+ BaseClass::FinishedBuilding();
+
+ SetControlPanelsActive( false );
+}
+
+//-----------------------------------------------------------------------------
+// Here's where we deal with weapons, ladders, etc.
+//-----------------------------------------------------------------------------
+void CVehicleTeleportStation::OnItemPostFrame( CBaseTFPlayer *pDriver )
+{
+ // I can't do anything if I'm not active
+ if ( !ShouldBeActive() )
+ return;
+
+ if ( GetPassengerRole( pDriver ) != VEHICLE_ROLE_DRIVER )
+ return;
+
+ if ( !IsDeployed() && ( pDriver->m_afButtonPressed & IN_ATTACK ) )
+ {
+ if ( ValidDeployPosition() )
+ {
+ Deploy();
+ }
+ }
+ else if ( IsDeployed() && ( pDriver->m_afButtonPressed & IN_ATTACK ) )
+ {
+ UnDeploy();
+
+ SetControlPanelsActive( false );
+ SetBodygroup( 1, true );
+ RemoveCornerSprites();
+ SetContextThink( NULL, 0, TELEPORT_STATION_THINK_CONTEXT );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Finished our deploy
+//-----------------------------------------------------------------------------
+void CVehicleTeleportStation::OnFinishedDeploy( void )
+{
+ BaseClass::OnFinishedDeploy();
+
+ ValidDeployPosition();
+ SetBodygroup( 1, false );
+
+ // Find the teleport in the mothership
+ CFuncMassTeleport *pTeleporter = NULL;
+ while ( (pTeleporter = (CFuncMassTeleport*)gEntList.FindEntityByClassname( pTeleporter, "func_mass_teleport" )) != NULL )
+ {
+ if ( pTeleporter->IsMCVTeleport() )
+ {
+ m_hTeleportExit = pTeleporter;
+ m_hTeleportExit->AddMCV( this );
+ break;
+ }
+ }
+
+ // Put some flares down to mark the teleport zone
+ Vector vecOrigin;
+ QAngle vecAngles;
+ for ( int i = 0; i < 4; i++ )
+ {
+ char buf[64];
+ Q_snprintf( buf, sizeof( buf ), "teleport_corner%d", i+1 );
+ if ( GetAttachment( buf, vecOrigin, vecAngles ) )
+ {
+ CheckBuildPoint( vecOrigin + Vector(0,0,64), Vector(0,0,128), &vecOrigin );
+ m_hTeleportCornerSprites[i] = CSprite::SpriteCreate( "sprites/redglow1.vmt", vecOrigin, false );
+ m_hTeleportCornerSprites[i]->SetTransparency( kRenderGlow, 255, 255, 255, 255, kRenderFxNoDissipation );
+ m_hTeleportCornerSprites[i]->SetScale( 0.1 );
+ }
+ }
+ // Get the ray point
+ if ( GetAttachment( "muzzle", vecOrigin, vecAngles ) )
+ {
+ m_hTeleportCornerSprites[4] = CSprite::SpriteCreate( "sprites/redglow1.vmt", vecOrigin, false );
+ m_hTeleportCornerSprites[4]->SetTransparency( kRenderGlow, 255, 255, 255, 255, kRenderFxNoDissipation );
+ m_hTeleportCornerSprites[4]->SetScale( 0.1 );
+ }
+
+ SetContextThink( PostTeleportThink, gpGlobals->curtime + 0.1, TELEPORT_STATION_THINK_CONTEXT );
+
+ // Add ourselves to the list of deployed MCVs.
+ s_DeployedTeleportStations.AddToTail( this );
+
+ // Set our vehicle bay screen's buildpoint
+ for ( i = m_hScreens.Count(); --i >= 0; )
+ {
+ if (m_hScreens[i].Get())
+ {
+ CVGuiScreenVehicleBay *pScreen = dynamic_cast<CVGuiScreenVehicleBay*>( m_hScreens[0].Get() );
+ if ( pScreen )
+ {
+ Vector vecOrigin;
+ QAngle vecAngles;
+ if ( GetAttachment( "vehiclebay", vecOrigin, vecAngles ) )
+ {
+ pScreen->SetBuildPoint( vecOrigin, vecAngles );
+ }
+ }
+ }
+ }
+
+ SetControlPanelsActive( true );
+
+ SignalChangeInMCVSelectionPanels();
+}
+
+
+void CVehicleTeleportStation::OnFinishedUnDeploy( void )
+{
+ BaseClass::OnFinishedUnDeploy();
+
+ s_DeployedTeleportStations.FindAndRemove( this );
+ SignalChangeInMCVSelectionPanels();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CVehicleTeleportStation::RemoveCornerSprites( void )
+{
+ // Remove all the corner sprites
+ for ( int i = 0; i < 5; i++ )
+ {
+ if ( m_hTeleportCornerSprites[i] )
+ {
+ UTIL_Remove( m_hTeleportCornerSprites[i] );
+ }
+ }
+
+ // Tell the exit we're done
+ if ( m_hTeleportExit )
+ {
+ m_hTeleportExit->RemoveMCV( this );
+ m_hTeleportExit = NULL;
+ }
+}
+
+
+int CVehicleTeleportStation::GetNumDeployedTeleportStations()
+{
+ return s_DeployedTeleportStations.Count();
+}
+
+
+CVehicleTeleportStation* CVehicleTeleportStation::GetDeployedTeleportStation( int i )
+{
+ return s_DeployedTeleportStations[i];
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Return true if the truck's in a valid position
+//-----------------------------------------------------------------------------
+bool CVehicleTeleportStation::ValidDeployPosition( void )
+{
+ // Setup for teleporting
+ QAngle vecAngles;
+ if ( !GetAttachment( "teleport_corner1", m_vecTeleporterMins, vecAngles ) || !GetAttachment( "teleport_corner4", m_vecTeleporterMaxs, vecAngles ) )
+ return false;
+
+ m_vecTeleporterMaxs.z += TELEPORT_STATION_ZONE_HEIGHT;
+
+ // Make sure we've got the right mins & maxs
+ for ( int i = 0; i < 3; i++ )
+ {
+ if ( m_vecTeleporterMaxs[i] < m_vecTeleporterMins[i] )
+ {
+ float flVal = m_vecTeleporterMaxs[i];
+ m_vecTeleporterMaxs[i] = m_vecTeleporterMins[i];
+ m_vecTeleporterMins[i] = flVal;
+ }
+ }
+
+ Vector vecDelta = (m_vecTeleporterMaxs - m_vecTeleporterMins) * 0.5;
+ Vector vecEnd = (m_vecTeleporterMaxs + m_vecTeleporterMins) * 0.5;
+ Vector vecSrc = vecEnd + Vector(0,0,TELEPORT_STATION_ZONE_HEIGHT * 0.5);
+
+ // Make sure it's clear
+ // Take the hull, start it high, and try and trace down
+ trace_t tr;
+ UTIL_TraceHull( vecSrc, vecEnd, -vecDelta, vecDelta, MASK_SOLID, this, COLLISION_GROUP_PLAYER_MOVEMENT, &tr );
+
+ //NDebugOverlay::Box( m_vecTeleporterMins, Vector(-2,-2,-2), Vector(2,2,2), 0,255,0,8, 0.1 );
+ //NDebugOverlay::Box( m_vecTeleporterMaxs, Vector(-2,-2,-2), Vector(2,2,2), 0,255,0,8, 0.1 );
+ //NDebugOverlay::Box( vecSrc, -vecDelta, vecDelta, 255,0,0,8, 0.1 );
+ //NDebugOverlay::Box( vecEnd, -vecDelta, vecDelta, 0,0,255,8, 0.1 );
+ //NDebugOverlay::Line( m_vecTeleporterMins, m_vecTeleporterMaxs, 255,255,255, 1, 0.1 );
+
+ // Make sure it's clear
+ if ( tr.startsolid )
+ return false;
+
+ // Get a list of nearby entities
+ CBaseEntity *pListOfNearbyEntities[100];
+ int iNumberOfNearbyEntities = UTIL_EntitiesInBox( pListOfNearbyEntities, 100, m_vecTeleporterMins, m_vecTeleporterMaxs, MASK_ALL );
+ for ( i = 0; i < iNumberOfNearbyEntities; i++ )
+ {
+ CBaseEntity *pEntity = pListOfNearbyEntities[i];
+ if ( pEntity->IsSolid( ) )
+ {
+ if ( pEntity == this )
+ continue;
+ if ( pEntity->GetCollisionGroup() == TFCOLLISION_GROUP_SHIELD )
+ continue;
+ if ( pEntity->IsPlayer() )
+ continue;
+
+ //NDebugOverlay::EntityBounds( pEntity, 0,255,0,8, 0.1 );
+ return false;
+ }
+ }
+
+ m_vecTeleporterCenter = vecEnd;
+ m_vecTeleporterCenter.z = m_vecTeleporterMins.z;
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CVehicleTeleportStation::DoTeleport( void )
+{
+ ResetDeteriorationTime();
+
+ if ( m_hTeleportExit )
+ {
+ // Teleport all players attached to me that are within my bbox.
+ EHANDLE hMCV;
+ hMCV = this;
+ Vector vecAbsMins, vecAbsMaxs;
+ m_hTeleportExit->CollisionProp()->WorldSpaceAABB( &vecAbsMins, &vecAbsMaxs );
+ m_hTeleportExit->DoTeleport( GetTFTeam(), vecAbsMins, vecAbsMaxs, m_vecTeleporterCenter, true, true, hMCV );
+ }
+
+ // Shrink the corner sprites
+ for ( int i = 0; i < 5; i++ )
+ {
+ if ( m_hTeleportCornerSprites[i] )
+ {
+ m_hTeleportCornerSprites[i]->SetScale( 0.1 );
+ }
+ }
+
+ SetContextThink( PostTeleportThink, gpGlobals->curtime + 0.1, TELEPORT_STATION_THINK_CONTEXT );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CVehicleTeleportStation::PostTeleportThink( void )
+{
+ float flTime = 10.0;
+ if ( g_hCurrentAct )
+ {
+ flTime = g_hCurrentAct->GetMCVTimer();
+ }
+
+ // Start the corner sprites up
+ for ( int i = 0; i < 4; i++ )
+ {
+ if ( m_hTeleportCornerSprites[i] )
+ {
+ m_hTeleportCornerSprites[i]->SetScale( 0.75, flTime );
+ }
+ }
+ m_hTeleportCornerSprites[4]->SetScale( 2, flTime );
+}
+
+
+// Returns INVALID_MCV_ID if there are no deployed MCVs.
+CVehicleTeleportStation* CVehicleTeleportStation::GetFirstDeployedMCV( int iTeam )
+{
+ for ( int i=0; i < s_DeployedTeleportStations.Count(); i++ )
+ {
+ CVehicleTeleportStation *pStation = s_DeployedTeleportStations[i];
+
+ if ( pStation->GetTeamNumber() == iTeam )
+ return pStation;
+ }
+ return NULL;
+}
+
diff --git a/game/server/tf2/tf_vehicle_teleport_station.h b/game/server/tf2/tf_vehicle_teleport_station.h
new file mode 100644
index 0000000..78f593f
--- /dev/null
+++ b/game/server/tf2/tf_vehicle_teleport_station.h
@@ -0,0 +1,82 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Teleport Station Vehicle
+//
+//=============================================================================//
+
+#ifndef TF_VEHICLE_TELEPORT_STATION_H
+#define TF_VEHICLE_TELEPORT_STATION_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include "tf_basefourwheelvehicle.h"
+#include "vphysics/vehicles.h"
+#include "Sprite.h"
+#include "tf_func_mass_teleport.h"
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Teleport Station Vehicle
+//-----------------------------------------------------------------------------
+class CVehicleTeleportStation : public CBaseTFFourWheelVehicle
+{
+ DECLARE_CLASS( CVehicleTeleportStation, CBaseTFFourWheelVehicle );
+
+public:
+
+ DECLARE_SERVERCLASS();
+ DECLARE_DATADESC();
+
+ static CVehicleTeleportStation *Create( const Vector &vOrigin, const QAngle &vAngles );
+
+ CVehicleTeleportStation();
+ virtual ~CVehicleTeleportStation();
+
+ void Spawn();
+ void Precache();
+ void UpdateOnRemove( void );
+ void GetControlPanelInfo( int nPanelIndex, const char *&pPanelName );
+ void GetControlPanelClassName( int nPanelIndex, const char *&pPanelName );
+ bool CanTakeEMPDamage( void ) { return true; }
+ virtual float GetMaxSnapDistance( int iPoint ) { return 192; }
+ virtual void FinishedBuilding( void );
+
+ bool ValidDeployPosition( void );
+ void OnFinishedDeploy( void );
+ void OnFinishedUnDeploy( void );
+ void DoTeleport( void );
+ void PostTeleportThink( void );
+ void RemoveCornerSprites( void );
+
+ // Vehicle overrides
+ bool IsPassengerVisible( int nRole ) { return true; }
+
+ static int GetNumDeployedTeleportStations();
+ static CVehicleTeleportStation* GetDeployedTeleportStation( int i );
+
+ // Returns INVALID_MCV_ID if there are no deployed MCVs.
+ static CVehicleTeleportStation* GetFirstDeployedMCV( int iTeam );
+
+
+protected:
+
+ // Here's where we deal with weapons
+ void OnItemPostFrame( CBaseTFPlayer *pDriver );
+
+private:
+
+ static CUtlVector<CVehicleTeleportStation*> s_DeployedTeleportStations;
+
+ Vector m_vecTeleporterMins;
+ Vector m_vecTeleporterMaxs;
+ Vector m_vecTeleporterCenter;
+
+ CHandle<CFuncMassTeleport> m_hTeleportExit;
+ CHandle<CSprite> m_hTeleportCornerSprites[5];
+};
+
+
+
+
+#endif // TF_VEHICLE_TELEPORT_STATION_H
diff --git a/game/server/tf2/tf_vehicle_wagon.cpp b/game/server/tf2/tf_vehicle_wagon.cpp
new file mode 100644
index 0000000..0bd916b
--- /dev/null
+++ b/game/server/tf2/tf_vehicle_wagon.cpp
@@ -0,0 +1,80 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: A moving vehicle that is used as a battering ram
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#include "cbase.h"
+#include "tf_vehicle_wagon.h"
+#include "engine/IEngineSound.h"
+#include "VGuiScreen.h"
+
+
+#define WAGON_MINS Vector(-30, -50, -10)
+#define WAGON_MAXS Vector( 30, 50, 55)
+#define WAGON_MODEL "models/objects/vehicle_wagon.mdl"
+
+
+IMPLEMENT_SERVERCLASS_ST(CVehicleWagon, DT_VehicleWagon)
+END_SEND_TABLE();
+
+LINK_ENTITY_TO_CLASS(vehicle_wagon, CVehicleWagon);
+PRECACHE_REGISTER(vehicle_wagon);
+
+// CVars
+ConVar vehicle_wagon_health( "vehicle_wagon_health","800", FCVAR_NONE, "Wagon health" );
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CVehicleWagon::CVehicleWagon()
+{
+
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CVehicleWagon::Precache()
+{
+ PrecacheModel( WAGON_MODEL );
+
+ PrecacheVGuiScreen( "screen_vehicle_wagon" );
+ PrecacheVGuiScreen( "screen_vulnerable_point");
+
+ BaseClass::Precache();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CVehicleWagon::Spawn()
+{
+ SetModel( WAGON_MODEL );
+
+ // This size is used for placement only...
+ UTIL_SetSize(this, WAGON_MINS, WAGON_MAXS);
+ m_takedamage = DAMAGE_YES;
+ m_iHealth = vehicle_wagon_health.GetInt();
+
+ SetType( OBJ_WAGON );
+ SetMaxPassengerCount( 4 );
+
+ BaseClass::Spawn();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Gets info about the control panels
+//-----------------------------------------------------------------------------
+void CVehicleWagon::GetControlPanelInfo( int nPanelIndex, const char *&pPanelName )
+{
+ //pPanelName = "screen_vehicle_wagon";
+ pPanelName = "screen_vulnerable_point";
+}
+
+
diff --git a/game/server/tf2/tf_vehicle_wagon.h b/game/server/tf2/tf_vehicle_wagon.h
new file mode 100644
index 0000000..e1ba433
--- /dev/null
+++ b/game/server/tf2/tf_vehicle_wagon.h
@@ -0,0 +1,42 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: A stationary gun that players can man
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef TF_VEHICLE_WAGON_H
+#define TF_VEHICLE_WAGON_H
+
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include "tf_basefourwheelvehicle.h"
+#include "vphysics/vehicles.h"
+
+
+// ------------------------------------------------------------------------ //
+// A wagon that players can build one object on the back of
+// ------------------------------------------------------------------------ //
+class CVehicleWagon: public CBaseTFFourWheelVehicle
+{
+ DECLARE_CLASS( CVehicleWagon, CBaseTFFourWheelVehicle );
+
+public:
+ DECLARE_SERVERCLASS();
+
+ static CVehicleWagon* Create(const Vector &vOrigin, const QAngle &vAngles);
+
+ CVehicleWagon();
+
+ virtual void Spawn();
+ virtual void Precache();
+ virtual void GetControlPanelInfo( int nPanelIndex, const char *&pPanelName );
+ virtual bool CanTakeEMPDamage( void ) { return true; }
+
+ // Vehicle overrides
+ virtual bool IsPassengerVisible( int nRole ) { return true; }
+};
+
+#endif // TF_VEHICLE_WAGON_H
diff --git a/game/server/tf2/tf_walker_base.cpp b/game/server/tf2/tf_walker_base.cpp
new file mode 100644
index 0000000..7fa3711
--- /dev/null
+++ b/game/server/tf2/tf_walker_base.cpp
@@ -0,0 +1,302 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+#include "cbase.h"
+#include "tf_walker_base.h"
+
+
+#include "in_buttons.h"
+#include "shake.h"
+
+
+static float MAX_WALKER_VEL = 100;
+
+
+IMPLEMENT_SERVERCLASS_ST( CWalkerBase, DT_WalkerBase )
+END_SEND_TABLE()
+
+
+CWalkerBase::CWalkerBase()
+{
+ m_vSteerVelocity.Init();
+ m_iMovePoseParamX = -1;
+ m_iMovePoseParamY = -1;
+ m_bWalkMode = false;
+ m_flDontMakeSoundsUntil = 0;
+ m_flPlaybackSpeedBoost = 1;
+ m_flVelocityDecayRate = 80;
+ m_LastButtons = 0;
+ m_vLastCmdViewAngles.Init();
+}
+
+
+void CWalkerBase::SpawnWalker(
+ const char *pModelName,
+ int objectType,
+ const Vector &vPlacementMins,
+ const Vector &vPlacementMaxs,
+ int iHealth,
+ int nMaxPassengers,
+ float flPlaybackSpeedBoost
+ )
+{
+ SetModel( pModelName );
+ SetType( objectType );
+
+ UTIL_SetSize( this, vPlacementMins, vPlacementMaxs );
+ m_iHealth = iHealth;
+ m_flPlaybackSpeedBoost = flPlaybackSpeedBoost;
+
+ m_takedamage = DAMAGE_YES;
+ SetMaxPassengerCount( nMaxPassengers );
+
+
+
+ // The model should be set before the derived class calls our Spawn().
+ Assert( GetModel() );
+
+ // By default, all walkers use the walk_box animation as they move.
+ m_iMovePoseParamX = LookupPoseParameter( "move_x" );
+ m_iMovePoseParamY = LookupPoseParameter( "move_y" );
+ EnableWalkMode( true );
+
+ // The base class spawn sets a default collision group, so this needs to
+ // be called post.
+ SetCollisionGroup( COLLISION_GROUP_VEHICLE );
+
+ BaseClass::Spawn();
+
+
+ // HACKHACK: this is just so CBaseObject doesn't call StudioFrameAdvance for us. We should probably have
+ // a specific flag for this behavior.
+ m_fObjectFlags |= OF_DOESNT_HAVE_A_MODEL;
+ m_fObjectFlags &= ~OF_MUST_BE_BUILT_ON_ATTACHMENT;
+
+
+ // We animate, so let's not use manual mode for now.
+ SetMoveType( MOVETYPE_STEP );
+ AddSolidFlags( FSOLID_CUSTOMRAYTEST | FSOLID_CUSTOMBOXTEST );
+
+ EnableServerIK();
+
+ SetContextThink( WalkerThink, gpGlobals->curtime, "WalkerThink" );
+}
+
+void CWalkerBase::EnableWalkMode( bool bEnable )
+{
+ m_bWalkMode = bEnable;
+
+ // Stop any movement..
+ m_vSteerVelocity.Init();
+
+ if ( bEnable )
+ {
+ ResetSequence( LookupSequence( "walk_box" ) );
+
+ // HACK: there should be a better way to this.. like CBaseAnimating::ResetAnimation,
+ // or ResetSequence should do it.
+ SetCycle( 0 );
+ }
+}
+
+
+void CWalkerBase::AdjustInitialBuildAngles()
+{
+ QAngle vNewAngles = GetAbsAngles();
+ vNewAngles[YAW] += 90;
+ SetAbsAngles( vNewAngles );
+}
+
+
+void CWalkerBase::WalkerThink()
+{
+ float dt = GetTimeDelta();
+
+ // Decay our velocity.
+ if ( m_bWalkMode )
+ {
+ m_flPlaybackRate = m_flPlaybackSpeedBoost;
+
+
+ float flDecayRate = m_flVelocityDecayRate;
+
+ float flLen = m_vSteerVelocity.Length();
+ Vector2DNormalize( m_vSteerVelocity );
+
+ float flDecayAmt = flDecayRate * dt;
+ flLen = MAX( 0, flLen - flDecayAmt );
+ m_vSteerVelocity *= flLen;
+
+
+ // Setup our pose parameters.
+ SetPoseParameter( m_iMovePoseParamX, RemapVal( m_vSteerVelocity.x, -MAX_WALKER_VEL, MAX_WALKER_VEL, -1, 1 ) );
+ SetPoseParameter( m_iMovePoseParamY, RemapVal( m_vSteerVelocity.y, -MAX_WALKER_VEL, MAX_WALKER_VEL, -1, 1 ) );
+
+ // Use an idle animation if they're not moving.
+ int iWantedSequence = LookupSequence( "walk_box" );
+ if ( m_vSteerVelocity.x == 0 && m_vSteerVelocity.y == 0 )
+ {
+ iWantedSequence = LookupSequence( "idle" );
+
+ // HACK: HL2 Strider has no idle
+ if ( iWantedSequence == -1 )
+ iWantedSequence = LookupSequence( "ragdoll" );
+ }
+
+ if ( iWantedSequence != -1 && GetSequence() != iWantedSequence )
+ ResetSequence( iWantedSequence );
+ }
+
+
+ // Now ask the model how far it thought it moved based on the animation.
+ // Turns out the animation thinks it's moving just a tiny bit, even when we're centered on the idle animation,
+ // so we just force it not to move here if we know we're not supposed to move.
+ if ( m_vSteerVelocity.Length() > 0 )
+ {
+ Vector vNewPos = GetWalkerLocalMovement();
+
+ SetLocalOrigin( vNewPos );
+ }
+
+
+ // Hard-coded for now. These should come from the vehicle's script eventually.
+ // Now slowly rotate towards the player's eye angles.
+ CBasePlayer *pPlayer = GetPassenger( VEHICLE_ROLE_DRIVER );
+ if ( pPlayer )
+ {
+ static float flAccelRate = 180;
+ static float flRotateRate = 60;
+
+
+ // Figure out a force to apply to our current velocity.
+ Vector2D vAccel( 0, 0 );
+
+ if ( m_LastButtons & IN_FORWARD )
+ vAccel.x += flAccelRate;
+
+ if ( m_LastButtons & IN_BACK )
+ vAccel.x -= flAccelRate;
+
+ if ( m_LastButtons & IN_MOVELEFT )
+ vAccel.y -= flAccelRate;
+
+ if ( m_LastButtons & IN_MOVERIGHT )
+ vAccel.y += flAccelRate;
+
+ m_vSteerVelocity += vAccel * dt;
+
+
+ m_vSteerVelocity.x = clamp( m_vSteerVelocity.x, -MAX_WALKER_VEL, MAX_WALKER_VEL );
+ m_vSteerVelocity.y = clamp( m_vSteerVelocity.y, -MAX_WALKER_VEL, MAX_WALKER_VEL );
+
+
+ float wantedYaw = m_vLastCmdViewAngles[YAW];
+ QAngle curAngles = GetAbsAngles();
+ curAngles[YAW] = ApproachAngle( wantedYaw, curAngles[YAW], flRotateRate * dt );
+ SetAbsAngles( curAngles );
+ }
+
+
+ DispatchAnimEvents( this );
+
+
+ // Get another think.
+ SetContextThink( WalkerThink, gpGlobals->curtime + dt, "WalkerThink" );
+}
+
+
+Vector CWalkerBase::GetWalkerLocalMovement()
+{
+ bool bIgnored;
+ Vector vNewPos;
+ QAngle vNewAngles;
+ GetIntervalMovement( GetAnimTimeInterval(), bIgnored, vNewPos, vNewAngles );
+ return vNewPos;
+}
+
+
+const Vector2D& CWalkerBase::GetSteerVelocity() const
+{
+ return m_vSteerVelocity;
+}
+
+
+
+void CWalkerBase::Spawn()
+{
+ // Derived classes should call SpawnWalker instead of chaining down to CWalkerBase::Spawn().
+ Assert( false );
+}
+
+
+void CWalkerBase::Activate()
+{
+ WalkerActivate();
+ BaseClass::Activate();
+}
+
+void CWalkerBase::WalkerActivate( void )
+{
+ // Until we're finished building, turn off vphysics-based motion
+ SetSolid( SOLID_VPHYSICS );
+ VPhysicsInitStatic();
+
+ SetPoseParameter( m_iMovePoseParamX, 0 );
+ SetPoseParameter( m_iMovePoseParamY, 0 );
+}
+
+
+void CWalkerBase::SetVelocityDecayRate( float flDecayRate )
+{
+ m_flVelocityDecayRate = flDecayRate;
+}
+
+
+float CWalkerBase::GetTimeDelta() const
+{
+ return 0.1;
+}
+
+
+void CWalkerBase::SetupMove( CBasePlayer *pPlayer, CUserCmd *ucmd, IMoveHelper *pHelper, CMoveData *move )
+{
+ // This calls StudioFrameAdvance for us.
+ //BaseClass::SetupMove( pPlayer, ucmd, pHelper, move );
+
+ // Lose control when the player dies
+ if ( pPlayer->IsAlive() == false )
+ {
+ m_LastButtons = 0;
+ return;
+ }
+
+ // Only the driver gets to drive.
+ int nRole = GetPassengerRole( pPlayer );
+ if ( nRole != VEHICLE_ROLE_DRIVER )
+ return;
+
+ m_LastButtons = ucmd->buttons;
+ m_vLastCmdViewAngles = ucmd->viewangles;
+}
+
+
+bool CWalkerBase::IsPassengerVisible( int nRole )
+{
+ return true;
+}
+
+
+bool CWalkerBase::StartBuilding( CBaseEntity *pBuilder )
+{
+ if ( !BaseClass::StartBuilding( pBuilder ) )
+ return false;
+
+ WalkerActivate();
+ return true;
+}
+
+
+
diff --git a/game/server/tf2/tf_walker_base.h b/game/server/tf2/tf_walker_base.h
new file mode 100644
index 0000000..269606c
--- /dev/null
+++ b/game/server/tf2/tf_walker_base.h
@@ -0,0 +1,115 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+#ifndef TF_WALKER_BASE_H
+#define TF_WALKER_BASE_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+
+#include "basetfvehicle.h"
+
+
+class CWalkerBase : public CBaseTFVehicle
+{
+public:
+ DECLARE_CLASS( CWalkerBase, CBaseTFVehicle );
+ DECLARE_SERVERCLASS();
+
+
+public:
+ CWalkerBase();
+
+ // Derived classes must call this from inside their Spawn() function. Their Spawn() function
+ // should NOT chain down to CWalkerBase.
+ void SpawnWalker(
+ const char *pModelName,
+ int objectType,
+ const Vector &vPlacementMins,
+ const Vector &vPlacementMaxs,
+ int iHealth,
+ int nMaxPassengers,
+ float flPlaybackSpeedBoost // Strider likes the animations played at 2x speed.
+ );
+
+ virtual void AdjustInitialBuildAngles();
+
+ // When it's in walk mode, it'll set a walk animation, process user input, set pose parameters,
+ // and move the entity around as it walks.
+ void EnableWalkMode( bool bEnable );
+
+ // This is called each frame to do thinking. Derived classes can hook this to do their own stuff each frame.
+ virtual void WalkerThink();
+
+ // Returns the new local origin to set based on the walker's movement.
+ // This usually just asks for the movement from the animation, but some walkers may
+ // want to generate the motion in code.
+ virtual Vector GetWalkerLocalMovement();
+
+ // See notes on m_vSteerVelocity below.
+ const Vector2D& GetSteerVelocity() const;
+
+ void WalkerActivate( void );
+
+ // See m_flVelocityDecayRate.
+ void SetVelocityDecayRate( float flDecayRate );
+
+ // Walkers think 10x/second.
+ float GetTimeDelta() const;
+
+// CBaseEntity.
+public:
+ virtual void Spawn();
+ virtual void Activate();
+
+// IVehicle overrides.
+public:
+ virtual void SetupMove( CBasePlayer *player, CUserCmd *ucmd, IMoveHelper *pHelper, CMoveData *move );
+
+
+// IServerVehicle overrides.
+public:
+ virtual bool IsPassengerVisible( int nRole );
+
+
+// CBaseObject overrides.
+public:
+ virtual bool StartBuilding( CBaseEntity *pBuilder );
+
+
+public:
+
+ // This is a hack to prevent the strider from playing a lot of footstep sounds, until we figure out 9-way anim events
+ float m_flDontMakeSoundsUntil;
+
+ // Last usercmd buttons.
+ int m_LastButtons;
+ QAngle m_vLastCmdViewAngles;
+
+
+private:
+
+ // This is the (local) velocity that the player's controls are saying the player wants to move in.
+ // Note that it is completely virtual - the speed that the walker moves at is gotten from the animations ground speed.
+ // It maps this velocity a 9-way blend for movement. +X = forward, +Y = left.
+ Vector2D m_vSteerVelocity;
+
+ // Which pose parameters we use to make this guy walk around on X and Y.
+ int m_iMovePoseParamX;
+ int m_iMovePoseParamY;
+
+ float m_flPlaybackSpeedBoost;
+
+ bool m_bWalkMode;
+
+ // This is a magic number that can be tweaked to make the velocity decay faster.
+ // Default: 80
+ float m_flVelocityDecayRate;
+};
+
+
+#endif // TF_WALKER_BASE_H
diff --git a/game/server/tf2/tf_walker_ministrider.cpp b/game/server/tf2/tf_walker_ministrider.cpp
new file mode 100644
index 0000000..3bc406a
--- /dev/null
+++ b/game/server/tf2/tf_walker_ministrider.cpp
@@ -0,0 +1,515 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+#include "cbase.h"
+#include "tf_walker_ministrider.h"
+#include "in_buttons.h"
+#include "studio.h"
+#include "shake.h"
+#include "tf_gamerules.h"
+#include "npcevent.h"
+#include "ndebugoverlay.h"
+#include "te_effect_dispatch.h"
+#include "beam_shared.h"
+#include "ai_activity.h"
+
+#define WALKER_MINI_STRIDER_MODEL "models/objects/alien_vehicle_strider.mdl"
+#define STRIDER_TORSO_VERTICAL_SLIDE_SPEED 100
+
+float LARGE_GUN_FIRE_TIME = 3;
+
+// Skirmisher CVars
+ConVar tf_skirmisher_speed( "tf_skirmisher_speed", "300" );
+ConVar tf_skirmisher_machinegun_range( "tf_skirmisher_machinegun_range", "2000" );
+ConVar tf_skirmisher_machinegun_damage( "tf_skirmisher_machinegun_damage", "20" );
+ConVar tf_skirmisher_machinegun_rof( "tf_skirmisher_machinegun_rof", "10" );
+
+IMPLEMENT_SERVERCLASS_ST( CWalkerMiniStrider, DT_WalkerMiniStrider )
+END_SEND_TABLE()
+
+LINK_ENTITY_TO_CLASS( walker_mini_strider, CWalkerMiniStrider );
+PRECACHE_REGISTER( walker_mini_strider );
+
+
+
+CWalkerMiniStrider::CWalkerMiniStrider()
+{
+ m_flWantedZ = -1;
+ m_bFiringMachineGun = m_bFiringLargeGun = false;
+ m_flOriginToLowestLegHeight = 133;
+ m_State = STATE_NORMAL;
+ m_bFiringLeftGun = true;
+ m_flNextFootstepSoundTime = 0;
+}
+
+
+CWalkerMiniStrider::~CWalkerMiniStrider()
+{
+ UTIL_Remove( m_pEnergyBeam );
+}
+
+
+void CWalkerMiniStrider::Precache()
+{
+ PrecacheModel( WALKER_MINI_STRIDER_MODEL );
+
+ PrecacheScriptSound( "Skirmisher.Footstep" );
+ PrecacheScriptSound( "Skirmisher.GunSound" );
+ PrecacheScriptSound( "Skirmisher.GunChargeSound" );
+
+ BaseClass::Precache();
+}
+
+
+void CWalkerMiniStrider::Spawn()
+{
+ SpawnWalker(
+ WALKER_MINI_STRIDER_MODEL, // Model name.
+ OBJ_WALKER_MINI_STRIDER, // Object type.
+ Vector( 0, 0, 140 ) + Vector( -100, -100, -100 ), // Placement dimensions.
+ Vector( 0, 0, 140 ) + Vector( 100, 100, 100 ),
+ 200, // Health.
+ 1, // Max passengers.
+ 1.0 // 1x speed multiplier
+ );
+
+ SetVelocityDecayRate( 110 );
+}
+
+
+bool CWalkerMiniStrider::IsPassengerVisible( int nRole )
+{
+ if ( nRole == VEHICLE_ROLE_DRIVER )
+ return false;
+
+ return true;
+}
+
+
+void CWalkerMiniStrider::StartFiringMachineGun()
+{
+ StopFiringLargeGun();
+ m_bFiringMachineGun = true;
+ m_flNextShootTime = gpGlobals->curtime;
+}
+
+
+void CWalkerMiniStrider::StopFiringMachineGun()
+{
+ m_bFiringMachineGun = false;
+}
+
+
+Vector CWalkerMiniStrider::GetLargeGunShootOrigin()
+{
+ Vector vRet;
+ QAngle dummyAngle;
+ if ( GetAttachment( LookupAttachment( "LargeGun" ), vRet, dummyAngle ) )
+ {
+ return vRet;
+ }
+ else
+ {
+ return GetAbsOrigin();
+ }
+}
+
+
+void CWalkerMiniStrider::StartFiringLargeGun()
+{
+ CBasePlayer *pPlayer = GetPassenger( VEHICLE_ROLE_DRIVER );
+ Assert( pPlayer );
+ if ( !pPlayer )
+ return;
+
+ // Figure out what we're shooting at.
+ Vector vSrc = GetLargeGunShootOrigin();
+
+ Vector vEyePos = pPlayer->EyePosition();
+ Vector vEyeForward;
+ AngleVectors( pPlayer->LocalEyeAngles(), &vEyeForward );
+
+ trace_t trace;
+ UTIL_TraceLine( vEyePos, vEyePos + vEyeForward * 2000, MASK_SOLID, this, COLLISION_GROUP_NONE, &trace );
+ if ( trace.fraction < 1 )
+ {
+ m_vLargeGunForward = trace.endpos - vSrc;
+ VectorNormalize( m_vLargeGunForward );
+
+ trace_t trace;
+ UTIL_TraceLine( vSrc, vSrc + m_vLargeGunForward * 2000, MASK_SOLID, this, COLLISION_GROUP_NONE, &trace );
+ if ( trace.fraction < 1 )
+ {
+ EnableWalkMode( false );
+
+ m_vLargeGunTargetPos = trace.endpos;
+ m_flLargeGunCountdown = LARGE_GUN_FIRE_TIME;
+ m_bFiringLargeGun = true;
+
+ // Show an energy beam until we actually shoot.
+ m_pEnergyBeam = CBeam::BeamCreate( "sprites/physbeam.vmt", 25 );
+ m_pEnergyBeam->SetColor( 255, 0, 0 );
+ m_pEnergyBeam->SetBrightness( 100 );
+ m_pEnergyBeam->SetNoise( 4 );
+ m_pEnergyBeam->PointsInit( vSrc, m_vLargeGunTargetPos );
+ m_pEnergyBeam->LiveForTime( LARGE_GUN_FIRE_TIME );
+
+ // Play a charge-up sound.
+ CPASAttenuationFilter filter( this, "Skirmisher.GunChargeSound" );
+ EmitSound( filter, 0, "Skirmisher.GunChargeSound", &vSrc );
+ }
+ }
+}
+
+
+void CWalkerMiniStrider::StopFiringLargeGun()
+{
+ if ( m_bFiringLargeGun )
+ {
+ m_bFiringLargeGun = false;
+
+ UTIL_Remove( m_pEnergyBeam );
+ m_pEnergyBeam = NULL;
+
+ EnableWalkMode( true );
+ }
+}
+
+
+void CWalkerMiniStrider::UpdateLargeGun()
+{
+ float dt = GetTimeDelta();
+
+ if ( !m_bFiringLargeGun )
+ return;
+
+ m_flLargeGunCountdown -= dt;
+ if ( m_flLargeGunCountdown <= 0 )
+ {
+ // Fire!
+ Vector vSrc = GetLargeGunShootOrigin();
+ trace_t trace;
+ UTIL_TraceLine( vSrc, vSrc + m_vLargeGunForward * 2000, MASK_SOLID, this, COLLISION_GROUP_NONE, &trace );
+ if ( trace.fraction < 1 )
+ {
+ CBasePlayer *pDriver = GetPassenger( VEHICLE_ROLE_DRIVER );
+ if ( pDriver )
+ {
+ UTIL_ImpactTrace( &trace, DMG_ENERGYBEAM, "Strider" );
+
+ Vector vHitPos = trace.endpos;
+ float flDamageRadius = 100;
+ float flDamage = 100;
+
+ CPASFilter filter( vHitPos );
+ te->Explosion( filter, 0.0,
+ &vHitPos,
+ g_sModelIndexFireball,
+ 2.0,
+ 15,
+ TE_EXPLFLAG_NONE,
+ flDamageRadius,
+ flDamage );
+
+ UTIL_ScreenShake( vHitPos, 10.0, 150.0, 1.0, 100, SHAKE_START );
+ RadiusDamage( CTakeDamageInfo( this, pDriver, flDamage, DMG_BLAST ), vHitPos, flDamageRadius, CLASS_NONE, NULL );
+ }
+ }
+
+ StopFiringLargeGun();
+ }
+}
+
+
+void CWalkerMiniStrider::Crouch()
+{
+ if ( m_State == STATE_CROUCHING || m_State == STATE_CROUCHED )
+ return;
+
+ // Disable the base class's walking functionality while we're crouched.
+ EnableWalkMode( false );
+
+ SetActivity( ACT_CROUCH );
+ m_flCrouchTimer = SequenceDuration();
+ m_State = STATE_CROUCHING;
+}
+
+
+void CWalkerMiniStrider::UnCrouch()
+{
+ if ( m_State == STATE_CROUCHING || m_State == STATE_UNCROUCHING || m_State == STATE_NORMAL )
+ return;
+
+ SetActivity( ACT_STAND );
+
+ m_flCrouchTimer = SequenceDuration();
+
+ m_State = STATE_UNCROUCHING;
+}
+
+
+void CWalkerMiniStrider::UpdateCrouch()
+{
+ float dt = GetTimeDelta();
+
+ m_flCrouchTimer -= dt;
+ if ( m_flCrouchTimer <= 0 )
+ {
+ if ( m_State == STATE_CROUCHING )
+ {
+ m_State = STATE_CROUCHED;
+ SetActivity( ACT_CROUCHIDLE );
+ }
+ else if ( m_State == STATE_UNCROUCHING )
+ {
+ EnableWalkMode( true );
+ m_State = STATE_NORMAL;
+ SetActivity( ACT_IDLE );
+ }
+ }
+}
+
+
+void CWalkerMiniStrider::FireMachineGun()
+{
+ CBaseEntity *pDriver = GetPassenger( VEHICLE_ROLE_DRIVER );
+ if ( pDriver )
+ {
+ // Alternate the gun we're firing
+ char *attachmentNames[2] = { "MachineGun_Left", "MachineGun_Right" };
+ int iAttachment = LookupAttachment( attachmentNames[!m_bFiringLeftGun] );
+ m_bFiringLeftGun = !m_bFiringLeftGun;
+
+ Vector vAttachmentPos;
+ QAngle vAttachmentAngles;
+ GetAttachment( iAttachment, vAttachmentPos, vAttachmentAngles );
+
+ Vector vEyePos = pDriver->EyePosition();
+ Vector vEyeForward;
+ QAngle vecAngles = pDriver->LocalEyeAngles();
+ // Use the skirmisher's yaw
+ vecAngles[YAW] = GetAbsAngles()[YAW];
+ AngleVectors( vecAngles, &vEyeForward );
+
+ // Trace ahead to find out where the crosshair is aiming
+ trace_t trace;
+ float flMaxRange = tf_skirmisher_machinegun_range.GetFloat();
+ UTIL_TraceLine(
+ vEyePos,
+ vEyePos + vEyeForward * flMaxRange,
+ MASK_SOLID,
+ this,
+ COLLISION_GROUP_NONE,
+ &trace );
+
+ Vector vecDir;
+ if ( trace.fraction < 1.0 )
+ {
+ vecDir = (trace.endpos - vAttachmentPos );
+ VectorNormalize( vecDir );
+ }
+ else
+ {
+ vecDir = vEyeForward;
+ }
+
+ // Shoot!
+ TFGameRules()->FireBullets( CTakeDamageInfo( this, pDriver, tf_skirmisher_machinegun_damage.GetFloat(), DMG_BULLET ),
+ 1, // Num shots
+ vAttachmentPos,
+ vecDir,
+ VECTOR_CONE_3DEGREES, // Spread
+ flMaxRange, // Range
+ DMG_BULLET,
+ 1, // Tracer freq
+ entindex(),
+ iAttachment, // Attachment ID
+ "MinigunTracer" );
+
+ m_flNextShootTime += (1.0f / tf_skirmisher_machinegun_rof.GetFloat());
+
+ // Play the fire sound.
+ Vector vCenter = WorldSpaceCenter();
+ CPASAttenuationFilter filter( this, "Skirmisher.GunSound" );
+ EmitSound( filter, 0, "Skirmisher.GunSound", &vCenter );
+ }
+}
+
+
+void CWalkerMiniStrider::WalkerThink()
+{
+ float dt = GetTimeDelta();
+
+ BaseClass::WalkerThink();
+
+ // Shoot the machine gun?
+ if ( !m_bFiringLargeGun )
+ {
+ if ( m_LastButtons & IN_ATTACK )
+ {
+ if ( !m_bFiringMachineGun )
+ StartFiringMachineGun();
+ }
+ else if ( m_bFiringMachineGun )
+ {
+ StopFiringMachineGun();
+ }
+ }
+
+ // Fire the large gun?
+ if ( !m_bFiringMachineGun )
+ {
+ if ( m_LastButtons & IN_ATTACK2 )
+ {
+ if ( !m_bFiringLargeGun )
+ StartFiringLargeGun();
+ }
+ }
+
+
+ UpdateCrouch();
+
+ // Make sure it's crouched when there is no driver.
+ if ( GetPassenger( VEHICLE_ROLE_DRIVER ) )
+ {
+ if ( m_LastButtons & IN_DUCK )
+ {
+ Crouch();
+ }
+ else
+ {
+ UnCrouch();
+ }
+ }
+ else
+ {
+ Crouch();
+ }
+
+ if ( m_bFiringMachineGun )
+ {
+ while ( gpGlobals->curtime > m_flNextShootTime )
+ {
+ FireMachineGun();
+ }
+ }
+
+ UpdateLargeGun();
+
+ // Move our torso within range of our feet.
+ if ( m_flOriginToLowestLegHeight != -1 )
+ {
+ Vector vCenter = WorldSpaceCenter();
+
+ //NDebugOverlay::EntityBounds( this, 255, 100, 0, 0 ,0 );
+ //NDebugOverlay::Line( vCenter, vCenter-Vector(0,0,2000), 255,0,0, true, 0 );
+
+ trace_t trace;
+ UTIL_TraceLine(
+ vCenter,
+ vCenter - Vector( 0, 0, 2000 ),
+ MASK_SOLID_BRUSHONLY,
+ this,
+ COLLISION_GROUP_NONE,
+ &trace );
+
+ if ( trace.fraction < 1 )
+ {
+ m_flWantedZ = trace.endpos.z + m_flOriginToLowestLegHeight;
+ }
+
+ // Move our Z towards the wanted Z.
+ if ( m_flWantedZ != -1 )
+ {
+ Vector vCur = vCenter;
+ vCur.z = Approach( m_flWantedZ, vCur.z, STRIDER_TORSO_VERTICAL_SLIDE_SPEED * dt );
+ SetAbsOrigin( GetAbsOrigin() + Vector( 0, 0, vCur.z - vCenter.z ) );
+ }
+ }
+}
+
+
+void CWalkerMiniStrider::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
+{
+ // Sapper removal
+ if ( RemoveEnemyAttachments( pActivator ) )
+ return;
+
+ CBaseTFPlayer *pPlayer = dynamic_cast<CBaseTFPlayer*>(pActivator);
+ if ( !pPlayer || !InSameTeam( pPlayer ) )
+ return;
+
+ // Ok, put them in the driver role.
+ AttemptToBoardVehicle( pPlayer );
+}
+
+
+void CWalkerMiniStrider::SetupMove( CBasePlayer *pPlayer, CUserCmd *ucmd, IMoveHelper *pHelper, CMoveData *move )
+{
+ BaseClass::SetupMove( pPlayer, ucmd, pHelper, move );
+}
+
+
+bool CWalkerMiniStrider::StartBuilding( CBaseEntity *pBuilder )
+{
+ return BaseClass::StartBuilding( pBuilder );
+}
+
+
+Vector CWalkerMiniStrider::GetWalkerLocalMovement()
+{
+ float dt = GetTimeDelta();
+
+ Vector vForward, vRight;
+ AngleVectors( GetLocalAngles(), &vForward, &vRight, NULL );
+
+ float flSpeed = (tf_skirmisher_speed.GetFloat() / 100) * dt;
+ Vector vMovement =
+ vForward * (GetSteerVelocity().x * flSpeed) +
+ vRight * (GetSteerVelocity().y * flSpeed);
+
+ return GetLocalOrigin() + vMovement;
+}
+
+
+void CWalkerMiniStrider::SetPassenger( int nRole, CBasePlayer *pPassenger )
+{
+ BaseClass::SetPassenger( nRole, pPassenger );
+
+ if ( nRole == VEHICLE_ROLE_DRIVER && pPassenger )
+ UnCrouch();
+}
+
+
+void CWalkerMiniStrider::FootHit( const char *pFootName )
+{
+ if ( gpGlobals->curtime >= m_flNextFootstepSoundTime )
+ {
+ Vector footPosition;
+ QAngle angles;
+
+ ((BaseClass*)this)->GetAttachment( pFootName, footPosition, angles );
+ CPASAttenuationFilter filter( this, "Skirmisher.Footstep" );
+ EmitSound( filter, entindex(), "Skirmisher.Footstep", &footPosition );
+
+ m_flNextFootstepSoundTime = gpGlobals->curtime + 0.2;
+ }
+}
+
+
+void CWalkerMiniStrider::HandleAnimEvent( animevent_t *pEvent )
+{
+ switch( pEvent->event )
+ {
+ case 1:
+ case 2:
+ case 3:
+ {
+ FootHit( "back foot" );
+ }
+ break;
+ }
+}
diff --git a/game/server/tf2/tf_walker_ministrider.h b/game/server/tf2/tf_walker_ministrider.h
new file mode 100644
index 0000000..eb0bed9
--- /dev/null
+++ b/game/server/tf2/tf_walker_ministrider.h
@@ -0,0 +1,117 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+#ifndef TF_WALKER_MINISTRIDER_H
+#define TF_WALKER_MINISTRIDER_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+
+#include "tf_walker_base.h"
+
+
+class CBeam;
+
+
+class CWalkerMiniStrider : public CWalkerBase
+{
+public:
+ DECLARE_CLASS( CWalkerMiniStrider, CWalkerBase );
+ DECLARE_SERVERCLASS();
+
+ CWalkerMiniStrider();
+ virtual ~CWalkerMiniStrider();
+
+
+// CWalkerBase.
+protected:
+ virtual void WalkerThink();
+ virtual Vector GetWalkerLocalMovement();
+
+
+// CBaseObject.
+public:
+ virtual bool StartBuilding( CBaseEntity *pBuilder );
+
+
+// CBaseEntity.
+public:
+ virtual void Precache();
+ virtual void Spawn();
+ virtual void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value );
+
+
+// CBaseAnimating.
+public:
+
+ virtual void HandleAnimEvent( animevent_t *pEvent );
+
+
+// IServerVehicle.
+public:
+ virtual bool IsPassengerVisible( int nRole );
+ virtual void SetPassenger( int nRole, CBasePlayer *pPassenger );
+
+
+// IVehicle overrides.
+public:
+ virtual void SetupMove( CBasePlayer *pPlayer, CUserCmd *ucmd, IMoveHelper *pHelper, CMoveData *move );
+
+
+private:
+ void FootHit( const char *pFootName );
+
+ void StartFiringMachineGun();
+ void StopFiringMachineGun();
+ void FireMachineGun();
+
+ Vector GetLargeGunShootOrigin();
+ void StartFiringLargeGun();
+ void StopFiringLargeGun();
+ void UpdateLargeGun();
+
+ void Crouch();
+ void UnCrouch();
+ void UpdateCrouch();
+
+
+private:
+
+ enum
+ {
+ STATE_CROUCHING=0,
+ STATE_CROUCHED,
+ STATE_UNCROUCHING,
+ STATE_UNCROUCHED,
+ STATE_NORMAL
+ };
+
+ int m_State;
+
+ float m_flCrouchTimer;
+
+ CNetworkVar( bool, m_bFiringMachineGun );
+ CNetworkVar( bool, m_bFiringLargeGun );
+ CNetworkVector( m_vLargeGunTargetPos );
+ float m_flLargeGunCountdown;
+ Vector m_vLargeGunForward;
+ CHandle<CBeam> m_pEnergyBeam;
+
+ // Firing
+ float m_flNextShootTime;
+ bool m_bFiringLeftGun;
+
+ // Used to keep him on the ground.
+ float m_flOriginToLowestLegHeight;
+ float m_flWantedZ;
+
+ // Used to get around an anim event bug where it triggers events a bunch of times when an animation loops.
+ float m_flNextFootstepSoundTime;
+};
+
+
+#endif // TF_WALKER_MINISTRIDER_H
diff --git a/game/server/tf2/tf_walker_strider.cpp b/game/server/tf2/tf_walker_strider.cpp
new file mode 100644
index 0000000..8fd9b36
--- /dev/null
+++ b/game/server/tf2/tf_walker_strider.cpp
@@ -0,0 +1,395 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+#include "cbase.h"
+#include "tf_walker_strider.h"
+#include "npcevent.h"
+#include "engine/IEngineSound.h"
+#include "tf_gamerules.h"
+#include "shake.h"
+#include "in_buttons.h"
+#include "studio.h"
+
+
+#define STRIDER_AE_FOOTSTEP_LEFT 1
+#define STRIDER_AE_FOOTSTEP_RIGHT 2
+#define STRIDER_AE_FOOTSTEP_BACK 3
+#define STRIDER_AE_FOOTSTEP_LEFTM 4
+#define STRIDER_AE_FOOTSTEP_RIGHTM 5
+#define STRIDER_AE_FOOTSTEP_BACKM 6
+#define STRIDER_AE_FOOTSTEP_LEFTL 7
+#define STRIDER_AE_FOOTSTEP_RIGHTL 8
+#define STRIDER_AE_FOOTSTEP_BACKL 9
+
+
+// How many inches/sec the strider's torso moves up and down to get the torso at the right height.
+#define STRIDER_TORSO_VERTICAL_SLIDE_SPEED 100
+#define STRIDER_FIRE_TIME 5
+#define STRIDER_FIRE_INTERVAL 0.3
+#define STRIDER_FIRE_ANGLE_ERROR 6 // N degrees of error when he fires.
+#define WALKER_STRIDER_MODEL "models/objects/walker_strider.mdl"
+
+
+IMPLEMENT_SERVERCLASS_ST( CWalkerStrider, DT_WalkerStrider )
+ SendPropInt( SENDINFO( m_bCrouched ), 1, SPROP_UNSIGNED )
+END_SEND_TABLE()
+LINK_ENTITY_TO_CLASS( walker_strider, CWalkerStrider );
+PRECACHE_REGISTER( walker_strider );
+
+
+char *g_StriderFeet[] =
+{
+ "left foot",
+ "right foot",
+ "back foot"
+};
+#define NUM_STRIDER_FEET ARRAYSIZE( g_StriderFeet )
+
+
+CWalkerStrider::CWalkerStrider()
+{
+ m_bCrouched = false;
+ m_flOriginToLowestLegHeight = -1;
+ m_flWantedZ = -1;
+ m_bFiring = false;
+ m_vDriverAngles.Init();
+}
+
+
+void CWalkerStrider::Precache()
+{
+ PrecacheModel( WALKER_STRIDER_MODEL );
+
+ PrecacheScriptSound( "Brush.Footstep" );
+ PrecacheScriptSound( "Brush.Fire" );
+
+ BaseClass::Precache();
+}
+
+
+void CWalkerStrider::Spawn()
+{
+ SpawnWalker(
+ WALKER_STRIDER_MODEL, // Model name.
+ OBJ_WALKER_STRIDER, // Object type.
+ Vector( -186, -157, -504 ), // Placement dimensions.
+ Vector( 194, 159, 41 ),
+ 200, // Health.
+ 1, // Max passengers.
+ 2.0 // 2x animation speed boost
+ );
+}
+
+bool CWalkerStrider::IsPassengerVisible( int nRole )
+{
+ if ( nRole == VEHICLE_ROLE_DRIVER )
+ return false;
+
+ return true;
+}
+
+
+void CWalkerStrider::Fire()
+{
+ EnableWalkMode( false );
+ m_bFiring = true;
+ m_flFireEndTime = gpGlobals->curtime + STRIDER_FIRE_TIME;
+ ResetSequence( LookupSequence( "shoot01a" ) );
+ m_flAnimTime = m_flPrevAnimTime = 0;
+ SetCycle( 0 );
+ m_flNextShootTime = gpGlobals->curtime;
+
+ m_vFireAngles[ROLL] = 0;
+ m_vFireAngles[PITCH] = m_vDriverAngles[PITCH];
+ m_vFireAngles[YAW] = GetAbsAngles()[YAW];
+
+ // Play the fire sound.
+ CPASAttenuationFilter filter( this, "Brush.Fire" );
+ EmitSound( filter, 0, "Brush.Fire", &GetAbsOrigin() );
+}
+
+
+void CWalkerStrider::Crouch()
+{
+ // Disable the base class's walking functionality while we're crouched.
+ EnableWalkMode( false );
+
+ m_bCrouched = true;
+ m_flNextCrouchTime = gpGlobals->curtime + 0.3;
+ ResetSequence( LookupSequence( "low" ) );
+
+ // HACK: there should be a better way to this.. like CBaseAnimating::ResetAnimation,
+ // or ResetSequence should do it.
+ m_flAnimTime = m_flPrevAnimTime = 0;
+ SetCycle( 0 );
+
+ // HACK CITY.. This forces it to invalidate the abs origins of the gun bases.
+ Vector vPos = GetLocalOrigin();
+ SetLocalOrigin( vPos + Vector( 100, 0, 0 ) );
+ SetLocalOrigin( vPos );
+}
+
+
+void CWalkerStrider::UnCrouch()
+{
+ EnableWalkMode( true );
+ m_flNextCrouchTime = gpGlobals->curtime + 0.3;
+ m_bCrouched = false;
+
+ // HACK CITY.. This forces it to invalidate the abs origins of the gun bases.
+ Vector vPos = GetLocalOrigin();
+ SetLocalOrigin( vPos + Vector( 100, 0, 0 ) );
+ SetLocalOrigin( vPos );
+}
+
+
+void CWalkerStrider::WalkerThink()
+{
+ float dt = GetTimeDelta();
+
+ BaseClass::WalkerThink();
+
+ if ( m_bCrouched )
+ {
+ // Just sit there until they hit IN_DUCK again.
+ if ( (m_LastButtons & IN_DUCK) && gpGlobals->curtime > m_flNextCrouchTime )
+ {
+ UnCrouch();
+ }
+ }
+ else
+ {
+ if ( gpGlobals->curtime > m_flNextCrouchTime )
+ {
+ if ( m_LastButtons & IN_DUCK )
+ {
+ // They want to crouch.
+ Crouch();
+ }
+ else if ( !m_bFiring && (m_LastButtons & IN_ATTACK) )
+ {
+ Fire();
+ }
+ }
+ }
+
+ m_vDriverAngles = m_vLastCmdViewAngles;
+ if ( m_bCrouched )
+ {
+ // The "low" animation gets back up at the end so don't let that happen.
+ SetCycle( MIN( GetCycle(), 0.5f ) );
+ }
+ else if ( m_bFiring )
+ {
+ if ( gpGlobals->curtime > m_flFireEndTime )
+ {
+ m_bFiring = false;
+ EnableWalkMode( true );
+ }
+ else
+ {
+ // Shoot?
+ if ( gpGlobals->curtime >= m_flNextShootTime )
+ {
+ Vector vSrc = GetAbsOrigin();
+ Vector vForward;
+
+ QAngle vFireAngles = m_vFireAngles;
+ vFireAngles[YAW] += RandomFloat( -STRIDER_FIRE_ANGLE_ERROR, STRIDER_FIRE_ANGLE_ERROR );
+ vFireAngles[PITCH] += RandomFloat( -STRIDER_FIRE_ANGLE_ERROR, STRIDER_FIRE_ANGLE_ERROR );
+
+ AngleVectors( vFireAngles, &vForward );
+ trace_t trace;
+ UTIL_TraceLine( vSrc, vSrc + vForward * 2000, MASK_SOLID, this, COLLISION_GROUP_NONE, &trace );
+
+ Vector vHitPos = trace.endpos;
+ float flDamageRadius = 100;
+ float flDamage = 50;
+
+ CBasePlayer *pDriver = GetPassenger( VEHICLE_ROLE_DRIVER );
+ if ( pDriver )
+ {
+ UTIL_ImpactTrace( &trace, DMG_ENERGYBEAM, "Strider" );
+
+ // Tell the client entity to make a beam effect.
+ EntityMessageBegin( this, true );
+ WRITE_VEC3COORD( vHitPos );
+ MessageEnd();
+
+ UTIL_ScreenShake( vHitPos, 10.0, 150.0, 1.0, 100, SHAKE_START );
+ RadiusDamage( CTakeDamageInfo( this, pDriver, flDamage, DMG_BLAST ), vHitPos, flDamageRadius, CLASS_NONE, NULL );
+ }
+
+ m_flNextShootTime = gpGlobals->curtime + STRIDER_FIRE_INTERVAL;
+ }
+ }
+ }
+ else
+ {
+ // Move our torso within range of our feet.
+ if ( m_flOriginToLowestLegHeight != -1 )
+ {
+ trace_t trace;
+ UTIL_TraceLine(
+ GetAbsOrigin(),
+ GetAbsOrigin() - Vector( 0, 0, 2000 ),
+ MASK_SOLID_BRUSHONLY,
+ this,
+ COLLISION_GROUP_NONE,
+ &trace );
+
+ if ( trace.fraction < 1 )
+ {
+ m_flWantedZ = trace.endpos.z + m_flOriginToLowestLegHeight;
+ }
+
+ // Move our Z towards the wanted Z.
+ if ( m_flWantedZ != -1 )
+ {
+ Vector vCur = GetAbsOrigin();
+ vCur.z = Approach( m_flWantedZ, vCur.z, STRIDER_TORSO_VERTICAL_SLIDE_SPEED * dt );
+ SetAbsOrigin( vCur );
+ }
+ }
+ }
+}
+
+
+void CWalkerStrider::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
+{
+ // Sapper removal
+ if ( RemoveEnemyAttachments( pActivator ) )
+ return;
+
+ CBaseTFPlayer *pPlayer = dynamic_cast<CBaseTFPlayer*>(pActivator);
+ if ( !pPlayer || !InSameTeam( pPlayer ) )
+ return;
+
+ // Ok, put them in the driver role.
+ AttemptToBoardVehicle( pPlayer );
+}
+
+
+void CWalkerStrider::SetupMove( CBasePlayer *pPlayer, CUserCmd *ucmd, IMoveHelper *pHelper, CMoveData *move )
+{
+ BaseClass::SetupMove( pPlayer, ucmd, pHelper, move );
+}
+
+
+//
+//
+// This is a TOTAL hack, but we don't have any nodes that work well at all for mounted guns.
+// This all goes away when we get a new model.
+//
+//
+float sideDist = 90;
+float downDist = -400;
+bool CWalkerStrider::GetAttachment( int iAttachment, matrix3x4_t &attachmentToWorld )
+{
+ CStudioHdr *pStudioHdr = GetModelPtr( );
+ if ( !pStudioHdr || iAttachment < 1 || iAttachment > pStudioHdr->GetNumAttachments() )
+ {
+ return false;
+ }
+
+ Vector vLocalPos( 0, 0, 0 );
+ const mstudioattachment_t &pAttachment = pStudioHdr->pAttachment( iAttachment-1 );
+ if ( stricmp( pAttachment.pszName(), "build_point_left_gun" ) == 0 )
+ {
+ vLocalPos.y = sideDist;
+ }
+ else if ( stricmp( pAttachment.pszName(), "build_point_right_gun" ) == 0 )
+ {
+ vLocalPos.y = -sideDist;
+ }
+ else if ( stricmp( pAttachment.pszName(), "ThirdPersonCameraOrigin" ) == 0 )
+ {
+ }
+ else
+ {
+ // Ok, it's not one of our magical attachments. Use the regular attachment setup stuff.
+ return BaseClass::GetAttachment( iAttachment, attachmentToWorld );
+ }
+
+ if ( m_bCrouched )
+ {
+ vLocalPos.z += downDist;
+ }
+
+ // Now build the output matrix.
+ matrix3x4_t localMatrix;
+ SetIdentityMatrix( localMatrix );
+ PositionMatrix( vLocalPos, localMatrix );
+
+ ConcatTransforms( EntityToWorldTransform(), localMatrix, attachmentToWorld );
+ return true;
+}
+
+
+bool CWalkerStrider::StartBuilding( CBaseEntity *pBuilder )
+{
+ if ( !BaseClass::StartBuilding( pBuilder ) )
+ return false;
+
+ // Now figure out our ideal Z distance from our lowest foot to our torso.
+ Vector vOrigin;
+ QAngle vAngles;
+ BaseClass::GetAttachment( "left foot", vOrigin, vAngles );
+ m_flOriginToLowestLegHeight = GetAbsOrigin().z - vOrigin.z;
+ m_flOriginToLowestLegHeight -= 40; // fudge it a little so the legs have room to stretch.
+ return true;
+}
+
+
+void CWalkerStrider::FootHit( const char *pFootName )
+{
+ if ( m_bCrouched || gpGlobals->curtime > m_flDontMakeSoundsUntil )
+ {
+ Vector footPosition;
+ QAngle angles;
+
+ ((BaseClass*)this)->GetAttachment( pFootName, footPosition, angles );
+ CPASAttenuationFilter filter( this, "Brush.Footstep" );
+ EmitSound( filter, entindex(), "Brush.Footstep", &footPosition );
+
+ UTIL_ScreenShake( footPosition, 20.0, 1.0, 0.5, 200, SHAKE_START, false );
+
+ CBasePlayer *pPlayer = GetPassenger( VEHICLE_ROLE_DRIVER );
+ if ( pPlayer )
+ {
+ CTakeDamageInfo info( this, pPlayer, 20, DMG_CLUB );
+ TFGameRules()->RadiusDamage( info, footPosition, 200, CLASS_NONE );
+ }
+
+ m_flDontMakeSoundsUntil = gpGlobals->curtime + 0.4;
+ }
+}
+
+
+void CWalkerStrider::HandleAnimEvent( animevent_t *pEvent )
+{
+ switch( pEvent->event )
+ {
+ case STRIDER_AE_FOOTSTEP_LEFT:
+ case STRIDER_AE_FOOTSTEP_LEFTM:
+ case STRIDER_AE_FOOTSTEP_LEFTL:
+ FootHit( "left foot" );
+ break;
+
+ case STRIDER_AE_FOOTSTEP_RIGHT:
+ case STRIDER_AE_FOOTSTEP_RIGHTM:
+ case STRIDER_AE_FOOTSTEP_RIGHTL:
+ FootHit( "right foot" );
+ break;
+
+ case STRIDER_AE_FOOTSTEP_BACK:
+ case STRIDER_AE_FOOTSTEP_BACKM:
+ case STRIDER_AE_FOOTSTEP_BACKL:
+ FootHit( "back foot" );
+ break;
+ }
+}
+
diff --git a/game/server/tf2/tf_walker_strider.h b/game/server/tf2/tf_walker_strider.h
new file mode 100644
index 0000000..050e7bd
--- /dev/null
+++ b/game/server/tf2/tf_walker_strider.h
@@ -0,0 +1,85 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+#ifndef TF_WALKER_STRIDER_H
+#define TF_WALKER_STRIDER_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+
+#include "tf_walker_base.h"
+
+
+class CWalkerStrider : public CWalkerBase
+{
+public:
+ DECLARE_CLASS( CWalkerStrider, CWalkerBase );
+ DECLARE_SERVERCLASS();
+
+ CWalkerStrider();
+
+
+// CWalkerBase.
+protected:
+ virtual void WalkerThink();
+
+
+// CBaseObject.
+public:
+ virtual bool StartBuilding( CBaseEntity *pBuilder );
+
+
+// CBaseEntity.
+public:
+ virtual void Precache();
+ virtual void Spawn();
+ virtual void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value );
+
+
+// CBaseAnimating.
+public:
+ virtual bool GetAttachment( int iAttachment, matrix3x4_t &attachmentToWorld );
+ virtual void HandleAnimEvent( animevent_t *pEvent );
+
+
+// IServerVehicle.
+public:
+ virtual bool IsPassengerVisible( int nRole );
+
+
+// IVehicle overrides.
+public:
+ virtual void SetupMove( CBasePlayer *pPlayer, CUserCmd *ucmd, IMoveHelper *pHelper, CMoveData *move );
+
+
+private:
+ void Fire();
+
+ void Crouch();
+ void UnCrouch();
+
+ void FootHit( const char *pFootName );
+
+
+
+private:
+ CNetworkVar( bool, m_bCrouched );
+ float m_flNextCrouchTime;
+
+ bool m_bFiring;
+ float m_flFireEndTime;
+ float m_flNextShootTime;
+ QAngle m_vFireAngles;
+
+ float m_flOriginToLowestLegHeight;
+ float m_flWantedZ;
+
+ QAngle m_vDriverAngles;
+};
+
+
+#endif // TF_WALKER_STRIDER_H
diff --git a/game/server/tf2/trigger_fall.cpp b/game/server/tf2/trigger_fall.cpp
new file mode 100644
index 0000000..3a4a1c8
--- /dev/null
+++ b/game/server/tf2/trigger_fall.cpp
@@ -0,0 +1,72 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Used at the bottom of maps where objects should fall away to infinity
+//
+// $NoKeywords: $
+//=============================================================================//
+#include "cbase.h"
+#include "triggers.h"
+
+//-----------------------------------------------------------------------------
+// Purpose: Used at the bottom of maps where objects should fall away to infinity
+//-----------------------------------------------------------------------------
+class CTriggerFall : public CBaseTrigger
+{
+ DECLARE_CLASS( CTriggerFall, CBaseTrigger );
+public:
+ void Spawn( void );
+ void FallTouch( CBaseEntity *pOther );
+
+ DECLARE_DATADESC();
+
+ // Outputs
+ COutputEvent m_OnFallingObject;
+};
+
+BEGIN_DATADESC( CTriggerFall )
+
+ // Function Pointers
+ DEFINE_FUNCTION( FallTouch ),
+
+ // Outputs
+ DEFINE_OUTPUT( m_OnFallingObject, "OnFallingObject" ),
+
+END_DATADESC()
+
+
+LINK_ENTITY_TO_CLASS( trigger_fall, CTriggerFall );
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Called when spawning, after keyvalues have been handled.
+//-----------------------------------------------------------------------------
+void CTriggerFall::Spawn( void )
+{
+ BaseClass::Spawn();
+ InitTrigger();
+ SetTouch( FallTouch );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Make the object fall away
+// Input : pOther - The entity that is touching us.
+//-----------------------------------------------------------------------------
+void CTriggerFall::FallTouch( CBaseEntity *pOther )
+{
+ // If it's a player, just kill him for now
+ if ( pOther->IsPlayer() )
+ {
+ if ( pOther->IsAlive() == false )
+ return;
+
+ pOther->TakeDamage( CTakeDamageInfo( this, this, 200, DMG_FALL ) );
+ }
+ else
+ {
+ // Just remove the entity
+ UTIL_Remove( pOther );
+ }
+
+ // Fire our output
+ m_OnFallingObject.FireOutput( pOther, this );
+}
diff --git a/game/server/tf2/trigger_skybox.cpp b/game/server/tf2/trigger_skybox.cpp
new file mode 100644
index 0000000..e3dd690
--- /dev/null
+++ b/game/server/tf2/trigger_skybox.cpp
@@ -0,0 +1,65 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Resource collection entity
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#include "cbase.h"
+#include "triggers.h"
+#include "env_meteor.h "
+
+//-----------------------------------------------------------------------------
+// 3DSkybox to World Transition Trigger Class
+//-----------------------------------------------------------------------------
+class CTrigger3DSkyboxToWorld : public CBaseTrigger
+{
+ DECLARE_CLASS( CTrigger3DSkyboxToWorld, CBaseTrigger );
+
+public:
+
+ CTrigger3DSkyboxToWorld();
+
+ DECLARE_DATADESC();
+
+ void Spawn( void );
+ void ImpactTouch( CBaseEntity *pOther );
+};
+
+BEGIN_DATADESC( CTrigger3DSkyboxToWorld )
+
+ // Function Pointers
+ DEFINE_FUNCTION( ImpactTouch ),
+
+END_DATADESC()
+
+LINK_ENTITY_TO_CLASS( trigger_skybox2world, CTrigger3DSkyboxToWorld );
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CTrigger3DSkyboxToWorld::CTrigger3DSkyboxToWorld()
+{
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTrigger3DSkyboxToWorld::Spawn( void )
+{
+ BaseClass::Spawn();
+ InitTrigger();
+// SetTouch( ImpactTouch );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Not handling transitions with a touch function currently!!
+//-----------------------------------------------------------------------------
+void CTrigger3DSkyboxToWorld::ImpactTouch( CBaseEntity *pOther )
+{
+#if 0
+ if ( FClassnameIs( pOther, "meteor" ) )
+ {
+ }
+#endif
+}