//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: Complete definition of the ControlZone behavioral entity // // $NoKeywords: $ //=============================================================================// #include "tf_shareddefs.h" #include "cbase.h" #include "EntityOutput.h" #include "tf_player.h" #include "controlzone.h" #include "team.h" //----------------------------------------------------------------------------- // Purpose: Since the control zone is a data only class, force it to always be sent ( shouldn't change often so ) // bandwidth usage should be small. // Input : **ppSendTable - // *recipient - // *pvs - // clientArea - // Output : Returns true on success, false on failure. //----------------------------------------------------------------------------- int CControlZone::UpdateTransmitState() { if ( IsEffectActive( EF_NODRAW ) ) { return SetTransmitState( FL_EDICT_DONTSEND ); } else { return SetTransmitState( FL_EDICT_ALWAYS ); } } IMPLEMENT_SERVERCLASS_ST(CControlZone, DT_ControlZone) SendPropInt( SENDINFO(m_nZoneNumber), 8, SPROP_UNSIGNED ), END_SEND_TABLE() LINK_ENTITY_TO_CLASS( trigger_controlzone, CControlZone); BEGIN_DATADESC( CControlZone ) // outputs DEFINE_OUTPUT( m_ControllingTeam, "ControllingTeam" ), // inputs DEFINE_INPUTFUNC( FIELD_VOID, "SetTeam", InputSetTeam ), DEFINE_INPUTFUNC( FIELD_VOID, "LockTeam", InputLockControllingTeam ), // keys DEFINE_KEYFIELD_NOT_SAVED( m_iLockAfterChange, FIELD_INTEGER, "LockAfterChange" ), DEFINE_KEYFIELD_NOT_SAVED( m_flTimeTillCaptured, FIELD_FLOAT, "UncontestedTime" ), DEFINE_KEYFIELD_NOT_SAVED( m_flTimeTillContested, FIELD_FLOAT, "ContestedTime" ), DEFINE_KEYFIELD_NOT_SAVED( m_nZoneNumber, FIELD_INTEGER, "ZoneNumber" ), END_DATADESC() // Control Zone Ent Flags #define CZF_DONT_USE_TOUCHES 1 //----------------------------------------------------------------------------- // Purpose: Initializes the control zone // Records who was the original controlling team (for control locking) //----------------------------------------------------------------------------- void CControlZone::Spawn( void ) { // set the starting controlling team m_ControllingTeam.Set( GetTeamNumber(), this, this ); // remember who the original controlling team was (for control locking) m_iDefendingTeam = GetTeamNumber(); // Solid SetSolid( SOLID_BSP ); AddSolidFlags( FSOLID_TRIGGER ); SetMoveType( MOVETYPE_NONE ); SetModel( STRING( GetModelName() ) ); // set size and link into world // TF2 rules m_flTimeTillContested = 10.0; // Go to contested 10 seconds after enemies enter the zone m_flTimeTillCaptured = 5.0; // Go to captured state as soon as only one team holds the zone if ( m_nZoneNumber == 0 ) { Warning( "Warning, trigger_controlzone without Zone Number set\n" ); } m_ZonePlayerList.Purge(); } //----------------------------------------------------------------------------- // Purpose: Records that a player has entered the zone, and updates it's state // according, maybe starting to change team. // Input : *pOther - the entity that left the zone //----------------------------------------------------------------------------- void CControlZone::StartTouch( CBaseEntity *pOther ) { CBaseTFPlayer *pl = ToBaseTFPlayer( pOther ); if ( !pl ) return; CHandle< CBaseTFPlayer > hHandle; hHandle = pl; m_ZonePlayerList.AddToTail( hHandle ); ReevaluateControllingTeam(); // Set this player's current zone to this zone pl->SetCurrentZone( this ); } //----------------------------------------------------------------------------- // Purpose: Records that a player has left the zone, and updates it's state // according, maybe starting to change team. // Input : *pOther - the entity that left the zone //----------------------------------------------------------------------------- void CControlZone::EndTouch( CBaseEntity *pOther ) { CBaseTFPlayer *pl = ToBaseTFPlayer( pOther ); if ( !pl ) return; CHandle< CBaseTFPlayer > hHandle; hHandle = pl; m_ZonePlayerList.FindAndRemove( hHandle ); ReevaluateControllingTeam(); // Unset this player's current zone if it's this one if ( pl->GetCurrentZone() == this ) pl->SetCurrentZone( NULL ); } //----------------------------------------------------------------------------- // Purpose: Checks to see if it's time to change controllers //----------------------------------------------------------------------------- void CControlZone::ReevaluateControllingTeam( void ) { // Count the number of players in each team int i; memset( m_iPlayersInZone, 0, sizeof( m_iPlayersInZone ) ); for ( i = 0; i < m_ZonePlayerList.Size(); i++ ) { if ( m_ZonePlayerList[i] != NULL && (m_ZonePlayerList[i]->GetTeamNumber() > 0) ) { m_iPlayersInZone[ m_ZonePlayerList[i]->GetTeamNumber() ] += 1; } } // Abort immediately if we're not using touches to changes teams if ( HasSpawnFlags( CZF_DONT_USE_TOUCHES ) ) return; // if we're locked in place, no changes can occur to controlling team except through an explicit map ResetTeam if ( m_iLocked ) return; bool foundAnyTeam = false; int teamFound = 0; // check to see if any teams have no players for ( i = 0; i < GetNumberOfTeams(); i++ ) { if ( m_iPlayersInZone[i] ) { if ( foundAnyTeam ) { // we've already found a team, so it's being contested; teamFound = ZONE_CONTESTED; break; } foundAnyTeam = true; teamFound = i; } } // no one in the area! if ( teamFound == 0 ) { // just leave it as it is, let it continue to change team // exception: if the zone state is contested, and there aren't any players in the zone, // just return to the team who used to own the zone. if ( GetTeamNumber() == ZONE_CONTESTED ) { ChangeTeam(m_iDefendingTeam); SetControllingTeam( this, m_iDefendingTeam ); } return; } // if it's the same controlling team, don't worry about it if ( teamFound == GetTeamNumber() ) { // the right team is in control, don't even think of switching m_iTryingToChangeToTeam = 0; SetNextThink( TICK_NEVER_THINK ); return; } // Find out if the zone isn't owned by anyone at all (hasn't been touched since the map started, and it started un-owned) bool bHasBeenOwned = true; if ( m_iDefendingTeam == 0 && GetTeamNumber() == 0 ) bHasBeenOwned = false; // if it's not contested, always go to contested mode if ( GetTeamNumber() != ZONE_CONTESTED && teamFound != GetTeamNumber() ) { // Unowned zones are captured immediately (no contesting stage) if ( bHasBeenOwned ) teamFound = ZONE_CONTESTED; } // if it's the team we're trying to change to, don't worry about it if ( teamFound == m_iTryingToChangeToTeam ) return; // set up the time to change to the new team soon m_iTryingToChangeToTeam = teamFound; // changing from contested->uncontested and visa-versa have different delays if ( m_iTryingToChangeToTeam != ZONE_CONTESTED ) { if ( !bHasBeenOwned ) { DevMsg( 1, "trigger_controlzone: (%s) changing team to %d NOW\n", GetDebugName(), m_iTryingToChangeToTeam ); SetNextThink( gpGlobals->curtime + 0.1f ); } else { DevMsg( 1, "trigger_controlzone: (%s) changing team to %d in %.2f seconds\n", GetDebugName(), m_iTryingToChangeToTeam, m_flTimeTillCaptured ); SetNextThink( gpGlobals->curtime + m_flTimeTillCaptured ); } } else { DevMsg( 1, "trigger_controlzone: (%s) changing to contested in %f seconds\n", GetDebugName(), m_flTimeTillContested ); SetNextThink( gpGlobals->curtime + m_flTimeTillContested ); } } //----------------------------------------------------------------------------- // Purpose: Checks to see if an uncontested territory is ready to change state // to the new controlling team. //----------------------------------------------------------------------------- void CControlZone::Think( void ) { if ( m_iTryingToChangeToTeam != 0 ) { // held zone long enough SetControllingTeam( this, m_iTryingToChangeToTeam ); // lock against further change if set if ( m_iLockAfterChange ) { LockControllingTeam(); } // Re-evaluate controlling team if we were changing to Contested (enemy may have withdrawn) if ( GetTeamNumber() == ZONE_CONTESTED ) { ReevaluateControllingTeam(); } } } //----------------------------------------------------------------------------- // Purpose: set it so the team can no longer change, until a set controlling team action occurs //----------------------------------------------------------------------------- void CControlZone::InputLockControllingTeam( inputdata_t &inputdata ) { LockControllingTeam(); } //----------------------------------------------------------------------------- // Purpose: Input handler that sets the controlling team to the activator's team. //----------------------------------------------------------------------------- void CControlZone::InputSetTeam( inputdata_t &inputdata ) { // Abort if it's already the defending team if ( inputdata.pActivator->GetTeamNumber() == GetTeamNumber() ) return; // set the new team ChangeTeam(inputdata.pActivator->GetTeamNumber()); SetControllingTeam( inputdata.pActivator, GetTeamNumber() ); } //----------------------------------------------------------------------------- // Purpose: Changes the team controlling this zone // Input : newTeam - the new team to change to //----------------------------------------------------------------------------- void CControlZone::SetControllingTeam( CBaseEntity *pActivator, int newTeam ) { DevMsg( 1, "trigger_controlzone: (%s) changing team to: %d\n", GetDebugName(), newTeam ); // remember this team as the defenders of the zone m_iDefendingTeam = GetTeamNumber(); // reset state, firing the output ChangeTeam(newTeam); m_ControllingTeam.Set( GetTeamNumber(), pActivator, this ); m_iLocked = FALSE; m_iTryingToChangeToTeam = 0; SetNextThink( TICK_NEVER_THINK ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CControlZone::LockControllingTeam( void ) { // never lock a zone in contested mode if ( GetTeamNumber() == ZONE_CONTESTED ) return; // zones never lock to the defenders if ( GetTeamNumber() == m_iDefendingTeam ) return; m_iLocked = TRUE; }