diff options
Diffstat (limited to 'game/server/tfc')
| -rw-r--r-- | game/server/tfc/tfc_bot_temp.cpp | 470 | ||||
| -rw-r--r-- | game/server/tfc/tfc_bot_temp.h | 21 | ||||
| -rw-r--r-- | game/server/tfc/tfc_building.h | 73 | ||||
| -rw-r--r-- | game/server/tfc/tfc_client.cpp | 155 | ||||
| -rw-r--r-- | game/server/tfc/tfc_client.h | 17 | ||||
| -rw-r--r-- | game/server/tfc/tfc_engineer.cpp | 73 | ||||
| -rw-r--r-- | game/server/tfc/tfc_engineer.h | 21 | ||||
| -rw-r--r-- | game/server/tfc/tfc_eventlog.cpp | 56 | ||||
| -rw-r--r-- | game/server/tfc/tfc_gameinterface.cpp | 33 | ||||
| -rw-r--r-- | game/server/tfc/tfc_mapitems.cpp | 3131 | ||||
| -rw-r--r-- | game/server/tfc/tfc_mapitems.h | 405 | ||||
| -rw-r--r-- | game/server/tfc/tfc_player.cpp | 1115 | ||||
| -rw-r--r-- | game/server/tfc/tfc_player.h | 254 | ||||
| -rw-r--r-- | game/server/tfc/tfc_playermove.cpp | 76 | ||||
| -rw-r--r-- | game/server/tfc/tfc_team.cpp | 279 | ||||
| -rw-r--r-- | game/server/tfc/tfc_team.h | 58 | ||||
| -rw-r--r-- | game/server/tfc/tfc_timer.cpp | 117 | ||||
| -rw-r--r-- | game/server/tfc/tfc_timer.h | 60 |
18 files changed, 6414 insertions, 0 deletions
diff --git a/game/server/tfc/tfc_bot_temp.cpp b/game/server/tfc/tfc_bot_temp.cpp new file mode 100644 index 0000000..6a36d2c --- /dev/null +++ b/game/server/tfc/tfc_bot_temp.cpp @@ -0,0 +1,470 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Basic BOT handling. +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" +#include "player.h" +#include "tfc_player.h" +#include "in_buttons.h" +#include "movehelper_server.h" + +void ClientPutInServer( edict_t *pEdict, const char *playername ); +void Bot_Think( CTFCPlayer *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 ConVar bot_mimic_yaw_offset( "bot_mimic_yaw_offset", "180", 0, "Offsets the bot yaw." ); + +static int BotNumber = 1; +static int g_iNextBotTeam = -1; +static int g_iNextBotClass = -1; + +typedef struct +{ + bool backwards; + + float nextturntime; + bool lastturntoright; + + float nextstrafetime; + float sidemove; + + QAngle forwardAngle; + QAngle lastAngles; + + float m_flJoinTeamTime; + int m_WantedTeam; + int m_WantedClass; + + bool m_bWasDead; + float m_flDeadTime; +} botdata_t; + +static botdata_t g_BotData[ MAX_PLAYERS ]; + + +//----------------------------------------------------------------------------- +// 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 = engine->CreateFakeClient( botname ); + if (!pEdict) + { + Msg( "Failed to create Bot.\n"); + return NULL; + } + + // Allocate a CBasePlayer for the bot, and call spawn + //ClientPutInServer( pEdict, botname ); + CTFCPlayer *pPlayer = ((CTFCPlayer *)CBaseEntity::Instance( pEdict )); + pPlayer->ClearFlags(); + pPlayer->AddFlag( FL_CLIENT | FL_FAKECLIENT ); + + if ( bFrozen ) + pPlayer->AddEFlags( EFL_BOT_FROZEN ); + + BotNumber++; + + botdata_t *pBot = &g_BotData[ pPlayer->entindex() - 1 ]; + pBot->m_bWasDead = false; + pBot->m_WantedTeam = iTeam; + pBot->m_WantedClass = iClass; + pBot->m_flJoinTeamTime = gpGlobals->curtime + 0.3; + + return pPlayer; +} + + +// Handler for the "bot" command. +CON_COMMAND_F( "bot", "Add a bot.", FCVAR_CHEAT ) +{ + //CDODPlayer *pPlayer = CDODPlayer::Instance( UTIL_GetCommandClientIndex() ); + + // The bot command uses switches like command-line switches. + // -count <count> tells how many bots to spawn. + // -team <index> selects the bot's team. Default is -1 which chooses randomly. + // Note: if you do -team !, then it + // -class <index> selects the bot's class. Default is -1 which chooses randomly. + // -frozen prevents the bots from running around when they spawn in. + + // Look at -count. + int count = args.FindArgInt( "-count", 1 ); + count = clamp( count, 1, 16 ); + + int iTeam = 0; + const char *pVal = args.FindArg( "-team" ); + if ( pVal ) + { + if ( stricmp( pVal, "red" ) == 0 ) + iTeam = TEAM_RED; + else + iTeam = TEAM_BLUE; + } + + // Look at -frozen. + bool bFrozen = !!args.FindArg( "-frozen" ); + + // Ok, spawn all the bots. + while ( --count >= 0 ) + { + // What class do they want? + int iClass = RandomInt( 0, PC_LASTCLASS-1 ); + pVal = args.FindArg( "-class" ); + if ( pVal ) + { + for ( int i=0; i < PC_LASTCLASS; i++ ) + { + if ( stricmp( GetTFCClassInfo( i )->m_pClassName, pVal ) == 0 ) + { + iClass = i; + break; + } + } + } + + BotPutInServer( bFrozen, iTeam, iClass ); + } +} + + +//----------------------------------------------------------------------------- +// 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++ ) + { + CTFCPlayer *pPlayer = ToTFCPlayer( UTIL_PlayerByIndex( i ) ); + + if ( pPlayer && (pPlayer->GetFlags() & FL_FAKECLIENT) ) + { + Bot_Think( pPlayer ); + } + } +} + +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(); + cmd.viewangles[YAW] += bot_mimic_yaw_offset.GetFloat(); + 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( CTFCPlayer *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( CTFCPlayer *pBot ) +{ + // 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->GetTeamNumber() == TEAM_UNASSIGNED && gpGlobals->curtime > botdata->m_flJoinTeamTime ) + { + pBot->HandleCommand_JoinTeam( botdata->m_WantedTeam == TEAM_RED ? "red" : "blue" ); + } + else if ( pBot->GetTeamNumber() != TEAM_UNASSIGNED && pBot->m_Shared.GetPlayerClass() == PC_UNDEFINED ) + { + // If they're on a team but haven't picked a class, choose a random class.. + pBot->HandleCommand_JoinClass( GetTFCClassInfo( botdata->m_WantedClass )->m_pClassName ); + } + else if ( pBot->IsAlive() && (pBot->GetSolid() == SOLID_BBOX) ) + { + trace_t trace; + + botdata->m_bWasDead = false; + + // 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(); + + // 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() ) + { + if ( botdata->m_bWasDead ) + { + // Wait for a few seconds before respawning. + if ( gpGlobals->curtime - botdata->m_flDeadTime > 3 ) + { + // Respawn the bot + buttons |= IN_JUMP; + } + } + else + { + // Start a timer to respawn them in a few seconds. + botdata->m_bWasDead = true; + botdata->m_flDeadTime = gpGlobals->curtime; + } + } + } + + 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/tfc/tfc_bot_temp.h b/game/server/tfc/tfc_bot_temp.h new file mode 100644 index 0000000..906ae00 --- /dev/null +++ b/game/server/tfc/tfc_bot_temp.h @@ -0,0 +1,21 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef TFC_BOT_TEMP_H +#define TFC_BOT_TEMP_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 ); + +void Bot_RunAll(); + + +#endif // TFC_BOT_TEMP_H diff --git a/game/server/tfc/tfc_building.h b/game/server/tfc/tfc_building.h new file mode 100644 index 0000000..401c5bf --- /dev/null +++ b/game/server/tfc/tfc_building.h @@ -0,0 +1,73 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#ifndef TFC_BUILDING_H +#define TFC_BUILDING_H +#ifdef _WIN32 +#pragma once +#endif + + +#include "baseanimating.h" +#include "tfc_shareddefs.h" + + +class CTFBaseBuilding : public CBaseAnimating +{ +public: + EHANDLE real_owner; +}; + + +class CTFTeleporter : public CTFBaseBuilding +{ +public: + void Spawn(void); + void Precache(void); + + void EXPORT Teleporter_Explode( void ); + void EXPORT TeleporterThink( void ); + void EXPORT TeleporterTouch( CBaseEntity *pOther ); + + Class_T Classify(void) { return CLASS_MACHINE; }; + int BloodColor( void ) { return DONT_BLEED; } + + void Remove( void ); + + void TeamFortress_TakeEMPBlast(CBaseEntity* pevGren); + void Finished( void ); + static CTFTeleporter *CreateTeleporter( Vector vecOrigin, Vector vecAngles, CBaseEntity *pOwner, int type ); + BOOL EngineerUse( CBasePlayer *pPlayer ); + void Killed( CBaseEntity *pevInflictor, CBaseEntity *pevAttacker, int iGib ); + void TeleporterSend( CBasePlayer *pPlayer ); + void TeleporterReceive( CBasePlayer *pPlayer, float flDelay ); + void TeleporterKilled( void ); + BOOL TeleportersReady( void ); + void TeleporterFadePlayer( int direction ); + void TeleporterProcessFade( void ); + void SetTeleporterRings( int state ); + void SetTeleporterParticles( int state ); + float GetDamageMultiplier( void ); + CTFTeleporter* FindMatch( void ); + const Vector& GetTeamColor( void ); + bool PlayerIsStandingOnTeleporter( CBaseEntity *pOther ); + + CBasePlayer *m_pPlayer; // player being teleported + + float m_flInitialUseDelay; + + int m_iType; // entry or exit + int m_iState; // state of the teleporter (idle, ready, sending, etc.) + int m_iDestroyed; // has this teleporter been destroyed + + int m_iShardIndex; // Metal shards + + float m_flMyNextThink; // used to control the pace at which the teleporters work + float m_flDamageDelay; // damage multiplier that slows the teleporters when they're damaged +}; + + +#endif // TFC_BUILDING_H diff --git a/game/server/tfc/tfc_client.cpp b/game/server/tfc/tfc_client.cpp new file mode 100644 index 0000000..af8f2a7 --- /dev/null +++ b/game/server/tfc/tfc_client.cpp @@ -0,0 +1,155 @@ +//========= 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 "entitylist.h" +#include "physics.h" +#include "game.h" +#include "ai_network.h" +#include "ai_node.h" +#include "ai_hull.h" +#include "shake.h" +#include "player_resource.h" +#include "engine/IEngineSound.h" +#include "tfc_player.h" +#include "tfc_gamerules.h" +#include "tier0/vprof.h" +#include "tfc_bot_temp.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + + +extern CBaseEntity *FindPickerEntity( CBasePlayer *pPlayer ); + +extern bool g_fGameOver; + + +void FinishClientPutInServer( CTFCPlayer *pPlayer ) +{ + pPlayer->InitialSpawn(); + pPlayer->Spawn(); + + 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>" ); +} + +/* +=========== +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 + CTFCPlayer *pPlayer = CTFCPlayer::CreatePlayer( "player", pEdict ); + pPlayer->SetPlayerName( playername ); +} + + +void ClientActive( edict_t *pEdict, bool bLoadGame ) +{ + // Can't load games in CS! + Assert( !bLoadGame ); + + CTFCPlayer *pPlayer = ToTFCPlayer( CBaseEntity::Instance( pEdict ) ); + FinishClientPutInServer( pPlayer ); +} + + +/* +=============== +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 "CounterStrike"; +} + + +//----------------------------------------------------------------------------- +// 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" ); +} + + +// 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 + dynamic_cast< CBasePlayer* >( 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() +{ + CreateGameRulesObject( "CTFCGameRules" ); +} diff --git a/game/server/tfc/tfc_client.h b/game/server/tfc/tfc_client.h new file mode 100644 index 0000000..a3a5356 --- /dev/null +++ b/game/server/tfc/tfc_client.h @@ -0,0 +1,17 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#ifndef TFC_CLIENT_H +#define TFC_CLIENT_H +#ifdef _WIN32 +#pragma once +#endif + + +void respawn( CBaseEntity *pEdict, bool fCopyCorpse ); + + +#endif // TFC_CLIENT_H diff --git a/game/server/tfc/tfc_engineer.cpp b/game/server/tfc/tfc_engineer.cpp new file mode 100644 index 0000000..ddbf32d --- /dev/null +++ b/game/server/tfc/tfc_engineer.cpp @@ -0,0 +1,73 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "cbase.h" +#include "tfc_player.h" +#include "tfc_building.h" + + +//========================================================================= +// Destroys a single Engineer building +void DestroyBuilding(CTFCPlayer *eng, char *bld) +{ + CBaseEntity *pEnt = gEntList.FindEntityByClassname( NULL, bld ); + while ( pEnt ) + { + CTFBaseBuilding *pBuilding = dynamic_cast<CTFBaseBuilding*>( pEnt ); + + if (pBuilding && pBuilding->real_owner == eng) + { + // If it's fallen out of the world, give the engineer + // some metal back + int pos = UTIL_PointContents(pEnt->GetAbsOrigin()); +#ifdef TFCTODO // CONTENTS_SKY doesn't exist in the new engine + if (pos == CONTENT_SOLID || pos == CONTENT_SKY) +#else + if (pos == CONTENTS_SOLID) +#endif + { + eng->GiveAmmo( 100, TFC_AMMO_CELLS ); + eng->TeamFortress_CheckClassStats(); + } + + pEnt->TakeDamage( CTakeDamageInfo( pEnt, pEnt, 500, 0 ) ); + } + + pEnt = gEntList.FindEntityByClassname( pEnt, bld ); + } +} + + +//========================================================================= +// Destroys a teleporter (determined by type) +void DestroyTeleporter(CTFCPlayer *eng, int type) +{ + CBaseEntity *pEnt = gEntList.FindEntityByClassname( NULL, "building_teleporter" ); + while ( pEnt ) + { + CTFTeleporter *pTeleporter = dynamic_cast<CTFTeleporter*>( pEnt ); + + if (pTeleporter && pTeleporter->real_owner == eng && pTeleporter->m_iType == type ) + { + // If it's fallen out of the world, give the engineer + // some metal back + int pos = UTIL_PointContents(pEnt->GetAbsOrigin()); +#ifdef TFCTODO // CONTENTS_SKY doesn't exist in the new engine + if (pos == CONTENT_SOLID || pos == CONTENT_SKY) +#else + if (pos == CONTENTS_SOLID) +#endif + { + eng->GiveAmmo( 100, TFC_AMMO_CELLS ); + eng->TeamFortress_CheckClassStats(); + } + + pEnt->TakeDamage( CTakeDamageInfo( pEnt, pEnt, 500, 0 ) ); + } + + pEnt = gEntList.FindEntityByClassname( pEnt, "building_teleporter" ); + } +} diff --git a/game/server/tfc/tfc_engineer.h b/game/server/tfc/tfc_engineer.h new file mode 100644 index 0000000..0a57dca --- /dev/null +++ b/game/server/tfc/tfc_engineer.h @@ -0,0 +1,21 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#ifndef TFC_ENGINEER_H +#define TFC_ENGINEER_H +#ifdef _WIN32 +#pragma once +#endif + + +class CTFCPlayer; + + +void DestroyBuilding(CTFCPlayer *eng, char *bld); +void DestroyTeleporter(CTFCPlayer *eng, int type); + + +#endif // TFC_ENGINEER_H diff --git a/game/server/tfc/tfc_eventlog.cpp b/game/server/tfc/tfc_eventlog.cpp new file mode 100644 index 0000000..a75fa45 --- /dev/null +++ b/game/server/tfc/tfc_eventlog.cpp @@ -0,0 +1,56 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +#include "cbase.h" +#include "../EventLog.h" +#include "KeyValues.h" + +class CTFCEventLog : public CEventLog +{ +private: + typedef CEventLog BaseClass; + +public: + virtual ~CTFCEventLog() {}; + +public: + bool PrintEvent( KeyValues * event ) // override virtual function + { + if ( BaseClass::PrintEvent( event ) ) + { + return true; + } + + if ( Q_strcmp(event->GetName(), "cstrike_") == 0 ) + { + return PrintCStrikeEvent( event ); + } + + return false; + } + +protected: + + bool PrintCStrikeEvent( KeyValues * event ) // print Mod specific logs + { + // const char * name = event->GetName() + Q_strlen("cstrike_"); // remove prefix + + return false; + } + +}; + +CTFCEventLog g_TFCEventLog; + +//----------------------------------------------------------------------------- +// Singleton access +//----------------------------------------------------------------------------- +IGameSystem* GameLogSystem() +{ + return &g_TFCEventLog; +} + diff --git a/game/server/tfc/tfc_gameinterface.cpp b/game/server/tfc/tfc_gameinterface.cpp new file mode 100644 index 0000000..67b8766 --- /dev/null +++ b/game/server/tfc/tfc_gameinterface.cpp @@ -0,0 +1,33 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "cbase.h" +#include "gameinterface.h" +#include "mapentities.h" + + +// -------------------------------------------------------------------------------------------- // +// Mod-specific CServerGameClients implementation. +// -------------------------------------------------------------------------------------------- // + +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/tfc/tfc_mapitems.cpp b/game/server/tfc/tfc_mapitems.cpp new file mode 100644 index 0000000..0ca6b87 --- /dev/null +++ b/game/server/tfc/tfc_mapitems.cpp @@ -0,0 +1,3131 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "cbase.h" +#include "tfc_mapitems.h" +#include "tfc_shareddefs.h" +#include "tfc_player.h" +#include "tfc_gamerules.h" +#include "tfc_timer.h" +#include "tfc_team.h" + + +bool ActivateDoResults(CTFGoal *Goal, CTFCPlayer *AP, CTFGoal *ActivatingGoal); +bool ActivationSucceeded(CTFGoal *Goal, CTFCPlayer *AP, CTFGoal *ActivatingGoal); +void DoResults(CTFGoal *Goal, CTFCPlayer *AP, BOOL bAddBonuses); + + +// ---------------------------------------------------------------------------------------- // +// Global helpers. +// ---------------------------------------------------------------------------------------- // + +const char* GetTeamName( int iTeam ) +{ + if ( iTeam == 0 ) + { + return "SPECTATOR"; + } + else + { + CTeam *pTeam = GetGlobalTeam( iTeam ); + if ( pTeam ) + return pTeam->GetName(); + else + return "UNKNOWN TEAM"; + } +} + + +//=========================================== +int GetTeamCheckTeam( const char *pTargetName ) +{ + CBaseEntity *pEntity = gEntList.FindEntityByName( NULL, pTargetName ); + if ( pEntity ) + { + if ( !strcmp( pEntity->GetClassname(), "info_tf_teamcheck" ) ) + return pEntity->GetTeamNumber(); + } + + return 0; +} + + +//========================================================================= +// Displays the state of a GoalItem +void DisplayItemStatus(CTFGoal *Goal, CTFCPlayer *Player, CTFGoalItem *Item) +{ + MDEBUG( Msg( "Displaying Item Status\nItem goal_no : %d\n", Item->goal_no) ); + + // If we have a teamcheck entity, use it instead + if ( Item->owned_by_teamcheck != NULL_STRING ) + Item->owned_by = GetTeamCheckTeam( STRING(Item->owned_by_teamcheck) ); + + if (Item->goal_state == TFGS_ACTIVE) + { + MDEBUG( Msg( " Item is ACTIVE\n") ); + + if ( (Goal->team_str_carried != NULL_STRING) || (Goal->non_team_str_carried != NULL_STRING) ) + { + CBaseEntity *pOwner = Item->GetOwnerEntity(); + + if (Player->GetTeamNumber() == Item->owned_by) + { + if (Player == Item->GetOwnerEntity()) + ClientPrint( Player, HUD_PRINTTALK, STRING(Goal->team_str_carried), "you" ); + else + ClientPrint( Player, HUD_PRINTTALK, STRING(Goal->team_str_carried), STRING(pOwner->GetEntityName()) ); + } + else + { + if (Player == Item->GetOwnerEntity()) + ClientPrint( Player, HUD_PRINTTALK, STRING(Goal->non_team_str_carried), "you" ); + else + ClientPrint( Player, HUD_PRINTTALK, STRING(Goal->non_team_str_carried), STRING(pOwner->GetEntityName()) ); + } + } + } + else if (Item->GetAbsOrigin() != Item->oldorigin) + { + MDEBUG( Msg( " Item has MOVED\n") ); + + if ( (Goal->team_str_moved != NULL_STRING) || (Goal->non_team_str_moved != NULL_STRING) ) + { + if (Player->GetTeamNumber() == Item->owned_by) + ClientPrint( Player, HUD_PRINTTALK, STRING(Goal->team_str_moved) ); + else + ClientPrint( Player, HUD_PRINTTALK, STRING(Goal->non_team_str_moved) ); + } + } + else + { + MDEBUG( Msg( " Item is AT HOME\n") ); + + if ( Goal->team_str_home != NULL_STRING || Goal->non_team_str_home != NULL_STRING ) + { + if (Player->GetTeamNumber() == Item->owned_by) + ClientPrint( Player, HUD_PRINTTALK, STRING(Goal->team_str_home) ); + else + ClientPrint( Player, HUD_PRINTTALK, STRING(Goal->non_team_str_home) ); + } + } +} + + +//========================================================================= +// Inactivates a Teamspawn point +void InactivateSpawn(CTFSpawn *Spawn) +{ + Spawn->goal_state = TFGS_REMOVED; +} + +//========================================================================= +// Activates a Teamspawn point +void ActivateSpawn(CTFSpawn *Spawn) +{ + Spawn->goal_state = TFGS_INACTIVE; +} + + +//========================================================================= +// Increase the score of a team +void TeamFortress_TeamIncreaseScore(int tno, int scoretoadd) +{ + if ( tno == 0 ) + return; + + CTeam *pTeam = GetGlobalTeam( tno ); + if ( !pTeam ) + return; + + pTeam->AddScore( scoretoadd ); +} + +// Returns true if the AP's carrying at least 1 of the items in the group +bool HasItemFromGroup( CBaseEntity *AP, int iGroupNo ) +{ + // Find all items in the group + CBaseEntity *pEnt = gEntList.FindEntityByClassname( NULL, "item_tfgoal" ); + while ( pEnt ) + { + CTFGoalItem *pGoal = dynamic_cast<CTFGoalItem*>( pEnt ); + if ( (pGoal->group_no == iGroupNo) && (pGoal->GetOwnerEntity() == AP) ) + return true; + + pEnt = gEntList.FindEntityByClassname( pEnt, "item_tfgoal" ); + } + + return false; +} + +//========================================================================= +// Returns true if all the goals in the specified group are in the specified state +bool AllGoalsInState( int iGroupNo, int iState ) +{ + // Find all goals in the group + CBaseEntity *pEnt = gEntList.FindEntityByClassname( NULL, "info_tfgoal" ); + while ( pEnt ) + { + CTFGoal *pGoal = dynamic_cast< CTFGoal* >( pEnt ); + if ( pGoal ) + { + if (pGoal->group_no == iGroupNo) + { + // All Goals in the group must be in the specified state + if (pGoal->goal_state != iState) + return false; + } + } + + pEnt = gEntList.FindEntityByClassname( pEnt, "info_tfgoal" ); + } + + return true; +} + + +// Return the item with a goal_no equal to ino +CTFGoalItem* Finditem(int ino) +{ + CBaseEntity *pEnt = gEntList.FindEntityByClassname( NULL, "item_tfgoal" ); + while ( pEnt ) + { + CTFGoalItem *pGoal = dynamic_cast<CTFGoalItem*>( pEnt ); + if (pGoal && pGoal->goal_no == ino) + return pGoal; + + pEnt = gEntList.FindEntityByClassname( pEnt, "item_tfgoal" ); + } + + // Goal does not exist + Warning("Could not find an item with a goal_no of %d.\n", ino); + return NULL; +} + + +//========================================================================= +// Return the TeamSpawn with a goal_no equal to gno +CTFSpawn* Findteamspawn(int gno) +{ + // Search by netname + //TFCTODO: I think FindEntityByClassname will do the same thing. + //CBaseEntity *pEnt = UTIL_FindEntityByString( NULL, "netname", "info_player_teamspawn" ); + CBaseEntity *pEnt = gEntList.FindEntityByClassname( NULL, "info_player_teamspawn" ); + while ( pEnt ) + { + CTFSpawn *pSpawn = dynamic_cast<CTFSpawn*>( pEnt ); + if ( pSpawn ) + { + if (pSpawn->goal_no == gno) + return pSpawn; + } + + pEnt = gEntList.FindEntityByClassname( pEnt, "info_player_teamspawn" ); + } + + // Goal does not exist + Warning("Could not find a Teamspawn with a goal_no of %d.\n", gno); + return NULL; +} + + +//========================================================================= +// Return the goal with a goal_no equal to gno +CTFGoal* Findgoal(int gno) +{ + CBaseEntity *pEnt = gEntList.FindEntityByClassname( NULL, "item_tfgoal" ); + while ( pEnt ) + { + CTFGoal *pGoal = dynamic_cast<CTFGoal*>( pEnt ); + if (pGoal && pGoal->goal_no == gno) + return pGoal; + + pEnt = gEntList.FindEntityByClassname( pEnt, "item_tfgoal" ); + } + + // Goal does not exist + Warning("Could not find a goal with a goal_no of %d.\n", gno); + return NULL; +} + + +//========================================================================= +// Remove a Timer/Goal +void RemoveGoal(CTFGoal *pGoal) +{ + pGoal->AddSolidFlags( FSOLID_NOT_SOLID ); + pGoal->goal_state = TFGS_REMOVED; + pGoal->AddEffects( EF_NODRAW ); +} + + +//========================================================================= +// Return true if the player meets the AP criteria +bool APMeetsCriteria(CTFGoal *Goal, CTFCPlayer *AP) +{ + MDEBUG(Warning("==========================\n")); + MDEBUG(Warning("AP Criteria Checking\n")); + MDEBUG(Warning(UTIL_VarArgs("Goal: %s", STRING(Goal->edict()->netname)))); + + CTFGoal *pGoal; + CTFGoalItem *pItem; + + if (AP != NULL && AP->Classify() == CLASS_PLAYER) + { + MDEBUG(Warning(UTIL_VarArgs("\nAP : %s\n", AP->GetPlayerName()))); + + // If a player of a specific team can only activate this + if (Goal->GetTeamNumber()) + { + MDEBUG(Warning(" Checking team.")); + if (Goal->GetTeamNumber() != AP->GetTeamNumber()) + return false; + if ( !AP->IsAlive() ) // don't want dead or dying players activating this + return false; + MDEBUG(Warning("passed.\n")); + } + + // If a player in a team specified by a teamcheck entity can activate this + if (Goal->teamcheck != NULL_STRING) + { + MDEBUG(Warning(" Checking teamcheck entity.")); + + if ( AP->GetTeamNumber() != GetTeamCheckTeam( STRING(Goal->teamcheck) ) ) + return false; + + MDEBUG(Warning(" passed.\n")); + } + + // If a player of a specific class can only activate this + if (Goal->playerclass) + { + MDEBUG(Warning(" Checking class.")); + if (Goal->playerclass != AP->m_Shared.GetPlayerClass()) + return false; + MDEBUG(Warning("passed.\n")); + } + + // If this activation needs a GoalItem, make sure the player has it + if (Goal->items_allowed) + { + MDEBUG(Warning(" Checking items.")); + pItem = Finditem(Goal->items_allowed); + if (!pItem) + return false; + if (pItem->GetOwnerEntity() != AP) + return false; + MDEBUG(Warning("passed.\n")); + } + } + + // Check Goal states + if (Goal->if_goal_is_active) + { + MDEBUG(Warning(" Checking if_goal_is_active.")); + pGoal = Findgoal(Goal->if_goal_is_active); + if (!pGoal) + return false; + if (pGoal->goal_state != TFGS_ACTIVE) + return false; + MDEBUG(Warning("passed.\n")); + } + + if (Goal->if_goal_is_inactive) + { + MDEBUG(Warning(" Checking if_goal_is_inactive.")); + pGoal = Findgoal(Goal->if_goal_is_inactive); + if (!pGoal) + return false; + if (pGoal->goal_state != TFGS_INACTIVE) + return false; + MDEBUG(Warning("passed.\n")); + } + + if (Goal->if_goal_is_removed) + { + MDEBUG(Warning(" Checking if_goal_is_removed.")); + pGoal = Findgoal(Goal->if_goal_is_removed); + if (!pGoal) + return false; + if (pGoal->goal_state != TFGS_REMOVED) + return false; + MDEBUG(Warning("passed.\n")); + } + + // Check Group States + if (Goal->if_group_is_active) + { + MDEBUG(Warning(" Checking if_group_is_active.")); + if ( !AllGoalsInState(Goal->if_group_is_active, TFGS_ACTIVE) ) + return false; + MDEBUG(Warning("passed.\n")); + } + + if (Goal->if_group_is_inactive) + { + MDEBUG(Warning(" Checking if_group_is_inactive.")); + if ( !AllGoalsInState(Goal->if_group_is_inactive, TFGS_INACTIVE) ) + return false; + MDEBUG(Warning("passed.\n")); + } + + if (Goal->if_group_is_removed) + { + MDEBUG(Warning(" Checking if_group_is_removed.")); + if ( !AllGoalsInState(Goal->if_group_is_removed, TFGS_REMOVED) ) + return false; + MDEBUG(Warning("passed.\n")); + } + + // Check Item States + if (Goal->if_item_has_moved) + { + MDEBUG(Warning(" Checking if_item_has_moved.")); + // Find the item + pItem = Finditem(Goal->if_item_has_moved); + if (!pItem) + return false; + if (pItem->goal_state != TFGS_ACTIVE && pItem->GetAbsOrigin() == pItem->oldorigin) + return false; + MDEBUG(Warning("passed.\n")); + } + + if (Goal->if_item_hasnt_moved) + { + MDEBUG(Warning(" Checking if_item_hasnt_moved.")); + // Find the item + pItem = Finditem(Goal->if_item_hasnt_moved); + if (!pItem) + return false; + if (pItem->goal_state == TFGS_ACTIVE || pItem->GetAbsOrigin() != pItem->oldorigin ) + return false; + MDEBUG(Warning("passed.\n")); + } + + // Check Items being carried + if (AP != NULL && AP->Classify() == CLASS_PLAYER) + { + if (Goal->has_item_from_group) + { + MDEBUG(Warning(" Checking has_item_from_group.")); + if ( !HasItemFromGroup(AP, Goal->has_item_from_group) ) + return false; + MDEBUG(Warning("passed.\n")); + } + + if (Goal->hasnt_item_from_group) + { + MDEBUG(Warning(" Checking hasnt_item_from_group.")); + if ( HasItemFromGroup(AP, Goal->hasnt_item_from_group) ) + return false; + MDEBUG(Warning("passed.\n")); + } + } + + MDEBUG(Warning("Criteria passed.\n")); + return true; +} + + +//========================================================================= +// Return true if the Entity should activate +bool ShouldActivate(CTFGoal *Goal, CTFCPlayer *AP) +{ +#ifdef MAP_DEBUG + Warning(UTIL_VarArgs("\nDoIActivate: ", Goal->edict()->netname ? STRING(Goal->edict()->netname) : STRING(Goal->edict()->classname))); + if (AP) + Warning(UTIL_VarArgs(", AP: %s\n", AP->GetPlayerName())); +#endif + + // Abort if it's already active + if (Goal->goal_state == TFGS_ACTIVE) + { + MDEBUG(Warning("-- Goal already active --\n")); + return false; + } + // Abort if it's been removed + if (Goal->goal_state == TFGS_REMOVED) + { + MDEBUG(Warning("-- Goal is in Removed state --\n")); + return false; + } + // Abort if it's been activated already and its activation's being delayed + if (Goal->goal_state == TFGS_DELAYED) + { + MDEBUG(Warning("-- Goal is being Delayed --\n")); + return false; + } + + // See if the AP matches the criteria + bool bAPMet = APMeetsCriteria(Goal, AP); + bool bAct = false; + bool bRevAct; + if ( FClassnameIs(Goal,"item_tfgoal") ) + bRevAct = (Goal->goal_activation & TFGI_REVERSE_AP) != 0; + else + bRevAct = (Goal->goal_activation & TFGA_REVERSE_AP) != 0; + + // Does the AP match the AP Criteria? + if (bAPMet) + { + MDEBUG(Warning("-- Criteria met --\n")); + if (!bRevAct) + bAct = true; + } + else + { + MDEBUG(Warning("-- Criteria not met --\n")); + if (bRevAct) + { + MDEBUG(Warning("Reverse Activation\n")); + bAct = true; + } + } + +#ifdef MAP_DEBUG + if (bAct) + Warning("Activation.\n"); + else + Warning("NO Activation.\n"); +#endif + + return bAct; +}; + + +//========================================================================= +// Return TRUE if the player is affected by the goal +BOOL IsAffectedBy(CTFGoal *Goal, CTFCPlayer *Player, CTFCPlayer *AP) +{ + // Don't affect anyone who isn't alive or is in Observer mode + if (Player->m_Shared.GetPlayerClass() == PC_UNDEFINED) + return FALSE; + + // Same Environment Check + if (Goal->goal_effects & TFGE_SAME_ENVIRONMENT) + { + int iEnviron = UTIL_PointContents( Goal->GetAbsOrigin() ); + if ( UTIL_PointContents( Player->GetAbsOrigin() ) != iEnviron ) + return FALSE; + } + + if (Goal->t_length != 0) + { + // Within radius? + if ((Goal->GetAbsOrigin() - Player->GetAbsOrigin()).Length() <= Goal->t_length) + { + // Obstructed by walls? + if (Goal->goal_effects & TFGE_WALL) + { + trace_t tr; + UTIL_TraceLine ( Goal->GetAbsOrigin(), Player->WorldSpaceCenter(), MASK_SOLID, Goal, COLLISION_GROUP_NONE, &tr ); + if ( tr.fraction == 1.0 ) + return TRUE; + } + else + { + return TRUE; + } + } + } + + if ( Goal->Classify() != CLASS_TFGOAL_TIMER && AP != NULL ) + { + // Spawnpoints always affect the player who spawns on them + if ((Goal->Classify() == CLASS_TFSPAWN) && (Player == AP)) + return TRUE; + + if ((Goal->goal_effects & TFGE_AP) && (Player == AP)) + return TRUE; + + if ((Goal->goal_effects & TFGE_AP_TEAM) && (AP->GetTeamNumber() == Player->GetTeamNumber())) + return TRUE; + } + + if (Goal->goal_effects & TFGE_NOT_AP_TEAM) + { + if (AP == NULL || AP->GetTeamNumber() != Player->GetTeamNumber()) + return TRUE; + } + + if ((Goal->goal_effects & TFGE_NOT_AP) && (Player != AP)) + return TRUE; + + if ((Goal->maxammo_shells != 0) && (Player->GetTeamNumber() == Goal->maxammo_shells)) + return TRUE; + + if ((Goal->maxammo_nails != 0) && (Player->GetTeamNumber() != Goal->maxammo_nails)) + return TRUE; + + return FALSE; +} + + +//========================================================================= +// Do all the checking of Item Groups +void DoItemGroupWork(CTFGoalItem *Item, CTFCPlayer *AP) +{ + if (Item->distance != 0) + { + if (Item->pain_finished == 0) + { + // No goal specified in .pain_finished. Print error. + Warning( "GoalItem %d has .distance specified, but no .pain_finished\n", Item->goal_no ); + } + + BOOL bAllCarried = TRUE; + // Find all items + CBaseEntity *pEnt = gEntList.FindEntityByClassname( NULL, "item_tfgoal" ); + while ( pEnt && bAllCarried ) + { + CTFGoalItem *pItem = dynamic_cast<CTFGoalItem*>( pEnt ); + if ( pItem ) + { + if (pItem->group_no == Item->distance && pItem->goal_state != TFGS_ACTIVE) + bAllCarried = FALSE; + } + + pEnt = gEntList.FindEntityByClassname( pEnt, "item_tfgoal" ); + } + + if (bAllCarried) + { + CTFGoal *pGoal = Findgoal(Item->pain_finished); + if (pGoal) + DoResults(pGoal, AP, (Item->goal_result & TFGR_ADD_BONUSES)); + } + } + + if (Item->speed != 0) + { + if (Item->attack_finished == 0) + { + // No goal specified in .attack_finished. Print error. + Warning( "GoalItem %d has .speed specified, but no .attack_finished\n", Item->goal_no ); + } + + BOOL bAllCarried = TRUE; + CBaseEntity *pCarrier = NULL; + // Find all goals + CBaseEntity *pEnt = gEntList.FindEntityByClassname( NULL, "item_tfgoal" ); + while ( pEnt && bAllCarried ) + { + CTFGoalItem *pItem = dynamic_cast<CTFGoalItem*>( pEnt ); + if ( pItem ) + { + if (pItem->group_no == Item->speed) + { + if (pItem->goal_state != TFGS_ACTIVE) + bAllCarried = FALSE; + else if (!pCarrier) // Store Player + pCarrier = pItem->GetOwnerEntity(); + else if (pCarrier != pItem->GetOwnerEntity()) // Need to all be carried by the same player + bAllCarried = FALSE; + } + } + + pEnt = gEntList.FindEntityByClassname( pEnt, "item_tfgoal" ); + } + + if (bAllCarried) + { + CTFGoal *pGoal = Findgoal(Item->attack_finished); + if (pGoal) + DoResults(pGoal, AP, (Item->goal_result & TFGR_ADD_BONUSES)); + } + } +} + + +//========================================================================= +// Remove any results applied to this player by the Goal +// Used when a GoalItem is dropped/removed +void RemoveResults(CTFGoal *Goal, CTFCPlayer *pPlayer) +{ + // Only remove the stats if the player has been affected + // by this item. This is needed because the player may have + // died since being affected + if ( FClassnameIs( Goal, "item_tfgoal" ) ) + { + if (!(pPlayer->item_list & Goal->item_list)) + return; + + if (Goal->goal_activation & TFGI_DONTREMOVERES) + return; + + // Remove the affected flag + pPlayer->item_list &= ~(Goal->item_list); + } + + if (Goal->GetHealth() > 0) + pPlayer->TakeDamage( CTakeDamageInfo( Goal, Goal, Goal->GetHealth(), DMG_IGNOREARMOR ) ); + if (Goal->GetHealth() < 0) + pPlayer->TakeHealth( (0 - Goal->GetHealth()), 0 ); + pPlayer->lives -= Goal->lives; + pPlayer->armortype -= Goal->armortype; + pPlayer->SetArmorValue( pPlayer->ArmorValue() - Goal->armorvalue ); + pPlayer->armorclass &= ~(Goal->armorclass); + + if (Goal->frags) + { + pPlayer->TF_AddFrags(Goal->frags); + } + + pPlayer->RemoveAmmo( Goal->ammo_shells, TFC_AMMO_SHELLS ); + pPlayer->RemoveAmmo( Goal->ammo_nails, TFC_AMMO_NAILS ); + pPlayer->RemoveAmmo( Goal->ammo_rockets, TFC_AMMO_ROCKETS ); + pPlayer->RemoveAmmo( Goal->ammo_cells, TFC_AMMO_CELLS ); + pPlayer->RemoveAmmo( Goal->ammo_medikit, TFC_AMMO_MEDIKIT ); + pPlayer->RemoveAmmo( Goal->ammo_detpack, TFC_AMMO_DETPACK ); + + // Detpacks +//TFCTODO: this should be handled in the GiveAmmo functions.. +// if (pPlayer->ammo_detpack > pPlayer->maxammo_detpack) +// pPlayer->ammo_detpack = pPlayer->maxammo_detpack; + + // Grenades + pPlayer->RemoveAmmo( Goal->no_grenades_1, TFC_AMMO_GRENADES1 ); + pPlayer->RemoveAmmo( Goal->no_grenades_2, TFC_AMMO_GRENADES2 ); + + // If they had a primed grenade, and they don't have any more of + // that type of grenade, unprime it and remove it. + if (pPlayer->m_Shared.GetStateFlags() & TFSTATE_GRENPRIMED) + { + if (pPlayer->GetAmmoCount( TFC_AMMO_GRENADES2 ) <= 0 || pPlayer->GetAmmoCount( TFC_AMMO_GRENADES1 ) <= 0) + { + pPlayer->m_Shared.RemoveStateFlags( TFSTATE_GRENPRIMED ); + pPlayer->m_Shared.RemoveStateFlags( TFSTATE_GRENTHROWING ); + pPlayer->bRemoveGrenade = TRUE; + } + } + + BOOL puinvin = FALSE; + BOOL puinvis = FALSE; + BOOL puquad = FALSE; + BOOL purad = FALSE; + // Make sure we don't remove an effect another Goal is also supplying + CBaseEntity *pEnt = gEntList.FindEntityByClassname( NULL, "item_tfgoal" ); + while ( pEnt ) + { + CTFGoalItem *pItem = dynamic_cast<CTFGoalItem*>( pEnt ); + if ( pItem ) + { + if ( (pItem->GetOwnerEntity() == pPlayer) && (pEnt != Goal) ) + { + if (pItem->invincible_finished > 0) + puinvin = TRUE; + if (pItem->invisible_finished > 0) + puinvis = TRUE; + if (pItem->super_damage_finished > 0) + puquad = TRUE; + if (pItem->radsuit_finished > 0) + purad = TRUE; + } + } + pEnt = gEntList.FindEntityByClassname( pEnt, "item_tfgoal" ); + } + + // Remove all powerups + if ((Goal->invincible_finished > 0) && (!puinvin)) + { + // if its a GoalItem, powerup was permanent, so we remove TFSTATE flag + pPlayer->m_Shared.RemoveStateFlags( TFSTATE_INVINCIBLE ); + pPlayer->m_Shared.AddItemFlags( IT_INVULNERABILITY ); + pPlayer->invincible_finished = gpGlobals->curtime + Goal->invincible_finished; + } + if ((Goal->invisible_finished > 0) && (!puinvis)) + { + // if its a GoalItem, powerup was permanent, so we remove TFSTATE flag + pPlayer->m_Shared.RemoveStateFlags( TFSTATE_INVISIBLE ); + pPlayer->m_Shared.AddItemFlags( IT_INVISIBILITY ); + pPlayer->invisible_finished = gpGlobals->curtime + Goal->invisible_finished; + } + if ((Goal->super_damage_finished > 0) && (!puquad)) + { + // if its a GoalItem, powerup was permanent, so we remove TFSTATE flag + pPlayer->m_Shared.RemoveStateFlags( TFSTATE_QUAD ); + pPlayer->m_Shared.AddItemFlags( IT_QUAD ); + pPlayer->super_damage_finished = gpGlobals->curtime + Goal->super_damage_finished; + } + if ((Goal->radsuit_finished > 0) && (!purad)) + { + // if its a GoalItem, powerup was permanent, so we remove TFSTATE flag + pPlayer->m_Shared.RemoveStateFlags( TFSTATE_RADSUIT ); + pPlayer->m_Shared.AddItemFlags( IT_SUIT ); + pPlayer->radsuit_finished = gpGlobals->curtime + Goal->radsuit_finished; + } + + // Now apply the pev->playerclass limitations & Redisplay Ammo counts + pPlayer->TeamFortress_CheckClassStats(); + //W_SetCurrentAmmo (); + + if (Goal->replacement_model != NULL_STRING) + { + // is it the same goal that gave us the replacment model? + if (pPlayer->replacement_model == Goal->replacement_model) + { + pPlayer->replacement_model = NULL_STRING; + pPlayer->replacement_model_body = 0; + pPlayer->replacement_model_skin = 0; + pPlayer->replacement_model_flags = 0; + + pPlayer->TeamFortress_SetSkin(); + } + } +} + + +//========================================================================= +// Give the GoalItem to a Player. +void tfgoalitem_GiveToPlayer(CTFGoalItem *Item, CTFCPlayer *AP, CTFGoal *Goal) +{ + MDEBUG(Warning( "Giving %s to %s\n", Item->GetEntityName().ToCStr(), AP->GetPlayerName())); + + // Don't let it re-drop + if (Item->redrop_count) + Item->SetThink( NULL ); + + Item->SetOwnerEntity( AP ); + // Remove it from the map + Item->FollowEntity( AP ); + // Play carry animations + if (Item->GetModelName() != NULL_STRING) + { + Item->RemoveEffects( EF_NODRAW ); + + Item->SetSequence( Item->LookupSequence( "carried" ) ); + if (Item->GetSequence() != -1) + { + Item->ResetSequenceInfo(); + Item->m_flCycle = 0; + } + } + + Item->AddSolidFlags( FSOLID_NOT_SOLID ); + + // Do the deeds on the player + if (Item->goal_activation & TFGI_GLOW) + AP->AddEffects( EF_BRIGHTLIGHT ); //TFCTODO: this used to be EF_BRIGHTFIELD.. make sure it's the same + if (Item->goal_activation & TFGI_SLOW) + AP->TeamFortress_SetSpeed(); + if (Item->speed_reduction) + AP->TeamFortress_SetSpeed(); + + if (Item->goal_activation & TFGI_ITEMGLOWS) + { + Item->m_nRenderFX = kRenderFxNone; + Item->SetRenderColor( 0, 0, 0, 0 ); + } + + // Light up console icons + if (Item->items & IT_KEY1) + AP->m_Shared.AddItemFlags( IT_KEY1 ); + if (Item->items & IT_KEY2) + AP->m_Shared.AddItemFlags( IT_KEY2 ); + if (Item->items & IT_KEY3) + AP->m_Shared.AddItemFlags( IT_KEY3 ); + if (Item->items & IT_KEY4) + AP->m_Shared.AddItemFlags( IT_KEY4 ); + + // Only do the results if we're allowed to + if (Goal != Item) + { + if (Goal->goal_result & TFGR_NO_ITEM_RESULTS) + { + Item->goal_state = TFGS_ACTIVE; + return; + } + } + + MDEBUG(Warning("Doing item results...\n")); + + // Prevent the Player from disguising themself if applicable + if (Item->goal_result & TFGR_REMOVE_DISGUISE) + AP->is_unableto_spy_or_teleport = 1; + + // Do the Results, adding the bonuses + DoResults(Item, AP, TRUE); + + // Check the Item Group Stuff + DoItemGroupWork(Item, AP); +} + + +//========================================================================= +// Drop the item +void tfgoalitem_drop(CTFGoalItem *Item, BOOL PAlive, CTFCPlayer *P) +{ + CBaseEntity *pOwner = Item->GetOwnerEntity(); + + // Backup origin for retry at the drop + if ( FBitSet( pOwner->GetFlags(), FL_DUCKING ) ) + Item->redrop_origin = pOwner->GetAbsOrigin() + Vector(0, 0, 26); + else + Item->redrop_origin = pOwner->GetAbsOrigin() + Vector(0, 0, 8); + Item->redrop_count = 0; + + Item->SetTouch( &CTFGoalItem::item_tfgoal_touch ); + Item->DoDrop( Item->redrop_origin ); + + Item->SetOwnerEntity( P ); + if (PAlive) + { + Vector vForward, vUp; + AngleVectors( P->EyeAngles(), &vForward, NULL, &vUp ); + Item->SetAbsVelocity( (vForward * 400) + (vUp * 200) ); + + Item->SetTouch( NULL ); + Item->SetThink( &CTFGoalItem::tfgoalitem_droptouch ); // give it 0.75 seconds + Item->SetNextThink( gpGlobals->curtime + 0.75 ); // and then set it's touch func + + // Prevent the dropping player from picking it up for longer + Item->enemy = P; + Item->m_flDroppedAt = gpGlobals->curtime; + } +} + + +//========================================================================= +// Remove the GoalItem from a Player. +void tfgoalitem_RemoveFromPlayer(CTFGoalItem *Item, CTFCPlayer *AP, int iMethod) +{ + MDEBUG(Warning("Removing %s from %s\n", STRING(Item->pev->netname), STRING(AP->pev->netname))); + + // If we have a teamcheck entity, use it instead + if ( Item->owned_by_teamcheck != NULL_STRING ) + Item->owned_by = GetTeamCheckTeam( STRING(Item->owned_by_teamcheck) ); + + BOOL lighton = FALSE; + BOOL slowon = FALSE; + BOOL key1on = FALSE; + BOOL key2on = FALSE; + BOOL key3on = FALSE; + BOOL key4on = FALSE; + BOOL spyoff = FALSE; + // Remove the effects from the player + // Make sure we don't remove an effect another Goal is also supplying + CBaseEntity *pEnt = gEntList.FindEntityByClassname( NULL, "item_tfgoal" ); + while ( pEnt ) + { + CTFGoalItem *pItem = dynamic_cast<CTFGoalItem*>( pEnt ); + if ( pItem ) + { + if ( (pItem->GetOwnerEntity() == AP) && (pEnt != Item) ) + { + if (pItem->goal_activation & TFGI_GLOW) + lighton = TRUE; + if (pItem->goal_activation & TFGI_SLOW) + slowon = TRUE; + + if (pItem->items & IT_KEY1) + key1on = TRUE; + if (pItem->items & IT_KEY2) + key2on = TRUE; + if (pItem->items & IT_KEY3) + key3on = TRUE; + if (pItem->items & IT_KEY4) + key4on = TRUE; + + if (pItem->goal_result & TFGR_REMOVE_DISGUISE) + spyoff = TRUE; + } + } + pEnt = gEntList.FindEntityByClassname( pEnt, "item_tfgoal" ); + } + + // Check Powerups too + if (!lighton) + { + if (AP->invincible_finished > gpGlobals->curtime + 3) + lighton = TRUE; + else if (AP->super_damage_finished > gpGlobals->curtime + 3) + lighton = TRUE; + } + if (!lighton) + { + //TFCTODO: Add support for EF_BRIGHTFIELD if necessary. + //AP->RemoveEffects( EF_BRIGHTFIELD ); + AP->RemoveEffects( EF_BRIGHTLIGHT ); + } + if (Item->goal_activation & TFGI_ITEMGLOWS) + { + Item->m_nRenderFX = kRenderFxGlowShell; + + if (Item->owned_by > 0 && Item->owned_by <= 4) + Item->m_clrRender = Vector255ToRGBColor( rgbcolors[Item->owned_by] ); + else + Item->m_clrRender = Vector255ToRGBColor( rgbcolors[0] ); + Item->SetRenderColorA( 100 ); // Shell size + } + + // Remove the Spy prevention + if (!spyoff) + AP->is_unableto_spy_or_teleport = FALSE; + // Remove the lit console key icons + if (!key1on) + AP->m_Shared.RemoveItemFlags( IT_KEY1 ); + if (!key2on) + AP->m_Shared.RemoveItemFlags( IT_KEY2 ); + if (!key3on) + AP->m_Shared.RemoveItemFlags( IT_KEY3 ); + if (!key4on) + AP->m_Shared.RemoveItemFlags( IT_KEY4 ); + + // Remove AP Modifications + // Go through all the players and do any results + for ( int i = 1; i <= gpGlobals->maxClients; i++ ) + { + CTFCPlayer *pPlayer = ToTFCPlayer( UTIL_PlayerByIndex( i ) ); + if ( pPlayer && IsAffectedBy(Item, pPlayer, AP) ) + RemoveResults(Item, pPlayer); + } + + // Setup animations + if (Item->GetModelName() != NULL_STRING) + { + Item->SetSequence( Item->LookupSequence( "not_carried" ) ); + if (Item->GetSequence() != -1) + { + Item->ResetSequenceInfo(); + Item->m_flCycle = 0; + } + } + + // Return it to the starting point if the flag is set + if (iMethod == GI_DROP_PLAYERDEATH || iMethod == GI_DROP_PLAYERDROP) + { + // Do messages + for ( int i = 1; i <= gpGlobals->maxClients; i++ ) + { + CBasePlayer *pPlayer = UTIL_PlayerByIndex( i ); + if ( !pPlayer ) + continue; + + if (pPlayer->GetTeamNumber() == Item->owned_by) + { + if (Item->team_drop != NULL_STRING) + UTIL_ShowMessage( STRING(Item->team_drop), pPlayer ); + if (Item->netname_team_drop != NULL_STRING) + ClientPrint( pPlayer, HUD_PRINTNOTIFY, STRING(Item->netname_team_drop), AP->GetPlayerName() ); + // Old printing + if (Item->org_team_drop != NULL_STRING) + ClientPrint( pPlayer, HUD_PRINTCENTER, STRING(Item->org_team_drop) ); + } + else // (pPlayer->GetTeamNumber() != Item->owned_by) + { + if (Item->non_team_drop != NULL_STRING) + UTIL_ShowMessage( STRING(Item->non_team_drop), pPlayer ); + if (Item->netname_non_team_drop != NULL_STRING) + ClientPrint( pPlayer, HUD_PRINTNOTIFY, STRING(Item->netname_non_team_drop), AP->GetPlayerName() ); + // Old printing + if (Item->org_non_team_drop != NULL_STRING) + ClientPrint( pPlayer, HUD_PRINTCENTER, STRING(Item->org_non_team_drop) ); + } + } + + // Drop it if the flag is set + if (Item->goal_activation & TFGI_RETURN_DROP) + { + CTimer *pTimer = Timer_CreateTimer( Item, TF_TIMER_RETURNITEM ); + pTimer->m_flNextThink = gpGlobals->curtime + 0.5f; + if (iMethod == GI_DROP_PLAYERDEATH) + pTimer->weapon = GI_RET_DROP_DEAD; + else + pTimer->weapon = GI_RET_DROP_LIVING; + } + else if (Item->goal_activation & TFGI_DROP) + { + if ( (iMethod == GI_DROP_PLAYERDROP) && (Item->goal_activation & TFGI_CANBEDROPPED) ) + tfgoalitem_drop(Item, TRUE, AP); + else + tfgoalitem_drop(Item, FALSE, AP); + } + else + { + // Remove the Item + Item->SetOwnerEntity( NULL ); + Item->SetNextThink( gpGlobals->curtime ); + Item->SetThink( &CBaseEntity::SUB_Remove ); + AP->TeamFortress_SetSpeed(); + return; + } + + Item->SetOwnerEntity( NULL ); + Item->RemoveFlag( FL_ONGROUND ); + UTIL_SetSize(Item, Item->goal_min, Item->goal_max); + + AP->TeamFortress_SetSpeed(); + } + else if (iMethod == GI_DROP_REMOVEGOAL) + { + Item->SetOwnerEntity( NULL ); + + if (Item->goal_activation & TFGI_RETURN_GOAL) + { + CTimer *pTimer = Timer_CreateTimer( Item, TF_TIMER_RETURNITEM ); + pTimer->m_flNextThink = gpGlobals->curtime + 0.5; + pTimer->weapon = GI_RET_GOAL; + AP->TeamFortress_SetSpeed(); + return; + } + + // Don't remove it, since it may be given away again later + Item->AddSolidFlags( FSOLID_NOT_SOLID ); + Item->AddEffects( EF_NODRAW ); + Item->StopFollowingEntity(); + AP->TeamFortress_SetSpeed(); + } +} + + +//========================================================================= +// Apply modifications to the Player passed in +void Apply_Results(CTFGoal *Goal, CTFCPlayer *Player, CTFCPlayer *AP, BOOL bAddBonuses) +{ + MDEBUG( Warning("Applying Results from %s to %s\n", STRING(Goal->pev->netname), STRING(Player->pev->netname)) ); + + // If this is a goalitem, record the fact that this player + // has been affected by it. + if ( FClassnameIs(Goal, "item_tfgoal") ) + Player->item_list |= Goal->item_list; + + if (Player == AP) + { + // Alter the team score + if (Goal->count != 0 && Player->GetTeamNumber() > 0) + { + TeamFortress_TeamIncreaseScore(Player->GetTeamNumber(), Goal->count); + // Display short team scores + //TeamFortress_TeamShowScores(FALSE, NULL); + } + } + + // Apply Stats, only if told to + if (bAddBonuses) + { + MDEBUG( Warning("Adding bonuses.\n") ); + // Some results are not applied to dead players + if ( Player->IsAlive() ) + { + if (Goal->GetHealth() > 0) + Player->TakeHealth(Goal->GetHealth(), 0); + if (Goal->GetHealth() < 0) + { + // Make sure we don't gib them, because it creates too many entities if + // a lot of players are affected by this Goal. + Player->TakeDamage( CTakeDamageInfo( Goal, Goal, (0 - Goal->GetHealth()), DMG_IGNOREARMOR | DMG_NEVERGIB ) ); + } + } + + // The player may be dead now, so check again + if ( Player->IsAlive() ) + { + if (Goal->armortype > 0) + Player->armortype = Goal->armortype; + else if (Goal->armorvalue > 0) + Player->armortype = Player->armor_allowed; + Player->IncrementArmorValue( Goal->armorvalue ); + if (Goal->armorclass > 0) + Player->armorclass = Goal->armorclass; + + Player->GiveAmmo( Goal->ammo_shells, TFC_AMMO_SHELLS ); + Player->GiveAmmo( Goal->ammo_nails, TFC_AMMO_NAILS ); + Player->GiveAmmo( Goal->ammo_rockets, TFC_AMMO_ROCKETS ); + Player->GiveAmmo( Goal->ammo_cells, TFC_AMMO_CELLS ); + Player->GiveAmmo( Goal->ammo_medikit, TFC_AMMO_MEDIKIT ); + Player->GiveAmmo( Goal->ammo_detpack, TFC_AMMO_DETPACK ); + +#ifdef TFCTODO // do this when grenades are implemented. + // Grenades + if ( Player->tp_grenades_1 != GR_TYPE_NONE ) + Player->no_grenades_1 += Goal->no_grenades_1; + if ( Player->tp_grenades_2 != GR_TYPE_NONE ) + Player->no_grenades_2 += Goal->no_grenades_2; + + // If they had a primed grenade, and they don't have any more of + // that type of grenade, unprime it and remove it. + if (Player->tfstate & TFSTATE_GRENPRIMED) + { + if ( (Player->m_iPrimedGrenType == 1 && Player->no_grenades_1 <= 0 && Goal->no_grenades_1 < 0) || + (Player->m_iPrimedGrenType == 2 && Player->no_grenades_2 <= 0 && Goal->no_grenades_2 < 0) ) + { + Player->tfstate &= ~TFSTATE_GRENPRIMED; + Player->tfstate &= ~TFSTATE_GRENTHROWING; + Player->bRemoveGrenade = TRUE; + } + } +#endif + + // Apply any powerups + if (Goal->invincible_finished > 0) + { + Player->m_Shared.AddItemFlags( IT_INVULNERABILITY ); + Player->invincible_finished = gpGlobals->curtime + Goal->invincible_finished; + // if its a GoalItem, powerup is permanent, so we use TFSTATE flags + if ( FClassnameIs(Goal, "item_tfgoal") ) + { + Player->m_Shared.AddStateFlags( TFSTATE_INVINCIBLE ); + Player->invincible_finished = gpGlobals->curtime + 666; + } + + // Force it to recalculate shell color + Player->m_nRenderFX = kRenderFxNone; + } + if (Goal->invisible_finished > 0) + { + Player->m_Shared.AddItemFlags( IT_INVISIBILITY ); + Player->invisible_finished = gpGlobals->curtime + Goal->invisible_finished; + // if its a GoalItem, powerup is permanent, so we use TFSTATE flags + if ( FClassnameIs(Goal, "item_tfgoal") ) + { + Player->m_Shared.AddStateFlags( TFSTATE_INVISIBLE ); + Player->invisible_finished = gpGlobals->curtime + 666; + } + + // Force it to recalculate shell color + Player->m_nRenderFX = kRenderFxNone; + } + if (Goal->super_damage_finished > 0) + { + Player->m_Shared.AddItemFlags( IT_QUAD ); + Player->super_damage_finished = gpGlobals->curtime + Goal->super_damage_finished; + // if its a GoalItem, powerup is permanent, so we use TFSTATE flags + if ( FClassnameIs(Goal, "item_tfgoal") ) + { + Player->m_Shared.AddStateFlags( TFSTATE_QUAD ); + Player->super_damage_finished = gpGlobals->curtime + 666; + } + + // Force it to recalculate shell color + Player->m_nRenderFX = kRenderFxNone; + } + if (Goal->radsuit_finished > 0) + { + Player->m_Shared.AddItemFlags( IT_SUIT ); + Player->radsuit_finished = gpGlobals->curtime + Goal->radsuit_finished; + // if its a GoalItem, powerup is permanent, so we use TFSTATE flags + if ( FClassnameIs(Goal, "item_tfgoal") ) + { + Player->m_Shared.AddStateFlags( TFSTATE_RADSUIT ); + Player->radsuit_finished = gpGlobals->curtime + 666; + } + } + } + + // These results are applied to dead and living players + Player->lives += Goal->lives; + + if ( Goal->frags != 0 ) + Player->TF_AddFrags(Goal->frags); + + // Now apply the m_Shared.GetPlayerClass() limitations & Redisplay Ammo counts + Player->TeamFortress_CheckClassStats(); + } +#ifdef MAP_DEBUG + else + ALERT( at_console, "NOT Adding bonuses.\n" ); +#endif + + // If the Goal resets Spy skin/color then do it + if (Player->m_Shared.GetPlayerClass() == PC_SPY && Goal->goal_result & TFGR_REMOVE_DISGUISE) + { + //TFCTODO: looks like this isn't actually used anywhere. + //Player->immune_to_check = gpGlobals->curtime + 10; + Player->Spy_RemoveDisguise(); + } + + // If there's a GoalItem for this goal, give it to the player + // GoalItems use "items" for the console lights... so don't do it for items. + if ( Goal->items != 0 && !FClassnameIs(Goal,"item_tfgoal") ) + { + // Find the item + CTFGoalItem *pItem = Finditem(Goal->items); + // Don't give them the item if it's the item that just affected them + if (pItem != NULL && pItem != Goal) + tfgoalitem_GiveToPlayer(pItem, Player, Goal); + } + + // If this goal removes an item from the player, remove it + if (Goal->axhitme != 0) + { + CTFGoalItem *pItem = Finditem(Goal->axhitme); + if (pItem->GetOwnerEntity() == Player) + tfgoalitem_RemoveFromPlayer(pItem, Player, GI_DROP_REMOVEGOAL); + } + + // if this goal removes a group of items from the player, remove them + if (Goal->remove_item_group != 0) + { + // Find all items in the group + CTFGoalItem *pItemToRemove = NULL; + CBaseEntity *pEnt = gEntList.FindEntityByClassname( NULL, "item_tfgoal" ); + while ( pEnt ) + { + CTFGoalItem *pItem = dynamic_cast<CTFGoalItem*>( pEnt ); + if ( pItem ) + { + if ( (pItem->group_no == Goal->remove_item_group) && (pItem->GetOwnerEntity() == Player) ) + pItemToRemove = pItem; + + // need to cycle before removing it from the player, because it may be destroyed + pEnt = gEntList.FindEntityByClassname( pEnt, "item_tfgoal" ); + if (pItemToRemove) + { + tfgoalitem_RemoveFromPlayer(pItemToRemove, Player, GI_DROP_REMOVEGOAL); + pItemToRemove = NULL; + } + } + } + } + + // Display all the item statuses + Player->DisplayLocalItemStatus(Goal); + + // Destroy buildings + if (Goal->goal_result & TFGR_DESTROY_BUILDINGS) + { + Player->no_sentry_message = TRUE; + Player->no_dispenser_message = TRUE; + Player->no_entry_teleporter_message = TRUE; + Player->no_exit_teleporter_message = TRUE; + Player->Engineer_RemoveBuildings(); + Player->TeamFortress_RemoveLiveGrenades(); + Player->TeamFortress_RemoveRockets(); + Player->RemovePipebombs(); + + // is the player setting a detpack? + if ( Player->is_detpacking ) + { + Player->TeamFortress_DetpackStop(); + } + else + { + // does the player have a detpack in the world? + if ( Player->TeamFortress_RemoveDetpacks() ) + { + Player->GiveAmmo( 1, TFC_AMMO_DETPACK ); + } + } + } + + // Force respawns + if (Goal->goal_result & TFGR_FORCE_RESPAWN) + { + // Only if they're alive + if ( Player->IsAlive() ) + Player->ForceRespawn(); + } + + if (Goal->replacement_model != NULL_STRING) + { + // if we don't already have a replacement_model + if ( !Player->replacement_model ) + { + Player->replacement_model = Goal->replacement_model; + Player->replacement_model_body = Goal->replacement_model_body; + Player->replacement_model_skin = Goal->replacement_model_skin; + Player->replacement_model_flags = Goal->replacement_model_flags; + + Player->TeamFortress_SetSkin(); + } + } +} + + +//========================================================================= +// Use (Triggered) function for Goals +void EndRound( CTFGoal *pGoal ) +{ + // fade everyones screen + color32 clr; + memset( &clr, 0, sizeof( clr ) ); + UTIL_ScreenFadeAll( clr, 0.3, pGoal->m_flEndRoundTime, FFADE_MODULATE | FFADE_OUT ); + + // Display Long TeamScores to everyone + TeamFortress_TeamShowScores(TRUE, NULL); + + int highestScore = -99990; + int winningTeam = 1; + const char *winnerMsg = ""; + // Only do team score check if the win one is set + if ( pGoal->m_iszEndRoundMsg_Team1_Win != NULL_STRING ) + { + // work out which team won + for ( int i = 1; i <= 4; i++ ) + { + int teamScore = TeamFortress_TeamGetScoreFrags( i ); + + if ( teamScore > highestScore ) + { + winningTeam = i; + highestScore = teamScore; + } + } + + // work out the winning msg + switch ( winningTeam ) + { + case 1: winnerMsg = STRING(pGoal->m_iszEndRoundMsg_Team1_Win); break; + case 2: winnerMsg = STRING(pGoal->m_iszEndRoundMsg_Team2_Win); break; + case 3: winnerMsg = STRING(pGoal->m_iszEndRoundMsg_Team3_Win); break; + case 4: winnerMsg = STRING(pGoal->m_iszEndRoundMsg_Team4_Win); break; + }; + } + + // Prevent players from moving and shooting + no_cease_fire_text = TRUE; + cease_fire = TRUE; + + // Send out the messages + CTFCPlayer *client = NULL; + while ( ((client = (CTFCPlayer*)gEntList.FindEntityByClassname( client, "player" )) != NULL) && (!FNullEnt(client->edict())) ) + { + if ( !client ) + continue; + + // Freeze all the players + if ( client->IsObserver() == FALSE ) + { + //TFCTODO implement something for this? + // iuser4 stops firing on the clients + //client->pev->iuser4 = TRUE; + + //TFCTODO: implement HIDEHUD_WEAPONS, or is it the same as HIDEHUD_WEAPONSELECTION? + //client->m_Local.m_iHideHUD |= (HIDEHUD_HEALTH | HIDEHUD_WEAPONS); + client->m_Local.m_iHideHUD |= (HIDEHUD_HEALTH | HIDEHUD_WEAPONSELECTION); + + client->m_Shared.AddStateFlags( TFSTATE_CANT_MOVE ); + } + client->TeamFortress_SetSpeed(); + + // Owned by and Non owned by take precedence + if ( pGoal->m_iszEndRoundMsg_OwnedBy != NULL_STRING && ( client->GetTeamNumber() == pGoal->owned_by ) ) + { + UTIL_ShowMessage( STRING( pGoal->m_iszEndRoundMsg_OwnedBy ), client ); + } + else if ( pGoal->m_iszEndRoundMsg_NonOwnedBy != NULL_STRING && ( client->GetTeamNumber() != pGoal->owned_by ) ) + { + UTIL_ShowMessage( STRING( pGoal->m_iszEndRoundMsg_NonOwnedBy ), client ); + } + else if ( pGoal->m_iszEndRoundMsg_Team1_Win != NULL_STRING && client->GetTeamNumber() == winningTeam ) + { + UTIL_ShowMessage( winnerMsg, client ); + } + else + { + const char *loserMsg = ""; + // work out the loser message and send it to them + if ( pGoal->m_iszEndRoundMsg_Team1_Win != NULL_STRING ) + { + switch ( client->GetTeamNumber() ) + { + case 1: loserMsg = STRING(pGoal->m_iszEndRoundMsg_Team1_Lose); break; + case 2: loserMsg = STRING(pGoal->m_iszEndRoundMsg_Team2_Lose); break; + case 3: loserMsg = STRING(pGoal->m_iszEndRoundMsg_Team3_Lose); break; + case 4: loserMsg = STRING(pGoal->m_iszEndRoundMsg_Team4_Lose); break; + }; + } + else + { + switch ( client->GetTeamNumber() ) + { + case 1: loserMsg = STRING(pGoal->m_iszEndRoundMsg_Team1); break; + case 2: loserMsg = STRING(pGoal->m_iszEndRoundMsg_Team2); break; + case 3: loserMsg = STRING(pGoal->m_iszEndRoundMsg_Team3); break; + case 4: loserMsg = STRING(pGoal->m_iszEndRoundMsg_Team4); break; + }; + } + + UTIL_ShowMessage( loserMsg, client ); + } + } + + // Create a timer to remove the EndRound in the specified time + CTimer *pTimer = Timer_CreateTimer( pGoal, TF_TIMER_ENDROUND ); + //pTimer->SetThink( &CBaseEntity::EndRoundEnd ); + pTimer->m_flNextThink = gpGlobals->curtime + pGoal->m_flEndRoundTime; +} + + +//========================================================================= +// Inactivate a Timer/Goal +void InactivateGoal(CTFGoal *Goal) +{ + MDEBUG( Warning("Inactivating %s", STRING(Goal->pev->netname)) ); + + if (Goal->goal_state == TFGS_ACTIVE) + { + MDEBUG( Warning("... succeeded.\n") ); + // Not a timer goal + if (Goal->Classify() != CLASS_TFGOAL_TIMER) + { + if ( Goal->goal_activation & TFGI_SOLID && (Goal->Classify() == CLASS_TFGOAL || Goal->Classify() == CLASS_TFGOAL_ITEM) ) + Goal->SetSolid( SOLID_BBOX ); + else + Goal->AddSolidFlags( FSOLID_TRIGGER ); + } + + Goal->goal_state = TFGS_INACTIVE; + const char *pModel = STRING( Goal->GetModelName() ); + if (pModel && pModel[0] != '*') + Goal->RemoveEffects( EF_NODRAW ); + } +#ifdef MAP_DEBUG + else + Warning("... failed. Goal is %s\n", g_szStates[Goal->goal_state]); +#endif +} + + +//========================================================================= +// Restores a Timer/Goal +void RestoreGoal(CTFGoal *Goal) +{ + MDEBUG( Warning("Attempting to Restore %s", STRING(Goal->pev->netname)) ); + + if (Goal->goal_state == TFGS_REMOVED) + { + MDEBUG( Warning("... succeeded.\n") ); + + // Not a timer goal + if (Goal->search_time == 0) + { + if (Goal->goal_activation & TFGI_SOLID && FClassnameIs(Goal, "item_tfgoal") ) + Goal->SetSolid( SOLID_BBOX ); + else + Goal->AddSolidFlags( FSOLID_TRIGGER ); + } + else + Goal->SetNextThink( gpGlobals->curtime + Goal->search_time ); + + Goal->goal_state = TFGS_INACTIVE; + const char *pModel = STRING(Goal->GetModelName()); + if (pModel[0] != '*') + Goal->RemoveEffects( EF_NODRAW ); + } +#ifdef MAP_DEBUG + else + Warning("... failed. Goal is %s\n", g_szStates[Goal->goal_state]); +#endif +} + + +//========================================================================= +// Do all the activation/inactivation/etc of Goal Groups +void DoGroupWork(CTFGoal *Goal, CTFCPlayer *AP) +{ +#ifdef MAP_DEBUG + if (Goal->all_active || Goal->activate_group_no || Goal->inactivate_group_no || Goal->restore_group_no || Goal->remove_group_no) + Warning("Doing Groupwork...\n"); +#endif + + // Check all goals activated flag + if (Goal->all_active != 0) + { + if (Goal->last_impulse == 0) + { + // No goal specified in .last_impulse. Print error. + Warning("Goal %d has .all_active specified, but no .last_impulse\n", Goal->goal_no); + } + else + { + MDEBUG( Warning("All Active Group Check.\n") ); + + BOOL bAllSet = TRUE; + // Find all goals + CBaseEntity *pEnt = gEntList.FindEntityByClassname( NULL, "info_tfgoal" ); + while ( pEnt && bAllSet) + { + CTFGoal *pGoal = dynamic_cast< CTFGoal* >( pEnt ); + if ( pGoal ) + { + if (pGoal->group_no == Goal->all_active && pGoal->goal_state != TFGS_ACTIVE) + bAllSet = FALSE; + } + + pEnt = gEntList.FindEntityByClassname( pEnt, "info_tfgoal" ); + } + + // If all goals in this group are activated, do it + if (bAllSet) + { + MDEBUG( Warning("All Active, Activating last_impulse.\n") ); + + CTFGoal *pGoal = Findgoal(Goal->last_impulse); + if (pGoal) + DoResults(pGoal, AP, (Goal->goal_result & TFGR_ADD_BONUSES)); + } + #ifdef MAP_DEBUG + else + { + Warning("Not all Active.\n"); + } + #endif + } + } + + // Check Activate all in the group flag + if (Goal->activate_group_no != 0) + { + // Find all goals + CBaseEntity *pEnt = gEntList.FindEntityByClassname( NULL, "info_tfgoal" ); + while ( pEnt ) + { + CTFGoal *pGoal = dynamic_cast< CTFGoal* >( pEnt ); + if ( pGoal ) + { + if (pGoal->group_no == Goal->activate_group_no) + ActivateDoResults(pGoal, AP, Goal); + } + + pEnt = gEntList.FindEntityByClassname( pEnt, "info_tfgoal" ); + } + } + + // Check Inactivate all in the group flag + if (Goal->inactivate_group_no != 0) + { + // Find all goals + CBaseEntity *pEnt = gEntList.FindEntityByClassname( NULL, "info_tfgoal" ); + while ( pEnt ) + { + CTFGoal *pGoal = dynamic_cast< CTFGoal* >( pEnt ); + if ( pGoal ) + { + if (pGoal->group_no == Goal->inactivate_group_no) + InactivateGoal(pGoal); + } + + pEnt = gEntList.FindEntityByClassname( pEnt, "info_tfgoal" ); + } + } + + // Check Remove all in the group flag + if (Goal->remove_group_no != 0) + { + // Find all goals + CBaseEntity *pEnt = gEntList.FindEntityByClassname( NULL, "info_tfgoal" ); + while ( pEnt ) + { + CTFGoal *pGoal = dynamic_cast< CTFGoal* >( pEnt ); + if ( pGoal ) + { + if (pGoal->group_no == Goal->remove_group_no) + RemoveGoal(pGoal); + } + + pEnt = gEntList.FindEntityByClassname( pEnt, "info_tfgoal" ); + } + } + + // Check Restore all in the group flag + if (Goal->restore_group_no != 0) + { + // Find all goals + CBaseEntity *pEnt = gEntList.FindEntityByClassname( NULL, "info_tfgoal" ); + while ( pEnt ) + { + CTFGoal *pGoal = dynamic_cast< CTFGoal* >( pEnt ); + if ( pGoal ) + { + if (pGoal->group_no == Goal->restore_group_no) + RestoreGoal(pGoal); + } + + pEnt = gEntList.FindEntityByClassname( pEnt, "info_tfgoal" ); + } + } + +#ifdef MAP_DEBUG + if (Goal->remove_spawngroup || Goal->restore_spawngroup) + Warning("Doing SpawnGroupwork...\n"); +#endif +} + + + +//========================================================================= +// Do all the activation/inactivation/etc of individual Goals +void DoGoalWork(CTFGoal *Goal, CTFCPlayer *AP) +{ +#ifdef MAP_DEBUG + if (Goal->activate_goal_no || Goal->inactivate_goal_no || Goal->restore_goal_no || Goal->remove_goal_no || Goal->return_item_no) + Warning("Doing Goalwork...\n"); +#endif + + // If another goal should be activated, activate it + if (Goal->activate_goal_no != 0) + { + CTFGoal *pFoundGoal = Findgoal(Goal->activate_goal_no); + if (pFoundGoal) + ActivateDoResults(pFoundGoal, AP, Goal); + } + + // If another goal should be inactivated, inactivate it + if (Goal->inactivate_goal_no != 0) + { + CTFGoal *pFoundGoal = Findgoal(Goal->inactivate_goal_no); + if (pFoundGoal) + InactivateGoal(pFoundGoal); + } + + // If another goal should be restored, restore it + if (Goal->restore_goal_no != 0) + { + CTFGoal *pFoundGoal = Findgoal(Goal->restore_goal_no); + if (pFoundGoal) + RestoreGoal(pFoundGoal); + } + + // If another goal should be removed, remove it + if (Goal->remove_goal_no != 0) + { + CTFGoal *pFoundGoal = Findgoal(Goal->remove_goal_no); + if (pFoundGoal) + RemoveGoal(pFoundGoal); + } + + // If a GoalItem should be returned, return it + if (Goal->return_item_no != 0) + { + CTFGoalItem *pFoundGoal = Finditem(Goal->return_item_no); + if (pFoundGoal) + { + CBaseEntity *pOwner = pFoundGoal->GetOwnerEntity(); + Assert( dynamic_cast<CTFCPlayer*>( pOwner ) ); + if (pFoundGoal->goal_state == TFGS_ACTIVE) + tfgoalitem_RemoveFromPlayer(pFoundGoal, (CTFCPlayer*)pOwner, GI_DROP_REMOVEGOAL); + + // Setup a ReturnItem timer + CTimer *pTimer = Timer_CreateTimer( pFoundGoal, TF_TIMER_RETURNITEM ); + pTimer->weapon = GI_RET_TIME; + pTimer->m_flNextThink = gpGlobals->curtime + 0.1; + + pFoundGoal->AddSolidFlags( FSOLID_NOT_SOLID ); + } + } + +#ifdef MAP_DEBUG + if (Goal->remove_spawnpoint || Goal->restore_spawnpoint) + Warning("Doing Spawnwork...\n"); +#endif + + // Spawnpoint behaviour + if (Goal->remove_spawnpoint != 0) + { + CTFSpawn *pFoundGoal = Findteamspawn(Goal->remove_spawnpoint); + if (pFoundGoal) + InactivateSpawn(pFoundGoal); + } + + if (Goal->restore_spawnpoint != 0) + { + CTFSpawn *pFoundGoal = Findteamspawn(Goal->restore_spawnpoint); + if (pFoundGoal) + { + if (pFoundGoal->goal_state == TFGS_REMOVED) + ActivateSpawn(pFoundGoal); + } + } +} + + +//========================================================================= +// Do all the activation/removal of Quake Triggers +void DoTriggerWork(CTFGoal *Goal, CTFCPlayer *AP) +{ + // remove killtargets + if (Goal->killtarget != NULL_STRING) + { + MDEBUG( Warning("Doing Triggerwork...\n") ); + MDEBUG( Warning("Killing Target(s): %s\n", STRING(Goal->killtarget)) ); + + CBaseEntity *pentKillTarget = gEntList.FindEntityByName( NULL, STRING(Goal->killtarget) ); + while ( pentKillTarget ) + { + UTIL_Remove( pentKillTarget ); + pentKillTarget = gEntList.FindEntityByName( pentKillTarget, STRING(Goal->killtarget) ); + } + } + + // fire targets + if (Goal->target != NULL_STRING) + { + MDEBUG( Warning("Doing Triggerwork...\n") ); + MDEBUG( ALERT( at_console, "Activating Target(s): %s\n", STRING(Goal->pev->target) ) ); + + CBaseEntity *pentTarget = gEntList.FindEntityByName( NULL, STRING(Goal->target) ); + while ( pentTarget ) + { + CBaseEntity *pTarget = pentTarget; + if ( !(pTarget->GetFlags() & FL_KILLME) ) + pTarget->Use( AP, Goal, USE_TOGGLE, 0 ); + pentTarget = gEntList.FindEntityByName( pentTarget, STRING(Goal->target) ); + } + } +} + + +//========================================================================= +// Setup the way this Timer/Goal/Item will respawn +void SetupRespawn(CTFGoal *pGoal) +{ + MDEBUG( Warning("Setting up Respawn...\n") ); + + pGoal->m_bAddBonuses = FALSE; + + // Check status of respawn for this goal + // Single Activation, do nothing + if (pGoal->goal_result & TFGR_SINGLE) + { + RemoveGoal(pGoal); + return; + } + + // Timer Goal? + if (pGoal->Classify() == CLASS_TFGOAL_TIMER) + { + InactivateGoal(pGoal); + pGoal->SetThink( &CTFGoal::tfgoal_timer_tick ); + pGoal->SetNextThink( gpGlobals->curtime + pGoal->search_time ); + return; + } + + // Respawn Activation, set up respawn + if (pGoal->wait > 0) + { + pGoal->SetThink(&CTFGoal::DoRespawn); + pGoal->SetNextThink( gpGlobals->curtime + pGoal->wait ); + return; + } + // Permanently active goal? + else if (pGoal->wait == -1) + return; + + // Otherwise, it's a Multiple Goal + InactivateGoal(pGoal); +} + + +//========================================================================= +// Do the results for the Timer/Goal/Item +void DoResults(CTFGoal *Goal, CTFCPlayer *AP, BOOL bAddBonuses) +{ + // Can't activate during PreMatch time + if ( (TFCGameRules()->IsInPreMatch()) && (Goal->Classify() != CLASS_TFGOAL_TIMER) ) + return; + + // Is the goal already activated? + // This check is needed for goals which are being activated by other goals + if (Goal->goal_state == TFGS_ACTIVE) + return; + + // Delayed Activation? + if (Goal->delay_time > 0 && Goal->goal_state != TFGS_DELAYED) + { + MDEBUG( Warning("Delaying Results of %s\n", STRING(Goal->edict()->netname)) ); + + Goal->goal_state = TFGS_DELAYED; + Timer_CreateTimer( Goal, TF_TIMER_DELAYEDGOAL ); + Goal->enemy = AP; + Goal->SetThink( &CTFGoal::DelayedResult ); + Goal->SetNextThink( gpGlobals->curtime + Goal->delay_time ); + Goal->weapon = bAddBonuses; + return; + } + + // If we have a teamcheck entity, use it instead + if ( Goal->owned_by_teamcheck != NULL_STRING ) + Goal->owned_by = GetTeamCheckTeam( STRING(Goal->owned_by_teamcheck) ); + + Goal->goal_state = TFGS_INACTIVE; + + + // if it's a TF goal, removes it's model + if ( Goal->Classify() == CLASS_TFGOAL || Goal->Classify() == CLASS_TFGOAL_TIMER ) + Goal->AddEffects( EF_NODRAW ); + +#ifdef MAP_DEBUG + Warning("---= Activation =---\n"); + if (AP) + Warning("Goal: %s\nAP : %s\n", STRING(Goal->edict()->netname), AP->GetPlayerName()); + else + Warning("Goal: %s\nAP : NONE\n", STRING(Goal->edict()->netname)); + if (bAddBonuses) + Warning(" adding bonuses\n-=================-\n"); + else + Warning("NOT adding bonuses\n-=================-\n"); +#endif + + // Make the sound + if (Goal->noise != NULL_STRING) + { + Goal->EmitSound( STRING( Goal->noise ) ); + } + + // Increase scores + BOOL bDumpScores = FALSE; + int i; + for ( i = 0; i <= 3; i++) + { + if (Goal->increase_team[i] != 0) + { + TeamFortress_TeamIncreaseScore(i + 1, Goal->increase_team[i]); + bDumpScores = TRUE; + } + } + + // Increase the score of the team that owns this entity + if ( ( Goal->increase_team_owned_by != 0 ) && ( Goal->owned_by != 0 ) ) + { + TeamFortress_TeamIncreaseScore( Goal->owned_by, Goal->increase_team_owned_by ); + bDumpScores = TRUE; + } + + // CTF Map support + if (TFCGameRules()->CTF_Map == TRUE && AP != NULL) + { + if (Goal->goal_no == CTF_FLAG1 || Goal->goal_no == CTF_FLAG1 || Goal->goal_no == CTF_DROPOFF1 || Goal->goal_no == CTF_DROPOFF1) + { + // Do Messaging + for ( int i = 1; i <= gpGlobals->maxClients; i++ ) + { + CTFCPlayer *pPlayer = ToTFCPlayer( UTIL_PlayerByIndex( i ) ); + if ( !pPlayer ) + continue; + + if ( (pPlayer->GetTeamNumber() == 2 && Goal->goal_no == CTF_FLAG1) || (pPlayer->GetTeamNumber() == 1 && Goal->goal_no == CTF_FLAG2) ) + { + if (pPlayer == AP) + ClientPrint( pPlayer, HUD_PRINTCENTER, "You got the enemy flag!\n\nReturn to base!"); + else + ClientPrint( pPlayer, HUD_PRINTCENTER, "Your team GOT the ENEMY flag!!"); + } + else if (Goal->goal_no == CTF_FLAG1 || Goal->goal_no == CTF_FLAG2) + { + ClientPrint( pPlayer, HUD_PRINTCENTER, "Your flag has been TAKEN!!"); + } + else if ( (pPlayer->GetTeamNumber() == 2 && Goal->goal_no == CTF_DROPOFF1) || (pPlayer->GetTeamNumber() == 1 && Goal->goal_no == CTF_DROPOFF2) ) + { + if (pPlayer == AP) + ClientPrint( pPlayer, HUD_PRINTCENTER, "You CAPTURED the FLAG!!"); + else + ClientPrint( pPlayer, HUD_PRINTCENTER, "Your flag was CAPTURED!!"); + } + else if (Goal->goal_no == CTF_DROPOFF1 || Goal->goal_no == CTF_DROPOFF2) + { + ClientPrint( pPlayer, HUD_PRINTCENTER, "Your team CAPTURED the flag!!"); + } + } + + const char *pTeamName = "SPECTATOR"; + if ( AP->GetTeamNumber() != 0 ) + { + CTeam *pTeam = GetGlobalTeam( AP->GetTeamNumber() ); + if ( pTeam ) + pTeamName = pTeam->GetName(); + } + + // Console Prints + switch(Goal->goal_no) + { + case CTF_FLAG1: + UTIL_ClientPrintAll( HUD_PRINTNOTIFY, UTIL_VarArgs("%s GOT the BLUE flag!", AP->GetPlayerName()) ); + + UTIL_LogPrintf("\"%s<%i><%s>\" triggered \"Stole_Blue_Flag\"\n", + AP->GetPlayerName(), + AP->GetUserID(), + pTeamName ); + + AP->m_Shared.AddItemFlags( IT_KEY1 ); + break; + case CTF_FLAG2: + UTIL_ClientPrintAll( HUD_PRINTNOTIFY, UTIL_VarArgs("%s GOT the RED flag!", AP->GetPlayerName()) ); + + UTIL_LogPrintf("\"%s<%i><%s>\" triggered \"Stole_Red_Flag\"\n", + AP->GetPlayerName(), + AP->GetUserID(), + pTeamName ); + + AP->m_Shared.AddItemFlags( IT_KEY2 ); + break; + case CTF_DROPOFF1: + UTIL_ClientPrintAll( HUD_PRINTNOTIFY, UTIL_VarArgs("%s CAPTURED the RED flag!", AP->GetPlayerName()) ); + + UTIL_LogPrintf("\"%s<%i><%s>\" triggered \"Captured_Red_Flag\"\n", + AP->GetPlayerName(), + AP->GetUserID(), + pTeamName ); + + AP->m_Shared.RemoveItemFlags( IT_KEY2 ); + break; + case CTF_DROPOFF2: + UTIL_ClientPrintAll( HUD_PRINTNOTIFY, UTIL_VarArgs("%s CAPTURED the BLUE flag!", AP->GetPlayerName()) ); + + UTIL_LogPrintf("\"%s<%i><%s>\" triggered \"Captured_Blue_Flag\"\n", + AP->GetPlayerName(), + AP->GetUserID(), + pTeamName ); + + AP->m_Shared.RemoveItemFlags( IT_KEY1 ); + break; + default: + break; + } + } + } + + // Do Spawnpoint work before cycling players, so Forced respawn players work correctly. + if (Goal->remove_spawngroup != 0) + { + // Find all goals + //TFCTODO: I think FindEntityByClassname will do the same thing. + //CBaseEntity *pEnt = UTIL_FindEntityByString( NULL, "netname", "info_player_teamspawn" ); + CBaseEntity *pEnt = gEntList.FindEntityByClassname( NULL, "info_player_teamspawn" ); + while ( pEnt ) + { + CTFSpawn *pSpawn = dynamic_cast<CTFSpawn*>( pEnt ); + if ( pSpawn ) + { + if ( pSpawn->group_no == Goal->remove_spawngroup) + InactivateSpawn(pSpawn); + } + + pEnt = gEntList.FindEntityByClassname( pEnt, "info_player_teamspawn" ); + } + } + + if (Goal->restore_spawngroup != 0) + { + // Find all goals + //TFCTODO: I think FindEntityByClassname will do the same thing. + //CBaseEntity *pEnt = UTIL_FindEntityByString( NULL, "netname", "info_player_teamspawn" ); + CBaseEntity *pEnt = gEntList.FindEntityByClassname( NULL, "info_player_teamspawn" ); + while ( pEnt ) + { + CTFSpawn *pSpawn = dynamic_cast<CTFSpawn*>( pEnt ); + if ( pSpawn ) + { + if (pSpawn->group_no == Goal->restore_spawngroup) + ActivateSpawn(pSpawn); + } + + pEnt = gEntList.FindEntityByClassname( pEnt, "info_player_teamspawn" ); + } + } + + // Go through all the players and do any results + if ( Goal->broadcast != NULL_STRING && TFCGameRules()->CTF_Map == FALSE ) + { + UTIL_LogPrintf("World triggered \"%s\"\n", STRING(Goal->broadcast) ); + } + if ( Goal->netname_broadcast != NULL_STRING && TFCGameRules()->CTF_Map == FALSE && AP != NULL ) + { + UTIL_LogPrintf("\"%s<%i><%s>\" triggered \"%s\"\n", + AP->GetPlayerName(), + AP->GetUserID(), + ( AP->GetTeamNumber() != 0 ) ? GetTeamName( AP->GetTeamNumber() ) : "SPECTATOR", + STRING(Goal->netname_broadcast) ); + } + + BOOL bGotOne = FALSE; + for ( i = 1; i <= gpGlobals->maxClients; i++ ) + { + CTFCPlayer *pPlayer = ToTFCPlayer( UTIL_PlayerByIndex( i ) ); + if ( !pPlayer ) + continue; + + // Centerprinting + if (Goal->broadcast != NULL_STRING && TFCGameRules()->CTF_Map == FALSE) + UTIL_ShowMessage( STRING(Goal->broadcast), pPlayer ); + if (Goal->netname_broadcast != NULL_STRING && TFCGameRules()->CTF_Map == FALSE && AP != NULL) + ClientPrint( pPlayer, HUD_PRINTNOTIFY, STRING(Goal->netname_broadcast), AP->GetPlayerName() ); + // Old printing + if (Goal->org_broadcast != NULL_STRING && TFCGameRules()->CTF_Map == FALSE) + ClientPrint( pPlayer, HUD_PRINTCENTER, STRING(Goal->org_broadcast) ); + + // VOX + if (Goal->speak != NULL_STRING) + pPlayer->ClientHearVox( STRING(Goal->speak) ); + + if (AP == pPlayer) + { + // Spawnpoints handle their own printing elsewhere + if (Goal->message != NULL_STRING && Goal->Classify() != CLASS_TFSPAWN) + UTIL_ShowMessage( STRING(Goal->message), pPlayer ); + if (Goal->org_message != NULL_STRING && Goal->Classify() != CLASS_TFSPAWN) + ClientPrint( pPlayer, HUD_PRINTCENTER, STRING(Goal->org_message) ); + + // VOX + if (Goal->AP_speak != NULL_STRING) + pPlayer->ClientHearVox( STRING(Goal->AP_speak) ); + } + else if ( (AP != NULL) && (AP->GetTeamNumber() == pPlayer->GetTeamNumber()) ) + { + // Text Printing + if (Goal->owners_team_broadcast != NULL_STRING && pPlayer->GetTeamNumber() == Goal->owned_by) + UTIL_ShowMessage( STRING(Goal->owners_team_broadcast), pPlayer ); + else if (Goal->non_owners_team_broadcast != NULL_STRING && pPlayer->GetTeamNumber() != Goal->owned_by) + UTIL_ShowMessage( STRING(Goal->non_owners_team_broadcast), pPlayer ); + else if (Goal->team_broadcast!= NULL_STRING ) + UTIL_ShowMessage( STRING(Goal->team_broadcast), pPlayer ); + // Old Text Printing + if (Goal->org_owners_team_broadcast != NULL_STRING && pPlayer->GetTeamNumber() == Goal->owned_by) + ClientPrint( pPlayer, HUD_PRINTCENTER, STRING(Goal->org_owners_team_broadcast) ); + else if (Goal->org_non_owners_team_broadcast != NULL_STRING && pPlayer->GetTeamNumber() != Goal->owned_by) + ClientPrint( pPlayer, HUD_PRINTCENTER, STRING(Goal->org_non_owners_team_broadcast) ); + else if (Goal->org_team_broadcast!= NULL_STRING ) + ClientPrint( pPlayer, HUD_PRINTCENTER, STRING(Goal->org_team_broadcast) ); + + + // VOX + if (Goal->owners_team_speak != NULL_STRING && pPlayer->GetTeamNumber() == Goal->owned_by) + pPlayer->ClientHearVox( STRING(Goal->owners_team_speak) ); + else if (Goal->non_owners_team_speak != NULL_STRING && pPlayer->GetTeamNumber() != Goal->owned_by) + pPlayer->ClientHearVox( STRING(Goal->non_owners_team_speak) ); + else if (Goal->team_speak!= NULL_STRING ) + pPlayer->ClientHearVox( STRING(Goal->team_speak) ); + + if (Goal->netname_owners_team_broadcast != NULL_STRING && pPlayer->GetTeamNumber() == Goal->owned_by) + ClientPrint( pPlayer, HUD_PRINTNOTIFY, STRING(Goal->netname_owners_team_broadcast), AP->GetPlayerName() ); + else if (Goal->netname_team_broadcast!= NULL_STRING ) + ClientPrint( pPlayer, HUD_PRINTNOTIFY, STRING(Goal->netname_team_broadcast), AP->GetPlayerName() ); + } + else + { + // Text Printing + if (Goal->owners_team_broadcast != NULL_STRING && pPlayer->GetTeamNumber() == Goal->owned_by) + UTIL_ShowMessage( STRING(Goal->owners_team_broadcast), pPlayer ); + else if (Goal->non_owners_team_broadcast != NULL_STRING && pPlayer->GetTeamNumber() != Goal->owned_by) + UTIL_ShowMessage( STRING(Goal->non_owners_team_broadcast), pPlayer ); + else if (Goal->non_team_broadcast != NULL_STRING ) + UTIL_ShowMessage( STRING(Goal->non_team_broadcast), pPlayer ); + // Old Text Printing + if (Goal->org_owners_team_broadcast != NULL_STRING && pPlayer->GetTeamNumber() == Goal->owned_by) + ClientPrint( pPlayer, HUD_PRINTCENTER, STRING(Goal->org_owners_team_broadcast) ); + else if (Goal->org_non_owners_team_broadcast != NULL_STRING && pPlayer->GetTeamNumber() != Goal->owned_by) + ClientPrint( pPlayer, HUD_PRINTCENTER, STRING(Goal->org_non_owners_team_broadcast) ); + else if (Goal->org_non_team_broadcast!= NULL_STRING ) + ClientPrint( pPlayer, HUD_PRINTCENTER, STRING(Goal->org_non_team_broadcast) ); + + // VOX + if (Goal->owners_team_speak != NULL_STRING && pPlayer->GetTeamNumber() == Goal->owned_by) + pPlayer->ClientHearVox( STRING(Goal->owners_team_speak) ); + else if (Goal->non_owners_team_speak != NULL_STRING && pPlayer->GetTeamNumber() != Goal->owned_by) + pPlayer->ClientHearVox( STRING(Goal->non_owners_team_speak) ); + else if (Goal->non_team_speak != NULL_STRING ) + pPlayer->ClientHearVox( STRING(Goal->non_team_speak) ); + + if (Goal->netname_owners_team_broadcast != NULL_STRING && pPlayer->GetTeamNumber() == Goal->owned_by && AP != NULL) + ClientPrint( pPlayer, HUD_PRINTNOTIFY, STRING(Goal->netname_owners_team_broadcast), AP->GetPlayerName() ); + else if (Goal->netname_non_team_broadcast != NULL_STRING && AP != NULL) + ClientPrint( pPlayer, HUD_PRINTNOTIFY, STRING(Goal->netname_non_team_broadcast), AP->GetPlayerName() ); + } + + if (IsAffectedBy(Goal, pPlayer, AP)) + { + // If its a Timer Goal, see if it needs to check Criteria again + if (Goal->search_time != 0 && Goal->goal_effects & TFGE_TIMER_CHECK_AP) + { + if (APMeetsCriteria(Goal, pPlayer)) + { + Apply_Results(Goal, (CTFCPlayer*)pPlayer, AP, bAddBonuses); + bGotOne = TRUE; + } + } + else + { + Apply_Results(Goal, (CTFCPlayer*)pPlayer, AP, bAddBonuses); + bGotOne = TRUE; + } + } + } + +#ifdef MAP_DEBUG + if (bGotOne == FALSE) + Warning("NO PLAYERS AFFECTED\n"); +#endif + + // Goal is now active + // Items are not set to active. They handle their modes. + if ( Goal->Classify() == CLASS_TFGOAL_TIMER || Goal->Classify() == CLASS_TFGOAL ) + Goal->goal_state = TFGS_ACTIVE; + + // EndGame checking + if (Goal->goal_result & TFGR_ENDGAME) + { + // Display Long TeamScores to everyone + TeamFortress_TeamShowScores(TRUE, NULL); + + if ( g_pGameRules->IsMultiplayer() ) + TFCGameRules()->TFCGoToIntermission(); + return; + } + + // EndRound checking + if (Goal->m_flEndRoundTime) + EndRound( Goal ); + + // Do Goal Group checking + DoGroupWork(Goal, AP); + + // Do Goal checking + DoGoalWork(Goal, AP); + + // Do Quake Trigger actions (Standard entities use SUB_UseTargets()) + if ( Goal->Classify() == CLASS_TFGOAL_TIMER || Goal->Classify() == CLASS_TFGOAL_ITEM || Goal->Classify() == CLASS_TFGOAL || Goal->Classify() == CLASS_TFSPAWN || Goal->do_triggerwork ) + DoTriggerWork(Goal, AP); + + // Setup for Respawn + // Items, Triggers, and Spawnpoints do their own respawn work + if ( Goal->Classify() == CLASS_TFGOAL || Goal->Classify() == CLASS_TFGOAL_TIMER ) + SetupRespawn(Goal); +} + + +//========================================================================= +// Check to see if the Goal should Activate. Handle Else Goals if not. +// If it does activate, Do the Results. Return true if the Goal activated. +bool ActivateDoResults(CTFGoal *Goal, CTFCPlayer *AP, CTFGoal *ActivatingGoal) +{ + // Check Goal activation. This func handles Else Goals. + if ( !ActivationSucceeded(Goal, AP, ActivatingGoal) ) + return false; + + // Do the Results. + if (ActivatingGoal == Goal || Goal->m_bAddBonuses == true) + DoResults(Goal, AP, true); + else if (ActivatingGoal != NULL) + DoResults(Goal, AP, (ActivatingGoal->goal_result & TFGR_ADD_BONUSES)); + else + DoResults(Goal, AP, 0); + + return true; +} + + +//========================================================================= +// Return true if the Goal should activate, and handle Else Goals +bool ActivationSucceeded(CTFGoal *Goal, CTFCPlayer *AP, CTFGoal *ActivatingGoal) +{ + // Can't activate during PreMatch time, except for timers + if ( (TFCGameRules()->IsInPreMatch()) && (Goal->Classify() != CLASS_TFGOAL_TIMER) ) + return false; + + // If activation fails, try and activate the Else Goal + if ( !ShouldActivate(Goal, AP) ) + { + // If an else goal should be activated, activate it + if (Goal->else_goal != 0) + { + MDEBUG( Warning(" Else Goal.\n") ); + + CTFGoal *pElseGoal = Findgoal(Goal->else_goal); + if (pElseGoal) + ActivateDoResults(pElseGoal, AP, Goal); + } + + return false; + } + + return true; +} + + +// ---------------------------------------------------------------------------------------- // +// CTFBaseItem existence. +// ---------------------------------------------------------------------------------------- // + +//=========================================== +// Check whether this entity should exist at this skill +bool CTFBaseItem::CheckExistence() +{ + if (ex_skill_min == -1 && g_iSkillLevel < 0) + return FALSE; + else if (ex_skill_max == -1 && g_iSkillLevel > 0) + return FALSE; + + if ( (ex_skill_min != 0) && (ex_skill_min != -1) && (g_iSkillLevel < ex_skill_min) ) + return FALSE; + else if ( (ex_skill_max != 0) && (ex_skill_max != -1) && (g_iSkillLevel > ex_skill_max) ) + return FALSE; + + return TRUE; +} + + +// ---------------------------------------------------------------------------------------- // +// CTFGoal implementation. +// ---------------------------------------------------------------------------------------- // + +LINK_ENTITY_TO_CLASS(info_tfgoal, CTFGoal); + +BEGIN_DATADESC( CTFGoal ) + DEFINE_FUNCTION( PlaceGoal ), + DEFINE_FUNCTION( DelayedResult ) +END_DATADESC() + + + +//=========================================== +// TF Goal spawn +void CTFGoal::Spawn( void ) +{ + if (CheckExistence() == false) + { + UTIL_Remove(this); + return; + } + + // Graphic + string_t modelName = GetModelName(); + if ( modelName != NULL_STRING ) + { + // Brush Models need to be invisible + const char *pModel = STRING( modelName ); + if (pModel[0] == '*') + AddEffects( EF_NODRAW ); + } + +#ifdef TFCTODO + // Activation sound + if (pev->noise) + PRECACHE_SOUND( (char*)STRING(pev->noise) ); + + // For the powerups + PRECACHE_SOUND("items/protect.wav"); + PRECACHE_SOUND("items/protect2.wav"); + PRECACHE_SOUND("items/protect3.wav"); + PRECACHE_SOUND("FVox/HEV_logon.wav"); + PRECACHE_SOUND("FVox/hev_shutdown.wav"); + PRECACHE_SOUND("items/inv1.wav"); + PRECACHE_SOUND("items/inv2.wav"); + PRECACHE_SOUND("items/inv3.wav"); + PRECACHE_SOUND("items/damage.wav"); + PRECACHE_SOUND("items/damage2.wav"); + PRECACHE_SOUND("items/damage3.wav"); +#endif + + // Set initial states + AddSolidFlags( FSOLID_TRIGGER ); + if (goal_state == 0) + goal_state = TFGS_INACTIVE; + + // Set Size + if (goal_min != vec3_origin && goal_max != vec3_origin) + UTIL_SetSize( this, goal_min, goal_max ); + + StartGoal(); +} + + +//========================================================================= +// Respawn the goal +void CTFGoal::DoRespawn() +{ + RestoreGoal(this); + InactivateGoal(this); +} + + +//========================================================================= +// Timer goal tick +void CTFGoal::tfgoal_timer_tick() +{ + // Check criteria + if (goal_state != TFGS_REMOVED) + { + #ifdef MAP_DEBUG + Warning("==========================\n"); + Warning("Timer Tick for: %s\nChecking Criteria...", GetEntityName().ToCStr()); + #endif + + // Timers don't fire during prematch. + // Instead, they setup to fire the correct amount of time past the prematch + if ( TFCGameRules()->IsInPreMatch() ) + { + MDEBUG( Warning("\n PREMATCH IS ON. DELAYING UNTIL AFTER PREMATCH.\n") ); + + SetThink( &CTFGoal::tfgoal_timer_tick ); + SetNextThink( TFCGameRules()->GetPreMatchEndTime() + search_time ); + return; + } + + if (APMeetsCriteria(this, NULL)) + { + DoResults(this, NULL, TRUE); + } + else + { + MDEBUG( Warning("\n") ); + SetThink( &CTFGoal::tfgoal_timer_tick ); + SetNextThink( gpGlobals->curtime + search_time ); + } + } +} + + +//========================================================================= +// Use (Triggered) function for Goals +void CTFGoal::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + // If the Activator isn't a player, pretend there isn't an activator + if (pActivator && !pActivator->IsPlayer()) + { + pActivator = NULL; + } + + CTFGoal *pCallerGoal = dynamic_cast<CTFGoal*>( pCaller ); + if ( !pCallerGoal ) + { + // Need to rethink some stuff if this can be called by entities that aren't CTFGoals. + Assert( false ); + return; + } + + // Goals are only activatable by players + if (!pActivator || pActivator->IsPlayer()) + { + // Force it to add bonuses + m_bAddBonuses = true; + ActivateDoResults(this, (CTFCPlayer*)pActivator, pCallerGoal); + } +} + + +//=========================================== +// Make Goal's more easy to touch +void CTFGoal::SetObjectCollisionBox( void ) +{ + const char *pModel = STRING( GetModelName() ); + if (pModel[0] != '*') + { + Vector vMins = WorldAlignMins() + Vector(-24, -24, 0); + Vector vMaxs = WorldAlignMaxs() + Vector(24, 24, 16); + SetCollisionBounds( vMins, vMaxs ); + } + else + { +// Do we even need to do this? The bmodel should be setup correctly at this point anyway. +#ifdef TFCTODO + // Ripped from ::SetObjectCollisionBox + float max, v; + int i; + + max = 0; + for (i=0 ; i<3 ; i++) + { + v = fabs( (( float * )pev->mins )[i]); + if (v > max) + max = v; + v = fabs( (( float * )pev->maxs )[i]); + if (v > max) + max = v; + } + for (i=0 ; i<3 ; i++) + { + ((float *)pev->absmin)[i] = (( float * )GetAbsOrigin())[i] - max; + ((float *)pev->absmax)[i] = (( float * )GetAbsOrigin())[i] + max; + } + + pev->absmin.x -= 1; + pev->absmin.y -= 1; + pev->absmin.z -= 1; + pev->absmax.x += 1; + pev->absmax.y += 1; + pev->absmax.z += 1; +#endif + } +} + +//=========================================== +// Start the Goal +void CTFGoal::StartGoal( void ) +{ + m_bAddBonuses = false; + SetThink( &CTFGoal::PlaceGoal ); + SetNextThink( gpGlobals->curtime + 0.2 ); // goals start after other solids + + if (goal_state == TFGS_REMOVED) + RemoveGoal(this); +}; + +//=========================================== +// Sets up the Goal's first thoughts +void CTFGoal::PlaceGoal( void ) +{ + if ( FClassnameIs(this, "info_tfgoal_timer") ) + { + // Set up the next Timer Tick + SetThink( &CTFGoal::tfgoal_timer_tick ); + SetNextThink( gpGlobals->curtime + search_time ); + } + else + { + // Only give touch functions to goals that can be activated by touch + if (goal_activation & TFGA_TOUCH) + SetTouch( &CTFGoal::tfgoal_touch ); + } + + // So searches for this goal work later on + Assert( stricmp( GetClassname(), "info_tfgoal" ) == 0 ); + + // Drop to ground + if (goal_activation & TFGA_DROPTOGROUND) + { +// Is this right? +#ifdef TFCTODO + SetMoveType( MOVETYPE_TOSS ); +#else + SetMoveType( MOVETYPE_FLYGRAVITY ); +#endif + SetAbsOrigin( GetAbsOrigin() + Vector( 0, 0, 6 ) ); + + if ( UTIL_DropToFloor(this, MASK_SOLID) == 0) + { + Error("TF Goal %s fell out of level at %f,%f,%f", GetEntityName(), GetAbsOrigin().x, GetAbsOrigin().y, GetAbsOrigin().z); + UTIL_Remove( this ); + return; + } + } + + SetMoveType( MOVETYPE_NONE ); + SetAbsVelocity( Vector( 0, 0, 0 ) ); + oldorigin = GetAbsOrigin(); // So we can return it later +} + + +// Touch function for Goals +void CTFGoal::tfgoal_touch( CBaseEntity *pOther ) +{ + // Can't touch during PreMatch time + if ( TFCGameRules()->IsInPreMatch() ) + return; + // If it is not activated in by the player's touch, return + if (!(goal_activation & TFGA_TOUCH)) + return; + // Only activatable by a player + if (!pOther->IsPlayer()) + return; + if ( pOther->IsAlive() == FALSE ) + return; + + // If it's already active, don't bother + if (goal_state == TFGS_ACTIVE) + { + MDEBUG( Warning("Goal already active. aborting touch.\n") ); + return; + } + + // CTF Hack to make sure the key is in place. Like the rest of the CTF_Map stuff, + // it's not needed... the base scripting could handle it all. + if (TFCGameRules()->CTF_Map) + { + if ((goal_no == CTF_DROPOFF1) && (pOther->GetTeamNumber() == 1)) + { + CTFGoalItem *pFlag = Finditem(CTF_FLAG1); + if ((pFlag->goal_state == TFGS_ACTIVE) || (pFlag->GetAbsOrigin() != pFlag->oldorigin)) + return; + } + if ((goal_no == CTF_DROPOFF2) && (pOther->GetTeamNumber() == 2)) + { + CTFGoalItem *pFlag = Finditem(CTF_FLAG2); + if ((pFlag->goal_state == TFGS_ACTIVE) || (pFlag->GetAbsOrigin() != pFlag->oldorigin)) + return; + } + } + + ActivateDoResults(this, ToTFCPlayer( pOther ), this); +} + +//========================================================================= +// Handles Delayed Activation of Goals +void CTFGoal::DelayedResult() +{ + CBaseEntity *pEnemy = enemy; + if (goal_state == TFGS_DELAYED) + DoResults(this, ToTFCPlayer( pEnemy ), weapon); +} + + +// ---------------------------------------------------------------------------------------- // +// CTFGoalItem implementation. +// ---------------------------------------------------------------------------------------- // + +LINK_ENTITY_TO_CLASS(item_tfgoal, CTFGoalItem); + + +//========================================================================= +// Spawn a goalitem entity +void CTFGoalItem::Spawn( void ) +{ + if (CheckExistence() == false) + { + UTIL_Remove(this); + return; + } + + // Set this in case they used abbreviations + Assert( stricmp( GetClassname(), "item_tfgoal" ) == 0 ); + + // Graphic + if ( GetModelName() != NULL_STRING ) + { + // Setup animations + SetSequence( LookupSequence( "not_carried" ) ); + if ( GetSequence() != -1 ) + { + ResetSequenceInfo(); + m_flCycle = 0; + } + } + +#ifdef TFCTODO + // Respawn sound + PRECACHE_SOUND("items/itembk2.wav"); + + // Activation sound + if (pev->noise) + PRECACHE_SOUND( (char*)STRING(pev->noise) ); + + if (!(pev->netname)) + pev->netname = MAKE_STRING("goalitem"); +#endif + + if (goal_state == 0) + goal_state = TFGS_INACTIVE; + + // Set initial solidity + if (goal_activation & TFGI_SOLID) + { + SetSolid( SOLID_BBOX ); + // Solid goalitems need a bbox + if (goal_min == vec3_origin) + goal_min = Vector(-16, -16, -24); + if (goal_max == vec3_origin) + goal_max = Vector(16, 16, 32); + } + else + { + SetSolidFlags( FSOLID_TRIGGER ); + } + + if (drop_time <= 0) + drop_time = 60; + + // Set Size + UTIL_SetSize(this, goal_min, goal_max); + + SetTouch( &CTFGoalItem::item_tfgoal_touch ); + StartItem(); +}; + +//========================================================================= +// Start the Goal Item +void CTFGoalItem::StartItem( void ) +{ + SetThink( &CTFGoalItem::PlaceItem ); + SetNextThink( gpGlobals->curtime + 0.2 ); // items start after other solids + + if (goal_state == TFGS_REMOVED) + RemoveGoal(this); +} + +//=========================================== +// Place the Goal Item +void CTFGoalItem::PlaceItem( void ) +{ + static int item_list_bit = 1; // used to determine what the bit of each new GoalItem will be. + + SetAbsVelocity( vec3_origin ); + + // Drop to ground + if (goal_activation & TFGA_DROPTOGROUND) + { +#ifdef TFCTODO + pev->movetype = MOVETYPE_TOSS; +#else + SetMoveType( MOVETYPE_FLYGRAVITY ); +#endif + SetAbsOrigin( GetAbsOrigin() + Vector( 0, 0, 6 ) ); + + if ( UTIL_DropToFloor( this, MASK_SOLID ) == 0) + { + Error("TF GoalItem %s fell out of level at %f,%f,%f", STRING( GetEntityName() ), GetAbsOrigin().x, GetAbsOrigin().y, GetAbsOrigin().z); + UTIL_Remove( this ); + return; + } + } + + SetMoveType( MOVETYPE_NONE ); + oldorigin = GetAbsOrigin(); // So we can return it later + + if (goal_activation & TFGI_ITEMGLOWS) + { + m_nRenderFX = kRenderFxGlowShell; + + // If we have a teamcheck entity, use it instead + if ( owned_by_teamcheck != NULL_STRING ) + owned_by = GetTeamCheckTeam( STRING(owned_by_teamcheck) ); + + if (owned_by > 0 && owned_by <= 4) + m_clrRender = Vector255ToRGBColor( rgbcolors[owned_by] ); + else + m_clrRender = Vector255ToRGBColor( rgbcolors[0] ); + + SetRenderColorA( 100 ); // Shell size + } + + // Set the item bit + item_list = item_list_bit; + item_list_bit *= 2; +} + + +// Touch function for the goalitem entity +void CTFGoalItem::item_tfgoal_touch( CBaseEntity *pOther ) +{ + if (!pOther->IsPlayer()) + return; + if ( pOther->IsAlive() == FALSE ) + return; + // Can't touch during PreMatch time + if ( TFCGameRules()->IsInPreMatch() ) + return; + + // Hack to prevent feigning spies from repicking up flags + CTFCPlayer *pPlayer = dynamic_cast<CTFCPlayer*>( pOther ); + if (pPlayer->is_feigning) + return; + + // only let players have one replacement_model goal item at a time + if ( replacement_model != NULL_STRING ) + { + if ( pPlayer->replacement_model != NULL_STRING ) + return; + } + + // Prevent the dropping player from picking it up for longer + if (enemy.Get() && m_flDroppedAt != 0) + { + if ( (enemy == pOther) && m_flDroppedAt + 5 > gpGlobals->curtime ) + return; + } + m_flDroppedAt = 0; + + ASSERT( pOther != GetOwnerEntity() ); // There is no way in hell this should ever happen, and yet it still does. + + // Prevent picking up flags through thin walls + trace_t tr; + UTIL_TraceLine ( WorldSpaceCenter(), pOther->WorldSpaceCenter(), MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr ); + if ( tr.fraction != 1.0 && tr.m_pEnt != pOther ) + return; + + // CTF Hack to return your key. + // Should use the builtin support now. + if (TFCGameRules()->CTF_Map == TRUE) + { + // Flag not at home? + if (GetAbsOrigin() != oldorigin) + { + if (GetTeamNumber() == 1) + { + if (goal_no == CTF_FLAG1) + { + UTIL_ClientPrintAll( HUD_PRINTNOTIFY, UTIL_VarArgs("%s RETURNED the BLUE flag!", pPlayer->GetPlayerName()) ); + UTIL_LogPrintf("\"%s<%i><%s>\" triggered \"Returned_Blue_Flag\"\n", + pPlayer->GetPlayerName(), + pPlayer->GetUserID(), + GetTeamName( pOther->GetTeamNumber() ) ); + } + else + { + UTIL_ClientPrintAll( HUD_PRINTNOTIFY, UTIL_VarArgs("%s RETURNED the RED flag!", pPlayer->GetPlayerName()) ); + UTIL_LogPrintf("\"%s<%i><%s>\" triggered \"Returned_Red_Flag\"\n", + pPlayer->GetPlayerName(), + pPlayer->GetUserID(), + GetTeamName( pOther->GetTeamNumber() ) ); + } + + for ( int i = 1; i <= gpGlobals->maxClients; i++ ) + { + CBasePlayer *pPlayer = UTIL_PlayerByIndex( i ); + if ( !pPlayer ) + continue; + + if (pPlayer->GetTeamNumber() == 1 && goal_no == CTF_FLAG1) + ClientPrint( pPlayer, HUD_PRINTCENTER, "Your flag was RETURNED!!\n"); + else if (pPlayer->GetTeamNumber() != 1 && goal_no == CTF_FLAG1) + ClientPrint( pPlayer, HUD_PRINTCENTER, "The ENEMY flag was RETURNED!!\n"); + else if (pPlayer->GetTeamNumber() == 2 && goal_no == CTF_FLAG2) + ClientPrint( pPlayer, HUD_PRINTCENTER, "Your flag was RETURNED!!\n"); + else if (pPlayer->GetTeamNumber() == 2 && goal_no == CTF_FLAG1) + ClientPrint( pPlayer, HUD_PRINTCENTER, "The ENEMY flag was RETURNED!!\n"); + } + + goal_state = TFGS_INACTIVE; + SetSolidFlags( FSOLID_TRIGGER ); + SetTouch( &CTFGoalItem::item_tfgoal_touch ); + SetAbsOrigin( oldorigin ); + + EmitSound( "GoalItem.Touch" ); + //EMIT_SOUND_DYN( ENT(pev), CHAN_WEAPON, "items/itembk2.wav", 1, ATTN_NORM, 0, 150 ); + return; + } + } + else + { + // Ignore touches to our own flag when it's at home + if (pOther->GetTeamNumber() == 1 && goal_no == CTF_FLAG1) + return; + if (pOther->GetTeamNumber() == 2 && goal_no == CTF_FLAG2) + return; + } + } + + // Activate. Handles Else Goals if it fails. + if ( ActivationSucceeded( this, ToTFCPlayer( pOther ), NULL) ) + { + // Give it to the player + tfgoalitem_GiveToPlayer(this, ToTFCPlayer( pOther ), this); + // It may have killed the player, so check: + if (pOther->GetHealth() > 0) + goal_state = TFGS_ACTIVE; + } +} + + +//========================================================================= +// Throw the item into the air at the specified position +void CTFGoalItem::DoDrop( Vector vecOrigin ) +{ + SetAbsOrigin( vecOrigin ); + + StopFollowingEntity(); + SetMoveType( MOVETYPE_FLYGRAVITY ); + + // Just drop it vertically first time to prevent it falling through walls too often + Vector vVel = GetAbsVelocity(); + vVel.z = 400; + if (redrop_count > 1) + { + // Second and third drops try pushing it in other directions + vVel.x = RandomFloat(-50, 50); + vVel.y = RandomFloat(-50, 50); + } + SetAbsVelocity( vVel ); + + goal_state = TFGS_INACTIVE; + SetAbsAngles( QAngle( 0, 0, 0 ) ); + if (goal_activation & TFGI_SOLID) + { + SetSolid( SOLID_BBOX ); + } + else + { + SetSolid( SOLID_BBOX ); + SetSolidFlags( FSOLID_TRIGGER ); + } + + RemoveEffects( EF_NODRAW ); + + UTIL_SetSize(this, goal_min, goal_max); + + redrop_count++; + + SetThink( &CTFGoalItem::tfgoalitem_dropthink ); // give it five seconds + SetNextThink( gpGlobals->curtime + 5.0 ); // and then find where it ended up +} + + +//========================================================================= +// Set the GoalItems touch func +void CTFGoalItem::tfgoalitem_droptouch() +{ + SetTouch( &CTFGoalItem::item_tfgoal_touch ); + SetThink( &CTFGoalItem::tfgoalitem_dropthink ); // give it five seconds since it was dropped + SetNextThink( gpGlobals->curtime + 4.25 ); // and then find where it ended up +} + + +//========================================================================= +// A quick check to make sure the items is not in a wall +void CTFGoalItem::tfgoalitem_dropthink() +{ + MDEBUG( Msg( "DropThink for %s\n", STRING(pev->netname)) ); + + StopFollowingEntity(); + SetMoveType( MOVETYPE_FLYGRAVITY ); + + if (drop_time != 0) + { + int iEnviron = UTIL_PointContents( GetAbsOrigin() ); + + if (iEnviron == CONTENTS_SLIME) + { + SetNextThink( gpGlobals->curtime + (drop_time / 4) ); + } +#ifdef TFCTODO // CONTENTS_LAVA and CONTENTS_SKY don't exist in src. + else if (iEnviron == CONTENTS_LAVA) + { + SetNextThink( gpGlobals->curtime + 5 ); + } + else if (iEnviron == CONTENTS_SOLID || iEnviron == CONTENTS_SKY) +#else + else if (iEnviron == CONTENTS_SOLID) +#endif + { + // Its out of the world + // Retry a drop from the original position 3 times + if (redrop_count < 3) + { + // Retry the Drop + DoDrop( redrop_origin ); + return; + } + else + { + // Fourth time round, just return it + SetNextThink( gpGlobals->curtime + 2 ); + } + } + else + { + SetNextThink( gpGlobals->curtime + drop_time ); + } + + SetThink( &CTFGoalItem::tfgoalitem_remove ); + } +} + + +//========================================================================= +// Remove the item, or Return it if needed +void CTFGoalItem::tfgoalitem_remove() +{ + MDEBUG( Msg( "RemoveItem for %s...", STRING(pev->netname)) ); + + // Has someone picked it up? + if (goal_state == TFGS_ACTIVE) + { + MDEBUG( Msg( "Item picked up, exiting.\n") ); + return; + } + + // Should it be returned? + if (goal_activation & TFGI_RETURN_REMOVE) + { + MDEBUG( Msg( "Returned.\n") ); + + CTimer *pTimer = Timer_CreateTimer( this, TF_TIMER_RETURNITEM ); + pTimer->weapon = GI_RET_TIME; + pTimer->m_flNextThink = gpGlobals->curtime + 0.1; + //pTimer->SetThink( &CBaseEntity::ReturnItem ); this is done by CreateTimer code now. + return; + } + + MDEBUG( Msg( "Removed.\n") ); + UTIL_Remove(this); +} + + +// ---------------------------------------------------------------------------------------------------- // +// CTFSpawn implementation. +// ---------------------------------------------------------------------------------------------------- // + +LINK_ENTITY_TO_CLASS(info_player_teamspawn, CTFSpawn); + +//=========================================== +void CTFSpawn::Spawn( void ) +{ + if (CheckExistence() == FALSE) + { + UTIL_Remove(this); + return; + } + + // Team spawnpoints must have a team associated with them + if ( (GetTeamNumber() <= 0 || GetTeamNumber() >= 5) && teamcheck == NULL_STRING) + { + Warning("Teamspawnpoint with an invalid GetTeamNumber() of %d\n", GetTeamNumber()); + return; + } + + + // find the highest team number + + if (number_of_teams < GetTeamNumber()) + number_of_teams = GetTeamNumber(); + + // Save out the info_player_teamspawn + Assert( stricmp( GetClassname(), "info_player_teamspawn" ) == 0 ); +#ifdef TFCTODO + pev->netname = pev->classname = MAKE_STRING( ); +#endif +} + +void CTFSpawn::Activate( void ) +{ + m_pTeamCheck = NULL; + + // Find the team check entity + CBaseEntity *pTarget = gEntList.FindEntityByName( NULL, STRING(teamcheck) ); + + if ( pTarget ) + { + if ( !strcmp( pTarget->GetClassname(), "info_tf_teamcheck" ) ) + m_pTeamCheck = pTarget; + } +} + +BOOL CTFSpawn::CheckTeam( int iTeamNo ) +{ + // First check team number + if ( GetTeamNumber() ) + { + return ( iTeamNo == GetTeamNumber() ); + } + + // Then check the teamcheck + if ( m_pTeamCheck ) + { + CTeamCheck *pTeamCheck = dynamic_cast<CTeamCheck*>( m_pTeamCheck.Get() ); + Assert( pTeamCheck ); + if ( pTeamCheck->TeamMatches( iTeamNo ) == FALSE ) + return FALSE; + + return TRUE; + } + + return FALSE; +} + + +// ---------------------------------------------------------------------------------------------------- // +// CBaseDelay implementation. +// ---------------------------------------------------------------------------------------------------- // + +LINK_ENTITY_TO_CLASS( DelayedUse, CBaseDelay ); + +void CBaseDelay::SUB_UseTargets( CBaseEntity *pActivator, USE_TYPE useType, float value ) +{ + // TeamFortress Goal Checking + if (!pActivator || pActivator->IsPlayer() ) + DoResults(this, ToTFCPlayer( pActivator ), TRUE); + + // + // exit immediatly if we don't have a target or kill target + // + if (target == NULL_STRING && !m_iszKillTarget) + return; + + // + // check for a delay + // + if (m_flDelay != 0) + { + // create a temp object to fire at a later time + CBaseDelay *pTemp = (CBaseDelay*)CreateEntityByName( "DelayedUse" ); + Assert( stricmp( pTemp->GetClassname(), "DelayedUse" ) == 0 ); + + pTemp->SetNextThink( gpGlobals->curtime + m_flDelay ); + pTemp->SetThink( &CBaseDelay::DelayThink ); + + // Save the useType + pTemp->button = (int)useType; + pTemp->m_iszKillTarget = m_iszKillTarget; + pTemp->m_flDelay = 0; // prevent "recursion" + pTemp->target = target; + return; + } + + // + // kill the killtargets + // + + if ( m_iszKillTarget != NULL_STRING ) + { + CBaseEntity *pentKillTarget = NULL; + + Msg( "KillTarget: %s\n", STRING(m_iszKillTarget) ); + pentKillTarget = gEntList.FindEntityByName( NULL, STRING(m_iszKillTarget) ); + while ( pentKillTarget ) + { + Msg( "killing %s\n", pentKillTarget->GetClassname() ); + + CBaseEntity *pNext = gEntList.FindEntityByName( pentKillTarget, STRING(m_iszKillTarget) ); + UTIL_Remove( pentKillTarget ); + pentKillTarget = pNext; + } + } + + // + // fire targets + // + if ( target != NULL_STRING ) + { + FireTargets( STRING(target), pActivator, this, useType, value ); + } +} + + +bool CBaseDelay::KeyValue( const char *szKeyName, const char *szValue ) +{ + if (FStrEq(szKeyName, "delay")) + { + m_flDelay = atof( szValue ); + return true; + } + else if (FStrEq(szKeyName, "killtarget")) + { + m_iszKillTarget = MAKE_STRING(szValue); + return true; + } + else + { + return BaseClass::KeyValue( szKeyName, szValue ); + } +} + +void CBaseDelay::DelayThink( void ) +{ + // The use type is cached (and stashed) in pev->button + SUB_UseTargets( NULL, (USE_TYPE)button, 0 ); + UTIL_Remove( this ); +} + + +// ---------------------------------------------------------------------------------------------------- // +// CTeamCheck implementation. +// ---------------------------------------------------------------------------------------------------- // + +LINK_ENTITY_TO_CLASS(info_tf_teamcheck, CTeamCheck ); + + +void CTeamCheck::Spawn( void ) +{ +} + +void CTeamCheck::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + // Either Toggle or get set to a specific team number + if ( useType == USE_TOGGLE ) + { + if ( GetTeamNumber() == 1 ) + ChangeTeam( 2 ); + else + ChangeTeam( 1 ); + } + else if ( useType == USE_SET ) + { + if ( value >= 1 && value <= 4 ) + ChangeTeam( value ); + } +} + +BOOL CTeamCheck::TeamMatches( int iTeam ) +{ + return ( iTeam == GetTeamNumber() ); +} diff --git a/game/server/tfc/tfc_mapitems.h b/game/server/tfc/tfc_mapitems.h new file mode 100644 index 0000000..6e6df70 --- /dev/null +++ b/game/server/tfc/tfc_mapitems.h @@ -0,0 +1,405 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#ifndef TFC_MAPITEMS_H +#define TFC_MAPITEMS_H +#ifdef _WIN32 +#pragma once +#endif + + +#include "tfc_shareddefs.h" + + +class CTFCPlayer; + + +/*==================================================*/ +/* CTF Support defines */ +/*==================================================*/ +#define CTF_FLAG1 1 +#define CTF_FLAG2 2 +#define CTF_DROPOFF1 3 +#define CTF_DROPOFF2 4 +#define CTF_SCORE1 5 +#define CTF_SCORE2 6 + + +// Defines for GoalItem Removing from Player Methods +#define GI_DROP_PLAYERDEATH 0 // Dropped by a dying player +#define GI_DROP_REMOVEGOAL 1 // Removed by a Goal +#define GI_DROP_PLAYERDROP 2 // Dropped by a player + + +// Defines for methods of GoalItem returning +#define GI_RET_DROP_DEAD 0 // Dropped by a dead player +#define GI_RET_DROP_LIVING 1 // Dropped by a living player +#define GI_RET_GOAL 2 // Returned by a Goal +#define GI_RET_TIME 3 // Returned due to timeout + + +// Defines for Goal States +#define TFGS_ACTIVE 1 +#define TFGS_INACTIVE 2 +#define TFGS_REMOVED 3 +#define TFGS_DELAYED 4 + + +// Defines for Goal Result types : goal_result +#define TFGR_SINGLE 1 // Goal can only be activated once +#define TFGR_ADD_BONUSES 2 // Any Goals activated by this one give their bonuses +#define TFGR_ENDGAME 4 // Goal fires Intermission, displays scores, and ends level +#define TFGR_NO_ITEM_RESULTS 8 // GoalItems given by this Goal don't do results +#define TFGR_REMOVE_DISGUISE 16 // Prevent/Remove undercover from any Spy +#define TFGR_FORCE_RESPAWN 32 // Forces the player to teleport to a respawn point +#define TFGR_DESTROY_BUILDINGS 64 // Destroys this player's buildings, if anys + + +// Defines for Goal Item types, : goal_activation (in items) +#define TFGI_GLOW 1 // Players carrying this GoalItem will glow +#define TFGI_SLOW 2 // Players carrying this GoalItem will move at half-speed +#define TFGI_DROP 4 // Players dying with this item will drop it +#define TFGI_RETURN_DROP 8 // Return if a player with it dies +#define TFGI_RETURN_GOAL 16 // Return if a player with it has it removed by a goal's activation +#define TFGI_RETURN_REMOVE 32 // Return if it is removed by TFGI_REMOVE +#define TFGI_REVERSE_AP 64 // Only pickup if the player _doesn't_ match AP Details +#define TFGI_REMOVE 128 // Remove if left untouched for 2 minutes after being dropped +#define TFGI_KEEP 256 // Players keep this item even when they die +#define TFGI_ITEMGLOWS 512 // Item glows when on the ground +#define TFGI_DONTREMOVERES 1024 // Don't remove results when the item is removed +#define TFGI_DROPTOGROUND 2048 // Drop To Ground when spawning +#define TFGI_CANBEDROPPED 4096 // Can be voluntarily dropped by players +#define TFGI_SOLID 8192 // Is solid... blocks bullets, etc + + +// For all these defines, see the tfortmap.txt that came with the zip +// for complete descriptions. +// Defines for Goal Activation types : goal_activation (in goals) +#define TFGA_TOUCH 1 // Activated when touched +#define TFGA_TOUCH_DETPACK 2 // Activated when touched by a detpack explosion +#define TFGA_REVERSE_AP 4 // Activated when AP details are _not_ met +#define TFGA_SPANNER 8 // Activated when hit by an engineer's spanner +#define TFGA_DROPTOGROUND 2048 // Drop to Ground when spawning + + +// Defines for Goal Effects types : goal_effect +#define TFGE_AP 1 // AP is affected. Default. +#define TFGE_AP_TEAM 2 // All of the AP's team. +#define TFGE_NOT_AP_TEAM 4 // All except AP's team. +#define TFGE_NOT_AP 8 // All except AP. +#define TFGE_WALL 16 // If set, walls stop the Radius effects +#define TFGE_SAME_ENVIRONMENT 32 // If set, players in a different environment to the Goal are not affected +#define TFGE_TIMER_CHECK_AP 64 // If set, Timer Goals check their critera for all players fitting their effects + + +class CTFBaseItem : public CBaseAnimating +{ +public: + + bool CheckExistence(); + + +public: + + int group_no; + int goal_no; + int goal_state; // TFGS_ + // Goal/Timer/GoalItem/Trigger existence checking + int ex_skill_min; // Exists when the skill is >= this value + int ex_skill_max; // Exists when the skill is <= this value + string_t teamcheck; // TeamCheck entity that should be checked +}; + + +class CTFGoal : public CTFBaseItem +{ +public: + DECLARE_CLASS( CTFGoal, CBaseAnimating ); + DECLARE_DATADESC(); + + void Spawn( void ); + void StartGoal( void ); + void PlaceGoal( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void tfgoal_touch( CBaseEntity *pOther ); + + void DelayedResult(); + + Class_T Classify ( void ) { return CLASS_TFGOAL; } + + void SetObjectCollisionBox( void ); + + void tfgoal_timer_tick(); + void DoRespawn(); + + +public: + //TFCTODO: lots of these variables need to be put in the FGD file. + int goal_effects; // TFGE_ + int goal_result; // TFGR_ + + int playerclass; // One of the PC_ defines. + + float t_length; // Goal Criteria radius check + + // NOTE: In CTFGoal, these are overridden to mean: if they're not zero, then this goal only + // affects players... + int maxammo_shells; // ... with that team number + int maxammo_nails; // ... without that team number + + int ammo_shells; + int ammo_nails; + int ammo_rockets; + int ammo_cells; + int ammo_medikit; + int ammo_detpack; + int no_grenades_1; + int no_grenades_2; + + // Replacement_Model Stuff + string_t replacement_model; + int replacement_model_body; + int replacement_model_skin; + int replacement_model_flags; + + // Item Displaying details + int display_item_status[4]; // Goal displays the status of these items + string_t team_str_home; // Displayed when the item is at home base + string_t team_str_moved; // Displayed when the item has been moved + string_t team_str_carried; // Displayed when the item is being carried + string_t non_team_str_home; // Displayed when the item is at home base + string_t non_team_str_moved; // Displayed when the item has been moved + string_t non_team_str_carried; // Displayed when the item is being carried + + float invincible_finished; + float invisible_finished; + float super_damage_finished; + float radsuit_finished; + + int lives; + int frags; + float wait; + + float search_time; // Timer goal delay + int item_list; // Used to keep track of which goalitems are + // affecting the player at any time. + // GoalItems use it to keep track of their own + // mask to apply to a player's item_list + + float drop_time; // Time spent untouched before item return + float armortype; + int armorvalue; + int armorclass; // Type of armor being worn; + + int count; // Change teamscores + + // Goal Size + Vector goal_min; + Vector goal_max; + + bool m_bAddBonuses; + + int items; + int items_allowed; + + int else_goal; + int if_goal_is_active; + int if_goal_is_inactive; + int if_goal_is_removed; + int if_group_is_active; + int if_group_is_inactive; + int if_group_is_removed; + + int speed_reduction; + + int return_item_no; + int if_item_has_moved; + int if_item_hasnt_moved; + + int has_item_from_group; + int hasnt_item_from_group; + + int goal_activation; + int delay_time; + int weapon; + string_t owned_by_teamcheck; + int owned_by; + string_t noise; + + // Spawnpoint behaviour + int remove_spawnpoint; + int restore_spawnpoint; + int remove_spawngroup; + int restore_spawngroup; + + // These are the old centerprinting methods. + // They now print using the large fancy text + string_t broadcast; // Centerprinted to all, overridden by the next two + string_t team_broadcast; // Centerprinted to AP's team members, but not the AP + string_t non_team_broadcast; // Centerprinted to non AP's team members + string_t owners_team_broadcast; // Centerprinted to the members of the team that own the Goal/Item + string_t non_owners_team_broadcast; // Centerprinted to the members of the team that don't own the Goal/Item + string_t team_drop; // Centerprinted to item owners team + string_t non_team_drop; // Centerprinted to everone not on item owners team + + // These are new fields that print the old fashioned centerprint method + string_t org_broadcast; // Centerprinted to all, overridden by the next two + string_t org_team_broadcast; // Centerprinted to AP's team members, but not the AP + string_t org_non_team_broadcast; // Centerprinted to non AP's team members + string_t org_owners_team_broadcast; // Centerprinted to the members of the team that own the Goal/Item + string_t org_non_owners_team_broadcast; // Centerprinted to the members of the team that don't own the Goal/Item + string_t org_team_drop; // Centerprinted to item owners team + string_t org_non_team_drop; // Centerprinted to everone not on item owners team + string_t org_message; // Centerprinted to the AP upon activation + string_t org_noise3; + string_t org_noise4; + // These still print the old centerprint fashion + string_t netname_broadcast; // same as above, prepended by AP netname and bprinted + string_t netname_team_broadcast; // same as above, prepended by AP netname and bprinted + string_t netname_non_team_broadcast; // same as above, prepended by AP netname and bprinted + string_t netname_owners_team_broadcast; // same as above, prepended by AP netname and bprinted + string_t netname_team_drop; // same as above, prepended by AP netname and bprinted + string_t netname_non_team_drop; // same as above, prepended by AP netname and bprinted + string_t speak; // VOX Spoken to Everyone + string_t AP_speak; // VOX Spoken the AP + string_t team_speak; // VOX Spoken to AP's team_members, including the AP + string_t non_team_speak; // VOX Spoken to non AP's team_members + string_t owners_team_speak; // VOX Spoken to members of the team that own this Goal + string_t non_owners_team_speak; // VOX Spoken to everyone bit the members of the team that own this Goal + + float m_flEndRoundTime; + string_t m_iszEndRoundMsg_Team1_Win; + string_t m_iszEndRoundMsg_Team2_Win; + string_t m_iszEndRoundMsg_Team3_Win; + string_t m_iszEndRoundMsg_Team4_Win; + string_t m_iszEndRoundMsg_Team1_Lose; + string_t m_iszEndRoundMsg_Team2_Lose; + string_t m_iszEndRoundMsg_Team3_Lose; + string_t m_iszEndRoundMsg_Team4_Lose; + string_t m_iszEndRoundMsg_Team1; + string_t m_iszEndRoundMsg_Team2; + string_t m_iszEndRoundMsg_Team3; + string_t m_iszEndRoundMsg_Team4; + string_t m_iszEndRoundMsg_OwnedBy; + string_t m_iszEndRoundMsg_NonOwnedBy; + + int all_active; + int last_impulse; // The previous impulse command from this player + + int activate_goal_no; + int inactivate_goal_no; + int remove_goal_no; + int restore_goal_no; + int activate_group_no; + int inactivate_group_no; + int remove_group_no; + int restore_group_no; + + BOOL do_triggerwork; // Overrides for trigger handling in TF Goals + string_t killtarget; // Remove ents with this target + string_t target; + + string_t message; + + // Score increases + int increase_team[4]; // Increase the scores of teams + int increase_team_owned_by; // Increase the score of the team that owns this entity + + EHANDLE enemy; + + Vector oldorigin; + int axhitme; // Remove item from AP + + int remove_item_group; +}; + + +class CTFGoalItem : public CTFGoal +{ +public: + void Spawn( void ); + void StartItem( void ); + void PlaceItem( void ); + + Class_T Classify ( void ) { return CLASS_TFGOAL_ITEM; } + + void item_tfgoal_touch( CBaseEntity *pOther ); + void tfgoalitem_droptouch(); + void tfgoalitem_dropthink(); + void tfgoalitem_remove(); + + void DoDrop( Vector vecOrigin ); + + +public: + + float m_flDroppedAt; + + float speed; + int speed_reduction; + + float distance; + float pain_finished; + float attack_finished; + + Vector redrop_origin; // Original drop position + int redrop_count; // Number of time's we redropped. +}; + + +class CTFTimerGoal : public CTFGoal +{ +public: + void Spawn( void ); + + Class_T Classify ( void ) { return CLASS_TFGOAL_TIMER; } +}; + + +class CTFSpawn : public CTFBaseItem +{ +public: + void Spawn( void ); + void Activate( void ); + Class_T Classify ( void ) { return CLASS_TFSPAWN; } + BOOL CheckTeam( int iTeamNo ); + + EHANDLE m_pTeamCheck; +}; + + +class CBaseDelay : public CTFGoal +{ +public: + DECLARE_CLASS( CBaseDelay, CTFGoal ); + + void SUB_UseTargets( CBaseEntity *pActivator, USE_TYPE useType, float value ); + void KeyValue( KeyValueData *pkvd ); + void DelayThink( void ); + virtual bool KeyValue( const char *szKeyName, const char *szValue ); + +public: + float m_flDelay; + string_t m_iszKillTarget; + int button; +}; + + +class CTeamCheck : public CBaseDelay +{ +public: + void Spawn( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + BOOL TeamMatches( int iTeam ); +}; + + +// Global functions. +CTFGoalItem* Finditem(int ino); +void DisplayItemStatus(CTFGoal *Goal, CTFCPlayer *Player, CTFGoalItem *Item); + + +#endif // TFC_MAPITEMS_H diff --git a/game/server/tfc/tfc_player.cpp b/game/server/tfc/tfc_player.cpp new file mode 100644 index 0000000..d006efc --- /dev/null +++ b/game/server/tfc/tfc_player.cpp @@ -0,0 +1,1115 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Player for HL1. +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" +#include "tfc_player.h" +#include "tfc_gamerules.h" +#include "KeyValues.h" +#include "viewport_panel_names.h" +#include "client.h" +#include "team.h" +#include "weapon_tfcbase.h" +#include "tfc_client.h" +#include "tfc_mapitems.h" +#include "tfc_timer.h" +#include "tfc_engineer.h" +#include "tfc_team.h" + + +#define TFC_PLAYER_MODEL "models/player/pyro.mdl" + + +// -------------------------------------------------------------------------------- // +// Player animation event. Sent to the client when a player fires, jumps, reloads, etc.. +// -------------------------------------------------------------------------------- // + +class CTEPlayerAnimEvent : public CBaseTempEntity +{ +public: + DECLARE_CLASS( CTEPlayerAnimEvent, CBaseTempEntity ); + DECLARE_SERVERCLASS(); + + CTEPlayerAnimEvent( const char *name ) : CBaseTempEntity( name ) + { + } + + CNetworkHandle( CBasePlayer, m_hPlayer ); + CNetworkVar( int, m_iEvent ); + CNetworkVar( int, m_nData ); +}; + +IMPLEMENT_SERVERCLASS_ST_NOBASE( CTEPlayerAnimEvent, DT_TEPlayerAnimEvent ) + SendPropEHandle( SENDINFO( m_hPlayer ) ), + SendPropInt( SENDINFO( m_iEvent ), Q_log2( PLAYERANIMEVENT_COUNT ) + 1, SPROP_UNSIGNED ) + SendPropInt( SENDINFO( m_nData ), 32 ) +END_SEND_TABLE() + +static CTEPlayerAnimEvent g_TEPlayerAnimEvent( "PlayerAnimEvent" ); + +void TE_PlayerAnimEvent( CBasePlayer *pPlayer, PlayerAnimEvent_t event, int nData ) +{ + CPVSFilter filter( pPlayer->EyePosition() ); + + // The player himself doesn't need to be sent his animation events + // unless cs_showanimstate wants to show them. + if ( !ToolsEnabled() && ( cl_showanimstate.GetInt() == pPlayer->entindex() ) ) + { + filter.RemoveRecipient( pPlayer ); + } + + g_TEPlayerAnimEvent.m_hPlayer = pPlayer; + g_TEPlayerAnimEvent.m_iEvent = event; + g_TEPlayerAnimEvent.m_nData = nData; + g_TEPlayerAnimEvent.Create( filter, 0 ); +} + + +// -------------------------------------------------------------------------------- // +// Tables. +// -------------------------------------------------------------------------------- // + +LINK_ENTITY_TO_CLASS( player, CTFCPlayer ); +PRECACHE_REGISTER(player); + +IMPLEMENT_SERVERCLASS_ST( CTFCPlayer, DT_TFCPlayer ) + SendPropExclude( "DT_BaseAnimating", "m_flPoseParameter" ), + SendPropExclude( "DT_BaseAnimating", "m_flPlaybackRate" ), + SendPropExclude( "DT_BaseAnimating", "m_nSequence" ), + SendPropExclude( "DT_BaseEntity", "m_angRotation" ), + SendPropExclude( "DT_BaseAnimatingOverlay", "overlay_vars" ), + + // cs_playeranimstate and clientside animation takes care of these on the client + SendPropExclude( "DT_ServerAnimationData" , "m_flCycle" ), + SendPropExclude( "DT_AnimTimeMustBeFirst" , "m_flAnimTime" ), + + SendPropAngle( SENDINFO_VECTORELEM(m_angEyeAngles, 0), 11 ), + SendPropAngle( SENDINFO_VECTORELEM(m_angEyeAngles, 1), 11 ), + + SendPropDataTable( SENDINFO_DT( m_Shared ), &REFERENCE_SEND_TABLE( DT_TFCPlayerShared ) ) +END_SEND_TABLE() + + +// -------------------------------------------------------------------------------- // + +void cc_CreatePredictionError_f() +{ + CBaseEntity *pEnt = CBaseEntity::Instance( 1 ); + pEnt->SetAbsOrigin( pEnt->GetAbsOrigin() + Vector( 63, 0, 0 ) ); +} + +ConCommand cc_CreatePredictionError( "CreatePredictionError", cc_CreatePredictionError_f, "Create a prediction error", FCVAR_CHEAT ); + + +CTFCPlayer::CTFCPlayer() +{ + m_PlayerAnimState = CreatePlayerAnimState( this ); + item_list = 0; + + UseClientSideAnimation(); + m_angEyeAngles.Init(); + m_pCurStateInfo = NULL; + m_lifeState = LIFE_DEAD; // Start "dead". + + SetViewOffset( TFC_PLAYER_VIEW_OFFSET ); + + SetContextThink( &CTFCPlayer::TFCPlayerThink, gpGlobals->curtime, "TFCPlayerThink" ); +} + + +void CTFCPlayer::TFCPlayerThink() +{ + if ( m_pCurStateInfo && m_pCurStateInfo->pfnThink ) + (this->*m_pCurStateInfo->pfnThink)(); + + SetContextThink( &CTFCPlayer::TFCPlayerThink, gpGlobals->curtime, "TFCPlayerThink" ); +} + + +CTFCPlayer::~CTFCPlayer() +{ + m_PlayerAnimState->Release(); +} + + +CTFCPlayer *CTFCPlayer::CreatePlayer( const char *className, edict_t *ed ) +{ + CTFCPlayer::s_PlayerEdict = ed; + return (CTFCPlayer*)CreateEntityByName( className ); +} + + +void CTFCPlayer::PostThink() +{ + BaseClass::PostThink(); + + QAngle angles = GetLocalAngles(); + angles[PITCH] = 0; + SetLocalAngles( angles ); + + // Store the eye angles pitch so the client can compute its animation state correctly. + m_angEyeAngles = EyeAngles(); + + m_PlayerAnimState->Update( m_angEyeAngles[YAW], m_angEyeAngles[PITCH] ); +} + + +void CTFCPlayer::Precache() +{ + for ( int i=0; i < PC_LASTCLASS; i++ ) + PrecacheModel( GetTFCClassInfo( i )->m_pModelName ); + + PrecacheScriptSound( "Player.Spawn" ); + + BaseClass::Precache(); +} + + +void CTFCPlayer::InitialSpawn( void ) +{ + BaseClass::InitialSpawn(); + + State_Enter( STATE_WELCOME ); +} + + +void CTFCPlayer::Spawn() +{ + SetModel( GetTFCClassInfo( m_Shared.GetPlayerClass() )->m_pModelName ); + + SetMoveType( MOVETYPE_WALK ); + m_iLegDamage = 0; + + BaseClass::Spawn(); + + // Kind of lame, but CBasePlayer::Spawn resets a lot of the state that we initially want on. + // So if we're in the welcome state, call its enter function to reset + if ( m_Shared.State_Get() == STATE_WELCOME ) + { + State_Enter_WELCOME(); + } + + // If they were dead, then they're respawning. Put them in the active state. + if ( m_Shared.State_Get() == STATE_DYING ) + { + State_Transition( STATE_ACTIVE ); + } + + // If they're spawning into the world as fresh meat, give them items and stuff. + if ( m_Shared.State_Get() == STATE_ACTIVE ) + { + EmitSound( "Player.Spawn" ); + GiveDefaultItems(); + } +} + + +void CTFCPlayer::ForceRespawn() +{ + //TFCTODO: goldsrc tfc has a big function for this.. doing what I'm doing here may not work. + respawn( this, false ); +} + + +void CTFCPlayer::GiveDefaultItems() +{ + switch( m_Shared.GetPlayerClass() ) + { + case PC_HWGUY: + { + GiveNamedItem( "weapon_crowbar" ); + + GiveNamedItem( "weapon_minigun" ); + GiveAmmo( 176, TFC_AMMO_SHELLS ); + } + break; + + case PC_PYRO: + { + GiveNamedItem( "weapon_crowbar" ); + } + break; + + case PC_ENGINEER: + { + GiveNamedItem( "weapon_spanner" ); + GiveNamedItem( "weapon_super_shotgun" ); + GiveAmmo( 20, TFC_AMMO_SHELLS ); + } + break; + + case PC_SCOUT: + { + GiveNamedItem( "weapon_crowbar" ); + GiveNamedItem( "weapon_shotgun" ); + GiveNamedItem( "weapon_nailgun" ); + GiveAmmo( 25, TFC_AMMO_SHELLS ); + GiveAmmo( 100, TFC_AMMO_NAILS ); + } + break; + + case PC_SNIPER: + { + GiveNamedItem( "weapon_crowbar" ); + } + break; + + case PC_SOLDIER: + { + GiveNamedItem( "weapon_crowbar" ); + } + break; + + case PC_DEMOMAN: + { + GiveNamedItem( "weapon_crowbar" ); + } + break; + + case PC_SPY: + { + GiveNamedItem( "weapon_knife" ); + } + break; + + case PC_MEDIC: + { + GiveNamedItem( "weapon_medikit" ); + GiveNamedItem( "weapon_super_nailgun" ); + GiveAmmo( 100, TFC_AMMO_NAILS ); + } + break; + } +} + + +void CTFCPlayer::DoAnimationEvent( PlayerAnimEvent_t event, int nData ) +{ + m_PlayerAnimState->DoAnimationEvent( event, nData ); + TE_PlayerAnimEvent( this, event, nData ); // Send to any clients who can see this guy. +} + + +void CTFCPlayer::State_Transition( TFCPlayerState newState ) +{ + State_Leave(); + State_Enter( newState ); +} + + +void CTFCPlayer::State_Enter( TFCPlayerState newState ) +{ + m_Shared.m_iPlayerState = newState; + m_pCurStateInfo = State_LookupInfo( newState ); + + // Initialize the new state. + if ( m_pCurStateInfo && m_pCurStateInfo->pfnEnterState ) + (this->*m_pCurStateInfo->pfnEnterState)(); +} + + +void CTFCPlayer::State_Leave() +{ + if ( m_pCurStateInfo && m_pCurStateInfo->pfnLeaveState ) + { + (this->*m_pCurStateInfo->pfnLeaveState)(); + } +} + + +CPlayerStateInfo* CTFCPlayer::State_LookupInfo( TFCPlayerState state ) +{ + // This table MUST match the + static CPlayerStateInfo playerStateInfos[] = + { + { STATE_ACTIVE, "STATE_ACTIVE", &CTFCPlayer::State_Enter_ACTIVE, NULL, NULL }, + { STATE_WELCOME, "STATE_WELCOME", &CTFCPlayer::State_Enter_WELCOME, NULL, NULL }, + { STATE_PICKINGTEAM, "STATE_PICKINGTEAM", &CTFCPlayer::State_Enter_PICKINGTEAM, NULL, NULL }, + { STATE_PICKINGCLASS, "STATE_PICKINGCLASS", &CTFCPlayer::State_Enter_PICKINGCLASS, NULL, NULL }, + { STATE_OBSERVER_MODE, "STATE_OBSERVER_MODE", &CTFCPlayer::State_Enter_OBSERVER_MODE, NULL, NULL }, + { STATE_DYING, "STATE_DYING", &CTFCPlayer::State_Enter_DYING, NULL, NULL } + }; + + for ( int i=0; i < ARRAYSIZE( playerStateInfos ); i++ ) + { + if ( playerStateInfos[i].m_iPlayerState == state ) + return &playerStateInfos[i]; + } + + return NULL; +} + + +void CTFCPlayer::State_Enter_WELCOME() +{ + SetMoveType( MOVETYPE_NONE ); + AddEffects( EF_NODRAW ); + AddSolidFlags( FSOLID_NOT_SOLID ); + PhysObjectSleep(); + + // Show info panel (if it's not a simple demo map). + KeyValues *data = new KeyValues("data"); + data->SetString( "title", "Message of the Day" ); // info panel title + data->SetString( "type", "3" ); // show a file + data->SetString( "msg", "motd.txt" ); // this file + data->SetString( "cmd", "joingame" ); // exec this command if panel closed + + ShowViewPortPanel( "info", true, data ); + + data->deleteThis(); +} + + +void CTFCPlayer::State_Enter_PICKINGTEAM() +{ + ShowViewPortPanel( PANEL_TEAM ); // show the team menu +} + + +void CTFCPlayer::State_Enter_PICKINGCLASS() +{ + // go to spec mode, if dying keep deathcam + if ( GetObserverMode() == OBS_MODE_DEATHCAM ) + { + StartObserverMode( OBS_MODE_DEATHCAM ); + } + else + { + StartObserverMode( OBS_MODE_ROAMING ); + } + + PhysObjectSleep(); + + // show the class menu: + ShowViewPortPanel( PANEL_CLASS ); +} + + +void CTFCPlayer::State_Enter_OBSERVER_MODE() +{ + StartObserverMode( m_iObserverLastMode ); + PhysObjectSleep(); +} + + +void CTFCPlayer::State_Enter_ACTIVE() +{ + SetMoveType( MOVETYPE_WALK ); + RemoveEffects( EF_NODRAW ); + RemoveSolidFlags( FSOLID_NOT_SOLID ); + m_Local.m_iHideHUD = 0; + PhysObjectWake(); +} + + +void CTFCPlayer::State_Enter_DYING() +{ + SetMoveType( MOVETYPE_NONE ); + AddSolidFlags( FSOLID_NOT_SOLID ); +} + + +void CTFCPlayer::PhysObjectSleep() +{ + IPhysicsObject *pObj = VPhysicsGetObject(); + if ( pObj ) + pObj->Sleep(); +} + + +void CTFCPlayer::PhysObjectWake() +{ + IPhysicsObject *pObj = VPhysicsGetObject(); + if ( pObj ) + pObj->Wake(); +} + + +void CTFCPlayer::HandleCommand_JoinTeam( const char *pTeamName ) +{ + int iTeam = TEAM_RED; + if ( stricmp( pTeamName, "auto" ) == 0 ) + { + iTeam = RandomInt( 0, 1 ) ? TEAM_RED : TEAM_BLUE; + } + else if ( stricmp( pTeamName, "spectate" ) == 0 ) + { + iTeam = TEAM_SPECTATOR; + } + else + { + for ( int i=0; i < TEAM_MAXCOUNT; i++ ) + { + if ( stricmp( pTeamName, teamnames[i] ) == 0 ) + { + iTeam = i; + break; + } + } + } + + if ( iTeam == TEAM_SPECTATOR ) + { + // Prevent this is the cvar is set + if ( !mp_allowspectators.GetInt() && !IsHLTV() ) + { + ClientPrint( this, HUD_PRINTCENTER, "#Cannot_Be_Spectator" ); + return; + } + + if ( GetTeamNumber() != TEAM_UNASSIGNED && !IsDead() ) + { + CommitSuicide(); + + // add 1 to frags to balance out the 1 subtracted for killing yourself + IncrementFragCount( 1 ); + } + + ChangeTeam( TEAM_SPECTATOR ); + + // do we have fadetoblack on? (need to fade their screen back in) + if ( mp_fadetoblack.GetInt() ) + { + color32_s clr = { 0,0,0,0 }; + UTIL_ScreenFade( this, clr, 0.001, 0, FFADE_IN ); + } + } + else + { + ChangeTeam( iTeam ); + State_Transition( STATE_PICKINGCLASS ); + } +} + + +void CTFCPlayer::ChangeTeam( int iTeamNum ) +{ + if ( !GetGlobalTeam( iTeamNum ) ) + { + Warning( "CCSPlayer::ChangeTeam( %d ) - invalid team index.\n", iTeamNum ); + return; + } + + int iOldTeam = GetTeamNumber(); + + // if this is our current team, just abort + if ( iTeamNum == iOldTeam ) + return; + + BaseClass::ChangeTeam( iTeamNum ); + + if ( iTeamNum == TEAM_UNASSIGNED ) + { + State_Transition( STATE_OBSERVER_MODE ); + } + else if ( iTeamNum == TEAM_SPECTATOR ) + { + State_Transition( STATE_OBSERVER_MODE ); + } + else // active player + { + if ( iOldTeam == TEAM_SPECTATOR ) + { + // If they're switching from being a spectator to ingame player + GetIntoGame(); + } + + if ( !IsDead() && iOldTeam != TEAM_UNASSIGNED ) + { + // Kill player if switching teams while alive + CommitSuicide(); + } + + // Put up the class selection menu. + State_Transition( STATE_PICKINGCLASS ); + } +} + + +void CTFCPlayer::HandleCommand_JoinClass( const char *pClassName ) +{ + int iClass = RandomInt( 0, PC_LAST_NORMAL_CLASS ); + if ( stricmp( pClassName, "random" ) != 0 ) + { + for ( int i=0; i < PC_LASTCLASS; i++ ) + { + if ( stricmp( pClassName, GetTFCClassInfo( i )->m_pClassName ) == 0 ) + { + iClass = i; + break; + } + } + if ( i == PC_LAST_NORMAL_CLASS ) + { + Warning( "HandleCommand_JoinClass( %s ) - invalid class name.\n", pClassName ); + } + } + + m_Shared.SetPlayerClass( iClass ); + + if ( !IsAlive() ) + GetIntoGame(); +} + + +void CTFCPlayer::GetIntoGame() +{ + State_Transition( STATE_ACTIVE ); + Spawn(); +} + + +bool CTFCPlayer::ClientCommand( const CCommand& args ) +{ + const char *pcmd = args[0]; + if ( FStrEq( pcmd, "joingame" ) ) + { + // player just closed MOTD dialog + if ( m_Shared.m_iPlayerState == STATE_WELCOME ) + { + State_Transition( STATE_PICKINGTEAM ); + } + return true; + } + else if ( FStrEq( pcmd, "jointeam" ) ) + { + if ( args.ArgC() >= 2 ) + { + HandleCommand_JoinTeam( args[1] ); + return true; + } + } + else if ( FStrEq( pcmd, "joinclass" ) ) + { + if ( args.ArgC() < 2 ) + { + Warning( "Player sent bad joinclass syntax\n" ); + } + + HandleCommand_JoinClass( args[1] ); + return true; + } + + return BaseClass::ClientCommand( args ); +} + + +bool CTFCPlayer::IsAlly( CBaseEntity *pEnt ) const +{ + return pEnt->GetTeamNumber() == GetTeamNumber(); +} + + +void CTFCPlayer::TF_AddFrags( int nFrags ) +{ + // TFCTODO: implement frags +} + + +void CTFCPlayer::ResetMenu() +{ + current_menu = 0; +} + + +int CTFCPlayer::GetNumFlames() const +{ + // TFCTODO: implement flames + return 0; +} + + +void CTFCPlayer::SetNumFlames( int nFlames ) +{ + // TFCTODO: implement frags + Assert( 0 ); +} + +int CTFCPlayer::TakeHealth( float flHealth, int bitsDamageType ) +{ + int bResult = false; + + // If the bit's set, ignore the monster's max health and add over it + if ( bitsDamageType & DMG_IGNORE_MAXHEALTH ) + { + int iDamage = g_pGameRules->Damage_GetTimeBased(); + m_bitsDamageType &= ~(bitsDamageType & ~iDamage); + m_iHealth += flHealth; + bResult = true; + } + else + { + bResult = BaseClass::TakeHealth( flHealth, bitsDamageType ); + } + + // Leg Healing + if (m_iLegDamage > 0) + { + // Allow even at full health + if ( GetHealth() >= (GetMaxHealth() - 5)) + m_iLegDamage = 0; + else + m_iLegDamage -= (GetHealth() + flHealth) / 20; + if (m_iLegDamage < 1) + m_iLegDamage = 0; + + TeamFortress_SetSpeed(); + bResult = true; + } + + return bResult; +} + + +void CTFCPlayer::TeamFortress_SetSpeed() +{ + int playerclass = m_Shared.GetPlayerClass(); + float maxfbspeed; + + // Spectators can move while in Classic Observer mode + if ( IsObserver() ) + { + if ( GetObserverMode() == OBS_MODE_ROAMING ) + SetMaxSpeed( GetTFCClassInfo( PC_SCOUT )->m_flMaxSpeed ); + else + SetMaxSpeed( 0 ); + + return; + } + + // Check for any reason why they can't move at all + if ( (m_Shared.GetStateFlags() & TFSTATE_CANT_MOVE) || (playerclass == PC_UNDEFINED) ) + { + SetAbsVelocity( vec3_origin ); + SetMaxSpeed( 1 ); + return; + } + + // First, get their max class speed + maxfbspeed = GetTFCClassInfo( playerclass )->m_flMaxSpeed; + + // 2nd, see if any GoalItems are slowing them down + CBaseEntity *pEnt = gEntList.FindEntityByClassname( NULL, "item_tfgoal" ); + while ( pEnt ) + { + CTFGoal *pGoal = dynamic_cast<CTFGoal*>( pEnt ); + if ( pGoal ) + { + if ( pGoal->GetOwnerEntity() == this ) + { + if (pGoal->goal_activation & TFGI_SLOW) + { + maxfbspeed = maxfbspeed / 2; + } + else if (pGoal->speed_reduction) + { + float flPercent = ((float)pGoal->speed_reduction) / 100.0; + maxfbspeed = flPercent * maxfbspeed; + } + } + } + + pEnt = gEntList.FindEntityByClassname( pEnt, "item_tfgoal" ); + } + + // 3rd, See if they're tranquilised + if (m_Shared.GetStateFlags() & TFSTATE_TRANQUILISED) + { + maxfbspeed = maxfbspeed / 2; + } + + // 4th, check for leg wounds + if (m_iLegDamage) + { + if (m_iLegDamage > 6) + m_iLegDamage = 6; + + // reduce speed by 10% per leg wound + maxfbspeed = (maxfbspeed * ((10 - m_iLegDamage) / 10)); + } + + // 5th, if they're a sniper, and they're aiming, their speed must be 80 or less + if (m_Shared.GetStateFlags() & TFSTATE_AIMING) + { + if (maxfbspeed > 80) + maxfbspeed = 80; + } + + // Set the speed + SetMaxSpeed( maxfbspeed ); +} + + +void CTFCPlayer::Event_Killed( const CTakeDamageInfo &info ) +{ + DoAnimationEvent( PLAYERANIMEVENT_DIE ); + State_Transition( STATE_DYING ); // Transition into the dying state. + + // Remove all items.. + RemoveAllItems( true ); + + BaseClass::Event_Killed( info ); + + // Don't overflow the value for this. + m_iHealth = 0; +} + + +void CTFCPlayer::ClientHearVox( const char *pSentence ) +{ + //TFCTODO: implement this. +} + + +//========================================================================= +// Check all stats to make sure they're good for this class +void CTFCPlayer::TeamFortress_CheckClassStats() +{ + // Check armor + if (armortype > armor_allowed) + armortype = armor_allowed; + + if (ArmorValue() > GetClassInfo()->m_iMaxArmor) + SetArmorValue( GetClassInfo()->m_iMaxArmor ); + + if (ArmorValue() < 0) + SetArmorValue( 0 ); + + if (armortype < 0) + armortype = 0; + + // Check ammo + for ( int iAmmoType=0; iAmmoType < TFC_NUM_AMMO_TYPES; iAmmoType++ ) + { + if ( GetAmmoCount( iAmmoType ) > GetClassInfo()->m_MaxAmmo[iAmmoType] ) + RemoveAmmo( GetAmmoCount( iAmmoType ) - GetClassInfo()->m_MaxAmmo[iAmmoType], iAmmoType ); + } + + // Check Grenades + Assert( GetAmmoCount( TFC_AMMO_GRENADES1 ) >= 0 ); + Assert( GetAmmoCount( TFC_AMMO_GRENADES2 ) >= 0 ); + + // Limit Nails + if ( no_grenades_1() > g_nMaxGrenades[tp_grenades_1()] ) + RemoveAmmo( TFC_AMMO_GRENADES1, no_grenades_1() - g_nMaxGrenades[tp_grenades_1()] ); + + if ( no_grenades_2() > g_nMaxGrenades[tp_grenades_2()] ) + RemoveAmmo( TFC_AMMO_GRENADES2, no_grenades_2() - g_nMaxGrenades[tp_grenades_2()] ); + + // Check health + if (GetHealth() > GetMaxHealth() && !(m_Shared.GetItemFlags() & IT_SUPERHEALTH)) + SetHealth( GetMaxHealth() ); + + if (GetHealth() < 0) + SetHealth( 0 ); + + // Update armor picture + m_Shared.RemoveItemFlags( IT_ARMOR1 | IT_ARMOR2 | IT_ARMOR3 ); + if (armortype >= 0.8) + m_Shared.AddItemFlags( IT_ARMOR3 ); + else if (armortype >= 0.6) + m_Shared.AddItemFlags( IT_ARMOR2 ); + else if (armortype >= 0.3) + m_Shared.AddItemFlags( IT_ARMOR1 ); +} + + +//====================================================================== +// DISGUISE HANDLING +//====================================================================== +// Reset spy skin and color or remove invisibility +void CTFCPlayer::Spy_RemoveDisguise() +{ + if (m_Shared.GetPlayerClass() == PC_SPY) + { + if ( undercover_team || undercover_skin ) + ClientPrint( this, HUD_PRINTCENTER, "#Disguise_Lost" ); + + // Set their color + undercover_team = 0; + undercover_skin = 0; + + immune_to_check = gpGlobals->curtime + 10; + is_undercover = 0; + + // undisguise weapon + TeamFortress_SetSkin(); + TeamFortress_SpyCalcName(); + + Spy_ResetExternalWeaponModel(); + + // get them out of any disguise menus + if ( current_menu == MENU_SPY || current_menu == MENU_SPY_SKIN || current_menu == MENU_SPY_COLOR ) + { + ResetMenu(); + } + + // Remove the Disguise timer + CTimer *pTimer = Timer_FindTimer( this, TF_TIMER_DISGUISE ); + if (pTimer) + { + ClientPrint( this, HUD_PRINTCENTER, "#Disguise_stop" ); + Timer_Remove( pTimer ); + } + } +} + + +// when the spy loses disguise reset his weapon +void CTFCPlayer::Spy_ResetExternalWeaponModel( void ) +{ + // we don't show any weapon models if we're feigning + if ( is_feigning ) + return; + +#ifdef TFCTODO // spy + pev->weaponmodel = MAKE_STRING( m_pszSavedWeaponModel ); + strcpy( m_szAnimExtention, m_szSavedAnimExtention ); + m_iCurrentAnimationState = 0; // force the current animation sequence to be recalculated +#endif +} + + +//========================================================================= +// Try and find the player's name who's skin and team closest fit the +// current disguise of the spy +void CTFCPlayer::TeamFortress_SpyCalcName() +{ + CBaseEntity *last_target = undercover_target;// don't redisguise self as this person + + undercover_target = NULL; + + // Find a player on the team the spy is disguised as to pretend to be + if (undercover_team != 0) + { + CTFCPlayer *pPlayer = NULL; + + // Loop through players + int i; + for ( i = 1; i <= gpGlobals->maxClients; i++ ) + { + pPlayer = ToTFCPlayer( UTIL_PlayerByIndex( i ) ); + if ( pPlayer ) + { + if ( pPlayer == last_target ) + { + // choose someone else, we're trying to rid ourselves of a disguise as this one + continue; + } + + // First, try to find a player with same color and skins + if (pPlayer->GetTeamNumber() == undercover_team && pPlayer->m_Shared.GetPlayerClass() == undercover_skin) + { + undercover_target = pPlayer; + return; + } + } + } + + for ( i = 1; i <= gpGlobals->maxClients; i++ ) + { + pPlayer = ToTFCPlayer( UTIL_PlayerByIndex( i ) ); + + if ( pPlayer ) + { + if (pPlayer->GetTeamNumber() == undercover_team) + { + undercover_target = pPlayer; + return; + } + } + } + } +} + + +//========================================================================= +// Set the skin of a player based on his/her class +void CTFCPlayer::TeamFortress_SetSkin() +{ + immune_to_check = gpGlobals->curtime + 10; + + // Find out whether we should show our actual class or a disguised class + int iClassToUse = m_Shared.GetPlayerClass(); + if (iClassToUse == PC_SPY && undercover_skin != 0) + iClassToUse = undercover_skin; + + int iTeamToUse = GetTeamNumber(); + if (m_Shared.GetPlayerClass() == PC_SPY && undercover_team != 0) + iTeamToUse = undercover_team; + +// TFCTODO: handle replacement_model here. + + SetModel( GetTFCClassInfo( iClassToUse )->m_pModelName ); + + // Skins in the models should be setup using the team IDs in tfc_shareddefs.h, subtracting 1 + // so they're 0-based. + m_nSkin = iTeamToUse - 1; + + if ( FBitSet(GetFlags(), FL_DUCKING) ) + UTIL_SetSize(this, VEC_DUCK_HULL_MIN, VEC_DUCK_HULL_MAX); + else + UTIL_SetSize(this, VEC_HULL_MIN, VEC_HULL_MAX); +} + + + +//========================================================================= +// Displays the state of the items specified by the Goal passed in +void CTFCPlayer::DisplayLocalItemStatus( CTFGoal *pGoal ) +{ + for (int i = 0; i < 4; i++) + { + if (pGoal->display_item_status[i] != 0) + { + CTFGoalItem *pItem = Finditem(pGoal->display_item_status[i]); + if (pItem) + DisplayItemStatus(pGoal, this, pItem); + else + ClientPrint( this, HUD_PRINTTALK, "#Item_missing" ); + } + } +} + + +//========================================================================= +// Removes all the Engineer's buildings +void CTFCPlayer::Engineer_RemoveBuildings() +{ + // If the player's building already, stop + if (is_building == 1) + { + m_Shared.RemoveStateFlags( TFSTATE_CANT_MOVE ); + TeamFortress_SetSpeed(); + + // Remove the timer + CTimer *pTimer = Timer_FindTimer( this, TF_TIMER_BUILD ); + if (pTimer) + Timer_Remove(pTimer); + + // Remove the building + UTIL_Remove( building ); + building = NULL; + is_building = 0; + + // Stop Build Sound + StopSound( "Engineer.Building" ); + //STOP_SOUND( ENT(pev), CHAN_STATIC, "weapons/building.wav" ); + + if ( GetActiveWeapon() ) + GetActiveWeapon()->Deploy(); + } + + DestroyBuilding(this, "building_dispenser"); + DestroyBuilding(this, "building_sentrygun"); + DestroyTeleporter(this, BUILD_TELEPORTER_ENTRY); + DestroyTeleporter(this, BUILD_TELEPORTER_EXIT); +} + + +//========================================================================= +// Removes all grenades that persist for a period of time from the world +void CTFCPlayer::TeamFortress_RemoveLiveGrenades( void ) +{ + RemoveOwnedEnt( "tf_weapon_napalmgrenade" ); + RemoveOwnedEnt( "tf_weapon_nailgrenade" ); + RemoveOwnedEnt( "tf_weapon_gasgrenade" ); + RemoveOwnedEnt( "tf_weapon_caltrop" ); +} + + +//========================================================================= +// Removes all rockets the player has fired into the world +// (this prevents a team kill cheat where players would fire rockets +// then change teams to kill their own team) +void CTFCPlayer::TeamFortress_RemoveRockets( void ) +{ + RemoveOwnedEnt( "tf_rpg_rocket" ); + RemoveOwnedEnt( "tf_ic_rocket" ); +} + +// removes the player's pipebombs with no explosions +void CTFCPlayer::RemovePipebombs( void ) +{ + CBaseEntity *pEnt = gEntList.FindEntityByClassname( NULL, "tf_gl_pipebomb" ); + while ( pEnt ) + { + CTFCPlayer *pOwner = ToTFCPlayer( pEnt->GetOwnerEntity() ); + if ( pOwner == this ) + { + pOwner->m_iPipebombCount--; + pEnt->AddFlag( FL_KILLME ); + } + + pEnt = gEntList.FindEntityByClassname( pEnt, "tf_gl_pipebomb" ); + } +} + + +//========================================================================= +// Stops the setting of the detpack +void CTFCPlayer::TeamFortress_DetpackStop( void ) +{ + CTimer *pTimer = Timer_FindTimer( this, TF_TIMER_DETPACKSET ); + + if (!pTimer) + return; + + ClientPrint( this, HUD_PRINTNOTIFY, "#Detpack_retrieve" ); + + // Return the detpack + GiveAmmo( 1, TFC_AMMO_DETPACK ); + Timer_Remove(pTimer); + + // Release player + m_Shared.RemoveStateFlags( TFSTATE_CANT_MOVE ); + is_detpacking = 0; + TeamFortress_SetSpeed(); + + // Return their weapon + if ( GetActiveWeapon() ) + GetActiveWeapon()->Deploy(); +} + + +//========================================================================= +// Removes any detpacks the player may have set +BOOL CTFCPlayer::TeamFortress_RemoveDetpacks( void ) +{ + // Remove all detpacks owned by the player + CBaseEntity *pEnt = gEntList.FindEntityByClassname( NULL, "detpack" ); + while ( pEnt ) + { + // if the player owns this detpack, remove it + if ( pEnt->GetOwnerEntity() == this ) + { + UTIL_Remove( pEnt ); + return TRUE; + } + + pEnt = gEntList.FindEntityByClassname( pEnt, "detpack" ); + } + + return FALSE; +} + + +//========================================================================= +// Remove all of an ent owned by this player +void CTFCPlayer::RemoveOwnedEnt( char *pEntName ) +{ + CBaseEntity *pEnt = gEntList.FindEntityByClassname( NULL, pEntName ); + while ( pEnt ) + { + // if the player owns this entity, remove it + if ( pEnt->GetOwnerEntity() == this ) + pEnt->AddFlag( FL_KILLME ); + + pEnt = gEntList.FindEntityByClassname( pEnt, pEntName ); + } +} + + diff --git a/game/server/tfc/tfc_player.h b/game/server/tfc/tfc_player.h new file mode 100644 index 0000000..484aaa3 --- /dev/null +++ b/game/server/tfc/tfc_player.h @@ -0,0 +1,254 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Player for HL1. +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef TFC_PLAYER_H +#define TFC_PLAYER_H +#pragma once + + +#include "player.h" +#include "server_class.h" +#include "tfc_playeranimstate.h" +#include "tfc_shareddefs.h" +#include "tfc_player_shared.h" + + +class CTFCPlayer; +class CTFGoal; +class CTFGoalItem; + + +// Function table for each player state. +class CPlayerStateInfo +{ +public: + TFCPlayerState m_iPlayerState; + const char *m_pStateName; + + void (CTFCPlayer::*pfnEnterState)(); // Init and deinit the state. + void (CTFCPlayer::*pfnLeaveState)(); + + void (CTFCPlayer::*pfnThink)(); // Called every frame. +}; + + +//============================================================================= +// >> CounterStrike player +//============================================================================= +class CTFCPlayer : public CBasePlayer +{ +public: + DECLARE_CLASS( CTFCPlayer, CBasePlayer ); + DECLARE_SERVERCLASS(); + + + CTFCPlayer(); + ~CTFCPlayer(); + + static CTFCPlayer *CreatePlayer( const char *className, edict_t *ed ); + static CTFCPlayer* Instance( int iEnt ); + + // This passes the event to the client's and server's CPlayerAnimState. + void DoAnimationEvent( PlayerAnimEvent_t event ); + + virtual void PostThink(); + virtual void InitialSpawn(); + virtual void Spawn(); + virtual void Precache(); + virtual bool ClientCommand( const CCommand &args ); + virtual void ChangeTeam( int iTeamNum ) OVERRIDE; + virtual int TakeHealth( float flHealth, int bitsDamageType ); + virtual void Event_Killed( const CTakeDamageInfo &info ); + + void ClientHearVox( const char *pSentence ); + void DisplayLocalItemStatus( CTFGoal *pGoal ); + + +public: + + // Is this entity an ally (on our team)? + bool IsAlly( CBaseEntity *pEnt ) const; + + TFCPlayerState State_Get() const; // Get the current state. + + void TF_AddFrags( int nFrags ); + + void ResetMenu(); + + // On fire.. + int GetNumFlames() const; + void SetNumFlames( int nFlames ); + + void ForceRespawn(); + + void TeamFortress_SetSpeed(); + void TeamFortress_CheckClassStats(); + void TeamFortress_SetSkin(); + void TeamFortress_RemoveLiveGrenades(); + void TeamFortress_RemoveRockets(); + void TeamFortress_DetpackStop( void ); + + BOOL TeamFortress_RemoveDetpacks( void ); + void RemovePipebombs( void ); + void RemoveOwnedEnt( char *pEntName ); + +// SPY STUFF +public: + + void Spy_RemoveDisguise(); + void TeamFortress_SpyCalcName(); + void Spy_ResetExternalWeaponModel( void ); + + +// ENGINEER STUFF +public: + + void Engineer_RemoveBuildings(); + + // Building + BOOL is_building; // TRUE for an ENGINEER if they're building something + EHANDLE building; // The building the ENGINEER is using + float building_wait; // Used to prevent using a building again immediately + EHANDLE real_owner; + float has_dispenser; // TRUE if engineer has a dispenser + float has_sentry; // TRUE if engineer has a sentry + float has_entry_teleporter; // TRUE if engineer has an entry teleporter + float has_exit_teleporter; // TRUE if engineer has an exit teleporter + + +// DEMO STUFF +public: + + int m_iPipebombCount; + + +public: + + // Get the class info associated with us. + const CTFCPlayerClassInfo* GetClassInfo() const; + + // Helpers to ease porting... + int tp_grenades_1() const { return GetClassInfo()->m_iGrenadeType1; } + int tp_grenades_2() const { return GetClassInfo()->m_iGrenadeType2; } + int no_grenades_1() const { return GetAmmoCount( TFC_AMMO_GRENADES1 ); } + int no_grenades_2() const { return GetAmmoCount( TFC_AMMO_GRENADES2 ); } + + +public: + + CTFCPlayerShared m_Shared; + + int item_list; // Used to keep track of which goalitems are + // affecting the player at any time. + // GoalItems use it to keep track of their own + // mask to apply to a player's item_list + + float armortype; + //float armorvalue; // Use CBasePlayer::m_ArmorValue. + int armorclass; // Type of armor being worn + float armor_allowed; + + float invincible_finished; + float invisible_finished; + float super_damage_finished; + float radsuit_finished; + + int lives; // The number of lives you have left + int is_unableto_spy_or_teleport; + + BOOL bRemoveGrenade; // removes the primed grenade if set + + // Replacement_Model Stuff + string_t replacement_model; + int replacement_model_body; + int replacement_model_skin; + int replacement_model_flags; + + // Spy + int undercover_team; // The team the Spy is pretending to be in + int undercover_skin; // The skin the Spy is pretending to have + EHANDLE undercover_target; // The player the Spy is pretending to be + BOOL is_feigning; // TRUE for a SPY if they're feigning death + float immune_to_check; + BOOL is_undercover; // TRUE for a SPY if they're undercover + + // TEAMFORTRESS VARIABLES + int no_sentry_message; + int no_dispenser_message; + + // teleporter variables + int no_entry_teleporter_message; + int no_exit_teleporter_message; + + BOOL is_detpacking; // TRUE for a DEMOMAN if they're setting a detpack + + float current_menu; // is set to the number of the current menu, is 0 if they are not in a menu + +// State management. +private: + + void State_Transition( TFCPlayerState newState ); + void State_Enter( TFCPlayerState newState ); + void State_Leave(); + CPlayerStateInfo* State_LookupInfo( TFCPlayerState state ); + + CPlayerStateInfo *m_pCurStateInfo; + + void State_Enter_WELCOME(); + void State_Enter_PICKINGTEAM(); + void State_Enter_PICKINGCLASS(); + void State_Enter_ACTIVE(); + void State_Enter_OBSERVER_MODE(); + void State_Enter_DYING(); + + +private: + + friend void Bot_Think( CTFCPlayer *pBot ); + void HandleCommand_JoinTeam( const char *pTeamName ); + void HandleCommand_JoinClass( const char *pClassName ); + + void GiveDefaultItems(); + + void TFCPlayerThink(); + + void PhysObjectSleep(); + void PhysObjectWake(); + + void GetIntoGame(); + + +private: + + // Copyed from EyeAngles() so we can send it to the client. + CNetworkQAngle( m_angEyeAngles ); + + ITFCPlayerAnimState *m_PlayerAnimState; + + int m_iLegDamage; +}; + + +inline CTFCPlayer *ToTFCPlayer( CBaseEntity *pEntity ) +{ + if ( !pEntity || !pEntity->IsPlayer() ) + return NULL; + +#ifdef _DEBUG + Assert( dynamic_cast<CTFCPlayer*>( pEntity ) != 0 ); +#endif + return static_cast< CTFCPlayer* >( pEntity ); +} + + +inline const CTFCPlayerClassInfo* CTFCPlayer::GetClassInfo() const +{ + return GetTFCClassInfo( m_Shared.GetPlayerClass() ); +} + + +#endif // TFC_PLAYER_H diff --git a/game/server/tfc/tfc_playermove.cpp b/game/server/tfc/tfc_playermove.cpp new file mode 100644 index 0000000..6ef1a90 --- /dev/null +++ b/game/server/tfc/tfc_playermove.cpp @@ -0,0 +1,76 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" +#include "player_command.h" +#include "igamemovement.h" +#include "in_buttons.h" +#include "ipredictionsystem.h" +#include "tfc_player.h" + + +static CMoveData g_MoveData; +CMoveData *g_pMoveData = &g_MoveData; + +IPredictionSystem *IPredictionSystem::g_pPredictionSystems = NULL; + + +//----------------------------------------------------------------------------- +// Sets up the move data for TF2 +//----------------------------------------------------------------------------- +class CTFCPlayerMove : public CPlayerMove +{ +DECLARE_CLASS( CTFCPlayerMove, CPlayerMove ); + +public: + virtual void StartCommand( CBasePlayer *player, CUserCmd *cmd ); + virtual void SetupMove( CBasePlayer *player, CUserCmd *ucmd, IMoveHelper *pHelper, CMoveData *move ); + virtual void FinishMove( CBasePlayer *player, CUserCmd *ucmd, CMoveData *move ); +}; + +// PlayerMove Interface +static CTFCPlayerMove g_PlayerMove; + +//----------------------------------------------------------------------------- +// Singleton accessor +//----------------------------------------------------------------------------- +CPlayerMove *PlayerMove() +{ + return &g_PlayerMove; +} + +//----------------------------------------------------------------------------- +// Main setup, finish +//----------------------------------------------------------------------------- + +void CTFCPlayerMove::StartCommand( CBasePlayer *player, CUserCmd *cmd ) +{ + BaseClass::StartCommand( player, cmd ); +} + +//----------------------------------------------------------------------------- +// 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 CTFCPlayerMove::SetupMove( CBasePlayer *player, CUserCmd *ucmd, IMoveHelper *pHelper, CMoveData *move ) +{ + BaseClass::SetupMove( player, ucmd, pHelper, move ); +} + + +//----------------------------------------------------------------------------- +// 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 CTFCPlayerMove::FinishMove( CBasePlayer *player, CUserCmd *ucmd, CMoveData *move ) +{ + // Call the default FinishMove code. + BaseClass::FinishMove( player, ucmd, move ); +} diff --git a/game/server/tfc/tfc_team.cpp b/game/server/tfc/tfc_team.cpp new file mode 100644 index 0000000..c4c7400 --- /dev/null +++ b/game/server/tfc/tfc_team.cpp @@ -0,0 +1,279 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Team management class. Contains all the details for a specific team +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "tfc_team.h" +#include "entitylist.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + + +// Datatable +IMPLEMENT_SERVERCLASS_ST(CTFCTeam, DT_TFCTeam) +END_SEND_TABLE() + +LINK_ENTITY_TO_CLASS( tfc_team_manager, CTFCTeam ); + + +Vector rgbcolors[5]; +team_color_t teamcolors[5][PC_LASTCLASS]; // Colors for each of the 4 teams +int number_of_teams = 0; // This is incremented for each map as info_player_teamspawn are created. +const char *teamnames[5] = +{ + "spectator", + "blue", + "red", + "yellow", + "green" +}; + +//======================================================================== +// Set the color for the team corresponding to the no passed in, to team_no +void TeamFortress_TeamSetColor() +{ + // Blue Team + teamcolors[1][PC_SCOUT].topColor = 153; + teamcolors[1][PC_SCOUT].bottomColor = 139; + + teamcolors[1][PC_SNIPER].topColor = 153; + teamcolors[1][PC_SNIPER].bottomColor = 145; + + teamcolors[1][PC_SOLDIER].topColor = 153; + teamcolors[1][PC_SOLDIER].bottomColor = 130; + + teamcolors[1][PC_DEMOMAN].topColor = 153; + teamcolors[1][PC_DEMOMAN].bottomColor = 145; + + teamcolors[1][PC_MEDIC].topColor = 153; + teamcolors[1][PC_MEDIC].bottomColor = 140; + + teamcolors[1][PC_HWGUY].topColor = 148; + teamcolors[1][PC_HWGUY].bottomColor = 138; + + teamcolors[1][PC_PYRO].topColor = 140; + teamcolors[1][PC_PYRO].bottomColor = 145; + + teamcolors[1][PC_SPY].topColor = 150; + teamcolors[1][PC_SPY].bottomColor = 145; + + teamcolors[1][PC_ENGINEER].topColor = 140; + teamcolors[1][PC_ENGINEER].bottomColor = 148; + + teamcolors[1][PC_CIVILIAN].topColor = 150; + teamcolors[1][PC_CIVILIAN].bottomColor = 140; + +#ifdef TFCTODO // sentry colors + teamcolors[1][SENTRY_COLOR].topColor = 150; + teamcolors[1][SENTRY_COLOR].bottomColor = 0; + + teamcolors[2][SENTRY_COLOR].topColor = 250; + teamcolors[2][SENTRY_COLOR].bottomColor = 0; + + teamcolors[3][SENTRY_COLOR].topColor = 45; + teamcolors[3][SENTRY_COLOR].bottomColor = 0; + + teamcolors[4][SENTRY_COLOR].topColor = 100; + teamcolors[4][SENTRY_COLOR].bottomColor = 0; +#endif + + // Red Team + teamcolors[2][PC_SCOUT].topColor = 255; + teamcolors[2][PC_SCOUT].bottomColor = 10; + + teamcolors[2][PC_SNIPER].topColor = 255; + teamcolors[2][PC_SNIPER].bottomColor = 10; + + teamcolors[2][PC_SOLDIER].topColor = 250; + teamcolors[2][PC_SOLDIER].bottomColor = 28; + + teamcolors[2][PC_DEMOMAN].topColor = 255; + teamcolors[2][PC_DEMOMAN].bottomColor = 20; + + teamcolors[2][PC_MEDIC].topColor = 255; + teamcolors[2][PC_MEDIC].bottomColor = 250; + + teamcolors[2][PC_HWGUY].topColor = 255; + teamcolors[2][PC_HWGUY].bottomColor = 25; + + teamcolors[2][PC_PYRO].topColor = 250; + teamcolors[2][PC_PYRO].bottomColor = 25; + + teamcolors[2][PC_SPY].topColor = 250; + teamcolors[2][PC_SPY].bottomColor = 240; + + teamcolors[2][PC_ENGINEER].topColor = 5; + teamcolors[2][PC_ENGINEER].bottomColor = 250; + + teamcolors[2][PC_CIVILIAN].topColor = 250; + teamcolors[2][PC_CIVILIAN].bottomColor = 240; + + + // Yellow Team + teamcolors[3][PC_SCOUT].topColor = 45; + teamcolors[3][PC_SCOUT].bottomColor = 35; + + teamcolors[3][PC_SNIPER].topColor = 45; + teamcolors[3][PC_SNIPER].bottomColor = 35; + + teamcolors[3][PC_SOLDIER].topColor = 45; + teamcolors[3][PC_SOLDIER].bottomColor = 35; + + teamcolors[3][PC_DEMOMAN].topColor = 45; + teamcolors[3][PC_DEMOMAN].bottomColor = 35; + + teamcolors[3][PC_MEDIC].topColor = 45; + teamcolors[3][PC_MEDIC].bottomColor = 35; + + teamcolors[3][PC_HWGUY].topColor = 45; + teamcolors[3][PC_HWGUY].bottomColor = 40; + + teamcolors[3][PC_PYRO].topColor = 45; + teamcolors[3][PC_PYRO].bottomColor = 35; + + teamcolors[3][PC_SPY].topColor = 45; + teamcolors[3][PC_SPY].bottomColor = 35; + + teamcolors[3][PC_ENGINEER].topColor = 45; + teamcolors[3][PC_ENGINEER].bottomColor = 45; + + teamcolors[3][PC_CIVILIAN].topColor = 45; + teamcolors[3][PC_CIVILIAN].bottomColor = 35; + + // Green Team + teamcolors[4][PC_SCOUT].topColor = 100; + teamcolors[4][PC_SCOUT].bottomColor = 90; + + teamcolors[4][PC_SNIPER].topColor = 80; + teamcolors[4][PC_SNIPER].bottomColor = 90; + + teamcolors[4][PC_SOLDIER].topColor = 100; + teamcolors[4][PC_SOLDIER].bottomColor = 40; + + teamcolors[4][PC_DEMOMAN].topColor = 100; + teamcolors[4][PC_DEMOMAN].bottomColor = 90; + + teamcolors[4][PC_MEDIC].topColor = 100; + teamcolors[4][PC_MEDIC].bottomColor = 90; + + teamcolors[4][PC_HWGUY].topColor = 100; + teamcolors[4][PC_HWGUY].bottomColor = 90; + + teamcolors[4][PC_PYRO].topColor = 100; + teamcolors[4][PC_PYRO].bottomColor = 50; + + teamcolors[4][PC_SPY].topColor = 100; + teamcolors[4][PC_SPY].bottomColor = 90; + + teamcolors[4][PC_ENGINEER].topColor = 100; + teamcolors[4][PC_ENGINEER].bottomColor = 90; + + teamcolors[4][PC_CIVILIAN].topColor = 100; + teamcolors[4][PC_CIVILIAN].bottomColor = 90; + + rgbcolors[0] = Vector( 255, 255, 255 ); // White for non-owned + rgbcolors[1] = Vector( 0, 0, 255 ); + rgbcolors[2] = Vector( 255, 0, 0 ); + rgbcolors[3] = Vector( 255, 255, 30 ); + rgbcolors[4] = Vector( 0, 255, 0 ); +} +class CColorInitializer +{ +public: + CColorInitializer() + { + TeamFortress_TeamSetColor(); + } +} g_ColorInitializer; + + + +//----------------------------------------------------------------------------- +// Purpose: Get a pointer to the specified TF team manager +//----------------------------------------------------------------------------- +CTFCTeam *GetGlobalTFCTeam( int iIndex ) +{ + return (CTFCTeam*)GetGlobalTeam( iIndex ); +} + + +// Display all the Team Scores +void TeamFortress_TeamShowScores(BOOL bLong, CBasePlayer *pPlayer) +{ + for (int i = 1; i < g_Teams.Count(); i++) + { + if (!bLong) + { + // Dump short scores + UTIL_ClientPrintAll( HUD_PRINTNOTIFY, UTIL_VarArgs("%s: %d\n", g_szTeamColors[i], GetGlobalTeam(i)->GetScore()) ); + } + else + { + // Dump long scores + if (pPlayer == NULL) + UTIL_ClientPrintAll( HUD_PRINTNOTIFY, UTIL_VarArgs("Team %d (%s): %d\n", i, g_szTeamColors[i], GetGlobalTeam(i)->GetScore()) ); + else // Print to just one client + ClientPrint( pPlayer, HUD_PRINTNOTIFY, UTIL_VarArgs("Team %d (%s): %d\n", i, g_szTeamColors[i], GetGlobalTeam(i)->GetScore()) ); + } + } +} + + +//========================================================================= +// Return the score/frags of a team, depending on whether TeamFrags is on +int TeamFortress_TeamGetScoreFrags(int tno) +{ + CTeam *pTeam = GetGlobalTeam( tno ); + if ( pTeam ) + { + return pTeam->GetScore(); + } + else + { + Assert( false ); + return -1; + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Needed because this is an entity, but should never be used +//----------------------------------------------------------------------------- +void CTFCTeam::Init( const char *pName, int iNumber ) +{ + BaseClass::Init( pName, iNumber ); + + // Only detect changes every half-second. + NetworkProp()->SetUpdateInterval( 0.75f ); +} + + +color32 CTFCTeam::GetTeamColor() +{ + int i = GetTeamNumber(); + if ( i >= 0 && i < ARRAYSIZE( rgbcolors ) ) + { + return Vector255ToRGBColor( rgbcolors[i] ); + } + else + { + Assert( false ); + color32 x; + memset( &x, 0, sizeof( x ) ); + return x; + } +} + + +color32 Vector255ToRGBColor( const Vector &vColor ) +{ + color32 ret; + ret.a = 0; + ret.r = (byte)vColor.x; + ret.g = (byte)vColor.y; + ret.b = (byte)vColor.z; + return ret; +} diff --git a/game/server/tfc/tfc_team.h b/game/server/tfc/tfc_team.h new file mode 100644 index 0000000..79953c7 --- /dev/null +++ b/game/server/tfc/tfc_team.h @@ -0,0 +1,58 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Team management class. Contains all the details for a specific team +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef TFC_TEAM_H +#define TFC_TEAM_H + +#ifdef _WIN32 +#pragma once +#endif + + +#include "utlvector.h" +#include "team.h" +#include "tfc_shareddefs.h" + + +//----------------------------------------------------------------------------- +// Purpose: Team Manager +//----------------------------------------------------------------------------- +class CTFCTeam : public CTeam +{ + DECLARE_CLASS( CTFCTeam, CTeam ); + DECLARE_SERVERCLASS(); + +public: + + // Initialization + virtual void Init( const char *pName, int iNumber ); + color32 GetTeamColor(); +}; + + +extern CTFCTeam *GetGlobalTFCTeam( int iIndex ); + +void TeamFortress_TeamShowScores(BOOL bLong, CBasePlayer *pPlayer); +int TeamFortress_TeamGetScoreFrags(int tno); + +// Colors for each team. +typedef struct +{ + int topColor; + int bottomColor; +} team_color_t; + +extern Vector rgbcolors[5]; +extern team_color_t teamcolors[5][PC_LASTCLASS]; // Colors for each of the 4 teams +extern int number_of_teams; // This is incremented for each map as info_player_teamspawn are created. +extern const char *teamnames[5]; +#define g_szTeamColors teamnames + +color32 Vector255ToRGBColor( const Vector &vColor ); + + +#endif // TF_TEAM_H diff --git a/game/server/tfc/tfc_timer.cpp b/game/server/tfc/tfc_timer.cpp new file mode 100644 index 0000000..a518602 --- /dev/null +++ b/game/server/tfc/tfc_timer.cpp @@ -0,0 +1,117 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "cbase.h" +#include "tfc_timer.h" + + +static CUtlLinkedList<CTimer*,int> g_Timers; + + +// ------------------------------------------------------------------------------------------ // +// CTimer functions. +// ------------------------------------------------------------------------------------------ // + +CTimer::CTimer() +{ + m_iTeamNumber = 0; + m_flNextThink = 0; +} + + +int CTimer::GetTeamNumber() const +{ + return m_iTeamNumber; +} + + +// ------------------------------------------------------------------------------------------ // +// Global timer functions. +// ------------------------------------------------------------------------------------------ // + +CTimer* Timer_FindTimer( CBaseEntity *pPlayer, TFCTimer_t timerType ) +{ + FOR_EACH_LL( g_Timers, i ) + { + CTimer *pTimer = g_Timers[i]; + + if ( pTimer->m_hOwner == pPlayer ) + { + if ( timerType == TF_TIMER_ANY || pTimer->m_Type == timerType ) + return pTimer; + } + } + return NULL; +} + + +CTimer* Timer_CreateTimer( CBaseEntity *pPlayer, TFCTimer_t timerType ) +{ + Assert( !Timer_FindTimer( pPlayer, timerType ) ); + + CTimer *pTimer = new CTimer; + pTimer->m_hOwner = pPlayer; + pTimer->m_Type = timerType; + pTimer->m_iListIndex = g_Timers.AddToTail( pTimer ); + + // TFCTODO: Register the think functions here.. + if ( pTimer->m_Type == TF_TIMER_ROTHEALTH ) + { + pTimer->m_flNextThink = gpGlobals->curtime + 5; + } + else if ( pTimer->m_Type == TF_TIMER_INFECTION ) + { + pTimer->m_flNextThink = gpGlobals->curtime + 2; + } + + // TFCTODO: hook up thinks... + // TF_TIMER_RETURNITEM -> CBaseEntity::ReturnItem -> <up to caller> + // TF_TIMER_ENDROUND -> CBaseEntity::EndRoundEnd -> <up to caller> + + return pTimer; +} + + +void Timer_Remove( CTimer *pTimer ) +{ + g_Timers.Remove( pTimer->m_iListIndex ); + delete pTimer; +} + + +void Timer_UpdateAll() +{ + int iNext = 0; + int i = g_Timers.Head(); + while ( i != g_Timers.InvalidIndex() ) + { + iNext = g_Timers.Next( i ); + CTimer *pTimer = g_Timers[i]; + i = iNext; + + // Get rid of invalid timers. + if ( pTimer->m_hOwner.Get() == NULL ) + { + g_Timers.Remove( i ); + delete pTimer; + } + else + { + // Is it time to think for this timer? + if ( gpGlobals->curtime >= pTimer->m_flNextThink ) + { + // TFCTODO: think here. + } + } + } +} + + +void Timer_RemoveAll() +{ + g_Timers.PurgeAndDeleteElements(); +} + diff --git a/game/server/tfc/tfc_timer.h b/game/server/tfc/tfc_timer.h new file mode 100644 index 0000000..156eb10 --- /dev/null +++ b/game/server/tfc/tfc_timer.h @@ -0,0 +1,60 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#ifndef TFC_TIMER_H +#define TFC_TIMER_H +#ifdef _WIN32 +#pragma once +#endif + + +#include "ehandle.h" +#include "tfc_shareddefs.h" + + +class CTFCPlayer; + + +class CTimer +{ +public: + CTimer(); + + int GetTeamNumber() const; + +public: + EHANDLE m_hOwner; + EHANDLE m_hEnemy; + TFCTimer_t m_Type; // One of the TF_TIMER_ defines. + int m_iTeamNumber; + float m_flNextThink; + int weapon; // GI_RET_ define. + + // For g_Timers. + int m_iListIndex; +}; + + +// This stuff replaces the functions like CBaseEntity::FindTimer, CBaseEntity::CreateTimer, +// and all the timer handlers in TFC. + +// Find an active timer on the specified entity. +CTimer* Timer_FindTimer( CBaseEntity *pPlayer, TFCTimer_t timerType ); + +// Create a new timer. +CTimer* Timer_CreateTimer( CBaseEntity *pPlayer, TFCTimer_t timerType ); + +// Get rid of a timer. +void Timer_Remove( CTimer *pTimer ); + +// Update all timers. +void Timer_UpdateAll(); + +// Call at round restart. +void Timer_RemoveAll(); + + +#endif // TFC_TIMER_H |