diff options
Diffstat (limited to 'game/server/tf2')
217 files changed, 45048 insertions, 0 deletions
diff --git a/game/server/tf2/basecombatcharacter_tf2.cpp b/game/server/tf2/basecombatcharacter_tf2.cpp new file mode 100644 index 0000000..453856a --- /dev/null +++ b/game/server/tf2/basecombatcharacter_tf2.cpp @@ -0,0 +1,249 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: TF2 specific CBaseCombatCharacter code. +// +//=============================================================================// +#include "cbase.h" +#include "basecombatcharacter.h" +#include "engine/IEngineSound.h" +#include "tf_player.h" +#include "tf_stats.h" + +extern char *g_pszEMPPulseStart; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CBaseCombatCharacter::HasPowerup( int iPowerup ) +{ + Assert( iPowerup >= 0 && iPowerup < MAX_POWERUPS ); + return ( m_iPowerups & (1 << iPowerup) ) != 0; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CBaseCombatCharacter::CanPowerupEver( int iPowerup ) +{ + Assert( iPowerup >= 0 && iPowerup < MAX_POWERUPS ); + + // Only objects use power + if ( iPowerup == POWERUP_POWER ) + return false; + + // Accept everything else + return true; +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CBaseCombatCharacter::CanPowerupNow( int iPowerup ) +{ + Assert( iPowerup >= 0 && iPowerup < MAX_POWERUPS ); + + if ( !CanPowerupEver(iPowerup) ) + return false; + + switch( iPowerup ) + { + case POWERUP_BOOST: + { + // Am I taking EMP damage, or is a technician trying to drain me? + if ( HasPowerup( POWERUP_EMP ) || ( (m_flPowerupAttemptTimes[POWERUP_EMP] + 0.5) > gpGlobals->curtime ) ) + { + // Reduce EMP time + m_flPowerupEndTimes[POWERUP_EMP] -= 0.05; + + // Don't apply any boost effects + return false; + } + } + break; + + case POWERUP_EMP: + { + // Was I just boosted? If so, I don't take EMP damage for a bit + if ( (m_flPowerupAttemptTimes[POWERUP_BOOST] + 0.5) > gpGlobals->curtime ) + return false; + } + break; + + default: + break; + } + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseCombatCharacter::SetPowerup( int iPowerup, bool bState, float flTime, float flAmount, CBaseEntity *pAttacker, CDamageModifier *pDamageModifier ) +{ + Assert( iPowerup >= 0 && iPowerup < MAX_POWERUPS ); + + // Some powerups trigger their on state continuously, as opposed to turning it on for some time. + bool bTriggerStart = ( bState && !HasPowerup( iPowerup ) ); + if ( bState && iPowerup == POWERUP_BOOST ) + { + // Health boost always triggers + bTriggerStart = true; + } + + bool bHadPowerup = false; + if ( HasPowerup( iPowerup ) && !bState ) + { + bHadPowerup = true; + } + + if ( bState ) + { + m_iPowerups |= (1 << iPowerup); + } + else + { + m_iPowerups &= ~(1 << iPowerup); + } + + // Fire start/end triggers + if ( bTriggerStart ) + { + PowerupStart( iPowerup, flAmount, pAttacker, pDamageModifier ); + } + else if ( bHadPowerup ) + { + PowerupEnd( iPowerup ); + } + + // If we've got an active powerup, keep thinking + if ( m_iPowerups ) + { + SetContextThink( PowerupThink, gpGlobals->curtime + 0.1, POWERUP_THINK_CONTEXT ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseCombatCharacter::PowerupThink( void ) +{ + // If we don't have any powerups, stop thinking + if ( !m_iPowerups ) + return; + + // Check all the powerups + for ( int i = 0; i < MAX_POWERUPS; i++ ) + { + // Don't check power, because it never runs out naturally + if ( i == POWERUP_POWER ) + continue; + + if ( m_iPowerups & (1 << i) ) + { + // Should it finish now? + if ( m_flPowerupEndTimes[i] < gpGlobals->curtime ) + { + SetPowerup( i, false ); + } + } + } + + SetNextThink( gpGlobals->curtime + 0.1, POWERUP_THINK_CONTEXT ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CBaseCombatCharacter::AttemptToPowerup( int iPowerup, float flTime, float flAmount, CBaseEntity *pAttacker, CDamageModifier *pDamageModifier ) +{ + Assert( iPowerup >= 0 && iPowerup < MAX_POWERUPS ); + + // Ignore it if I'm dead + if ( !IsAlive() ) + return false; + + m_flPowerupAttemptTimes[iPowerup] = gpGlobals->curtime; + + // If we can't be powerup this type, abort + if ( !CanPowerupNow( iPowerup ) ) + return false; + + // Get the correct duration + flTime = PowerupDuration( iPowerup, flTime ); + m_flPowerupEndTimes[iPowerup] = MAX( m_flPowerupEndTimes[iPowerup], gpGlobals->curtime + flTime ); + + // Turn it on + SetPowerup( iPowerup, true, flTime, flAmount, pAttacker, pDamageModifier ); + + // Add the damage modifier to the player + if ( pDamageModifier ) + { + pDamageModifier->AddModifierToEntity( this ); + } + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: Powerup has just started +//----------------------------------------------------------------------------- +void CBaseCombatCharacter::PowerupStart( int iPowerup, float flAmount, CBaseEntity *pAttacker, CDamageModifier *pDamageModifier ) +{ + Assert( iPowerup >= 0 && iPowerup < MAX_POWERUPS ); + + switch( iPowerup ) + { + case POWERUP_BOOST: + { + // Players can be boosted over their max + int iMaxBoostedHealth; + if ( IsPlayer() ) + { + iMaxBoostedHealth = GetMaxHealth() + GetMaxHealth() / 2; + } + else + { + iMaxBoostedHealth = GetMaxHealth(); + } + + // Can we boost health further? + if ( GetHealth() < iMaxBoostedHealth ) + { + int maxHealthToAdd = iMaxBoostedHealth - GetHealth(); + + // It uses floating point in here so it doesn't lose the fractional healing part on small frame times. + float flHealthToAdd = flAmount + m_flFractionalBoost; + int nHealthToAdd = (int)flHealthToAdd; + m_flFractionalBoost = flHealthToAdd - nHealthToAdd; + if ( nHealthToAdd ) + { + int nHealthAdded = MIN( nHealthToAdd, maxHealthToAdd ); + if ( IsPlayer() ) + { + ((CBaseTFPlayer*)this)->TakeHealthBoost( nHealthAdded, GetMaxHealth(), 25 ); + } + else + { + TakeHealth( nHealthAdded, DMG_GENERIC ); + } + + TFStats()->IncrementPlayerStat( pAttacker, TF_PLAYER_STAT_HEALTH_GIVEN, nHealthAdded ); + } + } + } + break; + + case POWERUP_EMP: + { + // EMP removes adrenalin rush + SetPowerup( POWERUP_RUSH, false ); + } + break; + + default: + break; + } +} + diff --git a/game/server/tf2/bot_base.cpp b/game/server/tf2/bot_base.cpp new file mode 100644 index 0000000..014c6a0 --- /dev/null +++ b/game/server/tf2/bot_base.cpp @@ -0,0 +1,424 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Basic BOT handling. +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" +#include "player.h" +#include "tf_player.h" +#include "menu_base.h" +#include "in_buttons.h" +#include "movehelper_server.h" +#include "weapon_twohandedcontainer.h" + +void ParseCommand( CBaseTFPlayer *pPlayer, const char *pcmd, const char *pargs ); +void ClientPutInServer( edict_t *pEdict, const char *playername ); +void Bot_Think( CBaseTFPlayer *pBot ); + +ConVar bot_forcefireweapon( "bot_forcefireweapon", "", 0, "Force bots with the specified weapon to fire." ); +ConVar bot_forceattack2( "bot_forceattack2", "0", 0, "When firing, use attack2." ); +ConVar bot_forceattackon( "bot_forceattackon", "0", 0, "When firing, don't tap fire, hold it down." ); +ConVar bot_flipout( "bot_flipout", "0", 0, "When on, all bots fire their guns." ); +ConVar bot_defend( "bot_defend", "0", 0, "Set to a team number, and that team will all keep their combat shields raised." ); +ConVar bot_changeclass( "bot_changeclass", "0", 0, "Force all bots to change to the specified class." ); +static ConVar bot_mimic( "bot_mimic", "0", 0, "Bot uses usercmd of player by index." ); + +static int BotNumber = 1; +static int g_iNextBotTeam = -1; +static int g_iNextBotClass = -1; + +//----------------------------------------------------------------------------- +// Purpose: Create a new Bot and put it in the game. +// Output : Pointer to the new Bot, or NULL if there's no free clients. +//----------------------------------------------------------------------------- +CBasePlayer *BotPutInServer( bool bFrozen, int iTeam, int iClass ) +{ + g_iNextBotTeam = iTeam; + g_iNextBotClass = iClass; + + char botname[ 64 ]; + Q_snprintf( botname, sizeof( botname ), "Bot%02i", BotNumber ); + + edict_t *pEdict = NULL; + { + bool oldLock = engine->LockNetworkStringTables( false ); + pEdict = engine->CreateFakeClient( botname ); + engine->LockNetworkStringTables( oldLock ); + } + + if ( !pEdict ) + { + Msg( "Failed to create Bot.\n"); + return NULL; + } + + // Allocate a CBasePlayer for the bot, and call spawn + ClientPutInServer( pEdict, botname ); + CBaseTFPlayer *pPlayer = ((CBaseTFPlayer *)CBaseEntity::Instance( pEdict )); + pPlayer->ClearFlags(); + pPlayer->AddFlag( FL_CLIENT | FL_FAKECLIENT ); + + if ( bFrozen ) + pPlayer->AddEFlags( EFL_BOT_FROZEN ); + + if ( iTeam != -1 ) + { + pPlayer->ChangeTeam( iTeam ); + } + pPlayer->ForceRespawn(); + + BotNumber++; + + return pPlayer; +} + +//----------------------------------------------------------------------------- +// Purpose: Run through all the Bots in the game and let them think. +//----------------------------------------------------------------------------- +void Bot_RunAll( void ) +{ + for ( int i = 1; i <= gpGlobals->maxClients; i++ ) + { + CBaseTFPlayer *pPlayer = ToBaseTFPlayer( UTIL_PlayerByIndex( i ) ); + + if ( pPlayer && (pPlayer->GetFlags() & FL_FAKECLIENT) ) + { + Bot_Think( pPlayer ); + } + } +} + +typedef struct +{ + bool backwards; + + float nextturntime; + bool lastturntoright; + + float nextstrafetime; + float sidemove; + + QAngle forwardAngle; + QAngle lastAngles; +} botdata_t; + +static botdata_t g_BotData[ MAX_PLAYERS ]; + +bool RunMimicCommand( CUserCmd& cmd ) +{ + if ( bot_mimic.GetInt() <= 0 ) + return false; + + if ( bot_mimic.GetInt() > gpGlobals->maxClients ) + return false; + + + CBasePlayer *pPlayer = UTIL_PlayerByIndex( bot_mimic.GetInt() ); + if ( !pPlayer ) + return false; + + if ( !pPlayer->GetLastUserCommand() ) + return false; + + cmd = *pPlayer->GetLastUserCommand(); + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: Simulates a single frame of movement for a player +// Input : *fakeclient - +// *viewangles - +// forwardmove - +// sidemove - +// upmove - +// buttons - +// impulse - +// msec - +// Output : virtual void +//----------------------------------------------------------------------------- +static void RunPlayerMove( CBaseTFPlayer *fakeclient, const QAngle& viewangles, float forwardmove, float sidemove, float upmove, unsigned short buttons, byte impulse, float frametime ) +{ + if ( !fakeclient ) + return; + + CUserCmd cmd; + + // Store off the globals.. they're gonna get whacked + float flOldFrametime = gpGlobals->frametime; + float flOldCurtime = gpGlobals->curtime; + + float flTimeBase = gpGlobals->curtime + gpGlobals->frametime - frametime; + fakeclient->SetTimeBase( flTimeBase ); + + Q_memset( &cmd, 0, sizeof( cmd ) ); + + if ( !RunMimicCommand( cmd ) ) + { + VectorCopy( viewangles, cmd.viewangles ); + cmd.forwardmove = forwardmove; + cmd.sidemove = sidemove; + cmd.upmove = upmove; + cmd.buttons = buttons; + cmd.impulse = impulse; + cmd.random_seed = random->RandomInt( 0, 0x7fffffff ); + } + + MoveHelperServer()->SetHost( fakeclient ); + fakeclient->PlayerRunCommand( &cmd, MoveHelperServer() ); + + // save off the last good usercmd + fakeclient->SetLastUserCommand( cmd ); + + // Clear out any fixangle that has been set + fakeclient->pl.fixangle = FIXANGLE_NONE; + + // Restore the globals.. + gpGlobals->frametime = flOldFrametime; + gpGlobals->curtime = flOldCurtime; +} + +//----------------------------------------------------------------------------- +// Purpose: Run this Bot's AI for one frame. +//----------------------------------------------------------------------------- +void Bot_Think( CBaseTFPlayer *pBot ) +{ + // Hack to make Bots use Menus + if ( pBot->m_pCurrentMenu == gMenus[MENU_CLASS] ) + { + int iClass = g_iNextBotClass; + if ( iClass == -1 ) + iClass = random->RandomInt( 1, TFCLASS_CLASS_COUNT ); + + pBot->m_pCurrentMenu->Input( pBot, iClass ); + } + else if ( bot_changeclass.GetInt() && bot_changeclass.GetInt() != pBot->PlayerClass() ) + { + pBot->m_pCurrentMenu = gMenus[MENU_CLASS]; + pBot->m_pCurrentMenu->Input( pBot, bot_changeclass.GetInt() ); + } + + // Make sure we stay being a bot + pBot->AddFlag( FL_FAKECLIENT ); + + botdata_t *botdata = &g_BotData[ ENTINDEX( pBot->edict() ) - 1 ]; + + QAngle vecViewAngles; + float forwardmove = 0.0; + float sidemove = botdata->sidemove; + float upmove = 0.0; + unsigned short buttons = 0; + byte impulse = 0; + float frametime = gpGlobals->frametime; + + vecViewAngles = pBot->GetLocalAngles(); + + + // Create some random values + if ( pBot->IsAlive() && (pBot->GetSolid() == SOLID_BBOX) ) + { + trace_t trace; + + // Stop when shot + if ( !pBot->IsEFlagSet(EFL_BOT_FROZEN) ) + { + if ( pBot->m_iHealth == 100 ) + { + forwardmove = 600 * ( botdata->backwards ? -1 : 1 ); + if ( botdata->sidemove != 0.0f ) + { + forwardmove *= random->RandomFloat( 0.1, 1.0f ); + } + } + else + { + forwardmove = 0; + } + } + + // Only turn if I haven't been hurt + if ( !pBot->IsEFlagSet(EFL_BOT_FROZEN) && pBot->m_iHealth == 100 ) + { + Vector vecEnd; + Vector forward; + + QAngle angle; + float angledelta = 15.0; + + int maxtries = (int)360.0/angledelta; + + if ( botdata->lastturntoright ) + { + angledelta = -angledelta; + } + + angle = pBot->GetLocalAngles(); + + Vector vecSrc; + while ( --maxtries >= 0 ) + { + AngleVectors( angle, &forward ); + + vecSrc = pBot->GetLocalOrigin() + Vector( 0, 0, 36 ); + + vecEnd = vecSrc + forward * 10; + + UTIL_TraceHull( vecSrc, vecEnd, VEC_HULL_MIN_SCALED( pBot ), VEC_HULL_MAX_SCALED( pBot ), + MASK_PLAYERSOLID, pBot, COLLISION_GROUP_NONE, &trace ); + + if ( trace.fraction == 1.0 ) + { + if ( gpGlobals->curtime < botdata->nextturntime ) + { + break; + } + } + + angle.y += angledelta; + + if ( angle.y > 180 ) + angle.y -= 360; + else if ( angle.y < -180 ) + angle.y += 360; + + botdata->nextturntime = gpGlobals->curtime + 2.0; + botdata->lastturntoright = random->RandomInt( 0, 1 ) == 0 ? true : false; + + botdata->forwardAngle = angle; + botdata->lastAngles = angle; + + } + + + if ( gpGlobals->curtime >= botdata->nextstrafetime ) + { + botdata->nextstrafetime = gpGlobals->curtime + 1.0f; + + if ( random->RandomInt( 0, 5 ) == 0 ) + { + botdata->sidemove = -600.0f + 1200.0f * random->RandomFloat( 0, 2 ); + } + else + { + botdata->sidemove = 0; + } + sidemove = botdata->sidemove; + + if ( random->RandomInt( 0, 20 ) == 0 ) + { + botdata->backwards = true; + } + else + { + botdata->backwards = false; + } + } + + pBot->SetLocalAngles( angle ); + vecViewAngles = angle; + } + + // Is my team being forced to defend? + if ( bot_defend.GetInt() == pBot->GetTeamNumber() ) + { + buttons |= IN_ATTACK2; + } + // If bots are being forced to fire a weapon, see if I have it + else if ( bot_forcefireweapon.GetString() ) + { + CBaseCombatWeapon *pWeapon = pBot->Weapon_OwnsThisType( bot_forcefireweapon.GetString() ); + if ( pWeapon ) + { + // Switch to it if we don't have it out + CBaseCombatWeapon *pActiveWeapon = pBot->GetActiveWeapon(); + // Is it a twohandedweapon? If so, get the left weapon + CWeaponTwoHandedContainer *pContainer = dynamic_cast< CWeaponTwoHandedContainer * >( pActiveWeapon ); + if ( pContainer ) + { + pActiveWeapon = pContainer->GetLeftWeapon(); + } + + // Switch? + if ( pActiveWeapon != pWeapon ) + { + pBot->Weapon_Switch( pWeapon ); + } + else + { + // Start firing + // Some weapons require releases, so randomise firing + if ( bot_forceattackon.GetBool() || (RandomFloat(0.0,1.0) > 0.5) ) + { + buttons |= bot_forceattack2.GetBool() ? IN_ATTACK2 : IN_ATTACK; + } + } + } + } + + if ( bot_flipout.GetInt() ) + { + if ( bot_forceattackon.GetBool() || (RandomFloat(0.0,1.0) > 0.5) ) + { + buttons |= bot_forceattack2.GetBool() ? IN_ATTACK2 : IN_ATTACK; + } + } + } + else + { + // Wait for Reinforcement wave + if ( !pBot->IsAlive() ) + { + // Try hitting my buttons occasionally + if ( random->RandomInt( 0, 100 ) > 80 ) + { + // Respawn the bot + if ( random->RandomInt( 0, 1 ) == 0 ) + { + buttons |= IN_JUMP; + } + else + { + buttons = 0; + } + } + } + } + + if ( bot_flipout.GetInt() >= 2 ) + { + + QAngle angOffset = RandomAngle( -1, 1 ); + + botdata->lastAngles += angOffset; + + for ( int i = 0 ; i < 2; i++ ) + { + if ( fabs( botdata->lastAngles[ i ] - botdata->forwardAngle[ i ] ) > 15.0f ) + { + if ( botdata->lastAngles[ i ] > botdata->forwardAngle[ i ] ) + { + botdata->lastAngles[ i ] = botdata->forwardAngle[ i ] + 15; + } + else + { + botdata->lastAngles[ i ] = botdata->forwardAngle[ i ] - 15; + } + } + } + + botdata->lastAngles[ 2 ] = 0; + + pBot->SetLocalAngles( botdata->lastAngles ); + } + + RunPlayerMove( pBot, pBot->GetLocalAngles(), forwardmove, sidemove, upmove, buttons, impulse, frametime ); +} + + diff --git a/game/server/tf2/bot_base.h b/game/server/tf2/bot_base.h new file mode 100644 index 0000000..2036f3d --- /dev/null +++ b/game/server/tf2/bot_base.h @@ -0,0 +1,19 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef BOT_BASE_H +#define BOT_BASE_H +#ifdef _WIN32 +#pragma once +#endif + + +// If iTeam or iClass is -1, then a team or class is randomly chosen. +CBasePlayer *BotPutInServer( bool bFrozen, int iTeam, int iClass ); + + +#endif // BOT_BASE_H diff --git a/game/server/tf2/c_obj_armor_upgrade.cpp b/game/server/tf2/c_obj_armor_upgrade.cpp new file mode 100644 index 0000000..158d6c3 --- /dev/null +++ b/game/server/tf2/c_obj_armor_upgrade.cpp @@ -0,0 +1,43 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "cbase.h" +#include "c_obj_armor_upgrade.h" + + +IMPLEMENT_CLIENTCLASS_DT(C_ArmorUpgrade, DT_ArmorUpgrade, CArmorUpgrade) +END_RECV_TABLE() + + + +C_ArmorUpgrade::C_ArmorUpgrade() +{ +} + + +int C_ArmorUpgrade::DrawModel( int flags ) +{ + C_BaseEntity *pParent = GetMoveParent(); + if ( pParent ) + { + C_BaseAnimating *pAnimating = dynamic_cast< C_BaseAnimating* >( pParent ); + if ( pAnimating ) + { + SetModelPointer( pParent->GetModel() ); + SetSequence( pAnimating->GetSequence() ); + m_nSkin = pAnimating->m_nSkin; + m_nBody = pAnimating->m_nBody; + + SetLocalOrigin( Vector( 0, 0, 50 ) ); + SetLocalAngles( QAngle( 0, 0, 0 ) ); + InvalidateBoneCache(); + } + } + + return BaseClass::DrawModel( 0 ); +} + + diff --git a/game/server/tf2/c_obj_armor_upgrade.h b/game/server/tf2/c_obj_armor_upgrade.h new file mode 100644 index 0000000..91898d1 --- /dev/null +++ b/game/server/tf2/c_obj_armor_upgrade.h @@ -0,0 +1,32 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#ifndef C_OBJ_ARMOR_UPGRADE_H +#define C_OBJ_ARMOR_UPGRADE_H +#ifdef _WIN32 +#pragma once +#endif + + +#include "tf_obj_baseupgrade_shared.h" + + +class C_ArmorUpgrade : public C_BaseObjectUpgrade +{ +public: + DECLARE_CLASS( C_ArmorUpgrade, C_BaseObjectUpgrade ); + DECLARE_CLIENTCLASS(); + C_ArmorUpgrade(); + + virtual int DrawModel( int flags ); + + +private: + C_ArmorUpgrade( const C_ArmorUpgrade & ) {} +}; + + +#endif // C_OBJ_ARMOR_UPGRADE_H diff --git a/game/server/tf2/controlzone.cpp b/game/server/tf2/controlzone.cpp new file mode 100644 index 0000000..a58a0ba --- /dev/null +++ b/game/server/tf2/controlzone.cpp @@ -0,0 +1,331 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Complete definition of the ControlZone behavioral entity +// +// $NoKeywords: $ +//=============================================================================// + +#include "tf_shareddefs.h" +#include "cbase.h" +#include "EntityOutput.h" +#include "tf_player.h" +#include "controlzone.h" +#include "team.h" + +//----------------------------------------------------------------------------- +// Purpose: Since the control zone is a data only class, force it to always be sent ( shouldn't change often so ) +// bandwidth usage should be small. +// Input : **ppSendTable - +// *recipient - +// *pvs - +// clientArea - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +int CControlZone::UpdateTransmitState() +{ + if ( IsEffectActive( EF_NODRAW ) ) + { + return SetTransmitState( FL_EDICT_DONTSEND ); + } + else + { + return SetTransmitState( FL_EDICT_ALWAYS ); + } +} + +IMPLEMENT_SERVERCLASS_ST(CControlZone, DT_ControlZone) + SendPropInt( SENDINFO(m_nZoneNumber), 8, SPROP_UNSIGNED ), +END_SEND_TABLE() + +LINK_ENTITY_TO_CLASS( trigger_controlzone, CControlZone); + +BEGIN_DATADESC( CControlZone ) + + // outputs + DEFINE_OUTPUT( m_ControllingTeam, "ControllingTeam" ), + + // inputs + DEFINE_INPUTFUNC( FIELD_VOID, "SetTeam", InputSetTeam ), + DEFINE_INPUTFUNC( FIELD_VOID, "LockTeam", InputLockControllingTeam ), + + // keys + DEFINE_KEYFIELD_NOT_SAVED( m_iLockAfterChange, FIELD_INTEGER, "LockAfterChange" ), + DEFINE_KEYFIELD_NOT_SAVED( m_flTimeTillCaptured, FIELD_FLOAT, "UncontestedTime" ), + DEFINE_KEYFIELD_NOT_SAVED( m_flTimeTillContested, FIELD_FLOAT, "ContestedTime" ), + DEFINE_KEYFIELD_NOT_SAVED( m_nZoneNumber, FIELD_INTEGER, "ZoneNumber" ), + +END_DATADESC() + + + +// Control Zone Ent Flags +#define CZF_DONT_USE_TOUCHES 1 + +//----------------------------------------------------------------------------- +// Purpose: Initializes the control zone +// Records who was the original controlling team (for control locking) +//----------------------------------------------------------------------------- +void CControlZone::Spawn( void ) +{ + // set the starting controlling team + m_ControllingTeam.Set( GetTeamNumber(), this, this ); + + // remember who the original controlling team was (for control locking) + m_iDefendingTeam = GetTeamNumber(); + + // Solid + SetSolid( SOLID_BSP ); + AddSolidFlags( FSOLID_TRIGGER ); + SetMoveType( MOVETYPE_NONE ); + SetModel( STRING( GetModelName() ) ); // set size and link into world + + // TF2 rules + m_flTimeTillContested = 10.0; // Go to contested 10 seconds after enemies enter the zone + m_flTimeTillCaptured = 5.0; // Go to captured state as soon as only one team holds the zone + + if ( m_nZoneNumber == 0 ) + { + Warning( "Warning, trigger_controlzone without Zone Number set\n" ); + } + + m_ZonePlayerList.Purge(); +} + +//----------------------------------------------------------------------------- +// Purpose: Records that a player has entered the zone, and updates it's state +// according, maybe starting to change team. +// Input : *pOther - the entity that left the zone +//----------------------------------------------------------------------------- +void CControlZone::StartTouch( CBaseEntity *pOther ) +{ + CBaseTFPlayer *pl = ToBaseTFPlayer( pOther ); + if ( !pl ) + return; + + CHandle< CBaseTFPlayer > hHandle; + hHandle = pl; + + m_ZonePlayerList.AddToTail( hHandle ); + + ReevaluateControllingTeam(); + + // Set this player's current zone to this zone + pl->SetCurrentZone( this ); +} + +//----------------------------------------------------------------------------- +// Purpose: Records that a player has left the zone, and updates it's state +// according, maybe starting to change team. +// Input : *pOther - the entity that left the zone +//----------------------------------------------------------------------------- +void CControlZone::EndTouch( CBaseEntity *pOther ) +{ + CBaseTFPlayer *pl = ToBaseTFPlayer( pOther ); + if ( !pl ) + return; + + CHandle< CBaseTFPlayer > hHandle; + hHandle = pl; + m_ZonePlayerList.FindAndRemove( hHandle ); + + ReevaluateControllingTeam(); + + // Unset this player's current zone if it's this one + if ( pl->GetCurrentZone() == this ) + pl->SetCurrentZone( NULL ); +} + +//----------------------------------------------------------------------------- +// Purpose: Checks to see if it's time to change controllers +//----------------------------------------------------------------------------- +void CControlZone::ReevaluateControllingTeam( void ) +{ + // Count the number of players in each team + int i; + memset( m_iPlayersInZone, 0, sizeof( m_iPlayersInZone ) ); + for ( i = 0; i < m_ZonePlayerList.Size(); i++ ) + { + if ( m_ZonePlayerList[i] != NULL && (m_ZonePlayerList[i]->GetTeamNumber() > 0) ) + { + m_iPlayersInZone[ m_ZonePlayerList[i]->GetTeamNumber() ] += 1; + } + } + + // Abort immediately if we're not using touches to changes teams + if ( HasSpawnFlags( CZF_DONT_USE_TOUCHES ) ) + return; + + // if we're locked in place, no changes can occur to controlling team except through an explicit map ResetTeam + if ( m_iLocked ) + return; + + bool foundAnyTeam = false; + int teamFound = 0; + + // check to see if any teams have no players + for ( i = 0; i < GetNumberOfTeams(); i++ ) + { + if ( m_iPlayersInZone[i] ) + { + if ( foundAnyTeam ) + { + // we've already found a team, so it's being contested; + teamFound = ZONE_CONTESTED; + break; + } + + foundAnyTeam = true; + teamFound = i; + } + } + + // no one in the area! + if ( teamFound == 0 ) + { + // just leave it as it is, let it continue to change team + // exception: if the zone state is contested, and there aren't any players in the zone, + // just return to the team who used to own the zone. + if ( GetTeamNumber() == ZONE_CONTESTED ) + { + ChangeTeam(m_iDefendingTeam); + SetControllingTeam( this, m_iDefendingTeam ); + } + + return; + } + + // if it's the same controlling team, don't worry about it + if ( teamFound == GetTeamNumber() ) + { + // the right team is in control, don't even think of switching + m_iTryingToChangeToTeam = 0; + SetNextThink( TICK_NEVER_THINK ); + return; + } + + // Find out if the zone isn't owned by anyone at all (hasn't been touched since the map started, and it started un-owned) + bool bHasBeenOwned = true; + if ( m_iDefendingTeam == 0 && GetTeamNumber() == 0 ) + bHasBeenOwned = false; + + // if it's not contested, always go to contested mode + if ( GetTeamNumber() != ZONE_CONTESTED && teamFound != GetTeamNumber() ) + { + // Unowned zones are captured immediately (no contesting stage) + if ( bHasBeenOwned ) + teamFound = ZONE_CONTESTED; + } + + // if it's the team we're trying to change to, don't worry about it + if ( teamFound == m_iTryingToChangeToTeam ) + return; + + // set up the time to change to the new team soon + m_iTryingToChangeToTeam = teamFound; + + // changing from contested->uncontested and visa-versa have different delays + if ( m_iTryingToChangeToTeam != ZONE_CONTESTED ) + { + if ( !bHasBeenOwned ) + { + DevMsg( 1, "trigger_controlzone: (%s) changing team to %d NOW\n", GetDebugName(), m_iTryingToChangeToTeam ); + SetNextThink( gpGlobals->curtime + 0.1f ); + } + else + { + DevMsg( 1, "trigger_controlzone: (%s) changing team to %d in %.2f seconds\n", GetDebugName(), m_iTryingToChangeToTeam, m_flTimeTillCaptured ); + SetNextThink( gpGlobals->curtime + m_flTimeTillCaptured ); + } + } + else + { + DevMsg( 1, "trigger_controlzone: (%s) changing to contested in %f seconds\n", GetDebugName(), m_flTimeTillContested ); + SetNextThink( gpGlobals->curtime + m_flTimeTillContested ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Checks to see if an uncontested territory is ready to change state +// to the new controlling team. +//----------------------------------------------------------------------------- +void CControlZone::Think( void ) +{ + if ( m_iTryingToChangeToTeam != 0 ) + { + // held zone long enough + SetControllingTeam( this, m_iTryingToChangeToTeam ); + + // lock against further change if set + if ( m_iLockAfterChange ) + { + LockControllingTeam(); + } + + // Re-evaluate controlling team if we were changing to Contested (enemy may have withdrawn) + if ( GetTeamNumber() == ZONE_CONTESTED ) + { + ReevaluateControllingTeam(); + } + } +} + + +//----------------------------------------------------------------------------- +// Purpose: set it so the team can no longer change, until a set controlling team action occurs +//----------------------------------------------------------------------------- +void CControlZone::InputLockControllingTeam( inputdata_t &inputdata ) +{ + LockControllingTeam(); +} + + +//----------------------------------------------------------------------------- +// Purpose: Input handler that sets the controlling team to the activator's team. +//----------------------------------------------------------------------------- +void CControlZone::InputSetTeam( inputdata_t &inputdata ) +{ + // Abort if it's already the defending team + if ( inputdata.pActivator->GetTeamNumber() == GetTeamNumber() ) + return; + + // set the new team + ChangeTeam(inputdata.pActivator->GetTeamNumber()); + SetControllingTeam( inputdata.pActivator, GetTeamNumber() ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Changes the team controlling this zone +// Input : newTeam - the new team to change to +//----------------------------------------------------------------------------- +void CControlZone::SetControllingTeam( CBaseEntity *pActivator, int newTeam ) +{ + DevMsg( 1, "trigger_controlzone: (%s) changing team to: %d\n", GetDebugName(), newTeam ); + + // remember this team as the defenders of the zone + m_iDefendingTeam = GetTeamNumber(); + + // reset state, firing the output + ChangeTeam(newTeam); + m_ControllingTeam.Set( GetTeamNumber(), pActivator, this ); + m_iLocked = FALSE; + m_iTryingToChangeToTeam = 0; + SetNextThink( TICK_NEVER_THINK ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CControlZone::LockControllingTeam( void ) +{ + // never lock a zone in contested mode + if ( GetTeamNumber() == ZONE_CONTESTED ) + return; + + // zones never lock to the defenders + if ( GetTeamNumber() == m_iDefendingTeam ) + return; + + m_iLocked = TRUE; +} + diff --git a/game/server/tf2/controlzone.h b/game/server/tf2/controlzone.h new file mode 100644 index 0000000..1f950b2 --- /dev/null +++ b/game/server/tf2/controlzone.h @@ -0,0 +1,60 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Control Zone entity +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef CONTROLZONE_H +#define CONTROLZONE_H +#ifdef _WIN32 +#pragma once +#endif + +//----------------------------------------------------------------------------- +// Purpose: Defines a team zone of control +// Usually the parent of many trigger entities +//----------------------------------------------------------------------------- +class CControlZone : public CBaseEntity +{ +public: + DECLARE_CLASS( CControlZone, CBaseEntity ); + DECLARE_SERVERCLASS(); + + void Spawn( void ); + void StartTouch( CBaseEntity * ); + void EndTouch( CBaseEntity * ); + void Think( void ); + + virtual int UpdateTransmitState(); + + // input functions + void InputLockControllingTeam( inputdata_t &inputdata ); + void InputSetTeam( inputdata_t &inputdata ); + + // internal methods + void LockControllingTeam( void ); + void ReevaluateControllingTeam( void ); + void SetControllingTeam( CBaseEntity *pActivator, int newTeam ); + + // outputs + int GetControllingTeam( void ) { return m_ControllingTeam.Get(); }; + COutputInt m_ControllingTeam; // outputs the team currently controlling this spot, whenever it changes - this is -1 when contended + +public: + // Data + CNetworkVar( int, m_nZoneNumber ); + int m_iDefendingTeam; // the original defeind team + int m_iLocked; // no more changes, until a reset it called + int m_iLockAfterChange; // auto-lock after the control zone changes hands through combat + float m_flTimeTillCaptured; // time that the control zone has to be uncontested for it to succesfully change teams + float m_flTimeTillContested; // time that the control zone has to be contested for for it to change to Contested mode (no team) + int m_iTryingToChangeToTeam; // the team is trying to change to + + CUtlVector< CHandle<CBaseTFPlayer> > m_ZonePlayerList; // List of all players in the zone at the moment + int m_iPlayersInZone[MAX_TF_TEAMS+1]; // count of players in the zone divided by team + + DECLARE_DATADESC(); +}; + +#endif // CONTROLZONE_H diff --git a/game/server/tf2/demo_entities.cpp b/game/server/tf2/demo_entities.cpp new file mode 100644 index 0000000..08aa366 --- /dev/null +++ b/game/server/tf2/demo_entities.cpp @@ -0,0 +1,123 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// +#include "cbase.h" +#include "ai_basenpc.h" +#include "animation.h" +#include "vstdlib/random.h" +#include "h_cycler.h" + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +class CCycler_TF2Commando : public CCycler +{ + DECLARE_CLASS( CCycler_TF2Commando, CCycler ); +public: + DECLARE_DATADESC(); + DECLARE_SERVERCLASS(); + + virtual void Spawn( void ); + virtual void Think( void ); + + // Inputs + void InputRaiseShield( inputdata_t &inputdata ); + void InputLowerShield( inputdata_t &inputdata ); + +private: + CNetworkVar( bool, m_bShieldActive ); + CNetworkVar( float, m_flShieldRaiseTime ); + CNetworkVar( float, m_flShieldLowerTime ); +}; + +IMPLEMENT_SERVERCLASS_ST(CCycler_TF2Commando, DT_Cycler_TF2Commando) + SendPropInt (SENDINFO(m_bShieldActive), 1, SPROP_UNSIGNED ), + SendPropFloat(SENDINFO(m_flShieldRaiseTime), 0, SPROP_NOSCALE ), + SendPropFloat(SENDINFO(m_flShieldLowerTime), 0, SPROP_NOSCALE ), +END_SEND_TABLE(); + +LINK_ENTITY_TO_CLASS( cycler_tf2commando, CCycler_TF2Commando ); +LINK_ENTITY_TO_CLASS( cycler_aliencommando, CCycler_TF2Commando ); + +BEGIN_DATADESC( CCycler_TF2Commando ) + + // Inputs + DEFINE_INPUTFUNC( FIELD_VOID, "RaiseShield", InputRaiseShield ), + DEFINE_INPUTFUNC( FIELD_VOID, "LowerShield", InputLowerShield ), + +END_DATADESC() + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCycler_TF2Commando::Spawn( void ) +{ + if (GetTeamNumber() == 1) + { + GenericCyclerSpawn( "models/player/human_commando.mdl", Vector(-16, -16, 0), Vector(16, 16, 72) ); + } + else + { + GenericCyclerSpawn( "models/player/alien_commando.mdl", Vector(-16, -16, 0), Vector(16, 16, 72) ); + } + + m_bShieldActive = false; + + BaseClass::Spawn(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCycler_TF2Commando::Think( void ) +{ + // Change sequence + if ( IsSequenceFinished() ) + { + // Raising our shield? + if ( m_bShieldActive ) + { + ResetSequence( LookupSequence( "ShieldUpIdle" ) ); + } + else if ( GetSequence() == LookupSequence( "ShieldDown" ) ) + { + ResetSequence( LookupSequence( "Idle" ) ); + } + } + + SetNextThink( gpGlobals->curtime + 0.1f ); + + if (m_animate) + { + StudioFrameAdvance ( ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Input that raises the cycler's shield +//----------------------------------------------------------------------------- +void CCycler_TF2Commando::InputRaiseShield( inputdata_t &inputdata ) +{ + if (m_animate) + { + m_bShieldActive = true; + ResetSequence( LookupSequence( "ShieldUp" ) ); + m_flShieldRaiseTime = gpGlobals->curtime; + } +} + +//----------------------------------------------------------------------------- +// Purpose: Input that lowers the cycler's shield +//----------------------------------------------------------------------------- +void CCycler_TF2Commando::InputLowerShield( inputdata_t &inputdata ) +{ + if (m_animate) + { + m_bShieldActive = false; + ResetSequence( LookupSequence( "ShieldDown" ) ); + m_flShieldLowerTime = gpGlobals->curtime; + } +} + diff --git a/game/server/tf2/entity_burn_effect.cpp b/game/server/tf2/entity_burn_effect.cpp new file mode 100644 index 0000000..3380fc7 --- /dev/null +++ b/game/server/tf2/entity_burn_effect.cpp @@ -0,0 +1,51 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" +#include "entity_burn_effect.h" + + + +IMPLEMENT_SERVERCLASS_ST_NOBASE( CEntityBurnEffect, DT_EntityBurnEffect ) + SendPropEHandle( SENDINFO( m_hBurningEntity ) ) +END_SEND_TABLE() + + +LINK_ENTITY_TO_CLASS( entity_burn_effect, CEntityBurnEffect ); + + +CEntityBurnEffect* CEntityBurnEffect::Create( CBaseEntity *pBurningEntity ) +{ + CEntityBurnEffect *pEffect = static_cast<CEntityBurnEffect*>(CreateEntityByName( "entity_burn_effect" )); + if ( pEffect ) + { + pEffect->m_hBurningEntity = pBurningEntity; + return pEffect; + } + else + { + return NULL; + } +} + +int CEntityBurnEffect::UpdateTransmitState() +{ + return SetTransmitState( FL_EDICT_FULLCHECK ); +} + + +int CEntityBurnEffect::ShouldTransmit( const CCheckTransmitInfo *pInfo ) +{ + CBaseEntity *pEnt = m_hBurningEntity; + if ( pEnt ) + return pEnt->ShouldTransmit( pInfo ); + else + return FL_EDICT_DONTSEND; +} + + + diff --git a/game/server/tf2/entity_burn_effect.h b/game/server/tf2/entity_burn_effect.h new file mode 100644 index 0000000..882abd5 --- /dev/null +++ b/game/server/tf2/entity_burn_effect.h @@ -0,0 +1,41 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef ENTITY_BURN_EFFECT_H +#define ENTITY_BURN_EFFECT_H +#ifdef _WIN32 +#pragma once +#endif + + +#include "baseentity.h" +#include "server_class.h" + + +class CEntityBurnEffect : public CBaseEntity +{ +public: + + DECLARE_CLASS( CEntityBurnEffect, CBaseEntity ); + DECLARE_SERVERCLASS(); + + static CEntityBurnEffect* Create( CBaseEntity *pBurningEntity ); + + +// Overrides. +public: + + virtual int UpdateTransmitState(); + virtual int ShouldTransmit( const CCheckTransmitInfo *pInfo ); + + +private: + CNetworkHandle( CBaseEntity, m_hBurningEntity ); +}; + + +#endif // ENTITY_BURN_EFFECT_H diff --git a/game/server/tf2/env_fallingrocks.cpp b/game/server/tf2/env_fallingrocks.cpp new file mode 100644 index 0000000..00b1634 --- /dev/null +++ b/game/server/tf2/env_fallingrocks.cpp @@ -0,0 +1,201 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" + +#define MAX_ROCK_MODELS 6 + +// Rock models +char *sRockModels[ MAX_ROCK_MODELS ] = +{ + "models/props/cliffside/inhibitor_rocks/inhibitor_rock12.mdl", + "models/props/cliffside/inhibitor_rocks/inhibitor_rock13.mdl", + "models/props/cliffside/inhibitor_rocks/inhibitor_rock14.mdl", + "models/props/cliffside/inhibitor_rocks/inhibitor_rock19.mdl", + "models/props/cliffside/inhibitor_rocks/inhibitor_rock20.mdl", + "models/props/cliffside/inhibitor_rocks/inhibitor_rock21.mdl", +}; + +//----------------------------------------------------------------------------- +// Purpose: A falling rock entity +//----------------------------------------------------------------------------- +class CFallingRock : public CBaseAnimating +{ + DECLARE_CLASS( CFallingRock, CBaseAnimating ); +public: + DECLARE_DATADESC(); + + CFallingRock( void ); + virtual void Spawn( void ); + virtual void VPhysicsUpdate( IPhysicsObject *pPhysics ); + static CFallingRock *Create( const Vector &vecOrigin, const QAngle &vecAngles, const Vector &vecVelocity, const AngularImpulse &vecRotationSpeed ); + + void RockTouch( CBaseEntity *pOther ); +public: +}; + +BEGIN_DATADESC( CFallingRock ) + + // functions + DEFINE_FUNCTION( RockTouch ), + +END_DATADESC() +LINK_ENTITY_TO_CLASS( fallingrock, CFallingRock ); + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CFallingRock::CFallingRock( void ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CFallingRock::Spawn( void ) +{ + SetModel( sRockModels[ random->RandomInt(0,MAX_ROCK_MODELS-1) ] ); + SetMoveType( MOVETYPE_NONE ); + m_takedamage = DAMAGE_NO; + + // Create the object in the physics system + VPhysicsInitNormal( SOLID_BBOX, 0, false ); + UTIL_SetSize( this, Vector(-4,-4,-4), Vector(4,4,4) ); + + SetTouch( RockTouch ); + SetThink( SUB_Remove ); + SetNextThink( gpGlobals->curtime + random->RandomFloat( 20.0, 30.0 ) ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CFallingRock::VPhysicsUpdate( IPhysicsObject *pPhysics ) +{ + BaseClass::VPhysicsUpdate( pPhysics ); +} + +//----------------------------------------------------------------------------- +// Purpose: Create a falling rock +//----------------------------------------------------------------------------- +CFallingRock *CFallingRock::Create( const Vector &vecOrigin, const QAngle &vecAngles, const Vector &vecVelocity, const AngularImpulse &vecRotationSpeed ) +{ + CFallingRock *pRock = (CFallingRock*)CreateEntityByName("fallingrock"); + + UTIL_SetOrigin( pRock, vecOrigin ); + pRock->SetLocalAngles( vecAngles ); + pRock->Spawn(); + + IPhysicsObject *pPhysicsObject = pRock->VPhysicsGetObject(); + if ( pPhysicsObject ) + { + pPhysicsObject->AddVelocity( &vecVelocity, &vecRotationSpeed ); + } + + return pRock; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CFallingRock::RockTouch( CBaseEntity *pOther ) +{ +} + + + + + + +//----------------------------------------------------------------------------- +// Purpose: A falling rock spawner entity +//----------------------------------------------------------------------------- +class CEnv_FallingRocks : public CPointEntity +{ + DECLARE_CLASS( CEnv_FallingRocks, CPointEntity ); +public: + DECLARE_DATADESC(); + + virtual void Spawn( void ); + virtual void Precache( void ); + void RockThink( void ); + void InputSpawnRock( inputdata_t &inputdata ); + +public: + float m_flFallStrength; + float m_flRotationSpeed; + float m_flMinSpawnTime; + float m_flMaxSpawnTime; + COutputEvent m_pOutputRockSpawned; +}; + +BEGIN_DATADESC( CEnv_FallingRocks ) + + // Fields + DEFINE_KEYFIELD( m_flFallStrength, FIELD_FLOAT, "FallSpeed"), + DEFINE_KEYFIELD( m_flRotationSpeed, FIELD_FLOAT, "RotationSpeed"), + DEFINE_KEYFIELD( m_flMinSpawnTime, FIELD_FLOAT, "MinSpawnTime"), + DEFINE_KEYFIELD( m_flMaxSpawnTime, FIELD_FLOAT, "MaxSpawnTime"), + + // Inputs + DEFINE_INPUTFUNC( FIELD_VOID, "SpawnRock", InputSpawnRock ), + + // Outputs + DEFINE_OUTPUT( m_pOutputRockSpawned, "OnRockSpawned" ), + + // Functions + DEFINE_FUNCTION( RockThink ), + +END_DATADESC() +LINK_ENTITY_TO_CLASS( env_fallingrocks, CEnv_FallingRocks ); + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CEnv_FallingRocks::Spawn( void ) +{ + Precache(); + SetThink( RockThink ); + SetNextThink( gpGlobals->curtime + random->RandomFloat( m_flMinSpawnTime, m_flMaxSpawnTime ) ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CEnv_FallingRocks::Precache( void ) +{ + for (int i = 0; i < MAX_ROCK_MODELS; i++ ) + { + PrecacheModel( sRockModels[i] ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CEnv_FallingRocks::RockThink( void ) +{ + // Spawn a rock + // Make it aim a little around the angle supplied + QAngle angFire = GetAbsAngles(); + angFire.y += random->RandomFloat( -10, 10 ); + Vector vecForward; + AngleVectors( angFire, &vecForward ); + CFallingRock::Create( GetAbsOrigin(), GetAbsAngles(), (vecForward * m_flFallStrength), AngularImpulse(0,0,m_flRotationSpeed) ); + + // Fire our output + m_pOutputRockSpawned.FireOutput( NULL,this ); + + SetNextThink( gpGlobals->curtime + random->RandomFloat( m_flMinSpawnTime, m_flMaxSpawnTime ) ); +} + +//------------------------------------------------------------------------------ +// Purpose: +//------------------------------------------------------------------------------ +void CEnv_FallingRocks::InputSpawnRock( inputdata_t &inputdata ) +{ + RockThink(); +}
\ No newline at end of file diff --git a/game/server/tf2/env_meteor.cpp b/game/server/tf2/env_meteor.cpp new file mode 100644 index 0000000..789df1f --- /dev/null +++ b/game/server/tf2/env_meteor.cpp @@ -0,0 +1,618 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" + +#include "tf_player.h" +#include "Env_Meteor.h" +#include "entitylist.h" +#include "vphysics_interface.h" +#include "tier1/strtools.h" +#include "mapdata_shared.h" +#include "sharedinterface.h" +#include "skycamera.h" +#include "ispatialpartition.h" +#include "gameinterface.h" +#include "props.h" +#include "tf_func_resource.h" +#include "resource_chunk.h" + + +#include "ndebugoverlay.h" + +//============================================================================= +// +// Enumerator for swept bbox collision. +// +class CCollideList : public IEntityEnumerator +{ +public: + CCollideList( Ray_t *pRay, CBaseEntity* pIgnoreEntity, int nContentsMask ) : + m_Entities( 0, 32 ), m_pIgnoreEntity( pIgnoreEntity ), + m_nContentsMask( nContentsMask ), m_pRay(pRay) {} + + virtual bool EnumEntity( IHandleEntity *pHandleEntity ) + { + trace_t tr; + enginetrace->ClipRayToEntity( *m_pRay, m_nContentsMask, pHandleEntity, &tr ); + if (( tr.fraction < 1.0f ) || (tr.startsolid) || (tr.allsolid)) + { + CBaseEntity *pEntity = gEntList.GetBaseEntity( pHandleEntity->GetRefEHandle() ); + m_Entities.AddToTail( pEntity ); + } + + return true; + } + + CUtlVector<CBaseEntity*> m_Entities; + +private: + CBaseEntity *m_pIgnoreEntity; + int m_nContentsMask; + Ray_t *m_pRay; +}; + + +//============================================================================= +// +// Meteor Factory Functions +// + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CMeteorFactory::CreateMeteor( int nID, int iType, + const Vector &vecPosition, const Vector &vecDirection, + float flSpeed, float flStartTime, float flDamageRadius, + const Vector &vecTriggerMins, const Vector &vecTriggerMaxs ) +{ + CEnvMeteor::Create( nID, iType, vecPosition, vecDirection, flSpeed, flStartTime, flDamageRadius, + vecTriggerMins, vecTriggerMaxs ); +} + +//============================================================================= +// +// Meteor Spawner Functions +// + +void SendProxy_MeteorTargetPositions( const SendProp *pProp, const void *pStruct, const void *pData, DVariant *pOut, int iElement, int objectID ) +{ + CEnvMeteorSpawnerShared *pMeteorSpawner = ( CEnvMeteorSpawnerShared* )pData; + pOut->m_Vector[0] = pMeteorSpawner->m_aTargets[iElement].m_vecPosition.x; + pOut->m_Vector[1] = pMeteorSpawner->m_aTargets[iElement].m_vecPosition.y; + pOut->m_Vector[2] = pMeteorSpawner->m_aTargets[iElement].m_vecPosition.z; +} + +void SendProxy_MeteorTargetRadii( const SendProp *pProp, const void *pStruct, const void *pData, DVariant *pOut, int iElement, int objectID ) +{ + CEnvMeteorSpawnerShared *pMeteorSpawner = ( CEnvMeteorSpawnerShared* )pData; + pOut->m_Float = pMeteorSpawner->m_aTargets[iElement].m_flRadius; +} + +int SendProxyArrayLength_MeteorTargets( const void *pStruct, int objectID ) +{ + CEnvMeteorSpawnerShared *pMeteorSpawner = ( CEnvMeteorSpawnerShared* )pStruct; + return pMeteorSpawner->m_aTargets.Count(); +} + +// Link the name "env_meteorspawner" to the CMeteorSpawner class. This +// links the WC entity with the game code. +LINK_ENTITY_TO_CLASS( env_meteorspawner, CEnvMeteorSpawner ); + +BEGIN_DATADESC( CEnvMeteorSpawner ) + + // Key Fields. + DEFINE_KEYFIELD( m_SpawnerShared.m_iMeteorType, FIELD_INTEGER, "MeteorType" ), + DEFINE_KEYFIELD( m_SpawnerShared.m_bSkybox, FIELD_INTEGER, "SpawnInSkybox" ), + DEFINE_KEYFIELD( m_SpawnerShared.m_flMinSpawnTime, FIELD_FLOAT, "SpawnIntervalMin" ), + DEFINE_KEYFIELD( m_SpawnerShared.m_flMaxSpawnTime, FIELD_FLOAT, "SpawnIntervalMax" ), + DEFINE_KEYFIELD( m_SpawnerShared.m_nMinSpawnCount, FIELD_INTEGER, "SpawnCountMin" ), + DEFINE_KEYFIELD( m_SpawnerShared.m_nMaxSpawnCount, FIELD_INTEGER, "SpawnCountMax" ), + DEFINE_KEYFIELD( m_SpawnerShared.m_flMinSpeed, FIELD_FLOAT, "MeteorSpeedMin" ), + DEFINE_KEYFIELD( m_SpawnerShared.m_flMaxSpeed, FIELD_FLOAT, "MeteorSpeedMax" ), + DEFINE_KEYFIELD( m_SpawnerShared.m_flMeteorDamageRadius, FIELD_FLOAT, "MeteorDamageRadius" ), + DEFINE_KEYFIELD( m_fDisabled, FIELD_BOOLEAN, "StartDisabled" ), + + // Function Pointers. + DEFINE_FUNCTION( MeteorSpawnerThink ), + + // Inputs + DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ), + DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputDisable ), + +END_DATADESC() + +BEGIN_SEND_TABLE_NOBASE( CEnvMeteorSpawnerShared, DT_EnvMeteorSpawnerShared ) + // Setup (read from) Worldcraft. + SendPropInt ( SENDINFO( m_iMeteorType ), 8, SPROP_UNSIGNED ), + SendPropInt ( SENDINFO( m_bSkybox ), 4, SPROP_UNSIGNED ), + SendPropFloat ( SENDINFO( m_flMinSpawnTime ), 0, SPROP_NOSCALE ), + SendPropFloat ( SENDINFO( m_flMaxSpawnTime ), 0, SPROP_NOSCALE ), + SendPropInt ( SENDINFO( m_nMinSpawnCount ), 16, SPROP_UNSIGNED ), + SendPropInt ( SENDINFO( m_nMaxSpawnCount ), 16, SPROP_UNSIGNED ), + SendPropFloat ( SENDINFO( m_flMinSpeed ), 0, SPROP_NOSCALE ), + SendPropFloat ( SENDINFO( m_flMaxSpeed ), 0, SPROP_NOSCALE ), + + // Setup through Init. + SendPropFloat ( SENDINFO( m_flStartTime ), -1, SPROP_NOSCALE ), + SendPropInt ( SENDINFO( m_nRandomSeed ), -1, SPROP_UNSIGNED ), + SendPropVector ( SENDINFO( m_vecMinBounds ), -1, SPROP_NOSCALE ), + SendPropVector ( SENDINFO( m_vecMaxBounds ), -1, SPROP_NOSCALE ), + SendPropVector ( SENDINFO( m_vecTriggerMins ), -1, SPROP_NOSCALE ), + SendPropVector ( SENDINFO( m_vecTriggerMaxs ), -1, SPROP_NOSCALE ), + + // Target List + SendPropArray2( SendProxyArrayLength_MeteorTargets, + SendPropVector( "meteortargetposition_array_element", 0, 0, 0, SPROP_NOSCALE, 0, 0, SendProxy_MeteorTargetPositions ), + 16, 0, "meteortargetposition_array" ), + + SendPropArray2( SendProxyArrayLength_MeteorTargets, + SendPropFloat( "meteortargetradius_array_element", 0, 0, 0, SPROP_NOSCALE, 0, 0, SendProxy_MeteorTargetRadii ), + 16, 0, "meteortargetradius_array" ) +END_SEND_TABLE() + +// This table encodes the CBaseEntity data. +IMPLEMENT_SERVERCLASS_ST_NOBASE( CEnvMeteorSpawner, DT_EnvMeteorSpawner ) + SendPropDataTable ( SENDINFO_DT( m_SpawnerShared ), &REFERENCE_SEND_TABLE( DT_EnvMeteorSpawnerShared ) ), + SendPropInt ( SENDINFO( m_fDisabled ), 1, SPROP_UNSIGNED ), +END_SEND_TABLE() + +// Meteor Models +char *strResourceMeteorModels[2] = +{ + "models/props/common/meteorites/meteor04.mdl", + "models/props/common/meteorites/meteor05.mdl", +}; + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +CEnvMeteorSpawner::CEnvMeteorSpawner() +{ +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CEnvMeteorSpawner::Spawn( void ) +{ + // Pre-cache. + Precache(); + + // Server-side is not visible -- for collision only. + SetSolid( SOLID_NONE ); + SetMoveType( MOVETYPE_NONE ); + AddEffects( EF_NODRAW ); + + // Set the "brush model" size and link into the world. + SetModel( STRING( GetModelName() ) ); + + // Set the think function and time. + if ( !m_fDisabled ) + { + SetThink( MeteorSpawnerThink ); + } +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CEnvMeteorSpawner::InputEnable( inputdata_t &inputdata ) +{ + m_fDisabled = false; + + m_SpawnerShared.m_flStartTime = gpGlobals->curtime; + m_SpawnerShared.m_flNextSpawnTime = m_SpawnerShared.m_flStartTime + m_SpawnerShared.m_flMaxSpawnTime; + + // Probably should set this as a message begin, etc..... will get to this later!! +// +// CEntityMessageFilter filter( this, "CEnvMeteorSpawner" ); +// MessageBegin( filter, 0 ); +// WRITE_LONG( m_SpawnerShared.m_flStartTime ); +// WRITE_LONG( m_SpawnerShared.m_flNextSpawnTime ); +// MessageEnd(); + + // Set the think function and time. + SetThink( MeteorSpawnerThink ); + SetNextThink( gpGlobals->curtime + m_SpawnerShared.m_flNextSpawnTime ); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CEnvMeteorSpawner::InputDisable( inputdata_t &inputdata ) +{ + m_fDisabled = true; + SetThink( NULL ); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CEnvMeteorSpawner::Get3DSkyboxWorldBounds( Vector &vecTriggerMins, + Vector &vecTriggerMaxs ) +{ + CBaseEntity *pEntity = gEntList.FindEntityByClassname( NULL, "trigger_skybox2world" ); + if ( pEntity && pEntity->edict() ) + { + pEntity->CollisionProp()->WorldSpaceAABB( &vecTriggerMins, &vecTriggerMaxs ); + } +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CEnvMeteorSpawner::Precache( void ) +{ + // Precache the meteor models! + for ( int iType = 0; iType < 2; iType++ ) + { + PrecacheModel( strResourceMeteorModels[iType] ); + } +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CEnvMeteorSpawner::MeteorSpawnerThink( void ) +{ + SetNextThink( gpGlobals->curtime + m_SpawnerShared.MeteorThink( gpGlobals->curtime ) ); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +int CEnvMeteorSpawner::ShouldTransmit( const CCheckTransmitInfo *pInfo ) +{ + if ( m_SpawnerShared.m_bSkybox ) + return FL_EDICT_ALWAYS; + + return BaseClass::ShouldTransmit( pInfo ); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CEnvMeteorSpawner::Activate( void ) +{ + // Parse the entity list looking for targets! + int nEntityCount = engine->GetEntityCount(); + for ( int iEntity = 0; iEntity < nEntityCount; ++iEntity ) + { + edict_t *pEdict = engine->PEntityOfEntIndex( iEntity ); + if ( !pEdict || pEdict->IsFree() ) + continue; + + CBaseEntity *pEntity = GetContainingEntity( pEdict ); + if ( !pEntity ) + continue; + + if ( pEntity->GetFlags()& FL_STATICPROP ) + continue; + + if ( !Q_strcmp( pEntity->GetClassname(), "env_meteortarget" ) ) + { + CEnvMeteorTarget *pMeteorTarget = static_cast<CEnvMeteorTarget*>( pEntity ); + if ( pMeteorTarget && pMeteorTarget->m_target != NULL_STRING ) + { + if ( !Q_strcmp( STRING( pMeteorTarget->m_target ), STRING( GetEntityName() ) ) ) + { + m_SpawnerShared.AddToTargetList( pMeteorTarget->GetLocalOrigin(), pMeteorTarget->m_flRadius ); + } + } + } + } + + // Get 3d skybox world trigger bounds. + Vector vecTriggerMins, vecTriggerMaxs; + Get3DSkyboxWorldBounds( vecTriggerMins, vecTriggerMaxs ); + + // Initialize the spawner. + float flTime = gpGlobals->curtime; + m_SpawnerShared.Init( &m_Factory, 0/* seed */, flTime, + WorldAlignMins(), WorldAlignMaxs(), vecTriggerMins, vecTriggerMaxs ); + + // Setup next think. + if ( !m_fDisabled ) + { + SetNextThink( gpGlobals->curtime + m_SpawnerShared.m_flNextSpawnTime ); + } +} + +//============================================================================= +// +// Meteor Target Functions +// + +LINK_ENTITY_TO_CLASS( env_meteortarget, CEnvMeteorTarget ); + +BEGIN_DATADESC( CEnvMeteorTarget ) + + // Key Fields. + DEFINE_KEYFIELD( m_flRadius, FIELD_FLOAT, "EffectRadius" ), + +END_DATADESC() + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +CEnvMeteorTarget::CEnvMeteorTarget() +{ + m_iTargetID = -1; + m_flRadius = 1.0f; +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CEnvMeteorTarget::Spawn( void ) +{ + BaseClass::Spawn(); +} + +//============================================================================= +// +// Meteor Functions +// + +// +// NOTE: The server-side meteor code has not really been tested. I do not +// trust that is works correctly and/or cleans itself up nicely! +// + +LINK_ENTITY_TO_CLASS( env_meteor, CEnvMeteor ); + +BEGIN_DATADESC( CEnvMeteor ) + + // Function Pointers. + DEFINE_FUNCTION( MeteorSkyboxThink ), + DEFINE_FUNCTION( MeteorWorldThink ), + +END_DATADESC() + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +CEnvMeteor::CEnvMeteor() +{ + m_vecMin.Init( -10.0f, -10.0f, -10.0f ); + m_vecMax.Init( 10.0f, 10.0f, 10.0f ); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +CEnvMeteor *CEnvMeteor::Create( int nID, int iMeteorType, + const Vector &vecOrigin, const Vector &vecDirection, + float flSpeed, float flStartTime, float flDamageRadius, + const Vector &vecTriggerMins, const Vector &vecTriggerMaxs ) +{ + CEnvMeteor *pMeteor = ( CEnvMeteor* )CreateEntityByName( "env_meteor" ); + if ( pMeteor ) + { + pMeteor->m_Meteor.Init( nID, flStartTime, METEOR_PASSIVE_TIME, vecOrigin, vecDirection, flSpeed, + flDamageRadius, vecTriggerMins, vecTriggerMaxs ); + + // If the meteor will never enter the world, then don't bother with a server-side version. + if ( pMeteor->m_Meteor.m_flWorldEnterTime == METEOR_INVALID_TIME ) + { + UTIL_Remove( pMeteor ); + } + + // Handle forward simulation. + if ( ( pMeteor->m_Meteor.m_flStartTime + METEOR_MAX_LIFETIME ) < gpGlobals->curtime ) + { + UTIL_Remove( pMeteor ); + } + + pMeteor->Spawn(); + pMeteor->SetNextThink( gpGlobals->curtime + pMeteor->m_Meteor.m_flWorldEnterTime ); + } + + return pMeteor; +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CEnvMeteor::Spawn( void ) +{ + // Pass data. + BaseClass::Spawn(); + + int iModel = modelinfo->GetModelIndex( "models/props/common/meteorites/meteor04.mdl" ); + if ( iModel > 0 ) + { + const model_t *pModel = modelinfo->GetModel( iModel ); + modelinfo->GetModelBounds( pModel, m_vecMin, m_vecMax ); + } + + // Assumes we start life in a skybox! + SetThink( MeteorSkyboxThink ); + + m_bPrevInSkybox = true; +} + +//----------------------------------------------------------------------------- +// Purpose: This think function should be called at the time when the meteor +// will be leaving the skybox and entering the world. +//----------------------------------------------------------------------------- +void CEnvMeteor::MeteorSkyboxThink( void ) +{ + SetThink( MeteorWorldThink ); + SetNextThink( gpGlobals->curtime + 0.2f ); +} + +//----------------------------------------------------------------------------- +// Purpose: This think function simulates (moves/collides) the meteor while in +// the world. +//----------------------------------------------------------------------------- +void CEnvMeteor::MeteorWorldThink( void ) +{ + // Get the current time. + float flTime = gpGlobals->curtime; + + // Convert if need be! + if ( m_bPrevInSkybox ) + { + m_Meteor.ConvertFromSkyboxToWorld(); + UTIL_SetOrigin( this, m_Meteor.m_vecStartPosition ); + + m_bPrevInSkybox = false; + } + + // Update meteor position for swept collision test. + Vector vecEndPosition; + m_Meteor.GetPositionAtTime( flTime, vecEndPosition ); + + // Debugging!! +// NDebugOverlay::Box( GetAbsOrigin(), m_vecMin * 0.5f, m_vecMax * 0.5f, 255, 255, 0, 0, 5 ); +// NDebugOverlay::Box( vecEndPosition, m_vecMin, m_vecMax, 255, 0, 0, 0, 5 ); + + Ray_t ray; + ray.Init( GetAbsOrigin(), vecEndPosition, m_vecMin, m_vecMax ); + + CCollideList collideList( &ray, this, MASK_SOLID ); + enginetrace->EnumerateEntities( ray, false, &collideList ); + + // Now get each entity and react accordinly! + for( int iEntity = collideList.m_Entities.Count(); --iEntity >= 0; ) + { + CBaseEntity *pEntity = collideList.m_Entities[iEntity]; + + if ( pEntity ) + { + Vector vecForceDir = m_Meteor.m_vecDirection; + + // Check for a physics object and apply force! + IPhysicsObject *pPhysObject = pEntity->VPhysicsGetObject(); + if ( pPhysObject ) + { +// float flMass = pPhysObject->GetMass(); + + // Send it flying!!! + vecForceDir *= 5000000000000.0f; + pPhysObject->ApplyForceCenter( vecForceDir ); + } + + if ( pEntity->m_takedamage ) + { + CTakeDamageInfo info( this, this, 200.0f, DMG_CLUB ); + CalculateExplosiveDamageForce( &info, vecForceDir, pEntity->GetAbsOrigin() ); + pEntity->TakeDamage( info ); + } + } + } + + trace_t trace; + UTIL_TraceHull( GetAbsOrigin(), vecEndPosition, m_vecMin, m_vecMax, + MASK_NPCWORLDSTATIC, this, COLLISION_GROUP_NONE, &trace ); + if( ( trace.fraction < 1.0f ) && !( trace.surface.flags & SURF_SKY ) ) + { + CBaseEntity *pEntity = trace.m_pEnt; + if ( pEntity ) + { + // Hit the world? The meteor is destroyed! + if ( pEntity->GetSolid() == SOLID_BSP ) + { +#if 0 + // Suppress resources for now!! + + // Create a random number or resource chunks. + int nChunkCount = random->RandomInt( 0, 4 ); + for( int iChunk = 0; iChunk < nChunkCount; ++iChunk ) + { + // Generate a random velocity vector. + Vector vVelocity = Vector( random->RandomFloat( -20,20 ), random->RandomFloat( -20,20 ), random->RandomFloat( 100,150 ) ); + CResourceChunk::Create( false, GetAbsOrigin(), vVelocity ); + } +#endif + + // Splash damage! + Vector vecImpactPoint; + vecImpactPoint = GetAbsOrigin() + ( ( vecEndPosition - GetAbsOrigin() ) * trace.fraction ); + + // Debugging!! +// NDebugOverlay::Box( vecImpactPoint, m_vecMin, m_vecMax, 0, 255, 0, 0, 5 ); + + //Iterate on all entities in the vicinity. + for ( CEntitySphereQuery sphere( vecImpactPoint, m_Meteor.GetDamageRadius() ); pEntity = sphere.GetCurrentEntity(); sphere.NextEntity() ) + { + // Get distance to object and use it as a scale value. + Vector vecSegment; + vecSegment = pEntity->GetAbsOrigin() - vecImpactPoint; + float flDistance = vecSegment.Length(); + + float flScale = flDistance / ( m_Meteor.GetDamageRadius() * 0.75f ); + if ( flScale > 1.0f ) + { + flScale = 1.0f; + } + + Vector vecForceDir = m_Meteor.m_vecDirection; + + // Check for a physics object and apply force! + IPhysicsObject *pPhysObject = pEntity->VPhysicsGetObject(); + if ( pPhysObject ) + { +// float flMass = pPhysObject->GetMass(); + + // Send it flying!!! + vecForceDir *= 5000000000000.0f * flScale; + pPhysObject->ApplyForceCenter( vecForceDir ); + } + + if ( pEntity->m_takedamage ) + { + CTakeDamageInfo info( this, this, 300.0f * flScale, DMG_CLUB ); + CalculateExplosiveDamageForce( &info, vecForceDir, pEntity->GetAbsOrigin() ); + pEntity->TakeDamage( info ); + } + } + + UTIL_Remove( this ); + return; + } + } + } + + // Always move full movement. + UTIL_SetOrigin( this, vecEndPosition ); + SetNextThink( gpGlobals->curtime + 0.2f ); + + // Check for death. + if ( flTime >= m_Meteor.m_flWorldExitTime ) + { + UTIL_Remove( this ); + return; + } +} + +//============================================================================= +// +// Shooting Star Spawner Functionality. +// + +// Link the name "env_meteorspawner" to the CMeteorSpawner class. This +// links the WC entity with the game code. +LINK_ENTITY_TO_CLASS( env_shootingstarspawner, CShootingStarSpawner ); + +BEGIN_DATADESC( CShootingStarSpawner ) + + // keys + DEFINE_KEYFIELD_NOT_SAVED( m_flSpawnInterval, FIELD_FLOAT, "SpawnInterval" ), + DEFINE_KEYFIELD_NOT_SAVED( m_bSkybox, FIELD_INTEGER, "SpawnInSkybox" ), + +END_DATADESC() + +IMPLEMENT_SERVERCLASS_ST( CShootingStarSpawner, DT_ShootingStarSpawner ) + SendPropFloat( SENDINFO( m_flSpawnInterval ), -1, SPROP_NOSCALE ), +END_SEND_TABLE() + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CShootingStarSpawner::CShootingStarSpawner() +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int CShootingStarSpawner::ShouldTransmit( const CCheckTransmitInfo *pInfo ) +{ + // Always send shooting star spawners if they are in the skybox! + if ( m_bSkybox ) + return FL_EDICT_ALWAYS ; + + return BaseClass::ShouldTransmit( pInfo ); +} diff --git a/game/server/tf2/env_meteor.h b/game/server/tf2/env_meteor.h new file mode 100644 index 0000000..ddeb2ca --- /dev/null +++ b/game/server/tf2/env_meteor.h @@ -0,0 +1,146 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef ENV_METEOR_H +#define ENV_METEOR_H +#pragma once + +#include "BaseEntity.h" +#include "BaseAnimating.h" +#include "Env_Meteor_Shared.h" +#include "utlvector.h" + +//============================================================================= +// +// Server-side Meteor Factory Class +// +class CMeteorFactory : public IMeteorFactory +{ +public: + + void CreateMeteor( int nID, int iType, const Vector &vecPosition, + const Vector &vecDirection, float flSpeed, float flStartTime, + float flDamageRadius, + const Vector &vecTriggerMins, const Vector &vecTriggerMaxs ); +}; + +//============================================================================= +// +// Meteor Spawner Class +// +class CEnvMeteorSpawner : public CBaseEntity +{ +public: + + DECLARE_CLASS( CEnvMeteorSpawner, CBaseEntity ); + + DECLARE_DATADESC(); + DECLARE_SERVERCLASS(); + + CEnvMeteorSpawner(); + + void Spawn( void ); + void Precache( void ); + void MeteorSpawnerThink( void ); + int UpdateTransmitState() { return SetTransmitState( FL_EDICT_FULLCHECK ); } + int ShouldTransmit( const CCheckTransmitInfo *pInfo ); + void Activate( void ); + +private: + + // Inputs + void InputEnable( inputdata_t &inputdata ); + void InputDisable( inputdata_t &inputdata ); + + void Get3DSkyboxWorldBounds( Vector &vecTriggerMins, Vector &vecTriggerMaxs ); + + CMeteorFactory m_Factory; + CNetworkVarEmbedded( CEnvMeteorSpawnerShared, m_SpawnerShared ); + + CNetworkVar( bool, m_fDisabled ); // Spawner active (trigger). NOTE: uses an f to remain consistent + // with entity input system +}; + +//============================================================================= +// +// Meteor Target Class +// +class CEnvMeteorTarget : public CBaseEntity +{ +public: + + DECLARE_CLASS( CEnvMeteorTarget, CBaseEntity ); + DECLARE_DATADESC(); + + CEnvMeteorTarget(); + void Spawn( void ); + + int m_iTargetID; + float m_flRadius; +}; + +//============================================================================= +// +// Meteor Class +// +class CEnvMeteor : public CBaseAnimating +{ + + DECLARE_CLASS( CEnvMeteor, CBaseAnimating ); + +public: + + DECLARE_DATADESC(); + + //------------------------------------------------------------------------- + // Initialization + //------------------------------------------------------------------------- + CEnvMeteor(); + static CEnvMeteor *Create( int nID, int iMeteorType, const Vector &vecOrigin, + const Vector &vecDirection, float flSpeed, float flStartTime, + float flDamageRadius, + const Vector &vecTriggerMins, const Vector &vecTriggerMaxs ); + void Spawn( void ); + + //------------------------------------------------------------------------- + // Think(s) + //------------------------------------------------------------------------- + void MeteorSkyboxThink( void ); + void MeteorWorldThink( void ); + +private: + + CEnvMeteorShared m_Meteor; + bool m_bPrevInSkybox; + Vector m_vecMin, m_vecMax; +}; + +//============================================================================= +// +// Shooting Star Spawner Class +// +class CShootingStarSpawner : public CBaseEntity +{ + DECLARE_CLASS( CShootingStarSpawner, CBaseEntity ); + +public: + + CShootingStarSpawner(); + + DECLARE_DATADESC(); + DECLARE_SERVERCLASS(); + + virtual int UpdateTransmitState() { return SetTransmitState( FL_EDICT_FULLCHECK ); } + virtual int ShouldTransmit( const CCheckTransmitInfo *pInfo ); + +public: + + CNetworkVar( float, m_flSpawnInterval ); // How often do I spawn shooting stars? + bool m_bSkybox; // Is the spawner in the skybox? +}; + +#endif // ENV_METEOR_H
\ No newline at end of file diff --git a/game/server/tf2/fire_damage_mgr.cpp b/game/server/tf2/fire_damage_mgr.cpp new file mode 100644 index 0000000..4d15084 --- /dev/null +++ b/game/server/tf2/fire_damage_mgr.cpp @@ -0,0 +1,301 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" +#include "fire_damage_mgr.h" +#include "entity_burn_effect.h" +#include "gasoline_blob.h" +#include "tf_obj.h" +#include "ai_basenpc.h" +#include "tf_gamerules.h" + + +#define FIRE_DAMAGE_APPLY_INTERVAL 0.5 // Apply the damage at this interval. +#define FIRE_DECAY_END_VALUE 0.00001 + + +// No more damage from fire can be applied to a player per second. +#define MAX_FIRE_DAMAGE_PER_SECOND 15 + +// The fire heat uses exponential decay. It goes from MAX_FIRE_DAMAGE_PER_SECOND to +// FIRE_DECAY_END_VALUE in FIRE_DECAY_SECONDS. +#define FIRE_DECAY_SECONDS 3 + + +ConVar fire_damageall( "fire_damageall", "0", 0, "Enable fire damaging team members." ); + + +bool CFireDamageMgr::Init() +{ + m_flApplyDamageCountdown = FIRE_DAMAGE_APPLY_INTERVAL; + + // Fire decays exponentially: B = A * e^(-kt) + // So we set B=FIRE_DECAY_END_VALUE, A=flMaxDamagePerSecond, and t=flFireDecaySeconds, then solve for K. + m_flMaxDamagePerSecond = MAX_FIRE_DAMAGE_PER_SECOND; + m_flDecayConstant = -log( FIRE_DECAY_END_VALUE / m_flMaxDamagePerSecond ) / FIRE_DECAY_SECONDS; + + return true; +} + + +void CFireDamageMgr::AddDamage( CBaseEntity *pTarget, CBaseEntity *pAttacker, float flDamageAccel, bool bMakeBurnEffect ) +{ + FOR_EACH_LL( m_DamageEnts, iDamageEnt ) + { + CDamageEnt *pEnt = &m_DamageEnts[iDamageEnt]; + + if ( pEnt->m_hEnt != pTarget ) + continue; + + for ( int i=0; i < pEnt->m_nAttackers; i++ ) + { + if ( pEnt->m_Attackers[i].m_hAttacker == pAttacker ) + { + pEnt->m_Attackers[i].m_flVelocity += flDamageAccel * gpGlobals->frametime; + return; + } + } + + + if ( pEnt->m_nAttackers < CDamageEnt::MAX_ATTACKERS ) + { + // Add a new attacker. + pEnt->m_Attackers[pEnt->m_nAttackers].Init( pAttacker, flDamageAccel * gpGlobals->frametime ); + ++pEnt->m_nAttackers; + return; + } + else + { + // No room for more attackers. + Warning( "CFireDamageMgr: ran out of attackers\n" ); + return; + } + } + + // Add a new CDamageEnt. + int iNew = m_DamageEnts.AddToTail(); + CDamageEnt *pEnt = &m_DamageEnts[iNew]; + pEnt->m_hEnt = pTarget; + pEnt->m_bWasAlive = pTarget->IsAlive(); + pEnt->m_nAttackers = 1; + pEnt->m_Attackers[0].Init( pAttacker, flDamageAccel * gpGlobals->frametime ); + if ( bMakeBurnEffect ) + pEnt->m_pBurnEffect = CEntityBurnEffect::Create( pTarget ); + else + pEnt->m_pBurnEffect = NULL; +} + + +void CFireDamageMgr::RemoveDamageEnt( int iEnt ) +{ + UTIL_Remove( m_DamageEnts[iEnt].m_pBurnEffect ); + m_DamageEnts.Remove( iEnt ); +} + + +void CFireDamageMgr::FrameUpdatePostEntityThink() +{ + VPROF( "CFireDamageMgr::FrameUpdatePostEntityThink" ); + float frametime = gpGlobals->frametime; + + // Update the damage countdown. + m_flApplyDamageCountdown -= gpGlobals->frametime; + bool bApplyDamageThisFrame = false; + if ( m_flApplyDamageCountdown <= 0 ) + { + bApplyDamageThisFrame = true; + m_flApplyDamageCountdown += FIRE_DAMAGE_APPLY_INTERVAL; + } + + + // (-kt) + // Figure out how much all the damage decays this frame: e + float flFrameDecay = pow( 2.718281828459045235360, -m_flDecayConstant * frametime ); + + + int iNext; + for ( int iCur = m_DamageEnts.Head(); iCur != m_DamageEnts.InvalidIndex(); iCur = iNext ) + { + iNext = m_DamageEnts.Next( iCur ); + CDamageEnt *pEnt = &m_DamageEnts[iCur]; + + + // If the entity was dead and is now alive, stop damage to them so their new body doesn't burn. + if ( !pEnt->m_hEnt.Get() || ( !pEnt->m_bWasAlive && pEnt->m_hEnt->IsAlive() ) ) + { + RemoveDamageEnt( iCur ); + pEnt = NULL; + continue; + } + + pEnt->m_bWasAlive = pEnt->m_hEnt->IsAlive(); + + // Sum up each attacker's velocity. + float flTotalVelocity = 0; + for ( int i=0; i < pEnt->m_nAttackers; i++ ) + flTotalVelocity += pEnt->m_Attackers[i].m_flVelocity; + + + // Figure out each attacker's contribution. + float flContributionPercent[CDamageEnt::MAX_ATTACKERS]; + for ( i=0; i < pEnt->m_nAttackers; i++ ) + flContributionPercent[i] = pEnt->m_Attackers[i].m_flVelocity / flTotalVelocity; + + + // Decay each attacker's velocity. + flTotalVelocity *= flFrameDecay; + + // Uniformly scale each attacker's velocity down so the sum total doesn't exceed our maximum. + float flPercentScale = 1; + if ( flTotalVelocity > m_flMaxDamagePerSecond ) + flPercentScale = m_flMaxDamagePerSecond / flTotalVelocity; + + for ( i=0; i < pEnt->m_nAttackers; i++ ) + { + CDamageAttacker *pAttacker = &pEnt->m_Attackers[i]; + + pAttacker->m_flVelocity *= flFrameDecay * flPercentScale; + + bool bEntsValid = (pEnt->m_Attackers[i].m_hAttacker.Get() != NULL); + if ( !bEntsValid || + pEnt->m_Attackers[i].m_flVelocity <= 0.001 ) + { + if ( bEntsValid ) + ApplyCollectedDamage( pEnt, i ); // Apply the last-remaining damage from this guy. + + Q_memmove( &pEnt->m_Attackers[i], &pEnt->m_Attackers[i+1], sizeof( pEnt->m_Attackers[0] ) * (pEnt->m_nAttackers-i-1) ); + Q_memmove( &flContributionPercent[i], &flContributionPercent[i+1], sizeof( flContributionPercent[0] ) * (pEnt->m_nAttackers-i-1) ); + + --pEnt->m_nAttackers; + if ( pEnt->m_nAttackers == 0 ) + { + // This ent isn't being damaged anymore. + RemoveDamageEnt( iCur ); + break; + } + + --i; + } + + // Update their current damage sum and maybe apply the damage. + pAttacker->m_flDamageSum += pAttacker->m_flVelocity * frametime; + if ( bApplyDamageThisFrame ) + { + ApplyCollectedDamage( pEnt, i ); + } + } + } +} + + +float GetFireDamageScale( CBaseEntity *pEnt ) +{ + // Objects have a lot more health and we want them to take damage faster. + if ( dynamic_cast< CBaseObject* >( pEnt ) ) + return 4; + else + return 1; +} + + +void CFireDamageMgr::ApplyCollectedDamage( CFireDamageMgr::CDamageEnt *pEnt, int iAttacker ) +{ + CDamageAttacker *pAttacker = &pEnt->m_Attackers[iAttacker]; + + CTakeDamageInfo info( NULL, pAttacker->m_hAttacker, pAttacker->m_flDamageSum * GetFireDamageScale( pEnt->m_hEnt ), DMG_BURN ); + pEnt->m_hEnt->TakeDamage( info ); + + pAttacker->m_flDamageSum = 0; +} + + +// ------------------------------------------------------------------------------------------------ // +// Global functions. +// ------------------------------------------------------------------------------------------------ // + +bool IsBurnableEnt( CBaseEntity *pEntity, int iIgnoreTeam ) +{ + if ( pEntity->m_takedamage == DAMAGE_NO ) + return false; + + CGasolineBlob *pBlob = dynamic_cast< CGasolineBlob* >( pEntity ); + if ( pBlob ) + { + return !pBlob->IsLit(); + } + + if ( pEntity->GetTeamNumber() == iIgnoreTeam && !fire_damageall.GetInt() ) + { + // Don't damage anyone on the pyro's team (including the pyro himself). + return false; + } + + // Now only allow specific types of objects to be damaged. + if ( dynamic_cast< CBasePlayer* >( pEntity ) || + dynamic_cast< CAI_BaseNPC* >( pEntity ) || + dynamic_cast< CBaseObject* >( pEntity ) ) + { + return true; + } + + return false; +} + + +int FindBurnableEntsInSphere( + CBaseEntity **ents, + float *dists, + int nMaxEnts, + const Vector &vecCenter, + float flSearchRadius, + CBaseEntity *pOwner ) +{ + Assert( nMaxEnts > 0 ); + int nOutEnts = 0; + + CBaseEntity *pEntity; + for ( CEntitySphereQuery sphere( vecCenter, flSearchRadius ); ( pEntity = sphere.GetCurrentEntity() ) != NULL; sphere.NextEntity() ) + { + if ( !IsBurnableEnt( pEntity, pOwner->GetTeamNumber() ) ) + continue; + + // Make sure it's not blocked. + trace_t tr; + Vector vCenter = pEntity->WorldSpaceCenter(); + + UTIL_TraceLine ( vecCenter, vCenter, MASK_SHOT & (~CONTENTS_HITBOX), NULL, COLLISION_GROUP_NONE, &tr ); + if ( tr.fraction != 1.0 && tr.m_pEnt != pEntity ) + continue; + + if ( TFGameRules()->IsTraceBlockedByWorldOrShield( vecCenter, vCenter, pOwner, DMG_BURN | DMG_PROBE, &tr ) ) + continue; + + // Make sure it's in range. + const Vector &mins = pEntity->WorldAlignMins(); + const Vector &maxs = pEntity->WorldAlignMaxs(); + float approxTargetRadius = ( Vector( maxs.x, maxs.y, 0 ) - Vector( mins.x, mins.y, 0 )).Length() * 0.5f; + + float flDistFromCenter = ( vecCenter - tr.endpos ).Length() - approxTargetRadius; + + ents[nOutEnts] = pEntity; + dists[nOutEnts] = flDistFromCenter; + nOutEnts++; + if ( nOutEnts >= nMaxEnts ) + return nOutEnts; + } + + return nOutEnts; +} + + +CFireDamageMgr g_FireDamageMgr; + +CFireDamageMgr* GetFireDamageMgr() +{ + return &g_FireDamageMgr; +} + diff --git a/game/server/tf2/fire_damage_mgr.h b/game/server/tf2/fire_damage_mgr.h new file mode 100644 index 0000000..0f0df10 --- /dev/null +++ b/game/server/tf2/fire_damage_mgr.h @@ -0,0 +1,121 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef FIRE_DAMAGE_MGR_H +#define FIRE_DAMAGE_MGR_H +#ifdef _WIN32 +#pragma once +#endif + + +#include "igamesystem.h" +#include "utllinkedlist.h" +#include "ehandle.h" + + +class CEntityBurnEffect; + + +// ------------------------------------------------------------------------------------------ // +// CFireDamageMgr. +// +// This class manages fire damage being applied to entities. It uses velocity, acceleration, +// and decay to model fire damage building up, and it puts a cap on the maximum amount of damage +// an entity can take from fire during a given frame. +// ------------------------------------------------------------------------------------------ // + +class CFireDamageMgr : public CAutoGameSystem +{ +// Overrides. +public: + + virtual bool Init(); + virtual void FrameUpdatePostEntityThink(); + + +public: + // Apply fire damage to an entity. flDamageAccel is in units per second, so in the absence of + // decay, it equals velocity increase per second. + // + // NOTE: the damage acceleration should always be greater than the decay per second, or no damage + // will be applied since it will decay faster than + void AddDamage( CBaseEntity *pTarget, CBaseEntity *pAttacker, float flDamageAccel, bool bMakeBurnEffect ); + + +private: + class CDamageAttacker + { + public: + void Init( CBaseEntity *pAttacker, float flVelocity ) + { + m_hAttacker = pAttacker; + m_flVelocity = flVelocity; + m_flDamageSum = 0; + } + + EHANDLE m_hAttacker; + float m_flVelocity; // Current damage velocity. + + float m_flDamageSum; // Damage is summed up and applied a couple times per second instead of + // each frame since fractional damage is rounded to 1. + }; + + class CDamageEnt + { + public: + enum + { + MAX_ATTACKERS = 4 + }; + + bool m_bWasAlive; + + EHANDLE m_hEnt; + CHandle<CEntityBurnEffect> m_pBurnEffect; + + // Each attacker gets credit for a portion of + CDamageAttacker m_Attackers[MAX_ATTACKERS]; + int m_nAttackers; + }; + + +private: + + void ApplyCollectedDamage( CFireDamageMgr::CDamageEnt *pEnt, int iAttacker ); + void RemoveDamageEnt( int iEnt ); + + +private: + + CUtlLinkedList<CDamageEnt,int> m_DamageEnts; + + float m_flMaxDamagePerSecond; + float m_flDecayConstant; + + // This counts down to zero so we only apply fire damage every so often. + float m_flApplyDamageCountdown; +}; + + +// Returns true if the entity is burnable by the specified team. +bool IsBurnableEnt( CBaseEntity *pEntity, int iTeam ); + + +// This is used by the flamethrower and the burning gasoline blobs to find entities to burn. +int FindBurnableEntsInSphere( + CBaseEntity **ents, + float *dists, + int nMaxEnts, + const Vector &vecCenter, + float flSearchRadius, + CBaseEntity *pOwner ); + + +CFireDamageMgr* GetFireDamageMgr(); + + +#endif // FIRE_DAMAGE_MGR_H diff --git a/game/server/tf2/gasoline_blob.cpp b/game/server/tf2/gasoline_blob.cpp new file mode 100644 index 0000000..e9c4a54 --- /dev/null +++ b/game/server/tf2/gasoline_blob.cpp @@ -0,0 +1,279 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" +#include "gasoline_blob.h" +#include "gasoline_shared.h" +#include "utllinkedlist.h" +#include "fire_damage_mgr.h" +#include "tf_gamerules.h" + + +// Flamethrower blobs wait a bit before they cause damage so they don't hurt the guy +// shooting them. +#define BLOB_DAMAGE_WAIT_TIME 1.0 + + +// At what heat level does an unlit blob ignite? +#define IGNITION_HEAT 0.1 + + +#define FIRE_DAMAGE_SEARCH_DISTANCE 200 // It searches within this sphere for entities to damage. +#define FIRE_DAMAGE_DISTANCE 90 // This is how far fire can damage an entity from. + + +ConVar fire_enable( "fire_enable", "1", 0, "Enable or disable fire." ); + + +// ------------------------------------------------------------------------------------------ // +// CGasolineBlob implementation. +// ------------------------------------------------------------------------------------------ // + +IMPLEMENT_SERVERCLASS_ST_NOBASE( CGasolineBlob, DT_GasolineBlob ) + SendPropVector( SENDINFO(m_vecOrigin), -1, SPROP_COORD ), + SendPropEHandle (SENDINFO_NAME(m_hMoveParent, moveparent)), + SendPropFloat( SENDINFO(m_flLitStartTime), -1, SPROP_NOSCALE ), + + SendPropFloat( SENDINFO(m_flCreateTime), -1, SPROP_NOSCALE ), + SendPropFloat( SENDINFO(m_flMaxLifetime), -1, SPROP_NOSCALE ), + + SendPropInt( SENDINFO(m_iTeamNum), TEAMNUM_NUM_BITS, 0 ), + SendPropInt( SENDINFO( m_BlobFlags ), NUM_BLOB_FLAGS, SPROP_UNSIGNED ), + SendPropVector( SENDINFO( m_vSurfaceNormal ), 0, SPROP_NORMAL ), +END_SEND_TABLE() + + +LINK_ENTITY_TO_CLASS( gasoline_blob, CGasolineBlob ); + + +CUtlLinkedList<CGasolineBlob*, int> g_GasolineBlobs; + + +CGasolineBlob* CGasolineBlob::Create( + CBaseEntity *pOwner, + const Vector &vOrigin, + const Vector &vStartVelocity, + bool bUseGravity, + float flAirLifetime, + float flLifetime ) +{ + CGasolineBlob *pBlob = (CGasolineBlob*)CreateEntityByName( "gasoline_blob" ); + if ( !pBlob ) + return NULL; + + // The "constructor". + pBlob->SetLocalOrigin( vOrigin ); + pBlob->SetAbsVelocity( vStartVelocity ); + pBlob->SetThink( &CGasolineBlob::Think ); + pBlob->SetNextThink( gpGlobals->curtime ); + pBlob->SetCollisionBounds( Vector( -GASOLINE_BLOB_RADIUS, -GASOLINE_BLOB_RADIUS, -GASOLINE_BLOB_RADIUS ), Vector( GASOLINE_BLOB_RADIUS, GASOLINE_BLOB_RADIUS, GASOLINE_BLOB_RADIUS ) ); + pBlob->SetMoveType( MOVETYPE_NONE ); + pBlob->SetSolid( SOLID_BBOX ); + pBlob->AddSolidFlags( FSOLID_NOT_SOLID ); + pBlob->AddEFlags( EFL_FORCE_CHECK_TRANSMIT ); + pBlob->m_BlobFlags = 0; + pBlob->m_HeatLevel = 0; + pBlob->m_hOwner = pOwner; + pBlob->m_flCreateTime = gpGlobals->curtime; + pBlob->m_flMaxLifetime = flLifetime; + pBlob->m_takedamage = DAMAGE_YES; + pBlob->m_flLitStartTime = 0; + pBlob->ChangeTeam( pOwner->GetTeamNumber() ); + + if ( bUseGravity ) + pBlob->m_BlobFlags |= BLOBFLAG_USE_GRAVITY; + + pBlob->m_flAirLifetime = flAirLifetime; + pBlob->m_flTimeInAir = 0; + + pBlob->SetNextThink( gpGlobals->curtime ); + g_GasolineBlobs.AddToTail( pBlob ); + + return pBlob; +} + + +CGasolineBlob::~CGasolineBlob() +{ + g_GasolineBlobs.Remove( g_GasolineBlobs.Find( this ) ); +} + + +void CGasolineBlob::AddAutoBurnBlob( CGasolineBlob *pBlob ) +{ + int index = m_AutoBurnBlobs.AddToTail(); + m_AutoBurnBlobs[index] = pBlob; +} + + +int CGasolineBlob::OnTakeDamage( const CTakeDamageInfo &info ) +{ + m_HeatLevel += info.GetDamage(); + if ( m_HeatLevel >= IGNITION_HEAT ) + SetLit( true ); + + return 0; +} + + +void CGasolineBlob::SetLit( bool bLit ) +{ + if ( bLit != IsLit() ) + { + if ( bLit ) + { + m_BlobFlags |= BLOBFLAG_LIT; + m_flLitStartTime = gpGlobals->curtime; + } + else + { + m_BlobFlags &= ~BLOBFLAG_LIT; + } + } +} + + +bool CGasolineBlob::IsLit() const +{ + return (m_BlobFlags & BLOBFLAG_LIT) != 0; +} + + +bool CGasolineBlob::IsStopped() const +{ + return (m_BlobFlags & BLOBFLAG_STOPPED) != 0; +} + + +void CGasolineBlob::AutoBurn_R( CGasolineBlob *pParent ) +{ + SetLit( true ); + + for ( int i=0; i < m_AutoBurnBlobs.Count(); i++ ) + { + CGasolineBlob *pTestBlob = m_AutoBurnBlobs[i]; + + if ( pTestBlob ) + { + if ( pTestBlob != pParent ) + pTestBlob->AutoBurn_R( this ); + } + else + { + m_AutoBurnBlobs.Remove( i ); + --i; + } + } +} + + +void CGasolineBlob::Think() +{ + if ( !fire_enable.GetInt() ) + { + UTIL_Remove( this ); + return; + } + + // Decay quickly while in the air. + if ( !IsStopped() ) + { + m_flTimeInAir += gpGlobals->frametime; + if ( m_flTimeInAir >= m_flAirLifetime ) + { + UTIL_Remove( this ); + return; + } + } + + float flLifetime = gpGlobals->curtime - m_flCreateTime; + if ( flLifetime >= m_flMaxLifetime ) + { + UTIL_Remove( this ); + return; + } + + if ( IsLit() ) + { + // Have we burnt out? + float litPercent = 1 - (flLifetime / m_flMaxLifetime); + if ( litPercent <= 0 ) + { + UTIL_Remove( this ); + return; + } + + // Look for nearby entities to burn. + CBaseEntity *ents[512]; + float dists[512]; + int nEnts = FindBurnableEntsInSphere( ents, dists, ARRAYSIZE( ents ), GetAbsOrigin(), FIRE_DAMAGE_SEARCH_DISTANCE, m_hOwner ); + + for ( int i=0; i < nEnts; i++ ) + { + float flDistFromBorder = MAX( 0, FIRE_DAMAGE_DISTANCE - dists[i] ); + if ( flDistFromBorder <= 0 ) + continue; + + float flDamage = litPercent * flDistFromBorder / FIRE_DAMAGE_DISTANCE * FIRE_DAMAGE_PER_SEC; + GetFireDamageMgr()->AddDamage( ents[i], m_hOwner, flDamage, !IsGasolineBlob( ents[i] ) ); + } + + // Ignite our "auto burn" blobs. + AutoBurn_R( NULL ); + } + + // Figure out where we want to go. + if ( !IsStopped() ) + { + // Apply gravity. + Vector vecNewVelocity = GetAbsVelocity(); + if ( m_BlobFlags & BLOBFLAG_USE_GRAVITY ) + { + vecNewVelocity.z -= 800 * gpGlobals->frametime; + SetAbsVelocity( vecNewVelocity ); + } + + Vector vNewPos = GetAbsOrigin() + vecNewVelocity * gpGlobals->frametime; + + // Can we go there? + trace_t trace; + UTIL_TraceLine( GetAbsOrigin(), vNewPos, CONTENTS_SOLID, NULL, COLLISION_GROUP_NONE, &trace ); + bool bStopped = (trace.fraction != 1); + + if ( !bStopped ) + { + // Trace against shields. + if ( TFGameRules()->IsTraceBlockedByWorldOrShield( GetAbsOrigin(), vNewPos, m_hOwner, DMG_BURN, &trace ) ) + { + // Blobs just fizzle out when they hit a shield. + UTIL_Remove( this ); + } + } + + if( bStopped ) + { + SetLocalOrigin( trace.endpos + trace.plane.normal * 2 ); + + // Ok, we hit something. Stop moving. + m_BlobFlags |= BLOBFLAG_STOPPED; + m_vSurfaceNormal = trace.plane.normal; + } + else + { + SetLocalOrigin( vNewPos ); + } + } + + SetNextThink( gpGlobals->curtime ); +} + + +bool IsGasolineBlob( CBaseEntity *pEnt ) +{ + return FClassnameIs( pEnt, "gasoline_blob" ); +} + diff --git a/game/server/tf2/gasoline_blob.h b/game/server/tf2/gasoline_blob.h new file mode 100644 index 0000000..5c3b303 --- /dev/null +++ b/game/server/tf2/gasoline_blob.h @@ -0,0 +1,93 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef GASOLINE_BLOB_H +#define GASOLINE_BLOB_H +#ifdef _WIN32 +#pragma once +#endif + + +#include "baseentity.h" + + +class CGasolineBlob : public CBaseEntity +{ +public: + DECLARE_CLASS( CGasolineBlob, CBaseEntity ); + DECLARE_SERVERCLASS(); + + + // Create a gasoline blob. + // flAirLifetime specifies how long it takes to fizzle out in the air. + static CGasolineBlob* Create( + CBaseEntity *pOwner, + const Vector &vOrigin, + const Vector &vStartVelocity, + bool bUseGravity, + float flAirLifetime, + float flLifetime ); + + virtual ~CGasolineBlob(); + + // A lit blob will always apply at least 25% damage to its "auto burn" blob. + // + // This is used when laying down gasoline blobs in a line. Since it's fairly easy to accidentally + // lay down blobs that won't damage each other because they're too far away, the line of blobs + // can be linked together using this. + void AddAutoBurnBlob( CGasolineBlob *pBlob ); + + +// Overrides. +public: + + virtual int OnTakeDamage( const CTakeDamageInfo &info ); + + + +// Implementation. +public: + + bool IsLit() const; + bool IsStopped() const; + void SetLit( bool bLit ); + + void Think(); + + void AutoBurn_R( CGasolineBlob *pParent ); + + +private: + + typedef CHandle<CGasolineBlob> CGasolineBlobHandle; + CUtlVector<CGasolineBlobHandle> m_AutoBurnBlobs; + + float m_flTimeInAir; // How long we've been in the air. + float m_flAirLifetime; // How long we're allowed to exist in the air. + + CNetworkVar( float, m_flLitStartTime ); // What time did the blob become lit at? + + CNetworkVar( int, m_BlobFlags ); // Combination of BLOBFLAG_ defines. + + // This is set at the start and is used to know the percentage of lifetime left. + CNetworkVar( float, m_flMaxLifetime ); + + // When the blob was created. + CNetworkVar( float, m_flCreateTime ); + + CNetworkVector( m_vSurfaceNormal ); // This is sent to the client so it can spread the fire out. + + EHANDLE m_hOwner; + float m_HeatLevel; // This rises when other flames are nearby until we ignite. +}; + + +// Returns true if the entity is a gasoline blob. +bool IsGasolineBlob( CBaseEntity *pEnt ); + + +#endif // GASOLINE_BLOB_H diff --git a/game/server/tf2/info_act.cpp b/game/server/tf2/info_act.cpp new file mode 100644 index 0000000..2f18cc5 --- /dev/null +++ b/game/server/tf2/info_act.cpp @@ -0,0 +1,587 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "EntityOutput.h" +#include "EntityList.h" +#include "tf_team.h" +#include "tier1/strtools.h" +#include "baseentity.h" +#include "tf_shareddefs.h" +#include "info_act.h" + +// Global pointer to the current act +CHandle<CInfoAct> g_hCurrentAct; + +BEGIN_DATADESC( CInfoAct ) + + // inputs + DEFINE_INPUTFUNC( FIELD_VOID, "Start", InputStart ), + DEFINE_INPUTFUNC( FIELD_VOID, "FinishWinNone", InputFinishWinNone ), + DEFINE_INPUTFUNC( FIELD_VOID, "FinishWin1", InputFinishWin1 ), + DEFINE_INPUTFUNC( FIELD_VOID, "FinishWin2", InputFinishWin2 ), + DEFINE_INPUTFUNC( FIELD_FLOAT, "AddTime", InputAddTime ), + + // outputs + DEFINE_OUTPUT( m_OnStarted, "OnStarted" ), + DEFINE_OUTPUT( m_OnFinishedTeamNone, "OnFinishedWinNone" ), + DEFINE_OUTPUT( m_OnFinishedTeam1, "OnFinishedWin1" ), + DEFINE_OUTPUT( m_OnFinishedTeam2, "OnFinishedWin2" ), + DEFINE_OUTPUT( m_OnTimerExpired, "OnTimerExpired" ), + + DEFINE_OUTPUT( m_Respawn1Team1Events[CInfoAct::RESPAWN_TIMER_90_REMAINING], "OnRespawn1Team1_90sec" ), + DEFINE_OUTPUT( m_Respawn1Team1Events[CInfoAct::RESPAWN_TIMER_60_REMAINING], "OnRespawn1Team1_60sec" ), + DEFINE_OUTPUT( m_Respawn1Team1Events[CInfoAct::RESPAWN_TIMER_45_REMAINING], "OnRespawn1Team1_45sec" ), + DEFINE_OUTPUT( m_Respawn1Team1Events[CInfoAct::RESPAWN_TIMER_30_REMAINING], "OnRespawn1Team1_30sec" ), + DEFINE_OUTPUT( m_Respawn1Team1Events[CInfoAct::RESPAWN_TIMER_10_REMAINING], "OnRespawn1Team1_10sec" ), + DEFINE_OUTPUT( m_Respawn1Team1Events[CInfoAct::RESPAWN_TIMER_0_REMAINING], "OnRespawn1Team1" ), + DEFINE_OUTPUT( m_Respawn1Team1TimeRemaining, "Respawn1Team1TimeRemaining" ), + + DEFINE_OUTPUT( m_Respawn2Team1Events[CInfoAct::RESPAWN_TIMER_90_REMAINING], "OnRespawn2Team1_90sec" ), + DEFINE_OUTPUT( m_Respawn2Team1Events[CInfoAct::RESPAWN_TIMER_60_REMAINING], "OnRespawn2Team1_60sec" ), + DEFINE_OUTPUT( m_Respawn2Team1Events[CInfoAct::RESPAWN_TIMER_45_REMAINING], "OnRespawn2Team1_45sec" ), + DEFINE_OUTPUT( m_Respawn2Team1Events[CInfoAct::RESPAWN_TIMER_30_REMAINING], "OnRespawn2Team1_30sec" ), + DEFINE_OUTPUT( m_Respawn2Team1Events[CInfoAct::RESPAWN_TIMER_10_REMAINING], "OnRespawn2Team1_10sec" ), + DEFINE_OUTPUT( m_Respawn2Team1Events[CInfoAct::RESPAWN_TIMER_0_REMAINING], "OnRespawn2Team1" ), + DEFINE_OUTPUT( m_Respawn2Team1TimeRemaining, "Respawn2Team1TimeRemaining" ), + + DEFINE_OUTPUT( m_Respawn1Team2Events[CInfoAct::RESPAWN_TIMER_90_REMAINING], "OnRespawn1Team2_90sec" ), + DEFINE_OUTPUT( m_Respawn1Team2Events[CInfoAct::RESPAWN_TIMER_60_REMAINING], "OnRespawn1Team2_60sec" ), + DEFINE_OUTPUT( m_Respawn1Team2Events[CInfoAct::RESPAWN_TIMER_45_REMAINING], "OnRespawn1Team2_45sec" ), + DEFINE_OUTPUT( m_Respawn1Team2Events[CInfoAct::RESPAWN_TIMER_30_REMAINING], "OnRespawn1Team2_30sec" ), + DEFINE_OUTPUT( m_Respawn1Team2Events[CInfoAct::RESPAWN_TIMER_10_REMAINING], "OnRespawn1Team2_10sec" ), + DEFINE_OUTPUT( m_Respawn1Team2Events[CInfoAct::RESPAWN_TIMER_0_REMAINING], "OnRespawn1Team2" ), + DEFINE_OUTPUT( m_Respawn1Team2TimeRemaining, "Respawn1Team2TimeRemaining" ), + + DEFINE_OUTPUT( m_Respawn2Team2Events[CInfoAct::RESPAWN_TIMER_90_REMAINING], "OnRespawn2Team2_90sec" ), + DEFINE_OUTPUT( m_Respawn2Team2Events[CInfoAct::RESPAWN_TIMER_60_REMAINING], "OnRespawn2Team2_60sec" ), + DEFINE_OUTPUT( m_Respawn2Team2Events[CInfoAct::RESPAWN_TIMER_45_REMAINING], "OnRespawn2Team2_45sec" ), + DEFINE_OUTPUT( m_Respawn2Team2Events[CInfoAct::RESPAWN_TIMER_30_REMAINING], "OnRespawn2Team2_30sec" ), + DEFINE_OUTPUT( m_Respawn2Team2Events[CInfoAct::RESPAWN_TIMER_10_REMAINING], "OnRespawn2Team2_10sec" ), + DEFINE_OUTPUT( m_Respawn2Team2Events[CInfoAct::RESPAWN_TIMER_0_REMAINING], "OnRespawn2Team2" ), + DEFINE_OUTPUT( m_Respawn2Team2TimeRemaining, "Respawn2Team2TimeRemaining" ), + + DEFINE_OUTPUT( m_Team1RespawnDelayDone, "OnTeam1RespawnDelayDone" ), + DEFINE_OUTPUT( m_Team2RespawnDelayDone, "OnTeam2RespawnDelayDone" ), + + // keys + DEFINE_KEYFIELD_NOT_SAVED( m_iActNumber, FIELD_INTEGER, "ActNumber" ), + DEFINE_KEYFIELD_NOT_SAVED( m_flActTimeLimit, FIELD_FLOAT, "ActTimeLimit" ), + DEFINE_KEYFIELD_NOT_SAVED( m_iszIntermissionCamera, FIELD_STRING, "IntermissionCamera" ), + DEFINE_KEYFIELD_NOT_SAVED( m_nRespawn1Team1Time, FIELD_INTEGER, "Respawn1Team1Time" ), + DEFINE_KEYFIELD_NOT_SAVED( m_nRespawn2Team1Time, FIELD_INTEGER, "Respawn2Team1Time" ), + DEFINE_KEYFIELD_NOT_SAVED( m_nRespawn1Team2Time, FIELD_INTEGER, "Respawn1Team2Time" ), + DEFINE_KEYFIELD_NOT_SAVED( m_nRespawn2Team2Time, FIELD_INTEGER, "Respawn2Team2Time" ), + DEFINE_KEYFIELD_NOT_SAVED( m_nRespawnTeam1Delay, FIELD_INTEGER, "RespawnTeam1InitialDelay" ), + DEFINE_KEYFIELD_NOT_SAVED( m_nRespawnTeam2Delay, FIELD_INTEGER, "RespawnTeam2InitialDelay" ), + + // functions + DEFINE_FUNCTION( ActThink ), + DEFINE_FUNCTION( ActThinkEndActOverlayTime ), + DEFINE_FUNCTION( RespawnTimerThink ), + DEFINE_FUNCTION( Team1RespawnDelayThink ), + DEFINE_FUNCTION( Team2RespawnDelayThink ), + +END_DATADESC() + +IMPLEMENT_SERVERCLASS_ST(CInfoAct, DT_InfoAct) + SendPropInt(SENDINFO(m_iActNumber), 5 ), + SendPropInt(SENDINFO(m_spawnflags), SF_ACT_BITS, SPROP_UNSIGNED ), + SendPropFloat(SENDINFO(m_flActTimeLimit), 12 ), + SendPropInt(SENDINFO(m_nRespawn1Team1Time), 8 ), + SendPropInt(SENDINFO(m_nRespawn2Team1Time), 8 ), + SendPropInt(SENDINFO(m_nRespawn1Team2Time), 8 ), + SendPropInt(SENDINFO(m_nRespawn2Team2Time), 8 ), + SendPropInt(SENDINFO(m_nRespawnTeam1Delay), 8 ), + SendPropInt(SENDINFO(m_nRespawnTeam2Delay), 8 ), +END_SEND_TABLE(); + +LINK_ENTITY_TO_CLASS( info_act, CInfoAct ); + + +#define RESPAWN_TIMER_CONTEXT "RespawnTimerThink" +#define RESPAWN_TEAM_1_DELAY_CONTEXT "RespawnTeam1DelayThink" +#define RESPAWN_TEAM_2_DELAY_CONTEXT "RespawnTeam2DelayThink" + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CInfoAct::CInfoAct( void ) +{ + // No act == -1 + m_iActNumber = -1; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int CInfoAct::UpdateTransmitState() +{ + return SetTransmitState( FL_EDICT_ALWAYS ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CInfoAct::Spawn( void ) +{ + m_flActStartedAt = 0; + m_iWinners = 0; +} + + +//----------------------------------------------------------------------------- +// Purpose: Set up respawn timers +//----------------------------------------------------------------------------- +void CInfoAct::SetUpRespawnTimers() +{ + // NOTE: Need to add the second there so the respawn timers don't immediately trigger + SetContextThink( RespawnTimerThink, gpGlobals->curtime + 1.0f, RESPAWN_TIMER_CONTEXT ); + + if (m_nRespawnTeam1Delay != 0) + { + SetContextThink( Team1RespawnDelayThink, gpGlobals->curtime + m_nRespawnTeam1Delay, RESPAWN_TEAM_1_DELAY_CONTEXT ); + } + else + { + m_Team1RespawnDelayDone.FireOutput( this, this ); + } + + if (m_nRespawnTeam2Delay != 0) + { + SetContextThink( Team2RespawnDelayThink, gpGlobals->curtime + m_nRespawnTeam2Delay, RESPAWN_TEAM_2_DELAY_CONTEXT ); + } + else + { + m_Team2RespawnDelayDone.FireOutput( this, this ); + } +} + +void CInfoAct::ShutdownRespawnTimers() +{ + SetContextThink( NULL, 0, RESPAWN_TIMER_CONTEXT ); + SetContextThink( NULL, 0, RESPAWN_TEAM_1_DELAY_CONTEXT ); + SetContextThink( NULL, 0, RESPAWN_TEAM_2_DELAY_CONTEXT ); +} + + +//----------------------------------------------------------------------------- +// Respawn delay +//----------------------------------------------------------------------------- +void CInfoAct::Team1RespawnDelayThink() +{ + m_Team1RespawnDelayDone.FireOutput( this, this ); + SetContextThink( NULL, 0, RESPAWN_TEAM_1_DELAY_CONTEXT ); +} + +void CInfoAct::Team2RespawnDelayThink() +{ + m_Team2RespawnDelayDone.FireOutput( this, this ); + SetContextThink( NULL, 0, RESPAWN_TEAM_2_DELAY_CONTEXT ); +} + + +//----------------------------------------------------------------------------- +// Computes the time remaining +//----------------------------------------------------------------------------- +int CInfoAct::ComputeTimeRemaining( int nPeriod, int nDelay ) +{ + if (nPeriod <= 0) + return -1; + + int nTimeDelta = (int)(gpGlobals->curtime - m_flActStartedAt); + Assert( nTimeDelta >= 0 ); + nTimeDelta -= nDelay; + + // This case takes care of the initial spawn delay time... + if (nTimeDelta <= 0) + { + return nPeriod - nTimeDelta; + } + + int nFactor = nTimeDelta / nPeriod; + int nTimeRemainder = nTimeDelta - nFactor * nPeriod; + if (nTimeRemainder == 0) + return 0; + + return nPeriod - nTimeRemainder; +} + + +//----------------------------------------------------------------------------- +// Fires respawn events +//----------------------------------------------------------------------------- +void CInfoAct::FireRespawnEvents( int nTimeRemaining, COutputEvent *pRespawnEvents, COutputInt &respawnTime ) +{ + if (nTimeRemaining < 0) + return; + + switch (nTimeRemaining) + { + case 90: + pRespawnEvents[RESPAWN_TIMER_90_REMAINING].FireOutput( this, this ); + break; + case 60: + pRespawnEvents[RESPAWN_TIMER_60_REMAINING].FireOutput( this, this ); + break; + case 45: + pRespawnEvents[RESPAWN_TIMER_45_REMAINING].FireOutput( this, this ); + break; + case 30: + pRespawnEvents[RESPAWN_TIMER_30_REMAINING].FireOutput( this, this ); + break; + case 10: + pRespawnEvents[RESPAWN_TIMER_10_REMAINING].FireOutput( this, this ); + break; + case 0: + pRespawnEvents[RESPAWN_TIMER_0_REMAINING].FireOutput( this, this ); + break; + default: + break; + } + + respawnTime.Set( nTimeRemaining, this, this ); +} + + +//----------------------------------------------------------------------------- +// Respawn timers +//----------------------------------------------------------------------------- +void CInfoAct::RespawnTimerThink() +{ + int nTimeRemaining = ComputeTimeRemaining( m_nRespawn1Team1Time, m_nRespawnTeam1Delay ); + FireRespawnEvents( nTimeRemaining, m_Respawn1Team1Events, m_Respawn1Team1TimeRemaining ); + + nTimeRemaining = ComputeTimeRemaining( m_nRespawn2Team1Time, m_nRespawnTeam1Delay ); + FireRespawnEvents( nTimeRemaining, m_Respawn2Team1Events, m_Respawn2Team1TimeRemaining ); + + nTimeRemaining = ComputeTimeRemaining( m_nRespawn1Team2Time, m_nRespawnTeam2Delay ); + FireRespawnEvents( nTimeRemaining, m_Respawn1Team2Events, m_Respawn1Team2TimeRemaining ); + + nTimeRemaining = ComputeTimeRemaining( m_nRespawn2Team2Time, m_nRespawnTeam2Delay ); + FireRespawnEvents( nTimeRemaining, m_Respawn2Team2Events, m_Respawn2Team2TimeRemaining ); + + SetNextThink( gpGlobals->curtime + 1.0f, RESPAWN_TIMER_CONTEXT ); +} + + +//----------------------------------------------------------------------------- +// Purpose: The act has started +//----------------------------------------------------------------------------- +void CInfoAct::StartAct( void ) +{ + // FIXME: Should this change? + // Don't allow two simultaneous acts + if (g_hCurrentAct) + { + g_hCurrentAct->FinishAct( ); + } + + // Set the global act to this + g_hCurrentAct = this; + + m_flActStartedAt = gpGlobals->curtime; + m_OnStarted.FireOutput( this, this ); + + // Do we have a timelimit? + if ( m_flActTimeLimit ) + { + SetNextThink( gpGlobals->curtime + m_flActTimeLimit ); + SetThink( ActThink ); + } + + SetUpRespawnTimers(); + + // Tell all the clients + CReliableBroadcastRecipientFilter filter; + + UserMessageBegin( filter, "ActBegin" ); + WRITE_BYTE( (byte)m_iActNumber ); + WRITE_FLOAT( m_flActStartedAt ); + MessageEnd(); + + // If we're not an intermission, clean up + if ( !HasSpawnFlags( SF_ACT_INTERMISSION ) ) + { + CleanupOnActStart(); + } + + // Cycle through all players and start the act + for ( int i = 1; i <= gpGlobals->maxClients; i++ ) + { + CBaseTFPlayer *pPlayer = (CBaseTFPlayer*)UTIL_PlayerByIndex( i ); + if ( pPlayer ) + { + // Am I an intermission? + if ( HasSpawnFlags( SF_ACT_INTERMISSION ) ) + { + StartIntermission( pPlayer ); + } + else + { + StartActOverlayTime( pPlayer ); + } + } + } + + // Think again soon, to remove player locks + if ( !HasSpawnFlags(SF_ACT_INTERMISSION) ) + { + SetNextThink( gpGlobals->curtime + MIN_ACT_OVERLAY_TIME ); + SetThink( ActThinkEndActOverlayTime ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Update a client who joined during the middle of an act +//----------------------------------------------------------------------------- +void CInfoAct::UpdateClient( CBaseTFPlayer *pPlayer ) +{ + CSingleUserRecipientFilter user( pPlayer ); + user.MakeReliable(); + + UserMessageBegin( user, "ActBegin" ); + WRITE_BYTE( (byte)m_iActNumber ); + WRITE_FLOAT( m_flActStartedAt ); + MessageEnd(); +} + +//----------------------------------------------------------------------------- +// Purpose: The act has finished +//----------------------------------------------------------------------------- +void CInfoAct::FinishAct( ) +{ + if ( g_hCurrentAct.Get() != this ) + { + DevWarning( 2, "Attempted to finish an act which wasn't started!\n" ); + return; + } + + ShutdownRespawnTimers(); + + switch( m_iWinners) + { + case 0: + m_OnFinishedTeamNone.FireOutput( this, this ); + break; + + case 1: + m_OnFinishedTeam1.FireOutput( this, this ); + break; + + case 2: + m_OnFinishedTeam2.FireOutput( this, this ); + break; + + default: + Assert(0); + break; + } + + g_hCurrentAct = NULL; + + // Tell all the clients + CReliableBroadcastRecipientFilter filter; + UserMessageBegin( filter, "ActEnd" ); + WRITE_BYTE( m_iWinners ); + MessageEnd(); + + // Am I an intermission? + if ( HasSpawnFlags( SF_ACT_INTERMISSION ) ) + { + // Cycle through all players and end the intermission for them + for ( int i = 1; i <= gpGlobals->maxClients; i++ ) + { + CBaseTFPlayer *pPlayer = (CBaseTFPlayer*)UTIL_PlayerByIndex( i ); + if ( pPlayer ) + { + EndIntermission( pPlayer ); + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CInfoAct::ActThink( void ) +{ + m_OnTimerExpired.FireOutput( this,this ); +} + +//----------------------------------------------------------------------------- +// Purpose: Force the players not to move to give them time to read the act overlays +//----------------------------------------------------------------------------- +void CInfoAct::StartActOverlayTime( CBaseTFPlayer *pPlayer ) +{ + // Lock the player in place + pPlayer->CleanupOnActStart(); + pPlayer->LockPlayerInPlace(); + + if ( pPlayer->GetActiveWeapon() ) + { + pPlayer->GetActiveWeapon()->Holster(); + } + + pPlayer->m_Local.m_iHideHUD |= (HIDEHUD_WEAPONSELECTION | HIDEHUD_HEALTH); + pPlayer->GetLocalData()->m_bForceMapOverview = true; +} + +//----------------------------------------------------------------------------- +// Purpose: Release the players after overlay time has finished +//----------------------------------------------------------------------------- +void CInfoAct::EndActOverlayTime( CBaseTFPlayer *pPlayer ) +{ + // Release the player + pPlayer->UnlockPlayer(); + + if ( pPlayer->GetActiveWeapon() ) + { + pPlayer->GetActiveWeapon()->Deploy(); + } + + pPlayer->m_Local.m_iHideHUD &= ~(HIDEHUD_WEAPONSELECTION | HIDEHUD_HEALTH); + pPlayer->GetLocalData()->m_bForceMapOverview = false; +} + +//----------------------------------------------------------------------------- +// Purpose: Unlock the players after an act has started +//----------------------------------------------------------------------------- +void CInfoAct::ActThinkEndActOverlayTime( void ) +{ + // Cycle through all players and end the intermission for them + for ( int i = 1; i <= gpGlobals->maxClients; i++ ) + { + CBaseTFPlayer *pPlayer = (CBaseTFPlayer*)UTIL_PlayerByIndex( i ); + if ( pPlayer ) + { + EndActOverlayTime( pPlayer ); + } + } + + // Think again when the act ends, if we have a timelimit + if ( m_flActTimeLimit ) + { + SetNextThink( gpGlobals->curtime + m_flActTimeLimit - MIN_ACT_OVERLAY_TIME ); + SetThink( ActThink ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Clean up entities before a new act starts +//----------------------------------------------------------------------------- +void CInfoAct::CleanupOnActStart( void ) +{ + // Remove all resource chunks + CBaseEntity *pEntity = NULL; + while ((pEntity = gEntList.FindEntityByClassname( pEntity, "resource_chunk" )) != NULL) + { + UTIL_Remove( pEntity ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Intermission handling +//----------------------------------------------------------------------------- +void CInfoAct::StartIntermission( CBaseTFPlayer *pPlayer ) +{ + // Do we have a camera point? + if ( m_iszIntermissionCamera != NULL_STRING ) + { + CBaseEntity *pCamera = gEntList.FindEntityByName( NULL, STRING(m_iszIntermissionCamera) ); + if ( pCamera ) + { + // Move the player to the camera point + pPlayer->SetViewEntity( pCamera ); + pPlayer->m_Local.m_iHideHUD |= (HIDEHUD_WEAPONSELECTION | HIDEHUD_HEALTH | HIDEHUD_MISCSTATUS); + } + } + + // Lock the player in place + pPlayer->LockPlayerInPlace(); +} + +//----------------------------------------------------------------------------- +// Purpose: Intermission handling +//----------------------------------------------------------------------------- +void CInfoAct::EndIntermission( CBaseTFPlayer *pPlayer ) +{ + // Force the player to respawn + pPlayer->UnlockPlayer(); + pPlayer->SetViewEntity( pPlayer ); + pPlayer->ForceRespawn(); + pPlayer->m_Local.m_iHideHUD &= ~(HIDEHUD_WEAPONSELECTION | HIDEHUD_HEALTH | HIDEHUD_MISCSTATUS); +} + +//----------------------------------------------------------------------------- +// Purpose: Force the act to start +//----------------------------------------------------------------------------- +void CInfoAct::InputStart( inputdata_t &inputdata ) +{ + StartAct(); +} + +//----------------------------------------------------------------------------- +// Purpose: Force the act to finish, with team 1 as the winners +//----------------------------------------------------------------------------- +void CInfoAct::InputFinishWinNone( inputdata_t &inputdata ) +{ + m_iWinners = 0; + FinishAct(); +} + +//----------------------------------------------------------------------------- +// Purpose: Force the act to finish, with team 1 as the winners +//----------------------------------------------------------------------------- +void CInfoAct::InputFinishWin1( inputdata_t &inputdata ) +{ + m_iWinners = 1; + FinishAct(); +} + +//----------------------------------------------------------------------------- +// Purpose: Force the act to finish, with team 2 as the winners +//----------------------------------------------------------------------------- +void CInfoAct::InputFinishWin2( inputdata_t &inputdata ) +{ + m_iWinners = 2; + FinishAct(); +} + +//----------------------------------------------------------------------------- +// Purpose: Add time to the act's time +//----------------------------------------------------------------------------- +void CInfoAct::InputAddTime( inputdata_t &inputdata ) +{ + float flNewTime = inputdata.value.Float(); + + // Think again when the act ends, if we have a timelimit + if ( flNewTime ) + { + m_flActTimeLimit += flNewTime; + SetNextThink( GetNextThink() + flNewTime ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CInfoAct::IsAWaitingAct( void ) +{ + return HasSpawnFlags(SF_ACT_WAITINGFORGAMESTART); +} + +//----------------------------------------------------------------------------- +// Purpose: Return true if the current act (if any) is a waiting act. +//----------------------------------------------------------------------------- +bool CurrentActIsAWaitingAct( void ) +{ + if ( g_hCurrentAct ) + return g_hCurrentAct->IsAWaitingAct(); + + return false; +}
\ No newline at end of file diff --git a/game/server/tf2/info_act.h b/game/server/tf2/info_act.h new file mode 100644 index 0000000..f946316 --- /dev/null +++ b/game/server/tf2/info_act.h @@ -0,0 +1,138 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef INFO_ACT_H +#define INFO_ACT_H +#ifdef _WIN32 +#pragma once +#endif + +class CBaseTFPlayer; + +//----------------------------------------------------------------------------- +// Purpose: Map entity that defines an act +//----------------------------------------------------------------------------- +class CInfoAct : public CBaseEntity +{ + DECLARE_CLASS( CInfoAct, CBaseEntity ); +public: + CInfoAct(); + + DECLARE_DATADESC(); + DECLARE_SERVERCLASS(); + + int UpdateTransmitState(); + + void Spawn( void ); + void StartAct( void ); + void UpdateClient( CBaseTFPlayer *pPlayer ); + void FinishAct( void ); + void ActThink( void ); + int ActNumber() const; + + void CleanupOnActStart( void ); + + bool IsAWaitingAct( void ); + + // Act player locking + void StartActOverlayTime( CBaseTFPlayer *pPlayer ); + void EndActOverlayTime( CBaseTFPlayer *pPlayer ); + void ActThinkEndActOverlayTime( void ); + + // Intermissions + void StartIntermission( CBaseTFPlayer *pPlayer ); + void EndIntermission( CBaseTFPlayer *pPlayer ); + + int GetMCVTimer( void ) { return m_nRespawn2Team2Time; } + +private: + enum + { + RESPAWN_TIMER_90_REMAINING = 0, + RESPAWN_TIMER_60_REMAINING, + RESPAWN_TIMER_45_REMAINING, + RESPAWN_TIMER_30_REMAINING, + RESPAWN_TIMER_10_REMAINING, + RESPAWN_TIMER_0_REMAINING, + + RESPAWN_TIMER_EVENT_COUNT + }; + + void SetUpRespawnTimers(); + void ShutdownRespawnTimers(); + + // Inputs + void InputStart( inputdata_t &inputdata ); + void InputFinishWinNone( inputdata_t &inputdata ); + void InputFinishWin1( inputdata_t &inputdata ); + void InputFinishWin2( inputdata_t &inputdata ); + void InputAddTime( inputdata_t &inputdata ); + + // Respawn timers + void RespawnTimerThink(); + + // Respawn delay + void Team1RespawnDelayThink(); + void Team2RespawnDelayThink(); + + // Computes the time remaining + int ComputeTimeRemaining( int nPeriod, int nDelay ); + + // Fires respawn events + void FireRespawnEvents( int nTimeRemaining, COutputEvent *pRespawnEvents, COutputInt &respawnTime ); + + // Outputs + COutputEvent m_OnStarted; + COutputEvent m_OnFinishedTeamNone; + COutputEvent m_OnFinishedTeam1; + COutputEvent m_OnFinishedTeam2; + COutputEvent m_OnTimerExpired; + + COutputEvent m_Team1RespawnDelayDone; + COutputEvent m_Team2RespawnDelayDone; + + COutputInt m_Respawn1Team1TimeRemaining; + COutputInt m_Respawn2Team1TimeRemaining; + COutputInt m_Respawn1Team2TimeRemaining; + COutputInt m_Respawn2Team2TimeRemaining; + + // A whole buncha respawn timer events + COutputEvent m_Respawn1Team1Events[RESPAWN_TIMER_EVENT_COUNT]; + COutputEvent m_Respawn2Team1Events[RESPAWN_TIMER_EVENT_COUNT]; + COutputEvent m_Respawn1Team2Events[RESPAWN_TIMER_EVENT_COUNT]; + COutputEvent m_Respawn2Team2Events[RESPAWN_TIMER_EVENT_COUNT]; + + // Respawn timer periods + CNetworkVar( int, m_nRespawn1Team1Time ); + CNetworkVar( int, m_nRespawn1Team2Time ); + CNetworkVar( int, m_nRespawn2Team1Time ); + CNetworkVar( int, m_nRespawn2Team2Time ); + CNetworkVar( int, m_nRespawnTeam1Delay ); + CNetworkVar( int, m_nRespawnTeam2Delay ); + + // Data + CNetworkVar( int, m_iActNumber ); + CNetworkVar( float, m_flActTimeLimit ); + int m_iWinners; + + // Acts + float m_flActStartedAt; + + // Intermissions + string_t m_iszIntermissionCamera; +}; + +inline int CInfoAct::ActNumber() const +{ + return m_iActNumber; +} + +extern CHandle<CInfoAct> g_hCurrentAct; + +bool CurrentActIsAWaitingAct( void ); + +#endif // INFO_ACT_H diff --git a/game/server/tf2/info_add_resources.cpp b/game/server/tf2/info_add_resources.cpp new file mode 100644 index 0000000..1260bb8 --- /dev/null +++ b/game/server/tf2/info_add_resources.cpp @@ -0,0 +1,72 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "EntityOutput.h" +#include "EntityList.h" +#include "tf_team.h" +#include "baseentity.h" +#include "tf_stats.h" + +//----------------------------------------------------------------------------- +// Purpose: Map entity that gives resources to players passed into it +//----------------------------------------------------------------------------- +class CInfoAddResources : public CBaseEntity +{ + DECLARE_CLASS( CInfoAddResources, CBaseEntity ); +public: + DECLARE_DATADESC(); + + void Spawn( void ); + + // Inputs + void InputPlayer( inputdata_t &inputdata ); + +public: + // Outputs + COutputEvent m_OnAdded; + + // Data + int m_iResourceAmount; +}; + +BEGIN_DATADESC( CInfoAddResources ) + + // inputs + DEFINE_INPUTFUNC( FIELD_EHANDLE, "Player", InputPlayer ), + + // outputs + DEFINE_OUTPUT( m_OnAdded, "OnAdded" ), + + // keys + DEFINE_KEYFIELD_NOT_SAVED( m_iResourceAmount, FIELD_INTEGER, "ResourceAmount" ), + +END_DATADESC() + +LINK_ENTITY_TO_CLASS( info_add_resources, CInfoAddResources ); + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CInfoAddResources::Spawn( void ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CInfoAddResources::InputPlayer( inputdata_t &inputdata ) +{ + CBaseEntity *pEntity = (inputdata.value.Entity()).Get(); + if ( pEntity && pEntity->IsPlayer() ) + { + CBaseTFPlayer *pPlayer = (CBaseTFPlayer *)pEntity; + pPlayer->AddBankResources( m_iResourceAmount ); + TFStats()->IncrementPlayerStat( pPlayer, TF_PLAYER_STAT_RESOURCES_ACQUIRED, m_iResourceAmount ); + } + + m_OnAdded.FireOutput( inputdata.pActivator, this ); +} diff --git a/game/server/tf2/info_buildpoint.cpp b/game/server/tf2/info_buildpoint.cpp new file mode 100644 index 0000000..961b4ff --- /dev/null +++ b/game/server/tf2/info_buildpoint.cpp @@ -0,0 +1,217 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Map entity that allows players to build objects on it +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "EntityOutput.h" +#include "EntityList.h" +#include "tf_team.h" +#include "baseentity.h" +#include "info_buildpoint.h" +#include "tf_gamerules.h" + +// Spawnflags +const int SF_BUILDPOINT_ALLOW_ALL_GUNS = 0x01; // Allow all manned guns to be built on this point +const int SF_BUILDPOINT_ALLOW_VEHICLES = 0x02; // Allow all vehicles to be built on this point + +BEGIN_DATADESC( CInfoBuildPoint ) + + // keys + DEFINE_KEYFIELD_NOT_SAVED( m_iszAllowedObject, FIELD_STRING, "AllowedObject" ), + +END_DATADESC() + +LINK_ENTITY_TO_CLASS( info_buildpoint, CInfoBuildPoint ); + +// List of buildpoints +CUtlVector<CInfoBuildPoint*> g_MapDefinedBuildPoints; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CInfoBuildPoint::Spawn( void ) +{ + g_MapDefinedBuildPoints.AddToTail( this ); + + m_iAllowedObjectType = -1; + if ( m_iszAllowedObject != NULL_STRING ) + { + for ( int i = 0; i < OBJ_LAST; i++ ) + { + if ( !Q_strcmp( STRING(m_iszAllowedObject), GetObjectInfo(i)->m_pClassName ) ) + { + m_iAllowedObjectType = i; + break; + } + } + } + + BaseClass::Spawn(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CInfoBuildPoint::UpdateOnRemove( void ) +{ + g_MapDefinedBuildPoints.FindAndRemove( this ); + BaseClass::UpdateOnRemove(); +} + +//----------------------------------------------------------------------------- +// Purpose: Tell me how many build points you have +//----------------------------------------------------------------------------- +int CInfoBuildPoint::GetNumBuildPoints( void ) const +{ + return 1; +} + +//----------------------------------------------------------------------------- +// Purpose: Give me the origin & angles of the specified build point +//----------------------------------------------------------------------------- +bool CInfoBuildPoint::GetBuildPoint( int iPoint, Vector &vecOrigin, QAngle &vecAngles ) +{ + ASSERT( iPoint <= GetNumBuildPoints() ); + + vecOrigin = GetAbsOrigin(); + vecAngles = GetAbsAngles(); + return true; +} + +int CInfoBuildPoint::GetBuildPointAttachmentIndex( int iPoint ) const +{ + return 0; +} + +//----------------------------------------------------------------------------- +// Purpose: Can I build the specified object on the specified build point? +//----------------------------------------------------------------------------- +bool CInfoBuildPoint::CanBuildObjectOnBuildPoint( int iPoint, int iObjectType ) +{ + ASSERT( iPoint <= GetNumBuildPoints() ); + if ( m_hObjectBuiltOnMe ) + return false; + + // Manned guns? + if ( m_spawnflags & SF_BUILDPOINT_ALLOW_ALL_GUNS ) + { + if ( (iObjectType == OBJ_MANNED_PLASMAGUN) || + (iObjectType == OBJ_MANNED_MISSILELAUNCHER) || + (iObjectType == OBJ_MANNED_SHIELD) || + (iObjectType == OBJ_SENTRYGUN_PLASMA) ) + return true; + } + + // Vehicles + if ( m_spawnflags & SF_BUILDPOINT_ALLOW_VEHICLES ) + { + if ( IsObjectAVehicle(iObjectType) ) + return true; + } + + // Check our unique + if ( m_iAllowedObjectType >= 0 && m_iAllowedObjectType == iObjectType ) + return true; + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: I've finished building the specified object on the specified build point +//----------------------------------------------------------------------------- +void CInfoBuildPoint::SetObjectOnBuildPoint( int iPoint, CBaseObject *pObject ) +{ + ASSERT( iPoint <= GetNumBuildPoints() ); + m_hObjectBuiltOnMe = pObject; +} + +//----------------------------------------------------------------------------- +// Purpose: Get the number of objects build on this entity +//----------------------------------------------------------------------------- +int CInfoBuildPoint::GetNumObjectsOnMe( void ) +{ + if ( m_hObjectBuiltOnMe ) + return 1; + + return 0; +} + +//----------------------------------------------------------------------------- +// Purpose: Get the first object that's built on me +//----------------------------------------------------------------------------- +CBaseEntity *CInfoBuildPoint::GetFirstObjectOnMe( void ) +{ + return m_hObjectBuiltOnMe; +} + +//----------------------------------------------------------------------------- +// Purpose: Get the first object of type, return NULL if no such type available +//----------------------------------------------------------------------------- +CBaseObject *CInfoBuildPoint::GetObjectOfTypeOnMe( int iObjectType ) +{ + if ( m_hObjectBuiltOnMe ) + { + if ( m_hObjectBuiltOnMe->ObjectType() == iObjectType ) + return m_hObjectBuiltOnMe; + } + + return NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: Remove all objects built on me +//----------------------------------------------------------------------------- +void CInfoBuildPoint::RemoveAllObjects( void ) +{ + UTIL_Remove( m_hObjectBuiltOnMe ); +} + +//----------------------------------------------------------------------------- +// Purpose: Return the maximum distance that this entity's build points can be snapped to +//----------------------------------------------------------------------------- +float CInfoBuildPoint::GetMaxSnapDistance( int iPoint ) +{ + return 64; +} + +//----------------------------------------------------------------------------- +// Purpose: Return true if it's possible that build points on this entity may move in local space (i.e. due to animation) +//----------------------------------------------------------------------------- +bool CInfoBuildPoint::ShouldCheckForMovement( void ) +{ + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: I've finished building the specified object on the specified build point +//----------------------------------------------------------------------------- +int CInfoBuildPoint::FindObjectOnBuildPoint( CBaseObject *pObject ) +{ + return 1; +} + +//----------------------------------------------------------------------------- +// Purpose: Returns an exit point for a vehicle built on a build point... +//----------------------------------------------------------------------------- +void CInfoBuildPoint::GetExitPoint( CBaseEntity *pPlayer, int iPoint, Vector *pAbsOrigin, QAngle *pAbsAngles ) +{ + // FIXME: In future, we may well want to use specific exit attachments here... + GetBuildPoint( iPoint, *pAbsOrigin, *pAbsAngles ); + + // Move back along the forward direction a bit... + Vector vecForward; + AngleVectors( *pAbsAngles, &vecForward ); + *pAbsOrigin -= vecForward * 60; + + // Now select a good spot to drop onto + Vector vNewPos; + if ( !EntityPlacementTest(pPlayer, *pAbsOrigin, vNewPos, true) ) + { + Warning("Can't find valid place to exit object.\n"); + return; + } + + *pAbsOrigin = vNewPos; +} diff --git a/game/server/tf2/info_buildpoint.h b/game/server/tf2/info_buildpoint.h new file mode 100644 index 0000000..294eee4 --- /dev/null +++ b/game/server/tf2/info_buildpoint.h @@ -0,0 +1,76 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Map entity that allows players to build objects on it +// +//=============================================================================// + +#ifndef INFO_BUILDPOINT_H +#define INFO_BUILDPOINT_H +#ifdef _WIN32 +#pragma once +#endif + +#include "ihasbuildpoints.h" + +//----------------------------------------------------------------------------- +// Purpose: Map entity that allows players to build objects on it +//----------------------------------------------------------------------------- +class CInfoBuildPoint : public CBaseEntity, public IHasBuildPoints +{ + DECLARE_CLASS( CInfoBuildPoint, CBaseEntity ); +public: + DECLARE_DATADESC(); + + void Spawn( void ); + void UpdateOnRemove( void ); + +// IHasBuildPoints +public: + // Tell me how many build points you have + virtual int GetNumBuildPoints( void ) const; + + // Give me the origin & angles of the specified build point + virtual bool GetBuildPoint( int iPoint, Vector &vecOrigin, QAngle &vecAngles ); + + virtual int GetBuildPointAttachmentIndex( int iPoint ) const; + + // Can I build the specified object on the specified build point? + virtual bool CanBuildObjectOnBuildPoint( int iPoint, int iObjectType ); + + // I've finished building the specified object on the specified build point + virtual void SetObjectOnBuildPoint( int iPoint, CBaseObject *pObject ); + + // Get the number of objects build on this entity + virtual int GetNumObjectsOnMe( void ); + + // Get the first object that's built on me + virtual CBaseEntity *GetFirstObjectOnMe( void ); + + // Get the first object of type, return NULL if no such type available + virtual CBaseObject *GetObjectOfTypeOnMe( int iObjectType ); + + // Remove all objects built on me + virtual void RemoveAllObjects( void ); + + // Return the maximum distance that this entity's build points can be snapped to + virtual float GetMaxSnapDistance( int iPoint ); + + // Return true if it's possible that build points on this entity may move in local space (i.e. due to animation) + virtual bool ShouldCheckForMovement( void ); + + // I've finished building the specified object on the specified build point + virtual int FindObjectOnBuildPoint( CBaseObject *pObject ); + + // Returns an exit point for a vehicle built on a build point... + virtual void GetExitPoint( CBaseEntity *pPlayer, int iPoint, Vector *pAbsOrigin, QAngle *pAbsAngles ); + + +private: + string_t m_iszAllowedObject; + int m_iAllowedObjectType; + CHandle<CBaseObject> m_hObjectBuiltOnMe; +}; + +extern CUtlVector<CInfoBuildPoint*> g_MapDefinedBuildPoints; + +#endif // INFO_BUILDPOINT_H diff --git a/game/server/tf2/info_customtech.cpp b/game/server/tf2/info_customtech.cpp new file mode 100644 index 0000000..d2a2e5a --- /dev/null +++ b/game/server/tf2/info_customtech.cpp @@ -0,0 +1,107 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Map entity that adds a custom technology to the techtree +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "EntityOutput.h" +#include "EntityList.h" +#include "tf_team.h" +#include "techtree.h" +#include "info_customtech.h" +#include "tier1/strtools.h" + +BEGIN_DATADESC( CInfoCustomTechnology ) + + // outputs + DEFINE_OUTPUT( m_flTechPercentage, "TechPercentage" ), + + // keys + DEFINE_KEYFIELD_NOT_SAVED( m_iszTech , FIELD_STRING, "TechToWatch" ), + DEFINE_KEYFIELD_NOT_SAVED( m_iszTechTreeFile , FIELD_STRING, "NewTechFile" ), + +END_DATADESC() + +IMPLEMENT_SERVERCLASS_ST( CInfoCustomTechnology, DT_InfoCustomTechnology ) + SendPropString( SENDINFO( m_szTechTreeFile ) ), +END_SEND_TABLE(); + +LINK_ENTITY_TO_CLASS( info_customtech, CInfoCustomTechnology ); + +//----------------------------------------------------------------------------- +// Purpose: Always transmit +//----------------------------------------------------------------------------- +int CInfoCustomTechnology::ShouldTransmit( const CCheckTransmitInfo *pInfo ) +{ + // Don't need to transmit ones that don't add new techs + if ( !m_iszTechTreeFile ) + return FL_EDICT_DONTSEND; + + // Only transmit to members of my team + CBaseEntity* pRecipientEntity = CBaseEntity::Instance( pInfo->m_pClientEnt ); + if ( InSameTeam( pRecipientEntity ) ) + { + //Msg( "SENDING\n" ); + return FL_EDICT_ALWAYS; + } + + return FL_EDICT_DONTSEND; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CInfoCustomTechnology::Spawn( void ) +{ + m_flTechPercentage.Set( 0, this, this ); + memset( m_szTechTreeFile.GetForModify(), 0, sizeof(m_szTechTreeFile) ); +} + +//----------------------------------------------------------------------------- +// Purpose: Add myself to the technology tree +//----------------------------------------------------------------------------- +void CInfoCustomTechnology::Activate( void ) +{ + BaseClass::Activate(); + if ( !GetTeamNumber() ) + { + Msg( "ERROR: info_customtech without a specified team.\n" ); + UTIL_Remove( this ); + return; + } + + // Get the Team's Technology Tree + CTFTeam *pTeam = (CTFTeam *)GetTeam(); + if ( pTeam ) + { + CTechnologyTree *pTechTree = pTeam->GetTechnologyTree(); + if ( pTechTree ) + { + // Am I supposed to add some new technologies to the tech tree? + if ( m_iszTechTreeFile != NULL_STRING ) + { + pTechTree->AddTechnologyFile( filesystem, GetTeamNumber(), (char*)STRING(m_iszTechTreeFile ) ); + + // Tell our clients about the technology + Q_strncpy( m_szTechTreeFile.GetForModify(), STRING(m_iszTechTreeFile), sizeof(m_szTechTreeFile) ); + } + + // Find the technology in the techtree + CBaseTechnology *pTechnology = pTechTree->GetTechnology( STRING(m_iszTech) ); + // Now hook the technology up to me + if ( pTechnology ) + { + pTechnology->RegisterWatcher( this ); + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Update the amount of our technology that's owned +//----------------------------------------------------------------------------- +void CInfoCustomTechnology::UpdateTechPercentage( float flPercentage ) +{ + m_flTechPercentage.Set( flPercentage, this, this ); +}
\ No newline at end of file diff --git a/game/server/tf2/info_customtech.h b/game/server/tf2/info_customtech.h new file mode 100644 index 0000000..dc8202f --- /dev/null +++ b/game/server/tf2/info_customtech.h @@ -0,0 +1,43 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef INFO_CUSTOMTECH_H +#define INFO_CUSTOMTECH_H +#ifdef _WIN32 +#pragma once +#endif + +#include "entityoutput.h" +#include "baseentity.h" + +//----------------------------------------------------------------------------- +// Purpose: Map entity that adds a custom technology to the techtree +//----------------------------------------------------------------------------- +class CInfoCustomTechnology : public CPointEntity +{ + DECLARE_CLASS( CInfoCustomTechnology, CPointEntity ); +public: + void Spawn( void ); + void Activate( void ); + void UpdateTechPercentage( float flPercentage ); + + virtual int UpdateTransmitState() { return SetTransmitState( FL_EDICT_FULLCHECK ); }; + virtual int ShouldTransmit( const CCheckTransmitInfo *pInfo ); + + DECLARE_SERVERCLASS(); + DECLARE_DATADESC(); + +public: + COutputFloat m_flTechPercentage; // Percentage of the tech that's owned + string_t m_iszTech; + string_t m_iszTechTreeFile; + + // Sent via datatable + CNetworkString( m_szTechTreeFile, 128 ); +}; + +#endif // INFO_CUSTOMTECH_H diff --git a/game/server/tf2/info_input_playsound.cpp b/game/server/tf2/info_input_playsound.cpp new file mode 100644 index 0000000..529e561 --- /dev/null +++ b/game/server/tf2/info_input_playsound.cpp @@ -0,0 +1,222 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "EntityOutput.h" +#include "EntityList.h" +#include "tf_team.h" +#include "baseentity.h" +#include "engine/IEngineSound.h" +#include "triggers.h" + +// Spawnflags +#define SF_PLAYSOUND_USE_THIS_ORIGIN 0x0001 + +//----------------------------------------------------------------------------- +// Purpose: Map entity that plays sounds to players +//----------------------------------------------------------------------------- +class CInfoInputPlaySound : public CBaseEntity +{ + DECLARE_CLASS( CInfoInputPlaySound, CBaseEntity ); +public: + DECLARE_DATADESC(); + + virtual void Spawn( void ); + virtual void Precache( void ); + virtual void Activate( void ); + + // Inputs + void InputPlaySoundToAll( inputdata_t &inputdata ); + void InputPlaySoundToTeam1( inputdata_t &inputdata ); + void InputPlaySoundToTeam2( inputdata_t &inputdata ); + void InputPlaySoundToPlayer( inputdata_t &inputdata ); + void InputSetSound( inputdata_t &inputdata ); + + // Sound playing + void PlaySoundToPlayer( CBaseTFPlayer *pPlayer ); + void PlaySoundToTeam( CTFTeam *pTeam ); + +private: + string_t m_iszSound; + float m_flVolume; + float m_flAttenuation; + string_t m_iszTestVolumeName; + EHANDLE m_hTestVolume; +}; + +BEGIN_DATADESC( CInfoInputPlaySound ) + + // variables + DEFINE_KEYFIELD( m_iszSound, FIELD_SOUNDNAME, "Sound" ), + DEFINE_KEYFIELD( m_flVolume, FIELD_FLOAT, "Volume" ), + DEFINE_KEYFIELD( m_flAttenuation, FIELD_FLOAT, "Attenuation" ), + DEFINE_KEYFIELD( m_iszTestVolumeName, FIELD_STRING, "TestVolume" ), + + // inputs + DEFINE_INPUTFUNC( FIELD_STRING, "SetSound", InputSetSound ), + DEFINE_INPUTFUNC( FIELD_VOID, "PlaySoundToAll", InputPlaySoundToAll ), + DEFINE_INPUTFUNC( FIELD_VOID, "PlaySoundToTeam1", InputPlaySoundToTeam1 ), + DEFINE_INPUTFUNC( FIELD_VOID, "PlaySoundToTeam2", InputPlaySoundToTeam2 ), + DEFINE_INPUTFUNC( FIELD_EHANDLE, "PlaySoundToPlayer", InputPlaySoundToPlayer ), + +END_DATADESC() + +LINK_ENTITY_TO_CLASS( info_input_playsound, CInfoInputPlaySound ); + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CInfoInputPlaySound::Spawn( void ) +{ + m_hTestVolume = NULL; + Precache(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CInfoInputPlaySound::Precache( void ) +{ + if ( m_iszSound != NULL_STRING ) + { + PrecacheScriptSound( STRING(m_iszSound) ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CInfoInputPlaySound::Activate( void ) +{ + BaseClass::Activate(); + + // Find our test volume, if we have one + if ( m_iszTestVolumeName != NULL_STRING ) + { + m_hTestVolume = gEntList.FindEntityByName( NULL, STRING(m_iszTestVolumeName) ); + if ( !m_hTestVolume ) + { + Msg("ERROR: Could not find test volume %s for info_input_playsound.\n", STRING(m_iszTestVolumeName) ); + } + else + { + // Make sure it's a trigger + CBaseTrigger *pTrigger = dynamic_cast<CBaseTrigger*>((CBaseEntity*)m_hTestVolume); + if ( !pTrigger ) + { + Msg("ERROR: info_input_playsound specifies a volume %s, but it's not a trigger.\n", STRING(m_iszTestVolumeName) ); + m_hTestVolume = NULL; + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Play sound to all players +//----------------------------------------------------------------------------- +void CInfoInputPlaySound::InputPlaySoundToAll( inputdata_t &inputdata ) +{ + for ( int i = 1; i <= gpGlobals->maxClients; i++ ) + { + CBaseTFPlayer *pPlayer = ToBaseTFPlayer( UTIL_PlayerByIndex(i) ); + if ( pPlayer ) + { + PlaySoundToPlayer( pPlayer ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Play sound to all players on team 1 +//----------------------------------------------------------------------------- +void CInfoInputPlaySound::InputPlaySoundToTeam1( inputdata_t &inputdata ) +{ + PlaySoundToTeam( GetGlobalTFTeam(1) ); +} + +//----------------------------------------------------------------------------- +// Purpose: Play sound to all players on team 2 +//----------------------------------------------------------------------------- +void CInfoInputPlaySound::InputPlaySoundToTeam2( inputdata_t &inputdata ) +{ + PlaySoundToTeam( GetGlobalTFTeam(2) ); +} + +//----------------------------------------------------------------------------- +// Purpose: Play sound to a specific player +//----------------------------------------------------------------------------- +void CInfoInputPlaySound::InputPlaySoundToPlayer( inputdata_t &inputdata ) +{ + CBaseEntity *pEntity = (inputdata.value.Entity()).Get(); + if ( pEntity && pEntity->IsPlayer() ) + { + CBaseTFPlayer *pPlayer = (CBaseTFPlayer *)pEntity; + PlaySoundToPlayer( pPlayer ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Set the sound to play +//----------------------------------------------------------------------------- +void CInfoInputPlaySound::InputSetSound( inputdata_t &inputdata ) +{ + m_iszSound = MAKE_STRING( inputdata.value.String() ); +} + +//----------------------------------------------------------------------------- +// Purpose: Play sound to a team +//----------------------------------------------------------------------------- +void CInfoInputPlaySound::PlaySoundToTeam( CTFTeam *pTeam ) +{ + for ( int i = 0; i < pTeam->GetNumPlayers(); i++ ) + { + PlaySoundToPlayer( (CBaseTFPlayer*)pTeam->GetPlayer(i) ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Play sound to a player +//----------------------------------------------------------------------------- +void CInfoInputPlaySound::PlaySoundToPlayer( CBaseTFPlayer *pPlayer ) +{ + // First, if we have a test volume, make sure the player's within it + if ( m_hTestVolume ) + { + CBaseTrigger *pTrigger = (CBaseTrigger *)(CBaseEntity*)m_hTestVolume; + if ( !pTrigger->IsTouching( pPlayer ) ) + return; + } + + // Check to see if we're supposed to play it from this entity's location + if ( HasSpawnFlags( SF_PLAYSOUND_USE_THIS_ORIGIN ) ) + { + CPASAttenuationFilter filter; + filter.AddRecipient( pPlayer ); + filter.Filter( GetAbsOrigin(), m_flAttenuation ); + + EmitSound_t ep; + ep.m_nChannel = CHAN_VOICE; + ep.m_pSoundName = STRING(m_iszSound); + ep.m_flVolume = m_flVolume; + ep.m_SoundLevel = ATTN_TO_SNDLVL( m_flAttenuation ); + + ep.m_pOrigin = &(GetAbsOrigin()); + + EmitSound( filter, entindex(), ep ); + } + else + { + CSingleUserRecipientFilter filter( pPlayer ); + + EmitSound_t ep; + ep.m_nChannel = CHAN_VOICE; + ep.m_pSoundName = STRING(m_iszSound); + ep.m_flVolume = m_flVolume; + ep.m_SoundLevel = ATTN_TO_SNDLVL( m_flAttenuation ); + + EmitSound( filter, pPlayer->entindex(), ep ); + } +} diff --git a/game/server/tf2/info_input_resetbanks.cpp b/game/server/tf2/info_input_resetbanks.cpp new file mode 100644 index 0000000..9e44b5c --- /dev/null +++ b/game/server/tf2/info_input_resetbanks.cpp @@ -0,0 +1,124 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "EntityOutput.h" +#include "EntityList.h" +#include "tf_team.h" +#include "baseentity.h" + +//----------------------------------------------------------------------------- +// Purpose: Map entity that resets player's banks +//----------------------------------------------------------------------------- +class CInfoInputResetBanks : public CBaseEntity +{ + DECLARE_CLASS( CInfoInputResetBanks, CBaseEntity ); +public: + DECLARE_DATADESC(); + + // Inputs + void InputResetAll( inputdata_t &inputdata ); + void InputResetTeam1( inputdata_t &inputdata ); + void InputResetTeam2( inputdata_t &inputdata ); + void InputResetPlayer( inputdata_t &inputdata ); + void InputSetResetAmount( inputdata_t &inputdata ); + + // Resetting + void ResetPlayersBank( CBaseTFPlayer *pPlayer ); + void ResetTeamsBanks( CTFTeam *pTeam ); + +private: + int m_iResetAmount; +}; + +BEGIN_DATADESC( CInfoInputResetBanks ) + + // variables + DEFINE_KEYFIELD( m_iResetAmount, FIELD_INTEGER, "ResetAmount" ), + + // inputs + DEFINE_INPUTFUNC( FIELD_INTEGER, "SetResetAmount", InputSetResetAmount ), + DEFINE_INPUTFUNC( FIELD_VOID, "ResetAll", InputResetAll ), + DEFINE_INPUTFUNC( FIELD_VOID, "ResetTeam1", InputResetTeam1 ), + DEFINE_INPUTFUNC( FIELD_VOID, "ResetTeam2", InputResetTeam2 ), + DEFINE_INPUTFUNC( FIELD_EHANDLE, "ResetPlayer", InputResetPlayer ), + +END_DATADESC() + +LINK_ENTITY_TO_CLASS( info_input_resetbanks, CInfoInputResetBanks ); + +//----------------------------------------------------------------------------- +// Purpose: Reset all the player's resource banks +//----------------------------------------------------------------------------- +void CInfoInputResetBanks::InputResetAll( inputdata_t &inputdata ) +{ + for ( int i = 1; i <= gpGlobals->maxClients; i++ ) + { + CBaseTFPlayer *pPlayer = ToBaseTFPlayer( UTIL_PlayerByIndex(i) ); + if ( pPlayer ) + { + ResetPlayersBank( pPlayer ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Reset all of team 1's player's resource banks +//----------------------------------------------------------------------------- +void CInfoInputResetBanks::InputResetTeam1( inputdata_t &inputdata ) +{ + ResetTeamsBanks( GetGlobalTFTeam(1) ); +} + +//----------------------------------------------------------------------------- +// Purpose: Reset all of team 2's player's resource banks +//----------------------------------------------------------------------------- +void CInfoInputResetBanks::InputResetTeam2( inputdata_t &inputdata ) +{ + ResetTeamsBanks( GetGlobalTFTeam(2) ); +} + +//----------------------------------------------------------------------------- +// Purpose: Reset a specific player's resource banks +//----------------------------------------------------------------------------- +void CInfoInputResetBanks::InputResetPlayer( inputdata_t &inputdata ) +{ + CBaseEntity *pEntity = (inputdata.value.Entity()).Get(); + if ( pEntity && pEntity->IsPlayer() ) + { + CBaseTFPlayer *pPlayer = (CBaseTFPlayer *)pEntity; + ResetPlayersBank( pPlayer ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Set the reset amount +//----------------------------------------------------------------------------- +void CInfoInputResetBanks::InputSetResetAmount( inputdata_t &inputdata ) +{ + m_iResetAmount = inputdata.value.Int(); + Assert( m_iResetAmount >= 0 ); +} + +//----------------------------------------------------------------------------- +// Purpose: Reset the team's resource banks +//----------------------------------------------------------------------------- +void CInfoInputResetBanks::ResetTeamsBanks( CTFTeam *pTeam ) +{ + pTeam->SetRecentBankSet( m_iResetAmount ); + for ( int i = 0; i < pTeam->GetNumPlayers(); i++ ) + { + ResetPlayersBank( (CBaseTFPlayer*)pTeam->GetPlayer(i) ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Reset the player's resource bank +//----------------------------------------------------------------------------- +void CInfoInputResetBanks::ResetPlayersBank( CBaseTFPlayer *pPlayer ) +{ + pPlayer->SetBankResources( m_iResetAmount ); +} diff --git a/game/server/tf2/info_input_resetobjects.cpp b/game/server/tf2/info_input_resetobjects.cpp new file mode 100644 index 0000000..e0475a1 --- /dev/null +++ b/game/server/tf2/info_input_resetobjects.cpp @@ -0,0 +1,106 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "EntityOutput.h" +#include "EntityList.h" +#include "tf_team.h" +#include "baseentity.h" + +//----------------------------------------------------------------------------- +// Purpose: Map entity that resets player's objects +//----------------------------------------------------------------------------- +class CInfoInputResetObjects : public CBaseEntity +{ + DECLARE_CLASS( CInfoInputResetObjects, CBaseEntity ); +public: + DECLARE_DATADESC(); + + // Inputs + void InputResetAll( inputdata_t &inputdata ); + void InputResetTeam1( inputdata_t &inputdata ); + void InputResetTeam2( inputdata_t &inputdata ); + void InputResetPlayer( inputdata_t &inputdata ); + + // Resetting + void ResetPlayersObjects( CBaseTFPlayer *pPlayer ); + void ResetTeamsObjects( CTFTeam *pTeam ); +}; + +BEGIN_DATADESC( CInfoInputResetObjects ) + + // inputs + DEFINE_INPUTFUNC( FIELD_VOID, "ResetAll", InputResetAll ), + DEFINE_INPUTFUNC( FIELD_VOID, "ResetTeam1", InputResetTeam1 ), + DEFINE_INPUTFUNC( FIELD_VOID, "ResetTeam2", InputResetTeam2 ), + DEFINE_INPUTFUNC( FIELD_EHANDLE, "ResetPlayer", InputResetPlayer ), + +END_DATADESC() + +LINK_ENTITY_TO_CLASS( info_input_resetobjects, CInfoInputResetObjects ); + +//----------------------------------------------------------------------------- +// Purpose: Reset all the player's objects +//----------------------------------------------------------------------------- +void CInfoInputResetObjects::InputResetAll( inputdata_t &inputdata ) +{ + for ( int i = 1; i <= gpGlobals->maxClients; i++ ) + { + CBaseTFPlayer *pPlayer = ToBaseTFPlayer( UTIL_PlayerByIndex(i) ); + if ( pPlayer ) + { + ResetPlayersObjects( pPlayer ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Reset all of team 1's player's objects +//----------------------------------------------------------------------------- +void CInfoInputResetObjects::InputResetTeam1( inputdata_t &inputdata ) +{ + ResetTeamsObjects( GetGlobalTFTeam(1) ); +} + +//----------------------------------------------------------------------------- +// Purpose: Reset all of team 2's player's objects +//----------------------------------------------------------------------------- +void CInfoInputResetObjects::InputResetTeam2( inputdata_t &inputdata ) +{ + ResetTeamsObjects( GetGlobalTFTeam(2) ); +} + +//----------------------------------------------------------------------------- +// Purpose: Reset a specific player's objects +//----------------------------------------------------------------------------- +void CInfoInputResetObjects::InputResetPlayer( inputdata_t &inputdata ) +{ + CBaseEntity *pEntity = (inputdata.value.Entity()).Get(); + if ( pEntity && pEntity->IsPlayer() ) + { + CBaseTFPlayer *pPlayer = (CBaseTFPlayer *)pEntity; + ResetPlayersObjects( pPlayer ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Reset the team's objects +//----------------------------------------------------------------------------- +void CInfoInputResetObjects::ResetTeamsObjects( CTFTeam *pTeam ) +{ + for ( int i = 0; i < pTeam->GetNumPlayers(); i++ ) + { + ResetPlayersObjects( (CBaseTFPlayer*)pTeam->GetPlayer(i) ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Reset the player's objects +//----------------------------------------------------------------------------- +void CInfoInputResetObjects::ResetPlayersObjects( CBaseTFPlayer *pPlayer ) +{ + pPlayer->RemoveAllObjects( true, 0, true ); +}
\ No newline at end of file diff --git a/game/server/tf2/info_input_respawnplayers.cpp b/game/server/tf2/info_input_respawnplayers.cpp new file mode 100644 index 0000000..4b9917f --- /dev/null +++ b/game/server/tf2/info_input_respawnplayers.cpp @@ -0,0 +1,121 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "EntityOutput.h" +#include "EntityList.h" +#include "tf_team.h" +#include "baseentity.h" + +// Spawnflags +const int SF_RESPAWNPLAYERS_RESETALL = 0x01; // Respawned players have their inventory completely reset +const int SF_RESPAWNPLAYERS_RESETAMMO = 0x02; // Respawned players have their ammo counts reset + +//----------------------------------------------------------------------------- +// Purpose: Map entity that respawns players +//----------------------------------------------------------------------------- +class CInfoInputRespawnPlayers : public CBaseEntity +{ + DECLARE_CLASS( CInfoInputRespawnPlayers, CBaseEntity ); +public: + DECLARE_DATADESC(); + + // Inputs + void InputRespawnAll( inputdata_t &inputdata ); + void InputRespawnTeam1( inputdata_t &inputdata ); + void InputRespawnTeam2( inputdata_t &inputdata ); + void InputRespawnPlayer( inputdata_t &inputdata ); + + // Respawning + void RespawnPlayer( CBaseTFPlayer *pPlayer ); + void RespawnTeam( CTFTeam *pTeam ); +}; + +BEGIN_DATADESC( CInfoInputRespawnPlayers ) + + // inputs + DEFINE_INPUTFUNC( FIELD_VOID, "RespawnAll", InputRespawnAll ), + DEFINE_INPUTFUNC( FIELD_VOID, "RespawnTeam1", InputRespawnTeam1 ), + DEFINE_INPUTFUNC( FIELD_VOID, "RespawnTeam2", InputRespawnTeam2 ), + DEFINE_INPUTFUNC( FIELD_EHANDLE, "RespawnPlayer", InputRespawnPlayer ), + +END_DATADESC() + +LINK_ENTITY_TO_CLASS( info_input_respawnplayers, CInfoInputRespawnPlayers ); + +//----------------------------------------------------------------------------- +// Purpose: Respawn all the players +//----------------------------------------------------------------------------- +void CInfoInputRespawnPlayers::InputRespawnAll( inputdata_t &inputdata ) +{ + for ( int i = 0; i < MAX_TF_TEAMS; i++ ) + { + RespawnTeam( GetGlobalTFTeam(i+1) ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Respawn all of team 1's players +//----------------------------------------------------------------------------- +void CInfoInputRespawnPlayers::InputRespawnTeam1( inputdata_t &inputdata ) +{ + RespawnTeam( GetGlobalTFTeam(1) ); +} + +//----------------------------------------------------------------------------- +// Purpose: Respawn all of team 2's players +//----------------------------------------------------------------------------- +void CInfoInputRespawnPlayers::InputRespawnTeam2( inputdata_t &inputdata ) +{ + RespawnTeam( GetGlobalTFTeam(2) ); +} + +//----------------------------------------------------------------------------- +// Purpose: Respawn a specific player +//----------------------------------------------------------------------------- +void CInfoInputRespawnPlayers::InputRespawnPlayer( inputdata_t &inputdata ) +{ + CBaseEntity *pEntity = (inputdata.value.Entity()).Get(); + if ( pEntity && pEntity->IsPlayer() ) + { + CBaseTFPlayer *pPlayer = (CBaseTFPlayer *)pEntity; + RespawnPlayer( pPlayer ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Respawn the team +//----------------------------------------------------------------------------- +void CInfoInputRespawnPlayers::RespawnTeam( CTFTeam *pTeam ) +{ + Assert( pTeam ); + if ( !pTeam ) + return; + + // Respawn all the players + for ( int i = 0; i < pTeam->GetNumPlayers(); i++ ) + { + RespawnPlayer( (CBaseTFPlayer*)pTeam->GetPlayer(i) ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Respawn the player +//----------------------------------------------------------------------------- +void CInfoInputRespawnPlayers::RespawnPlayer( CBaseTFPlayer *pPlayer ) +{ + // Reset ammo if the spawnflag's set + if ( HasSpawnFlags( SF_RESPAWNPLAYERS_RESETALL ) ) + { + pPlayer->ResupplyAmmo( 1.0, RESUPPLY_ALL_FROM_STATION ); + } + else if ( HasSpawnFlags( SF_RESPAWNPLAYERS_RESETAMMO ) ) + { + pPlayer->ResupplyAmmo( 1.0, RESUPPLY_RESPAWN ); + } + + pPlayer->ForceRespawn(); +}
\ No newline at end of file diff --git a/game/server/tf2/info_minimappulse.cpp b/game/server/tf2/info_minimappulse.cpp new file mode 100644 index 0000000..fa34a09 --- /dev/null +++ b/game/server/tf2/info_minimappulse.cpp @@ -0,0 +1,116 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "EntityOutput.h" +#include "EntityList.h" +#include "tf_team.h" +#include "baseentity.h" + +//----------------------------------------------------------------------------- +// Purpose: Map entity that makes a pulse on the minimap +//----------------------------------------------------------------------------- +class CInfoMinimapPulse : public CBaseEntity +{ + DECLARE_CLASS( CInfoMinimapPulse, CBaseEntity ); +public: + DECLARE_DATADESC(); + + void Spawn( void ); + + // Inputs + void InputPulseForAll( inputdata_t &inputdata ); + void InputPulseForTeam1( inputdata_t &inputdata ); + void InputPulseForTeam2( inputdata_t &inputdata ); + void InputPulseForPlayer( inputdata_t &inputdata ); + + // Pulsing + void PulseForPlayer( CBaseTFPlayer *pPlayer ); + void PulseForTeam( CTFTeam *pTeam ); +}; + +BEGIN_DATADESC( CInfoMinimapPulse ) + + // inputs + DEFINE_INPUTFUNC( FIELD_VOID, "PulseForAll", InputPulseForAll ), + DEFINE_INPUTFUNC( FIELD_VOID, "PulseForTeam1", InputPulseForTeam1 ), + DEFINE_INPUTFUNC( FIELD_VOID, "PulseForTeam2", InputPulseForTeam2 ), + DEFINE_INPUTFUNC( FIELD_EHANDLE, "PulseForPlayer", InputPulseForPlayer ), + +END_DATADESC() + +LINK_ENTITY_TO_CLASS( info_minimappulse, CInfoMinimapPulse ); + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CInfoMinimapPulse::Spawn( void ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: Pulse for all the players +//----------------------------------------------------------------------------- +void CInfoMinimapPulse::InputPulseForAll( inputdata_t &inputdata ) +{ + for ( int i = 0; i < MAX_TF_TEAMS; i++ ) + { + PulseForTeam( GetGlobalTFTeam(i) ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Pulse for all of team 1's players +//----------------------------------------------------------------------------- +void CInfoMinimapPulse::InputPulseForTeam1( inputdata_t &inputdata ) +{ + PulseForTeam( GetGlobalTFTeam(1) ); +} + +//----------------------------------------------------------------------------- +// Purpose: Pulse for all of team 2's players +//----------------------------------------------------------------------------- +void CInfoMinimapPulse::InputPulseForTeam2( inputdata_t &inputdata ) +{ + PulseForTeam( GetGlobalTFTeam(2) ); +} + +//----------------------------------------------------------------------------- +// Purpose: Pulse for a specific player +//----------------------------------------------------------------------------- +void CInfoMinimapPulse::InputPulseForPlayer( inputdata_t &inputdata ) +{ + CBaseEntity *pEntity = (inputdata.value.Entity()).Get(); + if ( pEntity && pEntity->IsPlayer() ) + { + CBaseTFPlayer *pPlayer = (CBaseTFPlayer *)pEntity; + PulseForPlayer( pPlayer ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Pulse for the team +//----------------------------------------------------------------------------- +void CInfoMinimapPulse::PulseForTeam( CTFTeam *pTeam ) +{ + // Pulse all the players + for ( int i = 0; i < pTeam->GetNumPlayers(); i++ ) + { + PulseForPlayer( (CBaseTFPlayer*)pTeam->GetPlayer(i) ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Make a minimap pulse for the player +//----------------------------------------------------------------------------- +void CInfoMinimapPulse::PulseForPlayer( CBaseTFPlayer *pPlayer ) +{ + CSingleUserRecipientFilter user( pPlayer ); + user.MakeReliable(); + UserMessageBegin( user, "MinimapPulse" ); + WRITE_VEC3COORD( GetAbsOrigin() ); + MessageEnd(); +}
\ No newline at end of file diff --git a/game/server/tf2/info_output_team.cpp b/game/server/tf2/info_output_team.cpp new file mode 100644 index 0000000..ffe3f97 --- /dev/null +++ b/game/server/tf2/info_output_team.cpp @@ -0,0 +1,71 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "EntityOutput.h" +#include "EntityList.h" +#include "tf_team.h" +#include "baseentity.h" + +//----------------------------------------------------------------------------- +// Purpose: Map entity that fires its output with all the players in a team +//----------------------------------------------------------------------------- +class CInfoOutputTeam : public CBaseEntity +{ + DECLARE_CLASS( CInfoOutputTeam, CBaseEntity ); +public: + DECLARE_DATADESC(); + + void Spawn( void ); + + // Inputs + void InputFire( inputdata_t &inputdata ); + +public: + // Outputs + COutputEHANDLE m_Player; +}; + +BEGIN_DATADESC( CInfoOutputTeam ) + + // inputs + DEFINE_INPUTFUNC( FIELD_VOID, "Fire", InputFire ), + + // outputs + DEFINE_OUTPUT( m_Player, "Player" ), + +END_DATADESC() + +LINK_ENTITY_TO_CLASS( info_output_team, CInfoOutputTeam ); + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CInfoOutputTeam::Spawn( void ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CInfoOutputTeam::InputFire( inputdata_t &inputdata ) +{ + // Loop through all the players on the team and fire our output with each of them. + for ( int i = 1; i <= gpGlobals->maxClients; i++ ) + { + CBaseTFPlayer *pPlayer = ToBaseTFPlayer( UTIL_PlayerByIndex(i) ); + if ( pPlayer ) + { + // If we don't belong to a team, loop through all players + if ( GetTeamNumber() == 0 || pPlayer->GetTeamNumber() == GetTeamNumber() ) + { + EHANDLE hHandle; + hHandle = pPlayer; + m_Player.Set( hHandle, inputdata.pActivator, this ); + } + } + } +}
\ No newline at end of file diff --git a/game/server/tf2/info_resourceprocessor.cpp b/game/server/tf2/info_resourceprocessor.cpp new file mode 100644 index 0000000..940202b --- /dev/null +++ b/game/server/tf2/info_resourceprocessor.cpp @@ -0,0 +1,139 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: A team's resource processor back at their base +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "tf_team.h" +#include "info_resourceprocessor.h" +#include "tf_player.h" +#include "npc_minicarrier.h" +#include "tf_gamerules.h" + +BEGIN_DATADESC( CResourceProcessor ) + + // functions + DEFINE_FUNCTION( ProcessorTouch ), + +END_DATADESC() + +IMPLEMENT_SERVERCLASS_ST(CResourceProcessor, DT_ResourceProcessor) +END_SEND_TABLE() + +LINK_ENTITY_TO_CLASS( info_resourceprocessor, CResourceProcessor); + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CResourceProcessor::Spawn( void ) +{ + Precache(); + + SetSolid( SOLID_SLIDEBOX ); + SetMoveType( MOVETYPE_NONE ); + AddFlag( FL_NOTARGET ); + + UTIL_SetSize( pev, Vector(-32,-32,0), Vector(32,32, 128) ); + SetModel( "models/objects/obj_resourceprocessor.mdl" ); + SetTouch( ProcessorTouch ); + + m_flHackSpawnHeight = 256; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CResourceProcessor::Precache( void ) +{ + PrecacheModel( "models/objects/obj_resourceprocessor.mdl" ); + + UTIL_PrecacheOther( "npc_minicarrier" ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CResourceProcessor::Activate( void ) +{ + BaseClass::Activate(); + if ( GetTeamNumber() < 0 || GetTeamNumber() >= GetNumberOfTeams() ) + { + Warning( "Warning, info_resourceprocessor with invalid Team Number set.\n" ); + UTIL_Remove( this ); + return; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CResourceProcessor::ProcessorTouch( CBaseEntity *pOther ) +{ + // Players + if ( pOther->IsPlayer() ) + { + // Ignore touches from enemy players + if ( !InSameTeam( pOther ) ) + return; + + CBaseTFPlayer *pPlayer = (CBaseTFPlayer *)pOther; + + // Remove all the player's resources and add them to the team's stash + for ( int i = 0; i < MAX_RESOURCE_TYPES; i++ ) + { + int iCount = pPlayer->GetResourceChunkCount(i, false); + if ( iCount ) + { + pPlayer->RemoveResourceChunks( i, iCount, false ); + AddResources( i, iCount * CHUNK_RESOURCE_VALUE ); + } + + // Now remove processed versions too + iCount = pPlayer->GetResourceChunkCount(i, true); + if ( iCount ) + { + pPlayer->RemoveResourceChunks( i, iCount, true ); + AddResources( i, iCount * PROCESSED_CHUNK_RESOURCE_VALUE ); + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Add resources to the processor, and hence the team's bank +//----------------------------------------------------------------------------- +void CResourceProcessor::AddResources( int iResourceType, float flResources ) +{ + if ( !GetTeam() ) + return; + + ((CTFTeam *)GetTeam())->AddResources( iResourceType, flResources ); + ((CTFTeam *)GetTeam())->ResourceLoadDeposited(); +} + + +//----------------------------------------------------------------------------- +// Purpose: Spawn a minicarrier +//----------------------------------------------------------------------------- +void CResourceProcessor::SpawnMiniCarrier( void ) +{ + CNPC_MiniCarrier *pMiniCarrier = (CNPC_MiniCarrier*)CreateEntityByName( "npc_minicarrier" ); + pMiniCarrier->Spawn(); + pMiniCarrier->ChangeTeam( m_iTeamNumber ); + + // Find a clear spot near me & spawn in it + if ( !EntityPlacementTest( pMiniCarrier, GetAbsOrigin() + Vector(0,0,m_flHackSpawnHeight), + pMiniCarrier->GetAbsOrigin(), false ) ) + { + Warning( "Failed to find empty space to spawn a minicarrier.\n" ); + ( ( CTFTeam * )GetTeam() )->RemoveRobot( pMiniCarrier ); + UTIL_Remove( pMiniCarrier ); + return; + } + + m_flHackSpawnHeight += 64; + + engine->SetOrigin( pMiniCarrier->pev, pMiniCarrier->GetOrigin() ); + pMiniCarrier->SetHomeProcessor( this ); +} diff --git a/game/server/tf2/info_vehicle_bay.cpp b/game/server/tf2/info_vehicle_bay.cpp new file mode 100644 index 0000000..30737b9 --- /dev/null +++ b/game/server/tf2/info_vehicle_bay.cpp @@ -0,0 +1,270 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "cbase.h" +#include "EntityOutput.h" +#include "EntityList.h" +#include "info_vehicle_bay.h" +#include "tf_obj.h" +#include "tf_player.h" +#include "info_act.h" + +extern ConVar tf_fastbuild; + +BEGIN_DATADESC( CInfoVehicleBay ) +END_DATADESC() + +LINK_ENTITY_TO_CLASS( info_vehicle_bay, CInfoVehicleBay ); + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CInfoVehicleBay::Spawn( void ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: Vehicle bays have 1 build point +//----------------------------------------------------------------------------- +int CInfoVehicleBay::GetNumBuildPoints( void ) const +{ + return 1; +} + +//----------------------------------------------------------------------------- +// Purpose: Return true if the specified object type can be built on this point +//----------------------------------------------------------------------------- +bool CInfoVehicleBay::CanBuildObjectOnBuildPoint( int iPoint, int iObjectType ) +{ + ASSERT( iPoint <= GetNumBuildPoints() ); + + // Don't allow building if there's another vehicle in the way + + + // Only vehicles can be built here + return ( iObjectType >= OBJ_BATTERING_RAM ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CInfoVehicleBay::GetBuildPoint( int iPoint, Vector &vecOrigin, QAngle &vecAngles ) +{ + ASSERT( iPoint <= GetNumBuildPoints() ); + + vecOrigin = GetAbsOrigin(); + vecAngles = GetAbsAngles(); + return true; +} + +int CInfoVehicleBay::GetBuildPointAttachmentIndex( int iPoint ) const +{ + return 0; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CInfoVehicleBay::SetObjectOnBuildPoint( int iPoint, CBaseObject *pObject ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int CInfoVehicleBay::GetNumObjectsOnMe( void ) +{ + return 0; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CBaseEntity *CInfoVehicleBay::GetFirstObjectOnMe( void ) +{ + return NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CBaseObject *CInfoVehicleBay::GetObjectOfTypeOnMe( int iObjectType ) +{ + return NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int CInfoVehicleBay::FindObjectOnBuildPoint( CBaseObject *pObject ) +{ + return -1; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CInfoVehicleBay::GetExitPoint( CBaseEntity *pPlayer, int iPoint, Vector *pAbsOrigin, QAngle *pAbsAngles ) +{ + Assert(0); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CInfoVehicleBay::RemoveAllObjects( void ) +{ +} + +//=================================================================================================================== +// Vehicle Bay VGui Screen +//=================================================================================================================== +BEGIN_DATADESC( CVGuiScreenVehicleBay ) + // outputs + DEFINE_OUTPUT( m_OnStartedBuild, "OnStartedBuild" ), + DEFINE_OUTPUT( m_OnFinishedBuild, "OnFinishedBuild" ), + DEFINE_OUTPUT( m_OnReadyToBuildAgain, "OnReadyToBuildAgain" ), + + // functions + DEFINE_FUNCTION( BayThink ), +END_DATADESC() + +LINK_ENTITY_TO_CLASS( vgui_screen_vehicle_bay, CVGuiScreenVehicleBay ); + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CVGuiScreenVehicleBay::Activate( void ) +{ + BaseClass::Activate(); + + // Make sure we have a buildpoint specified + CBaseEntity *pBuildPoint = gEntList.FindEntityByName( NULL, m_target ); + if ( !pBuildPoint ) + { + Msg("ERROR: vgui_screen_vehicle_bay with no buildpoint as its target.\n" ); + UTIL_Remove( this ); + return; + } + + Vector vecOrigin = pBuildPoint->GetAbsOrigin(); + QAngle vecAngles = pBuildPoint->GetAbsAngles(); + SetBuildPoint( vecOrigin, vecAngles ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CVGuiScreenVehicleBay::SetBuildPoint( Vector &vecOrigin, QAngle &vecAngles ) +{ + m_vecBuildPointOrigin = vecOrigin; + m_vecBuildPointAngles = vecAngles; + m_bBayIsClear = false; + + // Start checking to see when I'm clear again + SetThink( BayThink ); + SetNextThink( gpGlobals->curtime + 0.1f ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CVGuiScreenVehicleBay::BuildVehicle( CBaseTFPlayer *pPlayer, int iObjectType ) +{ + if ( !IsObjectAVehicle(iObjectType) ) + return; + Assert( m_vecBuildPointOrigin != vec3_origin ); + + // Can't build if the game hasn't started + if ( !tf_fastbuild.GetInt() && CurrentActIsAWaitingAct() ) + { + ClientPrint( pPlayer, HUD_PRINTCENTER, "Can't build until the game's started.\n" ); + return; + } + + // Try and spawn the object + CBaseEntity *pEntity = CreateEntityByName( GetObjectInfo(iObjectType)->m_pClassName ); + if ( !pEntity ) + return; + + if ( !m_bBayIsClear ) + { + ClientPrint( pPlayer, HUD_PRINTCENTER, "Vehicle bay isn't clear.\n" ); + return; + } + + pEntity->SetAbsOrigin( m_vecBuildPointOrigin ); + pEntity->SetAbsAngles( m_vecBuildPointAngles ); + + CBaseObject *pObject = dynamic_cast<CBaseObject*>(pEntity); + if ( pObject ) + pObject->AdjustInitialBuildAngles(); + + pEntity->Spawn(); + + // If it's an object, finish setting it up + if ( !pObject ) + return; + + pObject->StartPlacement( pPlayer ); + pObject->SetVehicleBay( this ); + + // StartBuilding will return false if the player couldn't afford the vehicle + if ( !pObject->StartBuilding( pPlayer ) ) + return; + + // Fire our started-building output + m_OnStartedBuild.FireOutput( pPlayer, this ); + + m_bBayIsClear = false; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CVGuiScreenVehicleBay::FinishedBuildVehicle( CBaseObject *pObject ) +{ + m_OnFinishedBuild.FireOutput( pObject->GetBuilder(), this ); + + // Start checking to see when I'm clear again + SetThink( BayThink ); + SetNextThink( gpGlobals->curtime + 0.3 ); +} + +//----------------------------------------------------------------------------- +// Purpose: Check to see if we're clear enough to allow another vehicle to be built here +//----------------------------------------------------------------------------- +void CVGuiScreenVehicleBay::BayThink( void ) +{ + // Get a list of entities around our buildpoint + CBaseEntity *pListOfNearbyEntities[100]; + int iNumberOfNearbyEntities = UTIL_EntitiesInSphere( pListOfNearbyEntities, 100, m_vecBuildPointOrigin, 128, 0 ); + for ( int i = 0; i < iNumberOfNearbyEntities; i++ ) + { + CBaseEntity *pEntity = pListOfNearbyEntities[i]; + if ( pEntity->IsSolid( ) ) + { + // Ignore shields.. + if ( pEntity->GetCollisionGroup() == TFCOLLISION_GROUP_SHIELD ) + continue; + // Ignore func brushes + if ( pEntity->GetMoveType() == MOVETYPE_PUSH ) + continue; + + //NDebugOverlay::EntityBounds( pEntity, 0,255,0,8, 0.1 ); + + // Check again soon + SetNextThink( gpGlobals->curtime + 1 ); + return; + } + } + + // We're clear + m_bBayIsClear = true; + SetThink( NULL ); + + m_OnReadyToBuildAgain.FireOutput( this, this ); +}
\ No newline at end of file diff --git a/game/server/tf2/info_vehicle_bay.h b/game/server/tf2/info_vehicle_bay.h new file mode 100644 index 0000000..3692ac9 --- /dev/null +++ b/game/server/tf2/info_vehicle_bay.h @@ -0,0 +1,72 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#ifndef INFO_VEHICLE_BAY_H +#define INFO_VEHICLE_BAY_H +#ifdef _WIN32 +#pragma once +#endif + +#include "ihasbuildpoints.h" +#include "vguiscreen.h" + +//----------------------------------------------------------------------------- +// Purpose: Entity that provides a place to build a vehicle +//----------------------------------------------------------------------------- +class CInfoVehicleBay : public CBaseEntity, public IHasBuildPoints +{ + DECLARE_CLASS( CInfoVehicleBay, CBaseEntity ); +public: + DECLARE_DATADESC(); + + void Spawn( void ); + +// IHasBuildPoints +public: + virtual int GetNumBuildPoints( void ) const; + virtual bool CanBuildObjectOnBuildPoint( int iPoint, int iObjectType ); + virtual bool GetBuildPoint( int iPoint, Vector &vecOrigin, QAngle &vecAngles ); + virtual int GetBuildPointAttachmentIndex( int iPoint ) const; + virtual void SetObjectOnBuildPoint( int iPoint, CBaseObject *pObject ); + virtual int GetNumObjectsOnMe( void ); + virtual CBaseEntity *GetFirstObjectOnMe( void ); + virtual CBaseObject *GetObjectOfTypeOnMe( int iObjectType ); + virtual int FindObjectOnBuildPoint( CBaseObject *pObject ); + virtual void GetExitPoint( CBaseEntity *pPlayer, int iPoint, Vector *pAbsOrigin, QAngle *pAbsAngles ); + virtual void RemoveAllObjects( void ); + virtual float GetMaxSnapDistance( int iPoint ) { return 128; } + virtual bool ShouldCheckForMovement( void ) { return false; } +}; + +//----------------------------------------------------------------------------- +// Purpose: Vgui screen for vehicle buying +//----------------------------------------------------------------------------- +class CVGuiScreenVehicleBay : public CVGuiScreen +{ + DECLARE_CLASS( CVGuiScreenVehicleBay, CVGuiScreen ); +public: + DECLARE_DATADESC(); + + virtual void Activate( void ); + + void BuildVehicle( CBaseTFPlayer *pPlayer, int iObjectType ); + void FinishedBuildVehicle( CBaseObject *pObject ); + void SetBuildPoint( Vector &vecOrigin, QAngle &vecAngles ); + + void BayThink( void ); + +private: + bool m_bBayIsClear; + Vector m_vecBuildPointOrigin; + QAngle m_vecBuildPointAngles; + + // Outputs + COutputEvent m_OnStartedBuild; + COutputEvent m_OnFinishedBuild; + COutputEvent m_OnReadyToBuildAgain; +}; + +#endif // INFO_VEHICLE_BAY_H diff --git a/game/server/tf2/mapdata_server.cpp b/game/server/tf2/mapdata_server.cpp new file mode 100644 index 0000000..258f580 --- /dev/null +++ b/game/server/tf2/mapdata_server.cpp @@ -0,0 +1,115 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" + +#include "mapdata_shared.h" +#include "sharedinterface.h" +#include "baseentity.h" +#include "world.h" +#include "player.h" + +class CMapData_Server : public IMapData +{ +public: + + // World data queries. + void GetMapBounds( Vector &vecMins, Vector &vecMaxs ); + void GetMapOrigin( Vector &vecOrigin ); + void GetMapSize( Vector &vecSize ); + + // 3D Skybox data queries. + void Get3DSkyboxOrigin( Vector &vecOrigin ); + float Get3DSkyboxScale( void ); +}; + +static CMapData_Server g_MapData; +IMapData *g_pMapData = &g_MapData; + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CMapData_Server::GetMapBounds( Vector &vecMins, Vector &vecMaxs ) +{ + CWorld *pWorld = static_cast<CWorld*>( GetWorldEntity() ); + if ( pWorld ) + { + // Get the world bounds. + pWorld->GetWorldBounds( vecMins, vecMaxs ); + + // Backward compatability... + if ( ( vecMins.LengthSqr() == 0.0f ) && ( vecMaxs.LengthSqr() == 0.0f ) ) + { + vecMins.Init( -6500.0f, -6500.0f, -6500.0f ); + vecMaxs.Init( 6500.0f, 6500.0f, 6500.0f ); + } + } + else + { + Assert( 0 ); + vecMins.Init( 0.0f, 0.0f, 0.0f ); + vecMaxs.Init( 1.0f, 1.0f, 1.0f ); + } +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CMapData_Server::GetMapOrigin( Vector &vecOrigin ) +{ + Vector vecMins, vecMaxs; + GetMapBounds( vecMins, vecMaxs ); + VectorAdd( vecMins, vecMaxs, vecOrigin ); + VectorMultiply( vecOrigin, 0.5f, vecOrigin ); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CMapData_Server::GetMapSize( Vector &vecSize ) +{ + Vector vecMins, vecMaxs; + GetMapBounds( vecMins, vecMaxs ); + VectorSubtract( vecMaxs, vecMins, vecSize ); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CMapData_Server::Get3DSkyboxOrigin( Vector &vecOrigin ) +{ + // NOTE: If the player hasn't been created yet -- this doesn't work!!! + // We need to pass the data along in the map - requires a tool change. + CBasePlayer *pPlayer = UTIL_GetLocalPlayer(); + if ( pPlayer ) + { + CPlayerLocalData *pLocalData = &pPlayer->m_Local; + VectorCopy( pLocalData->m_skybox3d.origin, vecOrigin ); + } + else + { + // Debugging! + Assert( 0 ); + vecOrigin.Init(); + } +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +float CMapData_Server::Get3DSkyboxScale( void ) +{ + // NOTE: If the player hasn't been created yet -- this doesn't work!!! + // We need to pass the data along in the map - requires a tool change. + CBasePlayer *pPlayer = UTIL_GetLocalPlayer(); + if ( pPlayer ) + { + CPlayerLocalData *pLocalData = &pPlayer->m_Local; + return pLocalData->m_skybox3d.scale; + } + else + { + // Debugging! + Assert( 0 ); + return 1.0f; + } +} diff --git a/game/server/tf2/menu_base.cpp b/game/server/tf2/menu_base.cpp new file mode 100644 index 0000000..e08adaa --- /dev/null +++ b/game/server/tf2/menu_base.cpp @@ -0,0 +1,262 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Ugly menus for prototyping +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "player.h" +#include "tf_player.h" +#include "menu_base.h" +#include "tf_team.h" +#include "baseviewmodel.h" +#include "tf_gamerules.h" +#include "tf_class_infiltrator.h" +#include "tier1/strtools.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +// Global list of menus +CMenu *gMenus[MENU_LAST]; + +//----------------------------------------------------------------------------- +// Purpose: Initialize the Global Menu structure +//----------------------------------------------------------------------------- +void InitializeMenus( void ) +{ + gMenus[MENU_DEFAULT] = NULL; + gMenus[MENU_TEAM] = new CMenuTeam(); + gMenus[MENU_CLASS] = new CMenuClass(); +} + +void DestroyMenus( void ) +{ + delete gMenus[MENU_DEFAULT]; + delete gMenus[MENU_TEAM]; + delete gMenus[MENU_CLASS]; +} + +//----------------------------------------------------------------------------- +// Purpose: Base Menu Handling +//----------------------------------------------------------------------------- +CMenu::CMenu() +{ + memset( m_szMenuString, 0, sizeof(m_szMenuString) ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CMenu::Display( CBaseTFPlayer *pViewer, int allowed, int display_time ) +{ + RecalculateMenu( pViewer ); + + // Only display if the menu's not identical to the one the player's already seeing + if ( pViewer->m_MenuUpdateTime > gpGlobals->curtime ) + { + if ( (allowed == pViewer->m_MenuSelectionBuffer) && FStrEq( m_szMenuString, pViewer->m_MenuStringBuffer) ) + return; + } + pViewer->m_MenuUpdateTime = gpGlobals->curtime + 10; + pViewer->m_MenuSelectionBuffer = allowed; + + const char *msg_portion = m_szMenuString; + Q_strncpy( pViewer->m_MenuStringBuffer, m_szMenuString, MENU_STRING_BUFFER_SIZE ); + + CSingleUserRecipientFilter user( pViewer ); + user.MakeReliable(); + + while ( strlen(msg_portion) >= MENU_MSG_TEXTCHUNK_SIZE ) + { + // split the string + char sbuf[MENU_MSG_TEXTCHUNK_SIZE+1]; + Q_strncpy( sbuf, msg_portion, MENU_MSG_TEXTCHUNK_SIZE+1 ); + msg_portion += MENU_MSG_TEXTCHUNK_SIZE; + + + // send the string portion + UserMessageBegin( user, "ShowMenu" ); + WRITE_WORD( allowed ); + WRITE_CHAR( display_time ); // display time (-1 means unlimited) + WRITE_BYTE( TRUE ); // there is more message to come + WRITE_STRING( sbuf ); + MessageEnd(); + } + + // send the remaining string + UserMessageBegin( user, "ShowMenu" ); + WRITE_WORD( allowed ); + WRITE_CHAR( display_time ); // display time (-1 means unlimited) + WRITE_BYTE( FALSE ); // there is no more message to come + WRITE_STRING( (char*)msg_portion ); + MessageEnd(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CMenu::RecalculateMenu( CBaseTFPlayer *pViewer ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CMenu::Input( CBaseTFPlayer *pViewer, int iInput ) +{ + return false; +} + + +//----------------------------------------------------------------------------- +// Purpose: Team Menu +//----------------------------------------------------------------------------- +CMenuTeam::CMenuTeam() +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CMenuTeam::RecalculateMenu( CBaseTFPlayer *pViewer ) +{ + Q_strncpy( m_szMenuString, "Pick a Team: \n\n\n->1. Humans\n\n->2. Aliens\n", sizeof(m_szMenuString) ); + + // Allow aborting if they have a class + if ( pViewer->GetTeam() ) + { + Q_strncat( m_szMenuString, "\n\n->9. Don't change team.\n", sizeof(m_szMenuString), COPY_ALL_CHARACTERS ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Handle input for the Team menu +//----------------------------------------------------------------------------- +bool CMenuTeam::Input( CBaseTFPlayer *pViewer, int iInput ) +{ + // Allow aborting if they have a team + if ( pViewer->GetTeam() && iInput == 9 ) + { + pViewer->MenuReset(); + return true; + } + + if (iInput < 0 || iInput >= GetNumberOfTeams()) + return false; + + // Ignore changeteam requests to their current team + if ( pViewer->GetTeam() ) + { + if ( iInput == pViewer->GetTeam()->GetTeamNumber() ) + { + pViewer->MenuReset(); + return true; + } + } + + // Add the player to the team and then bring up the Class Menu + pViewer->ChangeTeam( iInput ); + + // Clear out the class + if ( pViewer->GetPlayerClass() ) + { + // Remove all the player's items + pViewer->RemoveAllItems( false ); + pViewer->HideViewModels(); + pViewer->ClearPlayerClass(); + } + + pViewer->ForceRespawn(); + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: Class Menu +//----------------------------------------------------------------------------- +CMenuClass::CMenuClass() +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CMenuClass::RecalculateMenu( CBaseTFPlayer *pViewer ) +{ + if ( !pViewer->GetTeam() ) + return; + + Q_snprintf( m_szMenuString,sizeof(m_szMenuString), "You are on Team %s\n\n\n\n\n\n\nPick your Class: \n\n\n", pViewer->GetTeam()->GetName() ); + + int iClassNum = 1; + + // Check technology for each class + for ( int i = 0; i < TFCLASS_CLASS_COUNT; i++ ) + { + char sClass[256]; + + if ( !( pViewer->IsClassAvailable( (TFClass)i ) ) ) + continue; + + int iNumber = pViewer->GetTFTeam()->GetNumOfClass( (TFClass)i ); + if ( !iNumber ) + { + Q_snprintf( sClass, sizeof(sClass), "->%d. %s\n\n", iClassNum, GetTFClassInfo( i )->m_pClassName ); + } + else + { + Q_snprintf( sClass, sizeof(sClass), "->%d. %s (%d in your team)\n\n", iClassNum, GetTFClassInfo( i )->m_pClassName, iNumber ); + } + + Q_strncat( m_szMenuString, sClass,sizeof(m_szMenuString), COPY_ALL_CHARACTERS ); + iClassNum++; + } + + // Allow aborting if they have a class + if ( pViewer->GetPlayerClass() ) + { + Q_strncat( m_szMenuString, "\n\n->9. Don't change class.\n", sizeof(m_szMenuString), COPY_ALL_CHARACTERS ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Handle input for the Class menu +//----------------------------------------------------------------------------- +bool CMenuClass::Input( CBaseTFPlayer *pViewer, int iInput ) +{ + int iClassNum = 0; + + // Allow aborting if they have a class + if ( pViewer->GetPlayerClass() && iInput == 9 ) + { + pViewer->MenuReset(); + return true; + } + + // Get the class number + for ( int i = 1; iInput && i < TFCLASS_CLASS_COUNT; i++ ) + { + if ( !( pViewer->IsClassAvailable( (TFClass)i ) ) ) + continue; + iInput--; + iClassNum = i; + } + + // Ignore changeclass requests to their current class + if ( pViewer->GetPlayerClass() ) + { + if ( (TFClass)iClassNum == pViewer->PlayerClass() ) + { + pViewer->MenuReset(); + return true; + } + } + + if ( !pViewer->IsClassAvailable( (TFClass)iClassNum ) ) + return false; + + pViewer->ChangeClass( (TFClass)iClassNum ); + pViewer->m_pCurrentMenu = NULL; + return true; +}
\ No newline at end of file diff --git a/game/server/tf2/menu_base.h b/game/server/tf2/menu_base.h new file mode 100644 index 0000000..4566296 --- /dev/null +++ b/game/server/tf2/menu_base.h @@ -0,0 +1,77 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef MENU_BASE_H +#define MENU_BASE_H +#pragma once + +class CBasePlayer; +class CMenu; + +enum +{ + MENU_DEFAULT = 0, + MENU_TEAM, + MENU_CLASS, + + // Insert new Menus here + MENU_LAST, // Total Number of menus +}; + +// Global list of menus +extern CMenu *gMenus[]; + +//----------------------------------------------------------------------------- +// Purpose: Base Menu Class +//----------------------------------------------------------------------------- +class CMenu +{ +public: + CMenu(); + + virtual void RecalculateMenu( CBaseTFPlayer *pViewer ); + virtual void Display( CBaseTFPlayer *pViewer, int allowed = 0xFFFF, int display_time = -1 ); + virtual bool Input( CBaseTFPlayer *pViewer, int iInput ); + +protected: + char m_szMenuString[1024]; +}; + + +//----------------------------------------------------------------------------- +// Purpose: Team Menu +//----------------------------------------------------------------------------- +class CMenuTeam : public CMenu +{ +public: + CMenuTeam(); + + virtual void RecalculateMenu( CBaseTFPlayer *pViewer ); + virtual bool Input( CBaseTFPlayer *pViewer, int iInput ); +}; + + +//----------------------------------------------------------------------------- +// Purpose: Class Menu +//----------------------------------------------------------------------------- +class CMenuClass : public CMenu +{ +public: + CMenuClass(); + + virtual void RecalculateMenu( CBaseTFPlayer *pViewer ); + virtual bool Input( CBaseTFPlayer *pViewer, int iInput ); +}; + + +#endif // MENU_BASE_H diff --git a/game/server/tf2/mortar_round.cpp b/game/server/tf2/mortar_round.cpp new file mode 100644 index 0000000..38f72b9 --- /dev/null +++ b/game/server/tf2/mortar_round.cpp @@ -0,0 +1,257 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" +#include "mortar_round.h" +#include "engine/IEngineSound.h" +#include "tf_gamerules.h" +#include "tf_team.h" + + + +// Damage CVars +ConVar weapon_mortar_shell_damage( "weapon_mortar_shell_damage","0", FCVAR_NONE, "Mortar's standard shell maximum damage" ); +ConVar weapon_mortar_shell_radius( "weapon_mortar_shell_radius","0", FCVAR_NONE, "Mortar's standard shell splash radius" ); +ConVar weapon_mortar_starburst_damage( "weapon_mortar_starburst_damage","0", FCVAR_NONE, "Mortar's starburst maximum damage" ); +ConVar weapon_mortar_starburst_radius( "weapon_mortar_starburst_radius","0", FCVAR_NONE, "Mortar's starburst splash radius" ); +ConVar weapon_mortar_cluster_shells( "weapon_mortar_cluster_shells","0", FCVAR_NONE, "Number of shells a mortar cluster round bursts into" ); + +//===================================================================================================== +// MORTAR ROUND +//===================================================================================================== +BEGIN_DATADESC( CMortarRound ) + + // Function Pointers + DEFINE_FUNCTION( MissileTouch ), + DEFINE_FUNCTION( FallThink ), + +END_DATADESC() + +LINK_ENTITY_TO_CLASS( mortar_round, CMortarRound ); +PRECACHE_WEAPON_REGISTER(mortar_round); + +CMortarRound::CMortarRound() +{ + m_pSmokeTrail = NULL; + m_pLauncher = NULL; + m_iRoundType = MA_SHELL; + UseClientSideAnimation(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CMortarRound::Precache( void ) +{ + PrecacheModel( "models/weapons/w_grenade.mdl" ); + + PrecacheScriptSound( "MortarRound.StopSound" ); + PrecacheScriptSound( "MortarRound.IncomingSound" ); + +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CMortarRound::Spawn( void ) +{ + Precache(); + + SetMoveType( MOVETYPE_FLYGRAVITY ); + SetSolid( SOLID_BBOX ); + SetModel( "models/weapons/w_grenade.mdl" ); + UTIL_SetSize( this, vec3_origin, vec3_origin ); + + SetTouch( MissileTouch ); + SetCollisionGroup( TFCOLLISION_GROUP_WEAPON ); + + // Trail smoke + m_pSmokeTrail = SmokeTrail::CreateSmokeTrail(); + if ( m_pSmokeTrail ) + { + m_pSmokeTrail->m_SpawnRate = 90; + m_pSmokeTrail->m_ParticleLifetime = 1.5; + m_pSmokeTrail->m_StartColor.Init(0.0, 0.0, 0.0); + m_pSmokeTrail->m_EndColor.Init( 0.5,0.5,0.5 ); + m_pSmokeTrail->m_StartSize = 10; + m_pSmokeTrail->m_EndSize = 50; + m_pSmokeTrail->m_SpawnRadius = 1; + m_pSmokeTrail->m_MinSpeed = 15; + m_pSmokeTrail->m_MaxSpeed = 25; + m_pSmokeTrail->SetLifetime(15); + m_pSmokeTrail->FollowEntity( this ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CMortarRound::MissileTouch( CBaseEntity *pOther ) +{ + CMortarRound *pRound = dynamic_cast< CMortarRound* >(pOther); + if ( pRound ) + return; + + // Stop emitting smoke/sound + m_pSmokeTrail->SetEmit(false); + EmitSound( "MortarRound.StopSound" ); + + // Create an explosion. + if ( m_iRoundType == MA_STARBURST ) + { + // Small explosion, blind people in the area + float flBlind = 0; + + // Shift it up a bit for the explosion + SetLocalOrigin( GetAbsOrigin() + Vector(0,0,32) ); + CPASFilter filter( GetAbsOrigin() ); + te->Explosion( filter, 0.0, &GetAbsOrigin(), g_sModelIndexFireball, 3.0, 15, TE_EXPLFLAG_NONE, 512, 100 ); + RadiusDamage( CTakeDamageInfo( this, GetOwnerEntity(), weapon_mortar_starburst_damage.GetFloat(), DMG_BLAST ), GetAbsOrigin(), weapon_mortar_starburst_radius.GetFloat(), CLASS_NONE, NULL ); + + // Blind all players nearby + for ( int i = 1; i <= gpGlobals->maxClients; i++ ) + { + CBaseTFPlayer *pPlayer = ToBaseTFPlayer( UTIL_PlayerByIndex(i) ); + if ( pPlayer ) + { + Vector vecSrc = pPlayer->EyePosition(); + Vector vecToTarget = (vecSrc - GetAbsOrigin()); + float flLength = VectorNormalize( vecToTarget ); + // If the player's looking at the grenade, blind him a lot + if ( flLength < 2048 ) + { + Vector forward, right, up; + AngleVectors( pPlayer->pl.v_angle, &forward, &right, &up ); + float flDot = DotProduct( vecToTarget, forward ); + +// Msg( "Dot: %f\n", flDot ); + + // Make sure it's in front of the player + if ( flDot < 0.0f ) + { + flDot = fabs( DotProduct(vecToTarget, right ) ) + fabs( DotProduct(vecToTarget, up ) ); + + // Open LOS? + trace_t tr; + UTIL_TraceLine( vecSrc, GetAbsOrigin(), MASK_SHOT, this, COLLISION_GROUP_NONE, &tr ); + if ( ( tr.fraction == 1.0f ) || ( tr.m_pEnt == pPlayer ) ) + { + flBlind = flDot; + } + else + { + // Blocked blind is only half as effective + flBlind = flDot * 0.5; + } + } + else if ( flLength < 512 ) + { + // Otherwise, if the player's near the grenade blind him a little + flBlind = 0.2; + } + + + // Flash the screen red + color32 white = {255,255,255, 255}; + white.a = MIN( (flBlind * 255), 255 ); + UTIL_ScreenFade( pPlayer, white, 0.3, 5.0, 0 ); + } + } + } + + UTIL_Remove( this ); + } + else + { + // Large explosion + + // Shift it up a bit for the explosion + SetLocalOrigin( GetAbsOrigin() + Vector(0,0,64) ); + CPASFilter filter( GetAbsOrigin() ); + te->Explosion( filter, 0.0, &GetAbsOrigin(), g_sModelIndexFireball, 10.0, 15, TE_EXPLFLAG_NONE, 512, 300 ); + RadiusDamage( CTakeDamageInfo( this, GetOwnerEntity(), weapon_mortar_shell_damage.GetFloat(), DMG_BLAST ), GetAbsOrigin(), weapon_mortar_shell_radius.GetFloat(), CLASS_NONE, NULL ); + UTIL_Remove( this ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Play a fall sound when the mortar round begins to fall +//----------------------------------------------------------------------------- +void CMortarRound::FallThink( void ) +{ + if ( m_pLauncher ) + { + CRecipientFilter filter; + filter.AddAllPlayers(); + EmitSound( filter, entindex(), "MortarRound.IncomingSound" ); + + // Cluster bombs split up in the air + if ( m_iRoundType == MA_CLUSTER ) + { + Vector forward, right; + QAngle angles; + VectorAngles( GetAbsVelocity(), angles ); + SetLocalAngles( angles ); + AngleVectors( GetLocalAngles(), &forward, &right, NULL ); + for ( int i = 0; i < weapon_mortar_cluster_shells.GetInt(); i++ ) + { + Vector vecVelocity = GetAbsVelocity(); + vecVelocity += forward * random->RandomFloat( -200, 200 ); + vecVelocity += right * random->RandomFloat( -200, 200 ); + + CMortarRound *pRound = CMortarRound::Create( GetAbsOrigin(), vecVelocity, GetOwnerEntity() ? GetOwnerEntity()->edict() : NULL ); + pRound->SetLauncher( m_pLauncher ); + pRound->ChangeTeam( GetTeamNumber() ); + pRound->m_iRoundType = MA_SHELL; + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Keep a pointer to the Launcher +//----------------------------------------------------------------------------- +void CMortarRound::SetLauncher( CVehicleMortar *pLauncher ) +{ + m_pLauncher = pLauncher; +} + +//----------------------------------------------------------------------------- +// Purpose: Set the point at which we should start playing the fall sound +//----------------------------------------------------------------------------- +void CMortarRound::SetFallTime( float flFallTime ) +{ + SetThink( FallThink ); + SetNextThink( gpGlobals->curtime + flFallTime ); +} + +//----------------------------------------------------------------------------- +// Purpose: Set the round's type +//----------------------------------------------------------------------------- +void CMortarRound::SetRoundType( int iRoundType ) +{ + m_iRoundType = iRoundType; +} + +//----------------------------------------------------------------------------- +// Purpose: Create a missile +//----------------------------------------------------------------------------- +CMortarRound* CMortarRound::Create( const Vector &vecOrigin, const Vector &vecVelocity, edict_t *pentOwner = NULL ) +{ + CMortarRound *pGrenade = (CMortarRound*)CreateEntityByName("mortar_round"); + + UTIL_SetOrigin( pGrenade, vecOrigin ); + pGrenade->SetOwnerEntity( Instance( pentOwner ) ); + pGrenade->Spawn(); + pGrenade->SetAbsVelocity( vecVelocity ); + QAngle angles; + VectorAngles( vecVelocity, angles ); + pGrenade->SetLocalAngles( angles ); + + return pGrenade; +} + diff --git a/game/server/tf2/mortar_round.h b/game/server/tf2/mortar_round.h new file mode 100644 index 0000000..a4ecadb --- /dev/null +++ b/game/server/tf2/mortar_round.h @@ -0,0 +1,48 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef MORTAR_ROUND_H +#define MORTAR_ROUND_H +#ifdef _WIN32 +#pragma once +#endif + + +#include "tf_vehicle_mortar.h" +#include "smoke_trail.h" + + +class CMortarRound : public CBaseAnimating +{ +public: + DECLARE_CLASS( CMortarRound, CBaseAnimating ); + + CMortarRound(); + + DECLARE_DATADESC(); + +public: + virtual void Spawn( void ); + virtual void Precache( void ); + virtual void MissileTouch( CBaseEntity *pOther ); + virtual void FallThink( void ); + virtual void SetLauncher( CVehicleMortar *pLauncher ); + virtual void SetFallTime( float flFallTime ); + virtual void SetRoundType( int iRoundType ); + + // Damage type accessors + virtual int GetDamageType() const { return DMG_BLAST; } + + static CMortarRound* CMortarRound::Create( const Vector &vecOrigin, const Vector &vecVelocity, edict_t *pentOwner ); + + SmokeTrail *m_pSmokeTrail; + CHandle<CVehicleMortar> m_pLauncher; + int m_iRoundType; +}; + + +#endif // MORTAR_ROUND_H diff --git a/game/server/tf2/npc_bug_builder.cpp b/game/server/tf2/npc_bug_builder.cpp new file mode 100644 index 0000000..e2bdfe2 --- /dev/null +++ b/game/server/tf2/npc_bug_builder.cpp @@ -0,0 +1,577 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: The builder bug +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "AI_Task.h" +#include "AI_Default.h" +#include "AI_Schedule.h" +#include "AI_Hull.h" +#include "AI_Hint.h" +#include "AI_Navigator.h" +#include "activitylist.h" +#include "soundent.h" +#include "game.h" +#include "NPCEvent.h" +#include "tf_player.h" +#include "EntityList.h" +#include "ndebugoverlay.h" +#include "shake.h" +#include "monstermaker.h" +#include "decals.h" +#include "vstdlib/random.h" +#include "tf_obj.h" +#include "engine/IEngineSound.h" +#include "IEffects.h" +#include "npc_bug_builder.h" +#include "npc_bug_hole.h" + +ConVar npc_bug_builder_health( "npc_bug_builder_health", "100" ); + +BEGIN_DATADESC( CNPC_Bug_Builder ) + + DEFINE_FIELD( m_flIdleDelay, FIELD_FLOAT ), + +END_DATADESC() + +LINK_ENTITY_TO_CLASS( npc_bug_builder, CNPC_Bug_Builder ); +IMPLEMENT_CUSTOM_AI( npc_bug_builder, CNPC_Bug_Builder ); + +// Dawdling details +// Max & Min distances for dawdle forward movement +#define DAWDLE_MIN_DIST 64 +#define DAWDLE_MAX_DIST 1024 + +//================================================== +// Bug Conditions +//================================================== +enum BugConditions +{ + COND_BBUG_RETURN_TO_BUGHOLE = LAST_SHARED_CONDITION, +}; + +//================================================== +// Bug Schedules +//================================================== + +enum BugSchedules +{ + SCHED_BBUG_FLEE_ENEMY = LAST_SHARED_SCHEDULE, + SCHED_BBUG_RETURN_TO_BUGHOLE, + SCHED_BBUG_RETURN_TO_BUGHOLE_AND_REMOVE, + SCHED_BBUG_DAWDLE, +}; + +//================================================== +// Bug Tasks +//================================================== + +enum BugTasks +{ + TASK_BBUG_GET_PATH_TO_FLEE = LAST_SHARED_TASK, + TASK_BBUG_GET_PATH_TO_BUGHOLE, + TASK_BBUG_HOLE_REMOVE, + TASK_BBUG_GET_PATH_TO_DAWDLE, + TASK_BBUG_FACE_DAWDLE, +}; + +//================================================== +// Bug Activities +//================================================== + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CNPC_Bug_Builder::CNPC_Bug_Builder( void ) +{ + m_flFieldOfView = 0.5f; + m_flIdleDelay = 0.0f; +} + +//----------------------------------------------------------------------------- +// Purpose: Setup our schedules and tasks, etc. +//----------------------------------------------------------------------------- +void CNPC_Bug_Builder::InitCustomSchedules( void ) +{ + INIT_CUSTOM_AI( CNPC_Bug_Builder ); + + // Schedules + ADD_CUSTOM_SCHEDULE( CNPC_Bug_Builder, SCHED_BBUG_FLEE_ENEMY ); + ADD_CUSTOM_SCHEDULE( CNPC_Bug_Builder, SCHED_BBUG_RETURN_TO_BUGHOLE ); + ADD_CUSTOM_SCHEDULE( CNPC_Bug_Builder, SCHED_BBUG_RETURN_TO_BUGHOLE_AND_REMOVE ); + ADD_CUSTOM_SCHEDULE( CNPC_Bug_Builder, SCHED_BBUG_DAWDLE ); + + // Conditions + ADD_CUSTOM_CONDITION( CNPC_Bug_Builder, COND_BBUG_RETURN_TO_BUGHOLE ); + + // Tasks + ADD_CUSTOM_TASK( CNPC_Bug_Builder, TASK_BBUG_GET_PATH_TO_FLEE ); + ADD_CUSTOM_TASK( CNPC_Bug_Builder, TASK_BBUG_GET_PATH_TO_BUGHOLE ); + ADD_CUSTOM_TASK( CNPC_Bug_Builder, TASK_BBUG_HOLE_REMOVE ); + ADD_CUSTOM_TASK( CNPC_Bug_Builder, TASK_BBUG_GET_PATH_TO_DAWDLE ); + ADD_CUSTOM_TASK( CNPC_Bug_Builder, TASK_BBUG_FACE_DAWDLE ); + + // Activities + //ADD_CUSTOM_ACTIVITY( CNPC_Bug_Builder, ACT_BUG_WARRIOR_DISTRACT ); + + AI_LOAD_SCHEDULE( CNPC_Bug_Builder, SCHED_BBUG_FLEE_ENEMY ); + AI_LOAD_SCHEDULE( CNPC_Bug_Builder, SCHED_BBUG_RETURN_TO_BUGHOLE ); + AI_LOAD_SCHEDULE( CNPC_Bug_Builder, SCHED_BBUG_RETURN_TO_BUGHOLE_AND_REMOVE ); + AI_LOAD_SCHEDULE( CNPC_Bug_Builder, SCHED_BBUG_DAWDLE ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CNPC_Bug_Builder::Spawn( void ) +{ + Precache(); + + SetModel( BUG_BUILDER_MODEL ); + + SetHullType(HULL_TINY); + SetHullSizeNormal(); + SetDefaultEyeOffset(); + SetViewOffset( (WorldAlignMins() + WorldAlignMaxs()) * 0.5 ); // See from my center + SetDistLook( 1024.0 ); + m_flNextDawdle = 0; + + SetNavType(NAV_GROUND); + m_NPCState = NPC_STATE_NONE; + SetBloodColor( BLOOD_COLOR_YELLOW ); + m_iHealth = npc_bug_builder_health.GetFloat(); + + SetSolid( SOLID_BBOX ); + AddSolidFlags( FSOLID_NOT_STANDABLE ); + SetMoveType( MOVETYPE_STEP ); + + CapabilitiesAdd( bits_CAP_MOVE_GROUND ); + + NPCInit(); + + BaseClass::Spawn(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CNPC_Bug_Builder::Precache( void ) +{ + PrecacheModel( BUG_BUILDER_MODEL ); + + PrecacheScriptSound( "NPC_Bug_Builder.Idle" ); + PrecacheScriptSound( "NPC_Bug_Builder.Pain" ); + + BaseClass::Precache(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : int +//----------------------------------------------------------------------------- +int CNPC_Bug_Builder::SelectSchedule( void ) +{ + // If I'm not in idle anymore, don't idle + if ( m_NPCState != NPC_STATE_IDLE ) + { + m_flNextDawdle = 0; + } + + switch ( m_NPCState ) + { + case NPC_STATE_IDLE: + { + // BugHole might be requesting help + if ( HasCondition( COND_BBUG_RETURN_TO_BUGHOLE ) ) + return SCHED_BBUG_RETURN_TO_BUGHOLE; + + // Setup to dawdle a bit from now + if ( !m_flNextDawdle ) + { + m_flNextDawdle = gpGlobals->curtime + random->RandomFloat( 3.0, 5.0 ); + } + else if ( m_flNextDawdle < gpGlobals->curtime ) + { + m_flNextDawdle = 0; + return SCHED_BBUG_DAWDLE; + } + + // When I take damage, I flee + if ( HasCondition( COND_LIGHT_DAMAGE | COND_HEAVY_DAMAGE ) ) + return SCHED_BBUG_FLEE_ENEMY; + + // Return to my bughole + //return SCHED_BBUG_RETURN_TO_BUGHOLE_AND_REMOVE; + break; + } + case NPC_STATE_ALERT: + { + // BugHole might be requesting help + if ( HasCondition( COND_BBUG_RETURN_TO_BUGHOLE ) ) + return SCHED_BBUG_RETURN_TO_BUGHOLE; + + // When I take damage, I flee + if ( HasCondition( COND_LIGHT_DAMAGE ) || HasCondition( COND_HEAVY_DAMAGE ) ) + return SCHED_BBUG_FLEE_ENEMY; + + break; + } + case NPC_STATE_COMBAT: + { + // Did I lose my enemy? + if ( HasCondition ( COND_LOST_ENEMY ) || HasCondition ( COND_ENEMY_UNREACHABLE ) ) + { + SetEnemy( NULL ); + SetState(NPC_STATE_IDLE); + return BaseClass::SelectSchedule(); + } + + // When I take damage, I flee + if ( HasCondition( COND_LIGHT_DAMAGE ) || HasCondition( COND_HEAVY_DAMAGE ) ) + return SCHED_BBUG_FLEE_ENEMY; + } + break; + } + + return BaseClass::SelectSchedule(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pTask - +//----------------------------------------------------------------------------- +void CNPC_Bug_Builder::StartTask( const Task_t *pTask ) +{ + switch ( pTask->iTask ) + { + case TASK_BBUG_GET_PATH_TO_FLEE: + { + // Always tell our bughole that we're under attack + if ( m_hMyBugHole ) + { + m_hMyBugHole->IncomingFleeingBug( this ); + } + + // If we have no squad, or we couldn't get a path to our squadmate, move to our bughole + if ( m_hMyBugHole ) + { + SetTarget( m_hMyBugHole ); + AI_NavGoal_t goal( GOALTYPE_TARGETENT, vec3_origin, ACT_RUN ); + if ( GetNavigator()->SetGoal( goal ) ) + { + TaskComplete(); + return; + } + } + + TaskComplete(); + } + break; + + case TASK_BBUG_GET_PATH_TO_BUGHOLE: + { + // Get a path back to my bughole + // If we have no squad, or we couldn't get a path to our squadmate, look for a bughole + if ( m_hMyBugHole ) + { + SetTarget( m_hMyBugHole ); + AI_NavGoal_t goal( GOALTYPE_TARGETENT, vec3_origin, ACT_RUN ); + if ( GetNavigator()->SetGoal( goal ) ) + { + TaskComplete(); + return; + } + } + + TaskFail( "Couldn't get to bughole." ); + } + break; + + case TASK_BBUG_HOLE_REMOVE: + { + TaskComplete(); + + // Crawl inside the bughole and remove myself + AddEffects( EF_NODRAW ); + AddSolidFlags( FSOLID_NOT_SOLID ); + Event_Killed( CTakeDamageInfo( this, this, 200, DMG_CRUSH ) ); + + // Tell the bughole + if ( m_hMyBugHole ) + { + m_hMyBugHole->BugReturned(); + } + } + break; + + case TASK_BBUG_GET_PATH_TO_DAWDLE: + { + // Get a dawdle point ahead of us + Vector vecForward, vecTarget; + AngleVectors( GetAbsAngles(), &vecForward ); + VectorMA( GetAbsOrigin(), random->RandomFloat( DAWDLE_MIN_DIST, DAWDLE_MAX_DIST ), vecForward, vecTarget ); + + // See how far we could move ahead + trace_t tr; + UTIL_TraceEntity( this, GetAbsOrigin(), vecTarget, MASK_SOLID, &tr); + float flDistance = tr.fraction * (vecTarget - GetAbsOrigin()).Length(); + if ( flDistance >= DAWDLE_MIN_DIST ) + { + AI_NavGoal_t goal( tr.endpos ); + GetNavigator()->SetGoal( goal ); + } + + TaskComplete(); + } + break; + + case TASK_BBUG_FACE_DAWDLE: + { + // Turn a random amount to the right + float flYaw = GetMotor()->GetIdealYaw(); + flYaw = flYaw + random->RandomFloat( 45, 135 ); + GetMotor()->SetIdealYaw( UTIL_AngleMod(flYaw) ); + SetTurnActivity(); + break; + } + break; + + default: + BaseClass::StartTask( pTask ); + break; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pTask - +//----------------------------------------------------------------------------- +void CNPC_Bug_Builder::RunTask( const Task_t *pTask ) +{ + switch ( pTask->iTask ) + { + case TASK_BBUG_FACE_DAWDLE: + { + GetMotor()->UpdateYaw(); + if ( FacingIdeal() ) + { + TaskComplete(); + } + break; + } + + default: + BaseClass::RunTask( pTask ); + break; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CNPC_Bug_Builder::FValidateHintType(CAI_Hint *pHint) +{ + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pVictim - +//----------------------------------------------------------------------------- +void CNPC_Bug_Builder::Event_Killed( const CTakeDamageInfo &info ) +{ + BaseClass::Event_Killed( info ); + + // Remove myself in a minute + if ( !ShouldFadeOnDeath() ) + { + SetThink( SUB_Remove ); + SetNextThink( gpGlobals->curtime + 20 ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pEvent - +//----------------------------------------------------------------------------- +void CNPC_Bug_Builder::HandleAnimEvent( animevent_t *pEvent ) +{ + BaseClass::HandleAnimEvent( pEvent ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +float CNPC_Bug_Builder::MaxYawSpeed( void ) +{ + return 2.0f; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CNPC_Bug_Builder::IdleSound( void ) +{ + EmitSound( "NPC_Bug_Builder.Idle" ); + m_flIdleDelay = gpGlobals->curtime + 4.0f; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CNPC_Bug_Builder::PainSound( const CTakeDamageInfo &info ) +{ + EmitSound( "NPC_Bug_Builder.Pain" ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CNPC_Bug_Builder::ShouldPlayIdleSound( void ) +{ + //Only do idles in the right states + if ( ( m_NPCState != NPC_STATE_IDLE && m_NPCState != NPC_STATE_ALERT ) ) + return false; + + //Gagged monsters don't talk + if ( HasSpawnFlags( SF_NPC_GAG ) ) + return false; + + //Don't cut off another sound or play again too soon + if ( m_flIdleDelay > gpGlobals->curtime ) + return false; + + //Randomize it a bit + if ( random->RandomInt( 0, 20 ) ) + return false; + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CNPC_Bug_Builder::SetBugHole( CMaker_BugHole *pBugHole ) +{ + m_hMyBugHole = pBugHole; +} + +//----------------------------------------------------------------------------- +// Purpose: BugHole is calling me home to defend it +//----------------------------------------------------------------------------- +void CNPC_Bug_Builder::ReturnToBugHole( void ) +{ + SetCondition( COND_BBUG_RETURN_TO_BUGHOLE ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CNPC_Bug_Builder::AlertSound( void ) +{ + if ( GetEnemy() ) + { + //FIXME: We need a better solution for inner-squad alerts!! + //SOUND_DANGER is designed to frighten NPC's away. Need a different SOUND_ type. + CSoundEnt::InsertSound( SOUND_DANGER, GetEnemy()->GetAbsOrigin(), 1024, 0.5f, this ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Overridden for team handling +//----------------------------------------------------------------------------- +Disposition_t CNPC_Bug_Builder::IRelationType( CBaseEntity *pTarget ) +{ + // Builders ignore everything + return D_NU; +} + + +//----------------------------------------------------------------------------- +// +// Schedules +// +//----------------------------------------------------------------------------- + +//========================================================= +// Dawdle around +//========================================================= +AI_DEFINE_SCHEDULE +( + SCHED_BBUG_DAWDLE, + + " Tasks" + " TASK_SET_TOLERANCE_DISTANCE 32" + " TASK_BBUG_GET_PATH_TO_DAWDLE 0" + " TASK_RUN_PATH 0" + " TASK_WAIT_FOR_MOVEMENT 0" + " TASK_BBUG_FACE_DAWDLE 0" + " " + " Interrupts" + " COND_LIGHT_DAMAGE" + " COND_HEAVY_DAMAGE" +); + +//========================================================= +// Flee from our enemy +//========================================================= +AI_DEFINE_SCHEDULE +( + SCHED_BBUG_FLEE_ENEMY, + + " Tasks" + " TASK_SET_TOLERANCE_DISTANCE 128" + " TASK_BBUG_GET_PATH_TO_FLEE 0" + " TASK_RUN_PATH 0" + " TASK_WAIT_FOR_MOVEMENT 0" + " TASK_TURN_RIGHT 180" + " " + " Interrupts" + " COND_ENEMY_DEAD" + " COND_LOST_ENEMY" +); + +//========================================================= +// Retreat to a bughole +//========================================================= +AI_DEFINE_SCHEDULE +( + SCHED_BBUG_RETURN_TO_BUGHOLE, + + " Tasks" + " TASK_SET_TOLERANCE_DISTANCE 128" + " TASK_BBUG_GET_PATH_TO_BUGHOLE 0" + " TASK_RUN_PATH 0" + " TASK_WAIT_FOR_MOVEMENT 0" + " " + " Interrupts" + " COND_NEW_ENEMY" + " COND_HEAR_COMBAT" + " COND_HEAR_DANGER" +); + +//========================================================= +// Return to a bughole and remove myself +//========================================================= +AI_DEFINE_SCHEDULE +( + SCHED_BBUG_RETURN_TO_BUGHOLE_AND_REMOVE, + + " Tasks" + " TASK_WAIT 5" // Wait for 5-10 seconds to see if anything happens + " TASK_WAIT_RANDOM 5" + " TASK_SET_TOLERANCE_DISTANCE 128" + " TASK_BBUG_GET_PATH_TO_BUGHOLE 0" + " TASK_RUN_PATH 0" + " TASK_WAIT_FOR_MOVEMENT 0" + " TASK_BBUG_HOLE_REMOVE 0" + " " + " Interrupts" + " COND_NEW_ENEMY" + " COND_HEAR_COMBAT" + " COND_HEAR_DANGER" +); + diff --git a/game/server/tf2/npc_bug_builder.h b/game/server/tf2/npc_bug_builder.h new file mode 100644 index 0000000..cda1ead --- /dev/null +++ b/game/server/tf2/npc_bug_builder.h @@ -0,0 +1,69 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef NPC_BUG_BUILDER_H +#define NPC_BUG_BUILDER_H +#ifdef _WIN32 +#pragma once +#endif + +#include "AI_BaseNPC.h" + +#define BUG_BUILDER_MODEL "models/npcs/bugs/bug_builder.mdl" + +class CMaker_BugHole; + +//----------------------------------------------------------------------------- +// Purpose: BUILDER BUG +//----------------------------------------------------------------------------- +class CNPC_Bug_Builder : public CAI_BaseNPC +{ + DECLARE_CLASS( CNPC_Bug_Builder, CAI_BaseNPC ); +public: + CNPC_Bug_Builder( void ); + + virtual void Spawn( void ); + virtual void Precache( void ); + + virtual int SelectSchedule( void ); + virtual void StartTask( const Task_t *pTask ); + virtual void RunTask( const Task_t *pTask ); + + virtual float MaxYawSpeed( void ); + virtual void HandleAnimEvent( animevent_t *pEvent ); + virtual void IdleSound( void ); + virtual void PainSound( const CTakeDamageInfo &info ); + virtual void AlertSound( void ); + virtual bool FValidateHintType(CAI_Hint *pHint); + + virtual bool ShouldPlayIdleSound( void ); + + virtual Class_T Classify( void ) { return CLASS_ANTLION; } + virtual int GetSoundInterests( void ) { return 0; } + + DECLARE_DATADESC(); + + // BugHole handling + void SetBugHole( CMaker_BugHole *pBugHole ); + void ReturnToBugHole( void ); + +private: + virtual Disposition_t IRelationType( CBaseEntity *pTarget ); + + void Event_Killed( const CTakeDamageInfo &info ); + + float m_flIdleDelay; + + float m_flNextDawdle; + + // BugHole handling + CHandle< CMaker_BugHole > m_hMyBugHole; + + DEFINE_CUSTOM_AI; +}; + +#endif // NPC_BUG_BUILDER_H diff --git a/game/server/tf2/npc_bug_hole.cpp b/game/server/tf2/npc_bug_hole.cpp new file mode 100644 index 0000000..98923b3 --- /dev/null +++ b/game/server/tf2/npc_bug_hole.cpp @@ -0,0 +1,392 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Bug hole +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "AI_Task.h" +#include "AI_Default.h" +#include "AI_Schedule.h" +#include "AI_Hull.h" +#include "AI_Hint.h" +#include "activitylist.h" +#include "soundent.h" +#include "game.h" +#include "NPCEvent.h" +#include "tf_player.h" +#include "EntityList.h" +#include "ndebugoverlay.h" +#include "shake.h" +#include "monstermaker.h" +#include "decals.h" +#include "vstdlib/random.h" +#include "tf_obj.h" +#include "engine/IEngineSound.h" +#include "IEffects.h" +#include "npc_bug_warrior.h" +#include "npc_bug_builder.h" +#include "npc_bug_hole.h" + +LINK_ENTITY_TO_CLASS( npc_bughole, CMaker_BugHole ); + +IMPLEMENT_SERVERCLASS_ST(CMaker_BugHole, DT_Maker_BugHole) +END_SEND_TABLE(); + +BEGIN_DATADESC( CMaker_BugHole ) + + DEFINE_KEYFIELD( m_iMaxPool, FIELD_INTEGER, "PoolSize" ), + DEFINE_KEYFIELD( m_flPoolRegenTime, FIELD_FLOAT, "PoolRegen" ), + DEFINE_KEYFIELD( m_flPatrolTime, FIELD_FLOAT, "PatrolTime" ), + DEFINE_KEYFIELD( m_iszPatrolPathName, FIELD_STRING, "PatrolName" ), + DEFINE_KEYFIELD( m_iMaxNumberOfPatrollers, FIELD_INTEGER, "MaxPatrollers" ), + DEFINE_KEYFIELD( m_iMaxNumberOfBuilders, FIELD_INTEGER, "MaxBuilders" ), + +END_DATADESC() + +// Maximum speed at which a bughole thinks. Regen/Spawn times faster than this won't make it work faster. +#define BUGHOLE_THINK_SPEED 3.0 + +static ConVar npc_bughole_health( "npc_bughole_health","300", FCVAR_NONE, "Bug hole's health." ); + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CMaker_BugHole::CMaker_BugHole( void) +{ + m_iszNPCClassname_Warrior = MAKE_STRING( "npc_bug_warrior" ); + m_iszNPCClassname_Builder = MAKE_STRING( "npc_bug_builder" ); + m_iszNPCClassname = m_iszNPCClassname_Warrior; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CMaker_BugHole::Spawn( void ) +{ + // Set these up before calling base spawn + m_spawnflags |= SF_NPCMAKER_INF_CHILD; + m_bDisabled = true; + + // Start with a full pool + m_iPool = m_iMaxPool; + + BaseClass::Spawn(); + + // Bug holes are destroyable + SetSolid( SOLID_BBOX ); + SetModel( "models/npcs/bugs/bug_hole.mdl" ); + m_takedamage = DAMAGE_YES; + m_iHealth = npc_bughole_health.GetInt(); + + // Setup spawn & regen times + m_flNextSpawnTime = 0; + m_flNextRegenTime = gpGlobals->curtime + m_flPoolRegenTime; + m_flNextPatrolTime = gpGlobals->curtime + m_flPatrolTime; + + // Override the base class think, and think with some random so bugholes don't all think at the same time + SetThink ( BugHoleThink ); + SetNextThink( gpGlobals->curtime + BUGHOLE_THINK_SPEED + random->RandomFloat( -0.5, 0.5 ) ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CMaker_BugHole::Precache( void ) +{ + BaseClass::Precache(); + + PrecacheModel( "models/npcs/bugs/bug_hole.mdl" ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CMaker_BugHole::BugHoleThink( void ) +{ + // Regenerate our bug pool + if ( m_flNextRegenTime < gpGlobals->curtime ) + { + if ( m_iPool < m_iMaxPool ) + { + m_iPool++; + } + m_flNextRegenTime += m_flPoolRegenTime; + } + + // Spawn if we're set to + if ( m_flNextSpawnTime && m_flNextSpawnTime < gpGlobals->curtime ) + { + m_flNextSpawnTime = 0; + MakeNPC(); + } + else + { + // If I can see a player, try and spawn a bug + CBaseEntity *pList[100]; + Vector vecDelta( 768, 768, 768 ); + int count = UTIL_EntitiesInBox( pList, 32, GetAbsOrigin() - vecDelta, GetAbsOrigin() + vecDelta, FL_CLIENT|FL_OBJECT ); + for ( int i = 0; i < count; i++ ) + { + CBaseEntity *pEnt = pList[i]; + if ( pEnt->IsAlive() && FVisible(pEnt) ) + { + BugHoleUnderAttack(); + break; + } + } + } + + // Send out a patrol if we haven't spawned anything for a long time + if ( m_flNextPatrolTime < gpGlobals->curtime ) + { + m_flNextPatrolTime = gpGlobals->curtime + m_flPatrolTime; + StartPatrol(); + CheckBuilder(); + } + + SetNextThink( gpGlobals->curtime + BUGHOLE_THINK_SPEED ); +} + +//----------------------------------------------------------------------------- +// Purpose: Spawn a bug, if we're not waiting to spawn one already +//----------------------------------------------------------------------------- +void CMaker_BugHole::SpawnBug( float flTime ) +{ + // If no time was passed in, spawn immediately + if ( !flTime ) + { + MakeNPC(); + } + else if ( !m_flNextSpawnTime ) + { + m_flNextSpawnTime = gpGlobals->curtime + flTime; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CMaker_BugHole::SpawnWarrior( float flTime ) +{ + m_iszNPCClassname = m_iszNPCClassname_Warrior; + SpawnBug( flTime ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CMaker_BugHole::SpawnBuilder( float flTime ) +{ + m_iszNPCClassname = m_iszNPCClassname_Builder; + SpawnBug( flTime ); +} + +//----------------------------------------------------------------------------- +// Purpose: BugHole has spotted a player near it +//----------------------------------------------------------------------------- +void CMaker_BugHole::BugHoleUnderAttack( void ) +{ + // Call any patrollers back to defend the base + for ( int i = 0; i < m_aWarriorBugs.Size(); i++ ) + { + if ( m_aWarriorBugs[i]->IsPatrolling() ) + { + m_aWarriorBugs[i]->ReturnToBugHole(); + } + } + + // Try and spawn a warrior + SpawnWarrior( random->RandomFloat( 1.0, 3.0 ) ); +} + +//----------------------------------------------------------------------------- +// Purpose: Send a bug out to patrol +//----------------------------------------------------------------------------- +void CMaker_BugHole::StartPatrol( void ) +{ + // Don't patrol if I don't have a patrol name + if ( !m_iszPatrolPathName || !m_iMaxNumberOfPatrollers ) + return; + + // If I don't have any children, spawn one for to patrol with + if ( m_nLiveChildren < m_iMaxNumberOfPatrollers ) + { + SpawnWarrior(0); + + // Think again to use the bug we just created + m_flNextPatrolTime = gpGlobals->curtime + 2.0; + } + + // We might have failed due to having none in the pool + if ( !m_aWarriorBugs.Size() ) + return; + + // Count number of bugs patrolling + int i, iCount; + iCount = 0; + for ( i = 0; i < m_aWarriorBugs.Size(); i++ ) + { + if ( m_aWarriorBugs[i]->IsPatrolling() ) + { + iCount++; + } + } + + // Find bugs willing to patrol + for ( i = 0; i < m_aWarriorBugs.Size(); i++ ) + { + // Make sure we don't have too many + if ( iCount >= m_iMaxNumberOfPatrollers ) + return; + + if ( m_aWarriorBugs[i]->StartPatrolling( m_iszPatrolPathName ) ) + { + iCount++; + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: See if we should spawn a builder bug +//----------------------------------------------------------------------------- +void CMaker_BugHole::CheckBuilder( void ) +{ + // If my pool is full, and I have spaw builder spots, spawn a builder bug + /* + if ( m_iPool == m_iMaxPool && m_aBuilderBugs.Size() < m_iMaxNumberOfBuilders ) + { + SpawnBuilder(0); + } + */ +} + +//----------------------------------------------------------------------------- +// Purpose: Bughole makes multiple types of bugs +//----------------------------------------------------------------------------- +void CMaker_BugHole::MakeNPC( void ) +{ + // Don't try and patrol for a while + m_flNextPatrolTime = gpGlobals->curtime + m_flPatrolTime; + + // If my pool is empty, don't spawn a bug + if ( !m_iPool ) + return; + + BaseClass::MakeNPC(); +} + +//----------------------------------------------------------------------------- +// Purpose: Hook to allow bugs to move before they spawn +//----------------------------------------------------------------------------- +void CMaker_BugHole::ChildPreSpawn( CAI_BaseNPC *pChild ) +{ + BaseClass::ChildPreSpawn( pChild ); + + // Drop the bug down and remove it's onground flag + Vector origin = GetLocalOrigin(); + origin.z -= 64; + pChild->SetLocalOrigin( origin ); + pChild->SetGroundEntity( NULL ); +} + +//----------------------------------------------------------------------------- +// Purpose: Hook to allow bugs to pack specific data into their known class fields +// Input : *pChild - pointer to the spawned entity to work on +//----------------------------------------------------------------------------- +void CMaker_BugHole::ChildPostSpawn( CAI_BaseNPC *pChild ) +{ + BaseClass::ChildPostSpawn( pChild ); + + // May be a warrior or a builder + CNPC_Bug_Warrior *pWarrior = dynamic_cast<CNPC_Bug_Warrior*>((CAI_BaseNPC*)pChild); + if ( pWarrior ) + { + pWarrior->SetBugHole( this ); + + // Add him to my bug list + WarriorHandle_t hHandle; + hHandle = pWarrior; + m_aWarriorBugs.AddToTail( hHandle ); + } + else + { + CNPC_Bug_Builder *pBuilder = dynamic_cast<CNPC_Bug_Builder*>((CAI_BaseNPC*)pChild); + ASSERT( pBuilder ); + pBuilder->SetBugHole( this ); + + // Add him to my bug list + BuilderHandle_t hHandle; + hHandle = pBuilder; + m_aBuilderBugs.AddToTail( hHandle ); + } + + m_iPool--; +} + +//----------------------------------------------------------------------------- +// Purpose: Remove the bug from our list of bugs +//----------------------------------------------------------------------------- +void CMaker_BugHole::DeathNotice( CBaseEntity *pVictim ) +{ + BaseClass::DeathNotice( pVictim ); + + // May be a warrior or a builder + CNPC_Bug_Warrior *pWarrior = dynamic_cast<CNPC_Bug_Warrior*>((CAI_BaseNPC*)pVictim); + if ( pWarrior ) + { + // Remove him from my list + WarriorHandle_t hHandle; + hHandle = pWarrior; + m_aWarriorBugs.FindAndRemove( hHandle ); + } + else + { + CNPC_Bug_Builder *pBuilder = dynamic_cast<CNPC_Bug_Builder*>((CAI_BaseNPC*)pVictim); + ASSERT( pBuilder ); + + // Remove him from my list + BuilderHandle_t hHandle; + hHandle = pBuilder; + m_aBuilderBugs.FindAndRemove( hHandle ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: On death, fall to the ground and vanish +//----------------------------------------------------------------------------- +void CMaker_BugHole::Event_Killed( const CTakeDamageInfo &info ) +{ + BaseClass::Event_Killed( info ); + + SetMoveType( MOVETYPE_FLYGRAVITY ); + SetGroundEntity( NULL ); + + SetThink( SUB_Remove ); + SetNextThink( gpGlobals->curtime + 5.0 ); +} + +//----------------------------------------------------------------------------- +// Purpose: A bug is fleeing to me, see if I want to do anything +//----------------------------------------------------------------------------- +void CMaker_BugHole::IncomingFleeingBug( CAI_BaseNPC *pBug ) +{ + SpawnWarrior( random->RandomFloat( 3.0, 5.0 ) ); + + // If I have available warriors, tell them to engage + for ( int i = 0; i < m_aWarriorBugs.Size(); i++ ) + { + m_aWarriorBugs[i]->Assist( pBug ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: One of my bugs has returned to me +//----------------------------------------------------------------------------- +void CMaker_BugHole::BugReturned( void ) +{ + if ( m_iPool < m_iMaxPool ) + { + m_iPool++; + } +} diff --git a/game/server/tf2/npc_bug_hole.h b/game/server/tf2/npc_bug_hole.h new file mode 100644 index 0000000..10dbeec --- /dev/null +++ b/game/server/tf2/npc_bug_hole.h @@ -0,0 +1,76 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef NPC_BUG_HOLE_H +#define NPC_BUG_HOLE_H +#ifdef _WIN32 +#pragma once +#endif + +class CNPC_Bug_Warrior; +class CNPC_Bug_Builder; + +//----------------------------------------------------------------------------- +// Purpose: BUG HOLE +//----------------------------------------------------------------------------- +class CMaker_BugHole : public CNPCMaker +{ + DECLARE_CLASS( CMaker_BugHole, CNPCMaker ); +public: + CMaker_BugHole( void ); + + DECLARE_DATADESC(); + DECLARE_SERVERCLASS(); + + virtual void Spawn( void ); + virtual void Precache( void ); + virtual void MakeNPC( void ); + virtual void ChildPreSpawn( CAI_BaseNPC *pChild ); + virtual void ChildPostSpawn( CAI_BaseNPC *pChild ); + virtual void DeathNotice( CBaseEntity *pVictim ); + virtual void Event_Killed( const CTakeDamageInfo &info ); + + // Bug interactions + void BugHoleThink( void ); + void SpawnBug( float flTime ); + void SpawnWarrior( float flTime ); + void SpawnBuilder( float flTime ); + void BugHoleUnderAttack( void ); + void StartPatrol( void ); + void CheckBuilder( void ); + void IncomingFleeingBug( CAI_BaseNPC *pBug ); + void BugReturned( void ); + +private: + string_t m_iszNPCClassname_Warrior; + string_t m_iszNPCClassname_Builder; + + // Bug pool + int m_iPool; + int m_iMaxPool; + float m_flPoolRegenTime; + + float m_flNextSpawnTime; + float m_flNextRegenTime; + + // Patrols + int m_iMaxNumberOfPatrollers; + float m_flPatrolTime; + float m_flNextPatrolTime; + string_t m_iszPatrolPathName; + + // Builders + int m_iMaxNumberOfBuilders; + + // List of bugs I have out there + typedef CHandle<CNPC_Bug_Warrior> WarriorHandle_t; + CUtlVector<WarriorHandle_t> m_aWarriorBugs; + typedef CHandle<CNPC_Bug_Builder> BuilderHandle_t; + CUtlVector<BuilderHandle_t> m_aBuilderBugs; +}; + +#endif // NPC_BUG_HOLE_H diff --git a/game/server/tf2/npc_bug_warrior.cpp b/game/server/tf2/npc_bug_warrior.cpp new file mode 100644 index 0000000..af900d0 --- /dev/null +++ b/game/server/tf2/npc_bug_warrior.cpp @@ -0,0 +1,1039 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: The warrior bug +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "AI_Task.h" +#include "AI_Default.h" +#include "AI_Schedule.h" +#include "AI_Hull.h" +#include "AI_Hint.h" +#include "AI_Navigator.h" +#include "AI_Memory.h" +#include "AI_Squad.h" +#include "activitylist.h" +#include "soundent.h" +#include "game.h" +#include "NPCEvent.h" +#include "tf_player.h" +#include "EntityList.h" +#include "ndebugoverlay.h" +#include "shake.h" +#include "monstermaker.h" +#include "decals.h" +#include "vstdlib/random.h" +#include "tf_obj.h" +#include "engine/IEngineSound.h" +#include "IEffects.h" +#include "npc_bug_warrior.h" +#include "npc_bug_hole.h" + +ConVar npc_bug_warrior_health( "npc_bug_warrior_health", "150" ); +ConVar npc_bug_warrior_swipe_damage( "npc_bug_warrior_swipe_damage", "20" ); + +BEGIN_DATADESC( CNPC_Bug_Warrior ) + + DEFINE_FIELD( m_flIdleDelay, FIELD_FLOAT ), + +END_DATADESC() + +LINK_ENTITY_TO_CLASS( npc_bug_warrior, CNPC_Bug_Warrior ); +IMPLEMENT_CUSTOM_AI( npc_bug_warrior, CNPC_Bug_Warrior ); + +// Bug interactions +int g_interactionBugSquadAttacking = 0; + +//================================================== +// Bug Conditions +//================================================== +enum BugConditions +{ + COND_WBUG_STOP_FLEEING = LAST_SHARED_CONDITION, + COND_WBUG_RETURN_TO_BUGHOLE, + COND_WBUG_ASSIST_FELLOW_BUG, +}; + +//================================================== +// Bug Schedules +//================================================== + +enum BugSchedules +{ + SCHED_WBUG_CHASE_ENEMY = LAST_SHARED_SCHEDULE, + SCHED_WBUG_FLEE_ENEMY, + SCHED_WBUG_CHASE_ENEMY_FAILED, + SCHED_WBUG_PATROL, + SCHED_WBUG_RETURN_TO_BUGHOLE, + SCHED_WBUG_RETURN_TO_BUGHOLE_AND_REMOVE, + SCHED_WBUG_ASSIST_FELLOW_BUG, +}; + +//================================================== +// Bug Tasks +//================================================== + +enum BugTasks +{ + TASK_WBUG_GET_PATH_TO_FLEE = LAST_SHARED_TASK, + TASK_WBUG_GET_PATH_TO_PATROL, + TASK_WBUG_GET_PATH_TO_BUGHOLE, + TASK_WBUG_HOLE_REMOVE, + TASK_WBUG_GET_PATH_TO_ASSIST, +}; + +//================================================== +// Bug Activities +//================================================== + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CNPC_Bug_Warrior::CNPC_Bug_Warrior( void ) +{ + m_flFieldOfView = 0.5f; + m_flIdleDelay = 0.0f; +} + +//----------------------------------------------------------------------------- +// Purpose: Setup our schedules and tasks, etc. +//----------------------------------------------------------------------------- +void CNPC_Bug_Warrior::InitCustomSchedules( void ) +{ + INIT_CUSTOM_AI( CNPC_Bug_Warrior ); + + // Register our interactions + ADD_CUSTOM_INTERACTION( g_interactionBugSquadAttacking ); + + // Schedules + ADD_CUSTOM_SCHEDULE( CNPC_Bug_Warrior, SCHED_WBUG_CHASE_ENEMY ); + ADD_CUSTOM_SCHEDULE( CNPC_Bug_Warrior, SCHED_WBUG_FLEE_ENEMY ); + ADD_CUSTOM_SCHEDULE( CNPC_Bug_Warrior, SCHED_WBUG_CHASE_ENEMY_FAILED ); + ADD_CUSTOM_SCHEDULE( CNPC_Bug_Warrior, SCHED_WBUG_PATROL ); + ADD_CUSTOM_SCHEDULE( CNPC_Bug_Warrior, SCHED_WBUG_RETURN_TO_BUGHOLE ); + ADD_CUSTOM_SCHEDULE( CNPC_Bug_Warrior, SCHED_WBUG_RETURN_TO_BUGHOLE_AND_REMOVE ); + ADD_CUSTOM_SCHEDULE( CNPC_Bug_Warrior, SCHED_WBUG_ASSIST_FELLOW_BUG ); + + // Conditions + ADD_CUSTOM_CONDITION( CNPC_Bug_Warrior, COND_WBUG_STOP_FLEEING ); + ADD_CUSTOM_CONDITION( CNPC_Bug_Warrior, COND_WBUG_RETURN_TO_BUGHOLE ); + ADD_CUSTOM_CONDITION( CNPC_Bug_Warrior, COND_WBUG_ASSIST_FELLOW_BUG ); + + // Tasks + ADD_CUSTOM_TASK( CNPC_Bug_Warrior, TASK_WBUG_GET_PATH_TO_FLEE ); + ADD_CUSTOM_TASK( CNPC_Bug_Warrior, TASK_WBUG_GET_PATH_TO_PATROL ); + ADD_CUSTOM_TASK( CNPC_Bug_Warrior, TASK_WBUG_GET_PATH_TO_BUGHOLE ); + ADD_CUSTOM_TASK( CNPC_Bug_Warrior, TASK_WBUG_HOLE_REMOVE ); + ADD_CUSTOM_TASK( CNPC_Bug_Warrior, TASK_WBUG_GET_PATH_TO_ASSIST ); + + // Activities + //ADD_CUSTOM_ACTIVITY( CNPC_Bug_Warrior, ACT_BUG_WARRIOR_DISTRACT ); + + AI_LOAD_SCHEDULE( CNPC_Bug_Warrior, SCHED_WBUG_CHASE_ENEMY ); + AI_LOAD_SCHEDULE( CNPC_Bug_Warrior, SCHED_WBUG_FLEE_ENEMY ); + AI_LOAD_SCHEDULE( CNPC_Bug_Warrior, SCHED_WBUG_CHASE_ENEMY_FAILED ); + AI_LOAD_SCHEDULE( CNPC_Bug_Warrior, SCHED_WBUG_PATROL ); + AI_LOAD_SCHEDULE( CNPC_Bug_Warrior, SCHED_WBUG_RETURN_TO_BUGHOLE ); + AI_LOAD_SCHEDULE( CNPC_Bug_Warrior, SCHED_WBUG_RETURN_TO_BUGHOLE_AND_REMOVE ); + AI_LOAD_SCHEDULE( CNPC_Bug_Warrior, SCHED_WBUG_ASSIST_FELLOW_BUG ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CNPC_Bug_Warrior::Spawn( void ) +{ + Precache(); + + SetModel( BUG_WARRIOR_MODEL ); + + SetHullType(HULL_WIDE_HUMAN);//HULL_WIDE_SHORT; + SetHullSizeNormal(); + SetDefaultEyeOffset(); + SetViewOffset( (WorldAlignMins() + WorldAlignMaxs()) * 0.5 ); // See from my center + SetDistLook( 1024.0 ); + + SetNavType(NAV_GROUND); + m_NPCState = NPC_STATE_NONE; + SetBloodColor( BLOOD_COLOR_YELLOW ); + m_iHealth = npc_bug_warrior_health.GetFloat(); + + SetSolid( SOLID_BBOX ); + AddSolidFlags( FSOLID_NOT_STANDABLE ); + SetMoveType( MOVETYPE_STEP ); + + m_iszPatrolPathName = NULL_STRING; + + // Only do this if a squadname appears in the entity + if ( m_SquadName != NULL_STRING ) + { + CapabilitiesAdd( bits_CAP_SQUAD ); + } + + CapabilitiesAdd( bits_CAP_MOVE_GROUND | bits_CAP_INNATE_MELEE_ATTACK1 ); + + NPCInit(); + + BaseClass::Spawn(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CNPC_Bug_Warrior::Precache( void ) +{ + PrecacheModel( BUG_WARRIOR_MODEL ); + + PrecacheScriptSound( "NPC_Bug_Warrior.AttackHit" ); + PrecacheScriptSound( "NPC_Bug_Warrior.AttackSingle" ); + PrecacheScriptSound( "NPC_Bug_Warrior.Idle" ); + PrecacheScriptSound( "NPC_Bug_Warrior.Pain" ); + + BaseClass::Precache(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : int +//----------------------------------------------------------------------------- +int CNPC_Bug_Warrior::SelectSchedule( void ) +{ + ClearCondition( COND_WBUG_STOP_FLEEING ); + + // Turn towards sounds + if ( HasCondition(COND_HEAR_DANGER) || HasCondition(COND_HEAR_COMBAT) ) + { + CSound *pSound = GetBestSound(); + if ( pSound) + { + if ( !HasCondition( COND_SEE_ENEMY ) && ( pSound->m_iType & (SOUND_PLAYER | SOUND_COMBAT) ) ) + { + GetMotor()->SetIdealYawToTarget( pSound->GetSoundReactOrigin() ); + } + } + } + + switch ( m_NPCState ) + { + case NPC_STATE_IDLE: + { + // If I have an enemy, but I don't have any nearby friends, flee + if ( HasCondition( COND_SEE_ENEMY ) && ShouldFlee() ) + { + SetState( NPC_STATE_ALERT ); + return SCHED_WBUG_FLEE_ENEMY; + } + + // Fellow bug might be requesting assistance + if ( HasCondition( COND_WBUG_ASSIST_FELLOW_BUG ) ) + return SCHED_WBUG_ASSIST_FELLOW_BUG; + + // BugHole might be requesting help + if ( HasCondition( COND_WBUG_RETURN_TO_BUGHOLE ) ) + return SCHED_WBUG_RETURN_TO_BUGHOLE; + + // Return to my bughole + return SCHED_WBUG_RETURN_TO_BUGHOLE_AND_REMOVE; + break; + } + case NPC_STATE_ALERT: + { + // If I have an enemy, but I don't have any nearby friends, flee + if ( HasCondition( COND_SEE_ENEMY ) && ShouldFlee() ) + { + SetState( NPC_STATE_ALERT ); + return SCHED_WBUG_FLEE_ENEMY; + } + + // Fellow bug might be requesting assistance + if ( HasCondition( COND_WBUG_ASSIST_FELLOW_BUG ) ) + return SCHED_WBUG_ASSIST_FELLOW_BUG; + + // BugHole might be requesting help + if ( HasCondition( COND_WBUG_RETURN_TO_BUGHOLE ) ) + return SCHED_WBUG_RETURN_TO_BUGHOLE; + + // If I have a patrol path, walk it + if ( m_iszPatrolPathName != NULL_STRING ) + return SCHED_WBUG_PATROL; + + break; + } + case NPC_STATE_COMBAT: + { + // Did I lose my enemy? + if ( HasCondition ( COND_LOST_ENEMY ) || HasCondition ( COND_ENEMY_UNREACHABLE ) ) + { + SetEnemy( NULL ); + m_bFightingToDeath = false; + SetState(NPC_STATE_IDLE); + return BaseClass::SelectSchedule(); + } + + // If I have an enemy, but I don't have any nearby friends, flee + if ( HasCondition( COND_SEE_ENEMY ) && ShouldFlee() ) + return SCHED_WBUG_FLEE_ENEMY; + + // If we're able to melee, do so + if ( HasCondition( COND_CAN_MELEE_ATTACK1 ) ) + return BaseClass::SelectSchedule(); + } + break; + } + + return BaseClass::SelectSchedule(); +} + +//----------------------------------------------------------------------------- +// Purpose: override/translate a schedule by type +// Input : Type - schedule type +// Output : int - translated type +//----------------------------------------------------------------------------- +int CNPC_Bug_Warrior::TranslateSchedule( int scheduleType ) +{ + if ( scheduleType == SCHED_CHASE_ENEMY ) + { + // Tell my squad that I'm attacking this guy + if ( m_pSquad ) + { + m_pSquad->BroadcastInteraction( g_interactionBugSquadAttacking, (void *)GetEnemy(), this ); + } + return SCHED_WBUG_CHASE_ENEMY; + } + + return BaseClass::TranslateSchedule(scheduleType); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pTask - +//----------------------------------------------------------------------------- +void CNPC_Bug_Warrior::StartTask( const Task_t *pTask ) +{ + switch ( pTask->iTask ) + { + case TASK_WBUG_GET_PATH_TO_FLEE: + { + // Always tell our bughole that we're under attack + if ( m_hMyBugHole ) + { + m_hMyBugHole->IncomingFleeingBug( this ); + } + + // We're fleeing from an enemy. + // If I have a squadmate, run to him. + CAI_BaseNPC *pSquadMate; + if ( m_pSquad && (pSquadMate = m_pSquad->NearestSquadMember(this)) != false ) + { + SetTarget( pSquadMate ); + AI_NavGoal_t goal( GOALTYPE_TARGETENT, vec3_origin, ACT_RUN ); + if ( GetNavigator()->SetGoal( goal ) ) + { + TaskComplete(); + return; + } + } + + // If we have no squad, or we couldn't get a path to our squadmate, look for a bughole + if ( m_hMyBugHole ) + { + SetTarget( m_hMyBugHole ); + AI_NavGoal_t goal( GOALTYPE_TARGETENT, vec3_origin, ACT_RUN ); + if ( GetNavigator()->SetGoal( goal ) ) + { + TaskComplete(); + return; + } + } + + // Give up, and fight to the death + m_bFightingToDeath = true; + TaskComplete(); + } + break; + + case TASK_WBUG_GET_PATH_TO_PATROL: + { + // Get a path to the next point in the patrol. + // Abort if we have no patrol path + if ( !m_iszPatrolPathName ) + { + TaskFail( "Attempting to patrol without patrol path." ); + return; + } + + SetHintNode( CAI_HintManager::FindHint( this, HINT_BUG_PATROL_POINT, bits_HINT_NODE_RANDOM, 4096 ) ); + if( !GetHintNode() ) + { + TaskFail("Couldn't find patrol node"); + return; + } + + Vector vHintPos; + GetHintNode()->GetPosition( this, &vHintPos ); + AI_NavGoal_t goal( vHintPos, ACT_RUN ); + GetNavigator()->SetGoal( goal ); + TaskComplete(); + } + break; + + case TASK_WBUG_GET_PATH_TO_BUGHOLE: + { + // Get a path back to my bughole + // If we have no squad, or we couldn't get a path to our squadmate, look for a bughole + if ( m_hMyBugHole ) + { + SetTarget( m_hMyBugHole ); + AI_NavGoal_t goal( GOALTYPE_TARGETENT, vec3_origin, ACT_RUN ); + if ( GetNavigator()->SetGoal( goal ) ) + { + TaskComplete(); + return; + } + } + + TaskFail( "Couldn't get to bughole." ); + } + break; + + case TASK_WBUG_HOLE_REMOVE: + { + // Crawl inside the bughole and remove myself + AddEffects( EF_NODRAW ); + AddSolidFlags( FSOLID_NOT_SOLID ); + Event_Killed( CTakeDamageInfo( this, this, 200, DMG_CRUSH ) ); + + // Tell the bughole + if ( m_hMyBugHole ) + { + m_hMyBugHole->BugReturned(); + } + + TaskComplete(); + } + break; + + case TASK_WBUG_GET_PATH_TO_ASSIST: + { + if ( m_hAssistTarget ) + { + SetTarget( m_hAssistTarget ); + AI_NavGoal_t goal( GOALTYPE_TARGETENT, vec3_origin, ACT_RUN ); + if ( GetNavigator()->SetGoal( goal ) ) + { + TaskComplete(); + return; + } + } + + TaskFail( "Couldn't get to assist target." ); + } + break; + + default: + BaseClass::StartTask( pTask ); + break; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pTask - +//----------------------------------------------------------------------------- +void CNPC_Bug_Warrior::RunTask( const Task_t *pTask ) +{ + BaseClass::RunTask( pTask ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CNPC_Bug_Warrior::FValidateHintType(CAI_Hint *pHint) +{ + if ( pHint->HintType() == HINT_BUG_PATROL_POINT ) + { + // Make sure the name matches the patrol path I'm on + if ( pHint->GetEntityName() == m_iszPatrolPathName ) + return true; + } + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: Handle specific interactions with other NPCs +//----------------------------------------------------------------------------- +bool CNPC_Bug_Warrior::HandleInteraction( int interactionType, void *data, CBaseCombatCharacter *sender ) +{ + // Check for a target found while burrowed + if ( interactionType == g_interactionBugSquadAttacking ) + { + // Ignore ones sent by me + if ( sender == this ) + return false; + + // Interrupt me if I'm fleeing + if ( IsCurSchedule(SCHED_WBUG_FLEE_ENEMY) || + IsCurSchedule(SCHED_WBUG_CHASE_ENEMY_FAILED) ) + { + SetCondition( COND_WBUG_STOP_FLEEING ); + } + + return true; + } + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pVictim - +//----------------------------------------------------------------------------- +void CNPC_Bug_Warrior::Event_Killed( const CTakeDamageInfo &info ) +{ + BaseClass::Event_Killed( info ); + + // Remove myself in a minute + if ( !ShouldFadeOnDeath() ) + { + SetThink( SUB_Remove ); + SetNextThink( gpGlobals->curtime + 20 ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CNPC_Bug_Warrior::MeleeAttack( float distance, float damage, QAngle& viewPunch, Vector& shove ) +{ + if ( GetEnemy() == NULL ) + return; + + // Trace directly at my target + Vector vStart = GetAbsOrigin(); + vStart.z += WorldAlignSize().z * 0.5; + Vector vecForward = (GetEnemy()->EyePosition() - vStart); + VectorNormalize( vecForward ); + Vector vEnd = vStart + (vecForward * distance ); + + // Use half the size of my target for the box + Vector vecHalfTraceBox = (GetEnemy()->WorldAlignMaxs() - GetEnemy()->WorldAlignMins()) * 0.25; + //NDebugOverlay::Box( vStart, -Vector(10,10,10), Vector(10,10,10), 0,255,0,20,1.0); + //NDebugOverlay::Box( GetEnemy()->EyePosition(), -Vector(10,10,10), Vector(10,10,10), 255,255,255,20,1.0); + CBaseEntity *pHurt = CheckTraceHullAttack( vStart, vEnd, -vecHalfTraceBox, vecHalfTraceBox, damage, DMG_SLASH ); + + if ( pHurt ) + { + CBasePlayer *pPlayer = ToBasePlayer( pHurt ); + + if ( pPlayer ) + { + //Kick the player angles + pPlayer->ViewPunch( viewPunch ); + + Vector dir = pHurt->GetAbsOrigin() - GetAbsOrigin(); + VectorNormalize(dir); + + QAngle angles; + VectorAngles( dir, angles ); + Vector forward, right; + AngleVectors( angles, &forward, &right, NULL ); + + // Push the target back + Vector vecImpulse; + VectorMultiply( right, -shove[1], vecImpulse ); + VectorMA( vecImpulse, -shove[0], forward, vecImpulse ); + pHurt->ApplyAbsVelocityImpulse( vecImpulse ); + } + + // Play a random attack hit sound + EmitSound( "NPC_Bug_Warrior.AttackHit" ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pEvent - +//----------------------------------------------------------------------------- +void CNPC_Bug_Warrior::HandleAnimEvent( animevent_t *pEvent ) +{ + switch ( pEvent->event ) + { + case BUG_WARRIOR_AE_MELEE_HIT1: + MeleeAttack( BUG_WARRIOR_MELEE1_RANGE, npc_bug_warrior_swipe_damage.GetFloat(), QAngle( 20.0f, 0.0f, 0.0f ), Vector( -350.0f, 1.0f, 1.0f ) ); + return; + break; + + case BUG_WARRIOR_AE_MELEE_SOUND1: + { + EmitSound( "NPC_Bug_Warrior.AttackSingle" ); + return; + } + break; + } + + BaseClass::HandleAnimEvent( pEvent ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pInflictor - +// *pAttacker - +// flDamage - +// bitsDamageType - +// Output : int +//----------------------------------------------------------------------------- +int CNPC_Bug_Warrior::OnTakeDamage_Alive( const CTakeDamageInfo &info ) +{ + Vector faceDir; + + return BaseClass::OnTakeDamage_Alive( info ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CNPC_Bug_Warrior::IdleSound( void ) +{ + EmitSound( "NPC_Bug_Warrior.Idle" ); + m_flIdleDelay = gpGlobals->curtime + 4.0f; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CNPC_Bug_Warrior::PainSound( const CTakeDamageInfo &info ) +{ + EmitSound( "NPC_Bug_Warrior.Pain" ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : &vecTarget - +// Output : float +//----------------------------------------------------------------------------- +float CNPC_Bug_Warrior::CalcIdealYaw( const Vector &vecTarget ) +{ + //If we can see our enemy but not reach them, face them always + if ( ( GetEnemy() != NULL ) && ( HasCondition( COND_SEE_ENEMY ) && HasCondition( COND_ENEMY_UNREACHABLE ) ) ) + { + return UTIL_VecToYaw ( GetEnemy()->GetAbsOrigin() - GetAbsOrigin() ); + } + + return BaseClass::CalcIdealYaw( vecTarget ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : float +//----------------------------------------------------------------------------- +float CNPC_Bug_Warrior::MaxYawSpeed( void ) +{ + switch ( GetActivity() ) + { + case ACT_IDLE: + return 15.0f; + break; + + case ACT_WALK: + return 15.0f; + break; + + default: + case ACT_RUN: + return 30.0f; + break; + } + + return 30.0f; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CNPC_Bug_Warrior::ShouldPlayIdleSound( void ) +{ + //Only do idles in the right states + if ( ( m_NPCState != NPC_STATE_IDLE && m_NPCState != NPC_STATE_ALERT ) ) + return false; + + //Gagged monsters don't talk + if ( HasSpawnFlags( SF_NPC_GAG ) ) + return false; + + //Don't cut off another sound or play again too soon + if ( m_flIdleDelay > gpGlobals->curtime ) + return false; + + //Randomize it a bit + if ( random->RandomInt( 0, 20 ) ) + return false; + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : flDot - +// flDist - +// Output : int +//----------------------------------------------------------------------------- +int CNPC_Bug_Warrior::MeleeAttack1Conditions( float flDot, float flDist ) +{ + if ( ( gpGlobals->curtime - m_flLastAttackTime ) < 1.0f ) + return 0; + +#if 0 + + float flPrDist, flPrDot; + Vector vecPrPos; + Vector2D vec2DPrDir; + + //Get our likely position in one second + UTIL_GetPredictedPosition( GetEnemy(), 0.5f, &vecPrPos ); + + flPrDist = ( vecPrPos - GetAbsOrigin() ).Length(); + + vec2DPrDir = ( vecPrPos - GetAbsOrigin() ).AsVector2D(); + + Vector vecBodyDir; + + BodyDirection2D( &vecBodyDir ); + + Vector2D vec2DBodyDir = vecBodyDir.AsVector2D(); + + flPrDot = DotProduct2D (vec2DPrDir, vec2DBodyDir ); + + if ( flPrDist > BUG_WARRIOR_MELEE1_RANGE ) + return COND_TOO_FAR_TO_ATTACK; + + if ( flPrDot < 0.5f ) + return COND_NOT_FACING_ATTACK; + +#else + + if ( flDist > BUG_WARRIOR_MELEE1_RANGE ) + return COND_TOO_FAR_TO_ATTACK; + + if ( flDot < 0.5f ) + return COND_NOT_FACING_ATTACK; + +#endif + + return COND_CAN_MELEE_ATTACK1; +} + +//----------------------------------------------------------------------------- +// Purpose: Return true if this bug should flee +//----------------------------------------------------------------------------- +bool CNPC_Bug_Warrior::ShouldFlee( void ) +{ + // I don't flee if I'm fighting to the death + if ( m_bFightingToDeath ) + return false; + + // I don't flee if I'm not alone + if ( !IsAlone() ) + return false; + + // I don't flee if I'm within sight of a bughole + if ( m_hMyBugHole.Get() && FVisible( m_hMyBugHole ) ) + return false; + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CNPC_Bug_Warrior::IsAlone( void ) +{ + // If I'm not in a squad, make me just fight + if ( !m_pSquad ) + return false; + + // If there aren't any visible squad members, I'm alone + if ( m_pSquad->GetVisibleSquadMembers( this ) == 0 ) + return true; + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CNPC_Bug_Warrior::SetBugHole( CMaker_BugHole *pBugHole ) +{ + m_hMyBugHole = pBugHole; +} + +//----------------------------------------------------------------------------- +// Purpose: BugHole is calling me home to defend it +//----------------------------------------------------------------------------- +void CNPC_Bug_Warrior::ReturnToBugHole( void ) +{ + SetCondition( COND_WBUG_RETURN_TO_BUGHOLE ); +} + +//----------------------------------------------------------------------------- +// Purpose: Assist a bug that's under attack and making it's way back to the bughole +//----------------------------------------------------------------------------- +void CNPC_Bug_Warrior::Assist( CAI_BaseNPC *pBug ) +{ + // If I'm not idle, I can't assist + if ( m_NPCState != NPC_STATE_IDLE && m_NPCState != NPC_STATE_ALERT && m_NPCState != NPC_STATE_NONE ) + return; + + m_hAssistTarget = pBug; + SetCondition( COND_WBUG_ASSIST_FELLOW_BUG ); + SetCondition( COND_PROVOKED ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CNPC_Bug_Warrior::StartPatrolling( string_t iszPatrolPathName ) +{ + // If I'm not idle, I can't patrol + if ( m_NPCState != NPC_STATE_IDLE && m_NPCState != NPC_STATE_ALERT && m_NPCState != NPC_STATE_NONE ) + return false; + + // If I'm patrolling already, I can't patrol + if ( IsPatrolling() ) + return false; + + SetState( NPC_STATE_ALERT ); + SetCondition( COND_PROVOKED ); + + // Store off my patrol name + m_iszPatrolPathName = iszPatrolPathName; + m_iPatrolPoint = 0; + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: Return true if this bug is currently patrolling +//----------------------------------------------------------------------------- +bool CNPC_Bug_Warrior::IsPatrolling( void ) +{ + return ( IsCurSchedule(SCHED_WBUG_PATROL) ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CNPC_Bug_Warrior::AlertSound( void ) +{ + if ( GetEnemy() ) + { + //FIXME: We need a better solution for inner-squad alerts!! + //SOUND_DANGER is designed to frighten NPC's away. Need a different SOUND_ type. + CSoundEnt::InsertSound( SOUND_DANGER, GetEnemy()->GetAbsOrigin(), 1024, 0.5f, this ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Overridden for team handling +//----------------------------------------------------------------------------- +Disposition_t CNPC_Bug_Warrior::IRelationType( CBaseEntity *pTarget ) +{ + if ( pTarget->GetFlags() & FL_NOTARGET ) + return D_NU; + + if ( pTarget->Classify() == CLASS_PLAYER ) + { + // Ignore stealthed enemies + CBaseTFPlayer *pPlayer = (CBaseTFPlayer *)pTarget; + if ( pPlayer->IsCamouflaged() ) + return D_NU; + + return D_HT; + } + + // Attack objects + if ( pTarget->Classify() == CLASS_MILITARY ) + return D_HT; + + return D_NU; +} + +//------------------------------------------------------------------------------ +// Purpose : Hate sentryguns more than other types of objects +//------------------------------------------------------------------------------ +int CNPC_Bug_Warrior::IRelationPriority( CBaseEntity *pTarget ) +{ + if ( pTarget->Classify() == CLASS_MILITARY ) + { + CBaseObject* pBaseObject = dynamic_cast<CBaseObject*>(pTarget); + if ( pBaseObject && pBaseObject->IsSentrygun() ) + return ( BaseClass::IRelationPriority ( pTarget ) + 1 ); + } + + return BaseClass::IRelationPriority ( pTarget ); +} + + +//------------------------------------------------------------------------------ +// +// Schedules +// +//------------------------------------------------------------------------------ + +//========================================================= +// Chase Enemy +//========================================================= +AI_DEFINE_SCHEDULE +( + SCHED_WBUG_CHASE_ENEMY, + + " Tasks" + //" TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_WBUG_CHASE_ENEMY_FAILED" + " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_CHASE_ENEMY_FAILED" + " TASK_SET_TOLERANCE_DISTANCE 24" + " TASK_GET_PATH_TO_ENEMY 0" + " TASK_RUN_PATH 0" + " TASK_WAIT_FOR_MOVEMENT 0" + "" + " Interrupts" + " COND_NEW_ENEMY" + " COND_ENEMY_DEAD" + " COND_ENEMY_UNREACHABLE" + " COND_CAN_MELEE_ATTACK1" + " COND_CAN_MELEE_ATTACK2" + " COND_GIVE_WAY" + " COND_LOST_ENEMY" +); + +//========================================================= +// Failed to chase my enemy +//========================================================= +AI_DEFINE_SCHEDULE +( + SCHED_WBUG_CHASE_ENEMY_FAILED, + + " Tasks" + " TASK_STOP_MOVING 0" + " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE" + " TASK_WAIT_FACE_ENEMY 0.5" + "" + " Interrupts" + " COND_NEW_ENEMY" + " COND_ENEMY_DEAD" + " COND_HEAR_COMBAT" + " COND_HEAR_DANGER" + " COND_CAN_RANGE_ATTACK1" + " COND_CAN_MELEE_ATTACK1" + " COND_CAN_RANGE_ATTACK2" + " COND_CAN_MELEE_ATTACK2" + " COND_WBUG_STOP_FLEEING" +); + +//========================================================= +// Flee from our enemy +//========================================================= +AI_DEFINE_SCHEDULE +( + SCHED_WBUG_FLEE_ENEMY, + + " Tasks" + " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_WBUG_CHASE_ENEMY" + " TASK_SET_TOLERANCE_DISTANCE 128" + " TASK_WBUG_GET_PATH_TO_FLEE 0" + " TASK_RUN_PATH 0" + " TASK_WAIT_FOR_MOVEMENT 0" + " TASK_TURN_RIGHT 180" + "" + " Interrupts" + " COND_ENEMY_DEAD" + " COND_WBUG_STOP_FLEEING" + " COND_LOST_ENEMY" +); + +//========================================================= +// Walk a set of patrol points +//========================================================= +AI_DEFINE_SCHEDULE +( + SCHED_WBUG_PATROL, + + " Tasks" + //" TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_WBUG_CHASE_ENEMY" + " TASK_SET_TOLERANCE_DISTANCE 256" + " TASK_WBUG_GET_PATH_TO_PATROL 0" + " TASK_RUN_PATH 0" + " TASK_WAIT_FOR_MOVEMENT 0" + " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE" // Use look around + " TASK_WAIT 2" + " TASK_WAIT_RANDOM 4" + "" + " Interrupts" + " COND_NEW_ENEMY" + " COND_HEAR_COMBAT" + " COND_HEAR_DANGER" + " COND_WBUG_RETURN_TO_BUGHOLE" +); + +//========================================================= +// Return to defend a bughole +//========================================================= +AI_DEFINE_SCHEDULE +( + SCHED_WBUG_RETURN_TO_BUGHOLE, + + " Tasks" + " TASK_SET_TOLERANCE_DISTANCE 128" + " TASK_WBUG_GET_PATH_TO_BUGHOLE 0" + " TASK_RUN_PATH 0" + " TASK_WAIT_FOR_MOVEMENT 0" + "" + " Interrupts" + " COND_NEW_ENEMY" + " COND_HEAR_COMBAT" + " COND_HEAR_DANGER" +); + +//========================================================= +// Return to a bughole and remove myself +//========================================================= +AI_DEFINE_SCHEDULE +( + SCHED_WBUG_RETURN_TO_BUGHOLE_AND_REMOVE, + + " Tasks" + " TASK_WAIT 5" // Wait for 5-10 seconds to see if anything happens + " TASK_WAIT_RANDOM 5" + " TASK_SET_TOLERANCE_DISTANCE 128" + " TASK_WBUG_GET_PATH_TO_BUGHOLE 0" + " TASK_RUN_PATH 0" + " TASK_WAIT_FOR_MOVEMENT 0" + " TASK_WBUG_HOLE_REMOVE 0" + "" + " Interrupts" + " COND_NEW_ENEMY" + " COND_HEAR_COMBAT" + " COND_HEAR_DANGER" +); + +//========================================================= +// Move to assist a fellow bug +//========================================================= +AI_DEFINE_SCHEDULE +( + SCHED_WBUG_ASSIST_FELLOW_BUG, + + " Tasks" + " TASK_SET_TOLERANCE_DISTANCE 128" + " TASK_WBUG_GET_PATH_TO_ASSIST 0" + " TASK_RUN_PATH 0" + " TASK_WAIT_FOR_MOVEMENT 0" + "" + " Interrupts" + " COND_NEW_ENEMY" + " COND_CAN_MELEE_ATTACK1" + " COND_CAN_MELEE_ATTACK2" + " COND_GIVE_WAY" +);
\ No newline at end of file diff --git a/game/server/tf2/npc_bug_warrior.h b/game/server/tf2/npc_bug_warrior.h new file mode 100644 index 0000000..aef1922 --- /dev/null +++ b/game/server/tf2/npc_bug_warrior.h @@ -0,0 +1,102 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef NPC_BUG_WARRIOR_H +#define NPC_BUG_WARRIOR_H +#ifdef _WIN32 +#pragma once +#endif + +#include "AI_BaseNPC.h" + +// Animation events +#define BUG_WARRIOR_AE_MELEE_SOUND1 11 // Start attack sound +#define BUG_WARRIOR_AE_MELEE_HIT1 15 // Melee hit, one arm + +// Attack range definitions +#define BUG_WARRIOR_MELEE1_RANGE 128.0f +#define BUG_WARRIOR_MELEE1_RANGE_MIN 128.0f + +#define BUG_WARRIOR_MODEL "models/npcs/bugs/bug_warrior.mdl" + +class CMaker_BugHole; + +//----------------------------------------------------------------------------- +// Purpose: WARRIOR BUG +//----------------------------------------------------------------------------- +class CNPC_Bug_Warrior : public CAI_BaseNPC +{ + DECLARE_CLASS( CNPC_Bug_Warrior, CAI_BaseNPC ); +public: + CNPC_Bug_Warrior( void ); + + virtual void Spawn( void ); + virtual void Precache( void ); + + virtual int SelectSchedule( void ); + virtual int TranslateSchedule( int scheduleType ); + virtual void StartTask( const Task_t *pTask ); + virtual void RunTask( const Task_t *pTask ); + virtual bool FValidateHintType(CAI_Hint *pHint); + + virtual void HandleAnimEvent( animevent_t *pEvent ); + virtual void IdleSound( void ); + virtual void PainSound( const CTakeDamageInfo &info ); + virtual void AlertSound( void ); + virtual bool HandleInteraction( int interactionType, void *data, CBaseCombatCharacter *sender ); + + virtual bool ShouldPlayIdleSound( void ); + + virtual int MeleeAttack1Conditions( float flDot, float flDist ); + virtual int OnTakeDamage_Alive( const CTakeDamageInfo &info ); + + virtual Class_T Classify( void ) { return CLASS_ANTLION; } + + virtual float MaxYawSpeed ( void ); + virtual float CalcIdealYaw( const Vector &vecTarget ); + + DECLARE_DATADESC(); + + // Returns true if the bug should flee + bool ShouldFlee( void ); + + // Squad handling + bool IsAlone( void ); + + // BugHole handling + void SetBugHole( CMaker_BugHole *pBugHole ); + void ReturnToBugHole( void ); + void Assist( CAI_BaseNPC *pBug ); + + // Patrolling + bool StartPatrolling( string_t iszPatrolPathName ); + bool IsPatrolling( void ); + +private: + virtual Disposition_t IRelationType( CBaseEntity *pTarget ); + virtual int IRelationPriority( CBaseEntity *pTarget ); + + void Event_Killed( const CTakeDamageInfo &info ); + void MeleeAttack( float distance, float damage, QAngle& viewPunch, Vector& shove ); + + float m_flIdleDelay; + + // BugHole handling + CHandle< CMaker_BugHole > m_hMyBugHole; + CHandle< CAI_BaseNPC > m_hAssistTarget; + + // Patrolling + string_t m_iszPatrolPathName; + int m_iPatrolPoint; + + // Bug's decided he's not fleeing anymore + bool m_bFightingToDeath; + + DEFINE_CUSTOM_AI; +}; + +#endif // NPC_BUG_WARRIOR_H diff --git a/game/server/tf2/order_assist.cpp b/game/server/tf2/order_assist.cpp new file mode 100644 index 0000000..0a9f12e --- /dev/null +++ b/game/server/tf2/order_assist.cpp @@ -0,0 +1,177 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" +#include "order_assist.h" +#include "tf_team.h" +#include "order_helpers.h" + + +// If a player has been shot within this time delta, commandos will get orders to assist. +#define COMMANDO_ASSIST_SHOT_DELAY 3.0f + +// How close a commando has to be to a teammate to get an assist order. +#define COMMAND_ASSIST_DISTANCE 1200 +#define COMMAND_ASSIST_DISTANCE_SQR (COMMAND_ASSIST_DISTANCE*COMMAND_ASSIST_DISTANCE) + + + +IMPLEMENT_SERVERCLASS_ST( COrderAssist, DT_OrderAssist ) +END_SEND_TABLE() + + +static bool IsValidFn_OnEnemyTeam( void *pUserData, int a ) +{ + edict_t *pEdict = engine->PEntityOfEntIndex( a+1 ); + if ( !pEdict ) + return false; + + CBaseEntity *pBaseEntity = CBaseEntity::Instance( pEdict ); + if ( !pBaseEntity ) + return false; + + CSortBase *p = (CSortBase*)pUserData; + return pBaseEntity->GetTeam() != p->m_pPlayer->GetTeam(); +} + + +static bool IsValidFn_PlayersWantingAssist( void *pUserData, int a ) +{ + CSortBase *pSortBase = (CSortBase*)pUserData; + CBaseTFPlayer *pPlayer = (CBaseTFPlayer*)pSortBase->m_pPlayer->GetTeam()->GetPlayer( a ); + + if ( !pPlayer->IsAlive() ) + { + // This guy sure could have used an assist but YOU'RE TOO SLOW!!! + return false; + } + + // Don't try to assist yourself... + if ( pPlayer == pSortBase->m_pPlayer ) + return false; + + // Make sure this guy was shot recently. + if ( (gpGlobals->curtime - pPlayer->LastTimeDamagedByEnemy()) > COMMANDO_ASSIST_SHOT_DELAY ) + return false; + + // Is the guy close enough? + if ( pSortBase->m_pPlayer->GetAbsOrigin().DistToSqr( pPlayer->GetAbsOrigin() ) > COMMAND_ASSIST_DISTANCE_SQR ) + return false; + + return true; +} + + +bool COrderAssist::CreateOrder( CPlayerClass *pClass ) +{ + // Search for a (live) nearby player who's just been shot. + CSortBase info; + info.m_pPlayer = pClass->GetPlayer(); + + int sorted[512]; + int nSorted = BuildSortedActiveList( + sorted, + ARRAYSIZE( sorted ), + SortFn_TeamPlayersByDistance, + IsValidFn_PlayersWantingAssist, + &info, + pClass->GetTeam()->GetNumPlayers() + ); + + if ( nSorted ) + { + COrderAssist *pOrder = new COrderAssist; + + CBaseTFPlayer *pPlayerToAssist = (CBaseTFPlayer*)pClass->GetTeam()->GetPlayer( sorted[0] ); + + pClass->GetTeam()->AddOrder( + ORDER_ASSIST, + pPlayerToAssist, + info.m_pPlayer, + COMMAND_ASSIST_DISTANCE, + 25, + pOrder ); + + // Add the closest enemies. + CSortBase enemySortInfo; + enemySortInfo.m_pPlayer = pPlayerToAssist; + + int sortedEnemies[256]; + int nSortedEnemies = BuildSortedActiveList( + sortedEnemies, + ARRAYSIZE( sortedEnemies ), + SortFn_PlayerEntitiesByDistance, + IsValidFn_OnEnemyTeam, + &info, + gpGlobals->maxClients + ); + + nSortedEnemies = MIN( nSortedEnemies, NUM_ASSIST_ENEMIES ); + for ( int i=0; i < nSortedEnemies; i++ ) + { + CBaseEntity *pEnt = CBaseEntity::Instance( engine->PEntityOfEntIndex( sortedEnemies[i] + 1 ) ); + Assert( dynamic_cast<CBasePlayer*>( pEnt ) ); + pOrder->m_Enemies[i] = pEnt; + } + } + + return false; +} + + +bool COrderAssist::Update() +{ + if ( !GetTargetEntity() || !GetTargetEntity()->IsAlive() ) + return true; + + return BaseClass::Update(); +} + + +bool COrderAssist::UpdateOnEvent( COrderEvent_Base *pEvent ) +{ + if ( !GetTargetEntity() ) + return true; + + switch( pEvent->GetType() ) + { + // If our boy dies, then he doesn't care about assistance anymore. + case ORDER_EVENT_PLAYER_KILLED: + { + COrderEvent_PlayerKilled *pKilled = (COrderEvent_PlayerKilled*)pEvent; + if ( pKilled->m_pPlayer == GetTargetEntity() ) + return true; + } + break; + + // Did we damage one of the enemies? + case ORDER_EVENT_PLAYER_DAMAGED: + { + COrderEvent_PlayerDamaged *pPlayerDamaged = (COrderEvent_PlayerDamaged*)pEvent; + if ( pPlayerDamaged->m_TakeDamageInfo.GetInflictor() == GetOwner() ) + { + for ( int i=0; i < NUM_ASSIST_ENEMIES; i++ ) + { + if ( pPlayerDamaged->m_pPlayerDamaged == m_Enemies[i].Get() ) + { + // Reset the timer on the guy we're defending, in case we killed his + // attacker really quickly. +// CBaseTFPlayer *pPlayer = (CBaseTFPlayer*)GetTargetEntity(); +// pPlayer->m_flLastTimeDamagedByEnemy = 0; + + return true; + } + } + } + } + break; + } + + return BaseClass::UpdateOnEvent( pEvent ); +} + + diff --git a/game/server/tf2/order_assist.h b/game/server/tf2/order_assist.h new file mode 100644 index 0000000..821fab7 --- /dev/null +++ b/game/server/tf2/order_assist.h @@ -0,0 +1,51 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef TF_ORDER_ASSIST_H +#define TF_ORDER_ASSIST_H +#ifdef _WIN32 +#pragma once +#endif + + +#include "order_player.h" + + +class CPlayerClass; + + +class COrderAssist : public COrderPlayer +{ +public: + DECLARE_CLASS( COrderAssist, COrderPlayer ); + DECLARE_SERVERCLASS(); + + // Create an order for the player. + static bool CreateOrder( CPlayerClass *pClass ); + + +// COrder overrides. +public: + + virtual bool Update(); + virtual bool UpdateOnEvent( COrderEvent_Base *pEvent ); + + +private: + + enum + { + NUM_ASSIST_ENEMIES = 2 + }; + + // The order goes away when the player who has the assist order shoots + // one of these enemies. + CHandle<CBaseEntity> m_Enemies[NUM_ASSIST_ENEMIES]; +}; + + +#endif // TF_ORDER_ASSIST_H diff --git a/game/server/tf2/order_buildsentrygun.cpp b/game/server/tf2/order_buildsentrygun.cpp new file mode 100644 index 0000000..241947c --- /dev/null +++ b/game/server/tf2/order_buildsentrygun.cpp @@ -0,0 +1,55 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" +#include "order_buildsentrygun.h" +#include "tf_class_defender.h" +#include "tf_team.h" +#include "order_helpers.h" + + +// The defender will get orders to cover objects with sentry guns this far away. +#define SENTRYGUN_ORDER_DIST 3500 + + +IMPLEMENT_SERVERCLASS_ST( COrderBuildSentryGun, DT_OrderBuildSentryGun ) +END_SEND_TABLE() + + +bool COrderBuildSentryGun::CreateOrder( CPlayerClassDefender *pClass ) +{ + if ( !pClass->CanBuildSentryGun() ) + return false; + + COrderBuildSentryGun *pOrder = new COrderBuildSentryGun; + if ( OrderCreator_GenericObject( pClass, OBJ_SENTRYGUN_PLASMA, SENTRYGUN_ORDER_DIST, pOrder ) ) + { + return true; + } + else + { + UTIL_RemoveImmediate( pOrder ); + return false; + } +} + + +bool COrderBuildSentryGun::Update() +{ + // If the entity we were supposed to cover with the sentry gun is covered now, + // then the order is done. + CBaseEntity *pEnt = GetTargetEntity(); + if( !pEnt || !m_hOwningPlayer.Get() ) + return true; + + CTFTeam *pTeam = m_hOwningPlayer->GetTFTeam(); + if( pTeam->IsCoveredBySentryGun( pEnt->GetAbsOrigin() ) ) + return true; + + return false; +} + diff --git a/game/server/tf2/order_buildsentrygun.h b/game/server/tf2/order_buildsentrygun.h new file mode 100644 index 0000000..86307cc --- /dev/null +++ b/game/server/tf2/order_buildsentrygun.h @@ -0,0 +1,38 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef ORDER_BUILDSENTRYGUN_H +#define ORDER_BUILDSENTRYGUN_H +#ifdef _WIN32 +#pragma once +#endif + + +#include "orders.h" + + +class CPlayerClassDefender; + + +class COrderBuildSentryGun : public COrder +{ +public: + DECLARE_CLASS( COrderBuildSentryGun, COrder ); + DECLARE_SERVERCLASS(); + + // Create an order for the player. + static bool CreateOrder( CPlayerClassDefender *pClass ); + + +// COrder overrides. +public: + + virtual bool Update( void ); +}; + + +#endif // ORDER_BUILDSENTRYGUN_H diff --git a/game/server/tf2/order_buildshieldwall.cpp b/game/server/tf2/order_buildshieldwall.cpp new file mode 100644 index 0000000..f561f5b --- /dev/null +++ b/game/server/tf2/order_buildshieldwall.cpp @@ -0,0 +1,51 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" +#include "order_buildshieldwall.h" +#include "tf_team.h" +#include "order_helpers.h" + + +// The defender will get orders to cover objects with sentry guns this far away. +#define SANDBAG_ORDER_DIST 3500 + + +IMPLEMENT_SERVERCLASS_ST( COrderBuildShieldWall, DT_OrderBuildShieldWall ) +END_SEND_TABLE() + + +bool COrderBuildShieldWall::CreateOrder( CPlayerClass *pClass ) +{ + COrderBuildShieldWall *pOrder = new COrderBuildShieldWall; + if ( OrderCreator_GenericObject( pClass, OBJ_SHIELDWALL, 2000, pOrder ) ) + { + return true; + } + else + { + UTIL_RemoveImmediate( pOrder ); + return false; + } +} + + +bool COrderBuildShieldWall::Update() +{ + CBaseEntity *pEnt = GetTargetEntity(); + if( !pEnt ) + return true; + + if ( !m_hOwningPlayer.Get() || m_hOwningPlayer->CanBuild( OBJ_SHIELDWALL ) != CB_CAN_BUILD ) + return true; + + CTFTeam *pTeam = m_hOwningPlayer->GetTFTeam(); + if ( pTeam->GetNumShieldWallsCoveringPosition( pEnt->GetAbsOrigin() ) ) + return true; + + return false; +} diff --git a/game/server/tf2/order_buildshieldwall.h b/game/server/tf2/order_buildshieldwall.h new file mode 100644 index 0000000..5a8bd7b --- /dev/null +++ b/game/server/tf2/order_buildshieldwall.h @@ -0,0 +1,35 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef ORDER_BUILDSHIELDWALL_H +#define ORDER_BUILDSHIELDWALL_H +#ifdef _WIN32 +#pragma once +#endif + + +#include "orders.h" + + +class COrderBuildShieldWall : public COrder +{ +public: + DECLARE_CLASS( COrderBuildShieldWall, COrder ); + DECLARE_SERVERCLASS(); + + // Create an order for the player. + static bool CreateOrder( CPlayerClass *pClass ); + + +// COrder overrides. +public: + + virtual bool Update( void ); +}; + + +#endif // ORDER_BUILDSHIELDWALL_H diff --git a/game/server/tf2/order_events.cpp b/game/server/tf2/order_events.cpp new file mode 100644 index 0000000..617126a --- /dev/null +++ b/game/server/tf2/order_events.cpp @@ -0,0 +1,26 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// + + +#include "cbase.h" + +#include "order_events.h" +#include "tf_team.h" + +//----------------------------------------------------------------------------- +// Purpose: Fire an event for all teams telling them to update their orders +//----------------------------------------------------------------------------- +void GlobalOrderEvent( COrderEvent_Base *pOrder ) +{ + // Loop through the teams + for ( int i = 0; i < GetNumberOfTeams(); i++ ) + { + CTFTeam *pTeam = GetGlobalTFTeam( i ); + pTeam->UpdateOrdersOnEvent( pOrder ); + } +} diff --git a/game/server/tf2/order_events.h b/game/server/tf2/order_events.h new file mode 100644 index 0000000..20d0864 --- /dev/null +++ b/game/server/tf2/order_events.h @@ -0,0 +1,110 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef TF_ORDER_EVENTS_H +#define TF_ORDER_EVENTS_H +#ifdef _WIN32 +#pragma once +#endif + + +#include "tf_player.h" + + +//----------------------------------------------------------------------------- +// ORDER EVENTS +//----------------------------------------------------------------------------- + +typedef enum +{ + ORDER_EVENT_PLAYER_DISCONNECTED, // COrderEvent_PlayerDisconnected + ORDER_EVENT_PLAYER_KILLED, // CorderEvent_PlayerKilled + ORDER_EVENT_PLAYER_RESPAWNED, // COrderEvent_PlayerRespawned + ORDER_EVENT_OBJECT_DESTROYED, // COrderEvent_ObjectDestroyed + ORDER_EVENT_PLAYER_DAMAGED // COrderEvent_PlayerDamaged +} OrderEventType; + + +abstract_class COrderEvent_Base +{ +public: + virtual OrderEventType GetType() = 0; +}; + + +// Fire a global order event. It goes to all orders so they can determine if +// they want to react. +void GlobalOrderEvent( COrderEvent_Base *pOrder ); + + +class COrderEvent_PlayerDisconnected : public COrderEvent_Base +{ +public: + COrderEvent_PlayerDisconnected( CBaseEntity *pPlayer ) + { + m_pPlayer = pPlayer; + } + + virtual OrderEventType GetType() { return ORDER_EVENT_PLAYER_DISCONNECTED; } + + CBaseEntity *m_pPlayer; +}; + + +class COrderEvent_PlayerKilled : public COrderEvent_Base +{ +public: + COrderEvent_PlayerKilled( CBaseEntity *pPlayer ) + { + m_pPlayer = pPlayer; + } + + virtual OrderEventType GetType() { return ORDER_EVENT_PLAYER_KILLED; } + + CBaseEntity *m_pPlayer; +}; + + +class COrderEvent_PlayerRespawned : public COrderEvent_Base +{ +public: + COrderEvent_PlayerRespawned( CBaseEntity *pPlayer ) + { + m_pPlayer = pPlayer; + } + + virtual OrderEventType GetType() { return ORDER_EVENT_PLAYER_RESPAWNED; } + + CBaseEntity *m_pPlayer; +}; + + +class COrderEvent_ObjectDestroyed : public COrderEvent_Base +{ +public: + COrderEvent_ObjectDestroyed( CBaseEntity *pObj ) + { + m_pObject = pObj; + } + + virtual OrderEventType GetType() { return ORDER_EVENT_OBJECT_DESTROYED; } + + CBaseEntity *m_pObject; +}; + + +class COrderEvent_PlayerDamaged : public COrderEvent_Base +{ +public: + virtual OrderEventType GetType() { return ORDER_EVENT_PLAYER_DAMAGED; } + + CBaseEntity *m_pPlayerDamaged; + CTakeDamageInfo m_TakeDamageInfo; +}; + + +#endif // TF_ORDER_EVENTS_H diff --git a/game/server/tf2/order_heal.cpp b/game/server/tf2/order_heal.cpp new file mode 100644 index 0000000..2e6631b --- /dev/null +++ b/game/server/tf2/order_heal.cpp @@ -0,0 +1,111 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" +#include "order_heal.h" +#include "tf_team.h" +#include "tf_playerclass.h" +#include "order_helpers.h" + + +#define MAX_HEAL_DIST 1500 + +IMPLEMENT_SERVERCLASS_ST( COrderHeal, DT_OrderHeal ) +END_SEND_TABLE() + + +bool IsValidFn_Heal( void *pUserData, int a ) +{ + // Can't heal dead players. + CSortBase *p = (CSortBase*)pUserData; + CBasePlayer *pPlayer = p->m_pPlayer->GetTeam()->GetPlayer( a ); + + // Can't heal yourself... + if (p->m_pPlayer == pPlayer) + return false; + + // Don't heal players that are too far away... + const Vector &vPlayer = p->m_pPlayer->GetAbsOrigin(); + if (vPlayer.DistToSqr(pPlayer->GetAbsOrigin()) > MAX_HEAL_DIST * MAX_HEAL_DIST ) + return false; + + return pPlayer->IsAlive() && pPlayer->m_iHealth < pPlayer->m_iMaxHealth; +} + + +int SortFn_Heal( void *pUserData, int a, int b ) +{ + CSortBase *p = (CSortBase*)pUserData; + + const Vector &vPlayer = p->m_pPlayer->GetAbsOrigin(); + const Vector &va = p->m_pPlayer->GetTeam()->GetPlayer( a )->GetAbsOrigin(); + const Vector &vb = p->m_pPlayer->GetTeam()->GetPlayer( b )->GetAbsOrigin(); + + return vPlayer.DistToSqr( va ) < vPlayer.DistToSqr( vb ); +} + + +bool COrderHeal::CreateOrder( CPlayerClass *pClass ) +{ + CTFTeam *pTeam = pClass->GetTeam(); + + CSortBase info; + info.m_pPlayer = pClass->GetPlayer(); + + int sorted[MAX_PLAYERS]; + int nSorted = BuildSortedActiveList( + sorted, + MAX_PLAYERS, + SortFn_Heal, + IsValidFn_Heal, + &info, + pTeam->GetNumPlayers() + ); + + if ( nSorted ) + { + COrderHeal *pOrder = new COrderHeal; + + pClass->GetTeam()->AddOrder( + ORDER_HEAL, + pTeam->GetPlayer( sorted[0] ), + pClass->GetPlayer(), + 1e24, + 60, + pOrder ); + + return true; + } + else + { + return false; + } +} + + +bool COrderHeal::Update() +{ + CBaseEntity *pTarget = GetTargetEntity(); + if ( !pTarget || pTarget->m_iHealth >= pTarget->m_iMaxHealth ) + return true; + + return false; +} + + +bool COrderHeal::UpdateOnEvent( COrderEvent_Base *pEvent ) +{ + if ( pEvent->GetType() == ORDER_EVENT_PLAYER_KILLED ) + { + COrderEvent_PlayerKilled *pKilled = (COrderEvent_PlayerKilled*)pEvent; + if ( pKilled->m_pPlayer == GetTargetEntity() ) + return true; + } + + return BaseClass::UpdateOnEvent( pEvent ); +} + diff --git a/game/server/tf2/order_heal.h b/game/server/tf2/order_heal.h new file mode 100644 index 0000000..28271b2 --- /dev/null +++ b/game/server/tf2/order_heal.h @@ -0,0 +1,39 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef ORDER_HEAL_H +#define ORDER_HEAL_H +#ifdef _WIN32 +#pragma once +#endif + + +#include "order_player.h" + + +class CPlayerClass; + + +class COrderHeal : public COrderPlayer +{ +public: + DECLARE_CLASS( COrderHeal, COrderPlayer ); + DECLARE_SERVERCLASS(); + + // Create an order for the player. + static bool CreateOrder( CPlayerClass *pClass ); + + +// COrder overrides. +public: + + virtual bool Update(); + virtual bool UpdateOnEvent( COrderEvent_Base *pEvent ); +}; + + +#endif // ORDER_HEAL_H diff --git a/game/server/tf2/order_helpers.cpp b/game/server/tf2/order_helpers.cpp new file mode 100644 index 0000000..56274ce --- /dev/null +++ b/game/server/tf2/order_helpers.cpp @@ -0,0 +1,345 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" + +#include "order_helpers.h" +#include "tf_team.h" +#include "tf_func_resource.h" +#include "tf_obj.h" + + +// ------------------------------------------------------------------------ // +// CSortBase implementation. +// ------------------------------------------------------------------------ // + +CSortBase::CSortBase() +{ + m_pPlayer = 0; + m_pTeam = 0; +} + + +CTFTeam* CSortBase::GetTeam() +{ + if ( m_pTeam ) + return m_pTeam; + else + return m_pPlayer->GetTFTeam(); +} + + + +// ------------------------------------------------------------------------ // +// Global functions. +// ------------------------------------------------------------------------ // + +int SortFn_TeamPlayersByDistance( void *pUserData, int a, int b ) +{ + CSortBase *p = (CSortBase*)pUserData; + + const Vector &vPlayer = p->m_pPlayer->GetAbsOrigin(); + const Vector &va = p->m_pPlayer->GetTeam()->GetPlayer( a )->GetAbsOrigin(); + const Vector &vb = p->m_pPlayer->GetTeam()->GetPlayer( b )->GetAbsOrigin(); + + return vPlayer.DistTo( va ) < vPlayer.DistTo( vb ); +} + + +// This is a generic function that takes a number of items and builds a sorted +// list of the valid items. +int BuildSortedActiveList( + int *pList, // This is the list where the final data is placed. + int nMaxItems, + sortFn pSortFn, // Callbacks. + isValidFn pIsValidFn, // This can be null, in which case all items are valid. + void *pUserData, // Passed into the function pointers. + int nItems // Number of items in the list to sort. + ) +{ + // First build the list of active items. + if( nItems > nMaxItems ) + nItems = nMaxItems; + + int nActive = 0; + for( int i=0; i < nItems; i++ ) + { + if( pIsValidFn ) + { + if( !pIsValidFn( pUserData, i ) ) + continue; + } + + int j; + for( j=0; j < nActive; j++ ) + { + Assert( pList[j] < nItems ); + if( pSortFn( pUserData, i, pList[j] ) > 0 ) + { + break; + } + } + + // Slide everything up. + if( nActive ) + { + Q_memmove( &pList[j+1], &pList[j], (nActive-j) * sizeof(int) ); + } + + // Add the new item to the list. + pList[j] = i; + ++nActive; + + for (int l = 0; l < nActive ; ++l ) + { + Assert( pList[l] < nItems ); + } + + } + + return nActive; +} + + +// Finds the closest resource zone without the specified object on it and +// gives an order to the player to build the object. +bool OrderCreator_ResourceZoneObject( + CBaseTFPlayer *pPlayer, + int objType, + COrder *pOrder + ) +{ + // Can we even build a resource box? + if ( pPlayer->CanBuild( objType ) != CB_CAN_BUILD ) + return false; + + CTFTeam *pTeam = pPlayer->GetTFTeam(); + if( !pTeam ) + return false; + + // Let's have one near each resource zone that we own. + CResourceZone *pClosest = 0; + float flClosestDist = 100000000; + + CBaseEntity *pEntity = NULL; + while( (pEntity = gEntList.FindEntityByClassname( pEntity, "trigger_resourcezone" )) != NULL ) + { + CResourceZone *pZone = (CResourceZone*)pEntity; + + // Ignore empty zones and zones not captured by this team. + if ( pZone->IsEmpty() || !pZone->GetActive() ) + continue; + + Vector vZoneCenter = pZone->WorldSpaceCenter(); + + // Look for a resource pump on this zone. + bool bPump = objType == OBJ_RESOURCEPUMP && pPlayer->NumPumpsOnResourceZone( pZone ) == 0; + if ( bPump ) + { + // Make sure it's their preferred tech. + float flTestDist = pPlayer->GetAbsOrigin().DistTo( vZoneCenter ); + if ( flTestDist < flClosestDist ) + { + pClosest = pZone; + flClosestDist = flTestDist; + } + } + } + + if ( pClosest ) + { + // No pump here. Build one! + pPlayer->GetTFTeam()->AddOrder( + ORDER_BUILD, + pClosest, + pPlayer, + 1e24, + 60, + pOrder + ); + + return true; + } + else + { + return false; + } +} + + +int SortFn_PlayerObjectsByDistance( void *pUserData, int a, int b ) +{ + CSortBase *pSortBase = (CSortBase*)pUserData; + + CBaseObject* pObjA = pSortBase->m_pPlayer->GetObject(a); + CBaseObject* pObjB = pSortBase->m_pPlayer->GetObject(b); + if (!pObjA) + return false; + if (!pObjB) + return true; + + const Vector &v = pSortBase->m_pPlayer->GetAbsOrigin(); + + return v.DistTo( pObjA->GetAbsOrigin() ) < v.DistTo( pObjB->GetAbsOrigin() ); +} + + +int SortFn_TeamObjectsByDistance( void *pUserData, int a, int b ) +{ + CSortBase *pSortBase = (CSortBase*)pUserData; + + CBaseObject *pObj1 = pSortBase->m_pPlayer->GetTFTeam()->GetObject( a ); + CBaseObject *pObj2 = pSortBase->m_pPlayer->GetTFTeam()->GetObject( b ); + const Vector &v = pSortBase->m_pPlayer->GetAbsOrigin(); + + return v.DistTo( pObj1->GetAbsOrigin() ) < v.DistTo( pObj2->GetAbsOrigin() ); +} + + +int SortFn_PlayerEntitiesByDistance( void *pUserData, int a, int b ) +{ + CSortBase *pSortBase = (CSortBase*)pUserData; + + CBaseEntity *pObj1 = CBaseEntity::Instance( engine->PEntityOfEntIndex( a+1 ) ); + CBaseEntity *pObj2 = CBaseEntity::Instance( engine->PEntityOfEntIndex( b+1 ) ); + const Vector &v = pSortBase->m_pPlayer->GetAbsOrigin(); + + return v.DistTo( pObj1->GetAbsOrigin() ) < v.DistTo( pObj2->GetAbsOrigin() ); +} + + +int SortFn_DistanceAndConcentration( void *pUserData, int a, int b ) +{ + CSortBase *p = (CSortBase*)pUserData; + + // Compare distances. Each rope attachment to another ent + // subtracts 200 inches, so the order is biased towards covering + // groups of objects together. + CBaseObject *pObjectA = p->GetTeam()->GetObject( a ); + CBaseObject *pObjectB = p->GetTeam()->GetObject( b ); + + const Vector &vOrigin1 = pObjectA->GetAbsOrigin(); + const Vector &vOrigin2 = p->GetTeam()->GetObject( b )->GetAbsOrigin(); + + float flScore1 = -p->m_pPlayer->GetAbsOrigin().DistTo( vOrigin1 ); + float flScore2 = -p->m_pPlayer->GetAbsOrigin().DistTo( vOrigin2 ); + + flScore1 += pObjectA->RopeCount() * 200; + flScore2 += pObjectB->RopeCount() * 200; + + return flScore1 > flScore2; +} + + +bool IsValidFn_NearAndNotCovered( void *pUserData, int a ) +{ + CSortBase *p = (CSortBase*)pUserData; + CBaseObject *pObj = p->m_pPlayer->GetTFTeam()->GetObject( a ); + + // Is the object too far away to be covered? + if ( p->m_pPlayer->GetAbsOrigin().DistTo( pObj->GetAbsOrigin() ) > p->m_flMaxDist ) + return false; + + // Don't cover certain entities (like sentry guns, sand bags, etc). + switch( p->m_ObjectType ) + { + case OBJ_SENTRYGUN_PLASMA: + { + if ( !pObj->WantsCoverFromSentryGun() ) + return false; + + if ( p->m_pPlayer->GetTFTeam()->IsCoveredBySentryGun( pObj->GetAbsOrigin() ) ) + return false; + } + break; + + case OBJ_SHIELDWALL: + { + if ( !pObj->WantsCover() ) + return false; + + if ( p->m_pPlayer->GetTFTeam()->GetNumShieldWallsCoveringPosition( pObj->GetAbsOrigin() ) ) + return false; + } + break; + + case OBJ_RESUPPLY: + { + if ( p->m_pPlayer->GetTFTeam()->GetNumResuppliesCoveringPosition( pObj->GetAbsOrigin() ) ) + return false; + } + break; + + case OBJ_RESPAWN_STATION: + { + if ( p->m_pPlayer->GetTFTeam()->GetNumRespawnStationsCoveringPosition( pObj->GetAbsOrigin() ) ) + return false; + } + break; + + default: + { + Assert( !"Unsupported object type" ); + } + break; + } + + return true; +} + + +bool OrderCreator_GenericObject( + CPlayerClass *pClass, + int objectType, + float flMaxDist, + COrder *pOrder + ) +{ + // Can we build one? + if ( pClass->CanBuild( objectType ) != CB_CAN_BUILD ) + return false; + + CBaseTFPlayer *pPlayer = pClass->GetPlayer(); + CTFTeam *pTeam = pClass->GetTeam(); + + // Sort nearby objects. + CSortBase info; + info.m_pPlayer = pPlayer; + info.m_flMaxDist = flMaxDist; + info.m_ObjectType = objectType; + + int sorted[MAX_TEAM_OBJECTS]; + int nSorted = BuildSortedActiveList( + sorted, // the sorted list of objects + MAX_TEAM_OBJECTS, + SortFn_DistanceAndConcentration, // sort on distance and entity concentration + IsValidFn_NearAndNotCovered, // filter function + &info, // user data + pTeam->GetNumObjects() // number of objects to check + ); + + if( nSorted ) + { + // Ok, make an order to cover the closest object with a sentry gun. + CBaseEntity *pEnt = pTeam->GetObject( sorted[0] ); + + pTeam->AddOrder( + ORDER_BUILD, + pEnt, + pPlayer, + flMaxDist, + 60, + pOrder + ); + + return true; + } + else + { + return false; + } +} diff --git a/game/server/tf2/order_helpers.h b/game/server/tf2/order_helpers.h new file mode 100644 index 0000000..cfea580 --- /dev/null +++ b/game/server/tf2/order_helpers.h @@ -0,0 +1,128 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef ORDER_HELPERS_H +#define ORDER_HELPERS_H +#ifdef _WIN32 +#pragma once +#endif + + +#include "orders.h" + + +class CTFTeam; + + +class CSortBase +{ +public: + + CSortBase(); + + // Returns m_pTeam, and if that doesn't exist, returns m_pPlayer->GetTFTeam(). + CTFTeam* GetTeam(); + + +public: + + CBaseTFPlayer *m_pPlayer; + + // If this is left at null, then GetTeam() returns m_pPlayer->GetTFTeam(). + CTFTeam *m_pTeam; + + // One of the OBJ_ defines telling what type of object it's thinking of building. + int m_ObjectType; + + // If the object is further from m_vPlayerOrigin than this, then an order + // won't be generated to cover it. + float m_flMaxDist; +}; + + +// Return positive if iItem1 > iItem2. +// Return negative if iItem1 < iItem2. +// Return zero if they're equal. +typedef int (*sortFn)( void *pUserData, int iItem1, int iItem2 ); +typedef bool (*isValidFn)( void *pUserData, int iItem ); + + + +// Index engine->PEntityOfIndex(a+1) and b+1. +int SortFn_PlayerEntitiesByDistance( void *pUserData, int a, int b ); + +// Helper sort function. Sorts CSortBase::m_pPlayer's objects by distance. +// pUserData must be a CSortBase. +int SortFn_PlayerObjectsByDistance( void *pUserData, int a, int b ); + +// Helper sort function. Sorts CSortBase::m_pPlayer->GetTeam()'s objects by distance. +// pUserData must be a CSortBase. +int SortFn_TeamObjectsByDistance( void *pUserData, int a, int b ); + +// Sort by distance and concentation. pUserData must point at something +// derived from CSortBase. +int SortFn_DistanceAndConcentration( void *pUserData, int a, int b ); + +// pUserData is a CSortBase +// a and b index CSortBase::m_pPlayer->GetTeam()->GetPlayer() +// Sort players on distance. +int SortFn_TeamPlayersByDistance( void *pUserData, int a, int b ); + + +// pUserdata is a CSortBase. +// +// Rejects the object if: +// - it's already covered by CSortBase::m_ObjectType +// - it's being ferried +// - it's further from the player than CSortBase::m_flMaxDist; +// +// This function currently supports: +// - OBJ_SENTRYGUN_PLASMA +// - OBJ_SANDBAG +// - OBJ_AUTOREPAIR +// - OBJ_SHIELDWALL +// - OBJ_RESUPPLY +bool IsValidFn_NearAndNotCovered( void *pUserData, int a ); + + + +// This is a generic function that takes a number of items and builds a sorted +// list of the valid items. +int BuildSortedActiveList( + int *pList, // This is the list where the final data is placed. + int nMaxItems, + sortFn pSortFn, // Callbacks. + isValidFn pIsValidFn, // This can be null, in which case all items are valid. + void *pUserData, // Passed into the function pointers. + int nItems // Number of items in the list to sort. + ); + +// Finds the closest resource zone without the specified object on it and +// gives an order to the player to build the object. +// This function supports OBJ_RESOURCEBOX, OBJ_RESOURCEPUMP, and OBJ_ZONE_INCREASER. +bool OrderCreator_ResourceZoneObject( + CBaseTFPlayer *pPlayer, + int objType, + COrder *pOrder + ); + +// This function is shared by lots of the order creation functions. +// It makes an order to create a specific type of object by looking for nearby +// concentrations of team objects that aren't "covered" by objectType. +// +// It uses IsValidFn_NearAndNotCovered, so any object type you specify in here +// must be supported in IsValidFn_NearAndNotCovered. +bool OrderCreator_GenericObject( + CPlayerClass *pClass, + int objectType, + float flMaxDist, + COrder *pOrder + ); + + + +#endif // ORDER_HELPERS_H diff --git a/game/server/tf2/order_killmortarguy.cpp b/game/server/tf2/order_killmortarguy.cpp new file mode 100644 index 0000000..ff2bdff --- /dev/null +++ b/game/server/tf2/order_killmortarguy.cpp @@ -0,0 +1,125 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" +#include "order_helpers.h" +#include "order_killmortarguy.h" +#include "tf_team.h" + + +#define KILLMORTARGUY_DIST 3500 + + +IMPLEMENT_SERVERCLASS_ST( COrderKillMortarGuy, DT_OrderKillMortarGuy ) +END_SEND_TABLE() + + +static bool IsValidFn_DeployedBrianJacobsons( void *pUserData, int iClient ) +{ + CBaseTFPlayer *pPlayer = (CBaseTFPlayer*)UTIL_PlayerByIndex( iClient+1 ); + if ( !pPlayer || pPlayer->IsClass( TFCLASS_UNDECIDED ) || !pPlayer->GetTeam() ) + return false; + + // Is this person on an enemy team? + CSortBase *pSortBase = (CSortBase*)pUserData; + CBaseTFPlayer *pMyPlayer = pSortBase->m_pPlayer; + + if( pPlayer->GetTeam()->GetTeamNumber() == pMyPlayer->GetTeam()->GetTeamNumber() ) + return false; + + // Is he alive? + if( !pPlayer->IsAlive() ) + return false; + + // ROBIN: Removed mortar object. This needs to handle mortar vehicles instead. + // Is he looking surly? + //if( pPlayer->GetNumObjects( OBJ_MORTAR ) == 0 ) + //return false; + + // Is he close enough? + if( pMyPlayer->GetAbsOrigin().DistTo( pPlayer->GetAbsOrigin() ) > KILLMORTARGUY_DIST ) + return false; + + // Is he visible to the tactical? +// if( !pMyPlayer->GetTFTeam()->IsEntityVisibleToTactical( pPlayer ) ) +// return false; + + // KILL HIM!!! + return true; +} + + +static int SortFn_PlayerEntsByDistance( void *pUserData, int a, int b ) +{ + CBaseEntity *pEdictA = CBaseEntity::Instance( engine->PEntityOfEntIndex( a+1 ) ); + CBaseEntity *pEdictB = CBaseEntity::Instance( engine->PEntityOfEntIndex( b+1 ) ); + + if ( !pEdictA || !pEdictB ) + return 1; + + CSortBase *pSortBase = (CSortBase*)pUserData; + const Vector &v = pSortBase->m_pPlayer->GetAbsOrigin(); + + return v.DistTo( pEdictA->GetAbsOrigin() ) < v.DistTo( pEdictB->GetAbsOrigin() ); +} + + +bool COrderKillMortarGuy::CreateOrder( CPlayerClass *pClass ) +{ + CSortBase info; + info.m_pPlayer = pClass->GetPlayer(); + + // Look for an enemy sniper visible to the + int supports[MAX_PLAYERS]; + int nSupports = BuildSortedActiveList( + supports, // the sorted list + MAX_PLAYERS, + SortFn_PlayerEntsByDistance, // sort on distance + IsValidFn_DeployedBrianJacobsons, // only get deployed support guys + &info, // pUserData + gpGlobals->maxClients // how many players to look through + ); + + // Kill the closest punk. + if( nSupports ) + { + CBaseTFPlayer *pBrian = (CBaseTFPlayer*)UTIL_PlayerByIndex( supports[0]+1 ); + Assert( pBrian ); + + COrderKillMortarGuy *pOrder = new COrderKillMortarGuy; + pClass->GetTeam()->AddOrder( + ORDER_KILL, + pBrian, + pClass->GetPlayer(), + 1e24, + 60, + pOrder + ); + + return true; + } + else + { + return false; + } +} + + +bool COrderKillMortarGuy::UpdateOnEvent( COrderEvent_Base *pEvent ) +{ + if ( pEvent->GetType() == ORDER_EVENT_PLAYER_KILLED ) + { + COrderEvent_PlayerKilled *pKilled = (COrderEvent_PlayerKilled*)pEvent; + if ( pKilled->m_pPlayer == GetTargetEntity() ) + return true; + } + + return BaseClass::UpdateOnEvent( pEvent ); +} + + + diff --git a/game/server/tf2/order_killmortarguy.h b/game/server/tf2/order_killmortarguy.h new file mode 100644 index 0000000..c80f562 --- /dev/null +++ b/game/server/tf2/order_killmortarguy.h @@ -0,0 +1,38 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef ORDER_KILLMORTARGUY_H +#define ORDER_KILLMORTARGUY_H +#ifdef _WIN32 +#pragma once +#endif + + +#include "order_player.h" + + +class CPlayerClass; + + +class COrderKillMortarGuy : public COrderPlayer +{ +public: + DECLARE_CLASS( COrderKillMortarGuy, COrderPlayer ); + DECLARE_SERVERCLASS(); + + // Create an order for the player. + static bool CreateOrder( CPlayerClass *pClass ); + + +// COrder overrides. +public: + + virtual bool UpdateOnEvent( COrderEvent_Base *pEvent ); +}; + + +#endif // ORDER_KILLMORTARGUY_H diff --git a/game/server/tf2/order_mortar_attack.cpp b/game/server/tf2/order_mortar_attack.cpp new file mode 100644 index 0000000..1f98da3 --- /dev/null +++ b/game/server/tf2/order_mortar_attack.cpp @@ -0,0 +1,77 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" +#include "order_mortar_attack.h" +#include "tf_team.h" +#include "order_helpers.h" +#include "tf_obj.h" + + +// How far away the escort guy will get orders to shell enemy objects. +#define ENEMYOBJ_MORTAR_DIST 3000 + + +IMPLEMENT_SERVERCLASS_ST( COrderMortarAttack, DT_OrderMortarAttack ) +END_SEND_TABLE() + + +static bool IsValidFn_WithinMortarRange( void *pUserData, int a ) +{ + CSortBase *p = (CSortBase*)pUserData; + CBaseObject *pObj = p->GetTeam()->GetObject( a ); + return pObj->GetAbsOrigin().DistTo( p->m_pPlayer->GetAbsOrigin() ) < ENEMYOBJ_MORTAR_DIST; +} + + +bool COrderMortarAttack::CreateOrder( CPlayerClass *pClass ) +{ + // Look for some nearby enemy objects that would be fun to destroy. + CTFTeam *pEnemyTeam; + if ( !pClass->GetTeam() || (pEnemyTeam = pClass->GetTeam()->GetEnemyTeam()) == NULL ) + return false; + + CBaseTFPlayer *pPlayer = pClass->GetPlayer(); + + CSortBase info; + info.m_pPlayer = pPlayer; + info.m_pTeam = pEnemyTeam; + + int sorted[MAX_TEAM_OBJECTS]; + int nSorted = BuildSortedActiveList( + sorted, // the sorted list of objects + MAX_TEAM_OBJECTS, + SortFn_DistanceAndConcentration, // sort on distance and entity concentration + IsValidFn_WithinMortarRange, // filter function + &info, // user data + pEnemyTeam->GetNumObjects() // number of objects to check + ); + + if( nSorted > 0 ) + { + CBaseEntity *pEnt = pEnemyTeam->GetObject( sorted[0] ); + + COrderMortarAttack *pOrder = new COrderMortarAttack; + + pClass->GetTeam()->AddOrder( + ORDER_MORTAR_ATTACK, + pEnt, + pPlayer, + 1e24, + 40, + pOrder + ); + + return true; + } + else + { + return false; + } +} + + diff --git a/game/server/tf2/order_mortar_attack.h b/game/server/tf2/order_mortar_attack.h new file mode 100644 index 0000000..5162fdc --- /dev/null +++ b/game/server/tf2/order_mortar_attack.h @@ -0,0 +1,29 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef ORDER_BUILDMORTAR_H +#define ORDER_BUILDMORTAR_H +#ifdef _WIN32 +#pragma once +#endif + + +#include "orders.h" + + +class COrderMortarAttack : public COrder +{ +public: + DECLARE_CLASS( COrderMortarAttack, COrder ); + DECLARE_SERVERCLASS(); + + // Create an order for the player. + static bool CreateOrder( CPlayerClass *pClass ); +}; + + +#endif // ORDER_BUILDMORTAR_H diff --git a/game/server/tf2/order_player.cpp b/game/server/tf2/order_player.cpp new file mode 100644 index 0000000..3cd9043 --- /dev/null +++ b/game/server/tf2/order_player.cpp @@ -0,0 +1,26 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" + +#include "order_player.h" + + +IMPLEMENT_SERVERCLASS_ST( COrderPlayer, DT_OrderPlayer ) +END_SEND_TABLE() + + + +bool COrderPlayer::UpdateOnEvent( COrderEvent_Base *pEvent ) +{ + // All player orders give up if the player disconnects + if ( pEvent->GetType() == ORDER_EVENT_PLAYER_DISCONNECTED ) + return true; + + return BaseClass::UpdateOnEvent( pEvent ); +} + diff --git a/game/server/tf2/order_player.h b/game/server/tf2/order_player.h new file mode 100644 index 0000000..d757eed --- /dev/null +++ b/game/server/tf2/order_player.h @@ -0,0 +1,32 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef ORDER_PLAYER_H +#define ORDER_PLAYER_H +#ifdef _WIN32 +#pragma once +#endif + + +#include "orders.h" + + +class COrderPlayer : public COrder +{ +public: + DECLARE_CLASS( COrderPlayer, COrder ); + DECLARE_SERVERCLASS(); + + +// COrder overrides. +public: + + virtual bool UpdateOnEvent( COrderEvent_Base *pEvent ); +}; + + +#endif // ORDER_PLAYER_H diff --git a/game/server/tf2/order_repair.cpp b/game/server/tf2/order_repair.cpp new file mode 100644 index 0000000..a75f75d --- /dev/null +++ b/game/server/tf2/order_repair.cpp @@ -0,0 +1,157 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" +#include "order_repair.h" +#include "tf_team.h" +#include "tf_class_defender.h" +#include "order_helpers.h" +#include "tf_obj.h" + + +IMPLEMENT_SERVERCLASS_ST( COrderRepair, DT_OrderRepair ) +END_SEND_TABLE() + + +static int SortFn_Defender( void *pUserData, int a, int b ) +{ + CSortBase *p = (CSortBase*)pUserData; + + const Vector &vOrigin1 = p->m_pPlayer->GetTFTeam()->GetObject( a )->GetAbsOrigin(); + const Vector &vOrigin2 = p->m_pPlayer->GetTFTeam()->GetObject( b )->GetAbsOrigin(); + + return p->m_pPlayer->GetAbsOrigin().DistTo( vOrigin1 ) < p->m_pPlayer->GetAbsOrigin().DistTo( vOrigin2 ); +} + + +static bool IsValidFn_RepairFriendlyObjects( void *pUserData, int a ) +{ + // Only pick objects that are damaged. + CSortBase *p = (CSortBase*)pUserData; + CBaseObject *pObj = p->m_pPlayer->GetTFTeam()->GetObject( a ); + + // Skip objects under construction + if ( pObj->IsBuilding() ) + return false; + + return ( pObj->m_iHealth < pObj->m_iMaxHealth ); +} + + +static bool IsValidFn_RepairOwnObjects( void *pUserData, int a ) +{ + // Only pick objects that are damaged. + CSortBase *pSortBase = (CSortBase*)pUserData; + CBaseObject *pObj = pSortBase->m_pPlayer->GetObject(a); + + // Skip objects under construction + if ( !pObj || pObj->IsBuilding() ) + return false; + + return pObj->m_iHealth < pObj->m_iMaxHealth; +} + + +bool COrderRepair::CreateOrder_RepairFriendlyObjects( CPlayerClassDefender *pClass ) +{ + if( !pClass->CanBuildSentryGun() ) + return false; + + CBaseTFPlayer *pPlayer = pClass->GetPlayer(); + CTFTeam *pTeam = pClass->GetTeam(); + + // Sort the list and filter out fully healed objects.. + CSortBase info; + info.m_pPlayer = pPlayer; + + int sorted[MAX_TEAM_OBJECTS]; + int nSorted = BuildSortedActiveList( + sorted, + MAX_TEAM_OBJECTS, + SortFn_Defender, + IsValidFn_RepairFriendlyObjects, + &info, + pTeam->GetNumObjects() ); + + // If the player is close enough to the closest damaged object, issue an order. + if( nSorted ) + { + CBaseObject *pObjToHeal = pTeam->GetObject( sorted[0] ); + + static float flClosestDist = 1024; + if( pPlayer->GetAbsOrigin().DistTo( pObjToHeal->GetAbsOrigin() ) < flClosestDist ) + { + COrder *pOrder = new COrderRepair; + + pTeam->AddOrder( + ORDER_REPAIR, + pObjToHeal, + pPlayer, + 1e24, + 60, + pOrder + ); + + return true; + } + } + + return false; +} + + +bool COrderRepair::CreateOrder_RepairOwnObjects( CPlayerClass *pClass ) +{ + CSortBase info; + info.m_pPlayer = pClass->GetPlayer(); + + int sorted[16]; + int nSorted = BuildSortedActiveList( + sorted, + sizeof( sorted ) / sizeof( sorted[0] ), + SortFn_PlayerObjectsByDistance, + IsValidFn_RepairOwnObjects, + &info, + info.m_pPlayer->GetObjectCount() ); + + if( nSorted ) + { + // Make an order to repair the closest damaged object. + CBaseObject *pObj = info.m_pPlayer->GetObject( sorted[0] ); + if (!pObj) + return false; + + COrderRepair *pOrder = new COrderRepair; + info.m_pPlayer->GetTFTeam()->AddOrder( + ORDER_REPAIR, + pObj, + info.m_pPlayer, + 1e24, + 60, + pOrder + ); + + return true; + } + else + { + return false; + } +} + + +bool COrderRepair::Update() +{ + CBaseEntity *pEnt = GetTargetEntity(); + if( !pEnt ) + return true; + + // Kill the order when the object is repaired. + return pEnt->m_iHealth >= pEnt->m_iMaxHealth; +} + + diff --git a/game/server/tf2/order_repair.h b/game/server/tf2/order_repair.h new file mode 100644 index 0000000..fd94a0b --- /dev/null +++ b/game/server/tf2/order_repair.h @@ -0,0 +1,42 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef ORDER_REPAIR_H +#define ORDER_REPAIR_H +#ifdef _WIN32 +#pragma once +#endif + + +#include "orders.h" + + +class CPlayerClass; +class CPlayerClassDefender; + + +class COrderRepair : public COrder +{ +public: + DECLARE_CLASS( COrderRepair, COrder ); + DECLARE_SERVERCLASS(); + + // Create an order for the defender to fix friendly objects. + static bool CreateOrder_RepairFriendlyObjects( CPlayerClassDefender *pClass ); + + // Create an order for anyone to repair their own objects. + static bool CreateOrder_RepairOwnObjects( CPlayerClass *pClass ); + + +// COrder overrides. +public: + + virtual bool Update( void ); +}; + + +#endif // ORDER_REPAIR_H diff --git a/game/server/tf2/order_resourcepump.cpp b/game/server/tf2/order_resourcepump.cpp new file mode 100644 index 0000000..bd24706 --- /dev/null +++ b/game/server/tf2/order_resourcepump.cpp @@ -0,0 +1,66 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" +#include "order_resourcepump.h" +#include "tf_team.h" +#include "tf_obj_resourcepump.h" +#include "tf_func_resource.h" +#include "order_helpers.h" + + +IMPLEMENT_SERVERCLASS_ST( COrderResourcePump, DT_OrderResourcePump ) +END_SEND_TABLE() + + +bool COrderResourcePump::CreateOrder( CPlayerClass *pClass ) +{ + COrderResourcePump *pOrder = new COrderResourcePump; + + if ( OrderCreator_ResourceZoneObject( pClass->GetPlayer(), OBJ_RESOURCEPUMP, pOrder ) ) + { + return true; + } + else + { + UTIL_RemoveImmediate( pOrder ); + return false; + } +} + + +bool COrderResourcePump::Update() +{ + // Can they still build resource pumps? + if ( !m_hOwningPlayer.Get() || m_hOwningPlayer->CanBuild( OBJ_RESOURCEPUMP ) != CB_CAN_BUILD ) + return true; + + // Lost our resource zone? + if ( !GetTargetEntity() ) + return true; + // Is our target zone now empty? + if ( ((CResourceZone*)GetTargetEntity())->IsEmpty() ) + return true; + + // Have they built a pump on this zone? + for( int i=0; i < m_hOwningPlayer->GetObjectCount(); i++ ) + { + CBaseObject *pObj = m_hOwningPlayer->GetObject(i); + + if( pObj && pObj->GetType() == OBJ_RESOURCEPUMP ) + { + CObjectResourcePump *pPump = (CObjectResourcePump*)pObj; + CResourceZone *pZone = pPump->GetResourceZone(); + if( pZone && pZone->entindex() == m_iTargetEntIndex && !pZone->IsEmpty() ) + return true; + } + } + + return BaseClass::Update(); +} + + diff --git a/game/server/tf2/order_resourcepump.h b/game/server/tf2/order_resourcepump.h new file mode 100644 index 0000000..19cf2cf --- /dev/null +++ b/game/server/tf2/order_resourcepump.h @@ -0,0 +1,38 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef ORDER_RESOURCEPUMP_H +#define ORDER_RESOURCEPUMP_H +#ifdef _WIN32 +#pragma once +#endif + + +#include "orders.h" + + +class CPlayerClass; + + +class COrderResourcePump : public COrder +{ +public: + DECLARE_CLASS( COrderResourcePump, COrder ); + DECLARE_SERVERCLASS(); + + // Create an order for the player. + static bool CreateOrder( CPlayerClass *pClass ); + + +// COrder overrides. +public: + + virtual bool Update( void ); +}; + + +#endif // ORDER_RESOURCEPUMP_H diff --git a/game/server/tf2/order_resupply.cpp b/game/server/tf2/order_resupply.cpp new file mode 100644 index 0000000..54ddfc7 --- /dev/null +++ b/game/server/tf2/order_resupply.cpp @@ -0,0 +1,54 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" +#include "order_resupply.h" +#include "tf_team.h" +#include "tf_playerclass.h" +#include "order_helpers.h" + + +// Orders to build resupplies near objects come in within this range. +#define RESUPPLY_ORDER_MAXDIST 2000 + + +IMPLEMENT_SERVERCLASS_ST( COrderResupply, DT_OrderResupply ) +END_SEND_TABLE() + + +bool COrderResupply::CreateOrder( CPlayerClass *pClass ) +{ + COrderResupply *pOrder = new COrderResupply; + if ( OrderCreator_GenericObject( pClass, OBJ_RESUPPLY, RESUPPLY_ORDER_MAXDIST, pOrder ) ) + { + return true; + } + else + { + UTIL_RemoveImmediate( pOrder ); + return false; + } +} + + +bool COrderResupply::Update() +{ + CBaseEntity *pEnt = GetTargetEntity(); + if( !pEnt ) + return true; + + if ( !m_hOwningPlayer.Get() || m_hOwningPlayer->CanBuild( OBJ_RESUPPLY ) != CB_CAN_BUILD ) + return true; + + CTFTeam *pTeam = m_hOwningPlayer->GetTFTeam(); + if ( pTeam->GetNumResuppliesCoveringPosition( pEnt->GetAbsOrigin() ) ) + return true; + + return BaseClass::Update(); +} + + diff --git a/game/server/tf2/order_resupply.h b/game/server/tf2/order_resupply.h new file mode 100644 index 0000000..8aa516e --- /dev/null +++ b/game/server/tf2/order_resupply.h @@ -0,0 +1,38 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef ORDER_RESUPPLY_H +#define ORDER_RESUPPLY_H +#ifdef _WIN32 +#pragma once +#endif + + +#include "orders.h" + + +class CPlayerClass; + + +class COrderResupply : public COrder +{ +public: + DECLARE_CLASS( COrderResupply, COrder ); + DECLARE_SERVERCLASS(); + + // Create an order for the player. + static bool CreateOrder( CPlayerClass *pClass ); + + +// COrder overrides. +public: + + virtual bool Update(); +}; + + +#endif // ORDER_RESUPPLY_H diff --git a/game/server/tf2/orders.cpp b/game/server/tf2/orders.cpp new file mode 100644 index 0000000..3d3c906 --- /dev/null +++ b/game/server/tf2/orders.cpp @@ -0,0 +1,215 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Order handling +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "orders.h" +#include "tf_player.h" +#include "tf_func_resource.h" +#include "tf_team.h" +#include "tf_obj_resourcepump.h" + + +IMPLEMENT_SERVERCLASS_ST(COrder, DT_Order) + SendPropInt( SENDINFO(m_iOrderType), 4, SPROP_UNSIGNED ), + SendPropInt( SENDINFO(m_iTargetEntIndex), 16, SPROP_UNSIGNED ), +END_SEND_TABLE() + +LINK_ENTITY_TO_CLASS( tf_order, COrder ); + + +COrder::COrder() +{ + m_iOrderType = 0; + m_iTargetEntIndex = 0; + m_hTarget = NULL; + m_flDistanceToRemove = 0; + m_hOwningPlayer = NULL; + m_flDieTime = 0; +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void COrder::UpdateOnRemove( void ) +{ + DetachFromPlayer(); + // Chain at end to mimic destructor unwind order + BaseClass::UpdateOnRemove(); +} + +//----------------------------------------------------------------------------- +// Purpose: Transmit weapon data +//----------------------------------------------------------------------------- +int COrder::ShouldTransmit( const CCheckTransmitInfo *pInfo ) +{ + CBaseEntity* pRecipientEntity = CBaseEntity::Instance( pInfo->m_pClientEnt ); + + // If this is a personal order, only send to it's owner + if ( GetOwner() ) + { + if ( GetOwner() == pRecipientEntity ) + return FL_EDICT_ALWAYS; + + return FL_EDICT_DONTSEND; + } + + // Otherwise, only send to players on our team + if ( InSameTeam( pRecipientEntity ) ) + return FL_EDICT_ALWAYS; + + return FL_EDICT_DONTSEND; +} + + +void COrder::DetachFromPlayer() +{ + // Detach from our owner. + if ( m_hOwningPlayer ) + { + m_hOwningPlayer->SetOrder( NULL ); + m_hOwningPlayer = NULL; + + if ( GetTeam() ) + { + ((CTFTeam*)GetTeam())->RemoveOrder( this ); + } + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int COrder::GetType( void ) +{ + return m_iOrderType; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CBaseEntity *COrder::GetTargetEntity( void ) +{ + return m_hTarget; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void COrder::SetType( int iOrderType ) +{ + m_iOrderType = iOrderType; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void COrder::SetTarget( CBaseEntity *pTarget ) +{ + m_hTarget = pTarget; + if ( m_hTarget ) + { + m_iTargetEntIndex = m_hTarget->entindex(); + } + else + { + m_iTargetEntIndex = 0; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void COrder::SetDistance( float flDistance ) +{ + m_flDistanceToRemove = flDistance; +} + + +void COrder::SetLifetime( float flLifetime ) +{ + m_flDieTime = gpGlobals->curtime + flLifetime; +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +// Purpose: for updates on the order. Return true if the order should be removed. +//----------------------------------------------------------------------------- +bool COrder::Update( void ) +{ + // Orders with no targets & no owners don't go away on their own + if ( !GetOwner() ) + return false; + + // Has it timed out? + if( gpGlobals->curtime > m_flDieTime ) + return true; + + // Check to make sure we're still within the correct distance + if ( m_flDistanceToRemove ) + { + CBaseEntity *pTarget = GetTargetEntity(); + if ( pTarget ) + { + // Have the player and the target moved away from each other? + if ( (m_hOwningPlayer->GetAbsOrigin() - pTarget->GetAbsOrigin()).Length() > (m_flDistanceToRemove * 1.25) ) + return true; + } + } + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: An event for this order's target has arrived. Return true if this order should be removed. +//----------------------------------------------------------------------------- +bool COrder::UpdateOnEvent( COrderEvent_Base *pEvent ) +{ + // Default behavior is to get rid of the order if the object we're referencing + // gets destroyed. + if ( pEvent->GetType() == ORDER_EVENT_OBJECT_DESTROYED ) + { + COrderEvent_ObjectDestroyed *pObjDestroyed = (COrderEvent_ObjectDestroyed*)pEvent; + if ( pObjDestroyed->m_pObject == GetTargetEntity() ) + return true; + } + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CBaseTFPlayer *COrder::GetOwner( void ) +{ + return m_hOwningPlayer; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void COrder::SetOwner( CBaseTFPlayer *pPlayer ) +{ + // Null out our m_hOwningPlayer so we don't recurse infinitely. + CHandle<CBaseTFPlayer> hPlayer = m_hOwningPlayer; + m_hOwningPlayer = 0; + + if ( hPlayer.Get() && (hPlayer != pPlayer) ) + { + Assert( hPlayer->GetOrder() == this ); + hPlayer->SetOrder( NULL ); + } + + m_hOwningPlayer = pPlayer; +} + + + + + + diff --git a/game/server/tf2/orders.h b/game/server/tf2/orders.h new file mode 100644 index 0000000..0aa68e2 --- /dev/null +++ b/game/server/tf2/orders.h @@ -0,0 +1,90 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Order handling +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef ORDERS_H +#define ORDERS_H +#ifdef _WIN32 +#pragma once +#endif + + +class CTFTeam; +class CBaseTFPlayer; + + +#include "order_events.h" + + +//----------------------------------------------------------------------------- +// Purpose: Datatable container class for orders +//----------------------------------------------------------------------------- +class COrder : public CBaseEntity +{ + DECLARE_CLASS( COrder, CBaseEntity ); +public: + DECLARE_SERVERCLASS(); + + COrder(); + virtual void UpdateOnRemove( void ); + + virtual int UpdateTransmitState() { return SetTransmitState( FL_EDICT_FULLCHECK ); } + virtual int ShouldTransmit( const CCheckTransmitInfo *pInfo ); + + // This is called when removing the order. + void DetachFromPlayer(); + + +// Overridables. +public: + + // Purpose: for updates on the order. Return true if the order should be removed. + virtual bool Update( void ); + virtual bool UpdateOnEvent( COrderEvent_Base *pEvent ); + + + +public: + + CBaseTFPlayer *GetOwner( void ); + CBaseEntity *GetTargetEntity( void ); + int GetType( void ); + + void SetOwner( CBaseTFPlayer *pPlayer ); + void SetType( int iOrderType ); + void SetTarget( CBaseEntity *pTarget ); + void SetDistance( float flDistance ); + void SetLifetime( float flLifetime ); + +public: + // Sent via datatable + CNetworkVar( int, m_iOrderType ); + float m_flDistanceToRemove; + + // When the order goes away. + double m_flDieTime; + + // Personal order owner + CHandle< CBaseTFPlayer > m_hOwningPlayer; + EHANDLE m_hTarget; + CNetworkVar( int, m_iTargetEntIndex ); +}; + + + +//----------------------------------------------------------------------------- +// ORDER CREATION DATA +//----------------------------------------------------------------------------- +// Time between personal order updates +#define PERSONAL_ORDER_UPDATE_TIME 2.0 + +// KILL orders +#define ORDER_KILL_ENEMY_DISTANCE 2048 // Distance the enemy must be within for this player to receive this order + +// HEAL orders +#define ORDER_HEAL_FRIENDLY_DISTANCE 2048 // Distance the friendly must be within this player to receive this order + +#endif // ORDERS_H diff --git a/game/server/tf2/ragdoll_shadow.cpp b/game/server/tf2/ragdoll_shadow.cpp new file mode 100644 index 0000000..5c35c10 --- /dev/null +++ b/game/server/tf2/ragdoll_shadow.cpp @@ -0,0 +1,117 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Resource chunks +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "ragdoll_shadow.h" +#include "tf_player.h" +#include "sendproxy.h" + +// FIXME Hook up a real player standin +static char *sRagdollShadowModel = "models/player/human_commando.mdl"; + + +IMPLEMENT_SERVERCLASS_ST( CRagdollShadow, DT_RagdollShadow ) + SendPropInt( SENDINFO( m_nPlayer ), 10, SPROP_UNSIGNED ), + + SendPropExclude( "DT_BaseEntity", "m_angAbsRotation[0]" ), + SendPropExclude( "DT_BaseEntity", "m_angAbsRotation[1]" ), + SendPropExclude( "DT_BaseEntity", "m_angAbsRotation[2]" ), + +END_SEND_TABLE() + +LINK_ENTITY_TO_CLASS( ragdoll_shadow, CRagdollShadow ); +PRECACHE_REGISTER( ragdoll_shadow ); + +CRagdollShadow::CRagdollShadow( void ) +{ + m_pPlayer = NULL; + m_nPlayer = 0; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CRagdollShadow::Spawn( ) +{ + // Init value & model + if ( m_pPlayer ) + { + SetModelName( m_pPlayer->GetModelName() ); + } + else + { + SetModelName( AllocPooledString( sRagdollShadowModel ) ); + } + + BaseClass::Spawn(); + + // Create the object in the physics system + IPhysicsObject *pPhysics = VPhysicsInitNormal( SOLID_VPHYSICS, FSOLID_NOT_SOLID, false ); +// IPhysicsObject *pPhysics = VPhysicsInitNormal( SOLID_VPHYSICS, 0, false ); + + // disable physics sounds on this object + pPhysics->SetMaterialIndex( physprops->GetSurfaceIndex("default_silent") ); + + UTIL_SetSize( this, Vector(-36,-36, 0), Vector(36,36,72) ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : **ppSendTable - +// *recipient - +// *pvs - +// clientArea - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +int CRagdollShadow::ShouldTransmit( const CCheckTransmitInfo *pInfo ) +{ + // Always send to local player + if ( Instance( pInfo->m_pClientEnt ) == GetOwnerEntity() ) + return FL_EDICT_ALWAYS; + + return BaseClass::ShouldTransmit( pInfo ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CRagdollShadow::Precache( void ) +{ + PrecacheModel( sRagdollShadowModel ); +} + +//----------------------------------------------------------------------------- +// Purpose: Create a resource chunk +//----------------------------------------------------------------------------- +CRagdollShadow *CRagdollShadow::Create( CBaseTFPlayer *player, const Vector& force ) +{ + CRagdollShadow *pRagdollShadow = (CRagdollShadow*)CreateEntityByName("ragdoll_shadow"); + + UTIL_SetOrigin( pRagdollShadow, player->GetAbsOrigin() ); + + pRagdollShadow->m_pPlayer = player; + pRagdollShadow->m_nPlayer = player->entindex(); + + pRagdollShadow->Spawn(); + pRagdollShadow->SetAbsVelocity( force ); + pRagdollShadow->SetLocalAngles( vec3_angle ); + pRagdollShadow->SetLocalAngularVelocity( RandomAngle( -100, 100 ) ); + + //pRagdollShadow->AddEffects( EF_NODRAW ); + pRagdollShadow->AddEffects( EF_NOSHADOW ); + + pRagdollShadow->m_lifeState = LIFE_DYING; + + IPhysicsObject *pPhysicsObject = pRagdollShadow->VPhysicsGetObject(); + if ( pPhysicsObject ) + { + AngularImpulse tmp; + QAngleToAngularImpulse( pRagdollShadow->GetLocalAngularVelocity(), tmp ); + pPhysicsObject->AddVelocity( &pRagdollShadow->GetAbsVelocity(), &tmp ); + } + + return pRagdollShadow; +}
\ No newline at end of file diff --git a/game/server/tf2/ragdoll_shadow.h b/game/server/tf2/ragdoll_shadow.h new file mode 100644 index 0000000..93a027c --- /dev/null +++ b/game/server/tf2/ragdoll_shadow.h @@ -0,0 +1,43 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef RAGDOLL_SHADOW_H +#define RAGDOLL_SHADOW_H +#ifdef _WIN32 +#pragma once +#endif + +#include "props.h" + +class CBaseTFPlayer; +class IPhysicsObject; + +//----------------------------------------------------------------------------- +// Purpose: A shadow object used to bound the position of a player ragdoll +//----------------------------------------------------------------------------- +class CRagdollShadow : public CBaseProp +{ + DECLARE_CLASS( CRagdollShadow, CBaseProp ); +public: + DECLARE_SERVERCLASS(); + + CRagdollShadow( void ) ; + + virtual void Spawn( void ); + virtual void Precache( void ); + + virtual int UpdateTransmitState() { return SetTransmitState( FL_EDICT_FULLCHECK); } + virtual int ShouldTransmit( const CCheckTransmitInfo *pInfo ); + + static CRagdollShadow *Create( CBaseTFPlayer *player, const Vector& force ); + +public: + CBaseTFPlayer *m_pPlayer; + CNetworkVar( int, m_nPlayer ); +}; + +#endif // RAGDOLL_SHADOW_H diff --git a/game/server/tf2/resource_chunk.cpp b/game/server/tf2/resource_chunk.cpp new file mode 100644 index 0000000..a4a00d4 --- /dev/null +++ b/game/server/tf2/resource_chunk.cpp @@ -0,0 +1,172 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Resource chunks +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "tf_func_resource.h" +#include "tf_team.h" +#include "tf_basecombatweapon.h" +#include "tf_obj.h" +#include "resource_chunk.h" +#include "vstdlib/random.h" +#include "tf_stats.h" +#include "engine/IEngineSound.h" + +ConVar resource_chunk_value( "resource_chunk_value","20", FCVAR_NONE, "Resource value of a single resource chunk." ); +ConVar resource_chunk_processed_value( "resource_chunk_processed_value","80", FCVAR_NONE, "Resource value of a single processed resource chunk." ); + +// Resource Chunk Models +char *sResourceChunkModel = "models/resources/resource_chunk_B.mdl"; +char *sProcessedResourceChunkModel = "models/resources/processed_resource_chunk_B.mdl"; + +BEGIN_DATADESC( CResourceChunk ) + + // functions + DEFINE_FUNCTION( ChunkTouch ), + DEFINE_FUNCTION( ChunkRemove ), + +END_DATADESC() + +IMPLEMENT_SERVERCLASS_ST( CResourceChunk, DT_ResourceChunk ) +END_SEND_TABLE() + +LINK_ENTITY_TO_CLASS( resource_chunk, CResourceChunk ); +PRECACHE_REGISTER( resource_chunk ); + +//----------------------------------------------------------------------------- +// Purpose: Remove me from any lists I'm in when I'm deleted +//----------------------------------------------------------------------------- +void CResourceChunk::UpdateOnRemove( void ) +{ + if ( m_hZone ) + { + m_hZone->RemoveChunk( this, false ); + m_hZone = NULL; + } + + // Chain at end to mimic destructor unwind order + BaseClass::UpdateOnRemove(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CResourceChunk::Spawn( ) +{ + // Init model + if ( IsProcessed() ) + { + SetModelName( AllocPooledString( sProcessedResourceChunkModel ) ); + } + else + { + SetModelName( AllocPooledString( sResourceChunkModel ) ); + } + + BaseClass::Spawn(); + + UTIL_SetSize( this, Vector(-4,-4,-4), Vector(4,4,4) ); + SetMoveType( MOVETYPE_FLYGRAVITY, MOVECOLLIDE_FLY_BOUNCE ); + SetSolid( SOLID_BBOX ); + AddSolidFlags( FSOLID_TRIGGER ); + CollisionProp()->UseTriggerBounds( true, 24 ); + SetCollisionGroup( TFCOLLISION_GROUP_RESOURCE_CHUNK ); + SetGravity( 1.0 ); + SetFriction( 1 ); + SetTouch( ChunkTouch ); + SetThink( ChunkRemove ); + SetNextThink( gpGlobals->curtime + random->RandomFloat( 50.0, 80.0 ) ); // Remove myself the +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CResourceChunk::Precache( void ) +{ + PrecacheModel( sResourceChunkModel ); + PrecacheModel( sProcessedResourceChunkModel ); + PrecacheModel( "sprites/redglow1.vmt" ); + + PrecacheScriptSound( "ResourceChunk.Pickup" ); + +} + +//----------------------------------------------------------------------------- +// Purpose: Create a resource chunk +//----------------------------------------------------------------------------- +CResourceChunk *CResourceChunk::Create( bool bProcessed, const Vector &vecOrigin, const Vector &vecVelocity ) +{ + CResourceChunk *pChunk = (CResourceChunk*)CreateEntityByName("resource_chunk"); + + UTIL_SetOrigin( pChunk, vecOrigin ); + pChunk->m_bIsProcessed = bProcessed; + pChunk->m_bBeingCollected = false; + pChunk->Spawn(); + pChunk->SetAbsVelocity( vecVelocity ); + pChunk->SetLocalAngularVelocity( RandomAngle( -100, 100 ) ); + pChunk->SetLocalAngles( vec3_angle ); + + return pChunk; +} + +//----------------------------------------------------------------------------- +// Purpose: If we're picked up by another pla`yer, give resources to that team +//----------------------------------------------------------------------------- +void CResourceChunk::ChunkTouch( CBaseEntity *pOther ) +{ + if ( pOther->IsPlayer() || pOther->GetServerVehicle() ) + { + // Give the team the resources + int iAmountPerPlayer = ((CTFTeam *)pOther->GetTeam())->AddTeamResources( GetResourceValue(), TF_PLAYER_STAT_RESOURCES_ACQUIRED_FROM_CHUNKS ); + TFStats()->IncrementTeamStat( pOther->GetTeamNumber(), TF_TEAM_STAT_RESOURCE_CHUNKS_COLLECTED, GetResourceValue() ); + + pOther->EmitSound( "ResourceChunk.Pickup" ); + + // Tell the player + CSingleUserRecipientFilter user( (CBasePlayer*)pOther ); + UserMessageBegin( user, "PickupRes" ); + WRITE_BYTE( iAmountPerPlayer ); + MessageEnd(); + + // Tell our zone to remove this chunk from it's list + if ( m_hZone ) + { + m_hZone->RemoveChunk( this, false ); + m_hZone = NULL; + } + + // Remove this chunk + SetTouch( NULL ); + UTIL_Remove( this ); + return; + } +} + +//----------------------------------------------------------------------------- +// Purpose: Remove myself if I'm not being harvested +//----------------------------------------------------------------------------- +void CResourceChunk::ChunkRemove( void ) +{ + // Remove this chunk + if ( m_hZone ) + { + m_hZone->RemoveChunk( this, true ); + m_hZone = NULL; + } + UTIL_Remove( this ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Return the resource value of this chunk +//----------------------------------------------------------------------------- +float CResourceChunk::GetResourceValue( void ) +{ + // Init value & model + if ( IsProcessed() ) + return resource_chunk_processed_value.GetFloat(); + + return resource_chunk_value.GetFloat(); +}
\ No newline at end of file diff --git a/game/server/tf2/resource_chunk.h b/game/server/tf2/resource_chunk.h new file mode 100644 index 0000000..7f03278 --- /dev/null +++ b/game/server/tf2/resource_chunk.h @@ -0,0 +1,52 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef RESOURCE_CHUNK_H +#define RESOURCE_CHUNK_H +#ifdef _WIN32 +#pragma once +#endif + + +#include "props.h" + +class CResourceZone; + +extern ConVar resource_chunk_value; +extern ConVar resource_chunk_processed_value; + +//----------------------------------------------------------------------------- +// Purpose: A resource chunk that's harvestable by a player +//----------------------------------------------------------------------------- +class CResourceChunk : public CBaseProp +{ + DECLARE_CLASS( CResourceChunk, CBaseProp ); +public: + DECLARE_DATADESC(); + DECLARE_SERVERCLASS(); + + virtual void Spawn( void ); + virtual void Precache( void ); + virtual void UpdateOnRemove( void ); + + void ChunkTouch( CBaseEntity *pOther ); + void ChunkRemove( void ); + + virtual bool IsStandable( const CBaseEntity *pStander ) { return true; } // can pStander stand on this entity? + virtual bool IsProcessed( void ) { return m_bIsProcessed; }; + + float GetResourceValue( void ); + + static CResourceChunk *Create( bool bProcessed, const Vector &vecOrigin, const Vector &vecVelocity ); + +public: + CHandle<CResourceZone> m_hZone; + bool m_bIsProcessed; + bool m_bBeingCollected; +}; + +#endif // RESOURCE_CHUNK_H diff --git a/game/server/tf2/sensor_tf_team.cpp b/game/server/tf2/sensor_tf_team.cpp new file mode 100644 index 0000000..083d2d4 --- /dev/null +++ b/game/server/tf2/sensor_tf_team.cpp @@ -0,0 +1,139 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Definitions of all the entities that control logic flow within a map +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" +#include "EntityInput.h" +#include "EntityOutput.h" +#include "tf_team.h" +#include "tf_obj.h" + + +//----------------------------------------------------------------------------- +// Purpose: Detects a bunch of tf team state +//----------------------------------------------------------------------------- +class CSensorTFTeam : public CLogicalEntity +{ + DECLARE_CLASS( CSensorTFTeam, CLogicalEntity ); + +public: + void Spawn( void ); + void Think( void ); + +private: + DECLARE_DATADESC(); + + // Computes the number of respawns stations on the sensed team + int ComputeRespawnCount(); + + // outputs + COutputInt m_OnRespawnCountChanged; + COutputInt m_OnResourceCountChanged; + COutputInt m_OnMemberCountChanged; + COutputInt m_OnRespawnCountChangedDelta; + COutputInt m_OnResourceCountChangedDelta; + COutputInt m_OnMemberCountChangedDelta; + + // What team am I sensing? + int m_nTeam; + CTFTeam *m_pTeam; + + // So we can know when state changes... + int m_nRespawnCount; + int m_nResourceCount; + int m_nMemberCount; +}; + + +LINK_ENTITY_TO_CLASS( sensor_tf_team, CSensorTFTeam ); + + +BEGIN_DATADESC( CSensorTFTeam ) + + DEFINE_OUTPUT( m_OnRespawnCountChanged, "OnRespawnCountChanged" ), + DEFINE_OUTPUT( m_OnResourceCountChanged, "OnResourceCountChanged" ), + DEFINE_OUTPUT( m_OnMemberCountChanged, "OnMemberCountChanged" ), + DEFINE_OUTPUT( m_OnRespawnCountChangedDelta, "OnRespawnCountChangedDelta" ), + DEFINE_OUTPUT( m_OnResourceCountChangedDelta, "OnResourceCountChangedDelta" ), + DEFINE_OUTPUT( m_OnMemberCountChangedDelta, "OnMemberCountChangedDelta" ), + DEFINE_KEYFIELD( m_nTeam, FIELD_INTEGER, "team"), + +END_DATADESC() + + + + +//----------------------------------------------------------------------------- +// Spawn! +//----------------------------------------------------------------------------- +void CSensorTFTeam::Spawn( void ) +{ + // Hook us up to a team... + m_pTeam = GetGlobalTFTeam( m_nTeam ); + + // Gets us thinkin! + SetNextThink( gpGlobals->curtime + 0.1f ); + + // Force an output message on our first think + m_nRespawnCount = -1; + m_nResourceCount = -1; +} + + +//----------------------------------------------------------------------------- +// Compute the number of respawn stations on this team +//----------------------------------------------------------------------------- +int CSensorTFTeam::ComputeRespawnCount() +{ + int nCount = 0; + for (int i = m_pTeam->GetNumObjects(); --i >= 0; ) + { + CBaseObject *pObject = m_pTeam->GetObject(i); + if ( pObject && (pObject->GetType() == OBJ_RESPAWN_STATION) ) + { + ++nCount; + } + } + return nCount; +} + + +//----------------------------------------------------------------------------- +// Purpose: Forces a recompare +//----------------------------------------------------------------------------- +void CSensorTFTeam::Think( ) +{ + if (!m_pTeam) + return; + + // Check for a difference in the number of respawn stations + int nRespawnCount = ComputeRespawnCount(); + if ( nRespawnCount != m_nRespawnCount ) + { + m_OnRespawnCountChangedDelta.Set( nRespawnCount - m_nRespawnCount, this, this ); + m_nRespawnCount = nRespawnCount; + m_OnRespawnCountChanged.Set( m_nRespawnCount, this, this ); + } + + // Check for a difference in the number of resources harvested + if ( m_nResourceCount != m_pTeam->m_flTotalResourcesSoFar ) + { + m_OnResourceCountChangedDelta.Set( m_pTeam->m_flTotalResourcesSoFar - m_nResourceCount, this, this ); + m_nResourceCount = m_pTeam->m_flTotalResourcesSoFar; + m_OnResourceCountChanged.Set( m_nResourceCount, this, this ); + } + + // Check for a difference in the number of team members + if ( m_nMemberCount != m_pTeam->GetNumPlayers() ) + { + m_OnMemberCountChangedDelta.Set( m_pTeam->GetNumPlayers() - m_nMemberCount, this, this ); + m_nMemberCount = m_pTeam->GetNumPlayers(); + m_OnMemberCountChanged.Set( m_nMemberCount, this, this ); + } + + SetNextThink( gpGlobals->curtime + 0.1f ); +} + diff --git a/game/server/tf2/team_messages.cpp b/game/server/tf2/team_messages.cpp new file mode 100644 index 0000000..e41a028 --- /dev/null +++ b/game/server/tf2/team_messages.cpp @@ -0,0 +1,110 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "player.h" +#include "tf_team.h" +#include "team_messages.h" +#include "engine/IEngineSound.h" + +//----------------------------------------------------------------------------- +// Purpose: Create the right class of message based upon the type +//----------------------------------------------------------------------------- +CTeamMessage *CTeamMessage::Create( CTFTeam *pTeam, int iMessageID, CBaseEntity *pEntity ) +{ + CTeamMessage *pMessage = NULL; + + // Create the right type + switch ( iMessageID ) + { + // Sound orders + case TEAMMSG_REINFORCEMENTS_ARRIVED: + pMessage = new CTeamMessage_Sound( pTeam, iMessageID, pEntity, 5.0 ); + ((CTeamMessage_Sound*)pMessage)->SetSound( "TeamMessage.Reinforcements" ); + break; + case TEAMMSG_CARRIER_UNDER_ATTACK: + pMessage = new CTeamMessage_Sound( pTeam, iMessageID, pEntity, 10.0 ); + ((CTeamMessage_Sound*)pMessage)->SetSound( "TeamMessage.CarrierAttacked" ); + break; + case TEAMMSG_CARRIER_DESTROYED: + pMessage = new CTeamMessage_Sound( pTeam, iMessageID, pEntity, 10.0 ); + ((CTeamMessage_Sound*)pMessage)->SetSound( "TeamMessage.CarrierDestroyed" ); + break; + case TEAMMSG_HARVESTER_UNDER_ATTACK: + pMessage = new CTeamMessage_Sound( pTeam, iMessageID, pEntity, 10.0 ); + ((CTeamMessage_Sound*)pMessage)->SetSound( "TeamMessage.HarvesterAttacked" ); + break; + case TEAMMSG_HARVESTER_DESTROYED: + pMessage = new CTeamMessage_Sound( pTeam, iMessageID, pEntity, 10.0 ); + ((CTeamMessage_Sound*)pMessage)->SetSound( "TeamMessage.HarvesterDestroyed" ); + break; + case TEAMMSG_NEW_TECH_LEVEL_OPEN: + pMessage = new CTeamMessage_Sound( pTeam, iMessageID, pEntity, 10.0 ); + ((CTeamMessage_Sound*)pMessage)->SetSound( "TeamMessage.NewTechLevel" ); + break; + case TEAMMSG_RESOURCE_ZONE_EMPTIED: + pMessage = new CTeamMessage_Sound( pTeam, iMessageID, pEntity, 10.0 ); + ((CTeamMessage_Sound*)pMessage)->SetSound( "TeamMessage.ResourceZoneEmpty" ); + break; + case TEAMMSG_CUSTOM_SOUND: + pMessage = new CTeamMessage_Sound( pTeam, iMessageID, pEntity, 10.0 ); + break; + + default: + break; + }; + + return pMessage; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CTeamMessage::CTeamMessage( CTFTeam *pTeam, int iMessageID, CBaseEntity *pEntity, float flTTL ) +{ + m_pTeam = pTeam; + m_iMessageID = iMessageID; + m_hEntity = pEntity; + m_flTTL = gpGlobals->curtime + flTTL; +} + + +//=============================================================================================================== +// TEAM MESSAGE SOUND +//=============================================================================================================== +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CTeamMessage_Sound::CTeamMessage_Sound( CTFTeam *pTeam, int iMessageID, CBaseEntity *pEntity, float flTTL ) : + CTeamMessage( pTeam, iMessageID, pEntity, flTTL ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTeamMessage_Sound::SetSound( char *sSound ) +{ + CBaseEntity::PrecacheScriptSound( sSound ); + m_SoundName = sSound; +} + +//----------------------------------------------------------------------------- +// Purpose: Called when the team manager wants me to fire myself +//----------------------------------------------------------------------------- +void CTeamMessage_Sound::FireMessage( void ) +{ + Assert( m_SoundName.String() ); + + // Play my sound to all the team's members + for ( int i = 0; i < m_pTeam->GetNumPlayers(); i++ ) + { + CBasePlayer *pPlayer = m_pTeam->GetPlayer(i); + + CSingleUserRecipientFilter filter( pPlayer ); + CBaseEntity::EmitSound( filter, pPlayer->entindex(), m_SoundName.String() ); + } +}
\ No newline at end of file diff --git a/game/server/tf2/team_messages.h b/game/server/tf2/team_messages.h new file mode 100644 index 0000000..536e836 --- /dev/null +++ b/game/server/tf2/team_messages.h @@ -0,0 +1,83 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef TEAM_MESSAGES_H +#define TEAM_MESSAGES_H +#ifdef _WIN32 +#pragma once +#endif + +#include "utlsymbol.h" + +// Message IDs +enum +{ + // Reinforcements + TEAMMSG_REINFORCEMENTS_ARRIVED, + + // Carriers / Harvesters + TEAMMSG_CARRIER_UNDER_ATTACK, + TEAMMSG_CARRIER_DESTROYED, + TEAMMSG_HARVESTER_UNDER_ATTACK, + TEAMMSG_HARVESTER_DESTROYED, + + // Resources + TEAMMSG_RESOURCE_ZONE_EMPTIED, + + // Techtree + TEAMMSG_NEW_TECH_LEVEL_OPEN, + + // Custom sounds + TEAMMSG_CUSTOM_SOUND, +}; + +//----------------------------------------------------------------------------- +// Purpose: Message sent to a team for the purpose of updating its members about some event +//----------------------------------------------------------------------------- +abstract_class CTeamMessage +{ +public: + CTeamMessage( CTFTeam *pTeam, int iMessageID, CBaseEntity *pEntity, float flTTL ); + + static CTeamMessage *Create( CTFTeam *pTeam, int iMessageID, CBaseEntity *pEntity ); + + // Called when the team manager wants me to fire myself + virtual void FireMessage( void ) = 0; + + // Accessors + virtual int GetID( void ) { return m_iMessageID; }; + virtual float GetTTL( void ) { return m_flTTL; }; + virtual CBaseEntity *GetEntity( void ) { return m_hEntity; }; + virtual CTFTeam *GetTeam( void ) { return m_pTeam; }; + + virtual void SetData( char *pszData ) { return; } + +protected: + int m_iMessageID; + float m_flTTL; + EHANDLE m_hEntity; + CTFTeam *m_pTeam; + CUtlSymbol m_SoundName; +}; + +//----------------------------------------------------------------------------- +// Purpose: Team message that plays a sound to the members of the team +//----------------------------------------------------------------------------- +class CTeamMessage_Sound : public CTeamMessage +{ +public: + CTeamMessage_Sound( CTFTeam *pTeam, int iMessageID, CBaseEntity *pEntity, float flTTL ); + + // Set my sound + virtual void SetSound( char *sSound ); + // Called when the team manager wants me to fire myself + virtual void FireMessage( void ); + + virtual void SetData( char *pszData ) { SetSound( pszData ); } +}; + +#endif // TEAM_MESSAGES_H diff --git a/game/server/tf2/tf_accuracy.cpp b/game/server/tf2/tf_accuracy.cpp new file mode 100644 index 0000000..db2b342 --- /dev/null +++ b/game/server/tf2/tf_accuracy.cpp @@ -0,0 +1,168 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: TF2 Accuracy system +// +// $Workfile: $ +// $Date: $ +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "player.h" +#include "tf_player.h" +#include "basecombatweapon.h" +#include "vstdlib/random.h" + + + + +// THIS ISN'T USED ANYMORE. NO REASON TO MAKE OUR HITSCAN WPNS THIS COMPLEX + + + + +// Accuracy is measured as the weapons spread in inches at 1024 units (~85 feet) +// Accuracy is sent to the client, where it's used to generate the size of the accuracy representation. +// Accuracy is a "floating" value, in that it's always moving towards a target accuracy, and takes some time to change + +// Accuracy Multipliers +// < 1 increases accuracy, > 1 decreases +#define ACCMULT_DUCKING 0.75 // Player is ducking +#define ACCMULT_RUNNING 1.25 // Player is moving >50% of his max speed + +// Ricochet Multiplier +// This works differently to other acc multipliers. +#define ACCMULT_RICOCHET 1.0 // Player is being suppressed by bullet fire +#define ACC_RICOCHET_TIME 1.0 // Amount of time accuracy is affected by a ricochet near the player +#define ACC_RICOCHET_MULTIPLE 0.25 // The effect of ricochets on accuracy is multiplied by this by the number of ricochets nearby +#define ACC_RICOCHET_CAP 10 // Maximum number of bullets to register for suppression fire + +#define ACCURACY_CHANGE_SPEED 5 + +//----------------------------------------------------------------------------- +// Purpose: Calculates the players "accuracy" level +//----------------------------------------------------------------------------- +void CBaseTFPlayer::CalculateAccuracy( void ) +{ + static flLastTime = 0; + + // Get the time since the last calculation + float flTimeSlice = (gpGlobals->curtime - flLastTime); + m_flTargetAccuracy = 0; + + if ( !GetPlayerClass() ) + return; + + // Get the base accuracy from the current weapon + if ( m_hActiveWeapon ) + { + m_flTargetAccuracy = m_hActiveWeapon->GetAccuracy(); + + // Accuracy is increased if the player's crouching + if ( GetFlags() & FL_DUCKING ) + m_flTargetAccuracy *= m_hActiveWeapon->GetDuckingMultiplier(); + + // Accuracy is decreased if the player's moving + if ( m_vecVelocity.Length2D() > ( GetPlayerClass()->GetMaxSpeed() * 0.5 ) ) + m_flTargetAccuracy *= m_hActiveWeapon->GetRunningMultiplier(); + } + + // Accuracy is decreased if the player's arms are injured + + // Accuracy is increased if there's an Officer nearby + + // Accuracy is decreased if this player's being supressed (bullets/explosions impacting nearby) + float flFarTime = (m_flLastRicochetNearby + ACC_RICOCHET_TIME); + if ( gpGlobals->curtime <= flFarTime ) + m_flTargetAccuracy *= 1 + (m_flNumberOfRicochets * ACC_RICOCHET_MULTIPLE) * (ACCMULT_RICOCHET * ((flFarTime - gpGlobals->curtime) / ACC_RICOCHET_TIME)); + + // Accuracy is decreased if the player's just been hit by a bullet/explosion + + // Now float towards the target accuracy + if ( m_bSnapAccuracy ) + { + m_bSnapAccuracy = false; + m_flAccuracy = m_flTargetAccuracy; + } + else + { + if ( m_flAccuracy < m_flTargetAccuracy ) + { + m_flAccuracy += (flTimeSlice * ACCURACY_CHANGE_SPEED); + if ( m_flAccuracy > m_flTargetAccuracy ) + m_flAccuracy = m_flTargetAccuracy ; + } + else if ( m_flAccuracy > m_flTargetAccuracy ) + { + m_flAccuracy -= (flTimeSlice * ACCURACY_CHANGE_SPEED); + if ( m_flAccuracy < m_flTargetAccuracy ) + m_flAccuracy = m_flTargetAccuracy ; + } + } + + // Clip to prevent silly accuracies + if ( m_flAccuracy > 1024 ) + m_flAccuracy = 1024; + + flLastTime = gpGlobals->curtime; +} + +//----------------------------------------------------------------------------- +// Purpose: Snap the players accuracy immediately +//----------------------------------------------------------------------------- +void CBaseTFPlayer::SnapAccuracy( void ) +{ + m_bSnapAccuracy = true; +} + +//----------------------------------------------------------------------------- +// Purpose: Return the player's current accuracy +//----------------------------------------------------------------------------- +float CBaseTFPlayer::GetAccuracy( void ) +{ + return m_flAccuracy; +} + +//----------------------------------------------------------------------------- +// Purpose: Bullets / Explosions are hitting near the player. Reduce his/her accuracy. +//----------------------------------------------------------------------------- +void CBaseTFPlayer::Supress( void ) +{ + if ( gpGlobals->curtime <= (m_flLastRicochetNearby + ACC_RICOCHET_TIME) ) + { + m_flNumberOfRicochets = MIN( ACC_RICOCHET_CAP, m_flNumberOfRicochets + 1 ); + } + else + { + m_flNumberOfRicochets = 1; + } + + m_flLastRicochetNearby = gpGlobals->curtime; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +Vector CBaseTFPlayer::GenerateFireVector( Vector *viewVector ) +{ + // Calculate the weapon spread from the player's accuracy + float flAcc = (GetAccuracy() * 0.5) / ACCURACY_DISTANCE; + float flAccuracyAngle = RAD2DEG( atan( flAcc ) ); + // If the user passed in a viewVector, use it, otherwise use player's v_angle + Vector angShootAngles = viewVector ? *viewVector : pl->v_angle; + if ( flAccuracyAngle ) + { + float x, y, z; + do { + x = random->RandomFloat(-0.5,0.5) + random->RandomFloat(-0.5,0.5); + y = random->RandomFloat(-0.5,0.5) + random->RandomFloat(-0.5,0.5); + z = x*x+y*y; + } while (z > 1); + + angShootAngles.x = UTIL_AngleMod( angShootAngles.x + (x * flAccuracyAngle) ); + angShootAngles.y = UTIL_AngleMod( angShootAngles.y + (y * flAccuracyAngle) ); + } + + Vector forward; + AngleVectors( angShootAngles, &forward ); + return forward; +} diff --git a/game/server/tf2/tf_ai_hint.h b/game/server/tf2/tf_ai_hint.h new file mode 100644 index 0000000..8e305fa --- /dev/null +++ b/game/server/tf2/tf_ai_hint.h @@ -0,0 +1,34 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef TF_AI_HINT_H +#define TF_AI_HINT_H +#ifdef _WIN32 +#pragma once +#endif + +//========================================================= +// hints - these MUST coincide with the HINTS listed under +// info_node in the FGD file! +//========================================================= +enum TF_Hint_e +{ + HINT_RESOURCE_ZONE_AREA = 2000, + + // The carrier starts at base_area land spot, goes first to + // the hover spot, then goes to the dropoff hover spot and + // finally to the dropoff landspot + HINT_AIR_CARRIER_DROPOFF_POINT_LANDSPOT, + HINT_AIR_CARRIER_DROPOFF_POINT_HOVERSPOT, + HINT_AIR_CARRIER_BASE_AREA_LANDSPOT, + HINT_AIR_CARRIER_BASE_AREA_HOVERSPOT, + + // The ground collector needs hints to drive itself + HINT_GROUNDCOLLECTOR_ZONE_ENTRANCE, +}; + +#endif // TF_AI_HINT_H diff --git a/game/server/tf2/tf_basecombatweapon.cpp b/game/server/tf2/tf_basecombatweapon.cpp new file mode 100644 index 0000000..b4c374f --- /dev/null +++ b/game/server/tf2/tf_basecombatweapon.cpp @@ -0,0 +1,137 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Base TF Combat weapon +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "animation.h" +#include "tf_player.h" +#include "tf_basecombatweapon.h" +#include "soundent.h" +#include "weapon_twohandedcontainer.h" +#include "tf_gamerules.h" +#include "tf_obj.h" + +//==================================================================================================== +// BASE TF MACHINEGUN +//==================================================================================================== +IMPLEMENT_SERVERCLASS_ST(CTFMachineGun, DT_TFMachineGun ) +END_SEND_TABLE() + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFMachineGun::PrimaryAttack( void ) +{ + // Only the player fires this way so we can cast + CBasePlayer *pPlayer = ToBasePlayer( GetOwner() ); + if (!pPlayer) + return; + + // Abort here to handle burst and auto fire modes + if ( (GetMaxClip1() != -1 && m_iClip1 == 0) || (GetMaxClip1() == -1 && !pPlayer->GetAmmoCount(m_iPrimaryAmmoType) ) ) + return; + + pPlayer->DoMuzzleFlash(); + + + // To make the firing framerate independent, we may have to fire more than one bullet here on low-framerate systems, + // especially if the weapon we're firing has a really fast rate of fire. + if ( GetSequence() != SelectWeightedSequence( ACT_VM_PRIMARYATTACK )) + { + m_flNextPrimaryAttack = gpGlobals->curtime; + } + int iBulletsToFire = 0; + float fireRate = GetFireRate(); + + while ( m_flNextPrimaryAttack <= gpGlobals->curtime ) + { + // MUST call sound before removing a round from the clip of a CMachineGun + WeaponSound(SINGLE, m_flNextPrimaryAttack); + m_flNextPrimaryAttack = m_flNextPrimaryAttack + fireRate; + iBulletsToFire++; + } + + // Make sure we don't fire more than the amount in the clip, if this weapon uses clips + if ( GetMaxClip1() != -1 ) + { + if ( iBulletsToFire > m_iClip1 ) + iBulletsToFire = m_iClip1; + m_iClip1 -= iBulletsToFire; + } + else + { + if ( iBulletsToFire > pPlayer->GetAmmoCount(m_iPrimaryAmmoType) ) + iBulletsToFire = pPlayer->GetAmmoCount(m_iPrimaryAmmoType); + pPlayer->RemoveAmmo( iBulletsToFire, m_iPrimaryAmmoType ); + } + + // Not time to fire any bullets yet? + if ( !iBulletsToFire ) + return; + + // Fire the bullets + Vector vecSrc = pPlayer->Weapon_ShootPosition( ); + Vector vecAiming = pPlayer->GetAutoaimVector( AUTOAIM_5DEGREES ); + + // Factor in the view kick + AddViewKick(); + + float range = m_pRangeCVar ? m_pRangeCVar->GetFloat() : 1024.0f; + + if ( !m_pRangeCVar ) + { + Msg( "Weapon missing m_pRangeCVar!!!\n" ); + } + + FireBullets( this, iBulletsToFire, vecSrc, vecAiming, GetBulletSpread(), range, m_iPrimaryAmmoType, 2 ); + + if (!m_iClip1 && pPlayer->GetAmmoCount(m_iPrimaryAmmoType) <= 0) + { + // HEV suit - indicate out of ammo condition + pPlayer->SetSuitUpdate("!HEV_AMO0", FALSE, 0); + } + + PlayAttackAnimation( GetPrimaryAttackActivity() ); + + // Register a muzzleflash for the AI + pPlayer->SetMuzzleFlashTime( gpGlobals->curtime + 0.5 ); + + CheckRemoveDisguise(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +const Vector& CTFMachineGun::GetBulletSpread( void ) +{ + static Vector cone = VECTOR_CONE_3DEGREES; + return cone; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFMachineGun::FireBullets( CBaseTFCombatWeapon *pWeapon, int cShots, const Vector &vecSrc, const Vector &vecDirShooting, const Vector &vecSpread, float flDistance, int iBulletType, int iTracerFreq) +{ + if ( CBaseTFPlayer *pPlayer = (CBaseTFPlayer*)GetOwner() ) + { + float damage = m_pDamageCVar ? m_pDamageCVar->GetFloat() : 1; + + if ( !m_pDamageCVar ) + { + Msg( "Weapon missing m_pDamageCVar!!!!\n" ); + } + + TFGameRules()->FireBullets( CTakeDamageInfo( this, pPlayer, damage, DMG_BULLET ), cShots, vecSrc, vecDirShooting, vecSpread, flDistance, iBulletType, 4, entindex(), 0 ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +float CTFMachineGun::GetFireRate( void ) +{ + return 1.0; +} diff --git a/game/server/tf2/tf_basecombatweapon.h b/game/server/tf2/tf_basecombatweapon.h new file mode 100644 index 0000000..a35f7db --- /dev/null +++ b/game/server/tf2/tf_basecombatweapon.h @@ -0,0 +1,39 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: TF's derived BaseCombatWeapon +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef TF_BASECOMBATWEAPON_H +#define TF_BASECOMBATWEAPON_H +#ifdef _WIN32 +#pragma once +#endif + +#include "baseplayer_shared.h" +#include "basetfplayer_shared.h" +#include "basetfcombatweapon_shared.h" + +class CBaseObject; +class CBaseTechnology; +class CBaseTFPlayer; + +//----------------------------------------------------------------------------- +// Purpose: Base TF Machinegun +//----------------------------------------------------------------------------- +class CTFMachineGun : public CBaseTFCombatWeapon +{ + DECLARE_CLASS( CTFMachineGun, CBaseTFCombatWeapon ); +public: + + DECLARE_SERVERCLASS(); + + virtual void PrimaryAttack( void ); + virtual void FireBullets( CBaseTFCombatWeapon *pWeapon, int cShots, const Vector &vecSrc, const Vector &vecDirShooting, const Vector &vecSpread, float flDistance, int iBulletType, int iTracerFreq); + virtual const Vector& GetBulletSpread( void ); + virtual float GetFireRate( void ); +}; + +#endif // TF_BASECOMBATWEAPON_H + diff --git a/game/server/tf2/tf_basefourwheelvehicle.cpp b/game/server/tf2/tf_basefourwheelvehicle.cpp new file mode 100644 index 0000000..f2b0808 --- /dev/null +++ b/game/server/tf2/tf_basefourwheelvehicle.cpp @@ -0,0 +1,762 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: A moving vehicle that is used as a battering ram +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" +#include "tf_basefourwheelvehicle.h" +#include "engine/IEngineSound.h" +#include "soundenvelope.h" +#include "vcollide_parse.h" +#include "in_buttons.h" +#include "tf_movedata.h" + +#define BASEFOURWHEELEDVEHICLE_THINK_CONTEXT "BaseFourWheeledThink" +#define BASEFOURWHEELEDVEHICLE_DEPLOYTHINK_CONTEXT "BaseFourWheeledDeployThink" +// HACK HAC +#define BASEFOURWHEELEDVEHICLE_STOPTHERODEO_CONTEXT "BaseFourWheeledVehicleStopTheRodeoMadnessThink" + +ConVar road_feel( "road_feel", "0.1", FCVAR_NOTIFY | FCVAR_REPLICATED ); +extern ConVar tf_fastbuild; + +BEGIN_DATADESC( CBaseTFFourWheelVehicle ) + + DEFINE_EMBEDDED( m_VehiclePhysics ), + + DEFINE_INPUTFUNC( FIELD_FLOAT, "Throttle", InputThrottle ), + DEFINE_INPUTFUNC( FIELD_FLOAT, "Steer", InputSteering ), + DEFINE_INPUTFUNC( FIELD_FLOAT, "Action", InputAction ), + DEFINE_INPUTFUNC( FIELD_VOID, "TurnOn", InputTurnOn ), + DEFINE_INPUTFUNC( FIELD_VOID, "TurnOff", InputTurnOff ), + +END_DATADESC() + + +// Used for debugging to make vehicle deploying go really fast. +ConVar tf_fastdeploy( "tf_fastdeploy", "0", FCVAR_CHEAT ); + + +IMPLEMENT_SERVERCLASS_ST(CBaseTFFourWheelVehicle, DT_BaseTFFourWheelVehicle) + SendPropFloat( SENDINFO( m_flDeployFinishTime ), 0, SPROP_NOSCALE ), + SendPropInt( SENDINFO( m_eDeployMode ), NUM_VEHICLE_DEPLOYMODE_BITS, SPROP_UNSIGNED ), + SendPropInt( SENDINFO( m_bBoostUpgrade ), 1, SPROP_UNSIGNED ), + SendPropInt( SENDINFO( m_nBoostTimeLeft ), 8, SPROP_UNSIGNED ), +END_SEND_TABLE() + +ConVar fourwheelvehicle_hit_damage( "fourwheelvehicle_hit_damage","40", FCVAR_NONE, "Four-wheel vehicle hit damage" ); +ConVar fourwheelvehicle_hit_damage_boostmod( "fourwheelvehicle_hit_damage_boostmod", "1.5", FCVAR_NONE, "Four-wheel vehicle boosted hit damage modifier" ); +ConVar fourwheelvehicle_hit_mindamagevel( "fourwheelvehicle_hit_mindamagevel","100", FCVAR_NONE, "Four-wheel vehciel hit velocity for min damage" ); +ConVar fourwheelvehicle_hit_maxdamagevel( "fourwheelvehicle_hit_maxdamagevel","230", FCVAR_NONE, "Four-wheel vehicle hit velocity for max damage" ); +ConVar fourwheelvehicle_impact_time( "fourwheelvehicle_impact_time", "1.0", FCVAR_NONE, "Four-wheel vehicle impact wait time." ); +ConVar fourwheelvehicle_hit_damage_player( "fourwheelvehicle_hit_damage_player", "30.0f", FCVAR_NONE, "Four-wheel vehicle hit player damage." ); + +//----------------------------------------------------------------------------- +// Constructor +//----------------------------------------------------------------------------- +#pragma warning( disable: 4355 ) +CBaseTFFourWheelVehicle::CBaseTFFourWheelVehicle() : m_VehiclePhysics(this) +{ + m_flDeployFinishTime = -1; +} +#pragma warning( default: 4355 ) + +//----------------------------------------------------------------------------- +// Destructor +//----------------------------------------------------------------------------- +CBaseTFFourWheelVehicle::~CBaseTFFourWheelVehicle () +{ +} + +//----------------------------------------------------------------------------- +// Purpose: Put a vehicle in a deploy state. Turn off the engine and run a +// deploy animation. +//----------------------------------------------------------------------------- +bool CBaseTFFourWheelVehicle::Deploy( void ) +{ + // Make sure we're allowed to deploy here + if ( !IsReadyToDrive() ) + return false; + + // Check to see if we are already in a deploy mode. + if ( m_eDeployMode != VEHICLE_MODE_NORMAL ) + return false; + + // Disable the vehicle's motion. + DisableMotion(); + + // Save pre-deploy activity. + m_PreDeployActivity = GetActivity(); + + // Set the deploying activity - ACT_DEPLOY + SetActivity( ACT_DEPLOY ); + + // Get the deployment time. + float flDeployTime = SequenceDuration(); + if ( tf_fastdeploy.GetBool() ) + flDeployTime = 1; + + m_flDeployFinishTime = gpGlobals->curtime + flDeployTime; + + SetContextThink( BaseFourWheeledVehicleDeployThink, gpGlobals->curtime + flDeployTime, + BASEFOURWHEELEDVEHICLE_DEPLOYTHINK_CONTEXT ); + + // Set the deploy mode. + m_eDeployMode = VEHICLE_MODE_DEPLOYING; + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: Release a vehicle from a deployed state. Run a de-coupling +// animation and turn on the vehicle. +//----------------------------------------------------------------------------- +void CBaseTFFourWheelVehicle::UnDeploy( void ) +{ + // Make sure we are deployed. + if ( !IsDeployed() ) + return; + + // Enable motion and turn on the vehicle. + EnableMotion(); + + // Set the undeploying activity - ACT_UNDEPLOY + SetActivity( ACT_UNDEPLOY ); + + // Get the undeployment time. + float flUnDeployTime = SequenceDuration(); + if ( tf_fastdeploy.GetBool() ) + flUnDeployTime = 1; + + m_flDeployFinishTime = gpGlobals->curtime + flUnDeployTime; + + SetContextThink( BaseFourWheeledVehicleDeployThink, gpGlobals->curtime + flUnDeployTime, + BASEFOURWHEELEDVEHICLE_DEPLOYTHINK_CONTEXT ); + + // Set the deploy mode. + m_eDeployMode = VEHICLE_MODE_UNDEPLOYING; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseTFFourWheelVehicle::CancelDeploy( void ) +{ + // Check for the deploying state. + if ( !IsDeploying() ) + return; + + // Re-enable the motion. + EnableMotion(); + + // Reset the activity to the previous activity. + SetActivity( m_PreDeployActivity ); + + // Reset the deploy mode. + m_eDeployMode = VEHICLE_MODE_NORMAL; + + m_flDeployFinishTime = -1; + + // Turn off the context think. + SetContextThink( NULL, 0, BASEFOURWHEELEDVEHICLE_DEPLOYTHINK_CONTEXT ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseTFFourWheelVehicle::BaseFourWheeledVehicleDeployThink( void ) +{ + // Called from deploy. + if ( IsDeploying() ) + { + OnFinishedDeploy(); + m_flDeployFinishTime = -1; + } + // Called from undeploy. + else if ( IsUndeploying() ) + { + OnFinishedUnDeploy(); + m_flDeployFinishTime = -1; + } + + // Turn off the context think. + SetContextThink( NULL, 0, BASEFOURWHEELEDVEHICLE_DEPLOYTHINK_CONTEXT ); +} + +void CBaseTFFourWheelVehicle::BaseFourWheeledVehicleStopTheRodeoMadnessThink( void ) +{ + // HACK HACK: See note above at FinishBuilding call + // This resets the handbrake, so the newly placed object doesn't roll down any hills. + m_VehiclePhysics.ResetControls(); + + // Turn off the context think. + SetContextThink( NULL, 0, BASEFOURWHEELEDVEHICLE_STOPTHERODEO_CONTEXT ); + + // Start our base think + SetContextThink( BaseFourWheeledVehicleThink, gpGlobals->curtime + 0.1, BASEFOURWHEELEDVEHICLE_THINK_CONTEXT ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseTFFourWheelVehicle::OnFinishedDeploy( void ) +{ + SetActivity( ACT_DEPLOY_IDLE ); + m_eDeployMode = VEHICLE_MODE_DEPLOYED; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseTFFourWheelVehicle::OnFinishedUnDeploy( void ) +{ + m_eDeployMode = VEHICLE_MODE_NORMAL; +} + +//----------------------------------------------------------------------------- +// Purpose: Allow the vehicle to move. +//----------------------------------------------------------------------------- +void CBaseTFFourWheelVehicle::EnableMotion( void ) +{ + // Enable vehicle chasis motion. + IPhysicsObject *pVehicleObject = VPhysicsGetObject(); + if ( pVehicleObject ) + { + pVehicleObject->EnableMotion( true ); + } + + // Enable motion on the tires. + m_VehiclePhysics.EnableMotion(); +} + +//----------------------------------------------------------------------------- +// Purpose: Dis-allow the vehicle to move. +//----------------------------------------------------------------------------- +void CBaseTFFourWheelVehicle::DisableMotion( void ) +{ + // Disable vehicle chasis motion. + IPhysicsObject *pVehicleObject = VPhysicsGetObject(); + if ( pVehicleObject ) + { + pVehicleObject->EnableMotion( false ); + } + + // Disable motion on the tires. + m_VehiclePhysics.DisableMotion(); +} + +//----------------------------------------------------------------------------- +// Precache +//----------------------------------------------------------------------------- +void CBaseTFFourWheelVehicle::Precache() +{ + BaseClass::Precache(); + + PrecacheScriptSound( "BaseTFFourWheelVehicle.EMP" ); + PrecacheScriptSound( "BaseTFFourWheelVehicle.RamSound" ); +} + +//----------------------------------------------------------------------------- +// Spawn +//----------------------------------------------------------------------------- +void CBaseTFFourWheelVehicle::Spawn( ) +{ + SetModel( STRING( GetModelName() ) ); +// CFourWheelServerVehicle *pServerVehicle = dynamic_cast<CFourWheelServerVehicle*>(GetServerVehicle()); +// m_VehiclePhysics.SetOuter( this, pServerVehicle ); + m_VehiclePhysics.Spawn(); + BaseClass::Spawn(); + // The base class spawn sets a default collision group, so this needs to + // be called post. + SetCollisionGroup( COLLISION_GROUP_VEHICLE ); + + m_eDeployMode = VEHICLE_MODE_NORMAL; + + SetBoostUpgrade( false ); + + m_flNextHitTime = 0.0f; +} + +//----------------------------------------------------------------------------- +// Teleport +//----------------------------------------------------------------------------- +void CBaseTFFourWheelVehicle::Teleport( const Vector *newPosition, const QAngle *newAngles, const Vector *newVelocity ) +{ + // We basically just have to make sure the wheels are in the right place + // after teleportation occurs + matrix3x4_t startMatrixInv; + MatrixInvert( EntityToWorldTransform(), startMatrixInv ); + + BaseClass::Teleport( newPosition, newAngles, newVelocity ); + + // Teleport the vehicle physics from the starting position to the ending one + matrix3x4_t relativeTransform; + ConcatTransforms( EntityToWorldTransform(), startMatrixInv, relativeTransform ); + m_VehiclePhysics.Teleport( relativeTransform ); +} + + +//----------------------------------------------------------------------------- +// Debugging methods +//----------------------------------------------------------------------------- +void CBaseTFFourWheelVehicle::DrawDebugGeometryOverlays() +{ + if (m_debugOverlays & OVERLAY_BBOX_BIT) + { + m_VehiclePhysics.DrawDebugGeometryOverlays(); + } + BaseClass::DrawDebugGeometryOverlays(); +} + +int CBaseTFFourWheelVehicle::DrawDebugTextOverlays() +{ + int nOffset = BaseClass::DrawDebugTextOverlays(); + if (m_debugOverlays & OVERLAY_TEXT_BIT) + { + nOffset = m_VehiclePhysics.DrawDebugTextOverlays( nOffset ); + } + return nOffset; +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CBaseTFFourWheelVehicle::StartBuilding( CBaseEntity *pPlayer ) +{ + if (!BaseClass::StartBuilding(pPlayer)) + return false; + + // Until we're finished building, turn off vphysics-based motion + SetSolid( SOLID_VPHYSICS ); + VPhysicsInitStatic(); + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseTFFourWheelVehicle::FinishedBuilding( void ) +{ + BaseClass::FinishedBuilding(); + + char pScriptName[128]; + Q_snprintf( pScriptName, sizeof( pScriptName ), "scripts/vehicles/%s.txt", GetClassname() ); + m_VehiclePhysics.Initialize( pScriptName, true ); + + // HACK HACK: This is a hack to avoid physics spazzing out on a newly created vehicle with the handbrake + // set. We create and activate it, but then release the handbrake for a single Think function call and + // then zero out the controls right then. This seems to stabilize something in the physics simulator. + m_VehiclePhysics.ReleaseHandbrake(); + SetContextThink( BaseFourWheeledVehicleStopTheRodeoMadnessThink, gpGlobals->curtime, BASEFOURWHEELEDVEHICLE_STOPTHERODEO_CONTEXT ); + + ResetDeteriorationTime(); +} + +//----------------------------------------------------------------------------- +// Input methods +//----------------------------------------------------------------------------- +void CBaseTFFourWheelVehicle::InputThrottle( inputdata_t &inputdata ) +{ + m_VehiclePhysics.SetThrottle( inputdata.value.Float() ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseTFFourWheelVehicle::InputSteering( inputdata_t &inputdata ) +{ + m_VehiclePhysics.SetSteering( inputdata.value.Float(), 2*gpGlobals->frametime ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseTFFourWheelVehicle::InputAction( inputdata_t &inputdata ) +{ + m_VehiclePhysics.SetAction( inputdata.value.Float() ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseTFFourWheelVehicle::InputTurnOn( inputdata_t &inputdata ) +{ + if (!m_VehiclePhysics.IsOn()) + { + SetContextThink( BaseFourWheeledVehicleThink, gpGlobals->curtime + 0.1, BASEFOURWHEELEDVEHICLE_THINK_CONTEXT ); + m_VehiclePhysics.TurnOn( ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseTFFourWheelVehicle::InputTurnOff( inputdata_t &inputdata ) +{ + if ( m_VehiclePhysics.IsOn() ) + { + m_VehiclePhysics.TurnOff( ); + } +} + +//----------------------------------------------------------------------------- +// Input methods +//----------------------------------------------------------------------------- +void CBaseTFFourWheelVehicle::BaseFourWheeledVehicleThink() +{ + if (m_VehiclePhysics.Think()) + { + SetNextThink( gpGlobals->curtime, BASEFOURWHEELEDVEHICLE_THINK_CONTEXT ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseTFFourWheelVehicle::VPhysicsUpdate( IPhysicsObject *pPhysics ) +{ + // must be a wheel + if (!m_VehiclePhysics.VPhysicsUpdate(pPhysics)) + return; + + BaseClass::VPhysicsUpdate( pPhysics ); +} + + +//----------------------------------------------------------------------------- +// Methods related to getting in and out of the vehicle +//----------------------------------------------------------------------------- +void CBaseTFFourWheelVehicle::SetPassenger( int nRole, CBasePlayer *pEnt ) +{ + if ( nRole == VEHICLE_ROLE_DRIVER ) + { + if (pEnt) + { + PlayerControlInit( ToBasePlayer(pEnt) ); + } + else + { + PlayerControlShutdown( ); + } + } + BaseClass::SetPassenger( nRole, pEnt ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseTFFourWheelVehicle::PlayerControlInit( CBasePlayer *pPlayer ) +{ + // Blat out the view offset + m_savedViewOffset = pPlayer->GetViewOffset(); + pPlayer->SetViewOffset( vec3_origin ); + + m_playerOn.FireOutput( pPlayer, this, 0 ); + InputTurnOn( inputdata_t() ); + + // Release the handbrake. + if ( !IsDeployed() ) + { + m_VehiclePhysics.ReleaseHandbrake(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseTFFourWheelVehicle::ResetUseKey( CBasePlayer *pPlayer ) +{ + pPlayer->m_afButtonPressed &= ~IN_USE; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseTFFourWheelVehicle::PlayerControlShutdown() +{ + CBasePlayer *pPlayer = GetDriverPlayer(); + if ( !pPlayer ) + return; + + ResetUseKey( pPlayer ); + pPlayer->SetViewOffset( m_savedViewOffset ); + + m_playerOff.FireOutput( pPlayer, this, 0 ); + // clear out the fire buttons + m_attackaxis.Set( 0, pPlayer, this ); + m_attack2axis.Set( 0, pPlayer, this ); + + InputTurnOff( inputdata_t() ); +} + +//----------------------------------------------------------------------------- +// Purpose: Powerup has just started +//----------------------------------------------------------------------------- +void CBaseTFFourWheelVehicle::PowerupStart( int iPowerup, float flAmount, CBaseEntity *pAttacker, CDamageModifier *pDamageModifier ) +{ + switch( iPowerup ) + { + case POWERUP_EMP: + m_VehiclePhysics.SetMaxThrottle( 0.1 ); + break; + + default: + break; + } + + BaseClass::PowerupStart( iPowerup, flAmount, pAttacker, pDamageModifier ); +} + +//----------------------------------------------------------------------------- +// Purpose: Powerup has just started +//----------------------------------------------------------------------------- +void CBaseTFFourWheelVehicle::PowerupEnd( int iPowerup ) +{ + switch ( iPowerup ) + { + case POWERUP_EMP: + m_VehiclePhysics.SetMaxThrottle( 1.0 ); + break; + + default: + break; + } + + BaseClass::PowerupEnd( iPowerup ); +} + +//----------------------------------------------------------------------------- +// Methods related to actually driving the vehicle +//----------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseTFFourWheelVehicle::DriveVehicle( CBasePlayer *pPlayer, CUserCmd *ucmd ) +{ + // Lose control when the player dies + if ( pPlayer->IsAlive() == false ) + return; + + // Only the driver gets to drive. + int nRole = GetPassengerRole( pPlayer ); + if ( nRole != VEHICLE_ROLE_DRIVER ) + return; + + // No driving in the mothership, kids + if ( !tf_fastbuild.GetInt() && !IsReadyToDrive() ) + { + m_VehiclePhysics.SetHandbrake( true ); + m_VehiclePhysics.SetThrottle( 0 ); + m_VehiclePhysics.SetSteering( 0, 0 ); + m_attackaxis.Set( 0, pPlayer, this ); + m_attack2axis.Set( 0, pPlayer, this ); + return; + } + + // Update the boost time. + m_nBoostTimeLeft = m_VehiclePhysics.BoostTimeLeft(); + + // If the vehicle's emped, it can't drive + if ( HasPowerup( POWERUP_EMP ) ) + { + // Play sounds if they're trying to drive + if ( ucmd->buttons & (IN_MOVELEFT | IN_MOVERIGHT | IN_FORWARD | IN_BACK) ) + { + if ( m_flNextEmpSound < gpGlobals->curtime ) + { + EmitSound( "BaseTFFourWheelVehicle.EMP" ); + m_flNextEmpSound = gpGlobals->curtime + 2.0; + } + } + } + + ResetDeteriorationTime(); + + m_VehiclePhysics.UpdateDriverControls( ucmd, TICK_INTERVAL ); + + float attack = 0, attack2 = 0; + + if ( pPlayer->m_afButtonPressed & IN_ATTACK ) + { + m_pressedAttack.FireOutput( pPlayer, this, 0 ); + } + if ( pPlayer->m_afButtonPressed & IN_ATTACK2 ) + { + m_pressedAttack2.FireOutput( pPlayer, this, 0 ); + } + + if ( ucmd->buttons & IN_ATTACK ) + { + attack = 1; + } + if ( ucmd->buttons & IN_ATTACK2 ) + { + attack2 = 1; + } + + m_attackaxis.Set( attack, pPlayer, this ); + m_attack2axis.Set( attack2, pPlayer, this ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseTFFourWheelVehicle::SetupMove( CBasePlayer *pPlayer, CUserCmd *ucmd, IMoveHelper *pHelper, CMoveData *move ) +{ + BaseClass::SetupMove( pPlayer, ucmd, pHelper, move ); + + if ( IsDeployed() ) + return; + + DriveVehicle( pPlayer, ucmd ); + m_nMovementRole = GetPassengerRole( pPlayer ); + Assert( m_nMovementRole >= 0 ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseTFFourWheelVehicle::ProcessMovement( CBasePlayer *pPlayer, CMoveData *pMoveData ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseTFFourWheelVehicle::SetBoostUpgrade( bool bBoostUpgrade ) +{ + m_bBoostUpgrade = bBoostUpgrade; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CBaseTFFourWheelVehicle::IsBoostable( void ) +{ + return m_bBoostUpgrade; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseTFFourWheelVehicle::StartBoost( void ) +{ + if ( IsBoostable() ) + { + m_VehiclePhysics.SetBoost( 1.0f ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CBaseTFFourWheelVehicle::IsBoosting( void ) +{ + return m_VehiclePhysics.IsBoosting(); +} + +//----------------------------------------------------------------------------- +// Purpose: Vehicle damage! +//----------------------------------------------------------------------------- +void CBaseTFFourWheelVehicle::VPhysicsCollision( int index, gamevcollisionevent_t *pEvent ) +{ + BaseClass::VPhysicsCollision( index, pEvent ); + + int otherIndex = !index; + CBaseEntity *pEntity = pEvent->pEntities[otherIndex]; + + // We only damage objects... + // And only if we're travelling fast enough... + Assert( pEntity ); + if ( !pEntity->IsSolid() ) + return; + + // Ignore shields.. + if ( pEntity->GetCollisionGroup() == TFCOLLISION_GROUP_SHIELD ) + return; + + // Ignore anything that's not an object + if ( pEntity->Classify() != CLASS_MILITARY && !pEntity->IsPlayer() ) + return; + + // Ignore teammates + if ( InSameTeam( pEntity )) + return; + + // Ignore invulnerable stuff + if ( pEntity->m_takedamage == DAMAGE_NO ) + return; + + // See if we can damage again? (Time-based) + if ( m_flNextHitTime > gpGlobals->curtime ) + return; + + // Do damage based on velocity. + Vector vecVelocity = pEvent->preVelocity[index]; + + CTakeDamageInfo info; + info.SetInflictor( this ); + info.SetAttacker( GetDriverPlayer() ); + info.SetDamageType( DMG_CLUB ); + + float flMaxDamage = fourwheelvehicle_hit_damage.GetFloat(); + float flMaxDamageVel = fourwheelvehicle_hit_maxdamagevel.GetFloat(); + float flMinDamageVel = fourwheelvehicle_hit_mindamagevel.GetFloat(); + + float flVel = vecVelocity.Length(); + if ( flVel < flMinDamageVel ) + return; + + EmitSound( "BaseTFFourWheelVehicle.RamSound" ); + + float flDamageFactor = flMaxDamage; + // Special damage for players. + if ( pEntity->IsPlayer() ) + { + flDamageFactor = fourwheelvehicle_hit_damage_player.GetFloat(); + + if ( IsBoosting() ) + { + flDamageFactor *= 4.0f; + } + + // Knock the player up into the air + float flForceScale = (flVel*0.5) * 75 * 4; + Vector vecForce = vecVelocity; + VectorNormalize( vecForce ); + vecForce.z += 0.7; + vecForce *= flForceScale; + info.SetDamageForce( vecForce ); + } + // Damage to objects. + else + { + if ( IsBoosting() ) + { + flDamageFactor *= fourwheelvehicle_hit_damage_boostmod.GetFloat(); + } + + if ( ( flMaxDamageVel > flMinDamageVel ) && ( flVel < flMaxDamageVel ) ) + { + // Use less damage when we're not moving fast enough + float flVelocityFactor = ( flVel - flMinDamageVel ) / ( flMaxDamageVel - flMinDamageVel ); + flVelocityFactor *= flVelocityFactor; + flDamageFactor *= flVelocityFactor; + } + } + + info.SetDamage( flDamageFactor ); + Vector damagePos; + pEvent->pInternalData->GetContactPoint( damagePos ); + Vector damageForce = pEvent->postVelocity[index] * pEvent->pObjects[index]->GetMass(); + info.SetDamageForce( damageForce ); + info.SetDamagePosition( damagePos ); + PhysCallbackDamage( pEntity, info, *pEvent, index ); + + // Set next time hit time + m_flNextHitTime = gpGlobals->curtime + fourwheelvehicle_impact_time.GetFloat(); +} diff --git a/game/server/tf2/tf_basefourwheelvehicle.h b/game/server/tf2/tf_basefourwheelvehicle.h new file mode 100644 index 0000000..f9c6205 --- /dev/null +++ b/game/server/tf2/tf_basefourwheelvehicle.h @@ -0,0 +1,138 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: A base class that deals with four-wheel vehicles +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef TF_BASE_FOUR_WHEEL_VEHICLE_H +#define TF_BASE_FOUR_WHEEL_VEHICLE_H + +#ifdef _WIN32 +#pragma once +#endif + +#include "basetfvehicle.h" +#include "vphysics/vehicles.h" +#include "fourwheelvehiclephysics.h" +#include "tf_vehicleshared.h" + +class CMoveData; + +class CBaseTFFourWheelVehicle : public CBaseTFVehicle +{ +public: + DECLARE_CLASS( CBaseTFFourWheelVehicle, CBaseTFVehicle ); + DECLARE_SERVERCLASS(); + +public: + CBaseTFFourWheelVehicle(); + ~CBaseTFFourWheelVehicle (); + + // CBaseEntity + void Spawn(); + void Precache(); + void VPhysicsUpdate( IPhysicsObject *pPhysics ); + void DrawDebugGeometryOverlays(); + int DrawDebugTextOverlays(); + void Teleport( const Vector *newPosition, const QAngle *newAngles, const Vector *newVelocity ); + void BaseFourWheeledVehicleThink(); + void BaseFourWheeledVehicleDeployThink( void ); + + // HACK HACK: This is a hack to avoid physics spazzing out on a newly created vehicle with the handbrake + // set. We create and activate it, but then release the handbrake for a single Think function call and + // then zero out the controls right then. This seems to stabilize something in the physics simulator. + void BaseFourWheeledVehicleStopTheRodeoMadnessThink( void ); + + virtual bool StartBuilding( CBaseEntity *pPlayer ); + virtual void FinishedBuilding( void ); + + virtual void SetupMove( CBasePlayer *player, CUserCmd *ucmd, IMoveHelper *pHelper, CMoveData *move ); + virtual void ProcessMovement( CBasePlayer *pPlayer, CMoveData *pMoveData ); + virtual void SetPassenger( int nRole, CBasePlayer *pEnt ); + + // Powerup handling + virtual void PowerupStart( int iPowerup, float flAmount, CBaseEntity *pAttacker, CDamageModifier *pDamageModifier ); + virtual void PowerupEnd( int iPowerup ); + + // Collision + virtual void VPhysicsCollision( int index, gamevcollisionevent_t *pEvent ); + + // Inputs + void InputThrottle( inputdata_t &inputdata ); + void InputSteering( inputdata_t &inputdata ); + void InputAction( inputdata_t &inputdata ); + void InputTurnOn( inputdata_t &inputdata ); + void InputTurnOff( inputdata_t &inputdata ); + + // Boost + void SetBoostUpgrade( bool bBoostUpgrade ); + bool IsBoostable( void ); + bool IsBoosting( void ); + void StartBoost( void ); + + bool IsDeployed( void ) { return ( m_eDeployMode == VEHICLE_MODE_DEPLOYED ); } + bool IsDeploying( void ) { return ( m_eDeployMode == VEHICLE_MODE_DEPLOYING ); } + bool IsUndeploying( void ) { return ( m_eDeployMode == VEHICLE_MODE_UNDEPLOYING ); } + bool InDeployMode( void ) { return ( m_eDeployMode != VEHICLE_MODE_NORMAL ); } + + DECLARE_DATADESC(); + + // locals +protected: + // engine sounds + void SoundInit(); + void SoundShutdown(); + void SoundUpdate( const vehicle_operatingparams_t ¶ms, const vehicleparams_t &vehicle ); + void CalcWheelData( vehicleparams_t &vehicle ); + void ResetControls(); + + // Deploy + bool Deploy( void ); + void UnDeploy( void ); + void CancelDeploy( void ); + virtual void OnFinishedDeploy( void ); + virtual void OnFinishedUnDeploy( void ); + +private: + void DriveVehicle( CBasePlayer *pPlayer, CUserCmd *ucmd ); + void PlayerControlInit( CBasePlayer *pPlayer ); + void PlayerControlShutdown(); + void ResetUseKey( CBasePlayer *pPlayer ); + void InitializePoseParameters(); + bool ParseVehicleScript( solid_t &solid, vehicleparams_t &vehicle ); + + void EnableMotion( void ); + void DisableMotion( void ); + +private: + CFourWheelVehiclePhysics m_VehiclePhysics; + COutputEvent m_playerOn; + COutputEvent m_playerOff; + + COutputEvent m_pressedAttack; + COutputEvent m_pressedAttack2; + + COutputFloat m_attackaxis; + COutputFloat m_attack2axis; + + int m_nMovementRole; + Vector m_savedViewOffset; //[MAX_PASSENGERS]; + + float m_flNextEmpSound; + + // Deploy + CNetworkVar( VehicleModeDeploy_e, m_eDeployMode ); + Activity m_PreDeployActivity; + + // Used for vgui screens on the client. + CNetworkVar( float, m_flDeployFinishTime ); + CNetworkVar( bool, m_bBoostUpgrade ); + CNetworkVar( int, m_nBoostTimeLeft ); + + float m_flNextHitTime; +}; + + + +#endif // TF_BASE_FOUR_WHEEL_VEHICLE_H diff --git a/game/server/tf2/tf_carrier.cpp b/game/server/tf2/tf_carrier.cpp new file mode 100644 index 0000000..db4f992 --- /dev/null +++ b/game/server/tf2/tf_carrier.cpp @@ -0,0 +1 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// diff --git a/game/server/tf2/tf_carrier.h b/game/server/tf2/tf_carrier.h new file mode 100644 index 0000000..db4f992 --- /dev/null +++ b/game/server/tf2/tf_carrier.h @@ -0,0 +1 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// diff --git a/game/server/tf2/tf_class_commando.cpp b/game/server/tf2/tf_class_commando.cpp new file mode 100644 index 0000000..79a4943 --- /dev/null +++ b/game/server/tf2/tf_class_commando.cpp @@ -0,0 +1,586 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: The Commando Player Class +// +// $Workfile: $ +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "tf_player.h" +#include "tf_class_commando.h" +#include "tf_vehicle_teleport_station.h" +#include "EntityList.h" +#include "basecombatweapon.h" +#include "weapon_builder.h" +#include "tf_obj.h" +#include "tf_obj_rallyflag.h" +#include "tf_team.h" +#include "order_assist.h" +#include "engine/IEngineSound.h" +#include "weapon_twohandedcontainer.h" +#include "weapon_combatshield.h" + +ConVar tf_knockdowntime( "tf_knockdowntime", "3", FCVAR_NONE, "Length of time knocked-down players remain on the ground." ); + +// Adrenalin +ConVar class_commando_speed( "class_commando_speed","200", FCVAR_NONE, "Commando movement speed." ); +ConVar class_commando_rush_length( "class_commando_rush_length","10", FCVAR_NONE, "Commando's adrenalin rush length in seconds." ); +ConVar class_commando_rush_recharge( "class_commando_rush_recharge","60", FCVAR_NONE, "Commando's adrenalin rush recharge time in seconds." ); + +ConVar class_commando_battlecry_radius( "class_commando_battlecry_radius","512", FCVAR_NONE, "Commando's battlecry radius." ); +ConVar class_commando_battlecry_length( "class_commando_battlecry_length","10", FCVAR_NONE, "Length of adrenalin rush given by the Commando's battlecry in seconds." ); + +//============================================================================= +// +// Commando Data Table +// +BEGIN_SEND_TABLE_NOBASE( CPlayerClassCommando, DT_PlayerClassCommandoData ) + SendPropInt ( SENDINFO_STRUCTELEM( m_ClassData.m_bCanBullRush ), 1, SPROP_UNSIGNED ), + SendPropInt ( SENDINFO_STRUCTELEM( m_ClassData.m_bBullRush ), 1, SPROP_UNSIGNED ), + SendPropVector ( SENDINFO_STRUCTELEM( m_ClassData.m_vecBullRushDir ), -1, SPROP_COORD ), + SendPropVector ( SENDINFO_STRUCTELEM( m_ClassData.m_vecBullRushViewDir ), -1, SPROP_COORD ), + SendPropVector ( SENDINFO_STRUCTELEM( m_ClassData.m_vecBullRushViewGoalDir ), -1, SPROP_COORD ), + SendPropFloat ( SENDINFO_STRUCTELEM( m_ClassData.m_flBullRushTime ), 32, SPROP_NOSCALE ), + SendPropFloat ( SENDINFO_STRUCTELEM( m_ClassData.m_flDoubleTapForwardTime ), 32, SPROP_NOSCALE ), +END_SEND_TABLE() + +//----------------------------------------------------------------------------- +// Purpose: +// Output : const char +//----------------------------------------------------------------------------- +const char *CPlayerClassCommando::GetClassModelString( int nTeam ) +{ + if (nTeam == TEAM_HUMANS) + return "models/player/human_commando.mdl"; + else + return "models/player/alien_commando.mdl"; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CPlayerClassCommando::CPlayerClassCommando( CBaseTFPlayer *pPlayer, TFClass iClass ) : CPlayerClass( pPlayer, iClass ) +{ + for (int i = 0; i < MAX_TF_TEAMS; ++i) + { + SetClassModel( MAKE_STRING(GetClassModelString(i)), i ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CPlayerClassCommando::~CPlayerClassCommando() +{ + m_aHitPlayers.RemoveAll(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPlayerClassCommando::ClassActivate( void ) +{ + BaseClass::ClassActivate(); + + // Setup movement data. + SetupMoveData(); + + // Initialize the shared class data. + m_ClassData.m_bCanBullRush = false; + m_ClassData.m_bBullRush = false; + m_ClassData.m_vecBullRushDir.Init(); + m_ClassData.m_vecBullRushViewDir.Init(); + m_ClassData.m_vecBullRushViewGoalDir.Init(); + m_ClassData.m_flBullRushTime = COMMANDO_TIME_INVALID; + m_ClassData.m_flDoubleTapForwardTime = COMMANDO_TIME_INVALID; + + m_bCanRush = false; + m_bPersonalRush = false; + m_bHasBattlecry = false; + m_bCanBoot = false; + m_flNextBootCheck = 0.0f; // Time at which to recheck for the automatic melee attack + m_bOldBullRush = 0.0f; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPlayerClassCommando::ClassDeactivate( void ) +{ + m_hWpnShield = NULL; + m_hWpnPlasma = NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPlayerClassCommando::CreateClass( void ) +{ + BaseClass::CreateClass(); + + // Create our two handed weapon layout + m_hWpnShield = m_pPlayer->GetCombatShield(); + + CWeaponTwoHandedContainer *p = ( CWeaponTwoHandedContainer * )m_pPlayer->Weapon_OwnsThisType( "weapon_twohandedcontainer" ); + if ( !p ) + { + p = static_cast< CWeaponTwoHandedContainer * >( m_pPlayer->GiveNamedItem( "weapon_twohandedcontainer" ) ); + } + + if ( p && m_hWpnShield.Get() ) + { + m_hWpnShield->SetReflectViewModelAnimations( true ); + p->SetWeapons( NULL, m_hWpnShield ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPlayerClassCommando::RespawnClass( void ) +{ + BaseClass::RespawnClass(); + + m_flNextBootCheck = 0; +} + +//----------------------------------------------------------------------------- +// Purpose: Supply the player with Ammo. Return true if some ammo was given. +//----------------------------------------------------------------------------- +bool CPlayerClassCommando::ResupplyAmmo( float flFraction, ResupplyReason_t reason ) +{ + bool bGiven = false; + + if ((reason == RESUPPLY_ALL_FROM_STATION) || (reason == RESUPPLY_GRENADES_FROM_STATION)) + { + if (ResupplyAmmoType( 3 * flFraction, "Grenades" )) + bGiven = true; + if (ResupplyAmmoType( 1, "RallyFlags" )) + bGiven = true; + if (ResupplyAmmoType( 3, "Rockets" )) + bGiven = true; + } + + if ((reason == RESUPPLY_ALL_FROM_STATION) || (reason == RESUPPLY_AMMO_FROM_STATION)) + { + if (ResupplyAmmoType( 3, "Rockets" )) + bGiven = true; + } + + // On respawn, resupply base weapon ammo + if ( reason == RESUPPLY_RESPAWN ) + { + } + + if ( BaseClass::ResupplyAmmo(flFraction, reason) ) + bGiven = true; + + return bGiven; +} + + +//----------------------------------------------------------------------------- +// Purpose: Set commando class specific movement data here. +//----------------------------------------------------------------------------- +void CPlayerClassCommando::SetupMoveData( void ) +{ + // Setup Class statistics + m_flMaxWalkingSpeed = class_commando_speed.GetFloat(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPlayerClassCommando::SetupSizeData( void ) +{ + // Initially set the player to the base player class standing hull size. + m_pPlayer->SetCollisionBounds( COMMANDOCLASS_HULL_STAND_MIN, COMMANDOCLASS_HULL_STAND_MAX ); + m_pPlayer->SetViewOffset( COMMANDOCLASS_VIEWOFFSET_STAND ); + m_pPlayer->m_Local.m_flStepSize = COMMANDOCLASS_STEPSIZE; +} + +//----------------------------------------------------------------------------- +// Purpose: Return true if this player's allowed to build another one of the specified objects +//----------------------------------------------------------------------------- +int CPlayerClassCommando::CanBuild( int iObjectType ) +{ + if ( iObjectType == OBJ_RALLYFLAG ) + { + if ( !m_pPlayer->HasNamedTechnology( "com_obj_rallyflag" ) ) + return CB_NOT_RESEARCHED; + } + + return BaseClass::CanBuild( iObjectType ); +} + +//----------------------------------------------------------------------------- +// Purpose: Object has been built by this player +//----------------------------------------------------------------------------- +void CPlayerClassCommando::FinishedObject( CBaseObject *pObject ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: Calculate the Commando's Adrenalin Rush from available technologies +//----------------------------------------------------------------------------- +void CPlayerClassCommando::CalculateRush( void ) +{ + // Adrenalin Rush + if ( m_pPlayer->HasNamedTechnology( "com_adrenalin_rush" ) ) + { + m_bCanRush = true; + } + else + { + m_bCanRush = false; + } + + // Battlecry + m_bHasBattlecry = m_pPlayer->HasNamedTechnology( "com_adrenalin_battlecry" ); + + // Boot + // ROBIN: Removed for now + m_bCanBoot = false;//m_pPlayer->HasNamedTechnology( "com_automatic_boot" ); + + // Killing Rush + m_pPlayer->SetRampage( m_pPlayer->HasNamedTechnology( "com_adrenalin_rampage" ) ); +} + +//----------------------------------------------------------------------------- +// Purpose: Return true if the player is bullrushing. +//----------------------------------------------------------------------------- +bool CPlayerClassCommando::InBullRush( void ) +{ + return m_ClassData.m_bBullRush; +} + +//----------------------------------------------------------------------------- +// Purpose: Return true if the player's able to bull rush +//----------------------------------------------------------------------------- +bool CPlayerClassCommando::CanBullRush( void ) +{ + return m_ClassData.m_bCanBullRush; +} + + +//----------------------------------------------------------------------------- +// Should we take damage-based force? +//----------------------------------------------------------------------------- +bool CPlayerClassCommando::ShouldApplyDamageForce( const CTakeDamageInfo &info ) +{ + return !InBullRush(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPlayerClassCommando::BullRushTouch( CBaseEntity *pTouched ) +{ + if ( pTouched->IsPlayer() && !pTouched->InSameTeam( m_pPlayer ) ) + { + // Get the player. + CBaseTFPlayer *pTFPlayer = ( CBaseTFPlayer* )pTouched; + + // Check to see if we have "touched" this player already this bullrush cycle. + if ( m_aHitPlayers.Find( pTFPlayer ) != -1 ) + return; + + // Hitting the player now. + m_aHitPlayers.AddToTail( pTFPlayer ); + + // ROBIN: Bullrush now instantly kills again + float flDamage = 200; + // Calculate the damage a player takes based on distance(time). + //float flDamage = 1.0f - ( ( COMMANDO_BULLRUSH_TIME - m_ClassData.m_flBullRushTime ) * ( 1.0f / COMMANDO_BULLRUSH_TIME ) ); + //flDamage *= 115.0f; // max bullrush damage + + const trace_t &tr = m_pPlayer->GetTouchTrace(); + CTakeDamageInfo info( m_pPlayer, m_pPlayer, flDamage, DMG_CLUB, DMG_KILL_BULLRUSH ); + CalculateMeleeDamageForce( &info, (tr.endpos - tr.startpos), tr.endpos ); + pTFPlayer->TakeDamage( info ); + + CPASAttenuationFilter filter( m_pPlayer, "Commando.BullRushFlesh" ); + CBaseEntity::EmitSound( filter, m_pPlayer->entindex(), "Commando.BullRushFlesh" ); + + pTFPlayer->Touch( m_pPlayer ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: New technology has been gained. Recalculate any class specific technology dependencies. +//----------------------------------------------------------------------------- +void CPlayerClassCommando::GainedNewTechnology( CBaseTechnology *pTechnology ) +{ + // Technology handling + CalculateRush(); + + BaseClass::GainedNewTechnology( pTechnology ); +} + +//----------------------------------------------------------------------------- +// Purpose: Called when we are about to bullrush. +//----------------------------------------------------------------------------- +void CPlayerClassCommando::PreBullRush( void ) +{ + // Set the touch function to look for collisions! + SetClassTouch( m_pPlayer, BullRushTouch ); + + // Clear the player hit list. + m_aHitPlayers.RemoveAll(); + + // Start the bull rush sound. + CPASAttenuationFilter filter( m_pPlayer, "Commando.BullRushScream" ); + filter.MakeReliable(); + CBaseEntity::EmitSound( filter, m_pPlayer->entindex(), "Commando.BullRushScream" ); + + // Force the shield down, if it is up. + CWeaponTwoHandedContainer *pContainer = dynamic_cast<CWeaponTwoHandedContainer*>( m_pPlayer->GetActiveWeapon() ); + if ( pContainer ) + { + CWeaponCombatShield *pShield = dynamic_cast<CWeaponCombatShield*>( pContainer->GetRightWeapon() ); + if ( pShield ) + { + pShield->SetShieldUsable( false ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Called when we finish bullrushing. +//----------------------------------------------------------------------------- +void CPlayerClassCommando::PostBullRush( void ) +{ + SetClassTouch( m_pPlayer, NULL ); + + // Force the shield down, if it is up. + CWeaponTwoHandedContainer *pContainer = dynamic_cast<CWeaponTwoHandedContainer*>( m_pPlayer->GetActiveWeapon() ); + if ( pContainer ) + { + CWeaponCombatShield *pShield = dynamic_cast<CWeaponCombatShield*>( pContainer->GetRightWeapon() ); + if ( pShield ) + { + pShield->SetShieldUsable( true ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Called every frame by postthink +//----------------------------------------------------------------------------- +void CPlayerClassCommando::ClassThink( void ) +{ + // Check bullrush + m_ClassData.m_bCanBullRush = true; + + // Do the init thing here! + if ( m_bOldBullRush != m_ClassData.m_bBullRush ) + { + if ( m_ClassData.m_bBullRush ) + { + PreBullRush(); + } + else + { + PostBullRush(); + } + + m_bOldBullRush = (bool)m_ClassData.m_bBullRush; + } + + // Check for melee attack + if ( m_bCanBoot && m_pPlayer->IsAlive() && m_flNextBootCheck < gpGlobals->curtime ) + { + m_flNextBootCheck = gpGlobals->curtime + 0.2; + + CBaseEntity *pEntity = NULL; + Vector vecSrc = m_pPlayer->Weapon_ShootPosition( ); + Vector vecDir = m_pPlayer->BodyDirection2D( ); + Vector vecTarget = vecSrc + (vecDir * 48); + for ( CEntitySphereQuery sphere( vecTarget, 16 ); pEntity = sphere.GetCurrentEntity(); sphere.NextEntity() ) + { + if ( pEntity->IsPlayer() && (pEntity != m_pPlayer) ) + { + CBaseTFPlayer *pPlayer = (CBaseTFPlayer *)pEntity; + // Target needs to be on the enemy team + if ( !pPlayer->IsClass( TFCLASS_UNDECIDED ) && pPlayer->IsAlive() && pPlayer->InSameTeam( m_pPlayer ) == false ) + { + Boot( pPlayer ); + m_flNextBootCheck = gpGlobals->curtime + 1.5; + } + } + } + } + + BaseClass::ClassThink(); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPlayerClassCommando::StartAdrenalinRush( void ) +{ + // Am I actually alive? + if ( !m_pPlayer->IsAlive() ) + return; + + // Do I have rush capability? + if ( !m_bCanRush ) + return; + + m_bPersonalRush = true; + + // Start adrenalin rushing + m_pPlayer->AttemptToPowerup( POWERUP_RUSH, class_commando_rush_length.GetFloat() ); + + // If I have battlecry, adrenalin up all my nearby teammates + if ( m_bHasBattlecry ) + { + // Find nearby teammates + for ( int i = 0; i < m_pPlayer->GetTFTeam()->GetNumPlayers(); i++ ) + { + CBaseTFPlayer *pPlayer = (CBaseTFPlayer *)m_pPlayer->GetTFTeam()->GetPlayer(i); + assert(pPlayer); + + // Is it within range? + if ( pPlayer != m_pPlayer && (pPlayer->GetAbsOrigin() - m_pPlayer->GetAbsOrigin()).Length() < class_commando_battlecry_radius.GetFloat() ) + { + // Can I see it? + trace_t tr; + UTIL_TraceLine( m_pPlayer->EyePosition(), pPlayer->EyePosition(), MASK_SOLID_BRUSHONLY, m_pPlayer, COLLISION_GROUP_NONE, &tr); + CBaseEntity *pEntity = tr.m_pEnt; + if ( (tr.fraction == 1.0) || ( pEntity == pPlayer ) ) + { + pPlayer->AttemptToPowerup( POWERUP_RUSH, class_commando_battlecry_length.GetFloat() ); + } + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Automatic Melee Attack +//----------------------------------------------------------------------------- +void CPlayerClassCommando::Boot( CBaseTFPlayer *pTarget ) +{ + CPASAttenuationFilter filter( m_pPlayer, "Commando.BootSwing" ); + + CBaseEntity::EmitSound( filter, m_pPlayer->entindex(), "Commando.BootSwing" ); + CBaseEntity::EmitSound( filter, m_pPlayer->entindex(), "Commando.BootHit" ); + + // Damage the target + CTakeDamageInfo info( m_pPlayer, m_pPlayer, 25, DMG_CLUB ); + CalculateMeleeDamageForce( &info, (pTarget->GetAbsOrigin() - m_pPlayer->GetAbsOrigin()), pTarget->GetAbsOrigin() ); + pTarget->TakeDamage( info ); + + Vector vecForward; + AngleVectors( m_pPlayer->GetLocalAngles(), &vecForward ); + // Give it a lot of "in the air" + vecForward.z = MAX( 0.8, vecForward.z ); + VectorNormalize( vecForward ); + + // Knock the target to the ground for a few seconds (use default duration) + pTarget->KnockDownPlayer( vecForward, 500.0f, tf_knockdowntime.GetFloat() ); + +} + +//----------------------------------------------------------------------------- +// Purpose: Handle custom commands for this playerclass +//----------------------------------------------------------------------------- +bool CPlayerClassCommando::ClientCommand( const char *pcmd ) +{ + return BaseClass::ClientCommand( pcmd ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPlayerClassCommando::SetPlayerHull( void ) +{ + if ( m_pPlayer->GetFlags() & FL_DUCKING ) + { + m_pPlayer->SetCollisionBounds( COMMANDOCLASS_HULL_DUCK_MIN, COMMANDOCLASS_HULL_DUCK_MAX ); + } + else + { + m_pPlayer->SetCollisionBounds( COMMANDOCLASS_HULL_STAND_MIN, COMMANDOCLASS_HULL_STAND_MAX ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPlayerClassCommando::GetPlayerHull( bool bDucking, Vector &vecMin, Vector &vecMax ) +{ + if ( bDucking ) + { + VectorCopy( COMMANDOCLASS_HULL_DUCK_MIN, vecMin ); + VectorCopy( COMMANDOCLASS_HULL_DUCK_MAX, vecMax ); + } + else + { + VectorCopy( COMMANDOCLASS_HULL_STAND_MIN, vecMin ); + VectorCopy( COMMANDOCLASS_HULL_STAND_MAX, vecMax ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPlayerClassCommando::CreatePersonalOrder( void ) +{ + if ( CreateInitialOrder() ) + return; + + if ( COrderAssist::CreateOrder( this ) ) + return; + + BaseClass::CreatePersonalOrder(); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPlayerClassCommando::ResetViewOffset( void ) +{ + if ( m_pPlayer ) + { + m_pPlayer->SetViewOffset( COMMANDOCLASS_VIEWOFFSET_STAND ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPlayerClassCommando::InitVCollision( void ) +{ + CPhysCollide *pStandModel = PhysCreateBbox( COMMANDOCLASS_HULL_STAND_MIN, COMMANDOCLASS_HULL_STAND_MAX ); + CPhysCollide *pCrouchModel = PhysCreateBbox( COMMANDOCLASS_HULL_DUCK_MIN, COMMANDOCLASS_HULL_DUCK_MAX ); + m_pPlayer->SetupVPhysicsShadow( pStandModel, "tfplayer_commando_stand", pCrouchModel, "tfplayer_commando_crouch" ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CPlayerClassCommando::CanGetInVehicle( void ) +{ + if ( InBullRush() ) + return false; + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int CPlayerClassCommando::ClassCostAdjustment( ResupplyBuyType_t nType ) +{ + int nCost = 0; + if ( nType != RESUPPLY_BUY_HEALTH ) + { + nCost = RESUPPLY_ROCKET_COST; + } + + return nCost; +} diff --git a/game/server/tf2/tf_class_commando.h b/game/server/tf2/tf_class_commando.h new file mode 100644 index 0000000..54ca5b1 --- /dev/null +++ b/game/server/tf2/tf_class_commando.h @@ -0,0 +1,112 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef TF_CLASS_COMMANDO_H +#define TF_CLASS_COMMANDO_H + +#ifdef _WIN32 +#pragma once +#endif + +#include "tf_playerclass.h" +#include "TFClassData_Shared.h" +#include "basetfcombatweapon_shared.h" + +//===================================================================== +// Commando +class CPlayerClassCommando : public CPlayerClass +{ + DECLARE_CLASS( CPlayerClassCommando, CPlayerClass ); +public: + CPlayerClassCommando( CBaseTFPlayer *pPlayer, TFClass iClass ); + virtual ~CPlayerClassCommando(); + + virtual void ClassActivate( void ); + virtual void ClassDeactivate( void ); + + virtual const char* GetClassModelString( int nTeam ); + + // Class Initialization + virtual void CreateClass( void ); // Create the class upon initial spawn + virtual void RespawnClass( void ); // Called upon all respawns + virtual bool ResupplyAmmo( float flPercentage, ResupplyReason_t reason ); + virtual void SetupMoveData( void ); // Override class specific movement data here. + virtual void SetupSizeData( void ); // Override class specific size data here. + virtual void ResetViewOffset( void ); + + // Should we take damage-based force? + virtual bool ShouldApplyDamageForce( const CTakeDamageInfo &info ); + + PlayerClassCommandoData_t *GetClassData( void ) { return &m_ClassData; } + + // Class Abilities + virtual void ClassThink( void ); + + // Resources + int ClassCostAdjustment( ResupplyBuyType_t nType ); + + // Objects + virtual int CanBuild( int iObjectType ); + virtual void FinishedObject( CBaseObject *pObject ); + + virtual bool ClientCommand( const CCommand &args ); + virtual void GainedNewTechnology( CBaseTechnology *pTechnology ); + + // Adrenalin Rush + virtual void CalculateRush( void ); + virtual void StartAdrenalinRush( void ); + + // Automatic Melee Attack + virtual void Boot( CBaseTFPlayer *pTarget ); + + // Hooks + virtual void SetPlayerHull( void ); + virtual void GetPlayerHull( bool bDucking, Vector &vecMin, Vector &vecMax ); + + // Orders. + virtual void CreatePersonalOrder( void ); + + // Bull Rush. + bool InBullRush( void ); + bool CanBullRush( void ); + void BullRushTouch( CBaseEntity *pTouched ); + + CNetworkVarEmbedded( PlayerClassCommandoData_t, m_ClassData ); + + // Player physics shadow. + void InitVCollision( void ); + + // Vehicle + bool CanGetInVehicle( void ); + +protected: + // BullRush + void PreBullRush( void ); + void PostBullRush( void ); + +protected: + // Adrenalin Rush + bool m_bCanRush; // True if he has the ability to rush + bool m_bPersonalRush; // True if this he started his current rush, or outside effect + bool m_bHasBattlecry; // True if he has the ability to battlecry + + // Weapons + CHandle<CBaseTFCombatWeapon> m_hWpnPlasma; +// CHandle<CBaseTFCombatWeapon> m_hWpnGrenade; + + // Automatic Melee Attack + bool m_bCanBoot; // True if he has the ability to boot + float m_flNextBootCheck; // Time at which to recheck for the automatic melee attack + + bool m_bOldBullRush; + + CUtlVector<CBaseTFPlayer*> m_aHitPlayers; // Player I have hit during this bullrush. +}; + +EXTERN_SEND_TABLE( DT_PlayerClassCommandoData ) + +#endif // TF_CLASS_COMMANDO_H diff --git a/game/server/tf2/tf_class_defender.cpp b/game/server/tf2/tf_class_defender.cpp new file mode 100644 index 0000000..0de2034 --- /dev/null +++ b/game/server/tf2/tf_class_defender.cpp @@ -0,0 +1,352 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: The Defender Player Class +// +// $Workfile: $ +// $Date: $ +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "tf_player.h" +#include "tf_class_defender.h" +#include "tf_obj.h" +#include "tf_obj_sentrygun.h" +#include "basecombatweapon.h" +#include "weapon_builder.h" +#include "weapon_limpetmine.h" +#include "tf_team.h" +#include "orders.h" +#include "order_repair.h" +#include "order_buildsentrygun.h" +#include "weapon_twohandedcontainer.h" +#include "weapon_combatshield.h" +#include "tf_vehicle_teleport_station.h" + +ConVar class_defender_speed( "class_defender_speed","200", FCVAR_NONE, "Defender movement speed" ); + + +// An object must be this close to a sentry gun to be considered covered by it. +#define DEFENDER_SENTRY_COVERED_DIST 1000 + + +//============================================================================= +// +// Defender Data Table +// +BEGIN_SEND_TABLE_NOBASE( CPlayerClassDefender, DT_PlayerClassDefenderData ) +END_SEND_TABLE() + + +bool OrderCreator_BuildSentryGun( CPlayerClassDefender *pClass ) +{ + return COrderBuildSentryGun::CreateOrder( pClass ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Output : const char +//----------------------------------------------------------------------------- +const char *CPlayerClassDefender::GetClassModelString( int nTeam ) +{ + if (nTeam == TEAM_HUMANS) + return "models/player/human_defender.mdl"; + else + return "models/player/defender.mdl"; +} + +//----------------------------------------------------------------------------- +// Purpose: Defender +//----------------------------------------------------------------------------- +CPlayerClassDefender::CPlayerClassDefender( CBaseTFPlayer *pPlayer, TFClass iClass ) : CPlayerClass( pPlayer, iClass ) +{ + for (int i = 0; i < MAX_TF_TEAMS; ++i) + { + SetClassModel( MAKE_STRING(GetClassModelString(i)), i ); + } +} + +CPlayerClassDefender::~CPlayerClassDefender() +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPlayerClassDefender::ClassActivate( void ) +{ + BaseClass::ClassActivate(); + + // Setup movement data. + SetupMoveData(); + + m_iNumberOfSentriesAllowed = 0; + m_bHasSmarterSentryguns = false; + m_bHasSensorSentryguns = false; + m_bHasMachinegun = false; + m_bHasRocketlauncher = false; + m_bHasAntiair = false; + + m_hWpnShield = NULL; + m_hWpnPlasma = NULL; + memset( &m_ClassData, 0, sizeof( m_ClassData ) ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPlayerClassDefender::ClassDeactivate( void ) +{ + BaseClass::ClassDeactivate(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPlayerClassDefender::CreateClass( void ) +{ + BaseClass::CreateClass(); + + // Create our two handed weapon layout + m_hWpnPlasma = static_cast< CBaseTFCombatWeapon * >( m_pPlayer->GiveNamedItem( "weapon_combat_burstrifle" ) ); + m_hWpnShield = m_pPlayer->GetCombatShield(); + CWeaponTwoHandedContainer *p = ( CWeaponTwoHandedContainer * )m_pPlayer->Weapon_OwnsThisType( "weapon_twohandedcontainer" ); + if ( !p ) + { + p = static_cast< CWeaponTwoHandedContainer * >( m_pPlayer->GiveNamedItem( "weapon_twohandedcontainer" ) ); + } + if ( p && m_hWpnShield.Get() && m_hWpnPlasma.Get() ) + { + m_hWpnShield->SetReflectViewModelAnimations( true ); + p->SetWeapons( m_hWpnPlasma, m_hWpnShield ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CPlayerClassDefender::ResupplyAmmo( float flFraction, ResupplyReason_t reason ) +{ + bool bGiven = false; + if ((reason == RESUPPLY_ALL_FROM_STATION) || (reason == RESUPPLY_AMMO_FROM_STATION)) + { + if (ResupplyAmmoType( 20 * flFraction, "Limpets" )) + bGiven = true; + + // Defender doesn't use rockets, but his sentryguns do + if (ResupplyAmmoType( 50 * flFraction, "Rockets" )) + bGiven = true; + } + + if ((reason == RESUPPLY_ALL_FROM_STATION) || (reason == RESUPPLY_GRENADES_FROM_STATION)) + { + } + + // On respawn, resupply base weapon ammo + if ( reason == RESUPPLY_RESPAWN ) + { + } + + if ( BaseClass::ResupplyAmmo(flFraction, reason) ) + bGiven = true; + + return bGiven; +} + + +//----------------------------------------------------------------------------- +// Purpose: Set defender class specific movement data here. +//----------------------------------------------------------------------------- +void CPlayerClassDefender::SetupMoveData( void ) +{ + // Setup Class statistics + m_flMaxWalkingSpeed = class_defender_speed.GetFloat(); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPlayerClassDefender::SetupSizeData( void ) +{ + // Initially set the player to the base player class standing hull size. + m_pPlayer->SetCollisionBounds( DEFENDERCLASS_HULL_STAND_MIN, DEFENDERCLASS_HULL_STAND_MAX ); + m_pPlayer->SetViewOffset( DEFENDERCLASS_VIEWOFFSET_STAND ); + m_pPlayer->m_Local.m_flStepSize = DEFENDERCLASS_STEPSIZE; +} + + +//----------------------------------------------------------------------------- +// Purpose: New technology has been gained. Recalculate any class specific technology dependencies. +//----------------------------------------------------------------------------- +void CPlayerClassDefender::GainedNewTechnology( CBaseTechnology *pTechnology ) +{ + // Calculate the number of sentryguns allowed + if ( m_pPlayer->HasNamedTechnology( "sentrygun_three" ) ) + { + m_iNumberOfSentriesAllowed = 3; + } + else if ( m_pPlayer->HasNamedTechnology( "sentrygun_two" ) ) + { + m_iNumberOfSentriesAllowed = 2; + } + else + { + m_iNumberOfSentriesAllowed = 1; + } + + m_bHasSmarterSentryguns = false; + m_bHasSensorSentryguns = false; + m_bHasRocketlauncher = false; + + // Sentrygun levels + if ( m_pPlayer->HasNamedTechnology( "sentrygun_ai" ) ) + { + m_bHasSmarterSentryguns = true; + } + if ( m_pPlayer->HasNamedTechnology( "sentrygun_sensors" ) ) + { + m_bHasSensorSentryguns = true; + } + + // Sentrygun types + if ( m_pPlayer->HasNamedTechnology( "sentrygun_rocket" ) ) + { + m_bHasRocketlauncher = true; + } + + UpdateSentrygunTechnology(); + BaseClass::GainedNewTechnology( pTechnology ); +} + +//----------------------------------------------------------------------------- +// Purpose: Tell all sentryguns what level of technology the Defender has +//----------------------------------------------------------------------------- +void CPlayerClassDefender::UpdateSentrygunTechnology( void ) +{ + for (int i = 0; i < m_pPlayer->GetObjectCount(); i++) + { + CBaseObject *pObj = m_pPlayer->GetObject(i); + if ( pObj && pObj->IsSentrygun() ) + { + CObjectSentrygun *pSentry = static_cast<CObjectSentrygun *>(pObj); + pSentry->SetTechnology( m_bHasSmarterSentryguns, m_bHasSensorSentryguns ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Return true if this player's allowed to build another one of the specified objects +//----------------------------------------------------------------------------- +int CPlayerClassDefender::CanBuild( int iObjectType ) +{ + // First, check to see if we've got the technology + if ( iObjectType == OBJ_SENTRYGUN_ROCKET_LAUNCHER ) + { + if ( !m_bHasRocketlauncher ) + return CB_NOT_RESEARCHED; + } + + return BaseClass::CanBuild( iObjectType ); +} + + +int CPlayerClassDefender::CanBuildSentryGun() +{ + return + CanBuild( OBJ_SENTRYGUN_ROCKET_LAUNCHER ) == CB_CAN_BUILD || + CanBuild( OBJ_SENTRYGUN_PLASMA ) == CB_CAN_BUILD; +} + + +//----------------------------------------------------------------------------- +// Purpose: Object has been built by this player +//----------------------------------------------------------------------------- +void CPlayerClassDefender::FinishedObject( CBaseObject *pObject ) +{ + UpdateSentrygunTechnology(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPlayerClassDefender::PlayerDied( CBaseEntity *pAttacker ) +{ + CWeaponLimpetmine *weapon = (CWeaponLimpetmine*)m_pPlayer->Weapon_OwnsThisType( "weapon_limpetmine" ); + if ( weapon ) + { + weapon->RemoveDeployedLimpets(); + } + + BaseClass::PlayerDied( pAttacker ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPlayerClassDefender::SetPlayerHull( void ) +{ + if ( m_pPlayer->GetFlags() & FL_DUCKING ) + { + m_pPlayer->SetCollisionBounds( DEFENDERCLASS_HULL_DUCK_MIN, DEFENDERCLASS_HULL_DUCK_MAX ); + } + else + { + m_pPlayer->SetCollisionBounds( DEFENDERCLASS_HULL_STAND_MIN, DEFENDERCLASS_HULL_STAND_MAX ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPlayerClassDefender::GetPlayerHull( bool bDucking, Vector &vecMin, Vector &vecMax ) +{ + if ( bDucking ) + { + VectorCopy( DEFENDERCLASS_HULL_DUCK_MIN, vecMin ); + VectorCopy( DEFENDERCLASS_HULL_DUCK_MAX, vecMax ); + } + else + { + VectorCopy( DEFENDERCLASS_HULL_STAND_MIN, vecMin ); + VectorCopy( DEFENDERCLASS_HULL_STAND_MAX, vecMax ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPlayerClassDefender::ResetViewOffset( void ) +{ + if ( m_pPlayer ) + { + m_pPlayer->SetViewOffset( DEFENDERCLASS_VIEWOFFSET_STAND ); + } +} + +void CPlayerClassDefender::CreatePersonalOrder() +{ + if ( CreateInitialOrder() ) + return; + + if( COrderRepair::CreateOrder_RepairFriendlyObjects( this ) ) + return; + + // Alternate between sentrygun and sandbag orders. + if ( OrderCreator_BuildSentryGun( this ) ) + { + return; + } + + BaseClass::CreatePersonalOrder(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPlayerClassDefender::InitVCollision( void ) +{ + CPhysCollide *pStandModel = PhysCreateBbox( DEFENDERCLASS_HULL_STAND_MIN, DEFENDERCLASS_HULL_STAND_MAX ); + CPhysCollide *pCrouchModel = PhysCreateBbox( DEFENDERCLASS_HULL_DUCK_MIN, DEFENDERCLASS_HULL_DUCK_MAX ); + m_pPlayer->SetupVPhysicsShadow( pStandModel, "tfplayer_defender_stand", pCrouchModel, "tfplayer_defender_crouch" ); +} diff --git a/game/server/tf2/tf_class_defender.h b/game/server/tf2/tf_class_defender.h new file mode 100644 index 0000000..7493797 --- /dev/null +++ b/game/server/tf2/tf_class_defender.h @@ -0,0 +1,77 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Defender player class +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef TF_CLASS_DEFENDER_H +#define TF_CLASS_DEFENDER_H +#pragma once + +#include "TFClassData_Shared.h" + +//===================================================================== +// Defender +class CPlayerClassDefender : public CPlayerClass +{ +public: + DECLARE_CLASS( CPlayerClassDefender, CPlayerClass ); + + CPlayerClassDefender( CBaseTFPlayer *pPlayer, TFClass iClass ); + ~CPlayerClassDefender(); + + virtual void ClassActivate( void ); + virtual void ClassDeactivate( void ); + + virtual const char* GetClassModelString( int nTeam ); + + // Class Initialization + virtual void CreateClass( void ); // Create the class upon initial spawn + virtual bool ResupplyAmmo( float flPercentage, ResupplyReason_t reason ); + virtual void SetupMoveData( void ); // Override class specific movement data here. + virtual void SetupSizeData( void ); // Override class specific size data here. + virtual void ResetViewOffset( void ); + + PlayerClassDefenderData_t *GetClassData( void ) { return &m_ClassData; } + + virtual void PlayerDied( CBaseEntity *pAttacker ); + + virtual void GainedNewTechnology( CBaseTechnology *pTechnology ); + virtual void UpdateSentrygunTechnology( void ); + + // Sentrygun building + virtual int CanBuild( int iObjectType ); + int CanBuildSentryGun(); + virtual void FinishedObject( CBaseObject *pObject ); + + // Orders. + virtual void CreatePersonalOrder(); + + // Hooks + virtual void SetPlayerHull( void ); + virtual void GetPlayerHull( bool bDucking, Vector &vecMin, Vector &vecMax ); + + // Player physics shadow. + void InitVCollision( void ); + +public: + // Sentrygun building + int m_iNumberOfSentriesAllowed; + bool m_bHasSmarterSentryguns; + bool m_bHasSensorSentryguns; + // Sentrygun types + bool m_bHasMachinegun; + bool m_bHasRocketlauncher; + bool m_bHasAntiair; + + // Weapons + CHandle<CBaseTFCombatWeapon> m_hWpnPlasma; + +private: + PlayerClassDefenderData_t m_ClassData; +}; + +EXTERN_SEND_TABLE( DT_PlayerClassDefenderData ) + +#endif // TF_CLASS_DEFENDER_H diff --git a/game/server/tf2/tf_class_escort.cpp b/game/server/tf2/tf_class_escort.cpp new file mode 100644 index 0000000..de03f59 --- /dev/null +++ b/game/server/tf2/tf_class_escort.cpp @@ -0,0 +1,274 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: The Escort Player Class +// +// $Workfile: $ +// $Date: $ +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" +#include "tf_player.h" +#include "tf_class_escort.h" +#include "basecombatweapon.h" +#include "tf_obj_shieldwall.h" +#include "weapon_builder.h" +#include "tf_shareddefs.h" +#include "tf_team.h" +#include "orders.h" +#include "order_buildshieldwall.h" +#include "order_mortar_attack.h" +#include "weapon_twohandedcontainer.h" +#include "tf_shield.h" +#include "iservervehicle.h" +#include "weapon_combatshield.h" +#include "in_buttons.h" +#include "tf_vehicle_teleport_station.h" +#include "weapon_shield.h" + + +ConVar class_escort_speed( "class_escort_speed","200", FCVAR_NONE, "Escort movement speed" ); + + +//============================================================================= +// +// Escort Data Table +// +BEGIN_SEND_TABLE_NOBASE( CPlayerClassEscort, DT_PlayerClassEscortData ) +END_SEND_TABLE() + + + +bool OrderCreator_Mortar( CPlayerClassEscort *pClass ) +{ + return COrderMortarAttack::CreateOrder( pClass ); +} + + +bool OrderCreator_ShieldWall( CPlayerClassEscort *pClass ) +{ + return COrderBuildShieldWall::CreateOrder( pClass ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Output : const char +//----------------------------------------------------------------------------- +const char *CPlayerClassEscort::GetClassModelString( int nTeam ) +{ + static const char *string = "models/player/alien_escort.mdl"; + return string; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CPlayerClassEscort::CPlayerClassEscort( CBaseTFPlayer *pPlayer, TFClass iClass ) : CPlayerClass( pPlayer, iClass ) +{ + for (int i = 0; i < MAX_TF_TEAMS; ++i) + { + SetClassModel( MAKE_STRING(GetClassModelString(i)), i ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPlayerClassEscort::SetupSizeData( void ) +{ + // Initially set the player to the base player class standing hull size. + m_pPlayer->SetCollisionBounds( ESCORTCLASS_HULL_STAND_MIN, ESCORTCLASS_HULL_STAND_MAX ); + m_pPlayer->SetViewOffset( ESCORTCLASS_VIEWOFFSET_STAND ); + m_pPlayer->m_Local.m_flStepSize = ESCORTCLASS_STEPSIZE; +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CPlayerClassEscort::~CPlayerClassEscort() +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPlayerClassEscort::ClassActivate( void ) +{ + BaseClass::ClassActivate(); + + // Setup movement data. + m_hWeaponProjectedShield = NULL; + SetupMoveData(); + memset( &m_ClassData, 0, sizeof( m_ClassData ) ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CWeaponShield *CPlayerClassEscort::GetProjectedShield( void ) +{ + if ( !m_hWeaponProjectedShield ) + { + m_hWeaponProjectedShield = static_cast< CWeaponShield * >( m_pPlayer->Weapon_OwnsThisType( "weapon_shield" ) ); + if ( !m_hWeaponProjectedShield ) + { + m_hWeaponProjectedShield = static_cast< CWeaponShield * >( m_pPlayer->GiveNamedItem( "weapon_shield" ) ); + } + } + + if ( m_hWeaponProjectedShield ) + { + m_hWeaponProjectedShield->SetReflectViewModelAnimations( true ); + } + + return m_hWeaponProjectedShield; +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPlayerClassEscort::CreateClass( void ) +{ + BaseClass::CreateClass(); + + CWeaponTwoHandedContainer *p = ( CWeaponTwoHandedContainer * )m_pPlayer->Weapon_OwnsThisType( "weapon_twohandedcontainer" ); + if ( !p ) + { + p = static_cast< CWeaponTwoHandedContainer * >( m_pPlayer->GiveNamedItem( "weapon_twohandedcontainer" ) ); + } + + CWeaponShield *pShield = GetProjectedShield(); + if ( p && pShield ) + { + p->SetWeapons( NULL, pShield ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CPlayerClassEscort::ResupplyAmmo( float flFraction, ResupplyReason_t reason ) +{ + bool bGiven = false; + if ((reason == RESUPPLY_ALL_FROM_STATION) || (reason == RESUPPLY_AMMO_FROM_STATION)) + { + if ( ResupplyAmmoType( 200 * flFraction, "Bullets" ) ) + bGiven = true; + } + + if ((reason == RESUPPLY_ALL_FROM_STATION) || (reason == RESUPPLY_GRENADES_FROM_STATION)) + { + if (ResupplyAmmoType( 5 * flFraction, "ShieldGrenades" )) + bGiven = true; + } + + // On respawn, resupply base weapon ammo + if ( reason == RESUPPLY_RESPAWN ) + { + if ( ResupplyAmmoType( 200, "Bullets" ) ) + bGiven = true; + } + + if ( BaseClass::ResupplyAmmo(flFraction, reason) ) + bGiven = true; + + return bGiven; +} + + +//----------------------------------------------------------------------------- +// Purpose: Set escort class specific movement data here. +//----------------------------------------------------------------------------- +void CPlayerClassEscort::SetupMoveData( void ) +{ + // Setup Class statistics + m_flMaxWalkingSpeed = class_escort_speed.GetFloat(); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPlayerClassEscort::SetPlayerHull( void ) +{ + if ( m_pPlayer->GetFlags() & FL_DUCKING ) + { + m_pPlayer->SetCollisionBounds( ESCORTCLASS_HULL_DUCK_MIN, ESCORTCLASS_HULL_DUCK_MAX ); + } + else + { + m_pPlayer->SetCollisionBounds( ESCORTCLASS_HULL_STAND_MIN, ESCORTCLASS_HULL_STAND_MAX ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPlayerClassEscort::ResetViewOffset( void ) +{ + if ( m_pPlayer ) + { + m_pPlayer->SetViewOffset( ESCORTCLASS_VIEWOFFSET_STAND ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPlayerClassEscort::PowerupStart( int iPowerup, float flAmount, CBaseEntity *pAttacker, CDamageModifier *pDamageModifier ) +{ + /* + switch( iPowerup ) + { + case POWERUP_BOOST: + // Increase our shield's energy + if ( m_hDeployedShield ) + { + m_hDeployedShield->SetPower( m_hDeployedShield->GetPower() + (flAmount * 10) ); + } + break; + + case POWERUP_EMP: + if ( m_hDeployedShield ) + { + m_hDeployedShield->SetEMPed( true ); + } + break; + + default: + break; + } + */ + + BaseClass::PowerupStart( iPowerup, flAmount, pAttacker, pDamageModifier ); +} + +//----------------------------------------------------------------------------- +// Purpose: Powerup has just started +//----------------------------------------------------------------------------- +void CPlayerClassEscort::PowerupEnd( int iPowerup ) +{ + /* + switch( iPowerup ) + { + case POWERUP_EMP: + if ( m_hDeployedShield ) + { + m_hDeployedShield->SetEMPed( false ); + } + break; + + default: + break; + } + */ + + BaseClass::PowerupEnd( iPowerup ); +} diff --git a/game/server/tf2/tf_class_escort.h b/game/server/tf2/tf_class_escort.h new file mode 100644 index 0000000..414b578 --- /dev/null +++ b/game/server/tf2/tf_class_escort.h @@ -0,0 +1,64 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef TF_CLASS_ESCORT_H +#define TF_CLASS_ESCORT_H + +#ifdef _WIN32 +#pragma once +#endif + + +#include "tf_playerclass.h" +#include "TFClassData_Shared.h" +#include "tf_shieldshared.h" + +class CShield; +class CWeaponShield; + +//===================================================================== +// Indirect +class CPlayerClassEscort : public CPlayerClass +{ +public: + DECLARE_CLASS( CPlayerClassEscort, CPlayerClass ); + + CPlayerClassEscort( CBaseTFPlayer *pPlayer, TFClass iClass ); + ~CPlayerClassEscort(); + + virtual void ClassActivate( void ); + + virtual const char* GetClassModelString( int nTeam ); + + // Class Initialization + virtual void CreateClass( void ); // Create the class upon initial spawn + virtual bool ResupplyAmmo( float flPercentage, ResupplyReason_t reason ); + virtual void SetupMoveData( void ); // Override class specific movement data here. + virtual void SetupSizeData( void ); // Override class specific size data here. + virtual void ResetViewOffset( void ); + + PlayerClassEscortData_t *GetClassData( void ) { return &m_ClassData; } + + // Hooks + virtual void SetPlayerHull( void ); + + // Powerups + virtual void PowerupStart( int iPowerup, float flAmount, CBaseEntity *pAttacker, CDamageModifier *pDamageModifier ); + virtual void PowerupEnd( int iPowerup ); + +private: + // Purpose: + CWeaponShield *GetProjectedShield( void ); + +protected: + PlayerClassEscortData_t m_ClassData; + CHandle<CWeaponShield> m_hWeaponProjectedShield; +}; + +EXTERN_SEND_TABLE( DT_PlayerClassEscortData ) + +#endif // TF_CLASS_ESCORT_H diff --git a/game/server/tf2/tf_class_infiltrator.cpp b/game/server/tf2/tf_class_infiltrator.cpp new file mode 100644 index 0000000..bc59b18 --- /dev/null +++ b/game/server/tf2/tf_class_infiltrator.cpp @@ -0,0 +1,275 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: The Infiltrator Player Class +// +// $Workfile: $ +// $Date: $ +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "orders.h" +#include "tf_player.h" +#include "tf_class_infiltrator.h" +#include "EntityList.h" +#include "basecombatweapon.h" +#include "menu_base.h" +#include "tf_obj.h" +#include "tf_team.h" +#include "weapon_builder.h" + +ConVar class_infiltrator_speed( "class_infiltrator_speed","200", FCVAR_NONE, "Infiltrator movement speed" ); + +//============================================================================= +// +// Infiltrator Data Table +// +BEGIN_SEND_TABLE_NOBASE( CPlayerClassInfiltrator, DT_PlayerClassInfiltratorData ) +END_SEND_TABLE() + + +//----------------------------------------------------------------------------- +// Purpose: +// Output : const char +//----------------------------------------------------------------------------- +const char *CPlayerClassInfiltrator::GetClassModelString( int nTeam ) +{ + static const char *string = "models/player/spy.mdl"; + return string; +} + +// Infiltrator +CPlayerClassInfiltrator::CPlayerClassInfiltrator( CBaseTFPlayer *pPlayer, TFClass iClass ) : CPlayerClass( pPlayer, iClass ) +{ + for (int i = 0; i < MAX_TF_TEAMS; ++i) + { + SetClassModel( MAKE_STRING(GetClassModelString(i)), i ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CPlayerClassInfiltrator::~CPlayerClassInfiltrator() +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPlayerClassInfiltrator::ClassActivate( void ) +{ + BaseClass::ClassActivate(); + + // Setup movement data. + SetupMoveData(); + + //m_hAssassinationWeapon = NULL; + m_hSwappedWeapon = NULL; + + m_bCanConsumeCorpses = false; + m_flStartCamoAt = 0.0f; + + memset( &m_ClassData, 0, sizeof( m_ClassData ) ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Register for precaching. +//----------------------------------------------------------------------------- +void PrecacheInfiltrator(void *pUser) +{ +} +PRECACHE_REGISTER_FN(PrecacheInfiltrator); + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPlayerClassInfiltrator::RespawnClass( void ) +{ + BaseClass::RespawnClass(); + + m_flStartCamoAt = gpGlobals->curtime + INFILTRATOR_CAMOTIME_AFTER_SPAWN; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CPlayerClassInfiltrator::ResupplyAmmo( float flFraction, ResupplyReason_t reason ) +{ + bool bGiven = false; + if ((reason == RESUPPLY_ALL_FROM_STATION) || (reason == RESUPPLY_AMMO_FROM_STATION)) + { + if (ResupplyAmmoType( 200 * flFraction, "Bullets" )) + bGiven = true; + if (ResupplyAmmoType( 20 * flFraction, "Limpets" )) + bGiven = true; + } + + if ( BaseClass::ResupplyAmmo(flFraction, reason) ) + bGiven = true; + return bGiven; +} + +//----------------------------------------------------------------------------- +// Purpose: Set infiltrator class specific movement data here. +//----------------------------------------------------------------------------- +void CPlayerClassInfiltrator::SetupMoveData( void ) +{ + // Setup Class statistics + m_flMaxWalkingSpeed = class_infiltrator_speed.GetFloat(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPlayerClassInfiltrator::SetupSizeData( void ) +{ + // Initially set the player to the base player class standing hull size. + m_pPlayer->SetCollisionBounds( INFILTRATORCLASS_HULL_STAND_MIN, INFILTRATORCLASS_HULL_STAND_MAX ); + m_pPlayer->SetViewOffset( INFILTRATORCLASS_VIEWOFFSET_STAND ); + m_pPlayer->m_Local.m_flStepSize = INFILTRATORCLASS_STEPSIZE; +} + +//----------------------------------------------------------------------------- +// Purpose: New technology has been gained. Recalculate any class specific technology dependencies. +//----------------------------------------------------------------------------- +void CPlayerClassInfiltrator::GainedNewTechnology( CBaseTechnology *pTechnology ) +{ + // Consume corpse technology? + m_bCanConsumeCorpses = m_pPlayer->HasNamedTechnology( "inf_consume_corpse" ); + + BaseClass::GainedNewTechnology( pTechnology ); +} + +//----------------------------------------------------------------------------- +// Purpose: Return true if this player's allowed to build another one of the specified objects +//----------------------------------------------------------------------------- +int CPlayerClassInfiltrator::CanBuild( int iObjectType ) +{ + return BaseClass::CanBuild( iObjectType ); +} + +//----------------------------------------------------------------------------- +// Purpose: Called every frame by postthink +//----------------------------------------------------------------------------- +void CPlayerClassInfiltrator::ClassThink( void ) +{ + BaseClass::ClassThink(); + + CheckForAssassination(); +} + +//----------------------------------------------------------------------------- +// Purpose: The player's just had his camo cleared +//----------------------------------------------------------------------------- +void CPlayerClassInfiltrator::ClearCamouflage( void ) +{ + float flNewTime = gpGlobals->curtime + INFILTRATOR_RECAMO_TIME; + if ( flNewTime > m_flStartCamoAt ) + { + m_flStartCamoAt = flNewTime; + } +} + +//----------------------------------------------------------------------------- +// Purpose: Player's finished disguising. +//----------------------------------------------------------------------------- +void CPlayerClassInfiltrator::FinishedDisguising( void ) +{ + // Remove my camo + m_pPlayer->ClearCamouflage(); +} + +//----------------------------------------------------------------------------- +// Purpose: Player's lost his disguise. +//----------------------------------------------------------------------------- +void CPlayerClassInfiltrator::StopDisguising( void ) +{ + // Remove & restart my camo + m_pPlayer->ClearCamouflage(); +} + +//----------------------------------------------------------------------------- +// Purpose: Handle custom commands for this playerclass +//----------------------------------------------------------------------------- +bool CPlayerClassInfiltrator::ClientCommand( const char *pcmd ) +{ + // Toggle thermal vision + if ( FStrEq( pcmd, "special" ) ) + { + Assert( m_pPlayer ); + // Toggle + m_pPlayer->SetUsingThermalVision( !m_pPlayer->IsUsingThermalVision() ); + return true; + } + + return BaseClass::ClientCommand( pcmd ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Check to see if I can assassinate anyone +//----------------------------------------------------------------------------- +void CPlayerClassInfiltrator::CheckForAssassination( void ) +{ + /* + // Find my assassination weapon if I haven't already + if ( m_hAssassinationWeapon == NULL ) + { + m_hAssassinationWeapon = (CWeaponInfiltrator*)m_pPlayer->Weapon_OwnsThisType( "weapon_infiltrator" ); + } + + // No Assasination weapon? + if ( m_hAssassinationWeapon == NULL ) + return; + + // If we have a target, bring the assassination weapon up. + if ( m_hAssassinationWeapon->GetAssassinationTarget() ) + { + if ( m_pPlayer->GetActiveWeapon() != m_hAssassinationWeapon.Get() ) + { + m_hSwappedWeapon = m_pPlayer->GetActiveWeapon(); + m_pPlayer->Weapon_Switch( m_hAssassinationWeapon ); + } + } + else + { + // We have no target. If the assassination weapon's up, put it away. + if ( m_pPlayer->GetActiveWeapon() == m_hAssassinationWeapon.Get() ) + { + // Don't switch until the weapon's finished killing people + if ( m_pPlayer->GetActiveWeapon()->m_flNextPrimaryAttack <= gpGlobals->curtime ) + { + m_pPlayer->Weapon_Switch( m_hSwappedWeapon ); + m_hSwappedWeapon = NULL; + } + } + } + */ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPlayerClassInfiltrator::SetPlayerHull( void ) +{ + if ( m_pPlayer->GetFlags() & FL_DUCKING ) + { + m_pPlayer->SetCollisionBounds( INFILTRATORCLASS_HULL_DUCK_MIN, INFILTRATORCLASS_HULL_DUCK_MAX ); + } + else + { + m_pPlayer->SetCollisionBounds( INFILTRATORCLASS_HULL_STAND_MIN, INFILTRATORCLASS_HULL_STAND_MAX ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPlayerClassInfiltrator::ResetViewOffset( void ) +{ + if ( m_pPlayer ) + { + m_pPlayer->SetViewOffset( INFILTRATORCLASS_VIEWOFFSET_STAND ); + } +} diff --git a/game/server/tf2/tf_class_infiltrator.h b/game/server/tf2/tf_class_infiltrator.h new file mode 100644 index 0000000..f026bee --- /dev/null +++ b/game/server/tf2/tf_class_infiltrator.h @@ -0,0 +1,78 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Infiltrator +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef TF_CLASS_INFILTRATOR_H +#define TF_CLASS_INFILTRATOR_H + +#ifdef _WIN32 +#pragma once +#endif + +#define INFILTRATOR_EAVESDROP_RADIUS 256.0f +#define INFILTRATOR_DISGUISE_TIME 3.0f + +// Time after losing camo before the infiltrator can re-camo +#define INFILTRATOR_RECAMO_TIME 5.0 +// Time after spawning that the infilitrator's camo kicks in +#define INFILTRATOR_CAMOTIME_AFTER_SPAWN 3.0 + +#include "TFClassData_Shared.h" + +class CLootableCorpse; + +//===================================================================== +// Infiltrator +class CPlayerClassInfiltrator : public CPlayerClass +{ + DECLARE_CLASS( CPlayerClassInfiltrator, CPlayerClass ); +public: + CPlayerClassInfiltrator( CBaseTFPlayer *pPlayer, TFClass iClass ); + ~CPlayerClassInfiltrator(); + + virtual void ClassActivate( void ); + + virtual const char* GetClassModelString( int nTeam ); + + // Class Initialization + virtual void RespawnClass( void ); // Called upon all respawns + virtual bool ResupplyAmmo( float flPercentage, ResupplyReason_t reason ); + virtual void SetupMoveData( void ); // Override class specific movement data here. + virtual void SetupSizeData( void ); // Override class specific size data here. + virtual void ResetViewOffset( void ); + + PlayerClassInfiltratorData_t *GetClassData( void ) { return &m_ClassData; } + + virtual void ClassThink( void ); + virtual void ClearCamouflage( void ); + + virtual int CanBuild( int iObjectType ); + virtual bool ClientCommand( const CCommand &args ); + virtual void GainedNewTechnology( CBaseTechnology *pTechnology ); + + void CheckForAssassination( void ); + + // Disguise + virtual void FinishedDisguising( void ); + virtual void StopDisguising( void ); + + // Hooks + virtual void SetPlayerHull( void ); + +protected: + bool m_bCanConsumeCorpses; + float m_flStartCamoAt; + + // Assassination weapon + //CHandle<CWeaponInfiltrator> m_hAssassinationWeapon; + CHandle<CBaseCombatWeapon> m_hSwappedWeapon; + + PlayerClassInfiltratorData_t m_ClassData; +}; + +EXTERN_SEND_TABLE( DT_PlayerClassInfiltratorData ) + +#endif // TF_CLASS_INFILTRATOR_H diff --git a/game/server/tf2/tf_class_medic.cpp b/game/server/tf2/tf_class_medic.cpp new file mode 100644 index 0000000..be2a094 --- /dev/null +++ b/game/server/tf2/tf_class_medic.cpp @@ -0,0 +1,305 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: The Medic Player Class +// +// $Workfile: $ +// $Date: $ +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "basecombatweapon.h" +#include "tf_player.h" +#include "tf_class_medic.h" +#include "tf_obj.h" +#include "tf_obj_resupply.h" +#include "tf_obj_resourcepump.h" +#include "weapon_builder.h" +#include "tf_team.h" +#include "orders.h" +#include "entitylist.h" +#include "tf_func_resource.h" +#include "order_resupply.h" +#include "order_resourcepump.h" +#include "order_heal.h" +#include "weapon_twohandedcontainer.h" +#include "weapon_combatshield.h" + +// Radius buff defines +#define RADIUS_BUFF_RANGE 512.0 + +#define MEDIC_HEAL_TIME_AFTER_DAMAGE 10.0 + +// Medic +ConVar class_medic_speed( "class_medic_speed","200", FCVAR_NONE, "Medic movement speed" ); + +//============================================================================= +// +// Medic Data Table +// +BEGIN_SEND_TABLE_NOBASE( CPlayerClassMedic, DT_PlayerClassMedicData ) +END_SEND_TABLE() + + +//----------------------------------------------------------------------------- +// Purpose: +// Output : const char +//----------------------------------------------------------------------------- +const char *CPlayerClassMedic::GetClassModelString( int nTeam ) +{ + if (nTeam == TEAM_HUMANS) + return "models/player/human_medic.mdl"; + else + return "models/player/recon.mdl"; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CPlayerClassMedic::CPlayerClassMedic( CBaseTFPlayer *pPlayer, TFClass iClass ) : CPlayerClass( pPlayer, iClass ) +{ + for (int i = 0; i < MAX_TF_TEAMS; ++i) + { + SetClassModel( MAKE_STRING(GetClassModelString(i)), i ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPlayerClassMedic::SetupSizeData( void ) +{ + // Initially set the player to the base player class standing hull size. + m_pPlayer->SetCollisionBounds( MEDICCLASS_HULL_STAND_MIN, MEDICCLASS_HULL_STAND_MAX ); + m_pPlayer->SetViewOffset( MEDICCLASS_VIEWOFFSET_STAND ); + m_pPlayer->m_Local.m_flStepSize = MEDICCLASS_STEPSIZE; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CPlayerClassMedic::~CPlayerClassMedic() +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPlayerClassMedic::ClassActivate( void ) +{ + BaseClass::ClassActivate(); + + // Setup movement data. + SetupMoveData(); + + m_hWpnShield = NULL; + m_hWpnPlasma = NULL; + + m_bHasAutoRepair = false; + + m_flNextHealTime = 0; + + memset( &m_ClassData, 0, sizeof( m_ClassData ) ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPlayerClassMedic::ClassDeactivate( void ) +{ + BaseClass::ClassDeactivate(); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPlayerClassMedic::CreateClass( void ) +{ + BaseClass::CreateClass(); + + // Create our two handed weapon layout + m_hWpnPlasma = static_cast< CBaseTFCombatWeapon * >( m_pPlayer->GiveNamedItem( "weapon_combat_burstrifle" ) ); + m_hWpnShield = m_pPlayer->GetCombatShield(); + CWeaponTwoHandedContainer *p = ( CWeaponTwoHandedContainer * )m_pPlayer->Weapon_OwnsThisType( "weapon_twohandedcontainer" ); + if ( !p ) + { + p = static_cast< CWeaponTwoHandedContainer * >( m_pPlayer->GiveNamedItem( "weapon_twohandedcontainer" ) ); + } + if ( p && m_hWpnShield.Get() && m_hWpnPlasma.Get() ) + { + m_hWpnShield->SetReflectViewModelAnimations( true ); + p->SetWeapons( m_hWpnPlasma, m_hWpnShield ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPlayerClassMedic::RespawnClass( void ) +{ + BaseClass::RespawnClass(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CPlayerClassMedic::ResupplyAmmo( float flFraction, ResupplyReason_t reason ) +{ + bool bGiven = false; + + if ((reason == RESUPPLY_ALL_FROM_STATION) || (reason == RESUPPLY_GRENADES_FROM_STATION)) + { + if (ResupplyAmmoType( 3 * flFraction, "Grenades" )) + bGiven = true; + } + + if ((reason == RESUPPLY_ALL_FROM_STATION) || (reason == RESUPPLY_AMMO_FROM_STATION)) + { + } + + if ( reason == RESUPPLY_RESPAWN ) + { + } + + if ( BaseClass::ResupplyAmmo(flFraction, reason) ) + bGiven = true; + return bGiven; +} + +//----------------------------------------------------------------------------- +// Purpose: Set medic class specific movement data here. +//----------------------------------------------------------------------------- +void CPlayerClassMedic::SetupMoveData( void ) +{ + // Setup Class statistics + m_flMaxWalkingSpeed = class_medic_speed.GetFloat(); +} + +//----------------------------------------------------------------------------- +// Purpose: Called every frame by postthink +//----------------------------------------------------------------------------- +void CPlayerClassMedic::ClassThink( void ) +{ + // Heal me if it's time (and I'm not dead!) + if ( m_pPlayer->IsAlive() && m_flNextHealTime && m_flNextHealTime < gpGlobals->curtime ) + { + if ( m_pPlayer->GetHealth() < m_pPlayer->GetMaxHealth() ) + { + // Heal the player + m_pPlayer->TakeHealth( 1.0, DMG_GENERIC ); + m_flNextHealTime = gpGlobals->curtime + 0.1; + } + else + { + m_flNextHealTime = 0; + } + } + + BaseClass::ClassThink(); +} + +//----------------------------------------------------------------------------- +// Purpose: Return true if this player's allowed to build another one of the specified objects +//----------------------------------------------------------------------------- +int CPlayerClassMedic::CanBuild( int iObjectType ) +{ + return BaseClass::CanBuild( iObjectType ); +} + +//----------------------------------------------------------------------------- +// Purpose: Update abilities based on new technologies gained +//----------------------------------------------------------------------------- +void CPlayerClassMedic::GainedNewTechnology( CBaseTechnology *pTechnology ) +{ + // Autorepair + m_bHasAutoRepair = m_pPlayer->HasNamedTechnology( "obj_autorepair" ); + + BaseClass::GainedNewTechnology( pTechnology ); +} + +//----------------------------------------------------------------------------- +// Purpose: Create a personal order for this player +//----------------------------------------------------------------------------- +void CPlayerClassMedic::CreatePersonalOrder( void ) +{ + if ( CreateInitialOrder() ) + return; + + // Check for healing orders. + if ( COrderHeal::CreateOrder( this ) ) + return; + + // Should they build a resource pump? + if ( COrderResourcePump::CreateOrder( this ) ) + return; + + // Build a resupply station? + if ( COrderResupply::CreateOrder( this ) ) + return; + + BaseClass::CreatePersonalOrder(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPlayerClassMedic::SetPlayerHull( void ) +{ + if ( m_pPlayer->GetFlags() & FL_DUCKING ) + { + m_pPlayer->SetCollisionBounds( MEDICCLASS_HULL_DUCK_MIN, MEDICCLASS_HULL_DUCK_MAX ); + } + else + { + m_pPlayer->SetCollisionBounds( MEDICCLASS_HULL_STAND_MIN, MEDICCLASS_HULL_STAND_MAX ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPlayerClassMedic::GetPlayerHull( bool bDucking, Vector &vecMin, Vector &vecMax ) +{ + if ( bDucking ) + { + VectorCopy( MEDICCLASS_HULL_DUCK_MIN, vecMin ); + VectorCopy( MEDICCLASS_HULL_DUCK_MAX, vecMax ); + } + else + { + VectorCopy( MEDICCLASS_HULL_STAND_MIN, vecMin ); + VectorCopy( MEDICCLASS_HULL_STAND_MAX, vecMax ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPlayerClassMedic::ResetViewOffset( void ) +{ + if ( m_pPlayer ) + { + m_pPlayer->SetViewOffset( MEDICCLASS_VIEWOFFSET_STAND ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: The player has taken damage. Return the damage done. +//----------------------------------------------------------------------------- +float CPlayerClassMedic::OnTakeDamage( const CTakeDamageInfo &info ) +{ + // Stop healing when taking damage + m_flNextHealTime = gpGlobals->curtime + MEDIC_HEAL_TIME_AFTER_DAMAGE; + + return BaseClass::OnTakeDamage( info ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPlayerClassMedic::InitVCollision( void ) +{ + CPhysCollide *pStandModel = PhysCreateBbox( MEDICCLASS_HULL_STAND_MIN, MEDICCLASS_HULL_STAND_MAX ); + CPhysCollide *pCrouchModel = PhysCreateBbox( MEDICCLASS_HULL_DUCK_MIN, MEDICCLASS_HULL_DUCK_MAX ); + m_pPlayer->SetupVPhysicsShadow( pStandModel, "tfplayer_medic_stand", pCrouchModel, "tfplayer_medic_crouch" ); +} diff --git a/game/server/tf2/tf_class_medic.h b/game/server/tf2/tf_class_medic.h new file mode 100644 index 0000000..714575e --- /dev/null +++ b/game/server/tf2/tf_class_medic.h @@ -0,0 +1,73 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Medic playerclass +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef TF_CLASS_MEDIC_H +#define TF_CLASS_MEDIC_H + +#ifdef _WIN32 +#pragma once +#endif + + +#include "TFClassData_Shared.h" + +//===================================================================== +// Medic +class CPlayerClassMedic : public CPlayerClass +{ + DECLARE_CLASS( CPlayerClassMedic, CPlayerClass ); +public: + CPlayerClassMedic( CBaseTFPlayer *pPlayer, TFClass iClass ); + virtual ~CPlayerClassMedic(); + + virtual void ClassActivate( void ); + virtual void ClassDeactivate( void ); + + virtual const char* GetClassModelString( int nTeam ); + + // Class Initialization + virtual void CreateClass( void ); // Create the class upon initial spawn + virtual void RespawnClass( void ); // Called upon all respawns + virtual bool ResupplyAmmo( float flPercentage, ResupplyReason_t reason ); + virtual void SetupMoveData( void ); // Override class specific movement data here. + virtual void SetupSizeData( void ); // Override class specific size data here. + virtual void ResetViewOffset( void ); + + PlayerClassMedicData_t *GetClassData( void ) { return &m_ClassData; } + + virtual void ClassThink( void ); + virtual void GainedNewTechnology( CBaseTechnology *pTechnology ); + + // Objects + int CanBuild( int iObjectType ); + + // Orders + virtual void CreatePersonalOrder( void ); + + // Hooks + virtual void SetPlayerHull( void ); + virtual void GetPlayerHull( bool bDucking, Vector &vecMin, Vector &vecMax ); + + virtual float OnTakeDamage( const CTakeDamageInfo &info ); + + // Player physics shadow. + void InitVCollision( void ); + +protected: + + // Weapons + CHandle<CBaseTFCombatWeapon> m_hWpnPlasma; + + bool m_bHasAutoRepair; + float m_flNextHealTime; + + PlayerClassMedicData_t m_ClassData; +}; + +EXTERN_SEND_TABLE( DT_PlayerClassMedicData ) + +#endif // TF_CLASS_MEDIC_H diff --git a/game/server/tf2/tf_class_pyro.cpp b/game/server/tf2/tf_class_pyro.cpp new file mode 100644 index 0000000..a3dd5fd --- /dev/null +++ b/game/server/tf2/tf_class_pyro.cpp @@ -0,0 +1,164 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" +#include "basecombatweapon.h" +#include "tf_player.h" +#include "tf_class_pyro.h" +#include "weapon_twohandedcontainer.h" +#include "weapon_gas_can.h" +#include "weapon_flame_thrower.h" +#include "gasoline_shared.h" +#include "weapon_combatshield.h" + +BEGIN_SEND_TABLE_NOBASE( CPlayerClassPyro, DT_PlayerClassPyroData ) +END_SEND_TABLE() + + +CPlayerClassPyro::CPlayerClassPyro( CBaseTFPlayer *pPlayer, TFClass iClass ) : CPlayerClass( pPlayer, iClass ) +{ + for (int i = 0; i < MAX_TF_TEAMS; ++i) + { + SetClassModel( MAKE_STRING(GetClassModelString(i)), i ); + } +} + + +CPlayerClassPyro::~CPlayerClassPyro() +{ +} + + +void CPlayerClassPyro::SetupSizeData() +{ + // Initially set the player to the base player class standing hull size. + m_pPlayer->SetCollisionBounds( PYROCLASS_HULL_STAND_MIN, PYROCLASS_HULL_STAND_MAX ); + m_pPlayer->SetViewOffset( PYROCLASS_VIEWOFFSET_STAND ); + m_pPlayer->m_Local.m_flStepSize = PYROCLASS_STEPSIZE; +} + + +void CPlayerClassPyro::ClassActivate() +{ + BaseClass::ClassActivate(); + + // Setup movement data. + SetupMoveData(); + + memset( &m_ClassData, 0, sizeof( m_ClassData ) ); +} + + +const char *CPlayerClassPyro::GetClassModelString( int nTeam ) +{ + if (nTeam == TEAM_HUMANS) + return "models/player/medic.mdl"; + else + return "models/player/recon.mdl"; +} + + +void CPlayerClassPyro::CreateClass() +{ + BaseClass::CreateClass(); + + // Create our two handed weapon layout + m_hWpnFlameThrower = static_cast< CWeaponFlameThrower * >( m_pPlayer->GiveNamedItem( "weapon_flame_thrower" ) ); + m_hWpnGasCan = static_cast< CWeaponGasCan * >( m_pPlayer->GiveNamedItem( "weapon_gas_can" ) ); + m_hWpnShield = m_pPlayer->GetCombatShield(); + CWeaponTwoHandedContainer *p = ( CWeaponTwoHandedContainer * )m_pPlayer->Weapon_OwnsThisType( "weapon_twohandedcontainer" ); + if ( !p ) + { + p = static_cast< CWeaponTwoHandedContainer * >( m_pPlayer->GiveNamedItem( "weapon_twohandedcontainer" ) ); + } + if ( p && m_hWpnShield.Get() && m_hWpnGasCan.Get() ) + { + m_hWpnShield->SetReflectViewModelAnimations( true ); + p->SetWeapons( m_hWpnGasCan, m_hWpnShield ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPlayerClassPyro::SetPlayerHull() +{ + if ( m_pPlayer->GetFlags() & FL_DUCKING ) + { + m_pPlayer->SetCollisionBounds( PYROCLASS_HULL_DUCK_MIN, PYROCLASS_HULL_DUCK_MAX ); + } + else + { + m_pPlayer->SetCollisionBounds( PYROCLASS_HULL_STAND_MIN, PYROCLASS_HULL_STAND_MAX ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPlayerClassPyro::GetPlayerHull( bool bDucking, Vector &vecMin, Vector &vecMax ) +{ + if ( bDucking ) + { + VectorCopy( PYROCLASS_HULL_DUCK_MIN, vecMin ); + VectorCopy( PYROCLASS_HULL_DUCK_MAX, vecMax ); + } + else + { + VectorCopy( PYROCLASS_HULL_STAND_MIN, vecMin ); + VectorCopy( PYROCLASS_HULL_STAND_MAX, vecMax ); + } +} + + +void CPlayerClassPyro::ResetViewOffset() +{ + if ( m_pPlayer ) + { + m_pPlayer->SetViewOffset( PYROCLASS_VIEWOFFSET_STAND ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPlayerClassPyro::InitVCollision() +{ + CPhysCollide *pStandModel = PhysCreateBbox( PYROCLASS_HULL_STAND_MIN, PYROCLASS_HULL_STAND_MAX ); + CPhysCollide *pCrouchModel = PhysCreateBbox( PYROCLASS_HULL_DUCK_MIN, PYROCLASS_HULL_DUCK_MAX ); + m_pPlayer->SetupVPhysicsShadow( pStandModel, "tfplayer_medic_stand", pCrouchModel, "tfplayer_medic_crouch" ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CPlayerClassPyro::ResupplyAmmo( float flFraction, ResupplyReason_t reason ) +{ + bool bGiven = false; + + if ((reason == RESUPPLY_ALL_FROM_STATION) || (reason == RESUPPLY_AMMO_FROM_STATION)) + { + if (ResupplyAmmoType( 80 * flFraction, PYRO_AMMO_TYPE )) + bGiven = true; + } + + // On respawn, resupply base weapon ammo + if ( reason == RESUPPLY_RESPAWN ) + { + if ( ResupplyAmmoType( 30, PYRO_AMMO_TYPE ) ) + bGiven = true; + } + + if ( BaseClass::ResupplyAmmo(flFraction, reason) ) + bGiven = true; + + return bGiven; +} + diff --git a/game/server/tf2/tf_class_pyro.h b/game/server/tf2/tf_class_pyro.h new file mode 100644 index 0000000..600b768 --- /dev/null +++ b/game/server/tf2/tf_class_pyro.h @@ -0,0 +1,57 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef TF_CLASS_PYRO_H +#define TF_CLASS_PYRO_H +#ifdef _WIN32 +#pragma once +#endif + + +#include "TFClassData_Shared.h" + + +class CWeaponFlameThrower; +class CWeaponGasCan; + + +//===================================================================== +// Medic +class CPlayerClassPyro : public CPlayerClass +{ + DECLARE_CLASS( CPlayerClassPyro, CPlayerClass ); +public: + CPlayerClassPyro( CBaseTFPlayer *pPlayer, TFClass iClass ); + virtual ~CPlayerClassPyro(); + + +// Overrides. +public: + + virtual void SetupSizeData(); + virtual void CreateClass(); + virtual void SetPlayerHull(); + virtual void GetPlayerHull( bool bDucking, Vector &vecMin, Vector &vecMax ); + virtual void ResetViewOffset(); + virtual void InitVCollision(); + virtual bool ResupplyAmmo( float flFraction, ResupplyReason_t reason ); + virtual void ClassActivate(); + + virtual const char* GetClassModelString( int nTeam ); + + +private: + + PlayerClassPyroData_t m_ClassData; + + CHandle<CWeaponFlameThrower> m_hWpnFlameThrower; + CHandle<CWeaponGasCan> m_hWpnGasCan; +}; + + + +#endif // TF_CLASS_PYRO_H diff --git a/game/server/tf2/tf_class_recon.cpp b/game/server/tf2/tf_class_recon.cpp new file mode 100644 index 0000000..c5bc1fe --- /dev/null +++ b/game/server/tf2/tf_class_recon.cpp @@ -0,0 +1,221 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: The Recon Player Class +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" +#include "tf_player.h" +#include "tf_class_recon.h" +#include "tf_reconvars.h" +#include "in_buttons.h" +#include "basecombatweapon.h" +#include "tf_obj.h" +#include "weapon_builder.h" +#include "tf_team.h" +#include "tf_func_resource.h" +#include "orders.h" +#include "engine/IEngineSound.h" + + +extern short g_sModelIndexFireball; + +ConVar class_recon_speed( "class_recon_speed","250", FCVAR_NONE, "Recon movement speed" ); + +// ------------------------------------------------------------------------ // +// Globals. +// ------------------------------------------------------------------------ // + +//============================================================================= +// +// Recon Data Table +// +BEGIN_SEND_TABLE_NOBASE( CPlayerClassRecon, DT_PlayerClassReconData ) + SendPropInt ( SENDINFO_STRUCTELEM( m_ClassData.m_nJumpCount ), 3, SPROP_UNSIGNED ), + SendPropFloat ( SENDINFO_STRUCTELEM( m_ClassData.m_flSuppressionJumpTime ), 32, SPROP_NOSCALE ), + SendPropFloat ( SENDINFO_STRUCTELEM( m_ClassData.m_flSuppressionImpactTime ), 32, SPROP_NOSCALE ), + SendPropFloat ( SENDINFO_STRUCTELEM( m_ClassData.m_flStickTime ), 32, SPROP_NOSCALE ), + SendPropFloat ( SENDINFO_STRUCTELEM( m_ClassData.m_flActiveJumpTime ), 32, SPROP_NOSCALE ), + SendPropFloat ( SENDINFO_STRUCTELEM( m_ClassData.m_flImpactDist ), 32, SPROP_NOSCALE ), + SendPropVector ( SENDINFO_STRUCTELEM( m_ClassData.m_vecImpactNormal ), -1, SPROP_NORMAL ), + SendPropVector ( SENDINFO_STRUCTELEM( m_ClassData.m_vecUnstickVelocity ), -1, SPROP_COORD ), + SendPropInt ( SENDINFO_STRUCTELEM( m_ClassData.m_bTrailParticles ), 1, SPROP_UNSIGNED ), +END_SEND_TABLE() + +//----------------------------------------------------------------------------- +// Purpose: +// Output : const char +//----------------------------------------------------------------------------- +const char *CPlayerClassRecon::GetClassModelString( int nTeam ) +{ + static const char *string = "models/player/recon.mdl"; + return string; +} + +// ------------------------------------------------------------------------ // +// CPlayerClassRecon +// ------------------------------------------------------------------------ // +CPlayerClassRecon::CPlayerClassRecon( CBaseTFPlayer *pPlayer, TFClass iClass ) : CPlayerClass( pPlayer, iClass ) +{ + for (int i = 0; i < MAX_TF_TEAMS; ++i) + { + SetClassModel( MAKE_STRING(GetClassModelString(i)), i ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CPlayerClassRecon::~CPlayerClassRecon() +{ +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPlayerClassRecon::ClassActivate( void ) +{ + BaseClass::ClassActivate(); + + // Setup movement data. + SetupMoveData(); + + m_bHasRadarScanner = false; + + memset( &m_ClassData, 0, sizeof( m_ClassData ) ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPlayerClassRecon::ClassDeactivate( void ) +{ + BaseClass::ClassDeactivate(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CPlayerClassRecon::ResupplyAmmo( float flFraction, ResupplyReason_t reason ) +{ + bool bGiven = false; + + if ((reason == RESUPPLY_ALL_FROM_STATION) || (reason == RESUPPLY_GRENADES_FROM_STATION)) + { + } + + if ((reason == RESUPPLY_ALL_FROM_STATION) || (reason == RESUPPLY_AMMO_FROM_STATION)) + { + } + + if ( BaseClass::ResupplyAmmo(flFraction, reason) ) + bGiven = true; + return bGiven; +} + +//----------------------------------------------------------------------------- +// Purpose: Set recon class specific movement data here. +//----------------------------------------------------------------------------- +void CPlayerClassRecon::SetupMoveData( void ) +{ + // Setup Class statistics + m_flMaxWalkingSpeed = class_recon_speed.GetFloat(); + + m_ClassData.m_nJumpCount = 0; + m_ClassData.m_flSuppressionJumpTime = -99999; + m_ClassData.m_flSuppressionImpactTime = -99999; + m_ClassData.m_flActiveJumpTime = -99999; + m_ClassData.m_flStickTime = -99999; + m_ClassData.m_flImpactDist = -99999; + m_ClassData.m_vecImpactNormal.Init(); + m_ClassData.m_vecUnstickVelocity.Init(); + m_ClassData.m_bTrailParticles = false; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPlayerClassRecon::SetupSizeData( void ) +{ + // Initially set the player to the base player class standing hull size. + m_pPlayer->SetCollisionBounds( RECONCLASS_HULL_STAND_MIN, RECONCLASS_HULL_STAND_MAX ); + m_pPlayer->SetViewOffset( RECONCLASS_VIEWOFFSET_STAND ); + m_pPlayer->m_Local.m_flStepSize = RECONCLASS_STEPSIZE; +} + +//----------------------------------------------------------------------------- +// Purpose: Return true if this player's allowed to build another one of the specified objects +//----------------------------------------------------------------------------- +int CPlayerClassRecon::CanBuild( int iObjectType ) +{ + return BaseClass::CanBuild( iObjectType ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPlayerClassRecon::ClassThink() +{ + BaseClass::ClassThink(); + + m_ClassData.m_bTrailParticles = (m_pPlayer->IsAlive() && !(m_pPlayer->GetFlags() & FL_ONGROUND)); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPlayerClassRecon::CreatePersonalOrder() +{ + if ( CreateInitialOrder() ) + return; + + BaseClass::CreatePersonalOrder(); +} + +//----------------------------------------------------------------------------- +// Purpose: New technology has been gained. Recalculate any class specific technology dependencies. +//----------------------------------------------------------------------------- +void CPlayerClassRecon::GainedNewTechnology( CBaseTechnology *pTechnology ) +{ + // Radar Scanner + if ( m_pPlayer->HasNamedTechnology( "rec_b_radar_scanners" ) ) + { + m_bHasRadarScanner = true; + } + else + { + m_bHasRadarScanner = false; + } + + BaseClass::GainedNewTechnology( pTechnology ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPlayerClassRecon::SetPlayerHull( void ) +{ + if ( m_pPlayer->GetFlags() & FL_DUCKING ) + { + m_pPlayer->SetCollisionBounds( RECONCLASS_HULL_DUCK_MIN, RECONCLASS_HULL_DUCK_MAX ); + } + else + { + m_pPlayer->SetCollisionBounds( RECONCLASS_HULL_STAND_MIN, RECONCLASS_HULL_STAND_MAX ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPlayerClassRecon::ResetViewOffset( void ) +{ + if ( m_pPlayer ) + { + m_pPlayer->SetViewOffset( RECONCLASS_VIEWOFFSET_STAND ); + } +} + + diff --git a/game/server/tf2/tf_class_recon.h b/game/server/tf2/tf_class_recon.h new file mode 100644 index 0000000..ef61a04 --- /dev/null +++ b/game/server/tf2/tf_class_recon.h @@ -0,0 +1,60 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef TF_CLASS_RECON_H +#define TF_CLASS_RECON_H +#pragma once + +#include "TFClassData_Shared.h" +#include "tf_playerclass.h" + +class CTFJetpackSteam; +class CReconJetpackLevel; + + +//===================================================================== +// Recon +class CPlayerClassRecon : public CPlayerClass +{ +public: + DECLARE_CLASS( CPlayerClassRecon, CPlayerClass ); + + CPlayerClassRecon( CBaseTFPlayer *pPlayer, TFClass iClass ); + ~CPlayerClassRecon(); + + virtual void ClassActivate( void ); + virtual void ClassDeactivate( void ); + + virtual const char* GetClassModelString( int nTeam ); + + // Class Initialization + virtual bool ResupplyAmmo( float flPercentage, ResupplyReason_t reason ); + virtual void SetupMoveData( void ); // Override class specific movement data here. + virtual void SetupSizeData( void ); // Override class specific size data here. + virtual void ResetViewOffset( void ); + + PlayerClassReconData_t *GetClassData( void ) { return &m_ClassData; } + + virtual int CanBuild( int iObjectType ); + + virtual void ClassThink( void ); + virtual void GainedNewTechnology( CBaseTechnology *pTechnology ); + + virtual void CreatePersonalOrder(); + + // Hooks + virtual void SetPlayerHull( void ); + + CNetworkVarEmbedded( PlayerClassReconData_t, m_ClassData ); + +protected: + bool m_bHasRadarScanner; +}; + +EXTERN_SEND_TABLE( DT_PlayerClassReconData ) + +#endif // TF_CLASS_RECON_H diff --git a/game/server/tf2/tf_class_sapper.cpp b/game/server/tf2/tf_class_sapper.cpp new file mode 100644 index 0000000..63338c8 --- /dev/null +++ b/game/server/tf2/tf_class_sapper.cpp @@ -0,0 +1,328 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: The Sapper Player Class +// +// $Workfile: $ +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "tf_player.h" +#include "tf_class_sapper.h" +#include "basecombatweapon.h" +#include "weapon_builder.h" +#include "tf_team.h" +#include "entitylist.h" +#include "tf_obj.h" +#include "weapon_mortar.h" +#include "orders.h" +#include "engine/IEngineSound.h" +#include "weapon_twohandedcontainer.h" +#include "weapon_combatshield.h" +#include "tf_vehicle_teleport_station.h" +#define EMP_RADIUS_NORMAL ( 512.0f ) +#define EMP_RADIUS_MEDIUM ( 1024.0f ) +#define EMP_RADIUS_HUGE ( 4096.0f ) + +#define EMP_RECHARGETIME_NORMAL ( 20.0f ) +#define EMP_RECHARGETIME_FAST ( 15.0f ) + +#define EMP_EFFECTTIME_NORMAL ( 10.0f ) +#define EMP_EFFECTTIME_LONG ( 15.0f ) + +ConVar class_sapper_speed( "class_sapper_speed","225", FCVAR_NONE, "Sapper movement speed" ); +ConVar class_sapper_boost_amount( "class_sapper_boost_amount","35", FCVAR_NONE, "Amount of energy drained for the Sapper to get a boost." ); +ConVar class_sapper_boost_time( "class_sapper_boost_time","15", FCVAR_NONE, "Sapper's boost time after draining a full charge of boost." ); +ConVar class_sapper_boost_rate( "class_sapper_boost_rate","1", FCVAR_NONE, "Sapper's boost rate on his self-boost." ); +ConVar class_sapper_damage_modifier( "class_sapper_damage_modifier", "1.5", 0, "Scales the damage a Sapper does while he's self-boosted." ); + +BEGIN_SEND_TABLE_NOBASE( CPlayerClassSapper, DT_PlayerClassSapperData ) + SendPropFloat( SENDINFO( m_flDrainedEnergy ), 8, SPROP_UNSIGNED, 0.0f, 1.0f ), +END_SEND_TABLE() + +//----------------------------------------------------------------------------- +// Purpose: +// Output : const char +//----------------------------------------------------------------------------- +const char *CPlayerClassSapper::GetClassModelString( int nTeam ) +{ + static const char *string = "models/player/technician.mdl"; + return string; +} + +//----------------------------------------------------------------------------- +// Purpose: Sapper +//----------------------------------------------------------------------------- +CPlayerClassSapper::CPlayerClassSapper( CBaseTFPlayer *pPlayer, TFClass iClass ) : CPlayerClass( pPlayer, iClass ) +{ + for (int i = 0; i < MAX_TF_TEAMS; ++i) + { + SetClassModel( MAKE_STRING(GetClassModelString(i)), i ); + } + + // Setup movement data. + SetupMoveData(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CPlayerClassSapper::~CPlayerClassSapper() +{ +} + +//----------------------------------------------------------------------------- +// Purpose: Any objects created/owned by class should be allocated and destroyed here +//----------------------------------------------------------------------------- +void CPlayerClassSapper::ClassActivate( void ) +{ + BaseClass::ClassActivate(); + + m_bHasMediumRangeEMP = false; + m_bHasFasterRechargingEMP = false; + m_bHasHugeRadiusEMP = false; + m_bHasLongerLastingEMPEffect = false; + + m_vecLastOrigin.Init(); + m_flLastMoveTime = 0.0f; + + memset( &m_ClassData, 0, sizeof( m_ClassData ) ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPlayerClassSapper::ClassDeactivate( void ) +{ + m_DamageModifier.RemoveModifier(); + BaseClass::ClassDeactivate(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPlayerClassSapper::CreateClass( void ) +{ + BaseClass::CreateClass(); + + // Create our two handed weapon layout + m_hWpnPlasma = static_cast< CBaseTFCombatWeapon * >( m_pPlayer->GiveNamedItem( "weapon_combat_shotgun" ) ); + m_hWpnShield = m_pPlayer->GetCombatShield(); + CWeaponTwoHandedContainer *p = ( CWeaponTwoHandedContainer * )m_pPlayer->Weapon_OwnsThisType( "weapon_twohandedcontainer" ); + if ( !p ) + { + p = static_cast< CWeaponTwoHandedContainer * >( m_pPlayer->GiveNamedItem( "weapon_twohandedcontainer" ) ); + } + if ( p && m_hWpnShield.Get() && m_hWpnPlasma.Get() ) + { + m_hWpnShield->SetReflectViewModelAnimations( true ); + p->SetWeapons( m_hWpnPlasma, m_hWpnShield ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPlayerClassSapper::RespawnClass( void ) +{ + BaseClass::RespawnClass(); + + // Reset time of last move + m_vecLastOrigin.Init(); + m_flLastMoveTime = 0.0f; + m_flDrainedEnergy = 0; + m_flBoostEndTime = 0; + m_DamageModifier.SetModifier( class_sapper_damage_modifier.GetFloat() ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CPlayerClassSapper::ResupplyAmmo( float flFraction, ResupplyReason_t reason ) +{ + bool bGiven = false; + if ((reason == RESUPPLY_ALL_FROM_STATION) || (reason == RESUPPLY_GRENADES_FROM_STATION)) + { + if (ResupplyAmmoType( 3 * flFraction, "Grenades" )) + bGiven = true; + } + + if ((reason == RESUPPLY_ALL_FROM_STATION) || (reason == RESUPPLY_AMMO_FROM_STATION)) + { + } + + // On respawn, resupply base weapon ammo + if ( reason == RESUPPLY_RESPAWN ) + { + } + + if ( BaseClass::ResupplyAmmo(flFraction,reason) ) + bGiven = true; + return bGiven; +} + +//----------------------------------------------------------------------------- +// Purpose: Set sapper class specific movement data here. +//----------------------------------------------------------------------------- +void CPlayerClassSapper::SetupMoveData( void ) +{ + // Setup Class statistics + m_flMaxWalkingSpeed = class_sapper_speed.GetFloat(); + m_vecLastOrigin.Init(); + m_flLastMoveTime = 0.0f; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPlayerClassSapper::SetupSizeData( void ) +{ + // Initially set the player to the base player class standing hull size. + m_pPlayer->SetCollisionBounds( SAPPERCLASS_HULL_STAND_MIN, SAPPERCLASS_HULL_STAND_MAX ); + m_pPlayer->SetViewOffset( SAPPERCLASS_VIEWOFFSET_STAND ); + m_pPlayer->m_Local.m_flStepSize = SAPPERCLASS_STEPSIZE; +} + +//----------------------------------------------------------------------------- +// Purpose: New technology has been gained. Recalculate any class specific technology dependencies. +//----------------------------------------------------------------------------- +void CPlayerClassSapper::GainedNewTechnology( CBaseTechnology *pTechnology ) +{ + m_bHasMediumRangeEMP = false; + m_bHasFasterRechargingEMP = false; + m_bHasHugeRadiusEMP = false; + m_bHasLongerLastingEMPEffect = false; + + if ( m_pPlayer->HasNamedTechnology( "emp1" ) ) + { + m_bHasFasterRechargingEMP = true; + } + + if ( m_pPlayer->HasNamedTechnology( "emp3" ) ) + { + m_bHasMediumRangeEMP = true; + } + + if ( m_pPlayer->HasNamedTechnology( "emp4" ) ) + { + m_bHasLongerLastingEMPEffect = true; + } + + if ( m_pPlayer->HasNamedTechnology( "emp5" ) ) + { + m_bHasHugeRadiusEMP = true; + } + + BaseClass::GainedNewTechnology( pTechnology ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPlayerClassSapper::ClassThink( void ) +{ + if ( m_pPlayer->GetLocalOrigin() != m_vecLastOrigin ) + { + m_vecLastOrigin = m_pPlayer->GetLocalOrigin(); + m_flLastMoveTime = gpGlobals->curtime; + } + + // Am I boosting myself? + if ( m_pPlayer->IsAlive() && (m_flBoostEndTime > gpGlobals->curtime) ) + { + m_pPlayer->AttemptToPowerup( POWERUP_BOOST, class_sapper_boost_time.GetFloat(), class_sapper_boost_rate.GetFloat() * 0.1, m_pPlayer, &m_DamageModifier ); + } + else + { + m_DamageModifier.RemoveModifier(); + } + + BaseClass::ClassThink(); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CPlayerClassSapper::CheckStationaryTime( float time_required ) +{ + // Has enough time passed? + if ( gpGlobals->curtime >= m_flLastMoveTime + time_required ) + { + return true; + } + + // Not enough time yet + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPlayerClassSapper::CreatePersonalOrder( void ) +{ + if ( CreateInitialOrder() ) + return; + + BaseClass::CreatePersonalOrder(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPlayerClassSapper::SetPlayerHull( void ) +{ + if ( m_pPlayer->GetFlags() & FL_DUCKING ) + { + m_pPlayer->SetCollisionBounds( SAPPERCLASS_HULL_DUCK_MIN, SAPPERCLASS_HULL_DUCK_MAX ); + } + else + { + m_pPlayer->SetCollisionBounds( SAPPERCLASS_HULL_STAND_MIN, SAPPERCLASS_HULL_STAND_MAX ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPlayerClassSapper::ResetViewOffset( void ) +{ + if ( m_pPlayer ) + { + m_pPlayer->SetViewOffset( SAPPERCLASS_VIEWOFFSET_STAND ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPlayerClassSapper::AddDrainedEnergy( float flEnergy ) +{ + // Convert to 0->1 + flEnergy = ( flEnergy / class_sapper_boost_amount.GetFloat() ); + m_flDrainedEnergy = MIN( 1.0, m_flDrainedEnergy + flEnergy ); + + // Did we hit max? + if ( m_flDrainedEnergy >= 1.0 ) + { + m_flDrainedEnergy = 0; + + // Boost the player + m_flBoostEndTime = gpGlobals->curtime + class_sapper_boost_time.GetFloat(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +float CPlayerClassSapper::GetDrainedEnergy( void ) +{ + return m_flDrainedEnergy; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPlayerClassSapper::DeductDrainedEnergy( float flEnergy ) +{ + m_flDrainedEnergy = MAX( 0, m_flDrainedEnergy - flEnergy ); +} diff --git a/game/server/tf2/tf_class_sapper.h b/game/server/tf2/tf_class_sapper.h new file mode 100644 index 0000000..e003008 --- /dev/null +++ b/game/server/tf2/tf_class_sapper.h @@ -0,0 +1,97 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Sapper player class +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef TF_CLASS_SAPPER_H +#define TF_CLASS_SAPPER_H +#ifdef _WIN32 +#pragma once +#endif + +#include "TFClassData_Shared.h" +#include "damagemodifier.h" + +class CEMPPulseStatus; +class CBaseObject; + + +//----------------------------------------------------------------------------- +// Purpose: Sapper +//----------------------------------------------------------------------------- +class CPlayerClassSapper : public CPlayerClass +{ +public: + DECLARE_CLASS( CPlayerClassSapper, CPlayerClass ); + + CPlayerClassSapper( CBaseTFPlayer *pPlayer, TFClass iClass ); + ~CPlayerClassSapper(); + + // Any objects created/owned by class should be allocated and destroyed here + virtual void ClassActivate( void ); + virtual void ClassDeactivate( void ); + + virtual const char* GetClassModelString( int nTeam ); + + // Class Initialization + virtual void CreateClass( void ); // Create the class upon initial spawn + virtual void RespawnClass( void ); // Called upon all respawns + virtual bool ResupplyAmmo( float flPercentage, ResupplyReason_t reason ); + virtual void SetupMoveData( void ); // Override class specific movement data here. + virtual void SetupSizeData( void ); // Override class specific size data here. + virtual void ResetViewOffset( void ); + + PlayerClassSapperData_t *GetClassData( void ) { return &m_ClassData; } + + virtual void GainedNewTechnology( CBaseTechnology *pTechnology ); + + virtual void ClassThink( void ); + + // Energy draining + void AddDrainedEnergy( float flEnergy ); + + // See if the player has not moved in time_required seconds + bool CheckStationaryTime( float time_required ); + + // Orders + virtual void CreatePersonalOrder( void ); + + // Hooks + virtual void SetPlayerHull( void ); + + float GetDrainedEnergy( void ); + void DeductDrainedEnergy( float flEnergy ); + +public: + CNetworkVar( float, m_flDrainedEnergy ); + +private: + bool m_bHasMediumRangeEMP; + bool m_bHasFasterRechargingEMP; + bool m_bHasHugeRadiusEMP; + bool m_bHasLongerLastingEMPEffect; + + // Energy drained with the drainbeam + float m_flBoostEndTime; + CDamageModifier m_DamageModifier; + + static int m_nEffectIndex; + CEMPPulseStatus *m_pEMPPulseStatus; + + Vector m_vecLastOrigin; + float m_flLastMoveTime; + + PlayerClassSapperData_t m_ClassData; + + // Weapons + CHandle<CBaseTFCombatWeapon> m_hWpnPlasma; +}; + +EXTERN_SEND_TABLE( DT_PlayerClassSapperData ) + + +extern char *g_pszEMPPulseStart; + +#endif // TF_CLASS_SAPPER_H diff --git a/game/server/tf2/tf_class_sniper.cpp b/game/server/tf2/tf_class_sniper.cpp new file mode 100644 index 0000000..f724ede --- /dev/null +++ b/game/server/tf2/tf_class_sniper.cpp @@ -0,0 +1,261 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: The Sniper Player Class +// +// $Workfile: $ +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "tf_player.h" +#include "tf_class_sniper.h" +#include "in_buttons.h" +#include "tf_team.h" +#include "tf_class_support.h" +#include "order_killmortarguy.h" + + +bool IsEntityVisibleToTactical( int iLocalTeamNumber, int iLocalTeamPlayers, + int iLocalTeamScanners, int iEntIndex, const char *pEntName, int pEntTeamNumber, const Vector &pEntOrigin ); + +ConVar class_sniper_speed( "class_sniper_speed","200", FCVAR_NONE, "Sniper movement speed" ); + +// Stationary Camo +#define SNIPER_MAX_HIDE_LEVEL 60 +#define SNIPER_HIDE_INCREASE_PER_SEC 15 +#define SNIPER_FIRE_UNHIDE_TIME 5.0 + +//============================================================================= +// +// Sniper Data Table +// +BEGIN_SEND_TABLE_NOBASE( CPlayerClassSniper, DT_PlayerClassSniperData ) +END_SEND_TABLE() + + + +//----------------------------------------------------------------------------- +// Purpose: +// Output : const char +//----------------------------------------------------------------------------- +const char *CPlayerClassSniper::GetClassModelString( int nTeam ) +{ + static const char *string = "models/player/sniper.mdl"; + return string; +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CPlayerClassSniper::CPlayerClassSniper( CBaseTFPlayer *pPlayer, TFClass iClass ) : CPlayerClass( pPlayer, iClass ) +{ + for (int i = 0; i < MAX_TF_TEAMS; ++i) + { + SetClassModel( MAKE_STRING(GetClassModelString(i)), i ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CPlayerClassSniper::~CPlayerClassSniper() +{ +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPlayerClassSniper::ClassActivate( void ) +{ + BaseClass::ClassActivate(); + + // Setup movement data. + SetupMoveData(); + + m_bHiding = false; + m_flHideTransparency = 0.0f; + m_flLastHideUpdate = 0.0f; + + m_bCanHide = false; + + memset( &m_ClassData, 0, sizeof( m_ClassData ) ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPlayerClassSniper::ClassDeactivate( void ) +{ + BaseClass::ClassDeactivate(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPlayerClassSniper::RespawnClass( void ) +{ + BaseClass::RespawnClass(); + + // Hiding values + m_bHiding = false; + m_flHideTransparency = m_flLastHideUpdate = 0; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CPlayerClassSniper::ResupplyAmmo( float flFraction, ResupplyReason_t reason ) +{ + bool bGiven = false; + + if ((reason == RESUPPLY_ALL_FROM_STATION) || (reason == RESUPPLY_AMMO_FROM_STATION)) + { + if (ResupplyAmmoType( 100 * flFraction, "Bullets" )) + bGiven = true; + } + + if ( BaseClass::ResupplyAmmo(flFraction, reason) ) + bGiven = true; + + return bGiven; +} + +//----------------------------------------------------------------------------- +// Purpose: Set sniper class specific movement data here. +//----------------------------------------------------------------------------- +void CPlayerClassSniper::SetupMoveData( void ) +{ + // Setup Class statistics + m_flMaxWalkingSpeed = class_sniper_speed.GetFloat(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPlayerClassSniper::SetupSizeData( void ) +{ + // Initially set the player to the base player class standing hull size. + m_pPlayer->SetCollisionBounds( SNIPERCLASS_HULL_STAND_MIN, SNIPERCLASS_HULL_STAND_MAX ); + m_pPlayer->SetViewOffset( SNIPERCLASS_VIEWOFFSET_STAND ); + m_pPlayer->m_Local.m_flStepSize = SNIPERCLASS_STEPSIZE; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +float CPlayerClassSniper::GetDeployTime( void ) +{ + return 2.8; +}; + +//----------------------------------------------------------------------------- +// Purpose: Called every frame by the player PostThink() +//----------------------------------------------------------------------------- +void CPlayerClassSniper::ClassThink( void ) +{ + // Only hide if we have the technology + if ( m_bCanHide ) + { + CheckHiding(); + } + + BaseClass::ClassThink(); +} + +//----------------------------------------------------------------------------- +// Purpose: New technology has been gained. Recalculate any class specific technology dependencies. +//----------------------------------------------------------------------------- +void CPlayerClassSniper::GainedNewTechnology( CBaseTechnology *pTechnology ) +{ + // Stealthed on deploy + if ( m_pPlayer->HasNamedTechnology( "sniper_deploy_stealth" ) ) + { + m_bCanHide = true; + } + else + { + m_bCanHide = false; + } + + BaseClass::GainedNewTechnology( pTechnology ); +} + + +//----------------------------------------------------------------------------- +// Purpose: If the player's prone, slowly make him transparent +//----------------------------------------------------------------------------- +void CPlayerClassSniper::CheckHiding( void ) +{ + // Can't hide or stay hidden if taking EMP damage + if ( m_pPlayer->HasPowerup(POWERUP_EMP) ) + { + m_pPlayer->ClearCamouflage(); + return; + } + + // See if the player's just fired + if ( m_pPlayer->m_afButtonReleased & IN_ATTACK ) + { + // Immediately mostly unhide if so + m_pPlayer->ClearCamouflage(); + } + else if ( !m_pPlayer->IsDeployed() ) + { + // Not deployed? Immediately unhide if so + m_pPlayer->ClearCamouflage(); + } + else + { + // We're deployed, hide if we haven't fired for a bit. + if ( m_pPlayer->LastAttackTime() + SNIPER_FIRE_UNHIDE_TIME < gpGlobals->curtime ) + { + m_pPlayer->SetCamouflaged( SNIPER_MAX_HIDE_LEVEL, SNIPER_HIDE_INCREASE_PER_SEC ); + } + else + { + m_pPlayer->ClearCamouflage(); + } + } +} + + +void CPlayerClassSniper::CreatePersonalOrder( void ) +{ + if ( CreateInitialOrder() ) + return; + + // Kill a support guy? + if ( COrderKillMortarGuy::CreateOrder( this ) ) + return; + + BaseClass::CreatePersonalOrder(); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPlayerClassSniper::SetPlayerHull( void ) +{ + if ( m_pPlayer->GetFlags() & FL_DUCKING ) + { + m_pPlayer->SetCollisionBounds( SNIPERCLASS_HULL_DUCK_MIN, SNIPERCLASS_HULL_DUCK_MAX ); + } + else + { + m_pPlayer->SetCollisionBounds( SNIPERCLASS_HULL_STAND_MIN, SNIPERCLASS_HULL_STAND_MAX ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPlayerClassSniper::ResetViewOffset( void ) +{ + if ( m_pPlayer ) + { + m_pPlayer->SetViewOffset( SNIPERCLASS_VIEWOFFSET_STAND ); + } +} + diff --git a/game/server/tf2/tf_class_sniper.h b/game/server/tf2/tf_class_sniper.h new file mode 100644 index 0000000..7e504bf --- /dev/null +++ b/game/server/tf2/tf_class_sniper.h @@ -0,0 +1,69 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef TF_CLASS_SNIPER_H +#define TF_CLASS_SNIPER_H +#pragma once + +#include "TFClassData_Shared.h" + + +//===================================================================== +// Sniper +class CPlayerClassSniper : public CPlayerClass +{ +public: + DECLARE_CLASS( CPlayerClassSniper, CPlayerClass ); + + CPlayerClassSniper( CBaseTFPlayer *pPlayer, TFClass iClass ); + ~CPlayerClassSniper(); + + virtual void ClassActivate( void ); + virtual void ClassDeactivate( void ); + + virtual const char* GetClassModelString( int nTeam ); + + virtual void GainedNewTechnology( CBaseTechnology *pTechnology ); + + // Class Initialization + virtual void RespawnClass( void ); // Called upon all respawns + virtual bool ResupplyAmmo( float flPercentage, ResupplyReason_t reason ); + virtual void SetupMoveData( void ); // Override class specific movement data here. + virtual void SetupSizeData( void ); // Override class specific size data here. + virtual void ResetViewOffset( void ); + + PlayerClassSniperData_t *GetClassData( void ) { return &m_ClassData; } + + // Deployment + virtual float GetDeployTime( void ); + + // Class abilities + virtual void ClassThink(); + // Hiding + void CheckHiding( void ); + + // Orders + virtual void CreatePersonalOrder( void ); + + // Hooks + virtual void SetPlayerHull( void ); + +protected: + // Hiding + bool m_bHiding; + float m_flHideTransparency; + float m_flLastHideUpdate; + + PlayerClassSniperData_t m_ClassData; + +private: + bool m_bCanHide; +}; + +EXTERN_SEND_TABLE( DT_PlayerClassSniperData ) + +#endif // TF_CLASS_SNIPER_H diff --git a/game/server/tf2/tf_class_support.cpp b/game/server/tf2/tf_class_support.cpp new file mode 100644 index 0000000..9406f3c --- /dev/null +++ b/game/server/tf2/tf_class_support.cpp @@ -0,0 +1,153 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: The Support/Suppression Player Class +// +// $Workfile: $ +// $Date: $ +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "tf_player.h" +#include "tf_class_support.h" +#include "basecombatweapon.h" +#include "tf_obj.h" +#include "in_buttons.h" +#include "menu_base.h" +#include "tf_team.h" +#include "weapon_builder.h" + +ConVar class_support_speed( "class_support_speed","200", FCVAR_NONE, "Support movement speed" ); + +//============================================================================= +// +// Support Data Table +// +BEGIN_SEND_TABLE_NOBASE( CPlayerClassSupport, DT_PlayerClassSupportData ) +END_SEND_TABLE() + +//----------------------------------------------------------------------------- +// Purpose: +// Output : const char +//----------------------------------------------------------------------------- +const char *CPlayerClassSupport::GetClassModelString( int nTeam ) +{ + static const char *string = "models/player/alien_support.mdl"; + return string; +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CPlayerClassSupport::CPlayerClassSupport( CBaseTFPlayer *pPlayer, TFClass iClass ) : CPlayerClass( pPlayer, iClass ) +{ + for (int i = 1; i <= MAX_TF_TEAMS; ++i) + { + SetClassModel( MAKE_STRING(GetClassModelString(i)), i ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CPlayerClassSupport::~CPlayerClassSupport() +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPlayerClassSupport::ClassActivate( void ) +{ + BaseClass::ClassActivate(); + +// Any objects created/owned by class should be allocated and destroyed here + // Setup movement data. + SetupMoveData(); + + m_pPlayer->SetCollisionBounds( SUPPORTCLASS_HULL_STAND_MIN, SUPPORTCLASS_HULL_STAND_MAX ); + + memset( &m_ClassData, 0, sizeof( m_ClassData ) ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPlayerClassSupport::ClassDeactivate( void ) +{ + BaseClass::ClassDeactivate(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPlayerClassSupport::CreateClass( void ) +{ + BaseClass::CreateClass(); + +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CPlayerClassSupport::ResupplyAmmo( float flFraction, ResupplyReason_t reason ) +{ + bool bGiven = false; + + // On respawn, resupply base weapon ammo + if ( reason == RESUPPLY_RESPAWN ) + { + } + + if ( BaseClass::ResupplyAmmo(flFraction, reason) ) + bGiven = true; + return bGiven; +} + +//----------------------------------------------------------------------------- +// Purpose: Set support class specific movement data here. +//----------------------------------------------------------------------------- +void CPlayerClassSupport::SetupMoveData( void ) +{ + // Setup Class statistics + m_flMaxWalkingSpeed = class_support_speed.GetFloat(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPlayerClassSupport::SetupSizeData( void ) +{ + // Initially set the player to the base player class standing hull size. + m_pPlayer->SetCollisionBounds( SUPPORTCLASS_HULL_STAND_MIN, SUPPORTCLASS_HULL_STAND_MAX ); + m_pPlayer->SetViewOffset( SUPPORTCLASS_VIEWOFFSET_STAND ); + m_pPlayer->m_Local.m_flStepSize = SUPPORTCLASS_STEPSIZE; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPlayerClassSupport::SetPlayerHull( void ) +{ + if ( m_pPlayer->GetFlags() & FL_DUCKING ) + { + m_pPlayer->SetCollisionBounds( SUPPORTCLASS_HULL_DUCK_MIN, SUPPORTCLASS_HULL_DUCK_MAX ); + } + else + { + m_pPlayer->SetCollisionBounds( SUPPORTCLASS_HULL_STAND_MIN, SUPPORTCLASS_HULL_STAND_MAX ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPlayerClassSupport::ResetViewOffset( void ) +{ + if ( m_pPlayer ) + { + m_pPlayer->SetViewOffset( SUPPORTCLASS_VIEWOFFSET_STAND ); + } +} + + diff --git a/game/server/tf2/tf_class_support.h b/game/server/tf2/tf_class_support.h new file mode 100644 index 0000000..2271d39 --- /dev/null +++ b/game/server/tf2/tf_class_support.h @@ -0,0 +1,49 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: The support/suppression player class +// +// $NoKeywords: $ +//=============================================================================// +#ifndef TF_CLASS_SUPPORT_H +#define TF_CLASS_SUPPORT_H +#ifdef _WIN32 +#pragma once +#endif + +#include "tf_basecombatweapon.h" +#include "TFClassData_Shared.h" + +//===================================================================== +// Support +class CPlayerClassSupport : public CPlayerClass +{ +public: + DECLARE_CLASS( CPlayerClassSupport, CPlayerClass ); + + CPlayerClassSupport( CBaseTFPlayer *pPlayer, TFClass iClass ); + ~CPlayerClassSupport(); + + virtual void ClassActivate( void ); + virtual void ClassDeactivate( void ); + + virtual const char* GetClassModelString( int nTeam ); + + // Class Initialization + virtual void CreateClass( void ); // Create the class upon initial spawn + virtual bool ResupplyAmmo( float flPercentage, ResupplyReason_t reason ); + virtual void SetupMoveData( void ); // Override class specific movement data here. + virtual void SetupSizeData( void ); // Override class specific size data here. + virtual void ResetViewOffset( void ); + + PlayerClassSupportData_t *GetClassData( void ) { return &m_ClassData; } + + // Hooks + virtual void SetPlayerHull( void ); + +protected: + PlayerClassSupportData_t m_ClassData; +}; + +EXTERN_SEND_TABLE( DT_PlayerClassSupportData ) + +#endif // TF_CLASS_SUPPORT_H diff --git a/game/server/tf2/tf_client.cpp b/game/server/tf2/tf_client.cpp new file mode 100644 index 0000000..c39dc05 --- /dev/null +++ b/game/server/tf2/tf_client.cpp @@ -0,0 +1,180 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +/* + +===== tf_client.cpp ======================================================== + + HL2 client/server game specific stuff + +*/ + +#include "cbase.h" +#include "player.h" +#include "gamerules.h" +#include "teamplay_gamerules.h" +#include "tf_gamerules.h" +#include "EntityList.h" +#include "physics.h" +#include "game.h" +#include "ai_network.h" +#include "ai_node.h" +#include "ai_hull.h" +#include "tf_player.h" +#include "menu_base.h" +#include "shake.h" +#include "player_resource.h" +#include "engine/IEngineSound.h" + +#include "tier0/vprof.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + + +void Host_Say( edict_t *pEdict, bool teamonly ); +extern CBaseEntity *FindPickerEntity( CBasePlayer *pPlayer ); +void InitializeMenus( void ); +void DestroyMenus( void ); +void Bot_RunAll( void ); + +extern bool g_fGameOver; + +/* +=========== +ClientPutInServer + +called each time a player is spawned into the game +============ +*/ +void ClientPutInServer( edict_t *pEdict, const char *playername ) +{ + // Allocate a CBaseTFPlayer for pev, and call spawn + CBaseTFPlayer *pPlayer = CBaseTFPlayer::CreatePlayer( "player", pEdict ); + pPlayer->InitialSpawn(); + pPlayer->SetPlayerName( playername ); + pPlayer->Spawn(); +} + +/* +=============== +const char *GetGameDescription() + +Returns the descriptive name of this .dll. E.g., Half-Life, or Team Fortress 2 +=============== +*/ +const char *GetGameDescription() +{ + if ( g_pGameRules ) // this function may be called before the world has spawned, and the game rules initialized + return g_pGameRules->GetGameDescription(); + else + return "TeamFortress 2"; +} + +//----------------------------------------------------------------------------- +// Purpose: Precache game-specific models & sounds +//----------------------------------------------------------------------------- +void ClientGamePrecache( void ) +{ + // Materials used by the client effects + CBaseEntity::PrecacheModel( "sprites/white.vmt" ); + CBaseEntity::PrecacheModel( "sprites/physbeam.vmt" ); + CBaseEntity::PrecacheModel( "effects/human_object_glow.vmt" ); + + // Precache player models + CBaseEntity::PrecacheModel( "models/player/alien_commando.mdl" ); + CBaseEntity::PrecacheModel( "models/player/human_commando.mdl" ); + CBaseEntity::PrecacheModel( "models/player/human_defender.mdl"); + CBaseEntity::PrecacheModel( "models/player/medic.mdl" ); + CBaseEntity::PrecacheModel( "models/player/recon.mdl" ); + CBaseEntity::PrecacheModel( "models/player/human_medic.mdl" ); + CBaseEntity::PrecacheModel( "models/player/alien_escort.mdl" ); + CBaseEntity::PrecacheModel( "models/player/defender.mdl" ); + CBaseEntity::PrecacheModel( "models/player/technician.mdl" ); +} + + +// called by ClientKill and DeadThink +void respawn( CBaseEntity *pEdict, bool fCopyCorpse ) +{ + if (gpGlobals->coop || gpGlobals->deathmatch) + { + if ( fCopyCorpse ) + { + // make a copy of the dead body for appearances sake + ((CBaseTFPlayer *)pEdict)->CreateCorpse(); + } + + // respawn player + pEdict->Spawn(); + } + else + { // restart the entire server + engine->ServerCommand("reload\n"); + } +} + +void GameStartFrame( void ) +{ + VPROF("GameStartFrame()"); + + if ( g_pGameRules ) + g_pGameRules->Think(); + + if ( g_fGameOver ) + return; + + gpGlobals->teamplay = teamplay.GetInt() ? true : false; + + Bot_RunAll(); +} + +//========================================================= +// instantiate the proper game rules object +//========================================================= +void InstallGameRules() +{ + InitializeMenus(); + + // teamplay + CreateGameRulesObject( "CTeamFortress" ); +} + +void FinishClientPutInServer( CBaseTFPlayer *pPlayer ) +{ + pPlayer->InitialSpawn(); + pPlayer->Spawn(); + + // When the player first joins the server, they + pPlayer->m_lifeState = LIFE_DEAD; + pPlayer->AddEffects( EF_NODRAW ); + pPlayer->ChangeTeam( TEAM_UNASSIGNED ); + pPlayer->SetThink( NULL ); + + char sName[128]; + Q_strncpy( sName, pPlayer->GetPlayerName(), sizeof( sName ) ); + + // First parse the name and remove any %'s + for ( char *pApersand = sName; pApersand != NULL && *pApersand != 0; pApersand++ ) + { + // Replace it with a space + if ( *pApersand == '%' ) + *pApersand = ' '; + } + + // notify other clients of player joining the game + UTIL_ClientPrintAll( HUD_PRINTNOTIFY, "#Game_connected", sName[0] != 0 ? sName : "<unconnected>" ); +} + +void ClientActive( edict_t *pEdict, bool bLoadGame ) +{ + // Can't load games in CS! + Assert( !bLoadGame ); + + CBaseTFPlayer *pPlayer = static_cast<CBaseTFPlayer*>( CBaseEntity::Instance( pEdict ) ); + FinishClientPutInServer( pPlayer ); +} diff --git a/game/server/tf2/tf_eventlog.cpp b/game/server/tf2/tf_eventlog.cpp new file mode 100644 index 0000000..9fb99ee --- /dev/null +++ b/game/server/tf2/tf_eventlog.cpp @@ -0,0 +1,56 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +#include "cbase.h" +#include "../eventlog.h" +#include <KeyValues.h> + +class CTF2EventLog : public CEventLog +{ +private: + typedef CEventLog BaseClass; + +public: + virtual ~CTF2EventLog() {}; + +public: + bool PrintEvent( KeyValues * event ) // override virtual function + { + if ( BaseClass::PrintEvent( event ) ) + { + return true; + } + + if ( Q_strcmp(event->GetName(), "tf2_") == 0 ) + { + return PrintTF2Event( event ); + } + + return false; + } + +protected: + + bool PrintTF2Event( KeyValues * event ) // print Mod specific logs + { + // const char * name = event->GetName() + Q_strlen("tf2_"); // remove prefix + + return false; + } + +}; + +CTF2EventLog g_TF2EventLog; + +//----------------------------------------------------------------------------- +// Singleton access +//----------------------------------------------------------------------------- +IGameSystem* GameLogSystem() +{ + return &g_TF2EventLog; +} + diff --git a/game/server/tf2/tf_flare.cpp b/game/server/tf2/tf_flare.cpp new file mode 100644 index 0000000..d6961c0 --- /dev/null +++ b/game/server/tf2/tf_flare.cpp @@ -0,0 +1,249 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Auto Repair +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" +#include "tf_flare.h" +#include "tf_player.h" +#include "tf_team.h" +#include "IEffects.h" +#include "engine/IEngineSound.h" + +// Data Description +BEGIN_DATADESC( CSignalFlare ) + + // Function Pointers + DEFINE_FUNCTION( FlareTouch ), + DEFINE_FUNCTION( FlareThink ), + +END_DATADESC() + +//Data Table +IMPLEMENT_SERVERCLASS_ST( CSignalFlare, DT_SignalFlare ) + SendPropFloat( SENDINFO( m_flDuration ), 0, SPROP_NOSCALE ), + SendPropFloat( SENDINFO( m_flScale ), 0, SPROP_NOSCALE ), + SendPropInt( SENDINFO( m_bLight ), 1, SPROP_UNSIGNED ), + SendPropInt( SENDINFO( m_bSmoke ), 1, SPROP_UNSIGNED ), +END_SEND_TABLE() + +LINK_ENTITY_TO_CLASS( env_flare, CSignalFlare ); +PRECACHE_REGISTER( env_flare ); + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CSignalFlare::CSignalFlare( void ) +{ + m_nBounces = 0; + m_flScale = 1.0f; + + m_bFading = false; + m_bLight = true; + m_bSmoke = true; + + m_flNextDamage = gpGlobals->curtime; + m_lifeState = LIFE_ALIVE; + m_iHealth = 100; +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CSignalFlare::Precache( void ) +{ + // Precache model(s). + // bug: this model no longer exists: + //PrecacheModel("models/flare.mdl" ); + + PrecacheScriptSound( "SignalFlare.Impact" ); + PrecacheScriptSound( "SignalFlare.BurnLower" ); + PrecacheScriptSound( "SignalFlare.Burn" ); + +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CSignalFlare::Spawn( void ) +{ + Precache(); + SetModel( "models/flare.mdl" ); + + UTIL_SetSize( this, Vector( -2, -2, -2 ), Vector( 2, 2, 2 ) ); + + SetSolid( SOLID_BBOX ); + AddSolidFlags( FSOLID_NOT_SOLID ); + SetMoveType( MOVETYPE_NONE ); + SetFriction( 0.6f ); + SetGravity( UTIL_ScaleForGravity( 400 ) ); + m_flDuration = gpGlobals->curtime + 10; + + if ( HasSpawnFlags( SIGNALFLARE_NO_DLIGHT ) ) + { + m_bLight = false; + } + + if ( HasSpawnFlags( SIGNALFLARE_NO_SMOKE ) ) + { + m_bSmoke = false; + } + + if ( HasSpawnFlags( SIGNALFLARE_INFINITE ) ) + { + m_flDuration = -1.0f; + } + + AddFlag( FL_OBJECT | FL_NOTARGET ); + + EmitSound( "SignalFlare.Burn" ); + + SetThink( FlareThink ); + SetNextThink( gpGlobals->curtime + 0.1f ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : vecOrigin - +// vecAngles - +// *pOwner - +// Output : CSignalFlare +//----------------------------------------------------------------------------- +CSignalFlare *CSignalFlare::Create( Vector vecOrigin, QAngle vecAngles, CBaseEntity *pOwner, float lifetime ) +{ + // Create an instance of a flare. + CSignalFlare *pFlare = ( CSignalFlare* )CreateEntityByName( "env_flare" ); + if ( !pFlare ) + return NULL; + + // Set the initial origin and angle vectors. + UTIL_SetOrigin( pFlare, vecOrigin ); + pFlare->SetLocalAngles( vecAngles ); + + // Spawn the flare. + pFlare->Spawn(); + + // Set the flare's touch and think functions. + pFlare->SetTouch( FlareTouch ); + pFlare->SetThink( FlareThink ); + + // Don't start sparking immediately. + pFlare->SetNextThink( gpGlobals->curtime + 0.5f ); + + // Set the burn out time. + pFlare->m_flDuration = gpGlobals->curtime + lifetime; + + pFlare->AddSolidFlags( FSOLID_NOT_STANDABLE ); + pFlare->RemoveSolidFlags( FSOLID_NOT_SOLID ); + + pFlare->SetMoveType( MOVETYPE_FLYGRAVITY, MOVECOLLIDE_FLY_BOUNCE ); + + pFlare->SetOwnerEntity( pOwner ); + pFlare->m_pOwner = pOwner; + + return pFlare; +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CSignalFlare::FlareThink( void ) +{ + float deltaTime = ( m_flDuration - gpGlobals->curtime ); + + if ( m_flDuration != -1.0f ) + { + //Fading away + if ( ( deltaTime <= 2.0f ) && ( m_bFading == false ) ) + { + m_bFading = true; + + // Reduce sound! + EmitSound( "SignalFlare.BurnLower" ); + } + + //Burned out + if ( m_flDuration < gpGlobals->curtime ) + { + StopSound( "SignalFlare.Burn" ); + UTIL_Remove( this ); + return; + } + } + + //Act differently underwater + if ( GetWaterLevel() > 1 ) + { + UTIL_Bubbles( GetAbsOrigin() + Vector( -2, -2, -2 ), GetAbsOrigin() + Vector( 2, 2, 2 ), 1 ); + m_bSmoke = false; + } + else + { + //Shoot sparks + if ( random->RandomInt( 0, 8 ) == 1 ) + { + g_pEffects->Sparks( GetAbsOrigin() ); + } + } + + //Next update + SetNextThink( gpGlobals->curtime + 0.1f ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pOther - +//----------------------------------------------------------------------------- +void CSignalFlare::FlareTouch( CBaseEntity *pOther ) +{ + Assert( pOther ); + if ( !pOther->IsSolid() ) + return; + + if ( ( m_nBounces < 10 ) && ( GetWaterLevel() < 1 ) ) + { + //Throw some real chunks here + g_pEffects->Sparks( GetAbsOrigin() ); + } + + // hit the world, check the material type here, see if the flare should stick. +// const trace_t &tr = CBaseEntity::GetTouchTrace(); + + if ( m_nBounces == 0 ) + { + // Emit an impact sound. + EmitSound( "SignalFlare.Impact" ); + } + + // Change our flight characteristics + SetMoveType( MOVETYPE_FLYGRAVITY, MOVECOLLIDE_FLY_BOUNCE ); + SetGravity( UTIL_ScaleForGravity( 640 ) ); + + m_nBounces++; + + // After the first bounce, smacking into whoever fired the flare is fair game + SetOwnerEntity( this ); + + //Slow down + Vector newVelocity = GetAbsVelocity(); + newVelocity.x *= 0.8f; + newVelocity.y *= 0.8f; + + // Stopped? + if ( newVelocity.Length() < 64.0f ) + { + newVelocity.Init(); + SetMoveType( MOVETYPE_NONE ); + RemoveSolidFlags( FSOLID_NOT_SOLID ); + AddSolidFlags( FSOLID_TRIGGER ); + } + + SetAbsVelocity( newVelocity ); +} diff --git a/game/server/tf2/tf_flare.h b/game/server/tf2/tf_flare.h new file mode 100644 index 0000000..8aea8a1 --- /dev/null +++ b/game/server/tf2/tf_flare.h @@ -0,0 +1,58 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef TF_FLARE_H +#define TF_FLARE_H +#pragma once + +#define SIGNALFLARE_NO_DLIGHT 0x01 +#define SIGNALFLARE_NO_SMOKE 0x02 +#define SIGNALFLARE_INFINITE 0x04 +#define SIGNALFLARE_DURATION 5.0f + +//============================================================================= +// +// Signal Flare Class +// +class CSignalFlare : public CBaseAnimating +{ + + DECLARE_CLASS( CSignalFlare, CBaseAnimating ); + +public: + + DECLARE_DATADESC(); + DECLARE_SERVERCLASS(); + + CSignalFlare( void ); + + static CSignalFlare *Create( Vector vecOrigin, QAngle vecAngles, CBaseEntity *pOwner, float lifetime ); + + // Initialization + void Spawn( void ); + void Precache( void ); + + // Think and Touch + void FlareThink( void ); + void FlareTouch( CBaseEntity *pOther ); + +public: + + CBaseEntity *m_pOwner; + int m_nBounces; // how many times has this flare bounced? + CNetworkVar( float, m_flDuration ); // when will the flare burn out? + CNetworkVar( float, m_flScale ); + float m_flNextDamage; + + bool m_bFading; + CNetworkVar( bool, m_bLight ); + CNetworkVar( bool, m_bSmoke ); +}; + +EXTERN_SEND_TABLE( DT_SignalFlare ); + +#endif // TF_FLARE_H
\ No newline at end of file diff --git a/game/server/tf2/tf_func_construction_yard.cpp b/game/server/tf2/tf_func_construction_yard.cpp new file mode 100644 index 0000000..c2312e2 --- /dev/null +++ b/game/server/tf2/tf_func_construction_yard.cpp @@ -0,0 +1,235 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +#include "cbase.h" +#include "tf_func_construction_yard.h" +#include "tf_team.h" + +//----------------------------------------------------------------------------- +// Purpose: Defines an area from which resources can be collected +//----------------------------------------------------------------------------- +class CFuncConstructionYard : public CBaseEntity +{ + DECLARE_CLASS( CFuncConstructionYard, CBaseEntity ); +public: + CFuncConstructionYard(); + + DECLARE_DATADESC(); + DECLARE_SERVERCLASS(); + + virtual void Spawn( void ); + virtual void UpdateOnRemove( void ); + virtual void Precache( void ); + virtual void Activate( void ); + + // Inputs + void InputSetActive( inputdata_t &inputdata ); + void InputSetInactive( inputdata_t &inputdata ); + void InputToggleActive( inputdata_t &inputdata ); + + void SetActive( bool bActive ); + bool GetActive() const; + + bool IsEmpty( void ); + bool PointIsWithin( const Vector &vecPoint ); + + // need to transmit to players who are in commander mode + int UpdateTransmitState() { return SetTransmitState( FL_EDICT_FULLCHECK); } + int ShouldTransmit( const CCheckTransmitInfo *pInfo ); + +private: + bool m_bActive; +}; + +LINK_ENTITY_TO_CLASS( func_construction_yard, CFuncConstructionYard); + +BEGIN_DATADESC( CFuncConstructionYard ) + + DEFINE_INPUTFUNC( FIELD_VOID, "SetActive", InputSetActive ), + DEFINE_INPUTFUNC( FIELD_VOID, "SetInactive", InputSetInactive ), + DEFINE_INPUTFUNC( FIELD_VOID, "ToggleActive", InputToggleActive ), + +END_DATADESC() + + +IMPLEMENT_SERVERCLASS_ST(CFuncConstructionYard, DT_FuncConstructionYard) +END_SEND_TABLE(); + +PRECACHE_REGISTER( func_construction_yard ); + +// List of construction yards for fast lookup +typedef CHandle<CFuncConstructionYard> YardHandle; +CUtlVector< YardHandle > g_hConstructionYards; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CFuncConstructionYard::CFuncConstructionYard() +{ +} + +//----------------------------------------------------------------------------- +// Purpose: Initializes the resource zone +//----------------------------------------------------------------------------- +void CFuncConstructionYard::Spawn( void ) +{ + SetSolid( SOLID_BSP ); + AddSolidFlags( FSOLID_TRIGGER ); + SetMoveType( MOVETYPE_NONE ); + AddEffects( EF_NODRAW ); + SetModel( STRING( GetModelName() ) ); + m_bActive = false; + + YardHandle hYard; + hYard = this; + g_hConstructionYards.AddToTail( hYard ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CFuncConstructionYard::UpdateOnRemove( void ) +{ + YardHandle hYard; + hYard = this; + g_hConstructionYards.FindAndRemove( hYard ); + + BaseClass::UpdateOnRemove(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CFuncConstructionYard::Precache( void ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: See if we've got a gather point specified +//----------------------------------------------------------------------------- +void CFuncConstructionYard::Activate( void ) +{ + BaseClass::Activate(); + SetActive( true ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CFuncConstructionYard::InputSetActive( inputdata_t &inputdata ) +{ + SetActive( true ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CFuncConstructionYard::InputSetInactive( inputdata_t &inputdata ) +{ + SetActive( false ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CFuncConstructionYard::InputToggleActive( inputdata_t &inputdata ) +{ + if ( m_bActive ) + { + SetActive( false ); + } + else + { + SetActive( true ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CFuncConstructionYard::SetActive( bool bActive ) +{ + m_bActive = bActive; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CFuncConstructionYard::GetActive() const +{ + return m_bActive; +} + + +//----------------------------------------------------------------------------- +// Purpose: Return true if the specified point is within this zone +//----------------------------------------------------------------------------- +bool CFuncConstructionYard::PointIsWithin( const Vector &vecPoint ) +{ + return CollisionProp()->IsPointInBounds( vecPoint ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Transmit this to all players who are in commander mode +//----------------------------------------------------------------------------- +int CFuncConstructionYard::ShouldTransmit( const CCheckTransmitInfo *pInfo ) +{ + // Team rules may tell us that we should + CBaseEntity* pRecipientEntity = CBaseEntity::Instance( pInfo->m_pClientEnt ); + Assert( pRecipientEntity->IsPlayer() ); + + CBasePlayer *pPlayer = (CBasePlayer*)pRecipientEntity; + if ( pPlayer->GetTeam() ) + { + if (pPlayer->GetTeam()->ShouldTransmitToPlayer( pPlayer, this )) + return FL_EDICT_ALWAYS; + } + + return FL_EDICT_DONTSEND; +} + + +//----------------------------------------------------------------------------- +// Does a construction yard (or lack of one) prevent us from building? +//----------------------------------------------------------------------------- +bool PointInConstructionYard( const Vector &vecBuildOrigin ) +{ + // Find out whether we're in a construction yard or not + for ( int i = 0; i < g_hConstructionYards.Count(); i++ ) + { + CFuncConstructionYard *pYard = g_hConstructionYards[i]; + if ( pYard && pYard->PointIsWithin( vecBuildOrigin ) ) + return true; + } + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool ConstructionYardPreventsBuild( CBaseObject *pObject, const Vector &vecBuildOrigin ) +{ + bool bMustBuildInYard = pObject->MustBeBuiltInConstructionYard(); + + // Find out whether we're in a resource zone or not + if( PointInConstructionYard( vecBuildOrigin ) ) + { + if( !pObject->MustNotBeBuiltInConstructionYard() ) + return false; // An object that must be built in a yard is in a yard. + else + return true; // An object that can't be built in a yard is in a yard. + } + + // If we have to be built in a construction yard, we're not in one + if ( bMustBuildInYard ) + return true; + + return false; +} diff --git a/game/server/tf2/tf_func_construction_yard.h b/game/server/tf2/tf_func_construction_yard.h new file mode 100644 index 0000000..1dc6c72 --- /dev/null +++ b/game/server/tf2/tf_func_construction_yard.h @@ -0,0 +1,30 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Place where we can build vehicles +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef TF_FUNC_CONSTRUCTION_YARD_H +#define TF_FUNC_CONSTRUCTION_YARD_H + +#ifdef _WIN32 +#pragma once +#endif + +class CBaseObject; +class Vector; + +//----------------------------------------------------------------------------- +// Is a given point contained within any construction yard? +//----------------------------------------------------------------------------- +bool PointInConstructionYard( const Vector &vecBuildOrigin ); + +//----------------------------------------------------------------------------- +// Does a construction yard (or lack of one) prevent us from building? +//----------------------------------------------------------------------------- + +bool ConstructionYardPreventsBuild( CBaseObject *pObject, const Vector &vecBuildOrigin ); + + +#endif // TF_FUNC_CONSTRUCTION_YARD_H diff --git a/game/server/tf2/tf_func_mass_teleport.cpp b/game/server/tf2/tf_func_mass_teleport.cpp new file mode 100644 index 0000000..2571841 --- /dev/null +++ b/game/server/tf2/tf_func_mass_teleport.cpp @@ -0,0 +1,368 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +#include "cbase.h" +#include "tf_func_mass_teleport.h" +#include "tf_team.h" +#include "basetfvehicle.h" +#include "team_spawnpoint.h" +#include "NDebugOverlay.h" +#include "tf_vehicle_teleport_station.h" +#include "tf_gamerules.h" + +LINK_ENTITY_TO_CLASS( func_mass_teleport, CFuncMassTeleport); + +BEGIN_DATADESC( CFuncMassTeleport ) + + // Data + DEFINE_FIELD(m_hTargetEntity, FIELD_EHANDLE), + + // Keys + DEFINE_KEYFIELD_NOT_SAVED( m_bMCV, FIELD_BOOLEAN, "MCV" ), + + // Inputs + DEFINE_INPUTFUNC( FIELD_VOID, "DoTeleport", InputDoTeleport ), + + // Outputs + DEFINE_OUTPUT( m_OnTeleport, "OnTeleport" ), + DEFINE_OUTPUT( m_OnActive, "OnActive" ), + DEFINE_OUTPUT( m_OnInactive, "OnInactive" ), + +END_DATADESC() + +PRECACHE_REGISTER( func_mass_teleport ); + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CFuncMassTeleport::CFuncMassTeleport() +{ +} + +//----------------------------------------------------------------------------- +// Purpose: Initializes the resource zone +//----------------------------------------------------------------------------- +void CFuncMassTeleport::Spawn( void ) +{ + SetSolid( SOLID_BSP ); + AddSolidFlags( FSOLID_TRIGGER ); + SetMoveType( MOVETYPE_NONE ); + AddEffects( EF_NODRAW ); + SetModel( STRING( GetModelName() ) ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CFuncMassTeleport::Precache( void ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CFuncMassTeleport::Activate( void ) +{ + m_hTargetEntity = GetNextTarget(); + BaseClass::Activate(); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CFuncMassTeleport::MassTeleport( + CTFTeam *pTeam, + Vector vSourceMins, + Vector vSourceMaxs, + Vector vDest, + bool bSwap, + bool bPlayersOnly, + EHANDLE hMCV, + bool bTelefragDestination ) +{ + bSwap = false; + + Vector vSource = vSourceMins + ( ( vSourceMaxs - vSourceMins ) / 2.f ); + vSource.z = vSourceMins.z; +/* + if( bTelefragDestination ) + { + Vector vDestMins = vDest - ( ( vSourceMaxs - vSourceMins ) / 2.f ); + Vector vDestMaxs = vDest + ( ( vSourceMaxs - vSourceMins ) / 2.f ); + MassTelefrag(vDestMins,vDestMaxs); + } +*/ + CBaseEntity *pList[1024]; + int OriginalSolidFlags[1024]; + + int count = UTIL_EntitiesInBox( pList, 1024, vSourceMins, vSourceMaxs, FL_CLIENT|FL_NPC|FL_OBJECT ); + + for( int i = 0; i < count; i++ ) + { + CBaseEntity* pEnt = pList[i]; + + if ( !pEnt ) + continue; + + if ( bPlayersOnly ) + { + CBaseTFPlayer *pTestPlayer = dynamic_cast< CBaseTFPlayer* >( pEnt ); + if ( pTestPlayer ) + { + // If it's players only, then only teleport players attached to the specified MCV. + if ( pTestPlayer->GetSelectedMCV() != hMCV.Get() ) + { + pList[i] = NULL; + continue; + } + } + else + { + pList[i] = NULL; + continue; + } + } + + + if( pEnt->GetSolidFlags() & FSOLID_NOT_SOLID ) + { + pList[i] = NULL; + continue; + } + + // Only teleport team members + if ( pTeam && ((CTFTeam*)pEnt->GetTeam()) != pTeam ) + { + pList[i] = NULL; + continue; + } + + // Only teleport items at the root of hierarchies. + if( pEnt->GetMoveParent() ) + { + pList[i] = NULL; + continue; + } + + CBaseObject* pObj = dynamic_cast<CBaseObject*>(pEnt); + + // NJS: dont teleport map placed objects. + if( pObj ) + { + if ( pObj->WasMapPlaced() ) + { + pList[i] = NULL; + continue; + } + } + + OriginalSolidFlags[i] = pEnt->GetSolidFlags(); + pEnt->AddSolidFlags( FSOLID_NOT_SOLID); + + Vector vEntOrigin = pEnt->GetAbsOrigin(); + Vector vDelta = vEntOrigin - vSource; + Vector vEntTarget = vDest + vDelta + Vector(0,0,3); // Teleport them a little above the ground, to allow for the suspension drop. + QAngle aEntAngles= pEnt->GetAbsAngles(); + Vector vEntVelocity; + pEnt->GetVelocity(&vEntVelocity); + + // If it's a player, trace down from the top of the box to find a safe place + if ( bPlayersOnly && pEnt->IsPlayer() ) + { + // Start the target up the top of the dest box + Vector vecTop = vEntTarget; + vecTop.z = (vDest.z + (vSourceMaxs.z - vSourceMins.z)); + // Trace a hull down + trace_t tr; + UTIL_TraceEntity( pEnt, vecTop, vEntTarget, MASK_SOLID, NULL, COLLISION_GROUP_PLAYER_MOVEMENT, &tr ); + vEntTarget = tr.endpos + Vector(0,0,3); + } + + /* + if( ! bSwap ) + { + Vector origin(0,0,0); + if ( !EntityPlacementTest( pEnt, vEntTarget, origin, true ) ) + { + UTIL_Remove( pEnt ); + return; + } + + vEntTarget = origin; + } + */ + + pEnt->Teleport( &vEntTarget, &aEntAngles, &vEntVelocity ); + } + + // If I am to swap source and destination, do it while the current entites from this teleport are incorperal. + if( bSwap ) + { + MassTeleport( pTeam, vSourceMins - vSource + vDest, vSourceMaxs - vSource + vDest, vSource, false, bPlayersOnly, hMCV, false ); + } + + for( i = 0; i < count; i++ ) + { + CBaseEntity* pEnt = pList[i]; + + if( !pEnt ) + continue; + + pEnt->SetSolidFlags(OriginalSolidFlags[i]); + } +} + +void CFuncMassTeleport::MassTelefrag( Vector vMins, Vector vMaxs ) +{ + CBaseEntity *pList[4096]; + + int count = UTIL_EntitiesInBox( pList, 4096, vMins, vMaxs, FL_CLIENT|FL_NPC|FL_OBJECT ); + + for( int i = 0; i < count; i++ ) + { + CBaseEntity* pEnt = pList[i]; + + if ( !pEnt ) + continue; + + + if( pEnt->GetSolidFlags() & FSOLID_NOT_SOLID ) + { + pList[i] = NULL; + continue; + } + + // Only teleport items at the root of hierarchies. + if( pEnt->GetMoveParent() ) + { + pList[i] = NULL; + continue; + } + + CBaseObject* pObj = dynamic_cast<CBaseObject*>(pEnt); + + // dont frag map placed objects. + if( !pObj ) + continue; + + if ( pObj->WasMapPlaced() ) + { + pList[i] = NULL; + continue; + } + + // Do a massive amount of damage (DMG_GENERIC so there's no physics force) + CTakeDamageInfo info( this, this, 99999, DMG_GENERIC ); + + pObj->TakeDamage(info); + } + +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CFuncMassTeleport::DoTeleport( + CTFTeam *pTeam, + Vector vSourceMins, + Vector vSourceMaxs, + Vector vDest, + bool bSwap, + bool bPlayersOnly, + EHANDLE hMCV ) +{ + bool bTelefragDestination = hMCV ? false : true; + MassTeleport( pTeam, vSourceMins, vSourceMaxs, vDest, bSwap, bPlayersOnly, hMCV, bTelefragDestination ); + + // Fire our output + m_OnTeleport.FireOutput( this, this ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CFuncMassTeleport::InputDoTeleport( inputdata_t &inputdata ) +{ + // If I have MCVs attached, tell them to teleport. + for ( int i = 0; i < m_MCVs.Count(); i++ ) + { + m_MCVs[i]->DoTeleport(); + } + + // If we have a target entity, teleport to it + if ( m_hTargetEntity ) + { + Vector vecAbsMins, vecAbsMaxs; + CollisionProp()->WorldSpaceAABB( &vecAbsMins, &vecAbsMaxs ); + DoTeleport( ((CTFTeam*)GetTeam()), vecAbsMins, vecAbsMaxs, m_hTargetEntity->GetAbsOrigin(), true, false, EHANDLE() ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Return true if the specified point is within this zone +//----------------------------------------------------------------------------- +bool CFuncMassTeleport::PointIsWithin( const Vector &vecPoint ) +{ + return CollisionProp()->IsPointInBounds( vecPoint ); +} + + + +//----------------------------------------------------------------------------- +// Purpose: Transmit this to all players who are in commander mode +//----------------------------------------------------------------------------- +int CFuncMassTeleport::ShouldTransmit( const CCheckTransmitInfo *pInfo ) +{ + // Team rules may tell us that we should + CBaseEntity* pRecipientEntity = CBaseEntity::Instance( pInfo->m_pClientEnt ); + + Assert( pRecipientEntity->IsPlayer() ); + + CBasePlayer *pPlayer = (CBasePlayer*)pRecipientEntity; + if ( pPlayer->GetTeam() ) + { + if (pPlayer->GetTeam()->ShouldTransmitToPlayer( pPlayer, this )) + return FL_EDICT_ALWAYS; + } + + return FL_EDICT_DONTSEND; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CFuncMassTeleport::AddMCV( CBaseEntity *pMCV ) +{ + // Are we going active? + if ( !m_MCVs.Count() ) + { + // Fire our output + m_OnActive.FireOutput( this, this ); + } + + EHANDLE hMCV; + hMCV = pMCV; + m_MCVs.AddToTail( hMCV ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CFuncMassTeleport::RemoveMCV( CBaseEntity *pMCV ) +{ + EHANDLE hMCV; + hMCV = pMCV; + m_MCVs.FindAndRemove( hMCV ); + + // Have we just gone inactive? + if ( !m_MCVs.Count() ) + { + // Fire our output + m_OnInactive.FireOutput( this, this ); + } +}
\ No newline at end of file diff --git a/game/server/tf2/tf_func_mass_teleport.h b/game/server/tf2/tf_func_mass_teleport.h new file mode 100644 index 0000000..c9d178c --- /dev/null +++ b/game/server/tf2/tf_func_mass_teleport.h @@ -0,0 +1,72 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: An trigger that teleports its contents to a target when triggered. +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef TF_FUNC_MASS_TELEPORT_H +#define TF_FUNC_MASS_TELEPORT_H + +#ifdef _WIN32 +#pragma once +#endif + +class CBaseObject; +class Vector; +class CTFTeam; +class CVehicleTeleportStation; + +//----------------------------------------------------------------------------- +// Purpose: Defines an area from which resources can be collected +//----------------------------------------------------------------------------- +class CFuncMassTeleport : public CBaseEntity +{ + DECLARE_CLASS( CFuncMassTeleport, CBaseEntity ); +public: + CFuncMassTeleport(); + + DECLARE_DATADESC(); + + virtual void Spawn( void ); + virtual void Precache( void ); + virtual void Activate(void); + + void DoTeleport( CTFTeam *pTeam, Vector vSourceMins, Vector vSourceMaxs, Vector vDest, bool bSwap, bool bPlayersOnly, EHANDLE hMCV ); + + // Inputs + void InputDoTeleport( inputdata_t &inputdata ); + + bool PointIsWithin( const Vector &vecPoint ); + + // need to transmit to players who are in commander mode + int UpdateTransmitState() { return SetTransmitState( FL_EDICT_FULLCHECK); } + int ShouldTransmit( const CCheckTransmitInfo *pInfo ); + + // Return true if this teleport's for use by MCVs in the field + bool IsMCVTeleport( void ) { return m_bMCV; } + void AddMCV( CBaseEntity *pMCV ); + void RemoveMCV( CBaseEntity *pMCV ); + +private: + + void MassTeleport( CTFTeam *pTeam, Vector vSourceMins, Vector vSourceMaxs, Vector vDest, bool bSwap, bool bPlayersOnly, EHANDLE hMCV, bool bTelefragDestination ); + void MassTelefrag( Vector vMins, Vector vMaxs ); + + + // Entity to teleport to. + EHANDLE m_hTargetEntity; + + // If true, this teleport's designed to teleport players to MCVs in the field + bool m_bMCV; + typedef CHandle<CVehicleTeleportStation> hMCVHandle; + CUtlVector<hMCVHandle> m_MCVs; + + // Outputs + COutputEvent m_OnTeleport; + COutputEvent m_OnActive; + COutputEvent m_OnInactive; +}; + + +#endif // TF_FUNC_MASS_TELEPORT_H diff --git a/game/server/tf2/tf_func_no_build.cpp b/game/server/tf2/tf_func_no_build.cpp new file mode 100644 index 0000000..db6f8b2 --- /dev/null +++ b/game/server/tf2/tf_func_no_build.cpp @@ -0,0 +1,225 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +#include "cbase.h" +#include "tf_func_no_build.h" +#include "tf_team.h" +#include "NDebugOverlay.h" + +//----------------------------------------------------------------------------- +// Purpose: Defines an area from which resources can be collected +//----------------------------------------------------------------------------- +class CFuncNoBuild : public CBaseEntity +{ + DECLARE_CLASS( CFuncNoBuild, CBaseEntity ); +public: + CFuncNoBuild(); + + DECLARE_DATADESC(); + + virtual void Spawn( void ); + virtual void Precache( void ); + virtual void Activate( void ); + + // Inputs + void InputSetActive( inputdata_t &inputdata ); + void InputSetInactive( inputdata_t &inputdata ); + void InputToggleActive( inputdata_t &inputdata ); + + void SetActive( bool bActive ); + bool GetActive() const; + + bool IsEmpty( void ); + bool PointIsWithin( const Vector &vecPoint ); + + bool PreventsBuildOf( int iObjectType ); + + // need to transmit to players who are in commander mode + int UpdateTransmitState() { return SetTransmitState( FL_EDICT_FULLCHECK ); } + int ShouldTransmit( const CCheckTransmitInfo *pInfo ); + +private: + bool m_bActive; + bool m_bOnlyPreventTowers; +}; + +LINK_ENTITY_TO_CLASS( func_no_build, CFuncNoBuild); + +BEGIN_DATADESC( CFuncNoBuild ) + + // keys + DEFINE_KEYFIELD_NOT_SAVED( m_bOnlyPreventTowers, FIELD_BOOLEAN, "OnlyPreventTowers" ), + + // inputs + DEFINE_INPUTFUNC( FIELD_VOID, "SetActive", InputSetActive ), + DEFINE_INPUTFUNC( FIELD_VOID, "SetInactive", InputSetInactive ), + DEFINE_INPUTFUNC( FIELD_VOID, "ToggleActive", InputToggleActive ), + +END_DATADESC() + + +PRECACHE_REGISTER( func_no_build ); + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CFuncNoBuild::CFuncNoBuild() +{ +} + +//----------------------------------------------------------------------------- +// Purpose: Initializes the resource zone +//----------------------------------------------------------------------------- +void CFuncNoBuild::Spawn( void ) +{ + SetSolid( SOLID_BSP ); + AddSolidFlags( FSOLID_TRIGGER ); + SetMoveType( MOVETYPE_NONE ); + AddEffects( EF_NODRAW ); + SetModel( STRING( GetModelName() ) ); + m_bActive = true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CFuncNoBuild::Precache( void ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: See if we've got a gather point specified +//----------------------------------------------------------------------------- +void CFuncNoBuild::Activate( void ) +{ + BaseClass::Activate(); + SetActive( true ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CFuncNoBuild::InputSetActive( inputdata_t &inputdata ) +{ + SetActive( true ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CFuncNoBuild::InputSetInactive( inputdata_t &inputdata ) +{ + SetActive( false ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CFuncNoBuild::InputToggleActive( inputdata_t &inputdata ) +{ + if ( m_bActive ) + { + SetActive( false ); + } + else + { + SetActive( true ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CFuncNoBuild::SetActive( bool bActive ) +{ + m_bActive = bActive; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CFuncNoBuild::GetActive() const +{ + return m_bActive; +} + + +//----------------------------------------------------------------------------- +// Purpose: Return true if the specified point is within this zone +//----------------------------------------------------------------------------- +bool CFuncNoBuild::PointIsWithin( const Vector &vecPoint ) +{ + return CollisionProp()->IsPointInBounds( vecPoint ); +} + +//----------------------------------------------------------------------------- +// Purpose: Return true if this zone prevents building of the specified type +//----------------------------------------------------------------------------- +bool CFuncNoBuild::PreventsBuildOf( int iObjectType ) +{ + if ( m_bOnlyPreventTowers ) + return ( iObjectType == OBJ_TOWER ); + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: Transmit this to all players who are in commander mode +//----------------------------------------------------------------------------- +int CFuncNoBuild::ShouldTransmit( const CCheckTransmitInfo *pInfo ) +{ + // Team rules may tell us that we should + CBaseEntity* pRecipientEntity = CBaseEntity::Instance( pInfo->m_pClientEnt ); + Assert( pRecipientEntity->IsPlayer() ); + + CBasePlayer *pPlayer = (CBasePlayer*)pRecipientEntity; + if ( pPlayer->GetTeam() ) + { + if (pPlayer->GetTeam()->ShouldTransmitToPlayer( pPlayer, this )) + return FL_EDICT_ALWAYS; + } + + return FL_EDICT_DONTSEND; +} + + +//----------------------------------------------------------------------------- +// Purpose: Does a nobuild zone prevent us from building? +//----------------------------------------------------------------------------- +bool PointInNoBuild( CBaseObject *pObject, const Vector &vecBuildOrigin ) +{ + // Find out whether we're in a resource zone or not + CBaseEntity *pEntity = NULL; + while ((pEntity = gEntList.FindEntityByClassname( pEntity, "func_no_build" )) != NULL) + { + CFuncNoBuild *pNoBuild = (CFuncNoBuild *)pEntity; + + // Are we within this no build? + if ( pNoBuild->GetActive() && pNoBuild->PointIsWithin( vecBuildOrigin ) ) + { + if ( pNoBuild->PreventsBuildOf( pObject->GetType() ) ) + return true; // prevent building. + } + } + + return false; // Building should be ok. +} + +//----------------------------------------------------------------------------- +// Purpose: Return true if a nobuild zone prevents building this object at the specified origin +//----------------------------------------------------------------------------- +bool NoBuildPreventsBuild( CBaseObject *pObject, const Vector &vecBuildOrigin ) +{ + // Find out whether we're in a no build zone or not + if ( PointInNoBuild( pObject, vecBuildOrigin ) ) + { + return true; + } + + return false; +} diff --git a/game/server/tf2/tf_func_no_build.h b/game/server/tf2/tf_func_no_build.h new file mode 100644 index 0000000..071e0ca --- /dev/null +++ b/game/server/tf2/tf_func_no_build.h @@ -0,0 +1,30 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Place where we can't build things. +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef TF_FUNC_NO_BUILD_H +#define TF_FUNC_NO_BUILD_H + +#ifdef _WIN32 +#pragma once +#endif + +class CBaseObject; +class Vector; + +//----------------------------------------------------------------------------- +// Is a given point contained within any construction yard? +//----------------------------------------------------------------------------- +bool PointInNoBuild( const Vector &vecBuildOrigin ); + +//----------------------------------------------------------------------------- +// Does a construction yard (or lack of one) prevent us from building? +//----------------------------------------------------------------------------- + +bool NoBuildPreventsBuild( CBaseObject *pObject, const Vector &vecBuildOrigin ); + + +#endif // TF_FUNC_NO_BUILD_H diff --git a/game/server/tf2/tf_func_resource.cpp b/game/server/tf2/tf_func_resource.cpp new file mode 100644 index 0000000..cfb0d74 --- /dev/null +++ b/game/server/tf2/tf_func_resource.cpp @@ -0,0 +1,683 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +#include "cbase.h" +#include "EntityOutput.h" +#include "EntityList.h" +#include "tf_player.h" +#include "tf_func_resource.h" +#include "tf_team.h" +#include "tf_basecombatweapon.h" +#include "gamerules.h" +#include "ammodef.h" +#include "tf_obj.h" +#include "resource_chunk.h" +#include "vstdlib/random.h" +#include "engine/IEngineSound.h" +#include "team_messages.h" +#include "tf_stats.h" + +LINK_ENTITY_TO_CLASS( trigger_resourcezone, CResourceZone); + +BEGIN_DATADESC( CResourceZone ) + + // keys + DEFINE_KEYFIELD_NOT_SAVED( m_nResourcesLeft, FIELD_INTEGER, "ResourceAmount" ), + DEFINE_KEYFIELD_NOT_SAVED( m_iMaxChunks, FIELD_INTEGER, "ResourceChunks" ), + DEFINE_KEYFIELD_NOT_SAVED( m_flResourceRate, FIELD_FLOAT, "ResourceRate" ), + DEFINE_KEYFIELD_NOT_SAVED( m_flChunkValueMin, FIELD_FLOAT, "ChunkValueMin" ), + DEFINE_KEYFIELD_NOT_SAVED( m_flChunkValueMax, FIELD_FLOAT, "ChunkValueMax" ), + + // inputs + DEFINE_INPUTFUNC( FIELD_INTEGER, "SetAmount", InputSetAmount ), + DEFINE_INPUTFUNC( FIELD_VOID, "ResetAmount", InputResetAmount ), + DEFINE_INPUTFUNC( FIELD_VOID, "SetActive", InputSetActive ), + DEFINE_INPUTFUNC( FIELD_VOID, "SetInactive", InputSetInactive ), + DEFINE_INPUTFUNC( FIELD_VOID, "ToggleActive", InputToggleActive ), + + // outputs + DEFINE_OUTPUT( m_OnEmpty, "OnEmpty" ), + +END_DATADESC() + + +IMPLEMENT_SERVERCLASS_ST(CResourceZone, DT_ResourceZone) + SendPropFloat( SENDINFO( m_flClientResources ), 8, SPROP_UNSIGNED, 0.0f, 1.0f ), + SendPropInt( SENDINFO( m_nResourcesLeft ), 20, SPROP_UNSIGNED ), +END_SEND_TABLE(); + +PRECACHE_REGISTER( trigger_resourcezone ); + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CResourceZone::CResourceZone() +{ +#ifdef _DEBUG + m_vecGatherPoint.Init(); + m_angGatherPoint.Init(); +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: Initializes the resource zone +//----------------------------------------------------------------------------- +void CResourceZone::Spawn( void ) +{ + SetSolid( SOLID_BSP ); + AddSolidFlags( FSOLID_TRIGGER ); + SetMoveType( MOVETYPE_NONE ); + AddEffects( EF_NODRAW ); + SetModel( STRING( GetModelName() ) ); + + if ( !m_nResourcesLeft ) + { + m_nResourcesLeft = 10000; + } + + m_nMaxResources = m_nResourcesLeft; + m_flClientResources = 1; + + SetActive( false ); + m_vecGatherPoint = WorldSpaceCenter(); + m_angGatherPoint = vec3_angle; + m_iTeamGathering = -1; + m_aSpawners.Purge(); + m_hResourcePump = NULL; + + if ( !m_iMaxChunks ) + m_iMaxChunks = 5; + if ( !m_flChunkValueMin ) + m_flChunkValueMin = 20; + if ( !m_flChunkValueMax ) + m_flChunkValueMax = 60; + + m_flBaseResourceRate = m_flResourceRate; + + m_flRespawnTimeModifier = 1.0f; + + m_flTestTime = 0; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CResourceZone::Precache( void ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: See if we've got a gather point specified +//----------------------------------------------------------------------------- +void CResourceZone::Activate( void ) +{ + BaseClass::Activate(); + + if (m_target != NULL_STRING) + { + CBaseEntity *pEnt = gEntList.FindEntityByName( NULL, m_target ); + if ( pEnt ) + { + m_vecGatherPoint = pEnt->GetLocalOrigin(); + m_angGatherPoint = pEnt->GetLocalAngles(); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CResourceZone::InputSetAmount( inputdata_t &inputdata ) +{ + m_nMaxResources = m_nResourcesLeft = inputdata.value.Int(); + RecomputeClientResources(); + + // We may have just been reactivated + if ( m_nResourcesLeft ) + { + SetActive( true ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CResourceZone::InputResetAmount( inputdata_t &inputdata ) +{ + m_nResourcesLeft = m_nMaxResources; + m_flClientResources = 1; + + // We may have just been reactivated + if ( m_nResourcesLeft ) + { + SetActive( true ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CResourceZone::InputSetActive( inputdata_t &inputdata ) +{ + SetActive( true ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CResourceZone::InputSetInactive( inputdata_t &inputdata ) +{ + SetActive( false ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CResourceZone::InputToggleActive( inputdata_t &inputdata ) +{ + if ( GetActive() ) + { + SetActive( false ); + } + else + { + SetActive( true ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CResourceZone::SetActive( bool bActive ) +{ + // Going active? + if ( !m_bActive && bActive ) + { + // Start our ambient sound + //EmitAmbientSound( this, Center(), "ResourceZone.AmbientActiveSound" ); + } + else if ( m_bActive && !bActive ) + { + // Going inactive + + // Stop our sound loop + //StopSound( "ResourceZone.AmbientActiveSound" ); + } + + m_bActive = bActive; + + // Tell all my spawners + for ( int i = 0; i < m_aSpawners.Size(); i++ ) + { + m_aSpawners[i]->SetActive( bActive ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CResourceZone::GetActive() const +{ + return m_bActive; +} + +//----------------------------------------------------------------------------- +// Zone increasing.... +//----------------------------------------------------------------------------- +void CResourceZone::AddZoneIncreaser( float rate ) +{ + Assert( rate != 0.0f ); + + m_flRespawnTimeModifier *= rate; + m_flResourceRate = m_flBaseResourceRate / m_flRespawnTimeModifier; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CResourceZone::RemoveZoneIncreaser( float rate ) +{ + Assert( rate != 0.0f ); + + m_flRespawnTimeModifier /= rate; + m_flResourceRate = m_flBaseResourceRate / m_flRespawnTimeModifier; +} + +//----------------------------------------------------------------------------- +// Purpose: Removes resources from the zone, and returns true if it's now empty +//----------------------------------------------------------------------------- +bool CResourceZone::RemoveResources( int nResourcesRemoved ) +{ + if ( IsEmpty() ) + return true; + + m_nResourcesLeft = MAX(0, m_nResourcesLeft - nResourcesRemoved); + RecomputeClientResources(); + + // If I'm out of resources, destroy my resource spawners + if ( IsEmpty() ) + { + // Tell all existing chunks to stay forever + int i; + for ( i = 0; i < m_aChunks.Size(); i++ ) + { + // Clear them removal think + m_aChunks[i]->SetThink( NULL ); + } + + SetActive( false ); + + // Tell teams about it + for ( i = 0; i < GetNumberOfTeams(); i++ ) + { + CTFTeam *pTeam = GetGlobalTFTeam( i ); + pTeam->PostMessage( TEAMMSG_RESOURCE_ZONE_EMPTIED ); + } + + // Fire my output + m_OnEmpty.FireOutput( NULL,this ); + return true; + } + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: Return true if this zone is empty +//----------------------------------------------------------------------------- +bool CResourceZone::IsEmpty( void ) +{ + // Inactive zones pretend to be empty, so nothing tries to do anything with them + if ( !GetActive() ) + return true; + + return (m_nResourcesLeft <= 0); +} + +//----------------------------------------------------------------------------- +// Purpose: Return true if the specified point is within this zone +//----------------------------------------------------------------------------- +bool CResourceZone::PointIsWithin( const Vector &vecPoint ) +{ + return CollisionProp()->IsPointInBounds( vecPoint ); +} + +//----------------------------------------------------------------------------- +// Purpose: Resource zones have 1 build point +//----------------------------------------------------------------------------- +int CResourceZone::GetNumBuildPoints( void ) const +{ + return 1; +} + +//----------------------------------------------------------------------------- +// Purpose: Return true if the specified object type can be built on this point +//----------------------------------------------------------------------------- +bool CResourceZone::CanBuildObjectOnBuildPoint( int iPoint, int iObjectType ) +{ + ASSERT( iPoint <= GetNumBuildPoints() ); + + // Don't allow more than one pump + if ( m_hResourcePump.Get() ) + return false; + + // Only pumps can be built on zones + return ( iObjectType == OBJ_RESOURCEPUMP ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CResourceZone::GetBuildPoint( int iPoint, Vector &vecOrigin, QAngle &vecAngles ) +{ + ASSERT( iPoint <= GetNumBuildPoints() ); + + // If we have a gather point, return it + if ( m_vecGatherPoint != WorldSpaceCenter() ) + { + vecOrigin = m_vecGatherPoint; + } + else if ( m_aSpawners.Size() ) + { + // Return the first resource spawner + vecOrigin = m_aSpawners[0]->GetAbsOrigin(); + } + else + { + vecOrigin = GetAbsOrigin(); + } + + vecAngles = QAngle(0,0,0); + return true; +} + +int CResourceZone::GetBuildPointAttachmentIndex( int iPoint ) const +{ + return 0; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CResourceZone::SetObjectOnBuildPoint( int iPoint, CBaseObject *pObject ) +{ + m_hResourcePump = pObject; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int CResourceZone::GetNumObjectsOnMe( void ) +{ + if ( m_hResourcePump.Get() ) + return 1; + + return 0; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CBaseEntity *CResourceZone::GetFirstObjectOnMe( void ) +{ + return m_hResourcePump; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CBaseObject *CResourceZone::GetObjectOfTypeOnMe( int iObjectType ) +{ + if ( GetNumObjectsOnMe() == 1 ) + { + CBaseObject *pObject = dynamic_cast<CBaseObject*>( m_hResourcePump.Get() ); + if ( pObject ) + { + if ( pObject->GetType() == iObjectType ) + return pObject; + } + } + + return NULL; +} + +int CResourceZone::FindObjectOnBuildPoint( CBaseObject *pObject ) +{ + if (m_hResourcePump == pObject) + return 0; + return -1; +} + +void CResourceZone::GetExitPoint( CBaseEntity *pPlayer, int iPoint, Vector *pAbsOrigin, QAngle *pAbsAngles ) +{ + Assert(0); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CResourceZone::RemoveAllObjects( void ) +{ + UTIL_Remove( m_hResourcePump ); +} + +//----------------------------------------------------------------------------- +// Purpose: Get the team that's gathering from this point +//----------------------------------------------------------------------------- +CTFTeam *CResourceZone::GetOwningTeam( void ) +{ + if ( m_iTeamGathering == -1 ) + return NULL; + + return (CTFTeam*)GetGlobalTeam(m_iTeamGathering); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CResourceZone::SetOwningTeam( int iTeamNumber ) +{ + m_iTeamGathering = iTeamNumber; +} + +//----------------------------------------------------------------------------- +// Purpose: Transmit this to all players who are in commander mode +//----------------------------------------------------------------------------- +int CResourceZone::ShouldTransmit( const CCheckTransmitInfo *pInfo ) +{ + // Team rules may tell us that we should + CBaseEntity* pRecipientEntity = CBaseEntity::Instance( pInfo->m_pClientEnt ); + Assert( pRecipientEntity->IsPlayer() ); + + CBasePlayer *pPlayer = (CBasePlayer*)pRecipientEntity; + if ( pPlayer->GetTeam() ) + { + if (pPlayer->GetTeam()->ShouldTransmitToPlayer( pPlayer, this )) + return FL_EDICT_ALWAYS; + } + + return FL_EDICT_DONTSEND; +} + +//----------------------------------------------------------------------------- +// Purpose: Check to see if we should create any more resource chunks +//----------------------------------------------------------------------------- +bool CResourceZone::ShouldSpawnChunk( void ) +{ + // Don't spawn chunks if we're outta resources + if ( IsEmpty() ) + return false; + + // Create a chunk if we're below our max + if ( m_aChunks.Size() >= m_iMaxChunks ) + return false; + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CResourceZone::SpawnChunk( const Vector &vecOrigin ) +{ + // ROBIN: Disabled for now + return; + + TFStats()->IncrementStat( TF_STAT_RESOURCE_CHUNKS_SPAWNED, 1 ); + + // Create a resource chunk and add it to our list + Vector vecVelocity = Vector( random->RandomFloat( -100,100 ), random->RandomFloat( -100,100 ), random->RandomFloat( 300,600 )); + CResourceChunk *pChunk = CResourceChunk::Create( false, vecOrigin, vecVelocity ); + pChunk->m_hZone = this; + + // Add it to our list + m_aChunks.AddToTail( pChunk ); + + // Remove it's value from the zone + RemoveResources( pChunk->GetResourceValue() ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CResourceZone::RecomputeClientResources( ) +{ + m_flClientResources = clamp( (float)m_nResourcesLeft / (float)m_nMaxResources, 0.0f, 1.0f ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CResourceZone::RemoveChunk( CResourceChunk *pChunk, bool bReturn ) +{ + if (bReturn) + { + TFStats()->IncrementStat( TF_STAT_RESOURCE_CHUNKS_RETIRED, 1 ); + } + + m_aChunks.FindAndRemove( pChunk ); + + // If I'm being returned, re-add my value to the resource level of the zone + if ( bReturn ) + { + m_nResourcesLeft += pChunk->GetResourceValue(); + RecomputeClientResources(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CResourceZone::AddSpawner( CResourceSpawner *pSpawner ) +{ + m_aSpawners.AddToTail( pSpawner ); + pSpawner->SetActive( GetActive() ); +} + +//======================================================================================================================== +// RESOURCE CHUNK SPAWNER +//======================================================================================================================== +LINK_ENTITY_TO_CLASS( env_resourcespawner, CResourceSpawner ); +PRECACHE_REGISTER( env_resourcespawner ); + +BEGIN_DATADESC( CResourceSpawner ) + + // functions + DEFINE_FUNCTION( SpawnChunkThink ), + +END_DATADESC() + + +IMPLEMENT_SERVERCLASS_ST(CResourceSpawner, DT_ResourceSpawner) + SendPropInt( SENDINFO( m_bActive ), 1, SPROP_UNSIGNED ), +END_SEND_TABLE(); + +// Resource Spawner Models +char *sResourceSpawnerModel = "models/resources/resource_spawner_B.mdl"; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CResourceSpawner::Spawn( void ) +{ + m_hZone = NULL; + m_bActive = false; + SetModel( sResourceSpawnerModel ); + + // Create the object in the physics system + /* + VPhysicsInitStatic(); + */ + SetMoveType( MOVETYPE_NONE ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CResourceSpawner::Precache( void ) +{ + PrecacheModel( sResourceSpawnerModel ); +} + +//----------------------------------------------------------------------------- +// Purpose: Find my resource point +//----------------------------------------------------------------------------- +void CResourceSpawner::Activate( void ) +{ + if ( m_target != NULL_STRING ) + { + // Find my resource zone + CResourceZone *pZone = (CResourceZone*)gEntList.FindEntityByName( NULL, m_target ); + if ( pZone ) + { + m_hZone = pZone; + SetModel( sResourceSpawnerModel ); + m_hZone->AddSpawner( this ); + return; + } + } + + Warning( "ERROR: Resource Spawner without a target resource zone specified.\n" ); + UTIL_Remove( this ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CResourceSpawner::SetActive( bool bActive ) +{ + // Going active? + if ( !m_bActive && bActive ) + { + // Randomize the thinks a little to reduce network usage ong chunk spawning + SetNextThink( gpGlobals->curtime + m_hZone->GetResourceRate() + random->RandomFloat( 0.0, 1.0 ) ); + SetThink( SpawnChunkThink ); + RemoveEffects( EF_NODRAW ); + } + else if ( m_bActive && !bActive ) + { + // Going inactive + SetThink( NULL ); + AddEffects( EF_NODRAW ); + } + + m_bActive = bActive; +} + +//----------------------------------------------------------------------------- +// Purpose: Spawn a chunk from this spawner +//----------------------------------------------------------------------------- +void CResourceSpawner::SpawnChunkThink( void ) +{ + // Lost our zone? + if ( !m_hZone ) + { + SetActive( false ); + return; + } + + if ( m_hZone->ShouldSpawnChunk() ) + { + // Start spawning events + EntityMessageBegin( this ); + MessageEnd(); + + m_hZone->SpawnChunk( GetAbsOrigin() + Vector(0,0,64) ); + } + + // Randomize the thinks a little to reduce network usage on chunk spawning + SetNextThink( gpGlobals->curtime + m_hZone->GetResourceRate() + random->RandomFloat( 0.0, 1.0 ) ); +} + +//----------------------------------------------------------------------------- +// Purpose: Convert an amount of resources into a number of processed & unprocessed resource chunks +//----------------------------------------------------------------------------- +void ConvertResourceValueToChunks( int iResources, int *iNumProcessed, int *iNumNormal ) +{ + *iNumProcessed = *iNumNormal = 0; + + while ( iResources >= resource_chunk_processed_value.GetFloat() ) + { + iResources -= resource_chunk_processed_value.GetFloat(); + *iNumProcessed += 1; + } + + while ( iResources >= resource_chunk_value.GetFloat() ) + { + iResources -= resource_chunk_value.GetFloat(); + *iNumNormal += 1; + } + + // Round up + if ( iResources ) + { + *iNumNormal++; + } +}
\ No newline at end of file diff --git a/game/server/tf2/tf_func_resource.h b/game/server/tf2/tf_func_resource.h new file mode 100644 index 0000000..c22410a --- /dev/null +++ b/game/server/tf2/tf_func_resource.h @@ -0,0 +1,153 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Resource collection entity +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef TF_FUNC_RESOURCE_H +#define TF_FUNC_RESOURCE_H +#pragma once + +#include "utlvector.h" +#include "props.h" +#include "techtree.h" +#include "entityoutput.h" +#include "ihasbuildpoints.h" + +class CBaseTFPlayer; +class CTFTeam; +class CResourceChunk; +class CResourceSpawner; + +//----------------------------------------------------------------------------- +// Purpose: Defines an area from which resources can be collected +//----------------------------------------------------------------------------- +class CResourceZone : public CBaseEntity, public IHasBuildPoints +{ + DECLARE_CLASS( CResourceZone, CBaseEntity ); +public: + CResourceZone(); + + DECLARE_DATADESC(); + DECLARE_SERVERCLASS(); + + virtual void Spawn( void ); + virtual void Precache( void ); + virtual void Activate( void ); + + // Inputs + void InputSetAmount( inputdata_t &inputdata ); + void InputResetAmount( inputdata_t &inputdata ); + void InputSetActive( inputdata_t &inputdata ); + void InputSetInactive( inputdata_t &inputdata ); + void InputToggleActive( inputdata_t &inputdata ); + + void SetActive( bool bActive ); + bool GetActive() const; + + bool IsEmpty( void ); + bool PointIsWithin( const Vector &vecPoint ); + + // need to transmit to players who are in commander mode + int ShouldTransmit( const CCheckTransmitInfo *pInfo ); + + // Team handling + void SetOwningTeam( int iTeamNumber ); + CTFTeam *GetOwningTeam( void ); + + // Resource handling + bool RemoveResources( int nResourcesRemoved ); + int GetResources( void ) const { return m_nResourcesLeft; } + + // Resource Chunks + bool ShouldSpawnChunk( void ); + void SpawnChunk( const Vector &vecOrigin ); + void RemoveChunk( CResourceChunk *pChunk, bool bReturn ); + + // Resource Spawners + void AddSpawner( CResourceSpawner *pSpawner ); + + // Zone increasing.... + void AddZoneIncreaser( float rate ); + void RemoveZoneIncreaser( float rate ); + + CNetworkVar( float, m_flClientResources ); // Amount sent to clients (0->1 range) + + float GetResourceRate() const { return m_flResourceRate; } + +// IHasBuildPoints +public: + virtual int GetNumBuildPoints( void ) const; + virtual bool CanBuildObjectOnBuildPoint( int iPoint, int iObjectType ); + virtual bool GetBuildPoint( int iPoint, Vector &vecOrigin, QAngle &vecAngles ); + virtual int GetBuildPointAttachmentIndex( int iPoint ) const; + virtual void SetObjectOnBuildPoint( int iPoint, CBaseObject *pObject ); + virtual float GetMaxSnapDistance( int iPoint ) { return 128; } + virtual int GetNumObjectsOnMe( void ); + virtual CBaseEntity *GetFirstObjectOnMe( void ); + virtual CBaseObject *GetObjectOfTypeOnMe( int iObjectType ); + virtual void RemoveAllObjects( void ); + virtual bool ShouldCheckForMovement( void ) { return false; } + virtual int FindObjectOnBuildPoint( CBaseObject *pObject ); + virtual void GetExitPoint( CBaseEntity *pPlayer, int iPoint, Vector *pAbsOrigin, QAngle *pAbsAngles ); + +private: + void RecomputeClientResources( ); + + // Team handling + float m_flTestTime; // Time to next check to see if we've been captured. + + bool m_bActive; + + // Resource handling + int m_iTeamGathering; // Team that's gathering from this resource + Vector m_vecGatherPoint; // Position for the resource collector to be to gather from this point + QAngle m_angGatherPoint; // Angles for the collector to be at on the point + + // Resources + CNetworkVar( int, m_nResourcesLeft ); // Amount of the resource that's left + int m_nMaxResources; // Max resources at this zone + float m_flResourceRate; // Time between each suck from this zone by resource pumps + float m_flBaseResourceRate; + + // Resource chunks in this zone + int m_iMaxChunks; + float m_flRespawnTimeModifier; // speed deltas imposed by zone increasers + float m_flChunkValueMin; + float m_flChunkValueMax; + CUtlVector< CResourceChunk* > m_aChunks; + + COutputEvent m_OnEmpty; + + EHANDLE m_hResourcePump; + + // Resource spawners in this zone + CUtlVector< CResourceSpawner* > m_aSpawners; +}; + +//----------------------------------------------------------------------------- +// Purpose: A resource chunk spawning point +//----------------------------------------------------------------------------- +class CResourceSpawner : public CBaseAnimating +{ + DECLARE_CLASS( CResourceSpawner, CBaseAnimating ); +public: + DECLARE_DATADESC(); + DECLARE_SERVERCLASS(); + + void Spawn( void ); + void Precache( void ); + void Activate( void ); + void SetActive( bool bActive ); + + void SpawnChunkThink( void ); + +public: + CHandle<CResourceZone> m_hZone; + CNetworkVar( bool, m_bActive ); +}; + +void ConvertResourceValueToChunks( int iResources, int *iNumProcessed, int *iNumNormal ); + +#endif // TF_FUNC_RESOURCE_H diff --git a/game/server/tf2/tf_func_weldable_door.cpp b/game/server/tf2/tf_func_weldable_door.cpp new file mode 100644 index 0000000..3e278e4 --- /dev/null +++ b/game/server/tf2/tf_func_weldable_door.cpp @@ -0,0 +1,399 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Func door that's weldable shut +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "beam_shared.h" +#include "tf_player.h" +#include "tf_team.h" +#include "tf_basecombatweapon.h" +#include "tf_func_weldable_door.h" +#include "IEffects.h" + + +LINK_ENTITY_TO_CLASS( func_door_weldable, CWeldableDoor ); + +BEGIN_DATADESC( CWeldableDoor ) + + // keys + DEFINE_KEYFIELD( m_iszWeldPoints, FIELD_STRING, "weldpoints" ), + +END_DATADESC() + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CWeldableDoor::Spawn( void ) +{ + BaseClass::Spawn(); + + m_hWeldingPlayer = NULL; + m_flMaxWeldedPercentage = 0.0; + m_iWeldLeader = WL_UNASSIGNED; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CWeldableDoor::Activate( void ) +{ + BaseClass::Activate(); + + // Find my weld points + if ( !m_iszWeldPoints ) + { + Msg( "func_weldable_door without weldpoints specified.\n" ); + UTIL_Remove( this ); + return; + } + + // Find the weld points. Doors will almost always have multiple weld point pairs. + CWeldPoint *pWeldStartPoint = NULL; + while( (pWeldStartPoint = (CWeldPoint*)gEntList.FindEntityByName( pWeldStartPoint, m_iszWeldPoints ) ) != NULL ) + { + // Does it have an endpoint specified? + if ( !pWeldStartPoint->m_target ) + { + Msg( "func_weldable_door weldpoint '%s' didn't have an endpoint specified.\n", STRING(m_iszWeldPoints) ); + continue; + } + + // Find the endpoint for this startpoint + CWeldPoint *pWeldEndPoint = (CWeldPoint*)gEntList.FindEntityByName( NULL, pWeldStartPoint->m_target ); + if ( pWeldEndPoint ) + { + // Connect the start point to the endpoint + pWeldStartPoint->SetEndPoint( pWeldEndPoint->GetLocalOrigin() ); + + // Add it to the list + m_aWeldPoints.AddToTail( pWeldStartPoint ); + } + else + { + Msg( "func_weldable_door weldpoint couldn't find it's endpoint of '%s'.\n", STRING(pWeldStartPoint->m_target) ); + continue; + } + } + + // Did we find any weldpoint pairs? + if ( m_aWeldPoints.Size() == 0 ) + { + Msg( "func_weldable_door couldn't find any weldpoints for '%s'.\n", STRING(m_iszWeldPoints) ); + UTIL_Remove( this ); + return; + } +} + +//----------------------------------------------------------------------------- +// Purpose: Returns true if someone's allowed to start welding on this door +//----------------------------------------------------------------------------- +bool CWeldableDoor::IsWeldable( CBaseTFPlayer *pWeldee ) +{ + // Can't be welded if I'm open + if ( m_toggle_state != TS_AT_BOTTOM ) + return false; + + // Can't be welded if I'm already being welded by someone else + if ( m_hWeldingPlayer != NULL && (((CBaseEntity*)m_hWeldingPlayer) != pWeldee) ) + return false; + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: Return the weld leader. +// Doors can be made up of multiple door entities, so one of them needs to volunteer to control the welding. +//----------------------------------------------------------------------------- +CWeldableDoor *CWeldableDoor::GetWeldLeader( void ) +{ + // Am I the leader? + if ( m_iWeldLeader == WL_WELD_LEADER ) + return this; + + // If this guy's unassigned, he's volunteering + if ( m_iWeldLeader == WL_UNASSIGNED ) + { + m_iWeldLeader = WL_WELD_LEADER; + + // Tell all other friends they're children + CBaseEntity *pTarget = NULL; + if ( GetEntityName() != NULL_STRING ) + { + while ( (pTarget = gEntList.FindEntityByName( pTarget, GetEntityName() )) != NULL ) + { + if ( pTarget != this ) + { + CWeldableDoor *pWeldableDoor = dynamic_cast< CWeldableDoor * >( pTarget ); + if ( pWeldableDoor ) + { + pWeldableDoor->m_iWeldLeader = WL_WELD_CHILD; + } + } + } + } + + return this; + } + + // We're not the leader. so find him + CBaseEntity *pTarget = NULL; + if ( GetEntityName() != NULL_STRING ) + { + while ( (pTarget = gEntList.FindEntityByName( pTarget, GetEntityName() )) != NULL ) + { + if ( pTarget != this ) + { + CWeldableDoor *pWeldableDoor = dynamic_cast< CWeldableDoor * >( pTarget ); + if ( pWeldableDoor && pWeldableDoor->m_iWeldLeader == WL_WELD_LEADER ) + return pWeldableDoor; + } + } + } + + return NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: The Player's going to start welding this door. +//----------------------------------------------------------------------------- +void CWeldableDoor::StartWelding( CBaseTFPlayer *pWeldee ) +{ + m_hWeldingPlayer = pWeldee; + + // If this is the weld leader, tell all the door pieces that the player's welding them + if ( m_iWeldLeader == WL_WELD_LEADER ) + { + CBaseEntity *pTarget = NULL; + if ( GetEntityName() != NULL_STRING ) + { + while ( (pTarget = gEntList.FindEntityByName( pTarget, GetEntityName() )) != NULL ) + { + if ( pTarget != this ) + { + CWeldableDoor *pWeldableDoor = dynamic_cast< CWeldableDoor * >( pTarget ); + if ( pWeldableDoor ) + { + pWeldableDoor->StartWelding( pWeldee ); + } + } + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: The player's stopped welding this door +//----------------------------------------------------------------------------- +void CWeldableDoor::StopWelding( void ) +{ + m_hWeldingPlayer = NULL; + + // If this is the weld leader, tell all the door pieces that the player's stopped welding them + if ( m_iWeldLeader == WL_WELD_LEADER ) + { + CBaseEntity *pTarget = NULL; + if ( GetEntityName() != NULL_STRING ) + { + while ( (pTarget = gEntList.FindEntityByName( pTarget, GetEntityName() )) != NULL ) + { + if ( pTarget != this ) + { + CWeldableDoor *pWeldableDoor = dynamic_cast< CWeldableDoor * >( pTarget ); + if ( pWeldableDoor ) + { + pWeldableDoor->StopWelding(); + } + } + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Return the amount the door's been welded +//----------------------------------------------------------------------------- +float CWeldableDoor::GetWeldPercentage( void ) +{ + return m_flWeldedPercentage; +} + +//----------------------------------------------------------------------------- +// Purpose: Update the amount the door's been welded +//----------------------------------------------------------------------------- +void CWeldableDoor::UpdateWeld( bool bCutting, float flWeldPercentage ) +{ + if ( m_flMaxWeldedPercentage < flWeldPercentage ) + m_flMaxWeldedPercentage = flWeldPercentage; + m_flWeldedPercentage = flWeldPercentage; + + // Did we cut away the entire weld? + if ( m_flWeldedPercentage <= 0.0 ) + { + // Clear welded + m_flMaxWeldedPercentage = 0.0; + + if ( m_bLocked ) + { + // Unlock all the pieces of this door + m_bLocked = false; + + CBaseEntity *pTarget = NULL; + if ( GetEntityName() != NULL_STRING ) + { + while ( (pTarget = gEntList.FindEntityByName( pTarget, GetEntityName() )) != NULL ) + { + if ( pTarget != this ) + { + CWeldableDoor *pWeldableDoor = dynamic_cast< CWeldableDoor * >( pTarget ); + if ( pWeldableDoor ) + { + pWeldableDoor->Unlock(); + } + } + } + } + } + } + else + { + if ( !m_bLocked ) + { + // Lock all the pieces of this door + m_bLocked = true; + + CBaseEntity *pTarget = NULL; + if ( GetEntityName() != NULL_STRING ) + { + while ( (pTarget = gEntList.FindEntityByName( pTarget, GetEntityName() )) != NULL ) + { + if ( pTarget != this ) + { + CWeldableDoor *pWeldableDoor = dynamic_cast< CWeldableDoor * >( pTarget ); + if ( pWeldableDoor ) + { + pWeldableDoor->Lock(); + } + } + } + } + } + } + + // Update the beams + for ( int i = 0; i < m_aWeldPoints.Size(); i++ ) + { + // First time we've updated? + if ( m_aWeldBeams.Size() <= i ) + { + CBeam *pBeam = CBeam::BeamCreate( "sprites/physbeam.vmt", 4.0 ); + pBeam->SetColor( 128, 128, 128 ); + pBeam->SetBrightness( 128 ); + pBeam->PointsInit( m_aWeldPoints[i]->GetStartPoint(), m_aWeldPoints[i]->GetEndPoint() ); + m_aWeldBeams.AddToTail( pBeam ); + } + if ( m_aCutBeams.Size() <= i ) + { + CBeam *pBeam = CBeam::BeamCreate( "sprites/physbeam.vmt", 4.0 ); + pBeam->SetColor( 255, 255, 255 ); + pBeam->SetBrightness( 255 ); + pBeam->PointsInit( m_aWeldPoints[i]->GetStartPoint(), m_aWeldPoints[i]->GetEndPoint() ); + m_aCutBeams.AddToTail( pBeam ); + } + + // Figure out how far we've welded + Vector vecLine = ( m_aWeldPoints[i]->GetEndPoint() - m_aWeldPoints[i]->GetStartPoint()); + float flLength = vecLine.Length(); + VectorNormalize(vecLine); + vecLine = vecLine * (flLength * flWeldPercentage); + Vector vecWeldPoint = m_aWeldPoints[i]->GetStartPoint() + vecLine; + + // Update the beams + m_aWeldBeams[i]->SetStartPos( m_aWeldPoints[i]->GetStartPoint() ); + m_aWeldBeams[i]->SetEndPos( vecWeldPoint ); + m_aWeldBeams[i]->RelinkBeam(); + if ( flWeldPercentage <= m_flMaxWeldedPercentage ) + { + // Get the cut point + VectorNormalize(vecLine); + vecLine = vecLine * (flLength * m_flMaxWeldedPercentage); + Vector vecCutPoint = m_aWeldPoints[i]->GetStartPoint() + vecLine; + + m_aCutBeams[i]->SetEndPos( vecWeldPoint ); + m_aCutBeams[i]->SetStartPos( vecCutPoint ); + m_aCutBeams[i]->RelinkBeam(); + } + + // Some sparks + if ( random->RandomInt(0,2) == 0 ) + { + g_pEffects->Sparks( vecWeldPoint ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Find a weld point to watch for this player +//----------------------------------------------------------------------------- +Vector CWeldableDoor::GetPlayerWeldPoint( void ) +{ + CBaseTFPlayer *pPlayer = (CBaseTFPlayer *)(CBaseEntity*)m_hWeldingPlayer; + Vector vecSrc = pPlayer->Weapon_ShootPosition( ); + trace_t tr; + + for ( int i = 0; i < m_aWeldPoints.Size(); i++ ) + { + // Figure out where the weldpoint is + Vector vecLine = ( m_aWeldPoints[i]->GetEndPoint() - m_aWeldPoints[i]->GetStartPoint()); + float flLength = vecLine.Length(); + VectorNormalize(vecLine); + vecLine = vecLine * (flLength * m_flWeldedPercentage); + Vector vecWeldPoint = m_aWeldPoints[i]->GetStartPoint() + vecLine; + + // Is the weld point visible to our player? + UTIL_TraceLine( vecSrc, vecWeldPoint, MASK_SOLID, pPlayer, TFCOLLISION_GROUP_WEAPON, &tr ); + if ( tr.fraction == 1.0 ) + return vecWeldPoint; + } + + return vec3_origin; +} + + +//============================================================================================================ +// WELD POINTS +//============================================================================================================ + +LINK_ENTITY_TO_CLASS( info_weldpoint, CWeldPoint ); + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CWeldPoint::Spawn( void ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CWeldPoint::SetEndPoint( const Vector &vecEndPoint ) +{ + m_vecEndPoint = vecEndPoint; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +const Vector &CWeldPoint::GetStartPoint( void ) const +{ + return GetLocalOrigin(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +const Vector &CWeldPoint::GetEndPoint( void ) const +{ + return m_vecEndPoint; +} diff --git a/game/server/tf2/tf_func_weldable_door.h b/game/server/tf2/tf_func_weldable_door.h new file mode 100644 index 0000000..b887ba7 --- /dev/null +++ b/game/server/tf2/tf_func_weldable_door.h @@ -0,0 +1,81 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef TF_FUNC_WELDABLE_DOOR_H +#define TF_FUNC_WELDABLE_DOOR_H +#ifdef _WIN32 +#pragma once +#endif + +#include "doors.h" + +class CBeam; + + +//----------------------------------------------------------------------------- +// Purpose: Weld Point for a weldable door +//----------------------------------------------------------------------------- +class CWeldPoint : public CPointEntity +{ + DECLARE_CLASS( CWeldPoint, CPointEntity ); +public: + void Spawn( void ); + void SetEndPoint( const Vector &vecEndPoint ); + const Vector &GetStartPoint( void ) const; + const Vector &GetEndPoint( void ) const; + +private: + Vector m_vecEndPoint; +}; + +// Weld Leader +enum +{ + WL_UNASSIGNED, + WL_WELD_LEADER, + WL_WELD_CHILD, +}; + +//----------------------------------------------------------------------------- +// Purpose: A weldable door +//----------------------------------------------------------------------------- +class CWeldableDoor : public CBaseDoor +{ +public: + DECLARE_CLASS( CWeldableDoor, CBaseDoor ); + + DECLARE_DATADESC(); + + virtual void Spawn( void ); + virtual void Activate( void ); + + // Welding + virtual bool IsWeldable( CBaseTFPlayer *pWeldee ); + virtual void StartWelding( CBaseTFPlayer *pWeldee ); + virtual void StopWelding( void ); + virtual float GetWeldPercentage( void ); + virtual Vector GetPlayerWeldPoint( void ); + virtual void UpdateWeld( bool bCutting, float flWeldPercentage ); + CWeldableDoor *GetWeldLeader( void ); + + // Welding + int m_iWeldLeader; + EHANDLE m_hWeldingPlayer; + + // Weld points + string_t m_iszWeldPoints; + CUtlVector < CWeldPoint * > m_aWeldPoints; + + // Weld beams + float m_flMaxWeldedPercentage; + float m_flWeldedPercentage; + CUtlVector < CBeam * > m_aWeldBeams; + CUtlVector < CBeam * > m_aCutBeams; +}; + + +#endif // TF_FUNC_WELDABLE_DOOR_H diff --git a/game/server/tf2/tf_gameinterface.cpp b/game/server/tf2/tf_gameinterface.cpp new file mode 100644 index 0000000..d10728b --- /dev/null +++ b/game/server/tf2/tf_gameinterface.cpp @@ -0,0 +1,27 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "cbase.h" +#include "gameinterface.h" +#include "mapentities.h" + + +void CServerGameClients::GetPlayerLimits( int& minplayers, int& maxplayers, int &defaultMaxPlayers ) const +{ + minplayers = 2; // Force multiplayer. + maxplayers = MAX_PLAYERS; + defaultMaxPlayers = 32; +} + + +// -------------------------------------------------------------------------------------------- // +// Mod-specific CServerGameDLL implementation. +// -------------------------------------------------------------------------------------------- // + +void CServerGameDLL::LevelInit_ParseAllEntities( const char *pMapEntities ) +{ + MapEntity_ParseAllEntities( pMapEntities, NULL ); +} diff --git a/game/server/tf2/tf_hintmanager.cpp b/game/server/tf2/tf_hintmanager.cpp new file mode 100644 index 0000000..b5af27e --- /dev/null +++ b/game/server/tf2/tf_hintmanager.cpp @@ -0,0 +1,64 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +#include "cbase.h" + +#include "tf_hintmanager.h" + +#define TFHINTMANAGER_THINK_INTERVAL 1.0f + +IMPLEMENT_SERVERCLASS_ST_NOBASE(CTFHintManager, DT_TFHintManager) +END_SEND_TABLE() + +LINK_ENTITY_TO_CLASS( tf_hintmanager, CTFHintManager ); + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CTFHintManager::CTFHintManager( void ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFHintManager::Spawn( void ) +{ + Precache(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFHintManager::Think( void ) +{ + SetNextThink( gpGlobals->curtime + TFHINTMANAGER_THINK_INTERVAL ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *player - +// hintID - +//----------------------------------------------------------------------------- +void CTFHintManager::AddHint( CBaseTFPlayer *player, int hintID, int priority, int entityIndex /*=0*/ ) +{ + // Send a message to the client side entity +} + +//----------------------------------------------------------------------------- +// Purpose: Always send +// Input : **ppSendTable - +// *recipient - +// *pvs - +// clientArea - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- + +int CTFHintManager::UpdateTransmitState() +{ + return SetTransmitState( FL_EDICT_ALWAYS ); +}
\ No newline at end of file diff --git a/game/server/tf2/tf_hintmanager.h b/game/server/tf2/tf_hintmanager.h new file mode 100644 index 0000000..70295aa --- /dev/null +++ b/game/server/tf2/tf_hintmanager.h @@ -0,0 +1,42 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef TF_HINTMANAGER_H +#define TF_HINTMANAGER_H +#ifdef _WIN32 +#pragma once +#endif + +#include "cbase.h" + +class CBaseTFPlayer; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +class CTFHintManager : public CBaseEntity +{ + DECLARE_CLASS( CTFHintManager, CBaseEntity ); + +public: + + DECLARE_SERVERCLASS(); + + CTFHintManager( void ); + + virtual void Spawn( void ); + + virtual void Think( void ); + + virtual int UpdateTransmitState(); + +private: + + void AddHint( CBaseTFPlayer *player, int hintID, int priority, int entityIndex = 0 ); +}; + +#endif // TF_HINTMANAGER_H diff --git a/game/server/tf2/tf_obj.cpp b/game/server/tf2/tf_obj.cpp new file mode 100644 index 0000000..cd07213 --- /dev/null +++ b/game/server/tf2/tf_obj.cpp @@ -0,0 +1,3243 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Base Object built by players +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "tf_player.h" +#include "tf_team.h" +#include "tf_obj.h" +#include "tf_basecombatweapon.h" +#include "rope.h" +#include "rope_shared.h" +#include "bone_setup.h" +#include "tf_func_resource.h" +#include "ndebugoverlay.h" +#include "rope_helpers.h" +#include "IEffects.h" +#include "vstdlib/random.h" +#include "tier1/strtools.h" +#include "basegrenade_shared.h" +#include "grenade_objectsapper.h" +#include "tf_stats.h" +#include "tf_gamerules.h" +#include "engine/IEngineSound.h" +#include "tf_obj_sentrygun.h" +#include "tf_obj_powerpack.h" +#include "tf_shareddefs.h" +#include "VGuiScreen.h" +#include "resource_chunk.h" +#include "hierarchy.h" +#include "tf_func_construction_yard.h" +#include "tf_func_no_build.h" +#include <KeyValues.h> +#include "team_messages.h" +#include "info_act.h" +#include "info_vehicle_bay.h" +#include "ihasbuildpoints.h" +#include "tf_obj_buff_station.h" +#include "info_buildpoint.h" +#include "utldict.h" +#include "filesystem.h" +#include "npcevent.h" +#include "tf_shareddefs.h" +#include "animation.h" + +// Control panels +#define SCREEN_OVERLAY_MATERIAL "vgui/screens/vgui_overlay" + +#define ROPE_HANG_DIST 150 + + +ConVar object_verbose( "object_verbose", "0", 0, "Debug object system." ); +ConVar obj_damage_factor( "obj_damage_factor","0", FCVAR_NONE, "Factor applied to all damage done to objects" ); +ConVar obj_child_damage_factor( "obj_child_damage_factor","0.25", FCVAR_NONE, "Factor applied to damage done to objects that are built on a buildpoint" ); +ConVar tf_fastbuild("tf_fastbuild", "0", FCVAR_CHEAT); +ConVar tf_obj_ground_clearance( "tf_obj_ground_clearance", "60", 0, "Object corners can be this high above the ground" ); + +extern short g_sModelIndexFireball; + +// Minimum distance between 2 objects to ensure player movement between them +#define MINIMUM_OBJECT_SAFE_DISTANCE 100 + +// Maximum number of a type of objects on a single resource zone +#define MAX_OBJECTS_PER_ZONE 1 + +// Time it takes a fully healed object to deteriorate +ConVar object_deterioration_time( "object_deterioration_time", "30", 0, "Time it takes for a fully-healed object to deteriorate." ); + +// Time after taking damage that an object will still drop resources on death +#define MAX_DROP_TIME_AFTER_DAMAGE 5 + +#define OBJ_BASE_THINK_CONTEXT "BaseObjectThink" +#define OBJ_LOSTPOWER_THINK_CONTEXT "LostPowerThink" + +BEGIN_DATADESC( CBaseObject ) + // keys + DEFINE_KEYFIELD_NOT_SAVED( m_bInvulnerable, FIELD_BOOLEAN, "Invulnerable" ), + DEFINE_KEYFIELD_NOT_SAVED( m_flRepairMultiplier, FIELD_FLOAT, "RepairMult" ), + DEFINE_KEYFIELD_NOT_SAVED( m_iszUnderAttackSound, FIELD_STRING, "AttackNotify" ), + DEFINE_KEYFIELD_NOT_SAVED( m_flMinDisableHealth, FIELD_FLOAT, "MinDisabledHealth" ), + DEFINE_KEYFIELD_NOT_SAVED( m_SolidToPlayers, FIELD_INTEGER, "SolidToPlayer" ), + DEFINE_KEYFIELD_NOT_SAVED( m_iszDisabledModel, FIELD_STRING, "DisabledModel" ), + DEFINE_KEYFIELD_NOT_SAVED( m_bCantDie, FIELD_BOOLEAN, "CantDie" ), + + // Inputs + DEFINE_INPUTFUNC( FIELD_INTEGER, "SetHealth", InputSetHealth ), + DEFINE_INPUTFUNC( FIELD_INTEGER, "AddHealth", InputAddHealth ), + DEFINE_INPUTFUNC( FIELD_INTEGER, "RemoveHealth", InputRemoveHealth ), + DEFINE_INPUTFUNC( FIELD_INTEGER, "SetMinDisabledHealth", InputSetMinDisabledHealth ), + DEFINE_INPUTFUNC( FIELD_INTEGER, "SetSolidToPlayer", InputSetSolidToPlayer ), + + // Outputs + DEFINE_OUTPUT( m_OnDestroyed, "OnDestroyed" ), + DEFINE_OUTPUT( m_OnDamaged, "OnDamaged" ), + DEFINE_OUTPUT( m_OnRepaired, "OnRepaired" ), + DEFINE_OUTPUT( m_OnBecomingDisabled, "OnDisabled" ), + DEFINE_OUTPUT( m_OnBecomingReenabled, "OnReenabled" ), + DEFINE_OUTPUT( m_OnObjectHealthChanged, "OnObjectHealthChanged" ) +END_DATADESC() + + +IMPLEMENT_SERVERCLASS_ST(CBaseObject, DT_BaseObject) + SendPropInt(SENDINFO(m_iHealth), 13 ), + SendPropInt(SENDINFO(m_iMaxHealth), 13 ), + SendPropInt(SENDINFO(m_bHasSapper), 1, SPROP_UNSIGNED ), + SendPropInt(SENDINFO(m_iObjectType), 6, SPROP_UNSIGNED ), + SendPropInt(SENDINFO(m_bBuilding), 1, SPROP_UNSIGNED ), + SendPropInt(SENDINFO(m_bPlacing), 1, SPROP_UNSIGNED ), + SendPropFloat(SENDINFO(m_flPercentageConstructed), 8, 0, 0.0, 1.0f ), + SendPropInt(SENDINFO(m_fObjectFlags), OF_BIT_COUNT, SPROP_UNSIGNED ), + SendPropInt(SENDINFO(m_bDeteriorating), 1, SPROP_UNSIGNED ), + SendPropEHandle(SENDINFO(m_hBuiltOnEntity)), + SendPropInt( SENDINFO( m_takedamage ), 2, SPROP_UNSIGNED ), + SendPropInt( SENDINFO( m_bDisabled ), 1, SPROP_UNSIGNED ), + SendPropEHandle( SENDINFO( m_hBuilder ) ), +END_SEND_TABLE(); + + +// This controls whether ropes attached to objects are transmitted or not. It's important that +// ropes aren't transmitted to guys who don't own them. +class CObjectRopeTransmitProxy : public CBaseTransmitProxy +{ +public: + CObjectRopeTransmitProxy( CBaseEntity *pRope ) : CBaseTransmitProxy( pRope ) + { + } + + virtual int ShouldTransmit( const CCheckTransmitInfo *pInfo, int nPrevShouldTransmitResult ) + { + // Don't transmit the rope if it's not even visible. + if ( !nPrevShouldTransmitResult ) + return FL_EDICT_DONTSEND; + + // This proxy only wants to be active while one of the two objects is being placed. + // When they're done being placed, the proxy goes away and the rope draws like normal. + bool bAnyObjectPlacing = (m_hObj1 && m_hObj1->IsPlacing()) || (m_hObj2 && m_hObj2->IsPlacing()); + if ( !bAnyObjectPlacing ) + { + Release(); + return nPrevShouldTransmitResult; + } + + // Give control to whichever object is being placed. + if ( m_hObj1 && m_hObj1->IsPlacing() ) + return m_hObj1->ShouldTransmit( pInfo ); + + else if ( m_hObj2 && m_hObj2->IsPlacing() ) + return m_hObj2->ShouldTransmit( pInfo ); + + else + return FL_EDICT_ALWAYS; + } + + + CHandle<CBaseObject> m_hObj1; + CHandle<CBaseObject> m_hObj2; +}; + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CBaseObject::CBaseObject() +{ + m_hPowerPack = NULL; + m_iHealth = m_iMaxHealth = m_flHealth = 0; + m_aRopes.Purge(); + m_flPercentageConstructed = 0; + m_bPlacing = false; + m_bBuilding = false; + m_bInvulnerable = false; + m_bCantDie = false; + m_bDeteriorating = false; + m_flRepairMultiplier = 1; + m_hBuffStation = NULL; + m_bBuffActivated = false; + m_Activity = ACT_OBJ_IDLE; + m_bDisabled = false; + m_hVehicleBay = NULL; + m_flLastRepairTime = 0; + m_flNextRepairMultiplier = 0; + m_flRepairedSinceLastTime = 0; + m_iszUnderAttackSound = NULL_STRING; + m_SolidToPlayers = SOLID_TO_PLAYER_USE_DEFAULT; + m_iszDisabledModel = NULL_STRING; + m_iszEnabledModel = NULL_STRING; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseObject::UpdateOnRemove( void ) +{ + m_bDying = true; + + // Remove anything left on me + IHasBuildPoints *pBPInterface = dynamic_cast<IHasBuildPoints*>(this); + if ( pBPInterface && pBPInterface->GetNumObjectsOnMe() ) + { + pBPInterface->RemoveAllObjects(); + } + + DestroyObject(); + + if ( GetTeam() ) + { + ((CTFTeam*)GetTeam())->RemoveObject( this ); + } + + // Make sure the object isn't in either team's list of objects... + Assert( !GetGlobalTFTeam(1)->IsObjectOnTeam( this ) ); + Assert( !GetGlobalTFTeam(2)->IsObjectOnTeam( this ) ); + + // Chain at end to mimic destructor unwind order + BaseClass::UpdateOnRemove(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int CBaseObject::ShouldTransmit( const CCheckTransmitInfo *pInfo ) +{ + // Always transmit to owner + if ( GetBuilder() && pInfo->m_pClientEnt == GetBuilder()->edict() ) + return FL_EDICT_ALWAYS; + + // Placement models only transmit to owners + if ( IsPlacing() ) + return FL_EDICT_DONTSEND; + + return BaseClass::ShouldTransmit( pInfo ); +} + + +void CBaseObject::SetTransmit( CCheckTransmitInfo *pInfo, bool bAlways ) +{ + // Are we already marked for transmission? + if ( pInfo->m_pTransmitEdict->Get( entindex() ) ) + return; + + BaseClass::SetTransmit( pInfo, bAlways ); + + // Force our screens to be sent too. + int nTeam = CBaseEntity::Instance( pInfo->m_pClientEnt )->GetTeamNumber(); + for ( int i=0; i < m_hScreens.Count(); i++ ) + { + CVGuiScreen *pScreen = m_hScreens[i].Get(); + if ( pScreen && pScreen->IsVisibleToTeam( nTeam ) ) + pScreen->SetTransmit( pInfo, bAlways ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseObject::Precache() +{ + PrecacheVGuiScreen( "screen_basic_with_disable" ); + + if ( m_iszUnderAttackSound != NULL_STRING ) + { + PrecacheScriptSound( STRING(m_iszUnderAttackSound) ); + } + PrecacheMaterial( SCREEN_OVERLAY_MATERIAL ); + + if ( m_iszDisabledModel != NULL_STRING ) + { + PrecacheModel( STRING( m_iszDisabledModel ) ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseObject::Spawn( void ) +{ + Precache(); + + CollisionProp()->SetSurroundingBoundsType( USE_BEST_COLLISION_BOUNDS ); + SetSolidToPlayers( m_SolidToPlayers, true ); + + m_bWasMapPlaced = false; + m_bHasSapper = false; + m_takedamage = DAMAGE_YES; + m_flHealth = m_iMaxHealth = m_iHealth; + m_iAmountPlayerPaidForMe = CalculateObjectCost( GetType(), 0, GetTeamNumber() ); + + SetContextThink( BaseObjectThink, gpGlobals->curtime + 0.1, OBJ_BASE_THINK_CONTEXT ); + m_szAmmoName = NULL; + + AddFlag( FL_OBJECT ); // So NPCs will notice it + SetViewOffset( WorldSpaceCenter() - GetAbsOrigin() ); + + // Don't take damage if we're invulnerable, and don't require power either + if ( m_bInvulnerable ) + { + m_takedamage = DAMAGE_NO; + m_fObjectFlags |= OF_DOESNT_NEED_POWER; + AddFlag( FL_NOTARGET ); + } + + // Cache off the normal model name + m_iszEnabledModel = GetModelName(); +} + + +//----------------------------------------------------------------------------- +// Returns information about the various control panels +//----------------------------------------------------------------------------- +void CBaseObject::GetControlPanelInfo( int nPanelIndex, const char *&pPanelName ) +{ + pPanelName = NULL; +} + +//----------------------------------------------------------------------------- +// Returns information about the various control panels +//----------------------------------------------------------------------------- +void CBaseObject::GetControlPanelClassName( int nPanelIndex, const char *&pPanelName ) +{ + pPanelName = "vgui_screen"; +} + +//----------------------------------------------------------------------------- +// This is called by the base object when it's time to spawn the control panels +//----------------------------------------------------------------------------- +void CBaseObject::SpawnControlPanels() +{ + char buf[64]; + + // FIXME: Deal with dynamically resizing control panels? + + // If we're attached to an entity, spawn control panels on it instead of use + CBaseAnimating *pEntityToSpawnOn = this; + char *pOrgLL = "controlpanel%d_ll"; + char *pOrgUR = "controlpanel%d_ur"; + char *pAttachmentNameLL = pOrgLL; + char *pAttachmentNameUR = pOrgUR; + if ( IsBuiltOnAttachment() ) + { + pEntityToSpawnOn = dynamic_cast<CBaseAnimating*>((CBaseEntity*)m_hBuiltOnEntity.Get()); + if ( pEntityToSpawnOn ) + { + char sBuildPointLL[64]; + char sBuildPointUR[64]; + Q_snprintf( sBuildPointLL, sizeof( sBuildPointLL ), "bp%d_controlpanel%%d_ll", m_iBuiltOnPoint ); + Q_snprintf( sBuildPointUR, sizeof( sBuildPointUR ), "bp%d_controlpanel%%d_ur", m_iBuiltOnPoint ); + pAttachmentNameLL = sBuildPointLL; + pAttachmentNameUR = sBuildPointUR; + } + else + { + pEntityToSpawnOn = this; + } + } + + Assert( pEntityToSpawnOn ); + + // Lookup the attachment point... + int nPanel; + for ( nPanel = 0; true; ++nPanel ) + { + Q_snprintf( buf, sizeof( buf ), pAttachmentNameLL, nPanel ); + int nLLAttachmentIndex = pEntityToSpawnOn->LookupAttachment(buf); + if (nLLAttachmentIndex <= 0) + { + // Try and use my panels then + pEntityToSpawnOn = this; + Q_snprintf( buf, sizeof( buf ), pOrgLL, nPanel ); + nLLAttachmentIndex = pEntityToSpawnOn->LookupAttachment(buf); + if (nLLAttachmentIndex <= 0) + return; + } + + Q_snprintf( buf, sizeof( buf ), pAttachmentNameUR, nPanel ); + int nURAttachmentIndex = pEntityToSpawnOn->LookupAttachment(buf); + if (nURAttachmentIndex <= 0) + { + // Try and use my panels then + Q_snprintf( buf, sizeof( buf ), pOrgUR, nPanel ); + nURAttachmentIndex = pEntityToSpawnOn->LookupAttachment(buf); + if (nURAttachmentIndex <= 0) + return; + } + + const char *pScreenName; + GetControlPanelInfo( nPanel, pScreenName ); + if (!pScreenName) + continue; + + const char *pScreenClassname; + GetControlPanelClassName( nPanel, pScreenClassname ); + if ( !pScreenClassname ) + continue; + + // Compute the screen size from the attachment points... + matrix3x4_t panelToWorld; + pEntityToSpawnOn->GetAttachment( nLLAttachmentIndex, panelToWorld ); + + matrix3x4_t worldToPanel; + MatrixInvert( panelToWorld, worldToPanel ); + + // Now get the lower right position + transform into panel space + Vector lr, lrlocal; + pEntityToSpawnOn->GetAttachment( nURAttachmentIndex, panelToWorld ); + MatrixGetColumn( panelToWorld, 3, lr ); + VectorTransform( lr, worldToPanel, lrlocal ); + + float flWidth = lrlocal.x; + float flHeight = lrlocal.y; + + CVGuiScreen *pScreen = CreateVGuiScreen( pScreenClassname, pScreenName, pEntityToSpawnOn, this, nLLAttachmentIndex ); + pScreen->ChangeTeam( GetTeamNumber() ); + pScreen->SetActualSize( flWidth, flHeight ); + pScreen->SetActive( false ); + pScreen->MakeVisibleOnlyToTeammates( true ); + pScreen->SetOverlayMaterial( SCREEN_OVERLAY_MATERIAL ); + int nScreen = m_hScreens.AddToTail( ); + m_hScreens[nScreen].Set( pScreen ); + } +} + + +//----------------------------------------------------------------------------- +// Various commands sent by control panels +//----------------------------------------------------------------------------- +void CBaseObject::DismantleCommand( CBaseTFPlayer *pSender ) +{ + if (CanBeRemovedBy( pSender )) + { + PickupObject(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseObject::YawCommand( CBaseTFPlayer *pSender, float flYaw ) +{ + if ( CanBeRotatedBy(pSender) ) + { + QAngle angles = GetAbsAngles(); + + angles.y = anglemod( flYaw ); + SetLocalAngles( ConvertAbsAnglesToLocal( angles ) ); + Teleport( NULL, &GetLocalAngles(), NULL ); + + // Notify the object that it moved + ObjectMoved(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseObject::TakeControlCommand( CBaseTFPlayer *pSender ) +{ + // Deteriorating objects can be bought + if ( InSameTeam( pSender ) && IsDeteriorating() ) + { + if ( ClassCanBuild( pSender->PlayerClass(), GetType() ) ) + { + // Make sure he has the resources + int iCost = CalculateObjectCost( GetType(), pSender->GetNumObjects( GetType() ), GetTeamNumber() ); + if ( pSender->GetBankResources() >= iCost ) + { + pSender->RemoveBankResources( iCost ); + SetBuilder( pSender ); + pSender->AddObject( this ); + } + } + } +} + +//----------------------------------------------------------------------------- +// Handle commands sent from vgui panels on the client +//----------------------------------------------------------------------------- +bool CBaseObject::ClientCommand( CBaseTFPlayer *pSender, const char *pCmd, ICommandArguments *pArg ) +{ + if ( FStrEq( pCmd, "dismantle" ) ) + { + DismantleCommand( pSender ); + return true; + } + + if ( FStrEq( pCmd, "yaw" ) ) + { + if ( pArg->Argc() == 2 ) + { + float flYaw = atof( pArg->Argv(1) ); + YawCommand( pSender, flYaw ); + } + return true; + } + + if ( FStrEq( pCmd, "takecontrol" ) ) + { + TakeControlCommand( pSender ); + return true; + } + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseObject::BaseObjectThink( void ) +{ + SetNextThink( gpGlobals->curtime + 0.1, OBJ_BASE_THINK_CONTEXT ); + + // Make sure animation is up to date + DetermineAnimation(); + + // Can't animate without a model + if ( !(m_fObjectFlags & OF_DOESNT_HAVE_A_MODEL) ) + { + StudioFrameAdvance(); + } + + /* + ROBIN: Hierarchy should do this for us + + // If we were built on an attachment that's moved, update our position + if ( !IsPlacing() && IsBuiltOnAttachment() ) + { + IHasBuildPoints *pBPInterface = dynamic_cast<IHasBuildPoints*>((CBaseEntity*)m_hBuiltOnEntity); + Assert( pBPInterface ); + + if ( pBPInterface->ShouldCheckForMovement() ) + { + Vector vecOrigin; + QAngle vecAngles; + pBPInterface->GetBuildPoint( m_iBuiltOnPoint, vecOrigin, vecAngles ); + + EntityMatrix vehicleToWorld, childMatrix; + vehicleToWorld.InitFromEntity( GetMoveParent() ); // parent->world + vecOrigin = vehicleToWorld.WorldToLocal( vecOrigin ); + + if ( vecOrigin != GetLocalOrigin() ) + { + Teleport( &vecOrigin, NULL, NULL ); + } + } + } + */ + + // Do nothing while we're being placed + if ( IsPlacing() ) + { + for ( int i=0; i < m_aRopes.Count(); i++ ) + { + if ( m_aRopes[i].Get() ) + m_aRopes[i]->SetupHangDistance( ROPE_HANG_DIST ); + } + + return; + } + + // If we're deteriorating, keep going + if ( IsDeteriorating() ) + { + DeterioratingThink(); + } + + // If we're building, keep going + if ( IsBuilding() ) + { + BuildingThink(); + return; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CBaseTFPlayer *CBaseObject::GetOwner() +{ + return m_hBuilder; +} + + +//----------------------------------------------------------------------------- +// Do we have to be built in a resource zone? +//----------------------------------------------------------------------------- +bool CBaseObject::MustBeBuiltInResourceZone( void ) const +{ + return (m_fObjectFlags & OF_MUST_BE_BUILT_IN_RESOURCE_ZONE) != 0; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CBaseObject::MustBeBuiltInConstructionYard( ) const +{ + return (m_fObjectFlags & OF_MUST_BE_BUILT_IN_CONSTRUCTION_YARD) != 0; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CBaseObject::MustNotBeBuiltInConstructionYard( void ) const +{ + return !MustBeBuiltInConstructionYard(); +} + +//----------------------------------------------------------------------------- +// Do we have to be built on an attachment point +//----------------------------------------------------------------------------- +bool CBaseObject::MustBeBuiltOnAttachmentPoint( void ) const +{ + return (m_fObjectFlags & OF_MUST_BE_BUILT_ON_ATTACHMENT) != 0; +} + +//----------------------------------------------------------------------------- +// Purpose: Map placed objects need to setup here. +//----------------------------------------------------------------------------- +void CBaseObject::InitializeMapPlacedObject( void ) +{ + m_bWasMapPlaced = true; + m_fObjectFlags |= OF_CANNOT_BE_DISMANTLED; + + // If a map-placed object spawns child objects with their own control + // panels, all of this lovely code will already have been run + if (m_hBuiltOnEntity.Get()) + return; + + SetBuilder( NULL ); + + // NOTE: We must spawn the control panels now, instead of during + // Spawn, because until placement is started, we don't actually know + // the position of the control panel because we don't know what it's + // been attached to (could be a vehicle which supplies a different + // place for the control panel) + + if ( !(m_fObjectFlags & OF_DOESNT_HAVE_A_MODEL) ) + { + SpawnControlPanels(); + } + + SetHealth( GetMaxHealth() ); + + AlignToGround( GetAbsOrigin() ); + FinishedBuilding(); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseObject::Activate( void ) +{ + BaseClass::Activate(); + + // Add myself to the team + InitializeMapPlacedObject(); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseObject::SetBuilder( CBaseTFPlayer *pBuilder, bool moveobjects ) +{ + TRACE_OBJECT( UTIL_VarArgs( "%0.2f CBaseObject::SetBuilder builder %s, moveobjects == %s\n", gpGlobals->curtime, + pBuilder ? pBuilder->GetPlayerName() : "NULL", + moveobjects ? "true" : "false" ) ); + + ChangeBuilder( pBuilder, moveobjects ); +} + + +//----------------------------------------------------------------------------- +// Called when the builder rotates this object... +//----------------------------------------------------------------------------- +void CBaseObject::ObjectMoved( ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int CBaseObject::ObjectType( ) const +{ + return m_iObjectType; +} + + +//----------------------------------------------------------------------------- +// Purpose: Remove this object from it's team and mark for deletion +//----------------------------------------------------------------------------- +void CBaseObject::DestroyObject( void ) +{ + TRACE_OBJECT( UTIL_VarArgs( "%0.2f CBaseObject::DestroyObject %p:%s\n", gpGlobals->curtime, this, GetClassname() ) ); + + COrderEvent_ObjectDestroyed order( this ); + GlobalOrderEvent( &order ); + + if ( GetBuilder() ) + { + GetBuilder()->OwnedObjectDestroyed( this ); + } + + // Tell my powerpack that I'm gone + if ( m_hPowerPack != NULL ) + { + m_hPowerPack->UnPowerObject( this ); + } + + // Tell my power up source that I have been destroyed. + if ( GetBuffStation() ) + { + GetBuffStation()->DeBuffObject( this ); + } + + // Detach all my ropes + int i; + for ( i = 0; i < m_aRopes.Size(); i++ ) + { + if ( m_aRopes[i] ) + { + m_aRopes[i]->DieAtNextRest(); + } + } + + UTIL_Remove( this ); + + // Kill the control panels + for ( i = m_hScreens.Count(); --i >= 0; ) + { + DestroyVGuiScreen( m_hScreens[i].Get() ); + } + m_hScreens.RemoveAll(); +} + +//----------------------------------------------------------------------------- +// Purpose: My builder's switched class/team, so start deteriorating. +//----------------------------------------------------------------------------- +void CBaseObject::StartDeteriorating( void ) +{ + if ( tf_fastbuild.GetInt() ) + return; + + m_bDeteriorating = true; + m_flStartedDeterioratingAt = gpGlobals->curtime; + SetBuilder( NULL, true ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseObject::StopDeteriorating( void ) +{ + m_bDeteriorating = false; +} + +//----------------------------------------------------------------------------- +// Purpose: Continue deterioration of this object +//----------------------------------------------------------------------------- +void CBaseObject::DeterioratingThink( void ) +{ + // Calculate damage. The longer we've lasted, the faster we should go. + float flDamage; + float flDeteriorationTime = (gpGlobals->curtime - m_flStartedDeterioratingAt); + // If we've lasted less than the base time, we want to take the base time to die + flDamage = 0.1 * ( GetMaxHealth() / object_deterioration_time.GetFloat() ) * ceil(flDeteriorationTime / object_deterioration_time.GetFloat()); + // Hax0r the damage to get around the object damage reduction + if ( obj_damage_factor.GetFloat() ) + { + flDamage *= 1 / obj_damage_factor.GetFloat(); + } + + // Apply the damage + OnTakeDamage( CTakeDamageInfo( GetContainingEntity(INDEXENT(0)), GetContainingEntity(INDEXENT(0)), flDamage, DMG_GENERIC ) ); +} + +//----------------------------------------------------------------------------- +// Purpose: Get the total time it will take to build this object +//----------------------------------------------------------------------------- +float CBaseObject::GetTotalTime( void ) +{ + if (tf_fastbuild.GetInt()) + return 2.f; + + // If it's in a construction yard, don't take more than 5 seconds to build + if ( PointInConstructionYard( GetAbsOrigin() ) ) + { + if ( GetObjectInfo( ObjectType() )->m_flBuildTime > 5.0 ) + return 5.0; + } + + return GetObjectInfo( ObjectType() )->m_flBuildTime; +} + +//----------------------------------------------------------------------------- +// Purpose: Start placing the object +//----------------------------------------------------------------------------- +void CBaseObject::StartPlacement( CBaseTFPlayer *pPlayer ) +{ + AddSolidFlags( FSOLID_NOT_SOLID ); + + m_bPlacing = true; + m_bBuilding = false; + if ( pPlayer ) + { + SetBuilder( pPlayer ); + ChangeTeam( pPlayer->GetTeamNumber() ); + } + + // Make it semi-transparent + m_nRenderMode = kRenderTransAlpha; + SetRenderColorA( 128 ); + + // Set my build size + CollisionProp()->WorldSpaceAABB( &m_vecBuildMins, &m_vecBuildMaxs ); + m_vecBuildMins -= Vector( 4,4,0 ); + m_vecBuildMaxs += Vector( 4,4,0 ); + m_vecBuildMins -= GetAbsOrigin(); + m_vecBuildMaxs -= GetAbsOrigin(); +} + +//----------------------------------------------------------------------------- +// Purpose: Stop placing the object +//----------------------------------------------------------------------------- +void CBaseObject::StopPlacement( void ) +{ + UTIL_Remove( this ); +} + +//----------------------------------------------------------------------------- +// Purpose: Find the nearest buildpoint on the specified entity +//----------------------------------------------------------------------------- +bool CBaseObject::FindNearestBuildPoint( CBaseEntity *pEntity, Vector vecBuildOrigin, float &flNearestPoint, Vector &vecNearestBuildPoint ) +{ + bool bFoundPoint = false; + + IHasBuildPoints *pBPInterface = dynamic_cast<IHasBuildPoints*>(pEntity); + Assert( pBPInterface ); + + // Any empty buildpoints? + for ( int i = 0; i < pBPInterface->GetNumBuildPoints(); i++ ) + { + // Can this object build on this point? + if ( pBPInterface->CanBuildObjectOnBuildPoint( i, GetType() ) ) + { + // Close to this point? + Vector vecBPOrigin; + QAngle vecBPAngles; + if ( pBPInterface->GetBuildPoint(i, vecBPOrigin, vecBPAngles) ) + { + float flDist = (vecBPOrigin - vecBuildOrigin).Length(); + if ( flDist < MIN(flNearestPoint, pBPInterface->GetMaxSnapDistance( i )) ) + { + flNearestPoint = flDist; + vecNearestBuildPoint = vecBPOrigin; + m_hBuiltOnEntity = pEntity; + m_iBuiltOnPoint = i; + + // Set our angles to the buildpoint's angles + SetAbsAngles( vecBPAngles ); + + bFoundPoint = true; + } + } + } + } + + return bFoundPoint; +} + +//----------------------------------------------------------------------------- +// Purpose: Calculate the placement model's position +//----------------------------------------------------------------------------- +bool CBaseObject::CalculatePlacement( CBaseTFPlayer *pPlayer ) +{ + // Calculate build position + Vector forward; + QAngle vecAngles = vec3_angle; + vecAngles.y = pPlayer->EyeAngles().y; + SetLocalAngles( vecAngles ); + AngleVectors(vecAngles, &forward ); + + // Adjust build distance based upon object size + Vector2D xyDims; + xyDims.x = MAX( fabs( m_vecBuildMins.x ), fabs( m_vecBuildMaxs.x ) ); + xyDims.y = MAX( fabs( m_vecBuildMins.y ), fabs( m_vecBuildMaxs.y ) ); + float flDistance = xyDims.Length() + 16; // small safety buffer + Vector vecBuildOrigin = pPlayer->WorldSpaceCenter() + forward * flDistance; + + bool bSnappedToPoint = false; + bool bShouldAttachToParent = false; + + // See if there are any nearby build positions to snap to + Vector vecNearestBuildPoint = vec3_origin; + float flNearestPoint = 9999; + // First, look for nearby buildpoints on other objects + for ( int i = 0; i < GetTFTeam()->GetNumObjects(); i++ ) + { + CBaseObject *pObject = GetTFTeam()->GetObject(i); + if ( pObject && !pObject->IsPlacing() ) + { + if ( FindNearestBuildPoint( pObject, vecBuildOrigin, flNearestPoint, vecNearestBuildPoint ) ) + { + bSnappedToPoint = true; + + // If I'm a vehicle, I'm being built on an MCV. Don't attach to the parent. + if ( ShouldAttachToParent() ) + { + bShouldAttachToParent = true; + } + } + } + } + + // If we're a vehicle, look for vehicle build points + if ( IsAVehicle() ) + { + CBaseEntity *pEntity = NULL; + while ((pEntity = gEntList.FindEntityByClassname( pEntity, "info_vehicle_bay" )) != NULL) + { + if ( FindNearestBuildPoint( pEntity, vecBuildOrigin, flNearestPoint, vecNearestBuildPoint ) ) + { + bSnappedToPoint = true; + } + } + } + + // Check for resource zones for resource pumps + if ( GetType() == OBJ_RESOURCEPUMP ) + { + CBaseEntity *pEntity = NULL; + while ((pEntity = gEntList.FindEntityByClassname( pEntity, "trigger_resourcezone" )) != NULL) + { + if ( FindNearestBuildPoint( pEntity, vecBuildOrigin, flNearestPoint, vecNearestBuildPoint ) ) + { + bSnappedToPoint = true; + } + } + } + + // See if there's any mapdefined build points near me + int iCount = g_MapDefinedBuildPoints.Count(); + for ( i = 0; i < iCount; i++ ) + { + if ( !InSameTeam(g_MapDefinedBuildPoints[i]) ) + continue; + + if ( FindNearestBuildPoint( g_MapDefinedBuildPoints[i], vecBuildOrigin, flNearestPoint, vecNearestBuildPoint ) ) + { + bSnappedToPoint = true; + } + } + + // Upgrades become invisible if the player's not attaching them to a snap pint + if ( IsAnUpgrade() ) + { + if ( MustBeBuiltOnAttachmentPoint() && !bSnappedToPoint ) + { + AddEffects( EF_NODRAW ); + return false; + } + else + { + RemoveEffects( EF_NODRAW ); + } + } + + // Did we find a snap point? + if ( bSnappedToPoint ) + { + if ( bShouldAttachToParent ) + { + AttachObjectToObject( m_hBuiltOnEntity.Get(), m_iBuiltOnPoint, vecNearestBuildPoint ); + } + + return CheckBuildOrigin( pPlayer, vecNearestBuildPoint, true ); + } + + // Clear out previous parent + if ( m_hBuiltOnEntity.Get() ) + { + m_hBuiltOnEntity = NULL; + m_iBuiltOnPoint = 0; + SetParent( NULL ); + + SetupUnattachedVersion(); + } + + // Check the build position + return CheckBuildOrigin( pPlayer, vecBuildOrigin, false ); +} + + +bool CBaseObject::VerifyCorner( const Vector &vBottomCenter, float xOffset, float yOffset ) +{ + Vector vStart( vBottomCenter.x + xOffset, vBottomCenter.y + yOffset, vBottomCenter.z ); + + trace_t tr; + UTIL_TraceLine( + vStart, + vStart - Vector( 0, 0, tf_obj_ground_clearance.GetFloat() ), + MASK_SOLID, this, COLLISION_GROUP_PLAYER_MOVEMENT, &tr ); + + return !tr.startsolid && tr.fraction < 1; +} + + +//----------------------------------------------------------------------------- +// Purpose: Check under a build point to ensure it's buildable on +//----------------------------------------------------------------------------- +bool CBaseObject::CheckBuildPoint( Vector vecPoint, Vector &vecTrace, Vector *vecOutPoint ) +{ + trace_t tr; + + bool bClear = true; + Vector vecEnd; + + // Ensure that this point isn't in a no-build zone: + if( !tf_fastbuild.GetInt() && NoBuildPreventsBuild(this, vecPoint ) ) + bClear = false; + + // If the point isn't in solid, trace down until we find the ground + if ( enginetrace->GetPointContents( vecPoint ) == CONTENTS_EMPTY ) + { + vecEnd = vecPoint - vecTrace; + UTIL_TraceLine( vecPoint, vecEnd, MASK_SOLID, this, COLLISION_GROUP_PLAYER_MOVEMENT, &tr); + + // Can't find ground to build on? + if ( tr.fraction == 1.0 ) + { + bClear = false; + } + } + else + { + // If the point's solid, trace up until we find empty air + vecEnd = vecPoint + vecTrace; + UTIL_TraceLine( vecPoint, vecEnd, MASK_SOLID, this, COLLISION_GROUP_PLAYER_MOVEMENT, &tr); + + // Can't find ground to build on? + if ( tr.allsolid ) + { + bClear = false; + } + } + + // FIXME: HACK! This is a test to try to make mud non-buildable!! + const surfacedata_t *pSurfaceProp = physprops->GetSurfaceData( tr.surface.surfaceProps ); + if (pSurfaceProp->game.maxSpeedFactor < 1.0f) + bClear = false; + + if ( vecOutPoint ) + { + *vecOutPoint = tr.endpos; + } + + return bClear; +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- + +bool CBaseObject::CheckBuildOrigin( CBaseTFPlayer *pPlayer, const Vector &vecInitialBuildOrigin, bool bSnappedToPoint ) +{ + // By default, use the vecBuildOrigin.. + bool bResult = true; + m_vecBuildOrigin = vecInitialBuildOrigin; + Vector vErrorOrigin = vecInitialBuildOrigin - (m_vecBuildMaxs - m_vecBuildMins) * 0.5f - m_vecBuildMins; + + // If we're snapping to a build point, don't bother performing area checks + if ( !bSnappedToPoint ) + { + Vector vBuildDims = m_vecBuildMaxs - m_vecBuildMins; + Vector vHalfBuildDims = vBuildDims * 0.5; + Vector vHalfBuildDimsXY( vHalfBuildDims.x, vHalfBuildDims.y, 0 ); + + // Here, we start at the highest Z we'll allow for the top of the object. Then + // we sweep an XY cross section downwards until it hits the ground. + // + // The rule is that the top of to box can't go lower than the player's feet, and the bottom of the + // box can't go higher than the player's head. + // + // To simplify things in here, we treat the box as though it's symmetrical about all axes + // (so mins = -maxs), then reoffset the box at the very end. + Vector vHalfPlayerDims = (pPlayer->WorldAlignMaxs() - pPlayer->WorldAlignMins()) * 0.5f; + float flBoxTopZ = pPlayer->WorldSpaceCenter().z + vHalfPlayerDims.z + vBuildDims.z; + float flBoxBottomZ = pPlayer->WorldSpaceCenter().z - vHalfPlayerDims.z - vBuildDims.z; + + // First, find the ground (ie: where the bottom of the box goes). + trace_t tr; + float bottomZ = 0; + int nIterations = 6; + float topZ = flBoxTopZ; + float topZInc = (flBoxBottomZ - flBoxTopZ) / (nIterations-1); + for ( int iIteration = 0; iIteration < nIterations; iIteration++ ) + { + UTIL_TraceHull( + Vector( m_vecBuildOrigin.x, m_vecBuildOrigin.y, topZ ), + Vector( m_vecBuildOrigin.x, m_vecBuildOrigin.y, flBoxBottomZ ), + -vHalfBuildDimsXY, vHalfBuildDimsXY, MASK_SOLID, this, COLLISION_GROUP_PLAYER_MOVEMENT, &tr ); + bottomZ = tr.endpos.z; + + // If there is no ground, then we can't place here. + if ( tr.fraction == 1 ) + { + m_vecBuildOrigin = vErrorOrigin; + return false; + } + + // If it started in solid, keep moving down. + // Note that a working CGameTrace::fractionleftsolid would make this trivial, but it isn't + // working now so we must resort to rubitry. + if ( !tr.startsolid ) + break; + + topZ += topZInc; + } + + if ( iIteration == nIterations ) + { + m_vecBuildOrigin = vErrorOrigin; + return false; + } + + + // Now see if the range we've got leaves us room for our box. + if ( topZ - bottomZ < vBuildDims.z ) + { + m_vecBuildOrigin = vErrorOrigin; + return false; + } + + // Verify that it's not on too much of a slope by seeing how far the corners are from the ground. + Vector vBottomCenter( m_vecBuildOrigin.x, m_vecBuildOrigin.y, bottomZ ); + if ( !VerifyCorner( vBottomCenter, -vHalfBuildDims.x, -vHalfBuildDims.y ) || + !VerifyCorner( vBottomCenter, +vHalfBuildDims.x, +vHalfBuildDims.y ) || + !VerifyCorner( vBottomCenter, +vHalfBuildDims.x, -vHalfBuildDims.y ) || + !VerifyCorner( vBottomCenter, -vHalfBuildDims.x, +vHalfBuildDims.y ) ) + { + m_vecBuildOrigin = vErrorOrigin; + return false; + } + + // Ok, now we know the Z range where this box can fit. + Vector vBottomLeft = m_vecBuildOrigin - vHalfBuildDims; + vBottomLeft.z = bottomZ; + m_vecBuildOrigin = vBottomLeft - m_vecBuildMins; + } + + Vector vecForward, vecRight, vecUp; + AngleVectors( GetLocalAngles(), &vecForward, &vecRight, &vecUp ); + AttemptToFindPower(); + AttemptToFindBuffStation(); + + // Make sure construction yards don't screw us up (tf_fastbuild allows builds anywhere) + if ( !tf_fastbuild.GetInt() && ConstructionYardPreventsBuild( this, m_vecBuildOrigin )) + return false; + + // If we have to be attached to something, and we're not, abort + if ( !tf_fastbuild.GetInt() && MustBeBuiltOnAttachmentPoint() && !bSnappedToPoint ) + return false; + + // Make sure there aren't any solid objects in the area + if ( !bSnappedToPoint || IsAVehicle() ) + { + if ( !(m_fObjectFlags & OF_DONT_PREVENT_BUILD_NEAR_OBJ) ) + { + // Get a list of nearby entities + CBaseEntity *pListOfNearbyEntities[100]; + int iNumberOfNearbyEntities = UTIL_EntitiesInSphere( pListOfNearbyEntities, 100, m_vecBuildOrigin, GetNearbyObjectCheckRadius(), 0 ); + for ( int i = 0; i < iNumberOfNearbyEntities; i++ ) + { + CBaseEntity *pEntity = pListOfNearbyEntities[i]; + if ( pEntity->IsSolid( ) ) + { + // Ignore shields.. + if ( pEntity->GetCollisionGroup() == TFCOLLISION_GROUP_SHIELD ) + continue; + + // Ignore func brushes + // BUGBUG: Shouldn't this test against MOVETYPE_PUSH instead of SOLID_BSP? + if ( pEntity->GetSolid() == SOLID_BSP ) + continue; + + // Ignore the player who's building + if ( pEntity == GetBuilder() ) + continue; + + // YWB: Ignore other players + if ( pEntity->IsPlayer() ) + continue; + + // Ignore map placed objects + if ( pEntity->GetTeamNumber() == 0 ) + continue; + + //NDebugOverlay::EntityBounds( pEntity, 0,255,0,8, 0.1 ); + return false; + } + + // Sentryguns may be turtled, and non-solid + if ( pEntity->Classify() == CLASS_MILITARY ) + { + CObjectSentrygun *pSentry = dynamic_cast<CObjectSentrygun*>(pEntity); + if ( pSentry && pSentry->IsTurtled() ) + return false; + } + } + } + } + + if ( !bSnappedToPoint ) + { + AlignToGround( m_vecBuildOrigin ); + } + + return bResult; +} + +//----------------------------------------------------------------------------- +// Purpose: Align myself to the ground below the specified point +//----------------------------------------------------------------------------- +void CBaseObject::AlignToGround( Vector vecOrigin ) +{ + if ( !(m_fObjectFlags & OF_ALIGN_TO_GROUND) ) + return; + + trace_t tr; + Vector vecWorldMins, vecWorldMaxs; + CollisionProp()->WorldSpaceAABB( &vecWorldMins, &vecWorldMaxs ); + float flHeight = MAX( vecWorldMaxs.z - vecWorldMins.z, 60 ); + UTIL_TraceLine( vecOrigin, vecOrigin + Vector(0,0,-flHeight), MASK_SOLID, this, COLLISION_GROUP_PLAYER_MOVEMENT, &tr ); + if ( tr.fraction != 1.0 ) + { + // Orient the *up* axis to be along the plane normal + Vector perp( 1, 0, 0 ); + Vector forward, right; + CrossProduct( perp, tr.plane.normal, forward ); + if (forward.LengthSqr() < 0.1f) + { + perp.Init( 0, 1, 0 ); + CrossProduct( perp, tr.plane.normal, forward ); + } + VectorNormalize( forward ); + CrossProduct( tr.plane.normal, forward, right ); + + VMatrix orientation( forward, right, tr.plane.normal ); + + QAngle angles; + MatrixToAngles( orientation, angles ); + SetAbsAngles( angles ); + } +} + +//----------------------------------------------------------------------------- +// Exit points for mounted vehicles.... +//----------------------------------------------------------------------------- +void CBaseObject::GetExitPoint( CBaseEntity *pPlayer, int nBuildPoint, Vector *pAbsPosition, QAngle *pAbsAngles ) +{ + // Deal with hierarchy... + IHasBuildPoints *pMount = dynamic_cast<IHasBuildPoints*>(GetMoveParent()); + if (pMount) + { + int nBuildPoint = pMount->FindObjectOnBuildPoint( this ); + if (nBuildPoint >= 0) + { + pMount->GetExitPoint( pPlayer, nBuildPoint, pAbsPosition, pAbsAngles ); + return; + } + } + + // FIXME: In future, we may well want to use specific exit attachments here... + GetBuildPoint( nBuildPoint, *pAbsPosition, *pAbsAngles ); + + // Move back along the forward direction a bit... + Vector vecForward, vecUp; + AngleVectors( *pAbsAngles, &vecForward, NULL, &vecUp ); + *pAbsPosition -= vecForward * 60; + *pAbsPosition += vecUp * 30; + + // Now select a good spot to drop onto + Vector vNewPos; + if ( !EntityPlacementTest(pPlayer, *pAbsPosition, vNewPos, true) ) + { + Warning("Can't find valid place to exit object.\n"); + return; + } + + *pAbsPosition = vNewPos; +} + + +void CBaseObject::AdjustInitialBuildAngles() +{ +} + + +//----------------------------------------------------------------------------- +// Purpose: Try and find power for this object during placement +//----------------------------------------------------------------------------- +void CBaseObject::AttemptToFindPower( void ) +{ + // Human objects need power, so show the player if the current position will have power, but don't prevent building. + if ( !CanPowerupEver( POWERUP_POWER ) ) + return; + + // If I have a powerpack, see if I'm unable to keep power, or not needed. + // This is done before checking to see if the object needs power, because it may + // have once needed power, but doesn't anymore (i.e. snapped to an attachment point) + if ( m_hPowerPack ) + { + m_hPowerPack->EnsureObjectPower( this ); + } + + // If I don't have a powerpack, or I just moved too far from it, look for a powerpack + if ( !m_hPowerPack ) + { + GetTFTeam()->UpdatePowerpacks( NULL, this ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseObject::AttemptToFindBuffStation( void ) +{ + // Check to see if this object can be connected to a buff station. + if ( !CanBeHookedToBuffStation() ) + return; + + // We have already found a buff station, we want to use - check distances. + if ( GetBuffStation() ) + { + GetBuffStation()->CheckBuffConnection( this ); + } + // Look for a buff station to use. + else + { + GetTFTeam()->UpdateBuffStations( NULL, this, true ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Move the placement model to the current position. Return false if it's an invalid position +//----------------------------------------------------------------------------- +bool CBaseObject::UpdatePlacement( CBaseTFPlayer *pPlayer ) +{ + bool placementOk = CalculatePlacement( pPlayer ); + if ( placementOk ) + { + SetRenderColor( 255, 255, 255, GetRenderColor().a ); + } + else + { + SetRenderColor( 255, 0, 0, GetRenderColor().a ); + } + + Teleport( &m_vecBuildOrigin, &GetLocalAngles(), NULL ); + + return placementOk; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CBaseObject::PreStartBuilding() +{ + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: Start building the object +//----------------------------------------------------------------------------- +bool CBaseObject::StartBuilding( CBaseEntity *pBuilder ) +{ + // Need to add the object to the team now... + CTFTeam *pTFTeam = ( CTFTeam * )GetGlobalTeam( GetTeamNumber() ); + + // Deduct the cost from the player + if ( pBuilder && pBuilder->IsPlayer() ) + { + m_iAmountPlayerPaidForMe = ((CBaseTFPlayer*)pBuilder)->StartedBuildingObject( m_iObjectType ); + if ( !m_iAmountPlayerPaidForMe ) + { + // Player couldn't afford to pay for me, so abort + ClientPrint( (CBasePlayer*)pBuilder, HUD_PRINTCENTER, "Not enough resources.\n" ); + StopPlacement(); + return false; + } + } + + // Add this object to the team's list (because we couldn't add it during + // placement mode) + if ( pTFTeam && !pTFTeam->IsObjectOnTeam( this ) ) + { + pTFTeam->AddObject( this ); + } + + m_bPlacing = false; + m_bBuilding = true; + SetHealth( OBJECT_CONSTRUCTION_STARTINGHEALTH ); + m_flPercentageConstructed = 0; + + // Compute a good fitting AABB since we know where this thing belongs + if ( VPhysicsGetObject() && !IsBuiltOnAttachment() ) + { + Vector absmins, absmaxs; + physcollision->CollideGetAABB( &absmins, &absmaxs, VPhysicsGetObject()->GetCollide(), GetAbsOrigin(), GetAbsAngles() ); + + // This is required to get the client + server looking the same + // since the client uses the mins to compute absmins + absmaxs + SetCollisionBounds( absmins - GetAbsOrigin(), absmaxs - GetAbsOrigin() ); + } + + m_nRenderMode = kRenderNormal; + RemoveSolidFlags( FSOLID_NOT_SOLID ); + + // NOTE: We must spawn the control panels now, instead of during + // Spawn, because until placement is started, we don't actually know + // the position of the control panel because we don't know what it's + // been attached to (could be a vehicle which supplies a different + // place for the control panel) + // NOTE: We must also spawn it before FinishedBuilding can be called + SpawnControlPanels(); + + // Tell the object we've been built on that we exist + if ( IsBuiltOnAttachment() && ShouldAttachToParent() ) + { + IHasBuildPoints *pBPInterface = dynamic_cast<IHasBuildPoints*>((CBaseEntity*)m_hBuiltOnEntity.Get()); + Assert( pBPInterface ); + pBPInterface->SetObjectOnBuildPoint( m_iBuiltOnPoint, this ); + } + + // Start the build animations + m_flTotalConstructionTime = m_flConstructionTimeLeft = GetTotalTime(); + + if ( pBuilder && pBuilder->IsPlayer() ) + { + ((CBaseTFPlayer*)pBuilder)->FinishedObject( this ); + } + + m_vecBuildOrigin = GetAbsOrigin(); + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: Continue construction of this object +//----------------------------------------------------------------------------- +void CBaseObject::BuildingThink( void ) +{ + // Continue construction + Repair( (GetMaxHealth() - OBJECT_CONSTRUCTION_STARTINGHEALTH) / m_flTotalConstructionTime * OBJECT_CONSTRUCTION_INTERVAL ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseObject::AttemptToActivateBuffStation( void ) +{ + if ( !GetBuffStation() ) + return; + + if ( GetBuffStation()->IsPlacing() || GetBuffStation()->IsBuilding() || + !GetBuffStation()->IsPowered() ) + return; + + if ( m_bBuffActivated ) + return; + + BuffStationActivate(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseObject::SetControlPanelsActive( bool bState ) +{ + // Activate control panel screens + for ( int i = m_hScreens.Count(); --i >= 0; ) + { + if (m_hScreens[i].Get()) + { + m_hScreens[i]->SetActive( bState ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseObject::FinishedBuilding( void ) +{ + SetControlPanelsActive( true ); + + // Only make a shadow if the object doesn't use vphysics + if (!VPhysicsGetObject()) + { + VPhysicsInitStatic(); + } + + m_bBuilding = false; + + AttemptToGoActive(); + AttemptToActivateBuffStation(); + + // We're done building, add in the stat... + TFStats()->IncrementStat( (TFStatId_t)(TF_STAT_FIRST_OBJECT_BUILT + ObjectType()), 1 ); + + // Spawn any objects on this one + SpawnObjectPoints(); + + // Let our vehicle bay know, if we have one + if ( m_hVehicleBay ) + { + m_hVehicleBay->FinishedBuildVehicle( this ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Objects store health in hacky ways +//----------------------------------------------------------------------------- +void CBaseObject::SetHealth( float flHealth ) +{ + if ( IsDisabled() ) + { + if ( ( m_flMinDisableHealth != 0.0f && flHealth > m_flMinDisableHealth ) || + ( flHealth > 1 ) ) + { + // Reenable and fire output + SetDisabled( false ); + + m_OnBecomingReenabled.FireOutput( this, this ); + } + } + + bool changed = m_flHealth != flHealth; + + m_flHealth = flHealth; + m_iHealth = ceil(m_flHealth); + + // If we have a model, and a pose parameter, set the pose parameter to reflect our health + if ( !(m_fObjectFlags & OF_DOESNT_HAVE_A_MODEL) ) + { + if (LookupPoseParameter( "object_health") >= 0 && GetMaxHealth() > 0 ) + { + SetPoseParameter( "object_health", 100 * ( GetHealth() / (float)GetMaxHealth() ) ); + } + } + + if ( changed ) + { + // Set value and fire output + m_OnObjectHealthChanged.Set( m_flHealth, this, this ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Powerup has just started +//----------------------------------------------------------------------------- +void CBaseObject::PowerupStart( int iPowerup, float flAmount, CBaseEntity *pAttacker, CDamageModifier *pDamageModifier ) +{ + switch( iPowerup ) + { + case POWERUP_BOOST: + { + // Can we boost health further? + if ( GetHealth() < GetMaxHealth() ) + { + /* + if ( (gpGlobals->curtime - m_flLastRepairTime) > 0.01 ) + { + Msg("TOTAL REPAIR: %.2f in %.2f\n\n", m_flRepairedSinceLastTime, (gpGlobals->curtime - m_flLastRepairTime) ); + } + + if ( pAttacker->IsPlayer() ) + { + CBaseTFPlayer *pPlayer = (CBaseTFPlayer *)pAttacker; + Msg("(%.2f) %s repaired %s for %.2f health.\n", gpGlobals->curtime, pPlayer->GetPlayerName(), GetClassname(), flAmount ); + } + */ + // Is this repair happening at the same time as other repairing on me? + if ( (gpGlobals->curtime - m_flLastRepairTime) < 0.01 ) + { + //Msg(" ->Reducing repair by %.2f\n", m_flNextRepairMultiplier ); + flAmount *= m_flNextRepairMultiplier; + m_flNextRepairMultiplier *= 0.5; + } + else + { + m_flLastRepairTime = gpGlobals->curtime; + m_flNextRepairMultiplier = 0.5; + m_flRepairedSinceLastTime = 0; + } + + //Msg(" REPAIRED: %.2f\n", flAmount ); + + Repair( flAmount ); + + m_flRepairedSinceLastTime += flAmount; + } + + // Prevent callback to base class, since we handled it here + return; + } + break; + + case POWERUP_POWER: + { + Assert( m_hPowerPack ); + AttemptToGoActive(); + } + break; + + default: + break; + } + + BaseClass::PowerupStart( iPowerup, flAmount, pAttacker, pDamageModifier ); +} + +//----------------------------------------------------------------------------- +// Purpose: Powerup has just started +//----------------------------------------------------------------------------- +void CBaseObject::PowerupEnd( int iPowerup ) +{ + switch( iPowerup ) + { + case POWERUP_POWER: + { + OnGoInactive(); + m_hPowerPack = NULL; + } + break; + + default: + break; + } + + BaseClass::PowerupEnd( iPowerup ); +} + +//----------------------------------------------------------------------------- +// Purpose: Override base traceattack to prevent visible effects from team members shooting me +//----------------------------------------------------------------------------- +void CBaseObject::TraceAttack( const CTakeDamageInfo &inputInfo, const Vector &vecDir, trace_t *ptr ) +{ + // Prevent team damage here so blood doesn't appear + if ( inputInfo.GetAttacker() ) + { + if ( InSameTeam(inputInfo.GetAttacker()) ) + return; + } + + float fVulnerableMultiplier = FindVulnerablePointMultiplier( ptr->hitgroup, ptr->hitbox ); + + CTakeDamageInfo info = inputInfo; + info.ScaleDamage( fVulnerableMultiplier ); + + SpawnBlood( ptr->endpos, vecDir, BloodColor(), info.GetDamage() ); + AddMultiDamage( info, this ); +} + +//----------------------------------------------------------------------------- +// Purpose: Prevent Team Damage +//----------------------------------------------------------------------------- +ConVar object_show_damage( "obj_show_damage", "0", 0, "Show all damage taken by objects." ); +ConVar object_capture_damage( "obj_capture_damage", "0", 0, "Captures all damage taken by objects for dumping later." ); + +CUtlDict<int,int> g_DamageMap; + +void Cmd_DamageDump_f(void) +{ + CUtlDict<bool,int> g_UniqueColumns; + + // Build the unique columns: + for( int idx = g_DamageMap.First(); idx != g_DamageMap.InvalidIndex(); idx = g_DamageMap.Next(idx) ) + { + char* szColumnName = strchr(g_DamageMap.GetElementName(idx),',') + 1; + + int ColumnIdx = g_UniqueColumns.Find( szColumnName ); + + if( ColumnIdx == g_UniqueColumns.InvalidIndex() ) + { + g_UniqueColumns.Insert( szColumnName, false ); + } + } + + // Dump the column names: + FileHandle_t f = filesystem->Open("damage.txt","wt+"); + + for( idx = g_UniqueColumns.First(); idx != g_UniqueColumns.InvalidIndex(); idx = g_UniqueColumns.Next(idx) ) + { + filesystem->FPrintf(f,"\t%s",g_UniqueColumns.GetElementName(idx)); + } + + filesystem->FPrintf(f,"\n"); + + + CUtlDict<bool,int> g_CompletedRows; + + // Dump each row: + bool bDidRow; + + do + { + bDidRow = false; + + for( idx = g_DamageMap.First(); idx != g_DamageMap.InvalidIndex(); idx = g_DamageMap.Next(idx) ) + { + char szRowName[256]; + + // Check the Row name of each entry to see if I've done this row yet. + Q_strncpy(szRowName, g_DamageMap.GetElementName(idx), sizeof( szRowName ) ); + *strchr(szRowName,',') = '\0'; + + char szRowNameComma[256]; + Q_snprintf( szRowNameComma, sizeof( szRowNameComma ), "%s,", szRowName ); + + if( g_CompletedRows.Find(szRowName) == g_CompletedRows.InvalidIndex() ) + { + bDidRow = true; + g_CompletedRows.Insert(szRowName,false); + + + // Output the row name: + filesystem->FPrintf(f,szRowName); + + for( int ColumnIdx = g_UniqueColumns.First(); ColumnIdx != g_UniqueColumns.InvalidIndex(); ColumnIdx = g_UniqueColumns.Next( ColumnIdx ) ) + { + char szRowNameCommaColumn[256]; + Q_strncpy( szRowNameCommaColumn, szRowNameComma, sizeof( szRowNameCommaColumn ) ); + Q_strncat( szRowNameCommaColumn, g_UniqueColumns.GetElementName( ColumnIdx ), sizeof( szRowNameCommaColumn ), COPY_ALL_CHARACTERS ); + + int nDamageAmount = 0; + // Fine to reuse idx since we are going to break anyways. + for( idx = g_DamageMap.First(); idx != g_DamageMap.InvalidIndex(); idx = g_DamageMap.Next(idx) ) + { + if( !stricmp( g_DamageMap.GetElementName(idx), szRowNameCommaColumn ) ) + { + nDamageAmount = g_DamageMap[idx]; + break; + } + } + + filesystem->FPrintf(f,"\t%i",nDamageAmount); + + } + + filesystem->FPrintf(f,"\n"); + break; + } + } + // Grab the row name: + + } while(bDidRow); + + // close the file: + filesystem->Close(f); +} + +static ConCommand obj_dump_damage( "obj_dump_damage", Cmd_DamageDump_f ); + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void ReportDamage( const char* szInflictor, const char* szVictim, float fAmount, int nCurrent, int nMax ) +{ + int iAmount = (int)fAmount; + + if( object_show_damage.GetBool() && iAmount ) + { + Msg( "ShowDamage: Object %s taking %0.1f damage from %s ( %i / %i )\n", szVictim, fAmount, szInflictor, nCurrent, nMax ); + } + + if( object_capture_damage.GetBool() ) + { + char szMangledKey[256]; + + Q_snprintf(szMangledKey,sizeof(szMangledKey)/sizeof(szMangledKey[0]),"%s,%s",szInflictor,szVictim); + int idx = g_DamageMap.Find( szMangledKey ); + + if( idx == g_DamageMap.InvalidIndex() ) + { + g_DamageMap.Insert( szMangledKey, iAmount ); + + } else + { + g_DamageMap[idx] += iAmount; + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Pass the specified amount of damage through to any objects I have built on me +//----------------------------------------------------------------------------- +bool CBaseObject::PassDamageOntoChildren( const CTakeDamageInfo &info, float *flDamageLeftOver ) +{ + IHasBuildPoints *pBPInterface = dynamic_cast<IHasBuildPoints*>(this); + Assert( pBPInterface ); + + float flDamage = info.GetDamage(); + + // Double the amount of damage done (and get around the child damage modifier) + flDamage *= 2; + if ( obj_child_damage_factor.GetFloat() ) + { + flDamage *= (1 / obj_child_damage_factor.GetFloat()); + } + + // Remove blast damage because child objects (well specifically upgrades) + // want to ignore direct blast damage but still take damage from parent + CTakeDamageInfo childInfo = info; + childInfo.SetDamage( flDamage ); + childInfo.SetDamageType( info.GetDamageType() & (~DMG_BLAST) ); + + CBaseEntity *pEntity = pBPInterface->GetFirstObjectOnMe(); + while ( pEntity ) + { + Assert( pEntity->m_takedamage != DAMAGE_NO ); + // Do damage to the next object + float flDamageTaken = pEntity->OnTakeDamage( childInfo ); + // If we didn't kill it, abort + CBaseObject *pObject = dynamic_cast<CBaseObject*>(pEntity); + if ( !pObject || !pObject->IsDying() ) + { + char* szInflictor = "unknown"; + if( info.GetInflictor() ) + szInflictor = (char*)info.GetInflictor()->GetClassname(); + + ReportDamage( szInflictor, GetClassname(), flDamageTaken, GetHealth(), GetMaxHealth() ); + + *flDamageLeftOver = flDamage; + return true; + } + // Reduce the damage and move on to the next + flDamage -= flDamageTaken; + pEntity = pBPInterface->GetFirstObjectOnMe(); + } + + *flDamageLeftOver = flDamage; + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int CBaseObject::OnTakeDamage( const CTakeDamageInfo &info ) +{ + // Prevent damage if the game hasn't started yet + if ( CurrentActIsAWaitingAct() ) + return 0; + if ( !IsAlive() ) + return info.GetDamage(); + if (m_bInvulnerable) + return 0; + if ( m_takedamage == DAMAGE_NO ) + return 0; + if ( IsPlacing() ) + return 0; + + // Check teams + if ( info.GetAttacker() ) + { + if ( InSameTeam(info.GetAttacker()) ) + return 0; + } + + IHasBuildPoints *pBPInterface = dynamic_cast<IHasBuildPoints*>(this); + + float flDamage = info.GetDamage(); + + // Objects take half damage from bullets + if ( info.GetDamageType() & DMG_BULLET ) + { + flDamage *= 0.5; + } + + // Objects build on other objects take less damage + if ( !IsAnUpgrade() && GetParentObject() ) + { + flDamage *= obj_child_damage_factor.GetFloat(); + } + + if (obj_damage_factor.GetFloat()) + { + flDamage *= obj_damage_factor.GetFloat(); + } + + // Constructing objects take extra damage + if ( IsBuilding() ) + { + flDamage *= 3; + } + + // If has min health, and damage would put it below min health disable it if not already disabled + bool bShouldBeDisabled = false; + if ( m_flMinDisableHealth != 0 && ( m_flHealth - flDamage ) < m_flMinDisableHealth ) + { + bShouldBeDisabled = true; + } + else if ( pBPInterface && pBPInterface->GetNumObjectsOnMe() && (( m_flHealth - flDamage ) < 1) ) + { + bShouldBeDisabled = true; + } + + // Make sure we're disabled if we're supposed to be + if ( bShouldBeDisabled ) + { + // Remove any sappers on me + if ( m_bCantDie ) + { + RemoveAllSappers( this ); + } + + // Make sure this only fires first time we cross the threshold and go disabled + if ( !IsDisabled() ) + { + SetDisabled( true ); + m_OnBecomingDisabled.FireOutput( info.GetAttacker(), this ); + + // Special case: If we have a min disabled health, and we're set to not die, immediately fall to 1 health + if ( m_bCantDie ) + { + SetHealth( 1 ); + } + } + } + + // If I have objects on me, I can't be destroyed until they're gone. Ditto if I can't be killed. + bool bWillDieButCant = (m_bCantDie || pBPInterface->GetNumObjectsOnMe()) && (( m_flHealth - flDamage ) < 1); + if ( bWillDieButCant ) + { + // Soak up the damage it would take to drop us to 1 health + flDamage = flDamage - m_flHealth; + SetHealth( 1 ); + + // Pass leftover damage + if ( flDamage ) + { + if ( PassDamageOntoChildren( info, &flDamage ) ) + return flDamage; + } + } + + if ( flDamage ) + { + // Recheck our death possibility, because our objects may have all been blown off us by now + bWillDieButCant = (m_bCantDie || pBPInterface->GetNumObjectsOnMe()) && (( m_flHealth - flDamage ) < 1); + if ( !bWillDieButCant ) + { + // Reduce health + SetHealth( m_flHealth - flDamage ); + } + } + + m_OnDamaged.FireOutput(info.GetAttacker(), this); + + // Hurt by an enemy? + if ( info.GetAttacker() && info.GetAttacker()->entindex() > 0 ) + { + m_flLastRealDamage = gpGlobals->curtime; + } + + if ( GetHealth() <= 0 ) + { + if ( info.GetAttacker() ) + { + TFStats()->IncrementTeamStat( info.GetAttacker()->GetTeamNumber(), TF_TEAM_STAT_DESTROYED_OBJECT_COUNT, 1 ); + TFStats()->IncrementPlayerStat( info.GetAttacker(), TF_PLAYER_STAT_DESTROYED_OBJECT_COUNT, 1 ); + } + + m_lifeState = LIFE_DEAD; + m_OnDestroyed.FireOutput( info.GetAttacker(), this); + Killed(); + } + else + { + // Notify team about interesting stuff going on with this object + if ( !(m_fObjectFlags & OF_SUPPRESS_NOTIFY_UNDER_ATTACK) && ( m_iszUnderAttackSound != NULL_STRING ) ) + { + CTFTeam *pTeam = GetTFTeam(); + if ( pTeam ) + { + Vector vecPosition = GetAbsOrigin(); + + // Tell everyone on the team that this object's underattack + CRecipientFilter myteam; + myteam.MakeReliable(); + myteam.AddRecipientsByTeam( pTeam ); + UserMessageBegin( myteam, "MinimapPulse" ); + WRITE_VEC3COORD( vecPosition ); + MessageEnd(); + + GetTFTeam()->PostMessage( TEAMMSG_CUSTOM_SOUND, NULL, (char*)STRING(m_iszUnderAttackSound) ); + } + } + } + + { + char* szInflictor = "unknown"; + if( info.GetInflictor() ) + szInflictor = (char*)info.GetInflictor()->GetClassname(); + + ReportDamage( szInflictor, GetClassname(), flDamage, GetHealth(), GetMaxHealth() ); + } + + return flDamage; +} + +//----------------------------------------------------------------------------- +// Purpose: Get the time it will take to repair this object +//----------------------------------------------------------------------------- +float CBaseObject::GetRepairTime( void ) +{ + // Can't be repaired while being constructed + if ( IsBuilding() ) + return 0; + + int iRepairHealth = GetMaxHealth() - GetHealth(); + if ( iRepairHealth ) + { + return ((float)iRepairHealth / OBJECT_REPAIR_RATE); + } + + return 0; +} + +//----------------------------------------------------------------------------- +// Purpose: Repair myself for the time period passed in. Return true if I'm fully repaired. +//----------------------------------------------------------------------------- +bool CBaseObject::UpdateRepair( float flRepairTime ) +{ + return Repair( (flRepairTime * OBJECT_REPAIR_RATE) ); +} + +//----------------------------------------------------------------------------- +// Purpose: Repair / Help-Construct this object the specified amount +//----------------------------------------------------------------------------- +bool CBaseObject::Repair( float flHealth ) +{ + // Multiply it by the repair rate + flHealth *= m_flRepairMultiplier; + if ( !flHealth ) + return false; + + if ( IsBuilding() ) + { + if ( HasPowerup(POWERUP_EMP) ) + return false; + + // Reduce the construction time by the correct amount for the health passed in + float flConstructionTime = flHealth / ((GetMaxHealth() - OBJECT_CONSTRUCTION_STARTINGHEALTH) / m_flTotalConstructionTime); + m_flConstructionTimeLeft = MAX( 0, m_flConstructionTimeLeft - flConstructionTime); + m_flConstructionTimeLeft = clamp( m_flConstructionTimeLeft, 0.0f, m_flTotalConstructionTime ); + m_flPercentageConstructed = 1 - (m_flConstructionTimeLeft / m_flTotalConstructionTime); + m_flPercentageConstructed = clamp( m_flPercentageConstructed, 0.0f, 1.0f ); + + // Increase health. + SetHealth( MIN( GetMaxHealth(), m_flHealth + flHealth ) ); + + // Return true if we're constructed now + if ( m_flConstructionTimeLeft <= 0.0f ) + { + FinishedBuilding(); + return true; + } + } + else + { + // Return true if we're already fully healed + if ( GetHealth() >= GetMaxHealth() ) + return true; + + // Increase health. + SetHealth( MIN( GetMaxHealth(), m_flHealth + flHealth ) ); + + m_OnRepaired.FireOutput( this, this); + + // Return true if we're fully healed now + if ( GetHealth() == GetMaxHealth() ) + return true; + } + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: Object has been blown up. Drop resource chunks upto the value of my max health. +//----------------------------------------------------------------------------- +void CBaseObject::Killed( void ) +{ + m_bDying = true; + + // Do an explosion. + CPASFilter filter( GetAbsOrigin() ); + te->Explosion( + filter, + 0.0, + &GetAbsOrigin(), + g_sModelIndexFireball, + 5.4, // radius + 15, + TE_EXPLFLAG_NODLIGHTS, + 256, + 200); + + Vector vecOrigin = WorldSpaceCenter() + Vector(0,0,32); + + bool bDropResources = true; + + // Don't drop resources if I'm built out of brushes, or I'm an upgrade + if ( m_fObjectFlags & OF_DOESNT_HAVE_A_MODEL || IsAnUpgrade() ) + { + bDropResources = false; + } + + // Don't drop resources if I haven't taken damage from an enemy for a while (i.e. I've deteriorated instead) + if ( gpGlobals->curtime > (m_flLastRealDamage + MAX_DROP_TIME_AFTER_DAMAGE) ) + { + bDropResources = false; + } + + // Drop resources based upon our base cost + int iCost = CalculateObjectCost( GetType(), 0, GetTeamNumber() ); + iCost *= 0.5; + if ( bDropResources && iCost ) + { + // Convert value to chunks. + int nProcessedChunks = 0; + int nNormalChunks = 0; + ConvertResourceValueToChunks( iCost, &nProcessedChunks, &nNormalChunks ); + + // Make everything drop at least 1 chunk + if ( !nProcessedChunks && !nNormalChunks ) + { + nNormalChunks++; + } + + // Drop processed chunks. + int iChunk; + for ( iChunk = 0; iChunk < nProcessedChunks; iChunk++ ) + { + // Generate a random velocity vector. + Vector vecVelocity = Vector( random->RandomFloat( -250,250 ), random->RandomFloat( -250,250 ), random->RandomFloat( 200,450 ) ); + + // Create a processed chunk. + CResourceChunk *pChunk = CResourceChunk::Create( true, vecOrigin, vecVelocity ); + pChunk->ChangeTeam( GetTeamNumber() ); + } + + // Drop normal chunks + for ( iChunk = 0; iChunk < nNormalChunks; iChunk++ ) + { + // Generate a random velocity vector. + Vector vecVelocity = Vector( random->RandomFloat( -250,250 ), random->RandomFloat( -250,250 ), random->RandomFloat( 200,450 ) ); + + // Create a processed chunk. + CResourceChunk *pChunk = CResourceChunk::Create( false, vecOrigin, vecVelocity ); + pChunk->ChangeTeam( GetTeamNumber() ); + } + + TFStats()->IncrementTeamStat( GetTeamNumber(), TF_TEAM_STAT_RESOURCE_CHUNKS_DROPPED, (resource_chunk_processed_value.GetFloat() * nProcessedChunks) + (resource_chunk_value.GetFloat() * nNormalChunks) ); + } + + DetachObjectFromObject(); + + UTIL_Remove( this ); +} + +//----------------------------------------------------------------------------- +// Purpose: Indicates this NPC's place in the relationship table. +//----------------------------------------------------------------------------- +Class_T CBaseObject::Classify( void ) +{ + return (CLASS_MILITARY); +} + +//----------------------------------------------------------------------------- +// Purpose: Get the type of this object +//----------------------------------------------------------------------------- +int CBaseObject::GetType() +{ + return m_iObjectType; +} + +//----------------------------------------------------------------------------- +// Purpose: Get the builder of this object +//----------------------------------------------------------------------------- +CBaseTFPlayer *CBaseObject::GetBuilder( void ) +{ + return m_hBuilder; +} + +//----------------------------------------------------------------------------- +// Purpose: Get the original builder of this object +// Used to get the builder of a deteriorating object +//----------------------------------------------------------------------------- +CBaseTFPlayer *CBaseObject::GetOriginalBuilder( void ) +{ + return m_hOriginalBuilder; +} + +//----------------------------------------------------------------------------- +// Purpose: Return true if the Owning CTeam should clean this object up automatically +//----------------------------------------------------------------------------- +bool CBaseObject::ShouldAutoRemove( void ) +{ + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: If the object's still being built, it's not usable +//----------------------------------------------------------------------------- +int CBaseObject::ObjectCaps( void ) +{ + if ( IsPlacing() ) + return 0; + + // If I'm being built, only allow +use if I don't have a sapper on me and I haven't been disabled by a plasma weapon + if ( IsBuilding() && !HasSapper() && !IsPlasmaDisabled() ) + return 0; + + return FCAP_ONOFF_USE; +}; + +//----------------------------------------------------------------------------- +// Clean off the object of offensive material... +//----------------------------------------------------------------------------- +bool CBaseObject::RemoveEnemyAttachments( CBaseEntity *pActivator ) +{ + bool bRemoved = false; + + // Sapper removal + if ( pActivator->IsPlayer() ) + { + if ( HasSapper() ) + { + RemoveAllSappers( pActivator ); + bRemoved = true; + } + } + + return bRemoved; +} + + +//----------------------------------------------------------------------------- +// Object using! +//----------------------------------------------------------------------------- +void CBaseObject::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + // If we're friendly, pickup / remove sappers + // If we're an enemy, plant a sapper + if ( pActivator->IsPlayer() ) + { + if ( InSameTeam( pActivator ) ) + { + if ( useType == USE_ON ) + { + // Some combat objects can be picked up + if ( m_fObjectFlags & OF_CAN_BE_PICKED_UP ) + { + if ( GetBuilder() == pActivator ) + { + if ( GetBuilder()->GetPlayerClass()->ResupplyAmmoType( 1, m_szAmmoName ) ) + { + PickupObject(); + } + return; + } + } + + // Sapper removal + if ( RemoveEnemyAttachments( pActivator ) ) + return; + } + } + else + { + CBaseTFPlayer *pPlayer = (CBaseTFPlayer *)pActivator; + + // If we're already planting a sapper, abort + if ( useType == USE_OFF || pPlayer->IsAttachingSapper() ) + { + // Don't abort if we just started placing it. This is to catch people who'd like to +use toggle instead of hold down + if ( pPlayer->GetSapperAttachmentTime() > 0.2 && pPlayer->IsAttachingSapper() ) + { + pPlayer->StopAttaching(); + } + } + else if ( useType == USE_ON ) + { + // Don't allow sappers to be planted on invulnerable objects + if ( m_bInvulnerable ) + return; + + // If the object's already got a sapper from me on it, I can't put another + if ( HasSapperFromPlayer( ((CBaseTFPlayer*)pActivator ) ) ) + return; + + Vector vecAiming; + pPlayer->EyeVectors( &vecAiming ); + // Trace from the player to the object to find an attachment position + trace_t tr; + Vector vecStart = pPlayer->EyePosition(); + UTIL_TraceLine( vecStart, vecStart + (vecAiming * 256), MASK_SOLID, pPlayer, COLLISION_GROUP_NONE, &tr ); + if ( tr.fraction < 1.0 && tr.m_pEnt == this ) + { + CGrenadeObjectSapper *sapper = CGrenadeObjectSapper::Create( tr.endpos, vecAiming, pPlayer, this ); + pPlayer->StartAttachingSapper( this, sapper ); + } + } + } + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Builder has picked up the object +//----------------------------------------------------------------------------- +void CBaseObject::PickupObject( void ) +{ + // Tell the playerclass + if ( GetBuilder() && GetBuilder()->GetPlayerClass() ) + { + GetBuilder()->GetPlayerClass()->PickupObject( this ); + } + + UTIL_Remove( this ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Return true if the specified player's allowed to remove this object +//----------------------------------------------------------------------------- +bool CBaseObject::CanBeRemovedBy( CBaseTFPlayer *pPlayer ) +{ + if ( m_fObjectFlags & OF_CANNOT_BE_DISMANTLED ) + return false; + + // If I'm a map-defined object, I'm not removable by anyone + if ( WasMapPlaced() ) + return false; + + // If I have an owner, only he can remove me + if ( GetBuilder() != pPlayer ) + return false; + + return true; +} + + +//----------------------------------------------------------------------------- +// Purpose: Return true if the specified player's allowed to rotate this object +//----------------------------------------------------------------------------- +bool CBaseObject::CanBeRotatedBy( CBaseTFPlayer *pPlayer ) +{ + // If I'm a map-defined object, I'm not removable by anyone + if ( WasMapPlaced() ) + return false; + + // If I have an owner, only he can remove me + if ( GetBuilder() != pPlayer ) + return false; + + return true; +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : iTeamNum - +//----------------------------------------------------------------------------- +void CBaseObject::ChangeTeam( int iTeamNum ) +{ + CTFTeam *pTeam = ( CTFTeam * )GetGlobalTeam( iTeamNum ); + CTFTeam *pExisting = ( CTFTeam * )GetTeam(); + + TRACE_OBJECT( UTIL_VarArgs( "%0.2f CBaseObject::ChangeTeam old %s new %s\n", gpGlobals->curtime, + pExisting ? pExisting->GetName() : "NULL", + pTeam ? pTeam->GetName() : "NULL" ) ); + + // Already on this team + if ( GetTeamNumber() == iTeamNum ) + return; + + if ( pExisting ) + { + // Remove it from current team ( if it's in one ) and give it to new team + pExisting->RemoveObject( this ); + } + + // Change to new team + BaseClass::ChangeTeam( iTeamNum ); + + // Add this object to the team's list + // But only if we're not placing it + if ( pTeam && (!m_bPlacing) ) + { + pTeam->AddObject( this ); + } + + // Setup for our new team's model + SetupTeamModel(); + CreateBuildPoints(); + CreateVulnerablePoints(); + + // Alien buildings never need power + if ( GetTeamNumber() == TEAM_ALIENS ) + { + m_fObjectFlags |= OF_DOESNT_NEED_POWER; + } + + GainedNewTechnology( NULL ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : const char +//----------------------------------------------------------------------------- +const char *CBaseObject::GetWeaponClassnameForObject( void ) +{ + return NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pNewOwner - +//----------------------------------------------------------------------------- +void CBaseObject::AddItemsNeededForObject( CBaseTFPlayer *pNewOwner ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: Derived classes might want to give the new builder the appropriate +// items needed to own this object and move the objects owned over as well +// Input : *pNewOwner - +//----------------------------------------------------------------------------- +void CBaseObject::ChangeBuilder( CBaseTFPlayer *pNewBuilder, bool moveobjects ) +{ + CBaseTFPlayer *oldBuilder = GetOwner(); + + TRACE_OBJECT( UTIL_VarArgs( "%0.2f CBaseObject::ChangeBuilder old %s, new %s, moveobjects %s\n", gpGlobals->curtime, + oldBuilder ? oldBuilder->GetPlayerName() : "NULL", + pNewBuilder ? pNewBuilder->GetPlayerName() : "NULL", + moveobjects ? "true" : "false" ) ); + + // Store off original builder + if ( GetOwner() ) + { + m_hOriginalBuilder = GetOwner(); + } + + m_hBuilder = pNewBuilder; + + if ( !moveobjects ) + return; + + if ( oldBuilder ) + { + oldBuilder->OwnedObjectChangeTeam( this, pNewBuilder ); + } + + // For instance, if this is a mortar being added to a technician via subversion, then + // the "weapon_mortar" will be added to the player if the player doesn't have it. + AddItemsNeededForObject( pNewBuilder ); + + const char *classname = GetWeaponClassnameForObject(); + if ( !classname ) + return; + + TRACE_OBJECT( UTIL_VarArgs( "%0.2f CBaseObject::ChangeBuilder moving associated objects %s\n", gpGlobals->curtime, + classname ) ); + + // Find the old player who owned a weapon that owned this object type and remove it + // Then add to current player under the approrpriate weapon ( same classname ) which + // should have been added in ChangeBuilder + for ( int i = 1; i <= gpGlobals->maxClients; i++ ) + { + CBaseTFPlayer *player = static_cast< CBaseTFPlayer *>( UTIL_PlayerByIndex( i ) ); + if ( !player ) + continue; + + // Cycle through weapons + for ( int j = 0; j < player->WeaponCount(); j++ ) + { + if ( !player->GetWeapon( j ) ) + continue; + + if ( !FClassnameIs( player->GetWeapon( j ), classname ) ) + continue; + + // Add to this player + if ( player == pNewBuilder ) + { + ( ( CBaseTFCombatWeapon * )player->GetWeapon( j ))->AddAssociatedObject( this ); + } + // Remove from any other + else + { + ( ( CBaseTFCombatWeapon * )player->GetWeapon( j ))->RemoveAssociatedObject( this ); + } + } + + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Return true if I have at least 1 sapper on me +//----------------------------------------------------------------------------- +bool CBaseObject::HasSapper( void ) +{ + return ( m_hSappers.Size() > 0 ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Return true if the specified player has attached a sapper to me +//----------------------------------------------------------------------------- +bool CBaseObject::HasSapperFromPlayer( CBaseTFPlayer *pPlayer ) +{ + for ( int i = 0; i < m_hSappers.Size(); i++ ) + { + if ( m_hSappers[i] == NULL ) + continue; + + if ( m_hSappers[i]->GetThrower() == pPlayer ) + return true; + } + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: Add a sapper to this object +//----------------------------------------------------------------------------- +void CBaseObject::AddSapper( CGrenadeObjectSapper *pSapper ) +{ + SapperHandle hSapper; + hSapper = pSapper; + m_hSappers.AddToTail( hSapper ); + m_bHasSapper = true; +} + +//----------------------------------------------------------------------------- +// Purpose: Tell all sappers on this object to remove themselves +//----------------------------------------------------------------------------- +void CBaseObject::RemoveAllSappers( CBaseEntity *pRemovingEntity ) +{ + // Loop through all the sappers and fire a +use on them (backwards because list will change) + int iSize = m_hSappers.Size(); + for (int i = iSize-1; i >= 0; i--) + { + m_hSappers[i]->Use( pRemovingEntity, pRemovingEntity, USE_TOGGLE, 0 ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Remove a sapper from this object +//----------------------------------------------------------------------------- +void CBaseObject::RemoveSapper( CGrenadeObjectSapper *pSapper ) +{ + SapperHandle hSapper; + hSapper = pSapper; + m_hSappers.FindAndRemove( hSapper ); + m_bHasSapper = HasSapper(); +} + +//----------------------------------------------------------------------------- +// Purpose: My owner's just received a new technology, see if it affects me +//----------------------------------------------------------------------------- +void CBaseObject::GainedNewTechnology( CBaseTechnology *pTechnology ) +{ + // Base object doesn't respond to tech +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pRecipient - +// *techname - +//----------------------------------------------------------------------------- +void CBaseObject::GiveNamedTechnology( CBaseTFPlayer *pRecipient, const char *techname ) +{ + CTFTeam *team = static_cast< CTFTeam * >( pRecipient->GetTeam() ); + if ( !team ) + return; + + CBaseTechnology *tech = team->m_pTechnologyTree->GetTechnology( techname ); + if ( tech ) + { + team->EnableTechnology( tech, true ); + } +} + + +bool CBaseObject::ShowVGUIScreen( int panelIndex, bool bShow ) +{ + Assert( panelIndex >= 0 && panelIndex < m_hScreens.Count() ); + if ( m_hScreens[panelIndex].Get() ) + { + m_hScreens[panelIndex]->SetActive( bShow ); + return true; + } + else + { + return false; + } +} + +//----------------------------------------------------------------------------- +// Purpose: Returns true if this object was placed in the map, not built by a player +//----------------------------------------------------------------------------- +bool CBaseObject::WasMapPlaced( void ) +{ + return m_bWasMapPlaced; +} + +//----------------------------------------------------------------------------- +// Purpose: Find nearby objects on my team and connect to them +//----------------------------------------------------------------------------- +CRopeKeyframe *CBaseObject::ConnectCableTo( CBaseObject *pObject, int iLocalAttachment, int iTargetAttachment ) +{ + // Connect to it + CRopeKeyframe *pRope = CRopeKeyframe::Create( this, pObject, iLocalAttachment, iTargetAttachment ); + if ( pRope ) + { + pRope->m_Width = 3; + pRope->m_nSegments = ROPE_MAX_SEGMENTS; + //pRope->m_RopeFlags |= (ROPE_RESIZE | ROPE_COLLIDE); + pRope->EnableCollision(); + pRope->EnableWind( false ); + pRope->SetupHangDistance( ROPE_HANG_DIST ); + pRope->ActivateStartDirectionConstraints( true ); + pRope->ActivateEndDirectionConstraints( true ); + } + + // Add the rope to both Object's lists + CHandle< CRopeKeyframe > hHandle; + hHandle = pRope; + m_aRopes.AddToTail( hHandle ); + pObject->m_aRopes.AddToTail( hHandle ); + + // During placement, the rules for whether the rope is transmitted or not are + // tricky, so we make a proxy here to control it. + if ( IsPlacing() || pObject->IsPlacing() ) + { + CObjectRopeTransmitProxy *pProxy = new CObjectRopeTransmitProxy( pRope ); + pProxy->m_hObj1 = this; + pProxy->m_hObj2 = pObject; + // pRope->NetworkProp()->SetTransmitProxy( pProxy ); TODO + } + + return pRope; +} + +//----------------------------------------------------------------------------- +// Purpose: Return true if I have a cable to the specified object +//----------------------------------------------------------------------------- +bool CBaseObject::HasCableTo( CBaseObject *pObject ) +{ + for (int i = 0; i < m_aRopes.Size(); i++) + { + CHandle< CRopeKeyframe > hHandle; + hHandle = m_aRopes[i]; + if ( hHandle ) + { + if ( m_aRopes[i]->GetEndPoint() == pObject ) + return true; + } + } + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: Return an attachment point for a cable +//----------------------------------------------------------------------------- +int CBaseObject::GetCableAttachment( void ) +{ + Vector vecOrigin, vecAngles; + // If I already have a rope attached, try and use a different attachment point + if ( m_aRopes.Size() ) + { + // First, check to see if we've lost any ropes (this can happen because + // the other object it was attached to has been destroyed. + int iSize = m_aRopes.Size(); + for (int i = iSize-1; i >= 0; i--) + { + CHandle< CRopeKeyframe > hHandle; + hHandle = m_aRopes[i]; + if ( hHandle == NULL ) + { + m_aRopes.Remove(i); + } + } + + // If I have enough connections, tell 'em I don't want no more + if ( m_aRopes.Size() >= MAX_CABLE_CONNECTIONS ) + return -1; + + char sAttachment[32]; + Q_snprintf( sAttachment,sizeof(sAttachment), "cablepoint%d", m_aRopes.Size() + 1 ); + int iPoint = LookupAttachment( sAttachment ); + if ( iPoint > 0 ) + return iPoint; + } + + + return LookupAttachment( "cablepoint1" ); +} + + +//==================================================================================================================== +// POWER PACKS +//==================================================================================================================== +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseObject::SetPowerPack( CObjectPowerPack *pPack ) +{ + bool bHadPower = HasPowerup( POWERUP_POWER ); + CObjectPowerPack *pOldPack = m_hPowerPack; + + m_hPowerPack = pPack; + + // If it's placing, I don't get power yet + if ( m_hPowerPack && !m_hPowerPack->IsPlacing() ) + { + SetPowerup( POWERUP_POWER, true ); + } + else + { + // Lose power in a second, to give any nearby powerpacks time to connect to me and replace the power + if ( bHadPower ) + { + SetContextThink( LostPowerThink, gpGlobals->curtime + 1.0, OBJ_LOSTPOWER_THINK_CONTEXT ); + if ( GetTFTeam() ) + { + // Dirty hack to make powerpack think I need power + m_iPowerups &= ~(1 << POWERUP_POWER); + GetTFTeam()->UpdatePowerpacks( pOldPack, this ); + } + } + else + { + SetPowerup( POWERUP_POWER, false ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: We've lost power fully +//----------------------------------------------------------------------------- +void CBaseObject::LostPowerThink( void ) +{ + // We may have found another powerpack + if ( !m_hPowerPack ) + { + // Dirty hack to get our powerup removed properly + m_iPowerups |= (1 << POWERUP_POWER); + SetPowerup( POWERUP_POWER, false ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Set the health of the object +//----------------------------------------------------------------------------- +void CBaseObject::InputSetHealth( inputdata_t &inputdata ) +{ + m_iMaxHealth = inputdata.value.Int(); + SetHealth( m_iMaxHealth ); +} + +//----------------------------------------------------------------------------- +// Purpose: Add health to the object +//----------------------------------------------------------------------------- +void CBaseObject::InputAddHealth( inputdata_t &inputdata ) +{ + int iHealth = inputdata.value.Int(); + SetHealth( MIN( GetMaxHealth(), m_flHealth + iHealth ) ); +} + +//----------------------------------------------------------------------------- +// Purpose: Remove health from the object +//----------------------------------------------------------------------------- +void CBaseObject::InputRemoveHealth( inputdata_t &inputdata ) +{ + int iDamage = inputdata.value.Int(); + + SetHealth( m_flHealth - iDamage ); + if ( GetHealth() <= 0 ) + { + m_lifeState = LIFE_DEAD; + m_OnDestroyed.FireOutput(this, this); + Killed(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : &inputdata - +//----------------------------------------------------------------------------- +void CBaseObject::InputSetMinDisabledHealth( inputdata_t &inputdata ) +{ + float minhealth = inputdata.value.Float(); + + bool wasdisabled = IsDisabled(); + + if ( m_flHealth < minhealth ) + { + SetDisabled( true ); + // NOTE: This could theoretically add health, sigh. + SetHealth( minhealth ); + + // Disable it if not already disabled + if ( !wasdisabled ) + { + m_OnBecomingDisabled.FireOutput( inputdata.pActivator, this ); + } + } + else if ( wasdisabled && ( m_flHealth > minhealth ) ) + { + SetDisabled( false ); + + m_OnBecomingReenabled.FireOutput( inputdata.pActivator, this ); + } + + m_flMinDisableHealth = minhealth; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : &inputdata - +//----------------------------------------------------------------------------- +void CBaseObject::InputSetSolidToPlayer( inputdata_t &inputdata ) +{ + int ival = inputdata.value.Int(); + ival = clamp( ival, (int)SOLID_TO_PLAYER_USE_DEFAULT, (int)SOLID_TO_PLAYER_NO ); + OBJSOLIDTYPE stp = (OBJSOLIDTYPE)ival; + SetSolidToPlayers( stp ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseObject::PlayStartupAnimation( void ) +{ + SetActivity( ACT_OBJ_STARTUP ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseObject::DetermineAnimation( void ) +{ + Activity desiredActivity = m_Activity; + + switch ( m_Activity ) + { + default: + { + if ( IsPlacing() ) + { + desiredActivity = ACT_OBJ_PLACING; + } + else if ( IsBuilding() ) + { + desiredActivity = ACT_OBJ_ASSEMBLING; + } + /* + TODO: + ACT_OBJ_DISMANTLING; + ACT_OBJ_DETERIORATING; + */ + else + { + desiredActivity = ACT_OBJ_RUNNING; + } + } + break; + case ACT_OBJ_STARTUP: + { + if ( IsActivityFinished() ) + { + desiredActivity = ACT_OBJ_RUNNING; + } + } + break; + } + + if ( desiredActivity == m_Activity ) + return; + + SetActivity( desiredActivity ); +} + +//----------------------------------------------------------------------------- +// Purpose: Attach this object to the specified object +//----------------------------------------------------------------------------- + +void CBaseObject::AttachObjectToObject( const CBaseEntity *pEntity, int iPoint, Vector &vecOrigin ) +{ + SetupAttachedVersion(); + + m_hBuiltOnEntity = pEntity; + m_iBuiltOnPoint = iPoint; + + if ( m_hBuiltOnEntity.Get() ) + { + // Parent ourselves to the object + int iAttachment = 0; + const IHasBuildPoints *pBPInterface = dynamic_cast<const IHasBuildPoints*>( pEntity ); + if ( pBPInterface ) + iAttachment = pBPInterface->GetBuildPointAttachmentIndex( iPoint ); + + SetParent( m_hBuiltOnEntity.Get(), iAttachment ); + + if ( iAttachment >= 1 ) + { + // Stick right onto the attachment point. + vecOrigin.Init(); + SetLocalOrigin( vecOrigin ); + SetLocalAngles( QAngle(0,0,0) ); + } + else + { + SetAbsOrigin( vecOrigin ); + vecOrigin = GetLocalOrigin(); + } + } + + Assert( m_hBuiltOnEntity == GetMoveParent() ); +} + +//----------------------------------------------------------------------------- +// Purpose: Detach this object from its parent, if it has one +//----------------------------------------------------------------------------- +void CBaseObject::DetachObjectFromObject( void ) +{ + if ( !GetParentObject() ) + return; + + // Clear the build point + IHasBuildPoints *pBPInterface = dynamic_cast<IHasBuildPoints*>(GetParentObject() ); + Assert( pBPInterface ); + pBPInterface->SetObjectOnBuildPoint( m_iBuiltOnPoint, NULL ); + + SetParent( NULL ); + m_hBuiltOnEntity = NULL; + m_iBuiltOnPoint = 0; +} + + +//----------------------------------------------------------------------------- +// Purpose: Spawn any objects specified inside the mdl +//----------------------------------------------------------------------------- +void CBaseObject::SpawnEntityOnBuildPoint( const char *pEntityName, int iAttachmentNumber ) +{ + // Try and spawn the object + CBaseEntity *pEntity = CreateEntityByName( pEntityName ); + if ( !pEntity ) + return; + + Vector vecOrigin; + QAngle vecAngles; + GetAttachment( iAttachmentNumber, vecOrigin, vecAngles ); + pEntity->SetAbsOrigin( vecOrigin ); + pEntity->SetAbsAngles( vecAngles ); + pEntity->Spawn(); + + // If it's an object, finish setting it up + CBaseObject *pObject = dynamic_cast<CBaseObject*>(pEntity); + if ( !pObject ) + return; + + // Add a buildpoint here + int iPoint = AddBuildPoint( iAttachmentNumber ); + AddValidObjectToBuildPoint( iPoint, pObject->GetType() ); + pObject->SetBuilder( GetBuilder() ); + pObject->ChangeTeam( GetTeamNumber() ); + pObject->SpawnControlPanels(); + pObject->SetHealth( pObject->GetMaxHealth() ); + pObject->FinishedBuilding(); + pObject->AttachObjectToObject( this, iPoint, vecOrigin ); + pObject->m_fObjectFlags |= OF_CANNOT_BE_DISMANTLED; + pObject->AttemptToFindPower(); + + IHasBuildPoints *pBPInterface = dynamic_cast<IHasBuildPoints*>(this); + Assert( pBPInterface ); + pBPInterface->SetObjectOnBuildPoint( iPoint, pObject ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Spawn any objects specified inside the mdl +//----------------------------------------------------------------------------- +void CBaseObject::SpawnObjectPoints( void ) +{ + KeyValues *modelKeyValues = new KeyValues(""); + if ( !modelKeyValues->LoadFromBuffer( modelinfo->GetModelName( GetModel() ), modelinfo->GetModelKeyValueText( GetModel() ) ) ) + { + modelKeyValues->deleteThis(); + return; + } + + // Do we have a build point section? + KeyValues *pkvAllObjectPoints = modelKeyValues->FindKey("object_points"); + if ( !pkvAllObjectPoints ) + { + modelKeyValues->deleteThis(); + return; + } + + // Start grabbing the sounds and slotting them in + KeyValues *pkvObjectPoint; + for ( pkvObjectPoint = pkvAllObjectPoints->GetFirstSubKey(); pkvObjectPoint; pkvObjectPoint = pkvObjectPoint->GetNextKey() ) + { + // Find the attachment first + const char *sAttachment = pkvObjectPoint->GetName(); + int iAttachmentNumber = LookupAttachment( sAttachment ); + if ( iAttachmentNumber == 0 ) + { + Msg( "ERROR: Model %s specifies object point %s, but has no attachment named %s.\n", STRING(GetModelName()), pkvObjectPoint->GetString(), pkvObjectPoint->GetString() ); + continue; + } + + // Now see what we're supposed to spawn there + // The count check is because it seems wrong to emit multiple entities on the same point + int nCount = 0; + KeyValues *pkvObject; + for ( pkvObject = pkvObjectPoint->GetFirstSubKey(); pkvObject; pkvObject = pkvObject->GetNextKey() ) + { + SpawnEntityOnBuildPoint( pkvObject->GetName(), iAttachmentNumber ); + ++nCount; + Assert( nCount <= 1 ); + } + } + + modelKeyValues->deleteThis(); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseObject::CreateVulnerablePoints() +{ + KeyValues *modelKeyValues = new KeyValues(""); + if ( !modelKeyValues->LoadFromBuffer( modelinfo->GetModelName( GetModel() ), modelinfo->GetModelKeyValueText( GetModel() ) ) ) + return; + + // Do we have a build point section? + KeyValues *pkvAllVulnerablePoints = modelKeyValues->FindKey("vulnerable_points"); + if ( !pkvAllVulnerablePoints ) + return; + + // Start grabbing the sounds and slotting them in + KeyValues *pkvVulnerablePoint = pkvAllVulnerablePoints->GetFirstSubKey(); + while ( pkvVulnerablePoint ) + { + AddVulnerablePoint( pkvVulnerablePoint->GetName(), pkvVulnerablePoint->GetFloat() ); + pkvVulnerablePoint = pkvVulnerablePoint->GetNextKey(); + } + +} + +ConVar obj_debug_vulnerable( "obj_debug_vulnerable","0", FCVAR_NONE, "Show vulnerable points" ); + +void CBaseObject::AddVulnerablePoint( const char* szName, float fMultiplier ) +{ + // Make a new vulnerable point + VulnerablePoint_t v; + Q_memset(&v,0,sizeof(v)); + v.m_fDamageMultiplier = fMultiplier; + + int nSet, nBox; + + if( !LookupHitbox(szName, nSet, nBox) ) + { + Msg( "Error: Vulnerable point on model %s unable to locate hitbox %s\n", STRING(GetModelName()), szName); + return; + } + + v.m_nSet = nSet; + v.m_nBox = nBox; + + // Insert it into our list + if( obj_debug_vulnerable.GetBool() ) + { + Msg( "Vulnerable point %s on model %s added with a damage multiplier of %f. (%i, %i)\n", szName, STRING(GetModelName()), fMultiplier, nSet, nBox); + } + + m_VulnerablePoints.AddToTail( v ); +} + + +float CBaseObject::FindVulnerablePointMultiplier( int nGroup, int nBox ) +{ + for( int i=0; i < m_VulnerablePoints.Count(); i++ ) + { + VulnerablePoint_t& v = m_VulnerablePoints[i]; + + if( v.m_nBox == nBox /* && v.m_nSet == nGroup */) + { + if( obj_debug_vulnerable.GetBool() ) + { + Msg("VulnerablePoint: %f\n",v.m_fDamageMultiplier); + } + return v.m_fDamageMultiplier; + } + } + + if( obj_debug_vulnerable.GetBool() ) + { + Msg("Couldn't find vulnerable point: %i %i\n",nGroup,nBox); + } + return 1.f; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +QAngle CBaseObject::ConvertAbsAnglesToLocal( QAngle vecLocalAngles ) +{ + if ( !GetMoveParent() ) + return vecLocalAngles; + + EntityMatrix vehicleToWorld, childMatrix; + vehicleToWorld.InitFromEntity( GetMoveParent() ); // parent->world + + // Calculate the build point angles in vehicle space + VMatrix attachmentToWorld; + MatrixFromAngles( vecLocalAngles, attachmentToWorld ); + + VMatrix worldToVehicle = vehicleToWorld.Transpose(); + VMatrix attachmentToVehicle; + MatrixMultiply( worldToVehicle, attachmentToWorld, attachmentToVehicle ); + QAngle vecAbsAngles; + MatrixToAngles( attachmentToVehicle, vecAbsAngles ); + + return vecAbsAngles; +} + +bool CBaseObject::IsSolidToPlayers( void ) const +{ + switch ( m_SolidToPlayers ) + { + default: + break; + case SOLID_TO_PLAYER_USE_DEFAULT: + { + if ( GetObjectInfo( ObjectType() ) ) + { + return GetObjectInfo( ObjectType() )->m_bSolidToPlayerMovement; + } + } + break; + case SOLID_TO_PLAYER_YES: + return true; + case SOLID_TO_PLAYER_NO: + return false; + } + + return false; +} + +void CBaseObject::SetSolidToPlayers( OBJSOLIDTYPE stp, bool force ) +{ + bool changed = stp != m_SolidToPlayers; + m_SolidToPlayers = stp; + + if ( changed || force ) + { + SetCollisionGroup( + IsSolidToPlayers() ? + TFCOLLISION_GROUP_OBJECT_SOLIDTOPLAYERMOVEMENT : + TFCOLLISION_GROUP_OBJECT ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Hooks to support swapping out model if the object is damaged +// Input : bDisabled - +//----------------------------------------------------------------------------- +void CBaseObject::SetDisabled( bool bDisabled ) +{ + bool changed = m_bDisabled != bDisabled; + m_bDisabled = bDisabled; + + // value changed and mapper specified a "disabled"/damaged model + if ( changed && NULL_STRING != m_iszDisabledModel ) + { + // Change model + SetModel( STRING( m_bDisabled ? m_iszDisabledModel : m_iszEnabledModel ) ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseObject::SetBuffStation( CObjectBuffStation *pBuffStation, bool bPlacing ) +{ + // Activate + if ( pBuffStation && !GetBuffStation() && !bPlacing && !IsBuilding() && IsPowered() ) + { + BuffStationActivate(); + } + + if ( GetBuffStation() && ( pBuffStation == GetBuffStation() ) && !m_bBuffActivated && !bPlacing && !IsBuilding() && IsPowered() ) + { + BuffStationActivate(); + } + + // Deactivate + if ( !pBuffStation && GetBuffStation() ) + { + BuffStationDeactivate(); + } + + m_hBuffStation = pBuffStation; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CBaseObject::IsHookedAndBuffed( void ) +{ + if ( GetBuffStation() && HasPowerup( POWERUP_BOOST ) ) + { + if ( GetBuffStation()->IsPowered() && !GetBuffStation()->IsPlacing() && + !GetBuffStation()->IsBuilding() ) + return true; + } + + return false; +} diff --git a/game/server/tf2/tf_obj.h b/game/server/tf2/tf_obj.h new file mode 100644 index 0000000..b53002d --- /dev/null +++ b/game/server/tf2/tf_obj.h @@ -0,0 +1,492 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Base Object built by a player +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef TF_OBJ_H +#define TF_OBJ_H +#ifdef _WIN32 +#pragma once +#endif + +#include "baseentity.h" +#include "tf_func_resource.h" +#include "ihasbuildpoints.h" +#include "baseobject_shared.h" +#include "info_vehicle_bay.h" + +class CBaseTFPlayer; +class CTFTeam; +class CRopeKeyframe; +class CBaseTechnology; +class CGrenadeObjectSapper; +class CObjectPowerPack; +class CVGuiScreen; +class CResourceZone; +class KeyValues; +class CObjectBuffStation; +struct animevent_t; + +#define OBJECT_REPAIR_RATE 10 // Health healed per second while repairing + +// Construction +#define OBJECT_CONSTRUCTION_INTERVAL 0.1 +#define OBJECT_CONSTRUCTION_STARTINGHEALTH 0.1 + + +extern ConVar object_verbose; +extern ConVar obj_child_range_factor; + +#if defined( _DEBUG ) +#define TRACE_OBJECT( str ) \ +if ( object_verbose.GetInt() ) \ +{ \ + Msg( "%s", str ); \ +} +#else +#define TRACE_OBJECT( string ) +#endif + + +// ------------------------------------------------------------------------ // +// Resupply object that's built by the player +// ------------------------------------------------------------------------ // +class CBaseObject : public CBaseCombatCharacter, public IHasBuildPoints +{ + DECLARE_CLASS( CBaseObject, CBaseCombatCharacter ); +public: + CBaseObject(); + + virtual void UpdateOnRemove( void ); + + DECLARE_SERVERCLASS(); + DECLARE_DATADESC(); + + virtual bool IsBaseObject( void ) const { return true; } + + virtual void BaseObjectThink( void ); + virtual void LostPowerThink( void ); + virtual CBaseTFPlayer *GetOwner( void ); + + // Creation + virtual void Precache(); + virtual void Spawn( void ); + virtual void Activate( void ); + void InitializeMapPlacedObject( void ); + + virtual void SetBuilder( CBaseTFPlayer *pBuilder, bool moveobjects = false ); + virtual void SetupTeamModel( void ) { return; } + virtual void SetType( int iObjectType ); + int ObjectType( ) const; + + virtual int BloodColor( void ) { return BLOOD_COLOR_MECH; } + + // Building + virtual float GetTotalTime( void ); + virtual void StartPlacement( CBaseTFPlayer *pPlayer ); + void StopPlacement( void ); + bool FindNearestBuildPoint( CBaseEntity *pEntity, Vector vecBuildOrigin, float &flNearestPoint, Vector &vecNearestBuildPoint ); + virtual bool CalculatePlacement( CBaseTFPlayer *pPlayer ); + bool CheckBuildPoint( Vector vecPoint, Vector &vecTrace, Vector *vecOutPoint=NULL ); + bool VerifyCorner( const Vector &vBottomCenter, float xOffset, float yOffset ); + virtual bool CheckBuildOrigin( CBaseTFPlayer *pPlayer, const Vector &vecBuildOrigin, bool bSnappedToPoint = false ); + void AttemptToFindPower( void ); + void AttemptToFindBuffStation( void ); + void AttemptToActivateBuffStation( void ); + virtual float GetNearbyObjectCheckRadius( void ) { return 30.0; } + bool UpdatePlacement( CBaseTFPlayer *pPlayer ); + virtual bool ShouldAttachToParent( void ) { return true; } + void SetVehicleBay( CVGuiScreenVehicleBay *pBay ) { m_hVehicleBay = pBay; } + + // Sort of a hack for walkers - vehicles are pre-rotated by 90 degrees and walkers need to undo this. + virtual void AdjustInitialBuildAngles(); + + // Exit points for mounted vehicles.... + virtual void GetExitPoint( CBaseEntity *pPlayer, int nBuildPoint, Vector *pExitPoint, QAngle *pAngles ); + + // I've finished building the specified object on the specified build point + virtual int FindObjectOnBuildPoint( CBaseObject *pObject ); + + // This gives an object a chance to prevent itself from being built when the user clicks the + // attack button during placement. Barbed wire uses this to change which object the barbed wire + // is attached to. + virtual bool PreStartBuilding(); + + virtual bool StartBuilding( CBaseEntity *pPlayer ); + void BuildingThink( void ); + void SetControlPanelsActive( bool bState ); + virtual void FinishedBuilding( void ); + bool IsBuilding( void ) { return m_bBuilding; }; + bool IsPlacing( void ) { return m_bPlacing; }; + bool MustBeBuiltInResourceZone( void ) const; + bool MustBeBuiltInConstructionYard( void ) const; + virtual bool MustNotBeBuiltInConstructionYard( void ) const; + bool MustBeBuiltOnAttachmentPoint( void ) const; + + void AlignToGround( Vector vecOrigin ); + + // Returns information about the various control panels + virtual void GetControlPanelInfo( int nPanelIndex, const char *&pPanelName ); + virtual void GetControlPanelClassName( int nPanelIndex, const char *&pPanelName ); + + // Client commands sent by clicking on various panels.... + // NOTE: pArg->Argv(0) == pCmd, pArg->Argv(1) == the first argument + virtual bool ClientCommand( CBaseTFPlayer *pSender, const CCommand &args ); + + // Damage + void SetHealth( float flHealth ); + virtual void TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr ); + virtual int OnTakeDamage( const CTakeDamageInfo &info ); + bool PassDamageOntoChildren( const CTakeDamageInfo &info, float *flDamageLeftOver ); + virtual float GetRepairTime( void ); + virtual bool UpdateRepair( float flRepairTime ); + virtual bool Repair( float flHealth ); + + // Powerups + virtual bool CanPowerupEver( int iPowerup ); + virtual bool CanPowerupNow( int iPowerup ); + virtual void PowerupStart( int iPowerup, float flAmount, CBaseEntity *pAttacker, CDamageModifier *pDamageModifier ); + virtual void PowerupEnd( int iPowerup ); + + // Destruction + virtual bool ShouldAutoRemove( void ); + virtual void Killed( void ); + virtual void DestroyObject( void ); // Silent cleanup + virtual bool IsDying( void ) { return m_bDying; } + + // Data + virtual Class_T Classify( void ); + virtual int GetType( void ); + virtual CBaseTFPlayer *GetBuilder( void ); + virtual CBaseTFPlayer *GetOriginalBuilder( void ); + CTFTeam *GetTFTeam( void ) { return (CTFTeam*)GetTeam(); }; + + // ID functions + virtual bool IsAnUpgrade( void ) { return false; } + virtual bool IsAVehicle( void ) { return false; } + virtual bool IsSentrygun() { return false; } + virtual bool WantsCoverFromSentryGun() { return false; } + virtual bool WantsCover() { return false; } + + // Inputs + void InputSetHealth( inputdata_t &inputdata ); + void InputAddHealth( inputdata_t &inputdata ); + void InputRemoveHealth( inputdata_t &inputdata ); + void InputSetMinDisabledHealth( inputdata_t &inputdata ); + void InputSetSolidToPlayer( inputdata_t &inputdata ); + + // Pickup + virtual int ObjectCaps( void ); + virtual void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + virtual void PickupObject( void ); + virtual bool CanBeRemovedBy( CBaseTFPlayer *pPlayer ); + virtual bool CanBeRotatedBy( CBaseTFPlayer *pPlayer ); + + virtual void ChangeTeam( int iTeamNum ) OVERRIDE; // Assign this entity to a team. + + virtual void ChangeBuilder( CBaseTFPlayer *pNewBuilder, bool moveobjects ); + virtual const char *GetWeaponClassnameForObject( void ); + virtual void AddItemsNeededForObject( CBaseTFPlayer *pNewOwner ); + + // Objects that are damaged/disabled can return false here when checked for being available + virtual bool ComputeEMPDamageState( void ) { return true; } + + // Handling object inactive + virtual bool ShouldBeActive( void ); + + // Technology + virtual void GainedNewTechnology( CBaseTechnology *pTechnology ); + + // Sappers + bool HasSapper( void ); + bool HasSapperFromPlayer( CBaseTFPlayer *pPlayer ); + void AddSapper( CGrenadeObjectSapper *pSapper ); + void RemoveSapper( CGrenadeObjectSapper *pSapper ); + + // Called when the builder rotates this object... + virtual void ObjectMoved( ); + + // Minibase stuff + virtual bool WasMapPlaced( void ); + virtual CRopeKeyframe *ConnectCableTo( CBaseObject *pObject, int iLocalAttachment, int iTargetAttachment ); + virtual bool HasCableTo( CBaseObject *pObject ); + virtual int GetCableAttachment( void ); + + // Returns the object flags + int GetObjectFlags() const { return m_fObjectFlags; } + void SetObjectFlags( int flags ) { m_fObjectFlags = flags; } + + CResourceZone *GetResourceZone() { return m_hResourceZone.Get(); } + int RopeCount() const { return m_aRopes.Count(); } + + // Power handling (Human objects need power to operate) + bool IsPowered( void ); + void SetPowerPack( CObjectPowerPack *pPack ); + CObjectPowerPack *GetPowerPack( void ) { return m_hPowerPack; }; + + void AttemptToGoActive( void ); + virtual void OnGoActive( void ); + virtual void OnGoInactive( void ); + + // Buffed objects (objects that connect to a medic's buff station) + bool IsHookedAndBuffed( void ); + virtual bool CanBeHookedToBuffStation( void ); + void SetBuffStation( CObjectBuffStation *pBuffStation, bool bPlacing ); + CObjectBuffStation *GetBuffStation( void ); + + virtual void BuffStationActivate( void ); + virtual void BuffStationDeactivate( void ); + + // Deterioration + void StartDeteriorating( void ); + void StopDeteriorating( void ); + bool IsDeteriorating( void ) { return m_bDeteriorating; }; + void DeterioratingThink( void ); + + // Disabling + void SetDisabled( bool bDisabled ); + bool IsDisabled( void ) { return m_bDisabled; } + + // Animation + virtual void PlayStartupAnimation( void ); + + Activity GetActivity( ) const; + void SetActivity( Activity act ); + void SetObjectSequence( int sequence ); + + virtual void OnActivityChanged( Activity act ); + + // Object points + void SpawnObjectPoints( void ); + + // Derive to customize an object's attached version + virtual void SetupAttachedVersion( void ) { return; } + virtual void SetupUnattachedVersion( void ) { return; } + + QAngle ConvertAbsAnglesToLocal( QAngle vecLocalAngles ); + +public: + // VulnerablePoints + void CreateVulnerablePoints( void ); + void AddVulnerablePoint( const char* szHitboxName, float Multiplier ); + float FindVulnerablePointMultiplier( int nGroup, int nBox ); + + // Build points + CUtlVector<VulnerablePoint_t> m_VulnerablePoints; + + virtual bool TestHitboxes( const Ray_t &ray, unsigned int fContentsMask, trace_t& tr ); + +public: + // Client/Server shared build point code + void CreateBuildPoints( void ); + void AddAndParseBuildPoint( int iAttachmentNumber, KeyValues *pkvBuildPoint ); + virtual int AddBuildPoint( int iAttachmentNum ); + virtual void AddValidObjectToBuildPoint( int iPoint, int iObjectType ); + virtual CBaseObject *GetBuildPointObject( int iPoint ); + bool IsBuiltOnAttachment( void ) { return (m_hBuiltOnEntity.Get() != NULL); } + void AttachObjectToObject( const CBaseEntity *pEntity, int iPoint, Vector &vecOrigin ); + void DetachObjectFromObject( void ); + CBaseObject *GetParentObject( void ); + void SetBuildPointPassenger( int iPoint, int iPassenger ); + int GetBuildPointPassenger( int iPoint ) const; + + virtual float GetSapperAttachTime( void ); + +// IHasBuildPoints +public: + virtual int GetNumBuildPoints( void ) const; + virtual bool GetBuildPoint( int iPoint, Vector &vecOrigin, QAngle &vecAngles ); + virtual int GetBuildPointAttachmentIndex( int iPoint ) const; + virtual bool CanBuildObjectOnBuildPoint( int iPoint, int iObjectType ); + virtual void SetObjectOnBuildPoint( int iPoint, CBaseObject *pObject ); + virtual float GetMaxSnapDistance( int iBuildPoint ); + virtual bool ShouldCheckForMovement( void ) { return true; } + virtual int GetNumObjectsOnMe( void ); + virtual CBaseEntity *GetFirstObjectOnMe( void ); + virtual CBaseObject *GetObjectOfTypeOnMe( int iObjectType ); + virtual void RemoveAllObjects( void ); + + +// IServerNetworkable. +public: + + virtual int ShouldTransmit( const CCheckTransmitInfo *pInfo ); + virtual void SetTransmit( CCheckTransmitInfo *pInfo, bool bAlways ); + + +protected: + // Clean off the object of offensive material, returns true if it found anything + bool RemoveEnemyAttachments( CBaseEntity *pActivator ); + void RemoveAnalyzer( CBaseEntity *pRemovingEntity ); + void RemoveAllSappers( CBaseEntity *pRemovingEntity ); + + void GiveNamedTechnology( CBaseTFPlayer *pRecipient, const char *techname ); + + // Show/hide vgui screens. + bool ShowVGUIScreen( int panelIndex, bool bShow ); + +private: + void DetermineAnimation( void ); + + // Spawns the various control panels + void SpawnControlPanels(); + + // Purpose: Spawn any objects specified inside the mdl + void SpawnEntityOnBuildPoint( const char *pEntityName, int iAttachmentNumber ); + + // Various commands sent by control panels + void DismantleCommand( CBaseTFPlayer *pSender ); + void YawCommand( CBaseTFPlayer *pSender, float flYaw ); + void TakeControlCommand( CBaseTFPlayer *pSender ); + +protected: + enum OBJSOLIDTYPE + { + SOLID_TO_PLAYER_USE_DEFAULT = 0, + SOLID_TO_PLAYER_YES, + SOLID_TO_PLAYER_NO, + }; + + bool IsSolidToPlayers( void ) const; + + // object flags.... + CNetworkVar( int, m_fObjectFlags ); + CNetworkHandle( CBaseTFPlayer, m_hBuilder ); + + // Zone we're in (valid only for objects that sit in zones) + CNetworkHandle( CResourceZone, m_hResourceZone ); + + // Combat Objects + char *m_szAmmoName; // Ammo used by players to build me + + // Cables + CUtlVector< CHandle<CRopeKeyframe> > m_aRopes; + + // Placement + Vector m_vecBuildOrigin; + Vector m_vecBuildMins; + Vector m_vecBuildMaxs; + CNetworkHandle( CBaseEntity, m_hBuiltOnEntity ); + int m_iBuiltOnPoint; + + bool m_bInvulnerable; + bool m_bCantDie; + float m_flRepairMultiplier; + bool m_bDying; + + // Outputs + COutputEvent m_OnDestroyed; + COutputEvent m_OnDamaged; + COutputEvent m_OnRepaired; + + COutputEvent m_OnBecomingDisabled; + COutputEvent m_OnBecomingReenabled; + + COutputFloat m_OnObjectHealthChanged; + + // Control panel + typedef CHandle<CVGuiScreen> ScreenHandle_t; + CUtlVector<ScreenHandle_t> m_hScreens; + +private: + // Make sure we pick up changes to these. + IMPLEMENT_NETWORK_VAR_FOR_DERIVED( m_iHealth ); + IMPLEMENT_NETWORK_VAR_FOR_DERIVED( m_takedamage ); + + Activity m_Activity; + + CNetworkVar( int, m_iObjectType ); + + + // True if players shouldn't do collision avoidance, but should just collide exactly with the object. + OBJSOLIDTYPE m_SolidToPlayers; + void SetSolidToPlayers( OBJSOLIDTYPE stp, bool force = false ); + + // True if this was a map placed object, not a player built one + bool m_bWasMapPlaced; + + // Disabled + CNetworkVar( bool, m_bDisabled ); + + // Vehicle bays + CHandle<CVGuiScreenVehicleBay> m_hVehicleBay; + + // Building + CNetworkVar( bool, m_bPlacing ); // True while the object's being placed + CNetworkVar( bool, m_bBuilding ); // True while the object's still constructing itself + float m_flConstructionTimeLeft; // Current time left in construction + float m_flTotalConstructionTime; // Total construction time (the value of GetTotalTime() at the time construction + // started, ie, incase you teleport out of a construction yard) + + CNetworkVar( float, m_flPercentageConstructed ); // Used to send to client + float m_flHealth; // Health during construction. Needed a float due to small increases in health. + + // Repair capping + float m_flLastRepairTime; + float m_flNextRepairMultiplier; + float m_flRepairedSinceLastTime; + + // Sappers on me + CNetworkVar( bool, m_bHasSapper ); + typedef CHandle<CGrenadeObjectSapper> SapperHandle; + CUtlVector< SapperHandle > m_hSappers; + + // Power handling (Human objects need power to operate) + CHandle< CObjectPowerPack > m_hPowerPack; + + // Buff Station + CHandle< CObjectBuffStation > m_hBuffStation; + bool m_bBuffActivated; + + // Deterioration + CNetworkVar( bool, m_bDeteriorating ); + float m_flStartedDeterioratingAt; + CHandle<CBaseTFPlayer> m_hOriginalBuilder; + + // Build points + CUtlVector<BuildPoint_t> m_BuildPoints; + + // Store the last time I took damage from an enemy. Use this to know whether to drop resources + // when I die, because I only want to drop resources if I was "destroyed" by an enemy, not if I deteriorated. + float m_flLastRealDamage; + + // Amount of resources the player who built me paid for me + int m_iAmountPlayerPaidForMe; + + // Attack notification sounds + string_t m_iszUnderAttackSound; + + // If non-zero then if health gets below this amount, the object becomes disabled + float m_flMinDisableHealth; + + // If not NULL, then when going disabled, swith to this model + string_t m_iszDisabledModel; + string_t m_iszEnabledModel; +}; + +inline bool CBaseObject::CanBeHookedToBuffStation( void ) +{ + return false; +} + +inline CObjectBuffStation *CBaseObject::GetBuffStation( void ) +{ + return m_hBuffStation.Get(); +} + +inline void CBaseObject::BuffStationActivate( void ) +{ + m_bBuffActivated = true; +} + +inline void CBaseObject::BuffStationDeactivate( void ) +{ + m_bBuffActivated = false; +} + +extern short g_sModelIndexFireball; // holds the index for the fireball + + +#endif // TF_OBJ_H diff --git a/game/server/tf2/tf_obj_armor_upgrade.cpp b/game/server/tf2/tf_obj_armor_upgrade.cpp new file mode 100644 index 0000000..01b05f3 --- /dev/null +++ b/game/server/tf2/tf_obj_armor_upgrade.cpp @@ -0,0 +1,31 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "cbase.h" +#include "tf_obj_armor_upgrade.h" + + +#define AERIAL_SENTRY_STATION_MODEL "models/objects/obj_aerial_sentry_station.mdl" + + +LINK_ENTITY_TO_CLASS( obj_armor_upgrade, CArmorUpgrade ); + + +IMPLEMENT_SERVERCLASS_ST( CArmorUpgrade, DT_ArmorUpgrade ) +END_SEND_TABLE() + + +CArmorUpgrade::CArmorUpgrade() +{ + UseClientSideAnimation(); +} + + +void CArmorUpgrade::Spawn() +{ + SetModel( AERIAL_SENTRY_STATION_MODEL ); + SetType( OBJ_ARMOR_UPGRADE ); +} diff --git a/game/server/tf2/tf_obj_armor_upgrade.h b/game/server/tf2/tf_obj_armor_upgrade.h new file mode 100644 index 0000000..386982a --- /dev/null +++ b/game/server/tf2/tf_obj_armor_upgrade.h @@ -0,0 +1,33 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#ifndef TF_OBJ_ARMOR_UPGRADE_H +#define TF_OBJ_ARMOR_UPGRADE_H +#ifdef _WIN32 +#pragma once +#endif + + +#include "tf_obj_baseupgrade_shared.h" + + +// This class shows a temporary model until you get to an object it can upgrade. +// Then it draws a shell over the model you want to upgrade. +class CArmorUpgrade : public CBaseObjectUpgrade +{ +public: + DECLARE_CLASS( CArmorUpgrade, CBaseObjectUpgrade ); + DECLARE_SERVERCLASS(); + + + CArmorUpgrade(); + + + virtual void Spawn(); +}; + + +#endif // TF_OBJ_ARMOR_UPGRADE_H diff --git a/game/server/tf2/tf_obj_barbed_wire.cpp b/game/server/tf2/tf_obj_barbed_wire.cpp new file mode 100644 index 0000000..5666d36 --- /dev/null +++ b/game/server/tf2/tf_obj_barbed_wire.cpp @@ -0,0 +1,239 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "cbase.h" +#include "tf_obj_barbed_wire.h" +#include "tf_player.h" +#include "basetfvehicle.h" +#include "engine/IEngineSound.h" +#include "te_effect_dispatch.h" +#include "ndebugoverlay.h" + +#define BARBED_WIRE_MINS Vector(-5, -5, 0) +#define BARBED_WIRE_MAXS Vector( 5, 5, 40) +#define BARBED_WIRE_MODEL "models/objects/obj_barbed_wire.mdl" + +#define MAX_BARBED_WIRE_DISTANCE 768 +#define BARBED_WIRE_THINK_CONTEXT "BarbedWireThinkContext" + +#define BARBED_WIRE_THINK_INTERVAL 0.2 + +ConVar obj_barbed_wire_damage( "obj_barbed_wire_damage", "80" ); +ConVar obj_barbed_wire_health( "obj_barbed_wire_health", "100" ); + + +IMPLEMENT_SERVERCLASS_ST( CObjectBarbedWire, DT_ObjectBarbedWire ) + SendPropEHandle( SENDINFO( m_hConnectedTo ) ) +END_SEND_TABLE() + + +LINK_ENTITY_TO_CLASS( obj_barbed_wire, CObjectBarbedWire ); +PRECACHE_REGISTER( obj_barbed_wire ); + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CObjectBarbedWire::CObjectBarbedWire() +{ + m_iHealth = obj_barbed_wire_health.GetInt(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CObjectBarbedWire::Precache() +{ + PrecacheModel( BARBED_WIRE_MODEL ); + BaseClass::Precache(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CObjectBarbedWire::Spawn() +{ + Precache(); + + SetModel( BARBED_WIRE_MODEL ); + SetSolid( SOLID_BBOX ); + SetType( OBJ_BARBED_WIRE ); + UTIL_SetSize(this, BARBED_WIRE_MINS, BARBED_WIRE_MAXS ); + + // Set our flags. + m_fObjectFlags |= OF_DOESNT_NEED_POWER | OF_SUPPRESS_APPEAR_ON_MINIMAP | OF_SUPPRESS_NOTIFY_UNDER_ATTACK | OF_ALLOW_REPEAT_PLACEMENT; + + // Get the ball rolling here. + BarbedWireThink(); + + BaseClass::Spawn(); +} + +//----------------------------------------------------------------------------- +// Purpose: Enumerator +//----------------------------------------------------------------------------- +class CBarbedWireEnumerator : public IEntityEnumerator +{ +public: + CBarbedWireEnumerator( CObjectBarbedWire *pWire, Ray_t *pRay, int contentsMask ) + { + m_pWire = pWire; + m_pRay = pRay; + m_ContentsMask = contentsMask; + m_aDamagedEntities.Purge(); + } + + virtual bool EnumEntity( IHandleEntity *pHandleEntity ) + { + CBaseEntity *pEntity = gEntList.GetBaseEntity( pHandleEntity->GetRefEHandle() ); + if ( pEntity && !m_pWire->InSameTeam( pEntity ) ) + { + trace_t tr; + enginetrace->ClipRayToEntity( *m_pRay, m_ContentsMask, pHandleEntity, &tr ); + if ( tr.fraction < 1.0f ) + { + // Add them to the list & damage them later. + // Done this way so entities don't remove themselves from leaves while we're tracing + if ( m_aDamagedEntities.Find( pEntity ) == m_aDamagedEntities.InvalidIndex() ) + { + m_aDamagedEntities.AddToTail( pEntity ); + } + } + } + + return true; + } + + void DamageEntities( void ) + { + int iSize = m_aDamagedEntities.Count(); + for ( int i = iSize-1; i >= 0; i-- ) + { + CBaseEntity *pEntity = m_aDamagedEntities[i].Get(); + if ( pEntity ) + { + // DMG_CRUSH added so there's no physics force generated + CTakeDamageInfo info( m_pWire, m_pWire->GetBuilder(), obj_barbed_wire_damage.GetFloat() * BARBED_WIRE_THINK_INTERVAL, DMG_SLASH | DMG_CRUSH ); + pEntity->TakeDamage( info ); + + // Bloodspray + CEffectData data; + data.m_vOrigin = pEntity->WorldSpaceCenter(); + data.m_vNormal = Vector(0,0,1); + data.m_flScale = 4; + data.m_fFlags = FX_BLOODSPRAY_ALL; + data.m_nEntIndex = pEntity->entindex(); + DispatchEffect( "tf2blood", data ); + } + } + } + +private: + CObjectBarbedWire *m_pWire; + int m_ContentsMask; + Ray_t + *m_pRay; + CUtlVector< EHANDLE > m_aDamagedEntities; +}; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CObjectBarbedWire::BarbedWireThink( void ) +{ + // Cut the rope if we're too far from the entity it's attached to. + if ( m_hConnectedTo.Get() ) + { + if ( (WorldSpaceCenter() - m_hConnectedTo->WorldSpaceCenter()).Length() > MAX_BARBED_WIRE_DISTANCE ) + { + m_hConnectedTo = NULL; + } + + if ( m_hConnectedTo ) + { + Ray_t ray; + ray.Init( WorldSpaceCenter(), m_hConnectedTo->WorldSpaceCenter() ); + + //NDebugOverlay::Line( WorldSpaceCenter(), m_hConnectedTo->WorldSpaceCenter(), 255,255,255, false, 0.1 ); + //NDebugOverlay::Box( WorldSpaceCenter(), -Vector(5,5,5), Vector(5,5,5), 0,255,0,8, 0.1 ); + //NDebugOverlay::Box( m_hConnectedTo->WorldSpaceCenter(), -Vector(6,6,6), Vector(6,6,6), 255,255,255,8, 0.1 ); + + CBarbedWireEnumerator bwEnum( this, &ray, MASK_SHOT_HULL ); + enginetrace->EnumerateEntities( ray, false, &bwEnum ); + bwEnum.DamageEntities(); + } + } + + SetContextThink( BarbedWireThink, gpGlobals->curtime + BARBED_WIRE_THINK_INTERVAL, BARBED_WIRE_THINK_CONTEXT ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CObjectBarbedWire::StartPlacement( CBaseTFPlayer *pPlayer ) +{ + if ( pPlayer && !m_hConnectedTo ) + { + // Automatically connect to the nearest barbed wire on our team. + float flClosest = 1e24; + CObjectBarbedWire *pClosest = NULL; + + CBaseEntity *pCur = gEntList.FirstEnt(); + while ( pCur ) + { + CObjectBarbedWire *pWire = dynamic_cast< CObjectBarbedWire* >( pCur ); + if ( pWire ) + { + if ( pWire->GetTeamNumber() == pPlayer->GetTeamNumber() ) + { + float flDist = (pWire->WorldSpaceCenter() - pPlayer->WorldSpaceCenter()).Length(); + if ( flDist < flClosest ) + { + flClosest = flDist; + pClosest = pWire; + } + } + } + + pCur = gEntList.NextEnt( pCur ); + } + + if ( pClosest ) + { + m_hConnectedTo = pClosest; + } + } + + BaseClass::StartPlacement( pPlayer ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CObjectBarbedWire::PreStartBuilding() +{ + const CObjectBarbedWire *pWire = dynamic_cast< const CObjectBarbedWire* >( m_hBuiltOnEntity.Get() ); + if ( pWire ) + { + // Reconnect the wire to this entity and don't build yet. + m_hConnectedTo = pWire; + + return false; + } + else + { + // Go ahead and build. + return true; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CObjectBarbedWire::FinishedBuilding() +{ + BaseClass::FinishedBuilding(); +} + diff --git a/game/server/tf2/tf_obj_barbed_wire.h b/game/server/tf2/tf_obj_barbed_wire.h new file mode 100644 index 0000000..c897d22 --- /dev/null +++ b/game/server/tf2/tf_obj_barbed_wire.h @@ -0,0 +1,43 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#ifndef TF_OBJ_BARBED_WIRE_H +#define TF_OBJ_BARBED_WIRE_H +#ifdef _WIN32 +#pragma once +#endif + + +#include "tf_obj.h" + + +class CObjectBarbedWire : public CBaseObject +{ +public: + DECLARE_CLASS( CObjectBarbedWire, CBaseObject ); + DECLARE_SERVERCLASS(); + + + CObjectBarbedWire(); + + + void BarbedWireThink(); + + virtual void Precache(); + virtual void Spawn(); + + virtual void StartPlacement( CBaseTFPlayer *pPlayer ); + + virtual bool PreStartBuilding(); + virtual void FinishedBuilding(); + +private: + CNetworkHandle( CObjectBarbedWire, m_hConnectedTo ); + +}; + + +#endif // TF_OBJ_BARBED_WIRE_H diff --git a/game/server/tf2/tf_obj_buff_station.cpp b/game/server/tf2/tf_obj_buff_station.cpp new file mode 100644 index 0000000..0dc6791 --- /dev/null +++ b/game/server/tf2/tf_obj_buff_station.cpp @@ -0,0 +1,929 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Medic's portable power generator +// +//=============================================================================// + +#include "cbase.h" +#include "tf_obj_buff_station.h" +#include "tf_player.h" +#include "rope.h" +#include "rope_shared.h" +#include "entitylist.h" +#include "VGuiScreen.h" +#include "engine/IEngineSound.h" +#include "tf_team.h" + +//============================================================================= +// +// Console Variables +// +static ConVar obj_buff_station_damage_modifier( "obj_buff_station_damage_modifier", "1.5", 0, "Scales the damage a player does while connected to the buff station." ); +static ConVar obj_buff_station_heal_rate( "obj_buff_station_heal_rate", "10" ); +static ConVar obj_buff_station_range( "obj_buff_station_range", "300" ); +static ConVar obj_buff_station_obj_range( "obj_buff_station_obj_range", "800" ); +static ConVar obj_buff_station_health( "obj_buff_station_health","100", FCVAR_NONE, "Buff Station health" ); + +//----------------------------------------------------------------------------- +// Buff Station defines +//----------------------------------------------------------------------------- +#define BUFF_STATION_MINS Vector( -30, -30, 0 ) +#define BUFF_STATION_MAXS Vector( 30, 30, 50 ) + +#define BUFF_STATION_HUMAN_MODEL "models/objects/human_obj_buffstation.mdl" +#define BUFF_STATION_HUMAN_ASSEMBLING_MODEL "models/objects/human_obj_buffstation_build.mdl" +#define BUFF_STATION_ALIEN_MODEL "models/objects/alien_obj_buffstation.mdl" +#define BUFF_STATION_ALIEN_ASSEMBLING_MODEL "models/objects/alien_obj_buffstation_build.mdl" + +#define BUFF_STATION_VGUI_SCREEN "screen_obj_buffstation" + +#define BUFF_STATION_BOOST_PLAYER_THINK_CONTEXT "BoostPlayerThink" +#define BUFF_STATION_BOOST_OBJECT_THINK_CONTEXT "BoostObjectThink" +#define BUFF_STATION_BOOST_PLAYER_THINK_INTERVAL 0.1f +#define BUFF_STATION_BOOST_OBJECT_THINK_INTERVAL 2.0f + +#define BUFF_STATION_BUFF_RANGE ( 600 * 600 ) + +//============================================================================= +// +// Data Description +// +BEGIN_DATADESC( CObjectBuffStation ) + DEFINE_INPUTFUNC( FIELD_VOID, "PlayerSpawned", InputPlayerSpawned ), + DEFINE_INPUTFUNC( FIELD_VOID, "PlayerAttachedToGenerator", InputPlayerAttachedToGenerator ), + DEFINE_INPUTFUNC( FIELD_VOID, "PlayerEnteredVehicle", InputPlayerSpawned ), // NJS: Detach player from buff pack. +END_DATADESC() + +//============================================================================= +// +// Server Class +// +IMPLEMENT_SERVERCLASS_ST( CObjectBuffStation, DT_ObjectBuffStation ) + SendPropInt( SENDINFO( m_nPlayerCount ), BUFF_STATION_MAX_PLAYER_BITS, SPROP_UNSIGNED ), + SendPropArray( SendPropEHandle( SENDINFO_ARRAY( m_hPlayers ) ), m_hPlayers ), + SendPropInt( SENDINFO( m_nObjectCount ), BUFF_STATION_MAX_OBJECT_BITS, SPROP_UNSIGNED ), + SendPropArray( SendPropEHandle( SENDINFO_ARRAY( m_hObjects ) ), m_hObjects ), +END_SEND_TABLE() + +//============================================================================= +// +// Linking and Precache +// +LINK_ENTITY_TO_CLASS( obj_buff_station, CObjectBuffStation ); +PRECACHE_REGISTER( obj_buff_station ); + +// Backwards compatability... +LINK_ENTITY_TO_CLASS( obj_portable_power_generator, CObjectBuffStation ); + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +CObjectBuffStation::CObjectBuffStation() +{ + // Verify networking data. + COMPILE_TIME_ASSERT( BUFF_STATION_MAX_PLAYERS < ( 1 << BUFF_STATION_MAX_PLAYER_BITS ) ); + COMPILE_TIME_ASSERT( BUFF_STATION_MAX_PLAYERS >= ( 1 << ( BUFF_STATION_MAX_PLAYER_BITS - 1 ) ) ); + + COMPILE_TIME_ASSERT( BUFF_STATION_MAX_OBJECTS < ( 1 << BUFF_STATION_MAX_OBJECT_BITS ) ); + COMPILE_TIME_ASSERT( BUFF_STATION_MAX_OBJECTS >= ( 1 << ( BUFF_STATION_MAX_OBJECT_BITS - 1 ) ) ); + + // Uses the client-side animation system. + UseClientSideAnimation(); +} + +//----------------------------------------------------------------------------- +// Purpose: Spawn +//----------------------------------------------------------------------------- +void CObjectBuffStation::Spawn() +{ + // This must be set before calling the base class spawn. + m_iHealth = obj_buff_station_health.GetInt(); + + BaseClass::Spawn(); + + SetModel( BUFF_STATION_HUMAN_MODEL ); + SetSolid( SOLID_BBOX ); + + SetType( OBJ_BUFF_STATION ); + UTIL_SetSize( this, BUFF_STATION_MINS, BUFF_STATION_MAXS ); + + m_takedamage = DAMAGE_YES; + + // Initialize buff station attachment data. + InitAttachmentData(); + + // Thinking + SetContextThink( BoostPlayerThink, 1.0f, BUFF_STATION_BOOST_PLAYER_THINK_CONTEXT ); + SetContextThink( BoostObjectThink, 2.0f, BUFF_STATION_BOOST_OBJECT_THINK_CONTEXT ); + + m_bBuilding = false; +} + +//----------------------------------------------------------------------------- +// Purpose: Precache model, vgui elements, and sound. +//----------------------------------------------------------------------------- +void CObjectBuffStation::Precache() +{ + // Models + PrecacheModel( BUFF_STATION_HUMAN_MODEL ); + PrecacheModel( BUFF_STATION_ALIEN_MODEL ); + + // Build models + PrecacheModel( BUFF_STATION_HUMAN_ASSEMBLING_MODEL ); + PrecacheModel( BUFF_STATION_ALIEN_ASSEMBLING_MODEL ); + + // VGUI Screen + PrecacheVGuiScreen( BUFF_STATION_VGUI_SCREEN ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CObjectBuffStation::SetupTeamModel( void ) +{ + if ( GetTeamNumber() == TEAM_HUMANS ) + { + if ( m_bBuilding ) + { + SetModel( BUFF_STATION_HUMAN_ASSEMBLING_MODEL ); + } + else + { + SetModel( BUFF_STATION_HUMAN_MODEL ); + } + } + else + { + if ( m_bBuilding ) + { + SetModel( BUFF_STATION_ALIEN_ASSEMBLING_MODEL ); + } + else + { + SetModel( BUFF_STATION_ALIEN_MODEL ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Gets info about the control panels +//----------------------------------------------------------------------------- +void CObjectBuffStation::GetControlPanelInfo( int nControlPanelIndex, const char *&pPanelName ) +{ + pPanelName = BUFF_STATION_VGUI_SCREEN; +} + +//----------------------------------------------------------------------------- +// Purpose: Remove this object from it's team and mark it for deletion. +//----------------------------------------------------------------------------- +void CObjectBuffStation::DestroyObject( void ) +{ + // Detach all players. + for ( int iPlayer = 0; iPlayer < BUFF_STATION_MAX_PLAYERS; iPlayer++ ) + { + DetachPlayerByIndex( iPlayer ); + } + + // Detach all objects. + for( int iObject = m_nObjectCount - 1; iObject >= 0; --iObject ) + { + DetachObjectByIndex( iObject ); + } + + // Inform all other buff stations on this team to attempt to power object (cover for this one). + if ( GetTFTeam() ) + { + GetTFTeam()->UpdateBuffStations( this, NULL, false ); + } + + // We shouldn't get any more messages + g_pNotify->ClearEntity( this ); + BaseClass::DestroyObject(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CObjectBuffStation::OnGoInactive( void ) +{ + BaseClass::OnGoInactive(); + + // Detach all players. + for ( int iPlayer = 0; iPlayer < BUFF_STATION_MAX_PLAYERS; iPlayer++ ) + { + CBaseTFPlayer *pPlayer = m_hPlayers[iPlayer].Get(); + if ( pPlayer ) + { + ClientPrint( pPlayer, HUD_PRINTCENTER, "Lost power to Buff Station!" ); + } + + DetachPlayerByIndex( iPlayer ); + } + + // Detach all objects. + for ( int iObject = m_nObjectCount - 1; iObject >= 0; --iObject ) + { + DetachObjectByIndex( iObject ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Attach to players who touch me +//----------------------------------------------------------------------------- +void CObjectBuffStation::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( useType == USE_ON ) + { + // See if the activator is a player + if ( !pActivator->IsPlayer() || !InSameTeam( pActivator ) || !pActivator->CanBePoweredUp() ) + return; + + CBaseTFPlayer *pPlayer = static_cast<CBaseTFPlayer*>(pActivator); + if ( pPlayer ) + { + UpdatePlayerAttachment( pPlayer ); + } + } + + BaseClass::Use( pActivator, pCaller, useType, value ); +} + +//----------------------------------------------------------------------------- +// Purpose: Handle commands sent from vgui panels on the client +//----------------------------------------------------------------------------- +bool CObjectBuffStation::ClientCommand( CBaseTFPlayer *pPlayer, const char *pCmd, ICommandArguments *pArg ) +{ + if ( FStrEq( pCmd, "toggle_connect" ) ) + { + UpdatePlayerAttachment( pPlayer ); + return true; + } + + return BaseClass::ClientCommand( pPlayer, pCmd, pArg ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CObjectBuffStation::InitAttachmentData( void ) +{ + // Initialize the attachment data. + char szAttachName[13]; + + m_nPlayerCount = 0; + Q_strncpy( szAttachName, "playercable1", 13 ); + for ( int iPlayer = 0; iPlayer < BUFF_STATION_MAX_PLAYERS; ++iPlayer ) + { + m_hPlayers.Set( iPlayer, NULL ); + + szAttachName[11] = '1' + iPlayer; + m_aPlayerAttachInfo[iPlayer].m_iAttachPoint = LookupAttachment( szAttachName ); + } + + m_nObjectCount = 0; + Q_strncpy( szAttachName, "objectcable1", 13 ); + for ( int iObject = 0; iObject < BUFF_STATION_MAX_OBJECTS; ++iObject ) + { + m_hObjects.Set( iObject, NULL ); + + szAttachName[11] = '1' + iObject; + m_aObjectAttachInfo[iObject].m_iAttachPoint = LookupAttachment( szAttachName ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Create "Buff Station" cable (rope). +//----------------------------------------------------------------------------- +CRopeKeyframe *CObjectBuffStation::CreateRope( CBaseTFPlayer *pPlayer, int iAttachPoint ) +{ + CRopeKeyframe *pRope = CRopeKeyframe::Create( this, pPlayer, iAttachPoint, 0 ); + if ( pRope ) + { + pRope->m_RopeLength = obj_buff_station_range.GetFloat(); + pRope->m_Slack = 0.0f; + pRope->m_Width = 2; + pRope->m_nSegments = ROPE_MAX_SEGMENTS; + pRope->m_RopeFlags |= ROPE_COLLIDE; + pRope->EnablePlayerWeaponAttach( true ); + pRope->ActivateStartDirectionConstraints( true ); + if ( GetTeamNumber() == TEAM_HUMANS ) + { + pRope->SetMaterial( "cable/human_buffcable.vmt" ); + } + else + { + pRope->SetMaterial( "cable/alien_buffcable.vmt" ); + } + } + + return pRope; +} + +//----------------------------------------------------------------------------- +// Purpose: Create "Buff Station" cable (rope). +//----------------------------------------------------------------------------- +CRopeKeyframe *CObjectBuffStation::CreateRope( CBaseObject *pObject, int iAttachPoint, int iObjectAttachPoint ) +{ + CRopeKeyframe *pRope = CRopeKeyframe::Create( this, pObject, iAttachPoint, iObjectAttachPoint ); + if ( pRope ) + { + pRope->m_RopeLength = obj_buff_station_obj_range.GetFloat(); + pRope->m_Slack = 0.0f; + pRope->m_Width = 2; + pRope->m_nSegments = ROPE_MAX_SEGMENTS; + pRope->m_RopeFlags |= ROPE_COLLIDE; + pRope->EnablePlayerWeaponAttach( true ); + pRope->ActivateStartDirectionConstraints( true ); + if ( GetTeamNumber() == TEAM_HUMANS ) + { + pRope->SetMaterial( "cable/human_buffcable.vmt" ); + } + else + { + pRope->SetMaterial( "cable/alien_buffcable.vmt" ); + } + } + + return pRope; +} + +//----------------------------------------------------------------------------- +// Purpose: Is a particular player attached? +//----------------------------------------------------------------------------- +bool CObjectBuffStation::IsPlayerAttached( CBaseTFPlayer *pPlayer ) +{ + for ( int iPlayer = 0; iPlayer < BUFF_STATION_MAX_PLAYERS; iPlayer++ ) + { + if ( m_hPlayers[iPlayer].Get() == pPlayer ) + return true; + } + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: Is a particular object attached? +//----------------------------------------------------------------------------- +bool CObjectBuffStation::IsObjectAttached( CBaseObject *pObject ) +{ + for ( int iObject = 0; iObject < m_nObjectCount; ++iObject ) + { + if ( m_hObjects[iObject].Get() == pObject ) + return true; + } + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: Attach the player to the "Buff Station." +//----------------------------------------------------------------------------- +void CObjectBuffStation::AttachPlayer( CBaseTFPlayer *pPlayer ) +{ + // Player shouldn't already be attached. + Assert( !IsPlayerAttached( pPlayer ) ); + + // Check to see if the player is alive and on the correct team. + if ( !pPlayer->IsAlive() || !pPlayer->InSameTeam( this ) ) + return; + + // Check attachment availability. + if ( m_nPlayerCount == BUFF_STATION_MAX_PLAYERS ) + { + // Unless the player is the owner he cannot connect. + if ( pPlayer != GetOwner() ) + return; + + // Kick a non-owning player off. + DetachPlayerByIndex( BUFF_STATION_MAX_PLAYERS - 1 ); + } + + // This will disconnect the player from other Buff Stations, and keep track of important player events. + g_pNotify->ReportNamedEvent( pPlayer, "PlayerAttachedToGenerator" ); + g_pNotify->AddEntity( this, pPlayer ); + + // Connect player. + // Find the nearest empty slot + int iNearest = BUFF_STATION_MAX_PLAYERS; + float flNearestDist = 9999*9999; + for ( int iPlayer = 0; iPlayer < BUFF_STATION_MAX_PLAYERS; iPlayer++ ) + { + if ( !m_hPlayers[iPlayer] ) + { + Vector vecPoint; + QAngle angPoint; + GetAttachment( m_aPlayerAttachInfo[iPlayer].m_iAttachPoint, vecPoint, angPoint ); + float flDistance = ( vecPoint - pPlayer->GetAbsOrigin() ).LengthSqr(); + if ( flDistance < flNearestDist ) + { + flNearestDist = flDistance; + iNearest = iPlayer; + } + } + } + Assert( iNearest != BUFF_STATION_MAX_PLAYERS ); + + m_hPlayers.Set( iNearest, pPlayer ); + m_aPlayerAttachInfo[iNearest].m_DamageModifier.SetModifier( obj_buff_station_damage_modifier.GetFloat() ); + m_aPlayerAttachInfo[iNearest].m_hRope = CreateRope( pPlayer, m_aPlayerAttachInfo[iNearest].m_iAttachPoint ); + m_nPlayerCount++; + + // Tell the player to constrain his movement. + pPlayer->ActivateMovementConstraint( this, GetAbsOrigin(), obj_buff_station_range.GetFloat(), 75.0f, 0.15f ); + + // Update think. + if ( GetNextThink(BUFF_STATION_BOOST_PLAYER_THINK_CONTEXT) > gpGlobals->curtime + BUFF_STATION_BOOST_PLAYER_THINK_INTERVAL ) + { + SetNextThink( gpGlobals->curtime + BUFF_STATION_BOOST_PLAYER_THINK_INTERVAL, BUFF_STATION_BOOST_PLAYER_THINK_CONTEXT ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Detach the player from the "Buff Station." +//----------------------------------------------------------------------------- +void CObjectBuffStation::DetachPlayer( CBaseTFPlayer *pPlayer ) +{ + for ( int iPlayer = 0; iPlayer < BUFF_STATION_MAX_PLAYERS; iPlayer++ ) + { + if ( m_hPlayers[iPlayer].Get() == pPlayer ) + { + DetachPlayerByIndex( iPlayer ); + return; + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Detach the player from the "Buff Station." +//----------------------------------------------------------------------------- +void CObjectBuffStation::DetachPlayerByIndex( int nIndex ) +{ + // Valid index? + Assert( nIndex < BUFF_STATION_MAX_PLAYERS ); + + // Get the player. + CBaseTFPlayer *pPlayer = m_hPlayers[nIndex].Get(); + if ( !pPlayer ) + { + m_hPlayers.Set( nIndex, NULL ); + return; + } + + // Remove the damage modifier. + m_aPlayerAttachInfo[nIndex].m_DamageModifier.RemoveModifier(); + + // Remove the rope (cable). + if ( m_aPlayerAttachInfo[nIndex].m_hRope.Get() ) + { + m_aPlayerAttachInfo[nIndex].m_hRope->DetachPoint( 1 ); + m_aPlayerAttachInfo[nIndex].m_hRope->DieAtNextRest(); + } + + // Unconstrain the player movement. + pPlayer->DeactivateMovementConstraint(); + + // Keep track of player events. + g_pNotify->RemoveEntity( this, pPlayer ); + + // Reduce player count. + m_nPlayerCount--; + m_hPlayers.Set( nIndex, NULL ); +} + +//----------------------------------------------------------------------------- +// Purpose: Attach the object to the "Buff Station." +//----------------------------------------------------------------------------- +void CObjectBuffStation::AttachObject( CBaseObject *pObject, bool bPlacing ) +{ + // Check to see if the object is already attached. + if ( IsObjectAttached( pObject ) ) + return; + + // Check to see if the object is on the correct team. + if ( !pObject->InSameTeam( this ) ) + return; + + // Check to see if the object is already being buffed by another station. + if ( pObject->IsHookedAndBuffed() ) + return; + + // Check attachment availability. + if ( m_nObjectCount == BUFF_STATION_MAX_OBJECTS ) + return; + + // Attach cable to object - get the attachment point. + int nObjectAttachPoint = pObject->LookupAttachment( "boostpoint" ); + if ( nObjectAttachPoint <= 0 ) + nObjectAttachPoint = 1; + + // Connect object. + m_hObjects.Set( m_nObjectCount, pObject ); + m_aObjectAttachInfo[m_nObjectCount].m_DamageModifier.SetModifier( obj_buff_station_damage_modifier.GetFloat() ); + m_aObjectAttachInfo[m_nObjectCount].m_hRope = CreateRope( pObject, m_aObjectAttachInfo[m_nObjectCount].m_iAttachPoint, nObjectAttachPoint ); + m_nObjectCount += 1; + + // If we're placing, we're pretending to buff objects, but not really powering them + pObject->SetBuffStation( this, bPlacing ); + + // Update think. + if ( GetNextThink(BUFF_STATION_BOOST_OBJECT_THINK_CONTEXT) > gpGlobals->curtime + BUFF_STATION_BOOST_OBJECT_THINK_INTERVAL ) + { + SetNextThink( gpGlobals->curtime + BUFF_STATION_BOOST_OBJECT_THINK_INTERVAL, BUFF_STATION_BOOST_OBJECT_THINK_CONTEXT ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Detach the object from the "Buff Station." +//----------------------------------------------------------------------------- +void CObjectBuffStation::DetachObject( CBaseObject *pObject ) +{ + for ( int iObject = 0; iObject < m_nObjectCount; ++iObject ) + { + if ( m_hObjects[iObject].Get() == pObject ) + { + DetachObjectByIndex( iObject ); + return; + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Detach the object from the "Buff Station." +//----------------------------------------------------------------------------- +void CObjectBuffStation::DetachObjectByIndex( int nIndex ) +{ + // Valid index? + Assert( nIndex >= 0 ); + Assert( nIndex < m_nObjectCount ); + + // Get the object. + CBaseObject *pObject = m_hObjects[nIndex].Get(); + if ( !pObject ) + return; + + // Remove the damage modifier. + m_aObjectAttachInfo[nIndex].m_DamageModifier.RemoveModifier(); + + // Remove the rope (cable). + if ( m_aObjectAttachInfo[nIndex].m_hRope.Get() ) + { + m_aObjectAttachInfo[nIndex].m_hRope->DetachPoint( 1 ); + m_aObjectAttachInfo[nIndex].m_hRope->DieAtNextRest(); + } + + // Reduce object count. + m_nObjectCount -= 1; + + // Set the object as unbuffed. + pObject->SetBuffStation( NULL, false ); + + // If the detached object wasn't the last object in the list, swap placement. + if ( nIndex != m_nObjectCount ) + { + SwapObjectAttachment( nIndex ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CObjectBuffStation::UpdatePlayerAttachment( CBaseTFPlayer *pPlayer ) +{ + // Valid player? + if ( !pPlayer ) + return; + + // Attach/Detach (toggle). + if ( IsPlayerAttached( pPlayer ) ) + { + DetachPlayer( pPlayer ); + } + else + { + // Check for power, do not attach to unpowered generator. + if ( !IsPowered() ) + { + ClientPrint( pPlayer, HUD_PRINTCENTER, "No power source for the Buff Station!" ); + } + else + { + AttachPlayer( pPlayer ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CObjectBuffStation::SwapObjectAttachment( int nIndex ) +{ + bool bModifierActive = m_aObjectAttachInfo[m_nObjectCount].m_DamageModifier.GetCharacter() != NULL; + m_aObjectAttachInfo[m_nObjectCount].m_DamageModifier.RemoveModifier(); + + m_hObjects.Set( nIndex, m_hObjects[m_nObjectCount] ); + m_aObjectAttachInfo[nIndex] = m_aObjectAttachInfo[m_nObjectCount]; + + if ( bModifierActive ) + { + m_aObjectAttachInfo[nIndex].m_DamageModifier.AddModifierToEntity( m_hObjects[nIndex].Get() ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Input handler +//----------------------------------------------------------------------------- +void CObjectBuffStation::InputPlayerSpawned( inputdata_t &inputdata ) +{ + if ( inputdata.pActivator->IsPlayer() ) + { + CBaseTFPlayer *pPlayer = static_cast<CBaseTFPlayer*>( inputdata.pActivator ); + if ( IsPlayerAttached( pPlayer ) ) + { + DetachPlayer( pPlayer ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Input handler +//----------------------------------------------------------------------------- +void CObjectBuffStation::InputPlayerAttachedToGenerator( inputdata_t &inputdata ) +{ + if ( inputdata.pActivator->IsPlayer() ) + { + CBaseTFPlayer *pPlayer = static_cast<CBaseTFPlayer*>( inputdata.pActivator ); + if ( IsPlayerAttached( pPlayer ) ) + { + DetachPlayer( pPlayer ); + } + } +} + +//----------------------------------------------------------------------------- +// Boost those attached to me as long as I'm not EMPed +//----------------------------------------------------------------------------- +void CObjectBuffStation::BoostPlayerThink( void ) +{ + // Are we emped? + bool bIsEmped = HasPowerup( POWERUP_EMP ); + + // Get range (squared = faster test). + float flMaxRangeSq = obj_buff_station_range.GetFloat(); + flMaxRangeSq *= flMaxRangeSq; + + // Boost all attached players and objects. + for ( int iPlayer = 0; iPlayer < BUFF_STATION_MAX_PLAYERS; iPlayer++ ) + { + // Clean up dangling pointers + dead players, subversion, disconnection + CBaseTFPlayer *pPlayer = m_hPlayers[iPlayer].Get(); + if ( !pPlayer || !pPlayer->IsAlive() || !InSameTeam( pPlayer ) || !pPlayer->PlayerClass() ) + { + DetachPlayerByIndex( iPlayer ); + continue; + } + + // Check for out of range. + float flDistSq = GetAbsOrigin().DistToSqr( pPlayer->GetAbsOrigin() ); + if ( flDistSq > flMaxRangeSq ) + { + DetachPlayerByIndex( iPlayer ); + continue; + } + + bool bBoosted = false; + if ( !bIsEmped ) + { + float flHealAmount = obj_buff_station_heal_rate.GetFloat() * BUFF_STATION_BOOST_PLAYER_THINK_INTERVAL; + bBoosted = pPlayer->AttemptToPowerup( POWERUP_BOOST, 0, flHealAmount, this, &m_aPlayerAttachInfo[iPlayer].m_DamageModifier ); + } + + if ( !bBoosted ) + { + m_aPlayerAttachInfo[iPlayer].m_DamageModifier.RemoveModifier(); + } + } + + // Set next think time. + if ( m_nPlayerCount > 0 ) + { + SetNextThink( gpGlobals->curtime + BUFF_STATION_BOOST_PLAYER_THINK_INTERVAL, + BUFF_STATION_BOOST_PLAYER_THINK_CONTEXT ); + } + else + { + SetNextThink( gpGlobals->curtime + 1.0f, BUFF_STATION_BOOST_PLAYER_THINK_CONTEXT ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CObjectBuffStation::BoostObjectThink( void ) +{ + // Set next boost object think time. + SetNextThink( gpGlobals->curtime + BUFF_STATION_BOOST_OBJECT_THINK_INTERVAL, + BUFF_STATION_BOOST_OBJECT_THINK_CONTEXT ); + + // If we're emped, placing, or building, we're not ready to powerup + if ( IsPlacing() || IsBuilding() || HasPowerup( POWERUP_EMP ) ) + return; + + float flMaxRangeSq = obj_buff_station_obj_range.GetFloat(); + flMaxRangeSq *= flMaxRangeSq; + + // Boost objects. + for ( int iObject = m_nObjectCount; --iObject >= 0; ) + { + CBaseObject *pObject = m_hObjects[iObject].Get(); + if ( !pObject || !InSameTeam( pObject ) ) + { + DetachObjectByIndex( iObject ); + continue; + } + + // Check for out of range. + float flDistSq = GetAbsOrigin().DistToSqr( pObject->GetAbsOrigin() ); + if ( flDistSq > flMaxRangeSq ) + { + DetachObjectByIndex( iObject ); + continue; + } + + // Don't powerup it until it's finished building + if ( pObject->IsPlacing() || pObject->IsBuilding() ) + continue; + + // Boost it + if ( !pObject->AttemptToPowerup( POWERUP_BOOST, BUFF_STATION_BOOST_OBJECT_THINK_INTERVAL, 0, + this, &m_aObjectAttachInfo[iObject].m_DamageModifier ) ) + { + m_aObjectAttachInfo[iObject].m_DamageModifier.RemoveModifier(); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CObjectBuffStation::DeBuffObject( CBaseObject *pObject ) +{ + DetachObject( pObject ); +} + +//----------------------------------------------------------------------------- +// Purpose: Find nearby objects and buff them. +//----------------------------------------------------------------------------- +void CObjectBuffStation::BuffNearbyObjects( CBaseObject *pObjectToTarget, bool bPlacing ) +{ + // ROBIN: Disabled object buffing for now + return; + + // Check for a team. + if ( !GetTFTeam() ) + return; + + // Am I ready to power anything? + if ( IsBuilding() || ( !bPlacing && IsPlacing() ) ) + return; + + // Am I already full? + if ( m_nObjectCount >= BUFF_STATION_MAX_OBJECTS ) + return; + + // Do we have a specific target? + if ( pObjectToTarget ) + { + if( !pObjectToTarget->CanBeHookedToBuffStation() || pObjectToTarget->GetBuffStation() ) + return; + + if ( IsWithinBuffRange( pObjectToTarget ) ) + { + AttachObject( pObjectToTarget, bPlacing ); + } + } + else + { + // Find nearby objects + for ( int iObject = 0; iObject < GetTFTeam()->GetNumObjects(); iObject++ ) + { + CBaseObject *pObject = GetTFTeam()->GetObject( iObject ); + assert(pObject); + + if ( pObject == this || !pObject->CanBeHookedToBuffStation() || pObject->GetBuffStation() ) + continue; + + // Make sure it's within range + if ( IsWithinBuffRange( pObject ) ) + { + AttachObject( pObject, bPlacing ); + + // Am I now full? + if ( m_nObjectCount >= BUFF_STATION_MAX_OBJECTS ) + break; + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Update buff connections on the fly while placing +//----------------------------------------------------------------------------- +bool CObjectBuffStation::CalculatePlacement( CBaseTFPlayer *pPlayer ) +{ + bool bReturn = BaseClass::CalculatePlacement( pPlayer ); + + // First, disconnect any connections that should break (too far away). + for ( int iObject = m_nObjectCount - 1; iObject >= 0; --iObject ) + { + if ( GetBuffedObject( iObject ) ) + { + CheckBuffConnection( GetBuffedObject( iObject ) ); + } + } + + // If we have any spare connections, look for nearby objects to buff + if ( m_nObjectCount < BUFF_STATION_MAX_OBJECTS ) + { + BuffNearbyObjects( NULL, true ); + } + + return bReturn; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CObjectBuffStation::FinishedBuilding( void ) +{ + BaseClass::FinishedBuilding(); + + for( int iObject = 0; iObject < m_nObjectCount; ++iObject ) + { + if ( GetBuffedObject( iObject ) ) + { + GetBuffedObject( iObject )->SetBuffStation( this, false ); + } + } + + BuffNearbyObjects( NULL, false ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CObjectBuffStation::CheckBuffConnection( CBaseObject *pObject ) +{ + if ( !pObject->CanBeHookedToBuffStation() ) + return; + + // Check to see if the object is within the buff range. + if ( IsWithinBuffRange( pObject ) ) + return; + + // It's obscured, or out of range. Remove it. + DetachObject( pObject ); +} + +//----------------------------------------------------------------------------- +// Purpose: Return true if this object is powerable +//----------------------------------------------------------------------------- +bool CObjectBuffStation::IsWithinBuffRange( CBaseObject *pObject ) +{ + if ( ( pObject->GetAbsOrigin() - GetAbsOrigin() ).LengthSqr() < BUFF_STATION_BUFF_RANGE ) + { + // Can I see it? + // Ignore things we're attached to + trace_t tr; + CTraceFilterWorldAndPropsOnly buffFilter; + UTIL_TraceLine( WorldSpaceCenter(), pObject->WorldSpaceCenter(), MASK_SOLID_BRUSHONLY, &buffFilter, &tr ); + CBaseEntity *pEntity = tr.m_pEnt; + if ( ( tr.fraction == 1.0 ) || ( pEntity == pObject ) ) + return true; + } + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : act - +//----------------------------------------------------------------------------- +void CObjectBuffStation::OnActivityChanged( Activity act ) +{ + BaseClass::OnActivityChanged( act ); + + switch ( act ) + { + case ACT_OBJ_ASSEMBLING: + m_bBuilding = true; + break; + default: + m_bBuilding = false; + break; + } + + SetupTeamModel(); +} + + diff --git a/game/server/tf2/tf_obj_buff_station.h b/game/server/tf2/tf_obj_buff_station.h new file mode 100644 index 0000000..089dbf9 --- /dev/null +++ b/game/server/tf2/tf_obj_buff_station.h @@ -0,0 +1,116 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Medic's portable power generator (Buff Station) +// +//=============================================================================// + +#include "tf_obj.h" + +//============================================================================= +// +// Portable Power Generator Class (Buff Station) +// +class CObjectBuffStation : public CBaseObject +{ + +DECLARE_CLASS( CObjectBuffStation, CBaseObject ); + +public: + + DECLARE_DATADESC(); + DECLARE_SERVERCLASS(); + + CObjectBuffStation(); + + static CObjectBuffStation *Create(const Vector &vOrigin, const QAngle &vAngles); + + void Spawn(); + void Precache(); + void SetupTeamModel( void ); + void GetControlPanelInfo( int nPanelIndex, const char *&pPanelName ); + void DestroyObject( void ); + void OnGoInactive( void ); + bool CalculatePlacement( CBaseTFPlayer *pPlayer ); + void FinishedBuilding( void ); + + // Attach/Detach + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + bool ClientCommand( CBaseTFPlayer *pPlayer, const CCommand &args ); + + // EMP + bool CanTakeEMPDamage( void ) { return true; } + + // Buff + void DeBuffObject( CBaseObject *pObject ); + void BuffNearbyObjects( CBaseObject *pObjectToTarget, bool bPlacing ); + void CheckBuffConnection( CBaseObject *pObject ); + + virtual void OnActivityChanged( Activity act ); +private: + + void InitAttachmentData( void ); + CRopeKeyframe *CreateRope( CBaseTFPlayer *pPlayer, int iAttachPoint ); + CRopeKeyframe *CreateRope( CBaseObject *pObject, int iAttachPoint, int iObjectAttachPoint ); + + // Attach/Detach + bool IsPlayerAttached( CBaseTFPlayer *pPlayer ); + bool IsObjectAttached( CBaseObject *pObject ); + + void AttachPlayer( CBaseTFPlayer *pPlayer ); + void DetachPlayer( CBaseTFPlayer *pPlayer ); + void DetachPlayerByIndex( int nIndex ); + void AttachObject( CBaseObject *pObject, bool bPlacing ); + void DetachObject( CBaseObject *pObject ); + void DetachObjectByIndex( int nIndex ); + + void UpdatePlayerAttachment( CBaseTFPlayer *pPlayer ); + + void SwapObjectAttachment( int nIndex ); + + // Input handlers + void InputPlayerSpawned( inputdata_t &inputdata ); + void InputPlayerAttachedToGenerator( inputdata_t &inputdata ); + + // Buff Helpers + bool IsWithinBuffRange( CBaseObject *pObject ); + CBaseObject *GetBuffedObject( int iIndex ); + + // Think + void BoostPlayerThink( void ); + void BoostObjectThink( void ); + +private: + + struct AttachInfo_t + { + CDamageModifier m_DamageModifier; + CHandle<CRopeKeyframe> m_hRope; + int m_iAttachPoint; + }; + + typedef CHandle<CBaseTFPlayer> CPlayerHandle; + CNetworkArray( CPlayerHandle, m_hPlayers, BUFF_STATION_MAX_PLAYERS ); + + CNetworkVar( int, m_nPlayerCount ); + AttachInfo_t m_aPlayerAttachInfo[BUFF_STATION_MAX_PLAYERS]; + + typedef CHandle<CBaseObject> CObjectHandle; + CNetworkArray( CObjectHandle, m_hObjects, BUFF_STATION_MAX_OBJECTS ); + + CNetworkVar( int, m_nObjectCount ); + AttachInfo_t m_aObjectAttachInfo[BUFF_STATION_MAX_OBJECTS]; + + bool m_bBuilding; +}; + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +inline CBaseObject *CObjectBuffStation::GetBuffedObject( int iIndex ) +{ + if ( ( iIndex >= 0 ) && ( iIndex < m_nObjectCount ) ) + { + return m_hObjects[iIndex].Get(); + } + + return NULL; +}
\ No newline at end of file diff --git a/game/server/tf2/tf_obj_bunker.cpp b/game/server/tf2/tf_obj_bunker.cpp new file mode 100644 index 0000000..9382aa6 --- /dev/null +++ b/game/server/tf2/tf_obj_bunker.cpp @@ -0,0 +1,174 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: A stationary bunker that players can take cover in. +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" +#include "tf_obj.h" +#include "tf_team.h" +#include "tf_obj_bunker.h" +#include "engine/IEngineSound.h" + +#define BUNKER_MINS Vector(-170, -170, 0) +#define BUNKER_MAXS Vector( 170, 170, 150) +#define BUNKER_MODEL "models/objects/obj_bunker.mdl" +#define BUNKER_LADDER_MODEL "models/objects/obj_bunker_ladder.mdl" + +IMPLEMENT_SERVERCLASS_ST(CObjectBunker, DT_ObjectBunker) +END_SEND_TABLE(); + +LINK_ENTITY_TO_CLASS(obj_bunker, CObjectBunker); +PRECACHE_REGISTER(obj_bunker); + +IMPLEMENT_SERVERCLASS_ST( CObjectBunkerLadder, DT_ObjectBunkerLadder ) +END_SEND_TABLE(); + +LINK_ENTITY_TO_CLASS( obj_bunker_ladder, CObjectBunkerLadder ); +PRECACHE_REGISTER( obj_bunker_ladder ); + +// CVars +ConVar obj_bunker_health( "obj_bunker_health","100", FCVAR_NONE, "Bunker health" ); + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CObjectBunker::CObjectBunker( void ) +{ + m_hLadder = NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CObjectBunker::Spawn( void ) +{ + Precache(); + SetModel( BUNKER_MODEL ); + + UTIL_SetSize(this, BUNKER_MINS, BUNKER_MAXS); + m_takedamage = DAMAGE_YES; + m_iHealth = obj_bunker_health.GetInt(); + + m_fObjectFlags |= OF_DOESNT_NEED_POWER; + SetType( OBJ_BUNKER ); + + SetSolid( SOLID_VPHYSICS ); + VPhysicsInitStatic(); + + BaseClass::Spawn(); + + SetCollisionGroup( COLLISION_GROUP_VEHICLE ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CObjectBunker::Precache( void ) +{ + PrecacheModel( BUNKER_MODEL ); + PrecacheModel( BUNKER_LADDER_MODEL ); +} + + +//------------------------------------------------------------------------------ +// Purpose: +//------------------------------------------------------------------------------ +void CObjectBunker::UpdateOnRemove( void ) +{ + if ( m_hLadder.Get() ) + { + UTIL_Remove( m_hLadder ); + m_hLadder = NULL; + } + + // Chain at end to mimic destructor unwind order + BaseClass::UpdateOnRemove(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CObjectBunker::FinishedBuilding( void ) +{ + BaseClass::FinishedBuilding(); + + // Create the ladder. + Vector vecOrigin; + QAngle vecAngles; + GetAttachment( "ladder", vecOrigin, vecAngles ); + m_hLadder = CObjectBunkerLadder::Create( vecOrigin, vecAngles, this ); + m_hLadder->ChangeTeam( GetTeamNumber() ); +} + +//----------------------------------------------------------------------------- +// Purpose: Gets info about the control panels +//----------------------------------------------------------------------------- +void CObjectBunker::GetControlPanelInfo( int nPanelIndex, const char *&pPanelName ) +{ + pPanelName = "screen_basic_with_disable"; +} + +//============================================================================== +// Bunker Ladder +//============================================================================== + +//------------------------------------------------------------------------------ +// Purpose: +//------------------------------------------------------------------------------ +CObjectBunkerLadder::CObjectBunkerLadder() +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CObjectBunkerLadder *CObjectBunkerLadder::Create( const Vector &vOrigin, const QAngle &vAngles, CBaseEntity *pParent ) +{ + CObjectBunkerLadder *pLadder = static_cast<CObjectBunkerLadder*>( CBaseObject::Create( "obj_bunker_ladder", vOrigin, vAngles ) ); + if ( pLadder ) + { + pLadder->m_hBunker = pParent; + } + + return pLadder; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CObjectBunkerLadder::Spawn() +{ + Precache(); + SetModel( BUNKER_LADDER_MODEL ); + SetSolid( SOLID_VPHYSICS ); + m_takedamage = DAMAGE_NO; + + BaseClass::Spawn(); + + CollisionProp()->SetSurroundingBoundsType( USE_BEST_COLLISION_BOUNDS ); + IPhysicsObject *pPhysics = VPhysicsInitStatic(); + if ( pPhysics ) + { + pPhysics->EnableMotion( false ); + } + SetCollisionGroup( COLLISION_GROUP_VEHICLE ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CObjectBunkerLadder::Precache() +{ + PrecacheModel( BUNKER_LADDER_MODEL ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Pass all damage back to the bunker +//----------------------------------------------------------------------------- +int CObjectBunkerLadder::OnTakeDamage( const CTakeDamageInfo &info ) +{ + return m_hBunker->OnTakeDamage( info ); +}
\ No newline at end of file diff --git a/game/server/tf2/tf_obj_bunker.h b/game/server/tf2/tf_obj_bunker.h new file mode 100644 index 0000000..cdfb23e --- /dev/null +++ b/game/server/tf2/tf_obj_bunker.h @@ -0,0 +1,60 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: A stationary bunker that players can take cover in. +// +//=============================================================================// + +#ifndef TF_OBJ_BUNKER_H +#define TF_OBJ_BUNKER_H +#ifdef _WIN32 +#pragma once +#endif + +class CObjectBunkerLadder; + +// ------------------------------------------------------------------------ // +// Purpose: A stationary bunker that players can take cover in. +// ------------------------------------------------------------------------ // +class CObjectBunker : public CBaseObject +{ + DECLARE_CLASS( CObjectBunker, CBaseObject ); + +public: + DECLARE_SERVERCLASS(); + + CObjectBunker( void ); + + virtual void Spawn( void ); + virtual void Precache( void ); + virtual void GetControlPanelInfo( int nPanelIndex, const char *&pPanelName ); + virtual void UpdateOnRemove( void ); + virtual void FinishedBuilding( void ); + +private: + CHandle<CObjectBunkerLadder> m_hLadder; +}; + +//----------------------------------------------------------------------------- +// Purpose: Bunker ladder +//----------------------------------------------------------------------------- +class CObjectBunkerLadder : public CBaseAnimating +{ + DECLARE_CLASS( CObjectBunkerLadder, CBaseAnimating ); + +public: + + DECLARE_SERVERCLASS(); + + static CObjectBunkerLadder* Create( const Vector &vOrigin, const QAngle &vAngles, CBaseEntity *pParent ); + + CObjectBunkerLadder(); + + void Spawn(); + void Precache(); + virtual int OnTakeDamage( const CTakeDamageInfo &info ); + +public: + EHANDLE m_hBunker; +}; + +#endif // TF_OBJ_BUNKER_H diff --git a/game/server/tf2/tf_obj_dragonsteeth.cpp b/game/server/tf2/tf_obj_dragonsteeth.cpp new file mode 100644 index 0000000..07b01d0 --- /dev/null +++ b/game/server/tf2/tf_obj_dragonsteeth.cpp @@ -0,0 +1,97 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Vehicle blockers +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "tf_player.h" +#include "tf_team.h" +#include "tf_obj.h" +#include "tf_obj_dragonsteeth.h" + +// ------------------------------------------------------------------------ // +// Dragon's teeth defines +#define DRAGONSTEETH_MINS Vector(-20, -20, 0) +#define DRAGONSTEETH_MAXS Vector( 20, 20, 35) +#define DRAGONSTEETH_MODEL "models/objects/human_obj_dragonsteeth.mdl" +#define DRAGONSTEETH_ASSEMBLING_MODEL "models/objects/human_obj_dragonsteeth_build.mdl" + +IMPLEMENT_SERVERCLASS_ST( CObjectDragonsTeeth, DT_ObjectDragonsTeeth ) +END_SEND_TABLE(); + +LINK_ENTITY_TO_CLASS(obj_dragonsteeth, CObjectDragonsTeeth); +PRECACHE_REGISTER(obj_dragonsteeth); + +ConVar obj_dragonsteeth_health( "obj_dragonsteeth_health","200", FCVAR_NONE, "Dragon's Teeth health" ); + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CObjectDragonsTeeth::CObjectDragonsTeeth() +{ + UseClientSideAnimation(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CObjectDragonsTeeth::Spawn( void ) +{ + SetModel( DRAGONSTEETH_MODEL ); + SetSolid( SOLID_BBOX ); + UTIL_SetSize(this, DRAGONSTEETH_MINS, DRAGONSTEETH_MAXS); + + m_iHealth = obj_dragonsteeth_health.GetInt(); + m_fObjectFlags |= OF_DOESNT_NEED_POWER | OF_SUPPRESS_APPEAR_ON_MINIMAP | OF_ALLOW_REPEAT_PLACEMENT | OF_ALIGN_TO_GROUND; + SetType( OBJ_DRAGONSTEETH ); + + BaseClass::Spawn(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CObjectDragonsTeeth::Precache() +{ + BaseClass::Precache(); + PrecacheModel( DRAGONSTEETH_MODEL ); + PrecacheModel( DRAGONSTEETH_ASSEMBLING_MODEL ); +} + +//----------------------------------------------------------------------------- +// Purpose: Start building the object +//----------------------------------------------------------------------------- +bool CObjectDragonsTeeth::StartBuilding( CBaseEntity *pBuilder ) +{ + if ( BaseClass::StartBuilding( pBuilder ) ) + { + // Dragonsteeth randomise their Y before building + QAngle vecAngles = GetAbsAngles(); + vecAngles[YAW] = RandomFloat(0,360); + SetAbsAngles( vecAngles ); + + return true; + } + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : act - +//----------------------------------------------------------------------------- +void CObjectDragonsTeeth::OnActivityChanged( Activity act ) +{ + BaseClass::OnActivityChanged( act ); + + switch ( act ) + { + case ACT_OBJ_ASSEMBLING: + SetModel( DRAGONSTEETH_ASSEMBLING_MODEL ); + break; + default: + SetModel( DRAGONSTEETH_MODEL ); + break; + } +} diff --git a/game/server/tf2/tf_obj_dragonsteeth.h b/game/server/tf2/tf_obj_dragonsteeth.h new file mode 100644 index 0000000..3c4dfd1 --- /dev/null +++ b/game/server/tf2/tf_obj_dragonsteeth.h @@ -0,0 +1,37 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#ifndef TF_OBJ_DRAGONSTEETH_H +#define TF_OBJ_DRAGONSTEETH_H +#ifdef _WIN32 +#pragma once +#endif + +#include "tf_obj.h" + +// ------------------------------------------------------------------------ // +// Purpose: Object built to block vehicle movement +// ------------------------------------------------------------------------ // +class CObjectDragonsTeeth : public CBaseObject +{ +DECLARE_CLASS( CObjectDragonsTeeth, CBaseObject ); + +public: + DECLARE_SERVERCLASS(); + + CObjectDragonsTeeth(); + + virtual void Spawn(); + virtual void Precache(); + virtual float GetNearbyObjectCheckRadius( void ) { return 10.0; } + virtual bool StartBuilding( CBaseEntity *pBuilder ); + virtual void OnActivityChanged( Activity act ); + +public: + CHandle< CBaseObject > m_hOwningObject; // Object I was created for +}; + +#endif // TF_OBJ_DRAGONSTEETH_H diff --git a/game/server/tf2/tf_obj_empgenerator.cpp b/game/server/tf2/tf_obj_empgenerator.cpp new file mode 100644 index 0000000..45a0ba2 --- /dev/null +++ b/game/server/tf2/tf_obj_empgenerator.cpp @@ -0,0 +1,96 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "tf_player.h" +#include "tf_team.h" +#include "tf_gamerules.h" +#include "tf_obj.h" +#include "tf_obj_empgenerator.h" +#include "ndebugoverlay.h" + +BEGIN_DATADESC( CObjectEMPGenerator ) + + DEFINE_THINKFUNC( EMPThink ), + +END_DATADESC() + +IMPLEMENT_SERVERCLASS_ST(CObjectEMPGenerator, DT_ObjectEMPGenerator) +END_SEND_TABLE(); + +LINK_ENTITY_TO_CLASS(obj_empgenerator, CObjectEMPGenerator); +PRECACHE_REGISTER(obj_empgenerator); + +ConVar obj_empgenerator_health( "obj_empgenerator_health","100", FCVAR_NONE, "EMP Generator health" ); + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CObjectEMPGenerator::CObjectEMPGenerator() +{ + UseClientSideAnimation(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CObjectEMPGenerator::Spawn() +{ + BaseClass::Spawn(); + + Precache(); + SetModel( EMPGENERATOR_MODEL ); + SetSolid( SOLID_BBOX ); + SetCollisionGroup( TFCOLLISION_GROUP_COMBATOBJECT ); + + UTIL_SetSize(this, EMPGENERATOR_MINS, EMPGENERATOR_MAXS); + m_takedamage = DAMAGE_YES; + m_iHealth = obj_empgenerator_health.GetInt(); + + SetThink( EMPThink ); + SetNextThink( gpGlobals->curtime + EMPGENERATOR_RATE ); + m_flExpiresAt = gpGlobals->curtime + EMPGENERATOR_LIFETIME; + + SetType( OBJ_EMPGENERATOR ); + m_fObjectFlags |= OF_SUPPRESS_NOTIFY_UNDER_ATTACK | OF_SUPPRESS_TECH_ANALYZER | + OF_DONT_AUTO_REPAIR | OF_DONT_PREVENT_BUILD_NEAR_OBJ | OF_DOESNT_NEED_POWER; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CObjectEMPGenerator::Precache() +{ + PrecacheModel( EMPGENERATOR_MODEL ); +} + +//----------------------------------------------------------------------------- +// Purpose: Look for enemies to EMP +//----------------------------------------------------------------------------- +void CObjectEMPGenerator::EMPThink( void ) +{ + if ( !GetTeam() ) + return; + + // Time to die? + if ( gpGlobals->curtime > m_flExpiresAt ) + { + UTIL_Remove( this ); + return; + } + + // Look for nearby enemies to EMP + CBaseEntity *pEntity = NULL; + for ( CEntitySphereQuery sphere( GetAbsOrigin(), EMPGENERATOR_RADIUS ); ( pEntity = sphere.GetCurrentEntity() ) != NULL; sphere.NextEntity() ) + { + if ( InSameTeam( pEntity ) ) + continue; + if ( !pEntity->CanBePoweredUp() ) + continue; + + pEntity->AttemptToPowerup( POWERUP_EMP, EMPGENERATOR_EMP_TIME ); + } +} diff --git a/game/server/tf2/tf_obj_empgenerator.h b/game/server/tf2/tf_obj_empgenerator.h new file mode 100644 index 0000000..ffa5274 --- /dev/null +++ b/game/server/tf2/tf_obj_empgenerator.h @@ -0,0 +1,51 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef TF_OBJ_EMPGENERATOR_H +#define TF_OBJ_EMPGENERATOR_H +#ifdef _WIN32 +#pragma once +#endif + +// ------------------------------------------------------------------------ // +// EMP Generator defines +#define EMPGENERATOR_MINS Vector(-20, -20, 0) +#define EMPGENERATOR_MAXS Vector( 20, 20, 90) +#define EMPGENERATOR_RADIUS 400 +#define EMPGENERATOR_LIFETIME 15 +#define EMPGENERATOR_RATE 3 // Rate at which it looks for enemies to EMP +#define EMPGENERATOR_EMP_TIME 5 +#define EMPGENERATOR_MODEL "models/objects/obj_antimortar.mdl" + +// ------------------------------------------------------------------------ // +// EMP Generator Combat Object +// ------------------------------------------------------------------------ // +class CObjectEMPGenerator : public CBaseObject +{ +DECLARE_CLASS( CObjectEMPGenerator, CBaseObject ); + +public: + DECLARE_DATADESC(); + DECLARE_SERVERCLASS(); + + static CObjectEMPGenerator* Create(const Vector &vOrigin, const QAngle &vAngles); + + CObjectEMPGenerator(); + + virtual void Spawn(); + virtual void Precache(); + virtual bool CanTakeEMPDamage( void ) { return true; } + + // EMP + virtual void EMPThink( void ); + +private: + float m_flExpiresAt; +}; + + +#endif // TF_OBJ_EMPGENERATOR_H diff --git a/game/server/tf2/tf_obj_explosives.cpp b/game/server/tf2/tf_obj_explosives.cpp new file mode 100644 index 0000000..eea913a --- /dev/null +++ b/game/server/tf2/tf_obj_explosives.cpp @@ -0,0 +1,119 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Upgrade that explodes when it's object dies, injuring nearby enemies +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "tf_player.h" +#include "tf_team.h" +#include "tf_gamerules.h" +#include "tf_obj.h" +#include "ndebugoverlay.h" +#include "tf_obj_baseupgrade_shared.h" +#include "hierarchy.h" + +// ------------------------------------------------------------------------ // +// Explosives defines +#define EXPLOSIVE_MINS Vector(-10, -10, 0) +#define EXPLOSIVE_MAXS Vector( 10, 10, 10) +#define EXPLOSIVE_MODEL "models/objects/obj_explosives.mdl" + +// ------------------------------------------------------------------------ // +// Explosives upgrade +// ------------------------------------------------------------------------ // +class CObjectExplosives : public CBaseObjectUpgrade +{ + DECLARE_CLASS( CObjectExplosives, CBaseObjectUpgrade ); +public: + DECLARE_DATADESC(); + DECLARE_SERVERCLASS(); + + CObjectExplosives(); + + virtual void Spawn(); + virtual void Precache(); + virtual void Killed( void ); + + // Explosivo + void ExplodeThink( void ); +}; + +BEGIN_DATADESC( CObjectExplosives ) + + DEFINE_THINKFUNC( ExplodeThink ), + +END_DATADESC() + +IMPLEMENT_SERVERCLASS_ST(CObjectExplosives, DT_ObjectExplosives) +END_SEND_TABLE(); + +LINK_ENTITY_TO_CLASS(obj_explosives, CObjectExplosives); +PRECACHE_REGISTER(obj_explosives); + +ConVar obj_explosives_health( "obj_explosives_health","1", FCVAR_NONE, "Explosives health" ); +ConVar obj_explosives_damage( "obj_explosives_damage","100", FCVAR_NONE, "Explosives damage" ); +ConVar obj_explosives_radius( "obj_explosives_radius","256", FCVAR_NONE, "Explosives damage" ); + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CObjectExplosives::CObjectExplosives() +{ + UseClientSideAnimation(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CObjectExplosives::Spawn() +{ + Precache(); + SetModel( EXPLOSIVE_MODEL ); + SetCollisionGroup( TFCOLLISION_GROUP_COMBATOBJECT ); + + UTIL_SetSize(this, EXPLOSIVE_MINS, EXPLOSIVE_MAXS); + m_takedamage = DAMAGE_YES; + m_iHealth = obj_explosives_health.GetInt(); + + SetType( OBJ_EXPLOSIVES ); + m_fObjectFlags |= OF_SUPPRESS_NOTIFY_UNDER_ATTACK | OF_SUPPRESS_TECH_ANALYZER | OF_DONT_AUTO_REPAIR | OF_MUST_BE_BUILT_ON_ATTACHMENT; + + BaseClass::Spawn(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CObjectExplosives::Precache() +{ + PrecacheModel( EXPLOSIVE_MODEL ); +} + +//----------------------------------------------------------------------------- +// Purpose: Explosivo! +//----------------------------------------------------------------------------- +void CObjectExplosives::Killed( void ) +{ + // Tell 'em I'm dying now + m_bDying = true; + + // Remove myself from the entity I was built on so it can die + DetachObjectFromObject(); + + // Delay the explosion & death so that it's not blocked by the entity we were built on + SetThink( ExplodeThink ); + SetNextThink( gpGlobals->curtime + 0.3 ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CObjectExplosives::ExplodeThink( void ) +{ + // Do radius damage + RadiusDamage( CTakeDamageInfo( this, GetBuilder(), obj_explosives_damage.GetFloat(), DMG_BLAST ), GetAbsOrigin(), obj_explosives_radius.GetFloat(), CLASS_NONE, NULL ); + + // Kill myself + BaseClass::Killed(); +}
\ No newline at end of file diff --git a/game/server/tf2/tf_obj_manned_missilelauncher.cpp b/game/server/tf2/tf_obj_manned_missilelauncher.cpp new file mode 100644 index 0000000..631911b --- /dev/null +++ b/game/server/tf2/tf_obj_manned_missilelauncher.cpp @@ -0,0 +1,227 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: A stationary gun that players can man +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" +#include "tf_obj_manned_plasmagun.h" +#include "tf_obj_manned_plasmagun_shared.h" +#include "tf_team.h" +#include "tf_obj.h" +#include "sendproxy.h" +#include "in_buttons.h" +#include "tf_player.h" +#include "ammodef.h" +#include "engine/IEngineSound.h" +#include "tf_gamerules.h" +#include "plasmaprojectile.h" +#include "tf_movedata.h" +#include "VGuiScreen.h" +#include "weapon_grenade_rocket.h" +#include "tf_obj_manned_missilelauncher.h" + +#define MANNED_MISSILELAUNCHER_CLIP_COUNT 3 + +#define MANNED_MISSILELAUNCHER_MINS Vector(-20, -20, 0) +#define MANNED_MISSILELAUNCHER_MAXS Vector( 20, 20, 55) +#define MANNED_MISSILELAUNCHER_ALIEN_MODEL "models/objects/obj_manned_plasmagun.mdl" +#define MANNED_MISSILELAUNCHER_HUMAN_MODEL "models/objects/human_obj_manned_rocketlauncher.mdl" + +#define MANNED_MISSILELAUNCHER_RECHARGE_TIME 1.5 + +#define MANNED_MISSILELAUNCHER_REFIRE_TIME 1.25 + +BEGIN_DATADESC( CObjectMannedMissileLauncher ) + DEFINE_THINKFUNC( MissileRechargeThink ), +END_DATADESC() + +IMPLEMENT_SERVERCLASS_ST(CObjectMannedMissileLauncher, DT_ObjectMannedMissileLauncher) +END_SEND_TABLE(); + +LINK_ENTITY_TO_CLASS(obj_manned_missilelauncher, CObjectMannedMissileLauncher); +PRECACHE_REGISTER(obj_manned_missilelauncher); + +// CVars +ConVar obj_manned_missilelauncher_health( "obj_manned_missilelauncher_health","100", FCVAR_NONE, "Manned Missile Launcher health" ); +ConVar obj_manned_missilelauncher_range_def( "obj_manned_missilelauncher_range_def","1100", FCVAR_NONE, "Defensive Manned Missile Launcher range" ); +ConVar obj_manned_missilelauncher_range_off( "obj_manned_missilelauncher_range_off","900", FCVAR_NONE, "Offensive Manned Missile Launcher range" ); +ConVar obj_manned_missilelauncher_damage( "obj_manned_missilelauncher_damage","150", FCVAR_NONE, "Manned Missile Launcher damage" ); +ConVar obj_manned_missilelauncher_radius( "obj_manned_missilelauncher_radius","128", FCVAR_NONE, "Manned Missile Launcher explosive radius" ); + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CObjectMannedMissileLauncher::CObjectMannedMissileLauncher() +{ +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CObjectMannedMissileLauncher::Precache() +{ + PrecacheModel( MANNED_MISSILELAUNCHER_ALIEN_MODEL ); + PrecacheModel( MANNED_MISSILELAUNCHER_HUMAN_MODEL ); + + PrecacheScriptSound( "ObjectMannedMissileLauncher.Fire" ); + PrecacheScriptSound( "ObjectMannedMissileLauncher.Reload" ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CObjectMannedMissileLauncher::Spawn() +{ + m_iHealth = obj_manned_missilelauncher_health.GetInt(); + BaseClass::Spawn(); + + SetSolid( SOLID_BBOX ); + UTIL_SetSize(this, MANNED_MISSILELAUNCHER_MINS, MANNED_MISSILELAUNCHER_MAXS); + + SetThink( MissileRechargeThink ); + SetNextThink( gpGlobals->curtime + MANNED_MISSILELAUNCHER_RECHARGE_TIME ); + + SetType( OBJ_MANNED_MISSILELAUNCHER ); + m_nAmmoType = GetAmmoDef()->Index( "Rockets" ); + m_nAmmoCount = m_nMaxAmmoCount = MANNED_MISSILELAUNCHER_CLIP_COUNT; +} + +//----------------------------------------------------------------------------- +// Purpose: Finished the build +//----------------------------------------------------------------------------- +void CObjectMannedMissileLauncher::FinishedBuilding( void ) +{ + BaseClass::FinishedBuilding(); + + CalculateMaxRange( obj_manned_missilelauncher_range_def.GetFloat(), obj_manned_missilelauncher_range_off.GetFloat() ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CObjectMannedMissileLauncher::SetupTeamModel( void ) +{ + // FIXME: When adding in build animations here, make sure C_ObjectBaseMannedGun::OnDataChanged + // does the right thing on the client!! + if ( GetTeamNumber() == TEAM_HUMANS ) + { + SetMovementStyle( MOVEMENT_STYLE_BARREL_PIVOT ); + SetModel( MANNED_MISSILELAUNCHER_HUMAN_MODEL ); + } + else + { + SetMovementStyle( MOVEMENT_STYLE_STANDARD ); + SetModel( MANNED_MISSILELAUNCHER_ALIEN_MODEL ); + } + + // Call this to get all the attachment points happy + OnModelSelected(); +} + +//----------------------------------------------------------------------------- +// Recharge think... +//----------------------------------------------------------------------------- +void CObjectMannedMissileLauncher::MissileRechargeThink( void ) +{ + // Prevent manned guns from deteriorating + ResetDeteriorationTime(); + SetNextThink( gpGlobals->curtime + (HasPowerup(POWERUP_EMP) ? (MANNED_MISSILELAUNCHER_RECHARGE_TIME * 1.5) : MANNED_MISSILELAUNCHER_RECHARGE_TIME ) ); + + // I can't do anything if I'm not active + if ( !ShouldBeActive() ) + return; + + if (m_nAmmoCount < m_nMaxAmmoCount) + { + m_nAmmoCount += 1; + + EmitSound( "ObjectMannedMissileLauncher.Reload" ); + + // Push fire out + m_flNextAttack = gpGlobals->curtime + 0.3; + } + else + { + // No need to think when it's full + SetNextThink( gpGlobals->curtime + 5.0f ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Missile Launcher fire +//----------------------------------------------------------------------------- +void CObjectMannedMissileLauncher::Fire( ) +{ + if ( m_flNextAttack > gpGlobals->curtime ) + return; + if ( !m_nAmmoCount ) + return; + + // Push recharge out + SetNextThink( gpGlobals->curtime + (HasPowerup(POWERUP_EMP) ? (MANNED_MISSILELAUNCHER_RECHARGE_TIME * 1.5) : MANNED_MISSILELAUNCHER_RECHARGE_TIME ) ); + + // We have to flush the bone cache because it's possible that only the bone controllers + // have changed since the bonecache was generated, and bone controllers aren't checked. + InvalidateBoneCache(); + + QAngle vecAng; + Vector vecSrc, vecAim; + GetAttachment( m_nBarrelAttachment, vecSrc, vecAng ); + + // Get the distance to the target + AngleVectors( vecAng, &vecAim ); + + // Create the rocket. + CWeaponGrenadeRocket *pRocket = CWeaponGrenadeRocket::Create( vecSrc, vecAim, m_flMaxRange, this ); + if ( pRocket ) + { + pRocket->SetRealOwner( GetDriverPlayer() ); + pRocket->SetDamage( obj_manned_missilelauncher_damage.GetFloat() ); + pRocket->SetDamageRadius( obj_manned_missilelauncher_radius.GetFloat() ); + } + + SetActivity( ACT_VM_PRIMARYATTACK ); + + EmitSound( "ObjectMannedMissileLauncher.Fire" ); + // SetSentryAnim( TFTURRET_ANIM_FIRE ); + DoMuzzleFlash(); + + m_nAmmoCount -= 1; + + // Slow fire rate while EMPed + m_flNextAttack = gpGlobals->curtime + ( HasPowerup(POWERUP_EMP) ? MANNED_MISSILELAUNCHER_REFIRE_TIME * 2 : MANNED_MISSILELAUNCHER_REFIRE_TIME ); +} + +#if defined( CLIENT_DLL ) +//----------------------------------------------------------------------------- +// Purpose: +// Input : updateType - +//----------------------------------------------------------------------------- +void CObjectMannedMissileLauncher::PostDataUpdate( DataUpdateType_t updateType ) +{ + BaseClass::PostDataUpdate( updateType ); + + bool teamchanged = GetTeamNumber() != m_nPreviousTeam; + + if ( teamchanged || + updateType == DATA_UPDATE_CREATED ) + { + C_BaseAnimating::AllowBoneAccess( true, false ); + SetupTeamModel(); + C_BaseAnimating::AllowBoneAccess( false, false ); + } +} + +void CObjectMannedMissileLauncher::PreDataUpdate( DataUpdateType_t updateType ) +{ + BaseClass::PreDataUpdate( updateType ); + + m_nPreviousTeam = GetTeamNumber(); +} + +#endif diff --git a/game/server/tf2/tf_obj_manned_missilelauncher.h b/game/server/tf2/tf_obj_manned_missilelauncher.h new file mode 100644 index 0000000..ac03216 --- /dev/null +++ b/game/server/tf2/tf_obj_manned_missilelauncher.h @@ -0,0 +1,57 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#ifndef TF_OBJ_MANNED_MISSILELAUNCHER_H +#define TF_OBJ_MANNED_MISSILELAUNCHER_H +#ifdef _WIN32 +#pragma once +#endif + +#include "tf_obj_base_manned_gun.h" + +// ------------------------------------------------------------------------ // +// A stationary gun that players can man that's built by the player +// ------------------------------------------------------------------------ // +class CObjectMannedMissileLauncher : public CObjectBaseMannedGun +{ + DECLARE_CLASS( CObjectMannedMissileLauncher, CObjectBaseMannedGun ); + +public: + DECLARE_SERVERCLASS(); + DECLARE_DATADESC(); + + static CObjectMannedMissileLauncher* Create(const Vector &vOrigin, const QAngle &vAngles); + + CObjectMannedMissileLauncher(); + + virtual void Spawn(); + virtual void Precache(); + virtual void FinishedBuilding( void ); + virtual void SetupTeamModel( void ); + void MissileRechargeThink( void ); + +#if defined( CLIENT_DLL ) + virtual bool ShouldPredict( void ) + { + if ( GetOwner() == C_BasePlayer::GetLocalPlayer() ) + return true; + + return BaseClass::ShouldPredict(); + } + + virtual void PreDataUpdate( DataUpdateType_t updateType ); + virtual void PostDataUpdate( DataUpdateType_t updateType ); +#endif + +protected: + // Fire the weapon + virtual void Fire( void ); + + int m_nPreviousTeam; + int m_nMaxAmmoCount; +}; + +#endif // TF_OBJ_MANNED_MISSILELAUNCHER_H diff --git a/game/server/tf2/tf_obj_manned_shield.cpp b/game/server/tf2/tf_obj_manned_shield.cpp new file mode 100644 index 0000000..878c302 --- /dev/null +++ b/game/server/tf2/tf_obj_manned_shield.cpp @@ -0,0 +1,240 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: A stationary shield that players can man +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" +#include "tf_obj_base_manned_gun.h" +#include "tf_shield.h" +#include "in_buttons.h" + + +// ------------------------------------------------------------------------ // +// A stationary gun that players can man that's built by the player +// ------------------------------------------------------------------------ // +class CObjectMannedShield : public CObjectBaseMannedGun +{ + DECLARE_CLASS( CObjectMannedShield, CObjectBaseMannedGun ); + +public: + DECLARE_SERVERCLASS(); + DECLARE_DATADESC(); + + CObjectMannedShield(); + virtual ~CObjectMannedShield(); + + static CObjectMannedShield* Create(const Vector &vOrigin, const QAngle &vAngles); + + virtual void Spawn(); + virtual void Precache(); + virtual void SetPassenger( int nRole, CBasePlayer *pEnt ); + + virtual void PowerupStart( int iPowerup, float flAmount, CBaseEntity *pAttacker, CDamageModifier *pDamageModifier ); + virtual void PowerupEnd( int iPowerup ); + +protected: + + virtual void OnItemPostFrame( CBaseTFPlayer *pDriver ); + + +private: + + void ShieldRotationThink(); + + CHandle<CShield> m_hDeployedShield; +}; + +#define MANNED_SHIELD_CLIP_COUNT 3 + +#define MANNED_SHIELD_MINS Vector(-20, -20, 0) +#define MANNED_SHIELD_MAXS Vector( 20, 20, 55) +#define MANNED_SHIELD_MODEL "models/objects/obj_manned_plasmagun.mdl" + + +BEGIN_DATADESC( CObjectMannedShield ) +END_DATADESC() + +IMPLEMENT_SERVERCLASS_ST(CObjectMannedShield, DT_ObjectMannedShield) +END_SEND_TABLE() + +LINK_ENTITY_TO_CLASS(obj_manned_shield, CObjectMannedShield); +PRECACHE_REGISTER(obj_manned_shield); + +// CVars +ConVar obj_manned_shield_health( "obj_manned_shield_health","100", FCVAR_NONE, "Manned Missile Launcher health" ); + +const char *g_pMannedShieldThinkContextName = "MannedShieldThinkContext"; + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CObjectMannedShield::CObjectMannedShield() +{ +} + +CObjectMannedShield::~CObjectMannedShield() +{ + UTIL_Remove( m_hDeployedShield ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CObjectMannedShield::Spawn() +{ + m_iHealth = obj_manned_shield_health.GetInt(); + BaseClass::Spawn(); + + SetMovementStyle( MOVEMENT_STYLE_SIMPLE ); + SetModel( MANNED_SHIELD_MODEL ); + OnModelSelected(); + SetSolid( SOLID_BBOX ); + UTIL_SetSize(this, MANNED_SHIELD_MINS, MANNED_SHIELD_MAXS); + + SetMaxPassengerCount( 1 ); + + SetType( OBJ_MANNED_SHIELD ); + + SetContextThink( ShieldRotationThink, gpGlobals->curtime + 0.1, g_pMannedShieldThinkContextName ); +} + +//----------------------------------------------------------------------------- +// Purpose: Powerup has just started +//----------------------------------------------------------------------------- +void CObjectMannedShield::PowerupStart( int iPowerup, float flAmount, CBaseEntity *pAttacker, CDamageModifier *pDamageModifier ) +{ + switch( iPowerup ) + { + case POWERUP_BOOST: + // Increase our shield's energy + if ( m_hDeployedShield ) + { + m_hDeployedShield->SetPower( m_hDeployedShield->GetPower() + (flAmount * 10) ); + } + break; + + case POWERUP_EMP: + if (m_hDeployedShield) + { + m_hDeployedShield->SetEMPed(true); + } + break; + + default: + break; + } + + BaseClass::PowerupStart( iPowerup, flAmount, pAttacker, pDamageModifier ); +} + +//----------------------------------------------------------------------------- +// Purpose: Powerup has just started +//----------------------------------------------------------------------------- +void CObjectMannedShield::PowerupEnd( int iPowerup ) +{ + switch ( iPowerup ) + { + case POWERUP_EMP: + if (m_hDeployedShield) + { + m_hDeployedShield->SetEMPed(false); + } + break; + + default: + break; + } + + BaseClass::PowerupEnd( iPowerup ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CObjectMannedShield::ShieldRotationThink( void ) +{ + if ( m_hDeployedShield ) + { + QAngle vAngles; + vAngles[YAW] = GetAbsAngles()[YAW] + GetGunYaw(); + vAngles[PITCH] = GetAbsAngles()[PITCH] + GetGunPitch(); + vAngles[ROLL] = 0; + m_hDeployedShield->SetCenterAngles( vAngles ); + } + + SetContextThink( ShieldRotationThink, gpGlobals->curtime + 0.1, g_pMannedShieldThinkContextName ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CObjectMannedShield::Precache( void ) +{ + PrecacheModel( MANNED_SHIELD_MODEL ); +} + +//----------------------------------------------------------------------------- +// Purpose: Get and set the current driver. +//----------------------------------------------------------------------------- +void CObjectMannedShield::SetPassenger( int nRole, CBasePlayer *pEnt ) +{ + BaseClass::SetPassenger( nRole, pEnt ); + + // If we just got a player, create the shield. Otherwise, remove it + if ( pEnt ) + { + if ( !m_hDeployedShield ) + { + // Hack our angles so the mobile shield appears directly in front of the gun + QAngle vecOldAngles = GetAbsAngles(); + QAngle vAngles; + vAngles[YAW] = GetAbsAngles()[YAW] + GetGunYaw(); + vAngles[PITCH] = GetAbsAngles()[PITCH] + GetGunPitch(); + vAngles[ROLL] = 0; + SetAbsAngles( vAngles ); + + int nAttachmentIndex = LookupAttachment( "barrel" ); + m_hDeployedShield = CreateMobileShield( this, 0 ); + if ( m_hDeployedShield ) + { + m_hDeployedShield->SetThetaPhi( 60, 40 ); + m_hDeployedShield->SetAngularSpringConstant( 15 ); + m_hDeployedShield->SetAlwaysOrient( false ); + m_hDeployedShield->SetAttachmentIndex( nAttachmentIndex ); + } + + SetAbsAngles( vecOldAngles ); + } + } + else + { + if ( m_hDeployedShield ) + { + UTIL_Remove( m_hDeployedShield ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CObjectMannedShield::OnItemPostFrame( CBaseTFPlayer *pDriver ) +{ + int iOldButtons = pDriver->m_nButtons; + + // Manned shields have no gun, so map both attack buttons to laser designate + if ( pDriver->m_nButtons & IN_ATTACK ) + { + pDriver->m_nButtons &= ~IN_ATTACK; + pDriver->m_nButtons |= IN_ATTACK2; + } + + BaseClass::OnItemPostFrame( pDriver ); + + pDriver->m_nButtons = iOldButtons; +} diff --git a/game/server/tf2/tf_obj_mapdefined.cpp b/game/server/tf2/tf_obj_mapdefined.cpp new file mode 100644 index 0000000..56b1efa --- /dev/null +++ b/game/server/tf2/tf_obj_mapdefined.cpp @@ -0,0 +1,140 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "tf_player.h" +#include "tf_team.h" +#include "tf_gamerules.h" +#include "tf_obj.h" +#include "tf_obj_mapdefined.h" +#include "ndebugoverlay.h" + +extern ConVar obj_damage_factor; + +// Map defined object spawnflags +#define SF_MAPDEFOBJ_SUPPRESS_MINIMAP 0x0001 +#define SF_MAPDEFOBJ_SUPPRESS_ATTACKNOTIFY 0x0002 +#define SF_MAPDEFOBJ_DOESNT_NEED_POWER 0x0004 + +BEGIN_DATADESC( CObjectMapDefined ) + // keys + DEFINE_KEYFIELD_NOT_SAVED( m_iszCustomName , FIELD_STRING, "CustomName" ), +END_DATADESC() + +IMPLEMENT_SERVERCLASS_ST(CObjectMapDefined, DT_ObjectMapDefined) + SendPropString( SENDINFO( m_szCustomName ) ), +END_SEND_TABLE(); + +LINK_ENTITY_TO_CLASS(obj_mapdefined, CObjectMapDefined); +LINK_ENTITY_TO_CLASS(func_obj_mapdefined, CObjectMapDefined); + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CObjectMapDefined::CObjectMapDefined() +{ + UseClientSideAnimation(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CObjectMapDefined::Spawn() +{ + // Get the model from the map + char *szModel = (char *)STRING( GetModelName() ); + if (!szModel || !*szModel) + { + Warning( "obj_mapdefined at %.0f %.0f %0.f missing modelname\n", GetAbsOrigin().x, GetAbsOrigin().y, GetAbsOrigin().z ); + UTIL_Remove( this ); + return; + } + + memset( m_szCustomName.GetForModify(), 0, sizeof(m_szCustomName) ); + if ( m_iszCustomName != NULL_STRING ) + { + Q_strncpy( m_szCustomName.GetForModify(), STRING(m_iszCustomName), sizeof(m_szCustomName) ); + } + + Precache(); + + // Get the bounding box from the model + if ( szModel[0] != '*' ) + { + SetModel( szModel ); + Vector vecMins, vecMaxs; + const model_t *pModel = GetModel(); + modelinfo->GetModelBounds( pModel, vecMins, vecMaxs ); + UTIL_SetSize(this, vecMins, vecMaxs ); + } + else + { + // Don't call our internal setmodel to avoid the error + UTIL_SetModel( this, szModel ); + + // No control panels / power on map geometry objects + m_fObjectFlags |= OF_DOESNT_HAVE_A_MODEL; + m_fObjectFlags |= OF_DOESNT_NEED_POWER; + } + + SetSolid( SOLID_VPHYSICS ); + SetType( OBJ_MAPDEFINED ); + + // Setup object flags + if ( HasSpawnFlags( SF_MAPDEFOBJ_SUPPRESS_MINIMAP ) ) + { + m_fObjectFlags |= OF_SUPPRESS_APPEAR_ON_MINIMAP; + } + if ( HasSpawnFlags( SF_MAPDEFOBJ_SUPPRESS_ATTACKNOTIFY ) ) + { + m_fObjectFlags |= OF_SUPPRESS_NOTIFY_UNDER_ATTACK; + } + if ( HasSpawnFlags( SF_MAPDEFOBJ_DOESNT_NEED_POWER ) ) + { + m_fObjectFlags |= OF_DOESNT_NEED_POWER; + } + + // If I don't have health, make me invulnerable + if ( !m_iHealth ) + { + m_bInvulnerable = true; + } + + BaseClass::Spawn(); + + // Override base object settings + SetCollisionGroup( COLLISION_GROUP_NONE ); + + FinishedBuilding(); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CObjectMapDefined::Precache() +{ + PrecacheModel( STRING( GetModelName() ) ); + BaseClass::Precache(); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int CObjectMapDefined::OnTakeDamage( const CTakeDamageInfo &info ) +{ + CTakeDamageInfo childInfo = info; + + // Hack around the damage factor applied to objects + if ( obj_damage_factor.GetFloat() ) + { + float flDamage = info.GetDamage() * (1 / obj_damage_factor.GetFloat()); + childInfo.SetDamage( flDamage ); + } + + return BaseClass::OnTakeDamage( childInfo ); +}
\ No newline at end of file diff --git a/game/server/tf2/tf_obj_mapdefined.h b/game/server/tf2/tf_obj_mapdefined.h new file mode 100644 index 0000000..2b1d837 --- /dev/null +++ b/game/server/tf2/tf_obj_mapdefined.h @@ -0,0 +1,40 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef TF_OBJ_MAPDEFINED_H +#define TF_OBJ_MAPDEFINED_H +#ifdef _WIN32 +#pragma once +#endif + +// ------------------------------------------------------------------------ // +// Purpose: Map Defined object placed by mapmakers +// ------------------------------------------------------------------------ // +class CObjectMapDefined : public CBaseObject +{ +DECLARE_CLASS( CObjectMapDefined, CBaseObject ); + +public: + DECLARE_DATADESC(); + DECLARE_SERVERCLASS(); + + static CObjectMapDefined* Create(const Vector &vOrigin, const QAngle &vAngles); + + CObjectMapDefined(); + + virtual void Spawn(); + virtual void Precache(); + virtual bool CanTakeEMPDamage( void ) { return true; } + virtual int OnTakeDamage( const CTakeDamageInfo &info ); + +private: + // Custom names for ID strings + string_t m_iszCustomName; + CNetworkString( m_szCustomName, MAX_OBJ_CUSTOMNAME_SIZE ); +}; + +#endif // TF_OBJ_MAPDEFINED_H diff --git a/game/server/tf2/tf_obj_mcv_selection_panel.cpp b/game/server/tf2/tf_obj_mcv_selection_panel.cpp new file mode 100644 index 0000000..c293132 --- /dev/null +++ b/game/server/tf2/tf_obj_mcv_selection_panel.cpp @@ -0,0 +1,138 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "cbase.h" +#include "tf_obj.h" +#include "tf_shareddefs.h" +#include "vguiscreen.h" +#include "tf_vehicle_teleport_station.h" + + +#define MCV_SELECTION_MODEL "models/objects/obj_resupply.mdl" +#define MCV_SELECTION_SCREEN_NAME "screen_mcv_selection_panel" + + +class CObjMCVSelectionPanel : public CBaseObject +{ +public: + + DECLARE_CLASS( CObjMCVSelectionPanel, CBaseObject ); + DECLARE_SERVERCLASS(); + + CObjMCVSelectionPanel(); + ~CObjMCVSelectionPanel(); + + +public: + + virtual void Spawn(); + virtual void Precache(); + virtual void GetControlPanelInfo( int nPanelIndex, const char *&pPanelName ); + virtual bool ClientCommand( CBaseTFPlayer *pPlayer, const char *pCmd, ICommandArguments *pArg ); + + virtual void SetTransmit( CCheckTransmitInfo *pInfo, bool bAlways ); +}; + + +// This holds all the allocated CObjMCVSelectionPanels. +CUtlLinkedList<CObjMCVSelectionPanel*,int> g_MCVSelectionPanels; + + +LINK_ENTITY_TO_CLASS( obj_mcv_selection_panel, CObjMCVSelectionPanel ); + + +int SendProxy_TeleportStationCount( const void *pStruct, int objectID ) +{ + return CVehicleTeleportStation::GetNumDeployedTeleportStations(); +} + + +void SendProxy_TeleportStationElement( const SendProp *pProp, const void *pStructBase, const void *pData, DVariant *pOut, int iElement, int objectID ) +{ + // Get the EHANDLE. + EHANDLE hEnt; + hEnt = CVehicleTeleportStation::GetDeployedTeleportStation( iElement ); + + // Use the standard ehandle-encoding SendProxy to encode it. + SendProxy_EHandleToInt( pProp, pStructBase, &hEnt, pOut, iElement, objectID ); +} + + +void SignalChangeInMCVSelectionPanels() +{ +} + + +IMPLEMENT_SERVERCLASS_ST( CObjMCVSelectionPanel, DT_MCVSelectionPanel ) + SendPropVirtualArray( + SendProxy_TeleportStationCount, + 32, // max # elements we'd ever send + SendPropEHandle( "teleport_station_element", 0, 0, 0, SendProxy_TeleportStationElement ), + "teleport_stations" ) +END_SEND_TABLE() + + +CObjMCVSelectionPanel::CObjMCVSelectionPanel() +{ + g_MCVSelectionPanels.AddToTail( this ); +} + + +CObjMCVSelectionPanel::~CObjMCVSelectionPanel() +{ + g_MCVSelectionPanels.FindAndRemove( this ); +} + + +void CObjMCVSelectionPanel::Spawn() +{ + SetModel( MCV_SELECTION_MODEL ); + m_takedamage = DAMAGE_NO; + SetType( OBJ_MCV_SELECTION_PANEL ); + + BaseClass::Spawn(); +} + + +void CObjMCVSelectionPanel::Precache() +{ + PrecacheModel( MCV_SELECTION_MODEL ); + PrecacheVGuiScreen( MCV_SELECTION_SCREEN_NAME ); + + BaseClass::Precache(); +} + + +void CObjMCVSelectionPanel::GetControlPanelInfo( int nPanelIndex, const char *&pPanelName ) +{ + pPanelName = MCV_SELECTION_SCREEN_NAME; +} + + +bool CObjMCVSelectionPanel::ClientCommand( CBaseTFPlayer *pPlayer, const char *pCmd, ICommandArguments *pArg ) +{ + if ( stricmp( pCmd, "SelectMCV" ) == 0 ) + { + int mcvID = atoi( pArg->Argv( 1 ) ); + pPlayer->SetSelectedMCV( dynamic_cast< CVehicleTeleportStation* >( CBaseEntity::Instance( mcvID ) ) ); + } + + return BaseClass::ClientCommand( pPlayer, pCmd, pArg ); +} + + +void CObjMCVSelectionPanel::SetTransmit( CCheckTransmitInfo *pInfo, bool bAlways ) +{ + BaseClass::SetTransmit( pInfo, bAlways ); + + // Force deployed MCVs to be sent to the client too so the client can draw their position on its vgui screen. + int count = CVehicleTeleportStation::GetNumDeployedTeleportStations(); + for ( int i=0; i < count; i++ ) + { + CVehicleTeleportStation::GetDeployedTeleportStation( i )->SetTransmit( pInfo, bAlways ); + } +} + diff --git a/game/server/tf2/tf_obj_mcv_selection_panel.h b/game/server/tf2/tf_obj_mcv_selection_panel.h new file mode 100644 index 0000000..47e6984 --- /dev/null +++ b/game/server/tf2/tf_obj_mcv_selection_panel.h @@ -0,0 +1,18 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#ifndef TF_OBJ_MCV_SELECTION_PANEL_H +#define TF_OBJ_MCV_SELECTION_PANEL_H +#ifdef _WIN32 +#pragma once +#endif + + +// Tells the MCV selection panels to detect network state changes. +void SignalChangeInMCVSelectionPanels(); + + +#endif // TF_OBJ_MCV_SELECTION_PANEL_H diff --git a/game/server/tf2/tf_obj_mortar.cpp b/game/server/tf2/tf_obj_mortar.cpp new file mode 100644 index 0000000..2473519 --- /dev/null +++ b/game/server/tf2/tf_obj_mortar.cpp @@ -0,0 +1,266 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Indirect's mortar object +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "tf_player.h" +#include "tf_team.h" +#include "basecombatweapon.h" +#include "tf_obj.h" +#include "tf_obj_mortar.h" +#include "techtree.h" +#include "tf_shareddefs.h" +#include "weapon_mortar.h" +#include "vstdlib/random.h" +#include "movevars_shared.h" +#include "mortar_round.h" + + +LINK_ENTITY_TO_CLASS(obj_mortar, CObjectMortar); +PRECACHE_REGISTER(obj_mortar); + +IMPLEMENT_SERVERCLASS_ST(CObjectMortar, DT_ObjectMortar) + SendPropInt( SENDINFO( m_iRoundType ), 8, SPROP_UNSIGNED, 0 ), + SendPropArray( SendPropInt( SENDINFO_ARRAY(m_iMortarRounds), 7, 0 ), m_iMortarRounds ), +END_SEND_TABLE(); + +BEGIN_DATADESC( CObjectMortar ) + + DEFINE_THINKFUNC( ReloadingThink ), + +END_DATADESC() + +// Mortar size +#define MORTAR_MINS Vector(-16, -16, 0) +#define MORTAR_MAXS Vector( 16, 16, 64) + +ConVar obj_mortar_health( "obj_mortar_health","200", FCVAR_NONE, "Mortar object health" ); + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CObjectMortar::CObjectMortar() +{ + for ( int i=0; i < m_iMortarRounds.Count(); i++ ) + m_iMortarRounds.Set( i, 0 ); + m_flLastBlastTime = -1; + UseClientSideAnimation(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CObjectMortar::Spawn() +{ + SetModel( "models/objects/obj_mortar.mdl" ); + + SetSolid( SOLID_BBOX ); + UTIL_SetSize(this, MORTAR_MINS, MORTAR_MAXS); + m_takedamage = DAMAGE_YES; + m_iHealth = obj_mortar_health.GetInt(); + m_iRoundType = MA_SHELL; + m_iSalvoLeft = MORTAR_SALVO_SIZE; + m_fObjectFlags |= OF_DONT_PREVENT_BUILD_NEAR_OBJ; + + SetType( OBJ_MORTAR ); + + // Fill out the ammo levels + for ( int i = 0; i < MA_LASTAMMOTYPE; i++ ) + { + m_iMortarRounds.Set( i, MortarAmmoMax[i] ); + } + + BaseClass::Spawn(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CObjectMortar::Precache() +{ + PrecacheModel( "models/objects/obj_mortar.mdl" ); + PrecacheVGuiScreen( "screen_obj_mortar" ); +} + +//----------------------------------------------------------------------------- +// Gets info about the control panels +//----------------------------------------------------------------------------- +void CObjectMortar::GetControlPanelInfo( int nPanelIndex, const char *&pPanelName ) +{ + pPanelName = "screen_obj_mortar"; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CObjectMortar::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + // Sapper removal + if ( RemoveEnemyAttachments( pActivator ) ) + return; + + if ( pActivator == GetOwner() ) + { + int iOldType = m_iRoundType; + m_iRoundType += 1; + + // Cycle to the next ammo type + while ( m_iRoundType != iOldType ) + { + // Hit the end of the round types? + if ( m_iRoundType == MA_LASTAMMOTYPE ) + { + m_iRoundType = MA_SHELL; + break; + } + + // Does this round type need a technology? + if ( MortarAmmoTechs[ m_iRoundType ] && MortarAmmoTechs[ m_iRoundType ][0] ) + { + // Does the player have the technology? + if ( GetOwner() && GetOwner()->HasNamedTechnology( MortarAmmoTechs[ m_iRoundType ] ) ) + { + // Do we have ammo? + if ( m_iMortarRounds[ m_iRoundType ] > 0 ) + break; + } + } + + // Go to the next round type + m_iRoundType += 1; + } + } + else + { + // Let other team's technician try to subvert it + BaseClass::Use( pActivator, pCaller, useType, value ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Fire a round from the mortar +//----------------------------------------------------------------------------- +bool CObjectMortar::FireMortar( float flFiringPower, float flFiringAccuracy, bool bRangeUpgraded, bool bAccuracyUpgraded ) +{ + // Are we reloading? + if ( IsReloading() ) + return false; + // Do we have any ammo of this type left? + if ( m_iMortarRounds[ m_iRoundType ] != -1 && m_iMortarRounds[ m_iRoundType ] == 0 ) + return false; + + // Get target distance + float flDistance; + if ( bRangeUpgraded ) + { + flDistance = MORTAR_RANGE_MIN + (flFiringPower * (MORTAR_RANGE_MAX_UPGRADED - MORTAR_RANGE_MIN)); + } + else + { + flDistance = MORTAR_RANGE_MIN + (flFiringPower * (MORTAR_RANGE_MAX_INITIAL - MORTAR_RANGE_MIN)); + } + + // Factor in inaccuracy + float flInaccuracy; + if ( bAccuracyUpgraded ) + { + flInaccuracy = MORTAR_INACCURACY_MAX_UPGRADED * (flFiringAccuracy * 4); // flFiringAccuracy is a range from -0.25 to 0.25 + } + else + { + flInaccuracy = MORTAR_INACCURACY_MAX_INITIAL * (flFiringAccuracy * 4); // flFiringAccuracy is a range from -0.25 to 0.25 + } + flDistance += (flDistance * MORTAR_DIST_INACCURACY) * random->RandomFloat( -flInaccuracy, flInaccuracy ); + + Vector forward, right; + AngleVectors( GetAbsAngles(), &forward, &right, NULL ); + Vector vecTargetOrg = GetAbsOrigin() + (forward * flDistance); + // Add in sideways inaccuracy + vecTargetOrg += (right * (flDistance * flInaccuracy) ); + + // Trace down from the sky and find the point we're actually going to hit + trace_t tr; + Vector vecSky = vecTargetOrg + Vector(0,0,1024); + UTIL_TraceLine( vecSky, vecTargetOrg, MASK_ALL, this, COLLISION_GROUP_NONE, &tr ); + vecTargetOrg = tr.endpos; + + Vector vecMidPoint = vec3_origin; + // Start with a low arc, and keep aiming higher until we've got a roughly clear shot + for (int i = 2048; i <= 4096; i += 1024) + { + trace_t tr1; + trace_t tr2; + + vecMidPoint = Vector(0,0,i) + GetAbsOrigin() + (vecTargetOrg - GetAbsOrigin()) * 0.5; + UTIL_TraceLine(GetAbsOrigin(), vecMidPoint, MASK_ALL, this, COLLISION_GROUP_NONE, &tr1); + UTIL_TraceLine(vecMidPoint, vecTargetOrg, MASK_ALL, this, COLLISION_GROUP_NONE, &tr2); + + // Clear shot? + // We want a clear shot for the first half, and a fairly clear shot on the fall + if ( tr1.fraction == 1 && tr2.fraction > 0.5 ) + break; + } + + // How high should we travel to reach the apex + float distance1 = (vecMidPoint.z - GetAbsOrigin().z); + float distance2 = (vecMidPoint.z - vecTargetOrg.z); + + // How long will it take to travel this distance + float flGravity = GetCurrentGravity(); + float time1 = sqrt( distance1 / (0.5 * flGravity) ); + float time2 = sqrt( distance2 / (0.5 * flGravity) ); + if (time1 < 0.1) + return false; + + // how hard to launch to get there in time. + Vector vecTargetVel = (vecTargetOrg - GetLocalOrigin()) / (time1 + time2); + vecTargetVel.z = flGravity * time1; + + // Create the round + CMortarRound *pRound = CMortarRound::Create( GetLocalOrigin(), vecTargetVel, edict() ); + pRound->ChangeTeam( GetTeamNumber() ); + pRound->SetFallTime( time1 * 0.5 ); // Start a falling sound just a bit before we begin to fall + pRound->SetRoundType( m_iRoundType ); + + // Decrease ammo count + if ( m_iMortarRounds[ m_iRoundType ] > 0 ) + { + m_iMortarRounds.Set( m_iRoundType, m_iMortarRounds[m_iRoundType]-1 ); + } + + // Decrease salvo count + if ( m_iSalvoLeft ) + { + m_iSalvoLeft--; + if ( m_iSalvoLeft <= 0 ) + { + // Time to reload + StartReloading(); + } + } + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: Start reloading our next salvo +//----------------------------------------------------------------------------- +void CObjectMortar::StartReloading( void ) +{ + SetThink( ReloadingThink ); + SetNextThink( gpGlobals->curtime + MORTAR_RELOAD_TIME ); +} + +//----------------------------------------------------------------------------- +// Purpose: Finish reloading our salvo +//----------------------------------------------------------------------------- +void CObjectMortar::ReloadingThink( void ) +{ + SetThink( NULL ); + + m_iSalvoLeft = MORTAR_SALVO_SIZE; +} + diff --git a/game/server/tf2/tf_obj_mortar.h b/game/server/tf2/tf_obj_mortar.h new file mode 100644 index 0000000..da44da8 --- /dev/null +++ b/game/server/tf2/tf_obj_mortar.h @@ -0,0 +1,67 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef TF_OBJ_MORTAR_H +#define TF_OBJ_MORTAR_H +#ifdef _WIN32 +#pragma once +#endif + +#include "tf_obj.h" +#include "utllinkedlist.h" + +#define MAX_DEPLOYED_MORTARS 1 + +class CWeaponMortar; + +// ------------------------------------------------------------------------ // +// Mortar object that's built by the player +// ------------------------------------------------------------------------ // +class CObjectMortar : public CBaseObject +{ + DECLARE_CLASS( CObjectMortar, CBaseObject ); +public: + static CObjectMortar* Create(const Vector &vOrigin, const QAngle &vAngles, int team); + + DECLARE_SERVERCLASS(); + DECLARE_DATADESC(); + + CObjectMortar(); + + virtual int ObjectCaps( void ) { return FCAP_IMPULSE_USE; }; + virtual void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + virtual void Spawn(); + virtual void Precache(); + virtual void GetControlPanelInfo( int nPanelIndex, const char *&pPanelName ); + + // Firing called by the mortar "weapon" + bool FireMortar( float flFiringPower, float flFiringAccuracy, bool bRangeUpgraded, bool bAccuracyUpgraded ); + + // Salvo reloading + void StartReloading( void ); + void ReloadingThink( void ); + bool IsReloading( void ) { return (m_iSalvoLeft <= 0); }; + + float LastBlastTime() { return m_flLastBlastTime; } + void SetBlastTime( float time ) { m_flLastBlastTime = time; } + + const Vector &LastBlastPosition() { return m_vLastBlastPos; } + void SetBlastPosition( const Vector &pos ) { m_vLastBlastPos = pos; } + +private: + CNetworkVar( int, m_iRoundType ); + CNetworkArray( int, m_iMortarRounds, MA_LASTAMMOTYPE ); + + int m_iSalvoLeft; // Decremented every shot. Once depleted, the mortar must reload. + + // Stored for the global mortar list for anti-mortar orders. + unsigned short m_MortarListIndex; + Vector m_vLastBlastPos; + double m_flLastBlastTime; // -1 if no shots have hit anything yet. +}; + +#endif // TF_OBJ_MORTAR_H diff --git a/game/server/tf2/tf_obj_powerpack.cpp b/game/server/tf2/tf_obj_powerpack.cpp new file mode 100644 index 0000000..5ab64d6 --- /dev/null +++ b/game/server/tf2/tf_obj_powerpack.cpp @@ -0,0 +1,366 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Human's power pack +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "tf_player.h" +#include "tf_team.h" +#include "tf_obj.h" +#include "tf_obj_powerpack.h" +#include "tf_func_resource.h" +#include "resource_chunk.h" +#include "techtree.h" +#include "sendproxy.h" +#include "vstdlib/random.h" +#include "tf_stats.h" +#include "rope.h" +#include "tf_shareddefs.h" +#include "VGuiScreen.h" +#include "hierarchy.h" + +#define POWERPACK_MODEL "models/objects/human_obj_powerpack.mdl" +#define POWERPACK_ASSEMBLING_MODEL "models/objects/human_obj_powerpack_build.mdl" + +IMPLEMENT_SERVERCLASS_ST( CObjectPowerPack, DT_ObjectPowerPack ) + SendPropInt( SENDINFO(m_iObjectsAttached), 3, SPROP_UNSIGNED ), +END_SEND_TABLE(); + +LINK_ENTITY_TO_CLASS(obj_powerpack, CObjectPowerPack); +PRECACHE_REGISTER(obj_powerpack); + +ConVar obj_powerpack_health( "obj_powerpack_health","100", FCVAR_NONE, "Human powerpack health" ); + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CObjectPowerPack::CObjectPowerPack() +{ + UseClientSideAnimation(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CObjectPowerPack::Spawn( void ) +{ + SetModel( POWERPACK_MODEL ); + SetSolid( SOLID_BBOX ); + UTIL_SetSize(this, POWERPACK_MINS, POWERPACK_MAXS); + + m_iHealth = obj_powerpack_health.GetInt(); + m_fObjectFlags |= OF_DOESNT_NEED_POWER; + SetType( OBJ_POWERPACK ); + m_hPoweredObjects.Purge(); + m_iFreeAttachments = 0; + m_iObjectsAttached = 0; + + BaseClass::Spawn(); +} + + +//----------------------------------------------------------------------------- +// Gets info about the control panels +//----------------------------------------------------------------------------- +void CObjectPowerPack::GetControlPanelInfo( int nPanelIndex, const char *&pPanelName ) +{ + pPanelName = "screen_obj_power_pack"; +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CObjectPowerPack::FinishedBuilding( void ) +{ + BaseClass::FinishedBuilding(); + + // Now tell all our objects we connected to, during placement, that they're really getting power + // Walk backwards, because we might remove objects from our list that have somehow gained power + // inbetween the time we placed and the time we finished building. + int iSize = m_hPoweredObjects.Count(); + for (int i = iSize-1; i >= 0; i--) + { + if ( m_hPoweredObjects[i] ) + { + if ( m_hPoweredObjects[i]->IsPowered() ) + { + UnPowerObject( m_hPoweredObjects[i] ); + } + else + { + m_hPoweredObjects[i]->SetPowerPack( this ); + } + } + } + + PowerNearbyObjects(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CObjectPowerPack::Precache() +{ + BaseClass::Precache(); + PrecacheModel( POWERPACK_MODEL ); + PrecacheModel( POWERPACK_ASSEMBLING_MODEL ); + PrecacheVGuiScreen( "screen_obj_power_pack" ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CObjectPowerPack::DestroyObject( void ) +{ + // Remove power from all my objects (backwards because list will change) + int iSize = m_hPoweredObjects.Count(); + for (int i = iSize-1; i >= 0; i--) + { + if ( m_hPoweredObjects[i] ) + { + UnPowerObject( m_hPoweredObjects[i] ); + } + } + + // Now tell all other powerpacks on this team to power nearby objects, in case they can cover for this one. + if ( GetTFTeam() ) + { + GetTFTeam()->UpdatePowerpacks( this, NULL ); + } + + BaseClass::DestroyObject(); +} + +//----------------------------------------------------------------------------- +// Purpose: Update power connections on the fly while placing +//----------------------------------------------------------------------------- +bool CObjectPowerPack::CalculatePlacement( CBaseTFPlayer *pPlayer ) +{ + bool bReturn = BaseClass::CalculatePlacement( pPlayer ); + + // First, disconnect any connections that should break + int iSize = m_hPoweredObjects.Count(); + for (int i = iSize-1; i >= 0; i--) + { + if ( m_hPoweredObjects[i] ) + { + EnsureObjectPower( m_hPoweredObjects[i] ); + } + } + + // If we have any spare connections, look for nearby objects to power + if ( m_hPoweredObjects.Count() < MAX_OBJECTS_PER_PACK ) + { + PowerNearbyObjects( NULL, true ); + } + + return bReturn; +} + +//----------------------------------------------------------------------------- +// Purpose: Find nearby objects and provide them with power +//----------------------------------------------------------------------------- +void CObjectPowerPack::PowerNearbyObjects( CBaseObject *pObjectToTarget, bool bPlacing ) +{ + if ( !GetTFTeam() ) + return; + // Am I ready to power anything? + if ( IsBuilding() || (!bPlacing && IsPlacing()) ) + return; + + // Am I already full? + if ( m_hPoweredObjects.Count() >= MAX_OBJECTS_PER_PACK ) + return; + + // Do we have a specific target? + if ( pObjectToTarget ) + { + if ( !pObjectToTarget->CanPowerupNow(POWERUP_POWER) ) + return; + + if ( IsWithinPowerRange( pObjectToTarget ) ) + { + PowerObject( pObjectToTarget ); + } + } + else + { + // Find nearby objects + for ( int i = 0; i < GetTFTeam()->GetNumObjects(); i++ ) + { + CBaseObject *pObject = GetTFTeam()->GetObject(i); + assert(pObject); + if ( pObject == this || !pObject->CanPowerupNow(POWERUP_POWER) ) + continue; + // We might be rechecking our power because one of our own objects is dying. + // Make sure we don't re-attach the sucker. + if ( pObject->IsDying() ) + continue; + + // Make sure it's within range + if ( IsWithinPowerRange( pObject ) ) + { + PowerObject( pObject, bPlacing ); + } + + // Am I now full? + if ( m_hPoweredObjects.Count() >= MAX_OBJECTS_PER_PACK ) + break; + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Provide power to the specified object +//----------------------------------------------------------------------------- +void CObjectPowerPack::PowerObject( CBaseObject *pObject, bool bPlacing ) +{ + // Make sure we're not already powering it + ObjectHandle hObject; + hObject = pObject; + if ( m_hPoweredObjects.Find( hObject ) != m_hPoweredObjects.InvalidIndex() ) + return; + + // Add it to our list + m_hPoweredObjects.AddToTail( hObject ); + m_iObjectsAttached = m_hPoweredObjects.Count(); + + // Find a free attachment point + int iPoint = 1; + for ( int i = 0; i < MAX_OBJECTS_PER_PACK; i++ ) + { + if ( !(m_iFreeAttachments & (1<<i)) ) + { + m_iFreeAttachments |= (1<<i); + iPoint = i+1; + break; + } + } + + // Lookup the attachment point... + int nAttachmentIndex = pObject->LookupAttachment("powerpoint"); + if (nAttachmentIndex < 0) + nAttachmentIndex = 1; + + // FIXME: Cache these off + char sAttachment[32]; + Q_snprintf( sAttachment,sizeof(sAttachment), "cablepoint%d", iPoint ); + int nLocalAttachment = LookupAttachment( sAttachment ); + if ( nLocalAttachment > 0 ) + { + // Throw a cable to it + CRopeKeyframe *pRope = ConnectCableTo( pObject, nLocalAttachment, nAttachmentIndex ); + if ( pRope ) + { + pRope->SetMaterial( "cable/human_powercable.vmt" ); + } + } + + // If we're placing only, don't tell it we're supplying power yet + if ( IsPlacing() ) + return; + + pObject->SetPowerPack( this ); +} + +//----------------------------------------------------------------------------- +// Purpose: Remove power to the specified object +//----------------------------------------------------------------------------- +void CObjectPowerPack::UnPowerObject( CBaseObject *pObject ) +{ + // Make sure it's in our list + ObjectHandle hObject; + hObject = pObject; + if ( m_hPoweredObjects.Find( hObject ) == m_hPoweredObjects.InvalidIndex() ) + return; + + // Remove it from our list + m_hPoweredObjects.FindAndRemove( hObject ); + m_iObjectsAttached = m_hPoweredObjects.Count(); + + // Remove our cable to it + for ( int i = 0; i < m_aRopes.Count(); i++ ) + { + if ( (m_aRopes[i] != NULL) && (m_aRopes[i]->GetEndPoint() == pObject) ) + { + // Free up the attachment point + m_iFreeAttachments &= ~(1 << (m_aRopes[i]->GetEndAttachment()-1)); + UTIL_Remove( m_aRopes[i] ); + m_aRopes.Remove(i); + break; + } + } + + // Tell the object that it has lost power + if ( pObject->GetPowerPack() == this ) + { + pObject->SetPowerPack( NULL ); + } + + // If I'm not dying, immediately look for other things to power + if ( !IsDying() ) + { + PowerNearbyObjects(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Make sure the specified object is still within powering range +//----------------------------------------------------------------------------- +void CObjectPowerPack::EnsureObjectPower( CBaseObject *pObject ) +{ + if ( IsWithinPowerRange( pObject ) ) + return; + + // It's obscured, or out of range. Remove it. + UnPowerObject( pObject ); +} + +//----------------------------------------------------------------------------- +// Purpose: Return true if this object is powerable +//----------------------------------------------------------------------------- +bool CObjectPowerPack::IsWithinPowerRange( CBaseObject *pObject ) +{ + // If this powerpack is built on an attachment, it'll only power objects in the same hierarchy + if ( GetParentObject() ) + { + if ( GetRootMoveParent() != pObject->GetRootMoveParent() ) + return false; + } + + if ( (pObject->GetAbsOrigin() - GetAbsOrigin()).LengthSqr() < POWERPACK_RANGE ) + { + // Can I see it? + // Ignore things we're attached to + trace_t tr; + CTraceFilterWorldAndPropsOnly powerFilter; + UTIL_TraceLine( WorldSpaceCenter(), pObject->WorldSpaceCenter(), MASK_SOLID_BRUSHONLY, &powerFilter, &tr ); + CBaseEntity *pEntity = tr.m_pEnt; + if ( (tr.fraction == 1.0) || ( pEntity == pObject ) ) + return true; + } + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : act - +//----------------------------------------------------------------------------- +void CObjectPowerPack::OnActivityChanged( Activity act ) +{ + BaseClass::OnActivityChanged( act ); + + switch ( act ) + { + case ACT_OBJ_ASSEMBLING: + SetModel( POWERPACK_ASSEMBLING_MODEL ); + break; + default: + SetModel( POWERPACK_MODEL ); + break; + } +} diff --git a/game/server/tf2/tf_obj_powerpack.h b/game/server/tf2/tf_obj_powerpack.h new file mode 100644 index 0000000..4d78b27 --- /dev/null +++ b/game/server/tf2/tf_obj_powerpack.h @@ -0,0 +1,64 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Human's power pack +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef TF_OBJ_POWERPACK_H +#define TF_OBJ_POWERPACK_H +#ifdef _WIN32 +#pragma once +#endif + +#include "tf_obj.h" + +// ------------------------------------------------------------------------ // +// Pack defines +#define POWERPACK_MINS Vector(-20, -20, 0) +#define POWERPACK_MAXS Vector( 20, 20, 80) +#define POWERPACK_RANGE (600 * 600) + +// ------------------------------------------------------------------------ // +// Resupply object that's built by the player +// ------------------------------------------------------------------------ // +class CObjectPowerPack : public CBaseObject +{ + DECLARE_CLASS( CObjectPowerPack, CBaseObject ); +public: + DECLARE_SERVERCLASS(); + + CObjectPowerPack(); + static CObjectPowerPack *Create(const Vector &vOrigin, const QAngle &vAngles); + + virtual void Spawn(); + virtual void FinishedBuilding( void ); + virtual void Precache(); + virtual bool CanTakeEMPDamage( void ) { return true; } + virtual void DestroyObject( void ); + virtual bool CalculatePlacement( CBaseTFPlayer *pPlayer ); + + // This is called by the base object when it's time to spawn the control panels + virtual void GetControlPanelInfo( int nPanelIndex, const char *&pPanelName ); + + // Find nearby objects and provide them with power + void PowerNearbyObjects( CBaseObject *pObjectToTarget = NULL, bool bPlacing = false ); + void UnPowerAllObjects( void ); + void UnPowerObject( CBaseObject *pObject ); + void EnsureObjectPower( CBaseObject *pObject ); + + // Powerpack switches models after assembly + virtual void OnActivityChanged( Activity act ); + +private: + void PowerObject( CBaseObject *pObject, bool bPlacing = false ); + bool IsWithinPowerRange( CBaseObject *pObject ); + + // Objects powered from this pack + typedef CHandle<CBaseObject> ObjectHandle; + CUtlVector< ObjectHandle > m_hPoweredObjects; + int m_iFreeAttachments; + CNetworkVar( int, m_iObjectsAttached ); +}; + +#endif // TF_OBJ_POWERPACK_H diff --git a/game/server/tf2/tf_obj_rallyflag.cpp b/game/server/tf2/tf_obj_rallyflag.cpp new file mode 100644 index 0000000..8cc9bf9 --- /dev/null +++ b/game/server/tf2/tf_obj_rallyflag.cpp @@ -0,0 +1,108 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "tf_player.h" +#include "tf_team.h" +#include "tf_gamerules.h" +#include "tf_obj.h" +#include "tf_obj_rallyflag.h" +#include "ndebugoverlay.h" + +BEGIN_DATADESC( CObjectRallyFlag ) + + DEFINE_THINKFUNC( RallyThink ), + +END_DATADESC() + +IMPLEMENT_SERVERCLASS_ST(CObjectRallyFlag, DT_ObjectRallyFlag) +END_SEND_TABLE(); + +LINK_ENTITY_TO_CLASS(obj_rallyflag, CObjectRallyFlag); +PRECACHE_REGISTER(obj_rallyflag); + +ConVar obj_rallyflag_health( "obj_rallyflag_health","100", FCVAR_NONE, "Rally Flag health" ); + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CObjectRallyFlag::CObjectRallyFlag() +{ + UseClientSideAnimation(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CObjectRallyFlag::Spawn() +{ + Precache(); + SetModel( RALLYFLAG_MODEL ); + SetSolid( SOLID_BBOX ); + SetCollisionGroup( TFCOLLISION_GROUP_COMBATOBJECT ); + + UTIL_SetSize(this, RALLYFLAG_MINS, RALLYFLAG_MAXS); + m_takedamage = DAMAGE_YES; + m_iHealth = obj_rallyflag_health.GetInt(); + + SetThink( RallyThink ); + SetNextThink( gpGlobals->curtime + 0.1f ); + m_flExpiresAt = gpGlobals->curtime + RALLYFLAG_LIFETIME; + + SetType( OBJ_RALLYFLAG ); + m_fObjectFlags |= OF_SUPPRESS_NOTIFY_UNDER_ATTACK | OF_SUPPRESS_TECH_ANALYZER | + OF_DONT_AUTO_REPAIR | OF_DONT_PREVENT_BUILD_NEAR_OBJ | OF_DOESNT_NEED_POWER; + + BaseClass::Spawn(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CObjectRallyFlag::Precache() +{ + PrecacheModel( RALLYFLAG_MODEL ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Look for friendlies to rally +//----------------------------------------------------------------------------- +void CObjectRallyFlag::RallyThink( void ) +{ + if ( !GetTeam() ) + return; + + // Time to die? + if ( gpGlobals->curtime > m_flExpiresAt ) + { + UTIL_Remove( this ); + return; + } + + // Look for nearby players to rally + for ( int i = 0; i < GetTFTeam()->GetNumPlayers(); i++ ) + { + CBaseTFPlayer *pPlayer = (CBaseTFPlayer *)GetTFTeam()->GetPlayer(i); + assert(pPlayer); + + // Is it within range? + if ( ((pPlayer->GetAbsOrigin() - GetAbsOrigin()).Length() < RALLYFLAG_RADIUS ) && pPlayer->IsAlive() ) + { + // Can I see it? + trace_t tr; + UTIL_TraceLine( EyePosition(), pPlayer->EyePosition(), MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr); + CBaseEntity *pEntity = tr.m_pEnt; + if ( (tr.fraction == 1.0) || ( pEntity == pPlayer ) ) + { + pPlayer->AttemptToPowerup( POWERUP_RUSH, RALLYFLAG_ADRENALIN_TIME ); + } + } + } + + SetNextThink( gpGlobals->curtime + RALLYFLAG_RATE ); +} + diff --git a/game/server/tf2/tf_obj_rallyflag.h b/game/server/tf2/tf_obj_rallyflag.h new file mode 100644 index 0000000..cd86e77 --- /dev/null +++ b/game/server/tf2/tf_obj_rallyflag.h @@ -0,0 +1,40 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef TF_OBJ_RALLYFLAG_H +#define TF_OBJ_RALLYFLAG_H +#ifdef _WIN32 +#pragma once +#endif + +// ------------------------------------------------------------------------ // +// Rally flag that's built by players +// ------------------------------------------------------------------------ // +class CObjectRallyFlag : public CBaseObject +{ +DECLARE_CLASS( CObjectRallyFlag, CBaseObject ); + +public: + DECLARE_DATADESC(); + DECLARE_SERVERCLASS(); + + static CObjectRallyFlag* Create(const Vector &vOrigin, const QAngle &vAngles); + + CObjectRallyFlag(); + + virtual void Spawn(); + virtual void Precache(); + virtual bool CanTakeEMPDamage( void ) { return true; } + + // Rally + virtual void RallyThink( void ); + +private: + float m_flExpiresAt; +}; + +#endif // TF_OBJ_RALLYFLAG_H diff --git a/game/server/tf2/tf_obj_resourcepump.cpp b/game/server/tf2/tf_obj_resourcepump.cpp new file mode 100644 index 0000000..390bdfd --- /dev/null +++ b/game/server/tf2/tf_obj_resourcepump.cpp @@ -0,0 +1,260 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Resource pump +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "tf_player.h" +#include "tf_team.h" +#include "tf_obj.h" +#include "tf_obj_resourcepump.h" +#include "tf_func_resource.h" +#include "resource_chunk.h" +#include "techtree.h" +#include "sendproxy.h" +#include "vstdlib/random.h" +#include "tf_stats.h" +#include "VGuiScreen.h" +#include "engine/IEngineSound.h" + +BEGIN_DATADESC( CObjectResourcePump ) + + DEFINE_THINKFUNC( ResourcePumpThink ), + +END_DATADESC() + + +// Resource pump team-only vars. +BEGIN_SEND_TABLE_NOBASE( CObjectResourcePump, DT_ResourcePumpTeamOnlyVars ) + SendPropInt( SENDINFO(m_iPumpLevel), 4 ), + SendPropEHandle( SENDINFO(m_hResourceZone) ), +END_SEND_TABLE() + + +IMPLEMENT_SERVERCLASS_ST(CObjectResourcePump, DT_ResourcePump) + SendPropDataTable( "teamonly", 0, &REFERENCE_SEND_TABLE( DT_ResourcePumpTeamOnlyVars ), SendProxy_OnlyToTeam ), +END_SEND_TABLE() + +LINK_ENTITY_TO_CLASS(obj_resourcepump, CObjectResourcePump); +PRECACHE_REGISTER(obj_resourcepump); + +ConVar obj_resourcepump_health( "obj_resourcepump_health","100", FCVAR_NONE, "Resource pump health" ); +ConVar obj_resourcepump_rate( "obj_resourcepump_rate","0", FCVAR_NONE, "Base rate at which resource pumps give resources to their team." ); +ConVar obj_resourcepump_amount( "obj_resourcepump_amount","0", FCVAR_NONE, "Base amount of resources that resource pumps give to their team." ); + +#define RESOURCE_PUMP_CONTEXT "ResourcePumpThink" + +#define HUMAN_RESOURCEPUMP_MODEL "models/objects/obj_resourcepump.mdl" +#define ALIEN_RESOURCEPUMP_MODEL "models/objects/alien_obj_resourcepump.mdl" + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CObjectResourcePump::CObjectResourcePump() +{ + UseClientSideAnimation(); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CObjectResourcePump::SetupTeamModel( void ) +{ + if ( GetTeamNumber() == TEAM_HUMANS ) + { + SetModel( HUMAN_RESOURCEPUMP_MODEL ); + } + else + { + SetModel( ALIEN_RESOURCEPUMP_MODEL ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CObjectResourcePump::Spawn( void ) +{ + SetSolid( SOLID_BBOX ); + UTIL_SetSize(this, RESOURCEPUMP_MINS, RESOURCEPUMP_MAXS); + + m_iHealth = obj_resourcepump_health.GetInt(); + + m_hResourceZone = NULL; + m_flPumpSpeed = obj_resourcepump_rate.GetFloat(); + m_fObjectFlags |= OF_DONT_PREVENT_BUILD_NEAR_OBJ | OF_DOESNT_NEED_POWER | OF_MUST_BE_BUILT_IN_RESOURCE_ZONE | OF_MUST_BE_BUILT_ON_ATTACHMENT; + m_iPumpLevel = 1; + + SetType( OBJ_RESOURCEPUMP ); + + BaseClass::Spawn(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CObjectResourcePump::Activate( void ) +{ + BaseClass::Activate(); + + SetupPump(); +} + +//----------------------------------------------------------------------------- +// Purpose: Gets info about the control panels +//----------------------------------------------------------------------------- +void CObjectResourcePump::GetControlPanelInfo( int nPanelIndex, const char *&pPanelName ) +{ + pPanelName = "screen_obj_resourcepump"; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CObjectResourcePump::FinishedBuilding( void ) +{ + BaseClass::FinishedBuilding(); + + SetupPump(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CObjectResourcePump::SetupPump( void ) +{ + // Find the resource zone I've been planted in + m_hResourceZone = NULL; + CBaseEntity *pEntity = NULL; + while ((pEntity = gEntList.FindEntityByClassname( pEntity, "trigger_resourcezone" )) != NULL) + { + CResourceZone *pZone = (CResourceZone *)pEntity; + + // Are we within this zone? + if ( pZone->CollisionProp()->IsPointInBounds( GetAbsOrigin() ) ) + { + m_hResourceZone = pZone; + break; + } + } + + if ( m_hResourceZone == NULL ) + { + Msg( "Resource Pump (entindex %d) at (%.2f, %.2f, %.2f) can't find it's zone.\n", + entindex(), GetAbsOrigin().x, GetAbsOrigin().y, GetAbsOrigin().z ); + UTIL_Remove( this ); + return; + } + + // Tell the zone + if ( m_hResourceZone->GetNumBuildPoints() ) + { + m_hResourceZone->SetObjectOnBuildPoint( 0, this ); + } + + SetContextThink( ResourcePumpThink, gpGlobals->curtime + m_flPumpSpeed, RESOURCE_PUMP_CONTEXT ); + + // Start the pump animation + ResetSequence( SelectWeightedSequence( ACT_IDLE ) ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CObjectResourcePump::Precache() +{ + PrecacheModel( HUMAN_RESOURCEPUMP_MODEL ); + PrecacheModel( ALIEN_RESOURCEPUMP_MODEL ); + PrecacheVGuiScreen( "screen_obj_resourcepump" ); + + PrecacheScriptSound( "ObjectResourcePump.UpgradeFailed" ); + +} + +//----------------------------------------------------------------------------- +// Gets the resource zone (may be NULL!) +//----------------------------------------------------------------------------- +CResourceZone* CObjectResourcePump::GetResourceZone() +{ + return m_hResourceZone; +} + + +//----------------------------------------------------------------------------- +// Purpose: Pump resources out of the zone I'm sitting on +//----------------------------------------------------------------------------- +void CObjectResourcePump::ResourcePumpThink( void ) +{ + Assert( m_hResourceZone != NULL ); + if ( !GetTeam() ) + return; + + float flSpeed = m_hResourceZone->GetResourceRate() ? m_hResourceZone->GetResourceRate() : m_flPumpSpeed; + + SetNextThink( gpGlobals->curtime + flSpeed, RESOURCE_PUMP_CONTEXT ); + + // I can't do anything if I'm not active + if ( !ShouldBeActive() ) + return; + + // If the zone's not active, don't do anything + if ( !m_hResourceZone->GetActive() ) + return; + + // Suck resources from the zone I'm in + int iResourcesPerPlayer = 0; + for ( int i = 0; i < m_iPumpLevel; i++ ) + { + // Decreasing value for each level + float flLevelBonus = 1.0 - (i / (float)GetObjectInfo( GetType() )->m_MaxUpgradeLevel); + iResourcesPerPlayer += (obj_resourcepump_amount.GetFloat() * flLevelBonus); + } + int iPumpedResources = MIN( m_hResourceZone->GetResources(), iResourcesPerPlayer ); + if ( iPumpedResources ) + { + m_hResourceZone->RemoveResources( iPumpedResources ); + + // Give out resources to the team + GetTFTeam()->AddTeamResources( iPumpedResources * GetTFTeam()->GetNumPlayers() ); + TFStats()->IncrementTeamStat( GetTFTeam()->GetTeamNumber(), TF_TEAM_STAT_RESOURCES_HARVESTED, iPumpedResources ); + } + + // If we've just run out of resources in the zone, shut down + if ( m_hResourceZone->GetResources() <= 0 ) + { + UTIL_Remove( this ); + return; + } +} + +//----------------------------------------------------------------------------- +// Purpose: Handle commands sent from vgui panels on the client +//----------------------------------------------------------------------------- +bool CObjectResourcePump::ClientCommand( CBaseTFPlayer *pPlayer, const char *pCmd, ICommandArguments *pArg ) +{ + if ( FStrEq( pCmd, "upgrade" ) ) + { + int iCost = CalculateObjectUpgrade( GetType(), m_iPumpLevel ); + + // Do we have enough resources to activate it? + if ( !iCost || pPlayer->GetBankResources() < iCost ) + { + // Play a sound indicating it didn't work... + CSingleUserRecipientFilter filter( pPlayer ); + EmitSound( filter, pPlayer->entindex(), "ObjectResourcePump.UpgradeFailed" ); + return true; + } + + pPlayer->RemoveBankResources( iCost ); + + // Upgrade myself + m_iPumpLevel += 1; + return true; + } + + return BaseClass::ClientCommand( pPlayer, pCmd, pArg ); +} diff --git a/game/server/tf2/tf_obj_resourcepump.h b/game/server/tf2/tf_obj_resourcepump.h new file mode 100644 index 0000000..83229fc --- /dev/null +++ b/game/server/tf2/tf_obj_resourcepump.h @@ -0,0 +1,60 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Resource pump +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef TF_OBJ_RESOURCEPUMP_H +#define TF_OBJ_RESOURCEPUMP_H +#ifdef _WIN32 +#pragma once +#endif + + +#include "tf_obj.h" + + +class CResourceZone; + +// ------------------------------------------------------------------------ // +// Pump defines +#define RESOURCEPUMP_MINS Vector(-20, -20, 0) +#define RESOURCEPUMP_MAXS Vector( 20, 20, 140) + +// ------------------------------------------------------------------------ // +// Resupply object that's built by the player +// ------------------------------------------------------------------------ // +class CObjectResourcePump : public CBaseObject +{ +DECLARE_CLASS( CObjectResourcePump, CBaseObject ); + +public: + DECLARE_DATADESC(); + DECLARE_SERVERCLASS(); + + static CObjectResourcePump* Create(const Vector &vOrigin, const QAngle &vAngles); + + CObjectResourcePump(); + + virtual void Spawn(); + virtual void Activate( void ); + virtual void GetControlPanelInfo( int nPanelIndex, const char *&pPanelName ); + virtual void FinishedBuilding( void ); + void SetupPump( void ); + virtual void Precache(); + virtual bool CanTakeEMPDamage( void ) { return true; } + + virtual void ResourcePumpThink( void ); + virtual bool ClientCommand( CBaseTFPlayer *pPlayer, const CCommand &args ); + virtual void SetupTeamModel( void ); + + // Gets the resource zone (may be NULL!) + CResourceZone* GetResourceZone(); + +private: + float m_flPumpSpeed; + CNetworkVar( int, m_iPumpLevel ); +}; + +#endif // TF_OBJ_RESOURCEPUMP_H diff --git a/game/server/tf2/tf_obj_respawn_station.cpp b/game/server/tf2/tf_obj_respawn_station.cpp new file mode 100644 index 0000000..2888679 --- /dev/null +++ b/game/server/tf2/tf_obj_respawn_station.cpp @@ -0,0 +1,167 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Medic's resupply beacon +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "tf_obj.h" +#include "tf_player.h" +#include "tf_team.h" +#include "techtree.h" +#include "tf_shield.h" +#include "tf_obj_respawn_station.h" + +IMPLEMENT_SERVERCLASS_ST(CObjectRespawnStation, DT_ObjectRespawnStation) +END_SEND_TABLE(); + +BEGIN_DATADESC( CObjectRespawnStation ) + + // keys + DEFINE_KEYFIELD_NOT_SAVED( m_bIsInitialSpawnPoint, FIELD_BOOLEAN, "InitialSpawn" ), + +END_DATADESC() + +LINK_ENTITY_TO_CLASS(obj_respawn_station, CObjectRespawnStation); +PRECACHE_REGISTER(obj_respawn_station); + +ConVar obj_respawnstation_health( "obj_respawnstation_health","300", FCVAR_NONE, "Respawn Station health" ); + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CObjectRespawnStation::CObjectRespawnStation() +{ + m_bIsInitialSpawnPoint = false; + UseClientSideAnimation(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CObjectRespawnStation::Precache() +{ + PrecacheModel( "models/objects/obj_respawn_station.mdl" ); + m_iSpriteTexture = PrecacheModel( "sprites/laserbeam.vmt" ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CObjectRespawnStation::Spawn() +{ + Precache(); + SetMoveType( MOVETYPE_NONE ); + SetSolid( SOLID_BBOX ); + + SetModel( "models/objects/obj_respawn_station.mdl" ); + + UTIL_SetSize(this, RESPAWN_STATION_MINS, RESPAWN_STATION_MAXS); + + m_iHealth = m_iMaxHealth = obj_respawnstation_health.GetInt(); + m_takedamage = DAMAGE_YES; + m_fLastRespawnTime = -99999; + + SetType( OBJ_RESPAWN_STATION ); + + BaseClass::Spawn(); +} + + +//----------------------------------------------------------------------------- +// Object using! +//----------------------------------------------------------------------------- +void CObjectRespawnStation::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + // Sapper removal + if ( RemoveEnemyAttachments( pActivator ) ) + return; + + // See if the activator is a player + if ( pActivator->IsPlayer() ) + { + CBaseTFPlayer *player = static_cast< CBaseTFPlayer * >( pActivator ); + player->SetRespawnStation( this ); + } + + BaseClass::Use( pActivator, pCaller, useType, value ); +} + + + +//----------------------------------------------------------------------------- +// Gets called when someone respawns on this station +//----------------------------------------------------------------------------- +void CObjectRespawnStation::PerformRespawnEffect() +{ + if (gpGlobals->curtime - m_fLastRespawnTime > RESPAWN_EFFECT_TIME) + { + Vector vecEnd; + VectorAdd( GetAbsOrigin(), Vector( 0, 0, RESPAWN_BEAM_HEIGHT ), vecEnd ); + + CBroadcastRecipientFilter filter; + te->BeamPoints( filter, 0.0, + &GetAbsOrigin(), + &vecEnd, + m_iSpriteTexture, + 0, // Halo index + 0, // Start frame + 15, // Frame rate + 3.0, // Life + 50, // Width + 50, // EndWidth + 0, // FadeLength + 0, // Amplitude + 100, // r + 200, // g + 255, // b + 255, // a + 20 ); // speed + + m_fLastRespawnTime = gpGlobals->curtime; + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CObjectRespawnStation* CObjectRespawnStation::Create(const Vector &vOrigin, const QAngle &vAngles ) +{ + CObjectRespawnStation *pRet = (CObjectRespawnStation*)CreateEntityByName("obj_respawn_station"); + if(pRet) + { + pRet->SetLocalOrigin( vOrigin ); + pRet->SetLocalAngles( vAngles ); + pRet->Spawn(); + } + + return pRet; +} + + +//----------------------------------------------------------------------------- +// Plays a respawn effect on a respawn station... +//----------------------------------------------------------------------------- +void PlayRespawnEffect(CBaseEntity *pRespawnStation) +{ + // ROBIN: Removed this for now + return; + + // Check last respawn time; wait a couple seconds + if (!FClassnameIs(pRespawnStation, "obj_respawn_station")) + return; + + CObjectRespawnStation* pStation = static_cast<CObjectRespawnStation*>(pRespawnStation); + pStation->PerformRespawnEffect(); +} + +//----------------------------------------------------------------------------- +// Purpose: Returns true if this spawnpoint is the map specified initial spawnpoint for its team +//----------------------------------------------------------------------------- +bool CObjectRespawnStation::IsInitialSpawnPoint( void ) +{ + return m_bIsInitialSpawnPoint; +} + + diff --git a/game/server/tf2/tf_obj_respawn_station.h b/game/server/tf2/tf_obj_respawn_station.h new file mode 100644 index 0000000..6290f17 --- /dev/null +++ b/game/server/tf2/tf_obj_respawn_station.h @@ -0,0 +1,70 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Portable respawn station +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef TF_OBJ_RESPAWN_STATION_H +#define TF_OBJ_RESPAWN_STATION_H + +//----------------------------------------------------------------------------- +// forward declarations +//----------------------------------------------------------------------------- +class CBaseEntity; + +//----------------------------------------------------------------------------- +// Respawn station defines +//----------------------------------------------------------------------------- +#define RESPAWN_STATION_MINS Vector(-60, -45, 0) +#define RESPAWN_STATION_MAXS Vector( 60, 45, 140) + +#define RESPAWN_STATION_BUILD_MINS Vector(-60, -45, 0) +#define RESPAWN_STATION_BUILD_MAXS Vector( 60, 40, 140) + +#define RESPAWN_EFFECT_TIME 5.0f +#define RESPAWN_BEAM_HEIGHT 800.0f + +//----------------------------------------------------------------------------- +// Portable respawn station +//----------------------------------------------------------------------------- +class CObjectRespawnStation : public CBaseObject +{ +DECLARE_CLASS( CObjectRespawnStation, CBaseObject ); + +public: + DECLARE_SERVERCLASS(); + DECLARE_DATADESC(); + + CObjectRespawnStation(); + + // Gets called when someone respawns on this station + void PerformRespawnEffect(); + + static CObjectRespawnStation* Create(const Vector &vOrigin, const QAngle &vAngles); + + virtual void Precache(); + virtual void Spawn(); + + virtual bool WantsCover() { return true; } + + // Object using! + virtual void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + virtual bool CanTakeEMPDamage( void ) { return false; } + + // Map specified as the initial spawnpoint for a team + bool IsInitialSpawnPoint( void ); + +protected: + float m_fLastRespawnTime; + int m_iSpriteTexture; + bool m_bIsInitialSpawnPoint; +}; + +//----------------------------------------------------------------------------- +// Plays a respawn effect on a respawn station... +//----------------------------------------------------------------------------- +void PlayRespawnEffect(CBaseEntity *pRespawnStation); + +#endif // TF_OBJ_RESPAWN_STATION_H diff --git a/game/server/tf2/tf_obj_resupply.cpp b/game/server/tf2/tf_obj_resupply.cpp new file mode 100644 index 0000000..31fbbb2 --- /dev/null +++ b/game/server/tf2/tf_obj_resupply.cpp @@ -0,0 +1,383 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Medic's resupply beacon +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" + +#include "tf_obj_resupply.h" +#include "engine/IEngineSound.h" +#include "tf_player.h" +#include "tf_team.h" +#include "VGuiScreen.h" +#include "world.h" + +#define RESUPPLY_HEAL_AMT 100 +#define RESUPPLY_AMMO_AMT 0.25f + +// Wall mounted version +#define RESUPPLY_WALL_MODEL "models/objects/obj_resupply.mdl" +#define RESUPPLY_WALL_MODEL_ALIEN "models/objects/alien_obj_resupply.mdl" +#define RESUPPLY_WALL_MINS Vector(-10, -10, -40) +#define RESUPPLY_WALL_MAXS Vector( 10, 10, 40) + +// Ground placed version +#define RESUPPLY_GROUND_MODEL "models/objects/obj_resupply_ground.mdl" +#define RESUPPLY_GROUND_MODEL_HUMAN "models/objects/human_obj_resupply_ground.mdl" +#define RESUPPLY_GROUND_MINS Vector(-20, -20, 0) +#define RESUPPLY_GROUND_MAXS Vector( 20, 20, 55) + +IMPLEMENT_SERVERCLASS_ST( CObjectResupply, DT_ObjectResupply ) +END_SEND_TABLE() + +LINK_ENTITY_TO_CLASS(obj_resupply, CObjectResupply); +PRECACHE_REGISTER(obj_resupply); + +ConVar obj_resupply_health( "obj_resupply_health","100", FCVAR_NONE, "Resupply Station health" ); + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CObjectResupply::CObjectResupply() +{ + m_iHealth = obj_resupply_health.GetInt(); + UseClientSideAnimation(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CObjectResupply::Spawn() +{ + SetModel( RESUPPLY_WALL_MODEL ); + SetSolid( SOLID_BBOX ); + + UTIL_SetSize(this, RESUPPLY_WALL_MINS, RESUPPLY_WALL_MAXS); + m_takedamage = DAMAGE_YES; + + SetType( OBJ_RESUPPLY ); + m_fObjectFlags |= OF_DONT_PREVENT_BUILD_NEAR_OBJ; + + BaseClass::Spawn(); +} + + +//----------------------------------------------------------------------------- +// Spawn the vgui control screens on the object +//----------------------------------------------------------------------------- +void CObjectResupply::GetControlPanelInfo( int nPanelIndex, const char *&pPanelName ) +{ + pPanelName = "screen_obj_resupply"; +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CObjectResupply::Precache() +{ + BaseClass::Precache(); + PrecacheModel( RESUPPLY_WALL_MODEL ); + PrecacheModel( RESUPPLY_WALL_MODEL_ALIEN ); + PrecacheModel( RESUPPLY_GROUND_MODEL ); + PrecacheModel( RESUPPLY_GROUND_MODEL_HUMAN ); + PrecacheVGuiScreen( "screen_obj_resupply" ); + + PrecacheScriptSound( "ObjectResupply.InsufficientFunds" ); + PrecacheScriptSound( "BaseCombatCharacter.AmmoPickup" ); +} + + +//----------------------------------------------------------------------------- +// Resupply Health +//----------------------------------------------------------------------------- +bool CObjectResupply::ResupplyHealth( CBaseTFPlayer *pPlayer, float flFraction ) +{ + // Calculate the amount to heal + float flAmountToHeal = flFraction * RESUPPLY_HEAL_AMT; + if (flAmountToHeal > (pPlayer->m_iMaxHealth - pPlayer->m_iHealth)) + { + flAmountToHeal = (pPlayer->m_iMaxHealth - pPlayer->m_iHealth); + } + + if ( flAmountToHeal > 0 ) + { + pPlayer->TakeHealth( flAmountToHeal, 0 ); + return true; + } + + return false; +} + + +//----------------------------------------------------------------------------- +// Handle commands sent from vgui panels on the client +//----------------------------------------------------------------------------- +bool CObjectResupply::ClientCommand( CBaseTFPlayer *pPlayer, const char *pCmd, ICommandArguments *pArg ) +{ + // NOTE: Must match ResupplyBuyType_t + static float s_Costs[] = + { + RESUPPLY_AMMO_COST, + RESUPPLY_HEALTH_COST, + RESUPPLY_GRENADES_COST, + RESUPPLY_ALL_COST + }; + + COMPILE_TIME_ASSERT( RESUPPLY_BUY_TYPE_COUNT == 4 ); + + if ( FStrEq( pCmd, "buy" ) ) + { + if ( pArg->Argc() < 2 ) + return true; + + // I can't do anything if I'm not active + if ( !ShouldBeActive() ) + return true; + + // Do we have enough resources to activate it? + if (pPlayer->GetBankResources() <= 0) + { + // Play a sound indicating it didn't work... + CSingleUserRecipientFilter filter( pPlayer ); + EmitSound( filter, pPlayer->entindex(), "ObjectResupply.InsufficientFunds" ); + return true; + } + + bool bUsedResupply = false; + ResupplyBuyType_t type = (ResupplyBuyType_t)atoi( pArg->Argv(1) ); + if (type >= RESUPPLY_BUY_TYPE_COUNT) + return true; + + // Get the potential cost. + float flCost = s_Costs[type]; +// flCost += pPlayer->ClassCostAdjustment( type ); + + float flFraction = pPlayer->GetBankResources() / flCost; + if (flFraction > 1.0f) + flFraction = 1.0f; + + switch( type ) + { + case RESUPPLY_BUY_HEALTH: + // Calculate the amount to heal + if (ResupplyHealth(pPlayer, flFraction)) + { + bUsedResupply = true; + } + break; + + case RESUPPLY_BUY_AMMO: + // Refill the player's ammo too + if (pPlayer->ResupplyAmmo( flFraction * RESUPPLY_AMMO_AMT, RESUPPLY_AMMO_FROM_STATION )) + { + bUsedResupply = true; + } + break; + + case RESUPPLY_BUY_GRENADES: + // Refill the player's ammo too + if (pPlayer->ResupplyAmmo( flFraction * RESUPPLY_AMMO_AMT, RESUPPLY_GRENADES_FROM_STATION )) + { + bUsedResupply = true; + } + break; + + case RESUPPLY_BUY_ALL: + // Calculate the amount to heal + if (ResupplyHealth(pPlayer, flFraction)) + { + bUsedResupply = true; + } + + // Refill the player's ammo too + if (pPlayer->ResupplyAmmo( flFraction * RESUPPLY_AMMO_AMT, RESUPPLY_ALL_FROM_STATION )) + { + bUsedResupply = true; + } + break; + } + + if (bUsedResupply) + { + // Play an ammo pickup just to this player + CSingleUserRecipientFilter filter( pPlayer ); + pPlayer->EmitSound( filter, pPlayer->entindex(), "BaseCombatCharacter.AmmoPickup" ); + + pPlayer->RemoveBankResources( flFraction * flCost ); + } + + return true; + } + + return BaseClass::ClientCommand( pPlayer, pCmd, pArg ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CObjectResupply::DestroyObject( void ) +{ + if ( GetTeam() ) + { + ((CTFTeam*)GetTeam())->RemoveResupply( this ); + } + BaseClass::DestroyObject(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pTeam - +//----------------------------------------------------------------------------- +void CObjectResupply::ChangeTeam( int iTeamNum ) +{ + CTFTeam *pExisting = (CTFTeam*)GetTeam(); + CTFTeam *pTeam = (CTFTeam*)GetGlobalTeam( iTeamNum ); + + // Already on this team + if ( GetTeamNumber() == iTeamNum ) + return; + + if ( pExisting ) + { + // Remove it from current team ( if it's in one ) and give it to new team + pExisting->RemoveResupply( this ); + } + + // Change to new team + BaseClass::ChangeTeam( iTeamNum ); + + // Add this object to the team's list + if (pTeam) + { + pTeam->AddResupply( this ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Resupply always wants to use the wall mount for attachment points +//----------------------------------------------------------------------------- +void CObjectResupply::SetupAttachedVersion( void ) +{ + BaseClass::SetupAttachedVersion(); + + if ( GetTeamNumber() == TEAM_ALIENS ) + { + SetModel( RESUPPLY_WALL_MODEL_ALIEN ); + } + else + { + SetModel( RESUPPLY_WALL_MODEL ); + } + + UTIL_SetSize(this, RESUPPLY_WALL_MINS, RESUPPLY_WALL_MAXS); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CObjectResupply::CalculatePlacement( CBaseTFPlayer *pPlayer ) +{ + trace_t tr; + Vector vecAiming; + // Get an aim vector. Don't use GetAimVector() because we don't want autoaiming. + Vector vecSrc = pPlayer->Weapon_ShootPosition( ); + pPlayer->EyeVectors( &vecAiming ); + Vector vecTarget; + VectorMA( vecSrc, 90, vecAiming, vecTarget ); + m_vecBuildOrigin = vecTarget; + + // Angle it towards me + Vector vecForward = pPlayer->WorldSpaceCenter() - m_vecBuildOrigin; + SetLocalAngles( QAngle( 0, UTIL_VecToYaw( vecForward ), 0 ) ); + + // Is there something to attach to? + // Use my bounding box, not the build box, so I fit to the wall + UTIL_TraceLine( vecSrc, vecTarget, MASK_SOLID, pPlayer, COLLISION_GROUP_PLAYER_MOVEMENT, &tr); + //UTIL_TraceHull( vecSrc, vecTarget, WorldAlignMins(), WorldAlignMaxs(), MASK_SOLID, pPlayer, TFCOLLISION_GROUP_OBJECT, &tr ); + m_vecBuildOrigin = tr.endpos; + bool bTryToPlaceGroundVersion = false; + if ( tr.allsolid || (tr.fraction == 1.0) ) + { + bTryToPlaceGroundVersion = true; + } + else + { + // Make sure we're planting on the world + CBaseEntity *pEntity = tr.m_pEnt; + if ( pEntity != GetWorldEntity() ) + { + bTryToPlaceGroundVersion = true; + } + } + + // Make sure the wall we've touched is vertical + if ( !bTryToPlaceGroundVersion && fabs(tr.plane.normal.z) > 0.3 ) + { + bTryToPlaceGroundVersion = true; + } + + // Aborting? + if ( bTryToPlaceGroundVersion ) + { + // We couldn't find a wall, so try and place a ground version instead + if ( GetTeamNumber() == TEAM_HUMANS ) + { + SetModel( RESUPPLY_GROUND_MODEL_HUMAN ); + } + else + { + SetModel( RESUPPLY_GROUND_MODEL ); + } + UTIL_SetSize(this, RESUPPLY_GROUND_MINS, RESUPPLY_GROUND_MAXS); + m_vecBuildMins = WorldAlignMins() - Vector( 4,4,0 ); + m_vecBuildMaxs = WorldAlignMaxs() + Vector( 4,4,0 ); + return BaseClass::CalculatePlacement( pPlayer ); + } + + SetupAttachedVersion(); + m_vecBuildMins = WorldAlignMins() - Vector( 4,4,0 ); + m_vecBuildMaxs = WorldAlignMaxs() + Vector( 4,4,0 ); + + // Set the angles + vecForward = tr.plane.normal; + SetLocalAngles( QAngle( 0, UTIL_VecToYaw( vecForward ), 0 ) ); + + // Trace back from the corners + Vector vecMins, vecMaxs, vecModelMins, vecModelMaxs; + const model_t *pModel = GetModel(); + modelinfo->GetModelBounds( pModel, vecModelMins, vecModelMaxs ); + + // Check the four build points + Vector vecPointCheck = (vecForward * 32); + Vector vecUp = Vector(0,0,1); + Vector vecRight; + CrossProduct( vecUp, vecForward, vecRight ); + float flWidth = fabs(vecModelMaxs.y - vecModelMins.y) * 0.5; + float flHeight = fabs(vecModelMaxs.z - vecModelMins.z) * 0.5; + + bool bResult = true; + if ( bResult ) + { + bResult = CheckBuildPoint( m_vecBuildOrigin + (vecRight * flWidth) + (vecUp * flHeight), vecPointCheck ); + } + if ( bResult ) + { + bResult = CheckBuildPoint( m_vecBuildOrigin + (vecRight * flWidth) - (vecUp * flHeight), vecPointCheck ); + } + if ( bResult ) + { + bResult = CheckBuildPoint( m_vecBuildOrigin - (vecRight * flWidth) + (vecUp * flHeight), vecPointCheck ); + } + if ( bResult ) + { + bResult = CheckBuildPoint( m_vecBuildOrigin - (vecRight * flWidth) - (vecUp * flHeight), vecPointCheck ); + } + + AttemptToFindPower(); + + return bResult; +} diff --git a/game/server/tf2/tf_obj_resupply.h b/game/server/tf2/tf_obj_resupply.h new file mode 100644 index 0000000..373a32c --- /dev/null +++ b/game/server/tf2/tf_obj_resupply.h @@ -0,0 +1,60 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Medic's resupply beacon +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef TF_OBJ_RESUPPLY_H +#define TF_OBJ_RESUPPLY_H +#ifdef _WIN32 +#pragma once +#endif + +#include "tf_obj.h" + +// ------------------------------------------------------------------------ // +// Resupply defines +#define RESUPPLY_NUM_PLAYERS_REFILLED 5 // Number of full resupplies before refill need + +// An object is considered covered by a resupply station (for purposes of order creation) +// if it is within this distance of the station. +#define RESUPPLY_COVER_DIST 1500 + + + +// ------------------------------------------------------------------------ // +// Resupply object that's built by the player +// ------------------------------------------------------------------------ // +class CObjectResupply : public CBaseObject +{ +DECLARE_CLASS( CObjectResupply, CBaseObject ); + +public: + DECLARE_SERVERCLASS(); + + CObjectResupply(); + + static CObjectResupply* Create(const Vector &vOrigin, const QAngle &vAngles); + + virtual void Spawn(); + virtual void GetControlPanelInfo( int nPanelIndex, const char *&pPanelName ); + virtual void Precache(); + virtual void DestroyObject( void ); + + virtual bool CalculatePlacement( CBaseTFPlayer *pPlayer ); + virtual void SetupAttachedVersion( void ); + + // Resupply + virtual bool ClientCommand( CBaseTFPlayer *pPlayer, const CCommand &args ); + + virtual void ChangeTeam( int iTeamNum ) OVERRIDE; + + virtual bool CanTakeEMPDamage( void ) { return true; } + +private: + // Resupply Health + bool ResupplyHealth( CBaseTFPlayer *pPlayer, float flFraction ); +}; + +#endif // TF_OBJ_RESUPPLY_H diff --git a/game/server/tf2/tf_obj_sandbag_bunker.cpp b/game/server/tf2/tf_obj_sandbag_bunker.cpp new file mode 100644 index 0000000..d3d012f --- /dev/null +++ b/game/server/tf2/tf_obj_sandbag_bunker.cpp @@ -0,0 +1,73 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: A stationary sandbag bunker that players can take cover in. +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" +#include "tf_obj.h" +#include "tf_team.h" +#include "engine/IEngineSound.h" +#include "tf_obj_sandbag_bunker.h" + +#define SANDBAGBUNKER_MINS Vector(-60, -60, 0) +#define SANDBAGBUNKER_MAXS Vector( 60, 60, 50) +#define SANDBAGBUNKER_MODEL "models/objects/obj_sandbag_bunker.mdl" + + +IMPLEMENT_SERVERCLASS_ST(CObjectSandbagBunker, DT_ObjectSandbagBunker) +END_SEND_TABLE(); + +LINK_ENTITY_TO_CLASS(obj_sandbag_bunker, CObjectSandbagBunker); +PRECACHE_REGISTER(obj_sandbag_bunker); + +// CVars +ConVar obj_sandbag_bunker_health( "obj_sandbag_bunker_health","100", FCVAR_NONE, "Sandbag bunker health" ); + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CObjectSandbagBunker::CObjectSandbagBunker( void ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CObjectSandbagBunker::Spawn( void ) +{ + Precache(); + SetModel( SANDBAGBUNKER_MODEL ); + SetSolid( SOLID_BBOX ); + + UTIL_SetSize(this, SANDBAGBUNKER_MINS, SANDBAGBUNKER_MAXS); + m_takedamage = DAMAGE_YES; + m_iHealth = obj_sandbag_bunker_health.GetInt(); + + m_fObjectFlags |= OF_DOESNT_NEED_POWER; + SetType( OBJ_SANDBAG_BUNKER ); + + SetSolid( SOLID_VPHYSICS ); + VPhysicsInitStatic(); + + BaseClass::Spawn(); + + SetCollisionGroup( COLLISION_GROUP_VEHICLE ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CObjectSandbagBunker::Precache( void ) +{ + PrecacheModel( SANDBAGBUNKER_MODEL ); +} + +//----------------------------------------------------------------------------- +// Purpose: Gets info about the control panels +//----------------------------------------------------------------------------- +void CObjectSandbagBunker::GetControlPanelInfo( int nPanelIndex, const char *&pPanelName ) +{ + pPanelName = "screen_basic_with_disable"; +}
\ No newline at end of file diff --git a/game/server/tf2/tf_obj_sandbag_bunker.h b/game/server/tf2/tf_obj_sandbag_bunker.h new file mode 100644 index 0000000..3dd91c9 --- /dev/null +++ b/game/server/tf2/tf_obj_sandbag_bunker.h @@ -0,0 +1,33 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: A stationary sandbag bunker that players can take cover in. +// +//=============================================================================// + +#ifndef TF_OBJ_SANDBAG_BUNKER_H +#define TF_OBJ_SANDBAG_BUNKER_H +#ifdef _WIN32 +#pragma once +#endif + +// ------------------------------------------------------------------------ // +// Purpose: A stationary sandbag bunker that players can take cover in. +// ------------------------------------------------------------------------ // +class CObjectSandbagBunker : public CBaseObject +{ + DECLARE_CLASS( CObjectSandbagBunker, CBaseObject ); + +public: + DECLARE_SERVERCLASS(); + + CObjectSandbagBunker( void ); + + virtual void Spawn( void ); + virtual void Precache( void ); + virtual void GetControlPanelInfo( int nPanelIndex, const char *&pPanelName ); + +private: +}; + + +#endif // TF_OBJ_TOWER_H diff --git a/game/server/tf2/tf_obj_selfheal.cpp b/game/server/tf2/tf_obj_selfheal.cpp new file mode 100644 index 0000000..f4bcc7e --- /dev/null +++ b/game/server/tf2/tf_obj_selfheal.cpp @@ -0,0 +1,112 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Upgrade that heals the object over time +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "tf_player.h" +#include "tf_team.h" +#include "tf_gamerules.h" +#include "tf_obj.h" +#include "tf_obj_selfheal.h" +#include "ndebugoverlay.h" + +// ------------------------------------------------------------------------ // +// Self Heal defines +#define SELFHEAL_MINS Vector(-10, -10, 0) +#define SELFHEAL_MAXS Vector( 10, 10, 10) +#define SELFHEAL_MODEL "models/objects/obj_selfheal.mdl" + +BEGIN_DATADESC( CObjectSelfHeal ) + + DEFINE_THINKFUNC( SelfHealThink ), + +END_DATADESC() + +IMPLEMENT_SERVERCLASS_ST(CObjectSelfHeal, DT_ObjectSelfHeal) +END_SEND_TABLE(); + +LINK_ENTITY_TO_CLASS(obj_selfheal, CObjectSelfHeal); +PRECACHE_REGISTER(obj_selfheal); + +ConVar obj_selfheal_health( "obj_selfheal_health","100", FCVAR_NONE, "Self-Heal health" ); +ConVar obj_selfheal_rate( "obj_selfheal_rate","1.0", FCVAR_NONE, "Rate at which the Self-Heal object repairs it's parent" ); +ConVar obj_selfheal_amount( "obj_selfheal_amount","3", FCVAR_NONE, "Amount of health healed by a Self-Heal object per tick" ); + +#define SELFHEAL_THINK_CONTEXT "SelfHealThink" + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CObjectSelfHeal::CObjectSelfHeal() +{ + UseClientSideAnimation(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CObjectSelfHeal::Spawn() +{ + Precache(); + SetModel( SELFHEAL_MODEL ); + SetCollisionGroup( TFCOLLISION_GROUP_COMBATOBJECT ); + + UTIL_SetSize(this, SELFHEAL_MINS, SELFHEAL_MAXS); + m_takedamage = DAMAGE_YES; + m_iHealth = obj_selfheal_health.GetInt(); + + SetType( OBJ_SELFHEAL ); + m_fObjectFlags |= OF_SUPPRESS_NOTIFY_UNDER_ATTACK | OF_SUPPRESS_TECH_ANALYZER | + OF_DONT_AUTO_REPAIR | OF_MUST_BE_BUILT_ON_ATTACHMENT; + + BaseClass::Spawn(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CObjectSelfHeal::Precache() +{ + PrecacheModel( SELFHEAL_MODEL ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CObjectSelfHeal::FinishedBuilding( void ) +{ + BaseClass::FinishedBuilding(); + + SetContextThink( SelfHealThink, gpGlobals->curtime + obj_selfheal_rate.GetFloat(), SELFHEAL_THINK_CONTEXT ); +} + +//----------------------------------------------------------------------------- +// Purpose: Heal the object I'm attached to +//----------------------------------------------------------------------------- +void CObjectSelfHeal::SelfHealThink( void ) +{ + if ( !GetTeam() ) + return; + + CBaseObject *pObject = GetParentObject(); + if ( !pObject ) + { + Killed(); + return; + } + + SetNextThink( gpGlobals->curtime + obj_selfheal_rate.GetFloat(), SELFHEAL_THINK_CONTEXT ); + + // Don't heal if we've been EMPed + if ( HasPowerup( POWERUP_EMP ) ) + return; + + // Don't bring objects back from the dead + if ( !pObject->IsAlive() || pObject->IsDying() ) + return; + + // Repair our parent if it's hurt + pObject->Repair( obj_selfheal_amount.GetFloat() ); +} diff --git a/game/server/tf2/tf_obj_selfheal.h b/game/server/tf2/tf_obj_selfheal.h new file mode 100644 index 0000000..d015ab9 --- /dev/null +++ b/game/server/tf2/tf_obj_selfheal.h @@ -0,0 +1,37 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Upgrade that heals the object over time +// +//=============================================================================// + +#ifndef TF_OBJ_SELFHEAL_H +#define TF_OBJ_SELFHEAL_H +#ifdef _WIN32 +#pragma once +#endif + +#include "tf_obj_baseupgrade_shared.h" + +// ------------------------------------------------------------------------ // +// Self-Heal upgrade +// ------------------------------------------------------------------------ // +class CObjectSelfHeal : public CBaseObjectUpgrade +{ +DECLARE_CLASS( CObjectSelfHeal, CBaseObjectUpgrade ); + +public: + DECLARE_DATADESC(); + DECLARE_SERVERCLASS(); + + CObjectSelfHeal(); + + virtual void Spawn(); + virtual void Precache(); + virtual bool CanTakeEMPDamage( void ) { return true; } + virtual void FinishedBuilding( void ); + + // Repairing + virtual void SelfHealThink( void ); +}; + +#endif // TF_OBJ_SELFHEAL_H diff --git a/game/server/tf2/tf_obj_sentrygun.cpp b/game/server/tf2/tf_obj_sentrygun.cpp new file mode 100644 index 0000000..2ea32dc --- /dev/null +++ b/game/server/tf2/tf_obj_sentrygun.cpp @@ -0,0 +1,1178 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Defender's sentrygun objects +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "tf_player.h" +#include "tf_team.h" +#include "tf_obj.h" +#include "tf_obj_sentrygun.h" +#include "tf_obj_dragonsteeth.h" +#include "tf_obj_tower.h" +#include "tf_obj_sandbag_bunker.h" +#include "tf_obj_bunker.h" +#include "tf_obj_mapdefined.h" +#include "tf_gamerules.h" +#include "gamerules.h" +#include "ammodef.h" +#include "plasmaprojectile.h" +#include "tf_class_recon.h" +#include "sendproxy.h" +#include "vstdlib/random.h" +#include "engine/IEngineSound.h" +#include "grenade_rocket.h" +#include "VGuiScreen.h" + +extern short g_sModelIndexFireball; + +#define MAX_SUPPRESSION_TIME 5.0 // Max amount of time to supress for + +// Sentrygun size +#define SENTRYGUN_MINS Vector(-16, -16, 0) +#define SENTRYGUN_MAXS Vector( 16, 16, 65) + +//============================================================================= +// Link and precache all the sentrygun types +LINK_ENTITY_TO_CLASS(obj_sentrygun_plasma, CObjectSentrygunPlasma); +LINK_ENTITY_TO_CLASS(obj_sentrygun_rocketlauncher, CObjectSentrygunRocketlauncher); +PRECACHE_REGISTER(obj_sentrygun_plasma); +PRECACHE_REGISTER(obj_sentrygun_rocketlauncher); + +//============================================================================= +// Data description +BEGIN_DATADESC( CObjectSentrygun ) + + DEFINE_THINKFUNC( SentryRotate ), + DEFINE_THINKFUNC( Attack ), + +END_DATADESC() + +// Sentrygun team-only vars. +BEGIN_SEND_TABLE_NOBASE( CObjectSentrygun, DT_SentrygunTeamOnlyVars ) + SendPropInt( SENDINFO(m_iAmmo), 9 ), +END_SEND_TABLE() + +#define SENTRY_ANIMATION_PARITY_BITS 2 + +IMPLEMENT_SERVERCLASS_ST(CObjectSentrygun, DT_ObjectSentrygun) + SendPropInt( SENDINFO( m_iBaseTurnRate ), 3, SPROP_UNSIGNED ), + SendPropEHandle( SENDINFO( m_hEnemy ) ), + SendPropDataTable( "teamonly", 0, &REFERENCE_SEND_TABLE( DT_SentrygunTeamOnlyVars ), SendProxy_OnlyToTeam ), + SendPropInt( SENDINFO(m_bTurtled), 1, SPROP_UNSIGNED ), + SendPropInt( SENDINFO( m_nAnimationParity ), (1<<SENTRY_ANIMATION_PARITY_BITS), SPROP_UNSIGNED ), + SendPropInt( SENDINFO( m_nOrientationParity ), 1, SPROP_UNSIGNED ), +END_SEND_TABLE(); + +ConVar obj_sentrygun_plasma_health( "obj_sentrygun_plasma_health","200", FCVAR_NONE, "Plasma sentrygun health" ); +ConVar obj_sentrygun_plasma_range( "obj_sentrygun_plasma_range","1500", FCVAR_NONE, "Plasma sentrygun's shot range" ); +ConVar obj_sentrygun_rocketlauncher_health( "obj_sentrygun_rocketlauncher_health","250", FCVAR_NONE, "Rocket Launcher sentrygun health" ); +ConVar obj_sentrygun_range_mid( "obj_sentrygun_range_mid","768", FCVAR_NONE, "Sentrygun's mid targeting range. Targets beyond this need to be in the viewcone to be seen." ); +ConVar obj_sentrygun_range_max( "obj_sentrygun_range_max","1600", FCVAR_NONE, "Sentrygun's max targeting range." ); + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CObjectSentrygun::CObjectSentrygun( void ) +{ + UseClientSideAnimation(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CObjectSentrygun::Spawn( void ) +{ + m_bSmarter = false; + m_bSensors = false; + m_bSuppressing = false; + m_bTurtled = false; + m_bTurtling = false; + m_flTurtlingFinishedAt = 0; + + SetViewOffset( Vector(0,0,22) ); + + // Setup + SetSolid( SOLID_BBOX ); + AddSolidFlags( FSOLID_NOT_STANDABLE ); + UTIL_SetSize(this, SENTRYGUN_MINS, SENTRYGUN_MAXS); + + BaseClass::Spawn(); + + // Start searching for enemies + m_hEnemy = NULL; + m_hDesignatedEnemy = NULL; + SetThink( SentryRotate ); + SetNextThink( gpGlobals->curtime + 0.5f ); + m_flNextLook = gpGlobals->curtime; + + SetTechnology( false, false ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CObjectSentrygun::Precache() +{ + PrecacheModel( SG_PLASMA_MODEL ); + PrecacheModel( SG_ROCKETLAUNCHER_MODEL ); + PrecacheVGuiScreen( "screen_obj_sentrygun" ); + + PrecacheScriptSound( "ObjectSentrygun.ResupplyAmmo" ); + PrecacheScriptSound( "ObjectSentrygun.Idle" ); + PrecacheScriptSound( "ObjectSentrygun.FoundTarget" ); + PrecacheScriptSound( "ObjectSentrygun.Turtle" ); + PrecacheScriptSound( "ObjectSentrygun.UnTurtle" ); + PrecacheScriptSound( "ObjectSentrygun.Fire" ); + PrecacheScriptSound( "ObjectSentrygunRocketlauncher.Fire" ); + +} + +//----------------------------------------------------------------------------- +// Purpose: Gets info about the control panels +//----------------------------------------------------------------------------- +void CObjectSentrygun::GetControlPanelInfo( int nPanelIndex, const char *&pPanelName ) +{ + pPanelName = "screen_obj_sentrygun"; +} + +//----------------------------------------------------------------------------- +// Purpose: Hide the base of the gun if it's on an attachment +//----------------------------------------------------------------------------- +void CObjectSentrygun::SetupAttachedVersion( void ) +{ + BaseClass::SetupAttachedVersion(); + + SetBodygroup( 1, true ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CObjectSentrygun::SetupUnattachedVersion( void ) +{ + BaseClass::SetupUnattachedVersion(); + + SetBodygroup( 1, false ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CObjectSentrygun::FinishedBuilding( void ) +{ + BaseClass::FinishedBuilding(); + + // Orient it + m_vecCurAngles.y = UTIL_AngleMod( GetLocalAngles().y ); + RecomputeOrientation(); +} + +//----------------------------------------------------------------------------- +// Called when a rotation happens +//----------------------------------------------------------------------------- +void CObjectSentrygun::RecomputeOrientation( ) +{ + ResetOrientation(); + + m_iRightBound = UTIL_AngleMod( m_vecCurAngles.y - 50); + m_iLeftBound = UTIL_AngleMod( m_vecCurAngles.y + 50); + if ( m_iRightBound > m_iLeftBound ) + { + m_iRightBound = m_iLeftBound; + m_iLeftBound = UTIL_AngleMod( m_vecCurAngles.y - 50); + } + + // Start it rotating + m_vecGoalAngles.y = m_iRightBound; + m_vecGoalAngles.x = m_vecCurAngles.x = 0; + m_bTurningRight = true; +} + +//----------------------------------------------------------------------------- +// Purpose: Handle commands sent from vgui panels on the client +//----------------------------------------------------------------------------- +bool CObjectSentrygun::ClientCommand( CBaseTFPlayer *pPlayer, const char *pCmd, ICommandArguments *pArg ) +{ + if ( FStrEq( pCmd, "addammo" ) ) + { + if ( TakeAmmoFrom( pPlayer ) ) + { + // We got some ammo, so make a sound + CPASAttenuationFilter filter( pPlayer, "ObjectSentrygun.ResupplyAmmo" ); + EmitSound( filter, pPlayer->entindex(), "ObjectSentrygun.ResupplyAmmo" ); + } + return true; + } + + return BaseClass::ClientCommand( pPlayer, pCmd, pArg ); +} + +//----------------------------------------------------------------------------- +// Purpose: Return true if the player gave the sentrygun some ammo +//----------------------------------------------------------------------------- +bool CObjectSentrygun::TakeAmmoFrom( CBaseTFPlayer *pPlayer ) +{ + // Do I need ammo? + if ( m_iAmmo >= m_iMaxAmmo ) + return false; + + // Try to fill the sentry up a bit at a time + int iRoundsToGive = 10; + iRoundsToGive = MIN( iRoundsToGive, (m_iMaxAmmo - m_iAmmo) ); + iRoundsToGive = MIN( iRoundsToGive, pPlayer->GetAmmoCount( m_iAmmoType ) ); + if ( !iRoundsToGive ) + return false; + + // Give me the ammo + pPlayer->RemoveAmmo( iRoundsToGive, m_iAmmoType ); + m_iAmmo += iRoundsToGive; + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: Resupply has taken damage +//----------------------------------------------------------------------------- +int CObjectSentrygun::OnTakeDamage( const CTakeDamageInfo &info ) +{ + int iDamage = BaseClass::OnTakeDamage( info ); + + return iDamage; +} + +//----------------------------------------------------------------------------- +// Purpose: Object has been blown up +//----------------------------------------------------------------------------- +void CObjectSentrygun::Killed( void ) +{ + // Tell the player he's lost this resupply beacon + if ( GetOwner() ) + { + GetOwner()->OwnedObjectDestroyed( this ); + } + + BaseClass::Killed(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CObjectSentrygun::RestartAnimation( void ) +{ + // Increment and mask parity counter + m_nAnimationParity += 1; + m_nAnimationParity &= ( (1<<SENTRY_ANIMATION_PARITY_BITS) - 1 ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CObjectSentrygun::ResetOrientation() +{ + m_nOrientationParity = !m_nOrientationParity; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CObjectSentrygun::SetSentryAnim( TFTURRET_ANIM anim ) +{ + if ( GetSequence() != anim ) + { + switch(anim) + { + case TFTURRET_ANIM_FIRE: + case TFTURRET_ANIM_SPIN: + if ( GetSequence() != TFTURRET_ANIM_FIRE && GetSequence() != TFTURRET_ANIM_SPIN ) + { + RestartAnimation(); + } + break; + default: + RestartAnimation(); + break; + } + + ResetSequence( anim ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Handle movement of the turret +//----------------------------------------------------------------------------- +bool CObjectSentrygun::MoveTurret(void) +{ + bool bMoved = 0; + + // any x movement? + if ( m_vecCurAngles.x != m_vecGoalAngles.x ) + { + float flDir = m_vecGoalAngles.x > m_vecCurAngles.x ? 1 : -1 ; + + m_vecCurAngles.x += 0.1 * (m_iBaseTurnRate * 5) * flDir; + + // if we started below the goal, and now we're past, peg to goal + if (flDir == 1) + { + if (m_vecCurAngles.x > m_vecGoalAngles.x) + m_vecCurAngles.x = m_vecGoalAngles.x; + } + else + { + if (m_vecCurAngles.x < m_vecGoalAngles.x) + m_vecCurAngles.x = m_vecGoalAngles.x; + } + + m_fBoneYRotator = m_vecCurAngles.x; + + bMoved = 1; + } + + if ( m_vecCurAngles.y != m_vecGoalAngles.y ) + { + float flDir = m_vecGoalAngles.y > m_vecCurAngles.y ? 1 : -1 ; + float flDist = fabs(m_vecGoalAngles.y - m_vecCurAngles.y); + bool bReversed = false; + + if (flDist > 180) + { + flDist = 360 - flDist; + flDir = -flDir; + bReversed = true; + } + + if (m_hEnemy == NULL && !m_bSuppressing) + { + if (flDist > 30) + { + if (m_fTurnRate < m_iBaseTurnRate * 20) + { + m_fTurnRate += m_iBaseTurnRate; + } + } + else + { + // Slow down + if ( m_fTurnRate > (m_iBaseTurnRate * 5) ) + m_fTurnRate -= m_iBaseTurnRate; + } + } + else + { + // When tracking enemies, move faster and don't slow + if (flDist > 30) + { + if (m_fTurnRate < m_iBaseTurnRate * 30) + { + m_fTurnRate += m_iBaseTurnRate * 3; + } + } + } + + m_vecCurAngles.y += 0.1 * m_fTurnRate * flDir; + + // if we passed over the goal, peg right to it now + if (flDir == -1) + { + if ( (bReversed == false && m_vecGoalAngles.y > m_vecCurAngles.y) || (bReversed == true && m_vecGoalAngles.y < m_vecCurAngles.y) ) + m_vecCurAngles.y = m_vecGoalAngles.y; + } + else + { + if ( (bReversed == false && m_vecGoalAngles.y < m_vecCurAngles.y) || (bReversed == true && m_vecGoalAngles.y > m_vecCurAngles.y) ) + m_vecCurAngles.y = m_vecGoalAngles.y; + } + + if (m_vecCurAngles.y < 0) + m_vecCurAngles.y += 360; + else if (m_vecCurAngles.y >= 360) + m_vecCurAngles.y -= 360; + + if (flDist < (0.05 * m_iBaseTurnRate)) + m_vecCurAngles.y = m_vecGoalAngles.y; + + m_fBoneXRotator = m_vecCurAngles.y - GetLocalAngles().y; + + bMoved = 1; + } + + if ( !bMoved || !m_fTurnRate ) + { + m_fTurnRate = m_iBaseTurnRate; + } + + return bMoved; +} + +//----------------------------------------------------------------------------- +// Purpose: Returns true is the passed ent is in the caller's forward view cone. +// The dot product is performed in 2d, making the view cone infinitely tall. +//----------------------------------------------------------------------------- +bool CObjectSentrygun::FInViewCone( CBaseEntity *pEntity ) +{ + float flDot; + + Vector vecFacingDir; + AngleVectors( m_vecCurAngles, &vecFacingDir ); + Vector vecLOS = ( pEntity->GetAbsOrigin() - GetAbsOrigin() ); + flDot = DotProduct( vecLOS , vecFacingDir ); + + if ( flDot > VIEW_FIELD_NARROW ) + return true; + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: Check the shield's values +//----------------------------------------------------------------------------- +void CObjectSentrygun::CheckShield( void ) +{ + if ( m_nRenderFX == kRenderFxNone ) + return; +} + +//----------------------------------------------------------------------------- +// Purpose: Rotate and scan for targets +//----------------------------------------------------------------------------- +void CObjectSentrygun::SentryRotate( void ) +{ + SetNextThink( gpGlobals->curtime + 0.1f ); + + CheckShield(); + + // I can't do anything if I'm not active + if ( !ShouldBeActive() ) + return; + + // If we're turtling, see if we're finished yet + if ( IsTurtling() ) + { + if ( m_flTurtlingFinishedAt <= gpGlobals->curtime ) + { + m_bTurtling = false; + if ( m_bTurtled ) + { + AddSolidFlags( FSOLID_NOT_SOLID ); + m_takedamage = DAMAGE_NO; + } + } + return; + } + + // Turtling sentryguns don't think + if ( IsTurtled() ) + return; + + // animate + SetSentryAnim( TFTURRET_ANIM_SPIN ); + + // Abort if it's not time to search for enemies + if ( m_flNextLook < gpGlobals->curtime ) + { + m_flNextLook = gpGlobals->curtime + 1.0; + + // Look for a target + m_hEnemy = FindTarget(); + + if ( m_hEnemy != NULL ) + { + FoundTarget(); + return; + } + } + + // Rotate + if ( !MoveTurret() ) + { + // Play a sound occasionally + if ( random->RandomFloat(0, 1) < 0.02 ) + { + EmitSound( "ObjectSentrygun.Idle" ); + } + + // Switch rotation direction + if (m_bTurningRight) + { + m_bTurningRight = false; + m_vecGoalAngles.y = m_iLeftBound; + } + else + { + m_bTurningRight = true; + m_vecGoalAngles.y = m_iRightBound; + } + + // Randomly look up and down a bit + if ( random->RandomFloat(0, 1) < 0.3 ) + { + m_vecGoalAngles.x = (int)random->RandomFloat(-10,10); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Check to see if there's a valid target in sight +//----------------------------------------------------------------------------- +CBaseEntity *CObjectSentrygun::FindTarget( void ) +{ + CBaseEntity *pHighestPriorityTarget = NULL; + float fHighestPriority = 0; + + // If I have a designated enemy, and it's valid, assume it will be the target, unless something higher priority shows up. + if ( m_hDesignatedEnemy.Get() && ValidTarget( m_hDesignatedEnemy) ) + { + fHighestPriority = GetPriority( m_hDesignatedEnemy ); + pHighestPriorityTarget = m_hDesignatedEnemy; + } + + // Find a target. + CBaseEntity *pList[1024]; + Vector delta( 2048, 2048, 2048 ); + int count = UTIL_EntitiesInBox( pList, 1024, GetAbsOrigin() - delta, GetAbsOrigin() + delta, FL_CLIENT|FL_NPC|FL_OBJECT ); + + for ( int i = 0; i < count; i++ ) + { + if( !pList[i] ) + continue; + + if ( pList[i] == this ) + continue; + + float fPriority = GetPriority( pList[i] ); + + if( !pHighestPriorityTarget || (fPriority > fHighestPriority) ) + { + + if ( ValidTarget( pList[i] ) ) + { + pHighestPriorityTarget = pList[i]; + fHighestPriority = fPriority; + } + } + } + + return pHighestPriorityTarget; +} + +//----------------------------------------------------------------------------- +// Purpose: Get the priority of the target +//----------------------------------------------------------------------------- +float CObjectSentrygun::GetPriority( CBaseEntity *pTarget ) +{ + // Players + if ( pTarget->IsPlayer() ) + { + return 20; + } + + // NPCs + if ( pTarget->GetFlags() & FL_NPC ) + return 10; + + // Objects + if ( pTarget->Classify() == CLASS_MILITARY ) + { + // Sentryguns are highest priority + CBaseObject *pObject = (CBaseObject *)pTarget; + if ( pObject->IsSentrygun() ) + return 5; + return 2; + } + + return 1; +} + +//----------------------------------------------------------------------------- +// Purpose: Returns the sentry targeting range the target is in +//----------------------------------------------------------------------------- +int CObjectSentrygun::Range( CBaseEntity *pTarget ) +{ + Vector vecOrg = EyePosition(); + Vector vecTargetOrg = pTarget->EyePosition(); + + int iDist = ( vecTargetOrg - vecOrg ).Length(); + + // Sensors increase targeting range + if ( m_bSensors ) + { + iDist *= 0.75; + } + + if (iDist < obj_sentrygun_range_mid.GetFloat() ) + return RANGE_NEAR; + if (iDist < obj_sentrygun_range_max.GetFloat() ) + return RANGE_MID; + return RANGE_FAR; +} + +//----------------------------------------------------------------------------- +// Purpose: Check to see if a target's valid +//----------------------------------------------------------------------------- +bool CObjectSentrygun::ValidTarget( CBaseEntity *pTarget ) +{ + // Make sure we aren't borked: + if ( !pTarget ) + return false; + + // Don't attack things that have already died + if ( !pTarget->IsAlive() ) + return false; + + // Don't attack things that cant be hurt + if ( pTarget->m_takedamage != DAMAGE_YES ) + return false; + + // Don't shoot at objects on the neutral team. + if( !pTarget->IsInAnyTeam() ) + return false; + + + + // Ignore camoed players + if ( pTarget->IsPlayer() ) + { + CBaseTFPlayer *pPlayer = (CBaseTFPlayer*)pTarget; + + if ( InSameTeam( pPlayer ) ) + return false; + if ( pPlayer->IsClass( TFCLASS_UNDECIDED ) ) + return false; + if ( pPlayer->GetCamouflageAmount() >= 30.0f ) + return false; + + } + else + { + // Only attack enemies. + if ( InSameTeam( pTarget) ) + return false; + } + + if ( pTarget->GetFlags() & FL_NOTARGET ) + return false; + + if ( !FVisible(pTarget) ) + return false; + + // Ignore certain enemy infrastructure type objects: + CBaseObject *pObject = dynamic_cast< CBaseObject* >(pTarget); + if ( pObject ) + { + // Make sure it's not placing + if ( pObject->IsPlacing() ) + return false; + + // Ignore upgrades + if ( pObject->IsAnUpgrade() ) + return false; + + // Ignore defensive structures + if ( IsObjectADefensiveBuilding( pObject->GetType() ) ) + return false; + + // Ignore mapdefined objects + if ( pObject->GetType() == OBJ_MAPDEFINED ) + return false; + } + + // Make sure there's nothing inbetween us + Vector vecSrc = EyePosition(); + + // Now make sure there isn't something other than team players in the way. + trace_t tr; + CTraceFilterSimpleList sentryFilter( COLLISION_GROUP_NONE ); + sentryFilter.AddEntityToIgnore( GetOwner() ); + sentryFilter.AddEntityToIgnore( this ); + sentryFilter.AddEntityToIgnore( GetMoveParent() ); + UTIL_TraceLine( vecSrc, pTarget->WorldSpaceCenter(), MASK_SHOT, &sentryFilter, &tr ); + CBaseEntity *pEntity = tr.m_pEnt; + if ( (tr.fraction < 1.0) && ( pEntity != pTarget ) ) + return false; + + int iRange = Range(pTarget); + if ( iRange == RANGE_FAR ) + return false; + + // Better sensors allow them to track irrespective of facing + if ( iRange == RANGE_MID && (!FInViewCone(pTarget) && !m_bSensors) ) + return false; + + // Don't shoot at turtled sentry guns. + CObjectSentrygun *pSentry = dynamic_cast< CObjectSentrygun* >( pTarget ); + if ( pSentry && pSentry->IsTurtled() ) + return false; + + // Don't shoot at targets blocked by enemy shields + bool bBlocked = TFGameRules()->IsBlockedByEnemyShields( GetAbsOrigin(), pTarget->GetAbsOrigin(), GetTeamNumber() ); + if( bBlocked ) + return false; + + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: Return true if the sentry has some ammo to fire +//----------------------------------------------------------------------------- +bool CObjectSentrygun::HasAmmo( void ) +{ + return (m_iAmmo > 0); +} + +//----------------------------------------------------------------------------- +// Purpose: We've found a valid target +//----------------------------------------------------------------------------- +void CObjectSentrygun::FoundTarget() +{ + if ( HasAmmo() ) + { + EmitSound( "ObjectSentrygun.FoundTarget" ); + } + + SetThink( Attack ); + SetNextThink( gpGlobals->curtime + 0.1 ); +} + +//----------------------------------------------------------------------------- +// Purpose: Make sure our target is still valid, and if so, fire at it +//----------------------------------------------------------------------------- +void CObjectSentrygun::Attack( void ) +{ + SetNextThink( gpGlobals->curtime + 0.1f ); + + CheckShield(); + + // Turtling sentryguns don't attack + if ( IsTurtled() ) + { + SetThink( SentryRotate ); + return; + } + + // I can't do anything if I'm not active + if ( !ShouldBeActive() ) + return; + + // Make sure our target is still valid and that we've still got ammo + if ( m_bSuppressing && HasAmmo() ) + { + if ( gpGlobals->curtime > (m_flStartedSuppressing + MAX_SUPPRESSION_TIME) ) + { + m_bSuppressing = false; + SetThink( SentryRotate ); + return; + } + + // Check to see if we can find a valid target to switch to + m_hEnemy = FindTarget(); + if ( m_hEnemy ) + { + // Stop supressing and target this new enemy + m_bSuppressing = false; + m_flStartedSuppressing = 0; + FoundTarget(); + } + } + else if ( !ValidTarget(m_hEnemy) || HasAmmo() == false ) + { + m_hEnemy = NULL; + + // Smarter sentryguns will suppression fire for a few seconds + if ( m_bSmarter && WillSuppress() && HasAmmo() ) + { + m_bSuppressing = true; + m_flStartedSuppressing = gpGlobals->curtime; + } + else + { + RecomputeOrientation(); + SetThink( SentryRotate ); + return; + } + } + + // If I have a designated enemy, and it's valid, target it instead of my current enemy + if ( m_hDesignatedEnemy.Get() && (m_hEnemy.Get() != m_hDesignatedEnemy.Get()) ) + { + if ( ValidTarget( m_hDesignatedEnemy ) ) + { + m_hEnemy = m_hDesignatedEnemy; + FoundTarget(); + } + } + + // Figure out where we're firing at + Vector vecMid = EyePosition(); + if ( m_bSuppressing ) + { + // Suppression fire should just shoot at it's last known position + m_vecFireTarget = m_vecLastKnownPosition; + } + else + { + m_vecFireTarget = m_hEnemy->BodyTarget( vecMid ); + m_vecLastKnownPosition = m_vecFireTarget; + } + Vector vecDirToEnemy = m_vecFireTarget - vecMid; + QAngle angToTarget; + VectorAngles(vecDirToEnemy, angToTarget); + + angToTarget.y = UTIL_AngleMod( angToTarget.y ); + if (angToTarget.x < -180) + angToTarget.x += 360; + if (angToTarget.x > 180) + angToTarget.x -= 360; + + // now all numbers should be in [1...360] + // pin to turret limitations to [-50...50] + if (angToTarget.x > 50) + angToTarget.x = 50; + else if (angToTarget.x < -50) + angToTarget.x = -50; + m_vecGoalAngles.y = angToTarget.y; + m_vecGoalAngles.x = angToTarget.x; + + MoveTurret(); + + // Fire on the target if it's within 10 units of being aimed right at it + if ( m_flNextAttack <= gpGlobals->curtime && (m_vecGoalAngles - m_vecCurAngles).Length() <= 15 ) + { + // Suppressing turrets fire randomly + if ( m_bSuppressing ) + { + if ( random->RandomInt( 0,1 ) != 0 ) + { + m_flNextAttack = gpGlobals->curtime + 0.5; + return; + } + } + + // See if the object or its owner is taking emp damage, if so, don't fire + if ( ShouldBeActive() ) + { + Fire(); + } + } + else + { + SetSentryAnim( TFTURRET_ANIM_SPIN ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Called when a rotation happens +//----------------------------------------------------------------------------- +void CObjectSentrygun::ObjectMoved( void ) +{ + m_vecCurAngles.y = UTIL_AngleMod( GetLocalAngles().y ); + RecomputeOrientation(); + m_fBoneXRotator = 0; + BaseClass::ObjectMoved(); +} + +//----------------------------------------------------------------------------- +// Purpose: Fire at our target +//----------------------------------------------------------------------------- +bool CObjectSentrygun::Fire( void ) +{ + // Base sentry doesn't know how to fire + return true; +} + + +//----------------------------------------------------------------------------- +// Purpose: Tell this sentrygun to attack the following target, if it can +//----------------------------------------------------------------------------- +void CObjectSentrygun::DesignateTarget( CBaseEntity *pTarget ) +{ + m_hDesignatedEnemy = pTarget; + + if ( m_hEnemy.Get() != m_hDesignatedEnemy.Get() ) + { + m_hEnemy = NULL; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CObjectSentrygun::IsTurtled( void ) +{ + return m_bTurtled; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CObjectSentrygun::IsTurtling( void ) +{ + return m_bTurtling; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CObjectSentrygun::ToggleTurtle( void ) +{ + // Don't turtle while building + if ( IsPlacing() || IsBuilding() || IsTurtling() ) + return; + + // Don't turtle if I'm built on anything + if ( GetMoveParent() ) + return; + + // Swap turtle state + if ( IsTurtled() ) + { + UnTurtle(); + } + else + { + Turtle(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CObjectSentrygun::Turtle( void ) +{ + m_bTurtled = true; + m_bTurtling = true; + + m_flTurtlingFinishedAt = gpGlobals->curtime + SENTRY_TURTLE_TIME; + EmitSound( "ObjectSentrygun.Turtle" ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CObjectSentrygun::UnTurtle( void ) +{ + // Make sure there's enough room to unturtle + // NJS: this seems a bit hacky and returns false positives sometimes, for now we're just assuming that if it can turtle, it can also unturtle. + //trace_t tr; + //UTIL_TraceHull( GetAbsOrigin(), GetAbsOrigin(), WorldAlignMins() - Vector( 4,4,4 ), WorldAlignMaxs() + Vector( 4,4,4 ), MASK_SOLID, this, COLLISION_GROUP_NONE, &tr ); + //if ( tr.startsolid || tr.allsolid ) + // return; + + m_bTurtled = false; + m_bTurtling = true; + m_flTurtlingFinishedAt = gpGlobals->curtime + SENTRY_TURTLE_TIME; + EmitSound( "ObjectSentrygun.UnTurtle" ); + + RemoveSolidFlags( FSOLID_NOT_SOLID ); + m_takedamage = DAMAGE_YES; +} + +//----------------------------------------------------------------------------- +// Purpose: Set the technology levels of the sentrygun +//----------------------------------------------------------------------------- +void CObjectSentrygun::SetTechnology( bool bSmarter, bool bSensors ) +{ + m_bSmarter = bSmarter; + m_bSensors = bSensors; + + // Smarter sentryguns turn faster + if ( m_bSmarter ) + { + m_iBaseTurnRate = 6; + } + else + { + m_iBaseTurnRate = 4; + } +} + +//======================================================================================================== +// SENTRYGUN TYPES +//======================================================================================================== +IMPLEMENT_SERVERCLASS_ST(CObjectSentrygunPlasma, DT_ObjectSentrygunPlasma) +END_SEND_TABLE(); + +IMPLEMENT_SERVERCLASS_ST(CObjectSentrygunRocketlauncher, DT_ObjectSentrygunRocketlauncher) +END_SEND_TABLE(); + +void CObjectSentrygunPlasma::Spawn( void ) +{ + m_iHealth = obj_sentrygun_plasma_health.GetInt(); + + SetModel( SG_PLASMA_MODEL ); + BaseClass::Spawn(); + + SetType( OBJ_SENTRYGUN_PLASMA ); + + m_flNextAmmoRecharge = gpGlobals->curtime + PLASMA_SENTRYGUN_RECHARGE_TIME; + m_nBurstCount = PLASMA_SENTRY_BURST_COUNT; + + m_iAmmo = m_iMaxAmmo = 50; + m_iAmmoType = GetAmmoDef()->Index( "ShotgunEnergy" ); +} + +void CObjectSentrygunRocketlauncher::Spawn( void ) +{ + m_iHealth = obj_sentrygun_rocketlauncher_health.GetInt(); + + SetModel( SG_ROCKETLAUNCHER_MODEL ); + BaseClass::Spawn(); + + SetType( OBJ_SENTRYGUN_ROCKET_LAUNCHER ); + + m_iAmmo = m_iMaxAmmo = 50; + m_iAmmoType = GetAmmoDef()->Index( "Rockets" ); +} + +//----------------------------------------------------------------------------- +// Purpose: Plasma sentrygun's fire +//----------------------------------------------------------------------------- +bool CObjectSentrygunPlasma::Fire( void ) +{ + Vector vecSrc = EyePosition(); + Vector vecTarget = m_vecFireTarget; + Vector vecAim; + QAngle vecAng; + + // Because the plasma sentrygun always thinks it has ammo (see below) + // we might not have ammo here, in which case we should just abort. + if ( !m_iAmmo ) + return true; + + GetAttachment( "muzzle", vecSrc, vecAng ); + + // Get the distance to the target + float targetDist = (vecTarget - vecSrc).Length(); + float targetTime = targetDist / PLASMA_VELOCITY; + + // If we're not suppressing, calculate where the target's going to be in that time + if ( !m_bSuppressing ) + { + Vector vecVelocity = m_hEnemy->GetSmoothedVelocity(); + // Dampen Z velocity to prevent jumping people screwing the aim + vecVelocity.z *= 0.25; + // Get the target point to aim for + Vector vecEnd = vecTarget + ( vecVelocity * targetTime ); + vecAim = (vecEnd - vecSrc); + } + else + { + vecAim = (vecTarget - vecSrc); + } + VectorNormalize( vecAim ); + + int damageType = GetAmmoDef()->DamageType( m_iAmmoType ); + CBasePlasmaProjectile *pPlasma = CBasePlasmaProjectile::Create( vecSrc + (vecAim * 32), vecAim, damageType, this ); + pPlasma->SetDamage( 15 ); + pPlasma->SetMaxRange( obj_sentrygun_plasma_range.GetFloat() ); + pPlasma->m_hOwner = GetBuilder(); + + EmitSound( "ObjectSentrygun.Fire" ); + SetSentryAnim( TFTURRET_ANIM_FIRE ); + DoMuzzleFlash(); + + m_iAmmo -= 1; + + float flAttackTime; + if (--m_nBurstCount > 0) + { + flAttackTime = 0.2f; + } + else + { + flAttackTime = random->RandomFloat( 1.0f, 2.0f ); + m_nBurstCount = PLASMA_SENTRY_BURST_COUNT + random->RandomInt( 0, PLASMA_SENTRY_BURST_COUNT ); + } + + // If I'm EMPed, slow the firing rate down + if ( HasPowerup(POWERUP_EMP) ) + { + flAttackTime *= 3; + } + + m_flNextAttack = gpGlobals->curtime + flAttackTime; + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: Plasma sentry regenerates ammo, so always assume it has ammo left. +// This is to prevent it from continually unlocking & relocking when it's +// ammo is flickering between 0 and 1. +//----------------------------------------------------------------------------- +bool CObjectSentrygunPlasma::HasAmmo( void ) +{ + return true; +} + + +//----------------------------------------------------------------------------- +// Purpose: Plasma sentrygun recharges it's ammo +//----------------------------------------------------------------------------- +void CObjectSentrygunPlasma::CheckShield( void ) +{ + // ROBIN: Disabled recharging for now + /* + if ( m_flNextAmmoRecharge < gpGlobals->curtime ) + { + if ( m_iAmmo < m_iMaxAmmo ) + { + m_iAmmo++; + } + + m_flNextAmmoRecharge = gpGlobals->curtime + PLASMA_SENTRYGUN_RECHARGE_TIME; + } + */ + + BaseClass::CheckShield(); +} + + +//----------------------------------------------------------------------------- +// Purpose: Rocket launcher sentrygun's fire +//----------------------------------------------------------------------------- +bool CObjectSentrygunRocketlauncher::Fire() +{ + Vector vecSrc = EyePosition(); + Vector vecTarget = m_vecFireTarget; + Vector vecAim; + + // Get the distance to the target + float targetDist = (vecTarget - vecSrc).Length(); + float targetTime = targetDist / ROCKET_VELOCITY; + + // If we're not suppressing, calculate where the target's going to be in that time + if ( !m_bSuppressing ) + { + Vector vecVelocity = m_hEnemy->GetSmoothedVelocity(); + // Dampen velocity to prevent people rapidly switching strafe + vecVelocity *= 0.5; + // Dampen Z velocity to prevent jumping people screwing the aim + vecVelocity.z *= 0.5; + // Get the target point to aim for + Vector vecEnd = vecTarget + ( vecVelocity * targetTime ); + vecAim = (vecEnd - vecSrc); + } + else + { + vecAim = (vecTarget - vecSrc); + } + + CGrenadeRocket::Create( vecSrc, vecAim, edict(), GetOwner() ); + + EmitSound( "ObjectSentrygunRocketlauncher.Fire" ); + + m_iAmmo -= 1; + + m_flNextAttack = gpGlobals->curtime + 1.5f; + return true; +} + + +void CObjectSentrygunRocketlauncher::SetTechnology( bool bSmarter, bool bSensors ) +{ + BaseClass::SetTechnology( bSmarter, bSensors ); + m_iBaseTurnRate = 2; +} diff --git a/game/server/tf2/tf_obj_sentrygun.h b/game/server/tf2/tf_obj_sentrygun.h new file mode 100644 index 0000000..aabe44b --- /dev/null +++ b/game/server/tf2/tf_obj_sentrygun.h @@ -0,0 +1,190 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Defender's sentrygun +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef TF_OBJ_SENTRYGUN_H +#define TF_OBJ_SENTRYGUN_H +#ifdef _WIN32 +#pragma once +#endif + +#include "tf_obj.h" + +enum TFTURRET_ANIM +{ + TFTURRET_ANIM_NONE = 0, + TFTURRET_ANIM_FIRE, + TFTURRET_ANIM_SPIN, +}; + +enum target_ranges +{ + RANGE_NEAR, + RANGE_MID, + RANGE_FAR, +}; + +// Sentrygun damages +#define SG_MACHINEGUN_DAMAGE 5 + +// ------------------------------------------------------------------------ // +// The Base Sentrygun +// ------------------------------------------------------------------------ // +class CObjectSentrygun : public CBaseObject +{ + DECLARE_CLASS( CObjectSentrygun, CBaseObject ); +public: + DECLARE_DATADESC(); + DECLARE_SERVERCLASS(); + + CObjectSentrygun(); + + static CObjectSentrygun* Create(const Vector &vOrigin, const QAngle &vAngles, int iType); + + virtual void Spawn(); + virtual void Precache(); + virtual void SetupAttachedVersion( void ); + virtual void SetupUnattachedVersion( void ); + virtual void GetControlPanelInfo( int nPanelIndex, const char *&pPanelName ); + virtual void FinishedBuilding( void ); + virtual bool IsSentrygun( void ) { return true; }; + virtual bool WantsCoverFromSentryGun() { return false; } + virtual void SetTechnology( bool bSmarter, bool bSensors ); + virtual int OnTakeDamage( const CTakeDamageInfo &info ); + virtual void Killed( void ); + + // Ammo filling + virtual bool ClientCommand( CBaseTFPlayer *pPlayer, const CCommand &args ); + virtual bool TakeAmmoFrom( CBaseTFPlayer *pPlayer ); + + // Think functions + void SentryRotate(void); + void Attack(void); + void Sentry_Explode( void ); + bool FInViewCone( CBaseEntity *pEntity ); + int BloodColor( void ) { return DONT_BLEED; } + + virtual void CheckShield( void ); + void RestartAnimation(); + void ResetOrientation(); + + virtual void SetSentryAnim( TFTURRET_ANIM anim ); + virtual CBaseEntity *FindTarget( void ); + virtual float GetPriority( CBaseEntity *pTarget ); + virtual void FoundTarget(); + virtual bool ValidTarget( CBaseEntity *pTarget ); + virtual int Range( CBaseEntity *pTarget ); + + // Combat functions + virtual bool HasAmmo( void ); + virtual bool Fire( void ); + virtual bool WillSuppress( void ) { return true; }; + + virtual bool CanTakeEMPDamage( void ) { return true; } + + // Turret Functions + bool MoveTurret( void ); + + // Object functions + void ObjectMoved( void ); + + // Designator interactions + void DesignateTarget( CBaseEntity *pTarget ); + // Turtle mode + bool IsTurtled( void ); + bool IsTurtling( void ); // Return true if we're in the process of turtling / unturtling + void ToggleTurtle( void ); + void Turtle( void ); + void UnTurtle( void ); + +private: + // Recompute sentrygun orientation... + void RecomputeOrientation(); + +public: + // Variables + int m_iRightBound; + int m_iLeftBound; + bool m_bTurningRight; + int m_iShardIndex; + int m_iAmmoType; + bool m_bSmarter; + bool m_bSensors; + float m_flNextLook; + + // Attacking + float m_flNextAttack; + CNetworkVar( int, m_iAmmo ); + int m_iMaxAmmo; + Vector m_vecFireTarget; + Vector m_vecLastKnownPosition; + bool m_bSuppressing; + float m_flStartedSuppressing; + + // Movement + CNetworkVar( int, m_iBaseTurnRate ); + float m_fTurnRate; + QAngle m_vecCurAngles; + QAngle m_vecGoalAngles; + Vector m_vecCurDishAngles; + + // Turtling + CNetworkVar( bool, m_bTurtled ); + bool m_bTurtling; + float m_flTurtlingFinishedAt; + + // Data sent to clients + // Bone controllers + float m_fBoneXRotator; + float m_fBoneYRotator; + + // Target data + CNetworkHandle( CBaseEntity, m_hEnemy ); + EHANDLE m_hDesignatedEnemy; + + CNetworkVar( int, m_nAnimationParity ); + CNetworkVar( int, m_nOrientationParity ); +}; + +//----------------------------------------------------------------------------- +// Purpose: Plasma Sentrygun +//----------------------------------------------------------------------------- +class CObjectSentrygunPlasma : public CObjectSentrygun +{ + DECLARE_CLASS( CObjectSentrygunPlasma, CObjectSentrygun ); +public: + DECLARE_SERVERCLASS(); + virtual void Spawn(); + virtual bool Fire( void ); + virtual void CheckShield( void ); + virtual bool HasAmmo( void ); + +private: + float m_flNextAmmoRecharge; + int m_nBurstCount; +}; + +#define SG_PLASMA_MODEL "models/sentry2.mdl" +#define PLASMA_SENTRYGUN_RECHARGE_TIME 1.25 // Time it takes to recharge 1 round of ammo +#define PLASMA_SENTRY_BURST_COUNT 4 + +//----------------------------------------------------------------------------- +// Purpose: Rocket launcher Sentrygun +//----------------------------------------------------------------------------- +class CObjectSentrygunRocketlauncher : public CObjectSentrygun +{ + DECLARE_CLASS( CObjectSentrygunRocketlauncher, CObjectSentrygun ); +public: + DECLARE_SERVERCLASS(); + virtual void Spawn(); + virtual bool Fire( void ); + + virtual void SetTechnology( bool bSmarter, bool bSensors ); +}; + +#define SG_ROCKETLAUNCHER_MODEL "models/sentry3.mdl" + +#endif // TF_OBJ_SENTRYGUN_H diff --git a/game/server/tf2/tf_obj_shieldwall.cpp b/game/server/tf2/tf_obj_shieldwall.cpp new file mode 100644 index 0000000..37ccc47 --- /dev/null +++ b/game/server/tf2/tf_obj_shieldwall.cpp @@ -0,0 +1,270 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Medic's resupply beacon +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" +#include "tf_obj.h" +#include "tf_player.h" +#include "tf_team.h" +#include "techtree.h" +#include "tf_shield.h" +#include "VGuiScreen.h" + +//----------------------------------------------------------------------------- +// Shield wall defines +//----------------------------------------------------------------------------- + +#define SHIELDWALL_MINS Vector(-20, -20, 0) +#define SHIELDWALL_MAXS Vector( 20, 20, 100) + +#define SHIELD_WALL_PITCH -10.0f + + +ConVar obj_shieldwall_health( "obj_shieldwall_health","200", FCVAR_NONE, "Shield wall health" ); + + +//----------------------------------------------------------------------------- +// Shield wall object that's built by the player +//----------------------------------------------------------------------------- +class CObjectShieldWallBase : public CBaseObject +{ +DECLARE_CLASS( CObjectShieldWallBase, CBaseObject ); + +public: + CObjectShieldWallBase(); + + virtual void UpdateOnRemove( void ); + + virtual void Spawn(); + + virtual void PowerupStart( int iPowerup, float flAmount, CBaseEntity *pAttacker, CDamageModifier *pDamageModifier ); + virtual void PowerupEnd( int iPowerup ); + + // Team change + virtual void ChangeTeam( int nTeamNumber ) OVERRIDE; + +public: + CNetworkHandle( CShield, m_hDeployedShield ); +}; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CObjectShieldWallBase::CObjectShieldWallBase() +{ + m_hDeployedShield.Set(0); + AddEFlags( EFL_FORCE_CHECK_TRANSMIT ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CObjectShieldWallBase::UpdateOnRemove( void ) +{ + if ( m_hDeployedShield.Get() ) + { + UTIL_Remove( m_hDeployedShield ); + m_hDeployedShield.Set( NULL ); + } + + // Chain at end to mimic destructor unwind order + BaseClass::UpdateOnRemove(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CObjectShieldWallBase::Spawn() +{ + m_takedamage = DAMAGE_YES; + + SetType( OBJ_SHIELDWALL ); + + BaseClass::Spawn(); +} + +//----------------------------------------------------------------------------- +// Purpose: Powerup has just started +//----------------------------------------------------------------------------- +void CObjectShieldWallBase::PowerupStart( int iPowerup, float flAmount, CBaseEntity *pAttacker, CDamageModifier *pDamageModifier ) +{ + switch( iPowerup ) + { + case POWERUP_BOOST: + // Increase our shield's energy + if ( m_hDeployedShield ) + { + m_hDeployedShield->SetPower( m_hDeployedShield->GetPower() + (flAmount * 3) ); + } + break; + + case POWERUP_EMP: + if (m_hDeployedShield) + { + m_hDeployedShield->SetEMPed(true); + } + break; + + default: + break; + } + + BaseClass::PowerupStart( iPowerup, flAmount, pAttacker, pDamageModifier ); +} + +//----------------------------------------------------------------------------- +// Purpose: Powerup has just started +//----------------------------------------------------------------------------- +void CObjectShieldWallBase::PowerupEnd( int iPowerup ) +{ + switch ( iPowerup ) + { + case POWERUP_EMP: + if (m_hDeployedShield) + { + m_hDeployedShield->SetEMPed(false); + } + break; + + default: + break; + } + + BaseClass::PowerupEnd( iPowerup ); +} + + +//----------------------------------------------------------------------------- +// Team change +//----------------------------------------------------------------------------- +void CObjectShieldWallBase::ChangeTeam( int nTeamNumber ) +{ + BaseClass::ChangeTeam( nTeamNumber ); + if ( m_hDeployedShield ) + { + m_hDeployedShield->ChangeTeam( nTeamNumber ); + } +} + + +//----------------------------------------------------------------------------- +// Shield wall object that's built by the player +//----------------------------------------------------------------------------- +class CObjectShieldWall : public CObjectShieldWallBase +{ +DECLARE_CLASS( CObjectShieldWall, CObjectShieldWallBase ); + +public: + DECLARE_SERVERCLASS(); + + CObjectShieldWall(); + + virtual void Spawn(); + virtual void Precache(); + virtual void GetControlPanelInfo( int nPanelIndex, const char *&pPanelName ); + virtual void ObjectMoved( ); + virtual void FinishedBuilding( void ); +}; + +IMPLEMENT_SERVERCLASS_ST(CObjectShieldWall, DT_ObjectShieldWall) + SendPropEHandle(SENDINFO(m_hDeployedShield)), +END_SEND_TABLE(); + +LINK_ENTITY_TO_CLASS(obj_shieldwall, CObjectShieldWall); +PRECACHE_REGISTER(obj_shieldwall); + +CObjectShieldWall::CObjectShieldWall() +{ + UseClientSideAnimation(); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CObjectShieldWall::Spawn() +{ + SetModel( "models/objects/obj_shieldwall.mdl" ); + SetSolid( SOLID_BBOX ); + UTIL_SetSize(this, SHIELDWALL_MINS, SHIELDWALL_MAXS); + m_iHealth = obj_shieldwall_health.GetInt(); + m_hDeployedShield = NULL; + + SetType( OBJ_SHIELDWALL ); + + BaseClass::Spawn(); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CObjectShieldWall::Precache() +{ + PrecacheModel( "models/objects/obj_shieldwall.mdl" ); + PrecacheVGuiScreen( "screen_obj_shieldwall" ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Gets info about the control panels +//----------------------------------------------------------------------------- +void CObjectShieldWall::GetControlPanelInfo( int nPanelIndex, const char *&pPanelName ) +{ + pPanelName = "screen_obj_shieldwall"; +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CObjectShieldWall::FinishedBuilding( void ) +{ + BaseClass::FinishedBuilding(); + + int nAttachmentIndex = LookupAttachment( "projectionpoint" ); + + m_hDeployedShield = CreateMobileShield( this ); + m_hDeployedShield->SetAlwaysOrient( false ); + m_hDeployedShield->SetAttachmentIndex( nAttachmentIndex ); + m_hDeployedShield->SetAngularSpringConstant( 20 ); + ObjectMoved(); +} + + +//----------------------------------------------------------------------------- +// Called when the builder rotates this object... +//----------------------------------------------------------------------------- +void CObjectShieldWall::ObjectMoved( ) +{ + if (m_hDeployedShield) + { + VMatrix matangles; + VMatrix matoffset; + VMatrix matfinal; + + // This represents how much to pitch the shield up from the attachment point + QAngle angleOffset( SHIELD_WALL_PITCH, 0, 0 ); + + // Get the location and angles of the attachment point + // Attachment point position is the origin of the shield + QAngle angles; + + // Rotate the angles of the attachment point by the angle offset + MatrixFromAngles( GetAbsAngles(), matangles ); + MatrixFromAngles( angleOffset, matoffset ); + MatrixMultiply( matangles, matoffset, matfinal ); + MatrixToAngles( matfinal, angles ); + m_hDeployedShield->SetCenterAngles( angles ); + + m_hDeployedShield->ShieldMoved(); + } + BaseClass::ObjectMoved(); +} + + + + diff --git a/game/server/tf2/tf_obj_shieldwall.h b/game/server/tf2/tf_obj_shieldwall.h new file mode 100644 index 0000000..f78f242 --- /dev/null +++ b/game/server/tf2/tf_obj_shieldwall.h @@ -0,0 +1,13 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Medic's resupply beacon +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef TF_OBJ_SHIELDWALL_H +#define TF_OBJ_SHIELDWALL_H + +#define SHIELDWALL_RANGE 300.0f + +#endif // TF_OBJ_SHIELDWALL_H diff --git a/game/server/tf2/tf_obj_tower.cpp b/game/server/tf2/tf_obj_tower.cpp new file mode 100644 index 0000000..6ea8a23 --- /dev/null +++ b/game/server/tf2/tf_obj_tower.cpp @@ -0,0 +1,175 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: A stationary tower that players can take cover in. +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" +#include "tf_obj.h" +#include "tf_obj_tower.h" +#include "tf_team.h" +#include "tf_obj.h" +#include "engine/IEngineSound.h" + +#define TOWER_MINS Vector(-100, -100, 0) +#define TOWER_MAXS Vector( 100, 100, 200) +#define TOWER_MODEL "models/objects/obj_tower.mdl" +#define TOWER_LADDER_MODEL "models/objects/obj_tower_ladder.mdl" + +IMPLEMENT_SERVERCLASS_ST(CObjectTower, DT_ObjectTower) +END_SEND_TABLE(); + +LINK_ENTITY_TO_CLASS(obj_tower, CObjectTower); +PRECACHE_REGISTER(obj_tower); + +IMPLEMENT_SERVERCLASS_ST( CObjectTowerLadder, DT_ObjectTowerLadder ) +END_SEND_TABLE(); + +LINK_ENTITY_TO_CLASS( obj_tower_ladder, CObjectTowerLadder ); +PRECACHE_REGISTER( obj_tower_ladder ); + +// CVars +ConVar obj_tower_health( "obj_tower_health","100", FCVAR_NONE, "Tower health" ); + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CObjectTower::CObjectTower( void ) +{ + m_hLadder = NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CObjectTower::Spawn( void ) +{ + Precache(); + SetModel( TOWER_MODEL ); + SetSolid( SOLID_BBOX ); + + UTIL_SetSize(this, TOWER_MINS, TOWER_MAXS); + m_takedamage = DAMAGE_YES; + m_iHealth = obj_tower_health.GetInt(); + + m_fObjectFlags |= OF_DONT_PREVENT_BUILD_NEAR_OBJ | OF_DOESNT_NEED_POWER; + SetType( OBJ_TOWER ); + + SetSolid( SOLID_VPHYSICS ); + VPhysicsInitStatic(); + + BaseClass::Spawn(); + + SetCollisionGroup( COLLISION_GROUP_VEHICLE ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CObjectTower::Precache( void ) +{ + PrecacheModel( TOWER_MODEL ); + PrecacheModel( TOWER_LADDER_MODEL ); +} + +//------------------------------------------------------------------------------ +// Purpose: +//------------------------------------------------------------------------------ +void CObjectTower::UpdateOnRemove( void ) +{ + if ( m_hLadder.Get() ) + { + UTIL_Remove( m_hLadder ); + m_hLadder = NULL; + } + + // Chain at end to mimic destructor unwind order + BaseClass::UpdateOnRemove(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CObjectTower::FinishedBuilding( void ) +{ + BaseClass::FinishedBuilding(); + + // Create the ladder. + Vector vecOrigin; + QAngle vecAngles; + GetAttachment( "ladder", vecOrigin, vecAngles ); + m_hLadder = CObjectTowerLadder::Create( vecOrigin, vecAngles, this ); + m_hLadder->ChangeTeam( GetTeamNumber() ); +} + +//----------------------------------------------------------------------------- +// Purpose: Gets info about the control panels +//----------------------------------------------------------------------------- +void CObjectTower::GetControlPanelInfo( int nPanelIndex, const char *&pPanelName ) +{ + pPanelName = "screen_basic_with_disable"; +} + + +//============================================================================== +// Tower Ladder +//============================================================================== + +//------------------------------------------------------------------------------ +// Purpose: +//------------------------------------------------------------------------------ +CObjectTowerLadder::CObjectTowerLadder() +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CObjectTowerLadder *CObjectTowerLadder::Create( const Vector &vOrigin, const QAngle &vAngles, CBaseEntity *pParent ) +{ + CObjectTowerLadder *pLadder = static_cast<CObjectTowerLadder*>( CBaseObject::Create( "obj_tower_ladder", vOrigin, vAngles ) ); + if ( pLadder ) + { + pLadder->m_hTower = pParent; + } + + return pLadder; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CObjectTowerLadder::Spawn() +{ + Precache(); + SetModel( TOWER_LADDER_MODEL ); + SetSolid( SOLID_VPHYSICS ); + m_takedamage = DAMAGE_NO; + + BaseClass::Spawn(); + + CollisionProp()->SetSurroundingBoundsType( USE_BEST_COLLISION_BOUNDS ); + IPhysicsObject *pPhysics = VPhysicsInitStatic(); + if ( pPhysics ) + { + pPhysics->EnableMotion( false ); + } + SetCollisionGroup( COLLISION_GROUP_VEHICLE ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CObjectTowerLadder::Precache() +{ + PrecacheModel( TOWER_LADDER_MODEL ); +} + +//----------------------------------------------------------------------------- +// Purpose: Pass all damage back to the tower +//----------------------------------------------------------------------------- +int CObjectTowerLadder::OnTakeDamage( const CTakeDamageInfo &info ) +{ + return m_hTower->OnTakeDamage( info ); +}
\ No newline at end of file diff --git a/game/server/tf2/tf_obj_tower.h b/game/server/tf2/tf_obj_tower.h new file mode 100644 index 0000000..79eb503 --- /dev/null +++ b/game/server/tf2/tf_obj_tower.h @@ -0,0 +1,61 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: A stationary tower that players can take cover in. +// +//=============================================================================// + +#ifndef TF_OBJ_TOWER_H +#define TF_OBJ_TOWER_H +#ifdef _WIN32 +#pragma once +#endif + +class CObjectTowerLadder; + +// ------------------------------------------------------------------------ // +// Purpose: A stationary tower that players can take cover in. +// ------------------------------------------------------------------------ // +class CObjectTower : public CBaseObject +{ + DECLARE_CLASS( CObjectTower, CBaseObject ); + +public: + DECLARE_SERVERCLASS(); + + CObjectTower( void ); + + virtual void Spawn( void ); + virtual void Precache( void ); + virtual void UpdateOnRemove( void ); + virtual void GetControlPanelInfo( int nPanelIndex, const char *&pPanelName ); + virtual void FinishedBuilding( void ); + +private: + CHandle<CObjectTowerLadder> m_hLadder; +}; + + +//----------------------------------------------------------------------------- +// Purpose: Ladder tower +//----------------------------------------------------------------------------- +class CObjectTowerLadder : public CBaseAnimating +{ + DECLARE_CLASS( CObjectTowerLadder, CBaseAnimating ); + +public: + + DECLARE_SERVERCLASS(); + + static CObjectTowerLadder* Create( const Vector &vOrigin, const QAngle &vAngles, CBaseEntity *pParent ); + + CObjectTowerLadder(); + + void Spawn(); + void Precache(); + virtual int OnTakeDamage( const CTakeDamageInfo &info ); + +public: + EHANDLE m_hTower; +}; + +#endif // TF_OBJ_TOWER_H diff --git a/game/server/tf2/tf_obj_tunnel.cpp b/game/server/tf2/tf_obj_tunnel.cpp new file mode 100644 index 0000000..c00261e --- /dev/null +++ b/game/server/tf2/tf_obj_tunnel.cpp @@ -0,0 +1,571 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "tf_obj.h" +#include "tf_obj_mapdefined.h" +#include "engine/IEngineSound.h" +#include "entityoutput.h" +#include "tf_shareddefs.h" +#include "triggers.h" +#include "shake.h" +#include "tf_player.h" +#include "tf_gamerules.h" + +#define TUNNEL_THINK_INTERVAL 0.1f +#define TUNNEL_FADE_TIME 1.0f +#define MAX_TUNNEL_DURATION 30.0f +// If tunneling takes longer than this, use a countdown +#define TUNNEL_DURATION_MESSAGE_NEEDED 3.0f + +// It takes this long to tunnel +static ConVar tf_tunnel_time( "tf_tunnel_time", "2", 0, "Takes this long to traverse a tunnel." ); + +class CObjectTunnel : public CObjectMapDefined +{ + DECLARE_CLASS( CObjectTunnel, CObjectMapDefined ); +public: + + DECLARE_SERVERCLASS(); + + virtual void Spawn( void ); + virtual void Killed( void ); + + int UpdateTransmitState(); + +private: +}; + +IMPLEMENT_SERVERCLASS_ST(CObjectTunnel, DT_ObjectTunnel) +END_SEND_TABLE(); + +int CObjectTunnel::UpdateTransmitState() +{ + return SetTransmitState( FL_EDICT_ALWAYS ); +} + +void CObjectTunnel::Spawn( void ) +{ + BaseClass::Spawn(); + + AddFlag( FL_NOTARGET ); + SetType( OBJ_TUNNEL ); +} + +LINK_ENTITY_TO_CLASS(obj_tunnel,CObjectTunnel); +LINK_ENTITY_TO_CLASS(obj_tunnel_prop,CObjectTunnel); + +//----------------------------------------------------------------------------- +// Purpose: Object has been blown up. Tunnels are never fully destroyed, so they stay on the minimap. +//----------------------------------------------------------------------------- +void CObjectTunnel::Killed( void ) +{ + m_bDying = true; + + RemoveAllSappers( this ); + + // Do an explosion. + CPASFilter filter( GetAbsOrigin() ); + te->Explosion( + filter, + 0.0, + &GetAbsOrigin(), + g_sModelIndexFireball, + 5.4, // radius + 15, + TE_EXPLFLAG_NODLIGHTS, + 256, + 200); + + // Become non-solid and invisible + VPhysicsDestroyObject(); + AddSolidFlags( FSOLID_NOT_SOLID ); + m_takedamage = DAMAGE_NO; + AddEffects( EF_NODRAW ); +} + +class CInfoTunnelExit : public CPointEntity +{ +public: + DECLARE_CLASS( CInfoTunnelExit, CPointEntity ); +private: +}; + +LINK_ENTITY_TO_CLASS(info_tunnel_exit,CInfoTunnelExit); + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +class CObjectTunnelTrigger : public CBaseTrigger +{ + DECLARE_CLASS( CObjectTunnelTrigger, CBaseTrigger ); +public: + CObjectTunnelTrigger(); + + DECLARE_DATADESC(); + + virtual void Precache(); + virtual void Spawn(); + virtual void Activate(); + + virtual void StartTouch( CBaseEntity *pOther ); + + void SetActive( bool active ); + bool GetActive( void ) const; + + void InputSetActive( inputdata_t &inputdata ); + void InputSetInactive( inputdata_t &inputdata ); + void InputToggleActive( inputdata_t &inputdata ); + + void InputSetTarget( inputdata_t &inputdata ); + void InputSetTeleportDuration( inputdata_t &inputdata ); + void InputSetTeleportVelocity( inputdata_t &inputdata ); + + virtual void TunnelThink(); +private: + float GetTeleportDuration( void ); + + bool m_bActive; + CHandle< CInfoTunnelExit > m_hTunnelExit; + + COutputEvent OnTunnelTriggerStart; + COutputEvent OnTunnelTriggerEnd; + + struct TunnelPlayer + { + CHandle< CBaseTFPlayer > player; + Vector startpos; + float tunnelstarted; + float duration; + float teleporttime; + float fadeintime; + bool exitstarted; + float fadetime; + int iremaining; + int ilastremaining; + bool needremainigcounter; + }; + + CUtlVector< TunnelPlayer > m_Tunneling; + + void StartTunneling( CBaseTFPlayer *player ); + bool KeepTunneling( TunnelPlayer *tunnel ); + + + float m_flTeleportDuration; + float m_flTeleportVelocity; +}; + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CObjectTunnelTrigger::CObjectTunnelTrigger() +{ + m_flTeleportDuration = -1.0f; + m_flTeleportVelocity = 0.0f; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *player - +//----------------------------------------------------------------------------- +void CObjectTunnelTrigger::StartTunneling( CBaseTFPlayer *player ) +{ + if ( !player ) + return; + + // Ignore if it's already in the list + int c = m_Tunneling.Count(); + for ( int i = 0 ; i < c; i++ ) + { + TunnelPlayer *tp = &m_Tunneling[ i ]; + if ( tp->player == player ) + { + return; + } + } + + TunnelPlayer tunnel; + tunnel.player = player; + tunnel.tunnelstarted = gpGlobals->curtime; + tunnel.duration = GetTeleportDuration(); + tunnel.teleporttime = tunnel.tunnelstarted + tunnel.duration; + tunnel.exitstarted = false; + tunnel.startpos = player->GetAbsOrigin(); + tunnel.iremaining = (int)tunnel.duration; + tunnel.ilastremaining = tunnel.iremaining; + tunnel.needremainigcounter = ( tunnel.iremaining > TUNNEL_DURATION_MESSAGE_NEEDED ) ? true : false; + + // Fade user screen to black + color32 black = {0,0,0,255}; + + float duration = tunnel.duration; + float fadeouttime = TUNNEL_FADE_TIME; + float holdtime = 0.0f; + if ( duration < 2 * TUNNEL_FADE_TIME ) + { + fadeouttime = duration * 0.5f; + } + else + { + fadeouttime = TUNNEL_FADE_TIME; + holdtime = duration - 2 * fadeouttime; + } + + tunnel.fadetime = fadeouttime; + tunnel.fadeintime = tunnel.tunnelstarted + fadeouttime + holdtime; + + UTIL_ScreenFade( player, black, fadeouttime, holdtime, FFADE_OUT | FFADE_STAYOUT | FFADE_PURGE ); + + m_Tunneling.AddToTail( tunnel ); + + player->SetMoveType( MOVETYPE_NONE ); + player->EnableControl( false ); + player->AddEffects( EF_NODRAW ); + player->AddSolidFlags( FSOLID_NOT_SOLID ); + + CPASAttenuationFilter filter( player, "ObjectTunnelTrigger.TeleportSound" ); + EmitSound( filter, player->entindex(), "ObjectTunnelTrigger.TeleportSound" ); + + OnTunnelTriggerStart.FireOutput( player, this ); +} + +float CObjectTunnelTrigger::GetTeleportDuration( void ) +{ + float duration = m_flTeleportDuration; + + if ( m_flTeleportVelocity > 0.0f && m_hTunnelExit != NULL ) + { + Vector delta = m_hTunnelExit->GetAbsOrigin() - GetAbsOrigin(); + float dist = delta.Length(); + duration = dist / m_flTeleportVelocity; + } + else if ( m_flTeleportDuration == -1.0f ) + { + Msg( "obj_tunnel_trigger: must set TeleportVelocity or TeleportDuration" ); + m_flTeleportDuration = tf_tunnel_time.GetFloat(); + } + + duration = MIN( duration, MAX_TUNNEL_DURATION ); + return duration; +} + +bool CObjectTunnelTrigger::KeepTunneling( TunnelPlayer *tunnel ) +{ + if ( !tunnel || ( tunnel->player == NULL ) ) + { + return false; + } + + float remaining = tunnel->teleporttime - gpGlobals->curtime + 0.5f; + remaining = MAX( 0.0f, remaining ); + + tunnel->iremaining = (int)( remaining ); + + if ( !tunnel->exitstarted ) + { + if ( gpGlobals->curtime > tunnel->fadeintime ) + { + tunnel->exitstarted = true; + // Fade user screen to black + color32 black = {0,0,0,255}; + UTIL_ScreenFade( tunnel->player, black, tunnel->fadetime, 0.0, FFADE_IN | FFADE_PURGE ); + + // Move to tunnel exit spot now that we're half-way through teleport + if ( m_hTunnelExit != NULL ) + { + tunnel->player->EnableControl( true ); + tunnel->player->RemoveEffects( EF_NODRAW ); + + // Change the player to non-solid before the teleport, so the physics system doesn't think he + // actually moved this distance: + int OriginalSolidFlags = tunnel->player->GetSolidFlags(); + tunnel->player->AddSolidFlags( FSOLID_NOT_SOLID); + + // Do a placement test to prevent the player from teleporting inside another player, the ground, or just to help + // prevent badly placed tunnels from causing stuck situations. + Vector vTarget = m_hTunnelExit->GetAbsOrigin(); + Vector vOriginal = vTarget; + + if ( !EntityPlacementTest( tunnel->player, vOriginal, vTarget, true ) ) + { + Warning("Couldn't place entity after tunnel teleport.\n"); + } + + + tunnel->player->Teleport( &vTarget /*m_hTunnelExit->GetAbsOrigin()*/, &m_hTunnelExit->GetAbsAngles(), NULL ); + tunnel->player->SnapEyeAngles( m_hTunnelExit->GetAbsAngles() ); + tunnel->player->SetAbsVelocity( vec3_origin ); + + // Restore the player's solid flags. + tunnel->player->SetSolidFlags(OriginalSolidFlags); + + } + } +// Can't quite do this because the player's weapons are still visible flying across the map even if +// he is hidden +#if 0 + else if ( gpGlobals->curtime > tunnel->tunnelstarted + tunnel->fadetime ) + { + float travel_time = tunnel->duration - 2 * tunnel->fadetime; + if ( travel_time > 0.0f ) + { + float f = ( gpGlobals->curtime - tunnel->tunnelstarted - tunnel->fadetime ) / travel_time; + f = clamp( f, 0.0f, 1.0f ); + if ( m_hTunnelExit != NULL ) + { + Vector delta = m_hTunnelExit->GetAbsOrigin() - tunnel->startpos; + Vector currentPos; + VectorMA( tunnel->startpos, f, delta, currentPos ); + + tunnel->player->Teleport( ¤tPos, NULL, NULL ); + } + } + } +#endif + } + + if ( tunnel->ilastremaining != tunnel->iremaining && + tunnel->needremainigcounter && + tunnel->iremaining >= 1 && + tunnel->player != NULL ) + { + // Counter + ClientPrint( tunnel->player, HUD_PRINTCENTER, UTIL_VarArgs("\nExiting tunnel in %d %s\n", tunnel->iremaining, tunnel->iremaining > 1 ? "seconds" : "second" ) ); + } + + tunnel->ilastremaining = tunnel->iremaining; + + // TODO: Play footstep or some other teleport sounds occasionaly to this player? + + bool done = ( gpGlobals->curtime > tunnel->teleporttime ) ? true : false; + if ( done ) + { + color32 black = {0,0,0,255}; + UTIL_ScreenFade( tunnel->player, black, 0.0f, 0.0f, FFADE_IN | FFADE_PURGE ); + + tunnel->player->SetMoveType( MOVETYPE_WALK ); + tunnel->player->EnableControl( true ); + tunnel->player->RemoveEffects( EF_NODRAW ); + tunnel->player->RemoveSolidFlags( FSOLID_NOT_SOLID ); + + // TODO: Play an exit sound?? + OnTunnelTriggerEnd.FireOutput( tunnel->player, this ); + } + + return !done; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CObjectTunnelTrigger::TunnelThink() +{ + // Make sure it's not already in the list + int c = m_Tunneling.Count(); + for ( int i = c - 1; i >= 0; i-- ) + { + TunnelPlayer *tp = &m_Tunneling[ i ]; + + if ( !KeepTunneling( tp ) ) + { + m_Tunneling.Remove( i ); + } + } + + SetNextThink( gpGlobals->curtime + TUNNEL_THINK_INTERVAL ); +} + +void CObjectTunnelTrigger::Precache() +{ + BaseClass::Precache(); + + PrecacheScriptSound( "ObjectTunnelTrigger.TeleportSound" ); + PrecacheScriptSound( "ObjectTunnelTrigger.DisabledSound" ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CObjectTunnelTrigger::Spawn() +{ + Precache(); + + SetSolid( SOLID_BSP ); + AddSolidFlags( FSOLID_TRIGGER ); + SetMoveType( MOVETYPE_NONE ); + AddEffects( EF_NODRAW ); + SetModel( STRING( GetModelName() ) ); + AddFlag( FL_NOTARGET ); + + m_bActive = false; +} + +//----------------------------------------------------------------------------- +// Purpose: See if we've got a gather point specified +//----------------------------------------------------------------------------- +void CObjectTunnelTrigger::Activate( void ) +{ + BaseClass::Activate(); + + if (m_target != NULL_STRING) + { + CBaseEntity *pEnt = gEntList.FindEntityByName( NULL, m_target ); + if ( pEnt && FClassnameIs( pEnt, "info_tunnel_exit" ) ) + { + m_hTunnelExit = static_cast< CInfoTunnelExit * >( pEnt ); + } + else + { + Msg( "CObjectTunnelTrigger::Activate, unable to connect tunnel to target %s\n", + STRING( m_target ) ); + } + } + else + { + Msg( "CObjectTunnelTrigger::Activate, missing target\n" ); + } + + SetActive( true ); + + SetThink( TunnelThink ); + SetNextThink( gpGlobals->curtime + TUNNEL_THINK_INTERVAL ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : active - +//----------------------------------------------------------------------------- +void CObjectTunnelTrigger::SetActive( bool active ) +{ + m_bActive = active; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CObjectTunnelTrigger::GetActive( void ) const +{ + return m_bActive; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CObjectTunnelTrigger::InputSetActive( inputdata_t &inputdata ) +{ + SetActive( true ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CObjectTunnelTrigger::InputSetInactive( inputdata_t &inputdata ) +{ + SetActive( false ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CObjectTunnelTrigger::InputToggleActive( inputdata_t &inputdata ) +{ + if ( m_bActive ) + { + SetActive( false ); + } + else + { + SetActive( true ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : &inputdata - +//----------------------------------------------------------------------------- +void CObjectTunnelTrigger::InputSetTarget( inputdata_t &inputdata ) +{ + CBaseEntity *pEnt = gEntList.FindEntityByName( NULL, inputdata.value.String() ); + if ( pEnt && FClassnameIs( pEnt, "info_tunnel_exit" ) ) + { + m_hTunnelExit = static_cast< CInfoTunnelExit * >( pEnt ); + } + else + { + Msg( "CObjectTunnelTrigger::InputSetTarget: Couldn't find info_tunnel_exit named %s\n", + inputdata.value.String() ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : &inputdata - +//----------------------------------------------------------------------------- +void CObjectTunnelTrigger::InputSetTeleportDuration( inputdata_t &inputdata ) +{ + m_flTeleportDuration = inputdata.value.Float(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : &inputdata - +//----------------------------------------------------------------------------- +void CObjectTunnelTrigger::InputSetTeleportVelocity( inputdata_t &inputdata ) +{ + m_flTeleportVelocity = inputdata.value.Float(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pOther - +//----------------------------------------------------------------------------- +void CObjectTunnelTrigger::StartTouch( CBaseEntity *pOther ) +{ + if ( !pOther || !pOther->IsPlayer() ) + return; + + // Only works for my team, of course + if ( !pOther->InSameTeam( this ) ) + return; + + if ( m_hTunnelExit == NULL ) + return; + + // It's been damaged to the point of being disabled + if ( !GetActive() ) + { + // Play a deny sound + CPASAttenuationFilter filter( pOther, "ObjectTunnelTrigger.DisabledSound" ); + EmitSound( filter, pOther->entindex(), "ObjectTunnelTrigger.DisabledSound" ); + return; + } + + StartTunneling( (CBaseTFPlayer *)pOther ); +} + +LINK_ENTITY_TO_CLASS(obj_tunnel_trigger,CObjectTunnelTrigger); + +BEGIN_DATADESC( CObjectTunnelTrigger ) + // inputs + DEFINE_INPUTFUNC( FIELD_VOID, "SetActive", InputSetActive ), + DEFINE_INPUTFUNC( FIELD_VOID, "SetInactive", InputSetInactive ), + DEFINE_INPUTFUNC( FIELD_VOID, "ToggleActive", InputToggleActive ), + + DEFINE_INPUTFUNC( FIELD_STRING, "SetTarget", InputSetTarget ), + DEFINE_INPUTFUNC( FIELD_FLOAT, "SetTeleportDuration", InputSetTeleportDuration ), + DEFINE_INPUTFUNC( FIELD_FLOAT, "SetTeleportVelocity", InputSetTeleportVelocity ), + + // outputs + DEFINE_OUTPUT( OnTunnelTriggerStart, "OnTunnelTriggerStart" ), + DEFINE_OUTPUT( OnTunnelTriggerEnd, "OnTunnelTriggerEnd" ), + + // keyvalues + DEFINE_KEYFIELD_NOT_SAVED( m_flTeleportDuration, FIELD_FLOAT, "TeleportDuration" ), + DEFINE_KEYFIELD_NOT_SAVED( m_flTeleportVelocity, FIELD_FLOAT, "TeleportVelocity" ), +END_DATADESC() diff --git a/game/server/tf2/tf_obj_vehicleboost.cpp b/game/server/tf2/tf_obj_vehicleboost.cpp new file mode 100644 index 0000000..c08bf88 --- /dev/null +++ b/game/server/tf2/tf_obj_vehicleboost.cpp @@ -0,0 +1,76 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Upgrade that boosts vehicle speeds for short periods of time. +// +//=============================================================================// + +#include "cbase.h" +#include "tf_player.h" +#include "tf_obj.h" +#include "tf_obj_vehicleboost.h" +#include "tf_basefourwheelvehicle.h" + +#define VEHICLE_BOOST_MINS Vector( -10, -10, 0 ) +#define VEHICLE_BOOST_MAXS Vector( 10, 10, 10 ) +#define VEHICLE_BOOST_MODEL "models/objects/obj_vehicle_boost.mdl" + +BEGIN_DATADESC( CObjectVehicleBoost ) +END_DATADESC() + +IMPLEMENT_SERVERCLASS_ST( CObjectVehicleBoost, DT_ObjectVehicleBoost ) +END_SEND_TABLE(); + +LINK_ENTITY_TO_CLASS( obj_vehicle_boost, CObjectVehicleBoost ); +PRECACHE_REGISTER( obj_vehicle_boost ); + +ConVar obj_vehicle_boost_health( "obj_vehicle_boost_health","100", FCVAR_NONE, "Vehicle Boost Health" ); + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CObjectVehicleBoost::CObjectVehicleBoost() +{ + UseClientSideAnimation(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CObjectVehicleBoost::Spawn( void ) +{ + Precache(); + SetModel( VEHICLE_BOOST_MODEL ); + SetCollisionGroup( TFCOLLISION_GROUP_COMBATOBJECT ); + + UTIL_SetSize(this, VEHICLE_BOOST_MINS, VEHICLE_BOOST_MAXS ); + m_takedamage = DAMAGE_YES; + m_iHealth = obj_vehicle_boost_health.GetInt(); + + SetType( OBJ_VEHICLE_BOOST ); + m_fObjectFlags |= OF_SUPPRESS_NOTIFY_UNDER_ATTACK | OF_SUPPRESS_TECH_ANALYZER | + OF_DONT_AUTO_REPAIR | OF_MUST_BE_BUILT_ON_ATTACHMENT; + + BaseClass::Spawn(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CObjectVehicleBoost::Precache( void ) +{ + PrecacheModel( VEHICLE_BOOST_MODEL ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CObjectVehicleBoost::FinishedBuilding( void ) +{ + BaseClass::FinishedBuilding(); + + CBaseTFFourWheelVehicle *pVehicle = dynamic_cast<CBaseTFFourWheelVehicle*>( GetParent() ); + if ( pVehicle ) + { + pVehicle->SetBoostUpgrade( true ); + } +} diff --git a/game/server/tf2/tf_obj_vehicleboost.h b/game/server/tf2/tf_obj_vehicleboost.h new file mode 100644 index 0000000..3caed65 --- /dev/null +++ b/game/server/tf2/tf_obj_vehicleboost.h @@ -0,0 +1,37 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Upgrade that boosts vehicle speeds for short periods of time. +// +//=============================================================================// + +#ifndef TF_OBJ_VEHICLEBOOST_H +#define TF_OBJ_VEHICLEBOOST_H +#ifdef _WIN32 +#pragma once +#endif + +#include "tf_obj_baseupgrade_shared.h" + +//============================================================================= +// +// Vehicle Boost Upgrade +// +class CObjectVehicleBoost : public CBaseObjectUpgrade +{ + + DECLARE_CLASS( CObjectVehicleBoost, CBaseObjectUpgrade ); + +public: + + DECLARE_DATADESC(); + DECLARE_SERVERCLASS(); + + CObjectVehicleBoost(); + + void Spawn( void ); + void Precache( void ); + bool CanTakeEMPDamage( void ) { return true; } + void FinishedBuilding( void ); +}; + +#endif // TF_OBJ_VEHICLEBOOST_H diff --git a/game/server/tf2/tf_player.cpp b/game/server/tf2/tf_player.cpp new file mode 100644 index 0000000..6631454 --- /dev/null +++ b/game/server/tf2/tf_player.cpp @@ -0,0 +1,3720 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: TF2's player object. +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" +#include <stdarg.h> +#include "player.h" +#include "tf_player.h" +#include "gamerules.h" +#include "trains.h" +#include "entitylist.h" +#include "menu_base.h" +#include "basecombatweapon.h" +#include "controlzone.h" +#include "tf_shareddefs.h" +#include "AmmoDef.h" +#include "techtree.h" +#include "in_buttons.h" +#include "tf_team.h" +#include "client.h" +#include "baseviewmodel.h" +#include "tf_gamerules.h" +#include "tf_obj.h" +#include "weapon_builder.h" +#include "orders.h" +#include "decals.h" +#include "tf_func_resource.h" +#include "resource_chunk.h" +#include "team_messages.h" +#include "tier0/dbg.h" +#include "tf_obj_respawn_station.h" +#include "tf_obj_resourcepump.h" +#include "tf_class_commando.h" +#include "tf_class_defender.h" +#include "tf_class_escort.h" +#include "tf_class_infiltrator.h" +#include "tf_class_medic.h" +#include "tf_class_recon.h" +#include "tf_class_sniper.h" +#include "tf_class_support.h" +#include "tf_class_sapper.h" +#include "sendproxy.h" +#include "ragdoll_shadow.h" +#include "vstdlib/random.h" +#include "engine/IEngineSound.h" +#include "bone_setup.h" +#include "weapon_combatshield.h" +#include "weapon_twohandedcontainer.h" +#include "NDebugOverlay.h" +#include "tier1/strtools.h" +#include "IEffects.h" +#include "info_act.h" +#include "ai_basehumanoid.h" +#include "tf_stats.h" +#include "iservervehicle.h" +#include "tf_vehicle_teleport_station.h" +#include "globals.h" + +#define MAX_EXPLOSIVE_VELOCITY 600.0f + +extern ConVar tf_knockdowntime; + +extern ConVar inv_demo; + +ConVar tf_autoteam( "tf_autoteam", "1", 0, "Automatically place players on the team with the least players." ); +ConVar tf_destroyobjects( "tf_destroyobjects", "1", FCVAR_CHEAT, "Destroy objects when players change class or team." ); + +IMPLEMENT_SERVERCLASS_ST(CBaseTFPlayer, DT_BaseTFPlayer) + SendPropDataTable(SENDINFO_DT(m_TFLocal), &REFERENCE_SEND_TABLE(DT_TFLocal), SendProxy_SendLocalDataTable), + + SendPropInt(SENDINFO(m_iPlayerClass), 4, SPROP_UNSIGNED), + + // Class Data Tables + SendPropDataTable( SENDINFO_DT( m_PlayerClasses ), &REFERENCE_SEND_TABLE( DT_AllPlayerClasses ), SendProxy_SendLocalDataTable ), + + SendPropEHandle( SENDINFO( m_hSelectedMCV ) ), + SendPropInt( SENDINFO(m_iCurrentZoneState ), 3 ), + SendPropInt( SENDINFO(m_iMaxHealth ), 8, SPROP_UNSIGNED ), + SendPropInt( SENDINFO(m_TFPlayerFlags), TF_PLAYER_NUMFLAGS, SPROP_UNSIGNED ), + SendPropInt( SENDINFO( m_bUnderAttack ), 1, SPROP_UNSIGNED ), + SendPropInt( SENDINFO( m_bIsBlocking ), 1, SPROP_UNSIGNED ), + + // Sniper - will get moved to a class data table + SendPropVector( SENDINFO(m_vecDeployedAngles), -1, SPROP_COORD ), + SendPropInt( SENDINFO( m_bDeployed ), 1, SPROP_UNSIGNED ), + SendPropInt( SENDINFO( m_bDeploying ), 1, SPROP_UNSIGNED ), + SendPropInt( SENDINFO( m_bUnDeploying ), 1, SPROP_UNSIGNED ), + + // Infiltrator - will get moved to a class data table + SendPropFloat( SENDINFO( m_flCamouflageAmount ), 7, SPROP_ROUNDDOWN, 0.0f, 100.0f ), + + SendPropEHandle(SENDINFO(m_hSpawnPoint)), + + SendPropExclude( "DT_BaseAnimating" , "m_flPoseParameter" ), + SendPropExclude( "DT_BaseAnimating" , "m_flPlaybackRate" ), + +END_SEND_TABLE() + +LINK_ENTITY_TO_CLASS( player, CBaseTFPlayer ); +PRECACHE_REGISTER(player); + +BEGIN_DATADESC( CBaseTFPlayer ) + + DEFINE_INPUTFUNC( FIELD_VOID, "Respawn", InputRespawn ), + + // Function Pointers + DEFINE_THINKFUNC( TFPlayerDeathThink ), + +END_DATADESC() + + +BEGIN_PREDICTION_DATA_NO_BASE( CTFPlayerLocalData ) +END_PREDICTION_DATA() + +BEGIN_PREDICTION_DATA( CBaseTFPlayer ) +END_PREDICTION_DATA() + + +bool IsSpawnPointValid( CBaseEntity *pPlayer, CBaseEntity *pSpot ); +void respawn( CBaseEntity *pEdict, bool fCopyCorpse ); +int TrainSpeed(int iSpeed, int iMax); +void BulletWizz( Vector vecSrc, Vector vecEndPos, edict_t *pShooter, bool isTracer ); + +extern float g_flNextReinforcementTime; +extern short g_sModelIndexFireball; +extern CBaseEntity *g_pLastSpawn; + +//----------------------------------------------------------------------------- +// Purpose: Don't do anything for now +// Input : *pFormat - +// ... - +// Output : static void +//----------------------------------------------------------------------------- +void StatusPrintf( bool clear, int destination, char *pFormat, ... ) +{ + return; + + /* + va_list marker; + char msg[8192]; + + va_start(marker, pFormat); + Q_vsnprintf(msg, sizeof( msg ), pFormat, marker); + va_end(marker); + + Msg( msg ); + */ +} + +#pragma warning( disable : 4355 ) + +//===================================================================== +// PLAYER HANDLING +//===================================================================== +CBaseTFPlayer::CBaseTFPlayer() : + m_PlayerClasses( this ), m_PlayerAnimState( this ) +{ + // HACK because player's have pev set in baseclass constructor + // which triggers an assert that we want to keep. + { + edict_t *savepev = edict(); + NetworkProp()->SetEdict( NULL ); + UseClientSideAnimation(); + NetworkProp()->SetEdict( savepev ); + } + + m_bWasMoving = false; + + m_iLastSecondsToGo = -1; + m_TFLocal.m_nInTacticalView = 0; + m_TFLocal.m_pPlayer = this; + m_bSwitchingView = false; + ClearActiveWeapon(); + + m_iPlayerClass = TFCLASS_UNDECIDED; + SetPlayerClass( TFCLASS_UNDECIDED ); + m_pCurrentMenu = NULL; + m_TFPlayerFlags = 0; + m_bDeploying = false; + m_bDeployed = false; + m_bUnDeploying = false; + m_flFinishedDeploying = 0; + SetOrder( NULL ); + + m_nPreferredTechnology = -1; + m_nMedicDamageBoosts = 0; + + m_hSpawnPoint = NULL; + m_flLastTimeDamagedByEnemy = -1000; + + int i; + for ( i = 0; i < MOMENTUM_MAXSIZE; i++ ) + { + m_aMomentum[ i ] = 1.0f; + } +} + +void CBaseTFPlayer::UpdateOnRemove( void ) +{ + if ( m_hSelectedOrder ) + { + GetTFTeam()->RemoveOrdersToPlayer( this ); + Assert( !m_hSelectedOrder.Get() ); + } + + ClearPlayerClass(); + + ClearClientRagdoll( false ); + + // Chain at end to mimic destructor unwind order + BaseClass::UpdateOnRemove(); +} + +CBaseTFPlayer::~CBaseTFPlayer() +{ + SetPlayerClass( (TFClass)-1 ); +} + +bool CBaseTFPlayer::IsHidden() const +{ + return (m_TFPlayerFlags & TF_PLAYER_HIDDEN) != 0; +} + +void CBaseTFPlayer::SetHidden( bool bHidden ) +{ + if ( bHidden ) + m_TFPlayerFlags |= TF_PLAYER_HIDDEN; + else + m_TFPlayerFlags &= ~TF_PLAYER_HIDDEN; +} + +//----------------------------------------------------------------------------- +// Purpose: Called everytime the player's respawned +//----------------------------------------------------------------------------- +void CBaseTFPlayer::Spawn( void ) +{ + m_bUnderAttack = false; + m_pCurrentZone = NULL; + ClearClientRagdoll( false ); + + g_pNotify->ReportNamedEvent( this, "PlayerSpawned" ); + + DeactivateMovementConstraint(); + + if ( IsInAVehicle() ) + { + LeaveVehicle(); + } + + // If the player doesn't have a spawn station set, find one + if ( m_hSpawnPoint == NULL || !InSameTeam( m_hSpawnPoint ) ) + { + m_hSpawnPoint = GetInitialSpawnPoint(); + } + + if ( inv_demo.GetBool() ) + { + if ( !GetPlayerClass() ) + { + ChangeClass( TFCLASS_MEDIC ); + m_Local.m_iHideHUD |= HIDEHUD_MISCSTATUS; + engine->ServerCommand("r_DispEnableLOD 0\n"); + } + } + + // Must be done before baseclass spawn, so it's correct for when we find a spawnpoint + if ( GetPlayerClass() ) + { + GetPlayerClass()->SetPlayerHull(); + } + + // Use human commando model until we know our class + SetModel( "models/player/human_commando.mdl" ); + + BaseClass::Spawn(); + + m_flFractionalBoost = 0.0f; + + // Create second view model ( for support/commando, etc ) + CreateViewModel( 1 ); + + // Tell the PlayerClass that this player's just respawned + if ( GetPlayerClass() ) + { + RemoveFlag( FL_NOTARGET ); + RemoveSolidFlags( FSOLID_NOT_SOLID ); + + GetPlayerClass()->RespawnClass(); + if ( GetActiveWeapon() ) + { + // Holster weapon immediately, to allow it to cleanup +// GetActiveWeapon()->Holster( ); // NJS: test + + if (GetActiveWeapon()->HasAnyAmmo()) + { + Weapon_Switch( GetActiveWeapon() ); + } + else + { + SwitchToNextBestWeapon( GetActiveWeapon() ); + } + } + else + { + SwitchToNextBestWeapon( NULL ); + } + + SetPlayerModel(); + + // Make sure they're not deployed + FinishUnDeploying(); + + // Remove my personal orders + if ( GetTFTeam() ) + { + GetTFTeam()->RemoveOrdersToPlayer( this ); + } + + RemoveAllDecals(); + } + else + { + // No class? can't target this dude + AddFlag( FL_NOTARGET ); + + // Remove everything + RemoveAllItems( false ); + + // Set/unset m_bHidden instead to hide the tf player + SetHidden( true ); + + AddSolidFlags( FSOLID_NOT_SOLID ); + SetMoveType( MOVETYPE_NONE ); + + SetModel( "models/player/human_commando.mdl" ); + + // If they're not in a team, bring up the Team Menu + if ( !IsInAnyTeam() ) + { + if ( tf_autoteam.GetFloat() ) + { + // Autoteam the player + PlacePlayerInTeam(); + ForceRespawn(); + } + else + { + // Let players choose their team + m_pCurrentMenu = gMenus[MENU_TEAM]; + } + } + else // Bring up the Class Menu + { + m_pCurrentMenu = gMenus[MENU_CLASS]; + } + + m_MenuRefreshTime = gpGlobals->curtime; + + m_nPreferredTechnology = -1; + } + + SetCantMove( false ); + + + m_TFLocal.m_nInTacticalView = 0; + m_flLastTimeDamagedByEnemy = -1000; + + // Purge resource chunks + for ( int i=0; i < m_TFLocal.m_iResourceAmmo.Count(); i++ ) + m_TFLocal.m_iResourceAmmo.Set( i, 0 ); + + ResetKnockdown(); + SetGagged( false ); + SetUsingThermalVision( false ); + ClearCamouflage(); + SetIDEnt( NULL ); + m_iPowerups = 0; + + // MUST set the right player hull before placing the player somewhere. + if ( GetPlayerClass() ) + GetPlayerClass()->SetPlayerHull(); + + g_pGameRules->GetPlayerSpawnSpot( this ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseTFPlayer::CleanupOnActStart( void ) +{ + // Tell all our weapons + for ( int i = 0; i < WeaponCount(); i++ ) + { + if ( GetWeapon(i) ) + { + ((CBaseTFCombatWeapon*)GetWeapon(i))->CleanupOnActStart(); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseTFPlayer::RecalculateSpeed( void ) +{ + if ( GetPlayerClass() ) + { + GetPlayerClass()->SetMaxSpeed( GetPlayerClass()->GetMaxSpeed() ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: I just killed another player +//----------------------------------------------------------------------------- +void CBaseTFPlayer::KilledPlayer( CBaseTFPlayer *pVictim ) +{ + TFStats()->IncrementPlayerStat( this, TF_PLAYER_STAT_KILL_COUNT, 1 ); + TFStats()->IncrementTeamStat( GetTeamNumber(), TF_TEAM_STAT_KILL_COUNT, 1 ); + + // Am I in a rampage? + if ( HasPowerup( POWERUP_RUSH ) && IsInRampage() ) + { + // Extend my rush + AttemptToPowerup( POWERUP_RUSH, ADRENALIN_RAMPAGE_EXTEND ); + + // Let 'em know + EmitSound( "BaseTFPlayer.BloodSportKiller" ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Called only the first time a player's placed in the map +//----------------------------------------------------------------------------- +void CBaseTFPlayer::InitialSpawn( void ) +{ + BaseClass::InitialSpawn(); + SetWeaponBuilder( NULL ); + + m_bFirstTeamSpawn = true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseTFPlayer::Precache( void ) +{ + //!! hack for radar + BaseClass::Precache(); + + PrecacheScriptSound( "BaseTFPlayer.BloodSportKiller" ); + PrecacheScriptSound( "Humans.Death" ); + PrecacheScriptSound( "AlienCommando.Death" ); + PrecacheScriptSound( "AlienMedic.Death" ); + PrecacheScriptSound( "AlienDefender.Death" ); + PrecacheScriptSound( "AlienEscort.Death" ); + PrecacheScriptSound( "BaseTFPlayer.StartDeploying" ); + PrecacheScriptSound( "BaseTFPlayer.StartUnDeploying" ); + PrecacheScriptSound( "BaseTFPlayer.KnockedDown" ); + PrecacheScriptSound( "BaseTFPlayer.ThermalOn" ); + PrecacheScriptSound( "BaseTFPlayer.ThermalOff" ); + PrecacheScriptSound( "BaseTFPlayer.PickupResources" ); + PrecacheScriptSound( "BaseTFPlayer.DonateResources" ); + + // Class specific sounds + PrecacheScriptSound( "Commando.BootHit" ); + PrecacheScriptSound( "Commando.BootSwing" ); + PrecacheScriptSound( "Commando.BullRushScream" ); + PrecacheScriptSound( "Commando.BullRushFlesh" ); + +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseTFPlayer::UpdateClientData( void ) +{ + CTeam *pTeam = GetTeam(); + if ( pTeam ) + pTeam->UpdateClientData( this ); + + BaseClass::UpdateClientData(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseTFPlayer::ForceClientDllUpdate( void ) +{ + BaseClass::ForceClientDllUpdate(); + + // Force any active menu to be reset + m_MenuRefreshTime = 0; +} + +//----------------------------------------------------------------------------- +// Purpose: Forces an immediate respawn of the player +//----------------------------------------------------------------------------- +void CBaseTFPlayer::ForceRespawn( void ) +{ + Spawn(); +} + + +//----------------------------------------------------------------------------- +// Purpose: Input handler that forces a respawn of the player. +//----------------------------------------------------------------------------- +void CBaseTFPlayer::InputRespawn( inputdata_t &inputdata ) +{ + ForceRespawn(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseTFPlayer::InitHUD( void ) +{ + CSingleUserRecipientFilter user( this ); + user.MakeReliable(); + + // If we're in an act, tell it to update the client + if ( g_hCurrentAct ) + { + g_hCurrentAct->UpdateClient( this ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Player has just tried to switch to a new weapon +//----------------------------------------------------------------------------- +void CBaseTFPlayer::SelectItem( const char *pstr, int iSubType ) +{ + // can't change weapon while deployed + if ( IsPlayerLockedInPlace() || IsDeployed() || IsDeploying() ) + return; + + // Pass through to CBaseCombatWeapon code + BaseClass::SelectItem( pstr, iSubType ); +} + +//----------------------------------------------------------------------------- +// Purpose: Put the player in the specified team +//----------------------------------------------------------------------------- +void CBaseTFPlayer::ChangeTeam( int iTeamNum ) +{ + // If we're changing team, clear my order + if ( iTeamNum != GetTeamNumber() ) + { + SetOrder(NULL); + if ( tf_destroyobjects.GetFloat() ) + { + RemoveAllObjects( false ); + } + } + + // Force full tech tree update + for ( int i = 0 ; i < MAX_TECHNOLOGIES; i++ ) + { + m_rgClientTechAvail[ i ].m_nAvailable = -1; + } + + BaseClass::ChangeTeam( iTeamNum ); + + // Now handle resources: + // - If it's the first spawn ever, give the player the team's currently calculated resource amount + // - If the player has more resources than the team's joining amount, drop his resources to that amount. Otherwise, he can keep his current. + if ( GetGlobalTFTeam( iTeamNum ) ) + { + float flJoiningResources = GetGlobalTFTeam( iTeamNum )->GetJoiningPlayerResources(); + if ( m_bFirstTeamSpawn ) + { + m_bFirstTeamSpawn = false; + SetBankResources( flJoiningResources ); + } + else + { + if ( flJoiningResources < GetBankResources() ) + { + SetBankResources( flJoiningResources ); + } + } + } + + // Clear the client ragdoll, when changing teams. + ClearClientRagdoll( false ); +} + +//----------------------------------------------------------------------------- +// Purpose: Automatically place the player in the most appropriate team +//----------------------------------------------------------------------------- +void CBaseTFPlayer::PlacePlayerInTeam( void ) +{ + CTFTeam *pTargetTeam = NULL; + + // Find the team with the least players in it + for ( int i = 0; i < MAX_TF_TEAMS; i++ ) + { + CTFTeam *pTeam = GetGlobalTFTeam(i); + + if ( pTargetTeam ) + { + if ( pTeam->GetNumPlayers() < pTargetTeam->GetNumPlayers() ) + pTargetTeam = pTeam; + } + else + { + pTargetTeam = pTeam; + } + } + + ChangeTeam( pTargetTeam->GetTeamNumber() ); +} + +//----------------------------------------------------------------------------- +// Purpose: Return true if the specified class is available to this player +//----------------------------------------------------------------------------- +bool CBaseTFPlayer::IsClassAvailable( TFClass iClass ) +{ + char str[128]; + Q_snprintf( str, sizeof( str ), "class_%s", GetTFClassInfo( iClass )->m_pClassName ); + return HasNamedTechnology( str ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseTFPlayer::ChangeClass( TFClass iClass ) +{ + // If they've got a playerclass, kill it + if ( GetPlayerClass() ) + { + if ( tf_destroyobjects.GetFloat() ) + { + RemoveAllObjects( false, iClass ); + } + + ClearPlayerClass(); + } + + // can't change class if we have no team + if ( !IsInAnyTeam() ) + return; + + // Make sure client .dll can find out about it. + SetPlayerClass( iClass ); + + // Clear out current vote.... + CTFTeam *pTFTeam = GetTFTeam(); + SetPreferredTechnology( pTFTeam->m_pTechnologyTree, -1 ); + + // Force a respawn if they're alive + if ( IsAlive() ) + { + ForceRespawn(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Reset player class +//----------------------------------------------------------------------------- +void CBaseTFPlayer::ClearPlayerClass( void ) +{ + // Remove all weapons & items + if ( GetPlayerClass() ) + { + RemoveAllItems( false ); + m_hWeaponCombatShield = NULL; + } + + m_iPowerups = 0; + SetPlayerClass( TFCLASS_UNDECIDED ); +} + +//----------------------------------------------------------------------------- +// Purpose: Set the player's model to the correct one, taking into account +// class, gender, team, and disguise. +//----------------------------------------------------------------------------- +void CBaseTFPlayer::SetPlayerModel( void ) +{ + if (!GetPlayerClass()) + { + SetHidden( true ); + return; + } + + string_t sModel = GetPlayerClass()->GetClassModel( GetTeamNumber() ); + + // If they don't have a model, make the player invisible + if ( !sModel ) + { + SetHidden( true ); + return; + } + + // Make the player visible + SetHidden( false ); + + // Set the model + SetModel( STRING( sModel ) ); + + if ( GetFlags() & FL_DUCKING ) + UTIL_SetSize(this, VEC_DUCK_HULL_MIN, VEC_DUCK_HULL_MAX); + else + UTIL_SetSize(this, VEC_HULL_MIN, VEC_HULL_MAX); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseTFPlayer::PlayerRespawn( void ) +{ + m_nButtons = 0; + m_iRespawnFrames = 0; + + // don't copy a corpse if we're in deathcam. + respawn( this, !IsObserver() ); + SetThink( NULL ); +} + +//----------------------------------------------------------------------------- +// Purpose: Play a sound when we die +//----------------------------------------------------------------------------- +void CBaseTFPlayer::DeathSound( const CTakeDamageInfo &info ) +{ + if ( GetTeamNumber() == TEAM_HUMANS ) + { + EmitSound( "Humans.Death" ); + } + else if ( GetTeamNumber() == TEAM_ALIENS ) + { + switch( PlayerClass() ) + { + case TFCLASS_COMMANDO: + EmitSound( "AlienCommando.Death" ); + break; + + case TFCLASS_MEDIC: + EmitSound( "AlienMedic.Death" ); + break; + + case TFCLASS_DEFENDER: + EmitSound( "AlienDefender.Death" ); + break; + + case TFCLASS_ESCORT: + EmitSound( "AlienEscort.Death" ); + break; + + default: + break; + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseTFPlayer::ItemPostFrame() +{ + // Don't process items while in a vehicle. + if ( IsInAVehicle() ) + { + IServerVehicle *pVehicle = GetVehicle(); + Assert( pVehicle ); + + // NOTE: We *have* to do this before ItemPostFrame because ItemPostFrame + // may dump us out of the vehicle + int nRole = pVehicle->GetPassengerRole( this ); + bool bUsingStandardWeapons = pVehicle->IsPassengerUsingStandardWeapons( nRole ); + + pVehicle->ItemPostFrame( this ); + + // Fall through and check weapons, etc. if we're using them + if (!bUsingStandardWeapons || !IsInAVehicle()) + return; + } + + // If we're attaching a sapper, handle player use only + if ( m_TFLocal.m_bAttachingSapper ) + { + PlayerUse(); + return; + } + + BaseClass::ItemPostFrame(); + + if ( GetPlayerClass() ) + { + GetPlayerClass()->ItemPostFrame(); // Let the player class handle it. + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseTFPlayer::Jump( void ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseTFPlayer::PreThink(void) +{ + CheckBuffs(); + + // Riding a vehicle? + if ( IsInAVehicle() ) + { + BaseClass::PreThink(); + return; + } + + CheckDeployFinish(); + CheckKnockdown(); + CheckCamouflage(); + CheckSapperAttaching(); + + // Update reinforcement state + if (m_lifeState >= LIFE_DYING) + { + // After 3 seconds, move them to the Tactical Map + if ( (gpGlobals->curtime - m_flTimeOfDeath) > 3.0 ) + { + if ( m_TFLocal.m_nInTacticalView == false ) + { + ShowTacticalView( 1 ); + } + } + + // ROBIN: Maps will define whether or not teams reinforce + /* + // Aliens respawn in waves + if ( GetTeamNumber() == TEAM_ALIENS ) + { + int iSecondsToGo = (int)(g_flNextReinforcementTime - gpGlobals->curtime); + if ( iSecondsToGo != m_iLastSecondsToGo && iSecondsToGo >= 1 ) + { + m_iLastSecondsToGo = iSecondsToGo; + ClientPrint( this, HUD_PRINTCENTER, UTIL_VarArgs("\nReinforcing in %d %s\n", iSecondsToGo, iSecondsToGo > 1 ? "seconds" : "second" ) ); + } + } + */ + + TFPlayerDeathThink(); + } + + // Update zone state + if ( m_pCurrentZone ) + { + m_iCurrentZoneState = m_pCurrentZone->GetControllingTeam(); + if ( m_iCurrentZoneState != ZONE_CONTESTED ) + { + // Set the Zone state to the correct one + if ( m_iCurrentZoneState == GetTeamNumber() ) + m_iCurrentZoneState = ZONE_FRIENDLY; + else + m_iCurrentZoneState = ZONE_ENEMY; + } + } + else + { + m_iCurrentZoneState = 0; + } + + BaseClass::PreThink(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseTFPlayer::PostThink() +{ + BaseClass::PostThink(); + + // Make sure we have a valid MCV id. + CVehicleTeleportStation *pMCV = GetSelectedMCV(); + if ( !pMCV || !pMCV->IsDeployed() ) + { + m_hSelectedMCV = CVehicleTeleportStation::GetFirstDeployedMCV( GetTeamNumber() ); + } + + // Tell the client if our damage is boosted so it can do a smurfy effect on the weapon. + if ( GetAttackDamageScale( NULL ) == 1 ) + m_TFPlayerFlags &= ~TF_PLAYER_DAMAGE_BOOST; + else + m_TFPlayerFlags |= TF_PLAYER_DAMAGE_BOOST; + + m_PlayerAnimState.Update(); +// SetLocalAngles( m_PlayerAnimState.GetRenderAngles() ); + + float flTimeSinceAttacked = gpGlobals->curtime - LastTimeDamagedByEnemy(); + m_bUnderAttack = ((flTimeSinceAttacked >= 0.0f) && (flTimeSinceAttacked < 1.0f)); + + // TODO: This collision hull is set in the base class PostThink (so this is + // redundant), but I don't wanna re-write the whole thing at this point. + // We will just have to deal with a little redundancy for now. + if ( GetPlayerClass() ) + { + GetPlayerClass()->SetPlayerHull(); + } + + // Menus + MenuDisplay(); + + // Player class Think + if (GetPlayerClass()) + { + GetPlayerClass()->ClassThink(); + } + + if ( m_bSwitchingView ) + { + m_bSwitchingView = false; + SetMoveType( m_TFLocal.m_nInTacticalView ? MOVETYPE_ISOMETRIC : MOVETYPE_WALK ); + } + + FollowClientRagdoll(); +} + +//----------------------------------------------------------------------------- +// Purpose: selects a valid point that the player can spawn at +// Output : edict_t - the point in the world to spawn at +//----------------------------------------------------------------------------- +CBaseEntity *CBaseTFPlayer::EntSelectSpawnPoint( void ) +{ + // If we're in a team, ask the team for a spawnpoint + if ( GetTeam() ) + { + CBaseEntity *entity = NULL; + if ( GetPlayerClass() ) + { + // Let individual player classes override the respawn point + entity = GetPlayerClass()->SelectSpawnPoint(); + if ( entity ) + { + return entity; + } + + // Do we have a selected spawn point (from a respawn station)? + entity = m_hSpawnPoint; + if (entity && (entity->GetTeam() == GetTeam())) + { + PlayRespawnEffect( entity ); + return entity; + } + } + + entity = GetTeam()->SpawnPlayer( this ); + if ( entity ) + return entity; + } + + // If we're not in a team, or the team didn't have a spawnpoint for us, + // fall back to the basic spawnpoint code. + return BaseClass::EntSelectSpawnPoint(); +} + +void CBaseTFPlayer::RemoveShieldOverlays( void ) +{ + RemoveGesture( ACT_OVERLAY_SHIELD_UP ); + RemoveGesture( ACT_OVERLAY_SHIELD_DOWN ); + RemoveGesture( ACT_OVERLAY_SHIELD_UP_IDLE ); + RemoveGesture( ACT_OVERLAY_SHIELD_ATTACK ); + RemoveGesture( ACT_OVERLAY_SHIELD_KNOCKBACK ); +} + +static bool IsShieldOverlay( Activity activity ) +{ + switch ( activity ) + { + default: + return false; + case ACT_OVERLAY_SHIELD_UP: + case ACT_OVERLAY_SHIELD_DOWN: + case ACT_OVERLAY_SHIELD_UP_IDLE: + case ACT_OVERLAY_SHIELD_ATTACK: + case ACT_OVERLAY_SHIELD_KNOCKBACK: + return true; + } + return false; +} + +int CBaseTFPlayer::RemoveShieldOverlaysExcept( Activity activity, bool addifnotpresent /*= true */ ) +{ + int skip = FindGestureLayer( activity ); + + int i; + for ( i = 0; i < CBaseAnimatingOverlay::MAX_OVERLAYS; i++ ) + { + if ( i == skip ) + continue; + + if ( IsShieldOverlay( GetLayerActivity( i ) ) ) + { + RemoveLayer( i, 0.0, 0.0f ); + } + } + + // Add it in if it's not present already + if ( addifnotpresent && ( skip == -1 ) ) + { + return AddGesture( activity ); + } + else + { + return skip; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : activity - i/o : can be changed to a new activity +// overlayindex - o: if an overlay is picked, this gets changed +// Rest of paramters are input only +// moving - is player moving +// ducked - is player ducking +// overlay - animation choices for this state (either full body crouch/stand, or overlay on top of base crouch/stand ) +// crouch - +// normal - +// Overlay parameters +// autokill - if false, overlay will loop indefinitely +// blendin - amount of time over which to blend in (0.0f for snap) +// blendout - same but for blending out instead +//----------------------------------------------------------------------------- +void CBaseTFPlayer::PickShieldAnimation( Activity& activity, int& overlayindex, bool moving, bool ducked, + Activity overlay, Activity crouch, Activity normal, + bool autokill /*=true*/, float blendin /*=0.0f*/, float blendout /*=0.0f*/ ) +{ + if ( moving ) + { + overlayindex = RemoveShieldOverlaysExcept( overlay ); + if ( overlayindex != -1 ) + { + if ( blendin > 0.0f ) + { + SetLayerBlendIn( overlayindex, blendin ); + } + + if ( blendout > 0.0f ) + { + SetLayerBlendOut( overlayindex, blendout ); + } + + if ( !autokill ) + { + SetLayerAutokill( overlayindex, false ); + } + } + } + else + { + activity = ducked ? crouch : normal; + } +} + +Activity CBaseTFPlayer::ShieldTranslateActivity( Activity activity ) +{ + CWeaponTwoHandedContainer *container = dynamic_cast< CWeaponTwoHandedContainer * >( GetActiveWeapon() ); + if ( !container ) + return activity; + + CWeaponCombatShield *pShield = dynamic_cast< CWeaponCombatShield * >( container->GetLeftWeapon() ); + if ( !pShield ) + { + pShield = dynamic_cast< CWeaponCombatShield * >( container->GetRightWeapon() ); + if ( !pShield ) + { + return activity; + } + } + + float speed = GetAbsVelocity().Length2D(); + bool isMoving = speed != 0 ? true : false; + //bool isRunning = speed > 75 ? true : false; + bool isDucked = ( GetFlags() & FL_DUCKING ) ? true : false; + + int shieldState = pShield->GetShieldState(); + + float startframe = 0.0f; + + bool movechanged = isMoving ^ m_bWasMoving; + if ( movechanged) + { + // Grab frame from overlay + if ( !isMoving ) + { + for ( int i = 0; i < MAX_OVERLAYS; i++ ) + { + if ( IsShieldOverlay( GetLayerActivity( i ) ) ) + { + startframe = GetLayerCycle( i ); + } + } + + RemoveShieldOverlays(); + } + else + { + switch ( GetActivity() ) + { + case ACT_SHIELD_UP: + case ACT_SHIELD_DOWN: + case ACT_SHIELD_UP_IDLE: + case ACT_SHIELD_ATTACK: + //case ACT_SHIELD_KNOCKBACK: + case ACT_CROUCHING_SHIELD_UP: + case ACT_CROUCHING_SHIELD_DOWN: + case ACT_CROUCHING_SHIELD_UP_IDLE: + case ACT_CROUCHING_SHIELD_ATTACK: + //case ACT_CROUCHING_SHIELD_KNOCKBACK: + startframe = GetCycle(); + break; + default: + break; + } + } + } + + // Asume we should fix up animation based on move/stationary state change + bool fixup = true; + // Assume no overlay + int idx = -1; + + switch ( shieldState ) + { + default: + case SS_DOWN: + case SS_UNAVAILABLE: + RemoveShieldOverlays(); + // By default, remove shield overlays and don't do fixup + fixup = false; + break; + case SS_LOWERING: + { + PickShieldAnimation( activity, idx, isMoving, isDucked, + ACT_OVERLAY_SHIELD_DOWN, ACT_CROUCHING_SHIELD_DOWN, ACT_SHIELD_DOWN, + true, 0.0f, 0.2f ); + } + break; + case SS_RAISING: + { + PickShieldAnimation( activity, idx, isMoving, isDucked, + ACT_OVERLAY_SHIELD_UP, ACT_CROUCHING_SHIELD_UP, ACT_SHIELD_UP, + true, 0.2f, 0.0f ); + } + break; + case SS_UP: + { + PickShieldAnimation( activity, idx, isMoving, isDucked, + ACT_OVERLAY_SHIELD_UP_IDLE, ACT_CROUCHING_SHIELD_UP_IDLE, ACT_SHIELD_UP_IDLE, + false ); + } + break; + case SS_PARRYING: + { + PickShieldAnimation( activity, idx, isMoving, isDucked, + ACT_OVERLAY_SHIELD_ATTACK, ACT_CROUCHING_SHIELD_ATTACK, ACT_SHIELD_ATTACK, + true, 0.1f, 0.1f ); + } + break; + } + + // If started or stopped moving and still using shield, match the cycle to/from the overlay/base animation + // being used beforehand + if ( movechanged && fixup ) + { + // Fixup overlay frame + if ( idx != -1 ) + { + SetLayerCycle( idx, startframe ); + } + else + { + // Force animation blend + ResetSequenceInfo(); + + // Match start frame + SetCycle( startframe ); + } + } + + // Remember previous state + m_bWasMoving = isMoving; + + // Return translated activity + return activity; +} + +void CBaseTFPlayer::StoreCycle( void ) +{ + m_flStoredCycle = GetCycle(); // !!!!! +} + +float CBaseTFPlayer::RetrieveCycle( void ) +{ + return m_flStoredCycle; +} + +//----------------------------------------------------------------------------- +// Purpose: Certain activities have matched cycles +// Input : newActivity - +// currentActivity - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CBaseTFPlayer::ShouldMatchCycles( Activity newActivity, Activity currentActivity ) +{ + if ( ( newActivity == ACT_WALK || newActivity == ACT_RUN ) && + ( currentActivity == ACT_WALK || currentActivity == ACT_RUN ) ) + { + // Don't blend either + IncrementInterpolationFrame(); + return true; + } + return false; +} + +#define ARBITRARY_RUN_SPEED 75.0f + +//----------------------------------------------------------------------------- +// Purpose: Set the activity based on an event or current state +//----------------------------------------------------------------------------- +void CBaseTFPlayer::SetAnimation( PLAYER_ANIM playerAnim ) +{ + // Assume no change + Activity idealActivity = GetActivity(); + int animDesired = GetSequence(); + + float speed = GetAbsVelocity().Length2D(); + + bool isMoving = ( speed != 0.0f ) ? true : false; + bool isRunning = false; + + if ( GetPlayerClass() ) + { +// FIXME: TF2 makes no distinction between walking and running for now, +// use the run animation always + if ( speed > 10.0f ) + { + isRunning = true; + } + } + else + { + if ( speed > ARBITRARY_RUN_SPEED ) + { + isRunning = true; + } + } + + bool isDucked = ( GetFlags() & FL_DUCKING ) ? true : false; + bool isStillJumping = !( GetFlags() & FL_ONGROUND ) && ( GetActivity() == ACT_HOP ); + + StoreCycle(); + + // Decide upon an animation activity based upon the desired Player animation + switch ( playerAnim ) + { + default: + case PLAYER_RELOAD: + case PLAYER_ATTACK1: + case PLAYER_IDLE: + case PLAYER_WALK: + // Are we still jumping? + // If so, keep playing the jump animation. + if ( !isStillJumping ) + { + idealActivity = ACT_WALK; + + if ( isDucked ) + { + idealActivity = !isMoving ? ACT_CROUCHIDLE : ACT_CROUCH; + } + else + { + + if ( isRunning ) + { + idealActivity = ACT_RUN; + } + else + { + idealActivity = isMoving ? ACT_WALK : ACT_IDLE; + } + } + + // Allow shield to override + idealActivity = ShieldTranslateActivity( idealActivity ); + // Allow body yaw to override for standing and turning in place + idealActivity = m_PlayerAnimState.BodyYawTranslateActivity( idealActivity ); + } + break; + + case PLAYER_IN_VEHICLE: + // For now, use manned gun pose for all vehicles + idealActivity = ACT_RIDE_MANNED_GUN; + break; + + case PLAYER_JUMP: + idealActivity = ACT_HOP; + break; + + case PLAYER_DIE: + // Uses Ragdoll now??? + idealActivity = ACT_DIESIMPLE; + break; + + // FIXME: Use overlays for reload, start/leave aiming, attacking + case PLAYER_START_AIMING: + case PLAYER_LEAVE_AIMING: + idealActivity = ACT_WALK; + break; + } + + // No change requested? + if ( ( GetActivity() == idealActivity ) && ( GetSequence() != -1 ) ) + return; + + bool useStoredCycle = ShouldMatchCycles( idealActivity, GetActivity() ); + + animDesired = SelectWeightedSequence( idealActivity ); + + SetActivity( idealActivity ); + + // Already using the desired animation? + if ( GetSequence() == animDesired ) + return; + + ResetSequence( animDesired ); + + // Reset to first frame of desired animation or match previous animation if activities are + // meant to synchronize + SetCycle( useStoredCycle ? RetrieveCycle() : 0 ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseTFPlayer::CheatImpulseCommands( int iImpulse ) +{ + switch(iImpulse) + { + case 101: + if ( GetPlayerClass() ) + { + GetPlayerClass()->ResupplyAmmo( 1.0f, RESUPPLY_ALL_FROM_STATION ); + } + break; + + case 150: + if ( GetTFTeam() ) + GetTFTeam()->PostMessage( TEAMMSG_REINFORCEMENTS_ARRIVED ); + break; + + default: + BaseClass::CheatImpulseCommands(iImpulse); + break; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseTFPlayer::SetRespawnStation( CBaseEntity* pRespawnStation ) +{ + // This can happen because the object may get killed and its index reused + // between time the message was sent and the + if( !pRespawnStation || !FClassnameIs( pRespawnStation, "obj_respawn_station" ) ) + return; + + // Team could have changed (stolen object) + if ( GetTeam() != pRespawnStation->GetTeam() ) + return; + + // If the respawn station is the same one, then unselect! + if ( pRespawnStation != m_hSpawnPoint ) + { + // Make sure the respawn station is a respawn station; it could be some + m_hSpawnPoint = pRespawnStation; + } + else + { + m_hSpawnPoint = 0; + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Find a starting respawn station +//----------------------------------------------------------------------------- +CBaseEntity *CBaseTFPlayer::GetInitialSpawnPoint( void ) +{ + if ( !GetTFTeam() ) + return NULL; + + CBaseEntity *pFirstStation = NULL; + + // Cycle through all the respawn stations on my team + for ( int i = 0; i < GetTFTeam()->GetNumObjects(); i++ ) + { + CBaseObject *pObject = GetTFTeam()->GetObject(i); + if ( pObject->GetType() == OBJ_RESPAWN_STATION ) + { + // Store off the first station we find + if ( !pFirstStation ) + { + pFirstStation = pObject; + } + + // Map specified initial spawnpoint? + if ( ((CObjectRespawnStation*)pObject)->IsInitialSpawnPoint() ) + return pObject; + } + } + + return pFirstStation; +} + + + +CBaseEntity *FindEntityForward( CBasePlayer *pMe, bool fHull ); +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CBaseTFPlayer::ClientCommand( const CCommand &args ) +{ + if( HasClass() ) + { + if ( GetPlayerClass()->ClientCommand( args ) ) + return true; + + const char *cmd = args[0]; + if ( FStrEq( cmd, "emp" ) ) + { + Msg( "Self-inflicted EMP: testing\n" ); + float flTime = 10; + if ( args.ArgC() == 2 ) + { + flTime = atof( args[ 1 ] ); + } + + AttemptToPowerup( POWERUP_EMP, flTime ); + return true; + } + + if ( FStrEq( cmd, "emp_target" ) ) + { + CBaseEntity *pEntity = FindEntityForward( this, true ); + if ( pEntity && pEntity->CanBePoweredUp() ) + { + float flTime = 10; + if ( args.ArgC() == 2 ) + { + flTime = atof( args[ 1 ] ); + } + pEntity->AttemptToPowerup( POWERUP_EMP, flTime ); + } + return true; + } + + if ( FStrEq( cmd, "dmg_target" ) ) + { + CBaseEntity *pEntity = FindEntityForward( this, true ); + if ( pEntity && pEntity->m_takedamage ) + { + float flDamage = 1; + if ( args.ArgC() == 2 ) + { + flDamage = atof( args[ 1 ] ); + } + CBaseEntity *world = CBaseEntity::Instance( engine->PEntityOfEntIndex( 0 ) ); + if ( world ) + { + pEntity->OnTakeDamage( CTakeDamageInfo( world, world, flDamage, DMG_GENERIC ) ); + } + } + return true; + } + } + + if ( !stricmp( cmd, "kd" ) ) + { + Vector force( 0, 0, 0 ); + if ( args.ArgC() == 1 ) + { + force.x = random->RandomFloat( 0.5, 1.0 ); + force.y = random->RandomFloat( 0.5, 1.0 ); + + if ( random->RandomFloat( 0, 1 ) > 0.5 ) + { + force.x *= -1.0f; + } + if ( random->RandomFloat( 0, 1 ) > 0.5 ) + { + force.y *= -1.0f; + } + force.z = random->RandomFloat( 0.5, 1.0 ); + } + else + { + Vector fwd; + Vector right; + AngleVectors( GetAbsAngles(), &fwd, &right, NULL ); + + if ( !stricmp( args[ 1 ], "f" ) ) + { + force = fwd * -1.0f; + } + else if ( !stricmp( args[ 1 ], "b" ) ) + { + force = fwd; + } + else if ( !stricmp( args[ 1 ], "r" ) ) + { + force = right * -1.0f; + } + else if ( !stricmp( args[ 1 ], "l" ) ) + { + force = right; + } + else if ( !stricmp( args[ 1 ], "fr" ) ) + { + force = fwd * -1.0f; + force += right * -1.0f; + } + else if ( !stricmp( args[ 1 ], "br" ) ) + { + force = fwd; + force += right * -1.0f; + } + else if ( !stricmp( args[ 1 ], "fl" ) ) + { + force = fwd * -1.0f; + force += right; + } + else if ( !stricmp( args[ 1 ], "bl" ) ) + { + force = fwd; + force += right; + } + + force.z = 0.8f; + VectorNormalize( force ); + } + + KnockDownPlayer( force, 500.0f, 3.0f ); + return true; + } + + if ( FStrEq( cmd, "veryweak" ) ) + { + int ouch = m_iHealth - 1; + + CBaseEntity *world = CBaseEntity::Instance( engine->PEntityOfEntIndex( 0 ) ); + if ( world ) + { + OnTakeDamage( CTakeDamageInfo( world, world, (float)ouch, DMG_GENERIC ) ); + } + + return true; + } + + if ( FStrEq( cmd, "ragdoll" ) ) + { + bool on = true; + + if ( args.ArgC() >= 2 ) + { + on = atoi( args[ 1 ] ) ? true : false; + } + + if ( on ) + { + Vector force = RandomVector( -500, 500 ); + force.z = fabs( force.z ); + force.z = MIN( 200.0f, force.z ); + + BecomeRagdollOnClient( force ); + } + else + { + ClearClientRagdoll( true ); + } + return true; + } + + if ( FStrEq( cmd, "hbset" ) ) + { + if ( args.ArgC() >= 2 ) + { + SetHitboxSet( atoi( args[ 1 ] ) ); + Msg( "Hitboxset forced to %i %s\n", GetHitboxSet(), GetHitboxSetName() ); + } + + return true; + } + + return BaseClass::ClientCommand( args ); +} + + +//========================================================= +// Purpose: Override base TraceAttack +//========================================================= +void CBaseTFPlayer::TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr ) +{ + if ( m_takedamage ) + { + // Prevent team damage here so blood doesn't appear + if ( info.GetAttacker() ) + { + // Take damage from myself + if ( InSameTeam( info.GetAttacker() ) && info.GetAttacker() != this ) + return; + } + + // If we hit our shield, ignore the damage + float flDamage = info.GetDamage(); + if ( IsHittingShield( vecDir, &flDamage )) + return; + + // Shield may have blocked some + CTakeDamageInfo subInfo = info; + subInfo.SetDamage( flDamage ); + + SetLastHitGroup( ptr->hitgroup ); + + // Hit groups aren't evaluated here, like base TraceAttack. + // Weapons factor hit location into flDamage before it gets here +/* //SpawnBlood( ptr->endpos - (vecDir * 5), BloodColor(), subInfo.GetDamage() ); + //TraceBleed( subInfo.GetDamage(), vecDir, ptr, subInfo.GetDamageType() ); + + // Show the personal shield effect. + // What we do here is collide the trace line with an ellipse that is slightly larger + // than the player and put the effect there. + + // Translate the line so the player's (and the ellipse's) center is at the origin. + Vector vCenter = Center(); + Vector vStart = ptr->startpos - vCenter; + Vector vEnd = ptr->endpos - vCenter; + + // Figure out the ellipse dimensions. + Vector vDims = (WorldAlignMaxs() - WorldAlignMins()) * 0.5f; + Vector vEllipse = vDims * 1.5; + + // Squash the line we're testing so we're testing against a sphere of radius 1 at the origin. + vStart /= vEllipse; + vEnd /= vEllipse; + + // See where the line hits the sphere. + Vector vLineDir = vEnd - vStart; + float f1, f2; + if ( IntersectInfiniteRayWithSphere( vStart, vLineDir, vec3_origin, 1, &f1, &f2 ) ) + { + // Use the closest hit point on the sphere. + float fMin = MIN( f1, f2 ); + Vector vPos = vStart + vLineDir * fMin; + + // Unsquash back to the ellipse's dimensions. + vPos *= vEllipse; + + ShowPersonalShieldEffect( vPos, vecDir, subInfo.GetDamage() ); + } +*/ + + AddMultiDamage( subInfo, this ); + } +} + + +//----------------------------------------------------------------------------- +// Applies a force on the player when he takes damage +//----------------------------------------------------------------------------- +void CBaseTFPlayer::ApplyDamageForce( const CTakeDamageInfo &info, int nDamageToDo ) +{ + if (nDamageToDo <= 0) + return; + + if ( (info.GetDamageType() & (DMG_ENERGYBEAM | DMG_BLAST)) == 0 ) + return; + + if ( !info.GetInflictor() || (info.GetInflictor() == this) || info.GetAttacker()->IsSolidFlagSet(FSOLID_TRIGGER) ) + return; + + // Don't blow ragdolls around + if ( IsClientRagdoll() ) + return; + + // Don't bother with crouched players, or classes that have other rules about it + if ( GetFlags() & FL_DUCKING ) + return; + + if (!GetPlayerClass() || !GetPlayerClass()->ShouldApplyDamageForce( info )) + return; + + Vector vecDir; + // If the inflictor isn't moving, use the delta between it & me. If it's moving, use it's velocity. + Vector vecInflictorVelocity; + info.GetInflictor()->GetVelocity( &vecInflictorVelocity, NULL ); + + // Explosives never use the velocity of the inflictor + if ( !(info.GetDamageType() & DMG_BLAST) && vecInflictorVelocity != vec3_origin ) + { + vecDir = vecInflictorVelocity; + } + else + { + vecDir = WorldSpaceCenter( ); + vecDir -= info.GetInflictor()->WorldSpaceCenter( ); + } + VectorNormalize( vecDir ); + + float flForce = (nDamageToDo * 2) + 20; + if (flForce > 1000.0) + flForce = 1000.0; + + // Escorts get knocked half as far + if ( PlayerClass() == TFCLASS_ESCORT ) + { + flForce *= 0.5; + } + + vecDir *= flForce; + + if ( (GetMoveType() != MOVETYPE_FLY) && (GetMoveType() != MOVETYPE_FLYGRAVITY) && ((GetFlags() & FL_ONGROUND) != 0) ) + { + // Need large x-y component to overcome walking friction + vecDir.x *= 3; + vecDir.y *= 3; + } + + Vector vecNewVelocity = GetAbsVelocity(); + vecNewVelocity += vecDir; + + Vector vecTestVel = vecNewVelocity; + float flLen = VectorNormalize( vecTestVel ); + if (flLen > MAX_EXPLOSIVE_VELOCITY) + VectorMultiply( vecTestVel, MAX_EXPLOSIVE_VELOCITY, vecNewVelocity ); + + SetAbsVelocity( vecNewVelocity ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Deal damage to the player +//----------------------------------------------------------------------------- +int CBaseTFPlayer::OnTakeDamage( const CTakeDamageInfo &info ) +{ + if ( !IsAlive() ) + return 0; + + //if ( GetFlags() & FL_GODMODE ) + //return 0; + + // Generate a global order event. + COrderEvent_PlayerDamaged event; + event.m_pPlayerDamaged = this; + event.m_TakeDamageInfo = info; + GlobalOrderEvent( &event ); + + // Don't do damage if the player's in a vehicle, in a non-damagable spot. + if ( IsInAVehicle() && m_hVehicle.Get() ) + { + IServerVehicle* pVehicle = m_hVehicle.Get()->GetServerVehicle(); + Assert( pVehicle ); + int nRole = pVehicle->GetPassengerRole(this); + + if( ( nRole < 0 ) + || !pVehicle->IsPassengerVisible(nRole) + || !pVehicle->IsPassengerDamagable(nRole) ) + { + return 0; + } + } + + // Check teams + CBaseTFPlayer *pPlayer = (CBaseTFPlayer*)info.GetAttacker(); + if ( pPlayer ) + { + // Take damage from myself + if ( pPlayer != this ) + { + if ( InSameTeam(pPlayer) ) + { + return 0; + } + else + { + // Store off the last time we were damaged by an enemy so commandos can + // get orders to assist. + m_flLastTimeDamagedByEnemy = gpGlobals->curtime; + } + } + } + + CTakeDamageInfo subInfo = info; + + // Let the playerclass at it + if ( GetPlayerClass() ) + { + subInfo.SetDamage( GetPlayerClass()->OnTakeDamage( subInfo ) ); + } + + if ( !subInfo.GetDamage() ) + return 0; + + //Msg( "Weapon did: %f\n", flDamage ); + + int iDamageToDo = Ceil2Int( subInfo.GetDamage() ); + + if ( !(GetFlags() & FL_GODMODE) ) + { + // Only certain damage types knock players around + ApplyDamageForce( info, iDamageToDo ); + + m_iHealth = MAX(0, m_iHealth - iDamageToDo); + } + + //Msg( "m_iHealth: %d\n\n", m_iHealth ); + + // Dead? + if ( m_iHealth < 1 ) + { + Event_Killed( subInfo ); + } + + // Let the client know + // Try and figure out where the damage is coming from + Vector vecDamageOrigin = info.GetReportedPosition(); + // If we didn't get an origin to use, try using the attacker's origin + if ( vecDamageOrigin == vec3_origin && info.GetAttacker() ) + { + vecDamageOrigin = info.GetAttacker()->GetAbsOrigin(); + } + + CSingleUserRecipientFilter user( this ); + UserMessageBegin( user, "Damage" ); + WRITE_BYTE( clamp( iDamageToDo, 0, 255 ) ); + WRITE_FLOAT( vecDamageOrigin.x ); // BUG: Should be fixed point (to hud) not floats + WRITE_FLOAT( vecDamageOrigin.y ); // BUG: However, the HUD does _not_ implement bitfield messages (yet) + WRITE_FLOAT( vecDamageOrigin.z ); // BUG: We use WRITE_VEC3COORD for everything else + MessageEnd(); + + // Do special explosion damage effect + if ( info.GetDamageType() & DMG_BLAST ) + { + OnDamagedByExplosion( info ); + } + + return iDamageToDo; +} + + +void CBaseTFPlayer::ShowPersonalShieldEffect( + const Vector &vOffsetFromEnt, + const Vector &vIncomingDirection, + float flDamage ) +{ + Vector vNormalized = vIncomingDirection; + VectorNormalize( vNormalized ); + + EntityMessageBegin( this ); + WRITE_BYTE( PLAYER_MSG_PERSONAL_SHIELD ); + WRITE_VEC3COORD( vOffsetFromEnt ); + WRITE_VEC3NORMAL( vNormalized ); + WRITE_SHORT( (short)flDamage ); + MessageEnd(); +} + + +//----------------------------------------------------------------------------- +// Purpose: Player is being healed +//----------------------------------------------------------------------------- +int CBaseTFPlayer::TakeHealth( float flHealth, int bitsDamageType ) +{ + if ( m_iHealth == m_iMaxHealth ) + return 0; + + // Heal the location + float flAmountToHeal = flHealth; + if ( flAmountToHeal > (m_iMaxHealth - m_iHealth) ) + flAmountToHeal = (m_iMaxHealth - m_iHealth); + m_iHealth += flAmountToHeal; + + //Msg( "Health: %d\n", m_iHealth ); + + return flAmountToHeal; +} + + +//===================================================================== +// MENU HANDLING +//===================================================================== +void CBaseTFPlayer::MenuDisplay( void ) +{ + if ( !m_pCurrentMenu ) + { + m_MenuRefreshTime = 0; + return; + } + + if ( m_MenuRefreshTime > gpGlobals->curtime ) + { + // guard against sudden clock changes + m_MenuRefreshTime = MIN( m_MenuRefreshTime, gpGlobals->curtime + MENU_UPDATETIME ); + return; + } + + m_MenuRefreshTime = gpGlobals->curtime + MENU_UPDATETIME; + + if ( m_pCurrentMenu ) + m_pCurrentMenu->Display( this ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CBaseTFPlayer::MenuInput( int iInput ) +{ + if ( m_pCurrentMenu ) + { + return m_pCurrentMenu->Input( this, iInput ); + } + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseTFPlayer::MenuReset( void ) +{ + CSingleUserRecipientFilter user( this ); + user.MakeReliable(); + + UserMessageBegin( user, "ShowMenu" ); + WRITE_SHORT( 0 ); + WRITE_CHAR( 0 ); // display time (-1 means unlimited) + WRITE_BYTE( false ); // is there more message to come? no + WRITE_STRING( "" ); + MessageEnd(); + + Q_strncpy( m_MenuStringBuffer, "" , sizeof(m_MenuStringBuffer) ); + m_MenuRefreshTime = m_MenuDisplayTime = 0; + + m_pCurrentMenu = NULL; +}; + +//----------------------------------------------------------------------------- +// Purpose: Enables/disables tactical/map view for the player +// Input : bTactical - true == enable it +//----------------------------------------------------------------------------- +void CBaseTFPlayer::ShowTacticalView( bool bTactical ) +{ + // TODO: Decide if we are going to keep the tactical view in TF2 + if ( !inv_demo.GetBool() ) + return; + + m_bSwitchingView = true; + m_TFLocal.m_nInTacticalView = bTactical ? 1 : 0; +} + +//----------------------------------------------------------------------------- +// returns true if we're in tactical view +//----------------------------------------------------------------------------- +bool CBaseTFPlayer::IsInTacticalView( void ) const +{ + return m_TFLocal.m_nInTacticalView; +} + +int CBaseTFPlayer::UpdateTransmitState() +{ + return SetTransmitState( FL_EDICT_FULLCHECK ); +} + +//----------------------------------------------------------------------------- +// Purpose: Note, an entity can override the send table ( e.g., to send less data or to send minimal data for +// objects ( prob. players ) that are not in the pvs. +// Input : **ppSendTable - +// *recipient - +// *pvs - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +int CBaseTFPlayer::ShouldTransmit( const CCheckTransmitInfo *pInfo ) +{ + // Don't transmit if we have no team or class + if ((PlayerClass() == TFCLASS_UNDECIDED) || (GetTeamNumber() == 0)) + return FL_EDICT_DONTSEND; + + // Thermal vision in effect, if so, cull some players who are too far away + CBaseTFPlayer *pPlayer = ( ( CBaseTFPlayer * )CBaseEntity::Instance( pInfo->m_pClientEnt ) ); + if ( pPlayer ) + { + if ( pPlayer->IsUsingThermalVision() ) + { + // Do a radius check, and force sending of guys nearby (so we can see them through walls) + Vector dist = GetAbsOrigin() - pPlayer->GetAbsOrigin(); + if ( dist.Length() < THERMAL_VISION_RADIUS ) + return FL_EDICT_ALWAYS; + } + + // If the player we might see is camouflaged and not on our team, we can preclude based + // on distance + if ( IsCamouflaged() && !InSameTeam( pPlayer ) ) + { + Vector dist = GetAbsOrigin() - pPlayer->GetAbsOrigin(); + if ( dist.Length() > CAMO_OUTER_RADIUS ) + { + return FL_EDICT_ALWAYS; + } + } + } + + // Use default pvs etc. rules + return BaseClass::ShouldTransmit( pInfo ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CTechnologyTree *CBaseTFPlayer::GetTechTree( void ) +{ + CTFTeam *pTeam = GetTFTeam(); + if ( pTeam ) + return pTeam->m_pTechnologyTree; + + return NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int CBaseTFPlayer::PlayerClass( void ) +{ + return m_iPlayerClass; +} + +CPlayerClass *CBaseTFPlayer::GetPlayerClass() +{ + return m_PlayerClasses.GetPlayerClass( PlayerClass() ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *name - +//----------------------------------------------------------------------------- +void CBaseTFPlayer::SetPreferredTechnology( CTechnologyTree *pTechnologyTree, int iTechIndex ) +{ + Assert( pTechnologyTree ); + + // Have to be on a team to vote for tech + CTFTeam *pTeam = GetTFTeam(); + if ( !pTeam ) + return; + + if ( iTechIndex == -1 ) + { + m_nPreferredTechnology = -1; + } + else + { + if ( iTechIndex < 0 || iTechIndex >= pTechnologyTree->GetNumberTechnologies() ) + { + Msg( "%s tried to set voting preference to unknown technology index : %d\n", + GetPlayerName(), iTechIndex ); + return; + } + CBaseTechnology *tech = pTechnologyTree->GetTechnology( iTechIndex ); + if ( !tech ) + return; + + // Has the tech got incomplete dependancies? + if ( tech->HasInactiveDependencies() ) + return; + // Already have it? + if ( tech->GetAvailable() ) + return; + // Can't prefer a hidden tech + if ( tech->IsHidden() ) + return; + + m_nPreferredTechnology = iTechIndex; + } + + pTeam->RecomputePreferences(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : int +//----------------------------------------------------------------------------- +int CBaseTFPlayer::GetPreferredTechnology( void ) +{ + return m_nPreferredTechnology; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *name - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CBaseTFPlayer::HasNamedTechnology( const char *name ) +{ + if ( GetTFTeam() == NULL ) + return false; + + return GetTFTeam()->HasNamedTechnology( name ); +} + +//----------------------------------------------------------------------------- +// Purpose: Networking is about to update this player, let it override and specify it's own pvs +// Input : **pvs - +// **pas - +//----------------------------------------------------------------------------- +void CBaseTFPlayer::SetupVisibility( CBaseEntity *pViewEntity, unsigned char *pvs, int pvssize ) +{ + // Normal PVS + BaseClass::SetupVisibility( pViewEntity, pvs, pvssize ); + + // PVS has an additional origin + if ( m_vecAdditionalPVSOrigin != vec3_origin ) + { + // Add an additional origin to the pvs + engine->AddOriginToPVS( m_vecAdditionalPVSOrigin ); + } + + if ( m_vecCameraPVSOrigin != vec3_origin ) + { + engine->AddOriginToPVS( m_vecCameraPVSOrigin ); + } + + // If in tactical mode, merge in pvs from all of our teammates, too + // send all the others team info + if ( m_TFLocal.m_nInTacticalView ) + { + int i; + for ( i = 1; i <= gpGlobals->maxClients; i++ ) + { + CBaseTFPlayer *plr = ToBaseTFPlayer( UTIL_PlayerByIndex(i) ); + if ( plr && + ( plr != this ) && + ( plr->TeamID() == TeamID() ) ) + { + Vector org; + org = plr->EyePosition(); + + engine->AddOriginToPVS( org ); + } + } + } +} + +//-------------------------------------------------------------------------------------------------------------- +// ORDERS +//----------------------------------------------------------------------------- +// Purpose: Assign the player to the specified order +//----------------------------------------------------------------------------- +void CBaseTFPlayer::SetOrder( COrder *pOrder ) +{ + if ( m_hSelectedOrder.Get() && m_hSelectedOrder != pOrder ) + { + m_hSelectedOrder->SetOwner( NULL ); + } + m_hSelectedOrder = pOrder; +} + + +int CBaseTFPlayer::GetNumResourceZoneOrders() +{ + return GetTFTeam()->CountOrders( COUNTORDERS_TYPE | COUNTORDERS_OWNER, ORDER_ATTACK, 0, this ) + + GetTFTeam()->CountOrders( COUNTORDERS_TYPE | COUNTORDERS_OWNER, ORDER_DEFEND, 0, this ) + + GetTFTeam()->CountOrders( COUNTORDERS_TYPE | COUNTORDERS_OWNER, ORDER_CAPTURE, 0, this ); +} + + +void CBaseTFPlayer::KillResourceZoneOrders() +{ + if( GetNumResourceZoneOrders() ) + GetTFTeam()->RemoveOrdersToPlayer( this ); +} + + +//-------------------------------------------------------------------------------------------------------------- +// DEPLOYMENT +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseTFPlayer::StartDeploying( void ) +{ + if ( !GetPlayerClass() ) + return; + + m_bDeploying = true; + m_vecDeployedAngles = GetLocalAngles(); + + // No pitch or roll, though + m_vecDeployedAngles.SetX( 0 ); + m_vecDeployedAngles.SetZ( 0 ); + + SetCantMove( true ); + m_flFinishedDeploying = gpGlobals->curtime + GetPlayerClass()->GetDeployTime(); + + SetAnimation( PLAYER_START_AIMING ); + + EmitSound( "BaseTFPlayer.StartDeploying" ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseTFPlayer::StartUnDeploying( void ) +{ + if ( !GetPlayerClass() ) + return; + + m_bUnDeploying = true; + SetCantMove( true ); + m_flFinishedDeploying = gpGlobals->curtime + GetPlayerClass()->GetDeployTime(); + SetAnimation( PLAYER_LEAVE_AIMING ); + + EmitSound( "BaseTFPlayer.StartUnDeploying" ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseTFPlayer::CheckDeployFinish( void ) +{ + // Check to see if deployment has finished + if ( m_bDeploying ) + { + if ( gpGlobals->curtime > m_flFinishedDeploying ) + { + FinishDeploying(); + } + return; + } + + // Check to see if un-deployment has finished + if ( m_bUnDeploying ) + { + if ( gpGlobals->curtime > m_flFinishedDeploying ) + { + FinishUnDeploying(); + } + return; + } + + // Check to see if the player's trying to move while deployed + if ( IsAlive() && m_bDeployed ) + { + if ( m_nButtons & (IN_FORWARD | IN_BACK | IN_MOVELEFT | IN_MOVERIGHT ) ) + { + StartUnDeploying(); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseTFPlayer::FinishDeploying( void ) +{ + m_bDeploying = false; + m_bDeployed = true; + SetCantMove( true ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseTFPlayer::FinishUnDeploying( void ) +{ + m_bUnDeploying = false; + m_bDeployed = false; + SetCantMove( false ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CBaseTFPlayer::IsDeployed( void ) +{ + return m_bDeployed; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CBaseTFPlayer::IsDeploying( void ) +{ + return (m_bDeploying || m_bUnDeploying); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CBaseTFPlayer::IsUnDeploying( void ) +{ + return m_bUnDeploying; +} + +void CBaseTFPlayer::OnVehicleStart() +{ + // Do any class-specific stuff + if (GetPlayerClass()) + { + GetPlayerClass()->OnVehicleStart(); + } + + IServerVehicle *pVehicle = GetVehicle(); + CBaseCombatWeapon *weapon = GetActiveWeapon(); + if ( pVehicle && weapon ) + { + // Get Role for this player + int role = pVehicle->GetPassengerRole( this ); + bool allowweapons = pVehicle->IsPassengerUsingStandardWeapons( role ); + if ( !allowweapons ) + { + weapon->Holster(); + } + } +} + +void CBaseTFPlayer::OnVehicleEnd( Vector &playerDestPosition ) +{ + // Do any class-specific stuff + if (GetPlayerClass()) + { + GetPlayerClass()->OnVehicleEnd(); + } + + Vector vNewPos; + if ( !EntityPlacementTest( this, playerDestPosition, vNewPos, true) ) + { + Warning("Can't find valid place to exit vehicle.\n"); + return; + } + + // Move the player up a bit to be safe + playerDestPosition = vNewPos + Vector(0,0,16); + + CBaseCombatWeapon *weapon = GetActiveWeapon(); + if ( weapon ) + { + weapon->Deploy(); + } +} + +//-------------------------------------------------------------------------------------------------------------- +// Purpose: +//-------------------------------------------------------------------------------------------------------------- +bool CBaseTFPlayer::CanGetInVehicle( void ) +{ + // Class-specific? + if ( GetPlayerClass() ) + { + return GetPlayerClass()->CanGetInVehicle(); + } + + return true; +} + + +CVehicleTeleportStation* CBaseTFPlayer::GetSelectedMCV() const +{ + return dynamic_cast< CVehicleTeleportStation* >( m_hSelectedMCV.Get() ); +} + + +void CBaseTFPlayer::SetSelectedMCV( CVehicleTeleportStation *pMCV ) +{ + m_hSelectedMCV = pMCV; +} + + +//----------------------------------------------------------------------------- +// Purpose: Restore this player's ammo count to it's starting state +//----------------------------------------------------------------------------- +bool CBaseTFPlayer::ResupplyAmmo( float flPercentage, ResupplyReason_t reason ) +{ + if ( !GetPlayerClass() ) + return false; + + return GetPlayerClass()->ResupplyAmmo(flPercentage, reason); +} + + +//----------------------------------------------------------------------------- +// Purpose: Medic has provided this player with a health boost +//----------------------------------------------------------------------------- +void CBaseTFPlayer::TakeHealthBoost( int iHealthBoost, int iTarget, int iDuration ) +{ + m_iHealth += iHealthBoost; + m_iHealthBoostTarget = iTarget; + m_flHealthBoostDecrement = ceil((m_iHealth - iTarget) / (float)iDuration); + + // Start the health ticking down + m_flHealthBoostTime = gpGlobals->curtime + 1.0; + + if ( iTarget >= m_iMaxHealth ) + { + m_bBuffHealthBoost = true; + } +} + +//----------------------------------------------------------------------------- +// Purpose: Remove health buffs +//----------------------------------------------------------------------------- +void CBaseTFPlayer::RemoveHealthBoost( void ) +{ + m_bBuffHealthBoost = false; + m_iHealthBoostTarget = 0; + m_flHealthBoostTime = 0; + + if ( m_iHealth > m_iMaxHealth ) + { + m_iHealth = m_iMaxHealth; + } +} + +//----------------------------------------------------------------------------- +// Purpose: Check the state of all buffs on this player +//----------------------------------------------------------------------------- +void CBaseTFPlayer::CheckBuffs( void ) +{ + // Health boost? + if ( m_bBuffHealthBoost ) + { + // Dropped below normal max health? + if ( m_iHealth <= m_iHealthBoostTarget ) + { + RemoveHealthBoost(); + } + else + { + if ( m_flHealthBoostTime < gpGlobals->curtime ) + { + // Ticking down from a boost? or suffering poison damage? + if ( m_iHealth > m_iMaxHealth ) + { + // Drop back to normal health in 20 seconds + m_iHealth -= m_flHealthBoostDecrement; + } + + m_flHealthBoostTime = gpGlobals->curtime + 1.0; + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Powerup has just started +//----------------------------------------------------------------------------- +void CBaseTFPlayer::PowerupStart( int iPowerup, float flAmount, CBaseEntity *pAttacker, CDamageModifier *pDamageModifier ) +{ + Assert( iPowerup >= 0 && iPowerup < MAX_POWERUPS ); + + switch( iPowerup ) + { + case POWERUP_BOOST: + { + m_hLastBoostEntity = pAttacker; + + // Power up their shield + if ( GetCombatShield() ) + { + GetCombatShield()->AddShieldHealth( 0.06 ); + } + + // Let their playerclass know + GetPlayerClass()->PowerupStart( iPowerup, flAmount, pAttacker, pDamageModifier ); + } + break; + + case POWERUP_EMP: + { + // Let the playerclass know about it + GetPlayerClass()->PowerupStart( iPowerup, flAmount, pAttacker, pDamageModifier ); + } + break; + + case POWERUP_RUSH: + { + // Speed up + // We need to set this here so RecalculateSpeed() can check HasPowerup(POWERUP_RUSH) + m_iPowerups |= (1 << iPowerup); + RecalculateSpeed(); + } + break; + + default: + break; + } + + BaseClass::PowerupStart( iPowerup, flAmount, pAttacker, pDamageModifier ); +} + +//----------------------------------------------------------------------------- +// Purpose: Powerup has just started +//----------------------------------------------------------------------------- +void CBaseTFPlayer::PowerupEnd( int iPowerup ) +{ + switch( iPowerup ) + { + case POWERUP_EMP: + { + GetPlayerClass()->PowerupEnd(iPowerup); + } + break; + + case POWERUP_RUSH: + { + // Slow down + RecalculateSpeed(); + } + break; + + default: + break; + } + + BaseClass::PowerupEnd( iPowerup ); +} + +//-------------------------------------------------------------------------------------------------------------- +// OBJECTS +//----------------------------------------------------------------------------- +// Purpose: Return true if this player's allowed to build another one of the specified object +//----------------------------------------------------------------------------- +int CBaseTFPlayer::CanBuild( int iObjectType ) +{ + if ( iObjectType < 0 || iObjectType >= OBJ_LAST ) + return CB_NOT_RESEARCHED; + + if ( GetPlayerClass() ) + { + return GetPlayerClass()->CanBuild( iObjectType ); + } + + return CB_NOT_RESEARCHED; +} + +//----------------------------------------------------------------------------- +// Purpose: Get the number of objects of the specified type that this player has +//----------------------------------------------------------------------------- +int CBaseTFPlayer::GetNumObjects( int iObjectType ) +{ + int iCount = 0; + for (int i = 0; i < GetObjectCount(); i++) + { + if ( !GetObject(i) ) + continue; + + if ( GetObject(i)->GetType() == iObjectType ) + { + iCount++; + } + } + + return iCount; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int CBaseTFPlayer::GetObjectCount( void ) +{ + return m_TFLocal.m_aObjects.Count(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CBaseObject *CBaseTFPlayer::GetObject( int index ) +{ + return m_TFLocal.m_aObjects[index].Get(); +} + +//----------------------------------------------------------------------------- +// Purpose: Returns true if this player is building something +//----------------------------------------------------------------------------- +bool CBaseTFPlayer::IsBuilding( void ) +{ + CWeaponBuilder *pBuilder = GetWeaponBuilder(); + if ( pBuilder ) + return pBuilder->IsBuilding(); + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: Object built by this player has been destroyed +//----------------------------------------------------------------------------- +void CBaseTFPlayer::OwnedObjectDestroyed( CBaseObject *pObject ) +{ + TRACE_OBJECT( UTIL_VarArgs( "%0.2f CBaseTFPlayer::OwnedObjectDestroyed player %s object %p:%s\n", gpGlobals->curtime, + GetPlayerName(), + pObject, + pObject->GetClassname() ) ); + + if ( GetPlayerClass() ) + { + GetPlayerClass()->OwnedObjectDestroyed( pObject ); + } + + RemoveObject( pObject ); + + // Tell our builder weapon so it recalculates the state of the build icons + CWeaponBuilder *pBuilder = GetWeaponBuilder(); + if ( pBuilder ) + { + pBuilder->GainedNewTechnology( NULL ); + } +} + + +//----------------------------------------------------------------------------- +// Removes an object from the player +//----------------------------------------------------------------------------- +void CBaseTFPlayer::RemoveObject( CBaseObject *pObject ) +{ + TRACE_OBJECT( UTIL_VarArgs( "%0.2f CBaseTFPlayer::RemoveObject %p:%s from player %s\n", gpGlobals->curtime, + pObject, + pObject->GetClassname(), + GetPlayerName() ) ); + + Assert( pObject ); + for (int i = m_TFLocal.m_aObjects.Count(); --i >= 0; ) + { + // Also, while we're at it, remove all other bogus ones too... + if ((!m_TFLocal.m_aObjects[i].Get()) || (m_TFLocal.m_aObjects[i] == pObject)) + { + m_TFLocal.m_aObjects.FastRemove(i); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pObject - +// *pNewOwner - +//----------------------------------------------------------------------------- +void CBaseTFPlayer::OwnedObjectChangeTeam( CBaseObject *pObject, CBaseTFPlayer *pNewOwner ) +{ + TRACE_OBJECT( UTIL_VarArgs( "%0.2f CBaseTFPlayer::OwnedObjectChangeTeam player %s object %p:%s new player %s\n", gpGlobals->curtime, + GetPlayerName(), + pObject, + pObject->GetClassname(), + pNewOwner->GetPlayerName() ) ); + + if ( pNewOwner && pNewOwner->GetPlayerClass() ) + { + pNewOwner->GetPlayerClass()->OwnedObjectChangeFromTeam( pObject, this ); + } + + // Remove from my list of objects + RemoveObject( pObject ); + + // Add to new team + if ( pNewOwner ) + { + pNewOwner->AddObject( pObject ); + } + + if ( GetPlayerClass() ) + { + GetPlayerClass()->OwnedObjectChangeToTeam( pObject, pNewOwner ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pZone - +// Output : int +//----------------------------------------------------------------------------- +int CBaseTFPlayer::NumPumpsOnResourceZone( CResourceZone *pZone ) +{ + int ret = 0; + + for( int iObj=0; iObj < GetObjectCount(); iObj++ ) + { + CBaseObject *pObj = GetObject(iObj); + + if( pObj->GetType() == OBJ_RESOURCEPUMP ) + { + CObjectResourcePump *pPump = (CObjectResourcePump*)pObj; + + // Ok, this guy already has a pump here. + if( pPump->GetResourceZone() == pZone ) + ++ret; + } + } + + return ret; +} + + + +//----------------------------------------------------------------------------- +// Purpose: Remove all the player's objects +// If bForceAll is set, remove all of them immediately. +// Otherwise, make them all deteriorate over time. +// If iClass is passed in, don't remove any objects that can be built +// by that class. If bReturnResources is set, the cost of any destroyed +// objects will be returned to the player. +//----------------------------------------------------------------------------- +void CBaseTFPlayer::RemoveAllObjects( bool bForceAll, int iClass, bool bReturnResources ) +{ + // Remove all the player's objects + int iSize = GetObjectCount(); + for (int i = iSize-1; i >= 0; i--) + { + CBaseObject *obj = GetObject(i); + Assert( obj ); + if ( !obj ) + { + continue; + } + + if ( !bForceAll ) + { + if ( iClass ) + { + // Can our new class build this object? + if ( ClassCanBuild( iClass, obj->GetType() ) ) + continue; + } + + // Vehicles don't deteriorate when their owner changes teams/leaves. + // They'll deteriorate naturally if they're unused for a while. + if ( IsObjectAVehicle(obj->GetType()) ) + { + RemoveObject( obj ); + + // Just remove it from my list + obj->SetBuilder( NULL ); + continue; + } + } + + // Return the cost of the object? + if ( bReturnResources ) + { + GetPlayerClass()->PickupObject( obj ); + } + + // Remove or deteriorate? + if ( bForceAll ) + { + UTIL_Remove( obj ); + } + else + { + OwnedObjectDestroyed( obj ); + obj->StartDeteriorating(); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseTFPlayer::StopPlacement( void ) +{ + // Tell our builder weapon + CWeaponBuilder *pBuilder = GetWeaponBuilder(); + if ( pBuilder ) + { + pBuilder->StopPlacement(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Player has started building an object +//----------------------------------------------------------------------------- +int CBaseTFPlayer::StartedBuildingObject( int iObjectType ) +{ + // Tell our playerclass + if ( GetPlayerClass() ) + return GetPlayerClass()->StartedBuildingObject( iObjectType ); + + return 0; +} + +//----------------------------------------------------------------------------- +// Purpose: Player has aborted building something +//----------------------------------------------------------------------------- +void CBaseTFPlayer::StoppedBuilding( int iObjectType ) +{ + // Tell our playerclass + if ( GetPlayerClass() ) + { + GetPlayerClass()->StoppedBuilding( iObjectType ); + } + + // Tell our builder weapon + CWeaponBuilder *pBuilder = GetWeaponBuilder(); + if ( pBuilder ) + { + pBuilder->StoppedBuilding( iObjectType ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Object has been built by this player +//----------------------------------------------------------------------------- +void CBaseTFPlayer::FinishedObject( CBaseObject *pObject ) +{ + AddObject( pObject ); + + // Tell our playerclass + if ( GetPlayerClass() ) + { + GetPlayerClass()->FinishedObject( pObject ); + } + + // Tell our builder weapon + CWeaponBuilder *pBuilder = GetWeaponBuilder(); + if ( pBuilder ) + { + pBuilder->FinishedObject(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Add the specified object to this player's object list. +//----------------------------------------------------------------------------- +void CBaseTFPlayer::AddObject( CBaseObject *pObject ) +{ + TRACE_OBJECT( UTIL_VarArgs( "%0.2f CBaseTFPlayer::AddObject adding object %p:%s to player %s\n", gpGlobals->curtime, pObject, pObject->GetClassname(), GetPlayerName() ) ); + + // Make a handle out of it + CHandle<CBaseObject> hObject; + hObject = pObject; + + // Make sure it's not in the list already + bool alreadyInList = (m_TFLocal.m_aObjects.Find( hObject ) != -1); + Assert( !alreadyInList ); + if ( !alreadyInList ) + { + m_TFLocal.m_aObjects.AddToTail( hObject ); + } + + // Stop it deterioating, if it is + pObject->StopDeteriorating(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseTFPlayer::SetWeaponBuilder( CWeaponBuilder *pBuilder ) +{ + m_hWeaponBuilder = pBuilder; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CWeaponBuilder *CBaseTFPlayer::GetWeaponBuilder( void ) +{ + return m_hWeaponBuilder; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *attacker - +// sourceDir - +// duration - +//----------------------------------------------------------------------------- +void CBaseTFPlayer::KnockDownPlayer( const Vector& sourceDir, float magnitude, float duration ) +{ + // Already knocked down + if ( m_TFLocal.m_bKnockedDown ) + return; + // In a vehicle + if ( IsInAVehicle() ) + return; + + m_TFLocal.m_bKnockedDown = true; + + // Randomize it a bit + Vector jitter( 0, 0, 0 ); + //jitter.Random( -0.1, 0.1 ); + + Vector dir = sourceDir + jitter; + + VectorNormalize( dir ); + + Vector force = dir * magnitude; + ApplyAbsVelocityImpulse( force ); + + VectorAngles( dir, m_TFLocal.m_vecKnockDownDir.GetForModify() ); + + QAngle ang = GetAbsAngles(); + Vector forward, right; + AngleVectors( ang, &forward, &right, NULL ); + + float dotFwd = dir.Dot( forward ); + float dotRight = dir.Dot( right ); + + if ( dotFwd >= 0) + { + // if get hit from behind, pitch down a bit + m_TFLocal.m_vecKnockDownDir.SetX( dotFwd * 20.0f ); + // look in the direction you fell + m_TFLocal.m_vecKnockDownDir.SetZ( dotRight * 80.0f ); + } + else + { + //Invert knock yaw if hit from front, so you are looking straight up at the direction + // the hit cam efrom + m_TFLocal.m_vecKnockDownDir += QAngle( 0, 180, 0 ); + // Look up in the air + m_TFLocal.m_vecKnockDownDir.SetX( fabs( dotFwd ) * -60.0f ); + // And a bit to the side the hit came from + m_TFLocal.m_vecKnockDownDir.SetZ( dotRight * 20.0f ); + } + + m_flKnockdownEndTime = gpGlobals->curtime + duration; + + // Play some kind of knockdown sound + EmitSound( "BaseTFPlayer.KnockedDown" ); + + if ( BecomeRagdollOnClient( force ) ) + { + // We we are using ragdoll flight, then don't change underlying player + // velocity + ApplyAbsVelocityImpulse( -force ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseTFPlayer::ResetKnockdown( void ) +{ + // Don't get up if I'm dead + if ( IsAlive() ) + { + if ( !ClearClientRagdoll( true ) ) + return; + } + + m_TFLocal.m_bKnockedDown = false; + m_TFLocal.m_vecKnockDownDir.Init(); + m_flKnockdownEndTime = 0.0f; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CBaseTFPlayer::IsKnockedDown( void ) +{ + return m_TFLocal.m_bKnockedDown; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseTFPlayer::CheckKnockdown( void ) +{ + if ( !m_TFLocal.m_bKnockedDown ) + return; + + if ( gpGlobals->curtime < m_flKnockdownEndTime ) + return; + + // Remove knockdown + ResetKnockdown(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CBaseTFPlayer::IsGagged( void ) +{ + return m_bGagged; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : gag - +//----------------------------------------------------------------------------- +void CBaseTFPlayer::SetGagged( bool gag ) +{ + m_bGagged = gag; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CBaseTFPlayer::CanSpeak( void ) +{ + return !IsGagged(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CBaseTFPlayer::IsUsingThermalVision( void ) +{ + return m_TFLocal.m_bThermalVision; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseTFPlayer::SetIDEnt( CBaseEntity *pEntity ) +{ + if ( pEntity ) + m_TFLocal.m_iIDEntIndex = pEntity->entindex(); + else + m_TFLocal.m_iIDEntIndex = 0; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : thermal - +//----------------------------------------------------------------------------- +void CBaseTFPlayer::SetUsingThermalVision( bool thermal ) +{ + // Play sounds if we're changing + if ( m_TFLocal.m_bThermalVision != thermal ) + { + if ( thermal ) + { + EmitSound( "BaseTFPlayer.ThermalOn" ); + } + else + { + EmitSound( "BaseTFPlayer.ThermalOff" ); + } + } + + m_TFLocal.m_bThermalVision = thermal; +} + + +//----------------------------------------------------------------------------- +// Purpose: Add the specified number of resource chunks to the player. Return true if he can carry it all. +//----------------------------------------------------------------------------- +bool CBaseTFPlayer::AddResourceChunks( int iChunks, bool bProcessed ) +{ + // Am I allowed to carry any more chunks? + int iCurrentCount = GetTotalResourceChunks(); + // Somewhat hacky + int iIndex = GetAmmoDef()->Index("ResourceChunks"); + int iMax = GetAmmoDef()->MaxCarry( iIndex ); + if ( iCurrentCount >= iMax ) + { + bool bSwapped = false; + + // If this is a processed chunk, see if we can swap it for an unprocessed chunk + if ( bProcessed ) + { + if ( m_TFLocal.m_iResourceAmmo[ NORMAL_RESOURCES ] ) + { + // Drop this unprocessed chunk + Vector vecVelocity = Vector( random->RandomFloat( -250,250 ), random->RandomFloat( -250,250 ), random->RandomFloat( 200,450 ) ); + CResourceChunk::Create( false, GetAbsOrigin() + Vector(0,0,32), vecVelocity ); + RemoveResourceChunks( 1, false ); + bSwapped = true; + } + } + + if ( !bSwapped ) + return false; + } + + m_TFLocal.m_iResourceAmmo.Set( bProcessed, MIN( iMax, m_TFLocal.m_iResourceAmmo[ bProcessed ] + iChunks ) ); + SetAmmoCount( GetTotalResourceChunks(), iIndex ); + CPASAttenuationFilter filter( this,"BaseTFPlayer.PickupResources" ); + EmitSound( filter, entindex(),"BaseTFPlayer.PickupResources" ); + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: Remove the specified number of resources chunks from the player. +//----------------------------------------------------------------------------- +void CBaseTFPlayer::RemoveResourceChunks( int iChunks, bool bProcessed ) +{ + // Remove the amount + m_TFLocal.m_iResourceAmmo.Set( bProcessed, MAX( 0, m_TFLocal.m_iResourceAmmo[ bProcessed ] - iChunks ) ); + int iIndex = GetAmmoDef()->Index("ResourceChunks"); + SetAmmoCount( GetTotalResourceChunks(), iIndex ); +} + +//----------------------------------------------------------------------------- +// Purpose: Get the number of resource chunks of this type the player's carrying +//----------------------------------------------------------------------------- +int CBaseTFPlayer::GetResourceChunkCount( bool bProcessed ) +{ + return m_TFLocal.m_iResourceAmmo[ bProcessed ]; +} + +//----------------------------------------------------------------------------- +// Purpose: Get the total number of resource chunks being carried by the player +//----------------------------------------------------------------------------- +int CBaseTFPlayer::GetTotalResourceChunks( void ) +{ + int iCurrentCount = 0; + for ( int i = 0; i < RESOURCE_TYPES; i++ ) + { + iCurrentCount += m_TFLocal.m_iResourceAmmo[i]; + } + + return iCurrentCount; +} + + +//----------------------------------------------------------------------------- +// Purpose: Drop some resource chunks +//----------------------------------------------------------------------------- +void CBaseTFPlayer::DropAllResourceChunks( void ) +{ + Vector vecOrigin = GetAbsOrigin() + Vector(0,0,32); + + TFStats()->IncrementTeamStat( GetTeamNumber(), TF_TEAM_STAT_RESOURCE_CHUNKS_DROPPED, resource_chunk_value.GetFloat() ); + + // Drop a resource chunk + Vector vecVelocity = Vector( random->RandomFloat( -250,250 ), random->RandomFloat( -250,250 ), random->RandomFloat( 200,450 ) ); + CResourceChunk *pChunk = CResourceChunk::Create( FALSE, vecOrigin, vecVelocity ); + pChunk->ChangeTeam( GetTeamNumber() ); +} + + +//------------------------------------------------------------------------------------------------------------------ +// RESOURCE BANK +//----------------------------------------------------------------------------- +// Purpose: Get the amount of a resource in this player's bank +//----------------------------------------------------------------------------- +int CBaseTFPlayer::GetBankResources( void ) +{ + return m_TFLocal.ResourceCount(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseTFPlayer::SetBankResources( int iAmount ) +{ + int nOldAmount = m_TFLocal.ResourceCount(); + + TFStats()->IncrementPlayerStat( this, TF_PLAYER_STAT_RESOURCES_ACQUIRED, iAmount - nOldAmount ); + + m_TFLocal.SetResources( iAmount ); + + // Tell the player's builder weapon to update + CWeaponBuilder *pBuilder = GetWeaponBuilder(); + if ( pBuilder ) + { + pBuilder->GainedNewTechnology( NULL ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Add resources to this player's Bank +//----------------------------------------------------------------------------- +void CBaseTFPlayer::AddBankResources( int iAmount ) +{ + m_TFLocal.AddResources( iAmount ); + + // Tell the player's builder weapon to update + CWeaponBuilder *pBuilder = GetWeaponBuilder(); + if ( pBuilder ) + { + pBuilder->GainedNewTechnology( NULL ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Remove resources to this player's Bank +//----------------------------------------------------------------------------- +void CBaseTFPlayer::RemoveBankResources( int iAmount, bool bSpent ) +{ + m_TFLocal.RemoveResources( iAmount ); + + // Tell the player's builder weapon to update + CWeaponBuilder *pBuilder = GetWeaponBuilder(); + if ( pBuilder ) + { + pBuilder->GainedNewTechnology( NULL ); + } + + if (bSpent) + { + TFStats()->IncrementPlayerStat( this, TF_PLAYER_STAT_RESOURCES_SPENT, iAmount ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CBaseTFPlayer::IsCamouflaged( void ) +{ + return ( m_flCamouflageAmount > 0.0f ) ? true : false; +} + +//----------------------------------------------------------------------------- +// Purpose: Change state over time +//----------------------------------------------------------------------------- +void CBaseTFPlayer::CheckCamouflage( void ) +{ + if ( m_flCamouflageAmount == m_flGoalCamouflageAmount ) + return; + + float remaining = m_flGoalCamouflageAmount - m_flCamouflageAmount; + float maxstep = m_flGoalCamouflageChangeRate * gpGlobals->frametime; + + if ( remaining > 0.0f ) + { + m_flCamouflageAmount += MIN( remaining, maxstep ); + } + else + { + remaining = -remaining; + m_flCamouflageAmount -= MIN( remaining, maxstep ); + } + + m_flCamouflageAmount = MAX( 0.0f, m_flCamouflageAmount ); + m_flCamouflageAmount = MIN( 100.0f, m_flCamouflageAmount ); +} + +//----------------------------------------------------------------------------- +// Purpose: Goal % and rate in percent/second to achieve the goal +// Input : percentage - +// changerate - +//----------------------------------------------------------------------------- +void CBaseTFPlayer::SetCamouflaged( int percentage, float changerate ) +{ + m_flGoalCamouflageAmount = (float)percentage; + m_flGoalCamouflageChangeRate = changerate; +} + +//----------------------------------------------------------------------------- +// Purpose: Remove the player's camo +//----------------------------------------------------------------------------- +void CBaseTFPlayer::ClearCamouflage( void ) +{ + SetCamouflaged( 0, 1000 ); + + // Tell the playerclass + if ( GetPlayerClass() ) + { + GetPlayerClass()->ClearCamouflage(); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Confirm powerup durations +//----------------------------------------------------------------------------- +float CBaseTFPlayer::PowerupDuration( int iPowerup, float flTime ) +{ + // Medics are never EMPed for long + if ( PlayerClass() == TFCLASS_MEDIC && iPowerup == POWERUP_EMP ) + return 0.2; + + return BaseClass::PowerupDuration( iPowerup, flTime ); +} + +//----------------------------------------------------------------------------- +// Purpose: Return the player's anim speed multiplier. Used for speeding up viewmodels while rushed. +//----------------------------------------------------------------------------- +float CBaseTFPlayer::GetDefaultAnimSpeed( void ) +{ + if ( HasPowerup( POWERUP_RUSH ) ) + return ADRENALIN_ANIM_SPEED; + + // Weapons may modify animation times + if ( GetActiveWeapon() ) + return GetActiveWeapon()->GetDefaultAnimSpeed(); + + return 1.0; +} + + +//----------------------------------------------------------------------------- +// Donate resources to a teammate +//----------------------------------------------------------------------------- +void CBaseTFPlayer::DonateResources( CBaseTFPlayer *pTarget, int pCount ) +{ + Assert( pTarget ); + + int nTotalCountDonated = 0; + int nDonationCount = GetBankResources(); + if ( pCount < nDonationCount ) + nDonationCount = pCount; + + if (nDonationCount) + { + RemoveBankResources( nDonationCount, false ); + pTarget->AddBankResources( nDonationCount ); + nTotalCountDonated += nDonationCount; + } + + if (nTotalCountDonated > 0) + { + char buf[1024]; + Q_snprintf( buf, sizeof( buf ), "%s has donated %d resources to you\n", + GetPlayerName(), nTotalCountDonated ); + ClientPrint( pTarget, HUD_PRINTCENTER, buf ); + + CSingleUserRecipientFilter filter( this ); + EmitSound( filter, entindex(), "BaseTFPlayer.DonateResources" ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Infilitrator's can +use a corpse to consume it +//----------------------------------------------------------------------------- +void CBaseTFPlayer::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( pActivator->IsPlayer() ) + { + CBaseTFPlayer *pPlayer = static_cast<CBaseTFPlayer*>(pActivator); + + if ( InSameTeam( pActivator )) + { + // Resource donation + pPlayer->DonateResources( this, 25 ); + } + } + + BaseClass::Use( pActivator, pCaller, useType, value ); +} + + +//----------------------------------------------------------------------------- +// The player's usable... +//----------------------------------------------------------------------------- +int CBaseTFPlayer::ObjectCaps( void ) +{ + return ( (BaseClass::ObjectCaps() & ~FCAP_ACROSS_TRANSITION) | FCAP_IMPULSE_USE ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CBaseTFPlayer::CantMove( void ) +{ + return m_bCantMove; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseTFPlayer::SetCantMove( bool bCantMove ) +{ + m_bCantMove = bCantMove; + RecalculateSpeed(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseTFPlayer::ResetViewOffset( void ) +{ + if ( GetPlayerClass() ) + { + GetPlayerClass()->ResetViewOffset(); + } + else + { + SetViewOffset( VEC_VIEW_SCALED( this ) ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: If ragdolling, move the player along the path that the ragdoll takes +//----------------------------------------------------------------------------- +void CBaseTFPlayer::FollowClientRagdoll( void ) +{ + if (( m_hRagdollShadow == NULL ) || ( GetPlayerClass() == NULL )) + return; + + Vector vecMin, vecMax; + GetPlayerClass()->GetPlayerHull( ( ( GetFlags() & FL_DUCKING ) == 1 ), vecMin, vecMax ); + + // Follow shadow object + trace_t tr; + + UTIL_TraceHull( + m_hRagdollShadow->GetAbsOrigin() + Vector(0,0,18), + m_hRagdollShadow->GetAbsOrigin(), + vecMin, + vecMax, + MASK_PLAYERSOLID, + m_hRagdollShadow, + COLLISION_GROUP_NONE, + &tr ); + + // Only move if we can find a valid spot under where shadow rolled + if ( !tr.allsolid ) + { + UTIL_SetOrigin( this, tr.endpos ); + VectorCopy( tr.endpos, m_vecLastGoodRagdollPos ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Stop being a ragdoll +// Input : moveplayertofinalspot - +// Output : return whether or not the ragdoll was cleared +//----------------------------------------------------------------------------- +bool CBaseTFPlayer::ClearClientRagdoll( bool moveplayertofinalspot ) +{ + if ( m_hRagdollShadow ) + { + if ( GetContainingEntity( edict() ) ) + { + if ( moveplayertofinalspot ) + { + // Move player to resting spot of shadow object + FollowClientRagdoll(); + + // Check for a valid standing position. If an entity is blocking impart some + // velocity to them and check again. + trace_t trace; + if ( CheckRagdollToStand( trace ) ) + { + // Switch back to normal movement and kill off ragdoll bone setup on client + SetMoveType( MOVETYPE_WALK ); + m_nRenderFX = kRenderFxNone; + //RemoveSolidFlags( FSOLID_NOTSOLID ); + Assert( GetPlayerClass() != NULL ); + Vector vecMin, vecMax; + GetPlayerClass()->GetPlayerHull( ( ( GetFlags() & FL_DUCKING ) == 1 ), vecMin, vecMax ); + UTIL_SetSize( this, vecMin, vecMax ); + } + else + { + CBaseEntity *pEntity = trace.m_pEnt; + if ( pEntity != GetContainingEntity( INDEXENT( 0 ) ) ) + { + // Check for a physics object and apply force! + IPhysicsObject *pPhysObject = pEntity->VPhysicsGetObject(); + if ( pPhysObject ) + { + Vector vecDirection( random->RandomFloat( 0.0f, 1.0f ), + random->RandomFloat( 0.0f, 1.0f ), + random->RandomFloat( 0.0f, 1.0f ) ); + vecDirection *= 40000.0f; + pPhysObject->ApplyForceCenter( vecDirection ); + } + + return false; + } + else + { + UTIL_SetOrigin( this, Vector( m_vecLastGoodRagdollPos.x, m_vecLastGoodRagdollPos.y, m_vecLastGoodRagdollPos.z + 18.0f ) ); + return false; + } + } + } + } + // Kill the shadow object + UTIL_Remove( m_hRagdollShadow ); + m_hRagdollShadow = NULL; + } + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CBaseTFPlayer::CheckRagdollToStand( trace_t &trace ) +{ + Assert( GetPlayerClass() != NULL ); + Vector vecMin, vecMax; + GetPlayerClass()->GetPlayerHull( ( ( GetFlags() & FL_DUCKING ) == 1 ), vecMin, vecMax ); + + // Write this better -- this is just a test to get things started. + UTIL_TraceHull( + m_vecLastGoodRagdollPos + Vector( 0, 0, 18 ), + m_vecLastGoodRagdollPos, + vecMin, + vecMax, + MASK_PLAYERSOLID, + m_hRagdollShadow, + COLLISION_GROUP_NONE, + &trace ); + + if ( !trace.allsolid ) + return true; + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: Start being a ragdoll, creates client ragdoll object and server +// physics shadow object +// Input : &force - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CBaseTFPlayer::BecomeRagdollOnClient( const Vector &force ) +{ + // Defender doesn't support it yet + if ( PlayerClass() == TFCLASS_INFILTRATOR ) + return false; + + // Initialize the good ragdoll position. + VectorCopy( GetAbsOrigin(), m_vecLastGoodRagdollPos ); + + bool bret = BaseClass::BecomeRagdollOnClient( force ); + + // ROBIN: Disabled ragdoll shadows for now. + // We'll re-enable them if we need to know the end position again + // If we re-enable them, we need to fix the ragdoll shadow not having the correct mass + return bret; + + AddSolidFlags( FSOLID_NOT_SOLID ); + + // Clear any old shadow object ( should never occur ) + ClearClientRagdoll( false ); + + // Create new shadow object + m_hRagdollShadow = CRagdollShadow::Create( this, force ); + + return bret; +} + +//========================================================= +// AddGesture - add a gesture into the animation queue +//========================================================= +int CBaseTFPlayer::AddGesture( Activity activity, bool autokill /*= true*/ ) +{ + int layer = BaseClass::AddGesture( activity, autokill ); + SetLayerBlendIn( layer, 0.0 ); + SetLayerBlendOut( layer, 0.0 ); + return layer; +} + +//----------------------------------------------------------------------------- +// Purpose: Class specific touch functionality! +//----------------------------------------------------------------------------- +void CBaseTFPlayer::ClassTouch( CBaseEntity *pTouched ) +{ + if ( m_pfnClassTouch && HasClass() ) + { + (GetPlayerClass()->*m_pfnClassTouch)( pTouched ); + } +} + +const char* CBaseTFPlayer::GetClassModelString( int iClass, int iTeam ) +{ + return m_PlayerClasses.GetPlayerClass( iClass )->GetClassModelString( iTeam ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : bRampage - +//----------------------------------------------------------------------------- +void CBaseTFPlayer::SetRampage( bool bRampage ) +{ + m_bRampage = bRampage; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CBaseTFPlayer::IsInRampage( void ) +{ + return m_bRampage; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseTFPlayer::SetPlayerClass( TFClass iClass ) +{ + if ( m_iPlayerClass != iClass ) + { + m_Timer.End(); + + if ( m_iPlayerClass >= 0 && m_iPlayerClass < TFCLASS_CLASS_COUNT ) + { + void AddPlayerClassTime( int classnum, float seconds ); + + AddPlayerClassTime( m_iPlayerClass, m_Timer.GetDuration().GetSeconds() ); + } + } + + if ( m_iPlayerClass >= 0 ) + { + if ( GetPlayerClass() ) + { + GetPlayerClass()->ClassDeactivate(); + } + } + + m_iPlayerClass = iClass; + + if ( m_iPlayerClass >= 0 ) + { + SetPlayerModel(); + + m_Timer.Start(); + + if ( GetPlayerClass() ) + { + GetPlayerClass()->ClassActivate(); + // Setup the class on initial spawn + GetPlayerClass()->CreateClass(); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int CBaseTFPlayer::ClassCostAdjustment( ResupplyBuyType_t nType ) +{ + if ( m_iPlayerClass >= 0 ) + { + return GetPlayerClass()->ClassCostAdjustment( nType ); + } + + return 0; +} + +//============================================================================= +// +// Player Physics Shadow Code +// +class CPhysicsTFPlayerCallback : public IPhysicsPlayerControllerEvent +{ +public: + int ShouldMoveTo( IPhysicsObject *pObject, const Vector &position ) + { + CBaseTFPlayer *pPlayer = ( CBaseTFPlayer* )pObject->GetGameData(); + if ( pPlayer->TouchedPhysics() ) + { + return 0; + } + return 1; + } +}; + +static CPhysicsTFPlayerCallback TFPlayerCallback; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseTFPlayer::InitVCollision( void ) +{ + if ( GetPlayerClass() ) + { + GetPlayerClass()->InitVCollision(); + } + + // Setup the HL2 specific callback. + if ( GetPhysicsController() ) + { + GetPhysicsController()->SetEventHandler( &TFPlayerCallback ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Return the entity that should receive the score +//----------------------------------------------------------------------------- +CBasePlayer *CBaseTFPlayer::GetScorer( void ) +{ + return this; +} + +//----------------------------------------------------------------------------- +// Purpose: Return the entity that should get assistance credit +//----------------------------------------------------------------------------- +CBasePlayer *CBaseTFPlayer::GetAssistant( void ) +{ + // If I'm in a vehicle, the builder gets credit + if ( IsInAVehicle() ) + { + CBaseObject *pObject = dynamic_cast<CBaseObject*>( GetVehicle() ); + if ( pObject ) + { + CBasePlayer *pBuilder = pObject->GetBuilder(); + if ( pBuilder && pBuilder != this ) + return pBuilder; + } + } + + // If I'm boosted, someone's getting the assist + if ( HasPowerup( POWERUP_BOOST ) && m_hLastBoostEntity.Get() ) + { + // I may have boosted myself + if ( m_hLastBoostEntity.Get() != this ) + { + if ( m_hLastBoostEntity->IsPlayer() ) + return (CBasePlayer*)m_hLastBoostEntity.Get(); + + // If it's an object, give the builder the assist (i.e. buff station) + CBaseObject *pObject = dynamic_cast<CBaseObject*>( m_hLastBoostEntity.Get() ); + if ( pObject ) + { + CBasePlayer *pBuilder = pObject->GetBuilder(); + if ( pBuilder && pBuilder != this ) + return pBuilder; + } + } + } + + return NULL; +} diff --git a/game/server/tf2/tf_player.h b/game/server/tf2/tf_player.h new file mode 100644 index 0000000..5986872 --- /dev/null +++ b/game/server/tf2/tf_player.h @@ -0,0 +1,596 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef TF_PLAYER_H +#define TF_PLAYER_H +#ifdef _WIN32 +#pragma once +#endif + +#include "player.h" +#include "tf_shareddefs.h" +#include "tf_playerlocaldata.h" +#include "tf_playerclass.h" +#include "iscorer.h" +#include "tf_playeranimstate.h" +#include "vphysics/player_controller.h" + +#define MENU_STRING_BUFFER_SIZE 512 +#define MENU_MSG_TEXTCHUNK_SIZE 50 +#define MENU_UPDATETIME 2.0 + +class CVehicleTeleportStation; +class CPlayerClass; +class CThrownGrenade; +class CFlybyPoint; +class CMenu; +class CControlZone; +class CTechnologyTree; +class CTFTeam; +class CWeaponBuilder; +class CWeaponCombatShield; +class COrder; +class CBaseTFCombatWeapon; +class CResourceZone; +class CRagdollShadow; +class CBaseObject; +class CBasePredictedWeapon; +class IVehicle; +enum ResupplyReason_t; + +// If disguised player fires weapon, suppress disguise for this long +#define DISGUISE_FIRE_SUPPRESSTIME 4.0f +// If there are enemies within this radius, then +// firing your weapon will cause you to lose your +// disguise for DISGUISE_FIRE_SUPPRESSTIME +#define DISGUISE_SUPPRESSION_RADIUS 1024.0f +// Can only initiate disguise if no enemies within this radius +#define DISGUISE_START_RADIUS 512.0f + +// Adrenalin +#define ADRENALIN_DAMAGE_REDUCTION 0.5 // Damage reduction during adrenalin +#define ADRENALIN_SPEED_INCREASE 1.2 // Movement speed increase during adrenalin +#define ADRENALIN_RAMPAGE_EXTEND 5.0 // Time gained from killing an enemy during rampage + +// ID +#define MAX_ID_RANGE 2048 + +// Setup for class specific touch functions. +#define SetClassTouch( _player, a ) _player->SetTouch( CBaseTFPlayer::ClassTouch ); _player->m_pfnClassTouch = static_cast<void (CPlayerClass::*)(CBaseEntity*)>(a) + +//===================================================================== +// TF PLAYER +//===================================================================== +class CBaseTFPlayer : public CBasePlayer, public IScorer +{ + typedef CBasePlayer BaseClass; +public: + DECLARE_CLASS( CBaseTFPlayer, CBasePlayer ); + DECLARE_SERVERCLASS(); + DECLARE_DATADESC(); + DECLARE_PREDICTABLE(); + + CBaseTFPlayer(); + ~CBaseTFPlayer(); + + // For updating hud tech tree + struct tfplayertech_t + { + int m_nAvailable; + int m_nUserCount; + int m_nResourceLevel; + }; + + // Helper to get a CBaseTFPlayer by its entity index. + static CBaseTFPlayer* Instance( int iEnt ) + { + return dynamic_cast< CBaseTFPlayer* >( CBaseEntity::Instance( INDEXENT( iEnt ) ) ); + } + + bool IsHidden() const; + void SetHidden( bool bHidden ); + + // Class specific touch functions. + void ClassTouch( CBaseEntity *pTouched ); + + // This normally wouldn't go in here but we have to access CAllPlayerClasses and that is private. + const char* GetClassModelString( int iClass, int iTeam ); + + // The player class touch function + void (CPlayerClass ::*m_pfnClassTouch)( CBaseEntity *pOther ); + + // Override CBaseAnimatingOverlay to zero out m_dweights by default + virtual int AddGesture( Activity activity, bool autokill = true ); + + // Hacky shield stuff for now + void RemoveShieldOverlays( void ); + int RemoveShieldOverlaysExcept( Activity activity, bool addifnotpresent = true ); + Activity ShieldTranslateActivity( Activity activity ); + void PickShieldAnimation( Activity& activity, int& overlayindex, + bool moving, bool ducked, + Activity overlay, Activity crouch, Activity normal, + bool autokill = true, float blendin = 0.0f, float blendout = 0.0f ); + + // True if player was moving last time checked (used to switch between shield full body and overlay anims ) + bool m_bWasMoving; + + virtual void UpdateOnRemove( void ); + + static CBaseTFPlayer *CreatePlayer( const char *className, edict_t *ed ) + { + CBaseTFPlayer::s_PlayerEdict = ed; + return (CBaseTFPlayer*)CreateEntityByName( className ); + } + + virtual int UpdateTransmitState(); + virtual int ShouldTransmit( const CCheckTransmitInfo *pInfo ); + // Networking is about to update this player, let it override and specify it's own pvs + virtual void SetupVisibility( CBaseEntity *pViewEntity, unsigned char *pvs, int pvssize ); + + virtual void Spawn( void ); + virtual void InitialSpawn( void ); + virtual void ForceRespawn( void ); + virtual void UpdateClientData( void ); + + virtual void ForceClientDllUpdate( void ); // Forces all client .dll specific data to be resent to client. + + virtual void Precache( void ); + + virtual void InitHUD( void ); + virtual void SelectItem( const char *pstr, int iSubType = 0 ); + + virtual void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + virtual int ObjectCaps( void ); + + // Override + virtual bool CanSpeak( void ); + + void ResetViewOffset( void ); + + // Input handlers + void InputRespawn( inputdata_t &inputdata ); + + // Team Handling + CTFTeam *GetTFTeam( void ) { return (CTFTeam*)GetTeam(); }; + CTechnologyTree *GetTechTree( void ); + virtual void ChangeTeam( int iTeamNum ) OVERRIDE; + void PlacePlayerInTeam( void ); + + // Class Handling + int PlayerClass( void ); + bool IsClass( TFClass iClass ); + bool IsSameClass( CBaseTFPlayer* pPlayer ) { Assert(pPlayer); return IsClass( (TFClass)pPlayer->PlayerClass() ); } + + void ChangeClass( TFClass iClass ); + bool IsClassAvailable( TFClass iClass ); + void SetPlayerModel( void ); + CPlayerClass *GetPlayerClass(); + void ClearPlayerClass( void ); + void SetPlayerClass( TFClass iClass ); + + int ClassCostAdjustment( ResupplyBuyType_t nType ); + + // Menu Handling + void MenuDisplay( void ); + bool MenuInput( int iInput ); + void MenuReset( void ); + + // Standard functions + virtual void ItemPostFrame(); + + virtual void Jump( void ); + + void PostThink( void ); + void PreThink( void ); + void PlayerRespawn( void ); + CBaseEntity *EntSelectSpawnPoint( void ); + + // Death + virtual void DeathSound( const CTakeDamageInfo &info ); + virtual void PainSound( const CTakeDamageInfo &info ); + virtual void Event_Killed( const CTakeDamageInfo &info ); + void TFPlayerDeathThink( void ); + + virtual void CheatImpulseCommands( int iImpulse ); + virtual bool ClientCommand( const CCommand &args ); + virtual void SetAnimation( PLAYER_ANIM playerAnim ); + + // Combat + void RecalculateSpeed( void ); + void KilledPlayer( CBaseTFPlayer *pVictim ); + + int TakeHealth( float flHealth, int bitsDamageType ); + + // Combat + virtual int OnTakeDamage( const CTakeDamageInfo &info ); + virtual void TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr ); + void ShowPersonalShieldEffect( const Vector &vOffsetFromEnt, const Vector &vIncomingDirection, float flDamage ); + + // Objects + void AddObject( CBaseObject *pObject ); + void RemoveObject( CBaseObject *pObject ); + int CanBuild( int iObjectType ); + int GetNumObjects( int iObjectType ); + int GetObjectCount( ); + CBaseObject *GetObject( int iObjectIndex ); + bool IsBuilding( void ); + void OwnedObjectDestroyed( CBaseObject *pObject ); + void StopPlacement( void ); + int StartedBuildingObject( int iObjectType ); + void StoppedBuilding( int iObjectType ); + void FinishedObject( CBaseObject *pObject ); + void SetWeaponBuilder( CWeaponBuilder *pBuilder ); + CWeaponBuilder *GetWeaponBuilder( void ); + CWeaponCombatShield *GetCombatShield( void ); + void OwnedObjectChangeTeam( CBaseObject *pObject, CBaseTFPlayer *pNewOwner ); + void RemoveAllObjects( bool bForceAll = false, int iClass = 0, bool bReturnResources = false ); + + int NumPumpsOnResourceZone( CResourceZone *pZone ); + + // Deployment + void StartDeploying( void ); + void StartUnDeploying( void ); + void CheckDeployFinish( void ); + void FinishDeploying( void ); + void FinishUnDeploying( void ); + bool IsDeployed( void ); + bool IsDeploying( void ); + bool IsUnDeploying( void ); + + // Movement prevention + bool CantMove( void ); + void SetCantMove( bool bCantMove ); + + bool HasNamedTechnology( const char *name ); + void SetPreferredTechnology( CTechnologyTree *pTechnologyTree, int iTechIndex ); + int GetPreferredTechnology( void ); + tfplayertech_t &AvailableTech( int i ) { return m_rgClientTechAvail[i]; } + + // Powerups + virtual void PowerupStart( int iPowerup, float flAmount, CBaseEntity *pAttacker, CDamageModifier *pDamageModifier ); + virtual void PowerupEnd( int iPowerup ); + virtual float PowerupDuration( int iPowerup, float flTime ); + + void SetRampage( bool bRampage ); + float GetDefaultAnimSpeed( void ); + + // Knockdown + void KnockDownPlayer( const Vector& sourceDir, float velocity, float duration ); + bool IsKnockedDown( void ); + + // Resources + void AddBankResources( int iAmount ); + void RemoveBankResources( int iAmount, bool bSpent = true ); + void DonateResources( CBaseTFPlayer *pTarget, int pCount ); + int GetBankResources( void ); + void SetBankResources( int iAmount ); + + // Check for a collision.... + bool IsHittingShield( const Vector &vecVelocity, float *flDamage ); + + // Combat prototyping + bool IsBlocking( void ) const { return m_bIsBlocking; } + bool IsParrying( void ) const { return m_bIsParrying; } + void SetBlocking( bool bBlocking ) { m_bIsBlocking = bBlocking; } + void SetParrying( bool bParrying ) { m_bIsParrying = bParrying; } + + // Thermal Vision + bool IsUsingThermalVision( void ); + void SetUsingThermalVision( bool thermal ); + + CTFPlayerLocalData *GetLocalData() { return &m_TFLocal; } + + // Acts + void CleanupOnActStart( void ); + + // Resource chunks + bool AddResourceChunks( int iChunks, bool bProcessed ); + void RemoveResourceChunks( int iChunks, bool bProcessed ); + int GetTotalResourceChunks( void ); + int GetResourceChunkCount( bool bProcessed ); + + void SetGagged( bool gag ); + + // Control zone + CControlZone* GetCurrentZone( ) { return m_pCurrentZone; } + void SetCurrentZone( CControlZone* pZone ) { m_pCurrentZone = pZone; } + + // Camouflage + float GetCamouflageAmount( void ) { return m_flCamouflageAmount; }; + bool IsCamouflaged( void ); + void SetCamouflaged( int percentage, float changerate ); + void ClearCamouflage( void ); + + float LastAttackTime() const { return m_flLastAttackTime; } + void SetLastAttackTime( float flTime ) { m_flLastAttackTime = flTime; } + + bool ResupplyAmmo( float flFraction, ResupplyReason_t reason ); + + // The last time we were damaged by an enemy. + double LastTimeDamagedByEnemy() const { return m_flLastTimeDamagedByEnemy; } + + // Orders + COrder* GetOrder( void ) { return m_hSelectedOrder; } + void SetOrder( COrder *pOrder ); + + // Count resource zone orders. + int GetNumResourceZoneOrders(); + + // Reinforcement + bool IsReadyToReinforce( void ); + void Reinforce( void ); + + void ShowTacticalView( bool bTactical ); + + void SetRespawnStation( CBaseEntity *pStation ); + + // ID + void SetIDEnt( CBaseEntity *pEntity ); + + // Health boosts + void TakeHealthBoost( int iHealthBoost, int iTarget, int iDuration ); + + // Vehicle + bool CanGetInVehicle( void ); + + CVehicleTeleportStation* GetSelectedMCV() const; + void SetSelectedMCV( CVehicleTeleportStation *pMCV ); + + // Weapon handling + virtual bool Weapon_ShouldSetLast( CBaseCombatWeapon *pOldWeapon, CBaseCombatWeapon *pNewWeapon ); + virtual bool Weapon_ShouldSelectItem( CBaseCombatWeapon *pWeapon ); + virtual CBaseCombatWeapon *GetLastWeaponBeforeObject( void ) { return m_hLastWeaponBeforeObject; } + + // Sapper attaching + bool IsAttachingSapper( void ); + float GetSapperAttachmentTime( void ); + void StartAttachingSapper( CBaseObject *pObject, CGrenadeObjectSapper *pSapper ); + void CleanupAfterAttaching( void ); + void StopAttaching( void ); + void FinishAttaching( void ); + void CheckSapperAttaching( void ); + +// IScorer +public: + // Return the entity that should receive the score + virtual CBasePlayer *GetScorer( void ); + // Return the entity that should get assistance credit + virtual CBasePlayer *GetAssistant( void ); + +public: + // FIXME: Make these private + + // Menu-related goodies + CMenu *m_pCurrentMenu; + char m_MenuStringBuffer[MENU_STRING_BUFFER_SIZE]; + int m_MenuSelectionBuffer; + float m_MenuRefreshTime; + float m_MenuUpdateTime; + + bool m_bBuffHealthBoost; + + // Object sapper placement handling + float m_flSapperAttachmentFinishTime; + float m_flSapperAttachmentStartTime; + CHandle< CGrenadeObjectSapper > m_hSapper; + CHandle< CBaseObject > m_hSappedObject; + +private: + // Medic Buffs + void CheckBuffs( void ); + void RemoveHealthBoost( void ); + + // vehicles + void OnVehicleStart(); + void OnVehicleEnd( Vector & ); + + bool IsInTacticalView( void ) const; + + // Get rid of resource zone orders. + void KillResourceZoneOrders(); + + // Knockdowns + void ResetKnockdown( void ); + void CheckKnockdown( void ); + + // Gagging + bool IsGagged( void ); + + void DropAllResourceChunks( void ); + + // Thinking + void CheckCamouflage( void ); + + // Rampage + bool IsInRampage( void ); + + // Vertification + inline bool HasClass( void ) { return GetPlayerClass() != NULL; } + + // Respawn stations + CBaseEntity *GetInitialSpawnPoint( void ); + + // Go ragdoll, create shadow object, etc. + bool BecomeRagdollOnClient( const Vector &force ); + // Remove ragdoll + bool ClearClientRagdoll( bool moveplayertofinalspot ); + // Move origin in sync with shadow object if ragdolling + void FollowClientRagdoll( void ); + bool CheckRagdollToStand( trace_t &trace ); + bool IsClientRagdoll() const { return m_hRagdollShadow.Get() != 0; } + + // Deployed? + bool IsDeployed() const { return m_bDeployed; } + + + void StoreCycle( void ); + float RetrieveCycle( void ); + + bool ShouldMatchCycles( Activity newActivity, Activity currentActivity ); + + // TF Player Physics Shadow + void InitVCollision( void ); + + void ApplyDamageForce( const CTakeDamageInfo &info, int nDamageToDo ); + + // Movement. + Vector m_vecPosDelta; + + enum { MOMENTUM_MAXSIZE = 10 }; + float m_aMomentum[MOMENTUM_MAXSIZE]; + int m_iMomentumHead; + + // Spawn spot + CNetworkHandle( CBaseEntity, m_hSpawnPoint ); + + // This player's TF2 specific data that should only be replicated to + // the player and not to other players. + CNetworkVarEmbedded( CTFPlayerLocalData, m_TFLocal ); + IMPLEMENT_NETWORK_VAR_FOR_DERIVED( m_iMaxHealth ); // Make sure this ent is marked as changed when m_iMaxHealth changes. + + CHandle< CWeaponBuilder > m_hWeaponBuilder; + CHandle< CWeaponCombatShield > m_hWeaponCombatShield; + float m_flKnockdownEndTime; + + // Weapon used before switching to an object placement + CHandle<CBaseCombatWeapon> m_hLastWeaponBeforeObject; + + CNetworkQAngle( m_vecDeployedAngles ); + + // Tracks when medics are boosting the damage this guy applies. + int m_nMedicDamageBoosts; + + CNetworkVar( int, m_TFPlayerFlags ); // Combination of TF_PLAYER_ flags. + + bool m_bCantMove; + + bool m_bGagged; + + bool m_bFirstTeamSpawn; // When true, the player's joined the server but not picked a team for the first time + + float m_flLastAttackTime; + + // Camouflage + CNetworkVar( float, m_flCamouflageAmount ); + + // Camouflage state machine + float m_flGoalCamouflageAmount; + float m_flGoalCamouflageChangeRate; + + // State information + bool m_bSwitchingView; + + // Zone the player's currently in + CControlZone *m_pCurrentZone; + CNetworkVar( int, m_iCurrentZoneState ); + + // Accuracy + bool m_bSnapAccuracy; + float m_flAccuracy; + float m_flTargetAccuracy; + float m_flLastRicochetNearby; + float m_flNumberOfRicochets; + float m_flLastExplosionNearby; + + // Buff states + int m_iHealthBoostTarget; + float m_flHealthBoostDecrement; + float m_flHealthBoostTime; + + EHANDLE m_hNextPlayerToUpdateOnRadar; + + CNetworkHandle( CBaseEntity, m_hSelectedMCV ); + + // Orders + CHandle< COrder > m_hSelectedOrder; + + // Tech spending vote preference + int m_nPreferredTechnology; + + // The last time we were damaged by an enemy. + double m_flLastTimeDamagedByEnemy; + + // Menu Handling + float m_MenuDisplayTime; + + // Rampage + bool m_bRampage; // Do I get adrenalin extension for killing enemies? + + // Handheld shield + CNetworkVar( bool, m_bIsBlocking ); + bool m_bIsParrying; + + float m_flTimeOfDeath; + + EHANDLE m_hLastBoostEntity; // Last entity that gave me a power boost + + // Deployment + CNetworkVar( bool, m_bDeployed ); + CNetworkVar( bool, m_bDeploying ); + CNetworkVar( bool, m_bUnDeploying ); + float m_flFinishedDeploying; + + CNetworkVar( int, m_iPlayerClass ); + CAllPlayerClasses m_PlayerClasses; + + // This times how long each class is active. + CFastTimer m_Timer; + + tfplayertech_t m_rgClientTechAvail[ MAX_TECHNOLOGIES ]; + + CHandle< CRagdollShadow > m_hRagdollShadow; + Vector m_vecLastGoodRagdollPos; + + // Last reinforcment second counter for player ( so we don't spam the player + // with text every frame ) + int m_iLastSecondsToGo; + + CPlayerAnimState m_PlayerAnimState; + + // Fractional boost amount + float m_flFractionalBoost; + + float m_flStoredCycle; + + + // Are we under attack? + CNetworkVar( bool, m_bUnderAttack ); + + friend void* SendProxy_RideVehicle( const void *pStruct, const void *pData, CSendProxyRecipients *pRecipients, int objectID ); + friend class CTFPlayerMove; +}; + +inline CBaseTFPlayer *ToBaseTFPlayer( CBaseEntity *pEntity ) +{ + if ( !pEntity || !pEntity->IsPlayer() ) + return NULL; + + return dynamic_cast<CBaseTFPlayer *>( pEntity ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +class CPhysicsPlayerCallback : public IPhysicsPlayerControllerEvent +{ +public: + + int ShouldMoveTo( IPhysicsObject *pObject, const Vector &position ) + { + CBaseTFPlayer *pPlayer = ( CBaseTFPlayer* )pObject->GetGameData(); + if ( pPlayer->TouchedPhysics() ) + { + return 0; + } + return 1; + } +}; + +extern CPhysicsPlayerCallback playerCallback; + +#endif // TF_PLAYER_H diff --git a/game/server/tf2/tf_player_death.cpp b/game/server/tf2/tf_player_death.cpp new file mode 100644 index 0000000..da90f9e --- /dev/null +++ b/game/server/tf2/tf_player_death.cpp @@ -0,0 +1,230 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: CBaseTFPlayer functions dealing with death and reinforcement +// +// $Workfile: $ +// $Date: $ +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "player.h" +#include "tf_player.h" +#include "gamerules.h" +#include "basecombatweapon.h" +#include "EntityList.h" +#include "tf_shareddefs.h" +#include "tf_team.h" +#include "baseviewmodel.h" +#include "tf_class_infiltrator.h" +#include "in_buttons.h" +#include "globals.h" + +int g_iNumberOfCorpses; + +//----------------------------------------------------------------------------- +// Purpose: The player was just killed +//----------------------------------------------------------------------------- +void CBaseTFPlayer::Event_Killed( const CTakeDamageInfo &info ) +{ + // TODO don't use temp entities to transmit messages + CPASFilter filter( GetLocalOrigin() ); + te->KillPlayerAttachments( filter, 0.0, entindex() ); + + // Remove the player from any vehicle they're in + if ( IsInAVehicle() ) + { + LeaveVehicle(); + } + + // Holster weapon immediately, to allow it to cleanup + if (GetActiveWeapon()) + { + GetActiveWeapon()->Holster( ); + } + + // Stop attaching sappers + if ( IsAttachingSapper() ) + { + StopAttaching(); + } + + // stop them touching anything + AddFlag( FL_DONTTOUCH ); + + g_pGameRules->PlayerKilled( this, info ); + + ClearUseEntity(); + + // If I'm ragdolling due to a knockdown, don't play any animations + if ( m_hRagdollShadow == NULL ) + { + if ( PlayerClass() != TFCLASS_INFILTRATOR ) + { + // Calculate death force + Vector forceVector = CalcDamageForceVector( info ); + + BecomeRagdollOnClient( forceVector ); + } + else + { + SetAnimation( PLAYER_DIE ); + } + } + + DeathSound( info ); + + SetViewOffset( VEC_DEAD_VIEWHEIGHT_SCALED( this ) ); + m_lifeState = LIFE_DYING; + pl.deadflag = true; + + // Enter dying state + AddSolidFlags( FSOLID_NOT_SOLID ); + SetMoveType( MOVETYPE_NONE ); + QAngle angles = GetLocalAngles(); + angles.x = angles.z = 0; + SetLocalAngles( angles ); + m_takedamage = DAMAGE_NO; + + // clear out the suit message cache so we don't keep chattering + SetSuitUpdate(NULL, false, 0); + + // reset FOV + SetFOV( this, 0 ); + + // Setup for respawn + m_flTimeOfDeath = gpGlobals->curtime; + + SetThink(TFPlayerDeathThink); + SetNextThink( gpGlobals->curtime + 0.1f ); + + SetPowerup(POWERUP_EMP,false); + + // Tell the playerclass that the player died + if ( GetPlayerClass() ) + { + GetPlayerClass()->PlayerDied( info.GetAttacker() ); + } + + // Tell the attacker's playerclass that he killed someone + if ( info.GetAttacker() && info.GetAttacker()->IsPlayer() ) + { + CBaseTFPlayer *pPlayerAttacker = (CBaseTFPlayer*)info.GetAttacker(); + pPlayerAttacker->KilledPlayer( this ); + } + + DropAllResourceChunks(); + + // Tell all teams to update their orders + COrderEvent_PlayerKilled order( this ); + GlobalOrderEvent( &order ); +} + +//----------------------------------------------------------------------------- +// Purpose: Think function for dead/dying players. +// Play their death animation, then set up for reinforcement. +//----------------------------------------------------------------------------- +void CBaseTFPlayer::TFPlayerDeathThink(void) +{ + float flForward; + + SetNextThink( gpGlobals->curtime + 0.1f ); + + if ( GetFlags() & FL_ONGROUND ) + { + flForward = GetAbsVelocity().Length() - 20; + if (flForward <= 0) + { + SetAbsVelocity( vec3_origin ); + } + else + { + Vector vecNewVelocity = GetAbsVelocity(); + VectorNormalize( vecNewVelocity ); + vecNewVelocity *= flForward; + SetAbsVelocity( vecNewVelocity ); + } + } + + StudioFrameAdvance(); + + if (GetModelIndex() && (!IsSequenceFinished()) && (m_lifeState == LIFE_DYING)) + { + m_iRespawnFrames++; + if ( m_iRespawnFrames < 60 ) // animations should be no longer than this + return; + } + + // Start looping dying state + SetAnimation( PLAYER_DIE ); + + // ROBIN: Everyone respawns immediately now. Maps will define respawns in the future. + + if ( (gpGlobals->curtime - m_flTimeOfDeath) < 3 ) + return; + + m_lifeState = LIFE_RESPAWNABLE; + + // Respawn on button press, but not if they're checking the scores + // Also respawn if they're not looking at scores, and they've been dead for over 5 seconds + bool bButtonDown = (m_nButtons & ~IN_SCORE) > 0; + if ( (bButtonDown || (gpGlobals->curtime - m_flTimeOfDeath) > 5 ) ) + { + PlayerRespawn(); + } + + /* + // Aliens become respawnable immediately, because they're waiting for a reinforcement wave. + // Humans have to wait a short time. + if ( (GetTeamNumber() == TEAM_HUMANS) && (gpGlobals->curtime - m_flTimeOfDeath) < 3 ) + return; + if ( (GetTeamNumber() == TEAM_ALIENS) && (gpGlobals->curtime - m_flTimeOfDeath) < 1 ) + return; + + // Humans can respawn, Aliens can't (reinforcement wave for their kind) + // Aliens stop thinking now and wait for the reinforcement wave + if ( GetTeamNumber() == TEAM_ALIENS ) + { + m_lifeState = LIFE_RESPAWNABLE; + SetThink( NULL ); + } + else + { + m_lifeState = LIFE_RESPAWNABLE; + + // Respawn on button press + if ( m_nButtons & ~IN_SCORE ) + { + PlayerRespawn(); + } + } + */ +} + +//----------------------------------------------------------------------------- +// Purpose: Return true if this player is ready to reinforce +//----------------------------------------------------------------------------- +bool CBaseTFPlayer::IsReadyToReinforce( void ) +{ + // Only Aliens reinforce in waves, humans respawn normally + if ( (GetTeamNumber() == TEAM_ALIENS) && (m_lifeState == LIFE_RESPAWNABLE) ) + return true; + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: Bring the player back to life in a reinforcement wave +//----------------------------------------------------------------------------- +void CBaseTFPlayer::Reinforce( void ) +{ + // Tell all teams to update their orders + COrderEvent_PlayerRespawned order( this ); + GlobalOrderEvent( &order ); + + StopAnimation(); + IncrementInterpolationFrame(); + m_flPlaybackRate = 0.0; + + PlayerRespawn(); +} + diff --git a/game/server/tf2/tf_player_resource.cpp b/game/server/tf2/tf_player_resource.cpp new file mode 100644 index 0000000..ece062a --- /dev/null +++ b/game/server/tf2/tf_player_resource.cpp @@ -0,0 +1,35 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: TF's custom CPlayerResource +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "player.h" +#include "player_resource.h" +#include "tf_player_resource.h" + +// Datatable +IMPLEMENT_SERVERCLASS_ST(CTFPlayerResource, DT_TFPlayerResource) +END_SEND_TABLE() + +LINK_ENTITY_TO_CLASS( tf_player_manager, CTFPlayerResource ); + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFPlayerResource::Spawn( void ) +{ + BaseClass::Spawn(); + + // Use autodetect, but only once every second. + NetworkProp()->SetUpdateInterval( 2.0f ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFPlayerResource::UpdatePlayerData( void ) +{ + BaseClass::UpdatePlayerData(); +} diff --git a/game/server/tf2/tf_player_resource.h b/game/server/tf2/tf_player_resource.h new file mode 100644 index 0000000..0ccbe83 --- /dev/null +++ b/game/server/tf2/tf_player_resource.h @@ -0,0 +1,28 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: TF's custom CPlayerResource +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef TF_PLAYER_RESOURCE_H +#define TF_PLAYER_RESOURCE_H +#ifdef _WIN32 +#pragma once +#endif + +#include "player_resource.h" + +class CTFPlayerResource : public CPlayerResource +{ + DECLARE_CLASS( CTFPlayerResource, CPlayerResource ); +public: + DECLARE_SERVERCLASS(); + + virtual void Spawn( void ); + virtual void UpdatePlayerData( void ); + +public: +}; + +#endif // TF_PLAYER_RESOURCE_H diff --git a/game/server/tf2/tf_playerclass.cpp b/game/server/tf2/tf_playerclass.cpp new file mode 100644 index 0000000..5113435 --- /dev/null +++ b/game/server/tf2/tf_playerclass.cpp @@ -0,0 +1,1172 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +/* + +===== tf_playerclass.cpp ======================================================== + + functions dealing with the TF playerclasses + +*/ +#include "cbase.h" +#include "player.h" +#include "basecombatweapon.h" +#include "tf_player.h" +#include "tf_obj.h" +#include "weapon_builder.h" +#include "tf_team.h" +#include "tf_func_resource.h" +#include "orders.h" +#include "order_repair.h" +#include "engine/IEngineSound.h" +#include "tier1/strtools.h" +#include "textstatsmgr.h" +#include "ammodef.h" +#include "weapon_objectselection.h" +#include "vcollide_parse.h" +#include "weapon_combatshield.h" +#include "tf_vehicle_tank.h" +#include "tf_obj_manned_plasmagun.h" +#include "tf_obj_manned_missilelauncher.h" + +extern char *g_pszEMPPulseStart; +extern ConVar tf_fastbuild; + + +// Stat groupings +enum +{ + STATS_TANK = TFCLASS_CLASS_COUNT, + STATS_MANNEDGUN_PLASMA, + STATS_MANNEDGUN_ROCKET, + + STATS_NUM_GROUPS, +}; + +char *sNonClassStatNames[] = +{ + "Tanks", + "Manned Plasmaguns", + "Manned Rocket launchers", +}; + +// Stats between player classes (like how many snipers killed recons). +class CInterClassStats +{ +public: + + CInterClassStats() + { + m_flTotalDamageInflicted = 0; + m_nKills = 0; + + m_flTotalEngagementDist = 0; + m_nEngagements = 0; + + m_flTotalNormalizedEngagementDist = 0; + m_nNormalizedEngagements = 0; + } + +public: + // + // Note: "containing class" refers to the class in the CPlayerClassStats that owns this CInterClassStats. + // So if there's a CPlayerClassStats for a recon, and it has a CInterClassStats for a sniper, + // then "containing class" refers to the recon. In this example, m_flTotalDamageInflicted representss + // how much damage the recon players inflicted on the snipers. + // + double m_flTotalDamageInflicted; // Total damage inflicted on this class by the containing class. + double m_flTotalEngagementDist; // Used to get the average engagement distance. + int m_nEngagements; + + double m_flTotalNormalizedEngagementDist; + int m_nNormalizedEngagements; + + int m_nKills; // Now many times the containing class killed this one. +}; + +class CPlayerClassStats +{ +public: + CPlayerClassStats() + { + m_flPlayerTime = 0; + } + +public: + + CInterClassStats m_InterClassStats[STATS_NUM_GROUPS]; + double m_flPlayerTime; // How much player time was spent in this class. +}; + + +ConVar tf2_object_hard_limits( "tf2_object_hard_limits","0", FCVAR_NONE, "If true, use hard object limits instead of resource costs" ); + +CPlayerClassStats g_PlayerClassStats[STATS_NUM_GROUPS]; + +void AddPlayerClassTime( int classnum, float seconds ) +{ + g_PlayerClassStats[ classnum ].m_flPlayerTime += seconds; +} + +int GetStatGroupFor( CBaseTFPlayer *pPlayer ) +{ + // In a vehicle? + if ( pPlayer->IsInAVehicle() ) + { + if ( dynamic_cast<CVehicleTank*>( pPlayer->GetVehicle() ) ) + return STATS_TANK; + if ( dynamic_cast<CObjectMannedMissileLauncher*>( pPlayer->GetVehicle() ) ) + return STATS_MANNEDGUN_ROCKET; + if ( dynamic_cast<CObjectMannedPlasmagun*>( pPlayer->GetVehicle() ) ) + return STATS_MANNEDGUN_PLASMA; + } + + // Otherwise, use the playerclass + return pPlayer->GetPlayerClass()->GetTFClass(); +} + +const char* GetGroupNameFor( int iStatGroup ) +{ + Assert( iStatGroup > TFCLASS_UNDECIDED && iStatGroup < STATS_NUM_GROUPS ); + if ( iStatGroup < TFCLASS_CLASS_COUNT ) + return GetTFClassInfo( iStatGroup )->m_pClassName; + else + return sNonClassStatNames[ iStatGroup - TFCLASS_CLASS_COUNT ]; +} + + +void PrintPlayerClassStats() +{ + IFileSystem *pFileSys = filesystem; + + FileHandle_t hFile = pFileSys->Open( "class_stats.txt", "wt", "LOGDIR" ); + if ( hFile == FILESYSTEM_INVALID_HANDLE ) + return; + + + pFileSys->FPrintf( hFile, "Class\tPlayer Time (minutes)\tAvg Engagement Dist\t(OLD) Engagement Dist\n" ); + for ( int i=TFCLASS_UNDECIDED+1; i < STATS_NUM_GROUPS; i++ ) + { + CPlayerClassStats *pStats = &g_PlayerClassStats[i]; + + + // Figure out the average engagement distance across all classes. + int j; + double flAvgEngagementDist = 0; + int nTotalEngagements = 0; + double flTotalEngagementDist = 0; + + double flTotalNormalizedEngagementDist = 0; + int nTotalNormalizedEngagements = 0; + + for ( j=TFCLASS_UNDECIDED+1; j < STATS_NUM_GROUPS; j++ ) + { + CInterClassStats *pInter = &g_PlayerClassStats[i].m_InterClassStats[j]; + + nTotalEngagements += pInter->m_nEngagements; + flTotalEngagementDist += pInter->m_flTotalEngagementDist; + + nTotalNormalizedEngagements += pInter->m_nNormalizedEngagements; + flTotalNormalizedEngagementDist += pInter->m_flTotalNormalizedEngagementDist; + } + flAvgEngagementDist = nTotalEngagements ? ( flTotalEngagementDist / nTotalEngagements ) : 0; + double flAvgNormalizedEngagementDist = nTotalNormalizedEngagements ? (flTotalNormalizedEngagementDist / nTotalNormalizedEngagements) : 0; + + + pFileSys->FPrintf( hFile, "%s", GetGroupNameFor( i ) ); + pFileSys->FPrintf( hFile, "\t%.1f", (pStats->m_flPlayerTime / 60.0f) ); + pFileSys->FPrintf( hFile, "\t%d", (int)flAvgNormalizedEngagementDist ); + pFileSys->FPrintf( hFile, "\t%d", (int)flAvgEngagementDist ); + pFileSys->FPrintf( hFile, "\n" ); + } + + pFileSys->FPrintf( hFile, "\n" ); + pFileSys->FPrintf( hFile, "\n" ); + + + pFileSys->FPrintf( hFile, "Class\tTarget Class\tTotal Damage\tKills\tAvg Engagement Dist\t(OLD) Engagement Dist\n" ); + + for ( i=TFCLASS_UNDECIDED+1; i < STATS_NUM_GROUPS; i++ ) + { + CPlayerClassStats *pStats = &g_PlayerClassStats[i]; + + // Print the inter-class stats. + for ( int j=TFCLASS_UNDECIDED+1; j < STATS_NUM_GROUPS; j++ ) + { + CInterClassStats *pInter = &pStats->m_InterClassStats[j]; + + pFileSys->FPrintf( hFile, "%s", GetGroupNameFor( i ) ); + pFileSys->FPrintf( hFile, "\t%s", GetGroupNameFor( j ) ); + pFileSys->FPrintf( hFile, "\t%d", (int)pInter->m_flTotalDamageInflicted ); + pFileSys->FPrintf( hFile, "\t%d", pInter->m_nKills ); + pFileSys->FPrintf( hFile, "\t%d", (int)(pInter->m_nNormalizedEngagements ? (pInter->m_flTotalNormalizedEngagementDist / pInter->m_nNormalizedEngagements) : 0) ); + pFileSys->FPrintf( hFile, "\t%d", (int)(pInter->m_nEngagements ? (pInter->m_flTotalEngagementDist / pInter->m_nEngagements) : 0) ); + pFileSys->FPrintf( hFile, "\n" ); + } + } + + pFileSys->Close( hFile ); +} + +CTextStatFile g_PlayerClassStatsOutput( PrintPlayerClassStats ); + + +// ---------------------------------------------------------------------------------------------------------------- // +// Detailed stats output. +// ---------------------------------------------------------------------------------------------------------------- // + +ConVar tf_DetailedStats( "tf_DetailedStats", "1", 0, "Prints extensive detailed gameplay stats into detailed_stats.txt" ); + +class CShotInfo +{ +public: + float m_flDistance; + int m_nDamage; +}; + +CUtlLinkedList<CShotInfo,int> g_ClassShotInfos[STATS_NUM_GROUPS]; + +void PrintDetailedPlayerClassStats() +{ + if ( !tf_DetailedStats.GetInt() ) + return; + + IFileSystem *pFileSys = filesystem; + + FileHandle_t hFile = pFileSys->Open( "class_stats_detailed.txt", "wt", "LOGDIR" ); + if ( hFile != FILESYSTEM_INVALID_HANDLE ) + { + // Print the header. + for ( int i=TFCLASS_UNDECIDED+1; i < STATS_NUM_GROUPS; i++ ) + { + pFileSys->FPrintf( hFile, "%s dist\t%s dmg\t", GetGroupNameFor( i ), GetGroupNameFor( i ) ); + } + pFileSys->FPrintf( hFile, "\n" ); + + // Write out each column. + int iterators[STATS_NUM_GROUPS]; + for ( i=TFCLASS_UNDECIDED+1; i < STATS_NUM_GROUPS; i++ ) + iterators[i] = g_ClassShotInfos[i].Head(); + + bool bWroteAnything; + do + { + bWroteAnything = false; + for ( int i=TFCLASS_UNDECIDED+1; i < STATS_NUM_GROUPS; i++ ) + { + if ( iterators[i] == g_ClassShotInfos[i].InvalidIndex() ) + { + pFileSys->FPrintf( hFile, "\t\t" ); + } + else + { + CShotInfo *pInfo = &g_ClassShotInfos[i][iterators[i]]; + iterators[i] = g_ClassShotInfos[i].Next( iterators[i] ); + + pFileSys->FPrintf( hFile, "%.2f\t%d\t", pInfo->m_flDistance, pInfo->m_nDamage ); + bWroteAnything = true; + } + } + pFileSys->FPrintf( hFile, "\n" ); + + } while ( bWroteAnything ); + + + pFileSys->Close( hFile ); + } +} + +CTextStatFile g_PlayerClassStatsDetailedOutput( PrintDetailedPlayerClassStats ); + + + +//===================================================================== +// PLAYER CLASS HANDLING +//===================================================================== +// Base PlayerClass +CPlayerClass::CPlayerClass( CBaseTFPlayer *pPlayer, TFClass iClass ) +: m_TFClass( iClass ) +{ + m_pPlayer = pPlayer; + + for (int i = 0; i <= MAX_TF_TEAMS; ++i) + { + m_sClassModel[i] = NULL_STRING; + } + m_iNumWeaponTechAssociations = 0; + + m_bTechAssociationsSet = false; + + m_flNormalizedEngagementNextTime = -1; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CPlayerClass::~CPlayerClass() +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPlayerClass::ClassActivate( void ) +{ + // Setup the default player movement variables. + SetupMoveData(); + + AddWeaponTechAssociations(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPlayerClass::ClassDeactivate( void ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPlayerClass::AddWeaponTechAssociations( void ) +{ + ClearAllWeaponTechAssoc(); + + // Iterate tech tree for this class and find + Assert( m_pPlayer ); + CTechnologyTree *tree = m_pPlayer->GetTechTree(); + Assert( tree ); + + // Loop through all of the techs to see if any of them say yes to being applicable to + // this class specifically + for ( int i = 0 ; i < tree->GetNumberTechnologies(); i++ ) + { + CBaseTechnology *tech = tree->GetTechnology( i ); + if ( !tech ) + continue; + + if ( !tech->GetAssociateWeaponsForClass( GetTFClass() ) ) + continue; + + // Associate weapon tech name with class + AddWeaponTechAssoc( (char *)tech->GetName() ); + } +} + + +void CPlayerClass::NetworkStateChanged() +{ + if ( m_pPlayer ) + m_pPlayer->NetworkStateChanged(); +} + + +//----------------------------------------------------------------------------- +// Purpose: Setup the default movement parameters, quite a few of these will be +// overridden with class specific values. +//----------------------------------------------------------------------------- +void CPlayerClass::SetupMoveData( void ) +{ + // Set the default walking and sprinting speeds. + m_flMaxWalkingSpeed = 120; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPlayerClass::SetupSizeData( void ) +{ + // Initially set the player to the base player class standing hull size. + m_pPlayer->SetCollisionBounds( PLAYERCLASS_HULL_STAND_MIN, PLAYERCLASS_HULL_STAND_MAX ); + m_pPlayer->SetViewOffset( PLAYERCLASS_VIEWOFFSET_STAND ); + m_pPlayer->m_Local.m_flStepSize = PLAYERCLASS_STEPSIZE; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPlayerClass::CreateClass( void ) +{ + // Give them a full loadout on the initial spawn + ResupplyAmmo( 100.0f, RESUPPLY_ALL_FROM_STATION ); + + // Create the builder weapon & all objects in it (Helpers are automatically deleted when the weapon's deleted) + // Make sure they can build at least 1 object + if ( GetTFClassInfo( m_TFClass )->m_pClassObjects[0] != OBJ_LAST ) + { + m_pPlayer->GiveNamedItem( "weapon_builder" ); + + Assert( m_pPlayer->GetWeaponBuilder() ); + + // Do we have a construction yard? + bool bHaveYard = false; + CBaseEntity *pEntity = NULL; + while ((pEntity = gEntList.FindEntityByClassname( pEntity, "func_construction_yard" )) != NULL) + { + if ( m_pPlayer->InSameTeam( pEntity ) ) + { + bHaveYard = true; + break; + } + } + + for ( int i = 0; i < OBJ_LAST; i++ ) + { + int iClassObject = GetTFClassInfo( m_TFClass )->m_pClassObjects[i]; + + // Hit the end? + if ( iClassObject == OBJ_LAST ) + break; + + // Only Humans can build the powerpacks & mortars + if ( iClassObject == OBJ_POWERPACK || iClassObject == OBJ_MORTAR) + { + if ( m_pPlayer->GetTeamNumber() != TEAM_HUMANS ) + continue; + } + + // Only Aliens can build shields + if ( iClassObject == OBJ_SHIELDWALL ) + { + if ( m_pPlayer->GetTeamNumber() != TEAM_ALIENS ) + continue; + } + + // If my team doesn't have a construction yard, don't allow me to build vehicles + if ( !tf_fastbuild.GetBool() && !bHaveYard ) + { + if ( IsObjectAVehicle(iClassObject) ) + continue; + + // Don't allow them to build vehicle upgrades either + if ( iClassObject == OBJ_DRIVER_MACHINEGUN ) + continue; + } + + // If my team has a construction yard, don't allow me to build defensive buildings + if ( !tf_fastbuild.GetBool() && bHaveYard && IsObjectADefensiveBuilding(iClassObject) ) + continue; + + m_pPlayer->GetWeaponBuilder()->AddBuildableObject( iClassObject ); + + // Give the player a fake weapon to select this object with + CWeaponObjectSelection *pSelection = (CWeaponObjectSelection *)m_pPlayer->GiveNamedItem( "weapon_objectselection", iClassObject ); + if ( pSelection ) + { + pSelection->SetType( iClassObject ); + } + } + } + + // Give the player all the weapons from tech associations + CTechnologyTree *pTechTree = m_pPlayer->GetTechTree(); + if ( pTechTree ) + { + // Give the player any weapons s/he might have just received the tech for + for ( int i = 0; i < m_iNumWeaponTechAssociations; i++ ) + { + if ( m_pPlayer->HasNamedTechnology( m_WeaponTechAssociations[i].pWeaponTech ) ) + { + CBaseTechnology *tech = pTechTree->GetTechnology( m_WeaponTechAssociations[i].pWeaponTech ); + if ( tech ) + { + for ( int j = 0; j < tech->GetNumWeaponAssociations(); j++ ) + { + const char *weaponname = tech->GetAssociatedWeapon( j ); + Assert( weaponname ); + + m_pPlayer->GiveNamedItem( weaponname ); + } + } + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Called on each respawn +//----------------------------------------------------------------------------- +void CPlayerClass::RespawnClass( void ) +{ + ResupplyAmmo( 100.0f, RESUPPLY_ALL_FROM_STATION ); + GainedNewTechnology( NULL ); + SetMaxHealth( GetMaxHealthCVarValue() ); + SetMaxSpeed( GetMaxSpeed() ); + CheckDeterioratingObjects(); + + SetupSizeData(); + + // Refill the clips of all my weapons + for (int i = 0; i < MAX_WEAPONS; i++) + { + CBaseCombatWeapon *pWeapon = m_pPlayer->GetWeapon(i); + if ( pWeapon ) + { + if ( pWeapon->UsesClipsForAmmo1() ) + { + pWeapon->m_iClip1 = pWeapon->GetDefaultClip1(); + } + if ( pWeapon->UsesClipsForAmmo2() ) + { + pWeapon->m_iClip2 = pWeapon->GetDefaultClip2(); + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Supply the player with Ammo. Return true if some ammo was given. +//----------------------------------------------------------------------------- +bool CPlayerClass::ResupplyAmmoType( float flAmount, const char *pAmmoType ) +{ + if (flAmount <= 0) + return false; + + // Make sure at least 1 if given... + int nAmount; + if (flAmount <= 1) + nAmount = 1; + else + nAmount = (int)flAmount; + + bool bGiven = false; + if ( g_pGameRules->CanHaveAmmo( m_pPlayer, pAmmoType ) ) + { + if ( m_pPlayer->GiveAmmo( nAmount, pAmmoType, true ) > 0 ) + bGiven = true; + } + + return bGiven; +} + +//----------------------------------------------------------------------------- +// Purpose: Supply the player with Ammo. Return true if some ammo was given. +//----------------------------------------------------------------------------- +bool CPlayerClass::ResupplyAmmo( float flPercentage, ResupplyReason_t reason ) +{ + bool bGiven = false; + + // Fully resupply shield energy everytime + if ( m_pPlayer->GetCombatShield() ) + { + m_pPlayer->GetCombatShield()->AddShieldHealth( 1.0 ); + } + + if ((reason == RESUPPLY_RESPAWN) || (reason == RESUPPLY_ALL_FROM_STATION) || (reason == RESUPPLY_AMMO_FROM_STATION)) + { + if (ResupplyAmmoType( 1, "Sappers" )) + { + bGiven = true; + } + } + + return bGiven; +} + +//----------------------------------------------------------------------------- +// Purpose: Calculate the player's Speed +//----------------------------------------------------------------------------- +float CPlayerClass::GetMaxSpeed( void ) +{ + float flMaxSpeed = m_flMaxWalkingSpeed; + + // Is the player in adrenalin mode? + if ( m_pPlayer->HasPowerup( POWERUP_RUSH ) ) + { + flMaxSpeed *= ADRENALIN_SPEED_INCREASE; + } + + // Is the player unable to move right now? + if ( m_pPlayer->CantMove() ) + { + flMaxSpeed = 1; + } + + return flMaxSpeed; +} + +//----------------------------------------------------------------------------- +// Purpose: Get the player's maximum walking speed +//----------------------------------------------------------------------------- +float CPlayerClass::GetMaxWalkSpeed( void ) +{ + return m_flMaxWalkingSpeed; +} + +//----------------------------------------------------------------------------- +// Purpose: Set the player's speed +//----------------------------------------------------------------------------- +void CPlayerClass::SetMaxSpeed( float flMaxSpeed ) +{ + if ( m_pPlayer ) + { + m_pPlayer->SetMaxSpeed( flMaxSpeed ); + + float curspeed = m_pPlayer->GetAbsVelocity().Length(); + + if ( curspeed != 0.0f && curspeed > flMaxSpeed ) + { + float crop = flMaxSpeed / curspeed; + + Vector vecNewVelocity; + VectorScale( m_pPlayer->GetAbsVelocity(), crop, vecNewVelocity ); + m_pPlayer->SetAbsVelocity( vecNewVelocity ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Return the player's max health +//----------------------------------------------------------------------------- +int CPlayerClass::GetMaxHealthCVarValue() +{ + int val = GetTFClassInfo( GetTFClass() )->m_pMaxHealthCVar->GetInt(); + Assert( val > 0 ); // If you hit this assert, then you probably didn't add an entry to skill?.cfg + return val; +} + +//----------------------------------------------------------------------------- +// Purpose: Set the player's health +//----------------------------------------------------------------------------- +void CPlayerClass::SetMaxHealth( float flMaxHealth ) +{ + if ( m_pPlayer ) + { + m_pPlayer->m_iMaxHealth = m_pPlayer->m_iHealth = flMaxHealth; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +string_t CPlayerClass::GetClassModel( int nTeam ) +{ + return m_sClassModel[nTeam]; +} + + +const char* CPlayerClass::GetClassModelString( int nTeam ) +{ + // Each derived class should implement this. + Assert( false ); + return "INVALID"; +} + + +//----------------------------------------------------------------------------- +// Purpose: Called to see if another player should be displayed on the players radar +//----------------------------------------------------------------------------- +bool CPlayerClass::CanSeePlayerOnRadar( CBaseTFPlayer *pl ) +{ +// if ( pl->InSameTeam(m_pPlayer) ) +// return false; + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPlayerClass::ItemPostFrame() +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : TFClass +//----------------------------------------------------------------------------- +TFClass CPlayerClass::GetTFClass( void ) +{ + return m_TFClass; +} + +//----------------------------------------------------------------------------- +// Purpose: Handle custom commands for this playerclass +//----------------------------------------------------------------------------- +bool CPlayerClass::ClientCommand( const CCommand& args ) +{ + if ( FStrEq( args[0], "builder_select_obj" ) ) + { + if ( args.ArgC() < 2 ) + return true; + + // This is a total hack. Eventually this should come in via usercmds. + + // Get our builder weapon + if ( !m_pPlayer->GetWeaponBuilder() ) + return true; + + // Select a state for the builder weapon + m_pPlayer->GetWeaponBuilder()->SetCurrentObject( atoi( args[1] ) ); + m_pPlayer->GetWeaponBuilder()->SetCurrentState( BS_PLACING ); + m_pPlayer->GetWeaponBuilder()->StartPlacement(); + m_pPlayer->GetWeaponBuilder()->m_flNextPrimaryAttack = gpGlobals->curtime + 0.35f; + return true; + } + else if ( FStrEq( args[0], "builder_select_mode" ) ) + { + if ( args.ArgC() < 2 ) + return true; + + // Get our builder weapon + if ( !m_pPlayer->GetWeaponBuilder() ) + return true; + + // Select a state for the builder weapon + m_pPlayer->GetWeaponBuilder()->SetCurrentState( atoi( args[1] ) ); + m_pPlayer->GetWeaponBuilder()->m_flNextPrimaryAttack = gpGlobals->curtime + 0.35f; + return true; + } + + return false; +} + + +//----------------------------------------------------------------------------- +// Should we take damage-based force? +//----------------------------------------------------------------------------- +bool CPlayerClass::ShouldApplyDamageForce( const CTakeDamageInfo &info ) +{ + return true; +} + + +//----------------------------------------------------------------------------- +// Purpose: The player has taken damage. Return the damage done. +//----------------------------------------------------------------------------- +float CPlayerClass::OnTakeDamage( const CTakeDamageInfo &info ) +{ + if ( info.GetAttacker() ) + { + CBaseTFPlayer *pPlayer = dynamic_cast< CBaseTFPlayer* >( info.GetAttacker() ); + if ( pPlayer && pPlayer->GetPlayerClass() ) + { + int iStatGroup = GetStatGroupFor( pPlayer ); + CInterClassStats *pInter = &g_PlayerClassStats[iStatGroup].m_InterClassStats[GetTFClass()]; + + pInter->m_flTotalDamageInflicted += info.GetDamage(); + + float flDistToAttacker = pPlayer->GetAbsOrigin().DistTo( GetPlayer()->GetAbsOrigin() ); + pInter->m_flTotalEngagementDist += flDistToAttacker; + pInter->m_nEngagements++; + + if ( gpGlobals->curtime >= m_flNormalizedEngagementNextTime ) + { + pInter->m_flTotalNormalizedEngagementDist += flDistToAttacker; + pInter->m_nNormalizedEngagements++; + + m_flNormalizedEngagementNextTime = gpGlobals->curtime + 3; + } + + // Store detailed stats for the shot? + if ( tf_DetailedStats.GetInt() ) + { + CShotInfo shotInfo; + + shotInfo.m_flDistance = flDistToAttacker; + shotInfo.m_nDamage = (int)info.GetDamage(); + + g_ClassShotInfos[iStatGroup].AddToTail( shotInfo ); + } + } + } + + return info.GetDamage(); +} + +//----------------------------------------------------------------------------- +// Purpose: New technology has been gained. Recalculate any class specific technology dependencies. +//----------------------------------------------------------------------------- +void CPlayerClass::GainedNewTechnology( CBaseTechnology *pTechnology ) +{ + int i; + + // Tell the player's weapons that this player's gained new technology + for ( i = 0; i < m_pPlayer->WeaponCount(); i++ ) + { + if ( m_pPlayer->GetWeapon(i) ) + { + ((CBaseTFCombatWeapon*)m_pPlayer->GetWeapon(i))->GainedNewTechnology( pTechnology ); + } + } + + // Tell the player's objects that this player's gained new technology + for ( i = 0; i < m_pPlayer->GetObjectCount(); i++ ) + { + if (m_pPlayer->GetObject(i)) + { + m_pPlayer->GetObject(i)->GainedNewTechnology( pTechnology ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Return true if this player's allowed to build another one of the specified objects +//----------------------------------------------------------------------------- +int CPlayerClass::CanBuild( int iObjectType ) +{ + int iObjectCount = GetNumObjects( iObjectType ); + + // Make sure we haven't hit maximum number + if ( tf2_object_hard_limits.GetBool() ) + { + if ( iObjectCount >= GetObjectInfo( iObjectType )->m_nMaxObjects ) + return CB_LIMIT_REACHED; + } + else + { + // Find out how much the next object should cost + int iCost = CalculateObjectCost( iObjectType, GetNumObjects( iObjectType ), m_pPlayer->GetTeamNumber() ); + + // Make sure we have enough resources + if ( m_pPlayer->GetBankResources() < iCost ) + return CB_NEED_RESOURCES; + } + + return CB_CAN_BUILD; +} + +//----------------------------------------------------------------------------- +// Purpose: Object built by this player has been destroyed +//----------------------------------------------------------------------------- +void CPlayerClass::OwnedObjectDestroyed( CBaseObject *pObject ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: Get the number of the specified objects built by the player +//----------------------------------------------------------------------------- +int CPlayerClass::GetNumObjects( int iObjectType ) +{ + // For the purposes of costs/limits, Sentryguns tally up all sentrygun types + if ( iObjectType == OBJ_SENTRYGUN_PLASMA || iObjectType == OBJ_SENTRYGUN_ROCKET_LAUNCHER ) + { + return ( m_pPlayer->GetNumObjects( OBJ_SENTRYGUN_PLASMA ) + m_pPlayer->GetNumObjects( OBJ_SENTRYGUN_ROCKET_LAUNCHER ) ); + } + + return m_pPlayer->GetNumObjects( iObjectType ); +} + +//----------------------------------------------------------------------------- +// Purpose: Player has started building an object +//----------------------------------------------------------------------------- +int CPlayerClass::StartedBuildingObject( int iObjectType ) +{ + // Deduct the cost of the object + if ( !tf2_object_hard_limits.GetBool() ) + { + int iCost = CalculateObjectCost( iObjectType, GetNumObjects( iObjectType ), m_pPlayer->GetTeamNumber() ); + if ( iCost > m_pPlayer->GetBankResources() ) + { + // Player must have lost resources since he started placing + return 0; + } + m_pPlayer->RemoveBankResources( iCost ); + + // If the object costs 0, we need to return non-0 to mean success + if ( !iCost ) + return 1; + + return iCost; + } + + return 1; +} + +//----------------------------------------------------------------------------- +// Purpose: Player has aborted a build +//----------------------------------------------------------------------------- +void CPlayerClass::StoppedBuilding( int iObjectType ) +{ + // Return the cost of the object + if ( !tf2_object_hard_limits.GetBool() ) + { + int iCost = CalculateObjectCost( iObjectType, GetNumObjects( iObjectType ), m_pPlayer->GetTeamNumber() ); + m_pPlayer->AddBankResources( iCost ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Object has been built by this player +//----------------------------------------------------------------------------- +void CPlayerClass::FinishedObject( CBaseObject *pObject ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: Object has been picked up by this player +//----------------------------------------------------------------------------- +void CPlayerClass::PickupObject( CBaseObject *pObject ) +{ + // Return the cost of the object + if ( !tf2_object_hard_limits.GetBool() ) + { + int iCost = CalculateObjectCost( pObject->GetType(), GetNumObjects( pObject->GetType() ), m_pPlayer->GetTeamNumber(), true ); + m_pPlayer->AddBankResources( iCost ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Create a personal order for this player +//----------------------------------------------------------------------------- +bool CPlayerClass::CreateInitialOrder() +{ + if( AnyNonResourceZoneOrders() ) + return true; + + return false; +} + + +void CPlayerClass::CreatePersonalOrder() +{ + if( CreateInitialOrder() ) + return; + + // Make an order to fix any objects we own. + if ( COrderRepair::CreateOrder_RepairOwnObjects( this ) ) + return; +} + + +bool CPlayerClass::AnyResourceZoneOrders() +{ + return !!m_pPlayer->GetNumResourceZoneOrders(); +} + + +bool CPlayerClass::AnyNonResourceZoneOrders() +{ + return + m_pPlayer->GetNumResourceZoneOrders() == 0 && + m_pPlayer->GetOrder(); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPlayerClass::ClassThink( void ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPlayerClass::PowerupStart( int iPowerup, float flAmount, CBaseEntity *pAttacker, CDamageModifier *pDamageModifier ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPlayerClass::PowerupEnd( int iPowerup ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: My player has just died +//----------------------------------------------------------------------------- +void CPlayerClass::PlayerDied( CBaseEntity *pAttacker ) +{ + if ( pAttacker ) + { + CBaseTFPlayer *pAttackerPlayer = dynamic_cast< CBaseTFPlayer* >( pAttacker ); + if ( pAttackerPlayer ) + { + CPlayerClass *pAttackerClass = pAttackerPlayer->GetPlayerClass(); + if ( pAttackerClass ) + { + int iStatGroup = GetStatGroupFor( pAttackerPlayer ); + g_PlayerClassStats[ iStatGroup ].m_InterClassStats[GetTFClass()].m_nKills++; + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: My player just killed another player +//----------------------------------------------------------------------------- +void CPlayerClass::PlayerKilledPlayer( CBaseTFPlayer *pVictim ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPlayerClass::SetPlayerHull( void ) +{ + // Use the generic player hull if the class doesn't override this function. + if ( m_pPlayer->GetFlags() & FL_DUCKING ) + { + m_pPlayer->SetCollisionBounds( PLAYERCLASS_HULL_DUCK_MIN, PLAYERCLASS_HULL_DUCK_MAX ); + } + else + { + m_pPlayer->SetCollisionBounds( PLAYERCLASS_HULL_STAND_MIN, PLAYERCLASS_HULL_STAND_MAX ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPlayerClass::GetPlayerHull( bool bDucking, Vector &vecMin, Vector &vecMax ) +{ + // Use the generic player hull if the class doesn't override this function. + if ( bDucking ) + { + VectorCopy( PLAYERCLASS_HULL_DUCK_MIN, vecMin ); + VectorCopy( PLAYERCLASS_HULL_DUCK_MAX, vecMax ); + } + else + { + VectorCopy( PLAYERCLASS_HULL_STAND_MIN, vecMin ); + VectorCopy( PLAYERCLASS_HULL_STAND_MAX, vecMax ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPlayerClass::InitVCollision( void ) +{ + CPhysCollide *pStandModel = PhysCreateBbox( PLAYERCLASS_HULL_STAND_MIN, PLAYERCLASS_HULL_STAND_MAX ); + CPhysCollide *pCrouchModel = PhysCreateBbox( PLAYERCLASS_HULL_DUCK_MIN, PLAYERCLASS_HULL_DUCK_MAX ); + + solid_t solid; + solid.params = g_PhysDefaultObjectParams; + solid.params.mass = 85.0f; + solid.params.inertia = 1e24f; + solid.params.enableCollisions = false; + //disable drag + solid.params.dragCoefficient = 0; + + // create standing hull + m_pPlayer->m_pShadowStand = PhysModelCreateCustom( m_pPlayer, pStandModel, m_pPlayer->GetLocalOrigin(), m_pPlayer->GetLocalAngles(), "tfplayer_generic", false, &solid ); + m_pPlayer->m_pShadowStand->SetCallbackFlags( CALLBACK_SHADOW_COLLISION ); + + // create crouchig hull + m_pPlayer->m_pShadowCrouch = PhysModelCreateCustom( m_pPlayer, pCrouchModel, m_pPlayer->GetLocalOrigin(), m_pPlayer->GetLocalAngles(), "tfplayer_generic", false, &solid ); + m_pPlayer->m_pShadowCrouch->SetCallbackFlags( CALLBACK_SHADOW_COLLISION ); + + // default to stand + m_pPlayer->VPhysicsSetObject( m_pPlayer->m_pShadowStand ); + + //disable drag + m_pPlayer->m_pShadowStand->EnableDrag( false ); + m_pPlayer->m_pShadowCrouch->EnableDrag( false ); + + // tell physics lists I'm a shadow controller object + PhysAddShadow( m_pPlayer ); + m_pPlayer->m_pPhysicsController = physenv->CreatePlayerController( m_pPlayer->m_pShadowStand ); + + // init state + if ( m_pPlayer->GetFlags() & FL_DUCKING ) + { + m_pPlayer->SetVCollisionState( VPHYS_CROUCH ); + } + else + { + m_pPlayer->SetVCollisionState( VPHYS_WALK ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pObject - +// *pNewOwner - +//----------------------------------------------------------------------------- +void CPlayerClass::OwnedObjectChangeToTeam( CBaseObject *pObject, CBaseTFPlayer *pNewOwner ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pObject - +// *pOldOwner - +//----------------------------------------------------------------------------- +void CPlayerClass::OwnedObjectChangeFromTeam( CBaseObject *pObject, CBaseTFPlayer *pOldOwner ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: Check to see if there are any deteriorating objects I once owned that +// I can now re-own (i.e. I switched teams back to my original team). +// TODO: Make this use Steam IDs, so disconnecting & reconnecting players +// get their objects back. +//----------------------------------------------------------------------------- +void CPlayerClass::CheckDeterioratingObjects( void ) +{ + if ( !GetTeam() ) + return; + + // Cycle through the team's objects looking for any deteriorating objects once owned by me + for ( int i = 0; i < GetTeam()->GetNumObjects(); i++ ) + { + CBaseObject *pObject = GetTeam()->GetObject(i); + if ( pObject->IsDeteriorating() && pObject->GetOriginalBuilder() == m_pPlayer ) + { + // Can this class build this object? + if ( ClassCanBuild( GetTFClass(), pObject->GetType() ) ) + { + // Give the object back to this player + pObject->SetBuilder( m_pPlayer ); + m_pPlayer->AddObject( pObject ); + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : edict_t +//----------------------------------------------------------------------------- +CBaseEntity *CPlayerClass::SelectSpawnPoint( void ) +{ + return NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: Add a new weapon/tech association. Weapons that the player has the associated tech for +// will automatically be given out if the player has the tech. +//----------------------------------------------------------------------------- +void CPlayerClass::AddWeaponTechAssoc( char *pWeaponTech ) +{ + Assert( m_iNumWeaponTechAssociations < MAX_WEAPONS ); + + m_WeaponTechAssociations[m_iNumWeaponTechAssociations].pWeaponTech = pWeaponTech; + m_iNumWeaponTechAssociations++; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPlayerClass::ClearAllWeaponTechAssoc( ) +{ + m_iNumWeaponTechAssociations = 0; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CTFTeam *CPlayerClass::GetTeam() +{ + return (CTFTeam*)GetPlayer()->GetTeam(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPlayerClass::ResetViewOffset( void ) +{ + if ( m_pPlayer ) + { + m_pPlayer->SetViewOffset( PLAYERCLASS_VIEWOFFSET_STAND ); + } +} + diff --git a/game/server/tf2/tf_playerclass.h b/game/server/tf2/tf_playerclass.h new file mode 100644 index 0000000..873cb8c --- /dev/null +++ b/game/server/tf2/tf_playerclass.h @@ -0,0 +1,214 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +#ifndef TF_PLAYERCLASS_H +#define TF_PLAYERCLASS_H + +#ifdef _WIN32 +#pragma once +#endif + + +#include "tier0/fasttimer.h" +#include <crtdbg.h> + +class CPlayerClassData; +class CPlayerClass; +class CBaseTFPlayer; +class COrder; +class CBaseObject; +class CBaseTechnology; +class CWeaponCombatShield; + + +enum ResupplyReason_t +{ + RESUPPLY_RESPAWN = 0, + RESUPPLY_ALL_FROM_STATION, + RESUPPLY_AMMO_FROM_STATION, + RESUPPLY_GRENADES_FROM_STATION, +}; + + +//============================================================================== +// PLAYER CLASSES +//============================================================================== + +class CBaseTFPlayer; +class CTFTeam; + +// Base PlayerClass +// The PlayerClass classes handle all class specific weaponry / abilities / etc +class CPlayerClass +{ +public: + DECLARE_CLASS_NOBASE( CPlayerClass ); + CPlayerClass( CBaseTFPlayer *pPlayer, TFClass iClass ); + virtual ~CPlayerClass(); + + // Any objects created/owned by class should be allocated and destroyed here + virtual void ClassActivate( void ); + virtual void ClassDeactivate( void ); + + // Class initialization + virtual void CreateClass( void ); // Create the class upon initial spawn + virtual void RespawnClass( void ); // Called upon all respawns + virtual bool ResupplyAmmo( float flPercentage, ResupplyReason_t reason ); // Reset the ammo counts + virtual bool ResupplyAmmoType( float flAmount, const char *pAmmoType ); // Purpose: Supply the player with Ammo. Return true if some ammo was given. + + virtual void SetMaxHealth( float flMaxHealth ); // Set the player's max health + int GetMaxHealthCVarValue(); // Return the player class's max health cvar + + virtual float GetMaxSpeed( void ); // Calculate and return the player's max speed + virtual float GetMaxWalkSpeed( void ); // Calculate and return the player's max walking speed + virtual void SetMaxSpeed( float flMaxSpeed ); // Set the player's max speed + + virtual string_t GetClassModel( int nTeam ); // Return a string containing this class's model + virtual const char* GetClassModelString( int nTeam ); + + virtual void SetupMoveData( void ); // Setup the default player movement data. + virtual void SetupSizeData( void ); + + virtual bool CanSeePlayerOnRadar( CBaseTFPlayer *pl ); + virtual void ItemPostFrame( void ); + virtual bool ClientCommand( const CCommand &args ); + + // Class abilities + virtual void ClassThink( void ); + virtual void GainedNewTechnology( CBaseTechnology *pTechnology ); // New technology has been gained + + // Deployment + virtual float GetDeployTime( void ) { return 0.0; }; + + // Resources + virtual int ClassCostAdjustment( ResupplyBuyType_t nType ) { return 0; } + + // Objects + int GetNumObjects( int iObjectType ); + virtual int CanBuild( int iObjectType ); + virtual int StartedBuildingObject( int iObjectType ); + virtual void StoppedBuilding( int iObjectType ); + virtual void FinishedObject( CBaseObject *pObject ); + virtual void PickupObject( CBaseObject *pObject ); + virtual void OwnedObjectDestroyed( CBaseObject *pObject ); + virtual void OwnedObjectChangeToTeam( CBaseObject *pObject, CBaseTFPlayer *pNewOwner ); + virtual void OwnedObjectChangeFromTeam( CBaseObject *pObject, CBaseTFPlayer *pOldOwner ); + virtual void CheckDeterioratingObjects( void ); + + // Hooks + virtual float OnTakeDamage( const CTakeDamageInfo &info ); + virtual bool ShouldApplyDamageForce( const CTakeDamageInfo &info ); + + // Vehicles + virtual void OnVehicleStart() {} + virtual void OnVehicleEnd() {} + virtual bool CanGetInVehicle( void ) { return true; } + + virtual void PlayerDied( CBaseEntity *pAttacker ); + virtual void PlayerKilledPlayer( CBaseTFPlayer *pVictim ); + + virtual void SetPlayerHull( void ); + virtual void GetPlayerHull( bool bDucking, Vector &vecMin, Vector &vecMax ); + + // Player Physics Shadow + virtual void InitVCollision( void ); + + // Powerups + virtual void PowerupStart( int iPowerup, float flAmount, CBaseEntity *pAttacker, CDamageModifier *pDamageModifier ); + virtual void PowerupEnd( int iPowerup ); + + // Camo + virtual void ClearCamouflage( void ) { return; }; + + // Disguise + virtual void FinishedDisguising( void ) { return; }; + virtual void StopDisguising( void ) { return; }; + + // Orders + virtual void CreatePersonalOrder( void ); + + // Create a high-priority order. This should be called by all player classes before + // they try to create class-specific orders. This function returns true if an order is + // created. + bool CreateInitialOrder(); + + bool AnyResourceZoneOrders(); + bool AnyNonResourceZoneOrders(); // Returns true if there are any non-resource-zone orders. + // If there are, then no class should make any overriding orders. + + // Respawn ( allow classes to override spawn points ) + virtual CBaseEntity *SelectSpawnPoint( void ); + + void *operator new( size_t stAllocateBlock ) + { + Assert( stAllocateBlock != 0 ); + void *pMem = malloc( stAllocateBlock ); + memset( pMem, 0, stAllocateBlock ); + return pMem; + } + + void* operator new( size_t stAllocateBlock, int nBlockUse, const char *pFileName, int nLine ) + { + Assert( stAllocateBlock != 0 ); + void *pMem = _malloc_dbg( stAllocateBlock, nBlockUse, pFileName, nLine ); + memset( pMem, 0, stAllocateBlock ); + return pMem; + } + + void operator delete( void *pMem ) + { + free( pMem ); + } + + void SetClassModel( string_t sModelName, int nTeam ) { m_sClassModel[nTeam] = sModelName; } + + // Weapon & Tech Associations + void AddWeaponTechAssoc( char *pWeaponTech ); + + + // Accessors. + inline CBaseTFPlayer* GetPlayer() { return m_pPlayer; } + CTFTeam* GetTeam(); + + virtual void ResetViewOffset( void ); + + virtual TFClass GetTFClass( void ); + + void AddWeaponTechAssociations( void ); + + // For CNetworkVar support. Chain to the player entity. + void NetworkStateChanged(); + + TFClass m_TFClass; + +protected: + double m_flNormalizedEngagementNextTime; + + CBaseTFPlayer *m_pPlayer; // Reference to the player + + float m_flMaxWalkingSpeed; + + string_t m_sClassModel[ MAX_TF_TEAMS + 1 ]; + + // Weapon & Tech associations + // Used to give out all weapons the player currently has the technologies for. + struct WeaponTechAssociation_t + { + char *pWeaponTech; + }; + WeaponTechAssociation_t m_WeaponTechAssociations[ MAX_WEAPONS ]; + int m_iNumWeaponTechAssociations; + + CHandle<CWeaponCombatShield> m_hWpnShield; +private: + void ClearAllWeaponTechAssoc( void ); + +private: + bool m_bTechAssociationsSet; +}; + +#endif // TF_PLAYERCLASS_H
\ No newline at end of file diff --git a/game/server/tf2/tf_playerlocaldata.cpp b/game/server/tf2/tf_playerlocaldata.cpp new file mode 100644 index 0000000..1b82242 --- /dev/null +++ b/game/server/tf2/tf_playerlocaldata.cpp @@ -0,0 +1,147 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" +#include "tf_playerlocaldata.h" +#include "tf_player.h" +#include "mathlib/mathlib.h" +#include "entitylist.h" + +extern ConVar tf_fastbuild; +ConVar tf_maxbankresources( "tf_maxbankresources", "2000", 0, "Max resources a single player can have." ); + +#define BANK_RESOURCE_BITS 20 +#define MAX_PLAYER_RESOURCES ( (1 <<BANK_RESOURCE_BITS) - 1 ) + +//----------------------------------------------------------------------------- +// Purpose: SendProxy that converts the UtlVector list of objects to entindexes, where it's reassembled on the client +//----------------------------------------------------------------------------- +void SendProxy_PlayerObjectList( const SendProp *pProp, const void *pStruct, const void *pData, DVariant *pOut, int iElement, int objectID ) +{ + CTFPlayerLocalData *pLocalData = (CTFPlayerLocalData*)pStruct; + Assert( pLocalData->m_pPlayer ); + + // If this fails, then SendProxyArrayLength_PlayerObjects didn't work. + Assert( iElement < pLocalData->m_pPlayer->GetObjectCount() ); + + CBaseObject *pObject = pLocalData->m_pPlayer->GetObject(iElement); + + EHANDLE hObject; + hObject = pObject; + + SendProxy_EHandleToInt( pProp, pStruct, &hObject, pOut, iElement, objectID ); +} + + +int SendProxyArrayLength_PlayerObjects( const void *pStruct, int objectID ) +{ + CTFPlayerLocalData *pLocalData = (CTFPlayerLocalData*)pStruct; + Assert( pLocalData->m_pPlayer ); + int iObjects = pLocalData->m_pPlayer->GetObjectCount(); + Assert( iObjects < MAX_OBJECTS_PER_PLAYER ); + return iObjects; +} + +BEGIN_SEND_TABLE_NOBASE( CTFPlayerLocalData, DT_TFLocal ) + SendPropInt( SENDINFO(m_nInTacticalView), 1, SPROP_UNSIGNED ), + SendPropInt( SENDINFO( m_bKnockedDown ), 1, SPROP_UNSIGNED ), + SendPropVector( SENDINFO( m_vecKnockDownDir ), -1, SPROP_COORD ), + SendPropInt( SENDINFO( m_bThermalVision ), 1, SPROP_UNSIGNED ), + SendPropInt( SENDINFO( m_iIDEntIndex ), 10, SPROP_UNSIGNED ), + SendPropArray( SendPropInt( SENDINFO_ARRAY(m_iResourceAmmo), 4, SPROP_UNSIGNED ), m_iResourceAmmo ), + SendPropInt( SENDINFO(m_iBankResources), BANK_RESOURCE_BITS, SPROP_UNSIGNED ), + SendPropArray2( + SendProxyArrayLength_PlayerObjects, + SendPropInt("player_object_array_element", 0, SIZEOF_IGNORE, NUM_NETWORKED_EHANDLE_BITS, SPROP_UNSIGNED, SendProxy_PlayerObjectList), + MAX_OBJECTS_PER_PLAYER, + 0, + "player_object_array" + ), + SendPropInt( SENDINFO( m_bAttachingSapper ), 1, SPROP_UNSIGNED ), + SendPropFloat( SENDINFO( m_flSapperAttachmentFrac ), 7, SPROP_ROUNDDOWN, 0.0f, 1.0f ), + SendPropInt( SENDINFO( m_bForceMapOverview ), 1, SPROP_UNSIGNED ), +END_SEND_TABLE() + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CTFPlayerLocalData::CTFPlayerLocalData() +{ + m_nInTacticalView = 0; + m_pPlayer = NULL; + + m_bKnockedDown = false; + m_vecKnockDownDir.Init(); + + m_bThermalVision = false; + m_iIDEntIndex = 0; + + // Resource carrying + for ( int i = 0; i < RESOURCE_TYPES; i++ ) + { + m_iResourceAmmo.Set( i, 0 ); + } + + if( inv_demo.GetInt() ) + { + m_iBankResources = 5000; + } + else + { + m_iBankResources = 0; + } + m_aObjects.Purge(); + + m_bAttachingSapper = false; + m_flSapperAttachmentFrac = 0; + m_bForceMapOverview = false; +} + +//----------------------------------------------------------------------------- +// Add, remove, count resources +//----------------------------------------------------------------------------- +void CTFPlayerLocalData::AddResources( int iAmount ) +{ + m_iBankResources += iAmount; + + if ( !tf_fastbuild.GetBool() && (m_iBankResources > tf_maxbankresources.GetFloat()) ) + { + m_iBankResources = tf_maxbankresources.GetFloat(); + } + + // Clamp for overflow... + if ( m_iBankResources > MAX_PLAYER_RESOURCES ) + { + Msg("Warning: Player overflowed resource count!\n"); + m_iBankResources = MAX_PLAYER_RESOURCES; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFPlayerLocalData::RemoveResources( int iAmount ) +{ + Assert( iAmount <= m_iBankResources ); + m_iBankResources = MAX(0, m_iBankResources - iAmount); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int CTFPlayerLocalData::ResourceCount( void ) const +{ + return m_iBankResources; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFPlayerLocalData::SetResources( int iAmount ) +{ + m_iBankResources = iAmount; +}
\ No newline at end of file diff --git a/game/server/tf2/tf_playerlocaldata.h b/game/server/tf2/tf_playerlocaldata.h new file mode 100644 index 0000000..408f54d --- /dev/null +++ b/game/server/tf2/tf_playerlocaldata.h @@ -0,0 +1,68 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef TF_PLAYERLOCALDATA_H +#define TF_PLAYERLOCALDATA_H +#ifdef _WIN32 +#pragma once +#endif + +#include "techtree.h" +#include "predictable_entity.h" +#include "tf_obj.h" + +//----------------------------------------------------------------------------- +// Purpose: Player specific data for TF2 ( sent only to local player, too ) +//----------------------------------------------------------------------------- +class CTFPlayerLocalData +{ +public: + DECLARE_PREDICTABLE(); + DECLARE_CLASS_NOBASE( CTFPlayerLocalData ); + DECLARE_EMBEDDED_NETWORKVAR(); + + CTFPlayerLocalData(); + + CBaseTFPlayer *m_pPlayer; + + CNetworkVar( bool, m_nInTacticalView ); + + // Player has been knocked down + CNetworkVar( bool, m_bKnockedDown ); + CNetworkQAngle( m_vecKnockDownDir ); + + // Player is using thermal vision + CNetworkVar( bool, m_bThermalVision ); + + // ID + CNetworkVar( int, m_iIDEntIndex ); + + // Resource chunk carrying counts + CNetworkArray( int, m_iResourceAmmo, RESOURCE_TYPES ); // 0 = Normal resources, 1 = Processed resources + + // Resource manipulation + void AddResources( int iAmount ); + void RemoveResources( int iAmount ); + int ResourceCount( void ) const; + void SetResources( int iAmount ); + + // Resource bank + CNetworkVar( int, m_iBankResources ); // Current amounts of resources in my bank + + // Objects + CUtlVector< CHandle<CBaseObject> > m_aObjects; + + // Object sapper placement handling + CNetworkVar( bool, m_bAttachingSapper ); + CNetworkVar( float, m_flSapperAttachmentFrac ); + + CNetworkVar( bool, m_bForceMapOverview ); +}; + +EXTERN_SEND_TABLE(DT_TFLocal); + +#endif // TF_PLAYERLOCALDATA_H diff --git a/game/server/tf2/tf_playermove.cpp b/game/server/tf2/tf_playermove.cpp new file mode 100644 index 0000000..7f4e2b2 --- /dev/null +++ b/game/server/tf2/tf_playermove.cpp @@ -0,0 +1,291 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" +#include "player_command.h" +#include "tf_player.h" +#include "igamemovement.h" +#include "tf_shareddefs.h" +#include "in_buttons.h" +#include "tf_movedata.h" +#include "tf_class_recon.h" +#include "tf_reconvars.h" +#include "IserverVehicle.h" +#include "tf_class_commando.h" +#include "ipredictionsystem.h" + +ConVar jetpack_infinite("jetpack_infinite", "0"); +extern float g_JetpackDepleteRate; // How fast the jetpack depletes. +extern float g_JetpackNegSnap; // When the jetpack is fully depleted, it snaps to this so the pilot sputters. + +static CTFMoveData g_TFMoveData; +CMoveData *g_pMoveData = &g_TFMoveData; + +IPredictionSystem *IPredictionSystem::g_pPredictionSystems = NULL; + + +//----------------------------------------------------------------------------- +// Sets up the move data for TF2 +//----------------------------------------------------------------------------- +class CTFPlayerMove : public CPlayerMove +{ +DECLARE_CLASS( CTFPlayerMove, CPlayerMove ); + +public: + virtual void SetupMove( CBasePlayer *player, CUserCmd *ucmd, IMoveHelper *pHelper, CMoveData *move ); + virtual void FinishMove( CBasePlayer *player, CUserCmd *ucmd, CMoveData *move ); + +private: + + void SetupMoveCommando( CBaseTFPlayer *pTFPlayer, CUserCmd *pUcmd, IMoveHelper *pHelper, CTFMoveData *pTFMove ); + void SetupMoveRecon( CBaseTFPlayer *pTFPlayer, CUserCmd *pUcmd, IMoveHelper *pHelper, CTFMoveData *pTFMove ); + + void FinishMoveCommando( CBaseTFPlayer *pTFPlayer, CTFMoveData *pTFMove, CUserCmd *ucmd ); + void FinishMoveRecon( CBaseTFPlayer *pTFPlayer, CTFMoveData *pTFMove, CUserCmd *ucmd ); +}; + +// PlayerMove Interface +static CTFPlayerMove g_PlayerMove; + +//----------------------------------------------------------------------------- +// Singleton accessor +//----------------------------------------------------------------------------- +CPlayerMove *PlayerMove() +{ + return &g_PlayerMove; +} + +//----------------------------------------------------------------------------- +// Main setup, finish +//----------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- +// Purpose: This is called pre player movement and copies all the data necessary +// from the player for movement. (Server-side, the client-side version +// of this code can be found in prediction.cpp.) +//----------------------------------------------------------------------------- +void CTFPlayerMove::SetupMove( CBasePlayer *player, CUserCmd *ucmd, IMoveHelper *pHelper, CMoveData *move ) +{ + // Call the default SetupMove code. + BaseClass::SetupMove( player, ucmd, pHelper, move ); + + // + // Convert to TF2 data. + // + CBaseTFPlayer *pTFPlayer = static_cast<CBaseTFPlayer*>( player ); + Assert( pTFPlayer ); + + CTFMoveData *pTFMove = static_cast<CTFMoveData*>( move ); + Assert( pTFMove ); + + // + // Player movement data. + // + + // Copy the position delta. + pTFMove->m_vecPosDelta = pTFPlayer->m_vecPosDelta; + + // Copy the momentum data. + pTFMove->m_iMomentumHead = pTFPlayer->m_iMomentumHead; + for ( int iMomentum = 0; iMomentum < CTFMoveData::MOMENTUM_MAXSIZE; iMomentum++ ) + { + pTFMove->m_aMomentum[iMomentum] = pTFPlayer->m_aMomentum[iMomentum]; + } + + pTFMove->m_nClassID = pTFPlayer->PlayerClass(); + + IVehicle *pVehicle = player->GetVehicle(); + if (!pVehicle) + { + // Handle player class specific setup. + switch( pTFPlayer->PlayerClass() ) + { + case TFCLASS_RECON: + { + SetupMoveRecon( pTFPlayer, ucmd, pHelper, pTFMove ); + break; + } + case TFCLASS_COMMANDO: + { + SetupMoveCommando( pTFPlayer, ucmd, pHelper, pTFMove ); + break; + } + default: + { + // pTFMove->m_nClassID = TFCLASS_UNDECIDED; + break; + } + } + } + else + { + pVehicle->SetupMove( player, ucmd, pHelper, move ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFPlayerMove::SetupMoveRecon( CBaseTFPlayer *pTFPlayer, CUserCmd *pUcmd, IMoveHelper *pHelper, + CTFMoveData *pTFMove ) +{ + CPlayerClassRecon *pRecon = static_cast<CPlayerClassRecon*>( pTFPlayer->GetPlayerClass() ); + if ( pRecon ) + { + PlayerClassReconData_t *pReconData = pRecon->GetClassData(); + if ( pReconData ) + { + pTFMove->ReconData().m_nJumpCount = pReconData->m_nJumpCount; + pTFMove->ReconData().m_flSuppressionJumpTime = pReconData->m_flSuppressionJumpTime; + pTFMove->ReconData().m_flSuppressionImpactTime = pReconData->m_flSuppressionImpactTime; + pTFMove->ReconData().m_flActiveJumpTime = pReconData->m_flActiveJumpTime; + pTFMove->ReconData().m_flStickTime = pReconData->m_flStickTime; + pTFMove->ReconData().m_flImpactDist = pReconData->m_flImpactDist; + pTFMove->ReconData().m_vecImpactNormal = pReconData->m_vecImpactNormal; + pTFMove->ReconData().m_vecUnstickVelocity = pReconData->m_vecUnstickVelocity; + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFPlayerMove::SetupMoveCommando( CBaseTFPlayer *pTFPlayer, CUserCmd *pUcmd, IMoveHelper *pHelper, + CTFMoveData *pTFMove ) +{ + CPlayerClassCommando *pCommando = static_cast<CPlayerClassCommando*>( pTFPlayer->GetPlayerClass() ); + if ( pCommando ) + { + PlayerClassCommandoData_t *pCommandoData = pCommando->GetClassData(); + if ( pCommandoData ) + { + pTFMove->CommandoData().m_bCanBullRush = pCommandoData->m_bCanBullRush; + pTFMove->CommandoData().m_bBullRush = pCommandoData->m_bBullRush; + pTFMove->CommandoData().m_vecBullRushDir = pCommandoData->m_vecBullRushDir; + pTFMove->CommandoData().m_vecBullRushViewDir = pCommandoData->m_vecBullRushViewDir; + pTFMove->CommandoData().m_vecBullRushViewGoalDir = pCommandoData->m_vecBullRushViewGoalDir; + pTFMove->CommandoData().m_flBullRushTime = pCommandoData->m_flBullRushTime; + pTFMove->CommandoData().m_flDoubleTapForwardTime = pCommandoData->m_flDoubleTapForwardTime; + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: This is called post player movement to copy back all data that +// movement could have modified and that is necessary for future +// movement. (Server-side, the client-side version of this code can +// be found in prediction.cpp.) +//----------------------------------------------------------------------------- +void CTFPlayerMove::FinishMove( CBasePlayer *player, CUserCmd *ucmd, CMoveData *move ) +{ + // Call the default FinishMove code. + BaseClass::FinishMove( player, ucmd, move ); + + // + // Convert to TF2 data. + // + CBaseTFPlayer *pTFPlayer = static_cast<CBaseTFPlayer*>( player ); + Assert( pTFPlayer ); + + CTFMoveData *pTFMove = static_cast<CTFMoveData*>( move ); + Assert( pTFMove ); + + // The class had better not have changed during the move!! + Assert( pTFMove->m_nClassID == pTFPlayer->PlayerClass() ); + + IVehicle *pVehicle = player->GetVehicle(); + if (!pVehicle) + { + // Handle player class specific setup. + switch( pTFPlayer->PlayerClass() ) + { + case TFCLASS_RECON: + { + FinishMoveRecon( pTFPlayer, pTFMove, ucmd ); + break; + } + case TFCLASS_COMMANDO: + { + FinishMoveCommando( pTFPlayer, pTFMove, ucmd ); + break; + } + default: + { + break; + } + } + } + else + { + // Similarly, the vehicle had better not have changed during the move! + Assert( pTFPlayer->IsInAVehicle() ); + pVehicle->FinishMove( pTFPlayer, ucmd, pTFMove ); + } + + // + // Player movement data. + // + + // Copy the position delta. + pTFPlayer->m_vecPosDelta = pTFMove->m_vecPosDelta; + + COMPILE_TIME_ASSERT( CBaseTFPlayer::MOMENTUM_MAXSIZE == CTFMoveData::MOMENTUM_MAXSIZE ); + + // Copy the momentum data back (the movement may have updated it!). + pTFPlayer->m_iMomentumHead = pTFMove->m_iMomentumHead; + for ( int iMomentum = 0; iMomentum < CTFMoveData::MOMENTUM_MAXSIZE; iMomentum++ ) + { + pTFPlayer->m_aMomentum[iMomentum] = pTFMove->m_aMomentum[iMomentum]; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFPlayerMove::FinishMoveRecon( CBaseTFPlayer *pTFPlayer, CTFMoveData *pTFMove, + CUserCmd *ucmd ) +{ + CPlayerClassRecon *pRecon = static_cast<CPlayerClassRecon*>( pTFPlayer->GetPlayerClass() ); + if ( pRecon ) + { + PlayerClassReconData_t *pReconData = pRecon->GetClassData(); + if ( pReconData ) + { + pReconData->m_nJumpCount = pTFMove->ReconData().m_nJumpCount; + pReconData->m_flSuppressionJumpTime = pTFMove->ReconData().m_flSuppressionJumpTime; + pReconData->m_flSuppressionImpactTime = pTFMove->ReconData().m_flSuppressionImpactTime; + pReconData->m_flActiveJumpTime = pTFMove->ReconData().m_flActiveJumpTime; + pReconData->m_flStickTime = pTFMove->ReconData().m_flStickTime; + pReconData->m_flImpactDist = pTFMove->ReconData().m_flImpactDist; + pReconData->m_vecImpactNormal = pTFMove->ReconData().m_vecImpactNormal; + pReconData->m_vecUnstickVelocity = pTFMove->ReconData().m_vecUnstickVelocity; + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFPlayerMove::FinishMoveCommando( CBaseTFPlayer *pTFPlayer, CTFMoveData *pTFMove, + CUserCmd *ucmd ) +{ + CPlayerClassCommando *pCommando = static_cast<CPlayerClassCommando*>( pTFPlayer->GetPlayerClass() ); + if ( pCommando ) + { + PlayerClassCommandoData_t *pCommandoData = pCommando->GetClassData(); + if ( pCommandoData ) + { + pCommandoData->m_bCanBullRush = pTFMove->CommandoData().m_bCanBullRush; + pCommandoData->m_bBullRush = pTFMove->CommandoData().m_bBullRush; + pCommandoData->m_vecBullRushDir = pTFMove->CommandoData().m_vecBullRushDir; + pCommandoData->m_vecBullRushViewDir = pTFMove->CommandoData().m_vecBullRushViewDir; + pCommandoData->m_vecBullRushViewGoalDir = pTFMove->CommandoData().m_vecBullRushViewGoalDir; + pCommandoData->m_flBullRushTime = pTFMove->CommandoData().m_flBullRushTime; + pCommandoData->m_flDoubleTapForwardTime = pTFMove->CommandoData().m_flDoubleTapForwardTime; + } + } +} diff --git a/game/server/tf2/tf_rescollector_ground.cpp b/game/server/tf2/tf_rescollector_ground.cpp new file mode 100644 index 0000000..db4f992 --- /dev/null +++ b/game/server/tf2/tf_rescollector_ground.cpp @@ -0,0 +1 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// diff --git a/game/server/tf2/tf_rescollector_ground.h b/game/server/tf2/tf_rescollector_ground.h new file mode 100644 index 0000000..db4f992 --- /dev/null +++ b/game/server/tf2/tf_rescollector_ground.h @@ -0,0 +1 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// diff --git a/game/server/tf2/tf_shared_defines.h b/game/server/tf2/tf_shared_defines.h new file mode 100644 index 0000000..7a82c42 --- /dev/null +++ b/game/server/tf2/tf_shared_defines.h @@ -0,0 +1,28 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Shared defines between the game and client DLLs for TF +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef TF_DEFINES_H +#define TF_DEFINES_H +#pragma once + +// Zone states +#define ZONE_FRIENDLY 1 +#define ZONE_ENEMY 2 +#define ZONE_CONTESTED 3 + +// Loot state +#define LOOT_NOT 0 +#define LOOT_CAPABLE 1 +#define LOOT_LOOTING 2 + +#endif // TF_DEFINES_H diff --git a/game/server/tf2/tf_shield.cpp b/game/server/tf2/tf_shield.cpp new file mode 100644 index 0000000..c73c092 --- /dev/null +++ b/game/server/tf2/tf_shield.cpp @@ -0,0 +1,432 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: The Escort's Shield weapon +// +// $Revision: $ +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" +#include "tf_shield.h" +#include "tf_shareddefs.h" +#include "collisionutils.h" +#include <float.h> +#include "sendproxy.h" +#include "mathlib/mathlib.h" + +#define PROBE_EFFECT_TIME 0.15f + +ConVar shield_explosive_damage( "shield_explosive_damage","10", FCVAR_REPLICATED, "Shield power damage from explosions" ); + +// Percentage of total health that the shield's allowed to go below 0 +#define SHIELD_MIN_HEALTH_FACTOR (-0.5) + +//----------------------------------------------------------------------------- +// Stores a list of all active shields +//----------------------------------------------------------------------------- +CUtlVector< CShield* > CShield::s_Shields; + + +//----------------------------------------------------------------------------- +// Returns true if the entity is a shield +//----------------------------------------------------------------------------- +bool IsShield( CBaseEntity *pEnt ) +{ + // This is a faster way... + return pEnt && (pEnt->GetCollisionGroup() == TFCOLLISION_GROUP_SHIELD); +// return pEnt && FClassnameIs( pEnt, "shield" ) +} + + +//============================================================================= +// Shield effect +//============================================================================= +EXTERN_SEND_TABLE(DT_BaseEntity) + +IMPLEMENT_SERVERCLASS_ST(CShield, DT_Shield) + SendPropInt(SENDINFO(m_nOwningPlayerIndex), MAX_EDICT_BITS, SPROP_UNSIGNED ), + SendPropFloat(SENDINFO(m_flPowerLevel), 9, SPROP_ROUNDUP, SHIELD_MIN_HEALTH_FACTOR, 1.0f ), + SendPropInt(SENDINFO(m_bIsEMPed), 1, SPROP_UNSIGNED ), +END_SEND_TABLE() + +//----------------------------------------------------------------------------- +// constructor +//----------------------------------------------------------------------------- +CShield::CShield() +{ + s_Shields.AddToTail(this); + AddEFlags( EFL_FORCE_CHECK_TRANSMIT ); + SetupRecharge( 0,0,0,0 ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CShield::~CShield() +{ + int i = s_Shields.Find(this); + if (i >= 0) + s_Shields.FastRemove(i); +} + +//----------------------------------------------------------------------------- +// Precache !! +//----------------------------------------------------------------------------- +void CShield::Precache() +{ + SetClassname( "shield" ); +} + +//----------------------------------------------------------------------------- +// Spawn !! +//----------------------------------------------------------------------------- +void CShield::Spawn( void ) +{ + m_bIsEMPed = 0; + SetCollisionGroup( TFCOLLISION_GROUP_SHIELD ); + + // Make it translucent + m_nRenderFX = kRenderTransAlpha; + SetRenderColorA( 255 ); + + m_flNextRechargeTime = gpGlobals->curtime; + + CollisionProp()->SetSurroundingBoundsType( USE_GAME_CODE ); + SetSolid( SOLID_CUSTOM ); + + // Stuff can't come to a rest on shields! + AddSolidFlags( FSOLID_NOT_STANDABLE ); + UTIL_SetSize( this, vec3_origin, vec3_origin ); +} + + +//----------------------------------------------------------------------------- +// Owner +//----------------------------------------------------------------------------- +void CShield::SetOwnerEntity( CBaseEntity *pOwner ) +{ + BaseClass::SetOwnerEntity( pOwner ); + + if (pOwner->IsPlayer()) + { + m_nOwningPlayerIndex = pOwner->entindex(); + } + else + { + m_nOwningPlayerIndex = 0; + } + ChangeTeam( pOwner->GetTeamNumber() ); +} + + + +//----------------------------------------------------------------------------- +// Does this shield protect from a particular damage type? +//----------------------------------------------------------------------------- +float CShield::ProtectionAmount( int damageType ) const +{ + // As a test, we're trying to make shields impervious to everything + return 1.0f; +} + + +//----------------------------------------------------------------------------- +// Activates/deactivates a shield for collision purposes +//----------------------------------------------------------------------------- +void CShield::ActivateCollisions( bool activate ) +{ + if ( activate ) + { + RemoveSolidFlags( FSOLID_NOT_SOLID ); + } + else + { + AddSolidFlags( FSOLID_NOT_SOLID ); + } +} + +//----------------------------------------------------------------------------- +// Activates all shields +//----------------------------------------------------------------------------- +void CShield::ActivateShields( bool activate, int team ) +{ + for (int i = s_Shields.Count(); --i >= 0; ) + { + // Activate all shields on the same team + if ( (team == -1) || (team == s_Shields[i]->GetTeamNumber()) ) + { + s_Shields[i]->ActivateCollisions( activate ); + } + } +} + + +//----------------------------------------------------------------------------- +// Checks a ray against shields only +//----------------------------------------------------------------------------- +bool CShield::IsBlockedByShields( const Vector& src, const Vector& end ) +{ + trace_t tr; + Ray_t ray; + ray.Init( src, end ); + + for (int i = s_Shields.Count(); --i >= 0; ) + { + if (!s_Shields[i]->ShouldCollide( TFCOLLISION_GROUP_WEAPON, MASK_ALL )) + continue; + + // Coarse bbox test first + Vector vecAbsMins, vecAbsMaxs; + s_Shields[i]->CollisionProp()->WorldSpaceAABB( &vecAbsMins, &vecAbsMaxs ); + if (!IsBoxIntersectingRay( vecAbsMins, vecAbsMaxs, ray.m_Start, ray.m_Delta )) + continue; + + if (s_Shields[i]->TestCollision( ray, MASK_ALL, tr )) + return true; + } + + return false; +} + + +//----------------------------------------------------------------------------- +// Called when we hit something that we deflect... +//----------------------------------------------------------------------------- +void CShield::RegisterDeflection(const Vector& vecDir, int bitsDamageType, trace_t *ptr) +{ + // Probes don't hurt shields + if (bitsDamageType & DMG_PROBE) + return; + + Vector normalDir; + VectorCopy( vecDir, normalDir ); + VectorNormalize( normalDir ); + + // Uncomment this line when the client predicts the shield deflections + //filter.UsePredictionRules(); + EntityMessageBegin( this ); + WRITE_LONG( ptr->hitgroup ); + WRITE_VEC3NORMAL( normalDir ); + WRITE_BYTE( false ); // This was not a partial block + MessageEnd(); + + // If this is a buckshot round, don't pay the power cost to stop it once we've gone over our max buckshot hits this frame + if ( bitsDamageType & DMG_BUCKSHOT ) + { + m_iBuckshotHitsThisFrame++; + if ( m_iBuckshotHitsThisFrame > 4 ) + return; + } + + // If this is an explosion, it counts for extra + if ( bitsDamageType & DMG_BLAST ) + { + SetPower( m_flPower - shield_explosive_damage.GetFloat() ); + } + else + { + // Reduce our power level by a hit + SetPower( m_flPower - 1 ); + } + + // If we've lost power fully, don't recharge for a bit + if ( m_flPower <= 0 ) + { + m_flNextRechargeTime = gpGlobals->curtime + 1.0; + } +} + +//----------------------------------------------------------------------------- +// Called when we hit something that we let through... +//----------------------------------------------------------------------------- +void CShield::RegisterPassThru(const Vector& vecDir, int bitsDamageType, trace_t *ptr) +{ + // Probes don't hurt shields + if (bitsDamageType & DMG_PROBE) + return; + + Vector normalDir; + VectorCopy( vecDir, normalDir ); + VectorNormalize( normalDir ); + + EntityMessageBegin( this ); + WRITE_LONG( ptr->hitgroup ); + WRITE_VEC3NORMAL( normalDir ); + WRITE_BYTE( true ); // This was a partial block + MessageEnd(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CShield::SetPower( float flPower ) +{ + m_flPower = MAX( (SHIELD_MIN_HEALTH_FACTOR * m_flMaxPower), flPower ); + if ( m_flPower > m_flMaxPower ) + { + m_flPower = m_flMaxPower; + } + m_flPowerLevel = ( m_flPower / m_flMaxPower ); +} + +//----------------------------------------------------------------------------- +// Purpose: Setup shield recharging parameters +//----------------------------------------------------------------------------- +void CShield::SetupRecharge( float flPower, float flDelay, float flAmount, float flTickTime ) +{ + m_flMaxPower = flPower; + SetPower( flPower ); + m_flPowerLevel = 1.0; + m_flRechargeDelay = flDelay; + m_flRechargeAmount = flAmount; + m_flRechargeTime = flTickTime; +} + +//----------------------------------------------------------------------------- +// Purpose: Recharge the shield if we haven't taken damage for a while +//----------------------------------------------------------------------------- +void CShield::Think( void ) +{ + // Clear out buckshot count + m_iBuckshotHitsThisFrame = 0; + + if ( m_flNextRechargeTime < gpGlobals->curtime ) + { + if ( m_flPower < m_flMaxPower ) + { + SetPower( m_flPower + m_flRechargeAmount ); + } + + m_flNextRechargeTime = gpGlobals->curtime + m_flRechargeTime; + } + + // Let derived objects think if they want to + if ( m_pfnThink ) + { + (this->*m_pfnThink)(); + } +} + +//----------------------------------------------------------------------------- +// Indicates the shield has been EMPed (or not) +//----------------------------------------------------------------------------- +void CShield::SetEMPed( bool isEmped ) +{ + m_bIsEMPed = isEmped; +} + +//----------------------------------------------------------------------------- +// Helper method for collision testing +//----------------------------------------------------------------------------- +#pragma warning ( disable : 4701 ) + +bool CShield::TestCollision( const Ray_t& ray, unsigned int mask, trace_t& trace ) +{ + // Can't block anything if we're EMPed, or we've got no power left to block + if ( IsEMPed() ) + return false; + if ( m_flPower <= 0 ) + return false; + + // Here, we're gonna test for collision. + // If we don't stop this kind of bullet, we'll generate an effect here + // but we won't change the trace to indicate a collision. + + // It's just polygon soup... + int hitgroup; + bool firstTri; + int v1[2], v2[2], v3[2]; + float ihit, jhit; + float mint = FLT_MAX; + float t; + + int h = Height(); + int w = Width(); + + for (int i = 0; i < h - 1; ++i) + { + for (int j = 0; j < w - 1; ++j) + { + // Don't test if this panel ain't active... + if (!IsPanelActive( j, i )) + continue; + + // NOTE: Structure order of points so that our barycentric + // axes for each triangle are along the (u,v) directions of the mesh + // The barycentric coords we'll need below + + // Two triangles per quad... + t = IntersectRayWithTriangle( ray, + GetPoint( j, i + 1 ), + GetPoint( j + 1, i + 1 ), + GetPoint( j, i ), true ); + if ((t >= 0.0f) && (t < mint)) + { + mint = t; + v1[0] = j; v1[1] = i + 1; + v2[0] = j + 1; v2[1] = i + 1; + v3[0] = j; v3[1] = i; + ihit = i; jhit = j; + firstTri = true; + } + + t = IntersectRayWithTriangle( ray, + GetPoint( j + 1, i ), + GetPoint( j, i ), + GetPoint( j + 1, i + 1 ), true ); + if ((t >= 0.0f) && (t < mint)) + { + mint = t; + v1[0] = j + 1; v1[1] = i; + v2[0] = j; v2[1] = i; + v3[0] = j + 1; v3[1] = i + 1; + ihit = i; jhit = j; + firstTri = false; + } + } + } + + if (mint == FLT_MAX) + return false; + + // Stuff the barycentric coordinates of the triangle hit into the hit group + // For the first triangle, the first edge goes along u, the second edge goes + // along -v. For the second triangle, the first edge goes along -u, + // the second edge goes along v. + const Vector& v1vec = GetPoint(v1[0], v1[1]); + const Vector& v2vec = GetPoint(v2[0], v2[1]); + const Vector& v3vec = GetPoint(v3[0], v3[1]); + float u, v; + bool ok = ComputeIntersectionBarycentricCoordinates( ray, + v1vec, v2vec, v3vec, u, v ); + Assert( ok ); + if ( !ok ) + { + return false; + } + + if (firstTri) + v = 1.0 - v; + else + u = 1.0 - u; + v += ihit; u += jhit; + v /= (h - 1); + u /= (w - 1); + + // Compress (u,v) into 1 dot 15, v in top bits + hitgroup = (((int)(v * (1 << 15))) << 16) + (int)(u * (1 << 15)); + + Vector normal; + float intercept; + ComputeTrianglePlane( v1vec, v2vec, v3vec, normal, intercept ); + + UTIL_SetTrace( trace, ray, edict(), mint, hitgroup, CONTENTS_SOLID, normal, intercept ); + VectorAdd( trace.startpos, ray.m_StartOffset, trace.startpos ); + VectorAdd( trace.endpos, ray.m_StartOffset, trace.endpos ); + + return true; +} + +#pragma warning ( default : 4701 ) + + diff --git a/game/server/tf2/tf_shield.h b/game/server/tf2/tf_shield.h new file mode 100644 index 0000000..14bc7fc --- /dev/null +++ b/game/server/tf2/tf_shield.h @@ -0,0 +1,146 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef TF_SHIELD_H +#define TF_SHIELD_H + +#ifdef _WIN32 +#pragma once +#endif + +#include "baseentity.h" +#include "utlvector.h" + + +//----------------------------------------------------------------------------- +// forward declarations +//----------------------------------------------------------------------------- +class Vector; +class CGameTrace; +typedef CGameTrace trace_t; +struct edict_t; + + +//----------------------------------------------------------------------------- +// Base class for shield entities +//----------------------------------------------------------------------------- +class CShield : public CBaseEntity +{ +public: + DECLARE_CLASS( CShield, CBaseEntity ); + DECLARE_SERVERCLASS(); + + CShield(); + ~CShield(); + +public: + void Precache(); + void Spawn( void ); + + virtual void Think( void ); + + // Sets the desired center direction + virtual void SetCenterAngles( const QAngle &angles ) {} + virtual void SetAlwaysOrient( bool bOrient ) {} + virtual bool IsAlwaysOrienting( ) { return false; } + + // Used by the mobile shield. + + // Change the angular spring constant. This affects how fast the shield rotates to face the angles + // given in SetAngles. Higher numbers are more responsive, but if you go too high (around 40), it will + // jump past the specified angles and wiggle a little bit. + virtual void SetAngularSpringConstant( float flConstant ) {} + + // Move the shield out a certain amount. + virtual void SetFrontDistance( float flDistance ) {} + + // Called when we hit something that we deflect... + void RegisterDeflection(const Vector& vecDir, int bitsDamageType, trace_t *ptr); + + // Called when we hit something that we let through... + void RegisterPassThru(const Vector& vecDir, int bitsDamageType, trace_t *ptr); + + // Activates/deactivates a shield for collision purposes + void ActivateCollisions( bool activate ); + + // Does this shield protect from a particular damage type? + float ProtectionAmount( int weaponType ) const; + + // Deactivates all shields of players on a particular team + // If you don't specify a team, it'll affect all shields + static void ActivateShields( bool activate, int team = -1 ); + + // For collision testing + bool TestCollision( const Ray_t& ray, unsigned int mask, trace_t& trace ); + + // Called when the shield has moved + virtual void ShieldMoved() {} + + // Called when the shield is EMPed (or de-EMPed) + virtual void SetEMPed( bool isEmped ); + + // Indicates the visual center of the shape, may not be the actual center + // (best example is the dome: it projects from a point which is not + // at the center of the hemisphere). + virtual void SetGeometryOffset( const Vector& vector ) {}; + + // Shield power & recharging + void SetupRecharge( float flPower, float flDelay, float flAmount, float flTickTime ); + float GetPower( void ) { return m_flPower; } + void SetPower( float flPower ); + // Make the shield recharge it's health + void ShieldRechargeThink( void ); + + // Is this ray blocked by any shields? + static bool IsBlockedByShields( const Vector& src, const Vector& end ); + + virtual void SetOwnerEntity( CBaseEntity *pOwner ); + + virtual void SetThetaPhi( float flTheta, float flPhi ) { } + virtual void SetAttachmentIndex( int nAttachmentIndex ) {} + +protected: + // + // derived classes must implement these + // + virtual int Width() { return 0; } + virtual int Height() { return 0; } + virtual bool IsPanelActive( int x, int y ) { return true; } + virtual const Vector& GetPoint( int x, int y ) { return vec3_origin; } + bool IsEMPed() const { return m_bIsEMPed; } + + float m_flPower; + float m_flMaxPower; + CNetworkVar( float, m_flPowerLevel ); // m_flPower mapped to 0->1 range for networking + float m_flRechargeDelay; + float m_flRechargeAmount; + float m_flRechargeTime; + float m_flNextRechargeTime; + +private: + int m_iBuckshotHitsThisFrame; + float m_flLastProbeTime; + CNetworkVar( bool, m_bIsEMPed ); + CNetworkVar( int, m_nOwningPlayerIndex ); + + // List of all active shields + static CUtlVector< CShield* > s_Shields; +}; + + +//----------------------------------------------------------------------------- +// Class factory methods to create the various versions of the shield +//----------------------------------------------------------------------------- +CShield* CreateMobileShield( CBaseEntity *owner, float flFrontDistance = 0 ); + + +//----------------------------------------------------------------------------- +// Returns true if the entity is a shield +//----------------------------------------------------------------------------- +bool IsShield( CBaseEntity *pEnt ); + +#endif // TF_SHIELD_H diff --git a/game/server/tf2/tf_shield_flat.cpp b/game/server/tf2/tf_shield_flat.cpp new file mode 100644 index 0000000..cbe187d --- /dev/null +++ b/game/server/tf2/tf_shield_flat.cpp @@ -0,0 +1,150 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" +#include "tf_shield_flat.h" +#include "tf_shieldshared.h" + + +//----------------------------------------------------------------------------- +// Data tables +//----------------------------------------------------------------------------- +LINK_ENTITY_TO_CLASS( shield_flat, CShieldFlat ); + +IMPLEMENT_SERVERCLASS_ST(CShieldFlat, DT_Shield_Flat) + + SendPropInt (SENDINFO(m_ShieldState), 2, SPROP_UNSIGNED ), + SendPropFloat (SENDINFO(m_Width), 8, 0, 0, 256 ), + SendPropFloat (SENDINFO(m_Height), 8, 0, 0, 256 ), + +END_SEND_TABLE() + + +//----------------------------------------------------------------------------- +// Spawn +//----------------------------------------------------------------------------- +void CShieldFlat::Spawn( ) +{ + BaseClass::Spawn(); + m_ShieldState = 0; + ShieldMoved(); +} + + +//----------------------------------------------------------------------------- +// Computes the shield bounding box +//----------------------------------------------------------------------------- +void CShieldFlat::ShieldMoved( void ) +{ + Vector forward, right, up; + AngleVectors( GetAbsAngles(), &forward, &right, &up ); + + VectorMA( GetAbsOrigin(), -m_Width * 0.5, right, m_Pos[0] ); + VectorMA( m_Pos[0], -m_Height * 0.5, up, m_Pos[0] ); + VectorMA( m_Pos[0], m_Width, right, m_Pos[1] ); + VectorMA( m_Pos[0], m_Height, up, m_Pos[2] ); + VectorMA( m_Pos[2], m_Width, right, m_Pos[3] ); + + m_LastAngles = GetAbsAngles(); + m_LastPosition = GetAbsOrigin(); +} + + +//----------------------------------------------------------------------------- +// Shield size +//----------------------------------------------------------------------------- +void CShieldFlat::SetSize( float w, float h ) +{ + m_Width = w; + m_Height = h; + Vector mins( -1.0/16.0f, -w * 0.5f, -h * 0.5f ); + Vector maxs( 1.0/16.0f, w * 0.5f, h * 0.5f ); + UTIL_SetSize( this, mins, maxs ); + ShieldMoved(); +} + + +//----------------------------------------------------------------------------- +// Compute world axis-aligned bounding box +//----------------------------------------------------------------------------- +void CShieldFlat::ComputeWorldSpaceSurroundingBox( Vector *pWorldMins, Vector *pWorldMaxs ) +{ + TransformAABB( CollisionProp()->CollisionToWorldTransform(), + CollisionProp()->OBBMins(), CollisionProp()->OBBMaxs(), *pWorldMins, *pWorldMaxs ); +} + + +//----------------------------------------------------------------------------- +// Shield points +//----------------------------------------------------------------------------- +const Vector& CShieldFlat::GetPoint( int x, int y ) +{ + if ((m_LastAngles != GetAbsAngles()) || (m_LastPosition != GetAbsOrigin() )) + { + ShieldMoved(); + } + + int i = (x >= 1); + i += (y >= 1) * 2; + return m_Pos[i]; +} + + +//----------------------------------------------------------------------------- +// Called when the shield is EMPed +//----------------------------------------------------------------------------- +void CShieldFlat::SetEMPed( bool isEmped ) +{ + CShield::SetEMPed(isEmped); + if (IsEMPed()) + { + m_ShieldState |= SHIELD_FLAT_EMP; + } + else + { + m_ShieldState &= ~SHIELD_FLAT_EMP; + } +} + + +//----------------------------------------------------------------------------- +// Shield is killed here +//----------------------------------------------------------------------------- +void CShieldFlat::Activate( bool active ) +{ + if (active) + { + m_ShieldState &= ~SHIELD_FLAT_INACTIVE; + } + else + { + m_ShieldState |= SHIELD_FLAT_INACTIVE; + } + ActivateCollisions( active ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Create a mobile version of the shield +//----------------------------------------------------------------------------- +CShieldFlat* CreateFlatShield( CBaseEntity *pOwner, float w, float h, const Vector& relOrigin, const QAngle &relAngles ) +{ + CShieldFlat *pShield = (CShieldFlat*)CreateEntityByName("shield_flat"); + + pShield->SetParent( pOwner ); + pShield->SetOwnerEntity( pOwner ); + UTIL_SetOrigin( pShield, relOrigin ); + + // Compute relative angles.... + pShield->SetLocalAngles( relAngles ); + pShield->ChangeTeam( pOwner->GetTeamNumber() ); + pShield->Spawn(); + pShield->SetSize( w , h ); + + return pShield; +} + diff --git a/game/server/tf2/tf_shield_flat.h b/game/server/tf2/tf_shield_flat.h new file mode 100644 index 0000000..c107db4 --- /dev/null +++ b/game/server/tf2/tf_shield_flat.h @@ -0,0 +1,68 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef TF_SHIELD_FLAT_H +#define TF_SHIELD_FLAT_H + +#ifdef _WIN32 +#pragma once +#endif + +#include "tf_shield.h" +#include "mathlib/vector.h" + +//----------------------------------------------------------------------------- +// +// This is the shield projected by the grenade +// +//----------------------------------------------------------------------------- + +class CShieldFlat : public CShield +{ + DECLARE_CLASS( CShieldFlat, CShield ); + DECLARE_SERVERCLASS(); + +public: + void SetSize( float w, float h ); + + virtual void Spawn( void ); + + virtual void SetEMPed( bool isEmped ); + + void Activate( bool active ); + + virtual int Width() { return 2; } + virtual int Height() { return 2; } + virtual bool IsPanelActive( int x, int y ) { return true; } + virtual const Vector& GetPoint( int x, int y ); + virtual void ComputeWorldSpaceSurroundingBox( Vector *pWorldMins, Vector *pWorldMaxs ); + +public: + // Think methods + void ShieldMoved(); + +public: + + // networked data + CNetworkVar( unsigned char, m_ShieldState ); + CNetworkVar( float, m_Width ); + CNetworkVar( float, m_Height ); + +private: + QAngle m_LastAngles; + Vector m_LastPosition; + Vector m_Pos[4]; +}; + + +//----------------------------------------------------------------------------- +// Purpose: Create a mobile version of the shield +//----------------------------------------------------------------------------- + +CShieldFlat* CreateFlatShield( CBaseEntity *pOwner, float w, float h, const Vector& relOrigin, const QAngle &relAngles ); + +#endif TF_SHIELD_FLAT_H
\ No newline at end of file diff --git a/game/server/tf2/tf_shieldgrenade.cpp b/game/server/tf2/tf_shieldgrenade.cpp new file mode 100644 index 0000000..157395f --- /dev/null +++ b/game/server/tf2/tf_shieldgrenade.cpp @@ -0,0 +1,424 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" +#include "BaseAnimating.h" +#include "tf_shieldgrenade.h" +#include "tf_shieldshared.h" +#include "tf_shield_flat.h" +#include "tf_player.h" +#include "engine/IEngineSound.h" +#include "Sprite.h" + +#define SHIELD_GRENADE_FUSE_TIME 2.25f + +//----------------------------------------------------------------------------- +// +// The shield grenade class +// +//----------------------------------------------------------------------------- + +class CShieldGrenade : public CBaseAnimating +{ + DECLARE_CLASS( CShieldGrenade, CBaseAnimating ); +public: + DECLARE_DATADESC(); + + CShieldGrenade(); + + virtual void Spawn( void ); + virtual void Precache( void ); + virtual void UpdateOnRemove( void ); + void SetLifetime( float timer ); + + int GetDamageType() const { return DMG_ENERGYBEAM; } + + void StickyTouch( CBaseEntity *pOther ); + void BeepThink( void ); + void ShieldActiveThink( void ); + void DeathThink( void ); + + virtual bool CanTakeEMPDamage() { return true; } + virtual bool TakeEMPDamage( float duration ); + +private: + // Check when we're done with EMP + void CheckEMPDamageFinish( ); + void CreateShield( ); + void ComputeActiveThinkTime( ); + void BounceSound( ); + +private: + // Make sure all grenades explode + Vector m_LastCollision; + + // Time when EMP runs out + float m_flEMPDamageEndTime; + float m_ShieldLifetime; + float m_flDetonateTime; + + // Are we EMPed? + bool m_IsEMPed; + bool m_IsDeployed; + + // The deployed shield + CHandle<CShieldFlat> m_hDeployedShield; + + CSprite *m_pLiveSprite; +}; + +//----------------------------------------------------------------------------- +// Data table +//----------------------------------------------------------------------------- + +// Global Savedata for friction modifier +BEGIN_DATADESC( CShieldGrenade ) + + // Function Pointers + DEFINE_FUNCTION( StickyTouch ), + DEFINE_FUNCTION( BeepThink ), + DEFINE_FUNCTION( ShieldActiveThink ), + DEFINE_FUNCTION( DeathThink ), + +END_DATADESC() + +LINK_ENTITY_TO_CLASS( grenade_shield, CShieldGrenade ); +PRECACHE_REGISTER( grenade_shield ); + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CShieldGrenade::CShieldGrenade() +{ + UseClientSideAnimation(); + m_hDeployedShield.Set(0); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CShieldGrenade::Precache( void ) +{ + PrecacheModel( "models/weapons/w_grenade.mdl" ); + + PrecacheScriptSound( "ShieldGrenade.Bounce" ); + PrecacheScriptSound( "ShieldGrenade.StickBeep" ); + + PrecacheModel( "sprites/redglow1.vmt" ); + + BaseClass::Precache(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CShieldGrenade::Spawn( void ) +{ + BaseClass::Spawn(); + m_LastCollision.Init( 0, 0, 0 ); + SetMoveType( MOVETYPE_FLYGRAVITY, MOVECOLLIDE_FLY_BOUNCE ); + SetSolid( SOLID_BBOX ); + SetGravity( 1.0 ); + SetFriction( 0.9 ); + SetModel( "models/weapons/w_grenade.mdl"); + UTIL_SetSize(this, Vector( -4, -4, -4), Vector(4, 4, 4)); + m_IsEMPed = false; + m_IsDeployed = false; + + m_flEMPDamageEndTime = 0.0f; + + SetTouch( StickyTouch ); + SetCollisionGroup( TFCOLLISION_GROUP_GRENADE ); + + // Create a green light + m_pLiveSprite = CSprite::SpriteCreate( "sprites/redglow1.vmt", GetLocalOrigin() + Vector(0,0,1), false ); + m_pLiveSprite->SetTransparency( kRenderGlow, 0, 0, 255, 128, kRenderFxNoDissipation ); + m_pLiveSprite->SetScale( 1 ); + m_pLiveSprite->SetAttachment( this, 0 ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CShieldGrenade::UpdateOnRemove( void ) +{ + if ( m_pLiveSprite ) + { + UTIL_Remove( m_pLiveSprite ); + m_pLiveSprite = NULL; + } + + BaseClass::UpdateOnRemove(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CShieldGrenade::SetLifetime( float timer ) +{ + m_ShieldLifetime = timer; +} + + +//----------------------------------------------------------------------------- +// EMP Related methods +//----------------------------------------------------------------------------- +bool CShieldGrenade::TakeEMPDamage( float duration ) +{ + m_flEMPDamageEndTime = gpGlobals->curtime + duration; + m_IsEMPed = true; + if (m_hDeployedShield) + { + m_hDeployedShield->SetEMPed(true); + + // Recompute the next think time + ComputeActiveThinkTime(); + } + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: See if EMP impairment time has elapsed +//----------------------------------------------------------------------------- +void CShieldGrenade::CheckEMPDamageFinish( void ) +{ + if ( !m_flEMPDamageEndTime || gpGlobals->curtime < m_flEMPDamageEndTime ) + return; + + m_flEMPDamageEndTime = 0.0f; + m_IsEMPed = false; + if (m_hDeployedShield) + { + m_hDeployedShield->SetEMPed(false); + + // Recompute the next think time + ComputeActiveThinkTime(); + } +} + + +//----------------------------------------------------------------------------- +// Plays a random bounce sound +//----------------------------------------------------------------------------- +void CShieldGrenade ::BounceSound( void ) +{ + EmitSound( "ShieldGrenade.Bounce" ); +} + +//----------------------------------------------------------------------------- +// Purpose: Make the grenade stick to whatever it touches +//----------------------------------------------------------------------------- +void CShieldGrenade::StickyTouch( CBaseEntity *pOther ) +{ + if (m_IsDeployed) + return; + + // The touch can get called multiple times - create an ignore case if we + // have already stuck. + if ( m_LastCollision == GetLocalOrigin() ) + return; + + // Only stick to floors... + Vector up( 0, 0, 1 ); + if ( DotProduct( GetTouchTrace().plane.normal, up ) < 0.5f ) + return; + + // Only stick to BSP models + if ( pOther->IsBSPModel() == false ) + return; + + BounceSound(); + SetAbsVelocity( vec3_origin ); + SetMoveType( MOVETYPE_NONE ); + + // Beep + EmitSound( "ShieldGrenade.StickBeep" ); + + // Start ticking... + SetThink( BeepThink ); + m_IsDeployed = true; + SetNextThink( gpGlobals->curtime + 0.01f ); + m_flDetonateTime = gpGlobals->curtime + SHIELD_GRENADE_FUSE_TIME; + + m_LastCollision = GetLocalOrigin(); +} + +//----------------------------------------------------------------------------- +// Purpose: Play beeping sounds until the charge explode +//----------------------------------------------------------------------------- +void CShieldGrenade::CreateShield( void ) +{ + /* + // Set the orientation of the shield based on + // the closest teammate's relative position... + float mindist = FLT_MAX; + Vector dir; + + for ( int i = 1; i <= gpGlobals->maxClients; i++ ) + { + CBaseTFPlayer *pPlayer = ToBaseTFPlayer( UTIL_PlayerByIndex(i) ); + if ( pPlayer ) + { + if (pPlayer->GetTeamNumber() == GetTeamNumber()) + { + Vector tempdir; + VectorSubtract( pPlayer->Center(), Center(), tempdir ); + float dist = VectorNormalize( tempdir ); + if (dist < mindist) + { + mindist = dist; + VectorCopy( tempdir, dir ); + } + } + } + } + + if( mindist == FLT_MAX ) + { + AngleVectors( GetAngles(), &dir ); + } + + // Never pitch the shield + dir.z = 0.0f; + VectorNormalize(dir); + + QAngle relAngles; + VMatrix parentMatrix; + VMatrix worldShieldMatrix; + VMatrix relativeMatrix; + VMatrix parentInvMatrix; + + // Construct a transform from shield to grenade (parent) + MatrixFromAngles( GetAngles(), parentMatrix ); + +#ifdef _DEBUG + bool ok = +#endif + MatrixInverseGeneral( parentMatrix, parentInvMatrix ); + Assert( ok ); + + Vector up( 0, 0, 1 ); + Vector left; + CrossProduct( up, dir, left ); + MatrixSetIdentity( worldShieldMatrix ); + worldShieldMatrix.SetUp( up ); + worldShieldMatrix.SetLeft( left ); + worldShieldMatrix.SetForward( dir ); + + MatrixMultiply( parentInvMatrix, worldShieldMatrix, relativeMatrix ); + MatrixToAngles( relativeMatrix, relAngles ); + */ + + Vector offset( 0, 0, SHIELD_GRENADE_HEIGHT * 0.5f ); + m_hDeployedShield = CreateFlatShield( this, SHIELD_GRENADE_WIDTH, + SHIELD_GRENADE_HEIGHT, offset, vec3_angle ); + + // Notify it about EMP state + if (m_IsEMPed) + m_hDeployedShield->SetEMPed(true); + + // Play a sound +// WeaponSound( SPECIAL1 ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Play beeping sounds until the charge explode +//----------------------------------------------------------------------------- +void CShieldGrenade::BeepThink( void ) +{ + if (!IsInWorld()) + { + UTIL_Remove( this ); + return; + } + + if (m_flDetonateTime <= gpGlobals->curtime) + { + // Here we must project the shield + CreateShield(); + SetThink( ShieldActiveThink ); + m_flDetonateTime = gpGlobals->curtime + m_ShieldLifetime; + + // Get the EMP state correct + CheckEMPDamageFinish(); + ComputeActiveThinkTime(); + } + else + { + SetNextThink( gpGlobals->curtime + 1.0f ); + } +} + + +//----------------------------------------------------------------------------- +// Compute next think time while active +//----------------------------------------------------------------------------- +void CShieldGrenade::ComputeActiveThinkTime( void ) +{ + // Next think should be when we detonate, unless we un-EMP before then + SetNextThink( gpGlobals->curtime + m_flDetonateTime ); + if (m_IsEMPed) + { + Assert( m_flEMPDamageEndTime != 0.0f ); + if ( m_flEMPDamageEndTime < GetNextThink() ) + SetNextThink( gpGlobals->curtime + m_flEMPDamageEndTime ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Here's where the grenade "detonates" +//----------------------------------------------------------------------------- +void CShieldGrenade::ShieldActiveThink( void ) +{ + if (m_flDetonateTime > gpGlobals->curtime) + { + // If it's not time to die, check EMP state + CheckEMPDamageFinish(); + } + else + { + if (m_hDeployedShield) + { + m_hDeployedShield->Activate( false ); + } + SetNextThink( gpGlobals->curtime + SHIELD_FLAT_SHUTDOWN_TIME + 0.2f ); + SetThink( DeathThink ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CShieldGrenade::DeathThink( void ) +{ + // kill the grenade + if (m_hDeployedShield) + { + UTIL_Remove( m_hDeployedShield ); + m_hDeployedShield.Set(0); + } + UTIL_Remove( this ); +} + +//----------------------------------------------------------------------------- +// Creates a shield grenade +//----------------------------------------------------------------------------- +CBaseEntity *CreateShieldGrenade( const Vector &position, const QAngle &angles, const Vector &velocity, const QAngle &angVelocity, CBaseEntity *pOwner, float timer ) +{ + CShieldGrenade *pGrenade = (CShieldGrenade *)CBaseEntity::Create( "grenade_shield", position, angles, pOwner ); + pGrenade->SetLifetime( timer ); + pGrenade->SetAbsVelocity( velocity ); + + if (pOwner) + { + pGrenade->ChangeTeam( pOwner->GetTeamNumber() ); + } + + return pGrenade; +}
\ No newline at end of file diff --git a/game/server/tf2/tf_shieldgrenade.h b/game/server/tf2/tf_shieldgrenade.h new file mode 100644 index 0000000..c97b35e --- /dev/null +++ b/game/server/tf2/tf_shieldgrenade.h @@ -0,0 +1,19 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef TF_SHIELDGRENADE_H +#define TF_SHIELDGRENADE_H +#pragma once + + +class CBaseEntity; +struct edict_t; + +CBaseEntity *CreateShieldGrenade( const Vector& position, const QAngle &angles, + const Vector& velocity, const QAngle &angVelocity, CBaseEntity *pOwner, float timer ); + +#endif // TF_SHIELDGRENADE_H diff --git a/game/server/tf2/tf_stats.cpp b/game/server/tf2/tf_stats.cpp new file mode 100644 index 0000000..f2e3f02 --- /dev/null +++ b/game/server/tf2/tf_stats.cpp @@ -0,0 +1,624 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: game stat gathering +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" +#include "tf_stats.h" +#include "tf_shareddefs.h" +#include "tf_team.h" +#include "tf_player.h" +#include "utlbuffer.h" +#include "filesystem.h" +#include "igamesystem.h" +#include "textstatsmgr.h" +#include "info_act.h" + +static ConVar tf_stats( "tf_stats", "0", 0, "Enable stat gathering for TF2." ); + +//----------------------------------------------------------------------------- +// Collect stats every N seconds +//----------------------------------------------------------------------------- +#define TF_STATS_COLLECTION_TIME 1 + +#define TF_STAT_FILE "tf_stat_total" +#define TF_TEAM_STAT_FILE "tf_stat_team" +#define TF_PLAYER_STAT_FILE "tf_stat_class" + +static char s_pStatFile[MAX_PATH]; +static char s_pTeamStatFile[MAX_PATH]; +static char s_pPlayerStatFile[MAX_PATH]; + +//----------------------------------------------------------------------------- +// Strings assocaited with the stats +//----------------------------------------------------------------------------- +static const char *s_pStatStrings[TF_STAT_COUNT] = +{ + "Ferry Control", // TF_STAT_FERRY_CONTROL + "Resource Chunks Spawned", // TF_STAT_RESOURCE_CHUNKS_SPAWNED, + "Resource Gems Spawned", // TF_STAT_RESOURCE_PROCESSED_CHUNKS_SPAWNED, + "Resource Chunks Retired", // TF_STAT_RESOURCE_CHUNKS_RETIRED, +}; + +// These are the strings for the team stats above TFCLASS_CLASS_COUNT. +static const char *s_pNonClassTeamStatStrings[TF_TEAM_STAT_COUNT-TFCLASS_CLASS_COUNT] = +{ + "Player Count", // TF_TEAM_STAT_PLAYER_COUNT, + "Resources Collected", // TF_TEAM_STAT_RESOURCES_COLLECTED, + "Resources Harvested", // TF_TEAM_STAT_RESOURCES_HARVESTED, + "Chunks Dropped", // TF_TEAM_STAT_RESOURCE_CHUNKS_DROPPED, + "Chunks Collected", // TF_TEAM_STAT_RESOURCE_CHUNKS_COLLECTED, + "Kill Count", // TF_TEAM_STAT_KILL_COUNT, + "Destroyed Objects", // TF_TEAM_STAT_DESTROYED_OBJECT_COUNT, + "Ferry Control Time", // TF_TEAM_STAT_FERRY_CONTROL_TIME, +}; + +// These are initialized in the first call to GetTeamStatString(). +static const char *s_pTeamStatStrings[TF_TEAM_STAT_COUNT]; +static bool s_bTeamStatStringsInitted = false; + +static const char *s_pPlayerStatStrings[TF_PLAYER_STAT_COUNT] = +{ + "Player Count", // TF_PLAYER_STAT_PLAYER_COUNT + "Player Seconds", // TF_PLAYER_STAT_PLAYER_SECONDS + "Seconds At Least One Existed", // TF_PLAYER_STAT_EXISTING_SECONDS + + "Resources Acquired", // TF_PLAYER_STAT_RESOURCES_ACQUIRED + "Resources Acquired From Chunks", // TF_PLAYER_STAT_RESOURCES_ACQUIRED_FROM_CHUNKS + "Resources Carried", // TF_PLAYER_STAT_RESOURCES_CARRIED, + "Resources Spent", // TF_PLAYER_STAT_RESOURCES_SPENT, + "Object Value", // TF_PLAYER_STAT_CURRENT_OBJECT_VALUE + "Objects Owned", // TF_PLAYER_STAT_OBJECT_COUNT, + + "Kill Count", // TF_PLAYER_STAT_KILL_COUNT, + "Objects Destroyed", // TF_PLAYER_STAT_DESTROYED_OBJECT_COUNT, + "Health Given", // TF_PLAYER_STAT_HEALTH_GIVEN, + + "Animation Idle Time", // TF_PLAYER_STAT_ANIMATION_IDLE, + "Animation Walk Time", // TF_PLAYER_STAT_ANIMATION_WALKING, + "Animation Run Time", // TF_PLAYER_STAT_ANIMATION_RUNNING, + "Animation Crouch Time",// TF_PLAYER_STAT_ANIMATION_CROUCHING, + "Animation Jump Time", // TF_PLAYER_STAT_ANIMATION_JUMPING, + "Animation Other Time", // TF_PLAYER_STAT_ANIMATION_OTHER, +}; + + +static const char *GetStatString( int stat ) +{ + if (stat < TF_STAT_FIRST_OBJECT_BUILT) + return s_pStatStrings[stat]; + + static char s_TempBuf[256]; + Q_snprintf( s_TempBuf, sizeof( s_TempBuf ), "%s Count Built", GetObjectInfo( stat - TF_STAT_FIRST_OBJECT_BUILT )->m_pClassName ); + return s_TempBuf; +} + +static const char *GetTeamStatString( int stat ) +{ + if ( !s_bTeamStatStringsInitted ) + { + s_bTeamStatStringsInitted = true; + + // Go through and fill in the strings. + for ( int i=0; i < TFCLASS_CLASS_COUNT; i++ ) + s_pTeamStatStrings[i] = GetTFClassInfo( i )->m_pClassName; + + for ( i=TFCLASS_CLASS_COUNT; i < TF_TEAM_STAT_COUNT; i++ ) + s_pTeamStatStrings[i] = s_pNonClassTeamStatStrings[i - TFCLASS_CLASS_COUNT]; + } + + return s_pTeamStatStrings[stat]; +} + +static const char *GetPlayerStatString( int stat ) +{ + return s_pPlayerStatStrings[stat]; +} + + +//----------------------------------------------------------------------------- +// Implementation of the TF stats class +//----------------------------------------------------------------------------- +class CTFStats : public CAutoGameSystem, public ITFStats +{ +public: + CTFStats(); + + // Inherited from IAutoServerSystem + virtual void LevelInitPreEntity(); + virtual void FrameUpdatePostEntityThink( ); + + // Clear out the stats + their history + void ResetStats(); + + void IncrementStat( TFStatId_t stat, int nIncrement ); + void SetStat( TFStatId_t stat, int nAmount ); + + void IncrementTeamStat( int nTeam, TFTeamStatId_t stat, int nIncrement ); + void SetTeamStat( int nTeam, TFTeamStatId_t stat, int nAmount ); + + void IncrementPlayerStat( CBaseEntity *pPlayer, TFPlayerStatId_t stat, int nIncrement ); + void ClearPlayerStat( int nTeam, TFPlayerStatId_t stat ); + + // We need to be ticked once a frame + void FrameUpdate( ); + +private: + struct Stat_t + { + int m_nCount; + }; + + typedef const char * (*StatNameFunc_t)( int stat ); + + // Collects frame-based stats + void CollectFrameStats( ); + void CollectStats( ); + + int GetStat( TFStatId_t stat ) const { return m_Stats[stat].m_nCount; } + int GetTeamStat( int nTeam, TFTeamStatId_t stat ) const { return m_TeamStats[nTeam][stat].m_nCount; } + void WriteHeader( CUtlBuffer &buf, const char *pPrefix, int nCount, StatNameFunc_t func, bool bTerminate = true ); + void WriteStatLine( CUtlBuffer &buf, int nCount, Stat_t *pStats, bool bTerminate = true ); + void WriteAvgStatLine( CUtlBuffer &buf ); + void WriteStats(); + void WriteTeamStats(); + void WritePlayerStats( ); + void EraseFile( const char *pFileName ); + void AppendToFile( const char *pFileName, CUtlBuffer &buf ); + void ComputeFileNames(); + void ClearStats(); + + // Compute class-based stats from the player stats + int ComputeClassStats( int nTeam, TFClass classType, Stat_t *pStats ); + + bool m_bWrittenHeader; + int m_nLastWriteTime; + Stat_t m_Stats[TF_STAT_COUNT]; + Stat_t m_TeamStats[MAX_TF_TEAMS][TF_TEAM_STAT_COUNT]; + Stat_t m_ClassStats[MAX_TF_TEAMS][TFCLASS_CLASS_COUNT][TF_PLAYER_STAT_COUNT]; +}; + + +//----------------------------------------------------------------------------- +// Accessor method +//----------------------------------------------------------------------------- +static CTFStats s_TFStats; +ITFStats *TFStats() +{ + return &s_TFStats; +} + + +//----------------------------------------------------------------------------- +// Constructor, destructor +//----------------------------------------------------------------------------- +CTFStats::CTFStats() +{ + ResetStats(); +} + +//----------------------------------------------------------------------------- +// Clear out the stats + their history +//----------------------------------------------------------------------------- +void CTFStats::ClearStats() +{ + int i; + for (i = 0; i < TF_STAT_COUNT; ++i) + { + SetStat( (TFStatId_t)i, 0 ); + } + + for (i = 0; i < MAX_TF_TEAMS; ++i) + { + for (int j = 0; j < TF_TEAM_STAT_COUNT; ++j) + { + SetTeamStat(i, (TFTeamStatId_t)j, 0); + } + } + + for (int nTeam = 0; nTeam < MAX_TF_TEAMS; ++nTeam) + { + for (i = 0; i < TFCLASS_CLASS_COUNT; ++i) + { + for (int j = 0; j < TF_PLAYER_STAT_COUNT; ++j) + { + m_ClassStats[nTeam][i][(TFPlayerStatId_t)j].m_nCount = 0; + } + } + } + +} + +//----------------------------------------------------------------------------- +// Clear out the stats + their history +//----------------------------------------------------------------------------- +void CTFStats::ResetStats() +{ + ClearStats(); + m_bWrittenHeader = false; + m_nLastWriteTime = -9999; +} + + + +//----------------------------------------------------------------------------- +// Inherited from IAutoServerSystem +//----------------------------------------------------------------------------- +void CTFStats::LevelInitPreEntity() +{ + ResetStats(); +} + + +//----------------------------------------------------------------------------- +// Update stats... +//----------------------------------------------------------------------------- +void CTFStats::IncrementStat( TFStatId_t stat, int nIncrement ) +{ + m_Stats[stat].m_nCount += nIncrement; +} + +void CTFStats::SetStat( TFStatId_t stat, int nAmount ) +{ + m_Stats[stat].m_nCount = nAmount; +} + +void CTFStats::IncrementTeamStat( int nTeam, TFTeamStatId_t stat, int nIncrement ) +{ + m_TeamStats[nTeam][stat].m_nCount += nIncrement; +} + +void CTFStats::SetTeamStat( int nTeam, TFTeamStatId_t stat, int nAmount ) +{ + m_TeamStats[nTeam][stat].m_nCount = nAmount; +} + +void CTFStats::IncrementPlayerStat( CBaseEntity *pEntity, TFPlayerStatId_t stat, int nIncrement ) +{ + if (!pEntity->IsPlayer()) + return; + + CBaseTFPlayer *pTFPlayer = static_cast<CBaseTFPlayer*>(pEntity); + int nTeam = pTFPlayer->GetTeamNumber(); + CPlayerClass *pPlayerClass = pTFPlayer->GetPlayerClass(); + int nClass = pPlayerClass ? pPlayerClass->GetTFClass() : TFCLASS_UNDECIDED; + + m_ClassStats[nTeam][nClass][stat].m_nCount += nIncrement; +} + +void CTFStats::ClearPlayerStat( int nTeam, TFPlayerStatId_t stat ) +{ + for (int i = 0; i < TFCLASS_CLASS_COUNT; ++i) + { + m_ClassStats[nTeam][i][stat].m_nCount = 0; + } +} + + +//----------------------------------------------------------------------------- +// We need to be ticked once a frame +//----------------------------------------------------------------------------- +void CTFStats::CollectFrameStats( ) +{ + // This collects a bunch of polled stats so we don't have to pollute + // a bunch of code + for (int i = 0; i < MAX_TF_TEAMS; ++i) + { + CTFTeam *pTeam = GetGlobalTFTeam(i); + for (int j = pTeam->GetNumPlayers(); --j >= 0; ) + { + CBaseTFPlayer *pPlayer = static_cast<CBaseTFPlayer*>(pTeam->GetPlayer(j)); + if (!pPlayer) + continue; + + int nTimeMS = 1000 * gpGlobals->frametime; + + switch( pPlayer->GetActivity() ) + { + case ACT_IDLE: + IncrementPlayerStat( pPlayer, TF_PLAYER_STAT_ANIMATION_IDLE, nTimeMS ); + break; + + case ACT_WALK: + IncrementPlayerStat( pPlayer, TF_PLAYER_STAT_ANIMATION_WALKING, nTimeMS ); + break; + + case ACT_RUN: + IncrementPlayerStat( pPlayer, TF_PLAYER_STAT_ANIMATION_RUNNING, nTimeMS ); + break; + + case ACT_JUMP: + IncrementPlayerStat( pPlayer, TF_PLAYER_STAT_ANIMATION_JUMPING, nTimeMS ); + break; + + case ACT_CROUCHIDLE: + IncrementPlayerStat( pPlayer, TF_PLAYER_STAT_ANIMATION_CROUCHING, nTimeMS ); + break; + + default: + IncrementPlayerStat( pPlayer, TF_PLAYER_STAT_ANIMATION_OTHER, nTimeMS ); + break; + } + } + } +} + + +//----------------------------------------------------------------------------- +// We need to be ticked once a frame +//----------------------------------------------------------------------------- +void CTFStats::CollectStats( ) +{ + // This collects a bunch of polled stats so we don't have to pollute + // a bunch of code + bool bAtLeastOnePlayer = false; + for (int i = 0; i < MAX_TF_TEAMS; ++i) + { + CTFTeam *pTeam = GetGlobalTFTeam(i); + + for ( int iClass=0; iClass < TFCLASS_CLASS_COUNT; iClass++ ) + { + SetTeamStat( i, (TFTeamStatId_t)iClass, pTeam->GetNumOfClass( (TFClass)iClass ) ); + } + + SetTeamStat( i, TF_TEAM_STAT_PLAYER_COUNT, pTeam->GetNumPlayers() ); + + if ( GetStat( TF_STAT_FERRY_CONTROL ) == i ) + { + IncrementTeamStat( i, TF_TEAM_STAT_FERRY_CONTROL_TIME, TF_STATS_COLLECTION_TIME ); + } + + ClearPlayerStat( i, TF_PLAYER_STAT_OBJECT_COUNT ); + ClearPlayerStat( i, TF_PLAYER_STAT_RESOURCES_CARRIED ); + ClearPlayerStat( i, TF_PLAYER_STAT_CURRENT_OBJECT_VALUE ); + ClearPlayerStat( i, TF_PLAYER_STAT_PLAYER_COUNT ); + + bool bClassEncountered[TFCLASS_CLASS_COUNT]; + memset( bClassEncountered, 0, TFCLASS_CLASS_COUNT * sizeof(bool) ); + for (int j = pTeam->GetNumPlayers(); --j >= 0; ) + { + bAtLeastOnePlayer = true; + + CBaseTFPlayer *pPlayer = static_cast<CBaseTFPlayer*>(pTeam->GetPlayer(j)); + IncrementPlayerStat( pPlayer, TF_PLAYER_STAT_OBJECT_COUNT, pPlayer->GetObjectCount() ); + IncrementPlayerStat( pPlayer, TF_PLAYER_STAT_RESOURCES_CARRIED, pPlayer->GetBankResources() ); + IncrementPlayerStat( pPlayer, TF_PLAYER_STAT_PLAYER_COUNT, 1 ); + IncrementPlayerStat( pPlayer, TF_PLAYER_STAT_PLAYER_SECONDS, 1 ); + + CPlayerClass *pPlayerClass = pPlayer->GetPlayerClass(); + int nClass = pPlayerClass ? pPlayerClass->GetTFClass() : TFCLASS_UNDECIDED; + + if (!bClassEncountered[nClass]) + { + IncrementPlayerStat( pPlayer, TF_PLAYER_STAT_EXISTING_SECONDS, 1 ); + bClassEncountered[nClass] = true; + } + + // Count up the cost of all current objects.. + int nCost = 0; + int pObjectCount[OBJ_LAST]; + memset( pObjectCount, 0, OBJ_LAST * sizeof(int) ); + for (int k = pPlayer->GetObjectCount(); --k >= 0; ) + { + CBaseObject *pObject = pPlayer->GetObject(k); + if (pObject) + { + int nType = pObject->GetType(); + nCost += CalculateObjectCost( nType, pObjectCount[nType], pPlayer->GetTeamNumber(), false ); + ++pObjectCount[nType]; + } + } + IncrementPlayerStat( pPlayer, TF_PLAYER_STAT_CURRENT_OBJECT_VALUE, nCost ); + } + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Output : char const +//----------------------------------------------------------------------------- +void CTFStats::ComputeFileNames() +{ + Q_snprintf( s_pStatFile, sizeof( s_pStatFile ), "%s.txt", TF_STAT_FILE ); + Q_snprintf( s_pTeamStatFile, sizeof( s_pTeamStatFile ), "%s.txt", TF_TEAM_STAT_FILE ); + Q_snprintf( s_pPlayerStatFile, sizeof( s_pPlayerStatFile ), "%s.txt", TF_PLAYER_STAT_FILE ); +} + + +//----------------------------------------------------------------------------- +// File access. +//----------------------------------------------------------------------------- +void CTFStats::EraseFile( const char *pFileName ) +{ + filesystem->RemoveFile( pFileName, "GAME" ); +} + +void CTFStats::AppendToFile( const char *pFileName, CUtlBuffer &buf ) +{ + FileHandle_t fh = filesystem->Open( pFileName, "a", "LOGDIR" ); + if (fh != FILESYSTEM_INVALID_HANDLE) + { + filesystem->Write( buf.Base(), buf.TellPut(), fh ); + filesystem->Close( fh ); + } +} + + +//----------------------------------------------------------------------------- +// Write out a header. +//----------------------------------------------------------------------------- +void CTFStats::WriteHeader( CUtlBuffer &buf, const char *pPrefix, int nCount, StatNameFunc_t func, bool bTerminate ) +{ + for (int i = 0; i < nCount-1; ++i) + { + buf.Printf("%s %s\t", pPrefix, func(i) ); + } + + buf.Printf( bTerminate ? "%s %s\n" : "%s %s\t", pPrefix, func(nCount-1) ); +} + +void CTFStats::WriteStatLine( CUtlBuffer &buf, int nCount, Stat_t *pStats, bool bTerminate ) +{ + for (int i = 0; i < nCount-1; ++i) + { + buf.Printf("%d\t", pStats[i].m_nCount ); + } + + buf.Printf( bTerminate ? "%d\n" : "%d\t", pStats[nCount-1].m_nCount ); +} + +void CTFStats::WriteAvgStatLine( CUtlBuffer &buf ) +{ + int nTeam, i, j; + for ( nTeam = 0; nTeam < MAX_TF_TEAMS; ++nTeam ) + { + for ( i = 0; i < TF_PLAYER_STAT_COUNT; ++i ) + { + for ( j = 1; j < TFCLASS_CLASS_COUNT; ++j ) + { + if (!GetTFClassInfo(j)->m_pCurrentlyActive) + continue; + + buf.Printf("%d\t", m_ClassStats[nTeam][j][i].m_nCount); + } + } + } + + // Blat out that last tab and replace with a \n + buf.SeekPut( CUtlBuffer::SEEK_CURRENT, -1 ); + buf.Printf("\n"); +} + +//----------------------------------------------------------------------------- +// Write out total stats... +//----------------------------------------------------------------------------- +void CTFStats::WriteStats() +{ + CUtlBuffer buf( 0, 1024, CUtlBuffer::TEXT_BUFFER ); + + if (!m_bWrittenHeader) + { + EraseFile( s_pStatFile ); + WriteHeader( buf, "", TF_STAT_COUNT, GetStatString ); + } + + WriteStatLine( buf, TF_STAT_COUNT, m_Stats ); + AppendToFile( s_pStatFile, buf ); +} + + +//----------------------------------------------------------------------------- +// Write out total stats... +//----------------------------------------------------------------------------- +void CTFStats::WriteTeamStats() +{ + CUtlBuffer buf( 0, 1024, CUtlBuffer::TEXT_BUFFER ); + + int i,j; + if (!m_bWrittenHeader) + { + EraseFile( s_pTeamStatFile ); + for ( i = 0; i < TF_TEAM_STAT_COUNT; ++i ) + { + for ( j = 0; j < MAX_TF_TEAMS; ++j ) + { + buf.Printf("Team %d %s\t", j, GetTeamStatString(i) ); + } + } + // Blat out that last tab and replace with a \n + buf.SeekPut( CUtlBuffer::SEEK_CURRENT, -1 ); + buf.Printf("\n"); + } + + for ( i = 0; i < TF_TEAM_STAT_COUNT; ++i ) + { + for ( j = 0; j < MAX_TF_TEAMS; ++j ) + { + buf.Printf("%d\t", m_TeamStats[j][i].m_nCount ); + } + } + + // Blat out that last tab and replace with a \n + buf.SeekPut( CUtlBuffer::SEEK_CURRENT, -1 ); + buf.Printf("\n"); + + AppendToFile( s_pTeamStatFile, buf ); +} + + +//----------------------------------------------------------------------------- +// Write out total stats... +//----------------------------------------------------------------------------- +void CTFStats::WritePlayerStats( ) +{ + CUtlBuffer buf( 0, 1024, CUtlBuffer::TEXT_BUFFER ); + + int i, j, nTeam; + if (!m_bWrittenHeader) + { + EraseFile( s_pPlayerStatFile ); + for ( nTeam = 0; nTeam < MAX_TF_TEAMS; ++nTeam ) + { + for ( i = 0; i < TF_PLAYER_STAT_COUNT; ++i ) + { + for ( j = 1; j < TFCLASS_CLASS_COUNT; ++j ) + { + if (!GetTFClassInfo(j)->m_pCurrentlyActive) + continue; + + buf.Printf("Team %d %s %s\t", nTeam, GetTFClassInfo( j )->m_pClassName, GetPlayerStatString(i) ); + } + } + } + + // Blat out that last tab and replace with a \n + buf.SeekPut( CUtlBuffer::SEEK_CURRENT, -1 ); + buf.Printf("\n"); + } + + WriteAvgStatLine( buf ); + + AppendToFile( s_pPlayerStatFile, buf ); +} + + +//----------------------------------------------------------------------------- +// We need to be ticked once a frame +//----------------------------------------------------------------------------- +void CTFStats::FrameUpdatePostEntityThink( ) +{ + if (!tf_stats.GetBool()) + return; + + // Don't stat gather during waiting acts + if ( CurrentActIsAWaitingAct() ) + return; + + CollectFrameStats(); + + // NOTE: We could keep track of the history here if we wanted for later + // display when the map ends + + // Record the history every so often + if (gpGlobals->curtime - m_nLastWriteTime < TF_STATS_COLLECTION_TIME) + return; + + if (!m_bWrittenHeader) + { + ComputeFileNames(); + } + + CollectStats(); + WriteStats(); + WriteTeamStats(); + WritePlayerStats(); + ClearStats(); + + // By this point, we've written the header for each file + m_bWrittenHeader = true; + + m_nLastWriteTime = gpGlobals->curtime; +} diff --git a/game/server/tf2/tf_stats.h b/game/server/tf2/tf_stats.h new file mode 100644 index 0000000..c5e68e5 --- /dev/null +++ b/game/server/tf2/tf_stats.h @@ -0,0 +1,106 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: game stat gathering +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef TF_STATS_H +#define TF_STATS_H + +#ifdef _WIN32 +#pragma once +#endif + +#include "tf_shareddefs.h" + +// NOTE: If you change these enums, change the string lists in tf_stats.cpp! +enum TFStatId_t +{ + TF_STAT_FERRY_CONTROL = 0, + TF_STAT_RESOURCE_CHUNKS_SPAWNED, + TF_STAT_RESOURCE_PROCESSED_CHUNKS_SPAWNED, + TF_STAT_RESOURCE_CHUNKS_RETIRED, + + // NOTE: These should go last + TF_STAT_FIRST_OBJECT_BUILT, + TF_STAT_LAST_OBJECT_BUILT = TF_STAT_FIRST_OBJECT_BUILT + OBJ_LAST - 1, + + TF_STAT_COUNT, +}; + +enum TFTeamStatId_t +{ + // First TFCLASS_CLASS_COUNT entries are the # of players of each class. + + TF_TEAM_STAT_PLAYER_COUNT = TFCLASS_CLASS_COUNT, + + TF_TEAM_STAT_RESOURCES_COLLECTED, + TF_TEAM_STAT_RESOURCES_HARVESTED, + TF_TEAM_STAT_RESOURCE_CHUNKS_DROPPED, + TF_TEAM_STAT_RESOURCE_CHUNKS_COLLECTED, + + TF_TEAM_STAT_KILL_COUNT, + TF_TEAM_STAT_DESTROYED_OBJECT_COUNT, + + TF_TEAM_STAT_FERRY_CONTROL_TIME, + + TF_TEAM_STAT_COUNT, +}; + +enum TFPlayerStatId_t +{ + // Player count + TF_PLAYER_STAT_PLAYER_COUNT = 0, + TF_PLAYER_STAT_PLAYER_SECONDS, + TF_PLAYER_STAT_EXISTING_SECONDS, + TF_PLAYER_STAT_FIRST_AVERAGE_STAT, + + // Economy-based stats + TF_PLAYER_STAT_RESOURCES_ACQUIRED = TF_PLAYER_STAT_FIRST_AVERAGE_STAT, + TF_PLAYER_STAT_RESOURCES_ACQUIRED_FROM_CHUNKS, + TF_PLAYER_STAT_RESOURCES_CARRIED, + TF_PLAYER_STAT_RESOURCES_SPENT, + TF_PLAYER_STAT_CURRENT_OBJECT_VALUE, + TF_PLAYER_STAT_OBJECT_COUNT, + + // Combat based stats + TF_PLAYER_STAT_KILL_COUNT, + TF_PLAYER_STAT_DESTROYED_OBJECT_COUNT, + TF_PLAYER_STAT_HEALTH_GIVEN, + + // Animation-based stats + TF_PLAYER_STAT_ANIMATION_IDLE, + TF_PLAYER_STAT_ANIMATION_WALKING, + TF_PLAYER_STAT_ANIMATION_RUNNING, + TF_PLAYER_STAT_ANIMATION_CROUCHING, + TF_PLAYER_STAT_ANIMATION_JUMPING, + TF_PLAYER_STAT_ANIMATION_OTHER, + + TF_PLAYER_STAT_COUNT, +}; + + +class ITFStats +{ +public: + // Clear out the stats + their history + virtual void ResetStats() = 0; + + virtual void IncrementStat( TFStatId_t stat, int nIncrement ) = 0; + virtual void SetStat( TFStatId_t stat, int nAmount ) = 0; + + virtual void IncrementTeamStat( int nTeam, TFTeamStatId_t stat, int nIncrement ) = 0; + virtual void SetTeamStat( int nTeam, TFTeamStatId_t stat, int nAmount ) = 0; + + virtual void IncrementPlayerStat( CBaseEntity *pPlayer, TFPlayerStatId_t stat, int nIncrement ) = 0; +}; + + +//----------------------------------------------------------------------------- +// Accessor method +//----------------------------------------------------------------------------- +ITFStats *TFStats(); + + +#endif // TF_STATS_H diff --git a/game/server/tf2/tf_stressentities.cpp b/game/server/tf2/tf_stressentities.cpp new file mode 100644 index 0000000..27e5116 --- /dev/null +++ b/game/server/tf2/tf_stressentities.cpp @@ -0,0 +1,107 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" +#include "test_stressentities.h" +#include "tf_flare.h" +#include "tf_shareddefs.h" +#include "plasmaprojectile.h" + + +// ------------------------------------------------------------------------------------ // +// Functions to create random entities. +// ------------------------------------------------------------------------------------ // + +CBaseEntity* CreateSignalFlare() +{ + CBaseEntity *pLocalPlayer = UTIL_GetLocalPlayer(); + if ( pLocalPlayer ) + { + return CSignalFlare::Create( GetRandomSpot(), QAngle( 0, 0, 0 ), pLocalPlayer, 3 ); + } + else + { + return NULL; + } +} + + +CBaseEntity* CreateResourceChunk() +{ + CBaseEntity *pChunk = MoveToRandomSpot( CreateEntityByName( "resource_chunk" ) ); + if ( pChunk ) + { + pChunk->Spawn(); + } + + return pChunk; +} + + +CBaseEntity* CreateResourceBox() +{ + CBaseEntity *pRet = MoveToRandomSpot( CreateEntityByName( "obj_box" ) ); + if ( pRet ) + { + pRet->Spawn(); + } + + return pRet; +} + +CBaseEntity* CreatePlasmaProjectile() +{ + CBaseEntity *pLocalPlayer = UTIL_GetLocalPlayer(); + if ( pLocalPlayer ) + { + Vector vForward; + vForward.Random(-1,1); + VectorNormalize( vForward ); + + CBasePlasmaProjectile *pRet = CBasePlasmaProjectile::Create( + Vector(0,0,0), + vForward, + 0, + pLocalPlayer ); + + if ( pRet ) + MoveToRandomSpot( pRet ); + + return pRet; + } + + return NULL; +} + +CBaseEntity* CreatePlasmaShot() +{ + CBaseEntity *pEnt = MoveToRandomSpot( CreateEntityByName( "base_plasmaprojectile" ) ); + if ( pEnt ) + { + CBasePlasmaProjectile *pRet = dynamic_cast< CBasePlasmaProjectile* >( pEnt ); + if ( pRet ) + { + return pRet; + } + else + { + UTIL_Remove( pEnt ); + } + } + + return NULL; +} + + +REGISTER_STRESS_ENTITY( CreateResourceChunk ); +REGISTER_STRESS_ENTITY( CreateResourceBox ); +REGISTER_STRESS_ENTITY( CreatePlasmaProjectile ); +REGISTER_STRESS_ENTITY( CreatePlasmaShot ); +REGISTER_STRESS_ENTITY( CreateSignalFlare ); + + + diff --git a/game/server/tf2/tf_team.cpp b/game/server/tf2/tf_team.cpp new file mode 100644 index 0000000..0bec380 --- /dev/null +++ b/game/server/tf2/tf_team.cpp @@ -0,0 +1,1434 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Team management class. Contains all the details for a specific team +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "team.h" +#include "tf_team.h" +#include "tf_func_resource.h" +#include "tf_player.h" +#include "techtree.h" +#include "tf_obj.h" +#include "tf_obj_resupply.h" +#include "orders.h" +#include "entitylist.h" +#include "team_spawnpoint.h" +#include "team_messages.h" +#include "tf_obj_powerpack.h" +#include "tf_gamerules.h" +#include "engine/IEngineSound.h" +#include "tier1/strtools.h" +#include "tf_stats.h" +#include "tf_obj_buff_station.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +#define OBJECT_COVERED_DIST 1000 +#define RESOURCE_GIVE_TIME 30 +#define RESOURCE_GIVE_AMOUNT 150 +#define RESOURCE_DONATION_AMT_PER_PLAYER 10 + +bool IsEntityVisibleToTactical( int iLocalTeamNumber, int iLocalTeamPlayers, + int iLocalTeamObjects, int iEntIndex, const char *pEntName, int pEntTeamNumber, const Vector &pEntOrigin ); +extern ConVar tf_destroyobjects; + +//----------------------------------------------------------------------------- +// Purpose: SendProxy that converts the UtlVector list of radar scanners to entindexes, where it's reassembled on the client +//----------------------------------------------------------------------------- +void SendProxy_ObjectList( const SendProp *pProp, const void *pStruct, const void *pData, DVariant *pOut, int iElement, int objectID ) +{ + CTFTeam *pTeam = (CTFTeam*)pData; + + // If this fails, then SendProxyArrayLength_TeamObjects didn't work. + Assert( iElement < pTeam->GetNumObjects() ); + + CBaseObject *pObject = pTeam->GetObject(iElement); + EHANDLE hObject; + hObject = pObject; + + SendProxy_EHandleToInt( pProp, pStruct, &hObject, pOut, iElement, objectID ); +} + + +int SendProxyArrayLength_TeamObjects( const void *pStruct, int objectID ) +{ + CTFTeam *pTeam = (CTFTeam*)pStruct; + int iObjects = pTeam->GetNumObjects(); + Assert( iObjects < MAX_OBJECTS_PER_TEAM ); + return iObjects; +} + + +// Datatable +IMPLEMENT_SERVERCLASS_ST(CTFTeam, DT_TFTeam) + SendPropFloat( SENDINFO(m_fResources), 16, SPROP_NOSCALE ), + SendPropFloat( SENDINFO(m_fPotentialResources), 16, SPROP_NOSCALE ), + SendPropInt( SENDINFO(m_bHaveZone), 1, SPROP_UNSIGNED ), + + SendPropArray2( + SendProxyArrayLength_TeamObjects, + SendPropInt("object_array_element", 0, SIZEOF_IGNORE, NUM_NETWORKED_EHANDLE_BITS, SPROP_UNSIGNED, SendProxy_ObjectList), + MAX_OBJECTS_PER_TEAM, + 0, + "object_array" + ) +END_SEND_TABLE() + +LINK_ENTITY_TO_CLASS( tf_team_manager, CTFTeam ); + +//----------------------------------------------------------------------------- +// Purpose: Get a pointer to the specified TF team manager +//----------------------------------------------------------------------------- +CTFTeam *GetGlobalTFTeam( int iIndex ) +{ + return (CTFTeam*)GetGlobalTeam( iIndex ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Needed because this is an entity, but should never be used +//----------------------------------------------------------------------------- +void CTFTeam::Init( const char *pName, int iNumber ) +{ + BaseClass::Init( pName, iNumber ); + + InitializeTeamResources(); + InitializeTechTree(); + InitializeOrders(); + ClearMessages(); + + m_flNextResourceTime = 0; + + // Only detect changes every half-second. + NetworkProp()->SetUpdateInterval( 0.75f ); + + m_flTotalResourcesSoFar = m_iLastUpdateSentAt = 0; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CTFTeam::~CTFTeam( void ) +{ + m_aResourcesBeingCollected.Purge(); + m_aResupplyBeacons.Purge(); + m_aObjects.Purge(); + m_aOrders.Purge(); + + delete m_pTechnologyTree; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFTeam::Precache( void ) +{ + // Precache all the technologies in the techtree + for ( int i = 0; i < m_pTechnologyTree->GetNumberTechnologies(); i++ ) + { + PrecacheTechnology( m_pTechnologyTree->GetTechnology(i) ); + } + + PrecacheScriptSound( "TFTeam.CapturedZone" ); + PrecacheScriptSound( "TFTeam.LostZone" ); + PrecacheScriptSound( "TFTeam.ObtainStolenTechnology" ); + PrecacheScriptSound( "TFTeam.BoughtPreferredTechnology" ); + PrecacheScriptSound( "TFTeam.AddOrder" ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Precache a technology's files +//----------------------------------------------------------------------------- +void CTFTeam::PrecacheTechnology( CBaseTechnology *pTech ) +{ + // Precache sounds for every class result + for (int i = 0; i < TFCLASS_CLASS_COUNT; i++ ) + { + if ( pTech->GetSoundFile(i) && (pTech->GetSoundFile(i)[0] != 0) ) + { + PrecacheScriptSound( pTech->GetSoundFile(i) ); + pTech->SetClassResultSound( i, 0 ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Called every frame +//----------------------------------------------------------------------------- +void CTFTeam::Think( void ) +{ + UpdateOrders(); + UpdateMessages(); + + // FIXME: Try this out? + /* + // Give resources to the team at regular intervals + if (gpGlobals->curtime >= m_flNextResourceTime) + { + AddTeamResources( RESOURCE_GIVE_AMOUNT ); + m_flNextResourceTime = gpGlobals->curtime + RESOURCE_GIVE_TIME; + } + */ + + UpdateTechnologies(); + + /* FIXME: Re-enable once we figure out what the correct orders should be + // Create new personal orders + if ( m_flPersonalOrderUpdateTime < gpGlobals->curtime ) + { + CreatePersonalOrders(); + m_flPersonalOrderUpdateTime = gpGlobals->curtime + PERSONAL_ORDER_UPDATE_TIME; + } + */ +} + +//----------------------------------------------------------------------------- +// DATA HANDLING +//----------------------------------------------------------------------------- +// Purpose: Check to see if we should resend the entire tech tree to a player on Hud reinitialisation, which happens every player respawn +//----------------------------------------------------------------------------- +void CTFTeam::UpdateClientData( CBasePlayer *pPlayer ) +{ + CBaseTFPlayer *pTFPlayer = (CBaseTFPlayer *)pPlayer; + // If we're initialising the hud, update all technologies + if ( pTFPlayer->HUDNeedsRestart() ) + { + // Check all the technologies and resend any that differ from this client's representation + for ( int i = 0; i < m_pTechnologyTree->GetNumberTechnologies(); i++ ) + { + // Update all technologies + CBaseTechnology *technology = m_pTechnologyTree->GetTechnology(i); + if ( technology ) + { + // Check to see if any resource levels have changed + if ( pTFPlayer->AvailableTech(i).m_nResourceLevel != technology->GetResourceLevel() ) + { + UpdateClientTechnology( i, pTFPlayer ); + continue; + } + + if ( technology->GetAvailable() != pTFPlayer->AvailableTech(i).m_nAvailable ) + { + UpdateClientTechnology( i, pTFPlayer ); + continue; + } + + byte pcount = technology->GetPreferenceCount(); + if ( pTFPlayer->GetPreferredTechnology() == i ) + { + pcount |= 0x80; + } + + if ( pcount != pTFPlayer->AvailableTech(i).m_nUserCount ) + { + UpdateClientTechnology( i, pTFPlayer ); + } + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Update a technology for a player +//----------------------------------------------------------------------------- +void CTFTeam::UpdateClientTechnology( int iTechID, CBaseTFPlayer *pPlayer ) +{ + CBaseTechnology *pTechnology = m_pTechnologyTree->GetTechnology( iTechID ); + if ( !pTechnology ) + return; + + byte pcount = pTechnology->GetPreferenceCount(); + + if ( pPlayer->GetPreferredTechnology() == iTechID ) + { + pcount |= 0x80; + } + + CSingleUserRecipientFilter user( pPlayer ); + user.MakeReliable(); + + // Update this technology + UserMessageBegin( user, "Technology" ); + WRITE_BYTE( iTechID ); + WRITE_BYTE( pTechnology->GetAvailable() ); + WRITE_BYTE( pcount ); + WRITE_SHORT( (short)pTechnology->GetResourceLevel() ); + MessageEnd(); + + // Update the player's client tech representation + pPlayer->AvailableTech(iTechID).m_nAvailable = pTechnology->GetAvailable(); + pPlayer->AvailableTech(iTechID).m_nUserCount = pcount; + pPlayer->AvailableTech(iTechID).m_nResourceLevel = pTechnology->GetResourceLevel(); + + /* + Msg( "Sent %s(%d) to %s:\n", pTechnology->GetName(), iTechID, pPlayer->GetPlayerName() ); + Msg( " Available: %d\n", pTechnology->GetAvailable() ); + Msg( " PrefCount: %d\n", pTechnology->GetPreferenceCount() ); + Msg( " Level : %0.2f\n", pTechnology->GetResourceLevel() ); + */ +} + +//----------------------------------------------------------------------------- +// Purpose: Check to see if any technology has changed, and resend it to players if it has +//----------------------------------------------------------------------------- +void CTFTeam::UpdateTechnologyData( void ) +{ + for ( int i = 0; i < m_pTechnologyTree->GetNumberTechnologies(); i++ ) + { + // Update all technologies + CBaseTechnology *pTechnology = m_pTechnologyTree->GetTechnology(i); + if ( pTechnology && pTechnology->IsDirty() ) + { + // Send it to all our clients + for ( int iPlayer = 0; iPlayer < m_aPlayers.Count(); iPlayer++ ) + { + CBaseTFPlayer *pPlayer = (CBaseTFPlayer *)m_aPlayers[iPlayer]; + UpdateClientTechnology( i, pPlayer ); + } + + pTechnology->SetDirty( false ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CTFTeam::ShouldTransmitToPlayer( CBasePlayer* pRecipient, CBaseEntity* pEntity ) +{ + return IsEntityVisibleToTactical( pEntity ); +} + +//----------------------------------------------------------------------------- +// Purpose: Is the specified entity visible on this team's tactical view? +//----------------------------------------------------------------------------- +bool CTFTeam::IsEntityVisibleToTactical( CBaseEntity *pEntity ) +{ + return ::IsEntityVisibleToTactical( GetTeamNumber(), GetNumPlayers(), + GetNumObjects(), pEntity->entindex(), (char*)STRING(pEntity->m_iClassname), + pEntity->GetTeamNumber(), pEntity->GetAbsOrigin() ); +} + +//----------------------------------------------------------------------------- +// RESOURCES +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +// Purpose: Add a resource zone to the list of zones to collect from +//----------------------------------------------------------------------------- +void CTFTeam::AddResourceZone( CResourceZone *pResource ) +{ + TRACE_OBJECT( UTIL_VarArgs( "%0.2f CTFTeam::AddResourceZone adding res zone %p to team %s\n", gpGlobals->curtime, + pResource, GetName() ) ); + + // If this resource is already owned by another team, remove it from them + CTFTeam *pOwners = pResource->GetOwningTeam(); + if ( pOwners ) + { + pOwners->RemoveResourceZone( pResource ); + } + + pResource->SetOwningTeam( GetTeamNumber() ); + + m_aResourcesBeingCollected.AddToTail( pResource ); + m_bHaveZone = true; + + // Tell all the team's members + for ( int i = 0; i < m_aPlayers.Count(); i++ ) + { + CBaseTFPlayer *pPlayer = (CBaseTFPlayer *)m_aPlayers[i]; + CSingleUserRecipientFilter filter( pPlayer ); + filter.MakeReliable(); + CBaseEntity::EmitSound( filter, pPlayer->entindex(), "TFTeam.CapturedZone" ); + } + + // Recalculate team's orders + RecalcOrders(); +} + +//----------------------------------------------------------------------------- +// Purpose: Remove a resource zone from the list of zones being collected from +//----------------------------------------------------------------------------- +void CTFTeam::RemoveResourceZone( CResourceZone *pResource ) +{ + TRACE_OBJECT( UTIL_VarArgs( "%0.2f CTFTeam::RemoveResourceZone removing res zone %p from team %s\n", gpGlobals->curtime, + pResource, GetName() ) ); + + // Now remove the zone from our list + m_aResourcesBeingCollected.FindAndRemove( pResource ); + + // Still have a zone if there are other zones in the list + m_bHaveZone = ( m_aResourcesBeingCollected.Count() > 0 ); + + // Tell all the team's members + for ( int i = 0; i < m_aPlayers.Count(); i++ ) + { + CBaseTFPlayer *pPlayer = (CBaseTFPlayer *)m_aPlayers[i]; + CSingleUserRecipientFilter filter( pPlayer ); + filter.MakeReliable(); + CBaseEntity::EmitSound( filter, pPlayer->entindex(),"TFTeam.LostZone" ); + } + + // Recalculate team's orders + RecalcOrders(); +} + +//----------------------------------------------------------------------------- +// Purpose: Recalculate the potential resources +//----------------------------------------------------------------------------- +void CTFTeam::UpdatePotentialResources( void ) +{ + // Set potential to current amount + m_fPotentialResources = GetTeamResources(); + + // This used to be used for collectors. + // It could be updated to count all incoming resources in en-route resource boxes. +} + +//----------------------------------------------------------------------------- +// Purpose: Return the amount of resources a player should get when joining this team +//----------------------------------------------------------------------------- +float CTFTeam::GetJoiningPlayerResources( void ) +{ + // If we had our banks set recently, use that amount + if ( gpGlobals->curtime < (m_flLastBankSetTime + 30.0) ) + return m_flLastBankSetAmount; + + if ( !GetNumPlayers() ) + return 0; + + // Otherwise, take the average of all the players on the team + RecomputeTeamResources(); + return ( GetTeamResources() / GetNumPlayers() ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFTeam::SetRecentBankSet( float flResources ) +{ + m_flLastBankSetAmount = flResources; + m_flLastBankSetTime = gpGlobals->curtime; +} + +//------------------------------------------------------------------------------------------------------------------ +// TECHNOLOGY TREE +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFTeam::InitializeTechTree( void ) +{ + m_pTechnologyTree = new CTechnologyTree( filesystem, GetTeamNumber() ); + + // Now iterate through added techs and automatically make level 0 techs available + for (int i = 0; i < m_pTechnologyTree->GetNumberTechnologies(); i++ ) + { + CBaseTechnology *tech = m_pTechnologyTree->GetTechnology(i); + if ( !tech ) + continue; + + if ( tech->GetLevel() == 0 ) + { + EnableTechnology( tech ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CTechnologyTree *CTFTeam::GetTechnologyTree( void ) +{ + return m_pTechnologyTree; +} + +//----------------------------------------------------------------------------- +// Purpose: A new technology has been attained by this team. Give it to every player. +//----------------------------------------------------------------------------- +void CTFTeam::EnableTechnology( CBaseTechnology *technology, bool bStolen ) +{ + CTeamFortress *rules = TFGameRules(); + if ( rules ) + { + // Disable autoswitching if we are getting a weapon + rules->SetAllowWeaponSwitch( false ); + } + + // Set the technology's resources to the costs + // Needed because technologies can be enabled through other means than resource spending + technology->ForceComplete(); + + // Apply technology to team first. + technology->AddTechnologyToTeam( this ); + + // Iterate though players + for (int i = 0; i < m_aPlayers.Count(); i++ ) + { + CBaseTFPlayer *pPlayer = (CBaseTFPlayer *)m_aPlayers[i]; + technology->AddTechnologyToPlayer( pPlayer ); + + CSingleUserRecipientFilter filter( pPlayer ); + filter.MakeReliable(); + + // Play the sound + if (bStolen) + { + CBaseEntity::EmitSound( filter, pPlayer->entindex(), "TFTeam.ObtainStolenTechnology" ); + } + else + { + if ( technology->GetSoundFile(0) && technology->GetSoundFile(0)[0] ) + { + EmitSound_t ep; + ep.m_nChannel = CHAN_STATIC; + ep.m_pSoundName = technology->GetSoundFile(0); + + CBaseEntity::EmitSound( filter, pPlayer->entindex(), ep ); + } + } + + // Remove all the player's votes on this technology + CBaseTechnology *pPreferredTech = m_pTechnologyTree->GetTechnology( pPlayer->GetPreferredTechnology() ); + if ( pPreferredTech && pPreferredTech == technology ) + { + // Tell the player his preferred tech has been bought + if (!bStolen) + { + CBaseEntity::EmitSound( filter, pPlayer->entindex(), "TFTeam.BoughtPreferredTechnology" ); + } + + pPlayer->SetPreferredTechnology( m_pTechnologyTree, -1 ); + } + } + + // Let the team see if it wants to do anything with this specific technology + GainedNewTechnology( technology ); + + // Reenable autoswitching + if ( rules ) + { + rules->SetAllowWeaponSwitch( true ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: For debugging.. +//----------------------------------------------------------------------------- +void CTFTeam::EnableAllTechnologies() +{ + for ( int i = 0; i < m_pTechnologyTree->GetNumberTechnologies(); i++ ) + { + CBaseTechnology *technology = m_pTechnologyTree->GetTechnology(i); + if ( !technology || technology->IsHidden() ) + continue; + + EnableTechnology( technology ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Called any time a player votes/changes preferences on the client +//----------------------------------------------------------------------------- +void CTFTeam::RecomputePreferences( void ) +{ + // Zero total counters + m_pTechnologyTree->ClearPreferenceCount(); + + // Zero out all preferences and iterate through active players + int i; + for ( i = 0; i < m_pTechnologyTree->GetNumberTechnologies(); i++ ) + { + CBaseTechnology *technology = m_pTechnologyTree->GetTechnology(i); + if ( technology ) + { + // Zero internal counters + technology->ZeroPreferences(); + } + } + + // Now loop through players and see what's preferred + for ( i = 0; i < m_aPlayers.Count(); i++ ) + { + CBaseTFPlayer *pPlayer = (CBaseTFPlayer *)m_aPlayers[i]; + if ( !pPlayer ) + continue; + + int preferred = pPlayer->GetPreferredTechnology(); + // No preference set, don't worry about this player + if ( preferred == -1 ) + continue; + + if ( preferred < 0 || preferred >= MAX_TECHNOLOGIES ) + { + Msg( "Player %s tried to set preference to out of range tech %i\n", preferred ); + continue; + } + + // Reference technology + CBaseTechnology *technology = m_pTechnologyTree->GetTechnology(preferred); + Assert( technology ); + if ( !technology ) + continue; + + // Msg( "player %s prefers %s\n", pPlayer->GetPlayerName(), technology->GetPrintName() ); + + // Add one vote + technology->IncrementPreferences(); + // Add one vote to totals + m_pTechnologyTree->IncrementPreferences(); + } + + // Any time preferences are changed/set, see if we should make any purchases immediately. + RecomputePurchases(); +} + +//----------------------------------------------------------------------------- +// Purpose: Figure our how many resources we've got in the team +//----------------------------------------------------------------------------- +void CTFTeam::RecomputeTeamResources( void ) +{ + // Recalculate the total amount of resources the team has + m_fResources = 0.0f; + for ( int i = 0; i < GetNumPlayers(); i++ ) + { + m_fResources += ((CBaseTFPlayer*)GetPlayer(i))->GetBankResources(); + } + + UpdatePotentialResources(); +} + +//----------------------------------------------------------------------------- +// Purpose: Attempt to spend resources according to player's preferences +//----------------------------------------------------------------------------- +void CTFTeam::RecomputePurchases( void ) +{ + RecomputeTeamResources(); + + // Cycle through all players, and spend their resources on the technologies they're voting for + for ( int iPlayer = 0; iPlayer < m_aPlayers.Count(); iPlayer++ ) + { + CBaseTFPlayer *pPlayer = (CBaseTFPlayer *)GetPlayer( iPlayer ); + + // See if he has any resources to spend on a tech + if ( pPlayer->GetBankResources() <= 0 ) + continue; + + // Has he got a preffered tech? + if ( pPlayer->GetPreferredTechnology() != -1 ) + { + // Get the player's voted-for technology + CBaseTechnology *pPreferredTech = m_pTechnologyTree->GetTechnology( pPlayer->GetPreferredTechnology() ); + if ( pPreferredTech && pPreferredTech->GetAvailable() == false ) + { + if ( !pPreferredTech->GetResourceCost() ) + continue; + + // Try to spend resources on the tech + int iResourcesSpent = MIN( pPlayer->GetBankResources(), pPreferredTech->GetResourceCost() - pPreferredTech->GetResourceLevel() ); + if ( pPreferredTech->IncreaseResourceLevel( iResourcesSpent ) ) + { + // The technology's had enough resources spent to buy it, so enable it + EnableTechnology( pPreferredTech ); + Msg( "%s bought %s\n", GetName(), pPreferredTech->GetPrintName() ); + } + + // Reduce the player's bank + if ( iResourcesSpent ) + { + pPlayer->RemoveBankResources( iResourcesSpent ); + } + } + } + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Return true if the team owns the specified technology +//----------------------------------------------------------------------------- +bool CTFTeam::HasNamedTechnology( const char *name ) +{ + // Look it up + // FIXME: This could be too slow, consider using #define'd/indexed names? + CBaseTechnology *tech = m_pTechnologyTree->GetTechnology( name ); + if ( !tech ) + return false; + if ( !tech->GetAvailable() ) + return false; + + return true; +} + + +//----------------------------------------------------------------------------- +// Purpose: A new technology has been received by the team. Do anything specific to this technology here. +//----------------------------------------------------------------------------- +void CTFTeam::GainedNewTechnology( CBaseTechnology *pTechnology ) +{ +} + + +//----------------------------------------------------------------------------- +// Purpose: Called by the team's Think function +//----------------------------------------------------------------------------- +void CTFTeam::UpdateTechnologies( void ) +{ + // Update clients + UpdateTechnologyData(); +} + + +//------------------------------------------------------------------------------------------------------------------ +// PLAYERS +//----------------------------------------------------------------------------- +// Purpose: Add the specified player to this team. Remove them from their current team, if any. +//----------------------------------------------------------------------------- +void CTFTeam::AddPlayer( CBasePlayer *pPlayer ) +{ + BaseClass::AddPlayer( pPlayer ); + + // Give the player this team's technology + for ( int i = 0; i < m_pTechnologyTree->GetNumberTechnologies(); i++ ) + { + CBaseTechnology *technology = m_pTechnologyTree->GetTechnology(i); + if ( !technology ) + continue; + + if ( technology->IsHidden() ) + continue; + + // Not yet available to team, skip + if ( !technology->GetAvailable() ) + continue; + + // Add it. + technology->AddTechnologyToPlayer( (CBaseTFPlayer*)pPlayer ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Clean up the player's objects when they leave +//----------------------------------------------------------------------------- +void CTFTeam::RemovePlayer( CBasePlayer *pPlayer ) +{ + BaseClass::RemovePlayer( pPlayer ); + + // Destroy all objects belonging to this player + if ( tf_destroyobjects.GetFloat() ) + { + // Work backwards through the list because objects remove themselves + int iSize = m_aObjects.Count(); + for (int i = iSize-1; i >= 0; i--) + { + if ( (m_aObjects[i]->GetBuilder() == pPlayer) && (m_aObjects[i]->ShouldAutoRemove()) ) + { + UTIL_Remove( m_aObjects[i] ); + } + } + } + + RecomputePreferences(); +} + +//----------------------------------------------------------------------------- +// Purpose: Return the number of team members of the specified class +//----------------------------------------------------------------------------- +int CTFTeam::GetNumOfClass( TFClass iClass ) +{ + int iNumber = 0; + for ( int i = 0; i < GetNumPlayers(); i++ ) + { + if ( ((CBaseTFPlayer*)GetPlayer(i))->IsClass(iClass) ) + { + iNumber++; + } + } + return iNumber; +} + +//------------------------------------------------------------------------------------------------------------------ +// RESOURCE BANK +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFTeam::InitializeTeamResources( void ) +{ + m_fResources = 0.0f; + m_fPotentialResources = 0.0f; + m_bHaveZone = false; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +float CTFTeam::GetTeamResources( void ) +{ + return m_fResources; +} + +//----------------------------------------------------------------------------- +// Purpose: Add resources to this team +//----------------------------------------------------------------------------- +int CTFTeam::AddTeamResources( float fAmount, int nStat ) +{ + fAmount = clamp(fAmount, 0, 9999.f); + + m_flTotalResourcesSoFar += fAmount; + + TFStats()->IncrementTeamStat( GetTeamNumber(), TF_TEAM_STAT_RESOURCES_COLLECTED, fAmount ); + + // Divvy the resources out to the players + int iAmountPerPlayer = Ceil2Int( fAmount / GetNumPlayers() ); // Yes, this does create some resources in the roundoff. + for ( int i = 0; i < GetNumPlayers(); i++ ) + { + CBaseTFPlayer *pPlayer = (CBaseTFPlayer*)GetPlayer(i); + pPlayer->AddBankResources( iAmountPerPlayer ); + TFStats()->IncrementPlayerStat( pPlayer, TF_PLAYER_STAT_RESOURCES_ACQUIRED, fAmount ); + + if (nStat >= 0) + { + TFStats()->IncrementPlayerStat( pPlayer, (TFPlayerStatId_t)nStat, fAmount ); + } + } + + ResourceLoadDeposited(); + + return iAmountPerPlayer; +} + +//----------------------------------------------------------------------------- +// Purpose: Give resources to the player +//----------------------------------------------------------------------------- +void CTFTeam::DonateResources( CBaseTFPlayer *pPlayer ) +{ + int nPlayerCount = GetNumPlayers(); + if (nPlayerCount <= 1) + return; + + int pResourceCount; + int pResourcePerPlayer; + int pDonationCount; + + bool bDonating = false; + pResourceCount = pPlayer->GetBankResources(); + + // Figure out how many resources per player to donate + pResourcePerPlayer = pResourceCount / (nPlayerCount - 1); + if (pResourceCount % (nPlayerCount - 1) != 0) + ++pResourcePerPlayer; + + // Clamp to max amt per teammate for each hit... + if (pResourcePerPlayer > RESOURCE_DONATION_AMT_PER_PLAYER) + pResourcePerPlayer = RESOURCE_DONATION_AMT_PER_PLAYER; + + // Figure out if we are donating anything at all + if (pResourceCount > 0) + bDonating = true; + if (!bDonating) + return; + + // Now that we've figured how much to donate, do it! + for ( int i = 0; i < nPlayerCount; i++ ) + { + CBaseTFPlayer *pDest = (CBaseTFPlayer*)GetPlayer(i); + if (pDest == pPlayer) + continue; + + // The last guy(s) gets the scraps... too bad. + int nCountToDonate = pResourceCount; + if (nCountToDonate > pResourcePerPlayer) + nCountToDonate = pResourcePerPlayer; + pResourceCount -= nCountToDonate; + pDonationCount = nCountToDonate; + + pPlayer->DonateResources( pDest, pDonationCount ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: New resources have just been dumped in the bank +//----------------------------------------------------------------------------- +void CTFTeam::ResourceLoadDeposited( void ) +{ + // HACK TEST CODE + // Remove after Resource Experiment! + static int iIncrements = 250; + if ( m_flTotalResourcesSoFar >= (m_iLastUpdateSentAt + iIncrements) ) + { + while ( m_flTotalResourcesSoFar >= (m_iLastUpdateSentAt + iIncrements) ) + { + m_iLastUpdateSentAt += iIncrements; + } + + EntityMessageBegin( (CBaseEntity*)this ); + WRITE_LONG( m_iLastUpdateSentAt ); + MessageEnd(); + } + + // Now see if we should buy anything + RecomputePurchases(); +} + +//------------------------------------------------------------------------------------------------------------------ +// RESUPPLY BEACONS +//----------------------------------------------------------------------------- +// Purpose: Add the specified resupply beacon to this team. +//----------------------------------------------------------------------------- +void CTFTeam::AddResupply( CObjectResupply *pResupply ) +{ + TRACE_OBJECT( UTIL_VarArgs( "%0.2f CTFTeam::AddResupply adding resupply %p to team %s\n", gpGlobals->curtime, + pResupply, GetName() ) ); + + m_aResupplyBeacons.AddToTail( pResupply ); +} + +//----------------------------------------------------------------------------- +// Purpose: Remove this resupply beacon from the team +//----------------------------------------------------------------------------- +void CTFTeam::RemoveResupply( CObjectResupply *pResupply ) +{ + TRACE_OBJECT( UTIL_VarArgs( "%0.2f CTFTeam::RemoveResupply remove resupply %p from team %s\n", gpGlobals->curtime, + pResupply, GetName() ) ); + + // Now remove the beacon from our list + m_aResupplyBeacons.FindAndRemove( pResupply ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int CTFTeam::GetNumObjects( int iObjectType ) +{ + // Asking for a count of a specific object type? + if ( iObjectType > 0 ) + { + int iCount = 0; + for ( int i = 0; i < GetNumObjects(); i++ ) + { + CBaseObject *pObject = GetObject(i); + if ( pObject && pObject->GetType() == iObjectType ) + { + iCount++; + } + } + return iCount; + } + + return m_aObjects.Count(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CBaseObject *CTFTeam::GetObject( int num ) +{ + Assert( num >= 0 && num < m_aObjects.Count() ); + return m_aObjects[ num ]; +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int CTFTeam::GetNumResupplies( void ) +{ + return m_aResupplyBeacons.Count(); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CObjectResupply *CTFTeam::GetResupply( int num ) +{ + Assert( num >= 0 && num < m_aResupplyBeacons.Count() ); + return m_aResupplyBeacons[ num ]; +} + + +bool CTFTeam::IsCoveredBySentryGun( const Vector &vPos ) +{ + for( int i=0; i < m_aObjects.Count(); i++ ) + { + CBaseObject *pObj = m_aObjects[i]; + + if ( pObj->IsSentrygun() && vPos.DistTo( pObj->GetAbsOrigin() ) < OBJECT_COVERED_DIST ) + return true; + + } + + return false; +} + + +int CTFTeam::GetNumShieldWallsCoveringPosition( const Vector &vPos ) +{ + int count = 0; + + for ( int i=0; i < m_aObjects.Count(); i++ ) + { + CBaseObject *pObj = m_aObjects[i]; + + if ( pObj->GetType() == OBJ_SHIELDWALL ) + { + if ( vPos.DistTo( pObj->GetAbsOrigin() ) < OBJECT_COVERED_DIST ) + ++count; + } + } + + return count; +} + + +int CTFTeam::GetNumResuppliesCoveringPosition( const Vector &vPos ) +{ + int count = 0; + + for ( int i=0; i < m_aResupplyBeacons.Count(); i++ ) + { + CBaseObject *pObj = m_aResupplyBeacons[i]; + if ( vPos.DistTo( pObj->GetAbsOrigin() ) < RESUPPLY_COVER_DIST ) + ++count; + } + + return count; +} + + +int CTFTeam::GetNumRespawnStationsCoveringPosition( const Vector &vPos ) +{ + int count = 0; + + for ( int i=0; i < m_aObjects.Count(); i++ ) + { + CBaseObject *pObj = m_aObjects[i]; + + if ( pObj->GetType() == OBJ_RESPAWN_STATION ) + { + if ( vPos.DistTo( pObj->GetAbsOrigin() ) < OBJECT_COVERED_DIST ) + { + ++count; + } + } + } + + return count; +} + + +//------------------------------------------------------------------------------------------------------------------ +// OBJECTS +//----------------------------------------------------------------------------- +// Purpose: Add the specified object to this team. +//----------------------------------------------------------------------------- +void CTFTeam::AddObject( CBaseObject *pObject ) +{ + TRACE_OBJECT( UTIL_VarArgs( "%0.2f CTFTeam::AddObject adding object %p:%s to team %s\n", gpGlobals->curtime, + pObject, pObject->GetClassname(), GetName() ) ); + + bool alreadyInList = IsObjectOnTeam( pObject ); + Assert( !alreadyInList ); + if ( !alreadyInList ) + { + m_aObjects.AddToTail( pObject ); + } +} + +//----------------------------------------------------------------------------- +// Returns true if the object is in the team's list of objects +//----------------------------------------------------------------------------- +bool CTFTeam::IsObjectOnTeam( CBaseObject *pObject ) const +{ + return ( m_aObjects.Find( pObject ) != -1 ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Remove this object from the team +// Removes all references from all sublists as well +//----------------------------------------------------------------------------- +void CTFTeam::RemoveObject( CBaseObject *pObject ) +{ + if ( m_aObjects.Count() <= 0 ) + return; + + if ( m_aObjects.Find( pObject ) != -1 ) + { + TRACE_OBJECT( UTIL_VarArgs( "%0.2f CTFTeam::RemoveObject removing %p:%s from %s\n", gpGlobals->curtime, + pObject, pObject->GetClassname(), GetName() ) ); + + m_aObjects.FindAndRemove( pObject ); + } + else + { + TRACE_OBJECT( UTIL_VarArgs( "%0.2f CTFTeam::RemoveObject couldn't remove %p:%s from %s\n", gpGlobals->curtime, + pObject, pObject->GetClassname(), GetName() ) ); + } +} + + +//------------------------------------------------------------------------------------------------------------------ +// ORDERS +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFTeam::InitializeOrders( void ) +{ + m_flPersonalOrderUpdateTime = 0; +} + +//----------------------------------------------------------------------------- +// Purpose: Add a new order to our list. If it already exists, bump it's priority to the new priority. +//----------------------------------------------------------------------------- +COrder* CTFTeam::AddOrder( + int iOrderType, + CBaseEntity *pTarget, + CBaseTFPlayer *pPlayer, + float flDistanceToRemove, + float flLifetime, + COrder *pNewOrder + ) +{ + // Remove any orders to the player. + RemoveOrdersToPlayer( pPlayer ); + + // The new system requires order class to be passed in. + Assert( pNewOrder ); + + // All the order create functions should just use new to create the order class, + // then we'll attach the edict in here. There's no reason to use LINK_ENTITY_TO_CLASS + // and CreateEntityByName. + Assert( !pNewOrder->edict() ); + pNewOrder->NetworkProp()->AttachEdict(); + + pNewOrder->ChangeTeam( GetTeamNumber() ); + OrderHandle hOrder; + hOrder = pNewOrder; + m_aOrders.AddToTail( hOrder ); + + // Update target + pNewOrder->SetTarget( pTarget ); + pNewOrder->SetDistance( flDistanceToRemove ); + + // Update lifetime. + pNewOrder->SetLifetime( flLifetime ); + + Assert( pPlayer->GetOrder() == NULL ); + + pNewOrder->SetOwner( pPlayer ); + pPlayer->SetOrder( pNewOrder ); + + // "New Order Received!" + CSingleUserRecipientFilter filter( pPlayer ); + filter.MakeReliable(); + CBaseEntity::EmitSound( filter, pPlayer->entindex(),"TFTeam.AddOrder" ); + + // Debug check.. it should never create an order with its termination conditions + // already met. + Assert( !pNewOrder->Update() ); + + return pNewOrder; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFTeam::RemoveOrder( COrder *pOrder ) +{ + OrderHandle hOrder; + hOrder = pOrder; + + m_aOrders.FindAndRemove( hOrder ); +} + +//----------------------------------------------------------------------------- +// Purpose: Recalculate the team's orders & their priorities +//----------------------------------------------------------------------------- +void CTFTeam::RecalcOrders( void ) +{ + // Update all existing orders + UpdateOrders(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFTeam::UpdateOrders( void ) +{ + // Tell all our current orders to update themselves. Walk backwards because we may remove them. + int iSize = m_aOrders.Count(); + for (int i = iSize-1; i >= 0; i--) + { + // Orders without owners should be removed + bool bShouldRemove = ( + !m_aOrders[i] || + !m_aOrders[i]->GetOwner() || + m_aOrders[i]->Update() ); + if ( bShouldRemove ) + { + COrder *pOrder = m_aOrders[i]; + m_aOrders.Remove( i ); + UTIL_Remove( pOrder ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: An event has just occurred that affects orders. Tell all our orders that +// have the specified entity as a target. +//----------------------------------------------------------------------------- +void CTFTeam::UpdateOrdersOnEvent( COrderEvent_Base *pOrder ) +{ + // Tell all our current orders to update themselves. Walk backwards because we may remove them. + int iSize = m_aOrders.Count(); + for (int i = iSize-1; i >= 0; i--) + { + bool bShouldRemove = m_aOrders[i]->UpdateOnEvent( pOrder ); + if ( bShouldRemove ) + { + COrder *pOrder = m_aOrders[i]; + m_aOrders.Remove( i ); + UTIL_Remove( pOrder ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Create personal orders for all the team's members +//----------------------------------------------------------------------------- +void CTFTeam::CreatePersonalOrders( void ) +{ + // Create personal orders for each player + for ( int i = 0; i < m_aPlayers.Count(); i++ ) + { + CBaseTFPlayer *pPlayer = (CBaseTFPlayer *)m_aPlayers[i]; + + // Don't create orders for bots, undefined or dead people + if ( !(pPlayer->GetFlags() & FL_FAKECLIENT) && pPlayer->IsAlive() && !pPlayer->IsClass( TFCLASS_UNDECIDED ) ) + { + CreatePersonalOrder( pPlayer ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Create personal orders for specified player +//----------------------------------------------------------------------------- +void CTFTeam::CreatePersonalOrder( CBaseTFPlayer *pPlayer ) +{ + // We still haven't made a personal order, so ask the class if it wants to + if ( pPlayer->GetPlayerClass() ) + { + pPlayer->GetPlayerClass()->CreatePersonalOrder(); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFTeam::RemoveOrdersToPlayer( CBaseTFPlayer *pPlayer ) +{ + // Walk backwards because we're removing them. + int iSize = m_aOrders.Count(); + for (int i = iSize-1; i >= 0; i--) + { + // Orders without owners should be removed + if ( m_aOrders[i].Get() ) + { + if( m_aOrders[i]->GetOwner() == pPlayer ) + { + COrder *pOrder = m_aOrders[i]; + m_aOrders.Remove( i ); + + pOrder->DetachFromPlayer(); + UTIL_Remove( pOrder ); + } + } + else + { + m_aOrders.Remove( i ); + } + } + + pPlayer->SetOrder( NULL ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Count and return the number of orders of the type with the specified target +//----------------------------------------------------------------------------- +int CTFTeam::CountOrders( int flags, int iOrderType, CBaseEntity *pTarget, CBaseTFPlayer *pOwner ) +{ + int iOrderCount = 0; + + // Count the number of global orders + for ( int i = 0; i < m_aOrders.Count(); i++ ) + { + COrder *pOrder = m_aOrders[i]; + + if( flags & COUNTORDERS_TYPE ) + if( pOrder->GetType() != iOrderType ) + continue; + + if( flags & COUNTORDERS_TARGET ) + if( pOrder->GetTargetEntity() != pTarget ) + continue; + + if( flags & COUNTORDERS_OWNER ) + if( pOrder->GetOwner() != pOwner ) + continue; + + // Ok, this order matches the criteria. + iOrderCount++; + } + + return iOrderCount; +} + + +int CTFTeam::CountOrdersOwnedByPlayer( CBaseTFPlayer *pPlayer ) +{ + return CountOrders( COUNTORDERS_OWNER, 0, 0, pPlayer ); +} + + +//------------------------------------------------------------------------------------------------------------------ +// MESSAGES +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFTeam::ClearMessages( void ) +{ + int iSize = m_aMessages.Count(); + for (int i = iSize-1; i >= 0; i--) + { + CTeamMessage *pMessage = m_aMessages[i]; + m_aMessages.Remove( i ); + delete pMessage; + } + + m_aMessages.Purge(); +} + +//----------------------------------------------------------------------------- +// Purpose: Post a message of the specified type +//----------------------------------------------------------------------------- +void CTFTeam::PostMessage( int iMessageID, CBaseEntity *pEntity, char *sData ) +{ + // First see if we've got this message in the queue already + for ( int i = 0; i < m_aMessages.Count(); i++ ) + { + CTeamMessage *pMessage = m_aMessages[i]; + if ( (pMessage->GetID() == iMessageID) && (pMessage->GetEntity() == pEntity) ) + { + // Already in the queue, abort. + return; + } + } + + // Create a new message and add it to my tail + CTeamMessage *pMessage = CTeamMessage::Create( this, iMessageID, pEntity ); + if ( sData && sData[0] ) + { + pMessage->SetData( sData ); + } + m_aMessages.AddToTail( pMessage ); + + // Tell the message to fire + pMessage->FireMessage(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFTeam::UpdateMessages( void ) +{ + // Go through my messages and kill any that have reached their TTL + int iSize = m_aMessages.Count(); + for (int i = iSize-1; i >= 0; i--) + { + CTeamMessage *pMessage = m_aMessages[i]; + if ( gpGlobals->curtime > pMessage->GetTTL() ) + { + m_aMessages.Remove( i ); + delete pMessage; + } + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Tell all our powerpacks to update their powered objects. +// pPackToIgnore: Pack to ignore, because it's dying. +// pObjectToTarget: An object looking for power, because it's being placed +//----------------------------------------------------------------------------- +void CTFTeam::UpdatePowerpacks( CObjectPowerPack *pPackToIgnore, CBaseObject *pObjectToTarget ) +{ + for ( int i = 0; i < GetNumObjects(); i++ ) + { + CBaseObject *pObject = GetObject(i); + assert(pObject); + if ( pObject == pPackToIgnore || pObject->GetType() != OBJ_POWERPACK ) + continue; + + ((CObjectPowerPack*)pObject)->PowerNearbyObjects( pObjectToTarget ); + + // Quit as soon as we've powered the specified one, if there is one + if ( pObjectToTarget && pObjectToTarget->IsPowered() ) + break; + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Tell all our buff stations to update and look for objects. +// Input: pBuffStationToIgnore: Buff station to ignore, because it is dying +// pObjectToTarget: an object looking for a buff station, because it is being placed +//----------------------------------------------------------------------------- +void CTFTeam::UpdateBuffStations( CObjectBuffStation *pBuffStationToIgnore, CBaseObject *pObjectToTarget, bool bPlacing ) +{ + for ( int iObject = 0; iObject < GetNumObjects(); ++iObject ) + { + CBaseObject *pObject = GetObject( iObject ); + assert( pObject ); + + if ( pObject->GetType() != OBJ_BUFF_STATION ) + continue; + + CObjectBuffStation *pBuffStation = static_cast<CObjectBuffStation*>( pObject ); + if ( pBuffStation == pBuffStationToIgnore ) + continue; + + pBuffStation->BuffNearbyObjects( pObjectToTarget, bPlacing ); + + // Quit as soon as we've powered the specified one, if there is one. + if ( pObjectToTarget && pObjectToTarget->IsHookedAndBuffed() ) + break; + } +} + +//------------------------------------------------------------------------------------------------------------------ +// UTILITY FUNCS +//----------------------------------------------------------------------------- + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CTFTeam* CTFTeam::GetEnemyTeam() +{ + // Look for nearby enemy objects we can capture. + int iMyTeam = GetTeamNumber(); + if( iMyTeam == 0 ) + return NULL; + + int iEnemyTeam = !(iMyTeam - 1) + 1; + return (CTFTeam*)GetGlobalTeam( iEnemyTeam ); +} + + diff --git a/game/server/tf2/tf_team.h b/game/server/tf2/tf_team.h new file mode 100644 index 0000000..1f90b65 --- /dev/null +++ b/game/server/tf2/tf_team.h @@ -0,0 +1,219 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Team management class. Contains all the details for a specific team +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef TF_TEAM_H +#define TF_TEAM_H + +#ifdef _WIN32 +#pragma once +#endif + + +#include "utlvector.h" +#include "tf_shareddefs.h" +#include "techtree.h" +#include "team.h" +#include "order_events.h" + +class CBaseTFPlayer; +class CResourceZone; +class CTeamSpawnPoint; +class CBaseTFPlayer; +class CResourceDrop; +class CTechnologyTree; +class CBaseTechnology; +class CObjectResupply; +class CBaseObject; +class COrder; +class CTeamMessage; +class CObjectPowerPack; +class CObjectBuffStation; + + +enum +{ + COUNTORDERS_TYPE = (1<<0), + COUNTORDERS_TARGET = (1<<1), + COUNTORDERS_OWNER = (1<<2) +}; + + +// Maximum total number of objects a team can have. +#define MAX_TEAM_OBJECTS 1024 + + +//----------------------------------------------------------------------------- +// Purpose: Team Manager +//----------------------------------------------------------------------------- +class CTFTeam : public CTeam +{ + DECLARE_CLASS( CTFTeam, CTeam ); +public: + virtual ~CTFTeam( void ); + + DECLARE_SERVERCLASS(); + + // Initialization + virtual void Init( const char *pName, int iNumber ); + + virtual void Precache( void ); + virtual void PrecacheTechnology( CBaseTechnology *pTech ); + virtual void Think( void ); + + //----------------------------------------------------------------------------- + // Data Handling + //----------------------------------------------------------------------------- + virtual void UpdateClientData( CBasePlayer *pPlayer ); + virtual void UpdateClientTechnology( int iTechID, CBaseTFPlayer *pPlayer ); + virtual void UpdateTechnologyData( void ); + virtual bool ShouldTransmitToPlayer( CBasePlayer *pRecipient, CBaseEntity* pEntity ); + virtual bool IsEntityVisibleToTactical( CBaseEntity *pEntity ); + + //----------------------------------------------------------------------------- + // Resources + //----------------------------------------------------------------------------- + virtual void UpdatePotentialResources( void ); + + virtual void AddResourceZone( CResourceZone *pResource ); + virtual void RemoveResourceZone( CResourceZone *pResource ); + + // Handling for players joining the team during the game + float GetJoiningPlayerResources( void ); + void SetRecentBankSet( float flResources ); + + //----------------------------------------------------------------------------- + // Technology Tree + //----------------------------------------------------------------------------- + virtual void InitializeTechTree( void ); + virtual CTechnologyTree *GetTechnologyTree( void ); + virtual void EnableTechnology( CBaseTechnology *technology, bool bStolen = false ); // Give this team a technology + virtual void EnableAllTechnologies( void ); + virtual void RecomputeTeamResources( void ); + virtual void RecomputePreferences( void ); + virtual void RecomputePurchases( void ); + virtual bool HasNamedTechnology( const char *name ); + virtual void GainedNewTechnology( CBaseTechnology *pTechnology ); + virtual void UpdateTechnologies( void ); + + //----------------------------------------------------------------------------- + // Players + //----------------------------------------------------------------------------- + virtual void AddPlayer( CBasePlayer *pPlayer ); + virtual void RemovePlayer( CBasePlayer *pPlayer ); + int GetNumOfClass( TFClass iClass ); + + //----------------------------------------------------------------------------- + // Resource Bank + //----------------------------------------------------------------------------- + void InitializeTeamResources( void ); + float GetTeamResources( void ); + int AddTeamResources( float fAmount, int nStat = -1 ); + void ResourceLoadDeposited( void ); + void DonateResources( CBaseTFPlayer *pPlayer ); + + //----------------------------------------------------------------------------- + // Objects + //----------------------------------------------------------------------------- + void AddObject( CBaseObject *pObject ); + void RemoveObject( CBaseObject *pObject ); + bool IsObjectOnTeam( CBaseObject *pObject ) const; + void AddResupply( CObjectResupply *pResupply ); + void RemoveResupply( CObjectResupply *pResupply ); + int GetNumObjects( int iObjectType = -1 ); + CBaseObject *GetObject( int num ); + int GetNumResupplies( void ); + CObjectResupply *GetResupply( int num ); + + // Returns true if the position is covered by a sentry gun. + bool IsCoveredBySentryGun( const Vector &vPos ); + + int GetNumShieldWallsCoveringPosition( const Vector &vPos ); + int GetNumResuppliesCoveringPosition( const Vector &vPos ); + int GetNumRespawnStationsCoveringPosition( const Vector &vPos ); + + + //----------------------------------------------------------------------------- + // Orders + //----------------------------------------------------------------------------- + void InitializeOrders( void ); + + COrder* AddOrder( + int iOrderType, + CBaseEntity *pTarget, + CBaseTFPlayer *pPlayer = NULL, + float flDistanceToRemove = 1e24, + float flLifetime = 60, + COrder *pDefaultOrder = NULL // If this is specified, then it is used instead of + // asking COrder to allocate an order. + ); + void RemoveOrder( COrder *pOrder ); + void RecalcOrders( void ); + void UpdateOrders( void ); + void UpdateOrdersOnEvent( COrderEvent_Base *pEvent ); + + // Flags is a combination of COUNTORDERS_ flags telling which fields to check. + int CountOrders( int flags, int iOrderType, CBaseEntity *pTarget=0, CBaseTFPlayer *pOwner=0 ); + int CountOrdersOwnedByPlayer( CBaseTFPlayer *pPlayer ); + + void CreatePersonalOrders( void ); + void CreatePersonalOrder( CBaseTFPlayer *pPlayer ); + void RemoveOrdersToPlayer( CBaseTFPlayer *pPlayer ); + + //----------------------------------------------------------------------------- + // Messages + //----------------------------------------------------------------------------- + void ClearMessages( void ); + void PostMessage( int iMessageID, CBaseEntity *pEntity = NULL, char *sData = NULL ); + void UpdateMessages( void ); + + void UpdatePowerpacks( CObjectPowerPack *pPackToIgnore, CBaseObject *pObjectToTarget ); + void UpdateBuffStations( CObjectBuffStation *pBuffStationToIgnore, CBaseObject *pObjectToTarget, bool bPlacing ); + + //----------------------------------------------------------------------------- + // Utility funcs + //----------------------------------------------------------------------------- + CTFTeam* GetEnemyTeam(); + + // Technology Tree + CTechnologyTree *m_pTechnologyTree; + + // TEST CODE + // Remove after Resource Experiment! + float m_flTotalResourcesSoFar; + +private: + typedef CHandle<COrder> OrderHandle; + + // Resource UI data + CNetworkVar( bool, m_bHaveZone ); + + // Resources + CNetworkVar( float, m_fResources ); // Current amounts of resource + CNetworkVar( float, m_fPotentialResources ); // Amounts of resource when all collectors have returned + float m_flLastBankSetAmount; // Most recent amount of resources our players had their banks set to + float m_flLastBankSetTime; // Time at which our players last had their banks set + + // Orders + float m_flPersonalOrderUpdateTime; + + // Used to distribute resources to a team + float m_flNextResourceTime; + + int m_iLastUpdateSentAt; + + CUtlVector< CResourceZone * > m_aResourcesBeingCollected; + CUtlVector< CObjectResupply * > m_aResupplyBeacons; + CUtlVector< OrderHandle > m_aOrders; // Stored in order of priority + CUtlVector< CTeamMessage* > m_aMessages; + CUtlVector< CBaseObject* > m_aObjects; +}; + + +extern CTFTeam *GetGlobalTFTeam( int iIndex ); + + +#endif // TF_TEAM_H diff --git a/game/server/tf2/tf_teamspawnpoint.h b/game/server/tf2/tf_teamspawnpoint.h new file mode 100644 index 0000000..0c566e6 --- /dev/null +++ b/game/server/tf2/tf_teamspawnpoint.h @@ -0,0 +1,40 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Team spawnpoint entity +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef TF_TEAMSPAWNPOINT_H +#define TF_TEAMSPAWNPOINT_H +#pragma once + +#include "baseentity.h" +#include "EntityOutput.h" + +class CTeam; + +//----------------------------------------------------------------------------- +// Purpose: points at which the player can spawn, restricted by team +//----------------------------------------------------------------------------- +class CTeamSpawnPoint : public CPointEntity +{ +public: + DECLARE_CLASS( CTeamSpawnPoint, CPointEntity ); + + void Activate( void ); + bool IsValid( CBasePlayer *pPlayer ); + + COutputEvent m_OnPlayerSpawn; + +protected: + int m_iDisabled; + + void EnableSpawn( void ); + void DisableSpawn( void ); + + DECLARE_DATADESC(); +}; + + +#endif // TF_TEAMSPAWNPOINT_H diff --git a/game/server/tf2/tf_vehicle_battering_ram.cpp b/game/server/tf2/tf_vehicle_battering_ram.cpp new file mode 100644 index 0000000..122cfba --- /dev/null +++ b/game/server/tf2/tf_vehicle_battering_ram.cpp @@ -0,0 +1,226 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: A moving vehicle that is used as a battering ram +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" +#include "tf_vehicle_battering_ram.h" +#include "engine/IEngineSound.h" +#include "VGuiScreen.h" +#include "ammodef.h" +#include "in_buttons.h" +#include "shake.h" + +#define BATTERING_RAM_MINS Vector(-30, -50, -10) +#define BATTERING_RAM_MAXS Vector( 30, 50, 55) +#define BATTERING_RAM_MODEL "models/objects/vehicle_battering_ram.mdl" + +IMPLEMENT_SERVERCLASS_ST(CVehicleBatteringRam, DT_VehicleBatteringRam) +END_SEND_TABLE(); + +LINK_ENTITY_TO_CLASS(vehicle_battering_ram, CVehicleBatteringRam); +PRECACHE_REGISTER(vehicle_battering_ram); + +// CVars +ConVar vehicle_battering_ram_health( "vehicle_battering_ram_health","800", FCVAR_NONE, "Battering ram health" ); +ConVar vehicle_battering_ram_damage( "vehicle_battering_ram_damage","300", FCVAR_NONE, "Battering ram damage" ); +ConVar vehicle_battering_ram_damage_boostmod( "vehicle_battering_ram_damage_boostmod", "1.5", FCVAR_NONE, "Battering ram boosted damage modifier" ); +ConVar vehicle_battering_ram_mindamagevel( "vehicle_battering_ram_mindamagevel","100", FCVAR_NONE, "Battering ram velocity for min damage" ); +ConVar vehicle_battering_ram_maxdamagevel( "vehicle_battering_ram_maxdamagevel","260", FCVAR_NONE, "Battering ram velocity for max damage" ); +ConVar vehicle_battering_ram_impact_time( "vehicle_battering_ram_impact_time", "1.0", FCVAR_NONE, "Battering ram impact wait time." ); + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CVehicleBatteringRam::CVehicleBatteringRam() +{ +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CVehicleBatteringRam::Precache() +{ + PrecacheModel( BATTERING_RAM_MODEL ); + + PrecacheVGuiScreen( "screen_vehicle_battering_ram" ); + PrecacheVGuiScreen( "screen_vulnerable_point"); + + PrecacheScriptSound( "VehicleBatteringRam.BashSound" ); + + BaseClass::Precache(); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CVehicleBatteringRam::Spawn() +{ + SetModel( BATTERING_RAM_MODEL ); + + // This size is used for placement only... + UTIL_SetSize(this, BATTERING_RAM_MINS, BATTERING_RAM_MAXS); + m_takedamage = DAMAGE_YES; + m_iHealth = vehicle_battering_ram_health.GetInt(); + m_nBarrelAttachment = LookupAttachment( "barrel" ); + + SetType( OBJ_BATTERING_RAM ); + SetMaxPassengerCount( 4 ); +// SetTouch( BashTouch ); + + m_flNextBashTime = 0.0f; + + BaseClass::Spawn(); +} + + +//----------------------------------------------------------------------------- +// Does the player use his normal weapons while in this mode? +//----------------------------------------------------------------------------- +bool CVehicleBatteringRam::IsPassengerUsingStandardWeapons( int nRole ) +{ + return (nRole > 1); +} + + +//----------------------------------------------------------------------------- +// Purpose: Gets info about the control panels +//----------------------------------------------------------------------------- +void CVehicleBatteringRam::GetControlPanelInfo( int nPanelIndex, const char *&pPanelName ) +{ + pPanelName = "screen_vulnerable_point"; +} + + +//----------------------------------------------------------------------------- +// Purpose: Ram!!! +//----------------------------------------------------------------------------- +void CVehicleBatteringRam::VPhysicsCollision( int index, gamevcollisionevent_t *pEvent ) +{ + int otherIndex = !index; + CBaseEntity *pEntity = pEvent->pEntities[otherIndex]; + + // We only damage objects... + // And only if we're travelling fast enough... + if ( !pEntity->IsSolid( ) ) + return; + + // Ignore shields.. + if ( pEntity->GetCollisionGroup() == TFCOLLISION_GROUP_SHIELD ) + return; + + // Ignore anything that's not an object + if ( pEntity->Classify() != CLASS_MILITARY && !pEntity->IsPlayer() ) + return; + + // Ignore teammates + if ( InSameTeam( pEntity )) + return; + + // Ignore invulnerable stuff + if ( pEntity->m_takedamage == DAMAGE_NO ) + return; + + // See if we can damage again? (Time-based) + if ( m_flNextBashTime > gpGlobals->curtime ) + return; + + // Use the attachment point to make sure we damage stuff that hit our front + // FIXME: Should we be using hitboxes here? + // And damage them all + // We only damage objects... + QAngle vecAng; + Vector vecSrc, vecAim; + GetAttachment( m_nBarrelAttachment, vecSrc, vecAng ); + + // Check the forward direction + Vector forward; + AngleVectors( vecAng, &forward ); + + // Did we hit it in front of the vehicle? + Vector vecDelta; + VectorSubtract( pEntity->WorldSpaceCenter(), vecSrc, vecDelta ); + if ( ( DotProduct( vecDelta, forward ) <= 0 ) || pEntity->IsPlayer() ) + { + BaseClass::VPhysicsCollision( index, pEvent ); + return; + } + + // Call our parents base class. + BaseClass::BaseClass::VPhysicsCollision( index, pEvent ); + + // Do damage based on velocity (and only if we were heading forward) + Vector vecVelocity = pEvent->preVelocity[index]; + if (DotProduct( vecVelocity, forward ) <= 0) + return; + + CTakeDamageInfo info; + info.SetInflictor( this ); + info.SetAttacker( GetDriverPlayer() ); + info.SetDamageType( DMG_CLUB ); + + float flMaxDamage = vehicle_battering_ram_damage.GetFloat(); + float flMaxDamageVel = vehicle_battering_ram_maxdamagevel.GetFloat(); + float flMinDamageVel = vehicle_battering_ram_mindamagevel.GetFloat(); + + float flVel = vecVelocity.Length(); + if (flVel < flMinDamageVel) + return; + + // FIXME: Play a sound here... + EmitSound( "VehicleBatteringRam.BashSound" ); + + // Apply the damage + float flDamageFactor = flMaxDamage; + if ( IsBoosting() ) + { + flDamageFactor *= vehicle_battering_ram_damage_boostmod.GetFloat(); + } + + if ((flMaxDamageVel > flMinDamageVel) && (flVel < flMaxDamageVel)) + { + // Use less damage when we're not moving fast enough + float flVelocityFactor = (flVel - flMinDamageVel) / (flMaxDamageVel - flMinDamageVel); + flVelocityFactor *= flVelocityFactor; + flDamageFactor *= flVelocityFactor; + } + + UTIL_ScreenShakeObject( pEntity, vecSrc, 45.0f * flDamageFactor / flMaxDamage, 30.0f, 0.5f, 250.0f, SHAKE_START ); + + info.SetDamage( flDamageFactor ); + + Vector damagePos; + pEvent->pInternalData->GetContactPoint( damagePos ); + Vector damageForce = pEvent->postVelocity[index] * pEvent->pObjects[index]->GetMass(); + info.SetDamageForce( damageForce ); + info.SetDamagePosition( damagePos ); + + PhysCallbackDamage( pEntity, info, *pEvent, index ); + + // Set next time ram time + m_flNextBashTime = gpGlobals->curtime + vehicle_battering_ram_impact_time.GetFloat(); +} + +//----------------------------------------------------------------------------- +// Here's where we deal with weapons, ladders, etc. +//----------------------------------------------------------------------------- +void CVehicleBatteringRam::OnItemPostFrame( CBaseTFPlayer *pDriver ) +{ + // I can't do anything if I'm not active + if ( !ShouldBeActive() ) + return; + + if ( GetPassengerRole( pDriver ) != VEHICLE_ROLE_DRIVER ) + return; + + if ( pDriver->m_nButtons & (IN_ATTACK2 | IN_SPEED) ) + { + StartBoost(); + } + + BaseClass::OnItemPostFrame( pDriver ); +} diff --git a/game/server/tf2/tf_vehicle_battering_ram.h b/game/server/tf2/tf_vehicle_battering_ram.h new file mode 100644 index 0000000..d4c403a --- /dev/null +++ b/game/server/tf2/tf_vehicle_battering_ram.h @@ -0,0 +1,58 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: A stationary gun that players can man +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef TF_VEHICLE_BATTERING_RAM_H +#define TF_VEHICLE_BATTERING_RAM_H + +#ifdef _WIN32 +#pragma once +#endif + +#include "tf_basefourwheelvehicle.h" +#include "vphysics/vehicles.h" + + +// ------------------------------------------------------------------------ // +// A stationary gun that players can man that's built by the player +// ------------------------------------------------------------------------ // +class CVehicleBatteringRam : public CBaseTFFourWheelVehicle +{ + DECLARE_CLASS( CVehicleBatteringRam, CBaseTFFourWheelVehicle ); + +public: + DECLARE_SERVERCLASS(); + + static CVehicleBatteringRam* Create(const Vector &vOrigin, const QAngle &vAngles); + + CVehicleBatteringRam(); + + virtual void Spawn(); + virtual void Precache(); + virtual void GetControlPanelInfo( int nPanelIndex, const char *&pPanelName ); + virtual bool CanTakeEMPDamage( void ) { return true; } + + void OnItemPostFrame( CBaseTFPlayer *pDriver ); + + // Can a given passenger take damage? + virtual bool IsPassengerDamagable( int nRole ) { return (nRole > 1); } + + // Vehicle overrides + virtual bool IsPassengerVisible( int nRole ) { return true; } + + // Does the player use his normal weapons while in this mode? + virtual bool IsPassengerUsingStandardWeapons( int nRole ); + + // Used to bash things it touches + virtual void VPhysicsCollision( int index, gamevcollisionevent_t *pEvent ); + +private: + + int m_nBarrelAttachment; + float m_flNextBashTime; +}; + +#endif // TF_VEHICLE_BATTERING_RAM_H diff --git a/game/server/tf2/tf_vehicle_flatbed.cpp b/game/server/tf2/tf_vehicle_flatbed.cpp new file mode 100644 index 0000000..a86064c --- /dev/null +++ b/game/server/tf2/tf_vehicle_flatbed.cpp @@ -0,0 +1,78 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Flatbed truck that can carry multiple stationary objects +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" +#include "tf_vehicle_flatbed.h" +#include "engine/IEngineSound.h" +#include "VGuiScreen.h" +#include "ammodef.h" +#include "in_buttons.h" + +#define FLATBED_MINS Vector(-30, -50, -10) +#define FLATBED_MAXS Vector( 30, 50, 55) +#define FLATBED_MODEL "models/objects/obj_flatbed.mdl" + +IMPLEMENT_SERVERCLASS_ST(CVehicleFlatbed, DT_VehicleFlatbed) +END_SEND_TABLE(); + +LINK_ENTITY_TO_CLASS(vehicle_flatbed, CVehicleFlatbed); +PRECACHE_REGISTER(vehicle_flatbed); + +// CVars +ConVar vehicle_flatbed_health( "vehicle_flatbed_health","800", FCVAR_NONE, "Flatbed health" ); + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CVehicleFlatbed::CVehicleFlatbed() +{ + +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CVehicleFlatbed::Precache() +{ + PrecacheModel( FLATBED_MODEL ); + + PrecacheVGuiScreen( "screen_vehicle_battering_ram" ); + + BaseClass::Precache(); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CVehicleFlatbed::Spawn() +{ + SetModel( FLATBED_MODEL ); + + // This size is used for placement only... + UTIL_SetSize(this, FLATBED_MINS, FLATBED_MAXS); + m_takedamage = DAMAGE_YES; + m_iHealth = vehicle_flatbed_health.GetInt(); + + SetType( OBJ_FLATBED ); + SetMaxPassengerCount( 1 ); + + BaseClass::Spawn(); +} + + +//----------------------------------------------------------------------------- +// Purpose: Gets info about the control panels +//----------------------------------------------------------------------------- +void CVehicleFlatbed::GetControlPanelInfo( int nPanelIndex, const char *&pPanelName ) +{ + pPanelName = "screen_vehicle_battering_ram"; +} + + diff --git a/game/server/tf2/tf_vehicle_flatbed.h b/game/server/tf2/tf_vehicle_flatbed.h new file mode 100644 index 0000000..133c141 --- /dev/null +++ b/game/server/tf2/tf_vehicle_flatbed.h @@ -0,0 +1,39 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#ifndef TF_VEHICLE_FLATBED_H +#define TF_VEHICLE_FLATBED_H +#ifdef _WIN32 +#pragma once +#endif + +#include "tf_basefourwheelvehicle.h" +#include "vphysics/vehicles.h" + + +//----------------------------------------------------------------------------- +// Purpose: Flatbed truck that can carry multiple stationary objects +//----------------------------------------------------------------------------- +class CVehicleFlatbed: public CBaseTFFourWheelVehicle +{ + DECLARE_CLASS( CVehicleFlatbed, CBaseTFFourWheelVehicle ); + +public: + DECLARE_SERVERCLASS(); + + static CVehicleFlatbed *Create(const Vector &vOrigin, const QAngle &vAngles); + + CVehicleFlatbed(); + + virtual void Spawn(); + virtual void Precache(); + virtual void GetControlPanelInfo( int nPanelIndex, const char *&pPanelName ); + + // Vehicle overrides + virtual bool IsPassengerVisible( int nRole ) { return true; } +}; + +#endif // TF_VEHICLE_FLATBED_H diff --git a/game/server/tf2/tf_vehicle_mortar.cpp b/game/server/tf2/tf_vehicle_mortar.cpp new file mode 100644 index 0000000..d365e5c --- /dev/null +++ b/game/server/tf2/tf_vehicle_mortar.cpp @@ -0,0 +1,352 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: A moving vehicle that is used as a battering ram +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" +#include "tf_vehicle_mortar.h" +#include "engine/IEngineSound.h" +#include "VGuiScreen.h" +#include "ammodef.h" +#include "in_buttons.h" +#include "vehicle_mortar_shared.h" +#include "movevars_shared.h" +#include "mortar_round.h" + + +// Waits this long after each shot before they can fire. +#define MORTAR_FIRE_DELAY 2 + + +#define MORTAR_MINS Vector(-30, -50, -10) +#define MORTAR_MAXS Vector( 30, 50, 55) +#define MORTAR_MODEL "models/objects/vehicle_mortar.mdl" +#define MORTAR_SCREEN_NAME "screen_vehicle_mortar" + +#define ELEVATION_INTERVAL 0.3 + +const char *g_pMortarThinkContextName = "MortarThinkContext"; +const char *g_pMortarNextFireContextName = "MortarNextFireContext"; + + + +IMPLEMENT_SERVERCLASS_ST(CVehicleMortar, DT_VehicleMortar) + SendPropFloat( SENDINFO( m_flMortarYaw ), 0, SPROP_NOSCALE ), + SendPropFloat( SENDINFO( m_flMortarPitch ), 0, SPROP_NOSCALE ), + SendPropBool( SENDINFO( m_bAllowedToFire ) ) +END_SEND_TABLE(); + + +LINK_ENTITY_TO_CLASS(vehicle_mortar, CVehicleMortar); +PRECACHE_REGISTER(vehicle_mortar); + + +// CVars +ConVar vehicle_mortar_health( "vehicle_mortar_health","800", FCVAR_NONE, "Mortar vehicle health" ); + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CVehicleMortar::CVehicleMortar() +{ + m_flMortarYaw = 0; + m_flMortarPitch = 0; + m_bAllowedToFire = true; + + UseClientSideAnimation(); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CVehicleMortar::Precache() +{ + PrecacheModel( MORTAR_MODEL ); + + PrecacheVGuiScreen( MORTAR_SCREEN_NAME ); + + PrecacheScriptSound( "VehicleMortar.FireSound" ); + + BaseClass::Precache(); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CVehicleMortar::Spawn() +{ + SetModel( MORTAR_MODEL ); + + // This size is used for placement only... + SetSize(MORTAR_MINS, MORTAR_MAXS); + m_takedamage = DAMAGE_YES; + m_iHealth = vehicle_mortar_health.GetInt(); + + SetType( OBJ_VEHICLE_MORTAR ); + SetMaxPassengerCount( 1 ); + + BaseClass::Spawn(); +} + + +//----------------------------------------------------------------------------- +// Purpose: Gets info about the control panels +//----------------------------------------------------------------------------- +void CVehicleMortar::GetControlPanelInfo( int nPanelIndex, const char *&pPanelName ) +{ + pPanelName = MORTAR_SCREEN_NAME; +} + + +bool CVehicleMortar::CanGetInVehicle( CBaseTFPlayer *pPlayer ) +{ + return ( !InDeployMode() && BaseClass::CanGetInVehicle( pPlayer ) ); +} + + +//----------------------------------------------------------------------------- +// Here's where we deal with weapons +//----------------------------------------------------------------------------- +void CVehicleMortar::OnItemPostFrame( CBaseTFPlayer *pDriver ) +{ + // I can't do anything if I'm not active + if ( !ShouldBeActive() ) + return; + + if ( GetPassengerRole( pDriver ) != VEHICLE_ROLE_DRIVER ) + return; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CVehicleMortar::OnFinishedDeploy( void ) +{ + BaseClass::OnFinishedDeploy(); + + EntityMessageBegin( this, true ); + WRITE_STRING( "OnDeployed" ); + MessageEnd(); + + m_flMortarYaw = 0; + m_flMortarPitch = 45; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CVehicleMortar::OnFinishedUnDeploy( void ) +{ + BaseClass::OnFinishedUnDeploy(); + + // Called when we are deployed. + EntityMessageBegin( this, true ); + WRITE_STRING( "OnUndeployed" ); + MessageEnd(); + + m_flMortarYaw = 0; + m_flMortarPitch = 0; +} + + +void CVehicleMortar::SetPassenger( int nRole, CBasePlayer *pEnt ) +{ + if ( pEnt ) + ShowVGUIScreen( 0, false ); + else + ShowVGUIScreen( 0, true ); + + BaseClass::SetPassenger( nRole, pEnt ); +} + +void CVehicleMortar::UpdateElevation( const Vector &vecTargetVel ) +{ + QAngle angles; + VectorAngles( vecTargetVel, angles ); + m_flMortarPitch = anglemod( -angles[PITCH] ); + + SetBoneController( 0, m_flMortarYaw ); + SetBoneController( 1, m_flMortarPitch ); +} + + +bool CVehicleMortar::ClientCommand( CBaseTFPlayer *pPlayer, const char *pCmd, ICommandArguments *pArg ) +{ + ResetDeteriorationTime(); + + if ( !Q_stricmp( pCmd, "Deploy" ) ) + { + Deploy(); + return true; + } + else if ( !Q_stricmp( pCmd, "Undeploy" ) ) + { + UnDeploy(); + } + else if ( !Q_stricmp( pCmd, "CancelDeploy" ) ) + { + CancelDeploy(); + return true; + } + else if ( !Q_stricmp( pCmd, "FireMortar" ) ) + { + if ( pArg->Argc() == 3 ) + { + FireMortar( atof( pArg->Argv(1) ), atof( pArg->Argv(2) ), false, false ); + } + return true; + } + else if ( !Q_stricmp( pCmd, "MortarYaw" ) ) + { + if ( pArg->Argc() == 2 ) + { + m_flMortarYaw = atof( pArg->Argv(1) ); + } + return true; + } + + return BaseClass::ClientCommand( pPlayer, pCmd, pArg ); +} + + +bool CVehicleMortar::CalcFireInfo( + float flFiringPower, + float flFiringAccuracy, + bool bRangeUpgraded, + bool bAccuracyUpgraded, + Vector &vStartPt, + Vector &vecTargetVel, + float &fallTime + ) +{ + QAngle dummy; + if ( !GetAttachment( "barrel", vStartPt, dummy ) ) + vStartPt = WorldSpaceCenter(); + + // Get target distance + float flDistance; + if ( bRangeUpgraded ) + { + flDistance = MORTAR_RANGE_MIN + (flFiringPower * (MORTAR_RANGE_MAX_UPGRADED - MORTAR_RANGE_MIN)); + } + else + { + flDistance = MORTAR_RANGE_MIN + (flFiringPower * (MORTAR_RANGE_MAX_INITIAL - MORTAR_RANGE_MIN)); + } + + // Factor in inaccuracy + float flInaccuracy; + if ( bAccuracyUpgraded ) + { + flInaccuracy = MORTAR_INACCURACY_MAX_UPGRADED * (flFiringAccuracy * 4); // flFiringAccuracy is a range from -0.25 to 0.25 + } + else + { + flInaccuracy = MORTAR_INACCURACY_MAX_INITIAL * (flFiringAccuracy * 4); // flFiringAccuracy is a range from -0.25 to 0.25 + } + flDistance += (flDistance * MORTAR_DIST_INACCURACY) * random->RandomFloat( -flInaccuracy, flInaccuracy ); + + float flAngle = GetAbsAngles()[YAW] + m_flMortarYaw; + Vector forward( -sin( DEG2RAD( flAngle ) ), cos( DEG2RAD( flAngle ) ), 0 ); + Vector right( forward.y, -forward.x, 0 ); + + Vector vecTargetOrg = vStartPt + (forward * flDistance); + // Add in sideways inaccuracy + vecTargetOrg += (right * (flDistance * random->RandomFloat( -flInaccuracy, flInaccuracy )) ); + + // Trace down from the sky and find the point we're actually going to hit + trace_t tr; + Vector vecSky = vecTargetOrg + Vector(0,0,1024); + UTIL_TraceLine( vecSky, vecTargetOrg, MASK_ALL, this, COLLISION_GROUP_NONE, &tr ); + vecTargetOrg = tr.endpos; + + Vector vecMidPoint = vec3_origin; + // Start with a low arc, and keep aiming higher until we've got a roughly clear shot + for (int i = 512; i <= 4096; i += 512) + { + trace_t tr1; + trace_t tr2; + + vecMidPoint = Vector(0,0,i) + vStartPt + (vecTargetOrg - vStartPt) * 0.5; + UTIL_TraceLine(vStartPt, vecMidPoint, MASK_ALL, this, COLLISION_GROUP_NONE, &tr1); + UTIL_TraceLine(vecMidPoint, vecTargetOrg, MASK_ALL, this, COLLISION_GROUP_NONE, &tr2); + + // Clear shot? + // We want a clear shot for the first half, and a fairly clear shot on the fall + if ( tr1.fraction == 1 && tr2.fraction > 0.5 ) + break; + } + + // How high should we travel to reach the apex + float distance1 = (vecMidPoint.z - vStartPt.z); + float distance2 = (vecMidPoint.z - vecTargetOrg.z); + + // How long will it take to travel this distance + float flGravity = GetCurrentGravity(); + float time1 = sqrt( distance1 / (0.5 * flGravity) ); + float time2 = sqrt( distance2 / (0.5 * flGravity) ); + if (time1 < 0.1) + return false; + + // how hard to launch to get there in time. + vecTargetVel = (vecTargetOrg - vStartPt) / (time1 + time2); + vecTargetVel.z = flGravity * time1; + + fallTime = time1 * 0.5; + return true; +} + + +void CVehicleMortar::NextFireThink() +{ + // Ok, we can fire again now. + m_bAllowedToFire = true; +} + + +bool CVehicleMortar::FireMortar( float flFiringPower, float flFiringAccuracy, bool bRangeUpgraded, bool bAccuracyUpgraded ) +{ + SetActivity( ACT_RANGE_ATTACK1 ); + + // Calculate the shot. + Vector vStartPt; + Vector vecTargetVel; + float fallTime; + + if ( !CalcFireInfo( + flFiringPower, + flFiringAccuracy, + bRangeUpgraded, + bAccuracyUpgraded, + vStartPt, + vecTargetVel, + fallTime ) ) + { + return false; + } + + UpdateElevation( vecTargetVel ); + + // Create the round + CMortarRound *pRound = CMortarRound::Create( vStartPt, vecTargetVel, GetOwner()->edict() ); + pRound->SetLauncher( this ); + pRound->ChangeTeam( GetTeamNumber() ); + pRound->SetFallTime( fallTime ); // Start a falling sound just a bit before we begin to fall + pRound->SetRoundType( MA_SHELL ); + + // BOOM! + EmitSound( "VehicleMortar.FireSound" ); + + // Put in a delay before thinking again. + m_bAllowedToFire = false; + SetContextThink( NextFireThink, gpGlobals->curtime + MORTAR_FIRE_DELAY, g_pMortarNextFireContextName ); + + return true; +} + + diff --git a/game/server/tf2/tf_vehicle_mortar.h b/game/server/tf2/tf_vehicle_mortar.h new file mode 100644 index 0000000..5103390 --- /dev/null +++ b/game/server/tf2/tf_vehicle_mortar.h @@ -0,0 +1,85 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: A stationary gun that players can man +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef TF_VEHICLE_MORTAR_H +#define TF_VEHICLE_MORTAR_H + +#ifdef _WIN32 +#pragma once +#endif + +#include "tf_basefourwheelvehicle.h" +#include "vphysics/vehicles.h" + + +// ------------------------------------------------------------------------ // +// A wagon that players can build one object on the back of +// ------------------------------------------------------------------------ // +class CVehicleMortar : public CBaseTFFourWheelVehicle +{ + DECLARE_CLASS( CVehicleMortar, CBaseTFFourWheelVehicle ); + +public: + DECLARE_SERVERCLASS(); + + static CVehicleMortar* Create(const Vector &vOrigin, const QAngle &vAngles); + + CVehicleMortar(); + + virtual void Spawn(); + virtual void Precache(); + virtual void GetControlPanelInfo( int nPanelIndex, const char *&pPanelName ); + virtual bool CanTakeEMPDamage( void ) { return true; } + virtual bool ClientCommand( CBaseTFPlayer *pPlayer, const CCommand &args ); + + // Vehicle overrides + virtual bool IsPassengerVisible( int nRole ) { return true; } + virtual void OnFinishedDeploy( void ); + virtual void OnFinishedUnDeploy( void ); + virtual void SetPassenger( int nRole, CBasePlayer *pEnt ); + +protected: + virtual bool CanGetInVehicle( CBaseTFPlayer *pPlayer ); + + // Here's where we deal with weapons + virtual void OnItemPostFrame( CBaseTFPlayer *pDriver ); + + // Called when we are deployed. + void ElevationThink(); + + void UpdateElevation( const Vector &vecTargetVel ); + + bool CalcFireInfo( + float flFiringPower, + float flFiringAccuracy, + bool bRangeUpgraded, + bool bAccuracyUpgraded, + Vector &vStartPt, + Vector &vecTargetVel, + float &fallTime + ); + + bool FireMortar( float flFiringPower, float flFiringAccuracy, bool bRangeUpgraded, bool bAccuracyUpgraded ); + +private: + void NextFireThink(); + + // Start/end the attack + void StartAttack(); + void FinishAttack(); + + void AttackThink(); + + int m_nBuildPoint; + + CNetworkVar( float, m_flMortarYaw ); + CNetworkVar( float, m_flMortarPitch ); // Elevation angle, recalculated periodically. + + CNetworkVar( bool, m_bAllowedToFire ); +}; + +#endif // TF_VEHICLE_MORTAR_H diff --git a/game/server/tf2/tf_vehicle_motorcycle.cpp b/game/server/tf2/tf_vehicle_motorcycle.cpp new file mode 100644 index 0000000..c5cbf60 --- /dev/null +++ b/game/server/tf2/tf_vehicle_motorcycle.cpp @@ -0,0 +1,99 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "cbase.h" +#include "engine/IEngineSound.h" +#include "VGuiScreen.h" +#include "tf_basefourwheelvehicle.h" +#include "vphysics/vehicles.h" + +#define MOTORCYCLE_MINS Vector(-30, -50, -10) +#define MOTORCYCLE_MAXS Vector( 30, 50, 55) +#define MOTORCYCLE_MODEL "models/objects/vehicle_motorcycle.mdl" + +// ------------------------------------------------------------------------ // +// Purpose: +// ------------------------------------------------------------------------ // +class CVehicleMotorcycle : public CBaseTFFourWheelVehicle +{ + DECLARE_CLASS( CVehicleMotorcycle, CBaseTFFourWheelVehicle ); + +public: + DECLARE_SERVERCLASS(); + + static CVehicleMotorcycle* Create(const Vector &vOrigin, const QAngle &vAngles); + + CVehicleMotorcycle(); + + virtual void Spawn(); + virtual void Precache(); + virtual void GetControlPanelInfo( int nPanelIndex, const char *&pPanelName ); + virtual bool CanTakeEMPDamage( void ) { return true; } + + // Vehicle overrides + virtual bool IsPassengerVisible( int nRole ) { return true; } +}; + +IMPLEMENT_SERVERCLASS_ST(CVehicleMotorcycle, DT_VehicleMotorcycle) +END_SEND_TABLE(); + +LINK_ENTITY_TO_CLASS(vehicle_motorcycle, CVehicleMotorcycle); +PRECACHE_REGISTER(vehicle_motorcycle); + +// CVars +ConVar vehicle_motorcycle_health( "vehicle_motorcycle_health","200", FCVAR_NONE, "Motorcycle health" ); + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CVehicleMotorcycle::CVehicleMotorcycle() +{ + +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CVehicleMotorcycle::Precache() +{ + PrecacheModel( MOTORCYCLE_MODEL ); + + PrecacheVGuiScreen( "screen_vulnerable_point"); + + BaseClass::Precache(); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CVehicleMotorcycle::Spawn() +{ + SetModel( MOTORCYCLE_MODEL ); + + // This size is used for placement only... + UTIL_SetSize(this, MOTORCYCLE_MINS, MOTORCYCLE_MAXS); + m_takedamage = DAMAGE_YES; + m_iHealth = vehicle_motorcycle_health.GetInt(); + + SetType( OBJ_VEHICLE_MOTORCYCLE ); + SetMaxPassengerCount( 4 ); + + BaseClass::Spawn(); +} + + +//----------------------------------------------------------------------------- +// Purpose: Gets info about the control panels +//----------------------------------------------------------------------------- +void CVehicleMotorcycle::GetControlPanelInfo( int nPanelIndex, const char *&pPanelName ) +{ + pPanelName = "screen_vulnerable_point"; +} + + diff --git a/game/server/tf2/tf_vehicle_siege_tower.cpp b/game/server/tf2/tf_vehicle_siege_tower.cpp new file mode 100644 index 0000000..78d16f3 --- /dev/null +++ b/game/server/tf2/tf_vehicle_siege_tower.cpp @@ -0,0 +1,304 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Siege Tower Vehicle +// +//=============================================================================// + +#include "cbase.h" +#include "tf_vehicle_siege_tower.h" +#include "engine/IEngineSound.h" +#include "VGuiScreen.h" +#include "ammodef.h" +#include "in_buttons.h" + +#define SIEGE_TOWER_MINS Vector(-30, -50, -10) +#define SIEGE_TOWER_MAXS Vector( 30, 50, 55) +#define SIEGE_TOWER_MODEL "models/objects/vehicle_siege_tower.mdl" +#define SIEGE_TOWER_LADDER_MODEL "models/objects/vehicle_siege_ladder.mdl" +#define SIEGE_TOWER_PLATFORM_MODEL "models/objects/vehicle_siege_plat.mdl" + +IMPLEMENT_SERVERCLASS_ST( CVehicleSiegeTower, DT_VehicleSiegeTower ) +END_SEND_TABLE(); + +LINK_ENTITY_TO_CLASS( vehicle_siege_tower, CVehicleSiegeTower ); +PRECACHE_REGISTER( vehicle_siege_tower ); + +IMPLEMENT_SERVERCLASS_ST( CObjectSiegeLadder, DT_ObjectSiegeLadder ) +END_SEND_TABLE(); + +LINK_ENTITY_TO_CLASS( obj_siege_ladder, CObjectSiegeLadder ); +PRECACHE_REGISTER( obj_siege_ladder ); + +IMPLEMENT_SERVERCLASS_ST( CObjectSiegePlatform, DT_ObjectSiegePlatform ) +END_SEND_TABLE(); + +LINK_ENTITY_TO_CLASS( obj_siege_platform, CObjectSiegePlatform ); +PRECACHE_REGISTER( obj_siege_platform ); + +// CVars +ConVar vehicle_siege_tower_health( "vehicle_siege_tower_health","600", FCVAR_NONE, "Siege tower health" ); + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CVehicleSiegeTower::CVehicleSiegeTower() +{ + m_hLadder = NULL; + m_hPlatform = NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CVehicleSiegeTower::Precache() +{ + PrecacheModel( SIEGE_TOWER_MODEL ); + + PrecacheVGuiScreen( "screen_vehicle_siege_tower" ); + PrecacheVGuiScreen( "screen_vulnerable_point" ); + + BaseClass::Precache(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CVehicleSiegeTower::Spawn() +{ + SetModel( SIEGE_TOWER_MODEL ); + + // This size is used for placement only... + UTIL_SetSize( this, SIEGE_TOWER_MINS, SIEGE_TOWER_MAXS ); + m_takedamage = DAMAGE_YES; + m_iHealth = vehicle_siege_tower_health.GetInt(); + + SetType( OBJ_SIEGE_TOWER ); + SetMaxPassengerCount( 4 ); + + BaseClass::Spawn(); +} + +//----------------------------------------------------------------------------- +// Purpose: Gets info about the control panels +//----------------------------------------------------------------------------- +void CVehicleSiegeTower::GetControlPanelInfo( int nPanelIndex, const char *&pPanelName ) +{ + //pPanelName = "screen_vehicle_siege_tower"; + pPanelName = "screen_vulnerable_point"; +} + +//----------------------------------------------------------------------------- +// Here's where we deal with weapons, ladders, etc. +//----------------------------------------------------------------------------- +void CVehicleSiegeTower::OnItemPostFrame( CBaseTFPlayer *pDriver ) +{ + // I can't do anything if I'm not active + if ( !ShouldBeActive() ) + return; + + if ( GetPassengerRole( pDriver ) != VEHICLE_ROLE_DRIVER ) + return; + + if ( !IsDeployed() && ( pDriver->m_afButtonPressed & IN_ATTACK ) ) + { + InternalDeploy(); + } + else if ( IsDeployed() && ( pDriver->m_afButtonPressed & IN_ATTACK ) ) + { + InternalUnDeploy(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CVehicleSiegeTower::InternalDeploy( void ) +{ + // Deploy + if ( !Deploy() ) + return; + + InputTurnOff( inputdata_t() ); + + // Create the ladder. + Vector vecOrigin; + QAngle vecAngle; + GetAttachment( "ladder", vecOrigin, vecAngle ); + CreateLadder( vecOrigin, vecAngle ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CVehicleSiegeTower::InternalUnDeploy( void ) +{ + // Undeploy + UnDeploy(); + InputTurnOn( inputdata_t() ); + + // Destory the ladder. + DestroyLadder(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CVehicleSiegeTower::CreateLadder( const Vector &vecOrigin, const QAngle &vecAngles ) +{ + // NOTE: This ladder and platform code is a total hack to test vehicles with. This + // is not the correct way to handle this problem at all. + m_hLadder = CObjectSiegeLadder::Create( vecOrigin, vecAngles, this ); + m_hPlatform = CObjectSiegePlatform::Create( vecOrigin, vecAngles, this ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CVehicleSiegeTower::DestroyLadder( void ) +{ + if ( m_hLadder.Get() ) + { + UTIL_Remove( m_hLadder ); + m_hLadder = NULL; + } + + if ( m_hPlatform.Get() ) + { + UTIL_Remove( m_hPlatform ); + m_hPlatform = NULL; + } +} + +//------------------------------------------------------------------------------ +// Purpose: +//------------------------------------------------------------------------------ +void CVehicleSiegeTower::Killed( void ) +{ + DestroyLadder(); + BaseClass::Killed(); +} + +//============================================================================== +// +// Object Siege Ladder +// + +//------------------------------------------------------------------------------ +// Purpose: +//------------------------------------------------------------------------------ +CObjectSiegeLadder::CObjectSiegeLadder() +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CObjectSiegeLadder *CObjectSiegeLadder::Create( const Vector &vOrigin, const QAngle &vAngles, CBaseEntity *pParent ) +{ + CObjectSiegeLadder *pLadder = static_cast<CObjectSiegeLadder*>( CBaseObject::Create( "obj_siege_ladder", vOrigin, vAngles ) ); + if ( pLadder ) + { + pLadder->m_hTower = pParent; + } + + return pLadder; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CObjectSiegeLadder::Spawn() +{ + Precache(); + SetModel( SIEGE_TOWER_LADDER_MODEL ); + SetSolid( SOLID_VPHYSICS ); + m_takedamage = DAMAGE_NO; + + BaseClass::Spawn(); + + CollisionProp()->SetSurroundingBoundsType( USE_BEST_COLLISION_BOUNDS ); + IPhysicsObject *pPhysics = VPhysicsInitStatic(); + if ( pPhysics ) + { + pPhysics->EnableMotion( false ); + } + SetCollisionGroup( TFCOLLISION_GROUP_OBJECT_SOLIDTOPLAYERMOVEMENT ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CObjectSiegeLadder::Precache() +{ + PrecacheModel( SIEGE_TOWER_LADDER_MODEL ); +} + +//----------------------------------------------------------------------------- +// Purpose: Pass all damage back to the siege tower +//----------------------------------------------------------------------------- +int CObjectSiegeLadder::OnTakeDamage( const CTakeDamageInfo &info ) +{ + return m_hTower->OnTakeDamage( info ); +} + +//============================================================================== +// +// Object Siege Platform +// + +//------------------------------------------------------------------------------ +// Purpose: +//------------------------------------------------------------------------------ +CObjectSiegePlatform::CObjectSiegePlatform() +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CObjectSiegePlatform *CObjectSiegePlatform::Create( const Vector &vOrigin, const QAngle &vAngles, CBaseEntity *pParent ) +{ + CObjectSiegePlatform *pPlatform = static_cast<CObjectSiegePlatform*>( CBaseObject::Create( "obj_siege_platform", vOrigin, vAngles ) ); + if ( pPlatform ) + { + pPlatform->m_hTower = pParent; + } + + return pPlatform; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CObjectSiegePlatform::Spawn() +{ + Precache(); + SetModel( SIEGE_TOWER_PLATFORM_MODEL ); + SetSolid( SOLID_VPHYSICS ); + m_takedamage = DAMAGE_NO; + + BaseClass::Spawn(); + CollisionProp()->SetSurroundingBoundsType( USE_BEST_COLLISION_BOUNDS ); + IPhysicsObject *pPhysics = VPhysicsInitStatic(); + if ( pPhysics ) + { + pPhysics->EnableMotion( false ); + } + SetCollisionGroup( TFCOLLISION_GROUP_OBJECT_SOLIDTOPLAYERMOVEMENT ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CObjectSiegePlatform::Precache() +{ + PrecacheModel( SIEGE_TOWER_PLATFORM_MODEL ); +} + +//----------------------------------------------------------------------------- +// Purpose: Pass all damage back to the siege tower +//----------------------------------------------------------------------------- +int CObjectSiegePlatform::OnTakeDamage( const CTakeDamageInfo &info ) +{ + return m_hTower->OnTakeDamage( info ); +}
\ No newline at end of file diff --git a/game/server/tf2/tf_vehicle_siege_tower.h b/game/server/tf2/tf_vehicle_siege_tower.h new file mode 100644 index 0000000..6832adc --- /dev/null +++ b/game/server/tf2/tf_vehicle_siege_tower.h @@ -0,0 +1,109 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Siege Tower Vehicle +// +//=============================================================================// + +#ifndef TF_VEHICLE_SIEGE_TOWER_H +#define TF_VEHICLE_SIEGE_TOWER_H + +#ifdef _WIN32 +#pragma once +#endif + +#include "tf_basefourwheelvehicle.h" +#include "vphysics/vehicles.h" + +//============================================================================= +// +// Siege Ladder (Object) +// +class CObjectSiegeLadder : public CBaseAnimating +{ + DECLARE_CLASS( CObjectSiegeLadder, CBaseAnimating ); + +public: + + DECLARE_SERVERCLASS(); + + static CObjectSiegeLadder* Create( const Vector &vOrigin, const QAngle &vAngles, CBaseEntity *pParent ); + + CObjectSiegeLadder(); + + void Spawn(); + void Precache(); + virtual int OnTakeDamage( const CTakeDamageInfo &info ); + +public: + EHANDLE m_hTower; +}; + +//============================================================================= +// +// Siege Platform (Object) +// +class CObjectSiegePlatform : public CBaseAnimating +{ + DECLARE_CLASS( CObjectSiegePlatform, CBaseAnimating ); + +public: + + DECLARE_SERVERCLASS(); + + static CObjectSiegePlatform* Create( const Vector &vOrigin, const QAngle &vAngles, CBaseEntity *pParent ); + + CObjectSiegePlatform(); + + void Spawn(); + void Precache(); + virtual int OnTakeDamage( const CTakeDamageInfo &info ); + +public: + EHANDLE m_hTower; +}; + +//============================================================================= +// +// Siege Tower Vehicle +// +class CVehicleSiegeTower : public CBaseTFFourWheelVehicle +{ + DECLARE_CLASS( CVehicleSiegeTower, CBaseTFFourWheelVehicle ); + +public: + + DECLARE_SERVERCLASS(); + + static CVehicleSiegeTower* Create( const Vector &vOrigin, const QAngle &vAngles ); + + CVehicleSiegeTower(); + + void Spawn(); + void Precache(); + void Killed(void ); + void GetControlPanelInfo( int nPanelIndex, const char *&pPanelName ); + bool CanTakeEMPDamage( void ) { return true; } + + // Vehicle overrides + bool IsPassengerVisible( int nRole ) { return true; } + +protected: + + // Here's where we deal with weapons + void OnItemPostFrame( CBaseTFPlayer *pDriver ); + +private: + + // Deploy. + void InternalDeploy( void ); + void InternalUnDeploy( void ); + void CreateLadder( const Vector &vecOrigin, const QAngle &vecAngles ); + void DestroyLadder( void ); + +private: + + CHandle<CObjectSiegeLadder> m_hLadder; + CHandle<CObjectSiegePlatform> m_hPlatform; +}; + +#endif // TF_VEHICLE_SIEGE_TOWER_H diff --git a/game/server/tf2/tf_vehicle_tank.cpp b/game/server/tf2/tf_vehicle_tank.cpp new file mode 100644 index 0000000..96fea85 --- /dev/null +++ b/game/server/tf2/tf_vehicle_tank.cpp @@ -0,0 +1,262 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: A moving vehicle that is used as a battering ram +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" +#include "tf_vehicle_tank.h" +#include "engine/IEngineSound.h" +#include "VGuiScreen.h" +#include "ammodef.h" +#include "in_buttons.h" +#include "vehicle_mortar_shared.h" +#include "movevars_shared.h" +#include "mortar_round.h" +#include "explode.h" +#include "tf_gamerules.h" +#include "shake.h" +#include "basetempentity.h" +#include "weapon_grenade_rocket.h" + + +#define TANK_MINS Vector(-30, -50, -10) +#define TANK_MAXS Vector( 30, 50, 55) +#define TANK_MODEL "models/objects/vehicle_tank.mdl" + +// N seconds between tank shots. +#define TANK_FIRE_INTERVAL 2 + +#define TANK_MAX_RANGE 1800 + + +const char *g_pTurretThinkContextName = "TurretThinkContext"; + +ConVar vehicle_tank_damage( "vehicle_tank_damage","150", FCVAR_NONE, "Tank Rocket damage" ); +ConVar vehicle_tank_range( "vehicle_tank_range","1000", FCVAR_NONE, "Tank Rocket range" ); +ConVar vehicle_tank_radius( "vehicle_tank_radius","128", FCVAR_NONE, "Tank rocket explosive radius" ); + + +// How fast the turret rotates to where the driver is facing. +#define TURRET_DEGREES_PER_SEC 30 + + +IMPLEMENT_SERVERCLASS_ST( CVehicleTank, DT_VehicleTank ) + SendPropAngle( SENDINFO( m_flTurretYaw ), 11 ), + SendPropAngle( SENDINFO( m_flTurretPitch ), 11 ), +END_SEND_TABLE() + + +LINK_ENTITY_TO_CLASS( vehicle_tank, CVehicleTank ); +PRECACHE_REGISTER( vehicle_tank ); + + +// CVars +ConVar vehicle_tank_health( "vehicle_tank_health","800", FCVAR_NONE, "Tank health" ); + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CVehicleTank::CVehicleTank() +{ + m_flClientYaw = 0; + m_flClientPitch = 0; + + m_flTurretYaw = 0; + m_flTurretPitch = 0; + + m_flNextFireTime = 0; + + UseClientSideAnimation(); +} + + +CVehicleTank::~CVehicleTank() +{ +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CVehicleTank::Precache() +{ + PrecacheModel( TANK_MODEL ); + PrecacheVGuiScreen( "screen_vulnerable_point" ); + + PrecacheScriptSound( "VehicleTank.FireSound" ); + + + BaseClass::Precache(); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CVehicleTank::Spawn() +{ + SetModel( TANK_MODEL ); + + // This size is used for placement only... + SetSize( TANK_MINS, TANK_MAXS ); + m_takedamage = DAMAGE_YES; + m_iHealth = vehicle_tank_health.GetInt(); + + SetType( OBJ_VEHICLE_TANK ); + SetMaxPassengerCount( 3 ); + + // This can go away when the tank is using a real tank model. + SetActivity( ACT_DEPLOY ); + + SetContextThink( TurretThink, gpGlobals->curtime, g_pTurretThinkContextName ); + + BaseClass::Spawn(); +} + +//----------------------------------------------------------------------------- +// Purpose: Gets info about the control panels +//----------------------------------------------------------------------------- +void CVehicleTank::GetControlPanelInfo( int nPanelIndex, const char *&pPanelName ) +{ + //pPanelName = "screen_vehicle_siege_tower"; + pPanelName = "screen_vulnerable_point"; +} + +float MoveAngleTowards( float flAngle, float flTowards, float flRate ) +{ + // Translate so flTowards is 0, then interpolate towards 0 or 360. + flAngle -= flTowards; + + // Move yaw to the client yaw using the shortest path. + flAngle = anglemod( flAngle ); + if ( flAngle > 180 ) + { + flAngle = MIN( 360, flAngle + flRate ); + } + else + { + flAngle = MAX( 0, flAngle - flRate ); + } + + // Translate back. + flAngle += flTowards; + return flAngle; +} + + +void CVehicleTank::TurretThink() +{ + float flStartYaw = m_flTurretYaw; + float flStartPitch = m_flTurretPitch; + + m_flTurretYaw = MoveAngleTowards( m_flTurretYaw, m_flClientYaw, gpGlobals->frametime * TURRET_DEGREES_PER_SEC ); + m_flTurretPitch = MoveAngleTowards( m_flTurretPitch, m_flClientPitch, gpGlobals->frametime * TURRET_DEGREES_PER_SEC ); + + SetBoneController( 0, m_flTurretYaw ); + SetBoneController( 1, m_flTurretPitch ); + + SetContextThink( TurretThink, gpGlobals->curtime, g_pTurretThinkContextName ); +} + + +//----------------------------------------------------------------------------- +// Here's where we deal with weapons +//----------------------------------------------------------------------------- +void CVehicleTank::OnItemPostFrame( CBaseTFPlayer *pDriver ) +{ + // I can't do anything if I'm not active + if ( !ShouldBeActive() ) + return; + + // Make sure we're allowed to fire here + if ( !IsReadyToDrive() ) + return; + + if ( GetPassengerRole( pDriver ) != VEHICLE_ROLE_DRIVER ) + return; + + if ( pDriver->m_nButtons & IN_ATTACK ) + { + // Time to fire? + if ( gpGlobals->curtime >= m_flNextFireTime ) + { + Fire(); + } + } + else if ( pDriver->m_nButtons & (IN_ATTACK2 | IN_SPEED) ) + { + //StartBoost(); + BaseClass::OnItemPostFrame( pDriver ); + } +} + + +void CVehicleTank::Fire() +{ + // NOTE: this code reverses the steps taken in C_VehicleTank::ClientThink to + // reconstruct the fire direction. + + // Build local angles. + QAngle angles; + angles[YAW] = m_flTurretYaw + 90; + angles[PITCH] = -m_flTurretPitch; + angles[ROLL] = 0; + + // Convert to a forward vector. + Vector vForward, vLocalForward; + AngleVectors( angles, &vLocalForward ); + + // Move the vector to world space. + matrix3x4_t tankToWorld; + AngleMatrix( GetAbsAngles(), tankToWorld ); + VectorTransform( vLocalForward, tankToWorld, vForward ); + + + Vector vFireOrigin; + QAngle dummy; + GetAttachment( LookupAttachment( "muzzle" ), vFireOrigin, dummy ); + + // Create the rocket. + CWeaponGrenadeRocket *pRocket = CWeaponGrenadeRocket::Create( vFireOrigin, vForward, vehicle_tank_range.GetFloat(), this ); + if ( pRocket ) + { + pRocket->SetRealOwner( GetDriverPlayer() ); + pRocket->SetDamage( vehicle_tank_damage.GetFloat() ); + pRocket->SetDamageRadius( vehicle_tank_radius.GetFloat() ); + + // Apply a screen shake. + UTIL_ScreenShake( vFireOrigin, 12, 60, 1.5, 512, SHAKE_START, true ); + + // BOOM! + CPASAttenuationFilter pasAttenuation( this, "VehicleTank.FireSound" ); + EmitSound( pasAttenuation, entindex(), "VehicleTank.FireSound" ); + } + + m_flNextFireTime = gpGlobals->curtime + TANK_FIRE_INTERVAL; +} + + +bool CVehicleTank::ClientCommand( CBaseTFPlayer *pPlayer, const char *pCmd, ICommandArguments *pArg ) +{ + ResetDeteriorationTime(); + + if ( FStrEq( pCmd, "TurretAngles" ) ) + { + m_flClientYaw = atof( pArg->Argv(1) ); + m_flClientPitch = atof( pArg->Argv(2) ); + return true; + } + + return BaseClass::ClientCommand( pPlayer, pCmd, pArg ); +} + + +bool CVehicleTank::IsPassengerUsingStandardWeapons( int nRole ) +{ + return ( nRole != VEHICLE_ROLE_DRIVER ); +} + + diff --git a/game/server/tf2/tf_vehicle_tank.h b/game/server/tf2/tf_vehicle_tank.h new file mode 100644 index 0000000..3d3792c --- /dev/null +++ b/game/server/tf2/tf_vehicle_tank.h @@ -0,0 +1,72 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#ifndef TF_VEHICLE_TANK_H +#define TF_VEHICLE_TANK_H +#ifdef _WIN32 +#pragma once +#endif + + +#include "tf_basefourwheelvehicle.h" +#include "vphysics/vehicles.h" + + +// ------------------------------------------------------------------------ // +// A wagon that players can build one object on the back of +// ------------------------------------------------------------------------ // +class CVehicleTank : public CBaseTFFourWheelVehicle +{ + DECLARE_CLASS( CVehicleTank, CBaseTFFourWheelVehicle ); + +public: + DECLARE_SERVERCLASS(); + + static CVehicleTank* Create(const Vector &vOrigin, const QAngle &vAngles); + + CVehicleTank(); + virtual ~CVehicleTank(); + + virtual void Spawn(); + virtual void Precache(); + virtual void GetControlPanelInfo( int nPanelIndex, const char *&pPanelName ); + virtual bool CanTakeEMPDamage( void ) { return true; } + virtual bool ClientCommand( CBaseTFPlayer *pPlayer, const CCommand &args ); + + // Vehicle overrides + virtual bool IsPassengerVisible( int nRole ) { return true; } + virtual bool IsPassengerUsingStandardWeapons( int nRole ); + +protected: + // Here's where we deal with weapons + virtual void OnItemPostFrame( CBaseTFPlayer *pDriver ); + + +private: + + void TurretThink(); + void Fire(); + + + // These are the angles where the client currently wants the tank to look. + // The server smoothly interpolates to these. + // Pitch is 0 when facing straight ahead and 90 when facing straight up. + // Yaw is 0 when facing straight ahead and 90 when facing to the tank's left. + float m_flClientYaw; + + float m_flClientPitch; // This is the pitch that the client sends - we use it directly. + + // This is the real yaw (which smoothly interpolates towards m_flClientYaw). + CNetworkVar( float, m_flTurretYaw ); + CNetworkVar( float, m_flTurretPitch ); + + // Tracks when we can fire the cannon. + float m_flNextFireTime; +}; + + + +#endif // TF_VEHICLE_TANK_H diff --git a/game/server/tf2/tf_vehicle_teleport_station.cpp b/game/server/tf2/tf_vehicle_teleport_station.cpp new file mode 100644 index 0000000..2ecdd70 --- /dev/null +++ b/game/server/tf2/tf_vehicle_teleport_station.cpp @@ -0,0 +1,414 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Teleport Station vehicle +// +//=============================================================================// + +#include "cbase.h" +#include "tf_vehicle_teleport_station.h" +#include "engine/IEngineSound.h" +#include "VGuiScreen.h" +#include "ammodef.h" +#include "in_buttons.h" +#include "ndebugoverlay.h" +#include "IEffects.h" +#include "info_act.h" +#include "tf_obj_mcv_selection_panel.h" +#include "info_vehicle_bay.h" + +#define TELEPORT_STATION_MINS Vector(-30, -50, -10) +#define TELEPORT_STATION_MAXS Vector( 30, 50, 55) +#define TELEPORT_STATION_MODEL "models/objects/vehicle_teleport_station.mdl" +#define TELEPORT_STATION_ZONE_HEIGHT 200 +#define TELEPORT_STATION_THINK_CONTEXT "TeleportThink" + +BEGIN_DATADESC( CVehicleTeleportStation ) + + DEFINE_THINKFUNC( PostTeleportThink ), + +END_DATADESC() + +IMPLEMENT_SERVERCLASS_ST( CVehicleTeleportStation, DT_VehicleTeleportStation ) +END_SEND_TABLE() + +LINK_ENTITY_TO_CLASS( vehicle_teleport_station, CVehicleTeleportStation ); +PRECACHE_REGISTER( vehicle_teleport_station ); + +// CVars +ConVar vehicle_teleport_station_health( "vehicle_teleport_station_health","600", FCVAR_NONE, "Siege tower health" ); + + +CUtlVector<CVehicleTeleportStation*> g_AllTeleportStations; +CUtlVector<CVehicleTeleportStation*> CVehicleTeleportStation::s_DeployedTeleportStations; + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CVehicleTeleportStation::CVehicleTeleportStation() +{ + g_AllTeleportStations.AddToTail( this ); + + //FIXME: we should be able to use clientside animation on this vehicle, but prediction is messing it + //up right now. + //UseClientSideAnimation(); +} + + +CVehicleTeleportStation::~CVehicleTeleportStation() +{ + g_AllTeleportStations.FindAndRemove( this ); + s_DeployedTeleportStations.FindAndRemove( this ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CVehicleTeleportStation::Spawn() +{ + SetModel( TELEPORT_STATION_MODEL ); + + // This size is used for placement only... + UTIL_SetSize( this, TELEPORT_STATION_MINS, TELEPORT_STATION_MAXS ); + m_takedamage = DAMAGE_YES; + m_iHealth = vehicle_teleport_station_health.GetInt(); + + SetType( OBJ_VEHICLE_TELEPORT_STATION ); + SetMaxPassengerCount( 4 ); + SetBodygroup( 1, true ); + + BaseClass::Spawn(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CVehicleTeleportStation::Precache() +{ + PrecacheModel( TELEPORT_STATION_MODEL ); + + PrecacheVGuiScreen( "screen_vehicle_bay" ); + + PrecacheModel( "sprites/redglow1.vmt" ); + + BaseClass::Precache(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CVehicleTeleportStation::UpdateOnRemove( void ) +{ + RemoveCornerSprites(); + + // Chain at end to mimic destructor unwind order + BaseClass::UpdateOnRemove(); +} + +//----------------------------------------------------------------------------- +// Purpose: Gets info about the control panels +//----------------------------------------------------------------------------- +void CVehicleTeleportStation::GetControlPanelInfo( int nPanelIndex, const char *&pPanelName ) +{ + pPanelName = "screen_vehicle_bay"; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CVehicleTeleportStation::GetControlPanelClassName( int nPanelIndex, const char *&pPanelName ) +{ + pPanelName = "vgui_screen_vehicle_bay"; +} + +//----------------------------------------------------------------------------- +// Purpose: Screens aren't active until deployed +//----------------------------------------------------------------------------- +void CVehicleTeleportStation::FinishedBuilding( void ) +{ + BaseClass::FinishedBuilding(); + + SetControlPanelsActive( false ); +} + +//----------------------------------------------------------------------------- +// Here's where we deal with weapons, ladders, etc. +//----------------------------------------------------------------------------- +void CVehicleTeleportStation::OnItemPostFrame( CBaseTFPlayer *pDriver ) +{ + // I can't do anything if I'm not active + if ( !ShouldBeActive() ) + return; + + if ( GetPassengerRole( pDriver ) != VEHICLE_ROLE_DRIVER ) + return; + + if ( !IsDeployed() && ( pDriver->m_afButtonPressed & IN_ATTACK ) ) + { + if ( ValidDeployPosition() ) + { + Deploy(); + } + } + else if ( IsDeployed() && ( pDriver->m_afButtonPressed & IN_ATTACK ) ) + { + UnDeploy(); + + SetControlPanelsActive( false ); + SetBodygroup( 1, true ); + RemoveCornerSprites(); + SetContextThink( NULL, 0, TELEPORT_STATION_THINK_CONTEXT ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Finished our deploy +//----------------------------------------------------------------------------- +void CVehicleTeleportStation::OnFinishedDeploy( void ) +{ + BaseClass::OnFinishedDeploy(); + + ValidDeployPosition(); + SetBodygroup( 1, false ); + + // Find the teleport in the mothership + CFuncMassTeleport *pTeleporter = NULL; + while ( (pTeleporter = (CFuncMassTeleport*)gEntList.FindEntityByClassname( pTeleporter, "func_mass_teleport" )) != NULL ) + { + if ( pTeleporter->IsMCVTeleport() ) + { + m_hTeleportExit = pTeleporter; + m_hTeleportExit->AddMCV( this ); + break; + } + } + + // Put some flares down to mark the teleport zone + Vector vecOrigin; + QAngle vecAngles; + for ( int i = 0; i < 4; i++ ) + { + char buf[64]; + Q_snprintf( buf, sizeof( buf ), "teleport_corner%d", i+1 ); + if ( GetAttachment( buf, vecOrigin, vecAngles ) ) + { + CheckBuildPoint( vecOrigin + Vector(0,0,64), Vector(0,0,128), &vecOrigin ); + m_hTeleportCornerSprites[i] = CSprite::SpriteCreate( "sprites/redglow1.vmt", vecOrigin, false ); + m_hTeleportCornerSprites[i]->SetTransparency( kRenderGlow, 255, 255, 255, 255, kRenderFxNoDissipation ); + m_hTeleportCornerSprites[i]->SetScale( 0.1 ); + } + } + // Get the ray point + if ( GetAttachment( "muzzle", vecOrigin, vecAngles ) ) + { + m_hTeleportCornerSprites[4] = CSprite::SpriteCreate( "sprites/redglow1.vmt", vecOrigin, false ); + m_hTeleportCornerSprites[4]->SetTransparency( kRenderGlow, 255, 255, 255, 255, kRenderFxNoDissipation ); + m_hTeleportCornerSprites[4]->SetScale( 0.1 ); + } + + SetContextThink( PostTeleportThink, gpGlobals->curtime + 0.1, TELEPORT_STATION_THINK_CONTEXT ); + + // Add ourselves to the list of deployed MCVs. + s_DeployedTeleportStations.AddToTail( this ); + + // Set our vehicle bay screen's buildpoint + for ( i = m_hScreens.Count(); --i >= 0; ) + { + if (m_hScreens[i].Get()) + { + CVGuiScreenVehicleBay *pScreen = dynamic_cast<CVGuiScreenVehicleBay*>( m_hScreens[0].Get() ); + if ( pScreen ) + { + Vector vecOrigin; + QAngle vecAngles; + if ( GetAttachment( "vehiclebay", vecOrigin, vecAngles ) ) + { + pScreen->SetBuildPoint( vecOrigin, vecAngles ); + } + } + } + } + + SetControlPanelsActive( true ); + + SignalChangeInMCVSelectionPanels(); +} + + +void CVehicleTeleportStation::OnFinishedUnDeploy( void ) +{ + BaseClass::OnFinishedUnDeploy(); + + s_DeployedTeleportStations.FindAndRemove( this ); + SignalChangeInMCVSelectionPanels(); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CVehicleTeleportStation::RemoveCornerSprites( void ) +{ + // Remove all the corner sprites + for ( int i = 0; i < 5; i++ ) + { + if ( m_hTeleportCornerSprites[i] ) + { + UTIL_Remove( m_hTeleportCornerSprites[i] ); + } + } + + // Tell the exit we're done + if ( m_hTeleportExit ) + { + m_hTeleportExit->RemoveMCV( this ); + m_hTeleportExit = NULL; + } +} + + +int CVehicleTeleportStation::GetNumDeployedTeleportStations() +{ + return s_DeployedTeleportStations.Count(); +} + + +CVehicleTeleportStation* CVehicleTeleportStation::GetDeployedTeleportStation( int i ) +{ + return s_DeployedTeleportStations[i]; +} + + +//----------------------------------------------------------------------------- +// Purpose: Return true if the truck's in a valid position +//----------------------------------------------------------------------------- +bool CVehicleTeleportStation::ValidDeployPosition( void ) +{ + // Setup for teleporting + QAngle vecAngles; + if ( !GetAttachment( "teleport_corner1", m_vecTeleporterMins, vecAngles ) || !GetAttachment( "teleport_corner4", m_vecTeleporterMaxs, vecAngles ) ) + return false; + + m_vecTeleporterMaxs.z += TELEPORT_STATION_ZONE_HEIGHT; + + // Make sure we've got the right mins & maxs + for ( int i = 0; i < 3; i++ ) + { + if ( m_vecTeleporterMaxs[i] < m_vecTeleporterMins[i] ) + { + float flVal = m_vecTeleporterMaxs[i]; + m_vecTeleporterMaxs[i] = m_vecTeleporterMins[i]; + m_vecTeleporterMins[i] = flVal; + } + } + + Vector vecDelta = (m_vecTeleporterMaxs - m_vecTeleporterMins) * 0.5; + Vector vecEnd = (m_vecTeleporterMaxs + m_vecTeleporterMins) * 0.5; + Vector vecSrc = vecEnd + Vector(0,0,TELEPORT_STATION_ZONE_HEIGHT * 0.5); + + // Make sure it's clear + // Take the hull, start it high, and try and trace down + trace_t tr; + UTIL_TraceHull( vecSrc, vecEnd, -vecDelta, vecDelta, MASK_SOLID, this, COLLISION_GROUP_PLAYER_MOVEMENT, &tr ); + + //NDebugOverlay::Box( m_vecTeleporterMins, Vector(-2,-2,-2), Vector(2,2,2), 0,255,0,8, 0.1 ); + //NDebugOverlay::Box( m_vecTeleporterMaxs, Vector(-2,-2,-2), Vector(2,2,2), 0,255,0,8, 0.1 ); + //NDebugOverlay::Box( vecSrc, -vecDelta, vecDelta, 255,0,0,8, 0.1 ); + //NDebugOverlay::Box( vecEnd, -vecDelta, vecDelta, 0,0,255,8, 0.1 ); + //NDebugOverlay::Line( m_vecTeleporterMins, m_vecTeleporterMaxs, 255,255,255, 1, 0.1 ); + + // Make sure it's clear + if ( tr.startsolid ) + return false; + + // Get a list of nearby entities + CBaseEntity *pListOfNearbyEntities[100]; + int iNumberOfNearbyEntities = UTIL_EntitiesInBox( pListOfNearbyEntities, 100, m_vecTeleporterMins, m_vecTeleporterMaxs, MASK_ALL ); + for ( i = 0; i < iNumberOfNearbyEntities; i++ ) + { + CBaseEntity *pEntity = pListOfNearbyEntities[i]; + if ( pEntity->IsSolid( ) ) + { + if ( pEntity == this ) + continue; + if ( pEntity->GetCollisionGroup() == TFCOLLISION_GROUP_SHIELD ) + continue; + if ( pEntity->IsPlayer() ) + continue; + + //NDebugOverlay::EntityBounds( pEntity, 0,255,0,8, 0.1 ); + return false; + } + } + + m_vecTeleporterCenter = vecEnd; + m_vecTeleporterCenter.z = m_vecTeleporterMins.z; + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CVehicleTeleportStation::DoTeleport( void ) +{ + ResetDeteriorationTime(); + + if ( m_hTeleportExit ) + { + // Teleport all players attached to me that are within my bbox. + EHANDLE hMCV; + hMCV = this; + Vector vecAbsMins, vecAbsMaxs; + m_hTeleportExit->CollisionProp()->WorldSpaceAABB( &vecAbsMins, &vecAbsMaxs ); + m_hTeleportExit->DoTeleport( GetTFTeam(), vecAbsMins, vecAbsMaxs, m_vecTeleporterCenter, true, true, hMCV ); + } + + // Shrink the corner sprites + for ( int i = 0; i < 5; i++ ) + { + if ( m_hTeleportCornerSprites[i] ) + { + m_hTeleportCornerSprites[i]->SetScale( 0.1 ); + } + } + + SetContextThink( PostTeleportThink, gpGlobals->curtime + 0.1, TELEPORT_STATION_THINK_CONTEXT ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CVehicleTeleportStation::PostTeleportThink( void ) +{ + float flTime = 10.0; + if ( g_hCurrentAct ) + { + flTime = g_hCurrentAct->GetMCVTimer(); + } + + // Start the corner sprites up + for ( int i = 0; i < 4; i++ ) + { + if ( m_hTeleportCornerSprites[i] ) + { + m_hTeleportCornerSprites[i]->SetScale( 0.75, flTime ); + } + } + m_hTeleportCornerSprites[4]->SetScale( 2, flTime ); +} + + +// Returns INVALID_MCV_ID if there are no deployed MCVs. +CVehicleTeleportStation* CVehicleTeleportStation::GetFirstDeployedMCV( int iTeam ) +{ + for ( int i=0; i < s_DeployedTeleportStations.Count(); i++ ) + { + CVehicleTeleportStation *pStation = s_DeployedTeleportStations[i]; + + if ( pStation->GetTeamNumber() == iTeam ) + return pStation; + } + return NULL; +} + diff --git a/game/server/tf2/tf_vehicle_teleport_station.h b/game/server/tf2/tf_vehicle_teleport_station.h new file mode 100644 index 0000000..78f593f --- /dev/null +++ b/game/server/tf2/tf_vehicle_teleport_station.h @@ -0,0 +1,82 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Teleport Station Vehicle +// +//=============================================================================// + +#ifndef TF_VEHICLE_TELEPORT_STATION_H +#define TF_VEHICLE_TELEPORT_STATION_H +#ifdef _WIN32 +#pragma once +#endif + +#include "tf_basefourwheelvehicle.h" +#include "vphysics/vehicles.h" +#include "Sprite.h" +#include "tf_func_mass_teleport.h" + + +//----------------------------------------------------------------------------- +// Purpose: Teleport Station Vehicle +//----------------------------------------------------------------------------- +class CVehicleTeleportStation : public CBaseTFFourWheelVehicle +{ + DECLARE_CLASS( CVehicleTeleportStation, CBaseTFFourWheelVehicle ); + +public: + + DECLARE_SERVERCLASS(); + DECLARE_DATADESC(); + + static CVehicleTeleportStation *Create( const Vector &vOrigin, const QAngle &vAngles ); + + CVehicleTeleportStation(); + virtual ~CVehicleTeleportStation(); + + void Spawn(); + void Precache(); + void UpdateOnRemove( void ); + void GetControlPanelInfo( int nPanelIndex, const char *&pPanelName ); + void GetControlPanelClassName( int nPanelIndex, const char *&pPanelName ); + bool CanTakeEMPDamage( void ) { return true; } + virtual float GetMaxSnapDistance( int iPoint ) { return 192; } + virtual void FinishedBuilding( void ); + + bool ValidDeployPosition( void ); + void OnFinishedDeploy( void ); + void OnFinishedUnDeploy( void ); + void DoTeleport( void ); + void PostTeleportThink( void ); + void RemoveCornerSprites( void ); + + // Vehicle overrides + bool IsPassengerVisible( int nRole ) { return true; } + + static int GetNumDeployedTeleportStations(); + static CVehicleTeleportStation* GetDeployedTeleportStation( int i ); + + // Returns INVALID_MCV_ID if there are no deployed MCVs. + static CVehicleTeleportStation* GetFirstDeployedMCV( int iTeam ); + + +protected: + + // Here's where we deal with weapons + void OnItemPostFrame( CBaseTFPlayer *pDriver ); + +private: + + static CUtlVector<CVehicleTeleportStation*> s_DeployedTeleportStations; + + Vector m_vecTeleporterMins; + Vector m_vecTeleporterMaxs; + Vector m_vecTeleporterCenter; + + CHandle<CFuncMassTeleport> m_hTeleportExit; + CHandle<CSprite> m_hTeleportCornerSprites[5]; +}; + + + + +#endif // TF_VEHICLE_TELEPORT_STATION_H diff --git a/game/server/tf2/tf_vehicle_wagon.cpp b/game/server/tf2/tf_vehicle_wagon.cpp new file mode 100644 index 0000000..0bd916b --- /dev/null +++ b/game/server/tf2/tf_vehicle_wagon.cpp @@ -0,0 +1,80 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: A moving vehicle that is used as a battering ram +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" +#include "tf_vehicle_wagon.h" +#include "engine/IEngineSound.h" +#include "VGuiScreen.h" + + +#define WAGON_MINS Vector(-30, -50, -10) +#define WAGON_MAXS Vector( 30, 50, 55) +#define WAGON_MODEL "models/objects/vehicle_wagon.mdl" + + +IMPLEMENT_SERVERCLASS_ST(CVehicleWagon, DT_VehicleWagon) +END_SEND_TABLE(); + +LINK_ENTITY_TO_CLASS(vehicle_wagon, CVehicleWagon); +PRECACHE_REGISTER(vehicle_wagon); + +// CVars +ConVar vehicle_wagon_health( "vehicle_wagon_health","800", FCVAR_NONE, "Wagon health" ); + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CVehicleWagon::CVehicleWagon() +{ + +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CVehicleWagon::Precache() +{ + PrecacheModel( WAGON_MODEL ); + + PrecacheVGuiScreen( "screen_vehicle_wagon" ); + PrecacheVGuiScreen( "screen_vulnerable_point"); + + BaseClass::Precache(); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CVehicleWagon::Spawn() +{ + SetModel( WAGON_MODEL ); + + // This size is used for placement only... + UTIL_SetSize(this, WAGON_MINS, WAGON_MAXS); + m_takedamage = DAMAGE_YES; + m_iHealth = vehicle_wagon_health.GetInt(); + + SetType( OBJ_WAGON ); + SetMaxPassengerCount( 4 ); + + BaseClass::Spawn(); +} + + +//----------------------------------------------------------------------------- +// Purpose: Gets info about the control panels +//----------------------------------------------------------------------------- +void CVehicleWagon::GetControlPanelInfo( int nPanelIndex, const char *&pPanelName ) +{ + //pPanelName = "screen_vehicle_wagon"; + pPanelName = "screen_vulnerable_point"; +} + + diff --git a/game/server/tf2/tf_vehicle_wagon.h b/game/server/tf2/tf_vehicle_wagon.h new file mode 100644 index 0000000..e1ba433 --- /dev/null +++ b/game/server/tf2/tf_vehicle_wagon.h @@ -0,0 +1,42 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: A stationary gun that players can man +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef TF_VEHICLE_WAGON_H +#define TF_VEHICLE_WAGON_H + +#ifdef _WIN32 +#pragma once +#endif + +#include "tf_basefourwheelvehicle.h" +#include "vphysics/vehicles.h" + + +// ------------------------------------------------------------------------ // +// A wagon that players can build one object on the back of +// ------------------------------------------------------------------------ // +class CVehicleWagon: public CBaseTFFourWheelVehicle +{ + DECLARE_CLASS( CVehicleWagon, CBaseTFFourWheelVehicle ); + +public: + DECLARE_SERVERCLASS(); + + static CVehicleWagon* Create(const Vector &vOrigin, const QAngle &vAngles); + + CVehicleWagon(); + + virtual void Spawn(); + virtual void Precache(); + virtual void GetControlPanelInfo( int nPanelIndex, const char *&pPanelName ); + virtual bool CanTakeEMPDamage( void ) { return true; } + + // Vehicle overrides + virtual bool IsPassengerVisible( int nRole ) { return true; } +}; + +#endif // TF_VEHICLE_WAGON_H diff --git a/game/server/tf2/tf_walker_base.cpp b/game/server/tf2/tf_walker_base.cpp new file mode 100644 index 0000000..7fa3711 --- /dev/null +++ b/game/server/tf2/tf_walker_base.cpp @@ -0,0 +1,302 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "cbase.h" +#include "tf_walker_base.h" + + +#include "in_buttons.h" +#include "shake.h" + + +static float MAX_WALKER_VEL = 100; + + +IMPLEMENT_SERVERCLASS_ST( CWalkerBase, DT_WalkerBase ) +END_SEND_TABLE() + + +CWalkerBase::CWalkerBase() +{ + m_vSteerVelocity.Init(); + m_iMovePoseParamX = -1; + m_iMovePoseParamY = -1; + m_bWalkMode = false; + m_flDontMakeSoundsUntil = 0; + m_flPlaybackSpeedBoost = 1; + m_flVelocityDecayRate = 80; + m_LastButtons = 0; + m_vLastCmdViewAngles.Init(); +} + + +void CWalkerBase::SpawnWalker( + const char *pModelName, + int objectType, + const Vector &vPlacementMins, + const Vector &vPlacementMaxs, + int iHealth, + int nMaxPassengers, + float flPlaybackSpeedBoost + ) +{ + SetModel( pModelName ); + SetType( objectType ); + + UTIL_SetSize( this, vPlacementMins, vPlacementMaxs ); + m_iHealth = iHealth; + m_flPlaybackSpeedBoost = flPlaybackSpeedBoost; + + m_takedamage = DAMAGE_YES; + SetMaxPassengerCount( nMaxPassengers ); + + + + // The model should be set before the derived class calls our Spawn(). + Assert( GetModel() ); + + // By default, all walkers use the walk_box animation as they move. + m_iMovePoseParamX = LookupPoseParameter( "move_x" ); + m_iMovePoseParamY = LookupPoseParameter( "move_y" ); + EnableWalkMode( true ); + + // The base class spawn sets a default collision group, so this needs to + // be called post. + SetCollisionGroup( COLLISION_GROUP_VEHICLE ); + + BaseClass::Spawn(); + + + // HACKHACK: this is just so CBaseObject doesn't call StudioFrameAdvance for us. We should probably have + // a specific flag for this behavior. + m_fObjectFlags |= OF_DOESNT_HAVE_A_MODEL; + m_fObjectFlags &= ~OF_MUST_BE_BUILT_ON_ATTACHMENT; + + + // We animate, so let's not use manual mode for now. + SetMoveType( MOVETYPE_STEP ); + AddSolidFlags( FSOLID_CUSTOMRAYTEST | FSOLID_CUSTOMBOXTEST ); + + EnableServerIK(); + + SetContextThink( WalkerThink, gpGlobals->curtime, "WalkerThink" ); +} + +void CWalkerBase::EnableWalkMode( bool bEnable ) +{ + m_bWalkMode = bEnable; + + // Stop any movement.. + m_vSteerVelocity.Init(); + + if ( bEnable ) + { + ResetSequence( LookupSequence( "walk_box" ) ); + + // HACK: there should be a better way to this.. like CBaseAnimating::ResetAnimation, + // or ResetSequence should do it. + SetCycle( 0 ); + } +} + + +void CWalkerBase::AdjustInitialBuildAngles() +{ + QAngle vNewAngles = GetAbsAngles(); + vNewAngles[YAW] += 90; + SetAbsAngles( vNewAngles ); +} + + +void CWalkerBase::WalkerThink() +{ + float dt = GetTimeDelta(); + + // Decay our velocity. + if ( m_bWalkMode ) + { + m_flPlaybackRate = m_flPlaybackSpeedBoost; + + + float flDecayRate = m_flVelocityDecayRate; + + float flLen = m_vSteerVelocity.Length(); + Vector2DNormalize( m_vSteerVelocity ); + + float flDecayAmt = flDecayRate * dt; + flLen = MAX( 0, flLen - flDecayAmt ); + m_vSteerVelocity *= flLen; + + + // Setup our pose parameters. + SetPoseParameter( m_iMovePoseParamX, RemapVal( m_vSteerVelocity.x, -MAX_WALKER_VEL, MAX_WALKER_VEL, -1, 1 ) ); + SetPoseParameter( m_iMovePoseParamY, RemapVal( m_vSteerVelocity.y, -MAX_WALKER_VEL, MAX_WALKER_VEL, -1, 1 ) ); + + // Use an idle animation if they're not moving. + int iWantedSequence = LookupSequence( "walk_box" ); + if ( m_vSteerVelocity.x == 0 && m_vSteerVelocity.y == 0 ) + { + iWantedSequence = LookupSequence( "idle" ); + + // HACK: HL2 Strider has no idle + if ( iWantedSequence == -1 ) + iWantedSequence = LookupSequence( "ragdoll" ); + } + + if ( iWantedSequence != -1 && GetSequence() != iWantedSequence ) + ResetSequence( iWantedSequence ); + } + + + // Now ask the model how far it thought it moved based on the animation. + // Turns out the animation thinks it's moving just a tiny bit, even when we're centered on the idle animation, + // so we just force it not to move here if we know we're not supposed to move. + if ( m_vSteerVelocity.Length() > 0 ) + { + Vector vNewPos = GetWalkerLocalMovement(); + + SetLocalOrigin( vNewPos ); + } + + + // Hard-coded for now. These should come from the vehicle's script eventually. + // Now slowly rotate towards the player's eye angles. + CBasePlayer *pPlayer = GetPassenger( VEHICLE_ROLE_DRIVER ); + if ( pPlayer ) + { + static float flAccelRate = 180; + static float flRotateRate = 60; + + + // Figure out a force to apply to our current velocity. + Vector2D vAccel( 0, 0 ); + + if ( m_LastButtons & IN_FORWARD ) + vAccel.x += flAccelRate; + + if ( m_LastButtons & IN_BACK ) + vAccel.x -= flAccelRate; + + if ( m_LastButtons & IN_MOVELEFT ) + vAccel.y -= flAccelRate; + + if ( m_LastButtons & IN_MOVERIGHT ) + vAccel.y += flAccelRate; + + m_vSteerVelocity += vAccel * dt; + + + m_vSteerVelocity.x = clamp( m_vSteerVelocity.x, -MAX_WALKER_VEL, MAX_WALKER_VEL ); + m_vSteerVelocity.y = clamp( m_vSteerVelocity.y, -MAX_WALKER_VEL, MAX_WALKER_VEL ); + + + float wantedYaw = m_vLastCmdViewAngles[YAW]; + QAngle curAngles = GetAbsAngles(); + curAngles[YAW] = ApproachAngle( wantedYaw, curAngles[YAW], flRotateRate * dt ); + SetAbsAngles( curAngles ); + } + + + DispatchAnimEvents( this ); + + + // Get another think. + SetContextThink( WalkerThink, gpGlobals->curtime + dt, "WalkerThink" ); +} + + +Vector CWalkerBase::GetWalkerLocalMovement() +{ + bool bIgnored; + Vector vNewPos; + QAngle vNewAngles; + GetIntervalMovement( GetAnimTimeInterval(), bIgnored, vNewPos, vNewAngles ); + return vNewPos; +} + + +const Vector2D& CWalkerBase::GetSteerVelocity() const +{ + return m_vSteerVelocity; +} + + + +void CWalkerBase::Spawn() +{ + // Derived classes should call SpawnWalker instead of chaining down to CWalkerBase::Spawn(). + Assert( false ); +} + + +void CWalkerBase::Activate() +{ + WalkerActivate(); + BaseClass::Activate(); +} + +void CWalkerBase::WalkerActivate( void ) +{ + // Until we're finished building, turn off vphysics-based motion + SetSolid( SOLID_VPHYSICS ); + VPhysicsInitStatic(); + + SetPoseParameter( m_iMovePoseParamX, 0 ); + SetPoseParameter( m_iMovePoseParamY, 0 ); +} + + +void CWalkerBase::SetVelocityDecayRate( float flDecayRate ) +{ + m_flVelocityDecayRate = flDecayRate; +} + + +float CWalkerBase::GetTimeDelta() const +{ + return 0.1; +} + + +void CWalkerBase::SetupMove( CBasePlayer *pPlayer, CUserCmd *ucmd, IMoveHelper *pHelper, CMoveData *move ) +{ + // This calls StudioFrameAdvance for us. + //BaseClass::SetupMove( pPlayer, ucmd, pHelper, move ); + + // Lose control when the player dies + if ( pPlayer->IsAlive() == false ) + { + m_LastButtons = 0; + return; + } + + // Only the driver gets to drive. + int nRole = GetPassengerRole( pPlayer ); + if ( nRole != VEHICLE_ROLE_DRIVER ) + return; + + m_LastButtons = ucmd->buttons; + m_vLastCmdViewAngles = ucmd->viewangles; +} + + +bool CWalkerBase::IsPassengerVisible( int nRole ) +{ + return true; +} + + +bool CWalkerBase::StartBuilding( CBaseEntity *pBuilder ) +{ + if ( !BaseClass::StartBuilding( pBuilder ) ) + return false; + + WalkerActivate(); + return true; +} + + + diff --git a/game/server/tf2/tf_walker_base.h b/game/server/tf2/tf_walker_base.h new file mode 100644 index 0000000..269606c --- /dev/null +++ b/game/server/tf2/tf_walker_base.h @@ -0,0 +1,115 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#ifndef TF_WALKER_BASE_H +#define TF_WALKER_BASE_H +#ifdef _WIN32 +#pragma once +#endif + + +#include "basetfvehicle.h" + + +class CWalkerBase : public CBaseTFVehicle +{ +public: + DECLARE_CLASS( CWalkerBase, CBaseTFVehicle ); + DECLARE_SERVERCLASS(); + + +public: + CWalkerBase(); + + // Derived classes must call this from inside their Spawn() function. Their Spawn() function + // should NOT chain down to CWalkerBase. + void SpawnWalker( + const char *pModelName, + int objectType, + const Vector &vPlacementMins, + const Vector &vPlacementMaxs, + int iHealth, + int nMaxPassengers, + float flPlaybackSpeedBoost // Strider likes the animations played at 2x speed. + ); + + virtual void AdjustInitialBuildAngles(); + + // When it's in walk mode, it'll set a walk animation, process user input, set pose parameters, + // and move the entity around as it walks. + void EnableWalkMode( bool bEnable ); + + // This is called each frame to do thinking. Derived classes can hook this to do their own stuff each frame. + virtual void WalkerThink(); + + // Returns the new local origin to set based on the walker's movement. + // This usually just asks for the movement from the animation, but some walkers may + // want to generate the motion in code. + virtual Vector GetWalkerLocalMovement(); + + // See notes on m_vSteerVelocity below. + const Vector2D& GetSteerVelocity() const; + + void WalkerActivate( void ); + + // See m_flVelocityDecayRate. + void SetVelocityDecayRate( float flDecayRate ); + + // Walkers think 10x/second. + float GetTimeDelta() const; + +// CBaseEntity. +public: + virtual void Spawn(); + virtual void Activate(); + +// IVehicle overrides. +public: + virtual void SetupMove( CBasePlayer *player, CUserCmd *ucmd, IMoveHelper *pHelper, CMoveData *move ); + + +// IServerVehicle overrides. +public: + virtual bool IsPassengerVisible( int nRole ); + + +// CBaseObject overrides. +public: + virtual bool StartBuilding( CBaseEntity *pBuilder ); + + +public: + + // This is a hack to prevent the strider from playing a lot of footstep sounds, until we figure out 9-way anim events + float m_flDontMakeSoundsUntil; + + // Last usercmd buttons. + int m_LastButtons; + QAngle m_vLastCmdViewAngles; + + +private: + + // This is the (local) velocity that the player's controls are saying the player wants to move in. + // Note that it is completely virtual - the speed that the walker moves at is gotten from the animations ground speed. + // It maps this velocity a 9-way blend for movement. +X = forward, +Y = left. + Vector2D m_vSteerVelocity; + + // Which pose parameters we use to make this guy walk around on X and Y. + int m_iMovePoseParamX; + int m_iMovePoseParamY; + + float m_flPlaybackSpeedBoost; + + bool m_bWalkMode; + + // This is a magic number that can be tweaked to make the velocity decay faster. + // Default: 80 + float m_flVelocityDecayRate; +}; + + +#endif // TF_WALKER_BASE_H diff --git a/game/server/tf2/tf_walker_ministrider.cpp b/game/server/tf2/tf_walker_ministrider.cpp new file mode 100644 index 0000000..3bc406a --- /dev/null +++ b/game/server/tf2/tf_walker_ministrider.cpp @@ -0,0 +1,515 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "cbase.h" +#include "tf_walker_ministrider.h" +#include "in_buttons.h" +#include "studio.h" +#include "shake.h" +#include "tf_gamerules.h" +#include "npcevent.h" +#include "ndebugoverlay.h" +#include "te_effect_dispatch.h" +#include "beam_shared.h" +#include "ai_activity.h" + +#define WALKER_MINI_STRIDER_MODEL "models/objects/alien_vehicle_strider.mdl" +#define STRIDER_TORSO_VERTICAL_SLIDE_SPEED 100 + +float LARGE_GUN_FIRE_TIME = 3; + +// Skirmisher CVars +ConVar tf_skirmisher_speed( "tf_skirmisher_speed", "300" ); +ConVar tf_skirmisher_machinegun_range( "tf_skirmisher_machinegun_range", "2000" ); +ConVar tf_skirmisher_machinegun_damage( "tf_skirmisher_machinegun_damage", "20" ); +ConVar tf_skirmisher_machinegun_rof( "tf_skirmisher_machinegun_rof", "10" ); + +IMPLEMENT_SERVERCLASS_ST( CWalkerMiniStrider, DT_WalkerMiniStrider ) +END_SEND_TABLE() + +LINK_ENTITY_TO_CLASS( walker_mini_strider, CWalkerMiniStrider ); +PRECACHE_REGISTER( walker_mini_strider ); + + + +CWalkerMiniStrider::CWalkerMiniStrider() +{ + m_flWantedZ = -1; + m_bFiringMachineGun = m_bFiringLargeGun = false; + m_flOriginToLowestLegHeight = 133; + m_State = STATE_NORMAL; + m_bFiringLeftGun = true; + m_flNextFootstepSoundTime = 0; +} + + +CWalkerMiniStrider::~CWalkerMiniStrider() +{ + UTIL_Remove( m_pEnergyBeam ); +} + + +void CWalkerMiniStrider::Precache() +{ + PrecacheModel( WALKER_MINI_STRIDER_MODEL ); + + PrecacheScriptSound( "Skirmisher.Footstep" ); + PrecacheScriptSound( "Skirmisher.GunSound" ); + PrecacheScriptSound( "Skirmisher.GunChargeSound" ); + + BaseClass::Precache(); +} + + +void CWalkerMiniStrider::Spawn() +{ + SpawnWalker( + WALKER_MINI_STRIDER_MODEL, // Model name. + OBJ_WALKER_MINI_STRIDER, // Object type. + Vector( 0, 0, 140 ) + Vector( -100, -100, -100 ), // Placement dimensions. + Vector( 0, 0, 140 ) + Vector( 100, 100, 100 ), + 200, // Health. + 1, // Max passengers. + 1.0 // 1x speed multiplier + ); + + SetVelocityDecayRate( 110 ); +} + + +bool CWalkerMiniStrider::IsPassengerVisible( int nRole ) +{ + if ( nRole == VEHICLE_ROLE_DRIVER ) + return false; + + return true; +} + + +void CWalkerMiniStrider::StartFiringMachineGun() +{ + StopFiringLargeGun(); + m_bFiringMachineGun = true; + m_flNextShootTime = gpGlobals->curtime; +} + + +void CWalkerMiniStrider::StopFiringMachineGun() +{ + m_bFiringMachineGun = false; +} + + +Vector CWalkerMiniStrider::GetLargeGunShootOrigin() +{ + Vector vRet; + QAngle dummyAngle; + if ( GetAttachment( LookupAttachment( "LargeGun" ), vRet, dummyAngle ) ) + { + return vRet; + } + else + { + return GetAbsOrigin(); + } +} + + +void CWalkerMiniStrider::StartFiringLargeGun() +{ + CBasePlayer *pPlayer = GetPassenger( VEHICLE_ROLE_DRIVER ); + Assert( pPlayer ); + if ( !pPlayer ) + return; + + // Figure out what we're shooting at. + Vector vSrc = GetLargeGunShootOrigin(); + + Vector vEyePos = pPlayer->EyePosition(); + Vector vEyeForward; + AngleVectors( pPlayer->LocalEyeAngles(), &vEyeForward ); + + trace_t trace; + UTIL_TraceLine( vEyePos, vEyePos + vEyeForward * 2000, MASK_SOLID, this, COLLISION_GROUP_NONE, &trace ); + if ( trace.fraction < 1 ) + { + m_vLargeGunForward = trace.endpos - vSrc; + VectorNormalize( m_vLargeGunForward ); + + trace_t trace; + UTIL_TraceLine( vSrc, vSrc + m_vLargeGunForward * 2000, MASK_SOLID, this, COLLISION_GROUP_NONE, &trace ); + if ( trace.fraction < 1 ) + { + EnableWalkMode( false ); + + m_vLargeGunTargetPos = trace.endpos; + m_flLargeGunCountdown = LARGE_GUN_FIRE_TIME; + m_bFiringLargeGun = true; + + // Show an energy beam until we actually shoot. + m_pEnergyBeam = CBeam::BeamCreate( "sprites/physbeam.vmt", 25 ); + m_pEnergyBeam->SetColor( 255, 0, 0 ); + m_pEnergyBeam->SetBrightness( 100 ); + m_pEnergyBeam->SetNoise( 4 ); + m_pEnergyBeam->PointsInit( vSrc, m_vLargeGunTargetPos ); + m_pEnergyBeam->LiveForTime( LARGE_GUN_FIRE_TIME ); + + // Play a charge-up sound. + CPASAttenuationFilter filter( this, "Skirmisher.GunChargeSound" ); + EmitSound( filter, 0, "Skirmisher.GunChargeSound", &vSrc ); + } + } +} + + +void CWalkerMiniStrider::StopFiringLargeGun() +{ + if ( m_bFiringLargeGun ) + { + m_bFiringLargeGun = false; + + UTIL_Remove( m_pEnergyBeam ); + m_pEnergyBeam = NULL; + + EnableWalkMode( true ); + } +} + + +void CWalkerMiniStrider::UpdateLargeGun() +{ + float dt = GetTimeDelta(); + + if ( !m_bFiringLargeGun ) + return; + + m_flLargeGunCountdown -= dt; + if ( m_flLargeGunCountdown <= 0 ) + { + // Fire! + Vector vSrc = GetLargeGunShootOrigin(); + trace_t trace; + UTIL_TraceLine( vSrc, vSrc + m_vLargeGunForward * 2000, MASK_SOLID, this, COLLISION_GROUP_NONE, &trace ); + if ( trace.fraction < 1 ) + { + CBasePlayer *pDriver = GetPassenger( VEHICLE_ROLE_DRIVER ); + if ( pDriver ) + { + UTIL_ImpactTrace( &trace, DMG_ENERGYBEAM, "Strider" ); + + Vector vHitPos = trace.endpos; + float flDamageRadius = 100; + float flDamage = 100; + + CPASFilter filter( vHitPos ); + te->Explosion( filter, 0.0, + &vHitPos, + g_sModelIndexFireball, + 2.0, + 15, + TE_EXPLFLAG_NONE, + flDamageRadius, + flDamage ); + + UTIL_ScreenShake( vHitPos, 10.0, 150.0, 1.0, 100, SHAKE_START ); + RadiusDamage( CTakeDamageInfo( this, pDriver, flDamage, DMG_BLAST ), vHitPos, flDamageRadius, CLASS_NONE, NULL ); + } + } + + StopFiringLargeGun(); + } +} + + +void CWalkerMiniStrider::Crouch() +{ + if ( m_State == STATE_CROUCHING || m_State == STATE_CROUCHED ) + return; + + // Disable the base class's walking functionality while we're crouched. + EnableWalkMode( false ); + + SetActivity( ACT_CROUCH ); + m_flCrouchTimer = SequenceDuration(); + m_State = STATE_CROUCHING; +} + + +void CWalkerMiniStrider::UnCrouch() +{ + if ( m_State == STATE_CROUCHING || m_State == STATE_UNCROUCHING || m_State == STATE_NORMAL ) + return; + + SetActivity( ACT_STAND ); + + m_flCrouchTimer = SequenceDuration(); + + m_State = STATE_UNCROUCHING; +} + + +void CWalkerMiniStrider::UpdateCrouch() +{ + float dt = GetTimeDelta(); + + m_flCrouchTimer -= dt; + if ( m_flCrouchTimer <= 0 ) + { + if ( m_State == STATE_CROUCHING ) + { + m_State = STATE_CROUCHED; + SetActivity( ACT_CROUCHIDLE ); + } + else if ( m_State == STATE_UNCROUCHING ) + { + EnableWalkMode( true ); + m_State = STATE_NORMAL; + SetActivity( ACT_IDLE ); + } + } +} + + +void CWalkerMiniStrider::FireMachineGun() +{ + CBaseEntity *pDriver = GetPassenger( VEHICLE_ROLE_DRIVER ); + if ( pDriver ) + { + // Alternate the gun we're firing + char *attachmentNames[2] = { "MachineGun_Left", "MachineGun_Right" }; + int iAttachment = LookupAttachment( attachmentNames[!m_bFiringLeftGun] ); + m_bFiringLeftGun = !m_bFiringLeftGun; + + Vector vAttachmentPos; + QAngle vAttachmentAngles; + GetAttachment( iAttachment, vAttachmentPos, vAttachmentAngles ); + + Vector vEyePos = pDriver->EyePosition(); + Vector vEyeForward; + QAngle vecAngles = pDriver->LocalEyeAngles(); + // Use the skirmisher's yaw + vecAngles[YAW] = GetAbsAngles()[YAW]; + AngleVectors( vecAngles, &vEyeForward ); + + // Trace ahead to find out where the crosshair is aiming + trace_t trace; + float flMaxRange = tf_skirmisher_machinegun_range.GetFloat(); + UTIL_TraceLine( + vEyePos, + vEyePos + vEyeForward * flMaxRange, + MASK_SOLID, + this, + COLLISION_GROUP_NONE, + &trace ); + + Vector vecDir; + if ( trace.fraction < 1.0 ) + { + vecDir = (trace.endpos - vAttachmentPos ); + VectorNormalize( vecDir ); + } + else + { + vecDir = vEyeForward; + } + + // Shoot! + TFGameRules()->FireBullets( CTakeDamageInfo( this, pDriver, tf_skirmisher_machinegun_damage.GetFloat(), DMG_BULLET ), + 1, // Num shots + vAttachmentPos, + vecDir, + VECTOR_CONE_3DEGREES, // Spread + flMaxRange, // Range + DMG_BULLET, + 1, // Tracer freq + entindex(), + iAttachment, // Attachment ID + "MinigunTracer" ); + + m_flNextShootTime += (1.0f / tf_skirmisher_machinegun_rof.GetFloat()); + + // Play the fire sound. + Vector vCenter = WorldSpaceCenter(); + CPASAttenuationFilter filter( this, "Skirmisher.GunSound" ); + EmitSound( filter, 0, "Skirmisher.GunSound", &vCenter ); + } +} + + +void CWalkerMiniStrider::WalkerThink() +{ + float dt = GetTimeDelta(); + + BaseClass::WalkerThink(); + + // Shoot the machine gun? + if ( !m_bFiringLargeGun ) + { + if ( m_LastButtons & IN_ATTACK ) + { + if ( !m_bFiringMachineGun ) + StartFiringMachineGun(); + } + else if ( m_bFiringMachineGun ) + { + StopFiringMachineGun(); + } + } + + // Fire the large gun? + if ( !m_bFiringMachineGun ) + { + if ( m_LastButtons & IN_ATTACK2 ) + { + if ( !m_bFiringLargeGun ) + StartFiringLargeGun(); + } + } + + + UpdateCrouch(); + + // Make sure it's crouched when there is no driver. + if ( GetPassenger( VEHICLE_ROLE_DRIVER ) ) + { + if ( m_LastButtons & IN_DUCK ) + { + Crouch(); + } + else + { + UnCrouch(); + } + } + else + { + Crouch(); + } + + if ( m_bFiringMachineGun ) + { + while ( gpGlobals->curtime > m_flNextShootTime ) + { + FireMachineGun(); + } + } + + UpdateLargeGun(); + + // Move our torso within range of our feet. + if ( m_flOriginToLowestLegHeight != -1 ) + { + Vector vCenter = WorldSpaceCenter(); + + //NDebugOverlay::EntityBounds( this, 255, 100, 0, 0 ,0 ); + //NDebugOverlay::Line( vCenter, vCenter-Vector(0,0,2000), 255,0,0, true, 0 ); + + trace_t trace; + UTIL_TraceLine( + vCenter, + vCenter - Vector( 0, 0, 2000 ), + MASK_SOLID_BRUSHONLY, + this, + COLLISION_GROUP_NONE, + &trace ); + + if ( trace.fraction < 1 ) + { + m_flWantedZ = trace.endpos.z + m_flOriginToLowestLegHeight; + } + + // Move our Z towards the wanted Z. + if ( m_flWantedZ != -1 ) + { + Vector vCur = vCenter; + vCur.z = Approach( m_flWantedZ, vCur.z, STRIDER_TORSO_VERTICAL_SLIDE_SPEED * dt ); + SetAbsOrigin( GetAbsOrigin() + Vector( 0, 0, vCur.z - vCenter.z ) ); + } + } +} + + +void CWalkerMiniStrider::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + // Sapper removal + if ( RemoveEnemyAttachments( pActivator ) ) + return; + + CBaseTFPlayer *pPlayer = dynamic_cast<CBaseTFPlayer*>(pActivator); + if ( !pPlayer || !InSameTeam( pPlayer ) ) + return; + + // Ok, put them in the driver role. + AttemptToBoardVehicle( pPlayer ); +} + + +void CWalkerMiniStrider::SetupMove( CBasePlayer *pPlayer, CUserCmd *ucmd, IMoveHelper *pHelper, CMoveData *move ) +{ + BaseClass::SetupMove( pPlayer, ucmd, pHelper, move ); +} + + +bool CWalkerMiniStrider::StartBuilding( CBaseEntity *pBuilder ) +{ + return BaseClass::StartBuilding( pBuilder ); +} + + +Vector CWalkerMiniStrider::GetWalkerLocalMovement() +{ + float dt = GetTimeDelta(); + + Vector vForward, vRight; + AngleVectors( GetLocalAngles(), &vForward, &vRight, NULL ); + + float flSpeed = (tf_skirmisher_speed.GetFloat() / 100) * dt; + Vector vMovement = + vForward * (GetSteerVelocity().x * flSpeed) + + vRight * (GetSteerVelocity().y * flSpeed); + + return GetLocalOrigin() + vMovement; +} + + +void CWalkerMiniStrider::SetPassenger( int nRole, CBasePlayer *pPassenger ) +{ + BaseClass::SetPassenger( nRole, pPassenger ); + + if ( nRole == VEHICLE_ROLE_DRIVER && pPassenger ) + UnCrouch(); +} + + +void CWalkerMiniStrider::FootHit( const char *pFootName ) +{ + if ( gpGlobals->curtime >= m_flNextFootstepSoundTime ) + { + Vector footPosition; + QAngle angles; + + ((BaseClass*)this)->GetAttachment( pFootName, footPosition, angles ); + CPASAttenuationFilter filter( this, "Skirmisher.Footstep" ); + EmitSound( filter, entindex(), "Skirmisher.Footstep", &footPosition ); + + m_flNextFootstepSoundTime = gpGlobals->curtime + 0.2; + } +} + + +void CWalkerMiniStrider::HandleAnimEvent( animevent_t *pEvent ) +{ + switch( pEvent->event ) + { + case 1: + case 2: + case 3: + { + FootHit( "back foot" ); + } + break; + } +} diff --git a/game/server/tf2/tf_walker_ministrider.h b/game/server/tf2/tf_walker_ministrider.h new file mode 100644 index 0000000..eb0bed9 --- /dev/null +++ b/game/server/tf2/tf_walker_ministrider.h @@ -0,0 +1,117 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#ifndef TF_WALKER_MINISTRIDER_H +#define TF_WALKER_MINISTRIDER_H +#ifdef _WIN32 +#pragma once +#endif + + +#include "tf_walker_base.h" + + +class CBeam; + + +class CWalkerMiniStrider : public CWalkerBase +{ +public: + DECLARE_CLASS( CWalkerMiniStrider, CWalkerBase ); + DECLARE_SERVERCLASS(); + + CWalkerMiniStrider(); + virtual ~CWalkerMiniStrider(); + + +// CWalkerBase. +protected: + virtual void WalkerThink(); + virtual Vector GetWalkerLocalMovement(); + + +// CBaseObject. +public: + virtual bool StartBuilding( CBaseEntity *pBuilder ); + + +// CBaseEntity. +public: + virtual void Precache(); + virtual void Spawn(); + virtual void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + +// CBaseAnimating. +public: + + virtual void HandleAnimEvent( animevent_t *pEvent ); + + +// IServerVehicle. +public: + virtual bool IsPassengerVisible( int nRole ); + virtual void SetPassenger( int nRole, CBasePlayer *pPassenger ); + + +// IVehicle overrides. +public: + virtual void SetupMove( CBasePlayer *pPlayer, CUserCmd *ucmd, IMoveHelper *pHelper, CMoveData *move ); + + +private: + void FootHit( const char *pFootName ); + + void StartFiringMachineGun(); + void StopFiringMachineGun(); + void FireMachineGun(); + + Vector GetLargeGunShootOrigin(); + void StartFiringLargeGun(); + void StopFiringLargeGun(); + void UpdateLargeGun(); + + void Crouch(); + void UnCrouch(); + void UpdateCrouch(); + + +private: + + enum + { + STATE_CROUCHING=0, + STATE_CROUCHED, + STATE_UNCROUCHING, + STATE_UNCROUCHED, + STATE_NORMAL + }; + + int m_State; + + float m_flCrouchTimer; + + CNetworkVar( bool, m_bFiringMachineGun ); + CNetworkVar( bool, m_bFiringLargeGun ); + CNetworkVector( m_vLargeGunTargetPos ); + float m_flLargeGunCountdown; + Vector m_vLargeGunForward; + CHandle<CBeam> m_pEnergyBeam; + + // Firing + float m_flNextShootTime; + bool m_bFiringLeftGun; + + // Used to keep him on the ground. + float m_flOriginToLowestLegHeight; + float m_flWantedZ; + + // Used to get around an anim event bug where it triggers events a bunch of times when an animation loops. + float m_flNextFootstepSoundTime; +}; + + +#endif // TF_WALKER_MINISTRIDER_H diff --git a/game/server/tf2/tf_walker_strider.cpp b/game/server/tf2/tf_walker_strider.cpp new file mode 100644 index 0000000..8fd9b36 --- /dev/null +++ b/game/server/tf2/tf_walker_strider.cpp @@ -0,0 +1,395 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "cbase.h" +#include "tf_walker_strider.h" +#include "npcevent.h" +#include "engine/IEngineSound.h" +#include "tf_gamerules.h" +#include "shake.h" +#include "in_buttons.h" +#include "studio.h" + + +#define STRIDER_AE_FOOTSTEP_LEFT 1 +#define STRIDER_AE_FOOTSTEP_RIGHT 2 +#define STRIDER_AE_FOOTSTEP_BACK 3 +#define STRIDER_AE_FOOTSTEP_LEFTM 4 +#define STRIDER_AE_FOOTSTEP_RIGHTM 5 +#define STRIDER_AE_FOOTSTEP_BACKM 6 +#define STRIDER_AE_FOOTSTEP_LEFTL 7 +#define STRIDER_AE_FOOTSTEP_RIGHTL 8 +#define STRIDER_AE_FOOTSTEP_BACKL 9 + + +// How many inches/sec the strider's torso moves up and down to get the torso at the right height. +#define STRIDER_TORSO_VERTICAL_SLIDE_SPEED 100 +#define STRIDER_FIRE_TIME 5 +#define STRIDER_FIRE_INTERVAL 0.3 +#define STRIDER_FIRE_ANGLE_ERROR 6 // N degrees of error when he fires. +#define WALKER_STRIDER_MODEL "models/objects/walker_strider.mdl" + + +IMPLEMENT_SERVERCLASS_ST( CWalkerStrider, DT_WalkerStrider ) + SendPropInt( SENDINFO( m_bCrouched ), 1, SPROP_UNSIGNED ) +END_SEND_TABLE() +LINK_ENTITY_TO_CLASS( walker_strider, CWalkerStrider ); +PRECACHE_REGISTER( walker_strider ); + + +char *g_StriderFeet[] = +{ + "left foot", + "right foot", + "back foot" +}; +#define NUM_STRIDER_FEET ARRAYSIZE( g_StriderFeet ) + + +CWalkerStrider::CWalkerStrider() +{ + m_bCrouched = false; + m_flOriginToLowestLegHeight = -1; + m_flWantedZ = -1; + m_bFiring = false; + m_vDriverAngles.Init(); +} + + +void CWalkerStrider::Precache() +{ + PrecacheModel( WALKER_STRIDER_MODEL ); + + PrecacheScriptSound( "Brush.Footstep" ); + PrecacheScriptSound( "Brush.Fire" ); + + BaseClass::Precache(); +} + + +void CWalkerStrider::Spawn() +{ + SpawnWalker( + WALKER_STRIDER_MODEL, // Model name. + OBJ_WALKER_STRIDER, // Object type. + Vector( -186, -157, -504 ), // Placement dimensions. + Vector( 194, 159, 41 ), + 200, // Health. + 1, // Max passengers. + 2.0 // 2x animation speed boost + ); +} + +bool CWalkerStrider::IsPassengerVisible( int nRole ) +{ + if ( nRole == VEHICLE_ROLE_DRIVER ) + return false; + + return true; +} + + +void CWalkerStrider::Fire() +{ + EnableWalkMode( false ); + m_bFiring = true; + m_flFireEndTime = gpGlobals->curtime + STRIDER_FIRE_TIME; + ResetSequence( LookupSequence( "shoot01a" ) ); + m_flAnimTime = m_flPrevAnimTime = 0; + SetCycle( 0 ); + m_flNextShootTime = gpGlobals->curtime; + + m_vFireAngles[ROLL] = 0; + m_vFireAngles[PITCH] = m_vDriverAngles[PITCH]; + m_vFireAngles[YAW] = GetAbsAngles()[YAW]; + + // Play the fire sound. + CPASAttenuationFilter filter( this, "Brush.Fire" ); + EmitSound( filter, 0, "Brush.Fire", &GetAbsOrigin() ); +} + + +void CWalkerStrider::Crouch() +{ + // Disable the base class's walking functionality while we're crouched. + EnableWalkMode( false ); + + m_bCrouched = true; + m_flNextCrouchTime = gpGlobals->curtime + 0.3; + ResetSequence( LookupSequence( "low" ) ); + + // HACK: there should be a better way to this.. like CBaseAnimating::ResetAnimation, + // or ResetSequence should do it. + m_flAnimTime = m_flPrevAnimTime = 0; + SetCycle( 0 ); + + // HACK CITY.. This forces it to invalidate the abs origins of the gun bases. + Vector vPos = GetLocalOrigin(); + SetLocalOrigin( vPos + Vector( 100, 0, 0 ) ); + SetLocalOrigin( vPos ); +} + + +void CWalkerStrider::UnCrouch() +{ + EnableWalkMode( true ); + m_flNextCrouchTime = gpGlobals->curtime + 0.3; + m_bCrouched = false; + + // HACK CITY.. This forces it to invalidate the abs origins of the gun bases. + Vector vPos = GetLocalOrigin(); + SetLocalOrigin( vPos + Vector( 100, 0, 0 ) ); + SetLocalOrigin( vPos ); +} + + +void CWalkerStrider::WalkerThink() +{ + float dt = GetTimeDelta(); + + BaseClass::WalkerThink(); + + if ( m_bCrouched ) + { + // Just sit there until they hit IN_DUCK again. + if ( (m_LastButtons & IN_DUCK) && gpGlobals->curtime > m_flNextCrouchTime ) + { + UnCrouch(); + } + } + else + { + if ( gpGlobals->curtime > m_flNextCrouchTime ) + { + if ( m_LastButtons & IN_DUCK ) + { + // They want to crouch. + Crouch(); + } + else if ( !m_bFiring && (m_LastButtons & IN_ATTACK) ) + { + Fire(); + } + } + } + + m_vDriverAngles = m_vLastCmdViewAngles; + if ( m_bCrouched ) + { + // The "low" animation gets back up at the end so don't let that happen. + SetCycle( MIN( GetCycle(), 0.5f ) ); + } + else if ( m_bFiring ) + { + if ( gpGlobals->curtime > m_flFireEndTime ) + { + m_bFiring = false; + EnableWalkMode( true ); + } + else + { + // Shoot? + if ( gpGlobals->curtime >= m_flNextShootTime ) + { + Vector vSrc = GetAbsOrigin(); + Vector vForward; + + QAngle vFireAngles = m_vFireAngles; + vFireAngles[YAW] += RandomFloat( -STRIDER_FIRE_ANGLE_ERROR, STRIDER_FIRE_ANGLE_ERROR ); + vFireAngles[PITCH] += RandomFloat( -STRIDER_FIRE_ANGLE_ERROR, STRIDER_FIRE_ANGLE_ERROR ); + + AngleVectors( vFireAngles, &vForward ); + trace_t trace; + UTIL_TraceLine( vSrc, vSrc + vForward * 2000, MASK_SOLID, this, COLLISION_GROUP_NONE, &trace ); + + Vector vHitPos = trace.endpos; + float flDamageRadius = 100; + float flDamage = 50; + + CBasePlayer *pDriver = GetPassenger( VEHICLE_ROLE_DRIVER ); + if ( pDriver ) + { + UTIL_ImpactTrace( &trace, DMG_ENERGYBEAM, "Strider" ); + + // Tell the client entity to make a beam effect. + EntityMessageBegin( this, true ); + WRITE_VEC3COORD( vHitPos ); + MessageEnd(); + + UTIL_ScreenShake( vHitPos, 10.0, 150.0, 1.0, 100, SHAKE_START ); + RadiusDamage( CTakeDamageInfo( this, pDriver, flDamage, DMG_BLAST ), vHitPos, flDamageRadius, CLASS_NONE, NULL ); + } + + m_flNextShootTime = gpGlobals->curtime + STRIDER_FIRE_INTERVAL; + } + } + } + else + { + // Move our torso within range of our feet. + if ( m_flOriginToLowestLegHeight != -1 ) + { + trace_t trace; + UTIL_TraceLine( + GetAbsOrigin(), + GetAbsOrigin() - Vector( 0, 0, 2000 ), + MASK_SOLID_BRUSHONLY, + this, + COLLISION_GROUP_NONE, + &trace ); + + if ( trace.fraction < 1 ) + { + m_flWantedZ = trace.endpos.z + m_flOriginToLowestLegHeight; + } + + // Move our Z towards the wanted Z. + if ( m_flWantedZ != -1 ) + { + Vector vCur = GetAbsOrigin(); + vCur.z = Approach( m_flWantedZ, vCur.z, STRIDER_TORSO_VERTICAL_SLIDE_SPEED * dt ); + SetAbsOrigin( vCur ); + } + } + } +} + + +void CWalkerStrider::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + // Sapper removal + if ( RemoveEnemyAttachments( pActivator ) ) + return; + + CBaseTFPlayer *pPlayer = dynamic_cast<CBaseTFPlayer*>(pActivator); + if ( !pPlayer || !InSameTeam( pPlayer ) ) + return; + + // Ok, put them in the driver role. + AttemptToBoardVehicle( pPlayer ); +} + + +void CWalkerStrider::SetupMove( CBasePlayer *pPlayer, CUserCmd *ucmd, IMoveHelper *pHelper, CMoveData *move ) +{ + BaseClass::SetupMove( pPlayer, ucmd, pHelper, move ); +} + + +// +// +// This is a TOTAL hack, but we don't have any nodes that work well at all for mounted guns. +// This all goes away when we get a new model. +// +// +float sideDist = 90; +float downDist = -400; +bool CWalkerStrider::GetAttachment( int iAttachment, matrix3x4_t &attachmentToWorld ) +{ + CStudioHdr *pStudioHdr = GetModelPtr( ); + if ( !pStudioHdr || iAttachment < 1 || iAttachment > pStudioHdr->GetNumAttachments() ) + { + return false; + } + + Vector vLocalPos( 0, 0, 0 ); + const mstudioattachment_t &pAttachment = pStudioHdr->pAttachment( iAttachment-1 ); + if ( stricmp( pAttachment.pszName(), "build_point_left_gun" ) == 0 ) + { + vLocalPos.y = sideDist; + } + else if ( stricmp( pAttachment.pszName(), "build_point_right_gun" ) == 0 ) + { + vLocalPos.y = -sideDist; + } + else if ( stricmp( pAttachment.pszName(), "ThirdPersonCameraOrigin" ) == 0 ) + { + } + else + { + // Ok, it's not one of our magical attachments. Use the regular attachment setup stuff. + return BaseClass::GetAttachment( iAttachment, attachmentToWorld ); + } + + if ( m_bCrouched ) + { + vLocalPos.z += downDist; + } + + // Now build the output matrix. + matrix3x4_t localMatrix; + SetIdentityMatrix( localMatrix ); + PositionMatrix( vLocalPos, localMatrix ); + + ConcatTransforms( EntityToWorldTransform(), localMatrix, attachmentToWorld ); + return true; +} + + +bool CWalkerStrider::StartBuilding( CBaseEntity *pBuilder ) +{ + if ( !BaseClass::StartBuilding( pBuilder ) ) + return false; + + // Now figure out our ideal Z distance from our lowest foot to our torso. + Vector vOrigin; + QAngle vAngles; + BaseClass::GetAttachment( "left foot", vOrigin, vAngles ); + m_flOriginToLowestLegHeight = GetAbsOrigin().z - vOrigin.z; + m_flOriginToLowestLegHeight -= 40; // fudge it a little so the legs have room to stretch. + return true; +} + + +void CWalkerStrider::FootHit( const char *pFootName ) +{ + if ( m_bCrouched || gpGlobals->curtime > m_flDontMakeSoundsUntil ) + { + Vector footPosition; + QAngle angles; + + ((BaseClass*)this)->GetAttachment( pFootName, footPosition, angles ); + CPASAttenuationFilter filter( this, "Brush.Footstep" ); + EmitSound( filter, entindex(), "Brush.Footstep", &footPosition ); + + UTIL_ScreenShake( footPosition, 20.0, 1.0, 0.5, 200, SHAKE_START, false ); + + CBasePlayer *pPlayer = GetPassenger( VEHICLE_ROLE_DRIVER ); + if ( pPlayer ) + { + CTakeDamageInfo info( this, pPlayer, 20, DMG_CLUB ); + TFGameRules()->RadiusDamage( info, footPosition, 200, CLASS_NONE ); + } + + m_flDontMakeSoundsUntil = gpGlobals->curtime + 0.4; + } +} + + +void CWalkerStrider::HandleAnimEvent( animevent_t *pEvent ) +{ + switch( pEvent->event ) + { + case STRIDER_AE_FOOTSTEP_LEFT: + case STRIDER_AE_FOOTSTEP_LEFTM: + case STRIDER_AE_FOOTSTEP_LEFTL: + FootHit( "left foot" ); + break; + + case STRIDER_AE_FOOTSTEP_RIGHT: + case STRIDER_AE_FOOTSTEP_RIGHTM: + case STRIDER_AE_FOOTSTEP_RIGHTL: + FootHit( "right foot" ); + break; + + case STRIDER_AE_FOOTSTEP_BACK: + case STRIDER_AE_FOOTSTEP_BACKM: + case STRIDER_AE_FOOTSTEP_BACKL: + FootHit( "back foot" ); + break; + } +} + diff --git a/game/server/tf2/tf_walker_strider.h b/game/server/tf2/tf_walker_strider.h new file mode 100644 index 0000000..050e7bd --- /dev/null +++ b/game/server/tf2/tf_walker_strider.h @@ -0,0 +1,85 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#ifndef TF_WALKER_STRIDER_H +#define TF_WALKER_STRIDER_H +#ifdef _WIN32 +#pragma once +#endif + + +#include "tf_walker_base.h" + + +class CWalkerStrider : public CWalkerBase +{ +public: + DECLARE_CLASS( CWalkerStrider, CWalkerBase ); + DECLARE_SERVERCLASS(); + + CWalkerStrider(); + + +// CWalkerBase. +protected: + virtual void WalkerThink(); + + +// CBaseObject. +public: + virtual bool StartBuilding( CBaseEntity *pBuilder ); + + +// CBaseEntity. +public: + virtual void Precache(); + virtual void Spawn(); + virtual void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + +// CBaseAnimating. +public: + virtual bool GetAttachment( int iAttachment, matrix3x4_t &attachmentToWorld ); + virtual void HandleAnimEvent( animevent_t *pEvent ); + + +// IServerVehicle. +public: + virtual bool IsPassengerVisible( int nRole ); + + +// IVehicle overrides. +public: + virtual void SetupMove( CBasePlayer *pPlayer, CUserCmd *ucmd, IMoveHelper *pHelper, CMoveData *move ); + + +private: + void Fire(); + + void Crouch(); + void UnCrouch(); + + void FootHit( const char *pFootName ); + + + +private: + CNetworkVar( bool, m_bCrouched ); + float m_flNextCrouchTime; + + bool m_bFiring; + float m_flFireEndTime; + float m_flNextShootTime; + QAngle m_vFireAngles; + + float m_flOriginToLowestLegHeight; + float m_flWantedZ; + + QAngle m_vDriverAngles; +}; + + +#endif // TF_WALKER_STRIDER_H diff --git a/game/server/tf2/trigger_fall.cpp b/game/server/tf2/trigger_fall.cpp new file mode 100644 index 0000000..3a4a1c8 --- /dev/null +++ b/game/server/tf2/trigger_fall.cpp @@ -0,0 +1,72 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Used at the bottom of maps where objects should fall away to infinity +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "triggers.h" + +//----------------------------------------------------------------------------- +// Purpose: Used at the bottom of maps where objects should fall away to infinity +//----------------------------------------------------------------------------- +class CTriggerFall : public CBaseTrigger +{ + DECLARE_CLASS( CTriggerFall, CBaseTrigger ); +public: + void Spawn( void ); + void FallTouch( CBaseEntity *pOther ); + + DECLARE_DATADESC(); + + // Outputs + COutputEvent m_OnFallingObject; +}; + +BEGIN_DATADESC( CTriggerFall ) + + // Function Pointers + DEFINE_FUNCTION( FallTouch ), + + // Outputs + DEFINE_OUTPUT( m_OnFallingObject, "OnFallingObject" ), + +END_DATADESC() + + +LINK_ENTITY_TO_CLASS( trigger_fall, CTriggerFall ); + + +//----------------------------------------------------------------------------- +// Purpose: Called when spawning, after keyvalues have been handled. +//----------------------------------------------------------------------------- +void CTriggerFall::Spawn( void ) +{ + BaseClass::Spawn(); + InitTrigger(); + SetTouch( FallTouch ); +} + +//----------------------------------------------------------------------------- +// Purpose: Make the object fall away +// Input : pOther - The entity that is touching us. +//----------------------------------------------------------------------------- +void CTriggerFall::FallTouch( CBaseEntity *pOther ) +{ + // If it's a player, just kill him for now + if ( pOther->IsPlayer() ) + { + if ( pOther->IsAlive() == false ) + return; + + pOther->TakeDamage( CTakeDamageInfo( this, this, 200, DMG_FALL ) ); + } + else + { + // Just remove the entity + UTIL_Remove( pOther ); + } + + // Fire our output + m_OnFallingObject.FireOutput( pOther, this ); +} diff --git a/game/server/tf2/trigger_skybox.cpp b/game/server/tf2/trigger_skybox.cpp new file mode 100644 index 0000000..e3dd690 --- /dev/null +++ b/game/server/tf2/trigger_skybox.cpp @@ -0,0 +1,65 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Resource collection entity +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" +#include "triggers.h" +#include "env_meteor.h " + +//----------------------------------------------------------------------------- +// 3DSkybox to World Transition Trigger Class +//----------------------------------------------------------------------------- +class CTrigger3DSkyboxToWorld : public CBaseTrigger +{ + DECLARE_CLASS( CTrigger3DSkyboxToWorld, CBaseTrigger ); + +public: + + CTrigger3DSkyboxToWorld(); + + DECLARE_DATADESC(); + + void Spawn( void ); + void ImpactTouch( CBaseEntity *pOther ); +}; + +BEGIN_DATADESC( CTrigger3DSkyboxToWorld ) + + // Function Pointers + DEFINE_FUNCTION( ImpactTouch ), + +END_DATADESC() + +LINK_ENTITY_TO_CLASS( trigger_skybox2world, CTrigger3DSkyboxToWorld ); + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CTrigger3DSkyboxToWorld::CTrigger3DSkyboxToWorld() +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTrigger3DSkyboxToWorld::Spawn( void ) +{ + BaseClass::Spawn(); + InitTrigger(); +// SetTouch( ImpactTouch ); +} + +//----------------------------------------------------------------------------- +// Purpose: Not handling transitions with a touch function currently!! +//----------------------------------------------------------------------------- +void CTrigger3DSkyboxToWorld::ImpactTouch( CBaseEntity *pOther ) +{ +#if 0 + if ( FClassnameIs( pOther, "meteor" ) ) + { + } +#endif +} |