You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
3736 lines
107 KiB
3736 lines
107 KiB
//========= 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_weaponbase.h" |
|
#include "rope.h" |
|
#include "rope_shared.h" |
|
#include "bone_setup.h" |
|
#include "ndebugoverlay.h" |
|
#include "rope_helpers.h" |
|
#include "IEffects.h" |
|
#include "vstdlib/random.h" |
|
#include "tier1/strtools.h" |
|
#include "basegrenade_shared.h" |
|
#include "tf_gamerules.h" |
|
#include "engine/IEngineSound.h" |
|
#include "tf_shareddefs.h" |
|
#include "vguiscreen.h" |
|
#include "hierarchy.h" |
|
#include "func_no_build.h" |
|
#include "func_respawnroom.h" |
|
#include <KeyValues.h> |
|
#include "ihasbuildpoints.h" |
|
#include "utldict.h" |
|
#include "filesystem.h" |
|
#include "npcevent.h" |
|
#include "tf_shareddefs.h" |
|
#include "animation.h" |
|
#include "effect_dispatch_data.h" |
|
#include "te_effect_dispatch.h" |
|
#include "tf_gamestats.h" |
|
#include "tf_ammo_pack.h" |
|
#include "tf_obj_sapper.h" |
|
#include "particle_parse.h" |
|
#include "tf_fx.h" |
|
#include "trains.h" |
|
#include "serverbenchmark_base.h" |
|
#include "tf_weapon_wrench.h" |
|
#include "tf_weapon_grenade_pipebomb.h" |
|
#include "tf_weapon_builder.h" |
|
|
|
#include "player_vs_environment/tf_population_manager.h" |
|
|
|
// memdbgon must be the last include file in a .cpp file!!! |
|
#include "tier0/memdbgon.h" |
|
|
|
// Control panels |
|
#define SCREEN_OVERLAY_MATERIAL "vgui/screens/vgui_overlay" |
|
|
|
#define ROPE_HANG_DIST 150 |
|
#define UPGRADE_LEVEL_HEALTH_MULTIPLIER 1.2f |
|
|
|
|
|
ConVar tf_obj_gib_velocity_min( "tf_obj_gib_velocity_min", "100", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY ); |
|
ConVar tf_obj_gib_velocity_max( "tf_obj_gib_velocity_max", "450", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY ); |
|
ConVar tf_obj_gib_maxspeed( "tf_obj_gib_maxspeed", "800", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY ); |
|
ConVar tf_obj_upgrade_per_hit( "tf_obj_upgrade_per_hit", "25", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY ); |
|
|
|
ConVar object_verbose( "object_verbose", "0", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY, "Debug object system." ); |
|
ConVar obj_damage_factor( "obj_damage_factor","0", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY, "Factor applied to all damage done to objects" ); |
|
ConVar obj_child_damage_factor( "obj_child_damage_factor","0.25", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY, "Factor applied to damage done to objects that are built on a buildpoint" ); |
|
ConVar tf_fastbuild("tf_fastbuild", "0", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY ); |
|
ConVar tf_obj_ground_clearance( "tf_obj_ground_clearance", "32", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY, "Object corners can be this high above the ground" ); |
|
|
|
ConVar tf_obj_damage_tank_achievement_amount( "tf_obj_damage_tank_achievement_amount", "2000", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY ); |
|
|
|
extern short g_sModelIndexFireball; |
|
extern ConVar tf_cheapobjects; |
|
|
|
// 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 PLASMA_DISABLE_TIME 4 |
|
|
|
IMPLEMENT_AUTO_LIST( IBaseObjectAutoList ); |
|
|
|
BEGIN_DATADESC( CBaseObject ) |
|
// keys |
|
DEFINE_KEYFIELD_NOT_SAVED( m_SolidToPlayers, FIELD_INTEGER, "SolidToPlayer" ), |
|
DEFINE_KEYFIELD( m_nDefaultUpgradeLevel, FIELD_INTEGER, "defaultupgrade" ), |
|
|
|
DEFINE_THINKFUNC( UpgradeThink ), |
|
|
|
// Inputs |
|
DEFINE_INPUTFUNC( FIELD_INTEGER, "SetHealth", InputSetHealth ), |
|
DEFINE_INPUTFUNC( FIELD_INTEGER, "AddHealth", InputAddHealth ), |
|
DEFINE_INPUTFUNC( FIELD_INTEGER, "RemoveHealth", InputRemoveHealth ), |
|
DEFINE_INPUTFUNC( FIELD_INTEGER, "SetSolidToPlayer", InputSetSolidToPlayer ), |
|
DEFINE_INPUTFUNC( FIELD_STRING, "SetBuilder", InputSetBuilder ), |
|
DEFINE_INPUTFUNC( FIELD_VOID, "Show", InputShow ), |
|
DEFINE_INPUTFUNC( FIELD_VOID, "Hide", InputHide ), |
|
DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ), |
|
DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputDisable ), |
|
|
|
// 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), -1, SPROP_VARINT ), |
|
SendPropInt(SENDINFO(m_iMaxHealth), -1, SPROP_VARINT ), |
|
SendPropBool(SENDINFO(m_bHasSapper) ), |
|
SendPropInt(SENDINFO(m_iObjectType), Q_log2( OBJ_LAST ) + 1, SPROP_UNSIGNED ), |
|
SendPropBool(SENDINFO(m_bBuilding) ), |
|
SendPropBool(SENDINFO(m_bPlacing) ), |
|
SendPropBool(SENDINFO(m_bCarried) ), |
|
SendPropBool(SENDINFO(m_bCarryDeploy) ), |
|
SendPropBool(SENDINFO(m_bMiniBuilding) ), |
|
SendPropFloat(SENDINFO(m_flPercentageConstructed), 8, 0, 0.0, 1.0f ), |
|
SendPropInt(SENDINFO(m_fObjectFlags), OF_BIT_COUNT, SPROP_UNSIGNED ), |
|
SendPropEHandle(SENDINFO(m_hBuiltOnEntity)), |
|
SendPropBool( SENDINFO( m_bDisabled ) ), |
|
SendPropEHandle( SENDINFO( m_hBuilder ) ), |
|
SendPropVector( SENDINFO( m_vecBuildMaxs ), -1, SPROP_COORD ), |
|
SendPropVector( SENDINFO( m_vecBuildMins ), -1, SPROP_COORD ), |
|
SendPropInt( SENDINFO( m_iDesiredBuildRotations ), 2, SPROP_UNSIGNED ), |
|
SendPropBool( SENDINFO( m_bServerOverridePlacement ) ), |
|
SendPropInt( SENDINFO(m_iUpgradeLevel), 3 ), |
|
SendPropInt( SENDINFO(m_iUpgradeMetal), 10 ), |
|
SendPropInt( SENDINFO(m_iUpgradeMetalRequired), 10 ), |
|
SendPropInt( SENDINFO(m_iHighestUpgradeLevel), 3 ), |
|
SendPropInt( SENDINFO(m_iObjectMode), 2, SPROP_UNSIGNED ), |
|
SendPropBool( SENDINFO( m_bDisposableBuilding ) ), |
|
SendPropBool( SENDINFO( m_bWasMapPlaced ) ), |
|
SendPropBool( SENDINFO( m_bPlasmaDisable ) ), |
|
END_SEND_TABLE(); |
|
|
|
|
|
bool PlayerIndexLessFunc( const int &lhs, const int &rhs ) |
|
{ |
|
return lhs < rhs; |
|
} |
|
|
|
// 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_iHealth = m_iMaxHealth = m_flHealth = 0; |
|
m_flPercentageConstructed = 0; |
|
m_bPlacing = false; |
|
m_bBuilding = false; |
|
m_Activity = ACT_INVALID; |
|
m_bDisabled = false; |
|
m_SolidToPlayers = SOLID_TO_PLAYER_USE_DEFAULT; |
|
m_bPlacementOK = false; |
|
m_aGibs.Purge(); |
|
m_iHighestUpgradeLevel = 1; |
|
m_bCarryDeploy = false; |
|
m_flCarryDeployTime = 0; |
|
m_iHealthOnPickup = 0; |
|
m_iLifetimeDamage = 0; |
|
m_bCannotDie = false; |
|
m_bMiniBuilding = false; |
|
m_flPlasmaDisableTime = 0; |
|
m_bPlasmaDisable = false; |
|
|
|
m_bDisposableBuilding = false; |
|
|
|
m_vecBuildForward = vec3_origin; |
|
m_flBuildDistance = 0.0f; |
|
|
|
m_bForceQuickBuild = false; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CBaseObject::UpdateOnRemove( void ) |
|
{ |
|
m_bDying = true; |
|
|
|
// check for sapper crits |
|
CObjectSapper *pSapper = GetSapper(); |
|
if ( pSapper ) |
|
{ |
|
// give an assist to the sapper's owner |
|
CTFPlayer *pSapperOwner = pSapper->GetOwner(); |
|
if ( pSapperOwner ) |
|
{ |
|
pSapperOwner->m_Shared.IncrementRevengeCrits(); |
|
} |
|
} |
|
|
|
DestroyObject(); |
|
|
|
if ( GetTeam() ) |
|
{ |
|
((CTFTeam*)GetTeam())->RemoveObject( this ); |
|
} |
|
|
|
DetachObjectFromObject(); |
|
|
|
// 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::UpdateTransmitState() |
|
{ |
|
return SetTransmitState( FL_EDICT_FULLCHECK ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// 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; |
|
|
|
if ( pInfo->m_pClientEnt ) |
|
{ |
|
CBaseEntity *pRecipientEntity = CBaseEntity::Instance( pInfo->m_pClientEnt ); |
|
if ( pRecipientEntity && pRecipientEntity->ShouldForceTransmitsForTeam( GetTeamNumber() ) ) |
|
return FL_EDICT_ALWAYS; |
|
} |
|
|
|
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() |
|
{ |
|
PrecacheMaterial( SCREEN_OVERLAY_MATERIAL ); |
|
|
|
PrecacheScriptSound( GetObjectInfo( ObjectType() )->m_pExplodeSound ); |
|
PrecacheScriptSound( GetObjectInfo( ObjectType() )->m_pUpgradeSound ); |
|
|
|
const char *pEffect = GetObjectInfo( ObjectType() )->m_pExplosionParticleEffect; |
|
|
|
if ( pEffect && pEffect[0] != '\0' ) |
|
{ |
|
PrecacheParticleSystem( pEffect ); |
|
} |
|
|
|
PrecacheParticleSystem( "nutsnbolts_build" ); |
|
PrecacheParticleSystem( "nutsnbolts_upgrade" ); |
|
PrecacheParticleSystem( "nutsnbolts_repair" ); |
|
|
|
PrecacheModel( "models/weapons/w_models/w_toolbox.mdl" ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CBaseObject::Spawn( void ) |
|
{ |
|
Precache(); |
|
|
|
CollisionProp()->SetSurroundingBoundsType( USE_BEST_COLLISION_BOUNDS ); |
|
SetSolidToPlayers( m_SolidToPlayers, true ); |
|
|
|
m_bWasMapPlaced = false; |
|
m_bHasSapper = false; |
|
if ( HasSpawnFlags(SF_BASEOBJ_INVULN) ) |
|
{ |
|
m_takedamage = DAMAGE_NO; |
|
} |
|
else |
|
{ |
|
m_takedamage = DAMAGE_YES; |
|
} |
|
|
|
AddFlag( FL_OBJECT ); // So NPCs will notice it |
|
SetViewOffset( WorldSpaceCenter() - GetAbsOrigin() ); |
|
|
|
m_iDesiredBuildRotations = 0; |
|
m_flCurrentBuildRotation = 0; |
|
|
|
if ( MustBeBuiltOnAttachmentPoint() ) |
|
{ |
|
AddEffects( EF_NODRAW ); |
|
} |
|
|
|
// assume valid placement |
|
m_bServerOverridePlacement = true; |
|
|
|
m_iUpgradeLevel = 1; |
|
m_iUpgradeMetalRequired = GetUpgradeMetalRequired(); |
|
|
|
if ( !IsCarried() ) |
|
{ |
|
FirstSpawn(); |
|
} |
|
|
|
UpdateLastKnownArea(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Initialization that should only be done when the object is first created. |
|
//----------------------------------------------------------------------------- |
|
void CBaseObject::FirstSpawn() |
|
{ |
|
if ( !VPhysicsGetObject() ) |
|
VPhysicsInitStatic(); |
|
|
|
m_iUpgradeMetal = 0; |
|
m_iKills = 0; |
|
m_iAssists = 0; |
|
m_ConstructorList.SetLessFunc( PlayerIndexLessFunc ); |
|
m_flHealth = m_iMaxHealth = m_iHealth; |
|
|
|
SetContextThink( &CBaseObject::BaseObjectThink, gpGlobals->curtime + 0.1, OBJ_BASE_THINK_CONTEXT ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// 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; |
|
const char *pOrgLL = "controlpanel%d_ll"; |
|
const char *pOrgUR = "controlpanel%d_ur"; |
|
const char *pAttachmentNameLL = pOrgLL; |
|
const 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 = NULL; |
|
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 ); |
|
pScreen->SetTransparency( true ); |
|
|
|
// for now, only input by the owning player |
|
pScreen->SetPlayerOwner( GetBuilder(), true ); |
|
|
|
int nScreen = m_hScreens.AddToTail( ); |
|
m_hScreens[nScreen].Set( pScreen ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Handle commands sent from vgui panels on the client |
|
//----------------------------------------------------------------------------- |
|
bool CBaseObject::ClientCommand( CTFPlayer *pSender, const CCommand &args ) |
|
{ |
|
//const char *pCmd = args[0]; |
|
return false; |
|
} |
|
|
|
#define BASE_OBJECT_THINK_DELAY 0.1 |
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CBaseObject::BaseObjectThink( void ) |
|
{ |
|
SetNextThink( gpGlobals->curtime + BASE_OBJECT_THINK_DELAY, OBJ_BASE_THINK_CONTEXT ); |
|
|
|
// Make sure animation is up to date |
|
DetermineAnimation(); |
|
|
|
DeterminePlaybackRate(); |
|
|
|
if ( m_bPlasmaDisable ) |
|
{ |
|
if ( gpGlobals->curtime > (m_flPlasmaDisableTime ) ) |
|
{ |
|
m_bPlasmaDisable = false; |
|
UpdateDisabledState(); |
|
} |
|
} |
|
|
|
// Do nothing while we're being placed |
|
if ( IsPlacing() ) |
|
{ |
|
if ( MustBeBuiltOnAttachmentPoint() ) |
|
{ |
|
UpdateAttachmentPlacement(); |
|
m_bServerOverridePlacement = true; |
|
} |
|
else |
|
{ |
|
m_bServerOverridePlacement = IsPlacementPosValid(); |
|
|
|
UpdateDesiredBuildRotation( BASE_OBJECT_THINK_DELAY ); |
|
} |
|
|
|
return; |
|
} |
|
|
|
// If we're building, keep going |
|
if ( IsBuilding() ) |
|
{ |
|
BuildingThink(); |
|
return; |
|
} |
|
|
|
if ( IsUpgrading() ) |
|
{ |
|
UpgradeThink(); |
|
} |
|
else |
|
{ |
|
if ( GetReversesBuildingConstructionSpeed() > 0.0f ) |
|
{ |
|
DoReverseBuild(); |
|
} |
|
else |
|
{ |
|
if ( GetUpgradeLevel() < GetHighestUpgradeLevel() ) |
|
{ |
|
// Keep moving up levels until we reach the level we were at before. |
|
StartUpgrading(); |
|
} |
|
else |
|
{ |
|
m_bCarryDeploy = false; |
|
} |
|
} |
|
} |
|
} |
|
|
|
void CBaseObject::ResetPlacement( void ) |
|
{ |
|
m_bPlacementOK = false; |
|
|
|
// Clear out previous parent |
|
if ( m_hBuiltOnEntity.Get() ) |
|
{ |
|
m_hBuiltOnEntity = NULL; |
|
m_iBuiltOnPoint = 0; |
|
SetParent( NULL ); |
|
} |
|
|
|
// teleport to builder's origin |
|
CTFPlayer *pPlayer = GetOwner(); |
|
|
|
if ( pPlayer ) |
|
{ |
|
Teleport( &pPlayer->WorldSpaceCenter(), &GetLocalAngles(), NULL ); |
|
} |
|
} |
|
|
|
bool CBaseObject::UpdateAttachmentPlacement( CBaseObject *pObjectOverride ) |
|
{ |
|
// See if we should snap to a build position |
|
// finding one implies it is a valid position |
|
if ( FindSnapToBuildPos( pObjectOverride ) ) |
|
{ |
|
m_bPlacementOK = true; |
|
|
|
Teleport( &m_vecBuildOrigin, &GetLocalAngles(), NULL ); |
|
} |
|
else |
|
{ |
|
ResetPlacement(); |
|
} |
|
|
|
return m_bPlacementOK; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Cheap check to see if we are in any server-defined No-build areas. |
|
//----------------------------------------------------------------------------- |
|
bool CBaseObject::EstimateValidBuildPos( void ) |
|
{ |
|
// Make sure CalculatePlacementPos() has been called to setup the member variables used below |
|
|
|
CTFPlayer *pPlayer = GetOwner(); |
|
|
|
if ( !pPlayer ) |
|
return false; |
|
|
|
// Cannot build inside a nobuild brush |
|
if ( PointInNoBuild( m_vecBuildOrigin, this ) ) |
|
return false; |
|
|
|
if ( PointInNoBuild( m_vecBuildCenterOfMass, this ) ) |
|
return false; |
|
|
|
// If we're receiving trigger hurt damage, don't allow building here. |
|
if ( IsTakingTriggerHurtDamageAtPoint( m_vecBuildOrigin ) ) |
|
return false; |
|
|
|
if ( IsTakingTriggerHurtDamageAtPoint( m_vecBuildCenterOfMass ) ) |
|
return false; |
|
|
|
if ( PointInRespawnRoom( NULL, m_vecBuildOrigin ) && !g_pServerBenchmark->IsBenchmarkRunning() ) |
|
return false; |
|
|
|
if ( PointInRespawnRoom( NULL, m_vecBuildCenterOfMass ) && !g_pServerBenchmark->IsBenchmarkRunning() ) |
|
return false; |
|
|
|
Vector vecBuildFarEdge = m_vecBuildOrigin + m_vecBuildForward * ( m_flBuildDistance + 8.0f ); |
|
if ( PointsCrossRespawnRoomVisualizer( pPlayer->WorldSpaceCenter(), vecBuildFarEdge ) ) |
|
return false; |
|
|
|
return true; |
|
} |
|
|
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CBaseObject::DeterminePlaybackRate( void ) |
|
{ |
|
float flReverseBuildingConstructionSpeed = GetReversesBuildingConstructionSpeed(); |
|
if ( flReverseBuildingConstructionSpeed == 0.0f ) |
|
{ |
|
flReverseBuildingConstructionSpeed = 1.0f; |
|
} |
|
else |
|
{ |
|
flReverseBuildingConstructionSpeed *= -1.0f; |
|
} |
|
|
|
// If a sapper was added or removed part way through construction we need to invert the time to completion |
|
bool bAdjustCompleteTime = ( flReverseBuildingConstructionSpeed > 0.0f && GetPlaybackRate() < 0.0f ) || |
|
( flReverseBuildingConstructionSpeed < 0.0f && GetPlaybackRate() >= 0.0f ); |
|
|
|
if ( IsBuilding() ) |
|
{ |
|
// Default half rate, author build anim as if one player is building |
|
// ConstructionMultiplier already contains the reverse |
|
SetPlaybackRate( GetConstructionMultiplier() * 0.5 ); |
|
} |
|
else |
|
{ |
|
SetPlaybackRate( 1.0 * flReverseBuildingConstructionSpeed ); |
|
} |
|
|
|
if ( bAdjustCompleteTime ) |
|
{ |
|
float fRelativeCycle = ( ( flReverseBuildingConstructionSpeed > 0.0f ) ? ( 1.0f - GetCycle() ) : ( GetCycle() ) ); |
|
|
|
float flUpgradeTime = ( ShouldQuickBuild() ? 0 : GetObjectInfo( ObjectType() )->m_flUpgradeDuration ); |
|
flUpgradeTime /= ( ( flReverseBuildingConstructionSpeed < 0.0f ) ? ( flReverseBuildingConstructionSpeed * -1.0 ) : 1.0f ); |
|
m_flUpgradeCompleteTime = gpGlobals->curtime + flUpgradeTime * fRelativeCycle; |
|
m_flTotalConstructionTime = m_flConstructionTimeLeft = GetTotalTime(); |
|
|
|
float flNewConstructionTimeLeft = m_flConstructionTimeLeft * fRelativeCycle; |
|
m_flConstructionTimeLeft *= fRelativeCycle; |
|
|
|
m_flConstructionStartTime += m_flConstructionTimeLeft - flNewConstructionTimeLeft; |
|
m_flConstructionTimeLeft = flNewConstructionTimeLeft; |
|
} |
|
|
|
if ( !(m_fObjectFlags & OF_DOESNT_HAVE_A_MODEL) ) |
|
{ |
|
StudioFrameAdvance(); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
CTFPlayer *CBaseObject::GetOwner() |
|
{ |
|
return m_hBuilder; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CBaseObject::SetBuilder( CTFPlayer *pBuilder ) |
|
{ |
|
TRACE_OBJECT( UTIL_VarArgs( "%0.2f CBaseObject::SetBuilder builder %s\n", gpGlobals->curtime, |
|
pBuilder ? pBuilder->GetPlayerName() : "NULL" ) ); |
|
|
|
m_hBuilder = pBuilder; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
int CBaseObject::ObjectType( ) const |
|
{ |
|
return m_iObjectType; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Destroys the object, gives a chance to spawn an explosion |
|
//----------------------------------------------------------------------------- |
|
void CBaseObject::DetonateObject( void ) |
|
{ |
|
// Blow us up. |
|
CTakeDamageInfo info( this, this, vec3_origin, GetAbsOrigin(), 0, DMG_GENERIC ); |
|
Killed( info ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// 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() ) ); |
|
|
|
// If we are carried, uncarry us before destruction. |
|
if ( IsCarried() && GetBuilder() ) |
|
{ |
|
DropCarriedObject( GetBuilder() ); |
|
|
|
CTFWeaponBuilder *pBuilder = dynamic_cast<CTFWeaponBuilder*>( GetBuilder()->Weapon_OwnsThisID( TF_WEAPON_BUILDER ) ); |
|
if ( pBuilder ) |
|
{ |
|
pBuilder->SwitchOwnersWeaponToLast(); |
|
} |
|
} |
|
|
|
if ( GetBuilder() ) |
|
{ |
|
GetBuilder()->OwnedObjectDestroyed( this ); |
|
} |
|
|
|
UTIL_Remove( this ); |
|
|
|
DestroyScreens(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Remove any screens that are active on this object |
|
//----------------------------------------------------------------------------- |
|
void CBaseObject::DestroyScreens( void ) |
|
{ |
|
// Kill the control panels |
|
int i; |
|
for ( i = m_hScreens.Count(); --i >= 0; ) |
|
{ |
|
DestroyVGuiScreen( m_hScreens[i].Get() ); |
|
} |
|
m_hScreens.RemoveAll(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Get the total time it will take to build this object |
|
//----------------------------------------------------------------------------- |
|
float CBaseObject::GetTotalTime( void ) |
|
{ |
|
float flBuildTime = GetObjectInfo( ObjectType() )->m_flBuildTime; |
|
|
|
CTFPlayer *pTFBuilder= GetBuilder(); |
|
if ( pTFBuilder ) |
|
{ |
|
CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( pTFBuilder, flBuildTime, mod_build_rate ); |
|
} |
|
|
|
if ( tf_fastbuild.GetInt() ) |
|
{ |
|
flBuildTime = MIN( 2.f, flBuildTime ); |
|
} |
|
|
|
// quick builds for engineers in Mann Vs Machine mode during setup time |
|
if ( TFGameRules()->IsQuickBuildTime() ) |
|
{ |
|
flBuildTime = MIN( 1.0f, flBuildTime ); |
|
} |
|
|
|
return flBuildTime; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Start placing the object |
|
//----------------------------------------------------------------------------- |
|
void CBaseObject::StartPlacement( CTFPlayer *pPlayer ) |
|
{ |
|
AddSolidFlags( FSOLID_NOT_SOLID ); |
|
|
|
m_bPlacing = true; |
|
m_bBuilding = false; |
|
if ( pPlayer ) |
|
{ |
|
SetBuilder( pPlayer ); |
|
ChangeTeam( pPlayer->GetTeamNumber() ); |
|
} |
|
|
|
// needed? |
|
m_nRenderMode = kRenderNormal; |
|
|
|
// Set my build size |
|
CollisionProp()->WorldSpaceAABB( &m_vecBuildMins.GetForModify(), &m_vecBuildMaxs.GetForModify() ); |
|
m_vecBuildMins -= Vector( 4,4,0 ); |
|
m_vecBuildMaxs += Vector( 4,4,0 ); |
|
m_vecBuildMins -= GetAbsOrigin(); |
|
m_vecBuildMaxs -= GetAbsOrigin(); |
|
|
|
// Set the skin |
|
m_nSkin = ( GetTeamNumber() == TF_TEAM_RED ) ? 0 : 1; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// 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, CBasePlayer *pBuilder, float &flNearestPoint, Vector &vecNearestBuildPoint, bool bIgnoreChecks ) |
|
{ |
|
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) ) |
|
{ |
|
if ( !bIgnoreChecks ) |
|
{ |
|
// ignore build points outside our view |
|
if ( !pBuilder->FInViewCone( vecBPOrigin ) ) |
|
continue; |
|
|
|
// Do a trace to make sure we don't place attachments through things (players, world, etc...) |
|
Vector vecStart = pBuilder->EyePosition(); |
|
trace_t trace; |
|
CTraceFilterNoNPCsOrPlayer ignorePlayersFilter( pBuilder, COLLISION_GROUP_NONE ); |
|
UTIL_TraceLine( vecStart, vecBPOrigin, MASK_SOLID, &ignorePlayersFilter, &trace ); |
|
if ( trace.m_pEnt != pEntity && trace.fraction != 1.0 ) |
|
continue; |
|
} |
|
|
|
float flDist = (vecBPOrigin - pBuilder->GetAbsOrigin()).Length(); |
|
|
|
// if this is closer, or is the first one in our view, check it out |
|
if ( bIgnoreChecks || ( 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: Find a buildpoint on the specified player |
|
//----------------------------------------------------------------------------- |
|
bool CBaseObject::FindBuildPointOnPlayer( CTFPlayer *pTFPlayer, CBasePlayer *pBuilder, float &flNearestPoint, Vector &vecNearestBuildPoint ) |
|
{ |
|
bool bFoundPoint = false; |
|
|
|
if ( !pTFPlayer ) |
|
return false; |
|
|
|
if ( pTFPlayer->m_Shared.InCond( TF_COND_SAPPED ) ) |
|
return false; |
|
|
|
if ( pTFPlayer->m_Shared.IsInvulnerable() ) |
|
return false; |
|
|
|
if ( pTFPlayer->m_Shared.InCond( TF_COND_PHASE ) ) |
|
return false; |
|
|
|
Vector vecOrigin = pTFPlayer->GetAbsOrigin(); |
|
QAngle vecAngles = pTFPlayer->GetAbsAngles(); |
|
float flDist = ( vecOrigin - pBuilder->GetAbsOrigin() ).Length(); |
|
if ( flDist <= 160.f ) |
|
{ |
|
flNearestPoint = flDist; |
|
vecNearestBuildPoint = vecOrigin; |
|
m_hBuiltOnEntity = (CBaseEntity *)pTFPlayer; |
|
|
|
// Set our angles to the buildpoint's angles |
|
SetAbsAngles( vecAngles ); |
|
|
|
bFoundPoint = true; |
|
} |
|
|
|
return bFoundPoint; |
|
} |
|
|
|
/* |
|
class CTraceFilterIgnorePlayers : public CTraceFilterSimple |
|
{ |
|
public: |
|
// It does have a base, but we'll never network anything below here.. |
|
DECLARE_CLASS( CTraceFilterIgnorePlayers, CTraceFilterSimple ); |
|
|
|
CTraceFilterIgnorePlayers( const IHandleEntity *passentity, int collisionGroup ) |
|
: CTraceFilterSimple( passentity, collisionGroup ) |
|
{ |
|
} |
|
|
|
virtual bool ShouldHitEntity( IHandleEntity *pServerEntity, int contentsMask ) |
|
{ |
|
CBaseEntity *pEntity = EntityFromEntityHandle( pServerEntity ); |
|
|
|
if ( pEntity->IsPlayer() ) |
|
{ |
|
return false; |
|
} |
|
|
|
return true; |
|
} |
|
}; |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Test around this build position to make sure it does not block a path |
|
//----------------------------------------------------------------------------- |
|
bool CBaseObject::TestPositionForPlayerBlock( Vector vecBuildOrigin, CBasePlayer *pPlayer ) |
|
{ |
|
// find out the status of the 8 regions around this position |
|
int i; |
|
bool bNodeVisited[8]; |
|
bool bNodeClear[8]; |
|
|
|
// The first zone that is clear of obstructions |
|
int iFirstClear = -1; |
|
|
|
Vector vHalfPlayerDims = (VEC_HULL_MAX - VEC_HULL_MIN) * 0.5f; |
|
|
|
Vector vBuildDims = m_vecBuildMaxs - m_vecBuildMins; |
|
Vector vHalfBuildDims = vBuildDims * 0.5; |
|
|
|
|
|
// the locations of the 8 test positions |
|
// boxes are adjacent to the object box and are at least as large as |
|
// a player to ensure that a player can pass this location |
|
|
|
// 0 1 2 |
|
// 7 X 3 |
|
// 6 5 4 |
|
|
|
static int iPositions[8][2] = |
|
{ |
|
{ -1, -1 }, |
|
{ 0, -1 }, |
|
{ 1, -1 }, |
|
{ 1, 0 }, |
|
{ 1, 1 }, |
|
{ 0, 1 }, |
|
{ -1, 1 }, |
|
{ -1, 0 } |
|
}; |
|
|
|
CTraceFilterIgnorePlayers traceFilter( this, COLLISION_GROUP_NONE ); |
|
|
|
for ( i=0;i<8;i++ ) |
|
{ |
|
// mark them all as unvisited |
|
bNodeVisited[i] = false; |
|
|
|
Vector vecTest = vecBuildOrigin; |
|
vecTest.x += ( iPositions[i][0] * ( vHalfBuildDims.x + vHalfPlayerDims.x ) ); |
|
vecTest.y += ( iPositions[i][1] * ( vHalfBuildDims.y + vHalfPlayerDims.y ) ); |
|
|
|
trace_t trace; |
|
UTIL_TraceHull( vecTest, vecTest, VEC_HULL_MIN, VEC_HULL_MAX, MASK_SOLID_BRUSHONLY, &traceFilter, &trace ); |
|
|
|
bNodeClear[i] = ( trace.fraction == 1 && trace.allsolid != 1 && (trace.startsolid != 1) ); |
|
|
|
// NDebugOverlay::Box( vecTest, VEC_HULL_MIN, VEC_HULL_MAX, bNodeClear[i] ? 0 : 255, bNodeClear[i] ? 255 : 0, 0, 20, 0.1 ); |
|
|
|
// Store off the first clear location |
|
if ( iFirstClear < 0 && bNodeClear[i] ) |
|
{ |
|
iFirstClear = i; |
|
} |
|
} |
|
|
|
if ( iFirstClear < 0 ) |
|
{ |
|
// no clear space |
|
return false; |
|
} |
|
|
|
// visit all nodes that are adjacent |
|
RecursiveTestBuildSpace( iFirstClear, bNodeClear, bNodeVisited ); |
|
|
|
// if we still have unvisited nodes, return false |
|
// unvisited nodes means that one or more nodes was unreachable from our start position |
|
// ie, two places the player might want to traverse but would not be able to if we built here |
|
for ( i=0;i<8;i++ ) |
|
{ |
|
if ( bNodeVisited[i] == false && bNodeClear[i] == true ) |
|
{ |
|
return false; |
|
} |
|
} |
|
|
|
return true; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Test around the build position, one quadrant at a time |
|
//----------------------------------------------------------------------------- |
|
void CBaseObject::RecursiveTestBuildSpace( int iNode, bool *bNodeClear, bool *bNodeVisited ) |
|
{ |
|
// if the node is visited already |
|
if ( bNodeVisited[iNode] == true ) |
|
return; |
|
|
|
// if the test node is blocked |
|
if ( bNodeClear[iNode] == false ) |
|
return; |
|
|
|
bNodeVisited[iNode] = true; |
|
|
|
int iLeftNode = iNode - 1; |
|
if ( iLeftNode < 0 ) |
|
iLeftNode = 7; |
|
|
|
RecursiveTestBuildSpace( iLeftNode, bNodeClear, bNodeVisited ); |
|
|
|
int iRightNode = ( iNode + 1 ) % 8; |
|
|
|
RecursiveTestBuildSpace( iRightNode, bNodeClear, bNodeVisited ); |
|
} |
|
*/ |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Move the placement model to the current position. Return false if it's an invalid position |
|
//----------------------------------------------------------------------------- |
|
bool CBaseObject::UpdatePlacement( void ) |
|
{ |
|
if ( MustBeBuiltOnAttachmentPoint() ) |
|
{ |
|
return UpdateAttachmentPlacement(); |
|
} |
|
|
|
// Finds bsp-valid place for building to be built |
|
// Checks for validity, nearby to other entities, in line of sight |
|
m_bPlacementOK = IsPlacementPosValid(); |
|
|
|
Teleport( &m_vecBuildOrigin, &GetLocalAngles(), NULL ); |
|
|
|
return m_bPlacementOK; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: See if we should be snapping to a build position |
|
//----------------------------------------------------------------------------- |
|
bool CBaseObject::FindSnapToBuildPos( CBaseObject *pObjectOverride ) |
|
{ |
|
if ( !MustBeBuiltOnAttachmentPoint() ) |
|
return false; |
|
|
|
CTFPlayer *pPlayer = GetOwner(); |
|
|
|
if ( !pPlayer ) |
|
{ |
|
return false; |
|
} |
|
|
|
bool bSnappedToPoint = false; |
|
bool bShouldAttachToParent = false; |
|
|
|
Vector vecNearestBuildPoint = vec3_origin; |
|
|
|
// See if there are any nearby build positions to snap to |
|
float flNearestPoint = 9999; |
|
int i; |
|
|
|
bool bHostileAttachment = IsHostileUpgrade(); |
|
int iMyTeam = GetTeamNumber(); |
|
|
|
if ( !pObjectOverride ) |
|
{ |
|
int nTeamCount = TFTeamMgr()->GetTeamCount(); |
|
for ( int iTeam = FIRST_GAME_TEAM; iTeam < nTeamCount; ++iTeam ) |
|
{ |
|
// Hostile attachments look for enemy objects only |
|
if ( bHostileAttachment ) |
|
{ |
|
if ( iTeam == iMyTeam ) |
|
{ |
|
continue; |
|
} |
|
} |
|
// Friendly attachments look for friendly objects only |
|
else if ( iTeam != iMyTeam ) |
|
{ |
|
continue; |
|
} |
|
|
|
CTFTeam *pTeam = ( CTFTeam * )GetGlobalTeam( iTeam ); |
|
if ( !pTeam ) |
|
continue; |
|
|
|
// See if we're allowed to build on Robots |
|
if ( TFGameRules() && TFGameRules()->GameModeUsesMiniBosses() && |
|
GetType() == OBJ_ATTACHMENT_SAPPER && !pPlayer->IsBot() ) |
|
{ |
|
CUtlVector< CTFPlayer * > playerVector; |
|
CollectPlayers( &playerVector, pPlayer->GetOpposingTFTeam()->GetTeamNumber(), COLLECT_ONLY_LIVING_PLAYERS ); |
|
FOR_EACH_VEC( playerVector, i ) |
|
{ |
|
if ( !playerVector[i]->IsBot() ) |
|
continue; |
|
|
|
if ( FindBuildPointOnPlayer( playerVector[i], pPlayer, flNearestPoint, vecNearestBuildPoint ) ) |
|
{ |
|
bSnappedToPoint = true; |
|
bShouldAttachToParent = true; |
|
} |
|
} |
|
} |
|
|
|
// look for nearby buildpoints on other objects |
|
for ( i = 0; i < pTeam->GetNumObjects(); i++ ) |
|
{ |
|
CBaseObject *pObject = pTeam->GetObject(i); |
|
Assert( pObject ); |
|
if ( pObject && !pObject->IsPlacing() ) |
|
{ |
|
if ( FindNearestBuildPoint( pObject, pPlayer, flNearestPoint, vecNearestBuildPoint ) ) |
|
{ |
|
bSnappedToPoint = true; |
|
bShouldAttachToParent = true; |
|
} |
|
} |
|
} |
|
} |
|
} |
|
else |
|
{ |
|
if ( !pObjectOverride->IsPlacing() ) |
|
{ |
|
if ( FindNearestBuildPoint( pObjectOverride, pPlayer, flNearestPoint, vecNearestBuildPoint, true ) ) |
|
{ |
|
bSnappedToPoint = true; |
|
bShouldAttachToParent = true; |
|
} |
|
} |
|
} |
|
|
|
if ( !bSnappedToPoint ) |
|
{ |
|
AddEffects( EF_NODRAW ); |
|
} |
|
else |
|
{ |
|
RemoveEffects( EF_NODRAW ); |
|
|
|
if ( bShouldAttachToParent ) |
|
{ |
|
AttachObjectToObject( m_hBuiltOnEntity.Get(), m_iBuiltOnPoint, vecNearestBuildPoint ); |
|
} |
|
|
|
m_vecBuildOrigin = vecNearestBuildPoint; |
|
} |
|
|
|
return bSnappedToPoint; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Are we currently in a buildable position |
|
//----------------------------------------------------------------------------- |
|
bool CBaseObject::IsValidPlacement( void ) const |
|
{ |
|
return m_bPlacementOK; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
const char *CBaseObject::GetResponseRulesModifier( void ) |
|
{ |
|
switch ( GetType() ) |
|
{ |
|
case OBJ_DISPENSER: return "objtype:dispenser"; break; |
|
case OBJ_TELEPORTER: return "objtype:teleporter"; break; |
|
case OBJ_SENTRYGUN: return "objtype:sentrygun"; break; |
|
case OBJ_ATTACHMENT_SAPPER: return "objtype:sapper"; break; |
|
default: |
|
break; |
|
} |
|
return NULL; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// 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() ) |
|
{ |
|
/* |
|
if ( ((CTFPlayer*)pBuilder)->IsPlayerClass( TF_CLASS_ENGINEER ) ) |
|
{ |
|
((CTFPlayer*)pBuilder)->HintMessage( HINT_ENGINEER_USE_WRENCH_ONOWN ); |
|
} |
|
*/ |
|
|
|
if ( !IsCarried() ) |
|
{ |
|
if ( !ShouldQuickBuild() ) |
|
{ |
|
int iAmountPlayerPaidForMe = ((CTFPlayer*)pBuilder)->StartedBuildingObject( m_iObjectType ); |
|
if ( !iAmountPlayerPaidForMe ) |
|
{ |
|
// Player couldn't afford to pay for me, so abort |
|
ClientPrint( (CBasePlayer*)pBuilder, HUD_PRINTCENTER, "Not enough resources.\n" ); |
|
StopPlacement(); |
|
return false; |
|
} |
|
} |
|
|
|
((CTFPlayer*)pBuilder)->SpeakConceptIfAllowed( MP_CONCEPT_BUILDING_OBJECT, GetResponseRulesModifier() ); |
|
} |
|
else |
|
{ |
|
m_bCarried = false; |
|
m_bCarryDeploy = true; |
|
m_flCarryDeployTime = gpGlobals->curtime; |
|
SetActivity( ACT_OBJ_ASSEMBLING ); |
|
|
|
((CTFPlayer*)pBuilder)->m_flCommentOnCarrying = 0.f; |
|
((CTFPlayer*)pBuilder)->SpeakConceptIfAllowed( MP_CONCEPT_REDEPLOY_BUILDING, GetResponseRulesModifier() ); |
|
} |
|
} |
|
|
|
// Check to see if we need to add this to a hierarchy. We can just do a simple ray trace from the center as |
|
// the placement code has guarenteed we are in a valid position. |
|
trace_t trace; |
|
UTIL_TraceHull( GetAbsOrigin() + Vector( 0.0f, 0.0f, 2.0f ), GetAbsOrigin() - Vector( 0.0f, 0.0f, 2.0f ), vec3_origin, vec3_origin, MASK_PLAYERSOLID_BRUSHONLY, this, COLLISION_GROUP_PLAYER_MOVEMENT, &trace ); |
|
if ( trace.m_pEnt && trace.m_pEnt->IsBSPModel() ) |
|
{ |
|
CFuncTrackTrain *pTrain = dynamic_cast<CFuncTrackTrain*>( trace.m_pEnt ); |
|
if ( pTrain ) |
|
{ |
|
SetParent( pTrain ); |
|
} |
|
} |
|
|
|
// 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; |
|
if ( m_bCarryDeploy ) |
|
{ |
|
SetHealth( m_iHealthOnPickup ); |
|
|
|
IGameEvent * event = gameeventmanager->CreateEvent( "player_dropobject" ); |
|
if ( event ) |
|
{ |
|
CTFPlayer *pTFPlayer = ToTFPlayer( pBuilder ); |
|
event->SetInt( "userid", pTFPlayer ? pTFPlayer->GetUserID() : 0 ); |
|
event->SetInt( "object", GetType() ); |
|
event->SetInt( "index", entindex() ); // object entity index |
|
|
|
gameeventmanager->FireEvent( event, true ); // don't send to clients |
|
} |
|
} |
|
else if ( IsMiniBuilding() ) |
|
{ |
|
int iHealth = GetMaxHealthForCurrentLevel(); |
|
if ( !IsDisposableBuilding() ) |
|
{ |
|
iHealth /= 2.0f; |
|
} |
|
SetHealth( iHealth ); |
|
} |
|
else |
|
{ |
|
SetHealth( OBJECT_CONSTRUCTION_STARTINGHEALTH ); |
|
} |
|
m_flPercentageConstructed = 0; |
|
|
|
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 |
|
if ( !(m_fObjectFlags & OF_DOESNT_HAVE_A_MODEL) ) |
|
{ |
|
SpawnControlPanels(); |
|
} |
|
|
|
// Tell the object we've been built on that we exist |
|
if ( IsBuiltOnAttachment() && !m_hBuiltOnEntity->IsPlayer() ) |
|
{ |
|
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(); |
|
m_flConstructionStartTime = gpGlobals->curtime; |
|
|
|
if ( pBuilder && pBuilder->IsPlayer() ) |
|
{ |
|
CTFPlayer *pTFBuilder = ToTFPlayer( pBuilder ); |
|
pTFBuilder->FinishedObject( this ); |
|
IGameEvent * event = gameeventmanager->CreateEvent( "player_builtobject" ); |
|
if ( event ) |
|
{ |
|
event->SetInt( "userid", pTFBuilder->GetUserID() ); |
|
event->SetInt( "object", ObjectType() ); |
|
event->SetInt( "index", entindex() ); // object entity index |
|
gameeventmanager->FireEvent( event, true ); // don't send to clients |
|
} |
|
} |
|
|
|
m_vecBuildOrigin = GetAbsOrigin(); |
|
|
|
int contents = UTIL_PointContents( m_vecBuildOrigin ); |
|
if ( contents & MASK_WATER ) |
|
{ |
|
SetWaterLevel( 3 ); |
|
} |
|
|
|
// instantly play the build anim |
|
DetermineAnimation(); |
|
|
|
if ( IsMiniBuilding() && ( GetType() != OBJ_DISPENSER ) ) |
|
{ |
|
// Set the skin after placement mode. |
|
m_nSkin = ( GetTeamNumber() == TF_TEAM_RED ) ? 2 : 3; |
|
} |
|
|
|
if ( ShouldQuickBuild() ) |
|
{ |
|
DoQuickBuild(); |
|
} |
|
|
|
return true; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
bool CBaseObject::ShouldBeMiniBuilding( CTFPlayer* pPlayer ) |
|
{ |
|
if ( !pPlayer ) |
|
return false; |
|
|
|
CTFWrench* pWrench = dynamic_cast<CTFWrench*>( pPlayer->Weapon_OwnsThisID( TF_WEAPON_WRENCH ) ); |
|
if ( !pWrench ) |
|
return false; |
|
|
|
if ( TFGameRules()->GameModeUsesUpgrades() ) |
|
{ |
|
if ( pPlayer->GetNumObjects( OBJ_SENTRYGUN ) && pPlayer->CanBuild( OBJ_SENTRYGUN ) == CB_CAN_BUILD && !IsCarried() ) |
|
return true; |
|
} |
|
|
|
if ( !pWrench->IsPDQ() ) |
|
return false; |
|
|
|
return true; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CBaseObject::MakeMiniBuilding( CTFPlayer* pPlayer ) |
|
{ |
|
if ( !ShouldBeMiniBuilding( pPlayer ) || IsMiniBuilding() ) |
|
return; |
|
|
|
m_bMiniBuilding = true; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CBaseObject::MakeDisposableBuilding( CTFPlayer *pPlayer ) |
|
{ |
|
m_bDisposableBuilding = true; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Continue construction of this object |
|
//----------------------------------------------------------------------------- |
|
void CBaseObject::BuildingThink( void ) |
|
{ |
|
// Continue construction |
|
Construct( (GetMaxHealth() - OBJECT_CONSTRUCTION_STARTINGHEALTH) / m_flTotalConstructionTime * OBJECT_CONSTRUCTION_INTERVAL ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// 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; |
|
|
|
OnGoActive(); |
|
|
|
// 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(); |
|
|
|
if ( IsUsingReverseBuild() ) |
|
{ |
|
// if we don't have a sapper (but we should!) then set ourselves as the damager |
|
CObjectSapper *pSapper = GetSapper(); |
|
CBaseEntity *pDamager = pSapper ? pSapper : this; |
|
int iCustomDamageType = pSapper ? TF_DMG_CUSTOM_SAPPER_RECORDER_DEATH : 0; |
|
|
|
CTakeDamageInfo info; |
|
info.SetInflictor( pDamager ); |
|
info.SetAttacker( pDamager ); |
|
info.SetDamageForce( vec3_origin ); |
|
info.SetDamagePosition( GetAbsOrigin() ); |
|
info.SetDamage( 0 ); |
|
info.SetDamageType( DMG_CRUSH ); |
|
info.SetDamageCustom( iCustomDamageType ); |
|
Killed( info ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Objects store health in hacky ways |
|
//----------------------------------------------------------------------------- |
|
void CBaseObject::SetHealth( float flHealth ) |
|
{ |
|
if ( m_bCarryDeploy && (flHealth>m_iHealthOnPickup) ) |
|
{ |
|
// If we are re-deploying after being carried we shouldn't gain more health than we had |
|
// on pickup until the deploy process is finished. |
|
flHealth = m_iHealthOnPickup; |
|
} |
|
|
|
bool changed = m_flHealth != flHealth; |
|
|
|
m_flHealth = flHealth; |
|
m_iHealth = ceil(m_flHealth); |
|
|
|
|
|
/* |
|
// If we a pose parameter, set the pose parameter to reflect our health |
|
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: Override base traceattack to prevent visible effects from team members shooting me |
|
//----------------------------------------------------------------------------- |
|
void CBaseObject::TraceAttack( const CTakeDamageInfo &inputInfo, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator ) |
|
{ |
|
// Prevent team damage here so blood doesn't appear |
|
if ( inputInfo.GetAttacker() ) |
|
{ |
|
if ( InSameTeam(inputInfo.GetAttacker()) ) |
|
{ |
|
// Pass Damage to enemy attachments |
|
int iNumObjects = GetNumObjectsOnMe(); |
|
for ( int iPoint=iNumObjects-1;iPoint >= 0; --iPoint ) |
|
{ |
|
CBaseObject *pObject = GetBuildPointObject( iPoint ); |
|
|
|
if ( pObject && pObject->IsHostileUpgrade() ) |
|
{ |
|
pObject->TraceAttack(inputInfo, vecDir, ptr, pAccumulator ); |
|
} |
|
} |
|
return; |
|
} |
|
} |
|
|
|
SpawnBlood( ptr->endpos, vecDir, BloodColor(), inputInfo.GetDamage() ); |
|
|
|
AddMultiDamage( inputInfo, 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; |
|
int idx; |
|
|
|
// Build the unique columns: |
|
for( 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: Return the first non-hostile object build on this object |
|
//----------------------------------------------------------------------------- |
|
CBaseEntity *CBaseObject::GetFirstFriendlyObjectOnMe( void ) |
|
{ |
|
CBaseObject *pFirstObject = NULL; |
|
|
|
IHasBuildPoints *pBPInterface = dynamic_cast<IHasBuildPoints*>(this); |
|
int iNumObjects = pBPInterface->GetNumObjectsOnMe(); |
|
for ( int iPoint=0;iPoint<iNumObjects;iPoint++ ) |
|
{ |
|
CBaseObject *pObject = GetBuildPointObject( iPoint ); |
|
|
|
if ( pObject && !pObject->IsHostileUpgrade() ) |
|
{ |
|
pFirstObject = pObject; |
|
break; |
|
} |
|
} |
|
|
|
return pFirstObject; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Pass the specified amount of damage through to any objects I have built on me |
|
//----------------------------------------------------------------------------- |
|
bool CBaseObject::PassDamageOntoChildren( const CTakeDamageInfo &info, float *flDamageLeftOver ) |
|
{ |
|
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 = GetFirstFriendlyObjectOnMe(); |
|
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() ) |
|
{ |
|
const 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 = GetFirstFriendlyObjectOnMe(); |
|
} |
|
|
|
*flDamageLeftOver = flDamage; |
|
return false; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
int CBaseObject::OnTakeDamage( const CTakeDamageInfo &info ) |
|
{ |
|
if ( !IsAlive() ) |
|
return info.GetDamage(); |
|
|
|
if ( m_takedamage == DAMAGE_NO ) |
|
return 0; |
|
|
|
if ( IsPlacing() ) |
|
return 0; |
|
|
|
// Check teams |
|
if ( info.GetAttacker() ) |
|
{ |
|
if ( InSameTeam(info.GetAttacker()) ) |
|
return 0; |
|
|
|
if ( TFGameRules() && TFGameRules()->IsTruceActive() ) |
|
{ |
|
// players cannot damage buildings while a truce is active |
|
if ( info.GetAttacker()->IsPlayer() && info.GetAttacker()->IsTruceValidForEnt() && ( ( info.GetAttacker()->GetTeamNumber() == TF_TEAM_RED ) || ( info.GetAttacker()->GetTeamNumber() == TF_TEAM_BLUE ) ) ) |
|
return 0; |
|
} |
|
} |
|
|
|
m_AchievementData.AddDamagerToHistory( info.GetAttacker() ); |
|
if ( info.GetAttacker()->IsPlayer() ) |
|
{ |
|
ToTFPlayer( info.GetAttacker() )->m_AchievementData.AddTargetToHistory( this ); |
|
} |
|
|
|
IHasBuildPoints *pBPInterface = dynamic_cast<IHasBuildPoints*>(this); |
|
|
|
float flDamage = info.GetDamage(); |
|
|
|
// Buildings are resistant to plasma damage. |
|
if ( info.GetDamageCustom() == TF_DMG_CUSTOM_PLASMA ) |
|
{ |
|
flDamage *= 0.2f; |
|
} |
|
|
|
// Charged plasma damage disables buildings for a short time. |
|
if ( info.GetDamageCustom() == TF_DMG_CUSTOM_PLASMA_CHARGED ) |
|
{ |
|
flDamage *= 0.2f; |
|
m_flPlasmaDisableTime = gpGlobals->curtime + PLASMA_DISABLE_TIME; |
|
m_bPlasmaDisable = true; |
|
UpdateDisabledState(); |
|
} |
|
|
|
// 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(); |
|
} |
|
|
|
|
|
CTFWeaponBase *pWeapon = dynamic_cast<CTFWeaponBase *>(info.GetWeapon()); |
|
if ( pWeapon ) |
|
{ |
|
#ifdef STAGING_ONLY |
|
// Attacker has building disabling properties |
|
float flDisablingAttack = 0.0f; |
|
CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( pWeapon, flDisablingAttack, disable_buildings_on_hit ); |
|
if ( flDisablingAttack ) |
|
{ |
|
// do not override if existing time is longer |
|
m_flPlasmaDisableTime = Max( gpGlobals->curtime + flDisablingAttack, m_flPlasmaDisableTime ); |
|
m_bPlasmaDisable = true; |
|
UpdateDisabledState(); |
|
} |
|
#endif // STAGING_ONLY |
|
|
|
// Apply attributes that increase damage vs buildings |
|
CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( pWeapon, flDamage, mult_dmg_vs_buildings ); |
|
CTFPlayer *pAttacker = ToTFPlayer( info.GetAttacker() ); |
|
if ( pAttacker ) |
|
{ |
|
pWeapon->ApplyOnHitAttributes( NULL, pAttacker, info ); |
|
} |
|
} |
|
|
|
if ( TFGameRules()->IsPowerupMode() ) |
|
{ |
|
CTFPlayer *pAttacker = ToTFPlayer( info.GetAttacker() ); |
|
if ( pAttacker ) |
|
{ |
|
if ( pAttacker->m_Shared.GetCarryingRuneType() == RUNE_STRENGTH || pAttacker->m_Shared.InCond( TF_COND_RUNE_IMBALANCE ) ) |
|
{ |
|
flDamage *= 2.f; |
|
} |
|
|
|
if ( pAttacker->m_Shared.GetCarryingRuneType() == RUNE_KNOCKOUT ) |
|
{ |
|
flDamage *= 4.f; |
|
} |
|
|
|
if ( pAttacker->m_Shared.GetCarryingRuneType() == RUNE_VAMPIRE ) |
|
{ |
|
int iModHealthOnHit = flDamage; |
|
|
|
if ( iModHealthOnHit > 0 ) |
|
{ |
|
pAttacker->TakeHealth( iModHealthOnHit, DMG_GENERIC ); |
|
} |
|
} |
|
} |
|
} |
|
|
|
bool bFriendlyObjectsAttached = false; |
|
int iNumObjects = pBPInterface->GetNumObjectsOnMe(); |
|
for ( int iPoint=0;iPoint<iNumObjects;iPoint++ ) |
|
{ |
|
CBaseObject *pObject = GetBuildPointObject( iPoint ); |
|
|
|
if ( !pObject || pObject->IsHostileUpgrade() ) |
|
{ |
|
continue; |
|
} |
|
|
|
bFriendlyObjectsAttached = true; |
|
break; |
|
} |
|
|
|
// Don't look, Tom Bui! |
|
static struct |
|
{ |
|
bool operator()( const float flHealth, const float flDamage ) const |
|
{ |
|
return ( ( flHealth - flDamage ) < 1 ); |
|
} |
|
} IsDamageFatal; |
|
|
|
// Only track actual damage - not overkill |
|
m_AchievementData.AddDamageEventToHistory( info.GetAttacker(), ( IsDamageFatal( m_flHealth, flDamage ) ) ? m_flHealth : flDamage ); |
|
|
|
// if we cannot die |
|
if ( m_bCannotDie && IsDamageFatal( m_flHealth, flDamage ) ) |
|
{ |
|
flDamage = m_flHealth - 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 = ( bFriendlyObjectsAttached ) && IsDamageFatal( m_flHealth, flDamage ); |
|
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 ) |
|
{ |
|
m_iLifetimeDamage += floor( MIN( flDamage, m_flHealth ) ); |
|
if ( m_iLifetimeDamage > tf_obj_damage_tank_achievement_amount.GetInt() && GetBuilder() ) |
|
{ |
|
GetBuilder()->AwardAchievement( ACHIEVEMENT_TF_ENGINEER_TANK_DAMAGE ); |
|
} |
|
|
|
// Recheck our death possibility, because our objects may have all been blown off us by now |
|
bWillDieButCant = ( bFriendlyObjectsAttached ) && IsDamageFatal( m_flHealth, flDamage ); |
|
if ( !bWillDieButCant ) |
|
{ |
|
// Reduce health |
|
SetHealth( m_flHealth - flDamage ); |
|
} |
|
} |
|
|
|
m_OnDamaged.FireOutput(info.GetAttacker(), this); |
|
|
|
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( info ); |
|
|
|
// Tell our builder to speak about it |
|
if ( m_hBuilder ) |
|
{ |
|
m_hBuilder->SpeakConceptIfAllowed( MP_CONCEPT_LOST_OBJECT, GetResponseRulesModifier() ); |
|
} |
|
} |
|
|
|
const char* szInflictor = "unknown"; |
|
if( info.GetInflictor() ) |
|
szInflictor = (char*)info.GetInflictor()->GetClassname(); |
|
|
|
ReportDamage( szInflictor, GetClassname(), flDamage, GetHealth(), GetMaxHealth() ); |
|
|
|
IGameEvent *event = gameeventmanager->CreateEvent( "npc_hurt" ); |
|
if ( event ) |
|
{ |
|
event->SetInt( "entindex", entindex() ); |
|
event->SetInt( "health", Max( 0, (int)GetHealth() ) ); |
|
event->SetInt( "damageamount", flDamage ); |
|
event->SetBool( "crit", ( info.GetDamageType() & DMG_CRITICAL ) ? true : false ); |
|
|
|
CTFPlayer *pTFAttacker = ToTFPlayer( info.GetAttacker() ); |
|
if ( pTFAttacker ) |
|
{ |
|
event->SetInt( "attacker_player", pTFAttacker->GetUserID() ); |
|
|
|
if ( pTFAttacker->GetActiveTFWeapon() ) |
|
{ |
|
event->SetInt( "weaponid", pTFAttacker->GetActiveTFWeapon()->GetWeaponID() ); |
|
} |
|
else |
|
{ |
|
event->SetInt( "weaponid", 0 ); |
|
} |
|
} |
|
else |
|
{ |
|
// hurt by world |
|
event->SetInt( "attacker_player", 0 ); |
|
event->SetInt( "weaponid", 0 ); |
|
} |
|
|
|
gameeventmanager->FireEvent( event ); |
|
} |
|
|
|
return flDamage; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Repair / Help-Construct this object the specified amount |
|
//----------------------------------------------------------------------------- |
|
bool CBaseObject::Construct( float flHealth ) |
|
{ |
|
// Multiply it by the repair rate |
|
flHealth *= GetConstructionMultiplier(); |
|
if ( !flHealth ) |
|
return false; |
|
|
|
if ( IsBuilding() ) |
|
{ |
|
// Reduce the construction time by the correct amount for the health passed in |
|
float flConstructionTime = flHealth / ((GetMaxHealth() - OBJECT_CONSTRUCTION_STARTINGHEALTH) / m_flTotalConstructionTime); |
|
if ( flConstructionTime < 0.0f ) |
|
{ |
|
flConstructionTime *= -1.0f; |
|
} |
|
|
|
m_flConstructionTimeLeft = MAX( 0, m_flConstructionTimeLeft - flConstructionTime); |
|
m_flConstructionTimeLeft = clamp( m_flConstructionTimeLeft, 0.0f, m_flTotalConstructionTime ); |
|
|
|
m_flPercentageConstructed = m_flConstructionTimeLeft / m_flTotalConstructionTime; |
|
|
|
if ( flHealth >= 0.0f ) |
|
{ |
|
// Only do this if we're not reversing construction |
|
m_flPercentageConstructed = 1.0f - m_flPercentageConstructed; |
|
} |
|
m_flPercentageConstructed = clamp( (float) m_flPercentageConstructed, 0.0f, 1.0f ); |
|
|
|
// Increase health (unless it's a mini-building, which start at max health) |
|
// Minibuildings build health at a reduced rate |
|
// Staging_engy |
|
{ |
|
SetHealth( MIN( GetMaxHealth(), m_flHealth + (IsMiniBuilding() ? (flHealth * 0.5f) : 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(), MAX( 1, m_flHealth + flHealth ) ) ); |
|
|
|
m_OnRepaired.FireOutput( this, this); |
|
|
|
// Return true if we're fully healed now |
|
if ( GetHealth() == GetMaxHealth() ) |
|
return true; |
|
} |
|
|
|
return false; |
|
} |
|
|
|
//---------------------------------------------------------------------------------------------------------------------------------------- |
|
void CBaseObject::OnConstructionHit( CTFPlayer *pPlayer, CTFWrench *pWrench, Vector hitLoc ) |
|
{ |
|
// Get the player index |
|
int iPlayerIndex = pPlayer->entindex(); |
|
|
|
// The time the repair is going to expire |
|
float flRepairExpireTime = gpGlobals->curtime + 1.0; |
|
|
|
// Update or Add the expire time to the list |
|
int index = m_ConstructorList.Find( iPlayerIndex ); |
|
if ( index == m_ConstructorList.InvalidIndex() ) |
|
{ |
|
index = m_ConstructorList.Insert( iPlayerIndex ); |
|
m_ConstructorList[index].flValue = pWrench->GetConstructionValue(); |
|
} |
|
|
|
m_ConstructorList[index].flHitTime = flRepairExpireTime; |
|
|
|
// Play a construction hit effect. |
|
CPVSFilter filter( hitLoc ); |
|
TE_TFParticleEffect( filter, 0.0f, "nutsnbolts_build", hitLoc, QAngle(0,0,0) ); |
|
} |
|
|
|
//---------------------------------------------------------------------------------------------------------------------------------------- |
|
float CBaseObject::GetConstructionMultiplier( void ) |
|
{ |
|
if ( IsUsingReverseBuild() ) |
|
return -1.0f; |
|
|
|
float flMultiplier = 1.0; |
|
|
|
// expire all the old |
|
int i = m_ConstructorList.LastInorder(); |
|
while ( i != m_ConstructorList.InvalidIndex() ) |
|
{ |
|
int iThis = i; |
|
i = m_ConstructorList.PrevInorder( i ); |
|
if ( m_ConstructorList[iThis].flHitTime < gpGlobals->curtime ) |
|
{ |
|
m_ConstructorList.RemoveAt( iThis ); |
|
} |
|
else |
|
{ |
|
// STAGING_ENGY |
|
// each Player adds a fixed amount of speed boost |
|
// Carry deploy hits add more |
|
flMultiplier += ( m_ConstructorList[iThis].flValue ); |
|
} |
|
} |
|
|
|
// See if we have any attributes that want to modify our build rate |
|
CTFPlayer* pBuilder = GetOwner(); |
|
if( pBuilder ) |
|
{ |
|
flMultiplier += pBuilder->GetObjectBuildSpeedMultiplier( ObjectType(), m_bCarryDeploy ); |
|
} |
|
|
|
return flMultiplier; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Object is exploding because it was killed or detonate |
|
//----------------------------------------------------------------------------- |
|
void CBaseObject::Explode( void ) |
|
{ |
|
const char *pExplodeSound = GetObjectInfo( ObjectType() )->m_pExplodeSound; |
|
|
|
if ( pExplodeSound && Q_strlen(pExplodeSound) > 0 ) |
|
{ |
|
EmitSound( pExplodeSound ); |
|
} |
|
|
|
const char *pExplodeEffect = GetObjectInfo( ObjectType() )->m_pExplosionParticleEffect; |
|
if ( pExplodeEffect && pExplodeEffect[0] != '\0' ) |
|
{ |
|
// Send to everyone - we're inside prediction for the engy who hit this off, but we |
|
// don't predict that the hit will kill this object. |
|
CDisablePredictionFiltering disabler; |
|
|
|
Vector origin = GetAbsOrigin(); |
|
QAngle up(-90,0,0); |
|
|
|
CPVSFilter filter( origin ); |
|
TE_TFParticleEffect( filter, 0.0f, pExplodeEffect, origin, up ); |
|
} |
|
|
|
// create some delicious, metal filled gibs |
|
CreateObjectGibs(); |
|
} |
|
|
|
void CBaseObject::CreateObjectGibs( void ) |
|
{ |
|
if ( m_aGibs.Count() <= 0 ) |
|
{ |
|
return; |
|
} |
|
|
|
const CObjectInfo *pObjectInfo = GetObjectInfo( ObjectType() ); |
|
|
|
// grant some percentage of the cost to build if number of metal to drop is not specified |
|
const float flMetalCostPercentage = 0.5f; |
|
const int nTotalMetal = pObjectInfo->m_iMetalToDropInGibs == 0 ? pObjectInfo->m_Cost * flMetalCostPercentage : pObjectInfo->m_iMetalToDropInGibs; |
|
|
|
|
|
int nMetalPerGib = nTotalMetal / m_aGibs.Count(); |
|
int nLeftOver = nTotalMetal % m_aGibs.Count(); |
|
|
|
if ( IsMiniBuilding() ) |
|
{ |
|
// STAGING_ENGY |
|
nMetalPerGib = 0; |
|
nLeftOver = 0; |
|
} |
|
|
|
int i; |
|
for ( i=0; i<m_aGibs.Count(); i++ ) |
|
{ |
|
// make sure we drop all metal include left over from int math |
|
CreateAmmoPack( m_aGibs[i].modelName, i == 0 ? nMetalPerGib + nLeftOver : nMetalPerGib ); |
|
} |
|
} |
|
|
|
CTFAmmoPack* CBaseObject::CreateAmmoPack( const char *pchModel, int nMetal ) |
|
{ |
|
CTFAmmoPack *pAmmoPack = CTFAmmoPack::Create( GetAbsOrigin(), GetAbsAngles(), this, pchModel ); |
|
Assert( pAmmoPack ); |
|
if ( pAmmoPack ) |
|
{ |
|
pAmmoPack->ActivateWhenAtRest(); |
|
|
|
// Fill up the ammo pack. |
|
pAmmoPack->GiveAmmo( nMetal, TF_AMMO_METAL ); |
|
|
|
// Calculate the initial impulse on the weapon. |
|
Vector vecImpulse( random->RandomFloat( -0.5, 0.5 ), random->RandomFloat( -0.5, 0.5 ), random->RandomFloat( 0.75, 1.25 ) ); |
|
VectorNormalize( vecImpulse ); |
|
vecImpulse *= random->RandomFloat( tf_obj_gib_velocity_min.GetFloat(), tf_obj_gib_velocity_max.GetFloat() ); |
|
|
|
// Cap the impulse. |
|
float flSpeed = vecImpulse.Length(); |
|
if ( flSpeed > tf_obj_gib_maxspeed.GetFloat() ) |
|
{ |
|
VectorScale( vecImpulse, tf_obj_gib_maxspeed.GetFloat() / flSpeed, vecImpulse ); |
|
} |
|
|
|
if ( pAmmoPack->VPhysicsGetObject() ) |
|
{ |
|
// We can probably remove this when the mass on the weapons is correct! |
|
//pAmmoPack->VPhysicsGetObject()->SetMass( 25.0f ); |
|
AngularImpulse angImpulse( 0, random->RandomFloat( 0, 100 ), 0 ); |
|
pAmmoPack->VPhysicsGetObject()->SetVelocityInstantaneous( &vecImpulse, &angImpulse ); |
|
} |
|
|
|
pAmmoPack->SetInitialVelocity( vecImpulse ); |
|
|
|
pAmmoPack->m_nSkin = ( GetTeamNumber() == TF_TEAM_RED ) ? 0 : 1; |
|
|
|
// Give the ammo pack some health, so that trains can destroy it. |
|
pAmmoPack->SetCollisionGroup( COLLISION_GROUP_DEBRIS ); |
|
pAmmoPack->m_takedamage = DAMAGE_YES; |
|
pAmmoPack->SetHealth( 900 ); |
|
pAmmoPack->m_bObjGib = true; |
|
|
|
if ( IsMiniBuilding() ) |
|
{ |
|
pAmmoPack->SetModelScale( 0.6f ); |
|
} |
|
} |
|
|
|
return pAmmoPack; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Object has been blown up. Drop resource chunks upto the value of my max health. |
|
//----------------------------------------------------------------------------- |
|
void CBaseObject::Killed( const CTakeDamageInfo &info ) |
|
{ |
|
m_bDying = true; |
|
|
|
// Find the killer & the scorer |
|
CBaseEntity *pInflictor = info.GetInflictor(); |
|
CBaseEntity *pKiller = info.GetAttacker(); |
|
CTFPlayer *pScorer = ToTFPlayer( TFGameRules()->GetDeathScorer( pKiller, pInflictor, this ) ); |
|
CTFPlayer *pAssister = NULL; |
|
|
|
// If we are being carried, |
|
|
|
// if this object has a sapper on it, and was not killed by the sapper (killed by damage other than crush, since sapper does crushing damage), |
|
// award an assist to the owner of the sapper since it probably contributed to destroying this object |
|
CObjectSapper *pSapper = GetSapper(); |
|
if ( pSapper && !( DMG_CRUSH & info.GetDamageType() ) && !m_bPlasmaDisable ) |
|
{ |
|
// give an assist to the sapper's owner |
|
pAssister = pSapper->GetOwner(); |
|
if ( pAssister ) |
|
{ |
|
CTF_GameStats.Event_AssistDestroyBuilding( pAssister, this ); |
|
|
|
// Also increment the SapBuildings grind achievement |
|
pAssister->AwardAchievement( ACHIEVEMENT_TF_SPY_SAPPER_GRIND ); |
|
} |
|
} |
|
else if ( pScorer ) |
|
{ |
|
// If a player is healing the scorer, give that player credit for the assist |
|
CTFPlayer *pHealer = ToTFPlayer( static_cast<CBaseEntity *>( pScorer->m_Shared.GetFirstHealer() ) ); |
|
// Must be a medic to receive a healing assist, otherwise engineers get credit for assists from dispensers doing healing. |
|
// Also don't give an assist for healing if the inflictor was a sentry gun, otherwise medics healing engineers get assists for the engineer's sentry kills. |
|
if ( pHealer && ( pHealer->GetPlayerClass()->GetClassIndex() == TF_CLASS_MEDIC ) ) |
|
{ |
|
pAssister = pHealer; |
|
} |
|
} |
|
|
|
// Don't do anything if we were detonated or dismantled |
|
if ( pScorer && pInflictor != this ) |
|
{ |
|
IGameEvent * event = gameeventmanager->CreateEvent( "object_destroyed" ); |
|
|
|
// Work out what killed the player, and send a message to all clients about it |
|
int iWeaponID; |
|
const char *killer_weapon_name = TFGameRules()->GetKillingWeaponName( info, NULL, &iWeaponID ); |
|
const char *killer_weapon_log_name = killer_weapon_name; |
|
|
|
CTFPlayer *pTFPlayer = GetOwner(); |
|
|
|
CTFWeaponBase *pWeapon = dynamic_cast< CTFWeaponBase * >( pScorer->Weapon_OwnsThisID( iWeaponID ) ); |
|
if ( pWeapon ) |
|
{ |
|
CEconItemView *pItem = pWeapon->GetAttributeContainer()->GetItem(); |
|
|
|
if ( pItem ) |
|
{ |
|
if ( pItem->GetStaticData()->GetIconClassname() ) |
|
{ |
|
killer_weapon_name = pItem->GetStaticData()->GetIconClassname(); |
|
} |
|
|
|
if ( pItem->GetStaticData()->GetLogClassname() ) |
|
{ |
|
killer_weapon_log_name = pItem->GetStaticData()->GetLogClassname(); |
|
} |
|
} |
|
} |
|
|
|
if ( event ) |
|
{ |
|
if ( pTFPlayer ) |
|
{ |
|
event->SetInt( "userid", pTFPlayer->GetUserID() ); |
|
} |
|
if ( pAssister && ( pAssister != pScorer ) ) |
|
{ |
|
event->SetInt( "assister", pAssister->GetUserID() ); |
|
} |
|
|
|
event->SetInt( "attacker", pScorer->GetUserID() ); // attacker |
|
event->SetString( "weapon", killer_weapon_name ); |
|
event->SetString( "weapon_logclassname", killer_weapon_log_name ); |
|
event->SetInt( "weaponid", iWeaponID ); |
|
event->SetInt( "priority", 6 ); // HLTV event priority, not transmitted |
|
event->SetInt( "objecttype", GetType() ); |
|
event->SetInt( "index", entindex() ); // object entity index |
|
event->SetBool( "was_building", m_bBuilding ); |
|
|
|
gameeventmanager->FireEvent( event ); |
|
} |
|
|
|
CTF_GameStats.Event_PlayerDestroyedBuilding( pScorer, this ); |
|
pScorer->Event_KilledOther(this, info); |
|
|
|
// Also track stats for strange sappers. |
|
if ( pSapper ) |
|
{ |
|
CTFPlayer *pSapperOwner = pSapper->GetOwner(); |
|
Assert( pSapperOwner ); |
|
|
|
if ( pSapperOwner ) |
|
{ |
|
EconEntity_OnOwnerKillEaterEvent( dynamic_cast<CEconEntity *>( pSapperOwner->GetEntityForLoadoutSlot( LOADOUT_POSITION_BUILDING ) ), |
|
pSapperOwner, |
|
GetOwner(), |
|
kKillEaterEvent_BuildingSapped ); |
|
} |
|
} |
|
|
|
// Check for Demo achievement: |
|
// Kill an Engineer building that you can't see with a direct hit from a Grenade Launcher |
|
|
|
if ( pScorer && pScorer->IsPlayerClass( TF_CLASS_DEMOMAN) ) |
|
{ |
|
if ( pScorer->GetActiveTFWeapon() && ( pScorer->GetActiveTFWeapon()->GetWeaponID() == TF_WEAPON_GRENADELAUNCHER) ) |
|
{ |
|
if ( pInflictor && pInflictor->IsPlayer() == false ) |
|
{ |
|
CTFGrenadePipebombProjectile *pBaseGrenade = dynamic_cast< CTFGrenadePipebombProjectile* >( pInflictor ); |
|
if ( pBaseGrenade && pBaseGrenade->m_bTouched == false ) |
|
{ |
|
if ( pScorer->FVisible( this ) == false ) |
|
{ |
|
pScorer->AwardAchievement( ACHIEVEMENT_TF_DEMOMAN_KILL_BUILDING_DIRECT_HIT ); |
|
} |
|
} |
|
} |
|
} |
|
} |
|
} |
|
else |
|
{ |
|
IGameEvent * event = gameeventmanager->CreateEvent( "object_detonated" ); |
|
|
|
if ( event ) |
|
{ |
|
CTFPlayer *pTFPlayer = GetOwner(); |
|
if ( pTFPlayer ) |
|
{ |
|
event->SetInt( "userid", pTFPlayer->GetUserID() ); |
|
} |
|
event->SetInt( "objecttype", GetType() ); // object type |
|
event->SetInt( "index", entindex() ); // object entity index |
|
|
|
gameeventmanager->FireEvent( event ); |
|
} |
|
} |
|
|
|
// Don't create gibs if it reversed back to a toolbox |
|
if ( IsUsingReverseBuild() && ( DMG_CRUSH & info.GetDamageType() ) != 0 ) |
|
{ |
|
CTFAmmoPack *pAmmoPack = CreateAmmoPack( "models/weapons/w_models/w_toolbox.mdl", GetObjectInfo( ObjectType() )->m_iMetalToDropInGibs ); |
|
if ( pAmmoPack ) |
|
{ |
|
pAmmoPack->SetBodygroup( 1, 1 ); |
|
} |
|
|
|
CObjectSapper *pSapper = GetSapper(); |
|
if ( pSapper ) |
|
{ |
|
pSapper->Explode(); |
|
} |
|
} |
|
else |
|
{ |
|
// Do an explosion. |
|
Explode(); |
|
} |
|
|
|
// Stats tracking for strange items. |
|
EconEntity_OnOwnerKillEaterEvent( dynamic_cast<CEconEntity *>( info.GetWeapon() ), |
|
pScorer, |
|
GetOwner(), |
|
kKillEaterEvent_BuildingDestroyed ); |
|
|
|
UTIL_Remove( this ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Indicates this NPC's place in the relationship table. |
|
//----------------------------------------------------------------------------- |
|
Class_T CBaseObject::Classify( void ) |
|
{ |
|
return CLASS_NONE; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Get the type of this object |
|
//----------------------------------------------------------------------------- |
|
int CBaseObject::GetType() const |
|
{ |
|
return m_iObjectType; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Get the builder of this object |
|
//----------------------------------------------------------------------------- |
|
CTFPlayer *CBaseObject::GetBuilder( void ) const |
|
{ |
|
return m_hBuilder; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Return true if the Owning CTeam should clean this object up automatically |
|
//----------------------------------------------------------------------------- |
|
bool CBaseObject::ShouldAutoRemove( void ) |
|
{ |
|
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 |
|
CreateBuildPoints(); |
|
} |
|
|
|
CObjectSapper* CBaseObject::GetSapper( void ) |
|
{ |
|
if ( !HasSapper() ) |
|
return NULL; |
|
|
|
return dynamic_cast< CObjectSapper* >( FirstMoveChild() ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Return true if I have at least 1 sapper on me |
|
//----------------------------------------------------------------------------- |
|
bool CBaseObject::HasSapper( void ) |
|
{ |
|
return m_bHasSapper; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
bool CBaseObject::IsPlasmaDisabled( void ) |
|
{ |
|
return m_bPlasmaDisable; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
void CBaseObject::OnAddSapper( void ) |
|
{ |
|
// Assume we can only build 1 sapper per object |
|
Assert( m_bHasSapper == false ); |
|
|
|
m_bHasSapper = true; |
|
|
|
CTFPlayer *pPlayer = GetBuilder(); |
|
|
|
if ( pPlayer ) |
|
{ |
|
//pPlayer->HintMessage( HINT_OBJECT_YOUR_OBJECT_SAPPED, true ); |
|
pPlayer->SpeakConceptIfAllowed( MP_CONCEPT_SPY_SAPPER, GetResponseRulesModifier() ); |
|
} |
|
|
|
UpdateDisabledState(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
void CBaseObject::OnRemoveSapper( void ) |
|
{ |
|
m_bHasSapper = false; |
|
UpdateDisabledState(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
int CBaseObject::GetUpgradeMetalRequired() |
|
{ |
|
return GetObjectInfo( GetType() )->m_UpgradeCost; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
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: 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); |
|
|
|
CTakeDamageInfo info( inputdata.pCaller, inputdata.pActivator, vec3_origin, GetAbsOrigin(), iDamage, DMG_GENERIC ); |
|
Killed( info ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// 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: |
|
// Input : &inputdata - |
|
//----------------------------------------------------------------------------- |
|
void CBaseObject::InputSetBuilder( inputdata_t &inputdata ) |
|
{ |
|
CTFPlayer *pPlayer = ToTFPlayer( inputdata.pActivator ); |
|
if ( GetBuilder() == NULL && pPlayer != NULL ) |
|
{ |
|
SetBuilder( pPlayer ); |
|
ChangeTeam( pPlayer->GetTeamNumber() ); |
|
pPlayer->AddObject( this ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : &inputdata - |
|
//----------------------------------------------------------------------------- |
|
void CBaseObject::InputShow( inputdata_t &inputdata ) |
|
{ |
|
RemoveEffects( EF_NODRAW ); |
|
UpdateDisabledState(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : &inputdata - |
|
//----------------------------------------------------------------------------- |
|
void CBaseObject::InputHide( inputdata_t &inputdata ) |
|
{ |
|
AddEffects( EF_NODRAW ); |
|
SetDisabled( true ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : |
|
// Output : did this wrench hit do any work on the object? |
|
//----------------------------------------------------------------------------- |
|
bool CBaseObject::InputWrenchHit( CTFPlayer *pPlayer, CTFWrench *pWrench, Vector hitLoc ) |
|
{ |
|
Assert( pPlayer ); |
|
if ( !pPlayer ) |
|
return false; |
|
|
|
bool bDidWork = false; |
|
|
|
if ( HasSapper() ) |
|
{ |
|
// do damage to any attached buildings |
|
CTakeDamageInfo info( pPlayer, pPlayer, pWrench, WRENCH_DMG_VS_SAPPER, DMG_CLUB, TF_DMG_WRENCH_FIX ); |
|
|
|
IHasBuildPoints *pBPInterface = dynamic_cast< IHasBuildPoints * >( this ); |
|
int iNumObjects = pBPInterface->GetNumObjectsOnMe(); |
|
for ( int iPoint=0;iPoint<iNumObjects;iPoint++ ) |
|
{ |
|
CBaseObject *pObject = GetBuildPointObject( iPoint ); |
|
|
|
if ( pObject && pObject->IsHostileUpgrade() ) |
|
{ |
|
int iBeforeHealth = pObject->GetHealth(); |
|
|
|
pObject->TakeDamage( info ); |
|
|
|
// This should always be true |
|
if ( iBeforeHealth != pObject->GetHealth() ) |
|
{ |
|
bDidWork = true; |
|
Assert( bDidWork ); |
|
} |
|
} |
|
} |
|
} |
|
else if ( IsUpgrading() ) |
|
{ |
|
bDidWork = OnWrenchHit( pPlayer, pWrench, hitLoc ); |
|
// bDidWork = false; |
|
} |
|
else if ( IsBuilding() ) |
|
{ |
|
OnConstructionHit( pPlayer, pWrench, hitLoc ); |
|
bDidWork = true; |
|
} |
|
else |
|
{ |
|
// upgrade, refill, repair damage |
|
bDidWork = OnWrenchHit( pPlayer, pWrench, hitLoc ); |
|
} |
|
|
|
if ( bDidWork ) |
|
{ |
|
pPlayer->m_AchievementData.AddTargetToHistory( this ); |
|
} |
|
|
|
return bDidWork; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
bool CBaseObject::OnWrenchHit( CTFPlayer *pPlayer, CTFWrench *pWrench, Vector hitLoc ) |
|
{ |
|
bool bRepairHit = false; |
|
bool bUpgradeHit = false; |
|
|
|
bRepairHit = Command_Repair( pPlayer, pWrench->GetRepairValue() ); |
|
|
|
if ( !bRepairHit ) |
|
{ |
|
bUpgradeHit = CheckUpgradeOnHit( pPlayer ); |
|
} |
|
|
|
DoWrenchHitEffect( hitLoc, bRepairHit, bUpgradeHit ); |
|
|
|
return bUpgradeHit || bRepairHit; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CBaseObject::DoWrenchHitEffect( Vector hitLoc, bool bRepairHit, bool bUpgradeHit ) |
|
{ |
|
if ( bRepairHit ) |
|
{ |
|
// Play a repair hit effect. |
|
CPVSFilter filter( hitLoc ); |
|
TE_TFParticleEffect( filter, 0.0f, "nutsnbolts_repair", hitLoc, QAngle(0,0,0) ); |
|
} |
|
else if ( bUpgradeHit ) |
|
{ |
|
// Play an upgrade hit effect. |
|
CPVSFilter filter( hitLoc ); |
|
TE_TFParticleEffect( filter, 0.0f, "nutsnbolts_upgrade", hitLoc, QAngle(0,0,0) ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
bool CBaseObject::CheckUpgradeOnHit( CTFPlayer *pPlayer ) |
|
{ |
|
if ( !CanBeUpgraded() ) |
|
return false; |
|
|
|
if ( m_bCarryDeploy ) |
|
return false; |
|
|
|
if ( CanBeUpgraded( pPlayer ) ) |
|
{ |
|
int iPlayerMetal = pPlayer->GetAmmoCount( TF_AMMO_METAL ); |
|
int nMaxToAdd = tf_obj_upgrade_per_hit.GetInt(); |
|
if ( TFGameRules() && TFGameRules()->IsPowerupMode() ) |
|
{ |
|
nMaxToAdd *= 2; |
|
} |
|
CALL_ATTRIB_HOOK_INT_ON_OTHER( pPlayer, nMaxToAdd, upgrade_rate_mod ); |
|
int iAmountToAdd = MIN( nMaxToAdd, iPlayerMetal ); |
|
|
|
if ( iAmountToAdd > ( m_iUpgradeMetalRequired - m_iUpgradeMetal ) ) |
|
iAmountToAdd = ( m_iUpgradeMetalRequired - m_iUpgradeMetal ); |
|
|
|
if ( tf_cheapobjects.GetBool() == false && !ShouldQuickBuild() ) |
|
{ |
|
pPlayer->RemoveAmmo( iAmountToAdd, TF_AMMO_METAL ); |
|
} |
|
|
|
// testing quick builds for engineers in Raid mode |
|
if ( TFGameRules() && !TFGameRules()->IsPVEModeControlled( pPlayer ) ) |
|
{ |
|
#ifdef TF_RAID_MODE |
|
if ( TFGameRules()->IsRaidMode() ) |
|
{ |
|
iAmountToAdd = 200; |
|
} |
|
#endif |
|
|
|
if ( TFGameRules()->GameModeUsesUpgrades() && TFGameRules()->IsQuickBuildTime() ) |
|
{ |
|
iAmountToAdd = 200; |
|
} |
|
} |
|
|
|
m_iUpgradeMetal += iAmountToAdd; |
|
|
|
bool bDidWork = false; |
|
if ( iAmountToAdd > 0 ) |
|
{ |
|
bDidWork = true; |
|
} |
|
|
|
if ( m_iUpgradeMetal >= m_iUpgradeMetalRequired ) |
|
{ |
|
IGameEvent * event = gameeventmanager->CreateEvent( "player_upgradedobject" ); |
|
if ( event ) |
|
{ |
|
event->SetInt( "userid", pPlayer->GetUserID() ); |
|
event->SetInt( "object", ObjectType() ); |
|
event->SetInt( "index", entindex() ); |
|
event->SetBool( "isbuilder", pPlayer == GetBuilder() ); |
|
|
|
gameeventmanager->FireEvent( event ); |
|
} |
|
|
|
StartUpgrading(); |
|
m_iUpgradeMetal = 0; |
|
} |
|
|
|
return bDidWork; |
|
} |
|
|
|
return false; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
bool CBaseObject::CanBeUpgraded( CTFPlayer *pPlayer ) |
|
{ |
|
// Already upgrading |
|
if ( IsUpgrading() ) |
|
return false; |
|
|
|
if ( IsMiniBuilding() || IsDisposableBuilding() ) |
|
return false; |
|
|
|
// only engineers |
|
if ( !ClassCanBuild( pPlayer->GetPlayerClass()->GetClassIndex(), GetType() ) ) |
|
return false; |
|
|
|
// max upgraded |
|
if ( m_iUpgradeLevel >= OBJ_MAX_UPGRADE_LEVEL ) |
|
return false; |
|
|
|
return true; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Separated so it can be triggered by wrench hit or by vgui screen |
|
//----------------------------------------------------------------------------- |
|
bool CBaseObject::Command_Repair( CTFPlayer *pActivator, float flRepairMod ) |
|
{ |
|
if ( !CanBeRepaired() ) |
|
return false; |
|
|
|
const float flRepairToMetalRatio = 3.0f; // Amount Repaired per metal |
|
float flTargetHeal = 100.0f * flRepairMod; |
|
int iAmountToHeal = MIN( flTargetHeal, GetMaxHealth() - RoundFloatToInt( GetHealth() ) ); |
|
|
|
// repair the building |
|
int iRepairCost = ceil( (float)( iAmountToHeal ) / flRepairToMetalRatio ); |
|
|
|
TRACE_OBJECT( UTIL_VarArgs( "%0.2f CBaseObject::Command_Repair ( %f / %d ) - cost = %d\n", gpGlobals->curtime, |
|
GetHealth(), |
|
GetMaxHealth(), |
|
iRepairCost ) ); |
|
|
|
if ( iRepairCost > 0 ) |
|
{ |
|
if ( iRepairCost > pActivator->GetBuildResources() ) |
|
{ |
|
iRepairCost = pActivator->GetBuildResources(); |
|
} |
|
|
|
pActivator->RemoveBuildResources( iRepairCost ); |
|
|
|
float flNewHealth = MIN( GetMaxHealth(), m_flHealth + ( iRepairCost * flRepairToMetalRatio ) ); |
|
|
|
if ( pActivator != GetBuilder() ) |
|
{ |
|
float flAmountRepaired = flNewHealth - m_flHealth; |
|
pActivator->AwardAchievement( ACHIEVEMENT_TF_ENGINEER_REPAIR_TEAM_GRIND, floor( flAmountRepaired ) ); |
|
} |
|
|
|
SetHealth( flNewHealth ); |
|
|
|
return ( iRepairCost > 0 ); |
|
} |
|
|
|
return false; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Upgrade this object a single level |
|
//----------------------------------------------------------------------------- |
|
void CBaseObject::StartUpgrading( void ) |
|
{ |
|
// Increase level |
|
m_iUpgradeLevel++; |
|
|
|
if ( GetHighestUpgradeLevel() < m_iUpgradeLevel ) |
|
{ |
|
m_iHighestUpgradeLevel = m_iUpgradeLevel; |
|
} |
|
|
|
// more health |
|
if ( !m_bCarryDeploy && !IsUsingReverseBuild() ) |
|
{ |
|
int iMaxHealth = GetMaxHealthForCurrentLevel(); |
|
SetMaxHealth( iMaxHealth ); |
|
SetHealth( iMaxHealth ); |
|
} |
|
|
|
const char *pUpgradeSound = GetObjectInfo( ObjectType() )->m_pUpgradeSound; |
|
if ( pUpgradeSound && *pUpgradeSound ) |
|
{ |
|
EmitSound( pUpgradeSound ); |
|
} |
|
|
|
if ( ( !m_bWasMapPlaced || ( m_iUpgradeLevel > (m_nDefaultUpgradeLevel+1) ) ) ) |
|
{ |
|
SetActivity( ACT_OBJ_UPGRADING ); |
|
|
|
float flConstructionTime = ( ShouldQuickBuild() ? 0 : GetObjectInfo( ObjectType() )->m_flUpgradeDuration ); |
|
float flReverseBuildingConstructionSpeed = GetReversesBuildingConstructionSpeed(); |
|
flConstructionTime /= ( flReverseBuildingConstructionSpeed == 0.0f ? 1.0f : flReverseBuildingConstructionSpeed ); |
|
|
|
m_flUpgradeCompleteTime = gpGlobals->curtime + flConstructionTime; |
|
} |
|
else |
|
{ |
|
m_flUpgradeCompleteTime = gpGlobals->curtime; //asap |
|
} |
|
|
|
RemoveAllGestures(); |
|
|
|
if ( TFGameRules() && TFGameRules()->IsInTraining() && |
|
TFGameRules()->GetTrainingModeLogic() && |
|
GetOwner() && GetOwner()->IsFakeClient() == false ) |
|
{ |
|
TFGameRules()->GetTrainingModeLogic()->OnPlayerUpgradedBuilding( GetOwner(), this ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CBaseObject::FinishUpgrading( void ) |
|
{ |
|
const char *pUpgradeSound = GetObjectInfo( ObjectType() )->m_pUpgradeSound; |
|
if ( pUpgradeSound && *pUpgradeSound ) |
|
{ |
|
EmitSound( pUpgradeSound ); |
|
} |
|
|
|
if ( IsUsingReverseBuild() ) |
|
{ |
|
m_iUpgradeLevel--; |
|
DoReverseBuild(); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Playing the upgrade animation |
|
//----------------------------------------------------------------------------- |
|
void CBaseObject::UpgradeThink( void ) |
|
{ |
|
if ( gpGlobals->curtime > m_flUpgradeCompleteTime ) |
|
{ |
|
FinishUpgrading(); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Handles health upgrade for objects we've already built |
|
//----------------------------------------------------------------------------- |
|
void CBaseObject::ApplyHealthUpgrade( void ) |
|
{ |
|
CTFPlayer *pTFPlayer = GetOwner(); |
|
if ( !pTFPlayer ) |
|
return; |
|
|
|
int iHealth = GetMaxHealthForCurrentLevel(); |
|
SetMaxHealth( iHealth ); |
|
SetHealth( iHealth ); |
|
|
|
//DevMsg( "%i\n", GetMaxHealth() ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CBaseObject::PlayStartupAnimation( void ) |
|
{ |
|
SetActivity( ACT_OBJ_STARTUP ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CBaseObject::DetermineAnimation( void ) |
|
{ |
|
Activity desiredActivity = m_Activity; |
|
|
|
switch ( m_Activity ) |
|
{ |
|
default: |
|
{ |
|
if ( IsUpgrading() ) |
|
{ |
|
desiredActivity = ACT_OBJ_UPGRADING; |
|
} |
|
else if ( IsPlacing() ) |
|
{ |
|
/* |
|
if (1 || m_bPlacementOK ) |
|
{ |
|
desiredActivity = ACT_OBJ_PLACING; |
|
} |
|
else |
|
{ |
|
desiredActivity = ACT_OBJ_IDLE; |
|
} |
|
*/ |
|
} |
|
else if ( IsBuilding() ) |
|
{ |
|
desiredActivity = ACT_OBJ_ASSEMBLING; |
|
} |
|
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( CBaseEntity *pEntity, int iPoint, Vector &vecOrigin ) |
|
{ |
|
m_hBuiltOnEntity = pEntity; |
|
m_iBuiltOnPoint = iPoint; |
|
|
|
int iAttachment = 0; |
|
|
|
if ( m_hBuiltOnEntity.Get() ) |
|
{ |
|
CTFPlayer *pTFPlayer = ToTFPlayer( pEntity ); |
|
if ( pTFPlayer ) |
|
{ |
|
iAttachment = pTFPlayer->LookupAttachment( "head" ); |
|
} |
|
else |
|
{ |
|
CBaseAnimating *pAnimate = dynamic_cast<CBaseAnimating*>( pEntity ); |
|
if ( pAnimate ) |
|
{ |
|
iAttachment = pAnimate->LookupBone( "weapon_bone" ); |
|
if ( iAttachment >= 1 ) |
|
{ |
|
FollowEntity( m_hBuiltOnEntity.Get() ); |
|
} |
|
} |
|
|
|
// Parent ourselves to the object |
|
IHasBuildPoints *pBPInterface = dynamic_cast<IHasBuildPoints*>( pEntity ); |
|
Assert( pBPInterface ); |
|
if ( pBPInterface ) |
|
{ |
|
iAttachment = pBPInterface->GetBuildPointAttachmentIndex( iPoint ); |
|
|
|
// re-link to the build points if the sapper is already built |
|
if ( !( IsPlacing() || IsBuilding() ) ) |
|
{ |
|
pBPInterface->SetObjectOnBuildPoint( m_iBuiltOnPoint, this ); |
|
} |
|
} |
|
} |
|
|
|
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(); |
|
} |
|
|
|
SetupAttachedVersion(); |
|
} |
|
|
|
Assert( m_hBuiltOnEntity.Get() == 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() ); |
|
if ( !(pObject->m_fObjectFlags & OF_DOESNT_HAVE_A_MODEL) ) |
|
{ |
|
pObject->SpawnControlPanels(); |
|
} |
|
pObject->SetHealth( pObject->GetMaxHealth() ); |
|
pObject->FinishedBuilding(); |
|
pObject->AttachObjectToObject( this, iPoint, vecOrigin ); |
|
//pObject->m_fObjectFlags |= OF_CANNOT_BE_DISMANTLED; |
|
|
|
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(); |
|
} |
|
|
|
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 ); |
|
} |
|
} |
|
|
|
int CBaseObject::DrawDebugTextOverlays(void) |
|
{ |
|
int text_offset = BaseClass::DrawDebugTextOverlays(); |
|
|
|
if (m_debugOverlays & OVERLAY_TEXT_BIT) |
|
{ |
|
char tempstr[512]; |
|
|
|
Q_snprintf( tempstr, sizeof( tempstr ),"Health: %f / %d ( %.1f )", GetHealth(), GetMaxHealth(), (float)GetHealth() / (float)GetMaxHealth() ); |
|
EntityText(text_offset,tempstr,0); |
|
text_offset++; |
|
|
|
CTFPlayer *pBuilder = GetBuilder(); |
|
|
|
Q_snprintf( tempstr, sizeof( tempstr ),"Built by: (%d) %s", |
|
pBuilder ? pBuilder->entindex() : -1, |
|
pBuilder ? pBuilder->GetPlayerName() : "invalid builder" ); |
|
EntityText(text_offset,tempstr,0); |
|
text_offset++; |
|
|
|
if ( IsBuilding() ) |
|
{ |
|
Q_snprintf( tempstr, sizeof( tempstr ),"Build Rate: %.1f", GetConstructionMultiplier() ); |
|
EntityText(text_offset,tempstr,0); |
|
text_offset++; |
|
} |
|
} |
|
return text_offset; |
|
|
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Change build orientation |
|
//----------------------------------------------------------------------------- |
|
void CBaseObject::RotateBuildAngles( void ) |
|
{ |
|
// rotate the build angles by 90 degrees ( final angle calculated after we network this ) |
|
m_iDesiredBuildRotations++; |
|
m_iDesiredBuildRotations = m_iDesiredBuildRotations % 4; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: called on edge cases to see if we need to change our disabled state |
|
//----------------------------------------------------------------------------- |
|
void CBaseObject::UpdateDisabledState( void ) |
|
{ |
|
const bool bShouldBeEnabled = !m_bHasSapper |
|
&& !m_bPlasmaDisable |
|
&& (!TFGameRules()->RoundHasBeenWon() || TFGameRules()->GetWinningTeam() == GetTeamNumber()); |
|
|
|
SetDisabled( !bShouldBeEnabled ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: called when our disabled state changes |
|
//----------------------------------------------------------------------------- |
|
void CBaseObject::SetDisabled( bool bDisabled ) |
|
{ |
|
if ( bDisabled && !m_bDisabled ) |
|
{ |
|
OnStartDisabled(); |
|
} |
|
else if ( !bDisabled && m_bDisabled ) |
|
{ |
|
OnEndDisabled(); |
|
} |
|
|
|
m_bDisabled = bDisabled; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
void CBaseObject::SetPlasmaDisabled( float flDuration ) |
|
{ |
|
m_bPlasmaDisable = true; |
|
m_flPlasmaDisableTime = gpGlobals->curtime + flDuration; |
|
UpdateDisabledState(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CBaseObject::OnStartDisabled( void ) |
|
{ |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CBaseObject::OnEndDisabled( void ) |
|
{ |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Called when the model changes, find new attachments for the children |
|
//----------------------------------------------------------------------------- |
|
void CBaseObject::ReattachChildren( void ) |
|
{ |
|
// Go through and store the children one by one, then reattach them. We need |
|
// to store them like this because if we didnt and instead went through and |
|
// reattached them as we iterated over them we could get into a state where we have |
|
// children A and B and A has B as a sibling and B has NULL has a sibling, |
|
// but we reattach B first which set's B's sibling to A creating a infinite loop |
|
CUtlVector<CBaseEntity*> vecChildren; |
|
for (CBaseEntity *pChild = FirstMoveChild(); pChild; pChild = pChild->NextMovePeer()) |
|
{ |
|
if( vecChildren.Find( pChild ) != vecChildren.InvalidIndex() ) |
|
{ |
|
AssertMsg( 0, "Cyclic siblings found when reattaching children!" ); |
|
break; |
|
} |
|
|
|
vecChildren.AddToTail( pChild ); |
|
} |
|
|
|
int iNumBuildPoints = GetNumBuildPoints(); |
|
FOR_EACH_VEC( vecChildren, i ) |
|
{ |
|
CBaseObject *pObject = dynamic_cast<CBaseObject *>( vecChildren[i] ); |
|
|
|
if ( !pObject ) |
|
{ |
|
continue; |
|
} |
|
|
|
Assert( pObject->GetParent() == this ); |
|
|
|
// get the type |
|
int iObjectType = pObject->GetType(); |
|
|
|
bool bReattached = false; |
|
|
|
Vector vecDummy; |
|
|
|
for ( int j = 0; j < iNumBuildPoints && bReattached == false; j++ ) |
|
{ |
|
// Can this object build on this point? |
|
if ( CanBuildObjectOnBuildPoint( j, iObjectType ) ) |
|
{ |
|
pObject->AttachObjectToObject( this, j, vecDummy ); |
|
bReattached = true; |
|
} |
|
} |
|
|
|
// if we can't find an attach for the child, remove it and print an error |
|
if ( bReattached == false ) |
|
{ |
|
if ( m_bCarried && ( pObject->GetType() == OBJ_ATTACHMENT_SAPPER ) ) |
|
{ |
|
pObject->ResetPlacement(); |
|
} |
|
else |
|
{ |
|
pObject->DestroyObject(); |
|
Assert( !"Couldn't find attachment point on upgraded object for existing child.\n" ); |
|
} |
|
} |
|
} |
|
} |
|
|
|
void CBaseObject::SetModel( const char *pModel ) |
|
{ |
|
// Skip if we're already the proper model |
|
if ( V_strcmp( GetModelName().ToCStr(), pModel ) == 0 ) |
|
return; |
|
|
|
BaseClass::SetModel( pModel ); |
|
|
|
// Clear out the gib list and create a new one. |
|
m_aGibs.Purge(); |
|
BuildGibList( m_aGibs, GetModelIndex(), 1.0f, COLLISION_GROUP_NONE ); |
|
|
|
CObjectSapper *pSapper = GetSapper(); |
|
if ( pSapper ) |
|
{ |
|
pSapper->OnGoActive(); |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CBaseObject::Activate( void ) |
|
{ |
|
BaseClass::Activate(); |
|
|
|
InitializeMapPlacedObject(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// 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(); |
|
|
|
// Set the skin |
|
m_nSkin = ( GetTeamNumber() == TF_TEAM_RED ) ? 0 : 1; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Turns the object into one carried by someone. |
|
//----------------------------------------------------------------------------- |
|
void CBaseObject::MakeCarriedObject( CTFPlayer *pCarrier ) |
|
{ |
|
if ( pCarrier ) |
|
{ |
|
// Make the object inactive. |
|
m_bCarried = true; |
|
m_bCarryDeploy = false; |
|
pCarrier->m_Shared.SetCarriedObject( this ); |
|
m_iHealthOnPickup = m_iHealth; // If we are damaged, we want to remember how much damage we had sustained. |
|
|
|
// Remove screens. |
|
DestroyScreens(); |
|
|
|
// Mount it to the player. |
|
FollowEntity( pCarrier ); |
|
|
|
IGameEvent * event = gameeventmanager->CreateEvent( "player_carryobject" ); |
|
if ( event ) |
|
{ |
|
event->SetInt( "userid", pCarrier->GetUserID() ); |
|
event->SetInt( "object", GetType() ); |
|
event->SetInt( "index", entindex() ); // object entity index |
|
|
|
gameeventmanager->FireEvent( event, true ); // don't send to clients |
|
} |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Turns the object into one carried by someone. |
|
//----------------------------------------------------------------------------- |
|
void CBaseObject::DropCarriedObject( CTFPlayer* pCarrier ) |
|
{ |
|
m_bCarried = false; |
|
m_bCarryDeploy = false; |
|
|
|
if ( pCarrier ) |
|
{ |
|
pCarrier->m_Shared.SetCarriedObject( NULL ); |
|
} |
|
|
|
StopFollowingEntity(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Instantly build and upgrade this object |
|
//----------------------------------------------------------------------------- |
|
void CBaseObject::DoQuickBuild( bool bForceMax /* = false */ ) |
|
{ |
|
if ( IsBuilding() ) |
|
{ |
|
FinishedBuilding(); |
|
} |
|
|
|
int iTargetLevel = ( ( ( TFGameRules() && TFGameRules()->IsQuickBuildTime() ) || bForceMax ) ? OBJ_MAX_UPGRADE_LEVEL : GetUpgradeLevel() ); |
|
|
|
if ( CanBeUpgraded( GetOwner() ) ) |
|
{ |
|
for ( int i = GetUpgradeLevel(); i < iTargetLevel; i++ ) |
|
{ |
|
StartUpgrading(); |
|
} |
|
} |
|
else |
|
{ |
|
int iMaxHealth = GetMaxHealthForCurrentLevel(); |
|
SetMaxHealth( iMaxHealth ); |
|
SetHealth( iMaxHealth ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Builds instantly under certain conditions/modes |
|
//----------------------------------------------------------------------------- |
|
bool CBaseObject::ShouldQuickBuild( void ) |
|
{ |
|
if ( TFGameRules() ) |
|
{ |
|
if ( GetType() == OBJ_ATTACHMENT_SAPPER ) |
|
return false; |
|
|
|
#ifdef STAGING_ONLY |
|
if ( GetType() == OBJ_SPY_TRAP ) |
|
return false; |
|
#endif |
|
|
|
if ( TFGameRules()->IsQuickBuildTime() ) |
|
{ |
|
return true; |
|
} |
|
|
|
if ( TFGameRules()->IsMannVsMachineMode() ) |
|
{ |
|
if ( GetTeamNumber() == TF_TEAM_PVE_INVADERS ) |
|
{ |
|
// Engineer bots in MvM deploy pre-built sentries that build up at the normal rate |
|
return m_bForceQuickBuild; |
|
} |
|
|
|
if ( m_bCarryDeploy || TFGameRules()->State_Get() == GR_STATE_BETWEEN_RNDS ) |
|
{ |
|
return true; |
|
} |
|
} |
|
} |
|
|
|
return m_bForceQuickBuild; |
|
} |
|
|
|
void CBaseObject::DoReverseBuild( void ) |
|
{ |
|
m_iHighestUpgradeLevel = m_iUpgradeLevel; |
|
m_iUpgradeMetal = 0; |
|
|
|
int iMaxHealth = GetMaxHealthForCurrentLevel(); |
|
SetMaxHealth( iMaxHealth ); |
|
if ( GetHealth() > iMaxHealth ) |
|
{ |
|
SetHealth( iMaxHealth ); |
|
} |
|
|
|
if ( m_iUpgradeLevel > 1 ) |
|
{ |
|
m_iUpgradeLevel--; |
|
StartUpgrading(); |
|
} |
|
else |
|
{ |
|
m_bBuilding = true; |
|
m_bCarryDeploy = false; |
|
m_flTotalConstructionTime = m_flConstructionTimeLeft = GetTotalTime(); |
|
m_flConstructionStartTime = gpGlobals->curtime; |
|
SetStartBuildingModel(); |
|
SetControlPanelsActive( false ); |
|
} |
|
} |
|
|
|
float CBaseObject::GetReversesBuildingConstructionSpeed( void ) |
|
{ |
|
CObjectSapper *pSapper = GetSapper(); |
|
if ( !pSapper ) |
|
return 0.0f; |
|
|
|
return pSapper->GetReversesBuildingConstructionSpeed(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CBaseObject::InputEnable( inputdata_t &inputdata ) |
|
{ |
|
if ( IsDisabled() ) |
|
{ |
|
UpdateDisabledState(); |
|
if ( !IsDisabled() ) |
|
{ |
|
OnGoActive(); |
|
} |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CBaseObject::InputDisable( inputdata_t &inputdata ) |
|
{ |
|
if ( !IsDisabled() ) |
|
{ |
|
SetDisabled( true ); |
|
OnGoInactive(); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
int CBaseObject::GetMaxHealthForCurrentLevel( void ) |
|
{ |
|
int iMaxHealth = IsMiniBuilding() ? GetMiniBuildingStartingHealth() : GetBaseHealth(); |
|
if ( GetOwner() && !m_bDisposableBuilding ) |
|
{ |
|
CALL_ATTRIB_HOOK_INT_ON_OTHER( GetOwner(), iMaxHealth, mult_engy_building_health ); |
|
} |
|
|
|
if ( !IsMiniBuilding() && ( GetUpgradeLevel() > 1 ) ) |
|
{ |
|
float flMultiplier = pow( UPGRADE_LEVEL_HEALTH_MULTIPLIER, GetUpgradeLevel() - 1 ); |
|
iMaxHealth = (int)( iMaxHealth * flMultiplier ); |
|
} |
|
|
|
return iMaxHealth; |
|
}
|
|
|