This commit is contained in:
nephacks
2025-06-04 03:22:50 +02:00
parent f234f23848
commit f12416cffd
14243 changed files with 6446499 additions and 26 deletions
File diff suppressed because it is too large Load Diff
+150
View File
@@ -0,0 +1,150 @@
// chicken.h
// An interactive, shootable chicken
#ifndef CHICKEN_H
#define CHICKEN_H
#include "props.h"
#include "GameEventListener.h"
#include "nav_mesh.h"
#include "cs_nav_path.h"
#include "cs_nav_pathfind.h"
#include "improv_locomotor.h"
class CCSPlayer;
class CChicken : public CDynamicProp, public CGameEventListener, public CImprovLocomotor
{
public:
DECLARE_CLASS( CChicken, CDynamicProp );
DECLARE_SERVERCLASS();
DECLARE_DATADESC();
CChicken();
virtual ~CChicken();
virtual void Precache( void );
virtual void Spawn( void );
virtual void ChickenTouch( CBaseEntity *pOther );
virtual int OnTakeDamage( const CTakeDamageInfo &info );
virtual void Event_Killed( const CTakeDamageInfo &info );
virtual void FireGameEvent( IGameEvent *event );
virtual bool IsAlive( void ) { return true; }
void ChickenThink( void );
bool IsZombie( void ) { return m_flWhenZombified > 0; }
void Zombify( void ) { m_flWhenZombified = gpGlobals->curtime; }
bool IsFollowingSomeone( void );
bool IsFollowing( const CBaseEntity *entity );
bool IsOnGround( void ) const;
void Follow( CCSPlayer *leader ); // begin following "leader"
CCSPlayer *GetLeader( void ) const; // return our leader, or NULL
void FaceTowards( const Vector &target, float deltaT ); // rotate body to face towards "target"
int ObjectCaps( void ) { return ( BaseClass::ObjectCaps( ) | FCAP_IMPULSE_USE ); }
public:
// begin CImprovLocomotor -----------------------------------------------------------------------------------------------------------------
virtual const Vector &GetCentroid( void ) const;
virtual const Vector &GetFeet( void ) const; // return position of "feet" - point below centroid of improv at feet level
virtual const Vector &GetEyes( void ) const;
virtual float GetMoveAngle( void ) const; // return direction of movement
virtual CNavArea *GetLastKnownArea( void ) const;
virtual bool GetSimpleGroundHeightWithFloor( const Vector &pos, float *height, Vector *normal = NULL ); // find "simple" ground height, treating current nav area as part of the floor
virtual void Crouch( void );
virtual void StandUp( void ); // "un-crouch"
virtual bool IsCrouching( void ) const;
virtual void Jump( void ); // initiate a jump
virtual bool IsJumping( void ) const;
bool CanJump( void ) const;
void Jump( float flVelocity );
virtual void Run( void ); // set movement speed to running
virtual void Walk( void ); // set movement speed to walking
virtual bool IsRunning( void ) const;
virtual void StartLadder( const CNavLadder *ladder, NavTraverseType how, const Vector &approachPos, const Vector &departPos ); // invoked when a ladder is encountered while following a path
virtual bool TraverseLadder( const CNavLadder *ladder, NavTraverseType how, const Vector &approachPos, const Vector &departPos, float deltaT ); // traverse given ladder
virtual bool IsUsingLadder( void ) const;
virtual void TrackPath( const Vector &pathGoal, float deltaT ); // move along path by following "pathGoal"
virtual void OnMoveToSuccess( const Vector &goal ); // invoked when an improv reaches its MoveTo goal
virtual void OnMoveToFailure( const Vector &goal, MoveToFailureType reason ); // invoked when an improv fails to reach a MoveTo goal
// end CImprovLocomotor -------------------------------------------------------------------------------------------------------------------
protected:
virtual void ChickenUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value );
void SetChickenStartFollowingPlayer( CCSPlayer *pPlayer );
private:
void Idle();
// void Walk();
void Flee( CBaseEntity *fleeFrom, float duration );
void Fly();
void Land();
void Update( void );
CountdownTimer m_updateTimer;
float AvoidObstacles( void );
Vector m_stuckAnchor;
CountdownTimer m_stuckTimer;
void ResolveCollisions( const Vector &desiredPosition, float deltaT );
bool m_isOnGround;
Activity m_activity;
CountdownTimer m_activityTimer;
float m_turnRate;
CHandle< CBaseEntity > m_fleeFrom;
CountdownTimer m_moveRateThrottleTimer;
CountdownTimer m_startleTimer;
CountdownTimer m_vocalizeTimer;
float m_flWhenZombified;
CNetworkVar( bool, m_jumpedThisFrame );
CNetworkVar( EHANDLE, m_leader ); // the player we are following
void UpdateFollowing( float deltaT ); // do following behavior
int m_lastLeaderID;
CountdownTimer m_reuseTimer; // to throttle how often hostage can be used
bool m_hasBeenUsed;
CountdownTimer m_jumpTimer; // if zero, we can jump
float m_flLastJumpTime;
bool m_bInJump;
bool m_isWaitingForLeader; // true if we are waiting for our rescuer to move
CCSNavPath m_path; // current path to follow
CountdownTimer m_repathTimer; // throttle pathfinder
CountdownTimer m_inhibitDoorTimer;
CNavPathFollower m_pathFollower; // path tracking mechanism
CountdownTimer m_inhibitObstacleAvoidanceTimer; // when active, turn off path following feelers
CNavArea *m_lastKnownArea; // last area we were in
Vector m_vecPathGoal;
float m_flActiveFollowStartTime; // when the current follow started
};
#endif // CHICKEN_H
File diff suppressed because it is too large Load Diff
+163
View File
@@ -0,0 +1,163 @@
// Inferno.h
// An Inferno
// Author: Michael S. Booth, February 2005
// Copyright (c) 2005 Turtle Rock Studios, Inc. - All Rights Reserved
#ifndef _INFERNO_H_
#define _INFERNO_H_
#include "nav.h" // for extent class - should be moved somewhere more general
#include "GameEventListener.h"
// Forward declaration
class CBasePlayer;
/**
* The server-side Inferno entity.
* This entity manages the growth of the flames and damage-dealing.
* Keep this in sync with C_Inferno on the client, which manages the fire rendering
*/
enum
{
INFERNO_TYPE_FIRE = 0,
INFERNO_TYPE_INCGREN_FIRE, // incendiary grenade fire, used to play a different sound
INFERNO_TYPE_FIREWORKS,
};
class CInferno : public CBaseEntity, public CGameEventListener
{
public:
DECLARE_CLASS( CInferno, CBaseEntity );
DECLARE_SERVERCLASS();
DECLARE_DATADESC();
CInferno();
virtual ~CInferno();
virtual void Precache( void );
virtual void Spawn( void );
void SetMaxFlames( int nMaxFlames ) { m_nMaxFlames = nMaxFlames; }
virtual int UpdateTransmitState( void )
{
return FL_EDICT_ALWAYS;
}
void StartBurning( const Vector &pos, const Vector &normal, const Vector &velocity, int initialDepth = 0 ); // start the Inferno burning
void InfernoThink( void );
bool BShouldExtinguishSmokeGrenadeBounce( CBaseEntity *entity, Vector &posDropSmoke ) const;
bool IsTouching( const Vector &from, const Vector &to, Vector *where = NULL ) const; // return true if given ray intersects any fires, point of intersection in "where" if non-NULL
bool IsTouching( const CNavArea *area ) const; // return true if given area overlaps any fires
bool WasCreatedInSmoke( void ) const { return m_bWasCreatedInSmoke; }
virtual int GetDamageType() { return DMG_BURN; }
virtual float GetDamagePerSecond();
// I've moved some functionality into these member functions in order to
// make the insect swarm easier to implement (sjb)
virtual const char *GetParticleEffectName();
virtual const char *GetImpactParticleEffectName();
virtual float GetFlameLifetime() const;
virtual bool CanHarm( CBaseEntity *pEnt ) const { return true; }
virtual float GetFlameSpreadDelay() { return 0.0f; }
int GetInfernoType() const { return m_nInfernoType; }
void SetInfernoType( int type ) { m_nInfernoType = type; }
void SetSourceWeaponInfo( const CCSWeaponInfo* pWeaponInfo ) { m_pWeaponInfo = pWeaponInfo; }
const CCSWeaponInfo* GetSourceWeaponInfo() const { return m_pWeaponInfo; }
protected:
void FireGameEvent( IGameEvent *event );
int ExtinguishFlamesAroundSmokeGrenade( Vector vecStart ); // returns count of fire patches extinguished
void ExtinguishIndividualFlameBySmokeGrenade( int iFire, Vector vecStart );
// Checks if all flames have expired and we should delete the inferno
bool CheckExpired();
// Marks covered areas as damaging so bots will avoid them
void MarkCoveredAreaAsDamaging();
private:
enum { MAX_INFERNO_FIRES = 64 };
enum ECreateFireResult_t
{
k_ECreateFireResult_OK,
k_ECreateFireResult_LimitExceeded,
k_ECreateFireResult_AlreadyOnFire,
k_ECreateFireResult_InSmoke,
k_ECreateFireResult_AllSolid,
};
CNetworkArray( int, m_fireXDelta, MAX_INFERNO_FIRES );
CNetworkArray( int, m_fireYDelta, MAX_INFERNO_FIRES );
CNetworkArray( int, m_fireZDelta, MAX_INFERNO_FIRES );
CNetworkArray( bool, m_bFireIsBurning, MAX_INFERNO_FIRES );
CNetworkArray( Vector, m_BurnNormal, MAX_INFERNO_FIRES );
CNetworkVar( int, m_fireCount ); // total number of flames spawned
CNetworkVar( int, m_nInfernoType );
bool m_bWasCreatedInSmoke;
struct FireInfo
{
Vector m_pos; // location of this fire
Vector m_center; // center of fire
Vector m_normal; // surface normal at this fire
bool m_burning;
int m_treeDepth;
int m_spawnCount;
FireInfo *m_parent; // the fire that spawned us
CountdownTimer m_spawnLifetime; // how long we attempt to spawn new fires
CountdownTimer m_spawnTimer; // when we try to spawn a new fire
CountdownTimer m_lifetime; // lifetime of this fire
float m_flWaterHeight; // how much we were raised above water
};
bool IsFirePosInSmokeCloud( const Vector &pos ) const;
void Spread( const Vector &spreadVelocity );
ECreateFireResult_t CreateFire( const Vector &pos, const Vector &normal, FireInfo *parent, int depth ); // create an actual fire entity at the given position
FireInfo *m_fire[ MAX_INFERNO_FIRES ]; // set of all active fires
Extent m_extent;
void RecomputeExtent( void );
CountdownTimer m_damageTimer;
CountdownTimer m_damageRampTimer; // damage starts at zero and ramps up
bool IsTouching( CBaseEntity *entity, float radius, bool checkLOS ) const; // return true if position is in contact with a fire within the Inferno
Vector m_splashVelocity; // the velocity of the flame-causing incendiary that hit
Vector m_startPos; // the ignition point of the entire inferno
IntervalTimer m_activeTimer; // How long this inferno has been active.
int m_fireSpawnOffset;
int m_nMaxFlames;
CountdownTimer m_BookkeepingTimer; // Only check spread / force expiry checks every 0.1s;
CountdownTimer m_NextSpreadTimer;
// the weapon info for the grenade type (if any) that spawned this inferno
const CCSWeaponInfo* m_pWeaponInfo;
};
class CFireCrackerBlast : public CInferno
{
public:
DECLARE_CLASS( CFireCrackerBlast, CInferno );
DECLARE_SERVERCLASS();
virtual void Spawn();
virtual const char *GetParticleEffectName();
virtual const char *GetImpactParticleEffectName();
};
#endif // _INFERNO_H_
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+676
View File
@@ -0,0 +1,676 @@
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose: Bot radio chatter system
//
// $NoKeywords: $
//=============================================================================//
// Author: Michael S. Booth (mike@turtlerockstudios.com), 2003
#ifndef CS_BOT_CHATTER_H
#define CS_BOT_CHATTER_H
#pragma warning( disable : 4786 ) // long STL names get truncated in browse info.
#include "nav_mesh.h"
#include "cs_gamestate.h"
class CCSBot;
class BotChatterInterface;
#define MAX_PLACES_PER_MAP 64
typedef unsigned int PlaceCriteria;
typedef unsigned int CountCriteria;
#define UNDEFINED_COUNT 0xFFFF
#define COUNT_CURRENT_ENEMIES 0xFF // use the number of enemies we see right when we speak
#define COUNT_MANY 4 // equal to or greater than this is "many"
#define UNDEFINED_SUBJECT (-1)
/// @todo Make Place a class with member fuctions for this
bool GetRandomSpotAtPlace( Place place, Vector *pPos );
//----------------------------------------------------------------------------------------------------
/**
* A meme is a unit information that bots use to
* transmit information to each other via the radio
*/
class BotMeme
{
public:
void Transmit( CCSBot *sender ) const; ///< transmit meme to other bots
virtual void Interpret( CCSBot *sender, CCSBot *receiver ) const = 0; ///< cause the given bot to act on this meme
};
//----------------------------------------------------------------------------------------------------
class BotHelpMeme : public BotMeme
{
public:
BotHelpMeme( Place place = UNDEFINED_PLACE )
{
m_place = place;
}
virtual void Interpret( CCSBot *sender, CCSBot *receiver ) const; ///< cause the given bot to act on this meme
private:
Place m_place; ///< where the help is needed
};
//----------------------------------------------------------------------------------------------------
class BotBombsiteStatusMeme : public BotMeme
{
public:
enum StatusType { CLEAR, PLANTED };
BotBombsiteStatusMeme( int zoneIndex, StatusType status )
{
m_zoneIndex = zoneIndex;
m_status = status;
}
virtual void Interpret( CCSBot *sender, CCSBot *receiver ) const; ///< cause the given bot to act on this meme
private:
int m_zoneIndex; ///< the bombsite
StatusType m_status; ///< whether it is cleared or the bomb is there (planted)
};
//----------------------------------------------------------------------------------------------------
class BotBombStatusMeme : public BotMeme
{
public:
BotBombStatusMeme( CSGameState::BombState state, const Vector &pos )
{
m_state = state;
m_pos = pos;
}
virtual void Interpret( CCSBot *sender, CCSBot *receiver ) const; ///< cause the given bot to act on this meme
private:
CSGameState::BombState m_state;
Vector m_pos;
};
//----------------------------------------------------------------------------------------------------
class BotFollowMeme : public BotMeme
{
public:
virtual void Interpret( CCSBot *sender, CCSBot *receiver ) const; ///< cause the given bot to act on this meme
};
//----------------------------------------------------------------------------------------------------
class BotDefendHereMeme : public BotMeme
{
public:
BotDefendHereMeme( const Vector &pos )
{
m_pos = pos;
}
virtual void Interpret( CCSBot *sender, CCSBot *receiver ) const; ///< cause the given bot to act on this meme
private:
Vector m_pos;
};
//----------------------------------------------------------------------------------------------------
class BotWhereBombMeme : public BotMeme
{
public:
virtual void Interpret( CCSBot *sender, CCSBot *receiver ) const; ///< cause the given bot to act on this meme
};
//----------------------------------------------------------------------------------------------------
class BotRequestReportMeme : public BotMeme
{
public:
virtual void Interpret( CCSBot *sender, CCSBot *receiver ) const; ///< cause the given bot to act on this meme
};
//----------------------------------------------------------------------------------------------------
class BotAllHostagesGoneMeme : public BotMeme
{
public:
virtual void Interpret( CCSBot *sender, CCSBot *receiver ) const; ///< cause the given bot to act on this meme
};
//----------------------------------------------------------------------------------------------------
class BotHostageBeingTakenMeme : public BotMeme
{
public:
virtual void Interpret( CCSBot *sender, CCSBot *receiver ) const; ///< cause the given bot to act on this meme
};
//----------------------------------------------------------------------------------------------------
class BotHeardNoiseMeme : public BotMeme
{
public:
virtual void Interpret( CCSBot *sender, CCSBot *receiver ) const; ///< cause the given bot to act on this meme
};
//----------------------------------------------------------------------------------------------------
class BotWarnSniperMeme : public BotMeme
{
public:
virtual void Interpret( CCSBot *sender, CCSBot *receiver ) const; ///< cause the given bot to act on this meme
};
//----------------------------------------------------------------------------------------------------
enum BotStatementType
{
REPORT_VISIBLE_ENEMIES,
REPORT_ENEMY_ACTION,
REPORT_MY_CURRENT_TASK,
REPORT_MY_INTENTION,
REPORT_CRITICAL_EVENT,
REPORT_REQUEST_HELP,
REPORT_REQUEST_INFORMATION,
REPORT_ROUND_END,
REPORT_MY_PLAN,
REPORT_INFORMATION,
REPORT_EMOTE,
REPORT_ACKNOWLEDGE, ///< affirmative or negative
REPORT_ENEMIES_REMAINING,
REPORT_FRIENDLY_FIRE,
REPORT_KILLED_FRIEND,
REPORT_ENEMY_LOST,
NUM_BOT_STATEMENT_TYPES
};
//----------------------------------------------------------------------------------------------------
//----------------------------------------------------------------------------------------------------
/**
* BotSpeakables are the smallest unit of bot chatter.
* They represent a specific wav file of a phrase, and the criteria for which it is useful
*/
class BotSpeakable
{
public:
BotSpeakable();
~BotSpeakable();
char *m_phrase;
float m_duration;
PlaceCriteria m_place;
CountCriteria m_count;
};
typedef CUtlVector< BotSpeakable * > BotSpeakableVector;
typedef CUtlVector< BotSpeakableVector * > BotVoiceBankVector;
//----------------------------------------------------------------------------------------------------
/**
* The BotPhrase class is a collection of Speakables associated with a name, ID, and criteria
*/
class BotPhrase
{
public:
char *GetSpeakable( int bankIndex, float *duration = NULL ) const; ///< return a random speakable and its duration in seconds that meets the current criteria
// NOTE: Criteria must be set just before the GetSpeakable() call, since they are shared among all bots
void ClearCriteria( void ) const;
void SetPlaceCriteria( PlaceCriteria place ) const; ///< all returned phrases must have this place criteria
void SetCountCriteria( CountCriteria count ) const; ///< all returned phrases must have this count criteria
void SetCriteriaSet( AI_CriteriaSet &criteria ) const;
const char *GetName( void ) const { return m_name; }
const unsigned int GetPlace( void ) const { return m_place; }
RadioType GetRadioEquivalent( void ) const { return m_radioEvent; } ///< return equivalent "standard radio" event
bool IsImportant( void ) const { return m_isImportant; } ///< return true if this phrase is part of an important statement
bool IsPlace( void ) const { return m_isPlace; }
void Randomize( void ); ///< randomly shuffle the speakable order
PlaceCriteria GetPlaceCriteria( void ) const { return m_placeCriteria; }
CountCriteria GetCountCriteria( void ) const { return m_countCriteria; }
AI_CriteriaSet &GetCriteriaSet( void ) const { return m_contexts; }
private:
friend class BotPhraseManager;
BotPhrase( bool isPlace );
~BotPhrase();
char *m_name;
Place m_place;
bool m_isPlace; ///< true if this is a Place phrase
RadioType m_radioEvent; ///< equivalent radio event
bool m_isImportant; ///< mission-critical statement
mutable BotVoiceBankVector m_voiceBank; ///< array of voice banks (arrays of speakables)
CUtlVector< int > m_count; ///< number of speakables
mutable CUtlVector< int > m_index; ///< index of next speakable to return
int m_numVoiceBanks; ///< number of voice banks that have been initialized
void InitVoiceBank( int bankIndex ); ///< sets up the vector of voice banks for the first bankIndex voice banks
mutable PlaceCriteria m_placeCriteria;
mutable CountCriteria m_countCriteria;
mutable AI_CriteriaSet m_contexts;
};
typedef CUtlVector<BotPhrase *> BotPhraseList;
inline void BotPhrase::ClearCriteria( void ) const
{
m_placeCriteria = ANY_PLACE;
m_countCriteria = UNDEFINED_COUNT;
}
inline void BotPhrase::SetPlaceCriteria( PlaceCriteria place ) const
{
m_placeCriteria = place;
}
inline void BotPhrase::SetCountCriteria( CountCriteria count ) const
{
m_countCriteria = count;
}
inline void BotPhrase::SetCriteriaSet( AI_CriteriaSet &criteria ) const
{
m_contexts.Reset();
m_contexts.Merge( &criteria );
}
enum BotChatterOutputType
{
BOT_CHATTER_RADIO,
BOT_CHATTER_VOICE
};
typedef CUtlVector<BotChatterOutputType> BotOutputList;
//----------------------------------------------------------------------------------------------------
/**
* The BotPhraseManager is a singleton that provides an interface to all BotPhrase collections
*/
class BotPhraseManager
{
public:
BotPhraseManager( void );
~BotPhraseManager();
bool Initialize( const char *filename, int bankIndex ); ///< initialize phrase system from database file for a specific voice bank (0 is the default voice bank)
void OnRoundRestart( void ); ///< invoked when round resets
void OnMapChange( void ); ///< invoked when map changes
void Reset( void );
const BotPhrase *GetPhrase( const char *name ) const; ///< given a name, return the associated phrase collection
const BotPhrase *GetPainPhrase( void ) const { return m_painPhrase; } ///< optimization, replaces a static pointer to the phrase
const BotPhrase *GetAgreeWithPlanPhrase( void ) const { return m_agreeWithPlanPhrase; } ///< optimization, replaces a static pointer to the phrase
const BotPhrase *GetPlace( const char *name ) const; ///< given a name, return the associated Place phrase collection
const BotPhrase *GetPlace( unsigned int id ) const; ///< given an id, return the associated Place phrase collection
const BotPhraseList *GetPlaceList( void ) const { return &m_placeList; }
float GetPlaceStatementInterval( Place where ) const; ///< return time last statement of given type was emitted by a teammate for the given place
void ResetPlaceStatementInterval( Place where ); ///< set time of last statement of given type was emitted by a teammate for the given place
BotChatterOutputType GetOutputType( int voiceBank ) const;
private:
BotPhraseList m_list; ///< master list of all phrase collections
BotPhraseList m_placeList; ///< master list of all Place phrases
BotOutputList m_output;
const BotPhrase *m_painPhrase;
const BotPhrase *m_agreeWithPlanPhrase;
struct PlaceTimeInfo
{
Place placeID;
IntervalTimer timer;
};
mutable PlaceTimeInfo m_placeStatementHistory[ MAX_PLACES_PER_MAP ];
mutable int m_placeCount;
int FindPlaceIndex( Place where ) const;
};
inline int BotPhraseManager::FindPlaceIndex( Place where ) const
{
for( int i=0; i<m_placeCount; ++i )
if (m_placeStatementHistory[i].placeID == where)
return i;
// no such place - allocate it
if (m_placeCount < MAX_PLACES_PER_MAP)
{
m_placeStatementHistory[ m_placeCount ].placeID = where;
m_placeStatementHistory[ m_placeCount ].timer.Invalidate();
++m_placeCount;
return m_placeCount-1;
}
// place directory is full
return -1;
}
/**
* Return time last statement of given type was emitted by a teammate for the given place
*/
inline float BotPhraseManager::GetPlaceStatementInterval( Place place ) const
{
int index = FindPlaceIndex( place );
if (index < 0)
return 999999.9f;
if (index >= m_placeCount)
return 999999.9f;
return m_placeStatementHistory[ index ].timer.GetElapsedTime();
}
/**
* Set time of last statement of given type was emitted by a teammate for the given place
*/
inline void BotPhraseManager::ResetPlaceStatementInterval( Place place )
{
int index = FindPlaceIndex( place );
if (index < 0)
return;
if (index >= m_placeCount)
return;
// update entry
m_placeStatementHistory[ index ].timer.Reset();
}
extern BotPhraseManager *TheBotPhrases;
//----------------------------------------------------------------------------------------------------
/**
* Statements are meaningful collections of phrases
*/
class BotStatement
{
public:
BotStatement( BotChatterInterface *chatter, BotStatementType type, float expireDuration );
~BotStatement();
BotChatterInterface *GetChatter( void ) const { return m_chatter; }
CCSBot *GetOwner( void ) const;
BotStatementType GetType( void ) const { return m_type; } ///< return the type of statement this is
bool IsImportant( void ) const; ///< return true if this statement is "important" and not personality chatter
bool HasSubject( void ) const { return (m_subject == UNDEFINED_SUBJECT) ? false : true; }
void SetSubject( int playerID ) { m_subject = playerID; } ///< who this statement is about
int GetSubject( void ) const { return m_subject; } ///< who this statement is about
bool HasPlace( void ) const { return (GetPlace()) ? true : false; }
Place GetPlace( void ) const; ///< if this statement refers to a specific place, return that place
void SetPlace( Place where ) { m_place = where; } ///< explicitly set place
bool HasCount( void ) const; ///< return true if this statement has an associated count
bool IsRedundant( const BotStatement *say ) const; ///< return true if this statement is the same as the given one
bool IsObsolete( void ) const; ///< return true if this statement is no longer appropriate to say
void Convert( const BotStatement *say ); ///< possibly change what were going to say base on what teammate is saying
void AppendPhrase( const BotPhrase *phrase );
void SetStartTime( float timestamp ) { m_startTime = timestamp; } ///< define the earliest time this statement can be spoken
float GetStartTime( void ) const { return m_startTime; }
enum ConditionType
{
IS_IN_COMBAT,
RADIO_SILENCE,
ENEMIES_REMAINING,
NUM_CONDITIONS
};
void AddCondition( ConditionType condition ); ///< conditions must be true for the statement to be spoken
bool IsValid( void ) const; ///< verify all attached conditions
enum ContextType
{
CURRENT_ENEMY_COUNT,
REMAINING_ENEMY_COUNT,
SHORT_DELAY,
LONG_DELAY,
ACCUMULATE_ENEMIES_DELAY
};
void AppendPhrase( ContextType contextPhrase ); ///< special phrases that depend on the context
bool Update( void ); ///< emit statement over time, return false if statement is done
bool IsSpeaking( void ) const { return m_isSpeaking; } ///< return true if this statement is currently being spoken
float GetTimestamp( void ) const { return m_timestamp; } ///< get time statement was created (but not necessarily started talking)
void AttachMeme( BotMeme *meme ); ///< attach a meme to this statement, to be transmitted to other friendly bots when spoken
private:
friend class BotChatterInterface;
BotChatterInterface *m_chatter; ///< the chatter system this statement is part of
BotStatement *m_next, *m_prev; ///< linked list hooks
BotStatementType m_type; ///< what kind of statement this is
int m_subject; ///< who this subject is about
Place m_place; ///< explicit place - note some phrases have implicit places as well
BotMeme *m_meme; ///< a statement can only have a single meme for now
float m_timestamp; ///< time when message was created
float m_startTime; ///< the earliest time this statement can be spoken
float m_expireTime; ///< time when this statement is no longer valid
float m_speakTimestamp; ///< time when message began being spoken
bool m_isSpeaking; ///< true if this statement is current being spoken
float m_nextTime; ///< time for next phrase to begin
enum { MAX_BOT_PHRASES = 4 };
struct
{
bool isPhrase;
union
{
const BotPhrase *phrase;
ContextType context;
};
}
m_statement[ MAX_BOT_PHRASES ];
enum { MAX_BOT_CONDITIONS = 4 };
ConditionType m_condition[ MAX_BOT_CONDITIONS ]; ///< conditions that must be true for the statement to be said
int m_conditionCount;
int m_index; ///< m_index refers to the phrase currently being spoken, or -1 if we havent started yet
int m_count;
};
//----------------------------------------------------------------------------------------------------
/**
* This class defines the interface to the bot radio chatter system
*/
class BotChatterInterface
{
public:
BotChatterInterface( CCSBot *me );
virtual ~BotChatterInterface();
void Reset( void ); ///< reset to initial state
void Update( void ); ///< process ongoing chatter
/// invoked when event occurs in the game (some events have NULL entities)
void OnDeath( void ); ///< invoked when we die
enum VerbosityType
{
NORMAL, ///< full chatter
MINIMAL, ///< only scenario-critical events
RADIO, ///< use the standard radio instead
OFF ///< no chatter at all
};
VerbosityType GetVerbosity( void ) const; ///< return our current level of verbosity
CCSBot *GetOwner( void ) const { return m_me; }
bool IsTalking( void ) const; ///< return true if we are currently talking
float GetRadioSilenceDuration( void ); ///< return time since any teammate said anything
void ResetRadioSilenceDuration( void );
enum { MUST_ADD = 1 };
void AddStatement( BotStatement *statement, bool mustAdd = false ); ///< register a statement for speaking
void RemoveStatement( BotStatement *statement ); ///< remove a statement
BotStatement *GetActiveStatement( void ); ///< returns the statement that is being spoken, or is next to be spoken if no-one is speaking now
BotStatement *GetStatement( void ) const; ///< returns our current statement, or NULL if we aren't speaking
int GetPitch( void ) const { return m_pitch; }
//-- things the bots can say ---------------------------------------------------------------------
void Say( const char *phraseName, float lifetime = 3.0f, float delay = 0.0f );
void AnnouncePlan( const char *phraseName, Place where );
void Affirmative( void );
void Negative( void );
virtual void EnemySpotted( void ); ///< report enemy sightings
virtual void KilledMyEnemy( int victimID );
virtual void EnemiesRemaining( void );
void SpottedSniper( void );
void FriendSpottedSniper( void );
void Clear( Place where );
void ReportIn( void ); ///< ask for current situation
void ReportingIn( void ); ///< report current situation
virtual bool NeedBackup( void );
void PinnedDown( void );
void Scared( void );
void HeardNoise( const Vector &pos );
void FriendHeardNoise( void );
void TheyPickedUpTheBomb( void );
void GoingToPlantTheBomb( Place where );
void BombsiteClear( int zoneIndex );
void FoundPlantedBomb( int zoneIndex );
void PlantingTheBomb( Place where );
void SpottedBomber( CBasePlayer *bomber );
void SpottedLooseBomb( CBaseEntity *bomb );
void GuardingLooseBomb( CBaseEntity *bomb );
void RequestBombLocation( void );
#define IS_PLAN true
void GuardingHostages( Place where, bool isPlan = false );
void GuardingHostageEscapeZone( bool isPlan = false );
void HostagesBeingTaken( void );
void HostagesTaken( void );
void TalkingToHostages( void );
void EscortingHostages( void );
void HostageDown( void );
void GuardingBombsite( Place where );
virtual void CelebrateWin( void );
void Encourage( const char *phraseName, float repeatInterval = 10.0f, float lifetime = 3.0f ); ///< "encourage" the player to do the scenario
void KilledFriend( void );
void FriendlyFire( const char *pDmgType );
void DoPhoenixHeavyWakeTaunt( void );
bool SeesAtLeastOneEnemy( void ) const { return m_seeAtLeastOneEnemy; }
private:
BotStatement *m_statementList; ///< list of all active/pending messages for this bot
void ReportEnemies( void ); ///< track nearby enemy count and generate enemy activity statements
bool ShouldSpeak( void ) const; ///< return true if we speaking makes sense now
CCSBot *m_me; ///< the bot this chatter is for
bool m_seeAtLeastOneEnemy;
float m_timeWhenSawFirstEnemy;
bool m_reportedEnemies;
bool m_requestedBombLocation; ///< true if we already asked where the bomb has been planted
int m_pitch;
static IntervalTimer m_radioSilenceInterval[ 2 ]; ///< one timer for each team
IntervalTimer m_needBackupInterval;
IntervalTimer m_spottedBomberInterval;
IntervalTimer m_scaredInterval;
IntervalTimer m_planInterval;
CountdownTimer m_spottedLooseBombTimer;
CountdownTimer m_heardNoiseTimer;
CountdownTimer m_escortingHostageTimer;
CountdownTimer m_warnSniperTimer;
static CountdownTimer m_encourageTimer; ///< timer to know when we can "encourage" the human player again - shared by all bots
CountdownTimer m_heavyTauntTimer;
};
inline BotChatterInterface::VerbosityType BotChatterInterface::GetVerbosity( void ) const
{
const char *string = cv_bot_chatter.GetString();
if (string == NULL)
return NORMAL;
if (string[0] == 'm' || string[0] == 'M')
return MINIMAL;
if (string[0] == 'r' || string[0] == 'R')
return RADIO;
if (string[0] == 'o' || string[0] == 'O')
return OFF;
return NORMAL;
}
inline bool BotChatterInterface::IsTalking( void ) const
{
if (m_statementList)
return m_statementList->IsSpeaking();
return false;
}
inline BotStatement *BotChatterInterface::GetStatement( void ) const
{
return m_statementList;
}
inline void BotChatterInterface::Say( const char *phraseName, float lifetime, float delay )
{
BotStatement *say = new BotStatement( this, REPORT_MY_INTENTION, lifetime );
say->AppendPhrase( TheBotPhrases->GetPhrase( phraseName ) );
if (delay > 0.0f)
say->SetStartTime( gpGlobals->curtime + delay );
AddStatement( say );
}
// In player vs bot game modes, have the bots chatter about what they're doing to the players
class BotChatterCoop : public BotChatterInterface
{
typedef BotChatterInterface BaseClass;
public:
BotChatterCoop( CCSBot *me );
virtual void KilledMyEnemy( int nVictimID ) OVERRIDE;
virtual void EnemiesRemaining( void ) OVERRIDE;
virtual void CelebrateWin( void ) OVERRIDE;
virtual void EnemySpotted( void ) OVERRIDE;
};
#endif // CS_BOT_CHATTER_H
@@ -0,0 +1,82 @@
//============== Copyright Valve Corporation, All rights reserved. ============//
//
/// Custom chatter rules for bots when playing in cooperative modes
//
//=============================================================================//
#include "cbase.h"
#include "cs_player.h"
#include "bot_util.h"
#include "cs_bot.h"
#include "cs_bot_chatter.h"
#include "cs_team.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
BotChatterCoop::BotChatterCoop( CCSBot *me ) :
BotChatterInterface( me )
{
}
//---------------------------------------------------------------------------------------------------------------
void BotChatterCoop::KilledMyEnemy( int victimID )
{
if ( GetGlobalCSTeam(TEAM_CT)->GetAliveMembers() == 0 )
{
CelebrateWin();
}
else
{
BotStatement *say = new BotStatement( this, REPORT_ENEMY_ACTION, 3.0f );
say->AppendPhrase( TheBotPhrases->GetPhrase( "KilledMyEnemy" ) );
say->SetSubject( victimID );
AddStatement( say );
}
}
void BotChatterCoop::EnemiesRemaining( void )
{
if ( GetGlobalCSTeam(TEAM_CT)->GetAliveMembers() == 0 )
{
CelebrateWin();
}
else
{
BotStatement *say = new BotStatement( this, REPORT_ENEMIES_REMAINING, 5.0f );
say->AppendPhrase( BotStatement::REMAINING_ENEMY_COUNT );
say->SetStartTime( gpGlobals->curtime );
AddStatement( say );
}
}
void BotChatterCoop::EnemySpotted( void )
{
float flChance = RandomFloat();
if( flChance < 0.3 )
{
BaseClass::EnemySpotted();
}
else if ( flChance < 0.7 )
{
BotStatement *say = new BotStatement( this, REPORT_EMOTE, 3.0f );
say->AppendPhrase( TheBotPhrases->GetPhrase( "GoGoGo" ) );
AddStatement( say );
}
else
{
BotStatement *say = new BotStatement( this, REPORT_EMOTE, 3.0f );
say->AppendPhrase( TheBotPhrases->GetPhrase( "Cheer" ) );
AddStatement( say );
}
}
void BotChatterCoop::CelebrateWin( void )
{
BotStatement *say = new BotStatement( this, REPORT_EMOTE, 15.0f );
say->AppendPhrase( TheBotPhrases->GetPhrase( "WonRound" ) );
AddStatement( say );
}
+480
View File
@@ -0,0 +1,480 @@
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
// Author: Michael S. Booth (mike@turtlerockstudios.com), 2003
#include "cbase.h"
#include "cs_gamerules.h"
#include "keyvalues.h"
#include "cs_bot.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
//--------------------------------------------------------------------------------------------------------------
/**
* Checks if the bot can hear the event
*/
void CCSBot::OnAudibleEvent( IGameEvent *event, CBasePlayer *player, float range, PriorityType priority, bool isHostile, bool isFootstep, const Vector *actualOrigin )
{
/// @todo Listen to non-player sounds
if (player == NULL)
return;
// don't pay attention to noise that friends make (unless it is a decoy)
if ( !IsEnemy( player ) )
{
if ( !event || !FStrEq( event->GetName(), "decoy_firing" ) )
return;
}
Vector playerOrigin = GetCentroid( player );
Vector myOrigin = GetCentroid( this );
// If the event occurs far from the triggering player, it may override the origin
if ( actualOrigin )
{
playerOrigin = *actualOrigin;
}
// check if noise is close enough for us to hear
const Vector *newNoisePosition = &playerOrigin;
float newNoiseDist = (myOrigin - *newNoisePosition).Length();
if (newNoiseDist < range)
{
// we heard the sound
if ((IsLocalPlayerWatchingMe() && cv_bot_debug.GetInt() == 3) || cv_bot_debug.GetInt() == 4)
{
PrintIfWatched( "Heard noise (%s from %s, pri %s, time %3.1f)\n",
(FStrEq( "weapon_fire", event ? event->GetName() : "<no event>" )) ? "Weapon fire " : "",
(player) ? player->GetPlayerName() : "NULL",
(priority == PRIORITY_HIGH) ? "HIGH" : ((priority == PRIORITY_MEDIUM) ? "MEDIUM" : "LOW"),
gpGlobals->curtime );
}
// should we pay attention to it
// if noise timestamp is zero, there is no prior noise
if (m_noiseTimestamp > 0.0f)
{
// only overwrite recent sound if we are louder (closer), or more important - if old noise was long ago, its faded
const float shortTermMemoryTime = 3.0f;
if (gpGlobals->curtime - m_noiseTimestamp < shortTermMemoryTime)
{
// prior noise is more important - ignore new one
if (priority < m_noisePriority)
return;
float oldNoiseDist = (myOrigin - m_noisePosition).Length();
if (newNoiseDist >= oldNoiseDist)
return;
}
}
// find the area in which the noise occured
/// @todo Better handle when noise occurs off the nav mesh
/// @todo Make sure noise area is not through a wall or ceiling from source of noise
/// @todo Change GetNavTravelTime to better deal with NULL destination areas
CNavArea *noiseArea = TheNavMesh->GetNearestNavArea( *newNoisePosition );
if (noiseArea == NULL)
{
PrintIfWatched( " *** Noise occurred off the nav mesh - ignoring!\n" );
return;
}
m_noiseArea = noiseArea;
// remember noise priority
m_noisePriority = priority;
// randomize noise position in the area a bit - hearing isn't very accurate
// the closer the noise is, the more accurate our placement
/// @todo Make sure not to pick a position on the opposite side of ourselves.
const float maxErrorRadius = 400.0f;
const float maxHearingRange = 2000.0f;
float errorRadius = maxErrorRadius * newNoiseDist/maxHearingRange;
m_noisePosition.x = newNoisePosition->x + RandomFloat( -errorRadius, errorRadius );
m_noisePosition.y = newNoisePosition->y + RandomFloat( -errorRadius, errorRadius );
// note the *travel distance* to the noise
// EDIT: use straight line distance for now; the A* calc is really expensive
m_noiseTravelDistance = EyePosition().DistTo( player->EyePosition() );
// make sure noise position remains in the same area
m_noiseArea->GetClosestPointOnArea( m_noisePosition, &m_noisePosition );
// note when we heard the noise
m_noiseTimestamp = gpGlobals->curtime;
// if we hear a nearby enemy, become alert
const float nearbyNoiseRange = 1000.0f;
if (m_noiseTravelDistance < nearbyNoiseRange && m_noiseTravelDistance > 0.0f)
{
BecomeAlert();
}
}
}
//--------------------------------------------------------------------------------------------------------------
void CCSBot::OnHEGrenadeDetonate( IGameEvent *event )
{
if ( !IsAlive() )
return;
// don't react to our own events
CBasePlayer *player = UTIL_PlayerByUserId( event->GetInt( "userid" ) );
if ( player == this )
return;
OnAudibleEvent( event, player, 99999.0f, PRIORITY_HIGH, true ); // hegrenade_detonate
}
//--------------------------------------------------------------------------------------------------------------
void CCSBot::OnFlashbangDetonate( IGameEvent *event )
{
if ( !IsAlive() )
return;
// don't react to our own events
CBasePlayer *player = UTIL_PlayerByUserId( event->GetInt( "userid" ) );
if ( player == this )
return;
OnAudibleEvent( event, player, 1000.0f, PRIORITY_LOW, true ); // flashbang_detonate
}
//--------------------------------------------------------------------------------------------------------------
void CCSBot::OnSmokeGrenadeDetonate( IGameEvent *event )
{
if ( !IsAlive() )
return;
// don't react to our own events
CBasePlayer *player = UTIL_PlayerByUserId( event->GetInt( "userid" ) );
if ( player == this )
return;
OnAudibleEvent( event, player, 1000.0f, PRIORITY_LOW, true ); // smokegrenade_detonate
}
//--------------------------------------------------------------------------------------------------------------
void CCSBot::OnMolotovDetonate( IGameEvent *event )
{
if ( !IsAlive() )
return;
// don't react to our own events
CBasePlayer *player = UTIL_PlayerByUserId( event->GetInt( "userid" ) );
if ( player == this )
return;
OnAudibleEvent( event, player, 99999.0f, PRIORITY_HIGH, true ); // molotov_detonate
}
//--------------------------------------------------------------------------------------------------------------
void CCSBot::OnDecoyDetonate( IGameEvent *event )
{
if ( !IsAlive() )
return;
// don't react to our own events
CBasePlayer *player = UTIL_PlayerByUserId( event->GetInt( "userid" ) );
if ( player == this )
return;
OnAudibleEvent( event, player, 99999.0f, PRIORITY_HIGH, true ); // decoy_detonate
}
//--------------------------------------------------------------------------------------------------------------
void CCSBot::OnDecoyFiring( IGameEvent *event )
{
if ( !IsAlive() )
return;
// don't react to our own events
CBasePlayer *thrower = UTIL_PlayerByUserId( event->GetInt( "userid" ) );
if ( thrower == this )
return;
Vector decoySpot( event->GetInt( "x" ), event->GetInt( "y" ), event->GetInt( "z" ) );
OnAudibleEvent( event, thrower, 99999.0f, PRIORITY_HIGH, true, false, &decoySpot );
}
//--------------------------------------------------------------------------------------------------------------
void CCSBot::OnGrenadeBounce( IGameEvent *event )
{
if ( !IsAlive() )
return;
// don't react to our own events
CBasePlayer *player = UTIL_PlayerByUserId( event->GetInt( "userid" ) );
if ( player == this )
return;
OnAudibleEvent( event, player, 500.0f, PRIORITY_LOW, true ); // grenade_bounce
}
//--------------------------------------------------------------------------------------------------------------
void CCSBot::OnBulletImpact( IGameEvent *event )
{
if ( !IsAlive() )
return;
// don't react to our own events
CBasePlayer *player = UTIL_PlayerByUserId( event->GetInt( "userid" ) );
if ( player == this )
return;
// Construct an origin for the sound, since it can be far from the originating player
Vector actualOrigin;
actualOrigin.x = event->GetFloat( "x", 0.0f );
actualOrigin.y = event->GetFloat( "y", 0.0f );
actualOrigin.z = event->GetFloat( "z", 0.0f );
/// @todo Ignoring bullet impact events for now - we dont want bots to look directly at them!
//OnAudibleEvent( event, player, 1100.0f, PRIORITY_MEDIUM, true, false, &actualOrigin ); // bullet_impact
}
//--------------------------------------------------------------------------------------------------------------
void CCSBot::OnBreakProp( IGameEvent *event )
{
if ( !IsAlive() )
return;
// don't react to our own events
CBasePlayer *player = UTIL_PlayerByUserId( event->GetInt( "userid" ) );
if ( player == this )
return;
OnAudibleEvent( event, player, 1100.0f, PRIORITY_MEDIUM, true ); // break_prop
}
//--------------------------------------------------------------------------------------------------------------
void CCSBot::OnBreakBreakable( IGameEvent *event )
{
if ( !IsAlive() )
return;
// don't react to our own events
CBasePlayer *player = UTIL_PlayerByUserId( event->GetInt( "userid" ) );
if ( player == this )
return;
OnAudibleEvent( event, player, 1100.0f, PRIORITY_MEDIUM, true ); // break_glass
}
//--------------------------------------------------------------------------------------------------------------
void CCSBot::OnDoorMoving( IGameEvent *event )
{
if ( !IsAlive() )
return;
// don't react to our own events
CBasePlayer *player = UTIL_PlayerByUserId( event->GetInt( "userid" ) );
if ( player == this )
return;
OnAudibleEvent( event, player, 1100.0f, PRIORITY_MEDIUM, false ); // door_moving
}
//--------------------------------------------------------------------------------------------------------------
void CCSBot::OnHostageFollows( IGameEvent *event )
{
if ( !IsAlive() )
return;
// don't react to our own events
CBasePlayer *player = UTIL_PlayerByUserId( event->GetInt( "userid" ) );
if ( player == this )
return;
// player_follows needs a player
if (player == NULL)
return;
// don't pay attention to noise that friends make
if (!IsEnemy( player ))
return;
Vector playerOrigin = GetCentroid( player );
Vector myOrigin = GetCentroid( this );
const float range = 1200.0f;
// this is here so T's not only act on the noise, but look at it, too
if (GetTeamNumber() == TEAM_TERRORIST)
{
// make sure we can hear the noise
if ((playerOrigin - myOrigin).IsLengthGreaterThan( range ))
return;
// tell our teammates that the hostages are being taken
GetChatter()->HostagesBeingTaken();
// only move if we hear them being rescued and can't see any hostages
if (GetGameState()->GetNearestVisibleFreeHostage() == NULL)
{
// since we are guarding the hostages, presumably we know where they are
// if we're close enough to "hear" this event, either go to where the event occured,
// or head for an escape zone to head them off
if (GetTask() != CCSBot::GUARD_HOSTAGE_RESCUE_ZONE)
{
//const float headOffChance = 33.3f;
if (true) // || RandomFloat( 0, 100 ) < headOffChance)
{
// head them off at a rescue zone
if (GuardRandomZone())
{
SetTask( CCSBot::GUARD_HOSTAGE_RESCUE_ZONE );
SetDisposition( CCSBot::OPPORTUNITY_FIRE );
PrintIfWatched( "Trying to beat them to an escape zone!\n" );
}
}
else
{
SetTask( SEEK_AND_DESTROY );
StandUp();
Run();
MoveTo( playerOrigin, FASTEST_ROUTE );
}
}
}
}
else
{
// CT's don't care about this noise
return;
}
OnAudibleEvent( event, player, range, PRIORITY_MEDIUM, false ); // hostage_follows
}
//--------------------------------------------------------------------------------------------------------------
void CCSBot::OnRoundEnd( IGameEvent *event )
{
// Morale adjustments happen even for dead players
int winner = event->GetInt( "winner" );
switch ( winner )
{
case WINNER_TER:
if (GetTeamNumber() == TEAM_CT)
{
DecreaseMorale();
}
else
{
IncreaseMorale();
}
break;
case WINNER_CT:
if (GetTeamNumber() == TEAM_CT)
{
IncreaseMorale();
}
else
{
DecreaseMorale();
}
break;
default:
break;
}
m_gameState.OnRoundEnd( event );
if ( !IsAlive() )
return;
if ( event->GetInt( "winner" ) == WINNER_TER )
{
if (GetTeamNumber() == TEAM_TERRORIST)
GetChatter()->CelebrateWin();
}
else if ( event->GetInt( "winner" ) == WINNER_CT )
{
if (GetTeamNumber() == TEAM_CT)
GetChatter()->CelebrateWin();
}
}
//--------------------------------------------------------------------------------------------------------------
void CCSBot::OnRoundStart( IGameEvent *event )
{
m_gameState.OnRoundStart( event );
}
//--------------------------------------------------------------------------------------------------------------
void CCSBot::OnHostageRescuedAll( IGameEvent *event )
{
m_gameState.OnHostageRescuedAll( event );
}
//--------------------------------------------------------------------------------------------------------------
void CCSBot::OnNavBlocked( IGameEvent *event )
{
if ( event->GetBool( "blocked" ) )
{
unsigned int areaID = event->GetInt( "area" );
if ( areaID )
{
// An area was blocked off. Reset our path if it has this area on it.
for( int i=0; i<m_pathLength; ++i )
{
const ConnectInfo *info = &m_path[ i ];
if ( info->area && info->area->GetID() == areaID )
{
DestroyPath();
return;
}
}
}
}
}
//--------------------------------------------------------------------------------------------------------------
/**
* Invoked when bot enters a nav area
*/
void CCSBot::OnEnteredNavArea( CNavArea *newArea )
{
SNPROF("OnEnteredNavArea");
// assume that we "clear" an area of enemies when we enter it
newArea->SetClearedTimestamp( GetTeamNumber()-1 );
// if we just entered a 'stop' area, set the flag
if ( newArea->GetAttributes() & NAV_MESH_STOP )
{
m_isStopping = true;
}
/// @todo Flag these areas as spawn areas during load
if (IsAtEnemySpawn())
{
m_hasVisitedEnemySpawn = true;
}
}
@@ -0,0 +1,165 @@
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
// Author: Michael S. Booth (mike@turtlerockstudios.com), 2003
#include "cbase.h"
#include "cs_gamerules.h"
#include "keyvalues.h"
#include "cs_bot.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
//--------------------------------------------------------------------------------------------------------------
void CCSBot::OnBombPickedUp( IGameEvent *event )
{
if ( !IsAlive() )
return;
CBasePlayer *player = UTIL_PlayerByUserId( event->GetInt( "userid" ) );
// In guardian mode, terrorists always know who the bomber is
if ( CSGameRules()->IsPlayingCoopGuardian() && GetTeamNumber() == TEAM_TERRORIST )
{
GetGameState()->UpdateBomber( player->GetAbsOrigin() );
}
// don't react to our own events
if ( player == this )
return;
if (GetTeamNumber() == TEAM_CT && player)
{
// check if we're close enough to hear it
const float bombPickupHearRangeSq = 1000.0f * 1000.0f;
Vector myOrigin = GetCentroid( this );
if ((myOrigin - player->GetAbsOrigin()).LengthSqr() < bombPickupHearRangeSq)
{
GetChatter()->TheyPickedUpTheBomb();
GetGameState()->UpdateBomber( player->GetAbsOrigin() );
}
}
}
//--------------------------------------------------------------------------------------------------------------
void CCSBot::OnBombPlanted( IGameEvent *event )
{
m_gameState.OnBombPlanted( event );
if ( !IsAlive() )
return;
// don't react to our own events
CBasePlayer *player = UTIL_PlayerByUserId( event->GetInt( "userid" ) );
if ( player == this )
return;
// if we're a TEAM_CT, forget what we're doing and go after the bomb
if (GetTeamNumber() == TEAM_CT)
{
Idle();
}
// if we are following someone, stop following
if (IsFollowing())
{
StopFollowing();
Idle();
}
}
//--------------------------------------------------------------------------------------------------------------
void CCSBot::OnBombBeep( IGameEvent *event )
{
if ( !IsAlive() )
return;
// don't react to our own events
CBasePlayer *player = UTIL_PlayerByUserId( event->GetInt( "userid" ) );
if ( player == this )
return;
CBaseEntity *entity = UTIL_EntityByIndex( event->GetInt( "entindex" ) );
Vector myOrigin = GetCentroid( this );
// if we don't know where the bomb is, but heard it beep, we've discovered it
if (GetGameState()->IsPlantedBombLocationKnown() == false && entity)
{
// check if we're close enough to hear it
const float bombBeepHearRangeSq = 1500.0f * 1500.0f;
if ((myOrigin - entity->GetAbsOrigin()).LengthSqr() < bombBeepHearRangeSq)
{
// radio the news to our team
if (GetTeamNumber() == TEAM_CT && GetGameState()->GetPlantedBombsite() == CSGameState::UNKNOWN)
{
const CCSBotManager::Zone *zone = TheCSBots()->GetZone( entity->GetAbsOrigin() );
if (zone)
GetChatter()->FoundPlantedBomb( zone->m_index );
}
// remember where the bomb is
GetGameState()->UpdatePlantedBomb( entity->GetAbsOrigin() );
}
}
}
//--------------------------------------------------------------------------------------------------------------
void CCSBot::OnBombDefuseBegin( IGameEvent *event )
{
}
//--------------------------------------------------------------------------------------------------------------
void CCSBot::OnBombDefused( IGameEvent *event )
{
m_gameState.OnBombDefused( event );
if ( !IsAlive() )
return;
// don't react to our own events
CBasePlayer *player = UTIL_PlayerByUserId( event->GetInt( "userid" ) );
if ( player == this )
return;
if (GetTeamNumber() == TEAM_CT)
{
if (TheCSBots()->GetBombTimeLeft() < 2.0f)
GetChatter()->Say( "BarelyDefused" );
}
}
//--------------------------------------------------------------------------------------------------------------
void CCSBot::OnBombDefuseAbort( IGameEvent *event )
{
if ( !IsAlive() )
return;
// don't react to our own events
CBasePlayer *player = UTIL_PlayerByUserId( event->GetInt( "userid" ) );
if ( player == this )
return;
PrintIfWatched( "BOMB DEFUSE ABORTED\n" );
}
//--------------------------------------------------------------------------------------------------------------
void CCSBot::OnBombExploded( IGameEvent *event )
{
m_gameState.OnBombExploded( event );
}
@@ -0,0 +1,238 @@
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
// Author: Michael S. Booth (mike@turtlerockstudios.com), 2003
#include "cbase.h"
#include "cs_gamerules.h"
#include "keyvalues.h"
#include "cs_bot.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
//--------------------------------------------------------------------------------------------------------------
void CCSBot::OnPlayerDeath( IGameEvent *event )
{
if ( !IsAlive() )
return;
// don't react to our own events
CBasePlayer *player = UTIL_PlayerByUserId( event->GetInt( "userid" ) );
if ( player == this )
return;
Vector playerOrigin = (player) ? GetCentroid( player ) : Vector( 0, 0, 0 );
CBasePlayer *other = UTIL_PlayerByUserId( event->GetInt( "attacker" ) );
CBasePlayer *victim = player;
CBasePlayer *killer = (other && other->IsPlayer()) ? static_cast<CBasePlayer *>( other ) : NULL;
// if the human player died in the single player game, tell the team
if (CSGameRules()->IsCareer() && !victim->IsBot() && victim->GetTeamNumber() == GetTeamNumber())
{
GetChatter()->Say( "CommanderDown", 20.0f );
}
// keep track of the last player we killed
if (killer == this)
{
m_lastVictimID = victim->entindex();
}
// react to teammate death
if (victim->GetTeamNumber() == GetTeamNumber())
{
// note time of death
m_friendDeathTimestamp = gpGlobals->curtime;
// chastise friendly fire from humans
if (killer && !killer->IsBot() && killer->GetTeamNumber() == GetTeamNumber() && killer != this)
{
GetChatter()->KilledFriend();
}
if ( CSGameRules()->IsPlayingCoopMission() )
{
if ( victim )
{
Vector vDelta = GetAbsOrigin() - victim->GetAbsOrigin();
if ( vDelta.Length() < 320 )
m_bIsSleeping = false;
}
}
if (IsAttacking())
{
if ( !CSGameRules()->IsPlayingCoopMission() && GetTimeSinceLastSawEnemy() > 0.4f)
{
PrintIfWatched( "Rethinking my attack due to teammate death\n" );
// allow us to sneak past windows, doors, etc
IgnoreEnemies( 1.0f );
// move to last known position of enemy - this could cause us to flank if
// the danger has changed due to our teammate's recent death
SetTask( MOVE_TO_LAST_KNOWN_ENEMY_POSITION, GetBotEnemy() );
MoveTo( GetLastKnownEnemyPosition() );
return;
}
}
else // not attacking
{
//
// If we just saw a nearby friend die, and we haven't yet acquired an enemy
// automatically acquire our dead friend's killer
//
if (GetDisposition() == ENGAGE_AND_INVESTIGATE || GetDisposition() == OPPORTUNITY_FIRE)
{
CBasePlayer *other = UTIL_PlayerByUserId( event->GetInt( "attacker" ) );
// check that attacker is an enemy (for friendly fire, etc)
if (other && other->IsPlayer())
{
CCSPlayer *killer = static_cast<CCSPlayer *>( other );
if (killer->GetTeamNumber() != GetTeamNumber())
{
// check if we saw our friend die - dont check FOV - assume we're aware of our surroundings in combat
// snipers stay put
if (!IsSniper() && IsVisible( playerOrigin ))
{
// people are dying - we should hurry
Hurry( RandomFloat( 10.0f, 15.0f ) );
// if we're hiding with only our knife, be a little more cautious
const float knifeAmbushChance = 50.0f;
if (!IsHiding() || !IsUsingKnife() || RandomFloat( 0, 100 ) < knifeAmbushChance)
{
PrintIfWatched( "Attacking our friend's killer!\n" );
Attack( killer );
return;
}
}
// if friend was far away and we haven't seen an enemy in awhile, go to where our friend was killed
const float longHidingTime = 20.0f;
if (IsHunting() || IsInvestigatingNoise() || (IsHiding() && GetTask() != FOLLOW && GetHidingTime() > longHidingTime))
{
const float someTime = 10.0f;
const float farAway = 750.0f;
if (GetTimeSinceLastSawEnemy() > someTime && (playerOrigin - GetAbsOrigin()).IsLengthGreaterThan( farAway ))
{
PrintIfWatched( "Checking out where our friend was killed\n" );
MoveTo( playerOrigin, FASTEST_ROUTE );
return;
}
}
}
}
}
}
}
else // an enemy was killed
{
// forget our current noise - it may have come from the now dead enemy
ForgetNoise();
if (killer && killer->GetTeamNumber() == GetTeamNumber())
{
// only chatter about enemy kills if we see them occur, and they were the last one we see
if (GetNearbyEnemyCount() <= 1)
{
// report if number of enemies left is few and we killed the last one we saw locally
GetChatter()->EnemiesRemaining();
Vector victimOrigin = GetCentroid( victim );
if (IsVisible( victimOrigin, CHECK_FOV ))
{
// congratulate teammates on their kills
if (killer && killer != this)
{
float delay = RandomFloat( 2.0f, 3.0f );
if (killer->IsBot())
{
if (RandomFloat( 0.0f, 100.0f ) < 40.0f)
GetChatter()->Say( "NiceShot", 3.0f, delay );
}
else
{
// humans get the honorific
if (CSGameRules()->IsCareer())
GetChatter()->Say( "NiceShotCommander", 3.0f, delay );
else
GetChatter()->Say( "NiceShotSir", 3.0f, delay );
}
}
}
}
}
}
}
//--------------------------------------------------------------------------------------------------------------
void CCSBot::OnPlayerRadio( IGameEvent *event )
{
if ( !IsAlive() )
return;
// don't react to our own events
CCSPlayer *player = ToCSPlayer( UTIL_PlayerByUserId( event->GetInt( "userid" ) ) );
if ( player == this )
return;
//
// Process radio events from our team
//
if (player && player->GetTeamNumber() == GetTeamNumber() )
{
/// @todo Distinguish between radio commands and responses
RadioType radioEvent = (RadioType)event->GetInt( "slot" );
if (radioEvent != RADIO_INVALID && radioEvent != RADIO_AFFIRMATIVE && radioEvent != RADIO_NEGATIVE && radioEvent != RADIO_REPORTING_IN
&& radioEvent != RADIO_CHEER && radioEvent != RADIO_THANKS && radioEvent != RADIO_COMPLIMENT)
{
m_lastRadioCommand = radioEvent;
m_lastRadioRecievedTimestamp = gpGlobals->curtime;
m_radioSubject = player;
m_radioPosition = GetCentroid( player );
}
}
}
//--------------------------------------------------------------------------------------------------------------
void CCSBot::OnPlayerFallDamage( IGameEvent *event )
{
if ( !IsAlive() )
return;
// don't react to our own events
CBasePlayer *player = UTIL_PlayerByUserId( event->GetInt( "userid" ) );
if ( player == this )
return;
OnAudibleEvent( event, player, 1100.0f, PRIORITY_LOW, false ); // player_falldamage
}
//--------------------------------------------------------------------------------------------------------------
void CCSBot::OnPlayerFootstep( IGameEvent *event )
{
if ( !IsAlive() )
return;
// don't react to our own events
CBasePlayer *player = UTIL_PlayerByUserId( event->GetInt( "userid" ) );
if ( player == this )
return;
OnAudibleEvent( event, player, 1100.0f, PRIORITY_LOW, false, IS_FOOTSTEP ); // player_footstep
}
@@ -0,0 +1,156 @@
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
// Author: Michael S. Booth (mike@turtlerockstudios.com), 2003
#include "cbase.h"
#include "cs_gamerules.h"
#include "keyvalues.h"
#include "cs_bot.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
//--------------------------------------------------------------------------------------------------------------
void CCSBot::OnWeaponFire( IGameEvent *event )
{
if ( !IsAlive() )
return;
// don't react to our own events
CBasePlayer *player = UTIL_PlayerByUserId( event->GetInt( "userid" ) );
if ( player == this )
return;
// for knife fighting - if our victim is attacking or reloading, rush him
/// @todo Propagate events into active state
if (GetBotEnemy() == player && IsUsingKnife())
{
ForceRun( 5.0f );
}
const float ShortRange = 1000.0f;
const float NormalRange = 2000.0f;
float range;
/// @todo Check weapon type (knives are pretty quiet)
/// @todo Use actual volume, account for silencers, etc.
// [mlowrance] use the weapon as posted in the event message
int iWeaponID = -1;
const char *weaponName = event->GetString( "weapon" );
if ( weaponName )
{
iWeaponID = AliasToWeaponID( weaponName );
}
if ( iWeaponID == -1 )
return;
switch( iWeaponID )
{
// silent "firing"
case WEAPON_HEGRENADE:
case WEAPON_SMOKEGRENADE:
case WEAPON_FLASHBANG:
case WEAPON_INCGRENADE:
case WEAPON_MOLOTOV:
case WEAPON_DECOY:
case WEAPON_TAGRENADE:
case WEAPON_C4:
return;
// quiet
case WEAPON_KNIFE:
case WEAPON_KNIFE_GG:
range = ShortRange;
break;
// loud
case WEAPON_AWP:
range = 99999.0f;
break;
// normal
default:
if ( event->GetBool( "silenced" ) )
{
range = ShortRange;
}
else
{
range = NormalRange;
}
break;
}
OnAudibleEvent( event, player, range, PRIORITY_HIGH, true ); // weapon_fire
}
//--------------------------------------------------------------------------------------------------------------
void CCSBot::OnWeaponFireOnEmpty( IGameEvent *event )
{
if ( !IsAlive() )
return;
// don't react to our own events
CBasePlayer *player = UTIL_PlayerByUserId( event->GetInt( "userid" ) );
if ( player == this )
return;
// for knife fighting - if our victim is attacking or reloading, rush him
/// @todo Propagate events into active state
if (GetBotEnemy() == player && IsUsingKnife())
{
ForceRun( 5.0f );
}
OnAudibleEvent( event, player, 1100.0f, PRIORITY_LOW, false ); // weapon_fire_on_empty
}
//--------------------------------------------------------------------------------------------------------------
void CCSBot::OnWeaponReload( IGameEvent *event )
{
if ( !IsAlive() )
return;
// don't react to our own events
CBasePlayer *player = UTIL_PlayerByUserId( event->GetInt( "userid" ) );
if ( player == this )
return;
// for knife fighting - if our victim is attacking or reloading, rush him
/// @todo Propagate events into active state
if (GetBotEnemy() == player && IsUsingKnife())
{
ForceRun( 5.0f );
}
OnAudibleEvent( event, player, 1100.0f, PRIORITY_LOW, false ); // weapon_reload
}
//--------------------------------------------------------------------------------------------------------------
void CCSBot::OnWeaponZoom( IGameEvent *event )
{
if ( !IsAlive() )
return;
// don't react to our own events
CBasePlayer *player = UTIL_PlayerByUserId( event->GetInt( "userid" ) );
if ( player == this )
return;
OnAudibleEvent( event, player, 1100.0f, PRIORITY_LOW, false ); // weapon_zoom
}
+545
View File
@@ -0,0 +1,545 @@
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
// Author: Michael S. Booth (mike@turtlerockstudios.com), 2003
#include "cbase.h"
#include "cs_bot.h"
#include "cs_shareddefs.h"
#include "mathlib/mathlib.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
#pragma warning( disable : 4355 ) // warning 'this' used in base member initializer list - we're using it safely
ConVar mp_coopmission_bot_difficulty_offset(
"mp_coopmission_bot_difficulty_offset",
"0",
FCVAR_REPLICATED | FCVAR_RELEASE,
"The difficulty offset modifier for bots during coop missions." );
//--------------------------------------------------------------------------------------------------------------
static void PrefixChanged( IConVar *c, const char *oldPrefix, float flOldValue )
{
if ( TheCSBots() && TheCSBots()->IsServerActive() )
{
for( int i = 1; i <= gpGlobals->maxClients; ++i )
{
CBasePlayer *player = static_cast<CBasePlayer *>( UTIL_PlayerByIndex( i ) );
if ( !player )
continue;
if ( !player->IsBot() || !IsEntityValid( player ) )
continue;
CCSBot *bot = dynamic_cast< CCSBot * >( player );
if ( !bot )
continue;
// set the bot's name
char botName[MAX_PLAYER_NAME_LENGTH];
UTIL_ConstructBotNetName( botName, MAX_PLAYER_NAME_LENGTH, bot->GetProfile() );
engine->SetFakeClientConVarValue( bot->edict(), "name", botName );
}
}
}
ConVar cv_bot_traceview( "bot_traceview", "0", FCVAR_REPLICATED | FCVAR_CHEAT, "For internal testing purposes." );
ConVar cv_bot_stop( "bot_stop", "0", FCVAR_REPLICATED | FCVAR_CHEAT, "If nonzero, immediately stops all bot processing." );
ConVar cv_bot_show_nav( "bot_show_nav", "0", FCVAR_REPLICATED | FCVAR_CHEAT, "For internal testing purposes." );
ConVar cv_bot_walk( "bot_walk", "0", FCVAR_REPLICATED, "If nonzero, bots can only walk, not run." );
ConVar cv_bot_difficulty( "bot_difficulty", "1", FCVAR_REPLICATED | FCVAR_RELEASE, "Defines the skill of bots joining the game. Values are: 0=easy, 1=normal, 2=hard, 3=expert." );
ConVar cv_bot_debug( "bot_debug", "0", FCVAR_REPLICATED | FCVAR_CHEAT, "For internal testing purposes." );
ConVar cv_bot_debug_target( "bot_debug_target", "0", FCVAR_REPLICATED | FCVAR_CHEAT, "For internal testing purposes." );
ConVar cv_bot_quota( "bot_quota", "10", FCVAR_REPLICATED | FCVAR_RELEASE, "Determines the total number of bots in the game." );
ConVar cv_bot_quota_mode( "bot_quota_mode", "normal", FCVAR_REPLICATED | FCVAR_RELEASE, "Determines the type of quota.\nAllowed values: 'normal', 'fill', and 'match'.\nIf 'fill', the server will adjust bots to keep N players in the game, where N is bot_quota.\nIf 'match', the server will maintain a 1:N ratio of humans to bots, where N is bot_quota." );
ConVar cv_bot_prefix( "bot_prefix", "", FCVAR_REPLICATED, "This string is prefixed to the name of all bots that join the game.\n<difficulty> will be replaced with the bot's difficulty.\n<weaponclass> will be replaced with the bot's desired weapon class.\n<skill> will be replaced with a 0-100 representation of the bot's skill.", PrefixChanged );
ConVar cv_bot_allow_rogues( "bot_allow_rogues", "1", FCVAR_RELEASE|FCVAR_REPLICATED, "If nonzero, bots may occasionally go 'rogue'. Rogue bots do not obey radio commands, nor pursue scenario goals." );
ConVar cv_bot_allow_pistols( "bot_allow_pistols", "1", FCVAR_RELEASE|FCVAR_REPLICATED, "If nonzero, bots may use pistols." );
ConVar cv_bot_allow_shotguns( "bot_allow_shotguns", "1", FCVAR_RELEASE|FCVAR_REPLICATED, "If nonzero, bots may use shotguns." );
ConVar cv_bot_allow_sub_machine_guns( "bot_allow_sub_machine_guns", "1", FCVAR_RELEASE|FCVAR_REPLICATED, "If nonzero, bots may use sub-machine guns." );
ConVar cv_bot_allow_rifles( "bot_allow_rifles", "1", FCVAR_RELEASE|FCVAR_REPLICATED, "If nonzero, bots may use rifles." );
ConVar cv_bot_allow_machine_guns( "bot_allow_machine_guns", "1", FCVAR_RELEASE|FCVAR_REPLICATED, "If nonzero, bots may use the machine gun." );
ConVar cv_bot_allow_grenades( "bot_allow_grenades", "1", FCVAR_RELEASE|FCVAR_REPLICATED, "If nonzero, bots may use grenades." );
ConVar cv_bot_allow_snipers( "bot_allow_snipers", "1", FCVAR_RELEASE|FCVAR_REPLICATED, "If nonzero, bots may use sniper rifles." );
#ifdef CS_SHIELD_ENABLED
ConVar cv_bot_allow_shield( "bot_allow_shield", "1", FCVAR_REPLICATED );
#endif // CS_SHIELD_ENABLED
ConVar cv_bot_join_team( "bot_join_team", "any", FCVAR_REPLICATED | FCVAR_RELEASE, "Determines the team bots will join into. Allowed values: 'any', 'T', or 'CT'." );
ConVar cv_bot_join_after_player( "bot_join_after_player", "1", FCVAR_REPLICATED | FCVAR_RELEASE, "If nonzero, bots wait until a player joins before entering the game." );
ConVar cv_bot_auto_vacate( "bot_auto_vacate", "1", FCVAR_REPLICATED, "If nonzero, bots will automatically leave to make room for human players." );
ConVar cv_bot_zombie( "bot_zombie", "0", FCVAR_REPLICATED | FCVAR_CHEAT, "If nonzero, bots will stay in idle mode and not attack." );
ConVar cv_bot_defer_to_human_goals( "bot_defer_to_human_goals", "0", FCVAR_REPLICATED | FCVAR_RELEASE, "If nonzero and there is a human on the team, the bots will not do the scenario tasks." );
ConVar cv_bot_defer_to_human_items( "bot_defer_to_human_items", "1", FCVAR_REPLICATED | FCVAR_RELEASE, "If nonzero and there is a human on the team, the bots will not get scenario items." );
ConVar cv_bot_chatter( "bot_chatter", "normal", FCVAR_REPLICATED | FCVAR_RELEASE, "Control how bots talk. Allowed values: 'off', 'radio', 'minimal', or 'normal'." );
ConVar cv_bot_profile_db( "bot_profile_db", "BotProfile.db", FCVAR_REPLICATED, "The filename from which bot profiles will be read." );
ConVar cv_bot_dont_shoot( "bot_dont_shoot", "0", FCVAR_REPLICATED | FCVAR_RELEASE | FCVAR_CHEAT, "If nonzero, bots will not fire weapons (for debugging)." );
ConVar cv_bot_eco_limit( "bot_eco_limit", "2000", FCVAR_REPLICATED, "If nonzero, bots will not buy if their money falls below this amount." );
ConVar cv_bot_auto_follow( "bot_auto_follow", "0", FCVAR_REPLICATED, "If nonzero, bots with high co-op may automatically follow a nearby human player." );
ConVar cv_bot_flipout( "bot_flipout", "0", FCVAR_REPLICATED, "If nonzero, bots use no CPU for AI. Instead, they run around randomly." );
#if CS_CONTROLLABLE_BOTS_ENABLED
ConVar cv_bot_controllable( "bot_controllable", "1", FCVAR_REPLICATED, "Determines whether bots can be controlled by players" );
#endif
extern void FinishClientPutInServer( CCSPlayer *pPlayer );
//--------------------------------------------------------------------------------------------------------------
// Engine callback for custom server commands
void Bot_ServerCommand( void )
{
}
//--------------------------------------------------------------------------------------------------------------
/**
* Constructor
*/
CCSBot::CCSBot( void ) :
m_gameState( this ),
m_hasJoined( false ),
m_pLocalProfile( NULL )
{
if ( CSGameRules()->IsPlayingCoopMission() )
{
m_pChatter = new BotChatterCoop( this );
}
else
{
m_pChatter = new BotChatterInterface( this );
}
}
//--------------------------------------------------------------------------------------------------------------
/**
* Destructor
*/
CCSBot::~CCSBot()
{
if ( m_pLocalProfile )
{
delete m_pLocalProfile;
}
}
//--------------------------------------------------------------------------------------------------------------
/**
* Prepare bot for action
*/
bool CCSBot::Initialize( const BotProfile *profile, int team )
{
int preserved_voice_pitch = -1;
char preserved_name[256];
preserved_name[0] = 0;
AssertMsg( profile != NULL, "You cannot pass in null for a bot profile." );
if ( NULL == profile ) return false;
if ( m_pLocalProfile )
{
// Preserve the voice pitch and name from the existing profile
preserved_voice_pitch = m_pLocalProfile->GetVoicePitch();
if ( CSGameRules() && CSGameRules()->IsPlayingCooperativeGametype() )
{
// change names everytime in coop
Q_snprintf( preserved_name, 256, "%s", profile->GetName() );
}
else
Q_snprintf( preserved_name, 256, "%s", m_pLocalProfile->GetName() );
delete m_pLocalProfile;
}
m_pLocalProfile = new BotProfile;
if ( m_pLocalProfile )
{
// Copy the profile into the local profile
m_pLocalProfile->Clone( profile );
// Restore the name
if ( Q_strlen( preserved_name ) > 0 )
{
m_pLocalProfile->SetName( preserved_name );
}
// Restore the voice pitch
if ( preserved_voice_pitch > -1 )
{
m_pLocalProfile->SetVoicePitch( preserved_voice_pitch );
}
}
// extend
BaseClass::Initialize( m_pLocalProfile, team );
// CS bot initialization
m_diedLastRound = false;
if ( CSGameRules()->IsPlayingGunGameTRBomb() )
{
// in demolition, start the CT's off with terrible morale, so they try to camp the bombsite
if ( team == TEAM_CT )
{
m_morale = TERRIBLE;
}
else
{
m_morale = POSITIVE; // starting a new round makes everyone a little happy
}
}
else
{
m_morale = POSITIVE; // starting a new round makes everyone a little happy
}
m_combatRange = RandomFloat( 325.0f, 425.0f );
// set initial safe time guess for this map
m_safeTime = 15.0f + 5.0f * GetProfile()->GetAggression( );
m_name[0] = '\000';
ResetValues();
m_desiredTeam = team;
if (GetTeamNumber() == 0)
{
HandleCommand_JoinTeam( m_desiredTeam );
// join class is already called within JoinTeam
//HandleCommand_JoinClass();
}
return true;
}
void CCSBot::CoopInitialize( void )
{
if ( CSGameRules() && CSGameRules()->IsPlayingCoopMission() && GetTeamNumber() == TEAM_TERRORIST )
{
// bots start asleep when they spawn and wake up when players come close to them
SpawnPointCoopEnemy *pEnemySpawnSpot = GetLastCoopSpawnPoint();
if ( pEnemySpawnSpot )
{
SetAbsAngles( pEnemySpawnSpot->GetAbsAngles() );
int nDifficulty = pEnemySpawnSpot->GetBotDifficulty();
int nOffset = mp_coopmission_bot_difficulty_offset.GetInt();
nDifficulty = nDifficulty + nOffset;
BotDifficultyType botDifficultyType = BOT_EASY;
if ( nDifficulty >= 6 )
botDifficultyType = BOT_EXPERT;
else if ( nDifficulty >= 3 )
botDifficultyType = BOT_HARD;
else if ( nDifficulty >= 1 )
botDifficultyType = BOT_NORMAL;
bool bIsAgressive = pEnemySpawnSpot->IsBotAgressive();
char profileName[128];
Q_snprintf( profileName, sizeof( profileName ), "Level_%d%s", ( int )nDifficulty, bIsAgressive ? "_Agressive" : "" );
const BotProfile* pNewProfileData = TheBotProfiles->GetProfileMatchingTemplate( profileName, TEAM_TERRORIST, botDifficultyType, CSGameRules()->GetMatchDevice(), true );
if ( pNewProfileData )
{
char szHeavy[64] = {};
int nArmor = pEnemySpawnSpot->GetArmorToSpawnWith();
if ( nArmor == 1 )
Q_snprintf( szHeavy, sizeof( szHeavy ), "%s", "" );
else if ( nArmor == 2 )
Q_snprintf( szHeavy, sizeof( szHeavy ), "%s", "Heavy " );
char szName[128] = {};
char szDifficulty[128] = {};
if ( nDifficulty >= 6 )
Q_snprintf( szDifficulty, sizeof( szDifficulty ), "%s", "Elite " );
else if ( nDifficulty >= 4 )
Q_snprintf( szDifficulty, sizeof( szDifficulty ), "%s", "Expert " );
else
Q_snprintf( szDifficulty, sizeof( szDifficulty ), "%s", "" );
Q_snprintf( szName, sizeof( szName ), "%s%sPhoenix", szHeavy, szDifficulty );
Initialize( pNewProfileData, GetTeamNumber() );
SetPlayerName( szName );
// have to inform the engine that the bot name has been updated
engine->SetFakeClientConVarValue( edict(), "name", szName );
}
// sleeping needs to be set after the initialize because we reset values in the init
m_bIsSleeping = pEnemySpawnSpot->ShouldStartAsleep();
}
}
}
//--------------------------------------------------------------------------------------------------------------
/**
* Reset internal data to initial state
*/
void CCSBot::ResetValues( void )
{
m_pChatter->Reset();
m_gameState.Reset();
m_avoid = NULL;
m_avoidTimestamp = 0.0f;
m_hurryTimer.Invalidate();
m_alertTimer.Invalidate();
m_sneakTimer.Invalidate();
m_noiseBendTimer.Invalidate();
m_bendNoisePositionValid = false;
m_isStuck = false;
m_stuckTimestamp = 0.0f;
m_wiggleTimer.Invalidate();
m_stuckJumpTimer.Invalidate();
m_nextCleanupCheckTimestamp = 0.0f;
m_pathLength = 0;
m_pathIndex = 0;
m_areaEnteredTimestamp = 0.0f;
m_currentArea = NULL;
m_lastKnownArea = NULL;
m_isStopping = false;
m_avoidFriendTimer.Invalidate();
m_isFriendInTheWay = false;
m_isWaitingBehindFriend = false;
m_isAvoidingGrenade.Invalidate();
StopPanicking();
m_disposition = ENGAGE_AND_INVESTIGATE;
m_enemy = NULL;
m_grenadeTossState = NOT_THROWING;
m_initialEncounterArea = NULL;
m_wasSafe = true;
m_nearbyEnemyCount = 0;
m_enemyPlace = 0;
m_nearbyFriendCount = 0;
m_closestVisibleFriend = NULL;
m_closestVisibleHumanFriend = NULL;
for( int w=0; w<MAX_PLAYERS; ++w )
{
m_watchInfo[w].timestamp = 0.0f;
m_watchInfo[w].isEnemy = false;
m_playerTravelDistance[ w ] = -1.0f;
}
// randomly offset each bot's timer to spread computation out
m_updateTravelDistanceTimer.Start( RandomFloat( 0.0f, 0.9f ) );
m_travelDistancePhase = 0;
m_isEnemyVisible = false;
m_visibleEnemyParts = NONE;
m_lastSawEnemyTimestamp = -999.9f;
m_firstSawEnemyTimestamp = 0.0f;
m_currentEnemyAcquireTimestamp = 0.0f;
m_isLastEnemyDead = true;
m_attacker = NULL;
m_attackedTimestamp = 0.0f;
m_enemyDeathTimestamp = 0.0f;
m_friendDeathTimestamp = 0.0f;
m_lastVictimID = 0;
m_isAimingAtEnemy = false;
m_fireWeaponTimestamp = 0.0f;
m_equipTimer.Invalidate();
m_zoomTimer.Invalidate();
m_isFollowing = false;
m_leader = NULL;
m_followTimestamp = 0.0f;
m_allowAutoFollowTime = 0.0f;
m_enemyQueueIndex = 0;
m_enemyQueueCount = 0;
m_enemyQueueAttendIndex = 0;
m_bomber = NULL;
m_bIsSleeping = false;
m_isEnemySniperVisible = false;
m_sawEnemySniperTimer.Invalidate();
m_lookAroundStateTimestamp = 0.0f;
m_inhibitLookAroundTimestamp = 0.0f;
m_lookPitch = 0.0f;
m_lookPitchVel = 0.0f;
m_lookYaw = 0.0f;
m_lookYawVel = 0.0f;
m_lookAtSpotState = NOT_LOOKING_AT_SPOT;
m_targetSpot.Zero();
m_targetSpotVelocity.Zero();
m_targetSpotPredicted.Zero();
m_aimError.Init();
m_aimGoal.Init();
m_targetSpotTime = 0.0f;
m_aimFocus = 0.0f;
m_aimFocusInterval = 0.0f;
m_aimFocusNextUpdate = 0.0f;
for( int p=0; p<MAX_PLAYERS; ++p )
{
m_partInfo[p].m_validFrame = 0;
}
m_spotEncounter = NULL;
m_spotCheckTimestamp = 0.0f;
m_peripheralTimestamp = 0.0f;
m_avgVelIndex = 0;
m_avgVelCount = 0;
m_lastOrigin = GetCentroid( this );
m_lastRadioCommand = RADIO_INVALID;
m_lastRadioRecievedTimestamp = 0.0f;
m_lastRadioSentTimestamp = 0.0f;
m_radioSubject = NULL;
m_voiceEndTimestamp = 0.0f;
m_hostageEscortCount = 0;
m_hostageEscortCountTimestamp = 0.0f;
m_noisePosition = Vector( 0, 0, 0 );
m_noiseTimestamp = 0.0f;
m_stateTimestamp = 0.0f;
m_task = SEEK_AND_DESTROY;
m_taskEntity = NULL;
m_approachPointCount = 0;
m_approachPointViewPosition.x = 99999999999.9f;
m_approachPointViewPosition.y = 0.0f;
m_approachPointViewPosition.z = 0.0f;
m_checkedHidingSpotCount = 0;
StandUp();
Run();
m_mustRunTimer.Invalidate();
m_waitTimer.Invalidate();
m_pathLadder = NULL;
m_repathTimer.Invalidate();
m_huntState.ClearHuntArea();
m_hasVisitedEnemySpawn = false;
m_stillTimer.Invalidate();
// adjust morale - if we died, our morale decreased,
// but if we live, no adjustement (round win/loss also adjusts morale)
if (m_diedLastRound)
DecreaseMorale();
m_diedLastRound = false;
// IsRogue() randomly changes this
m_isRogue = false;
m_surpriseTimer.Invalidate();
// even though these are EHANDLEs, they need to be NULL-ed
m_goalEntity = NULL;
m_avoid = NULL;
m_enemy = NULL;
for ( int i=0; i<MAX_ENEMY_QUEUE; ++i )
{
m_enemyQueue[i].player = NULL;
m_enemyQueue[i].isReloading = false;
m_enemyQueue[i].isProtectedByShield = false;
}
m_burnedByFlamesTimer.Invalidate();
#ifdef OPT_VIS_CSGO
V_memset( m_bVis, 0, sizeof(m_bVis) );
V_memset( m_aVisParts, 0, sizeof(m_aVisParts) );
#endif
// start in idle state
m_isOpeningDoor = false;
StopAttacking();
Idle();
}
//--------------------------------------------------------------------------------------------------------------
/**
* Called when bot is placed in map, and when bots are reset after a round ends.
* NOTE: For some reason, this can be called twice when a bot is added.
*/
void CCSBot::Spawn( void )
{
if ( CSGameRules() && CSGameRules()->IsPlayingCoopMission() && GetTeamNumber() == TEAM_TERRORIST )
SetLastCoopSpawnPoint( NULL );
// do the normal player spawn process
BaseClass::Spawn();
ResetValues();
Buy();
if ( CSGameRules()->IsPlayingCoopMission() )
{
if ( GetTeamNumber() == TEAM_CT )
{
SetDisposition( CCSBot::SELF_DEFENSE );
for ( int i = 1; i <= MAX_PLAYERS; i++ )
{
CCSPlayer *pPlayer = ToCSPlayer( UTIL_PlayerByIndex( i ) );
if ( pPlayer && !pPlayer->IsBot() && pPlayer->GetTeamNumber() == TEAM_CT )
{
//m_isFollowing = true;
//m_leader = pPlayer;
//SetTask( CCSBot::FOLLOW );
Follow( pPlayer );
m_lastRadioCommand = RADIO_FOLLOW_ME;
m_lastRadioRecievedTimestamp = gpGlobals->curtime;
m_radioSubject = pPlayer;
m_radioPosition = GetCentroid( pPlayer );
break;
}
}
}
else if ( GetTeamNumber() == TEAM_TERRORIST )
{
CoopInitialize();
}
}
// set the bot name here (after we've had a chance to initialize a new profile
V_strcpy_safe( m_name, GetPlayerName() );
}
+217
View File
@@ -0,0 +1,217 @@
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
// Author: Michael S. Booth (mike@turtlerockstudios.com), 2003
#include "cbase.h"
#include "cs_bot.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
//--------------------------------------------------------------------------------------------------------------
bool CCSBot::IsNoiseHeard( void ) const
{
if (m_noiseTimestamp <= 0.0f)
return false;
// primitive reaction time simulation - cannot "hear" noise until reaction time has elapsed
if (gpGlobals->curtime - m_noiseTimestamp >= GetProfile()->GetReactionTime())
return true;
return false;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Listen for enemy noises, and determine if we should react to them.
* Returns true if heard a noise and should move to investigate.
*/
bool CCSBot::HeardInterestingNoise( void )
{
if (IsBlind())
return false;
// don't investigate noises during safe time
if (!IsWellPastSafe())
return false;
// if our disposition is not to investigate, dont investigate
if (GetDisposition() != ENGAGE_AND_INVESTIGATE)
return false;
// listen for enemy noises
if (IsNoiseHeard())
{
// if we are hiding, only react to noises very nearby, depending on how aggressive we are
if (IsAtHidingSpot() && GetNoiseRange() > 100.0f + 400.0f * GetProfile()->GetAggression())
return false;
float chance = GetNoiseInvestigateChance();
if (RandomFloat( 0.0f, 100.0f ) <= chance)
{
return true;
}
}
return false;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Return true if we hear nearby threatening enemy gunfire within given range
* -1 == infinite range
*/
bool CCSBot::CanHearNearbyEnemyGunfire( float range ) const
{
Vector myOrigin = GetCentroid( this );
// only attend to noise if it just happened
if (gpGlobals->curtime - m_noiseTimestamp > 0.5f)
return false;
// gunfire is high priority
if (m_noisePriority < PRIORITY_HIGH)
return false;
// check noise range
if (range > 0.0f && (myOrigin - m_noisePosition).IsLengthGreaterThan( range ))
return false;
// if we dont have line of sight, it's not threatening (cant get shot)
if (!CanSeeNoisePosition())
return false;
if (IsAttacking() && m_enemy != NULL && GetTimeSinceLastSawEnemy() < 1.0f)
{
// gunfire is only threatening if it is closer than our current enemy
float gunfireDistSq = (m_noisePosition - myOrigin).LengthSqr();
float enemyDistSq = (GetCentroid( m_enemy ) - myOrigin).LengthSqr();
const float muchCloserSq = 100.0f * 100.0f;
if (gunfireDistSq > enemyDistSq - muchCloserSq)
return false;
}
return true;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Return true if we directly see where we think the noise came from
* NOTE: Dont check FOV, since this is used to determine if we should turn our head to look at the noise
* NOTE: Dont use IsVisible(), because smoke shouldnt cause us to not look toward noises
*/
bool CCSBot::CanSeeNoisePosition( void ) const
{
trace_t result;
CTraceFilterNoNPCsOrPlayer traceFilter( this, COLLISION_GROUP_NONE );
UTIL_TraceLine( EyePositionConst(), m_noisePosition + Vector( 0, 0, HalfHumanHeight ), MASK_VISIBLE_AND_NPCS, &traceFilter, &result );
if (result.fraction == 1.0f)
{
// we can see the source of the noise
return true;
}
return false;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Return true if we decided to look towards the most recent noise source
* Assumes m_noisePosition is valid.
*/
bool CCSBot::UpdateLookAtNoise( void )
{
// make sure a noise exists
if (!IsNoiseHeard())
{
return false;
}
Vector spot;
// if we have clear line of sight to noise position, look directly at it
if (CanSeeNoisePosition())
{
/// @todo adjust noise Z to keep consistent with current height while fighting
spot = m_noisePosition + Vector( 0, 0, HalfHumanHeight );
// since we can see the noise spot, forget about it
ForgetNoise();
}
else
{
// line of sight is blocked, bend it
// the bending algorithm is very expensive, throttle how often it is done
if (m_noiseBendTimer.IsElapsed())
{
const float noiseBendLOSInterval = RandomFloat( 0.2f, 0.3f );
m_noiseBendTimer.Start( noiseBendLOSInterval );
// line of sight is blocked, bend it
if (BendLineOfSight( EyePosition(), m_noisePosition, &spot ) == false)
{
m_bendNoisePositionValid = false;
return false;
}
m_bentNoisePosition = spot;
m_bendNoisePositionValid = true;
}
else if (m_bendNoisePositionValid)
{
// use result of prior bend computation
spot = m_bentNoisePosition;
}
else
{
// prior bend failed
return false;
}
}
// it's always important to look at enemy noises, because they come from ... enemies!
PriorityType pri = PRIORITY_HIGH;
// look longer if we're hiding
if (IsAtHidingSpot())
{
// if there is only one enemy left, look for a long time
if (GetEnemiesRemaining() == 1)
{
SetLookAt( "Noise", spot, pri, RandomFloat( 5.0f, 15.0f ), true );
}
else
{
SetLookAt( "Noise", spot, pri, RandomFloat( 3.0f, 5.0f ), true );
}
}
else
{
const float closeRange = 500.0f;
if (GetNoiseRange() < closeRange)
{
// look at nearby enemy noises for a longer time
SetLookAt( "Noise", spot, pri, RandomFloat( 3.0f, 5.0f ), true );
}
else
{
SetLookAt( "Noise", spot, pri, RandomFloat( 1.0f, 2.0f ), true );
}
}
return true;
}
File diff suppressed because it is too large Load Diff
+452
View File
@@ -0,0 +1,452 @@
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
// Author: Michael S. Booth (mike@turtlerockstudios.com), 2003
#ifndef CS_CONTROL_H
#define CS_CONTROL_H
#include "bot_manager.h"
#include "nav_area.h"
#include "bot_util.h"
#include "bot_profile.h"
#include "cs_shareddefs.h"
#include "cs_player.h"
extern ConVar mp_friendlyfire;
extern ConVar throttle_expensive_ai;
class CBasePlayerWeapon;
/**
* Given one team, return the other
*/
inline int OtherTeam( int team )
{
return (team == TEAM_TERRORIST) ? TEAM_CT : TEAM_TERRORIST;
}
class CCSBotManager;
// accessor for CS-specific bots
inline CCSBotManager *TheCSBots( void )
{
return reinterpret_cast< CCSBotManager * >( TheBots );
}
//--------------------------------------------------------------------------------------------------------------
class BotEventInterface : public IGameEventListener2
{
public:
virtual const char *GetEventName( void ) const = 0;
};
//--------------------------------------------------------------------------------------------------------------
//--------------------------------------------------------------------------------------------------------------
/**
* Macro to set up an OnEventClass() in TheCSBots.
*/
#define DECLARE_BOTMANAGER_EVENT_LISTENER( BotManagerSingleton, EventClass, EventName ) \
public: \
virtual void On##EventClass( IGameEvent *data ); \
private: \
class EventClass##Event : public BotEventInterface \
{ \
bool m_enabled; \
public: \
EventClass##Event( void ) \
{ \
gameeventmanager->AddListener( this, #EventName, true ); \
m_enabled = true; \
} \
~EventClass##Event( void ) \
{ \
if ( m_enabled ) gameeventmanager->RemoveListener( this ); \
} \
virtual const char *GetEventName( void ) const \
{ \
return #EventName; \
} \
void Enable( bool enable ) \
{ \
m_enabled = enable; \
if ( enable ) \
gameeventmanager->AddListener( this, #EventName, true ); \
else \
gameeventmanager->RemoveListener( this ); \
} \
bool IsEnabled( void ) const { return m_enabled; } \
void FireGameEvent( IGameEvent *event ) \
{ \
BotManagerSingleton()->On##EventClass( event ); \
} \
int GetEventDebugID( void ) \
{ \
return EVENT_DEBUG_ID_INIT; \
} \
}; \
EventClass##Event m_##EventClass##Event;
//--------------------------------------------------------------------------------------------------------------
#define DECLARE_CSBOTMANAGER_EVENT_LISTENER( EventClass, EventName ) DECLARE_BOTMANAGER_EVENT_LISTENER( TheCSBots, EventClass, EventName )
//--------------------------------------------------------------------------------------------------------------
/**
* Macro to propogate an event from the bot manager to all bots
*/
// @kutta: changed dynamic_cast -> static_cast; what other bot types can there be?
#define CCSBOTMANAGER_ITERATE_BOTS( Callback, arg1 ) \
{ \
for ( int idx = 1; idx <= gpGlobals->maxClients; ++idx ) \
{ \
CBasePlayer *player = UTIL_PlayerByIndex( idx ); \
if (player == NULL) continue; \
if (!player->IsBot()) continue; \
CCSBot *bot = static_cast< CCSBot * >(player); \
bot->Callback( arg1 ); \
} \
}
//--------------------------------------------------------------------------------------------------------------
//
// The manager for Counter-Strike specific bots
//
class CCSBotManager : public CBotManager
{
public:
CCSBotManager();
virtual CBasePlayer *AllocateBotEntity( void ); ///< factory method to allocate the appropriate entity for the bot
virtual void ClientDisconnect( CBaseEntity *entity );
virtual bool ClientCommand( CBasePlayer *player, const CCommand &args );
virtual void ServerActivate( void );
virtual void ServerDeactivate( void );
virtual bool ServerCommand( const char *cmd );
bool IsServerActive( void ) const { return m_serverActive; }
virtual void RestartRound( void ); ///< (EXTEND) invoked when a new round begins
virtual void StartFrame( void ); ///< (EXTEND) called each frame
virtual unsigned int GetPlayerPriority( CBasePlayer *player ) const; ///< return priority of player (0 = max pri)
virtual bool IsImportantPlayer( CCSPlayer *player ) const; ///< return true if player is important to scenario (VIP, bomb carrier, etc)
void ExtractScenarioData( void ); ///< search the map entities to determine the game scenario and define important zones
// difficulty levels -----------------------------------------------------------------------------------------
static BotDifficultyType GetDifficultyLevel( void )
{
static ConVarRef sv_mmqueue_reservation( "sv_mmqueue_reservation" );
if ( sv_mmqueue_reservation.GetString()[0] == 'Q' && CSGameRules() && !CSGameRules()->IsPlayingCooperativeGametype() )
return BOT_EASY; // Queue matchmaking always subs people with easy bots
if (cv_bot_difficulty.GetFloat() < 0.9f)
return BOT_EASY;
if (cv_bot_difficulty.GetFloat() < 1.9f)
return BOT_NORMAL;
if (cv_bot_difficulty.GetFloat() < 2.9f)
return BOT_HARD;
return BOT_EXPERT;
}
// the supported game scenarios ------------------------------------------------------------------------------
enum GameScenarioType
{
SCENARIO_DEATHMATCH,
SCENARIO_DEFUSE_BOMB,
SCENARIO_RESCUE_HOSTAGES,
SCENARIO_ESCORT_VIP
};
GameScenarioType GetScenario( void ) const { return m_gameScenario; }
// "zones" ---------------------------------------------------------------------------------------------------
// depending on the game mode, these are bomb zones, rescue zones, etc.
enum { MAX_ZONES = 4 }; ///< max # of zones in a map
enum { MAX_ZONE_NAV_AREAS = 16 }; ///< max # of nav areas in a zone
struct Zone
{
CBaseEntity *m_entity; ///< the map entity
CNavArea *m_area[ MAX_ZONE_NAV_AREAS ]; ///< nav areas that overlap this zone
int m_areaCount;
Vector m_center;
bool m_isLegacy; ///< if true, use pev->origin and 256 unit radius as zone
int m_index;
bool m_isBlocked;
Extent m_extent;
};
const Zone *GetZone( int i ) const { return ( ( i >= 0 ) && ( i < m_zoneCount ) ) ? ( &m_zone[i] ) : NULL; }
const Zone *GetZone( const Vector &pos ) const; ///< return the zone that contains the given position
const Zone *GetClosestZone( const Vector &pos ) const; ///< return the closest zone to the given position
const Zone *GetClosestZone( const CBaseEntity *entity ) const; ///< return the closest zone to the given entity
int GetZoneCount( void ) const { return m_zoneCount; }
void CheckForBlockedZones( void );
const Vector *GetRandomPositionInZone( const Zone *zone ) const; ///< return a random position inside the given zone
CNavArea *GetRandomAreaInZone( const Zone *zone ) const; ///< return a random area inside the given zone
/**
* Return the zone closest to the given position, using the given cost heuristic
*/
template< typename CostFunctor >
const Zone *GetClosestZone( CNavArea *startArea, CostFunctor costFunc, float *travelDistance = NULL ) const
{
const Zone *closeZone = NULL;
float closeDist = 99999999.9f;
if (startArea == NULL)
return NULL;
for( int i=0; i<m_zoneCount; ++i )
{
if (m_zone[i].m_areaCount == 0)
continue;
if ( m_zone[i].m_isBlocked )
continue;
// just use the first overlapping nav area as a reasonable approximation
float dist = NavAreaTravelDistance( startArea, m_zone[i].m_area[0], costFunc );
if (dist >= 0.0f && dist < closeDist)
{
closeZone = &m_zone[i];
closeDist = dist;
}
}
if (travelDistance)
*travelDistance = closeDist;
return closeZone;
}
/// pick a zone at random and return it
const Zone *GetRandomZone( void ) const
{
if (m_zoneCount == 0)
return NULL;
int i;
CUtlVector< const Zone * > unblockedZones;
for ( i=0; i<m_zoneCount; ++i )
{
if ( m_zone[i].m_isBlocked )
continue;
unblockedZones.AddToTail( &(m_zone[i]) );
}
if ( unblockedZones.Count() == 0 )
return NULL;
return unblockedZones[ RandomInt( 0, unblockedZones.Count()-1 ) ];
}
/// returns a random spawn point for the given team (no arg means use both team spawnpoints)
CBaseEntity *GetRandomSpawn( int team = TEAM_MAXCOUNT ) const;
bool IsBombPlanted( void ) const { return m_isBombPlanted; } ///< returns true if bomb has been planted
float GetBombPlantTimestamp( void ) const { return m_bombPlantTimestamp; } ///< return time bomb was planted
bool IsTimeToPlantBomb( void ) const; ///< return true if it's ok to try to plant bomb
CCSPlayer *GetBombDefuser( void ) const { return m_bombDefuser; } ///< return the player currently defusing the bomb, or NULL
float GetBombTimeLeft( void ) const; ///< get the time remaining before the planted bomb explodes
CBaseEntity *GetLooseBomb( void ) { return m_looseBomb; } ///< return the bomb if it is loose on the ground
CNavArea *GetLooseBombArea( void ) const { return m_looseBombArea; } ///< return area that bomb is in/near
void SetLooseBomb( CBaseEntity *bomb );
enum ETStrat
{
k_ETStrat_Rush, // Hit the target site asap
k_ETStrat_Slow, // Spend time at initial engagement spots, move in if time is low or if team gets a kill
k_ETStrat_Fake, // One player goes to the site not being hit
k_ETStrat_Count,
};
ETStrat GetTStrat( void ) const { return m_eTStrat; }
int GetTerroristTargetSite( void ) const { return m_iTerroristTargetSite; }
enum ECTStrat
{
k_ECTStrat_GuardRandomSite, // Used for testing
k_ECTStrat_212, // 2 each site, one aggressive mid
k_ECTStrat_StackSite, // 4 on priority site, 1 alternate site
k_ECTStrat_Count,
};
ECTStrat GetCTStrat( void ) const { return m_eCTStrat; }
int GetCTPrioritySite( void ) const { return m_iCTPrioritySite; }
float GetRadioMessageTimestamp( RadioType event, int teamID ) const; ///< return the last time the given radio message was sent for given team
float GetRadioMessageInterval( RadioType event, int teamID ) const; ///< return the interval since the last time this message was sent
void SetRadioMessageTimestamp( RadioType event, int teamID );
void ResetRadioMessageTimestamps( void );
float GetLastSeenEnemyTimestamp( void ) const { return m_lastSeenEnemyTimestamp; } ///< return the last time anyone has seen an enemy
void SetLastSeenEnemyTimestamp( void ) { m_lastSeenEnemyTimestamp = gpGlobals->curtime; }
float GetRoundStartTime( void ) const { return m_roundStartTimestamp; }
float GetElapsedRoundTime( void ) const { return gpGlobals->curtime - m_roundStartTimestamp; } ///< return the elapsed time since the current round began
bool AllowRogues( void ) const { return cv_bot_allow_rogues.GetBool(); }
bool AllowPistols( void ) const { return cv_bot_allow_pistols.GetBool(); }
bool AllowShotguns( void ) const { return cv_bot_allow_shotguns.GetBool(); }
bool AllowSubMachineGuns( void ) const { return cv_bot_allow_sub_machine_guns.GetBool(); }
bool AllowRifles( void ) const { return cv_bot_allow_rifles.GetBool(); }
bool AllowMachineGuns( void ) const { return cv_bot_allow_machine_guns.GetBool(); }
bool AllowGrenades( void ) const { return cv_bot_allow_grenades.GetBool(); }
bool AllowSnipers( void ) const { return cv_bot_allow_snipers.GetBool(); }
#ifdef CS_SHIELD_ENABLED
bool AllowTacticalShield( void ) const { return cv_bot_allow_shield.GetBool(); }
#else
bool AllowTacticalShield( void ) const { return false; }
#endif // CS_SHIELD_ENABLED
bool AllowFriendlyFireDamage( void ) const { return mp_friendlyfire.GetBool(); }
bool IsWeaponUseable( const CWeaponCSBase *weapon ) const; ///< return true if the bot can use this weapon
bool IsDefenseRushing( void ) const { return m_isDefenseRushing; } ///< returns true if defense team has "decided" to rush this round
bool IsOnDefense( const CCSPlayer *player ) const; ///< return true if this player is on "defense"
bool IsOnOffense( const CCSPlayer *player ) const; ///< return true if this player is on "offense"
bool IsRoundOver( void ) const { return m_isRoundOver; } ///< return true if the round has ended
#define FROM_CONSOLE true
bool BotAddCommand( int team, bool isFromConsole = false, const char *profileName = NULL, CSWeaponType weaponType = WEAPONTYPE_UNKNOWN, BotDifficultyType difficulty = NUM_DIFFICULTY_LEVELS ); ///< process the "bot_add" console command
bool BotPlaceCommand( uint nTeamMask = 0xFFFFFFFF ); //Moves a bot at the location under the cursor. For perf and lighting testing.
// Called to mark that an expensive operation has happened this frame, used for budgeting/throttling AI
void OnExpensiveBotOperation() { m_nNumExpensiveOperationsThisFrame ++; }
// Query to see if we have exceeded our per-frame budget for "expensive" AI operations
bool AllowedToDoExpensiveBotOperationThisFrame() { return !throttle_expensive_ai.GetBool() || m_nNumExpensiveOperationsThisFrame < 1; }
void ForceMaintainBotQuota( void ) { MaintainBotQuota(); }
private:
enum SkillType { LOW, AVERAGE, HIGH, RANDOM };
void MaintainBotQuota( void );
static bool m_isMapDataLoaded; ///< true if we've attempted to load map data
bool m_serverActive; ///< true between ServerActivate() and ServerDeactivate()
GameScenarioType m_gameScenario; ///< what kind of game are we playing
Zone m_zone[ MAX_ZONES ];
int m_zoneCount;
bool m_isBombPlanted; ///< true if bomb has been planted
float m_bombPlantTimestamp; ///< time bomb was planted
float m_earliestBombPlantTimestamp; ///< don't allow planting until after this time has elapsed
CHandle<CCSPlayer> m_bombDefuser; ///< the player currently defusing a bomb
EHANDLE m_looseBomb; ///< will be non-NULL if bomb is loose on the ground
CNavArea *m_looseBombArea; ///< area that bomb is is/near
bool m_isRoundOver; ///< true if the round has ended
CountdownTimer m_checkTransientAreasTimer; ///< when elapsed, all transient nav areas should be checked for blockage
float m_radioMsgTimestamp[ RADIO_END - RADIO_START_1 ][ 2 ];
float m_lastSeenEnemyTimestamp;
float m_roundStartTimestamp; ///< the time when the current round began
bool m_isDefenseRushing; ///< whether defensive team is rushing this round or not
int m_nNumExpensiveOperationsThisFrame;
// Cooperative mode vars
ETStrat m_eTStrat; // Current plan for T
int m_iTerroristTargetSite; // Bomb site Ts are planing to plant at
ECTStrat m_eCTStrat; // Current plan for CT
int m_iCTPrioritySite; // Guess at which site will be attacked
// Event Handlers --------------------------------------------------------------------------------------------
DECLARE_CSBOTMANAGER_EVENT_LISTENER( PlayerFootstep, player_footstep )
DECLARE_CSBOTMANAGER_EVENT_LISTENER( PlayerRadio, player_radio )
DECLARE_CSBOTMANAGER_EVENT_LISTENER( PlayerDeath, player_death )
DECLARE_CSBOTMANAGER_EVENT_LISTENER( PlayerFallDamage, player_falldamage )
DECLARE_CSBOTMANAGER_EVENT_LISTENER( BombPickedUp, bomb_pickup )
DECLARE_CSBOTMANAGER_EVENT_LISTENER( BombPlanted, bomb_planted )
DECLARE_CSBOTMANAGER_EVENT_LISTENER( BombBeep, bomb_beep )
DECLARE_CSBOTMANAGER_EVENT_LISTENER( BombDefuseBegin, bomb_begindefuse )
DECLARE_CSBOTMANAGER_EVENT_LISTENER( BombDefused, bomb_defused )
DECLARE_CSBOTMANAGER_EVENT_LISTENER( BombDefuseAbort, bomb_abortdefuse )
DECLARE_CSBOTMANAGER_EVENT_LISTENER( BombExploded, bomb_exploded )
DECLARE_CSBOTMANAGER_EVENT_LISTENER( RoundEnd, round_end )
DECLARE_CSBOTMANAGER_EVENT_LISTENER( RoundStart, round_start )
DECLARE_CSBOTMANAGER_EVENT_LISTENER( RoundFreezeEnd, round_freeze_end )
DECLARE_CSBOTMANAGER_EVENT_LISTENER( DoorMoving, door_moving )
DECLARE_CSBOTMANAGER_EVENT_LISTENER( BreakProp, break_prop )
DECLARE_CSBOTMANAGER_EVENT_LISTENER( BreakBreakable, break_breakable )
DECLARE_CSBOTMANAGER_EVENT_LISTENER( HostageFollows, hostage_follows )
DECLARE_CSBOTMANAGER_EVENT_LISTENER( HostageRescuedAll, hostage_rescued_all )
DECLARE_CSBOTMANAGER_EVENT_LISTENER( WeaponFire, weapon_fire )
DECLARE_CSBOTMANAGER_EVENT_LISTENER( WeaponFireOnEmpty, weapon_fire_on_empty )
DECLARE_CSBOTMANAGER_EVENT_LISTENER( WeaponReload, weapon_reload )
DECLARE_CSBOTMANAGER_EVENT_LISTENER( WeaponZoom, weapon_zoom )
DECLARE_CSBOTMANAGER_EVENT_LISTENER( BulletImpact, bullet_impact )
DECLARE_CSBOTMANAGER_EVENT_LISTENER( HEGrenadeDetonate, hegrenade_detonate )
DECLARE_CSBOTMANAGER_EVENT_LISTENER( FlashbangDetonate, flashbang_detonate )
DECLARE_CSBOTMANAGER_EVENT_LISTENER( SmokeGrenadeDetonate, smokegrenade_detonate )
DECLARE_CSBOTMANAGER_EVENT_LISTENER( MolotovDetonate, molotov_detonate )
DECLARE_CSBOTMANAGER_EVENT_LISTENER( DecoyDetonate, decoy_detonate )
DECLARE_CSBOTMANAGER_EVENT_LISTENER( DecoyFiring, decoy_firing )
DECLARE_CSBOTMANAGER_EVENT_LISTENER( GrenadeBounce, grenade_bounce )
DECLARE_CSBOTMANAGER_EVENT_LISTENER( NavBlocked, nav_blocked )
DECLARE_CSBOTMANAGER_EVENT_LISTENER( ServerShutdown, server_shutdown )
CUtlVector< BotEventInterface * > m_commonEventListeners; // These event listeners fire often, and can be disabled for performance gains when no bots are present.
bool m_eventListenersEnabled;
void EnableEventListeners( bool enable );
};
inline CBasePlayer *CCSBotManager::AllocateBotEntity( void )
{
return static_cast<CBasePlayer *>( CreateEntityByName( "cs_bot" ) );
}
inline bool CCSBotManager::IsTimeToPlantBomb( void ) const
{
return (gpGlobals->curtime >= m_earliestBombPlantTimestamp);
}
inline const CCSBotManager::Zone *CCSBotManager::GetClosestZone( const CBaseEntity *entity ) const
{
if (entity == NULL)
return NULL;
Vector centroid = entity->GetAbsOrigin();
centroid.z += HalfHumanHeight;
return GetClosestZone( centroid );
}
#endif
+985
View File
@@ -0,0 +1,985 @@
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
// Author: Michael S. Booth (mike@turtlerockstudios.com), 2003
#include "cbase.h"
#include "cs_bot.h"
#include "obstacle_pushaway.h"
#include "fmtstr.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
const float NearBreakableCheckDist = 20.0f;
const float FarBreakableCheckDist = 300.0f;
#define DEBUG_BREAKABLES 0
#define DEBUG_DOORS 0
//--------------------------------------------------------------------------------------------------------------
#if DEBUG_BREAKABLES
static void DrawOutlinedQuad( const Vector &p1,
const Vector &p2,
const Vector &p3,
const Vector &p4,
int r, int g, int b,
float duration )
{
NDebugOverlay::Triangle( p1, p2, p3, r, g, b, 20, false, duration );
NDebugOverlay::Triangle( p3, p4, p1, r, g, b, 20, false, duration );
NDebugOverlay::Line( p1, p2, r, g, b, false, duration );
NDebugOverlay::Line( p2, p3, r, g, b, false, duration );
NDebugOverlay::Line( p3, p4, r, g, b, false, duration );
NDebugOverlay::Line( p4, p1, r, g, b, false, duration );
}
ConVar bot_debug_breakable_duration( "bot_debug_breakable_duration", "30" );
#endif // DEBUG_BREAKABLES
//--------------------------------------------------------------------------------------------------------------
CBaseEntity * CheckForEntitiesAlongSegment( const Vector &start, const Vector &end, const Vector &mins, const Vector &maxs, CPushAwayEnumerator *enumerator )
{
CBaseEntity *entity = NULL;
Ray_t ray;
ray.Init( start, end, mins, maxs );
::partition->EnumerateElementsAlongRay( PARTITION_ENGINE_SOLID_EDICTS, ray, false, enumerator );
if ( enumerator->m_nAlreadyHit > 0 )
{
entity = enumerator->m_AlreadyHit[0];
}
#if DEBUG_BREAKABLES
if ( entity )
{
DrawOutlinedQuad( start + mins, start + maxs, end + maxs, end + mins, 255, 0, 0, bot_debug_breakable_duration.GetFloat() );
}
else
{
DrawOutlinedQuad( start + mins, start + maxs, end + maxs, end + mins, 0, 255, 0, 0.1 );
}
#endif // DEBUG_BREAKABLES
return entity;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Look up to 'distance' units ahead on the bot's path for entities. Returns the closest one.
*/
CBaseEntity * CCSBot::FindEntitiesOnPath( float distance, CPushAwayEnumerator *enumerator, bool checkStuck )
{
Vector goal;
int pathIndex = FindPathPoint( distance, &goal, NULL );
bool isDegeneratePath = ( pathIndex == m_pathLength );
if ( isDegeneratePath )
{
goal = m_goalPosition;
}
goal.z += HalfHumanHeight;
Vector mins, maxs;
mins = Vector( 0, 0, -HalfHumanWidth );
maxs = Vector( 0, 0, HalfHumanHeight );
if ( distance <= NearBreakableCheckDist && m_isStuck && checkStuck )
{
mins = Vector( -HalfHumanWidth, -HalfHumanWidth, -HalfHumanWidth );
maxs = Vector( HalfHumanWidth, HalfHumanWidth, HalfHumanHeight );
}
CBaseEntity *entity = NULL;
if ( isDegeneratePath )
{
entity = CheckForEntitiesAlongSegment( WorldSpaceCenter(), m_goalPosition + Vector( 0, 0, HalfHumanHeight ), mins, maxs, enumerator );
#if DEBUG_BREAKABLES
if ( entity )
{
NDebugOverlay::HorzArrow( WorldSpaceCenter(), m_goalPosition, 6, 0, 0, 255, 255, true, bot_debug_breakable_duration.GetFloat() );
}
#endif // DEBUG_BREAKABLES
}
else
{
int startIndex = MAX( 0, m_pathIndex );
float distanceLeft = distance;
// HACK: start with an index one lower than normal, so we can trace from the bot's location to the
// start of the path nodes.
for( int i=startIndex-1; i<m_pathLength-1; ++i )
{
Vector start, end;
if ( i == startIndex - 1 )
{
start = GetAbsOrigin();
end = m_path[i+1].pos;
}
else
{
start = m_path[i].pos;
end = m_path[i+1].pos;
if ( m_path[i+1].how == GO_LADDER_UP )
{
// Need two checks. First we'll check along the ladder
start = m_path[i].pos;
end = m_path[i+1].ladder->m_top;
}
else if ( m_path[i].how == GO_LADDER_UP )
{
start = m_path[i].ladder->m_top;
}
else if ( m_path[i+1].how == GO_LADDER_DOWN )
{
// Need two checks. First we'll check along the ladder
start = m_path[i].pos;
end = m_path[i+1].ladder->m_bottom;
}
else if ( m_path[i].how == GO_LADDER_DOWN )
{
start = m_path[i].ladder->m_bottom;
}
}
float segmentLength = (start - end).Length();
if ( distanceLeft - segmentLength < 0 )
{
// scale our segment back so we don't look too far
Vector direction = end - start;
direction.NormalizeInPlace();
end = start + direction * distanceLeft;
}
entity = CheckForEntitiesAlongSegment( start + Vector( 0, 0, HalfHumanHeight ), end + Vector( 0, 0, HalfHumanHeight ), mins, maxs, enumerator );
if ( entity )
{
#if DEBUG_BREAKABLES
NDebugOverlay::HorzArrow( start, end, 4, 0, 255, 0, 255, true, bot_debug_breakable_duration.GetFloat() );
#endif // DEBUG_BREAKABLES
break;
}
if ( m_path[i].ladder && !IsOnLadder() && distance > NearBreakableCheckDist ) // don't try to break breakables on the other end of a ladder
break;
distanceLeft -= segmentLength;
if ( distanceLeft < 0 )
break;
if ( i != startIndex - 1 && m_path[i+1].ladder )
{
// Now we'll check from the ladder out to the endpoint
start = ( m_path[i+1].how == GO_LADDER_DOWN ) ? m_path[i+1].ladder->m_bottom : m_path[i+1].ladder->m_top;
end = m_path[i+1].pos;
entity = CheckForEntitiesAlongSegment( start + Vector( 0, 0, HalfHumanHeight ), end + Vector( 0, 0, HalfHumanHeight ), mins, maxs, enumerator );
if ( entity )
{
#if DEBUG_BREAKABLES
NDebugOverlay::HorzArrow( start, end, 4, 0, 255, 0, 255, true, bot_debug_breakable_duration.GetFloat() );
#endif // DEBUG_BREAKABLES
break;
}
}
}
}
if ( entity && !IsVisible( entity->WorldSpaceCenter(), false, entity ) )
return NULL;
return entity;
}
//--------------------------------------------------------------------------------------------------------------
void CCSBot::PushawayTouch( CBaseEntity *pOther )
{
#if DEBUG_BREAKABLES
NDebugOverlay::EntityBounds( pOther, 255, 0, 0, 127, 0.1f );
#endif // DEBUG_BREAKABLES
// if we're not stuck or crouched, we don't care
if ( !m_isStuck && !IsCrouching() )
return;
// See if it's breakable
CBaseEntity *props[1];
CBotBreakableEnumerator enumerator( props, ARRAYSIZE( props ) );
enumerator.EnumElement( pOther );
if ( enumerator.m_nAlreadyHit == 1 )
{
// it's breakable - try to shoot it.
SetLookAt( "Breakable", pOther->WorldSpaceCenter(), PRIORITY_HIGH, 0.1f, false, 5.0f, true );
}
}
//--------------------------------------------------------------------------------------------------------------
/**
* Check for breakable physics props and other breakable entities. We do this here instead of catching them
* in OnTouch() because players don't collide with physics props, so OnTouch() doesn't get called. Also,
* looking ahead like this lets us anticipate when we'll need to break something, and do it before being
* stopped by it.
*/
void CCSBot::BreakablesCheck( void )
{
SNPROF( "BreakablesCheck" );
if ( !TheCSBots()->AllowedToDoExpensiveBotOperationThisFrame() )
return;
#if DEBUG_BREAKABLES
/*
// Debug code to visually mark all breakables near us
{
Ray_t ray;
Vector origin = WorldSpaceCenter();
Vector mins( -400, -400, -400 );
Vector maxs( 400, 400, 400 );
ray.Init( origin, origin, mins, maxs );
CBaseEntity *props[40];
CBotBreakableEnumerator enumerator( props, ARRAYSIZE( props ) );
::partition->EnumerateElementsAlongRay( PARTITION_ENGINE_SOLID_EDICTS, ray, false, &enumerator );
for ( int i=0; i<enumerator.m_nAlreadyHit; ++i )
{
CBaseEntity *prop = props[i];
if ( prop && prop->m_takedamage == DAMAGE_YES )
{
CFmtStr msg;
const char *text = msg.sprintf( "%s, %d health", prop->GetClassname(), prop->m_iHealth );
if ( prop->m_iHealth > 200 )
{
NDebugOverlay::EntityBounds( prop, 255, 0, 0, 10, 0.2f );
prop->EntityText( 0, text, 0.2f, 255, 0, 0, 255 );
}
else
{
NDebugOverlay::EntityBounds( prop, 0, 255, 0, 10, 0.2f );
prop->EntityText( 0, text, 0.2f, 0, 255, 0, 255 );
}
}
}
}
*/
#endif // DEBUG_BREAKABLES
if ( IsAttacking() )
{
// make sure we aren't running into a breakable trying to knife an enemy
if ( IsUsingKnife() && m_enemy != NULL )
{
CBaseEntity *breakables[1];
CBotBreakableEnumerator enumerator( breakables, ARRAYSIZE( breakables ) );
CBaseEntity *breakable = NULL;
Vector mins = Vector( -HalfHumanWidth, -HalfHumanWidth, -HalfHumanWidth );
Vector maxs = Vector( HalfHumanWidth, HalfHumanWidth, HalfHumanHeight );
breakable = CheckForEntitiesAlongSegment( WorldSpaceCenter(), m_enemy->WorldSpaceCenter(), mins, maxs, &enumerator );
if ( breakable )
{
#if DEBUG_BREAKABLES
NDebugOverlay::HorzArrow( WorldSpaceCenter(), m_enemy->WorldSpaceCenter(), 6, 0, 0, 255, 255, true, bot_debug_breakable_duration.GetFloat() );
#endif // DEBUG_BREAKABLES
// look at it (chances are we'll already be looking at it, since it's between us and our enemy)
SetLookAt( "Breakable", breakable->WorldSpaceCenter(), PRIORITY_HIGH, 0.1f, false, 5.0f, true );
// break it (again, don't wait: we don't have ammo, since we're using the knife, and we're looking mostly at it anyway)
PrimaryAttack();
}
}
return;
}
if ( !HasPath() )
return;
bool isNear = true;
// Check just in front of us on the path
CBaseEntity *breakables[4];
CBotBreakableEnumerator enumerator( breakables, ARRAYSIZE( breakables ) );
CBaseEntity *breakable = FindEntitiesOnPath( NearBreakableCheckDist, &enumerator, true );
// If we don't have an object right in front of us, check a ways out
if ( !breakable )
{
breakable = FindEntitiesOnPath( FarBreakableCheckDist, &enumerator, false );
isNear = false;
}
// Try to shoot a breakable we know about
if ( breakable )
{
// look at it
SetLookAt( "Breakable", breakable->WorldSpaceCenter(), PRIORITY_HIGH, 0.1f, false, 5.0f, true );
}
// break it
if ( IsLookingAtSpot( PRIORITY_HIGH ) && m_lookAtSpotAttack )
{
if ( IsUsingGrenade() || ( !isNear && IsUsingKnife() ) )
{
EquipBestWeapon( MUST_EQUIP );
}
else if ( GetActiveWeapon() && GetActiveWeapon()->m_flNextPrimaryAttack <= gpGlobals->curtime )
{
bool shouldShoot = IsLookingAtPosition( m_lookAtSpot, 10.0f );
if ( !shouldShoot )
{
CBaseEntity *breakables[ 1 ];
CBotBreakableEnumerator LOSbreakable( breakables, ARRAYSIZE( breakables ) );
// compute the unit vector along our view
Vector aimDir = GetViewVector();
// trace the potential bullet's path
trace_t result;
UTIL_TraceLine( EyePosition(), EyePosition() + FarBreakableCheckDist * aimDir, MASK_PLAYERSOLID, this, COLLISION_GROUP_NONE, &result );
if ( result.DidHitNonWorldEntity() )
{
LOSbreakable.EnumElement( result.m_pEnt );
if ( LOSbreakable.m_nAlreadyHit == 1 && LOSbreakable.m_AlreadyHit[ 0 ] == breakable )
{
shouldShoot = true;
}
}
}
// Shoot out breakable if the LOS is clear, or if we're playing a mode with no friendly fire
shouldShoot = shouldShoot && ( !TheCSBots()->AllowFriendlyFireDamage() || !IsFriendInLineOfFire() );
if ( shouldShoot )
{
PrimaryAttack();
}
}
}
}
//--------------------------------------------------------------------------------------------------------------
/**
* Check for doors that need +use to open.
*/
void CCSBot::DoorCheck( void )
{
SNPROF( "DoorCheck" );
if ( !TheCSBots()->AllowedToDoExpensiveBotOperationThisFrame() )
return;
if ( IsAttacking() && !IsUsingKnife() )
{
// If we're attacking with a gun or nade, don't bother with doors. If we're trying to
// knife someone, we might need to open a door.
m_isOpeningDoor = false;
return;
}
if ( !HasPath() )
return;
// Find any doors that need a +use to open just in front of us along the path.
CBaseEntity *doors[4];
CBotDoorEnumerator enumerator( doors, ARRAYSIZE( doors ) );
CBaseEntity *door = FindEntitiesOnPath( NearBreakableCheckDist, &enumerator, false );
if ( door )
{
if ( !IsLookingAtSpot( PRIORITY_UNINTERRUPTABLE ) )
{
if ( !IsOpeningDoor() )
{
CBasePropDoor *pPropDoor = dynamic_cast<CBasePropDoor*>( door );
if ( pPropDoor && pPropDoor->IsDoorLocked() )
return;
else if ( CBaseDoor *pFuncDoor = dynamic_cast< CBaseDoor * >( door ) )
{
if ( pFuncDoor->m_bLocked )
return;
}
OpenDoor( door );
}
}
}
}
//--------------------------------------------------------------------------------------------------------------
/**
* Reset the stuck-checker.
*/
void CCSBot::ResetStuckMonitor( void )
{
if (m_isStuck)
{
if (IsLocalPlayerWatchingMe() && cv_bot_debug.GetBool() && UTIL_GetListenServerHost())
{
CBasePlayer *localPlayer = UTIL_GetListenServerHost();
CSingleUserRecipientFilter filter( localPlayer );
EmitSound( filter, localPlayer->entindex(), "Bot.StuckSound" );
}
}
m_isStuck = false;
m_stuckTimestamp = 0.0f;
m_stuckJumpTimer.Invalidate();
m_avgVelIndex = 0;
m_avgVelCount = 0;
m_nextCleanupCheckTimestamp = 0.0f;
m_areaEnteredTimestamp = gpGlobals->curtime;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Test if we have become stuck
*/
void CCSBot::StuckCheck( void )
{
SNPROF("StuckCheck");
if (m_isStuck)
{
// we are stuck - see if we have moved far enough to be considered unstuck
Vector delta = GetAbsOrigin() - m_stuckSpot;
const float unstuckRange = 75.0f;
if (delta.IsLengthGreaterThan( unstuckRange ))
{
// we are no longer stuck
ResetStuckMonitor();
PrintIfWatched( "UN-STUCK\n" );
}
}
else
{
// check if we are stuck
// compute average velocity over a short period (for stuck check)
Vector vel = GetAbsOrigin() - m_lastOrigin;
// if we are jumping, ignore Z
if (IsJumping())
vel.z = 0.0f;
// cannot be Length2D, or will break ladder movement (they are only Z)
float moveDist = vel.Length();
float deltaT = g_BotUpdateInterval;
m_avgVel[ m_avgVelIndex++ ] = moveDist/deltaT;
if (m_avgVelIndex == MAX_VEL_SAMPLES)
m_avgVelIndex = 0;
if (m_avgVelCount < MAX_VEL_SAMPLES)
{
m_avgVelCount++;
}
else
{
// we have enough samples to know if we're stuck
float avgVel = 0.0f;
for( int t=0; t<m_avgVelCount; ++t )
avgVel += m_avgVel[t];
avgVel /= m_avgVelCount;
// cannot make this velocity too high, or bots will get "stuck" when going down ladders
//(IsUsingLadder()) ? 10.0f : 20.0f;
float stuckVel = ( IsRunning() ) ? 10.0f : 5.0f;
if (avgVel < stuckVel)
{
// we are stuck - note when and where we initially become stuck
m_stuckTimestamp = gpGlobals->curtime;
m_stuckSpot = GetAbsOrigin();
m_stuckJumpTimer.Start( RandomFloat( 0.3f, 0.75f ) ); // 1.0
PrintIfWatched( "STUCK\n" );
if (IsLocalPlayerWatchingMe() && cv_bot_debug.GetInt() > 0 && UTIL_GetListenServerHost() )
{
CBasePlayer *localPlayer = UTIL_GetListenServerHost();
CSingleUserRecipientFilter filter( localPlayer );
EmitSound( filter, localPlayer->entindex(), "Bot.StuckStart" );
}
m_isStuck = true;
}
}
}
// always need to track this
m_lastOrigin = GetAbsOrigin();
}
//--------------------------------------------------------------------------------------------------------------
/**
* Check if we need to jump due to height change
*/
bool CCSBot::DiscontinuityJump( float ground, bool onlyJumpDown, bool mustJump )
{
// Don't try to jump if in the air.
if( !(GetFlags() & FL_ONGROUND) )
{
return false;
}
float dz = ground - GetFeetZ();
if (dz > StepHeight && !onlyJumpDown)
{
// dont restrict jump time when going up
if (Jump( MUST_JUMP ))
{
return true;
}
}
else if (!IsUsingLadder() && dz < -JumpHeight)
{
if (Jump( mustJump ))
{
return true;
}
}
return false;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Find "simple" ground height, treating current nav area as part of the floor
*/
bool CCSBot::GetSimpleGroundHeightWithFloor( const Vector &pos, float *height, Vector *normal )
{
if (TheNavMesh->GetSimpleGroundHeight( pos, height, normal ))
{
// our current nav area also serves as a ground polygon
if (m_lastKnownArea && m_lastKnownArea->IsOverlapping( pos ))
*height = MAX( (*height), m_lastKnownArea->GetZ( pos ) );
return true;
}
return false;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Get our current radio chatter place
*/
Place CCSBot::GetPlace( void ) const
{
if (m_lastKnownArea)
return m_lastKnownArea->GetPlace();
return UNDEFINED_PLACE;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Move towards position, independant of view angle
*/
void CCSBot::MoveTowardsPosition( const Vector &pos )
{
Vector myOrigin = GetCentroid( this );
//
// Jump up on ledges
// Because we may not be able to get to our goal position and enter the next
// area because our extent collides with a nearby vertical ledge, make sure
// we look far enough ahead to avoid this situation.
// Can't look too far ahead, or bots will try to jump up slopes.
//
// NOTE: We need to do this frequently to catch edges at the right time
// @todo Look ahead *along path* instead of straight line
//
if ( (m_lastKnownArea == NULL || !( m_lastKnownArea->GetAttributes() & ( NAV_MESH_NO_JUMP | NAV_MESH_STAIRS ) ) ) &&
!IsOnLadder())
{
float ground;
Vector aheadRay( pos.x - myOrigin.x, pos.y - myOrigin.y, 0 );
aheadRay.NormalizeInPlace();
// look far ahead to allow us to smoothly jump over gaps, ledges, etc
// only jump if ground is flat at lookahead spot to avoid jumping up slopes
bool jumped = false;
if (IsRunning())
{
const float farLookAheadRange = 80.0f; // 60
Vector normal;
Vector stepAhead = myOrigin + farLookAheadRange * aheadRay;
stepAhead.z += HalfHumanHeight;
if (GetSimpleGroundHeightWithFloor( stepAhead, &ground, &normal ))
{
if (normal.z > 0.9f)
jumped = DiscontinuityJump( ground, ONLY_JUMP_DOWN );
}
}
if (!jumped)
{
// close up jumping
const float lookAheadRange = 30.0f; // cant be less or will miss jumps over low walls
Vector stepAhead = myOrigin + lookAheadRange * aheadRay;
stepAhead.z += HalfHumanHeight;
if (GetSimpleGroundHeightWithFloor( stepAhead, &ground ))
{
jumped = DiscontinuityJump( ground );
}
}
if (!jumped)
{
// about to fall gap-jumping
const float lookAheadRange = 10.0f;
Vector stepAhead = myOrigin + lookAheadRange * aheadRay;
stepAhead.z += HalfHumanHeight;
if (GetSimpleGroundHeightWithFloor( stepAhead, &ground ))
{
jumped = DiscontinuityJump( ground, ONLY_JUMP_DOWN, MUST_JUMP );
}
}
}
// compute our current forward and lateral vectors
float angle = EyeAngles().y;
Vector2D dir( BotCOS(angle), BotSIN(angle) );
Vector2D lat( -dir.y, dir.x );
// compute unit vector to goal position
Vector2D to( pos.x - myOrigin.x, pos.y - myOrigin.y );
to.NormalizeInPlace();
// move towards the position independant of our view direction
float toProj = to.x * dir.x + to.y * dir.y;
float latProj = to.x * lat.x + to.y * lat.y;
const float c = 0.25f; // 0.5
if (toProj > c)
MoveForward();
else if (toProj < -c)
MoveBackward();
// if we are avoiding someone via strafing, don't override
if (m_avoid != NULL)
return;
if (latProj >= c)
StrafeLeft();
else if (latProj <= -c)
StrafeRight();
}
//--------------------------------------------------------------------------------------------------------------
/**
* Move away from position, independant of view angle
*/
void CCSBot::MoveAwayFromPosition( const Vector &pos )
{
// compute our current forward and lateral vectors
float angle = EyeAngles().y;
Vector2D dir( BotCOS(angle), BotSIN(angle) );
Vector2D lat( -dir.y, dir.x );
// compute unit vector to goal position
Vector2D to( pos.x - GetAbsOrigin().x, pos.y - GetAbsOrigin().y );
to.NormalizeInPlace();
// move away from the position independant of our view direction
float toProj = to.x * dir.x + to.y * dir.y;
float latProj = to.x * lat.x + to.y * lat.y;
const float c = 0.5f;
if (toProj > c)
MoveBackward();
else if (toProj < -c)
MoveForward();
if (latProj >= c)
StrafeRight();
else if (latProj <= -c)
StrafeLeft();
}
//--------------------------------------------------------------------------------------------------------------
/**
* Strafe (sidestep) away from position, independant of view angle
*/
void CCSBot::StrafeAwayFromPosition( const Vector &pos )
{
// compute our current forward and lateral vectors
float angle = EyeAngles().y;
Vector2D dir( BotCOS(angle), BotSIN(angle) );
Vector2D lat( -dir.y, dir.x );
// compute unit vector to goal position
Vector2D to( pos.x - GetAbsOrigin().x, pos.y - GetAbsOrigin().y );
to.NormalizeInPlace();
float latProj = to.x * lat.x + to.y * lat.y;
if (latProj >= 0.0f)
StrafeRight();
else
StrafeLeft();
}
//--------------------------------------------------------------------------------------------------------------
/**
* For getting un-stuck
*/
void CCSBot::Wiggle( void )
{
if (IsCrouching())
{
return;
}
// for wiggling
if (m_wiggleTimer.IsElapsed())
{
m_wiggleDirection = (NavRelativeDirType)RandomInt( 0, 3 );
m_wiggleTimer.Start( RandomFloat( 0.3f, 0.5f ) ); // 0.3, 0.5
}
Vector forward, right;
EyeVectors( &forward, &right );
const float lookAheadRange = (m_lastKnownArea && (m_lastKnownArea->GetAttributes() & NAV_MESH_WALK)) ? 5.0f : 30.0f;
float ground;
switch( m_wiggleDirection )
{
case LEFT:
{
// don't move left if we will fall
Vector pos = GetAbsOrigin() - (lookAheadRange * right);
if (GetSimpleGroundHeightWithFloor( pos, &ground ))
{
if (GetAbsOrigin().z - ground < StepHeight)
{
StrafeLeft();
}
}
break;
}
case RIGHT:
{
// don't move right if we will fall
Vector pos = GetAbsOrigin() + (lookAheadRange * right);
if (GetSimpleGroundHeightWithFloor( pos, &ground ))
{
if (GetAbsOrigin().z - ground < StepHeight)
{
StrafeRight();
}
}
break;
}
case FORWARD:
{
// don't move forward if we will fall
Vector pos = GetAbsOrigin() + (lookAheadRange * forward);
if (GetSimpleGroundHeightWithFloor( pos, &ground ))
{
if (GetAbsOrigin().z - ground < StepHeight)
{
MoveForward();
}
}
break;
}
case BACKWARD:
{
// don't move backward if we will fall
Vector pos = GetAbsOrigin() - (lookAheadRange * forward);
if (GetSimpleGroundHeightWithFloor( pos, &ground ))
{
if (GetAbsOrigin().z - ground < StepHeight)
{
MoveBackward();
}
}
break;
}
}
if (m_stuckJumpTimer.IsElapsed() && m_lastKnownArea && !(m_lastKnownArea->GetAttributes() & NAV_MESH_NO_JUMP))
{
if (Jump())
{
m_stuckJumpTimer.Start( RandomFloat( 1.0f, 2.0f ) );
}
}
}
//--------------------------------------------------------------------------------------------------------------
/**
* Determine approach points from eye position and approach areas of current area
*/
void CCSBot::ComputeApproachPoints( void )
{
m_approachPointCount = 0;
if (m_lastKnownArea == NULL)
{
return;
}
// assume we're crouching for now
Vector eye = GetCentroid( this ); // + pev->view_ofs; // eye position
Vector ap;
float halfWidth;
for( int i=0; i<m_lastKnownArea->GetApproachInfoCount() && m_approachPointCount < MAX_APPROACH_POINTS; ++i )
{
const CCSNavArea::ApproachInfo *info = m_lastKnownArea->GetApproachInfo( i );
if (info->here.area == NULL || info->prev.area == NULL)
{
continue;
}
// compute approach point (approach area is "info->here")
if (info->prevToHereHow <= GO_WEST)
{
info->prev.area->ComputePortal( info->here.area, (NavDirType)info->prevToHereHow, &ap, &halfWidth );
ap.z = info->here.area->GetZ( ap );
}
else
{
// use the area's center as an approach point
ap = info->here.area->GetCenter();
}
// "bend" our line of sight around corners until we can see the approach point
Vector bendPoint;
if (BendLineOfSight( eye, ap + Vector( 0, 0, HalfHumanHeight ), &bendPoint ))
{
// put point on the ground
if (TheNavMesh->GetGroundHeight( bendPoint, &bendPoint.z ) == false)
{
bendPoint.z = ap.z;
}
m_approachPoint[ m_approachPointCount ].m_pos = bendPoint;
m_approachPoint[ m_approachPointCount ].m_area = info->here.area;
++m_approachPointCount;
}
}
}
//--------------------------------------------------------------------------------------------------------------
void CCSBot::DrawApproachPoints( void ) const
{
for( int i=0; i<m_approachPointCount; ++i )
{
if (TheCSBots()->GetElapsedRoundTime() >= m_approachPoint[i].m_area->GetEarliestOccupyTime( OtherTeam( GetTeamNumber() ) ))
NDebugOverlay::Cross3D( m_approachPoint[i].m_pos, 10.0f, 255, 0, 255, true, 0.1f );
else
NDebugOverlay::Cross3D( m_approachPoint[i].m_pos, 10.0f, 100, 100, 100, true, 0.1f );
}
}
//--------------------------------------------------------------------------------------------------------------
/**
* Find the approach point that is nearest to our current path, ahead of us
*/
bool CCSBot::FindApproachPointNearestPath( Vector *pos )
{
if (!HasPath())
return false;
// make sure approach points are accurate
ComputeApproachPoints();
if (m_approachPointCount == 0)
return false;
Vector target = Vector( 0, 0, 0 ), close;
float targetRangeSq = 0.0f;
bool found = false;
int start = m_pathIndex;
int end = m_pathLength;
//
// We dont want the strictly closest point, but the farthest approach point
// from us that is near our path
//
const float nearPathSq = 10000.0f; // (100)
for( int i=0; i<m_approachPointCount; ++i )
{
if (FindClosestPointOnPath( m_approachPoint[i].m_pos, start, end, &close ) == false)
continue;
float rangeSq = (m_approachPoint[i].m_pos - close).LengthSqr();
if (rangeSq > nearPathSq)
continue;
if (rangeSq > targetRangeSq)
{
target = close;
targetRangeSq = rangeSq;
found = true;
}
}
if (found)
{
*pos = target + Vector( 0, 0, HalfHumanHeight );
return true;
}
return false;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Return true if we are at the/an enemy spawn right now
*/
bool CCSBot::IsAtEnemySpawn( void ) const
{
CBaseEntity *spot;
const char *spawnName = (GetTeamNumber() == TEAM_TERRORIST) ? "info_player_counterterrorist" : "info_player_terrorist";
// check if we are at any of the enemy's spawn points
for( spot = gEntList.FindEntityByClassname( NULL, spawnName ); spot; spot = gEntList.FindEntityByClassname( spot, spawnName ) )
{
CNavArea *area = TheNavMesh->GetNearestNavArea( spot->WorldSpaceCenter() );
if (area && GetLastKnownArea() == area)
{
return true;
}
}
return false;
}
File diff suppressed because it is too large Load Diff
+380
View File
@@ -0,0 +1,380 @@
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
// Author: Michael S. Booth (mike@turtlerockstudios.com), 2003
#include "cbase.h"
#include "cs_bot.h"
#include "usermessages.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
extern int gmsgBotVoice;
//--------------------------------------------------------------------------------------------------------------
/**
* Returns true if the radio message is an order to do something
* NOTE: "Report in" is not considered a "command" because it doesnt ask the bot to go somewhere, or change its mind
*/
bool CCSBot::IsRadioCommand( RadioType event ) const
{
if (event == RADIO_AFFIRMATIVE ||
event == RADIO_NEGATIVE ||
event == RADIO_ENEMY_SPOTTED ||
event == RADIO_SECTOR_CLEAR ||
event == RADIO_REPORTING_IN ||
event == RADIO_REPORT_IN_TEAM ||
event == RADIO_ENEMY_DOWN ||
event == RADIO_INVALID )
return false;
return true;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Respond to radio commands from HUMAN players
*/
void CCSBot::RespondToRadioCommands( void )
{
// bots use the chatter system to respond to each other
if (m_radioSubject != NULL && m_radioSubject->IsPlayer())
{
CCSPlayer *player = m_radioSubject;
if (player->IsBot())
{
m_lastRadioCommand = RADIO_INVALID;
return;
}
}
if (m_lastRadioCommand == RADIO_INVALID)
return;
// a human player has issued a radio command
GetChatter()->ResetRadioSilenceDuration();
// if we are doing something important, ignore the radio
// unless it is a "report in" request - we can do that while we continue to do other things
/// @todo Create "uninterruptable" flag
if (m_lastRadioCommand != RADIO_REPORT_IN_TEAM)
{
if (IsBusy())
{
// consume command
m_lastRadioCommand = RADIO_INVALID;
return;
}
}
// wait for reaction time before responding
// delay needs to be long enough for the radio message we're responding to to finish
float respondTime = 1.0f + 2.0f * GetProfile()->GetReactionTime();
if (IsRogue())
respondTime += 2.0f;
if (gpGlobals->curtime - m_lastRadioRecievedTimestamp < respondTime)
return;
// rogues won't follow commands, unless already following the player
if (!IsFollowing() && IsRogue())
{
if (IsRadioCommand( m_lastRadioCommand ))
{
GetChatter()->Negative();
}
// consume command
m_lastRadioCommand = RADIO_INVALID;
return;
}
CCSPlayer *player = m_radioSubject;
if (player == NULL)
return;
// respond to command
bool canDo = false;
const float inhibitAutoFollowDuration = 60.0f;
switch( m_lastRadioCommand )
{
case RADIO_REPORT_IN_TEAM:
{
GetChatter()->ReportingIn();
break;
}
case RADIO_FOLLOW_ME:
case RADIO_COVER_ME:
case RADIO_STICK_TOGETHER_TEAM:
case RADIO_REGROUP_TEAM:
{
if (!IsFollowing())
{
Follow( player );
player->AllowAutoFollow();
canDo = true;
}
break;
}
case RADIO_ENEMY_SPOTTED:
case RADIO_NEED_BACKUP:
case RADIO_TAKING_FIRE:
if (!IsFollowing())
{
Follow( player );
GetChatter()->Say( "OnMyWay" );
player->AllowAutoFollow();
canDo = false;
}
break;
case RADIO_TEAM_FALL_BACK:
{
if (TryToRetreat())
canDo = true;
break;
}
case RADIO_HOLD_THIS_POSITION:
{
// find the leader's area
SetTask( HOLD_POSITION );
StopFollowing();
player->InhibitAutoFollow( inhibitAutoFollowDuration );
Hide( TheNavMesh->GetNearestNavArea( m_radioPosition ) );
canDo = true;
break;
}
case RADIO_GO_GO_GO:
case RADIO_STORM_THE_FRONT:
StopFollowing();
Hunt();
canDo = true;
player->InhibitAutoFollow( inhibitAutoFollowDuration );
break;
case RADIO_GET_OUT_OF_THERE:
if (TheCSBots()->IsBombPlanted())
{
EscapeFromBomb();
player->InhibitAutoFollow( inhibitAutoFollowDuration );
canDo = true;
}
break;
case RADIO_SECTOR_CLEAR:
{
// if this is a defusal scenario, and the bomb is planted,
// and a human player cleared a bombsite, check it off our list too
if (TheCSBots()->GetScenario() == CCSBotManager::SCENARIO_DEFUSE_BOMB)
{
if (GetTeamNumber() == TEAM_CT && TheCSBots()->IsBombPlanted())
{
const CCSBotManager::Zone *zone = TheCSBots()->GetClosestZone( player );
if (zone)
{
GetGameState()->ClearBombsite( zone->m_index );
// if we are huting for the planted bomb, re-select bombsite
if (GetTask() == FIND_TICKING_BOMB)
Idle();
canDo = true;
}
}
}
break;
}
default:
// ignore all other radio commands for now
return;
}
if (canDo)
{
// affirmative
GetChatter()->Affirmative();
// if we agreed to follow a new command, put away our grenade
if (IsRadioCommand( m_lastRadioCommand ) && IsUsingGrenade())
{
EquipBestWeapon();
}
}
// consume command
m_lastRadioCommand = RADIO_INVALID;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Decide if we should move to help the player, return true if we will
*/
bool CCSBot::RespondToHelpRequest( CCSPlayer *them, Place place, float maxRange )
{
if (IsRogue())
return false;
// if we're busy, ignore
if (IsBusy())
return false;
Vector themOrigin = GetCentroid( them );
// if we are too far away, ignore
if (maxRange > 0.0f)
{
// compute actual travel distance
PathCost cost(this);
float travelDistance = NavAreaTravelDistance( m_lastKnownArea, TheNavMesh->GetNearestNavArea( themOrigin ), cost );
if (travelDistance < 0.0f)
return false;
if (travelDistance > maxRange)
return false;
}
if (place == UNDEFINED_PLACE)
{
// if we have no "place" identifier, go directly to them
// if we are already there, ignore
float rangeSq = (them->GetAbsOrigin() - GetAbsOrigin()).LengthSqr();
const float close = 750.0f * 750.0f;
if (rangeSq < close)
return true;
MoveTo( themOrigin, FASTEST_ROUTE );
}
else
{
// if we are already there, ignore
if (GetPlace() == place)
return true;
// go to where help is needed
Vector pos;
if ( GetRandomSpotAtPlace( place, &pos ) )
{
MoveTo( pos, FASTEST_ROUTE );
}
else
{
MoveTo( themOrigin, FASTEST_ROUTE );
}
}
// acknowledge
GetChatter()->Say( "OnMyWay" );
return true;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Send a radio message
*/
void CCSBot::SendRadioMessage( RadioType event )
{
// make sure this is a radio event
if (event <= RADIO_START_1 || event >= RADIO_END)
return;
PrintIfWatched( "%3.1f: SendRadioMessage( %s )\n", gpGlobals->curtime, RadioEventName[ event ] );
// note the time the message was sent
TheCSBots()->SetRadioMessageTimestamp( event, GetTeamNumber() );
m_lastRadioSentTimestamp = gpGlobals->curtime;
char slot[2];
slot[1] = '\000';
if (event > RADIO_START_1 && event < RADIO_START_2)
{
HandleMenu_Radio1( event );
}
else if (event > RADIO_START_2 && event < RADIO_START_3)
{
HandleMenu_Radio2( event );
}
else
{
HandleMenu_Radio3( event );
}
}
//--------------------------------------------------------------------------------------------------------------
/**
* Send voice chatter. Also sends the entindex and duration for voice feedback.
*/
void CCSBot::SpeakAudio( const char *voiceFilename, float duration, int pitch )
{
if( !IsAlive() )
return;
if ( IsObserver() )
return;
CRecipientFilter filter;
ConstructRadioFilter( filter );
CCSUsrMsg_RawAudio msg;
msg.set_pitch( pitch );
msg.set_entidx( entindex() );
msg.set_duration( duration );
msg.set_voice_filename( voiceFilename );
SendUserMessage ( filter, CS_UM_RawAudio, msg );
GetChatter()->ResetRadioSilenceDuration();
m_voiceEndTimestamp = gpGlobals->curtime + duration;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Send voice chatter through the response rules system.
*/
bool CCSBot::SpeakAudioResponseRules( const char *pConcept, AI_CriteriaSet *criteria, float duration )
{
if( !IsAlive() )
return false;
if ( IsObserver() )
return false;
CRecipientFilter filter;
ConstructRadioFilter( filter );
AI_CriteriaSet local;
if ( !criteria )
criteria = &local;
AIConcept_t concept( pConcept );
if ( Speak( concept, criteria, NULL, 0, &filter ) )
{
GetChatter()->ResetRadioSilenceDuration();
m_voiceEndTimestamp = gpGlobals->curtime + duration;
return true;
}
return false;
}
@@ -0,0 +1,732 @@
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
// Author: Michael S. Booth (mike@turtlerockstudios.com), 2003
#include "cbase.h"
#include "cs_bot.h"
#include "cs_nav_path.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
//--------------------------------------------------------------------------------------------------------------
/**
* This method is the ONLY legal way to change a bot's current state
*/
void CCSBot::SetState( BotState *state )
{
PrintIfWatched( "%s: SetState: %s -> %s\n", GetPlayerName(), (m_state) ? m_state->GetName() : "NULL", state->GetName() );
/*
if ( IsDefusingBomb() )
{
const Vector *bombPos = GetGameState()->GetBombPosition();
if ( bombPos != NULL )
{
if ( TheCSBots()->GetBombDefuser() == this )
{
if ( TheCSBots()->IsBombPlanted() )
{
Msg( "Bot %s is switching from defusing the bomb to %s\n",
GetPlayerName(), state->GetName() );
}
}
}
}
*/
// if we changed state from within the special Attack state, we are no longer attacking
if (m_isAttacking)
StopAttacking();
if (m_state)
m_state->OnExit( this );
state->OnEnter( this );
m_state = state;
m_stateTimestamp = gpGlobals->curtime;
}
//--------------------------------------------------------------------------------------------------------------
void CCSBot::Idle( void )
{
SetTask( SEEK_AND_DESTROY );
SetState( &m_idleState );
}
//--------------------------------------------------------------------------------------------------------------
void CCSBot::EscapeFromBomb( void )
{
SetTask( ESCAPE_FROM_BOMB );
SetState( &m_escapeFromBombState );
}
//--------------------------------------------------------------------------------------------------------------
void CCSBot::EscapeFromFlames( void )
{
SetTask( ESCAPE_FROM_FLAMES );
SetState( &m_escapeFromFlamesState );
}
//--------------------------------------------------------------------------------------------------------------
void CCSBot::Follow( CCSPlayer *player )
{
if (player == NULL)
return;
// note when we began following
if (!m_isFollowing || m_leader != player)
m_followTimestamp = gpGlobals->curtime;
m_isFollowing = true;
m_leader = player;
SetTask( FOLLOW );
m_followState.SetLeader( player );
SetState( &m_followState );
}
//--------------------------------------------------------------------------------------------------------------
/**
* Continue following our leader after finishing what we were doing
*/
void CCSBot::ContinueFollowing( void )
{
SetTask( FOLLOW );
m_followState.SetLeader( m_leader );
SetState( &m_followState );
}
//--------------------------------------------------------------------------------------------------------------
/**
* Stop following
*/
void CCSBot::StopFollowing( void )
{
m_isFollowing = false;
m_leader = NULL;
m_allowAutoFollowTime = gpGlobals->curtime + 10.0f;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Begin process of rescuing hostages
*/
void CCSBot::RescueHostages( void )
{
SetTask( RESCUE_HOSTAGES );
}
//--------------------------------------------------------------------------------------------------------------
/**
* Use the entity
*/
void CCSBot::UseEntity( CBaseEntity *entity )
{
m_useEntityState.SetEntity( entity );
SetState( &m_useEntityState );
}
//--------------------------------------------------------------------------------------------------------------
/**
* Open the door.
* This assumes the bot is directly in front of the door with no obstructions.
* NOTE: This state is special, like Attack, in that it suspends the current behavior and returns to it when done.
*/
void CCSBot::OpenDoor( CBaseEntity *door )
{
m_openDoorState.SetDoor( door );
m_isOpeningDoor = true;
m_openDoorState.OnEnter( this );
}
//--------------------------------------------------------------------------------------------------------------
/**
* DEPRECATED: Use TryToHide() instead.
* Move to a hiding place.
* If 'searchFromArea' is non-NULL, hiding spots are looked for from that area first.
*/
void CCSBot::Hide( CNavArea *searchFromArea, float duration, float hideRange, bool holdPosition )
{
if ( !TheCSBots()->AllowedToDoExpensiveBotOperationThisFrame() )
return;
DestroyPath();
CNavArea *source;
Vector sourcePos;
if (searchFromArea)
{
source = searchFromArea;
sourcePos = searchFromArea->GetCenter();
}
else
{
source = m_lastKnownArea;
sourcePos = GetCentroid( this );
}
if (source == NULL)
{
PrintIfWatched( "Hide from area is NULL.\n" );
Idle();
return;
}
m_hideState.SetSearchArea( source );
m_hideState.SetSearchRange( hideRange );
m_hideState.SetDuration( duration );
m_hideState.SetHoldPosition( holdPosition );
// search around source area for a good hiding spot
Vector useSpot;
const Vector *pos = FindNearbyHidingSpot( this, sourcePos, hideRange, IsSniper() );
if (pos == NULL)
{
PrintIfWatched( "No available hiding spots.\n" );
// hide at our current position
useSpot = GetCentroid( this );
}
else
{
useSpot = *pos;
}
m_hideState.SetHidingSpot( useSpot );
// build a path to our new hiding spot
if (ComputePath( useSpot, FASTEST_ROUTE ) == false)
{
PrintIfWatched( "Can't pathfind to hiding spot\n" );
Idle();
return;
}
SetState( &m_hideState );
}
//--------------------------------------------------------------------------------------------------------------
/**
* Move to the given hiding place
*/
void CCSBot::Hide( const Vector &hidingSpot, float duration, bool holdPosition )
{
CNavArea *hideArea = TheNavMesh->GetNearestNavArea( hidingSpot );
if (hideArea == NULL)
{
PrintIfWatched( "Hiding spot off nav mesh\n" );
Idle();
return;
}
DestroyPath();
m_hideState.SetSearchArea( hideArea );
m_hideState.SetSearchRange( 750.0f );
m_hideState.SetDuration( duration );
m_hideState.SetHoldPosition( holdPosition );
m_hideState.SetHidingSpot( hidingSpot );
// build a path to our new hiding spot
if (ComputePath( hidingSpot, FASTEST_ROUTE ) == false)
{
PrintIfWatched( "Can't pathfind to hiding spot\n" );
Idle();
return;
}
SetState( &m_hideState );
}
//--------------------------------------------------------------------------------------------------------------
/**
* Try to hide nearby. Return true if hiding, false if can't hide here.
* If 'searchFromArea' is non-NULL, hiding spots are looked for from that area first.
*/
bool CCSBot::TryToHide( CNavArea *searchFromArea, float duration, float hideRange, bool holdPosition, bool useNearest, const Vector *pStartPosOverride /*= NULL*/ )
{
CNavArea *source;
Vector sourcePos;
if (searchFromArea)
{
source = searchFromArea;
sourcePos = searchFromArea->GetCenter();
}
else
{
source = m_lastKnownArea;
sourcePos = GetCentroid( this );
}
// Optionally force the starting position instead of using the center of an area...
if ( pStartPosOverride )
sourcePos = *pStartPosOverride;
if (source == NULL)
{
PrintIfWatched( "Hide from area is NULL.\n" );
return false;
}
m_hideState.SetSearchArea( source );
m_hideState.SetSearchRange( hideRange );
m_hideState.SetDuration( duration );
m_hideState.SetHoldPosition( holdPosition );
// search around source area for a good hiding spot
const Vector *pos = FindNearbyHidingSpot( this, sourcePos, hideRange, IsSniper(), useNearest );
if (pos == NULL)
{
PrintIfWatched( "No available hiding spots.\n" );
return false;
}
m_hideState.SetHidingSpot( *pos );
// build a path to our new hiding spot
if (ComputePath( *pos, FASTEST_ROUTE ) == false)
{
PrintIfWatched( "Can't pathfind to hiding spot\n" );
return false;
}
SetState( &m_hideState );
return true;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Retreat to a nearby hiding spot, away from enemies
*/
bool CCSBot::TryToRetreat( float maxRange, float duration )
{
// We don't want heavies to retreat
if ( CSGameRules() && CSGameRules()->IsPlayingCooperativeGametype() && HasHeavyArmor() )
return false;
const Vector *spot = FindNearbyRetreatSpot( this, maxRange );
if (spot)
{
// ignore enemies for a second to give us time to hide
// reaching our hiding spot clears our disposition
IgnoreEnemies( 10.0f );
if (duration < 0.0f)
{
duration = RandomFloat( 3.0f, 15.0f );
}
StandUp();
Run();
Hide( *spot, duration );
PrintIfWatched( "Retreating to a safe spot!\n" );
return true;
}
return false;
}
//--------------------------------------------------------------------------------------------------------------
void CCSBot::Hunt( void )
{
SetState( &m_huntState );
}
//--------------------------------------------------------------------------------------------------------------
/**
* Attack our the given victim
* NOTE: Attacking does not change our task.
*/
void CCSBot::Attack( CCSPlayer *victim )
{
if (victim == NULL)
return;
// zombies never attack
if (cv_bot_zombie.GetBool())
return;
// cannot attack if we are reloading
if (IsReloading())
return;
// change enemy
SetBotEnemy( victim );
//
// Do not "re-enter" the attack state if we are already attacking
//
if (IsAttacking())
return;
if (IsUsingGrenade())
{
// throw towards their feet
ThrowGrenade( victim->GetAbsOrigin() );
return;
}
// if we are currently hiding, increase our chances of crouching and holding position
if (IsAtHidingSpot())
m_attackState.SetCrouchAndHold( (RandomFloat( 0.0f, 100.0f ) < 60.0f) ? true : false );
else
m_attackState.SetCrouchAndHold( false );
//SetState( &m_attackState );
//PrintIfWatched( "ATTACK BEGIN (reaction time = %g (+ update time), surprise time = %g, attack delay = %g)\n",
// GetProfile()->GetReactionTime(), m_surpriseDelay, GetProfile()->GetAttackDelay() );
m_isAttacking = true;
m_attackState.OnEnter( this );
Vector victimOrigin = GetCentroid( victim );
// cheat a bit and give the bot the initial location of its victim
m_lastEnemyPosition = victimOrigin;
m_lastSawEnemyTimestamp = gpGlobals->curtime;
m_aimFocus = GetProfile()->GetAimFocusInitial();
PickNewAimSpot();
// forget any look at targets we have
ClearLookAt();
}
//--------------------------------------------------------------------------------------------------------------
/**
* Exit the Attack state
*/
void CCSBot::StopAttacking( void )
{
PrintIfWatched( "ATTACK END\n" );
m_attackState.OnExit( this );
m_isAttacking = false;
// if we are following someone, go to the Idle state after the attack to decide whether we still want to follow
if (IsFollowing())
{
Idle();
}
}
//--------------------------------------------------------------------------------------------------------------
bool CCSBot::IsAttacking( void ) const
{
return m_isAttacking;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Return true if we are escaping from the bomb
*/
bool CCSBot::IsEscapingFromBomb( void ) const
{
if (m_state == static_cast<const BotState *>( &m_escapeFromBombState ))
return true;
return false;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Return true if we are escaping from a field of flames
*/
bool CCSBot::IsEscapingFromFlames( void ) const
{
if ( m_state == static_cast< const BotState * >( &m_escapeFromFlamesState ) )
return true;
return false;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Return true if we are defusing the bomb
*/
bool CCSBot::IsDefusingBomb( void ) const
{
if (m_state == static_cast<const BotState *>( &m_defuseBombState ))
return true;
return false;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Return true if we are defusing the bomb
*/
bool CCSBot::IsPickingupHostage( void ) const
{
if (m_state == static_cast<const BotState *>( &m_pickupHostageState ))
return true;
return false;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Return true if we are hiding
*/
bool CCSBot::IsHiding( void ) const
{
if (m_state == static_cast<const BotState *>( &m_hideState ))
return true;
return false;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Return true if we are hiding and at our hiding spot
*/
bool CCSBot::IsAtHidingSpot( void ) const
{
if (!IsHiding())
return false;
return m_hideState.IsAtSpot();
}
//--------------------------------------------------------------------------------------------------------------
/**
* Return number of seconds we have been at our current hiding spot
*/
float CCSBot::GetHidingTime( void ) const
{
if (IsHiding())
{
return m_hideState.GetHideTime();
}
return 0.0f;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Return true if we are huting
*/
bool CCSBot::IsHunting( void ) const
{
if (m_state == static_cast<const BotState *>( &m_huntState ))
return true;
return false;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Return true if we are in the MoveTo state
*/
bool CCSBot::IsMovingTo( void ) const
{
if (m_state == static_cast<const BotState *>( &m_moveToState ))
return true;
return false;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Return true if we are buying
*/
bool CCSBot::IsBuying( void ) const
{
if (m_state == static_cast<const BotState *>( &m_buyState ))
return true;
return false;
}
//--------------------------------------------------------------------------------------------------------------
bool CCSBot::IsInvestigatingNoise( void ) const
{
if (m_state == static_cast<const BotState *>( &m_investigateNoiseState ))
return true;
return false;
}
//--------------------------------------------------------------------------------------------------------------
bool CCSBot::IsIdling( void ) const
{
if ( m_state == static_cast< const BotState * >( &m_idleState ) )
return true;
return false;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Move to potentially distant position
*/
void CCSBot::MoveTo( const Vector &pos, RouteType route )
{
m_moveToState.SetGoalPosition( pos );
m_moveToState.SetRouteType( route );
SetState( &m_moveToState );
}
//--------------------------------------------------------------------------------------------------------------
void CCSBot::PlantBomb( void )
{
SetState( &m_plantBombState );
}
//--------------------------------------------------------------------------------------------------------------
/**
* Bomb has been dropped - go get it
*/
void CCSBot::FetchBomb( void )
{
SetState( &m_fetchBombState );
}
//--------------------------------------------------------------------------------------------------------------
void CCSBot::DefuseBomb( void )
{
SetState( &m_defuseBombState );
}
void CCSBot::PickupHostage( CBaseEntity *entity )
{
m_pickupHostageState.SetEntity( entity );
SetState( &m_pickupHostageState );
}
//--------------------------------------------------------------------------------------------------------------
/**
* Investigate recent enemy noise
*/
void CCSBot::InvestigateNoise( void )
{
SetState( &m_investigateNoiseState );
}
//--------------------------------------------------------------------------------------------------------------
void CCSBot::Buy( void )
{
SetState( &m_buyState );
}
//--------------------------------------------------------------------------------------------------------------
/**
* Move to a hiding spot and wait for initial encounter with enemy team.
* Return false if can't do this behavior (ie: no hiding spots available).
*/
bool CCSBot::MoveToInitialEncounter( void )
{
int myTeam = GetTeamNumber();
int enemyTeam = OtherTeam( myTeam );
// build a path to an enemy spawn point
CBaseEntity *enemySpawn = TheCSBots()->GetRandomSpawn( enemyTeam );
if (enemySpawn == NULL)
{
PrintIfWatched( "MoveToInitialEncounter: No enemy spawn points?\n" );
return false;
}
if ( !TheCSBots()->AllowedToDoExpensiveBotOperationThisFrame() )
return false;
TheCSBots()->OnExpensiveBotOperation();
// build a path from us to the enemy spawn
CCSNavPath path;
PathCost cost( this, FASTEST_ROUTE );
path.Compute( WorldSpaceCenter(), enemySpawn->GetAbsOrigin(), cost );
if (!path.IsValid())
{
PrintIfWatched( "MoveToInitialEncounter: Pathfind failed.\n" );
return false;
}
// find battlefront area where teams will first meet along this path
int i;
for( i=0; i<path.GetSegmentCount(); ++i )
{
if (path[i]->area->GetEarliestOccupyTime( myTeam ) > path[i]->area->GetEarliestOccupyTime( enemyTeam ))
{
break;
}
}
if (i == path.GetSegmentCount())
{
PrintIfWatched( "MoveToInitialEncounter: Can't find battlefront!\n" );
return false;
}
/// @todo Remove this evil side-effect
SetInitialEncounterArea( path[i]->area );
// find a hiding spot on our side of the battlefront that has LOS to it
const float maxRange = 1500.0f;
const HidingSpot *spot = FindInitialEncounterSpot( this, path[i]->area->GetCenter(), path[i]->area->GetEarliestOccupyTime( enemyTeam ), maxRange, IsSniper() );
if (spot == NULL)
{
PrintIfWatched( "MoveToInitialEncounter: Can't find a hiding spot\n" );
return false;
}
float timeToWait = path[i]->area->GetEarliestOccupyTime( enemyTeam ) - spot->GetArea()->GetEarliestOccupyTime( myTeam );
float minWaitTime = 4.0f * GetProfile()->GetAggression() + 3.0f;
if (timeToWait < minWaitTime)
{
timeToWait = minWaitTime;
}
Hide( spot->GetPosition(), timeToWait );
return true;
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,22 @@
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
// Author: Michael Booth (mike@turtlerockstudios.com), 2003
// Author: Matthew D. Campbell (matt@turtlerockstudios.com), 2003
#include "cbase.h"
#include "cs_bot.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
//--------------------------------------------------------------------------------------------------------
//
// Temporary solution until we have time to build something more elegant
// Very nasty - need to keep in sync with the buy aliases
// NOTE: Array must be NULL terminated
//
+777
View File
@@ -0,0 +1,777 @@
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose: Encapsulation of the current scenario/game state. Allows each bot imperfect knowledge.
//
// $NoKeywords: $
//=============================================================================//
// Author: Michael S. Booth (mike@turtlerockstudios.com), 2003
#include "cbase.h"
#include "keyvalues.h"
#include "cs_bot.h"
#include "cs_gamestate.h"
#include "cs_simple_hostage.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
//--------------------------------------------------------------------------------------------------------------
CSGameState::CSGameState( CCSBot *owner )
{
m_owner = owner;
m_isRoundOver = false;
m_bombState = MOVING;
m_lastSawBomber.Invalidate();
m_lastSawLooseBomb.Invalidate();
m_isPlantedBombPosKnown = false;
m_plantedBombsite = UNKNOWN;
m_bombsiteCount = 0;
m_bombsiteSearchIndex = 0;
for( int i=0; i<MAX_HOSTAGES; ++i )
{
m_hostage[i].hostage = NULL;
m_hostage[i].isValid = false;
m_hostage[i].isAlive = false;
m_hostage[i].isFree = true;
m_hostage[i].knownPos = Vector( 0, 0, 0 );
}
}
//--------------------------------------------------------------------------------------------------------------
/**
* Reset at round start
*/
void CSGameState::Reset( void )
{
m_isRoundOver = false;
// bomb -----------------------------------------------------------------------
if ( !CSGameRules()->IsPlayingCoopGuardian() )
{
m_lastSawLooseBomb.Invalidate();
m_bombState = MOVING;
}
m_lastSawBomber.Invalidate();
m_isPlantedBombPosKnown = false;
m_plantedBombsite = UNKNOWN;
m_bombsiteCount = TheCSBots()->GetZoneCount();
int i;
for( i=0; i<m_bombsiteCount; ++i )
{
m_isBombsiteClear[i] = false;
m_bombsiteSearchOrder[i] = i;
}
// shuffle the bombsite search order
// allows T's to plant at random site, and TEAM_CT's to search in a random order
// NOTE: VS6 std::random_shuffle() doesn't work well with an array of two elements (most maps)
for( i=0; i < m_bombsiteCount; ++i )
{
int swap = m_bombsiteSearchOrder[i];
int rnd = RandomInt( i, m_bombsiteCount-1 );
m_bombsiteSearchOrder[i] = m_bombsiteSearchOrder[ rnd ];
m_bombsiteSearchOrder[ rnd ] = swap;
}
m_bombsiteSearchIndex = 0;
// hostage ---------------------------------------------------------------------
InitializeHostageInfo();
}
//--------------------------------------------------------------------------------------------------------------
/**
* Update game state based on events we have received
*/
void CSGameState::OnHostageRescuedAll( IGameEvent *event )
{
m_allHostagesRescued = true;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Update game state based on events we have received
*/
void CSGameState::OnRoundEnd( IGameEvent *event )
{
m_isRoundOver = true;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Update game state based on events we have received
*/
void CSGameState::OnRoundStart( IGameEvent *event )
{
Reset();
}
//--------------------------------------------------------------------------------------------------------------
/**
* Update game state based on events we have received
*/
void CSGameState::OnBombPlanted( IGameEvent *event )
{
// change state - the event is announced to everyone
SetBombState( PLANTED );
CBasePlayer *plantingPlayer = UTIL_PlayerByUserId( event->GetInt( "userid" ) );
// Terrorists always know where the bomb is
if (m_owner->GetTeamNumber() == TEAM_TERRORIST && plantingPlayer)
{
UpdatePlantedBomb( plantingPlayer->GetAbsOrigin() );
}
}
//--------------------------------------------------------------------------------------------------------------
/**
* Update game state based on events we have received
*/
void CSGameState::OnBombDefused( IGameEvent *event )
{
// change state - the event is announced to everyone
SetBombState( DEFUSED );
}
//--------------------------------------------------------------------------------------------------------------
/**
* Update game state based on events we have received
*/
void CSGameState::OnBombExploded( IGameEvent *event )
{
// change state - the event is announced to everyone
SetBombState( EXPLODED );
}
//--------------------------------------------------------------------------------------------------------------
/**
* True if round has been won or lost (but not yet reset)
*/
bool CSGameState::IsRoundOver( void ) const
{
return m_isRoundOver;
}
//--------------------------------------------------------------------------------------------------------------
void CSGameState::SetBombState( BombState state )
{
// if state changed, reset "last seen" timestamps
if (m_bombState != state)
{
m_bombState = state;
}
}
//--------------------------------------------------------------------------------------------------------------
void CSGameState::UpdateLooseBomb( const Vector &pos )
{
m_looseBombPos = pos;
m_lastSawLooseBomb.Reset();
// we saw the loose bomb, update our state
SetBombState( LOOSE );
}
//--------------------------------------------------------------------------------------------------------------
float CSGameState::TimeSinceLastSawLooseBomb( void ) const
{
return m_lastSawLooseBomb.GetElapsedTime();
}
//--------------------------------------------------------------------------------------------------------------
bool CSGameState::IsLooseBombLocationKnown( void ) const
{
if ( CSGameRules()->IsPlayingCoopGuardian() )
return true;
if (m_bombState != LOOSE)
return false;
return (m_lastSawLooseBomb.HasStarted()) ? true : false;
}
//--------------------------------------------------------------------------------------------------------------
void CSGameState::UpdateBomber( const Vector &pos )
{
m_bomberPos = pos;
m_lastSawBomber.Reset();
// we saw the bomber, update our state
SetBombState( MOVING );
}
//--------------------------------------------------------------------------------------------------------------
float CSGameState::TimeSinceLastSawBomber( void ) const
{
return m_lastSawBomber.GetElapsedTime();
}
//--------------------------------------------------------------------------------------------------------------
bool CSGameState::IsPlantedBombLocationKnown( void ) const
{
if (m_bombState != PLANTED)
return false;
return m_isPlantedBombPosKnown;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Return the zone index of the planted bombsite, or UNKNOWN
*/
int CSGameState::GetPlantedBombsite( void ) const
{
if (m_bombState != PLANTED)
return UNKNOWN;
return m_plantedBombsite;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Return true if we are currently in the bombsite where the bomb is planted
*/
bool CSGameState::IsAtPlantedBombsite( void ) const
{
if (m_bombState != PLANTED)
return false;
Vector myOrigin = GetCentroid( m_owner );
const CCSBotManager::Zone *zone = TheCSBots()->GetClosestZone( myOrigin );
if (zone)
{
return (m_plantedBombsite == zone->m_index);
}
return false;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Return the zone index of the next bombsite to search
*/
int CSGameState::GetNextBombsiteToSearch( void )
{
if (m_bombsiteCount <= 0)
return 0;
int i;
// return next non-cleared bombsite index
for( i=m_bombsiteSearchIndex; i<m_bombsiteCount; ++i )
{
int z = m_bombsiteSearchOrder[i];
if (!m_isBombsiteClear[z])
{
m_bombsiteSearchIndex = i;
return z;
}
}
// all the bombsites are clear, someone must have been mistaken - start search over
for( i=0; i<m_bombsiteCount; ++i )
m_isBombsiteClear[i] = false;
m_bombsiteSearchIndex = 0;
return GetNextBombsiteToSearch();
}
//--------------------------------------------------------------------------------------------------------------
/**
* Returns position of bomb in its various states (moving, loose, planted),
* or NULL if we don't know where the bomb is
*/
const Vector *CSGameState::GetBombPosition( void ) const
{
switch( m_bombState )
{
case MOVING:
{
if (!m_lastSawBomber.HasStarted())
return NULL;
return &m_bomberPos;
}
case LOOSE:
{
if (IsLooseBombLocationKnown())
return &m_looseBombPos;
return NULL;
}
case PLANTED:
{
if (IsPlantedBombLocationKnown())
return &m_plantedBombPos;
return NULL;
}
}
return NULL;
}
//--------------------------------------------------------------------------------------------------------------
/**
* We see the planted bomb at 'pos'
*/
void CSGameState::UpdatePlantedBomb( const Vector &pos )
{
const CCSBotManager::Zone *zone = TheCSBots()->GetClosestZone( pos );
if (zone == NULL)
{
CONSOLE_ECHO( "ERROR: Bomb planted outside of a zone!\n" );
m_plantedBombsite = UNKNOWN;
}
else
{
m_plantedBombsite = zone->m_index;
}
m_plantedBombPos = pos;
// add an epsilon to handle bomb origin slightly embedded in a model/world
m_plantedBombPos.z += 1.0f;
m_isPlantedBombPosKnown = true;
SetBombState( PLANTED );
}
//--------------------------------------------------------------------------------------------------------------
/**
* Someone told us where the bomb is planted
*/
void CSGameState::MarkBombsiteAsPlanted( int zoneIndex )
{
m_plantedBombsite = zoneIndex;
SetBombState( PLANTED );
}
//--------------------------------------------------------------------------------------------------------------
/**
* Someone told us a bombsite is clear
*/
void CSGameState::ClearBombsite( int zoneIndex )
{
if (zoneIndex >= 0 && zoneIndex < m_bombsiteCount)
m_isBombsiteClear[ zoneIndex ] = true;
}
//--------------------------------------------------------------------------------------------------------------
bool CSGameState::IsBombsiteClear( int zoneIndex ) const
{
if (zoneIndex >= 0 && zoneIndex < m_bombsiteCount)
return m_isBombsiteClear[ zoneIndex ];
return false;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Initialize our knowledge of the number and location of hostages
*/
void CSGameState::InitializeHostageInfo( void )
{
m_hostageCount = 0;
m_allHostagesRescued = false;
m_haveSomeHostagesBeenTaken = false;
for( int i=0; i<g_Hostages.Count(); ++i )
{
m_hostage[ m_hostageCount ].hostage = g_Hostages[i];
m_hostage[ m_hostageCount ].knownPos = g_Hostages[i]->GetAbsOrigin();
m_hostage[ m_hostageCount ].isValid = true;
m_hostage[ m_hostageCount ].isAlive = true;
m_hostage[ m_hostageCount ].isFree = true;
++m_hostageCount;
}
}
//--------------------------------------------------------------------------------------------------------------
/**
* Return the closest free and live hostage
* If we are a CT this information is perfect.
* Otherwise, this is based on our individual memory of the game state.
* If NULL is returned, we don't think there are any hostages left, or we dont know where they are.
* NOTE: a T can remember a hostage who has died. knowPos will be filled in, but NULL will be
* returned, since CHostages get deleted when they die.
*/
CHostage *CSGameState::GetNearestFreeHostage( Vector *knowPos ) const
{
if (m_owner == NULL)
return NULL;
CNavArea *startArea = m_owner->GetLastKnownArea();
if (startArea == NULL)
return NULL;
CHostage *close = NULL;
Vector closePos( 0, 0, 0 );
float closeDistance = 9999999999.9f;
for( int i=0; i<m_hostageCount; ++i )
{
CHostage *hostage = m_hostage[i].hostage;
Vector hostagePos;
if (m_owner->GetTeamNumber() == TEAM_CT)
{
// we know exactly where the hostages are, and if they are alive
if (!m_hostage[i].hostage || !m_hostage[i].hostage->IsValid())
continue;
if (m_hostage[i].hostage->IsFollowingSomeone())
continue;
hostagePos = m_hostage[i].hostage->GetAbsOrigin();
}
else
{
// use our memory of where we think the hostages are
if (m_hostage[i].isValid == false)
continue;
hostagePos = m_hostage[i].knownPos;
}
CNavArea *hostageArea = TheNavMesh->GetNearestNavArea( hostagePos );
if (hostageArea)
{
ShortestPathCost cost;
float travelDistance = NavAreaTravelDistance( startArea, hostageArea, cost );
if (travelDistance >= 0.0f && travelDistance < closeDistance)
{
closeDistance = travelDistance;
closePos = hostagePos;
close = hostage;
}
}
}
// return where we think the hostage is
if (knowPos && close)
*knowPos = closePos;
return close;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Return the location of a "free" hostage, or NULL if we dont know of any
*/
const Vector *CSGameState::GetRandomFreeHostagePosition( void ) const
{
if (m_owner == NULL)
return NULL;
static Vector freePos[ MAX_HOSTAGES ];
int freeCount = 0;
for( int i=0; i<m_hostageCount; ++i )
{
const HostageInfo *info = &m_hostage[i];
if (m_owner->GetTeamNumber() == TEAM_CT)
{
// we know exactly where the hostages are, and if they are alive
if (!info->hostage || !info->hostage->IsAlive())
continue;
// escorted hostages are not "free"
if (info->hostage->IsFollowingSomeone())
continue;
freePos[ freeCount++ ] = info->hostage->GetAbsOrigin();
}
else
{
// use our memory of where we think the hostages are
if (info->isValid == false)
continue;
freePos[ freeCount++ ] = info->knownPos;
}
}
if (freeCount)
{
return &freePos[ RandomInt( 0, freeCount-1 ) ];
}
return NULL;
}
//--------------------------------------------------------------------------------------------------------------
/**
* If we can see any of the positions where we think a hostage is, validate it
* Return status of any changes (a hostage died or was moved)
*/
unsigned char CSGameState::ValidateHostagePositions( void )
{
// limit how often we validate
if (!m_validateInterval.IsElapsed())
return NO_CHANGE;
const float validateInterval = 0.5f;
m_validateInterval.Start( validateInterval );
// check the status of hostages
unsigned char status = NO_CHANGE;
int i;
int startValidCount = 0;
for( i=0; i<m_hostageCount; ++i )
if (m_hostage[i].isValid)
++startValidCount;
for( i=0; i<m_hostageCount; ++i )
{
HostageInfo *info = &m_hostage[i];
if (!info->hostage )
continue;
// if we can see a hostage, update our knowledge of it
Vector pos = info->hostage->GetAbsOrigin() + Vector( 0, 0, HalfHumanHeight );
if (m_owner->IsVisible( pos, CHECK_FOV ))
{
if (info->hostage->IsAlive())
{
// live hostage
// if hostage is being escorted by a CT, we don't "see" it, we see the CT
if (info->hostage->IsFollowingSomeone())
{
info->isValid = false;
}
else
{
info->knownPos = info->hostage->GetAbsOrigin();
info->isValid = true;
}
}
else
{
// dead hostage
// if we thought it was alive, this is news to us
if (info->isAlive)
status |= HOSTAGE_DIED;
info->isAlive = false;
info->isValid = false;
}
continue;
}
// if we dont know where this hostage is, nothing to validate
if (!info->isValid)
continue;
// can't directly see this hostage
// check line of sight to where we think this hostage is, to see if we noticed that is has moved
pos = info->knownPos + Vector( 0, 0, HalfHumanHeight );
if (m_owner->IsVisible( pos, CHECK_FOV ))
{
// we can see where we thought the hostage was - verify it is still there and alive
if (!info->hostage->IsValid())
{
// since we have line of sight to an invalid hostage, it must be dead
// discovered that hostage has been killed
status |= HOSTAGE_DIED;
info->isAlive = false;
info->isValid = false;
continue;
}
if (info->hostage->IsFollowingSomeone())
{
// discovered the hostage has been taken
status |= HOSTAGE_GONE;
info->isValid = false;
continue;
}
const float tolerance = 50.0f;
if ((info->hostage->GetAbsOrigin() - info->knownPos).IsLengthGreaterThan( tolerance ))
{
// discovered that hostage has been moved
status |= HOSTAGE_GONE;
info->isValid = false;
continue;
}
}
}
int endValidCount = 0;
for( i=0; i<m_hostageCount; ++i )
if (m_hostage[i].isValid)
++endValidCount;
if (endValidCount == 0 && startValidCount > 0)
{
// we discovered all the hostages are gone
status &= ~HOSTAGE_GONE;
status |= HOSTAGES_ALL_GONE;
}
return status;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Return the nearest visible free hostage
* Since we can actually see any hostage we return, we know its actual position
*/
CHostage *CSGameState::GetNearestVisibleFreeHostage( void ) const
{
CHostage *close = NULL;
float closeRangeSq = 999999999.9f;
float rangeSq;
Vector pos;
Vector myOrigin = GetCentroid( m_owner );
for( int i=0; i<m_hostageCount; ++i )
{
const HostageInfo *info = &m_hostage[i];
if ( !info->hostage )
continue;
// if the hostage is dead or rescued, its not free
if (!info->hostage->IsAlive())
continue;
// if this hostage is following someone, its not free
if (info->hostage->IsFollowingSomeone())
continue;
/// @todo Use travel distance here
pos = info->hostage->GetAbsOrigin();
rangeSq = (pos - myOrigin).LengthSqr();
if (rangeSq < closeRangeSq)
{
if (!m_owner->IsVisible( pos ))
continue;
close = info->hostage;
closeRangeSq = rangeSq;
}
}
return close;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Return true if there are no free hostages
*/
bool CSGameState::AreAllHostagesBeingRescued( void ) const
{
// if the hostages have all been rescued, they are not being rescued any longer
if (m_allHostagesRescued)
return false;
bool isAllDead = true;
for( int i=0; i<m_hostageCount; ++i )
{
const HostageInfo *info = &m_hostage[i];
if (m_owner->GetTeamNumber() == TEAM_CT)
{
// CT's have perfect knowledge via their radar
if (info->hostage && info->hostage->IsValid())
{
if (!info->hostage->IsFollowingSomeone())
return false;
isAllDead = false;
}
}
else
{
if (info->isValid && info->isAlive)
return false;
if (info->isAlive)
isAllDead = false;
}
}
// if all of the remaining hostages are dead, they arent being rescued
if (isAllDead)
return false;
return true;
}
//--------------------------------------------------------------------------------------------------------------
/**
* All hostages have been rescued or are dead
*/
bool CSGameState::AreAllHostagesGone( void ) const
{
if (m_allHostagesRescued)
return true;
// do we know that all the hostages are dead
for( int i=0; i<m_hostageCount; ++i )
{
const HostageInfo *info = &m_hostage[i];
if (m_owner->GetTeamNumber() == TEAM_CT)
{
// CT's have perfect knowledge via their radar
if (info->hostage && info->hostage->IsAlive())
return false;
}
else
{
if (info->isValid && info->isAlive)
return false;
}
}
return true;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Someone told us all the hostages are gone
*/
void CSGameState::AllHostagesGone( void )
{
for( int i=0; i<m_hostageCount; ++i )
m_hostage[i].isValid = false;
}
+151
View File
@@ -0,0 +1,151 @@
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
// Author: Michael S. Booth (mike@turtlerockstudios.com), 2003
#ifndef _GAME_STATE_H_
#define _GAME_STATE_H_
#include "bot_util.h"
class CHostage;
class CCSBot;
/**
* This class represents the game state as known by a particular bot
*/
class CSGameState
{
public:
CSGameState( CCSBot *owner );
void Reset( void );
// Event handling
void OnHostageRescuedAll( IGameEvent *event );
void OnRoundEnd( IGameEvent *event );
void OnRoundStart( IGameEvent *event );
void OnBombPlanted( IGameEvent *event );
void OnBombDefused( IGameEvent *event );
void OnBombExploded( IGameEvent *event );
bool IsRoundOver( void ) const; ///< true if round has been won or lost (but not yet reset)
// bomb defuse scenario -----------------------------------------------------------------------------
enum BombState
{
MOVING, ///< being carried by a Terrorist
LOOSE, ///< loose on the ground somewhere
PLANTED, ///< planted and ticking
DEFUSED, ///< the bomb has been defused
EXPLODED ///< the bomb has exploded
};
bool IsBombMoving( void ) const { return (m_bombState == MOVING); }
bool IsBombLoose( void ) const { return (m_bombState == LOOSE); }
bool IsBombPlanted( void ) const { return (m_bombState == PLANTED); }
bool IsBombDefused( void ) const { return (m_bombState == DEFUSED); }
bool IsBombExploded( void ) const { return (m_bombState == EXPLODED); }
void UpdateLooseBomb( const Vector &pos ); ///< we see the loose bomb
float TimeSinceLastSawLooseBomb( void ) const; ///< how long has is been since we saw the loose bomb
bool IsLooseBombLocationKnown( void ) const; ///< do we know where the loose bomb is
void UpdateBomber( const Vector &pos ); ///< we see the bomber
float TimeSinceLastSawBomber( void ) const; ///< how long has is been since we saw the bomber
void UpdatePlantedBomb( const Vector &pos ); ///< we see the planted bomb
bool IsPlantedBombLocationKnown( void ) const; ///< do we know where the bomb was planted
void MarkBombsiteAsPlanted( int zoneIndex ); ///< mark bombsite as the location of the planted bomb
enum { UNKNOWN = -1 };
int GetPlantedBombsite( void ) const; ///< return the zone index of the planted bombsite, or UNKNOWN
bool IsAtPlantedBombsite( void ) const; ///< return true if we are currently in the bombsite where the bomb is planted
int GetNextBombsiteToSearch( void ); ///< return the zone index of the next bombsite to search
bool IsBombsiteClear( int zoneIndex ) const; ///< return true if given bombsite has been cleared
void ClearBombsite( int zoneIndex ); ///< mark bombsite as clear
const Vector *GetBombPosition( void ) const; ///< return where we think the bomb is, or NULL if we don't know
// hostage rescue scenario ------------------------------------------------------------------------
CHostage *GetNearestFreeHostage( Vector *knowPos = NULL ) const; ///< return the closest free hostage, and where we think it is (knowPos)
const Vector *GetRandomFreeHostagePosition( void ) const;
bool AreAllHostagesBeingRescued( void ) const; ///< return true if there are no free hostages
bool AreAllHostagesGone( void ) const; ///< all hostages have been rescued or are dead
void AllHostagesGone( void ); ///< someone told us all the hostages are gone
bool HaveSomeHostagesBeenTaken( void ) const ///< return true if one or more hostages have been moved by the CT's
{
return m_haveSomeHostagesBeenTaken;
}
void HostageWasTaken( void ) ///< someone told us a CT is talking to a hostage
{
m_haveSomeHostagesBeenTaken = true;
}
CHostage *GetNearestVisibleFreeHostage( void ) const;
enum ValidateStatusType
{
NO_CHANGE = 0x00,
HOSTAGE_DIED = 0x01,
HOSTAGE_GONE = 0x02,
HOSTAGES_ALL_GONE = 0x04
};
unsigned char ValidateHostagePositions( void ); ///< update our knowledge with what we currently see - returns bitflag events
BombState GetBombState( void ) const { return m_bombState; }
private:
CCSBot *m_owner; ///< who owns this gamestate
bool m_isRoundOver; ///< true if round is over, but no yet reset
// bomb defuse scenario ---------------------------------------------------------------------------
void SetBombState( BombState state );
BombState m_bombState; ///< what we think the bomb is doing
IntervalTimer m_lastSawBomber;
Vector m_bomberPos;
IntervalTimer m_lastSawLooseBomb;
Vector m_looseBombPos;
bool m_isBombsiteClear[ CCSBotManager::MAX_ZONES ]; ///< corresponds to zone indices in CCSBotManager
int m_bombsiteSearchOrder[ CCSBotManager::MAX_ZONES ]; ///< randomized order of bombsites to search
int m_bombsiteCount;
int m_bombsiteSearchIndex; ///< the next step in the search
int m_plantedBombsite; ///< zone index of the bombsite where the planted bomb is
bool m_isPlantedBombPosKnown; ///< if true, we know the exact location of the bomb
Vector m_plantedBombPos;
// hostage rescue scenario ------------------------------------------------------------------------
struct HostageInfo
{
CHandle<CHostage> hostage;
Vector knownPos;
bool isValid;
bool isAlive;
bool isFree; ///< not being escorted by a CT
}
m_hostage[ MAX_HOSTAGES ];
int m_hostageCount; ///< number of hostages left in map
CountdownTimer m_validateInterval;
CBaseEntity *GetNearestHostage( void ) const; ///< return the closest live hostage
void InitializeHostageInfo( void ); ///< initialize our knowledge of the number and location of hostages
bool m_allHostagesRescued;
bool m_haveSomeHostagesBeenTaken; ///< true if a hostage has been moved by a CT (and we've seen it)
};
#endif // _GAME_STATE_
@@ -0,0 +1,732 @@
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
// Author: Michael S. Booth (mike@turtlerockstudios.com), 2003
#include "cbase.h"
#include "cs_bot.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
//
// Various tweakable parameters which control overall bot behavior.
// Note[pmf]: I moved them all to the top of the file together so it's easier to get a sense of how they interact
//
const float crouchFarRange = 750.0f; // 50% (increased) chance to crouch when enemy is farther than this
const float hysterisRange = 125.0f; // (+/-) m_combatRange, used to control dodging
const float dodgeRange = 2000.0f; // maximum range from enemy to consider dodging
const float jumpChance = 33.3f; // chance of low-skill to jump when first engaging the enemy (if they are moving)
const float lookAheadRange = 30.0f; // how far L/R to consider whether we can fall while strafing
const float sniperMinRange = 160.0f; // what distance to switch to pistol if enemy is too close. Must be larger than NO_ZOOM range in AdjustZoom()
const float shotgunMaxRange = 600.0f; // what distance to switch to pistol if enemy is too far
const float slashRange = 70.0f; // when using knife, repath to enemy if they are farther than this
const float repathInterval = 0.5f;
const float repathRange = 100.0f; // repath when enemy has moved this far from our current path endpoint
//--------------------------------------------------------------------------------------------------------------
/**
* Begin attacking
*/
void AttackState::OnEnter( CCSBot *me )
{
CBasePlayer *enemy = me->GetBotEnemy();
// store our posture when the attack began
me->PushPostureContext();
me->DestroyPath();
// if we are using a knife, try to sneak up on the enemy
if (enemy && me->IsUsingKnife() && !me->IsPlayerFacingMe( enemy ))
me->Walk();
else
me->Run();
me->GetOffLadder();
me->ResetStuckMonitor();
m_repathTimer.Invalidate();
m_haveSeenEnemy = me->IsEnemyVisible();
m_nextDodgeStateTimestamp = 0.0f;
m_firstDodge = true;
m_isEnemyHidden = false;
m_reacquireTimestamp = 0.0f;
m_pinnedDownTimestamp = gpGlobals->curtime + RandomFloat( 7.0f, 10.0f );
m_shieldToggleTimestamp = gpGlobals->curtime + RandomFloat( 2.0f, 10.0f );
m_shieldForceOpen = false;
// if we encountered someone while escaping, grab our weapon and fight!
if (me->IsEscapingFromBomb())
me->EquipBestWeapon();
if (me->IsUsingKnife())
{
// can't crouch and hold with a knife
m_crouchAndHold = false;
me->StandUp();
}
else if (me->CanSeeSniper() && !me->IsSniper())
{
// don't sit still if we see a sniper!
m_crouchAndHold = false;
me->StandUp();
}
else
{
// decide whether to crouch where we are, or run and gun (if we havent already - see CCSBot::Attack())
if (!m_crouchAndHold)
{
if (enemy)
{
float crouchChance;
// more likely to crouch if using sniper rifle or if enemy is far away
if (me->IsUsingSniperRifle())
crouchChance = 50.0f;
else if ((GetCentroid( me ) - GetCentroid( enemy )).IsLengthGreaterThan( crouchFarRange ))
crouchChance = 50.0f;
else
crouchChance = 20.0f * (1.0f - me->GetProfile()->GetAggression());
if (RandomFloat( 0.0f, 100.0f ) < crouchChance)
{
// make sure we can still see if we crouch
trace_t result;
Vector origin = GetCentroid( me );
if (!me->IsCrouching())
{
// we are standing, adjust for lower crouch origin
origin.z -= 20.0f;
}
UTIL_TraceLine( origin, enemy->EyePosition(), MASK_PLAYERSOLID, me, COLLISION_GROUP_NONE, &result );
if (result.fraction == 1.0f)
{
m_crouchAndHold = true;
}
}
}
}
if (m_crouchAndHold)
{
me->Crouch();
me->PrintIfWatched( "Crouch and hold attack!\n" );
}
}
m_scopeTimestamp = 0;
m_didAmbushCheck = false;
float skill = me->GetProfile()->GetSkill();
// tendency to dodge is proportional to skill
float dodgeChance = 80.0f * skill;
// high skill bots always dodge if outnumbered, or they see a sniper
if (skill > 0.5f && (me->IsOutnumbered() || me->CanSeeSniper()))
{
dodgeChance = 100.0f;
}
m_shouldDodge = (RandomFloat( 0, 100 ) <= dodgeChance);
// decide whether we might bail out of this fight
m_isCoward = (RandomFloat( 0, 100 ) > 100.0f * me->GetProfile()->GetAggression());
// HEAVY BOTS: If we have heavy armor, attack differently
if ( me->HasHeavyArmor() )
{
m_isCoward = m_shouldDodge = false;
me->Walk();
m_pinnedDownTimestamp = gpGlobals->curtime + 60*60;
}
}
//--------------------------------------------------------------------------------------------------------------
/**
* When we are done attacking, this is invoked
*/
void AttackState::StopAttacking( CCSBot *me )
{
if (me->GetTask() == CCSBot::SNIPING)
{
// stay in our hiding spot
me->Hide( me->GetLastKnownArea(), -1.0f, 50.0f );
}
else
{
me->StopAttacking();
}
}
//--------------------------------------------------------------------------------------------------------------
/**
* Do dodge behavior
*/
void AttackState::Dodge( CCSBot *me )
{
//
// Dodge.
// If sniping or crouching, stand still.
//
if (m_shouldDodge && !me->IsUsingSniperRifle() && !m_crouchAndHold)
{
CBasePlayer *enemy = me->GetBotEnemy();
if (enemy == NULL)
{
return;
}
Vector toEnemy = enemy->GetAbsOrigin() - me->GetAbsOrigin();
float range = toEnemy.Length();
float minRange = me->GetCombatRange() - hysterisRange;
float maxRange = me->GetCombatRange() + hysterisRange;
if (me->IsUsingKnife())
{
// dodge when far away if armed only with a knife
maxRange = 999999.9f;
}
// move towards (or away from) enemy if we are using a knife, behind a corner, or we aren't very skilled
if (me->GetProfile()->GetSkill() < 0.66f || !me->IsEnemyVisible())
{
if (range > maxRange)
me->MoveForward();
else if (range < minRange)
me->MoveBackward();
}
// don't dodge if enemy is facing away
if (!me->CanSeeSniper() && (range > dodgeRange || !me->IsPlayerFacingMe( enemy )))
{
m_dodgeState = STEADY_ON;
m_nextDodgeStateTimestamp = 0.0f;
}
else if (gpGlobals->curtime >= m_nextDodgeStateTimestamp)
{
int next;
// high-skill bots keep moving and don't jump if they see a sniper
if (me->GetProfile()->GetSkill() > 0.5f && me->CanSeeSniper())
{
// juke back and forth
if (m_firstDodge)
{
next = (RandomInt( 0, 100 ) < 50) ? SLIDE_RIGHT : SLIDE_LEFT;
}
else
{
next = (m_dodgeState == SLIDE_LEFT) ? SLIDE_RIGHT : SLIDE_LEFT;
}
}
else
{
// select next dodge state that is different that our current one
do
{
// low-skill bots may jump when first engaging the enemy (if they are moving)
if (m_firstDodge && me->GetProfile()->GetSkill() < 0.5f && RandomFloat( 0, 100 ) < jumpChance && !me->IsNotMoving())
next = RandomInt( 0, NUM_ATTACK_STATES-1 );
else
next = RandomInt( 0, NUM_ATTACK_STATES-2 );
}
while( !m_firstDodge && next == m_dodgeState );
}
m_dodgeState = (DodgeStateType)next;
m_nextDodgeStateTimestamp = gpGlobals->curtime + RandomFloat( 0.3f, 1.0f );
m_firstDodge = false;
}
Vector forward, right;
me->EyeVectors( &forward, &right );
float ground;
switch( m_dodgeState )
{
case STEADY_ON:
{
break;
}
case SLIDE_LEFT:
{
// don't move left if we will fall
Vector pos = me->GetAbsOrigin() - (lookAheadRange * right);
if (me->GetSimpleGroundHeightWithFloor( pos, &ground ))
{
if (me->GetAbsOrigin().z - ground < StepHeight)
{
me->StrafeLeft();
}
}
break;
}
case SLIDE_RIGHT:
{
// don't move left if we will fall
Vector pos = me->GetAbsOrigin() + (lookAheadRange * right);
if (me->GetSimpleGroundHeightWithFloor( pos, &ground ))
{
if (me->GetAbsOrigin().z - ground < StepHeight)
{
me->StrafeRight();
}
}
break;
}
case JUMP:
{
if (me->m_isEnemyVisible)
{
me->Jump();
}
break;
}
}
}
}
//--------------------------------------------------------------------------------------------------------------
/**
* Perform attack behavior
*/
void AttackState::OnUpdate( CCSBot *me )
{
// can't be stuck while attacking
me->ResetStuckMonitor();
// if we somehow ended up with the C4 or a grenade in our hands, grab our weapon!
CWeaponCSBase *weapon = me->GetActiveCSWeapon();
if (weapon && !CSGameRules()->IsPlayingCoopMission())
{
if (weapon->GetCSWeaponID() == WEAPON_C4 ||
weapon->GetCSWeaponID() == WEAPON_HEGRENADE ||
weapon->GetCSWeaponID() == WEAPON_FLASHBANG ||
weapon->GetCSWeaponID() == WEAPON_SMOKEGRENADE ||
weapon->GetCSWeaponID() == WEAPON_MOLOTOV ||
weapon->GetCSWeaponID() == WEAPON_INCGRENADE ||
weapon->GetCSWeaponID() == WEAPON_DECOY ||
weapon->GetCSWeaponID() == WEAPON_TAGRENADE )
{
me->EquipBestWeapon();
}
}
CBasePlayer *enemy = me->GetBotEnemy();
if (enemy == NULL)
{
StopAttacking( me );
return;
}
Vector myOrigin = GetCentroid( me );
Vector enemyOrigin = GetCentroid( enemy );
// keep track of whether we have seen our enemy at least once yet
if (!m_haveSeenEnemy)
m_haveSeenEnemy = me->IsEnemyVisible();
//
// Retreat check
// Do not retreat if the enemy is too close
//
if (m_retreatTimer.IsElapsed())
{
// If we've been fighting this battle for awhile, we're "pinned down" and
// need to do something else.
// If we are outnumbered, retreat.
// If we see a sniper and we aren't a sniper, retreat.
bool isPinnedDown = (gpGlobals->curtime > m_pinnedDownTimestamp);
if (isPinnedDown ||
(me->CanSeeSniper() && !me->IsSniper()) ||
(me->IsOutnumbered() && m_isCoward) ||
(me->OutnumberedCount() >= 2 && me->GetProfile()->GetAggression() < 1.0f))
{
// only retreat if at least one of them is aiming at me
if (me->IsAnyVisibleEnemyLookingAtMe( CHECK_FOV ))
{
// tell our teammates our plight
if (isPinnedDown)
me->GetChatter()->PinnedDown();
else if (!me->CanSeeSniper())
me->GetChatter()->Scared();
m_retreatTimer.Start( RandomFloat( 3.0f, 15.0f ) );
// try to retreat
if (me->TryToRetreat())
{
// if we are a sniper, equip our pistol so we can fire while retreating
/*
if (me->IsUsingSniperRifle())
{
// wait a moment to allow one last shot
me->Wait( 0.5f );
//me->EquipPistol();
}
*/
// request backup if outnumbered
if (me->IsOutnumbered())
{
me->GetChatter()->NeedBackup();
}
}
else
{
me->PrintIfWatched( "I want to retreat, but no safe spots nearby!\n" );
}
}
}
}
//
// Knife fighting
// We need to pathfind right to the enemy to cut him
//
if (me->IsUsingKnife())
{
// can't crouch and hold with a knife
m_crouchAndHold = false;
me->StandUp();
// if we are using a knife and our prey is looking towards us, run at him
if (me->IsPlayerFacingMe( enemy ))
{
me->ForceRun( 5.0f );
me->Hurry( 10.0f );
}
// slash our victim
me->FireWeaponAtEnemy();
// if toe to toe with our enemy, don't dodge, just slash
if ((enemy->GetAbsOrigin() - me->GetAbsOrigin()).IsLengthGreaterThan( slashRange ))
{
// if our victim has moved, repath
bool repath = false;
if (me->HasPath())
{
if ((me->GetPathEndpoint() - enemy->GetAbsOrigin()).IsLengthGreaterThan( repathRange ))
{
repath = true;
}
}
else
{
repath = true;
}
if (repath && m_repathTimer.IsElapsed())
{
Vector enemyPos = enemy->GetAbsOrigin() + Vector( 0, 0, HalfHumanHeight );
me->ComputePath( enemyPos, FASTEST_ROUTE );
m_repathTimer.Start( repathInterval );
}
// move towards victim
if (me->UpdatePathMovement( NO_SPEED_CHANGE ) != CCSBot::PROGRESSING)
{
me->DestroyPath();
}
}
return;
}
//
// Simple shield usage
//
if (me->HasShield())
{
if (me->IsEnemyVisible() && !m_shieldForceOpen)
{
if (!me->IsRecognizedEnemyReloading() && !me->IsReloading() && me->IsPlayerLookingAtMe( enemy ))
{
// close up - enemy is pointing his gun at us
if (!me->IsProtectedByShield())
me->SecondaryAttack();
}
else
{
// enemy looking away or reloading his weapon - open up and shoot him
if (me->IsProtectedByShield())
me->SecondaryAttack();
}
}
else
{
// can't see enemy, open up
if (me->IsProtectedByShield())
me->SecondaryAttack();
}
if (gpGlobals->curtime > m_shieldToggleTimestamp)
{
m_shieldToggleTimestamp = gpGlobals->curtime + RandomFloat( 0.5, 2.0f );
// toggle shield force open
m_shieldForceOpen = !m_shieldForceOpen;
}
}
// check if our weapon range is bad and we should switch to pistol
if (me->IsUsingSniperRifle())
{
// if we have a sniper rifle and our enemy is too close, switch to pistol
if ((enemyOrigin - myOrigin).IsLengthLessThan( sniperMinRange ))
me->EquipPistol();
}
else if (me->IsUsingShotgun())
{
// if we have a shotgun equipped and enemy is too far away, switch to pistol
if ((enemyOrigin - myOrigin).IsLengthGreaterThan( shotgunMaxRange ))
me->EquipPistol();
}
// if we're sniping, look through the scope - need to do this here in case a reload resets our scope
if (me->IsUsingSniperRifle())
{
// for Scouts and AWPs, we need to wait for zoom to resume
if (me->m_bResumeZoom)
{
m_scopeTimestamp = gpGlobals->curtime;
return;
}
Vector toAimSpot3D = me->m_targetSpot - myOrigin;
float targetRange = toAimSpot3D.Length();
// dont adjust zoom level if we're already zoomed in - just fire
if (me->GetZoomLevel() == CCSBot::NO_ZOOM && me->AdjustZoom( targetRange ))
m_scopeTimestamp = gpGlobals->curtime;
const float waitScopeTime = 0.3f + me->GetProfile()->GetReactionTime();
if (gpGlobals->curtime - m_scopeTimestamp < waitScopeTime)
{
// force us to wait until zoomed in before firing
return;
}
}
// see if we "notice" that our prey is dead
if (me->IsAwareOfEnemyDeath())
{
// let team know if we killed the last enemy
if (me->GetLastVictimID() == enemy->entindex() && me->GetNearbyEnemyCount() <= 1)
{
me->GetChatter()->KilledMyEnemy( enemy->entindex() );
// if there are other enemies left, wait a moment - they usually come in groups
if (me->GetEnemiesRemaining())
{
me->Wait( RandomFloat( 1.0f, 3.0f ) );
}
}
StopAttacking( me );
return;
}
float notSeenEnemyTime = gpGlobals->curtime - me->GetLastSawEnemyTimestamp();
// if we haven't seen our enemy for a moment, continue on if we dont want to fight, or decide to ambush if we do
if (!me->IsEnemyVisible())
{
// attend to nearby enemy gunfire
if (notSeenEnemyTime > 0.5f && me->CanHearNearbyEnemyGunfire())
{
// give up the attack, since we didn't want it in the first place
StopAttacking( me );
const Vector *pos = me->GetNoisePosition();
if (pos)
{
me->SetLookAt( "Nearby enemy gunfire", *pos, PRIORITY_HIGH, 0.0f );
me->PrintIfWatched( "Checking nearby threatening enemy gunfire!\n" );
return;
}
}
// check if we have lost track of our enemy during combat
if (notSeenEnemyTime > 0.25f)
{
m_isEnemyHidden = true;
}
if (notSeenEnemyTime > 0.1f)
{
if (me->GetDisposition() == CCSBot::ENGAGE_AND_INVESTIGATE)
{
// decide whether we should hide and "ambush" our enemy
if (m_haveSeenEnemy && !m_didAmbushCheck)
{
float hideChance = 33.3f;
if (RandomFloat( 0.0, 100.0f ) < hideChance)
{
float ambushTime = RandomFloat( 3.0f, 15.0f );
// hide in ambush nearby
/// @todo look towards where we know enemy is
const Vector *spot = FindNearbyRetreatSpot( me, 200.0f );
if (spot)
{
me->IgnoreEnemies( 1.0f );
me->Run();
me->StandUp();
me->Hide( *spot, ambushTime, true );
return;
}
}
// don't check again
m_didAmbushCheck = true;
}
}
else
{
// give up the attack, since we didn't want it in the first place
StopAttacking( me );
return;
}
}
}
else
{
// we can see the enemy again - reset our ambush check
m_didAmbushCheck = false;
// if the enemy is coming out of hiding, we need time to react
if (m_isEnemyHidden)
{
m_reacquireTimestamp = gpGlobals->curtime + me->GetProfile()->GetReactionTime();
m_isEnemyHidden = false;
}
}
// if we haven't seen our enemy for a long time, chase after them
float chaseTime = 2.0f + 2.0f * (1.0f - me->GetProfile()->GetAggression());
// if we are sniping, be very patient
if (me->IsUsingSniperRifle())
chaseTime += 3.0f;
else if (me->IsCrouching()) // if we are crouching, be a little patient
chaseTime += 1.0f;
// if we can't see the enemy, and have either seen him but currently lost sight of him,
// or haven't yet seen him, chase after him (unless we are a sniper)
if (!me->IsEnemyVisible() && (notSeenEnemyTime > chaseTime || !m_haveSeenEnemy))
{
// snipers don't chase their prey - they wait for their prey to come to them
if ( CSGameRules()->IsPlayingCoopGuardian() == false && me->GetTask() == CCSBot::SNIPING )
{
StopAttacking( me );
return;
}
else
{
// move to last known position of enemy
me->SetTask( CCSBot::MOVE_TO_LAST_KNOWN_ENEMY_POSITION, enemy );
me->MoveTo( me->GetLastKnownEnemyPosition() );
return;
}
}
// if we can't see our enemy at the moment, and were shot by
// a different visible enemy, engage them instead
const float hurtRecentlyTime = 3.0f;
if (!me->IsEnemyVisible() &&
me->GetTimeSinceAttacked() < hurtRecentlyTime &&
me->GetAttacker() &&
me->GetAttacker() != me->GetBotEnemy())
{
// if we can see them, attack, otherwise panic
if (me->IsVisible( me->GetAttacker(), CHECK_FOV ))
{
me->Attack( me->GetAttacker() );
me->PrintIfWatched( "Switching targets to retaliate against new attacker!\n" );
}
/*
* Rethink this
else
{
me->Panic( me->GetAttacker() );
me->PrintIfWatched( "Panicking from crossfire while attacking!\n" );
}
*/
return;
}
if (true || gpGlobals->curtime > m_reacquireTimestamp)
me->FireWeaponAtEnemy();
// do dodge behavior
Dodge( me );
if ( me->HasHeavyArmor() && me->GetBotEnemy() && me->IsEnemyVisible() )
{
me->MoveForward();
me->GetChatter()->DoPhoenixHeavyWakeTaunt();
}
}
//--------------------------------------------------------------------------------------------------------------
/**
* Finish attack
*/
void AttackState::OnExit( CCSBot *me )
{
me->PrintIfWatched( "AttackState:OnExit()\n" );
m_crouchAndHold = false;
// clear any noises we heard during battle
me->ForgetNoise();
me->ResetStuckMonitor();
// resume our original posture
me->PopPostureContext();
// put shield away
if (me->IsProtectedByShield())
me->SecondaryAttack();
//me->StopAiming();
}
@@ -0,0 +1,703 @@
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
// Author: Michael S. Booth (mike@turtlerockstudios.com), 2003
#include "cbase.h"
#include "cs_gamerules.h"
#include "cs_bot.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
//--------------------------------------------------------------------------------------------------------------
ConVar bot_loadout( "bot_loadout", "", FCVAR_CHEAT, "bots are given these items at round start" );
ConVar bot_randombuy( "bot_randombuy", "0", FCVAR_CHEAT, "should bots ignore their prefered weapons and just buy weapons at random?" );
ConVar bot_gungameselect_weapons_t( "bot_gungameselect_weapons_t", "deagle awp p90 ak47 sg556", 0, "the list of weapons that T bots start with in gun game select" );
ConVar bot_gungameselect_weapons_ct( "bot_gungameselect_weapons_ct", "deagle awp p90 aug m4a1", 0, "the list of weapons that CT bots start with in gun game select" );
ConVar sv_bot_buy_grenade_chance( "sv_bot_buy_grenade_chance", "33", FCVAR_RELEASE | FCVAR_GAMEDLL, "Chance bots will buy a grenade with leftover money (after prim, sec and armor). Input as percent (0-100.0)", true, 0.0f, true, 100.0f );
ConVar sv_bot_buy_smoke_weight ( "sv_bot_buy_smoke_weight", "1", FCVAR_RELEASE | FCVAR_GAMEDLL, "Given a bot will buy a grenade, controls the odds of the grenade type. Proportional to all other sv_bot_buy_*_weight convars.", true, 0.0f, false, 0.0f );
ConVar sv_bot_buy_flash_weight ( "sv_bot_buy_flash_weight", "1", FCVAR_RELEASE | FCVAR_GAMEDLL, "Given a bot will buy a grenade, controls the odds of the grenade type. Proportional to all other sv_bot_buy_*_weight convars.", true, 0.0f, false, 0.0f );
ConVar sv_bot_buy_decoy_weight ( "sv_bot_buy_decoy_weight", "1", FCVAR_RELEASE | FCVAR_GAMEDLL, "Given a bot will buy a grenade, controls the odds of the grenade type. Proportional to all other sv_bot_buy_*_weight convars.", true, 0.0f, false, 0.0f );
ConVar sv_bot_buy_molotov_weight ( "sv_bot_buy_molotov_weight", "1", FCVAR_RELEASE | FCVAR_GAMEDLL, "Given a bot will buy a grenade, controls the odds of the grenade type. Proportional to all other sv_bot_buy_*_weight convars.", true, 0.0f, false, 0.0f );
ConVar sv_bot_buy_hegrenade_weight ( "sv_bot_buy_hegrenade_weight", "6", FCVAR_RELEASE | FCVAR_GAMEDLL, "Given a bot will buy a grenade, controls the odds of the grenade type. Proportional to all other sv_bot_buy_*_weight convars.", true, 0.0f, false, 0.0f );
struct { const ConVar *cv; const char* szName; } g_GrenadeWeights[] =
{
{ &sv_bot_buy_smoke_weight, "smokegrenade" },
{ &sv_bot_buy_flash_weight, "flashbang" },
{ &sv_bot_buy_decoy_weight, "decoy" },
{ &sv_bot_buy_molotov_weight, "molotov" },
{ &sv_bot_buy_hegrenade_weight, "hegrenade" },
};
// Pick a grenade for bots to buy based on set weights
const char* Helper_PickBotGrenade()
{
int iNumGrenades = ARRAYSIZE( g_GrenadeWeights );
float flGrenadeWeight = 0.0f;
for ( int i = 0; i < iNumGrenades; ++i )
flGrenadeWeight += g_GrenadeWeights[ i ].cv->GetFloat();
float flRand = RandomFloat( 0.0f + FLT_EPSILON , flGrenadeWeight );
float flAccumulator = 0.0f;
for ( int i = 0; i < iNumGrenades; ++i )
{
flAccumulator += g_GrenadeWeights[ i ].cv->GetFloat();
if ( flRand <= flAccumulator )
{
return g_GrenadeWeights[ i ].szName;
}
}
return NULL;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Debug command to give a named weapon
*/
void CCSBot::GiveWeapon( const char *weaponAlias )
{
const char *translatedAlias = GetTranslatedWeaponAlias( weaponAlias );
char wpnName[128];
Q_snprintf( wpnName, sizeof( wpnName ), "weapon_%s", translatedAlias );
WEAPON_FILE_INFO_HANDLE hWpnInfo = LookupWeaponInfoSlot( wpnName );
if ( hWpnInfo == GetInvalidWeaponInfoHandle() )
{
return;
}
CCSWeaponInfo *pWeaponInfo = dynamic_cast< CCSWeaponInfo* >( GetFileWeaponInfoFromHandle( hWpnInfo ) );
if ( !pWeaponInfo )
{
return;
}
if ( !Weapon_OwnsThisType( wpnName ) )
{
CBaseCombatWeapon *pWeapon = Weapon_GetSlot( pWeaponInfo->iSlot );
if ( pWeapon )
{
if ( pWeaponInfo->iSlot == WEAPON_SLOT_PISTOL )
{
DropWeaponSlot( WEAPON_SLOT_PISTOL );
}
else if ( pWeaponInfo->iSlot == WEAPON_SLOT_RIFLE )
{
DropWeaponSlot( WEAPON_SLOT_RIFLE );
}
}
}
GiveNamedItem( wpnName );
}
//--------------------------------------------------------------------------------------------------------------
static bool HasDefaultPistol( CCSBot *me )
{
CWeaponCSBase *pistol = (CWeaponCSBase *)me->Weapon_GetSlot( WEAPON_SLOT_PISTOL );
if (pistol == NULL)
return false;
if (me->GetTeamNumber() == TEAM_TERRORIST && pistol->IsA( WEAPON_GLOCK ))
return true;
if (me->GetTeamNumber() == TEAM_CT && pistol->IsA( WEAPON_HKP2000 ))
return true;
return false;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Buy weapons, armor, etc.
*/
void BuyState::OnEnter( CCSBot *me )
{
m_retries = 0;
m_prefRetries = 0;
m_prefIndex = 0;
const char *cheatWeaponString = bot_loadout.GetString();
if ( cheatWeaponString && *cheatWeaponString )
{
m_doneBuying = false; // we're going to be given weapons - ignore the eco limit
}
else
{
// check if we are saving money for the next round
if (me->GetAccountBalance() < cv_bot_eco_limit.GetFloat())
{
me->PrintIfWatched( "Saving money for next round.\n" );
m_doneBuying = true;
}
else
{
m_doneBuying = false;
}
}
m_isInitialDelay = true;
// this will force us to stop holding live grenade
me->EquipBestWeapon( MUST_EQUIP );
m_buyShield = false;
if (me->GetTeamNumber() == TEAM_CT)
{
// determine if we want a tactical shield
if (!me->HasPrimaryWeapon() && TheCSBots()->AllowTacticalShield())
{
if (me->GetAccountBalance() > 2500)
{
if (me->GetAccountBalance() < 4000)
m_buyShield = (RandomFloat( 0, 100.0f ) < 33.3f) ? true : false;
else
m_buyShield = (RandomFloat( 0, 100.0f ) < 10.0f) ? true : false;
}
}
}
if (TheCSBots()->AllowGrenades())
{
m_buyGrenade = (RandomFloat( 0.0f, 100.0f ) < sv_bot_buy_grenade_chance.GetFloat() ) ? true : false;
}
else
{
m_buyGrenade = false;
}
m_buyPistol = false;
if (TheCSBots()->AllowPistols())
{
// check if we have a pistol
if (me->Weapon_GetSlot( WEAPON_SLOT_PISTOL ))
{
// if we have our default pistol, think about buying a different one
if (HasDefaultPistol( me ))
{
// if everything other than pistols is disallowed, buy a pistol
if (TheCSBots()->AllowShotguns() == false &&
TheCSBots()->AllowSubMachineGuns() == false &&
TheCSBots()->AllowRifles() == false &&
TheCSBots()->AllowMachineGuns() == false &&
TheCSBots()->AllowTacticalShield() == false &&
TheCSBots()->AllowSnipers() == false)
{
m_buyPistol = (RandomFloat( 0, 100 ) < 75.0f);
}
else if (me->GetAccountBalance() < 1000)
{
// if we're low on cash, buy a pistol
m_buyPistol = (RandomFloat( 0, 100 ) < 75.0f);
}
else
{
m_buyPistol = (RandomFloat( 0, 100 ) < 33.3f);
}
}
}
else
{
// we dont have a pistol - buy one
m_buyPistol = true;
}
}
}
enum WeaponType
{
PISTOL,
SHOTGUN,
SUB_MACHINE_GUN,
RIFLE,
MACHINE_GUN,
SNIPER_RIFLE,
GRENADE,
NUM_WEAPON_TYPES,
INVALID_WEAPON_TYPE = NUM_WEAPON_TYPES
};
struct BuyInfo
{
WeaponType type;
bool preferred; ///< more challenging bots prefer these weapons
char *buyAlias; ///< the buy alias for this equipment
};
#define PRIMARY_WEAPON_BUY_COUNT 16
#define SECONDARY_WEAPON_BUY_COUNT 3
/**
* These tables MUST be kept in sync with the CT and T buy aliases
*/
static BuyInfo primaryWeaponBuyInfoCT[ PRIMARY_WEAPON_BUY_COUNT ] =
{
{ SHOTGUN, false, "nova" },
{ SHOTGUN, false, "xm1014" },
{ SHOTGUN, false, "mag7" },
{ SUB_MACHINE_GUN, false, "mp9" },
{ SUB_MACHINE_GUN, false, "bizon" },
{ SUB_MACHINE_GUN, false, "mp7" },
{ SUB_MACHINE_GUN, false, "ump45" },
{ SUB_MACHINE_GUN, false, "p90" },
{ RIFLE, true, "famas" },
{ SNIPER_RIFLE, false, "ssg08" },
{ RIFLE, true, "m4a1" },
{ RIFLE, true, "aug" },
{ SNIPER_RIFLE, true, "scar20" },
{ SNIPER_RIFLE, true, "awp" },
{ MACHINE_GUN, false, "m249" },
{ MACHINE_GUN, false, "negev" }
};
static BuyInfo secondaryWeaponBuyInfoCT[ SECONDARY_WEAPON_BUY_COUNT ] =
{
// { PISTOL, false, "glock" },
// { PISTOL, false, "usp" },
{ PISTOL, true, "p250" },
{ PISTOL, true, "deagle" },
{ PISTOL, true, "fiveseven" }
};
static BuyInfo primaryWeaponBuyInfoT[ PRIMARY_WEAPON_BUY_COUNT ] =
{
{ SHOTGUN, false, "nova" },
{ SHOTGUN, false, "xm1014" },
{ SHOTGUN, false, "sawedoff" },
{ SUB_MACHINE_GUN, false, "mac10" },
{ SUB_MACHINE_GUN, false, "ump45" },
{ SUB_MACHINE_GUN, false, "p90" },
{ RIFLE, true, "galilar" },
{ RIFLE, true, "ak47" },
{ SNIPER_RIFLE, false, "ssg08" },
{ RIFLE, true, "sg556" },
{ SNIPER_RIFLE, true, "awp" },
{ SNIPER_RIFLE, true, "g3sg1" },
{ MACHINE_GUN, false, "m249" },
{ MACHINE_GUN, false, "negev" },
{ INVALID_WEAPON_TYPE, false, "" },
{ INVALID_WEAPON_TYPE, false, "" }
};
static BuyInfo secondaryWeaponBuyInfoT[ SECONDARY_WEAPON_BUY_COUNT ] =
{
// { PISTOL, false, "glock" },
// { PISTOL, false, "usp" },
{ PISTOL, true, "p250" },
{ PISTOL, true, "deagle" },
{ PISTOL, true, "elites" }
};
/**
* Given a weapon alias, return the kind of weapon it is
*/
inline WeaponType GetWeaponType( const char *alias )
{
int i;
for( i=0; i<PRIMARY_WEAPON_BUY_COUNT; ++i )
{
if (!stricmp( alias, primaryWeaponBuyInfoCT[i].buyAlias ))
return primaryWeaponBuyInfoCT[i].type;
if (!stricmp( alias, primaryWeaponBuyInfoT[i].buyAlias ))
return primaryWeaponBuyInfoT[i].type;
}
for( i=0; i<SECONDARY_WEAPON_BUY_COUNT; ++i )
{
if (!stricmp( alias, secondaryWeaponBuyInfoCT[i].buyAlias ))
return secondaryWeaponBuyInfoCT[i].type;
if (!stricmp( alias, secondaryWeaponBuyInfoT[i].buyAlias ))
return secondaryWeaponBuyInfoT[i].type;
}
return INVALID_WEAPON_TYPE;
}
//--------------------------------------------------------------------------------------------------------------
void BuyState::OnUpdate( CCSBot *me )
{
char cmdBuffer[256];
// wait for a Navigation Mesh
if (!TheNavMesh->IsLoaded())
return;
// apparently we cant buy things in the first few seconds, so wait a bit
if (m_isInitialDelay)
{
const float waitToBuyTime = 0.25f;
if (gpGlobals->curtime - me->GetStateTimestamp() < waitToBuyTime)
return;
m_isInitialDelay = false;
}
// if we're done buying and still in the freeze period, wait
if (m_doneBuying)
{
if (CSGameRules()->IsMultiplayer() && CSGameRules()->IsFreezePeriod())
{
// make sure we're locked and loaded
me->EquipBestWeapon( MUST_EQUIP );
me->Reload();
me->ResetStuckMonitor();
return;
}
me->Idle();
return;
}
// If we're supposed to buy a specific weapon for debugging, do so and then bail
const char *cheatWeaponString = bot_loadout.GetString();
if ( cheatWeaponString && *cheatWeaponString )
{
CSplitString loadout( cheatWeaponString, " " );
for ( int i=0; i<loadout.Count(); ++i )
{
const char *item = loadout[i];
if ( FStrEq( item, "vest" ) )
{
me->GiveNamedItem( "item_kevlar" );
}
else if ( FStrEq( item, "vesthelm" ) )
{
me->GiveNamedItem( "item_assaultsuit" );
}
else if ( FStrEq( item, "defuser" ) )
{
if ( me->GetTeamNumber() == TEAM_CT )
{
me->GiveDefuser();
}
}
else if ( FStrEq( item, "nvgs" ) )
{
me->m_bHasNightVision = true;
}
else if ( FStrEq( item, "primammo" ) )
{
me->AttemptToBuyAmmo( 0 );
}
else if ( FStrEq( item, "secammo" ) )
{
me->AttemptToBuyAmmo( 1 );
}
else
{
me->GiveWeapon( item );
}
}
m_doneBuying = true;
return;
}
if (!me->IsInBuyZone())
{
m_doneBuying = true;
CONSOLE_ECHO( "%s bot spawned outside of a buy zone (%d, %d, %d)\n",
(me->GetTeamNumber() == TEAM_CT) ? "CT" : "Terrorist",
(int)me->GetAbsOrigin().x,
(int)me->GetAbsOrigin().y,
(int)me->GetAbsOrigin().z );
return;
}
// try to buy some weapons
const float buyInterval = 0.02f;
if (gpGlobals->curtime - me->GetStateTimestamp() > buyInterval)
{
me->m_stateTimestamp = gpGlobals->curtime;
bool isPreferredAllDisallowed = true;
// try to buy our preferred weapons first
if (m_prefIndex < me->GetProfile()->GetWeaponPreferenceCount() && bot_randombuy.GetBool() == false )
{
// need to retry because sometimes first buy fails??
const int maxPrefRetries = 2;
if (m_prefRetries >= maxPrefRetries)
{
// try to buy next preferred weapon
++m_prefIndex;
m_prefRetries = 0;
return;
}
int weaponPreference = me->GetProfile()->GetWeaponPreference( m_prefIndex );
// don't buy it again if we still have one from last round
char weaponPreferenceName[32];
Q_snprintf( weaponPreferenceName, sizeof(weaponPreferenceName), "weapon_%s", me->GetProfile()->GetWeaponPreferenceAsString( m_prefIndex ) );
if( me->Weapon_OwnsThisType(weaponPreferenceName) )//Prefs and buyalias use the short version, this uses the long
{
// done with buying preferred weapon
m_prefIndex = 9999;
return;
}
const char *buyAlias = NULL;
buyAlias = WeaponIDToAlias( weaponPreference );
WeaponType type = GetWeaponType( buyAlias );
switch( type )
{
case PISTOL:
if (!TheCSBots()->AllowPistols())
buyAlias = NULL;
break;
case SHOTGUN:
if (!TheCSBots()->AllowShotguns())
buyAlias = NULL;
break;
case SUB_MACHINE_GUN:
if (!TheCSBots()->AllowSubMachineGuns())
buyAlias = NULL;
break;
case RIFLE:
if (!TheCSBots()->AllowRifles())
buyAlias = NULL;
break;
case MACHINE_GUN:
if (!TheCSBots()->AllowMachineGuns())
buyAlias = NULL;
break;
case SNIPER_RIFLE:
if (!TheCSBots()->AllowSnipers())
buyAlias = NULL;
break;
}
if (buyAlias)
{
Q_snprintf( cmdBuffer, 256, "buy %s\n", buyAlias );
CCommand args;
args.Tokenize( cmdBuffer );
me->ClientCommand( args );
me->PrintIfWatched( "Tried to buy preferred weapon %s.\n", buyAlias );
isPreferredAllDisallowed = false;
}
++m_prefRetries;
#if 0 // Actually, DO waste money on other equipment...
// bail out so we dont waste money on other equipment
// unless everything we prefer has been disallowed, then buy at random
if (isPreferredAllDisallowed == false)
return;
#endif
}
// if we have no preferred primary weapon (or everything we want is disallowed), buy at random
if (!me->HasPrimaryWeapon() && (isPreferredAllDisallowed || !me->GetProfile()->HasPrimaryPreference()))
{
if (m_buyShield)
{
// buy a shield
CCommand args;
args.Tokenize( "buy shield" );
me->ClientCommand( args );
me->PrintIfWatched( "Tried to buy a shield.\n" );
}
else
{
// build list of allowable weapons to buy
BuyInfo *masterPrimary = (me->GetTeamNumber() == TEAM_TERRORIST) ? primaryWeaponBuyInfoT : primaryWeaponBuyInfoCT;
BuyInfo *stockPrimary[ PRIMARY_WEAPON_BUY_COUNT ];
int stockPrimaryCount = 0;
// dont choose sniper rifles as often
const float sniperRifleChance = 50.0f;
bool wantSniper = (RandomFloat( 0, 100 ) < sniperRifleChance) ? true : false;
if ( bot_randombuy.GetBool() )
{
wantSniper = true;
}
for( int i=0; i<PRIMARY_WEAPON_BUY_COUNT; ++i )
{
if ((masterPrimary[i].type == SHOTGUN && TheCSBots()->AllowShotguns()) ||
(masterPrimary[i].type == SUB_MACHINE_GUN && TheCSBots()->AllowSubMachineGuns()) ||
(masterPrimary[i].type == RIFLE && TheCSBots()->AllowRifles()) ||
(masterPrimary[i].type == SNIPER_RIFLE && TheCSBots()->AllowSnipers() && wantSniper) ||
(masterPrimary[i].type == MACHINE_GUN && TheCSBots()->AllowMachineGuns()))
{
stockPrimary[ stockPrimaryCount++ ] = &masterPrimary[i];
}
}
if (stockPrimaryCount)
{
// buy primary weapon if we don't have one
int which;
// on hard difficulty levels, bots try to buy preferred weapons on the first pass
if (m_retries == 0 && TheCSBots()->GetDifficultyLevel() >= BOT_HARD && bot_randombuy.GetBool() == false )
{
// count up available preferred weapons
int prefCount = 0;
for( which=0; which<stockPrimaryCount; ++which )
if (stockPrimary[which]->preferred)
++prefCount;
if (prefCount)
{
int whichPref = RandomInt( 0, prefCount-1 );
for( which=0; which<stockPrimaryCount; ++which )
if (stockPrimary[which]->preferred && whichPref-- == 0)
break;
}
else
{
// no preferred weapons available, just pick randomly
which = RandomInt( 0, stockPrimaryCount-1 );
}
}
else
{
which = RandomInt( 0, stockPrimaryCount-1 );
}
Q_snprintf( cmdBuffer, 256, "buy %s\n", stockPrimary[ which ]->buyAlias );
CCommand args;
args.Tokenize( cmdBuffer );
me->ClientCommand( args );
me->PrintIfWatched( "Tried to buy %s.\n", stockPrimary[ which ]->buyAlias );
}
}
}
//
// If we now have a weapon, or have tried for too long, we're done
//
if (me->HasPrimaryWeapon() || m_retries++ > 5)
{
// primary ammo
CCommand args;
if (me->HasPrimaryWeapon())
{
args.Tokenize( "buy primammo" );
me->ClientCommand( args );
}
// buy armor last, to make sure we bought a weapon first
args.Tokenize( "buy vesthelm" );
me->ClientCommand( args );
args.Tokenize( "buy vest" );
me->ClientCommand( args );
// pistols - if we have no preferred pistol, buy at random
if (TheCSBots()->AllowPistols() && !me->GetProfile()->HasPistolPreference())
{
if (m_buyPistol)
{
int which = RandomInt( 0, SECONDARY_WEAPON_BUY_COUNT-1 );
const char *what = NULL;
if (me->GetTeamNumber() == TEAM_TERRORIST)
what = secondaryWeaponBuyInfoT[ which ].buyAlias;
else
what = secondaryWeaponBuyInfoCT[ which ].buyAlias;
Q_snprintf( cmdBuffer, 256, "buy %s\n", what );
args.Tokenize( cmdBuffer );
me->ClientCommand( args );
// only buy one pistol
m_buyPistol = false;
}
// make sure we have enough pistol ammo
args.Tokenize( "buy secammo" );
me->ClientCommand( args );
}
// buy a grenade if we wish, and we don't already have one
if (m_buyGrenade && !me->HasGrenade())
{
const char *szGrenade = Helper_PickBotGrenade();
if ( szGrenade )
args.Tokenize( CFmtStr( "buy %s", szGrenade ).Access() );
/*
if (rnd < 10)//10% chance
{
args.Tokenize( "buy smokegrenade" );
}
else if (rnd < 20)//10% chance
{
// only allow Flashbangs if everyone on the team is a bot (dont want to blind our friendly humans)
if (UTIL_IsTeamAllBots( me->GetTeamNumber() ))
{
args.Tokenize( "buy flashbang" );
}
else
{
args.Tokenize( "buy hegrenade" );
}
}
else if (rnd < 30)//10% chance
{
args.Tokenize( "buy decoy" );
}
else if (rnd < 40)//10% chance
{
args.Tokenize( "buy molotov" );
}
else//60%-70% chance (because of the flashbang case for full team of bots).
{
args.Tokenize( "buy hegrenade" );
}
*/
me->ClientCommand( args );
}
m_doneBuying = true;
}
}
}
//--------------------------------------------------------------------------------------------------------------
void BuyState::OnExit( CCSBot *me )
{
me->ResetStuckMonitor();
me->EquipBestWeapon();
}
@@ -0,0 +1,83 @@
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
// Author: Michael S. Booth (mike@turtlerockstudios.com), 2003
#include "cbase.h"
#include "cs_bot.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
//--------------------------------------------------------------------------------------------------------------
/**
* Begin defusing the bomb
*/
void DefuseBombState::OnEnter( CCSBot *me )
{
me->SetDisposition( CCSBot::SELF_DEFENSE );
me->GetChatter()->Say( "DefusingBomb" );
}
//--------------------------------------------------------------------------------------------------------------
/**
* Defuse the bomb
*/
void DefuseBombState::OnUpdate( CCSBot *me )
{
const Vector *bombPos = me->GetGameState()->GetBombPosition();
// stay in SELF_DEFENSE so we get to the bomb in time!
me->SetDisposition( CCSBot::SELF_DEFENSE );
if (bombPos == NULL)
{
me->PrintIfWatched( "In Defuse state, but don't know where the bomb is!\n" );
me->Idle();
return;
}
// look at the bomb
me->SetLookAt( "Defuse bomb", *bombPos, PRIORITY_HIGH );
// defuse...
me->UseEnvironment();
if (gpGlobals->curtime - me->GetStateTimestamp() > 1.0f)
{
// if we missed starting the defuse, give up
if (TheCSBots()->GetBombDefuser() == NULL)
{
me->PrintIfWatched( "Failed to start defuse, giving up\n" );
me->Idle();
return;
}
else if (TheCSBots()->GetBombDefuser() != me)
{
// if someone else got the defuse, give up
me->PrintIfWatched( "Someone else started defusing, giving up\n" );
me->Idle();
return;
}
}
// if bomb has been defused, give up
if (!TheCSBots()->IsBombPlanted())
{
me->Idle();
return;
}
}
//--------------------------------------------------------------------------------------------------------------
void DefuseBombState::OnExit( CCSBot *me )
{
me->ResetStuckMonitor();
me->SetTask( CCSBot::SEEK_AND_DESTROY );
me->SetDisposition( CCSBot::ENGAGE_AND_INVESTIGATE );
me->ClearLookAt();
}
@@ -0,0 +1,67 @@
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
// Author: Michael S. Booth (mike@turtlerockstudios.com), 2003
#include "cbase.h"
#include "cs_bot.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
//--------------------------------------------------------------------------------------------------------------
/**
* Escape from the bomb.
*/
void EscapeFromBombState::OnEnter( CCSBot *me )
{
me->StandUp();
me->Run();
me->DestroyPath();
me->EquipKnife();
}
//--------------------------------------------------------------------------------------------------------------
/**
* Escape from the bomb.
*/
void EscapeFromBombState::OnUpdate( CCSBot *me )
{
const Vector *bombPos = me->GetGameState()->GetBombPosition();
// if we don't know where the bomb is, we shouldn't be in this state
if (bombPos == NULL)
{
me->Idle();
return;
}
// grab our knife to move quickly
me->EquipKnife();
// look around
me->UpdateLookAround();
if (me->UpdatePathMovement() != CCSBot::PROGRESSING)
{
// we have no path, or reached the end of one - create a new path far away from the bomb
FarAwayFromPositionFunctor func( *bombPos );
CNavArea *goalArea = FindMinimumCostArea( me->GetLastKnownArea(), func );
// if this fails, we'll try again next time
me->ComputePath( goalArea->GetCenter(), FASTEST_ROUTE );
}
}
//--------------------------------------------------------------------------------------------------------------
/**
* Escape from the bomb.
*/
void EscapeFromBombState::OnExit( CCSBot *me )
{
me->EquipBestWeapon();
}
@@ -0,0 +1,117 @@
//========= Copyright Š 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
#include "cbase.h"
#include "cs_bot.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
//--------------------------------------------------------------------------------------------------------------
/**
* Escape from flames we're standing in!
*/
void EscapeFromFlamesState::OnEnter( CCSBot *me )
{
me->StandUp();
me->Run();
me->StopWaiting();
me->DestroyPath();
me->EquipKnife();
m_safeArea = NULL;
m_searchTimer.Invalidate();
}
//--------------------------------------------------------------------------------------------------------------
class CNonDamagingScan : public ISearchSurroundingAreasFunctor
{
public:
CNonDamagingScan( void )
{
m_safeArea = NULL;
m_safeAreaTravelRange = FLT_MAX;
}
virtual ~CNonDamagingScan() { }
virtual bool operator() ( CNavArea *area, CNavArea *priorArea, float travelDistanceSoFar )
{
const float maxSearchRange = 2000.0f;
if ( travelDistanceSoFar < maxSearchRange )
{
if ( !area->IsDamaging() && travelDistanceSoFar < m_safeAreaTravelRange )
{
m_safeArea = area;
}
}
return true;
}
CNavArea *m_safeArea;
float m_safeAreaTravelRange;
};
//--------------------------------------------------------------------------------------------------------------
CNavArea *EscapeFromFlamesState::FindNearestNonDamagingArea( CCSBot *me ) const
{
CNavArea *myArea = me->GetLastKnownArea();
if ( !myArea )
{
return NULL;
}
CNonDamagingScan scan;
SearchSurroundingAreas( myArea, scan );
return scan.m_safeArea;
}
//--------------------------------------------------------------------------------------------------------------
void EscapeFromFlamesState::OnUpdate( CCSBot *me )
{
// default behavior if we're safe
if ( me->GetTimeSinceBurnedByFlames() > 1.5f )
{
me->Idle();
return;
}
if ( m_searchTimer.IsElapsed() )
{
// because flames grow and change, periodically re-search
m_searchTimer.Start( RandomFloat( 0.5f, 1.0f ) );
m_safeArea = FindNearestNonDamagingArea( me );
}
// look around
me->UpdateLookAround();
me->EquipBestWeapon();
me->FireWeaponAtEnemy();
if ( me->UpdatePathMovement() != CCSBot::PROGRESSING )
{
if ( m_safeArea )
{
me->ComputePath( m_safeArea->GetCenter(), FASTEST_ROUTE );
}
}
}
//--------------------------------------------------------------------------------------------------------------
void EscapeFromFlamesState::OnExit( CCSBot *me )
{
me->EquipBestWeapon();
}
@@ -0,0 +1,69 @@
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
// Author: Michael S. Booth (mike@turtlerockstudios.com), 2003
#include "cbase.h"
#include "cs_bot.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
//--------------------------------------------------------------------------------------------------------------
/**
* Move to the bomb on the floor and pick it up
*/
void FetchBombState::OnEnter( CCSBot *me )
{
me->DestroyPath();
}
//--------------------------------------------------------------------------------------------------------------
/**
* Move to the bomb on the floor and pick it up
*/
void FetchBombState::OnUpdate( CCSBot *me )
{
if (me->HasC4())
{
me->PrintIfWatched( "I picked up the bomb\n" );
me->Idle();
return;
}
CBaseEntity *bomb = TheCSBots()->GetLooseBomb();
if (bomb)
{
if (!me->HasPath())
{
// build a path to the bomb
if (me->ComputePath( bomb->GetAbsOrigin() ) == false)
{
me->PrintIfWatched( "Fetch bomb pathfind failed\n" );
// go Hunt instead of Idle to prevent continuous re-pathing to inaccessible bomb
me->Hunt();
return;
}
}
}
else
{
// someone picked up the bomb
me->PrintIfWatched( "Someone else picked up the bomb.\n" );
me->Idle();
return;
}
// look around
me->UpdateLookAround();
if (me->UpdatePathMovement() != CCSBot::PROGRESSING)
me->Idle();
}
@@ -0,0 +1,366 @@
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
// Author: Michael S. Booth (mike@turtlerockstudios.com), 2003
#include "cbase.h"
#include "cs_bot.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
//--------------------------------------------------------------------------------------------------------------
/**
* Follow our leader
*/
void FollowState::OnEnter( CCSBot *me )
{
me->StandUp();
me->Run();
me->DestroyPath();
m_isStopped = false;
m_stoppedTimestamp = 0.0f;
// to force immediate repath
m_lastLeaderPos.x = -99999999.9f;
m_lastLeaderPos.y = -99999999.9f;
m_lastLeaderPos.z = -99999999.9f;
m_lastSawLeaderTime = 0;
// set re-pathing frequency
m_repathInterval.Invalidate();
m_isSneaking = false;
m_walkTime.Invalidate();
m_isAtWalkSpeed = false;
m_leaderMotionState = INVALID;
m_idleTimer.Start( RandomFloat( 2.0f, 5.0f ) );
}
//--------------------------------------------------------------------------------------------------------------
/**
* Determine the leader's motion state by tracking his speed
*/
void FollowState::ComputeLeaderMotionState( float leaderSpeed )
{
// walk = 130, run = 250
const float runWalkThreshold = 140.0f;
const float walkStopThreshold = 10.0f; // 120.0f;
LeaderMotionStateType prevState = m_leaderMotionState;
if (leaderSpeed > runWalkThreshold)
{
m_leaderMotionState = RUNNING;
m_isAtWalkSpeed = false;
}
else if (leaderSpeed > walkStopThreshold)
{
// track when began to walk
if (!m_isAtWalkSpeed)
{
m_walkTime.Start();
m_isAtWalkSpeed = true;
}
const float minWalkTime = 0.25f;
if (m_walkTime.GetElapsedTime() > minWalkTime)
{
m_leaderMotionState = WALKING;
}
}
else
{
m_leaderMotionState = STOPPED;
m_isAtWalkSpeed = false;
}
// track time spent in this motion state
if (prevState != m_leaderMotionState)
{
m_leaderMotionStateTime.Start();
m_waitTime = RandomFloat( 1.0f, 3.0f );
}
}
//--------------------------------------------------------------------------------------------------------------
/**
* Functor to collect all areas in the forward direction of the given player within a radius
*/
class FollowTargetCollector
{
public:
FollowTargetCollector( CBasePlayer *player )
{
m_player = player;
Vector playerVel = player->GetAbsVelocity();
m_forward.x = playerVel.x;
m_forward.y = playerVel.y;
float speed = m_forward.NormalizeInPlace();
Vector playerOrigin = GetCentroid( player );
const float walkSpeed = 100.0f;
if (speed < walkSpeed)
{
m_cutoff.x = playerOrigin.x;
m_cutoff.y = playerOrigin.y;
m_forward.x = 0.0f;
m_forward.y = 0.0f;
}
else
{
const float k = 1.5f; // 2.0f;
float trimSpeed = MIN( speed, 200.0f );
m_cutoff.x = playerOrigin.x + k * trimSpeed * m_forward.x;
m_cutoff.y = playerOrigin.y + k * trimSpeed * m_forward.y;
}
m_targetAreaCount = 0;
}
enum { MAX_TARGET_AREAS = 128 };
bool operator() ( CNavArea *area )
{
if (m_targetAreaCount >= MAX_TARGET_AREAS)
return false;
// only use two-way connections
if (!area->GetParent() || area->IsConnected( area->GetParent(), NUM_DIRECTIONS ))
{
if (m_forward.IsZero())
{
m_targetArea[ m_targetAreaCount++ ] = area;
}
else
{
// collect areas in the direction of the player's forward motion
Vector2D to( area->GetCenter().x - m_cutoff.x, area->GetCenter().y - m_cutoff.y );
to.NormalizeInPlace();
//if (DotProduct( to, m_forward ) > 0.7071f)
if ((to.x * m_forward.x + to.y * m_forward.y) > 0.7071f)
m_targetArea[ m_targetAreaCount++ ] = area;
}
}
return (m_targetAreaCount < MAX_TARGET_AREAS);
}
CBasePlayer *m_player;
Vector2D m_forward;
Vector2D m_cutoff;
CNavArea *m_targetArea[ MAX_TARGET_AREAS ];
int m_targetAreaCount;
};
//--------------------------------------------------------------------------------------------------------------
/**
* Follow our leader
* @todo Clean up this nasty mess
*/
void FollowState::OnUpdate( CCSBot *me )
{
// if we lost our leader, give up
if (m_leader == NULL || !m_leader->IsAlive())
{
me->Idle();
return;
}
// if we are carrying the bomb and at a bombsite, plant
if (me->HasC4() && me->IsAtBombsite())
{
// plant it
me->SetTask( CCSBot::PLANT_BOMB );
me->PlantBomb();
// radio to the team
me->GetChatter()->PlantingTheBomb( me->GetPlace() );
return;
}
// look around
me->UpdateLookAround();
// if we are moving, we are not idle
if (me->IsNotMoving() == false)
m_idleTimer.Start( RandomFloat( 2.0f, 5.0f ) );
// compute the leader's speed
Vector leaderVel = m_leader->GetAbsVelocity();
float leaderSpeed = Vector2D( leaderVel.x, leaderVel.y ).Length();
// determine our leader's movement state
ComputeLeaderMotionState( leaderSpeed );
// track whether we can see the leader
bool isLeaderVisible;
Vector leaderOrigin = GetCentroid( m_leader );
if (me->IsVisible( leaderOrigin ))
{
m_lastSawLeaderTime = gpGlobals->curtime;
isLeaderVisible = true;
}
else
{
isLeaderVisible = false;
}
// determine whether we should sneak or not
const float farAwayRange = 750.0f;
Vector myOrigin = GetCentroid( me );
if ((leaderOrigin - myOrigin).IsLengthGreaterThan( farAwayRange ))
{
// far away from leader - run to catch up
m_isSneaking = false;
}
else if (isLeaderVisible)
{
// if we see leader walking and we are nearby, walk
if (m_leaderMotionState == WALKING)
m_isSneaking = true;
// if we are sneaking and our leader starts running, stop sneaking
if (m_isSneaking && m_leaderMotionState == RUNNING)
m_isSneaking = false;
}
// if we haven't seen the leader for a long time, run
const float longTime = 20.0f;
if (gpGlobals->curtime - m_lastSawLeaderTime > longTime)
m_isSneaking = false;
if (m_isSneaking)
me->Walk();
else
me->Run();
bool repath = false;
// if the leader has stopped, hide nearby
const float nearLeaderRange = 250.0f;
if (!me->HasPath() && m_leaderMotionState == STOPPED && m_leaderMotionStateTime.GetElapsedTime() > m_waitTime)
{
// throttle how often this check occurs
m_waitTime += RandomFloat( 1.0f, 3.0f );
// the leader has stopped - if we are close to him, take up a hiding spot
if ((leaderOrigin - myOrigin).IsLengthLessThan( nearLeaderRange ))
{
const float hideRange = 250.0f;
if (me->TryToHide( NULL, -1.0f, hideRange, false, USE_NEAREST ))
{
me->ResetStuckMonitor();
return;
}
}
}
// if we have been idle for awhile, move
if (m_idleTimer.IsElapsed())
{
repath = true;
// always walk when we move such a short distance
m_isSneaking = true;
}
// if our leader has moved, repath (don't repath if leading is stopping)
if (leaderSpeed > 100.0f && m_leaderMotionState != STOPPED)
{
repath = true;
}
// move along our path
if (me->UpdatePathMovement( NO_SPEED_CHANGE ) != CCSBot::PROGRESSING)
{
me->DestroyPath();
}
// recompute our path if necessary
if (repath && m_repathInterval.IsElapsed() && !me->IsOnLadder())
{
// recompute our path to keep us near our leader
m_lastLeaderPos = leaderOrigin;
me->ResetStuckMonitor();
const float runSpeed = 200.0f;
const float collectRange = (leaderSpeed > runSpeed) ? 600.0f : 400.0f; // 400, 200
FollowTargetCollector collector( m_leader );
SearchSurroundingAreas( TheNavMesh->GetNearestNavArea( m_lastLeaderPos ), m_lastLeaderPos, collector, collectRange );
if (cv_bot_debug.GetBool())
{
for( int i=0; i<collector.m_targetAreaCount; ++i )
collector.m_targetArea[i]->Draw( /*255, 0, 0, 2*/ );
}
// move to one of the collected areas
if (collector.m_targetAreaCount)
{
CNavArea *target = NULL;
Vector targetPos;
// if we are idle, pick a random area
if (m_idleTimer.IsElapsed())
{
target = collector.m_targetArea[ RandomInt( 0, collector.m_targetAreaCount-1 ) ];
targetPos = target->GetCenter();
me->PrintIfWatched( "%4.1f: Bored. Repathing to a new nearby area\n", gpGlobals->curtime );
}
else
{
me->PrintIfWatched( "%4.1f: Repathing to stay with leader.\n", gpGlobals->curtime );
// find closest area to where we are
CNavArea *area;
float closeRangeSq = 9999999999.9f;
Vector close;
for( int a=0; a<collector.m_targetAreaCount; ++a )
{
area = collector.m_targetArea[a];
area->GetClosestPointOnArea( myOrigin, &close );
float rangeSq = (myOrigin - close).LengthSqr();
if (rangeSq < closeRangeSq)
{
target = area;
targetPos = close;
closeRangeSq = rangeSq;
}
}
}
if (target == NULL || me->ComputePath( target->GetCenter(), FASTEST_ROUTE ) == NULL)
me->PrintIfWatched( "Pathfind to leader failed.\n" );
// throttle how often we repath
m_repathInterval.Start( 0.5f );
m_idleTimer.Reset();
}
}
}
//--------------------------------------------------------------------------------------------------------------
void FollowState::OnExit( CCSBot *me )
{
}
@@ -0,0 +1,549 @@
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
// Author: Michael S. Booth (mike@turtlerockstudios.com), 2003
#include "cbase.h"
#include "cs_simple_hostage.h"
#include "cs_bot.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
//--------------------------------------------------------------------------------------------------------------
/**
* Begin moving to a nearby hidey-hole.
* NOTE: Do not forget this state may include a very long "move-to" time to get to our hidey spot!
*/
void HideState::OnEnter( CCSBot *me )
{
m_isAtSpot = false;
m_isLookingOutward = false;
// if duration is "infinite", set it to a reasonably long time to prevent infinite camping
if (m_duration < 0.0f)
{
m_duration = RandomFloat( 30.0f, 60.0f );
}
// decide whether to "ambush" or not - never set to false so as not to override external setting
if (RandomFloat( 0.0f, 100.0f ) < 50.0f)
{
m_isHoldingPosition = true;
}
// if we are holding position, decide for how long
if (m_isHoldingPosition)
{
m_holdPositionTime = RandomFloat( 3.0f, 10.0f );
}
else
{
m_holdPositionTime = 0.0f;
}
m_heardEnemy = false;
m_firstHeardEnemyTime = 0.0f;
m_retry = 0;
if ( me->IsFollowing() && me->GetFollowLeader() )
{
m_leaderAnchorPos = GetCentroid( me->GetFollowLeader() );
}
// if we are a sniper, we need to periodically pause while we retreat to squeeze off a shot or two
if (me->IsSniper())
{
// start off paused to allow a final shot before retreating
m_isPaused = false;
m_pauseTimer.Invalidate();
}
}
//--------------------------------------------------------------------------------------------------------------
/**
* Move to a nearby hidey-hole.
* NOTE: Do not forget this state may include a very long "move-to" time to get to our hidey spot!
*/
void HideState::OnUpdate( CCSBot *me )
{
Vector myOrigin = GetCentroid( me );
// wait until finished reloading to leave hide state
if (!me->IsReloading())
{
// if we are momentarily hiding while following someone, check to see if he has moved on
if ( me->IsFollowing() && me->GetFollowLeader() )
{
CCSPlayer *leader = static_cast<CCSPlayer *>( static_cast<CBaseEntity *>( me->GetFollowLeader() ) );
Vector leaderOrigin = GetCentroid( leader );
// BOTPORT: Determine walk/run velocity thresholds
float runThreshold = 200.0f;
if (leader->GetAbsVelocity().IsLengthGreaterThan( runThreshold ))
{
// leader is running, stay with him
me->Follow( leader );
return;
}
// if leader has moved, stay with him
const float followRange = 250.0f;
if ((m_leaderAnchorPos - leaderOrigin).IsLengthGreaterThan( followRange ))
{
me->Follow( leader );
return;
}
}
// if we see a nearby buddy in combat, join him
/// @todo - Perhaps tie in to TakeDamage(), so it works for human players, too
//
// Scenario logic
//
switch( TheCSBots()->GetScenario() )
{
case CCSBotManager::SCENARIO_DEFUSE_BOMB:
{
if (me->GetTeamNumber() == TEAM_CT)
{
// if we are just holding position (due to a radio order) and the bomb has just planted, go defuse it
if (me->GetTask() == CCSBot::HOLD_POSITION &&
TheCSBots()->IsBombPlanted() &&
TheCSBots()->GetBombPlantTimestamp() > me->GetStateTimestamp())
{
me->Idle();
return;
}
// if we are guarding the defuser and he dies/gives up, stop hiding (to choose another defuser)
if (me->GetTask() == CCSBot::GUARD_BOMB_DEFUSER && TheCSBots()->GetBombDefuser() == NULL)
{
me->Idle();
return;
}
// if we are guarding the loose bomb and it is picked up, stop hiding
if (me->GetTask() == CCSBot::GUARD_LOOSE_BOMB && TheCSBots()->GetLooseBomb() == NULL)
{
me->GetChatter()->TheyPickedUpTheBomb();
me->Idle();
return;
}
// if we are guarding a bombsite and the bomb is dropped and we hear about it, stop guarding
if (me->GetTask() == CCSBot::GUARD_BOMB_ZONE && me->GetGameState()->IsLooseBombLocationKnown())
{
me->Idle();
return;
}
// if we are guarding (bombsite, initial encounter, etc) and the bomb is planted, go defuse it
if (me->IsDoingScenario() && me->GetTask() != CCSBot::GUARD_BOMB_DEFUSER && TheCSBots()->IsBombPlanted())
{
me->Idle();
return;
}
}
else // TERRORIST
{
// if we are near the ticking bomb and someone starts defusing it, attack!
if (TheCSBots()->GetBombDefuser())
{
Vector defuserOrigin = GetCentroid( TheCSBots()->GetBombDefuser() );
Vector toDefuser = defuserOrigin - myOrigin;
const float hearDefuseRange = 2000.0f;
if (toDefuser.IsLengthLessThan( hearDefuseRange ))
{
// if we are nearby, attack, otherwise move to the bomb (which will cause us to attack when we see defuser)
if (me->CanSeePlantedBomb())
{
me->Attack( TheCSBots()->GetBombDefuser() );
}
else
{
me->MoveTo( defuserOrigin, FASTEST_ROUTE );
me->InhibitLookAround( 10.0f );
}
return;
}
}
}
break;
}
//--------------------------------------------------------------------------------------------------
case CCSBotManager::SCENARIO_RESCUE_HOSTAGES:
{
// if we're guarding the hostages and they all die or are taken, do something else
if (me->GetTask() == CCSBot::GUARD_HOSTAGES)
{
if (me->GetGameState()->AreAllHostagesBeingRescued() || me->GetGameState()->AreAllHostagesGone())
{
me->Idle();
return;
}
}
else if (me->GetTask() == CCSBot::GUARD_HOSTAGE_RESCUE_ZONE)
{
// if we stumble across a hostage, guard it
CHostage *hostage = me->GetGameState()->GetNearestVisibleFreeHostage();
if (hostage)
{
// we see a free hostage, guard it
Vector hostageOrigin = GetCentroid( hostage );
CNavArea *area = TheNavMesh->GetNearestNavArea( hostageOrigin );
if (area)
{
me->SetTask( CCSBot::GUARD_HOSTAGES );
me->Hide( area );
me->PrintIfWatched( "I'm guarding hostages I found\n" );
// don't chatter here - he'll tell us when he's in his hiding spot
return;
}
}
}
}
}
bool isSettledInSniper = (me->IsSniper() && m_isAtSpot) ? true : false;
// only investigate noises if we are initiating attacks, and we aren't a "settled in" sniper
// dont investigate noises if we are reloading
if (!me->IsReloading() &&
!isSettledInSniper &&
me->GetDisposition() == CCSBot::ENGAGE_AND_INVESTIGATE)
{
// if we are holding position, and have heard the enemy nearby, investigate after our hold time is up
if (m_isHoldingPosition && m_heardEnemy && (gpGlobals->curtime - m_firstHeardEnemyTime > m_holdPositionTime))
{
/// @todo We might need to remember specific location of last enemy noise here
me->InvestigateNoise();
return;
}
// investigate nearby enemy noises
if (me->HeardInterestingNoise())
{
// if we are holding position, check if enough time has elapsed since we first heard the enemy
if (m_isAtSpot && m_isHoldingPosition)
{
if (!m_heardEnemy)
{
// first time we heard the enemy
m_heardEnemy = true;
m_firstHeardEnemyTime = gpGlobals->curtime;
me->PrintIfWatched( "Heard enemy, holding position for %f2.1 seconds...\n", m_holdPositionTime );
}
}
else
{
// not holding position - investigate enemy noise
me->InvestigateNoise();
return;
}
}
}
} // end reloading check
// look around
me->UpdateLookAround();
// if we are at our hiding spot, crouch and wait
if (m_isAtSpot)
{
me->ResetStuckMonitor();
CNavArea *area = TheNavMesh->GetNavArea( m_hidingSpot );
if ( !area || !( area->GetAttributes() & NAV_MESH_STAND ) )
{
me->Crouch();
}
// check if duration has expired
if (m_hideTimer.IsElapsed())
{
if (me->GetTask() == CCSBot::GUARD_LOOSE_BOMB)
{
// if we're guarding the loose bomb, continue to guard it but pick a new spot
me->Hide( TheCSBots()->GetLooseBombArea() );
return;
}
else if (me->GetTask() == CCSBot::GUARD_BOMB_ZONE)
{
// if we're guarding a bombsite, continue to guard it but pick a new spot
const CCSBotManager::Zone *zone = TheCSBots()->GetClosestZone( myOrigin );
if (zone)
{
CNavArea *area = TheCSBots()->GetRandomAreaInZone( zone );
if (area)
{
me->Hide( area );
return;
}
}
}
else if (me->GetTask() == CCSBot::GUARD_HOSTAGE_RESCUE_ZONE)
{
// if we're guarding a rescue zone, continue to guard this or another rescue zone
if (me->GuardRandomZone())
{
me->SetTask( CCSBot::GUARD_HOSTAGE_RESCUE_ZONE );
me->PrintIfWatched( "Continuing to guard hostage rescue zones\n" );
me->SetDisposition( CCSBot::OPPORTUNITY_FIRE );
me->GetChatter()->GuardingHostageEscapeZone( IS_PLAN );
return;
}
}
me->Idle();
return;
}
/*
// if we are watching for an approaching noisy enemy, anticipate and fire before they round the corner
/// @todo Need to check if we are looking at an ENEMY_NOISE here
const float veryCloseNoise = 250.0f;
if (me->IsLookingAtSpot() && me->GetNoiseRange() < veryCloseNoise)
{
// fire!
me->PrimaryAttack();
me->PrintIfWatched( "Firing at anticipated enemy coming around the corner!\n" );
}
*/
// if we have a shield, hide behind it
if (me->HasShield() && !me->IsProtectedByShield())
me->SecondaryAttack();
// while sitting at our hiding spot, if we are being attacked but can't see our attacker, move somewhere else
const float hurtRecentlyTime = 1.0f;
if (!me->IsEnemyVisible() && me->GetTimeSinceAttacked() < hurtRecentlyTime)
{
me->Idle();
return;
}
// encourage the human player
if (!me->IsDoingScenario())
{
if (me->GetTeamNumber() == TEAM_CT)
{
if (me->GetTask() == CCSBot::GUARD_BOMB_ZONE &&
me->IsAtHidingSpot() &&
TheCSBots()->IsBombPlanted())
{
if (me->GetNearbyEnemyCount() == 0)
{
const float someTime = 30.0f;
const float littleTime = 11.0;
if (TheCSBots()->GetBombTimeLeft() > someTime)
me->GetChatter()->Encourage( "BombsiteSecure", RandomFloat( 10.0f, 15.0f ) );
else if (TheCSBots()->GetBombTimeLeft() > littleTime)
me->GetChatter()->Encourage( "WaitingForHumanToDefuseBomb", RandomFloat( 5.0f, 8.0f ) );
else
me->GetChatter()->Encourage( "WaitingForHumanToDefuseBombPanic", RandomFloat( 3.0f, 4.0f ) );
}
}
if (me->GetTask() == CCSBot::GUARD_HOSTAGES && me->IsAtHidingSpot())
{
if (me->GetNearbyEnemyCount() == 0)
{
CHostage *hostage = me->GetGameState()->GetNearestVisibleFreeHostage();
if (hostage)
{
me->GetChatter()->Encourage( "WaitingForHumanToRescueHostages", RandomFloat( 10.0f, 15.0f ) );
}
}
}
}
}
}
else
{
// we are moving to our hiding spot
// snipers periodically pause and fire while retreating
if (me->IsSniper() && me->IsEnemyVisible())
{
if (m_isPaused)
{
if (m_pauseTimer.IsElapsed())
{
// get moving
m_isPaused = false;
m_pauseTimer.Start( RandomFloat( 1.0f, 3.0f ) );
}
else
{
me->Wait( 0.2f );
}
}
else
{
if (m_pauseTimer.IsElapsed())
{
// pause for a moment
m_isPaused = true;
m_pauseTimer.Start( RandomFloat( 0.5f, 1.5f ) );
}
}
}
// if a Player is using this hiding spot, give up
float range;
CCSPlayer *camper = static_cast<CCSPlayer *>( UTIL_GetClosestPlayer( m_hidingSpot, &range ) );
const float closeRange = 75.0f;
if (camper && camper != me && range < closeRange && me->IsVisible( camper, CHECK_FOV ))
{
// player is in our hiding spot
me->PrintIfWatched( "Someone's in my hiding spot - picking another...\n" );
const int maxRetries = 3;
if (m_retry++ >= maxRetries)
{
me->PrintIfWatched( "Can't find a free hiding spot, giving up.\n" );
me->Idle();
return;
}
// pick another hiding spot near where we were planning on hiding
me->Hide( TheNavMesh->GetNavArea( m_hidingSpot ) );
return;
}
Vector toSpot;
toSpot.x = m_hidingSpot.x - myOrigin.x;
toSpot.y = m_hidingSpot.y - myOrigin.y;
toSpot.z = m_hidingSpot.z - me->GetFeetZ(); // use feet location
range = toSpot.Length();
// look outwards as we get close to our hiding spot
if (!me->IsEnemyVisible() && !m_isLookingOutward)
{
const float lookOutwardRange = 200.0f;
const float nearSpotRange = 10.0f;
if (range < lookOutwardRange && range > nearSpotRange)
{
m_isLookingOutward = true;
toSpot.x /= range;
toSpot.y /= range;
toSpot.z /= range;
me->SetLookAt( "Face outward", me->EyePosition() - 1000.0f * toSpot, PRIORITY_HIGH, 3.0f );
}
}
const float atDist = 20.0f;
if (range < atDist)
{
//-------------------------------------
// Just reached our hiding spot
//
m_isAtSpot = true;
m_hideTimer.Start( m_duration );
// make sure our approach points are valid, since we'll be watching them
me->ComputeApproachPoints();
me->ClearLookAt();
// ready our weapon and prepare to attack
me->EquipBestWeapon( me->IsUsingGrenade() );
me->SetDisposition( CCSBot::OPPORTUNITY_FIRE );
// if we are a sniper, update our task
if (me->GetTask() == CCSBot::MOVE_TO_SNIPER_SPOT)
{
me->SetTask( CCSBot::SNIPING );
}
else if (me->GetTask() == CCSBot::GUARD_INITIAL_ENCOUNTER)
{
const float campChatterChance = 20.0f;
if (RandomFloat( 0, 100 ) < campChatterChance)
{
me->GetChatter()->Say( "WaitingHere" );
}
}
// determine which way to look
trace_t result;
float outAngle = 0.0f;
float outAngleRange = 0.0f;
for( float angle = 0.0f; angle < 360.0f; angle += 45.0f )
{
UTIL_TraceLine( me->EyePosition(), me->EyePosition() + 1000.0f * Vector( BotCOS(angle), BotSIN(angle), 0.0f ), MASK_PLAYERSOLID, me, COLLISION_GROUP_NONE, &result );
if (result.fraction > outAngleRange)
{
outAngle = angle;
outAngleRange = result.fraction;
}
}
me->SetLookAheadAngle( outAngle );
}
// move to hiding spot
if (me->UpdatePathMovement() != CCSBot::PROGRESSING && !m_isAtSpot)
{
// we couldn't get to our hiding spot - pick another
me->PrintIfWatched( "Can't get to my hiding spot - finding another...\n" );
// search from hiding spot, since we know it was valid
const Vector *pos = FindNearbyHidingSpot( me, m_hidingSpot, m_range, me->IsSniper() );
if (pos == NULL)
{
// no available hiding spots
me->PrintIfWatched( "No available hiding spots - hiding where I'm at.\n" );
// hide where we are
m_hidingSpot.x = myOrigin.x;
m_hidingSpot.x = myOrigin.y;
m_hidingSpot.z = me->GetFeetZ();
}
else
{
m_hidingSpot = *pos;
}
// build a path to our new hiding spot
if (me->ComputePath( m_hidingSpot, FASTEST_ROUTE ) == false)
{
me->PrintIfWatched( "Can't pathfind to hiding spot\n" );
me->Idle();
return;
}
}
}
}
//--------------------------------------------------------------------------------------------------------------
void HideState::OnExit( CCSBot *me )
{
m_isHoldingPosition = false;
me->StandUp();
me->ResetStuckMonitor();
//me->ClearLookAt();
me->ClearApproachPoints();
// if we have a shield, put it away
if (me->HasShield() && me->IsProtectedByShield())
me->SecondaryAttack();
}
@@ -0,0 +1,237 @@
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
// Author: Michael S. Booth (mike@turtlerockstudios.com), 2003
#include "cbase.h"
#include "cs_simple_hostage.h"
#include "cs_bot.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
//--------------------------------------------------------------------------------------------------------------
/**
* Begin the hunt
*/
void HuntState::OnEnter( CCSBot *me )
{
// lurking death
if (me->IsUsingKnife() && me->IsWellPastSafe() && !me->IsHurrying())
me->Walk();
else
me->Run();
me->StandUp();
me->SetDisposition( CCSBot::ENGAGE_AND_INVESTIGATE );
me->SetTask( CCSBot::SEEK_AND_DESTROY );
me->DestroyPath();
}
//--------------------------------------------------------------------------------------------------------------
/**
* Hunt down our enemies
*/
void HuntState::OnUpdate( CCSBot *me )
{
// if we've been hunting for a long time, drop into Idle for a moment to
// select something else to do
const float huntingTooLongTime = 30.0f;
if (gpGlobals->curtime - me->GetStateTimestamp() > huntingTooLongTime)
{
// stop being a rogue and do the scenario, since there must not be many enemies left to hunt
me->PrintIfWatched( "Giving up hunting.\n" );
me->SetRogue( false );
me->Idle();
return;
}
// scenario logic
if (TheCSBots()->GetScenario() == CCSBotManager::SCENARIO_DEFUSE_BOMB)
{
if (me->GetTeamNumber() == TEAM_TERRORIST)
{
// if we have the bomb and it's time to plant, or we happen to be in a bombsite and it seems safe, do it
if (me->HasC4())
{
const float safeTime = 3.0f;
if (TheCSBots()->IsTimeToPlantBomb() ||
(me->IsAtBombsite() && gpGlobals->curtime - me->GetLastSawEnemyTimestamp() > safeTime))
{
me->Idle();
return;
}
}
// if we notice the bomb lying on the ground, go get it
if (me->NoticeLooseBomb())
{
me->FetchBomb();
return;
}
// if bomb has been planted, and we hear it, move to a hiding spot near the bomb and watch it
const Vector *bombPos = me->GetGameState()->GetBombPosition();
if (!me->IsRogue() && me->GetGameState()->IsBombPlanted() && bombPos)
{
me->SetTask( CCSBot::GUARD_TICKING_BOMB );
me->Hide( TheNavMesh->GetNavArea( *bombPos ) );
return;
}
}
else // CT
{
if (!me->IsRogue() && me->CanSeeLooseBomb())
{
// if we are near the loose bomb and can see it, hide nearby and guard it
me->SetTask( CCSBot::GUARD_LOOSE_BOMB );
me->Hide( TheCSBots()->GetLooseBombArea() );
me->GetChatter()->GuardingLooseBomb( TheCSBots()->GetLooseBomb() );
return;
}
else if (TheCSBots()->IsBombPlanted())
{
// rogues will defuse a bomb, but not guard the defuser
if (!me->IsRogue() || !TheCSBots()->GetBombDefuser())
{
// search for the planted bomb to defuse
me->Idle();
return;
}
}
}
}
else if (TheCSBots()->GetScenario() == CCSBotManager::SCENARIO_RESCUE_HOSTAGES)
{
if (me->GetTeamNumber() == TEAM_TERRORIST)
{
if (me->GetGameState()->AreAllHostagesBeingRescued())
{
// all hostages are being rescued, head them off at the escape zones
if (me->GuardRandomZone())
{
me->SetTask( CCSBot::GUARD_HOSTAGE_RESCUE_ZONE );
me->PrintIfWatched( "Trying to beat them to an escape zone!\n" );
me->SetDisposition( CCSBot::OPPORTUNITY_FIRE );
me->GetChatter()->GuardingHostageEscapeZone( IS_PLAN );
return;
}
}
// if safe time is up, and we stumble across a hostage, guard it
if (!me->IsRogue() && !me->IsSafe())
{
CHostage *hostage = me->GetGameState()->GetNearestVisibleFreeHostage();
if (hostage)
{
CNavArea *area = TheNavMesh->GetNearestNavArea( GetCentroid( hostage ) );
if (area)
{
// we see a free hostage, guard it
me->SetTask( CCSBot::GUARD_HOSTAGES );
me->Hide( area );
me->PrintIfWatched( "I'm guarding hostages\n" );
me->GetChatter()->GuardingHostages( area->GetPlace(), IS_PLAN );
return;
}
}
}
}
}
// listen for enemy noises
if (me->HeardInterestingNoise())
{
me->InvestigateNoise();
return;
}
// look around
me->UpdateLookAround();
if ( !TheCSBots()->AllowedToDoExpensiveBotOperationThisFrame() )
return;
// if we have reached our destination area, pick a new one
// if our path fails, pick a new one
if (me->GetLastKnownArea() == m_huntArea || me->UpdatePathMovement() != CCSBot::PROGRESSING)
{
// pick a new hunt area
const float earlyGameTime = 45.0f;
if (TheCSBots()->GetElapsedRoundTime() < earlyGameTime && !me->HasVisitedEnemySpawn())
{
// in the early game, rush the enemy spawn
CBaseEntity *enemySpawn = TheCSBots()->GetRandomSpawn( OtherTeam( me->GetTeamNumber() ) );
//ADRIAN: REVISIT
if ( enemySpawn )
{
m_huntArea = TheNavMesh->GetNavArea( enemySpawn->WorldSpaceCenter() );
}
}
else
{
m_huntArea = NULL;
float oldest = 0.0f;
int areaCount = 0;
const float minSize = 150.0f;
FOR_EACH_VEC( TheNavAreas, it )
{
CNavArea *area = TheNavAreas[ it ];
++areaCount;
// skip the small areas
Extent extent;
area->GetExtent(&extent);
if (extent.hi.x - extent.lo.x < minSize || extent.hi.y - extent.lo.y < minSize)
continue;
// keep track of the least recently cleared area
float age = gpGlobals->curtime - area->GetClearedTimestamp( me->GetTeamNumber()-1 );
if (age > oldest)
{
oldest = age;
m_huntArea = area;
}
}
// if all the areas were too small, pick one at random
int which = RandomInt( 0, areaCount-1 );
areaCount = 0;
FOR_EACH_VEC( TheNavAreas, hit )
{
m_huntArea = TheNavAreas[ hit ];
if (which == areaCount)
break;
--which;
}
}
if (m_huntArea)
{
// create a new path to a far away area of the map
me->ComputePath( m_huntArea->GetCenter() );
}
}
}
//--------------------------------------------------------------------------------------------------------------
/**
* Done hunting
*/
void HuntState::OnExit( CCSBot *me )
{
}
@@ -0,0 +1,909 @@
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
// Author: Michael S. Booth (mike@turtlerockstudios.com), 2003
#include "cbase.h"
#include "cs_simple_hostage.h"
#include "cs_bot.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
// range for snipers to select a hiding spot
const float sniperHideRange = 2000.0f;
extern ConVar mp_guardian_target_site;
//--------------------------------------------------------------------------------------------------------------
/**
* The Idle state.
* We never stay in the Idle state - it is a "home base" for the state machine that
* does various checks to determine what we should do next.
*/
void IdleState::OnEnter( CCSBot *me )
{
me->DestroyPath();
me->SetBotEnemy( NULL );
// lurking death
if (me->IsUsingKnife() && me->IsWellPastSafe() && !me->IsHurrying())
me->Walk();
//
// Since Idle assigns tasks, we assume that coming back to Idle means our task is complete
//
me->SetTask( CCSBot::SEEK_AND_DESTROY );
me->SetDisposition( CCSBot::ENGAGE_AND_INVESTIGATE );
}
//--------------------------------------------------------------------------------------------------------------
/**
* Determine what we should do next
*/
void IdleState::OnUpdate( CCSBot *me )
{
// all other states assume GetLastKnownArea() is valid, ensure that it is
if (me->GetLastKnownArea() == NULL && me->StayOnNavMesh() == false)
return;
// zombies never leave the Idle state
if (cv_bot_zombie.GetBool())
{
me->ResetStuckMonitor();
return;
}
// if we are in the early "safe" time, grab a knife or grenade
if (me->IsSafe())
{
// if we have a grenade, use it
if (!me->EquipGrenade())
{
// high-skill bots run with the knife
if (me->GetProfile()->GetSkill() > 0.33f)
{
me->EquipKnife();
}
}
}
// if round is over, hunt
if (me->GetGameState()->IsRoundOver())
{
// if we are escorting hostages, try to get to the rescue zone
if (me->GetHostageEscortCount())
{
const CCSBotManager::Zone *zone = TheCSBots()->GetClosestZone( me->GetLastKnownArea(), PathCost( me, FASTEST_ROUTE ) );
const Vector *zonePos = TheCSBots()->GetRandomPositionInZone( zone );
if (zonePos)
{
me->SetTask( CCSBot::RESCUE_HOSTAGES );
me->Run();
me->SetDisposition( CCSBot::SELF_DEFENSE );
me->MoveTo( *zonePos, FASTEST_ROUTE );
me->PrintIfWatched( "Trying to rescue hostages at the end of the round\n" );
return;
}
}
me->Hunt();
return;
}
const float defenseSniperCampChance = 75.0f;
const float offenseSniperCampChance = 10.0f;
// if we were following someone, continue following them
if (me->IsFollowing())
{
me->ContinueFollowing();
return;
}
if ( CSGameRules()->IsPlayingCooperativeGametype() )
{
UpdateCoop( me );
return;
}
//
// Scenario logic
//
switch (TheCSBots()->GetScenario())
{
//======================================================================================================
case CCSBotManager::SCENARIO_DEFUSE_BOMB:
{
// if this is a bomb game and we have the bomb, go plant it
if (me->GetTeamNumber() == TEAM_TERRORIST)
{
if (me->GetGameState()->IsBombPlanted())
{
if (me->GetGameState()->GetPlantedBombsite() != CSGameState::UNKNOWN)
{
// T's always know where the bomb is - go defend it
const CCSBotManager::Zone *zone = TheCSBots()->GetZone( me->GetGameState()->GetPlantedBombsite() );
if (zone)
{
me->SetTask( CCSBot::GUARD_TICKING_BOMB );
Place place = TheNavMesh->GetPlace( zone->m_center );
if (place != UNDEFINED_PLACE)
{
// pick a random hiding spot in this place
const Vector *spot = FindRandomHidingSpot( me, place, me->IsSniper() );
if (spot)
{
me->Hide( *spot );
return;
}
}
// hide nearby
me->Hide( TheNavMesh->GetNearestNavArea( zone->m_center ) );
return;
}
}
else
{
// ask our teammates where the bomb is
me->GetChatter()->RequestBombLocation();
// we dont know where the bomb is - we must search the bombsites
int zoneIndex = me->GetGameState()->GetNextBombsiteToSearch();
// move to bombsite - if we reach it, we'll update its cleared status, causing us to select another
const Vector *pos = TheCSBots()->GetRandomPositionInZone( TheCSBots()->GetZone( zoneIndex ) );
if (pos)
{
me->SetTask( CCSBot::FIND_TICKING_BOMB );
me->MoveTo( *pos );
return;
}
}
}
else if (me->HasC4())
{
// always pick a random spot to plant in case the spot we'd picked is inaccessible
if (TheCSBots()->IsTimeToPlantBomb())
{
// move to the closest bomb site
const CCSBotManager::Zone *zone = TheCSBots()->GetClosestZone( me->GetLastKnownArea(), PathCost( me ) );
if (zone)
{
// pick a random spot within the bomb zone
const Vector *pos = TheCSBots()->GetRandomPositionInZone( zone );
if (pos)
{
// move to bombsite
me->SetTask( CCSBot::PLANT_BOMB );
me->Run();
me->MoveTo( *pos );
return;
}
}
}
}
else
{
// at the start of the round, we may decide to defend "initial encounter" areas
// where we will first meet the enemy rush
if (me->IsSafe())
{
float defendRushChance = -17.0f * (me->GetMorale() - 2);
if (me->IsSniper() || RandomFloat( 0.0f, 100.0f ) < defendRushChance)
{
if (me->MoveToInitialEncounter())
{
me->PrintIfWatched( "I'm guarding an initial encounter area\n" );
me->SetTask( CCSBot::GUARD_INITIAL_ENCOUNTER );
me->SetDisposition( CCSBot::OPPORTUNITY_FIRE );
return;
}
}
}
// small chance of sniper camping on offense, if we aren't carrying the bomb
if (me->GetFriendsRemaining() && me->IsSniper() && RandomFloat( 0, 100.0f ) < offenseSniperCampChance)
{
me->SetTask( CCSBot::MOVE_TO_SNIPER_SPOT );
me->Hide( me->GetLastKnownArea(), RandomFloat( 10.0f, 30.0f ), sniperHideRange );
me->SetDisposition( CCSBot::OPPORTUNITY_FIRE );
me->PrintIfWatched( "Sniping!\n" );
return;
}
// if the bomb is loose (on the ground), go get it
if (me->NoticeLooseBomb())
{
me->FetchBomb();
return;
}
// if bomb has been planted, and we hear it, move to a hiding spot near the bomb and guard it
if (!me->IsRogue() && me->GetGameState()->IsBombPlanted() && me->GetGameState()->GetBombPosition())
{
const Vector *bombPos = me->GetGameState()->GetBombPosition();
if (bombPos)
{
me->SetTask( CCSBot::GUARD_TICKING_BOMB );
me->Hide( TheNavMesh->GetNavArea( *bombPos ) );
return;
}
}
}
}
else // CT ------------------------------------------------------------------------------------------
{
if (me->GetGameState()->IsBombPlanted())
{
// if the bomb has been planted, attempt to defuse it
const Vector *bombPos = me->GetGameState()->GetBombPosition();
if (bombPos)
{
// if someone is defusing the bomb, guard them
if (TheCSBots()->GetBombDefuser())
{
if (!me->IsRogue())
{
me->SetTask( CCSBot::GUARD_BOMB_DEFUSER );
me->Hide( TheNavMesh->GetNavArea( *bombPos ) );
me->SetDisposition( CCSBot::OPPORTUNITY_FIRE );
return;
}
}
else if (me->IsDoingScenario())
{
// move to the bomb and defuse it
me->SetTask( CCSBot::DEFUSE_BOMB );
me->SetDisposition( CCSBot::SELF_DEFENSE );
me->MoveTo( *bombPos );
return;
}
else
{
// we're not allowed to defuse, guard the bomb zone
me->SetTask( CCSBot::GUARD_BOMB_ZONE );
me->Hide( TheNavMesh->GetNavArea( *bombPos ) );
me->SetDisposition( CCSBot::OPPORTUNITY_FIRE );
return;
}
}
else if (me->GetGameState()->GetPlantedBombsite() != CSGameState::UNKNOWN)
{
// we know which bombsite, but not exactly where the bomb is, go there
const CCSBotManager::Zone *zone = TheCSBots()->GetZone( me->GetGameState()->GetPlantedBombsite() );
if (zone)
{
if (me->IsDoingScenario())
{
me->SetTask( CCSBot::DEFUSE_BOMB );
me->MoveTo( zone->m_center );
me->SetDisposition( CCSBot::OPPORTUNITY_FIRE );
return;
}
else
{
// we're not allowed to defuse, guard the bomb zone
me->SetTask( CCSBot::GUARD_BOMB_ZONE );
me->Hide( TheNavMesh->GetNavArea( zone->m_center ) );
me->SetDisposition( CCSBot::OPPORTUNITY_FIRE );
return;
}
}
}
else
{
// we dont know where the bomb is - we must search the bombsites
// find closest un-cleared bombsite
const CCSBotManager::Zone *zone = NULL;
float travelDistance = 9999999.9f;
for( int z=0; z<TheCSBots()->GetZoneCount(); ++z )
{
if (TheCSBots()->GetZone(z)->m_areaCount == 0)
continue;
// don't check bombsites that have been cleared
if (me->GetGameState()->IsBombsiteClear( z ))
continue;
// just use the first overlapping nav area as a reasonable approximation
ShortestPathCost cost = ShortestPathCost();
float dist = NavAreaTravelDistance( me->GetLastKnownArea(),
TheNavMesh->GetNearestNavArea( TheCSBots()->GetZone(z)->m_center ),
cost );
if (dist >= 0.0f && dist < travelDistance)
{
zone = TheCSBots()->GetZone(z);
travelDistance = dist;
}
}
if (zone)
{
const float farAwayRange = 2000.0f;
if (travelDistance > farAwayRange)
{
zone = NULL;
}
}
// if closest bombsite is "far away", pick one at random
if (zone == NULL)
{
int zoneIndex = me->GetGameState()->GetNextBombsiteToSearch();
zone = TheCSBots()->GetZone( zoneIndex );
}
// move to bombsite - if we reach it, we'll update its cleared status, causing us to select another
if (zone)
{
const Vector *pos = TheCSBots()->GetRandomPositionInZone( zone );
if (pos)
{
me->SetTask( CCSBot::FIND_TICKING_BOMB );
me->MoveTo( *pos );
return;
}
}
}
AssertMsg( 0, "A CT bot doesn't know what to do while the bomb is planted!\n" );
}
// if we have a sniper rifle, we like to camp, whether rogue or not
if (me->IsSniper() && !me->IsSafe())
{
if (RandomFloat( 0, 100 ) <= defenseSniperCampChance)
{
CNavArea *snipingArea = NULL;
// if the bomb is loose, snipe near it
const Vector *bombPos = me->GetGameState()->GetBombPosition();
if (me->GetGameState()->IsLooseBombLocationKnown() && bombPos)
{
snipingArea = TheNavMesh->GetNearestNavArea( *bombPos );
me->PrintIfWatched( "Sniping near loose bomb\n" );
}
else
{
// snipe bomb zone(s)
const CCSBotManager::Zone *zone = TheCSBots()->GetRandomZone();
if (zone)
{
snipingArea = TheCSBots()->GetRandomAreaInZone( zone );
me->PrintIfWatched( "Sniping near bombsite\n" );
}
}
if (snipingArea)
{
me->SetTask( CCSBot::MOVE_TO_SNIPER_SPOT );
me->Hide( snipingArea, -1.0, sniperHideRange );
me->SetDisposition( CCSBot::OPPORTUNITY_FIRE );
return;
}
}
}
// rogues just hunt, unless they want to snipe
// if the whole team has decided to rush, hunt
// if we know the bomb is dropped, hunt for enemies and the loose bomb
if (me->IsRogue() || TheCSBots()->IsDefenseRushing() || me->GetGameState()->IsLooseBombLocationKnown())
{
me->Hunt();
return;
}
// the lower our morale gets, the more we want to camp the bomb zone(s)
// only decide to camp at the start of the round, or if we haven't seen anything for a long time
if (me->IsSafe() || me->HasNotSeenEnemyForLongTime())
{
float guardBombsiteChance = -34.0f * me->GetMorale();
if (RandomFloat( 0.0f, 100.0f ) < guardBombsiteChance)
{
float guardRange = 500.0f + 100.0f * (me->GetMorale() + 3);
// guard bomb zone(s)
const CCSBotManager::Zone *zone = TheCSBots()->GetRandomZone();
if (zone)
{
CNavArea *area = TheCSBots()->GetRandomAreaInZone( zone );
if (area)
{
me->PrintIfWatched( "I'm guarding a bombsite\n" );
me->GetChatter()->GuardingBombsite( area->GetPlace() );
me->SetTask( CCSBot::GUARD_BOMB_ZONE );
me->Hide( area, -1.0, guardRange );
me->SetDisposition( CCSBot::OPPORTUNITY_FIRE );
return;
}
}
}
// at the start of the round, we may decide to defend "initial encounter" areas
// where we will first meet the enemy rush
if (me->IsSafe())
{
float defendRushChance = -17.0f * (me->GetMorale() - 2);
if (me->IsSniper() || RandomFloat( 0.0f, 100.0f ) < defendRushChance)
{
if (me->MoveToInitialEncounter())
{
me->PrintIfWatched( "I'm guarding an initial encounter area\n" );
me->SetTask( CCSBot::GUARD_INITIAL_ENCOUNTER );
me->SetDisposition( CCSBot::OPPORTUNITY_FIRE );
return;
}
}
}
}
}
break;
}
//======================================================================================================
case CCSBotManager::SCENARIO_ESCORT_VIP:
{
if (me->GetTeamNumber() == TEAM_TERRORIST)
{
// if we have a sniper rifle, we like to camp, whether rogue or not
if (me->IsSniper())
{
if (RandomFloat( 0, 100 ) <= defenseSniperCampChance)
{
// snipe escape zone(s)
const CCSBotManager::Zone *zone = TheCSBots()->GetRandomZone();
if (zone)
{
CNavArea *area = TheCSBots()->GetRandomAreaInZone( zone );
if (area)
{
me->SetTask( CCSBot::MOVE_TO_SNIPER_SPOT );
me->Hide( area, -1.0, sniperHideRange );
me->SetDisposition( CCSBot::OPPORTUNITY_FIRE );
me->PrintIfWatched( "Sniping near escape zone\n" );
return;
}
}
}
}
// rogues just hunt, unless they want to snipe
// if the whole team has decided to rush, hunt
if (me->IsRogue() || TheCSBots()->IsDefenseRushing())
break;
// the lower our morale gets, the more we want to camp the escape zone(s)
float guardEscapeZoneChance = -34.0f * me->GetMorale();
if (RandomFloat( 0.0f, 100.0f ) < guardEscapeZoneChance)
{
// guard escape zone(s)
const CCSBotManager::Zone *zone = TheCSBots()->GetRandomZone();
if (zone)
{
CNavArea *area = TheCSBots()->GetRandomAreaInZone( zone );
if (area)
{
// guard the escape zone - stay closer if our morale is low
me->SetTask( CCSBot::GUARD_VIP_ESCAPE_ZONE );
me->PrintIfWatched( "I'm guarding an escape zone\n" );
float escapeGuardRange = 750.0f + 250.0f * (me->GetMorale() + 3);
me->Hide( area, -1.0, escapeGuardRange );
me->SetDisposition( CCSBot::OPPORTUNITY_FIRE );
return;
}
}
}
}
else // CT
{
if (me->m_bIsVIP)
{
// if early in round, pick a random zone, otherwise pick closest zone
const float earlyTime = 20.0f;
const CCSBotManager::Zone *zone = NULL;
if (TheCSBots()->GetElapsedRoundTime() < earlyTime)
{
// pick random zone
zone = TheCSBots()->GetRandomZone();
}
else
{
// pick closest zone
zone = TheCSBots()->GetClosestZone( me->GetLastKnownArea(), PathCost( me ) );
}
if (zone)
{
// pick a random spot within the escape zone
const Vector *pos = TheCSBots()->GetRandomPositionInZone( zone );
if (pos)
{
// move to escape zone
me->SetTask( CCSBot::VIP_ESCAPE );
me->Run();
me->MoveTo( *pos );
// tell team to follow
const float repeatTime = 30.0f;
if (me->GetFriendsRemaining() &&
TheCSBots()->GetRadioMessageInterval( RADIO_FOLLOW_ME, me->GetTeamNumber() ) > repeatTime)
me->SendRadioMessage( RADIO_FOLLOW_ME );
return;
}
}
}
else
{
// small chance of sniper camping on offense, if we aren't VIP
if (me->GetFriendsRemaining() && me->IsSniper() && RandomFloat( 0, 100.0f ) < offenseSniperCampChance)
{
me->SetTask( CCSBot::MOVE_TO_SNIPER_SPOT );
me->Hide( me->GetLastKnownArea(), RandomFloat( 10.0f, 30.0f ), sniperHideRange );
me->SetDisposition( CCSBot::OPPORTUNITY_FIRE );
me->PrintIfWatched( "Sniping!\n" );
return;
}
}
}
break;
}
//======================================================================================================
case CCSBotManager::SCENARIO_RESCUE_HOSTAGES:
{
if (me->GetTeamNumber() == TEAM_TERRORIST)
{
bool campHostages;
// if we are in early game, camp the hostages
if (me->IsSafe())
{
campHostages = true;
}
else if (me->GetGameState()->HaveSomeHostagesBeenTaken() || me->GetGameState()->AreAllHostagesBeingRescued())
{
campHostages = false;
}
else
{
// later in the game, camp either hostages or escape zone
const float campZoneChance = 100.0f * (TheCSBots()->GetElapsedRoundTime() - me->GetSafeTime())/120.0f;
campHostages = (RandomFloat( 0, 100 ) > campZoneChance) ? true : false;
}
// if we have a sniper rifle, we like to camp, whether rogue or not
if (me->IsSniper())
{
// the at start of the round, snipe the initial rush
if (me->IsSafe())
{
if (me->MoveToInitialEncounter() && CSGameRules()->IsPlayingCooperativeGametype() == false )
{
me->PrintIfWatched( "I'm sniping an initial encounter area\n" );
me->SetTask( CCSBot::GUARD_INITIAL_ENCOUNTER );
me->SetDisposition( CCSBot::OPPORTUNITY_FIRE );
return;
}
}
if (RandomFloat( 0, 100 ) <= defenseSniperCampChance)
{
const Vector *hostagePos = me->GetGameState()->GetRandomFreeHostagePosition();
if (hostagePos && campHostages)
{
me->SetTask( CCSBot::MOVE_TO_SNIPER_SPOT );
me->PrintIfWatched( "Sniping near hostages\n" );
me->Hide( TheNavMesh->GetNearestNavArea( *hostagePos ), -1.0, sniperHideRange );
me->SetDisposition( CCSBot::OPPORTUNITY_FIRE );
return;
}
else
{
// camp the escape zone(s)
if (me->GuardRandomZone( sniperHideRange ))
{
me->SetTask( CCSBot::MOVE_TO_SNIPER_SPOT );
me->PrintIfWatched( "Sniping near a rescue zone\n" );
me->SetDisposition( CCSBot::OPPORTUNITY_FIRE );
return;
}
}
}
}
// if safe time is up, and we stumble across a hostage, guard it
if (!me->IsSafe() && !me->IsRogue())
{
CBaseEntity *hostage = me->GetGameState()->GetNearestVisibleFreeHostage();
if (hostage)
{
// we see a free hostage, guard it
CNavArea *area = TheNavMesh->GetNearestNavArea( GetCentroid( hostage ) );
if (area)
{
me->SetTask( CCSBot::GUARD_HOSTAGES );
me->Hide( area );
me->PrintIfWatched( "I'm guarding hostages I found\n" );
// don't chatter here - he'll tell us when he's in his hiding spot
return;
}
}
}
// decide if we want to hunt, or guard
const float huntChance = 70.0f + 25.0f * me->GetMorale();
// rogues just hunt, unless they want to snipe
// if the whole team has decided to rush, hunt
if (me->GetFriendsRemaining())
{
if (me->IsRogue() || TheCSBots()->IsDefenseRushing() || RandomFloat( 0, 100 ) < huntChance)
{
me->Hunt();
return;
}
}
// at the start of the round, we may decide to defend "initial encounter" areas
// where we will first meet the enemy rush
if (me->IsSafe())
{
float defendRushChance = -17.0f * (me->GetMorale() - 2);
if (me->IsSniper() || RandomFloat( 0.0f, 100.0f ) < defendRushChance)
{
if (me->MoveToInitialEncounter())
{
me->PrintIfWatched( "I'm guarding an initial encounter area\n" );
me->SetTask( CCSBot::GUARD_INITIAL_ENCOUNTER );
me->SetDisposition( CCSBot::OPPORTUNITY_FIRE );
return;
}
}
}
// decide whether to camp the hostages or the escape zones
const Vector *hostagePos = me->GetGameState()->GetRandomFreeHostagePosition();
if (hostagePos && campHostages)
{
CNavArea *area = TheNavMesh->GetNearestNavArea( *hostagePos );
if (area)
{
// guard the hostages - stay closer to hostages if our morale is low
me->SetTask( CCSBot::GUARD_HOSTAGES );
me->PrintIfWatched( "I'm guarding hostages\n" );
float hostageGuardRange = 750.0f + 250.0f * (me->GetMorale() + 3); // 2000
me->Hide( area, -1.0, hostageGuardRange );
me->SetDisposition( CCSBot::OPPORTUNITY_FIRE );
if (RandomFloat( 0, 100 ) < 50)
me->GetChatter()->GuardingHostages( area->GetPlace(), IS_PLAN );
return;
}
}
// guard rescue zone(s)
if (me->GuardRandomZone())
{
me->SetTask( CCSBot::GUARD_HOSTAGE_RESCUE_ZONE );
me->PrintIfWatched( "I'm guarding a rescue zone\n" );
me->SetDisposition( CCSBot::OPPORTUNITY_FIRE );
me->GetChatter()->GuardingHostageEscapeZone( IS_PLAN );
return;
}
}
else // CT ---------------------------------------------------------------------------------
{
// only decide to do something else if we aren't already rescuing hostages
if (!me->GetHostageEscortCount())
{
// small chance of sniper camping on offense
if (me->GetFriendsRemaining() && me->IsSniper() && RandomFloat( 0, 100.0f ) < offenseSniperCampChance)
{
if ( CSGameRules()->IsPlayingCooperativeGametype() == false )
{
me->SetTask( CCSBot::MOVE_TO_SNIPER_SPOT );
me->Hide( me->GetLastKnownArea(), RandomFloat( 10.0f, 30.0f ), sniperHideRange );
me->SetDisposition( CCSBot::OPPORTUNITY_FIRE );
me->PrintIfWatched( "Sniping!\n" );
return;
}
}
if (me->GetFriendsRemaining() && !me->GetHostageEscortCount())
{
// rogues just hunt, unless all friends are dead
// if we have friends left, we might go hunting instead of hostage rescuing
const float huntChance = 33.3f;
if (me->IsRogue() || RandomFloat( 0.0f, 100.0f ) < huntChance)
{
me->Hunt();
return;
}
}
}
// at the start of the round, we may decide to defend "initial encounter" areas
// where we will first meet the enemy rush
if (me->IsSafe())
{
float defendRushChance = -17.0f * (me->GetMorale() - 2);
if (CSGameRules()->IsPlayingCooperativeGametype() == false &&
(me->IsSniper() || RandomFloat( 0.0f, 100.0f ) < defendRushChance) )
{
if (me->MoveToInitialEncounter())
{
me->PrintIfWatched( "I'm guarding an initial encounter area\n" );
me->SetTask( CCSBot::GUARD_INITIAL_ENCOUNTER );
me->SetDisposition( CCSBot::OPPORTUNITY_FIRE );
return;
}
}
}
// look for free hostages - CT's have radar so they know where hostages are at all times
CHostage *hostage = me->GetGameState()->GetNearestFreeHostage();
// if we are not allowed to do the scenario, guard the hostages to clear the area for the human(s)
if (!me->IsDoingScenario())
{
if (hostage)
{
CNavArea *area = TheNavMesh->GetNearestNavArea( GetCentroid( hostage ) );
if (area)
{
me->SetTask( CCSBot::GUARD_HOSTAGES );
me->Hide( area );
me->PrintIfWatched( "I'm securing the hostages for a human to rescue\n" );
return;
}
}
me->Hunt();
return;
}
bool fetchHostages = false;
bool rescueHostages = false;
const CCSBotManager::Zone *zone = NULL;
me->SetGoalEntity( NULL );
// if we are escorting hostages, determine where to take them
if (me->GetHostageEscortCount())
zone = TheCSBots()->GetClosestZone( me->GetLastKnownArea(), PathCost( me, FASTEST_ROUTE ) );
// if we are escorting hostages and there are more hostages to rescue,
// determine whether it's faster to rescue the ones we have, or go get the remaining ones
if ( zone && HOSTAGE_RULE_CAN_PICKUP ) // We can only carry one hostage at a time so go ahead and rescue the one we have
{
rescueHostages = true;
}
else if (hostage)
{
Vector hostageOrigin = GetCentroid( hostage );
if (zone)
{
PathCost cost( me, FASTEST_ROUTE );
float toZone = NavAreaTravelDistance( me->GetLastKnownArea(), zone->m_area[0], cost );
float toHostage = NavAreaTravelDistance( me->GetLastKnownArea(), TheNavMesh->GetNearestNavArea( GetCentroid( hostage ) ), cost );
if (toHostage < 0.0f)
{
rescueHostages = true;
}
else
{
if (toZone < toHostage)
rescueHostages = true;
else
fetchHostages = true;
}
}
else
{
fetchHostages = true;
}
}
else if (zone)
{
rescueHostages = true;
}
if (fetchHostages)
{
// go get hostages
me->SetTask( CCSBot::COLLECT_HOSTAGES );
me->Run();
me->SetGoalEntity( hostage );
me->ResetWaitForHostagePatience();
// if we already have some hostages, move to the others by the quickest route
RouteType route = (me->GetHostageEscortCount()) ? FASTEST_ROUTE : SAFEST_ROUTE;
me->MoveTo( GetCentroid( hostage ), route );
me->PrintIfWatched( "I'm collecting hostages\n" );
return;
}
const Vector *zonePos = TheCSBots()->GetRandomPositionInZone( zone );
if (rescueHostages && zonePos)
{
me->SetTask( CCSBot::RESCUE_HOSTAGES );
me->Run();
me->SetDisposition( CCSBot::SELF_DEFENSE );
me->MoveTo( *zonePos, FASTEST_ROUTE );
me->PrintIfWatched( "I'm rescuing hostages\n" );
me->GetChatter()->EscortingHostages();
return;
}
}
break;
}
default: // deathmatch
{
// if we just spawned, cheat and make us aware of other players so players can't spawncamp us effectively
if ( me->m_spawnedTime - gpGlobals->curtime < 1.0f )
{
CUtlVector< CCSPlayer * > playerVector;
CollectPlayers( &playerVector, TEAM_ANY, COLLECT_ONLY_LIVING_PLAYERS );
for( int i=0; i<playerVector.Count(); ++i )
{
if ( me->entindex() == playerVector[i]->entindex() )
{
continue;
}
me->OnAudibleEvent( NULL, playerVector[i], 9999999.9f, PRIORITY_HIGH, true );
}
}
// sniping check
if (me->GetFriendsRemaining() && me->IsSniper() && RandomFloat( 0, 100.0f ) < offenseSniperCampChance)
{
me->SetTask( CCSBot::MOVE_TO_SNIPER_SPOT );
me->Hide( me->GetLastKnownArea(), RandomFloat( 10.0f, 30.0f ), sniperHideRange );
me->SetDisposition( CCSBot::OPPORTUNITY_FIRE );
me->PrintIfWatched( "Sniping!\n" );
return;
}
break;
}
}
// if we have nothing special to do, go hunting for enemies
me->Hunt();
}
@@ -0,0 +1,259 @@
//========= Copyright © Valve Corporation, All rights reserved. ============//
//
// Purpose: Idle update function for coop mode. Split into different file for clarity
//
//===========================================================================//
#include "cbase.h"
#include "cs_bot.h"
#include "cs_team.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
ConVar bot_coop_force_throw_grenade_chance( "bot_coop_force_throw_grenade_chance", "0.3", FCVAR_CHEAT );
bool ReactToBombState( CCSBot *me )
{
const Vector* pVecPos = me->GetGameState()->GetBombPosition();
if ( !pVecPos )
return false;
if ( me->GetTeamNumber() == TEAM_TERRORIST )
{
if ( TheCSBots()->IsBombPlanted() )
me->SetTask( CCSBot::GUARD_TICKING_BOMB );
else if ( me->GetGameState()->IsBombLoose() )
{
me->FetchBomb();
return true;
}
else
return false;
}
else if ( me->GetTeamNumber() == TEAM_CT )
{
if ( TheCSBots()->IsBombPlanted() )
me->SetTask( CCSBot::DEFUSE_BOMB );
else if ( me->GetGameState()->IsBombLoose() )
me->SetTask( CCSBot::GUARD_LOOSE_BOMB );
/*
else if ( me->GetGameState()->GetBombState() == CSGameState::MOVING )
{
float flDistToSite0 = ( *pVecPos - TheCSBots()->GetZone( 0 )->m_center ).Length2D();
float flDistToSite1 = ( *pVecPos - TheCSBots()->GetZone( 1 )->m_center ).Length2D();
} */
else
return false;
}
me->MoveTo( *pVecPos );
me->SetDisposition( CCSBot::OPPORTUNITY_FIRE );
return true;
}
// Terrorists rush the chosen site
void RunStrat_Rush( CCSBot * me, int iBombSite )
{
if ( !ReactToBombState( me ) )
{
const CCSBotManager::Zone *zone = TheCSBots()->GetZone( iBombSite );
const Vector *pos = TheCSBots()->GetRandomPositionInZone( zone );
if ( !pos)
{
Warning( "ERROR: Map either has < 2 bomb sites, or one of the sites has no areas. Coop bots will be broken." );
return;
}
if ( me->IsAtBombsite() )
{
bool bIsSafe = gpGlobals->curtime - me->GetLastSawEnemyTimestamp() > 2.0f; // TODO: Might be better to use enemy death/remaining count for this
if ( me->HasC4() && bIsSafe )
{
me->SetTask( CCSBot::PLANT_BOMB );
}
else
{
Place place = TheNavMesh->GetPlace( zone->m_center );
if ( const Vector* pVecHidingSpot = FindRandomHidingSpot( me, place, me->IsSniper() ) )
{
pos = pVecHidingSpot;
}
me->SetTask( CCSBot::GUARD_BOMB_ZONE );
}
}
else
{
me->SetTask( CCSBot::SEEK_AND_DESTROY );
}
me->MoveTo( *pos );
me->Run();
me->PrintIfWatched( "I'm rushing the bomb site\n" );
me->SetDisposition( CCSBot::OPPORTUNITY_FIRE );
}
}
void RunStrat_GuardSpot( CCSBot *me )
{
if ( !ReactToBombState( me ) )
{
const CCSBotManager::Zone *zone = TheCSBots()->GetRandomZone();
if ( zone )
{
CNavArea *area = TheCSBots()->GetRandomAreaInZone( zone );
if ( area )
{
me->PrintIfWatched( "I'm guarding a bombsite\n" );
me->GetChatter()->GuardingBombsite( area->GetPlace() );
me->SetTask( CCSBot::GUARD_BOMB_ZONE );
me->Hide( area, -1.0, 1000.0f );
me->SetDisposition( CCSBot::OPPORTUNITY_FIRE );
}
}
}
}
void MakeAwareOfCTs( CCSBot * me )
{
CUtlVector< CCSPlayer * > vecCTs;
CollectPlayers( &vecCTs, TEAM_CT, COLLECT_ONLY_LIVING_PLAYERS );
if ( vecCTs.Count() == 0 )
return;
CCSPlayer *pTargetPlayer = NULL;
float flClosePlayer = 1e6;
FOR_EACH_VEC( vecCTs, iter )
{
if ( me->IsVisible( vecCTs[ iter ] ) )
{
float dist = me->GetTravelDistanceToPlayer( vecCTs[ iter ] );
if ( dist < flClosePlayer )
{
flClosePlayer = dist;
pTargetPlayer = vecCTs[ iter ];
}
}
me->OnAudibleEvent( NULL, vecCTs[ iter ], MAX_COORD_FLOAT, PRIORITY_HIGH, true );
}
if ( !pTargetPlayer )
pTargetPlayer = vecCTs[ RandomInt( 0, vecCTs.Count() - 1 ) ];
me->SetBotEnemy( pTargetPlayer );
}
extern ConVar mp_guardian_target_site;
void IdleState::UpdateCoop( CCSBot *me )
{
if ( CSGameRules()->IsPlayingCoopGuardian() )
{
Assert( mp_guardian_target_site.GetInt() >= 0 );
// if this is a bomb game and we have the bomb, go plant it
if ( me->GetTeamNumber() == TEAM_TERRORIST )
{
switch ( TheCSBots()->GetTStrat() )
{
case CCSBotManager::k_ETStrat_Rush:
{
RunStrat_Rush( me, TheCSBots()->GetTerroristTargetSite() );
break;
}
}
}
else if ( me->GetTeamNumber() == TEAM_CT )
{
switch ( TheCSBots()->GetCTStrat() )
{
case CCSBotManager::k_ECTStrat_StackSite:
{
RunStrat_GuardSpot( me );
break;
}
}
}
}
else if ( CSGameRules()->IsPlayingCoopMission() )
{
if ( me->GetTeamNumber() == TEAM_TERRORIST )
{
SpawnPointCoopEnemy* pSpawn = me->GetLastCoopSpawnPoint();
if ( !pSpawn )
return;
// if we're holding a grenade, throw it at the victim
if ( me->HasGrenade() && CSGameRules()->IsPlayingCooperativeGametype() && RandomFloat() < bot_coop_force_throw_grenade_chance.GetFloat() )
me->EquipGrenade();
// HACK: We'd like last guy alive to stop hiding and hunt down the players
// but don't want the first spawned guy to go straight to charging... kick this clause into the future a few seconds
// to make sure our wave is in before making this check.
SpawnPointCoopEnemy::BotDefaultBehavior_t behavior = pSpawn->GetDefaultBehavior();
if ( me->GetFriendsRemaining() == 0 && gpGlobals->curtime - me->m_spawnedTime > 5.0f )
behavior = SpawnPointCoopEnemy::CHARGE_ENEMY;
switch ( behavior )
{
case SpawnPointCoopEnemy::HUNT:
{
me->Hunt();
break;
}
case SpawnPointCoopEnemy::DEFEND_AREA:
case SpawnPointCoopEnemy::DEFEND_INVESTIGATE:
{
me->SetDisposition( CCSBot::OPPORTUNITY_FIRE );
if ( me->IsSniper() )
me->SetTask( CCSBot::MOVE_TO_SNIPER_SPOT );
else
me->SetTask( CCSBot::HOLD_POSITION );
if ( pSpawn->HideRadius() > 0 )
{
if ( !me->TryToHide( pSpawn->FindNearestArea(), -1.0, pSpawn->HideRadius(), true, false, &me->GetAbsOrigin() ) )
{
// This spawn isn't hideable, stop trying
pSpawn->HideRadius( 0 );
}
}
else
{
// look around
me->UpdateLookAround();
if ( pSpawn->GetDefaultBehavior() == SpawnPointCoopEnemy::DEFEND_INVESTIGATE )
{
me->SetDisposition( CCSBot::ENGAGE_AND_INVESTIGATE );
// listen for enemy noises
if ( me->HeardInterestingNoise() )
{
me->InvestigateNoise();
pSpawn->SetDefaultBehavior( SpawnPointCoopEnemy::HUNT );
}
}
}
break;
}
case SpawnPointCoopEnemy::CHARGE_ENEMY:
{
MakeAwareOfCTs( me );
me->SetDisposition( CCSBot::ENGAGE_AND_INVESTIGATE );
if ( me->GetBotEnemy() )
{
me->SetTask( CCSBot::MOVE_TO_LAST_KNOWN_ENEMY_POSITION, me->GetBotEnemy() );
me->MoveTo( me->GetBotEnemy()->GetAbsOrigin() );
}
break;
}
}
}
}
}
@@ -0,0 +1,139 @@
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
// Author: Michael S. Booth (mike@turtlerockstudios.com), 2003
#include "cbase.h"
#include "cs_bot.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
//--------------------------------------------------------------------------------------------------------------
/**
* Move towards currently heard noise
*/
void InvestigateNoiseState::AttendCurrentNoise( CCSBot *me )
{
if (!me->IsNoiseHeard() && me->GetNoisePosition())
return;
// remember where the noise we heard was
m_checkNoisePosition = *me->GetNoisePosition();
// tell our teammates (unless the noise is obvious, like gunfire)
if (me->IsWellPastSafe() && me->HasNotSeenEnemyForLongTime() && me->GetNoisePriority() != PRIORITY_HIGH)
me->GetChatter()->HeardNoise( *me->GetNoisePosition() );
// figure out how to get to the noise
me->PrintIfWatched( "Attending to noise...\n" );
me->ComputePath( m_checkNoisePosition, FASTEST_ROUTE );
const float minAttendTime = 3.0f;
const float maxAttendTime = 10.0f;
m_minTimer.Start( RandomFloat( minAttendTime, maxAttendTime ) );
// consume the noise
me->ForgetNoise();
}
//--------------------------------------------------------------------------------------------------------------
void InvestigateNoiseState::OnEnter( CCSBot *me )
{
AttendCurrentNoise( me );
}
//--------------------------------------------------------------------------------------------------------------
/**
* @todo Use TravelDistance instead of distance...
*/
void InvestigateNoiseState::OnUpdate( CCSBot *me )
{
Vector myOrigin = GetCentroid( me );
// keep an ear out for closer noises...
if (m_minTimer.IsElapsed())
{
const float nearbyRange = 500.0f;
if (me->HeardInterestingNoise() && me->GetNoiseRange() < nearbyRange)
{
// new sound is closer
AttendCurrentNoise( me );
}
}
// if the pathfind fails, give up
if (!me->HasPath())
{
me->Idle();
return;
}
// look around
me->UpdateLookAround();
// get distance remaining on our path until we reach the source of the noise
float range = me->GetPathDistanceRemaining();
if (me->IsUsingKnife())
{
if (me->IsHurrying())
me->Run();
else
me->Walk();
}
else
{
const float closeToNoiseRange = 1500.0f;
if (range < closeToNoiseRange)
{
// if we dont have many friends left, or we are alone, and we are near noise source, sneak quietly
if ((me->GetNearbyFriendCount() == 0 || me->GetFriendsRemaining() <= 2) && !me->IsHurrying())
{
me->Walk();
}
else
{
me->Run();
}
}
else
{
me->Run();
}
}
// if we can see the noise position and we're close enough to it and looking at it,
// we don't need to actually move there (it's checked enough)
const float closeRange = 500.0f;
if (range < closeRange)
{
if (me->IsVisible( m_checkNoisePosition, CHECK_FOV ))
{
// can see noise position
me->PrintIfWatched( "Noise location is clear.\n" );
me->ForgetNoise();
me->Idle();
return;
}
}
// move towards noise
if (me->UpdatePathMovement() != CCSBot::PROGRESSING)
{
me->Idle();
}
}
//--------------------------------------------------------------------------------------------------------------
void InvestigateNoiseState::OnExit( CCSBot *me )
{
// reset to run mode in case we were sneaking about
me->Run();
}
@@ -0,0 +1,408 @@
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
// Author: Michael S. Booth (mike@turtlerockstudios.com), 2003
#include "cbase.h"
#include "cs_simple_hostage.h"
#include "cs_bot.h"
#include "cs_gamerules.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
//--------------------------------------------------------------------------------------------------------------
/**
* Move to a potentially far away position.
*/
void MoveToState::OnEnter( CCSBot *me )
{
if ( ( me->IsUsingKnife() && me->IsWellPastSafe() && !me->IsHurrying() ) ||
( me->HasHeavyArmor() && me->GetBotEnemy() ) )
{
me->Walk();
}
else
{
me->Run();
}
// if we need to find the bomb, get there as quick as we can
RouteType route;
switch (me->GetTask())
{
case CCSBot::FIND_TICKING_BOMB:
case CCSBot::DEFUSE_BOMB:
case CCSBot::MOVE_TO_LAST_KNOWN_ENEMY_POSITION:
route = FASTEST_ROUTE;
break;
default:
route = SAFEST_ROUTE;
break;
}
// build path to, or nearly to, goal position
me->ComputePath( m_goalPosition, route );
m_radioedPlan = false;
m_askedForCover = false;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Move to a potentially far away position.
*/
void MoveToState::OnUpdate( CCSBot *me )
{
Vector myOrigin = GetCentroid( me );
// assume that we are paying attention and close enough to know our enemy died
if (me->GetTask() == CCSBot::MOVE_TO_LAST_KNOWN_ENEMY_POSITION)
{
/// @todo Account for reaction time so we take some time to realized the enemy is dead
CBasePlayer *victim = static_cast<CBasePlayer *>( me->GetTaskEntity() );
if (victim == NULL || !victim->IsAlive())
{
me->PrintIfWatched( "The enemy I was chasing was killed - giving up.\n" );
me->Idle();
return;
}
}
// look around
me->UpdateLookAround();
//
// Scenario logic
//
switch (TheCSBots()->GetScenario())
{
case CCSBotManager::SCENARIO_DEFUSE_BOMB:
{
// if the bomb has been planted, find it
// NOTE: This task is used by both CT and T's to find the bomb
if (me->GetTask() == CCSBot::FIND_TICKING_BOMB)
{
if (!me->GetGameState()->IsBombPlanted())
{
// the bomb is not planted - give up this task
me->Idle();
return;
}
if (me->GetGameState()->GetPlantedBombsite() != CSGameState::UNKNOWN)
{
// we know where the bomb is planted, stop searching
me->Idle();
return;
}
// check off bombsites that we explore or happen to stumble into
for( int z=0; z<TheCSBots()->GetZoneCount(); ++z )
{
// don't re-check zones
if (me->GetGameState()->IsBombsiteClear( z ))
continue;
if (TheCSBots()->GetZone(z)->m_extent.Contains( myOrigin ))
{
// note this bombsite is clear
me->GetGameState()->ClearBombsite( z );
if (me->GetTeamNumber() == TEAM_CT)
{
// tell teammates this bombsite is clear
me->GetChatter()->BombsiteClear( z );
}
// find another zone to check
me->Idle();
return;
}
}
// move to a bombsite
break;
}
if (me->GetTeamNumber() == TEAM_CT)
{
if (me->GetGameState()->IsBombPlanted())
{
switch( me->GetTask() )
{
case CCSBot::DEFUSE_BOMB:
{
// if we are near the bombsite and there is time left, sneak in (unless all enemies are dead)
if (me->GetEnemiesRemaining())
{
const float plentyOfTime = 15.0f;
if (TheCSBots()->GetBombTimeLeft() > plentyOfTime)
{
// get distance remaining on our path until we reach the bombsite
float range = me->GetPathDistanceRemaining();
const float closeRange = 1500.0f;
if (range < closeRange)
{
me->Walk();
}
else
{
me->Run();
}
}
}
else
{
// everyone is dead - run!
me->Run();
}
// if we are trying to defuse the bomb, and someone has started defusing, guard them instead
if (me->CanSeePlantedBomb() && TheCSBots()->GetBombDefuser())
{
me->GetChatter()->Say( "CoveringFriend" );
me->Idle();
return;
}
// if we are near the bomb, defuse it (if we are reloading, don't try to defuse until we finish)
const Vector *bombPos = me->GetGameState()->GetBombPosition();
if (bombPos && !me->IsReloading())
{
if ((*bombPos - me->EyePosition()).IsLengthLessThan( 72 ) && ( me->EyePosition().AsVector2D().DistTo( bombPos->AsVector2D() ) < 48 ))
{
// make sure we can see the bomb
if (me->IsVisible( *bombPos ))
{
me->DefuseBomb();
return;
}
}
}
break;
}
default:
{
// we need to find the bomb
me->Idle();
return;
}
}
}
}
else // TERRORIST
{
if (me->GetTask() == CCSBot::PLANT_BOMB )
{
if ( me->GetFriendsRemaining() )
{
// if we are about to plant, radio for cover
if (!m_askedForCover)
{
const float nearPlantSite = 50.0f;
if (me->IsAtBombsite() && me->GetPathDistanceRemaining() < nearPlantSite)
{
// radio to the team
me->GetChatter()->PlantingTheBomb( me->GetPlace() );
m_askedForCover = true;
}
// after we have started to move to the bombsite, tell team we're going to plant, and where
// don't do this if we have already radioed that we are starting to plant
if (!m_radioedPlan)
{
const float radioTime = 2.0f;
if (gpGlobals->curtime - me->GetStateTimestamp() > radioTime)
{
// radio to the team if we're more than 10 seconds (2400 units) out
const float nearPlantSite = 2400.0f;
if ( me->GetPathDistanceRemaining() >= nearPlantSite )
{
me->GetChatter()->GoingToPlantTheBomb( TheNavMesh->GetPlace( m_goalPosition ) );
}
m_radioedPlan = true;
}
}
}
}
}
}
break;
}
//--------------------------------------------------------------------------------------------------
case CCSBotManager::SCENARIO_RESCUE_HOSTAGES:
{
if (me->GetTask() == CCSBot::COLLECT_HOSTAGES)
{
//
// Since CT's have a radar, they can directly look at the actual hostage state
//
// check if someone else collected our hostage, or the hostage died or was rescued
CHostage *hostage = static_cast<CHostage *>( me->GetGoalEntity() );
if (hostage == NULL || !hostage->IsValid() || hostage->IsFollowingSomeone() )
{
me->Idle();
return;
}
Vector hostageOrigin = GetCentroid( hostage );
// if our hostage has moved, repath
const float repathToleranceSq = 75.0f * 75.0f;
float error = (hostageOrigin - m_goalPosition).LengthSqr();
if (error > repathToleranceSq)
{
m_goalPosition = hostageOrigin;
me->ComputePath( m_goalPosition, SAFEST_ROUTE );
}
/// @todo Generalize ladder priorities over other tasks
if (!me->IsUsingLadder())
{
Vector pos = hostage->EyePosition();
Vector to = pos - me->EyePosition(); // "Use" checks from eye position, so we should too
// look at the hostage as we approach
const float watchHostageRange = 100.0f;
if (to.IsLengthLessThan( watchHostageRange ))
{
me->SetLookAt( "Hostage", pos, PRIORITY_LOW, 0.5f );
// randomly move just a bit to avoid infinite use loops from bad hostage placement
NavRelativeDirType dir = (NavRelativeDirType)RandomInt( 0, 3 );
switch( dir )
{
case LEFT: me->StrafeLeft(); break;
case RIGHT: me->StrafeRight(); break;
case FORWARD: me->MoveForward(); break;
case BACKWARD: me->MoveBackward(); break;
}
// check if we are close enough to the hostage to talk to him
const float useRange = PLAYER_USE_RADIUS - 10.0f; // shave off a fudge factor to make sure we're within range
if (to.IsLengthLessThan( useRange ))
{
if ( HOSTAGE_RULE_CAN_PICKUP == 1 )
{
//me->PickupHostage( me->GetGoalEntity() );
bool bBeingRescued = false;
CHostage *hostage = static_cast<CHostage*>( me->GetGoalEntity() );
if ( hostage && hostage->GetHostageState() != k_EHostageStates_GettingPickedUp &&
hostage->IsFollowingSomeone() == false && me->GetNearbyFriendCount() > 0 )
{
// see if one of my friends if picking up this hostage
for ( int i = 1; i <= gpGlobals->maxClients; ++i )
{
CCSBot *player = dynamic_cast< CCSBot * >( UTIL_PlayerByIndex( i ) );
if ( player == NULL || !player->IsAlive() ||
me->IsOtherEnemy( player ) || player->entindex() == me->entindex() )
continue;
if ( player->IsPickingupHostage() )
{
bBeingRescued = true;
break;
}
}
}
// if not, pick it up
if ( bBeingRescued == false )
me->PickupHostage( me->GetGoalEntity() );
else
{
if ( hostage && me->IsVisible( hostage->GetAbsOrigin(), false, NULL ) )
{
// if we can see the hostage, guard it
me->GetChatter()->Say( "CoveringFriend" );
me->Idle();
}
}
}
else
me->UseEntity( me->GetGoalEntity() );
return;
}
}
}
}
else if (me->GetTask() == CCSBot::RESCUE_HOSTAGES)
{
// periodically check if we lost all our hostages
if (me->GetHostageEscortCount() == 0)
{
// lost our hostages - go get 'em
me->Idle();
return;
}
}
break;
}
}
if (me->UpdatePathMovement() != CCSBot::PROGRESSING)
{
// reached destination
switch( me->GetTask() )
{
case CCSBot::PLANT_BOMB:
// if we are at bombsite with the bomb, plant it
if (me->IsAtBombsite() && me->HasC4())
{
me->PlantBomb();
return;
}
break;
case CCSBot::MOVE_TO_LAST_KNOWN_ENEMY_POSITION:
{
CBasePlayer *victim = static_cast<CBasePlayer *>( me->GetTaskEntity() );
if (victim && victim->IsAlive())
{
// if we got here and haven't re-acquired the enemy, we lost him
BotStatement *say = new BotStatement( me->GetChatter(), REPORT_ENEMY_LOST, 8.0f );
say->AppendPhrase( TheBotPhrases->GetPhrase( "LostEnemy" ) );
say->SetStartTime( gpGlobals->curtime + RandomFloat( 3.0f, 5.0f ) );
me->GetChatter()->AddStatement( say );
}
break;
}
}
// default behavior when destination is reached
me->Idle();
return;
}
}
//--------------------------------------------------------------------------------------------------------------
void MoveToState::OnExit( CCSBot *me )
{
// reset to run in case we were walking near our goal position
me->Run();
me->SetDisposition( CCSBot::ENGAGE_AND_INVESTIGATE );
//me->StopAiming();
}
@@ -0,0 +1,120 @@
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
// Author: Michael S. Booth (mike@turtlerockstudios.com), April 2005
#include "cbase.h"
#include "cs_bot.h"
#include "BasePropDoor.h"
#include "doors.h"
#include "props.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
//-------------------------------------------------------------------------------------------------
/**
* Face the door and open it.
* NOTE: This state assumes we are standing in range of the door to be opened, with no obstructions.
*/
void OpenDoorState::OnEnter( CCSBot *me )
{
m_isDone = false;
m_timeout.Start( 0.5f );
}
//-------------------------------------------------------------------------------------------------
void OpenDoorState::SetDoor( CBaseEntity *door )
{
CBaseDoor *funcDoor = dynamic_cast< CBaseDoor * >(door);
if ( funcDoor )
{
m_funcDoor = funcDoor;
return;
}
CBasePropDoor *propDoor = dynamic_cast< CBasePropDoor * >(door);
if ( propDoor )
{
m_propDoor = propDoor;
return;
}
}
//-------------------------------------------------------------------------------------------------
void OpenDoorState::OnUpdate( CCSBot *me )
{
me->ResetStuckMonitor();
// look at the door
Vector pos;
bool isDoorMoving = false;
CBaseEntity *door = NULL;
if ( m_funcDoor.Get() )
{
door = m_funcDoor;
isDoorMoving = m_funcDoor->m_toggle_state == TS_GOING_UP || m_funcDoor->m_toggle_state == TS_GOING_DOWN;
}
else if ( m_propDoor.Get() )
{
door = m_propDoor;
isDoorMoving = m_propDoor->IsDoorOpening() || m_propDoor->IsDoorClosing();
CPropDoorRotatingBreakable *pPropDoor = dynamic_cast< CPropDoorRotatingBreakable* >( door );
if ( pPropDoor && pPropDoor->IsDoorLocked() && pPropDoor->IsBreakable() == false )
{
m_isDone = true;
return;
}
}
// wait for door to swing open before leaving state
if ( isDoorMoving || !door )
{
m_isDone = true;
return;
}
me->SetLookAt( "Open door", door->WorldSpaceCenter(), PRIORITY_UNINTERRUPTABLE );
// if we are looking at the door, "use" it and exit
if ( me->IsLookingAtPosition( door->WorldSpaceCenter() ) )
{
if ( m_timeout.IsElapsed() )
{
// possibly stuck - blow the damn door away!
me->PrimaryAttack();
if ( door )
{
AssertMsg( door->GetHealth() > 2, "Bot is stuck on a door and is going to destroy it to get free!\n" );
CTakeDamageInfo damageInfo( me, me, 2.0f, DMG_GENERIC );
door->TakeDamage( damageInfo );
}
}
// we are looking at it - use it directly to avoid complications
door->Use( me, me, USE_ON, 0.0f );
}
}
//-------------------------------------------------------------------------------------------------
void OpenDoorState::OnExit( CCSBot *me )
{
me->ClearLookAt();
me->ResetStuckMonitor();
}
@@ -0,0 +1,76 @@
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
// Author: Michael S. Booth (mike@turtlerockstudios.com), 2003
#include "cbase.h"
#include "cs_bot.h"
#include "weapon_c4.h"
#include "cs_simple_hostage.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
//--------------------------------------------------------------------------------------------------------------
/**
* Plant the bomb.
*/
void PickupHostageState::OnEnter( CCSBot *me )
{
me->Crouch();
me->SetDisposition( CCSBot::SELF_DEFENSE );
}
//--------------------------------------------------------------------------------------------------------------
/**
* Plant the bomb.
*/
void PickupHostageState::OnUpdate( CCSBot *me )
{
const float timeout = 7.0f;
if (gpGlobals->curtime - me->GetStateTimestamp() > timeout)
{
// find a new spot
me->Idle();
return;
}
// look at the entity
Vector pos = m_entity->EyePosition();
me->SetLookAt( "Use entity", pos, PRIORITY_HIGH );
// if we are looking at the entity, "use" it and exit
if (me->IsLookingAtPosition( pos ))
{
me->UseEnvironment();
}
CHostage *hostage = assert_cast<CHostage *>( m_entity.Get() );
if ( hostage && hostage->IsFollowingSomeone() )
{
me->Idle();
return;
}
}
//--------------------------------------------------------------------------------------------------------------
void PickupHostageState::OnExit( CCSBot *me )
{
// equip our rifle (in case we were interrupted while holding C4)
me->EquipBestWeapon();
me->StandUp();
me->ResetStuckMonitor();
me->SetDisposition( CCSBot::ENGAGE_AND_INVESTIGATE );
me->ClearLookAt();
CHostage *hostage = assert_cast<CHostage *>( m_entity.Get() );
if ( hostage && hostage->IsFollowing( me ) )
me->IncreaseHostageEscortCount();
}
@@ -0,0 +1,98 @@
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
// Author: Michael S. Booth (mike@turtlerockstudios.com), 2003
#include "cbase.h"
#include "cs_bot.h"
#include "weapon_c4.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
//--------------------------------------------------------------------------------------------------------------
/**
* Plant the bomb.
*/
void PlantBombState::OnEnter( CCSBot *me )
{
me->Crouch();
me->SetDisposition( CCSBot::SELF_DEFENSE );
// look at the floor
// Vector down( myOrigin.x, myOrigin.y, -1000.0f );
float yaw = me->EyeAngles().y;
Vector2D dir( BotCOS(yaw), BotSIN(yaw) );
Vector myOrigin = GetCentroid( me );
Vector down( myOrigin.x + 10.0f * dir.x, myOrigin.y + 10.0f * dir.y, me->GetFeetZ() );
me->SetLookAt( "Plant bomb on floor", down, PRIORITY_HIGH );
}
//--------------------------------------------------------------------------------------------------------------
/**
* Plant the bomb.
*/
void PlantBombState::OnUpdate( CCSBot *me )
{
// can't be stuck while planting
me->ResetStuckMonitor();
CBaseCombatWeapon *gun = me->GetActiveWeapon();
bool holdingC4 = false;
if (gun)
{
if (FStrEq( gun->GetClassname(), "weapon_c4" ))
holdingC4 = true;
}
// if we aren't holding the C4, grab it, otherwise plant it
if ( holdingC4 )
{
me->PrimaryAttack();
CC4 *pC4 = dynamic_cast< CC4 * >( gun );
if ( pC4 && !pC4->m_bStartedArming && gpGlobals->curtime - me->GetStateTimestamp() > 2.0f )
{
// can't plant here for some reason - try another spot
me->Idle();
return;
}
}
else
{
me->SelectItem( "weapon_c4" );
}
// if we no longer have the C4, we've successfully planted
if (!me->HasC4())
{
// move to a hiding spot and watch the bomb
me->SetTask( CCSBot::GUARD_TICKING_BOMB );
me->Hide();
}
// if we time out, it's because we slipped into a non-plantable area
const float timeout = 5.0f;
if (gpGlobals->curtime - me->GetStateTimestamp() > timeout)
{
// find a new spot
me->Idle();
}
}
//--------------------------------------------------------------------------------------------------------------
void PlantBombState::OnExit( CCSBot *me )
{
// equip our rifle (in case we were interrupted while holding C4)
me->EquipBestWeapon();
me->StandUp();
me->ResetStuckMonitor();
me->SetDisposition( CCSBot::ENGAGE_AND_INVESTIGATE );
me->ClearLookAt();
}
@@ -0,0 +1,71 @@
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
// Author: Michael S. Booth (mike@turtlerockstudios.com), 2003
#include "cbase.h"
#include "cs_bot.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
/**
* Face the entity and "use" it
* NOTE: This state assumes we are standing in range of the entity to be used, with no obstructions.
*/
void UseEntityState::OnEnter( CCSBot *me )
{
}
void UseEntityState::OnUpdate( CCSBot *me )
{
// in the case when an entity we were supposed to +use is deleted we shouldn't try to
// dereference a NULL pointer and should just go to a higher level objective
if ( !m_entity.Get() )
{
me->Idle();
return;
}
// in the very rare situation where two or more bots "used" a hostage at the same time,
// one bot will fail and needs to time out of this state
const float useTimeout = 5.0f;
if (me->GetStateTimestamp() - gpGlobals->curtime > useTimeout)
{
me->Idle();
return;
}
// look at the entity
Vector pos = m_entity->EyePosition();
me->SetLookAt( "Use entity", pos, PRIORITY_HIGH );
// if we are looking at the entity, "use" it and exit
if (me->IsLookingAtPosition( pos ))
{
if (TheCSBots()->GetScenario() == CCSBotManager::SCENARIO_RESCUE_HOSTAGES &&
me->GetTeamNumber() == TEAM_CT &&
me->GetTask() == CCSBot::COLLECT_HOSTAGES)
{
// we are collecting a hostage, assume we were successful - the update check will correct us if we weren't
me->IncreaseHostageEscortCount();
}
me->UseEnvironment();
me->Idle();
}
}
void UseEntityState::OnExit( CCSBot *me )
{
me->ClearLookAt();
me->ResetStuckMonitor();
}
+69
View File
@@ -0,0 +1,69 @@
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose: Data for Autobuy and Rebuy
//
//=============================================================================//
#include "cbase.h"
#include "cs_autobuy.h"
// Weapon class information for each weapon including the class name and the buy command alias.
AutoBuyInfoStruct g_autoBuyInfo[] =
{
{ (AutoBuyClassType)(AUTOBUYCLASS_PRIMARY | AUTOBUYCLASS_RIFLE), LOADOUT_POSITION_RIFLE0, "galil", "weapon_galil" }, // galil
{ (AutoBuyClassType)(AUTOBUYCLASS_PRIMARY | AUTOBUYCLASS_RIFLE), LOADOUT_POSITION_RIFLE0, "galilar", "weapon_galilar" }, // galilar
{ (AutoBuyClassType)(AUTOBUYCLASS_PRIMARY | AUTOBUYCLASS_RIFLE), LOADOUT_POSITION_RIFLE1, "ak47", "weapon_ak47" }, // ak47
{ (AutoBuyClassType)(AUTOBUYCLASS_PRIMARY | AUTOBUYCLASS_SNIPERRIFLE), LOADOUT_POSITION_INVALID, "scout", "weapon_scout" }, // scout
{ (AutoBuyClassType)(AUTOBUYCLASS_PRIMARY | AUTOBUYCLASS_RIFLE), LOADOUT_POSITION_INVALID, "sg552", "weapon_sg552" }, // sg552
{ (AutoBuyClassType)(AUTOBUYCLASS_PRIMARY | AUTOBUYCLASS_SNIPERRIFLE), LOADOUT_POSITION_RIFLE4, "awp", "weapon_awp" }, // awp
{ (AutoBuyClassType)(AUTOBUYCLASS_PRIMARY | AUTOBUYCLASS_SNIPERRIFLE), LOADOUT_POSITION_RIFLE5, "g3sg1", "weapon_g3sg1" }, // g3sg1
{ (AutoBuyClassType)(AUTOBUYCLASS_PRIMARY | AUTOBUYCLASS_RIFLE), LOADOUT_POSITION_RIFLE0, "famas", "weapon_famas" }, // famas
{ (AutoBuyClassType)(AUTOBUYCLASS_PRIMARY | AUTOBUYCLASS_RIFLE), LOADOUT_POSITION_RIFLE1, "m4a1", "weapon_m4a1" }, // m4a1
{ (AutoBuyClassType)(AUTOBUYCLASS_PRIMARY | AUTOBUYCLASS_RIFLE), LOADOUT_POSITION_RIFLE1, "m4a1_silencer", "weapon_m4a1_silencer" }, // m4a1_silencer
{ (AutoBuyClassType)(AUTOBUYCLASS_PRIMARY | AUTOBUYCLASS_RIFLE), LOADOUT_POSITION_RIFLE3, "aug", "weapon_aug" }, // aug
{ (AutoBuyClassType)(AUTOBUYCLASS_PRIMARY | AUTOBUYCLASS_RIFLE), LOADOUT_POSITION_INVALID, "scar17", "weapon_scar17" }, // scar17
{ (AutoBuyClassType)(AUTOBUYCLASS_PRIMARY | AUTOBUYCLASS_RIFLE), LOADOUT_POSITION_RIFLE3, "sg556", "weapon_sg556" }, // sg556
{ (AutoBuyClassType)(AUTOBUYCLASS_PRIMARY | AUTOBUYCLASS_SNIPERRIFLE), LOADOUT_POSITION_INVALID, "sg550", "weapon_sg550" }, // sg550
{ (AutoBuyClassType)(AUTOBUYCLASS_PRIMARY | AUTOBUYCLASS_SNIPERRIFLE), LOADOUT_POSITION_RIFLE5, "scar20", "weapon_scar20" }, // scar20
{ (AutoBuyClassType)(AUTOBUYCLASS_PRIMARY | AUTOBUYCLASS_SNIPERRIFLE), LOADOUT_POSITION_RIFLE2, "ssg08", "weapon_ssg08" }, // ssg08
{ (AutoBuyClassType)(AUTOBUYCLASS_SECONDARY | AUTOBUYCLASS_PISTOL), LOADOUT_POSITION_SECONDARY0, "glock", "weapon_glock" }, // glock
{ (AutoBuyClassType)(AUTOBUYCLASS_SECONDARY | AUTOBUYCLASS_PISTOL), LOADOUT_POSITION_INVALID, "usp", "weapon_usp" }, // usp
{ (AutoBuyClassType)(AUTOBUYCLASS_SECONDARY | AUTOBUYCLASS_PISTOL), LOADOUT_POSITION_INVALID, "p228", "weapon_p228" }, // p228
{ (AutoBuyClassType)(AUTOBUYCLASS_SECONDARY | AUTOBUYCLASS_PISTOL), LOADOUT_POSITION_SECONDARY4, "deagle", "weapon_deagle" }, // deagle
{ (AutoBuyClassType)(AUTOBUYCLASS_SECONDARY | AUTOBUYCLASS_PISTOL), LOADOUT_POSITION_SECONDARY1, "elite", "weapon_elite" }, // elites
{ (AutoBuyClassType)(AUTOBUYCLASS_SECONDARY | AUTOBUYCLASS_PISTOL), LOADOUT_POSITION_SECONDARY3, "fn57", "weapon_fiveseven" }, // fn57
{ (AutoBuyClassType)(AUTOBUYCLASS_SECONDARY | AUTOBUYCLASS_PISTOL), LOADOUT_POSITION_SECONDARY0, "hkp2000", "weapon_hkp2000" }, // hkp2000
{ (AutoBuyClassType)(AUTOBUYCLASS_SECONDARY | AUTOBUYCLASS_PISTOL), LOADOUT_POSITION_SECONDARY0, "usp_silencer", "weapon_usp_silencer" }, // usp_silencer
{ (AutoBuyClassType)(AUTOBUYCLASS_SECONDARY | AUTOBUYCLASS_PISTOL), LOADOUT_POSITION_SECONDARY2, "p250", "weapon_p250" }, // p250
{ (AutoBuyClassType)(AUTOBUYCLASS_SECONDARY | AUTOBUYCLASS_PISTOL), LOADOUT_POSITION_SECONDARY3, "tec9", "weapon_tec9" }, // p250
{ (AutoBuyClassType)(AUTOBUYCLASS_SECONDARY | AUTOBUYCLASS_PISTOL), LOADOUT_POSITION_INVALID, "taser", "weapon_taser" }, // taser
{ (AutoBuyClassType)(AUTOBUYCLASS_PRIMARY | AUTOBUYCLASS_SHOTGUN), LOADOUT_POSITION_HEAVY0, "nova", "weapon_nova" }, // nova
{ (AutoBuyClassType)(AUTOBUYCLASS_PRIMARY | AUTOBUYCLASS_SHOTGUN), LOADOUT_POSITION_HEAVY1, "xm1014", "weapon_xm1014" }, // xm1014
{ (AutoBuyClassType)(AUTOBUYCLASS_PRIMARY | AUTOBUYCLASS_SHOTGUN), LOADOUT_POSITION_HEAVY2, "mag7", "weapon_mag7" }, // mag7
{ (AutoBuyClassType)(AUTOBUYCLASS_PRIMARY | AUTOBUYCLASS_SHOTGUN), LOADOUT_POSITION_INVALID, "stryker", "weapon_stryker" }, // stryker
{ (AutoBuyClassType)(AUTOBUYCLASS_PRIMARY | AUTOBUYCLASS_SMG), LOADOUT_POSITION_SMG0, "mac10", "weapon_mac10" }, // mac10
{ (AutoBuyClassType)(AUTOBUYCLASS_PRIMARY | AUTOBUYCLASS_SMG), LOADOUT_POSITION_INVALID, "tmp", "weapon_tmp" }, // tmp
{ (AutoBuyClassType)(AUTOBUYCLASS_PRIMARY | AUTOBUYCLASS_SMG), LOADOUT_POSITION_INVALID, "mp5navy", "weapon_mp5navy" }, // mp5navy
{ (AutoBuyClassType)(AUTOBUYCLASS_PRIMARY | AUTOBUYCLASS_SMG), LOADOUT_POSITION_SMG2, "ump45", "weapon_ump45" }, // ump45
{ (AutoBuyClassType)(AUTOBUYCLASS_PRIMARY | AUTOBUYCLASS_SMG), LOADOUT_POSITION_SMG3, "p90", "weapon_p90" }, // p90
{ (AutoBuyClassType)(AUTOBUYCLASS_PRIMARY | AUTOBUYCLASS_SMG), LOADOUT_POSITION_SMG4, "bizon", "weapon_bizon" }, // bizon
{ (AutoBuyClassType)(AUTOBUYCLASS_PRIMARY | AUTOBUYCLASS_SMG), LOADOUT_POSITION_SMG1, "mp7", "weapon_mp7" }, // mp7
{ (AutoBuyClassType)(AUTOBUYCLASS_PRIMARY | AUTOBUYCLASS_SMG), LOADOUT_POSITION_SMG0, "mp9", "weapon_mp9" }, // mp9
{ (AutoBuyClassType)(AUTOBUYCLASS_PRIMARY | AUTOBUYCLASS_MACHINEGUN), LOADOUT_POSITION_HEAVY3, "m249", "weapon_m249" }, // m249
{ (AutoBuyClassType)(AUTOBUYCLASS_PRIMARY | AUTOBUYCLASS_MACHINEGUN), LOADOUT_POSITION_HEAVY4, "negev", "weapon_negev" }, // negev
{ (AutoBuyClassType)(AUTOBUYCLASS_PRIMARY | AUTOBUYCLASS_AMMO), LOADOUT_POSITION_INVALID, "primammo", "primammo" }, // primammo
{ (AutoBuyClassType)(AUTOBUYCLASS_SECONDARY | AUTOBUYCLASS_AMMO), LOADOUT_POSITION_INVALID, "secammo", "secammo" }, // secmmo
{ (AutoBuyClassType)(AUTOBUYCLASS_ARMOR), LOADOUT_POSITION_INVALID, "vest", "item_kevlar" }, // vest
{ (AutoBuyClassType)(AUTOBUYCLASS_ARMOR), LOADOUT_POSITION_INVALID, "vesthelm", "item_assaultsuit" }, // vesthelm
{ (AutoBuyClassType)(AUTOBUYCLASS_GRENADE), LOADOUT_POSITION_GRENADE2, "flashbang", "weapon_flashbang" }, // flash
{ (AutoBuyClassType)(AUTOBUYCLASS_GRENADE), LOADOUT_POSITION_GRENADE3, "hegrenade", "weapon_hegrenade" }, // hegren
{ (AutoBuyClassType)(AUTOBUYCLASS_GRENADE), LOADOUT_POSITION_GRENADE4, "smokegrenade", "weapon_smokegrenade" }, // sgren
{ (AutoBuyClassType)(AUTOBUYCLASS_GRENADE), LOADOUT_POSITION_GRENADE0, "molotov", "weapon_molotov" }, // molotov
{ (AutoBuyClassType)(AUTOBUYCLASS_GRENADE), LOADOUT_POSITION_GRENADE0, "incgrenade", "weapon_incgrenade" }, // incgrenade
{ (AutoBuyClassType)(AUTOBUYCLASS_GRENADE), LOADOUT_POSITION_GRENADE1, "decoy", "weapon_decoy" }, // molotov
{ (AutoBuyClassType)(AUTOBUYCLASS_NIGHTVISION), LOADOUT_POSITION_INVALID, "nvgs", "nvgs" }, // nvgs
{ (AutoBuyClassType)(AUTOBUYCLASS_DEFUSER), LOADOUT_POSITION_INVALID, "defuser", "defuser" }, // defuser
{ (AutoBuyClassType)(AUTOBUYCLASS_PRIMARY | AUTOBUYCLASS_SHIELD), LOADOUT_POSITION_INVALID, "shield", "shield" }, // shield
{ (AutoBuyClassType)0, LOADOUT_POSITION_INVALID, "", "" } // last one, must be at end.
};
+151
View File
@@ -0,0 +1,151 @@
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose: Headers and defines for Autobuy and Rebuy
//
//=============================================================================//
/**
* Weapon classes as used by the AutoBuy
* Has to be different that the previous ones because these are bitmasked values as a weapon can be from
* more than one class. This also includes all the classes of equipment that a player can buy.
*/
enum AutoBuyClassType
{
AUTOBUYCLASS_PRIMARY = 1,
AUTOBUYCLASS_SECONDARY = 2,
AUTOBUYCLASS_AMMO = 4,
AUTOBUYCLASS_ARMOR = 8,
AUTOBUYCLASS_DEFUSER = 16,
AUTOBUYCLASS_PISTOL = 32,
AUTOBUYCLASS_SMG = 64,
AUTOBUYCLASS_RIFLE = 128,
AUTOBUYCLASS_SNIPERRIFLE = 256,
AUTOBUYCLASS_SHOTGUN = 512,
AUTOBUYCLASS_MACHINEGUN = 1024,
AUTOBUYCLASS_GRENADE = 2048,
AUTOBUYCLASS_NIGHTVISION = 4096,
AUTOBUYCLASS_SHIELD = 8192,
};
struct AutoBuyInfoStruct
{
AutoBuyClassType m_class;
loadout_positions_t m_LoadoutPosition;
char *m_command;
char *m_classname;
};
class RebuyStruct
{
private:
int m_nPrimaryPos;
int m_nSecondaryPos;
CSWeaponID m_tertiaryId; // used just for taser right now
CSWeaponID m_grenades[8];
int m_armor; // 0, 1, or 2 (0 = none, 1 = vest, 2 = vest + helmet)
bool m_defuser; // do we want a defuser
bool m_nightVision; // do we want night vision
bool m_isNotEmpty;
public:
RebuyStruct()
{
Clear();
}
void Clear()
{
memset(this, 0, sizeof(RebuyStruct));
}
bool isEmpty( void )
{
return !m_isNotEmpty;
}
void SetPrimary( int nPrimaryPos )
{
m_nPrimaryPos = nPrimaryPos;
m_isNotEmpty = true;
}
int GetPrimary( void )
{
return m_nPrimaryPos;
}
void SetSecondary( int nSecondaryPos )
{
m_nSecondaryPos = nSecondaryPos;
m_isNotEmpty = true;
}
int GetSecondary( void )
{
return m_nSecondaryPos;
}
void SetTertiary( CSWeaponID tertiary )
{
m_tertiaryId = tertiary;
m_isNotEmpty = true;
}
CSWeaponID GetTertiary( void )
{
return m_tertiaryId;
}
void SetGrenade( int index, CSWeaponID grenade )
{
m_grenades[ index ] = grenade;
m_isNotEmpty = true;
}
CSWeaponID GetGrenade( int index )
{
return m_grenades[index];
}
void SetArmor( int armor )
{
m_armor = armor;
m_isNotEmpty = true;
}
int GetArmor( void )
{
return m_armor;
}
void SetDefuser( bool defuser )
{
m_defuser = defuser;
m_isNotEmpty = true;
}
bool GetDefuser( void )
{
return m_defuser;
}
void SetNightVision( bool nv )
{
m_nightVision = nv;
m_isNotEmpty = true;
}
bool GetNightVision( void )
{
return m_nightVision;
}
int numGrenades( void )
{
return ARRAYSIZE( m_grenades );
}
};
extern AutoBuyInfoStruct g_autoBuyInfo[];
+557
View File
@@ -0,0 +1,557 @@
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose: Basic BOT handling.
//
// $Workfile: $
// $Date: $
//
//-----------------------------------------------------------------------------
// $Log: $
//
// $NoKeywords: $
//=============================================================================//
#include "cbase.h"
#include "cs_player.h"
#include "in_buttons.h"
#include "movehelper_server.h"
#include "team.h"
#include "cs_gamerules.h"
#include "client.h"
void Bot_Think( CCSPlayer *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_mimic( "bot_mimic", "0", 0, "Bot uses usercmd of player by index." );
static ConVar bot_mimic_yaw_offset( "bot_mimic_yaw_offset", "0", 0, "Offsets the bot yaw." );
static int BotNumber = 1;
static int g_iNextBotTeam = -1;
typedef struct
{
bool backwards;
float nextturntime;
bool lastturntoright;
float nextstrafetime;
float sidemove;
QAngle forwardAngle;
QAngle lastAngles;
int m_WantedTeam;
float m_flJoinTeamTime;
bool m_bTempBot; // Is this slot a dump temp bot or a real bot?
} 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 )
{
g_iNextBotTeam = iTeam;
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 );
//ClientActive( pEdict, false );
CCSPlayer *pPlayer = ((CCSPlayer *)CBaseEntity::Instance( pEdict ));
pPlayer->ClearFlags();
pPlayer->AddFlag( FL_CLIENT | FL_FAKECLIENT );
if ( bFrozen )
pPlayer->AddEFlags( EFL_BOT_FROZEN );
if ( iTeam == -1 )
iTeam = ( pPlayer->entindex() & 1 ) ? TEAM_TERRORIST : TEAM_CT;
botdata_t *pData = &g_BotData[pPlayer->entindex()-1];
pData->m_WantedTeam = iTeam;
pData->m_flJoinTeamTime = gpGlobals->curtime + 0.3;
pData->m_bTempBot = true;
BotNumber++;
return pPlayer;
}
bool IsTempBot( CBaseEntity *pEnt )
{
if ( !pEnt )
return false;
if ( !(pEnt->GetFlags() & FL_FAKECLIENT) )
return false;
int i = pEnt->entindex();
if ( i >= 1 && i < MAX_PLAYERS )
return g_BotData[i-1].m_bTempBot;
else
return false;
}
//-----------------------------------------------------------------------------
// 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++ )
{
CCSPlayer *pPlayer = ToCSPlayer( UTIL_PlayerByIndex( i ) );
if ( IsTempBot( pPlayer ) )
{
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( CCSPlayer *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;
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( CCSPlayer *pBot )
{
// Make sure we stay being a bot
pBot->AddFlag( FL_FAKECLIENT );
botdata_t *botdata = &g_BotData[ pBot->entindex() - 1 ];
float forwardmove = 0.0;
float sidemove = botdata->sidemove;
float upmove = 0.0;
unsigned short buttons = 0;
byte impulse = 0;
float frametime = gpGlobals->frametime;
if ( pBot->GetTeamNumber() == TEAM_UNASSIGNED && gpGlobals->curtime > botdata->m_flJoinTeamTime )
{
pBot->HandleCommand_JoinTeam( botdata->m_WantedTeam );
}
else if ( pBot->GetTeamNumber() != TEAM_UNASSIGNED && pBot->PlayerClass() == CS_CLASS_NONE )
{
// If they're on a team but haven't picked a class, choose a random class..
pBot->HandleCommand_JoinClass( 0 );
}
else
{
QAngle vecViewAngles;
vecViewAngles = pBot->GetLocalAngles();
// Create some random values
if ( pBot->IsAlive() && (pBot->GetSolid() == SOLID_BBOX) )
{
trace_t trace;
// Stop when shot
if ( !pBot->IsEFlagSet(EFL_BOT_FROZEN) )
{
if ( pBot->m_iHealth == 100 )
{
forwardmove = 600 * ( botdata->backwards ? -1 : 1 );
if ( botdata->sidemove != 0.0f )
{
forwardmove *= random->RandomFloat( 0.1, 1.0f );
}
}
else
{
forwardmove = 0;
}
}
// Only turn if I haven't been hurt
if ( !pBot->IsEFlagSet(EFL_BOT_FROZEN) && pBot->m_iHealth == 100 )
{
Vector vecEnd;
Vector forward;
QAngle angle;
float angledelta = 15.0;
int maxtries = (int)360.0/angledelta;
if ( botdata->lastturntoright )
{
angledelta = -angledelta;
}
angle = pBot->GetLocalAngles();
Vector vecSrc;
while ( --maxtries >= 0 )
{
AngleVectors( angle, &forward, NULL, NULL );
vecSrc = pBot->GetLocalOrigin() + Vector( 0, 0, 36 );
vecEnd = vecSrc + forward * 10;
UTIL_TraceHull( vecSrc, vecEnd, VEC_HULL_MIN, VEC_HULL_MAX,
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;
}
// 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() )
{
// Try hitting my buttons occasionally
if ( random->RandomInt( 0, 100 ) > 80 )
{
// Respawn the bot
if ( random->RandomInt( 0, 1 ) == 0 )
{
buttons |= IN_JUMP;
}
else
{
buttons = 0;
}
}
}
}
if ( bot_flipout.GetInt() >= 2 )
{
QAngle angOffset = RandomAngle( -1, 1 );
botdata->lastAngles += angOffset;
for ( int i = 0 ; i < 2; i++ )
{
if ( fabs( botdata->lastAngles[ i ] - botdata->forwardAngle[ i ] ) > 15.0f )
{
if ( botdata->lastAngles[ i ] > botdata->forwardAngle[ i ] )
{
botdata->lastAngles[ i ] = botdata->forwardAngle[ i ] + 15;
}
else
{
botdata->lastAngles[ i ] = botdata->forwardAngle[ i ] - 15;
}
}
}
botdata->lastAngles[ 2 ] = 0;
pBot->SetLocalAngles( botdata->lastAngles );
}
}
// Fix up the m_fEffects flags
pBot->PostClientMessagesSent();
pBot->SetPunchAngle( QAngle( 0, 0, 0 ) );
RunPlayerMove( pBot, pBot->GetLocalAngles(), forwardmove, sidemove, upmove, buttons, impulse, frametime );
}
// Handler for the "bot" command.
CON_COMMAND_F( "bot_old", "Add a bot.", FCVAR_CHEAT )
{
// Disable the CS bots, otherwise they'll interfere with the bot code here.
//extern bool g_bEnableCSBots;
//g_bEnableCSBots = false;
CCSPlayer *pPlayer = CCSPlayer::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.
int count = args.FindArgInt( "-count", 1 );
count = clamp( count, 1, 16 );
int iTeam = -1;
const char *pVal = args.FindArg( "-team" );
if ( pVal )
{
if ( pVal[0] == '!' )
{
if ( pPlayer->GetTeamNumber() == TEAM_TERRORIST )
iTeam = TEAM_CT;
else
iTeam = TEAM_TERRORIST;
}
else if ( pVal[0] == 't' || pVal[0] == 'T' )
{
iTeam = TEAM_TERRORIST;
}
else if ( pVal[0] == 'c' || pVal[0] == 'C' )
{
iTeam = TEAM_CT;
}
else
{
iTeam = atoi( pVal );
if ( iTeam == 1 )
iTeam = TEAM_TERRORIST;
else
iTeam = TEAM_CT;
}
}
// Look at -frozen.
bool bFrozen = !!args.FindArg( "-frozen" );
// Ok, spawn all the bots.
while ( --count >= 0 )
{
extern CBasePlayer *BotPutInServer( bool bFrozen, int iTeam );
BotPutInServer( bFrozen, iTeam );
}
}
// Handle the "PossessBot" command.
void PossessBot_f( const CCommand &args )
{
CCSPlayer *pPlayer = CCSPlayer::Instance( UTIL_GetCommandClientIndex() );
if ( !pPlayer )
return;
// Put the local player in control of this bot.
if ( args.ArgC() != 2 )
{
Warning( "PossessBot <client index>\n" );
return;
}
int iBotClient = atoi( args[1] );
int iBotEnt = iBotClient + 1;
if ( iBotClient < 0 ||
iBotClient >= gpGlobals->maxClients ||
pPlayer->entindex() == iBotEnt )
{
Warning( "PossessBot <client index>\n" );
}
else
{
edict_t *pPlayerData = pPlayer->edict();
edict_t *pBotData = INDEXENT( iBotEnt );
if ( pBotData && pBotData )
{
// SWAP EDICTS
// Backup things we don't want to swap.
edict_t oldPlayerData = *pPlayerData;
edict_t oldBotData = *pBotData;
// Swap edicts.
edict_t tmp = *pPlayerData;
*pPlayerData = *pBotData;
*pBotData = tmp;
// Restore things we didn't want to swap.
//pPlayerData->m_EntitiesTouched = oldPlayerData.m_EntitiesTouched;
//pBotData->m_EntitiesTouched = oldBotData.m_EntitiesTouched;
CBaseEntity *pPlayerBaseEnt = CBaseEntity::Instance( pPlayerData );
CBaseEntity *pBotBaseEnt = CBaseEntity::Instance( pBotData );
// Make the other a bot and make the player not a bot.
pPlayerBaseEnt->RemoveFlag( FL_FAKECLIENT );
pBotBaseEnt->AddFlag( FL_FAKECLIENT );
// Point the CBaseEntities at the right players.
pPlayerBaseEnt->NetworkProp()->SetEdict( pPlayerData );
pBotBaseEnt->NetworkProp()->SetEdict( pBotData );
// Freeze the bot.
pBotBaseEnt->AddEFlags( EFL_BOT_FROZEN );
}
}
}
ConCommand cc_PossessBot( "PossessBot", PossessBot_f, "Toggle. Possess a bot.\n\tArguments: <bot client number>", FCVAR_CHEAT );
+17
View File
@@ -0,0 +1,17 @@
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
//=============================================================================//
#ifndef CS_BOT_TEMP_H
#define CS_BOT_TEMP_H
#ifdef _WIN32
#pragma once
#endif
extern ConVar bot_mimic;
#endif // CS_BOT_TEMP_H
+220
View File
@@ -0,0 +1,220 @@
//========= Copyright © 1996-2005, 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 "cs_player.h"
#include "cs_gamerules.h"
#include "cs_bot.h"
#include "tier0/vprof.h"
#include "teamplayroundbased_gamerules.h"
#include "usermessages.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
extern bool g_fGameOver;
extern ConVar mp_maxrounds;
void FinishClientPutInServer( CCSPlayer *pPlayer )
{
pPlayer->InitialSpawn();
pPlayer->Spawn();
if (!pPlayer->IsBot())
{
// When the player first joins the server, they
pPlayer->m_iNumSpawns = 0;
pPlayer->m_takedamage = DAMAGE_NO;
pPlayer->pl.deadflag = true;
pPlayer->m_lifeState = LIFE_DEAD;
pPlayer->AddEffects( EF_NODRAW );
pPlayer->ChangeTeam( TEAM_UNASSIGNED );
// TICK_NEVER_TICK We don't want to force a Team select until after MOTD closes
pPlayer->SetContextThink( &CBasePlayer::PlayerForceTeamThink, TICK_NEVER_THINK, CS_FORCE_TEAM_THINK_CONTEXT );
pPlayer->InitializeAccount();
// Move them to the first intro camera.
pPlayer->MoveToNextIntroCamera();
pPlayer->SetMoveType( MOVETYPE_NONE );
}
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 = ' ';
}
if ( !pPlayer->IsBot() )
{
// 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
CCSPlayer *pPlayer = CCSPlayer::CreatePlayer( "player", pEdict );
pPlayer->SetPlayerName( playername );
}
void ClientActive( edict_t *pEdict, bool bLoadGame )
{
// Can't load games in CS!
Assert( !bLoadGame );
CCSPlayer *pPlayer = ToCSPlayer( CBaseEntity::Instance( pEdict ) );
FinishClientPutInServer( pPlayer );
CSingleUserRecipientFilter user( pPlayer );
user.MakeReliable();
// send the 4 end of match conditions. long frag limit, long max rounds, long rounds needed won, and long time
CCSUsrMsg_MatchEndConditions msg;
msg.set_fraglimit( fraglimit.GetInt() );
msg.set_mp_maxrounds( mp_maxrounds.GetInt() );
msg.set_mp_winlimit( mp_winlimit.GetInt() );
msg.set_mp_timelimit( mp_timelimit.GetInt() );
SendUserMessage( user, CS_UM_MatchEndConditions, msg );
}
/*
===============
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 "Counter-Strike: Global Offensive";
}
//-----------------------------------------------------------------------------
// Purpose: Precache game-specific models & sounds
//-----------------------------------------------------------------------------
PRECACHE_REGISTER_BEGIN( GLOBAL, ClientGamePrecache )
// Materials used by the client effects
PRECACHE( MODEL, "sprites/white.vmt" );
PRECACHE( MODEL, "sprites/physbeam.vmt" );
// Legacy temp ents sounds
PRECACHE( GAMESOUND, "Bounce.PistolShell" );
PRECACHE( GAMESOUND, "Bounce.RifleShell" );
PRECACHE( GAMESOUND, "Bounce.ShotgunShell" );
PRECACHE_REGISTER_END()
void ClientGamePrecache( void )
{
// Flashbang-related files
engine->ForceExactFile( "sprites/white.vmt" );
engine->ForceExactFile( "sprites/white.vtf" );
engine->ForceExactFile( "vgui/white.vmt" );
engine->ForceExactFile( "vgui/white.vtf" );
engine->ForceExactFile( "effects/flashbang.vmt" );
engine->ForceExactFile( "effects/flashbang_white.vmt" );
// Smoke grenade-related files
engine->ForceExactFile( "particle/particle_smokegrenade1.vmt" );
engine->ForceExactFile( "particle/particle_smokegrenade.vtf" );
// Sniper scope
engine->ForceExactFile( "sprites/scope_arc.vmt" );
engine->ForceExactFile( "sprites/scope_arc.vtf" );
// DSP presets - don't want people avoiding the deafening + ear ring
engine->ForceExactFile( "scripts/dsp_presets.txt" );
}
// 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_fGameOver )
return;
gpGlobals->teamplay = teamplay.GetBool();
}
//=========================================================
// instantiate the proper game rules object
//=========================================================
void InstallGameRules()
{
CreateGameRulesObject( "CCSGameRules" );
}
//-----------------------------------------------------------------------------
// Purpose:
// Input :
// Output :
//-----------------------------------------------------------------------------
void ClientFullyConnect( edict_t *pEntity )
{
}
+19
View File
@@ -0,0 +1,19 @@
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
//=============================================================================//
#ifndef CS_CLIENT_H
#define CS_CLIENT_H
#ifdef _WIN32
#pragma once
#endif
void respawn( CBaseEntity *pEdict, bool fCopyCorpse );
void FinishClientPutInServer( CCSPlayer *pPlayer );
#endif // CS_CLIENT_H
@@ -0,0 +1,463 @@
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose: A game system for tracking and updating entity spot state
//
// $NoKeywords: $
//=============================================================================//
#include "cbase.h"
#include "cs_entity_spotting.h"
#include "cs_player.h"
#include "cs_bot.h"
#include "sensorgrenade_projectile.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
#define ENTITY_SPOT_FREQUENCY 0.5f
static CCSEntitySpotting s_EntitySpotting("CCSEntitySpotting");
CCSEntitySpotting * g_EntitySpotting = &s_EntitySpotting;
ConVar radarvis( "radarvismethod", "1", FCVAR_CHEAT, "0 for traditional method, 1 for more realistic method", true, 0, true, 1 );
ConVar radarpow( "radarvispow", ".4", FCVAR_CHEAT, "the degree to which you can point away from a target, and still see them on radar." );
ConVar radardist( "radarvisdistance", "1000.0f", FCVAR_CHEAT, "at this distance and beyond you need to be point right at someone to see them", true, 10, false, 0 );
ConVar radarmaxdot( "radarvismaxdot", ".996", FCVAR_CHEAT, "how closely you have to point at someone to see them beyond max distance", true, 0, true, 1.0f );
//--------------------------------------------------------------------------------------------------------
// Functors
//--------------------------------------------------------------------------------------------------------
template < typename SpotFunctor >
bool ForEachEntitySpotter( SpotFunctor &func )
{
VPROF( "ForEachEntitySpotter" );
for ( int i = 1; i <= gpGlobals->maxClients; ++i )
{
CBasePlayer *player = static_cast< CBasePlayer * >( UTIL_PlayerByIndex( i ) );
if ( player == NULL )
continue;
if ( FNullEnt( player->edict() ) )
continue;
if ( !player->IsPlayer() )
continue;
if ( !player->IsConnected() )
continue;
if ( func( player ) == false )
return false;
}
//ActiveGrenadeList activeGrenadeList = TheBots->m_activeGrenadeList;
FOR_EACH_LL( TheCSBots()->m_activeGrenadeList, it )
{
ActiveGrenade *ag = TheCSBots()->m_activeGrenadeList[it];
if ( ag->IsSensor() == false )
continue;
CBaseGrenade *grenade = ag->GetEntity();
if ( grenade == NULL )
continue;
if ( FNullEnt( grenade->edict() ) )
continue;
if ( func( grenade ) == false )
return false;
}
/*
CBaseEntity * pEntity = gEntList.FirstEnt();
while ( pEntity )
{
if ( pEntity == NULL || FNullEnt( pEntity->edict() ) )
{
pEntity = gEntList.NextEnt( pEntity );
continue;
}
if ( FNullEnt( pEntity->edict() ) )
{
pEntity = gEntList.NextEnt( pEntity );
continue;
}
if ( pEntity->IsPlayer() )
{
CBasePlayer *player = static_cast< CBasePlayer * >( pEntity );
if ( !player->IsConnected() )
{
pEntity = gEntList.NextEnt( pEntity );
continue;
}
}
if ( func( pEntity ) == false )
return false;
// else if ( dynamic_cast<CSensorGrenadeProjectile*>( pEntity ) )
// {
// if ( pThrower->IsOtherEnemy( pPlayer ) )
// {
// Vector vDelta = pPlayer->EyePosition() - GetAbsOrigin();
// float flDistance = vDelta.Length();
//
// float flMaxDrawDist = 1024;
// if ( flDistance <= flMaxDrawDist )
// {
// trace_t tr;
// //if ( pCSPlayer->IsAlive() && ( flTargetIDCone > flViewCone ) && !bShowAllNamesForSpec )
// {
// if ( TheCSBots()->IsLineBlockedBySmoke( pPlayer->EyePosition(), GetAbsOrigin(), 1.0f ) )
// {
// continue;
// }
//
// UTIL_TraceLine( pPlayer->EyePosition(), GetAbsOrigin(), MASK_VISIBLE, pPlayer, COLLISION_GROUP_DEBRIS, &tr );
// if ( tr.fraction != 1 )
// {
// trace_t tr2;
// UTIL_TraceLine( pPlayer->GetAbsOrigin() + Vector( 0, 0, 16 ), GetAbsOrigin(), MASK_VISIBLE, pPlayer, COLLISION_GROUP_DEBRIS, &tr2 );
// if ( tr2.fraction != 1 )
// {
// continue;
// }
// }
//
// pPlayer->SetIsSpotted( true );
// pPlayer->SetIsSpottedBy( nThrowerIndex );
// }
// }
// }
// }
pEntity = gEntList.NextEnt( pEntity );
}*/
return true;
}
//==================================================
// - CanPlayerSeeTargetEntityFunctor -
//
// Determine if a player has spotted a target entity.
// Query IsSpotted() for result
//==================================================
class CanPlayerSeeTargetEntityFunctor
{
public:
CanPlayerSeeTargetEntityFunctor( CBaseEntity *entity, int spottingTeam )
{
m_targetEntity = entity;
m_target = entity->EyePosition();
m_team = spottingTeam;
m_spotted = false;
}
bool operator()( CBaseEntity *spotter )
{
CCSPlayer *csPlayer = ToCSPlayer( spotter );
if ( (csPlayer && csPlayer->IsAlive() == false ) || spotter->GetTeamNumber() != m_team )
return true;
CCSPlayer *csPlayerSpotter = NULL;
bool doTrace = false;
Vector eye, forward;
if ( csPlayer )
{
csPlayerSpotter = csPlayer;
if ( csPlayer->IsBlind() )
return true;
csPlayer->EyePositionAndVectors( &eye, &forward, NULL, NULL );
Vector path( m_target - eye );
float distance = path.Length();
path.NormalizeInPlace();
float dot = DotProduct( forward, path );
if ( dot < 0 )
return true;
int rvm = radarvis.GetInt();
switch ( rvm )
{
case 0:// original method
doTrace = ( ( dot > 0.995f )
|| ( dot > 0.98f && distance < 900 )
|| ( dot > 0.8f && distance < 250 )
);
break;
case 1: // new method method
{
int fov = csPlayer->GetFOVForNetworking() / 2;
float cosfov = cosf( ( float )fov*3.1415f / 180.0f );
float d = distance / radardist.GetFloat();
d = clamp( powf( d, radarpow.GetFloat() ), cosfov, radarmaxdot.GetFloat() );
doTrace = ( dot > d );
}
break;
}
}
else if ( dynamic_cast<CSensorGrenadeProjectile*>( spotter ) )
{
//CCSPlayer *csTargetPlayer = ToCSPlayer( m_targetEntity );
//if ( spotter->IsOtherEnemy( m_targetEntity ) )
{
eye = spotter->GetAbsOrigin();
csPlayerSpotter = ToCSPlayer( dynamic_cast<CSensorGrenadeProjectile*>( spotter )->GetThrower() );
doTrace = true;
/*
Vector vDelta = csTargetPlayer->EyePosition() - spotter->GetAbsOrigin();
float flDistance = vDelta.Length();
float flMaxDrawDist = 1024;
if ( flDistance <= flMaxDrawDist )
{
trace_t tr;
//if ( pCSPlayer->IsAlive() && ( flTargetIDCone > flViewCone ) && !bShowAllNamesForSpec )
{
if ( TheCSBots()->IsLineBlockedBySmoke( csTargetPlayer->EyePosition(), GetAbsOrigin(), 1.0f ) == false )
{
UTIL_TraceLine( csTargetPlayer->EyePosition(), GetAbsOrigin(), MASK_VISIBLE, csTargetPlayer, COLLISION_GROUP_DEBRIS, &tr );
trace_t tr2;
UTIL_TraceLine( csTargetPlayer->GetAbsOrigin() + Vector( 0, 0, 16 ), GetAbsOrigin(), MASK_VISIBLE, csTargetPlayer, COLLISION_GROUP_DEBRIS, &tr2 );
if ( tr1.fraction == 1 || tr2.fraction == 1 )
{
continue;
}
}
}
pPlayer->SetIsSpotted( true );
pPlayer->SetIsSpottedBy( nThrowerIndex );
}
}
*/
}
}
if (doTrace && csPlayerSpotter)
{
trace_t tr;
CTraceFilterSkipTwoEntities filter( spotter, m_targetEntity, COLLISION_GROUP_DEBRIS );
UTIL_TraceLine( eye, m_target,
(CONTENTS_OPAQUE|CONTENTS_SOLID|CONTENTS_MOVEABLE|CONTENTS_DEBRIS|MASK_OPAQUE_AND_NPCS), &filter, &tr );
if ( tr.fraction == 1.0f && TheCSBots()->IsLineBlockedBySmoke( eye, m_target, 1.0f ) == false )
{
if ( !csPlayer || (csPlayer && csPlayer->GetFogObscuredRatio( m_targetEntity ) < 0.9) )
{
m_spotted = true;
m_spottedBy.AddToTail( csPlayerSpotter->entindex() );
//return false; // spotted already, so no reason to check for other players spotting the same thing.
}
}
}
return true;
}
bool IsSpotted( void ) const
{
return m_spotted;
}
int GetIsSpottedBy( int nUtlIndex ) const
{
if ( nUtlIndex < m_spottedBy.Count() )
return m_spottedBy[nUtlIndex];
return 0;
}
int GetSpottedByCount( void ) const
{
return m_spottedBy.Count();
}
private:
CBaseEntity *m_targetEntity;
Vector m_target;
int m_team;
bool m_spotted;
CUtlVector < int > m_spottedBy;
};
#define BIT_SET( a, b ) ((a)[(b)>>3] & (1<<((b)&7)))
extern bool WasPlayerOccluded( int fromplayer, int toplayer );
//===========================================================
// - GatherNonPVSSpottedEntitiesFunctor -
//
// Given a player, generate a list of spotted entities that
// exist outside of that player's PVS.
// Query GetSpotted for result
//===========================================================
GatherNonPVSSpottedEntitiesFunctor::GatherNonPVSSpottedEntitiesFunctor( CCSPlayer * pPlayer ) : m_pPlayer( pPlayer )
{
if ( pPlayer )
{
m_nSourceTeam = pPlayer->GetAssociatedTeamNumber();
engine->GetPVSForCluster( engine->GetClusterForOrigin( pPlayer->EyePosition() ), sizeof( m_pSourcePVS ), m_pSourcePVS );
// spectators and OBS_ALLOW_ALL observers receive updates on all spottable entities
if ( m_nSourceTeam == TEAM_SPECTATOR )
{
m_bForceSpot = true;
}
else if ( pPlayer->GetObserverMode() != OBS_MODE_NONE )
{
ConVarRef mp_forcecamera( "mp_forcecamera" );
m_bForceSpot = mp_forcecamera.GetInt() == OBS_ALLOW_ALL;
}
else
{
m_bForceSpot = false;
}
}
}
bool GatherNonPVSSpottedEntitiesFunctor::operator()( CBaseEntity * pEntity )
{
if ( !pEntity->edict() || !pEntity->CanBeSpotted() )
{
return true;
}
CBaseEntity *pParent = pEntity->GetRootMoveParent();
int iBitNumber = engine->GetClusterForOrigin( pParent->EyePosition() );
// We only care about entities who are not within this player's PVS
// We include being occluded as being outside of PVS.
if ( !BIT_SET( m_pSourcePVS, iBitNumber ) || ( m_pPlayer && pParent->entindex() <= MAX_PLAYERS && WasPlayerOccluded( pParent->entindex(), m_pPlayer->entindex() ) ) )
{
// target outside of PVS
int nSpotRules = pEntity->GetSpotRules();
bool bForceSpotted = false;
if ( ( nSpotRules & CCSEntitySpotting::SPOT_RULE_ALWAYS_SEEN_BY_FRIEND ) &&
( pEntity->GetTeamNumber() == m_nSourceTeam ) )
bForceSpotted = true;
if ( ( nSpotRules & CCSEntitySpotting::SPOT_RULE_ALWAYS_SEEN_BY_CT ) &&
( TEAM_CT == m_nSourceTeam ) )
bForceSpotted = true;
if ( ( nSpotRules & CCSEntitySpotting::SPOT_RULE_ALWAYS_SEEN_BY_T ) &&
( TEAM_TERRORIST == m_nSourceTeam ) )
bForceSpotted = true;
CBasePlayer * pPlayer = UTIL_PlayerByIndex( pEntity->entindex() );
if ( pPlayer )
{
// do not include dead players, observers, etc
if ( !pPlayer->IsAlive() || pPlayer->IsObserver() || !pPlayer->IsConnected() )
{
return true;
}
}
m_EntitySpotted.Set( pEntity->entindex(), ( m_bForceSpot || pEntity->IsSpotted() || bForceSpotted ) );
}
return true;
}
//--------------------------------------------------------------------------------------------------------
// Entity Spotting Game System
//--------------------------------------------------------------------------------------------------------
CCSEntitySpotting::CCSEntitySpotting( const char * szName ) : CAutoGameSystemPerFrame( szName ),
m_fLastUpdate( 0.0f )
{
}
void CCSEntitySpotting::FrameUpdatePostEntityThink( void )
{
if ( gpGlobals->curtime > ( m_fLastUpdate + ENTITY_SPOT_FREQUENCY ) )
{
UpdateSpottedEntities();
}
}
void CCSEntitySpotting::UpdateSpottedEntities( void )
{
m_fLastUpdate = gpGlobals->curtime;
CBaseEntity * pEntity = gEntList.FirstEnt();
while ( pEntity )
{
if ( pEntity->CanBeSpotted() )
{
int nTeamID = 0;
if ( pEntity->GetSpotRules() & SPOT_RULE_ENEMY )
{
nTeamID = (pEntity->GetTeamNumber( ) == TEAM_CT) ? TEAM_TERRORIST : TEAM_CT;
}
else if ( pEntity->GetSpotRules() & SPOT_RULE_CT )
{
nTeamID = TEAM_CT;
}
else if ( pEntity->GetSpotRules() & SPOT_RULE_T )
{
nTeamID = TEAM_TERRORIST;
}
CanPlayerSeeTargetEntityFunctor canPlayerSeeTargetEntity( pEntity, nTeamID );
ForEachEntitySpotter( canPlayerSeeTargetEntity );
pEntity->SetIsSpotted( canPlayerSeeTargetEntity.IsSpotted( ) );
pEntity->ClearSpottedBy();
if ( canPlayerSeeTargetEntity.IsSpotted() )
{
for ( int i = 0; i < canPlayerSeeTargetEntity.GetSpottedByCount(); i++ )
{
pEntity->SetIsSpottedBy( canPlayerSeeTargetEntity.GetIsSpottedBy( i ) );
}
}
}
pEntity = gEntList.NextEnt( pEntity );
}
}
bool CCSEntitySpotting::Init( void )
{
return true;
}
@@ -0,0 +1,77 @@
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose: A game system for tracking and updating entity spot state
//
// $NoKeywords: $
//=============================================================================//
#ifndef CS_ENTITY_SPOTTING_H
#define CS_ENTITY_SPOTTING_H
#ifdef _WIN32
#pragma once
#endif
#include "igamesystem.h"
class CCSPlayer;
class CCSEntitySpotting : public CAutoGameSystemPerFrame
{
public:
enum SpottingRules_T
{
SPOT_RULE_ENEMY = ( 1<<0 ), // spotted when seen by an enemy
SPOT_RULE_CT = ( 1<<1 ), // spotted when seen by CT
SPOT_RULE_T = ( 1<<2 ), // spotted when seen by T
SPOT_RULE_ALWAYS_SEEN_BY_FRIEND = ( 1<<3 ), // always visible to friend
SPOT_RULE_ALWAYS_SEEN_BY_CT = ( 1<<4 ), // always visible to CT
SPOT_RULE_ALWAYS_SEEN_BY_T = ( 1<<5 ), // always visible to T
};
CCSEntitySpotting( const char * szName );
virtual ~CCSEntitySpotting( void ) {}
virtual bool Init( void );
virtual char const *Name() { return "CCSEntitySpotting"; }
virtual void FrameUpdatePostEntityThink( void );
void UpdateSpottedEntities( void );
protected:
float m_fLastUpdate;
};
extern CCSEntitySpotting * g_EntitySpotting;
//===========================================================
// - GatherNonPVSSpottedEntitiesFunctor -
//
// Given a player, generate a list of spotted entities that
// exist outside of that player's PVS.
// Query GetSpotted for result
//===========================================================
class GatherNonPVSSpottedEntitiesFunctor
{
public:
GatherNonPVSSpottedEntitiesFunctor( CCSPlayer * pPlayer );
bool operator()( CBaseEntity * pEntity );
const CBitVec<MAX_EDICTS> & GetSpotted( void )
{
return m_EntitySpotted;
}
private:
CCSPlayer *m_pPlayer;
int m_nSourceTeam;
CBitVec<MAX_EDICTS> m_EntitySpotted;
byte m_pSourcePVS[MAX_MAP_LEAFS/8];
bool m_bForceSpot;
};
#endif // ENTITY_RESOURCE_H
+573
View File
@@ -0,0 +1,573 @@
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//
//=============================================================================//
#include "cbase.h"
#include "../EventLog.h"
#include "team.h"
#include "cs_gamerules.h"
#include "keyvalues.h"
#define LOG_DETAIL_ENEMY_ATTACKS 0x01
#define LOG_DETAIL_TEAMMATE_ATTACKS 0x02
ConVar mp_logdetail( "mp_logdetail", "0", FCVAR_RELEASE, "Logs attacks. Values are: 0=off, 1=enemy, 2=teammate, 3=both)", true, 0.0f, true, 3.0f );
class CCSEventLog : public CEventLog
{
private:
typedef CEventLog BaseClass;
public:
bool PrintEvent( IGameEvent *event ) // override virtual function
{
if ( !PrintCStrikeEvent( event ) ) // allow CS to override logging
{
return BaseClass::PrintEvent( event );
}
else
{
return true;
}
}
bool Init()
{
BaseClass::Init();
// listen to CS events
ListenForGameEvent( "round_end" );
ListenForGameEvent( "round_start" );
ListenForGameEvent( "bomb_pickup" );
ListenForGameEvent( "bomb_begindefuse" );
ListenForGameEvent( "bomb_dropped" );
ListenForGameEvent( "bomb_defused" );
ListenForGameEvent( "bomb_planted" );
ListenForGameEvent( "bomb_beginplant" );
ListenForGameEvent( "hostage_rescued" );
ListenForGameEvent( "hostage_killed" );
ListenForGameEvent( "hostage_follows" );
ListenForGameEvent( "player_hurt" );
ListenForGameEvent( "player_death" );
ListenForGameEvent( "other_death" );
ListenForGameEvent( "hegrenade_detonate" );
ListenForGameEvent( "flashbang_detonate" );
ListenForGameEvent( "player_blind" );
ListenForGameEvent( "smokegrenade_detonate" );
ListenForGameEvent( "molotov_detonate" );
ListenForGameEvent( "decoy_detonate" );
ListenForGameEvent( "item_purchase" );
return true;
}
protected:
bool PrintCStrikeEvent( IGameEvent *event ) // print Mod specific logs
{
const char *eventName = event->GetName();
// messages that don't have a user associated to them
if ( StringHasPrefixCaseSensitive( eventName, "round_end" ) )
{
const int winner = event->GetInt( "winner" );
const int reason = event->GetInt( "reason" );
const char *msg = event->GetString( "message" );
msg++; // remove the '#' char
switch( reason )
{
case Game_Commencing:
UTIL_LogPrintf( "World triggered \"Game_Commencing\"\n" );
return true;
break;
}
CTeam *ct = GetGlobalTeam( TEAM_CT );
CTeam *ter = GetGlobalTeam( TEAM_TERRORIST );
Assert( ct && ter );
switch ( winner )
{
case WINNER_CT:
UTIL_LogPrintf( "Team \"%s\" triggered \"%s\" (CT \"%i\") (T \"%i\")\n", ct->GetName(), msg, ct->GetScore(), ter->GetScore() );
break;
case WINNER_TER:
UTIL_LogPrintf( "Team \"%s\" triggered \"%s\" (CT \"%i\") (T \"%i\")\n", ter->GetName(), msg, ct->GetScore(), ter->GetScore() );
break;
case WINNER_DRAW:
default:
UTIL_LogPrintf( "World triggered \"%s\" (CT \"%i\") (T \"%i\")\n", msg, ct->GetScore(), ter->GetScore() );
break;
}
UTIL_LogPrintf( "Team \"CT\" scored \"%i\" with \"%i\" players\n", ct->GetScore(), ct->GetNumPlayers() );
UTIL_LogPrintf( "Team \"TERRORIST\" scored \"%i\" with \"%i\" players\n", ter->GetScore(), ter->GetNumPlayers() );
UTIL_LogPrintf("World triggered \"Round_End\"\n");
return true;
}
else if ( StringHasPrefixCaseSensitive( eventName, "other_death" ) )
{
const int attackerid = event->GetInt( "attacker" );
const char *weapon = event->GetString( "weapon" );
const bool headShot = ( event->GetInt( "headshot" ) == 1 );
const bool penetrated = ( event->GetInt( "penetrated" ) > 0 );
const bool domination = false;
const bool revenge = false;
CBasePlayer *pAttacker = UTIL_PlayerByUserId( attackerid );
const int otherid = event->GetInt( "otherid" );
CBaseEntity *pOther = UTIL_EntityByIndex( otherid );
if ( pAttacker && otherid && pOther )
{
UTIL_LogPrintf( "\"%s<%i><%s><%s>\" [%.0f %.0f %.0f] killed other \"%s<%i>\" [%.0f %.0f %.0f] with \"%s\"%s%s%s%s%s%s%s%s%s\n",
//attacker
pAttacker->GetPlayerName(),
attackerid,
pAttacker->GetNetworkIDString(),
pAttacker->GetTeam()->GetName(),
pAttacker->GetAbsOrigin().x,
pAttacker->GetAbsOrigin().y,
pAttacker->GetAbsOrigin().z,
//target
event->GetString( "othertype" ),
event->GetInt( "otherid" ),
pOther->GetAbsOrigin().x,
pOther->GetAbsOrigin().y,
pOther->GetAbsOrigin().z,
//weapon
weapon,
( domination || revenge || headShot || penetrated ) ? " (" : "",
domination ? "domination" : "",
( revenge && ( domination ) ) ? " " : "",
revenge ? "revenge" : "",
( headShot && ( domination || revenge ) ) ? " " : "",
headShot ? "headshot" : "",
( penetrated && ( domination || revenge || headShot ) ) ? " " : "",
penetrated ? "penetrated" : "",
( domination || revenge || headShot || penetrated ) ? ")" : ""
);
}
else if ( pAttacker )
{
UTIL_LogPrintf( "\"%s<%i><%s><%s>\" [%.0f %.0f %.0f] killed other \"%s\" with \"%s\"%s%s%s%s%s%s%s%s%s\n",
//attacker
pAttacker->GetPlayerName(),
attackerid,
pAttacker->GetNetworkIDString(),
pAttacker->GetTeam()->GetName(),
pAttacker->GetAbsOrigin().x,
pAttacker->GetAbsOrigin().y,
pAttacker->GetAbsOrigin().z,
//target
event->GetString( "othertype" ),
//weapon
weapon,
( domination || revenge || headShot || penetrated ) ? " (" : "",
domination ? "domination" : "",
( revenge && ( domination ) ) ? " " : "",
revenge ? "revenge" : "",
( headShot && ( domination || revenge ) ) ? " " : "",
headShot ? "headshot" : "",
( penetrated && ( domination || revenge || headShot ) ) ? " " : "",
penetrated ? "penetrated" : "",
( domination || revenge || headShot || penetrated ) ? ")" : ""
);
}
return true;
}
else if ( StringHasPrefixCaseSensitive( eventName, "server_" ) )
{
return false; // ignore server_ messages
}
const int userid = event->GetInt( "userid" );
CBasePlayer *pPlayer = UTIL_PlayerByUserId( userid );
if ( !pPlayer )
{
return false;
}
if ( FStrEq( eventName, "player_hurt" ) )
{
const int attackerid = event->GetInt("attacker" );
const char *weapon = event->GetString( "weapon" );
CBasePlayer *pAttacker = UTIL_PlayerByUserId( attackerid );
if ( !pAttacker )
{
return false;
}
bool isTeamAttack = ( (pPlayer->GetTeamNumber() == pAttacker->GetTeamNumber() ) && (pPlayer != pAttacker) );
int detail = mp_logdetail.GetInt();
if ( ( isTeamAttack && ( detail & LOG_DETAIL_TEAMMATE_ATTACKS ) ) ||
( !isTeamAttack && ( detail & LOG_DETAIL_ENEMY_ATTACKS ) ) )
{
int hitgroup = event->GetInt( "hitgroup" );
const char *hitgroupStr = "GENERIC";
switch ( hitgroup )
{
case HITGROUP_GENERIC:
hitgroupStr = "generic";
break;
case HITGROUP_HEAD:
hitgroupStr = "head";
break;
case HITGROUP_CHEST:
hitgroupStr = "chest";
break;
case HITGROUP_STOMACH:
hitgroupStr = "stomach";
break;
case HITGROUP_LEFTARM:
hitgroupStr = "left arm";
break;
case HITGROUP_RIGHTARM:
hitgroupStr = "right arm";
break;
case HITGROUP_LEFTLEG:
hitgroupStr = "left leg";
break;
case HITGROUP_RIGHTLEG:
hitgroupStr = "right leg";
break;
}
UTIL_LogPrintf( "\"%s<%i><%s><%s>\" [%.0f %.0f %.0f] attacked \"%s<%i><%s><%s>\" [%.0f %.0f %.0f] with \"%s\" (damage \"%d\") (damage_armor \"%d\") (health \"%d\") (armor \"%d\") (hitgroup \"%s\")\n",
pAttacker->GetPlayerName(),
attackerid,
pAttacker->GetNetworkIDString(),
pAttacker->GetTeam()->GetName(),
pAttacker->GetAbsOrigin().x,
pAttacker->GetAbsOrigin().y,
pAttacker->GetAbsOrigin().z,
pPlayer->GetPlayerName(),
userid,
pPlayer->GetNetworkIDString(),
pPlayer->GetTeam()->GetName(),
pPlayer->GetAbsOrigin().x,
pPlayer->GetAbsOrigin().y,
pPlayer->GetAbsOrigin().z,
weapon,
event->GetInt( "dmg_health" ),
event->GetInt( "dmg_armor" ),
event->GetInt( "health" ),
event->GetInt( "armor" ),
hitgroupStr );
}
return true;
}
else if ( StringHasPrefixCaseSensitive( eventName, "player_death" ) )
{
const int attackerid = event->GetInt("attacker" );
const char *weapon = event->GetString( "weapon" );
const bool headShot = (event->GetInt( "headshot" ) == 1);
const bool penetrated = (event->GetInt( "penetrated" ) > 0);
const bool domination = (event->GetInt( "dominated" ) > 0);
const bool revenge = (event->GetInt( "revenge" ) > 0);
CBasePlayer *pAttacker = UTIL_PlayerByUserId( attackerid );
const int assisterid = event->GetInt( "assister" );
CBasePlayer *pAssister = UTIL_PlayerByUserId( assisterid );
if ( pPlayer == pAttacker )
{
UTIL_LogPrintf( "\"%s<%i><%s><%s>\" [%.0f %.0f %.0f] committed suicide with \"%s\"\n",
pPlayer->GetPlayerName(),
userid,
pPlayer->GetNetworkIDString(),
pPlayer->GetTeam()->GetName(),
pPlayer->GetAbsOrigin().x,
pPlayer->GetAbsOrigin().y,
pPlayer->GetAbsOrigin().z,
weapon
);
}
else if ( pAttacker )
{
UTIL_LogPrintf( "\"%s<%i><%s><%s>\" [%.0f %.0f %.0f] killed \"%s<%i><%s><%s>\" [%.0f %.0f %.0f] with \"%s\"%s%s%s%s%s%s%s%s%s\n",
//attacker
pAttacker->GetPlayerName(),
attackerid,
pAttacker->GetNetworkIDString(),
pAttacker->GetTeam()->GetName(),
pAttacker->GetAbsOrigin().x,
pAttacker->GetAbsOrigin().y,
pAttacker->GetAbsOrigin().z,
//target
pPlayer->GetPlayerName(),
userid,
pPlayer->GetNetworkIDString(),
pPlayer->GetTeam()->GetName(),
pPlayer->GetAbsOrigin().x,
pPlayer->GetAbsOrigin().y,
pPlayer->GetAbsOrigin().z,
weapon,
( domination || revenge || headShot || penetrated ) ? " (" : "",
domination ? "domination":"",
( revenge && ( domination ) ) ? " " : "",
revenge ? "revenge" : "",
( headShot && ( domination || revenge ) ) ? " " : "",
headShot ? "headshot":"",
( penetrated && ( domination || revenge || headShot ) ) ? " " : "",
penetrated ? "penetrated" : "",
( domination || revenge || headShot || penetrated ) ? ")" : ""
);
if ( pAssister )
{
UTIL_LogPrintf( "\"%s<%i><%s><%s>\" assisted killing \"%s<%i><%s><%s>\"\n",
//attacker
pAssister->GetPlayerName(),
assisterid,
pAssister->GetNetworkIDString(),
pAssister->GetTeam()->GetName(),
// target
pPlayer->GetPlayerName(),
userid,
pPlayer->GetNetworkIDString(),
pPlayer->GetTeam()->GetName()
);
}
}
else
{
// killed by the world
UTIL_LogPrintf( "\"%s<%i><%s><%s>\" [%.0f %.0f %.0f] committed suicide with \"world\"\n",
pPlayer->GetPlayerName(),
userid,
pPlayer->GetNetworkIDString(),
pPlayer->GetTeam()->GetName(),
pPlayer->GetAbsOrigin().x,
pPlayer->GetAbsOrigin().y,
pPlayer->GetAbsOrigin().z
);
}
return true;
}
else if ( StringHasPrefixCaseSensitive( eventName, "hegrenade_detonate" ) )
{
UTIL_LogPrintf( "\"%s<%i><%s><%s>\" threw hegrenade [%.0f %.0f %.0f]\n",
pPlayer->GetPlayerName(),
userid,
pPlayer->GetNetworkIDString(),
pPlayer->GetTeam()->GetName(),
event->GetFloat( "x" ),
event->GetFloat( "y" ),
event->GetFloat( "z" )
);
return true;
}
else if ( StringHasPrefixCaseSensitive( eventName, "flashbang_detonate" ) )
{
UTIL_LogPrintf( "\"%s<%i><%s><%s>\" threw flashbang [%.0f %.0f %.0f] flashbang entindex %d)\n",
pPlayer->GetPlayerName(),
userid,
pPlayer->GetNetworkIDString(),
pPlayer->GetTeam()->GetName(),
event->GetFloat( "x" ),
event->GetFloat( "y" ),
event->GetFloat( "z" ),
event->GetInt( "entityid")
);
return true;
}
else if ( StringHasPrefixCaseSensitive( eventName, "player_blind" ) )
{
const int attackerid = event->GetInt( "attacker" );
CBasePlayer *pAttacker = UTIL_PlayerByUserId( attackerid );
float flDuration = event->GetFloat( "blind_duration" );
int entnum = event->GetInt( "entityid" );
UTIL_LogPrintf( "\"%s<%i><%s><%s>\" blinded for %.2f by \"%s<%i><%s><%s>\" from flashbang entindex %d \n",
pPlayer->GetPlayerName(),
userid,
pPlayer->GetNetworkIDString(),
pPlayer->GetTeam()->GetName(),
flDuration,
pAttacker ? pAttacker->GetPlayerName() : "unknown",
attackerid,
pAttacker ? pAttacker->GetNetworkIDString() : "unknown",
pAttacker ? pAttacker->GetTeam()->GetName() : "unknown",
entnum
);
return true;
}
else if ( StringHasPrefixCaseSensitive( eventName, "smokegrenade_detonate" ) )
{
UTIL_LogPrintf( "\"%s<%i><%s><%s>\" threw smokegrenade [%.0f %.0f %.0f]\n",
pPlayer->GetPlayerName(),
userid,
pPlayer->GetNetworkIDString(),
pPlayer->GetTeam()->GetName(),
event->GetFloat( "x" ),
event->GetFloat( "y" ),
event->GetFloat( "z" )
);
return true;
}
else if ( StringHasPrefixCaseSensitive( eventName, "molotov_detonate" ) )
{
UTIL_LogPrintf( "\"%s<%i><%s><%s>\" threw molotov [%.0f %.0f %.0f]\n",
pPlayer->GetPlayerName(),
userid,
pPlayer->GetNetworkIDString(),
pPlayer->GetTeam()->GetName(),
event->GetFloat( "x" ),
event->GetFloat( "y" ),
event->GetFloat( "z" )
);
return true;
}
else if ( StringHasPrefix( eventName, "decoy_detonate" ) )
{
UTIL_LogPrintf( "\"%s<%i><%s><%s>\" threw decoy [%.0f %.0f %.0f]\n",
pPlayer->GetPlayerName(),
userid,
pPlayer->GetNetworkIDString(),
pPlayer->GetTeam()->GetName(),
event->GetFloat( "x" ),
event->GetFloat( "y" ),
event->GetFloat( "z" )
);
return true;
}
else if ( StringHasPrefixCaseSensitive( eventName, "round_start" ) )
{
UTIL_LogPrintf("World triggered \"Round_Start\"\n");
return true;
}
else if ( StringHasPrefixCaseSensitive( eventName, "player_team" ) )
{
UTIL_LogPrintf( "\"%s<%i><%s>\" switched from team <%s> to <%s>\n",
pPlayer->GetPlayerName(),
userid,
pPlayer->GetNetworkIDString(),
GetGlobalTeam( event->GetInt("oldteam") )->GetName(),
GetGlobalTeam( event->GetInt("team") )->GetName()
);
return true;
}
else if ( StringHasPrefixCaseSensitive( eventName, "hostage_follows" ) )
{
UTIL_LogPrintf( "\"%s<%i><%s><CT>\" triggered \"Touched_A_Hostage\"\n",
pPlayer->GetPlayerName(),
userid,
pPlayer->GetNetworkIDString()
);
return true;
}
else if ( StringHasPrefixCaseSensitive( eventName, "hostage_killed" ) )
{
UTIL_LogPrintf( "\"%s<%i><%s><%s>\" triggered \"Killed_A_Hostage\"\n",
pPlayer->GetPlayerName(),
userid,
pPlayer->GetNetworkIDString(),
pPlayer->GetTeam()->GetName()
);
return true;
}
else if ( StringHasPrefixCaseSensitive( eventName, "hostage_rescued" ) )
{
UTIL_LogPrintf("\"%s<%i><%s><CT>\" triggered \"Rescued_A_Hostage\"\n",
pPlayer->GetPlayerName(),
userid,
pPlayer->GetNetworkIDString()
);
return true;
}
else if ( StringHasPrefixCaseSensitive( eventName, "bomb_planted" ) )
{
UTIL_LogPrintf("\"%s<%i><%s><TERRORIST>\" triggered \"Planted_The_Bomb\"\n",
pPlayer->GetPlayerName(),
userid,
pPlayer->GetNetworkIDString()
);
return true;
}
else if ( StringHasPrefix( eventName, "bomb_planted" ) )
{
UTIL_LogPrintf("\"%s<%i><%s><TERRORIST>\" triggered \"Bomb_Begin_Plant\"\n",
pPlayer->GetPlayerName(),
userid,
pPlayer->GetNetworkIDString()
);
return true;
}
else if ( StringHasPrefixCaseSensitive( eventName, "bomb_defused" ) )
{
UTIL_LogPrintf("\"%s<%i><%s><CT>\" triggered \"Defused_The_Bomb\"\n",
pPlayer->GetPlayerName(),
userid,
pPlayer->GetNetworkIDString()
);
return true;
}
else if ( StringHasPrefixCaseSensitive( eventName, "bomb_dropped" ) )
{
UTIL_LogPrintf("\"%s<%i><%s><TERRORIST>\" triggered \"Dropped_The_Bomb\"\n",
pPlayer->GetPlayerName(),
userid,
pPlayer->GetNetworkIDString()
);
return true;
}
else if ( StringHasPrefixCaseSensitive( eventName, "bomb_begindefuse" ) )
{
const bool haskit = (event->GetInt( "haskit" ) == 1);
UTIL_LogPrintf("\"%s<%i><%s><CT>\" triggered \"%s\"\n",
pPlayer->GetPlayerName(),
userid,
pPlayer->GetNetworkIDString(),
haskit ? "Begin_Bomb_Defuse_With_Kit" : "Begin_Bomb_Defuse_Without_Kit"
);
return true;
}
else if ( StringHasPrefixCaseSensitive( eventName, "bomb_pickup" ) )
{
UTIL_LogPrintf("\"%s<%i><%s><TERRORIST>\" triggered \"Got_The_Bomb\"\n",
pPlayer->GetPlayerName(),
userid,
pPlayer->GetNetworkIDString()
);
return true;
}
else if ( StringHasPrefixCaseSensitive( eventName, "item_purchase" ) )
{
const char *weapon = event->GetString( "weapon" );
UTIL_LogPrintf( "\"%s<%i><%s><%s>\" purchased \"%s\"\n",
pPlayer->GetPlayerName(),
userid,
pPlayer->GetNetworkIDString(),
pPlayer->GetTeam()->GetName(),
weapon
);
return true;
}
// unused events:
//hostage_hurt
//bomb_exploded
return false;
}
};
CCSEventLog g_CSEventLog;
//-----------------------------------------------------------------------------
// Singleton access
//-----------------------------------------------------------------------------
CEventLog* GameLogSystem()
{
return &g_CSEventLog;
}
+672
View File
@@ -0,0 +1,672 @@
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
//=============================================================================//
#include "cbase.h"
#include "cdll_int.h"
#include "gameinterface.h"
#include "mapentities.h"
#include "cs_gameinterface.h"
#include "ai_responsesystem.h"
#include "iachievementmgr.h"
#include "fmtstr.h"
#include "gametypes.h"
#include "matchmaking/imatchframework.h"
#include "cs_shareddefs.h"
#include "cs_gamerules.h"
#include "gametypes.h"
#include "engine/inetsupport.h"
#include "dedicated_server_ugc_manager.h"
#include "cs_player.h"
#include "server_log_http_dispatcher.h"
#include "netmessages.h"
#include "usermessages.h"
// NOTE: This has to be the last file included!
#include "tier0/memdbgon.h"
//////////////////////////////////////////////////////////////////////////
//
// Convars
//
ConVar sv_workshop_allow_other_maps( "sv_workshop_allow_other_maps", "1", FCVAR_RELEASE, "When hosting a workshop collection, users can play other workshop map on this server when it is empty and then mapcycle into this server collection." );
static ConVar tv_allow_camera_man_steamid( "tv_allow_camera_man_steamid", "", FCVAR_RELEASE, "Allows tournament production cameraman to run csgo.exe -interactivecaster on SteamID 7650123456XXX and be the camera man." );
// #define SVGC_RESERVATION_DEBUG 1
// -------------------------------------------------------------------------------------------- //
// Mod-specific CServerGameClients implementation.
// -------------------------------------------------------------------------------------------- //
void CServerGameClients::GetPlayerLimits( int& minplayers, int& maxplayers, int &defaultMaxPlayers ) const
{
minplayers = 1; // allow single player for the test maps (but we default to multi)
maxplayers = MAX_PLAYERS;
defaultMaxPlayers = MAX_PLAYERS;
}
// -------------------------------------------------------------------------------------------- //
// Mod-specific CServerGameDLL implementation.
// -------------------------------------------------------------------------------------------- //
void CServerGameDLL::LevelInit_ParseAllEntities( const char *pMapEntities )
{
if ( Q_strcmp( STRING(gpGlobals->mapname), "cs_" ) )
{
// don't precache AI responses (hostages) if it's not a hostage rescure map
extern IResponseSystem *g_pResponseSystem;
g_pResponseSystem->PrecacheResponses( false );
}
}
//
// Twitch.tv reservation updates
//
class ClientJob_EMsgGCCStrike15_v2_GC2ServerReservationUpdate : public GCSDK::CGCClientJob
{
public:
ClientJob_EMsgGCCStrike15_v2_GC2ServerReservationUpdate( GCSDK::CGCClient *pGCClient )
: GCSDK::CGCClientJob( pGCClient )
{
}
virtual bool BYieldingRunJobFromMsg( GCSDK::IMsgNetPacket *pNetPacket )
{
GCSDK::CProtoBufMsg<CMsgGCCStrike15_v2_GC2ServerReservationUpdate> msg( pNetPacket );
uint32 numTotalViewers = msg.Body().viewers_external_total();
uint32 numSteamLinkedViewers = msg.Body().viewers_external_steam();
engine->UpdateHltvExternalViewers( numTotalViewers, numSteamLinkedViewers );
return true;
}
};
GC_REG_CLIENT_JOB( ClientJob_EMsgGCCStrike15_v2_GC2ServerReservationUpdate, k_EMsgGCCStrike15_v2_GC2ServerReservationUpdate );
void GCCStrikeWelcomeMessageReceived( CMsgCStrike15Welcome const &msgCStrike )
{
}
bool Helper_FillServerReservationStateAndPlayers( CMsgGCCStrike15_v2_MatchmakingServerReservationResponse &msgbody )
{
if ( !engine->IsDedicatedServer() )
return false;
msgbody.set_server_version( ( ( INetSupport * ) g_pMatchFramework->GetMatchExtensions()->GetRegisteredExtensionInterface( INETSUPPORT_VERSION_STRING ) )->GetEngineBuildNumber() );
msgbody.set_map( STRING( gpGlobals->mapname ) );
static ConVarRef sv_steamdatagramtransport_port( "sv_steamdatagramtransport_port" );
// Expose information about our community server GOTV port so that clients could connect
static ConVarRef tv_advertise_watchable( "tv_advertise_watchable" );
static int s_nTvPort = 0; // make the TV port sticky: if we reported it non-zero once then keep reporting
CEngineHltvInfo_t engineHltvInfo;
if ( tv_advertise_watchable.GetBool() &&
engine->GetEngineHltvInfo( engineHltvInfo ) && engineHltvInfo.m_bBroadcastActive )
{
s_nTvPort = engineHltvInfo.m_nTvPort;
}
if ( s_nTvPort )
{
msgbody.mutable_tv_info()->set_tv_udp_port( s_nTvPort );
}
// Build the list of players who are actively playing on the game server
for ( int i = 1; i <= gpGlobals->maxClients; i++ )
{
CBasePlayer *pPlayer = UTIL_PlayerByIndex( i );
if ( pPlayer )
{
if ( pPlayer->IsBot() )
continue;
CSteamID steamIdPlayer;
if ( !pPlayer->GetSteamID( &steamIdPlayer ) )
continue;
if ( !steamIdPlayer.IsValid() )
continue;
switch ( pPlayer->GetTeamNumber() )
{
case TEAM_CT:
case TEAM_TERRORIST:
msgbody.add_reward_player_accounts( steamIdPlayer.GetAccountID() );
break;
default:
msgbody.add_idle_player_accounts( steamIdPlayer.GetAccountID() );
break;
}
}
}
return true;
}
void CServerGameDLL::UpdateGCInformation()
{
/** Removed for partner depot **/
}
// Marks the queue matchmaking game as starting
void CServerGameDLL::ReportGCQueuedMatchStart( int32 iReservationStage, uint32 *puiConfirmedAccounts, int numConfirmedAccounts )
{
/** Removed for partner depot **/
}
//-----------------------------------------------------------------------------
// Purpose: A user has had their network id setup and validated
//-----------------------------------------------------------------------------
void CServerGameClients::NetworkIDValidated( const char *pszUserName, const char *pszNetworkID, CSteamID steamID )
{
/** Removed for partner depot **/
}
//
// Order workshop maps by MRU
//
static int Helper_SortWorkshopMapsMRU( const DedicatedServerUGCFileInfo_t * const *a, const DedicatedServerUGCFileInfo_t * const *b )
{
if ( (*a)->m_dblPlatFloatTimeReceived != (*b)->m_dblPlatFloatTimeReceived )
return ( (*a)->m_dblPlatFloatTimeReceived > (*b)->m_dblPlatFloatTimeReceived ) ? -1 : 1;
else
return 0;
}
//
// Matchmaking game data buffer to set into SteamGameServer()->SetGameData
//
void CServerGameDLL::GetMatchmakingGameData( char *buf, size_t bufSize )
{
char * const bufBase = buf;
int len = 0;
extern ConVar game_type;
extern ConVar game_mode;
// Put the game key
Q_snprintf( buf, bufSize, "g:csgo,gt:%u,gm:%u,", game_type.GetInt(), game_mode.GetInt() );
len = strlen( buf );
buf += len;
bufSize -= len;
if ( gpGlobals && !StringIsEmpty( gpGlobals->mapGroupName.ToCStr() ) )
{
const CUtlStringList* mapsInGroup = g_pGameTypes->GetMapGroupMapList( gpGlobals->mapGroupName.ToCStr() );
if ( mapsInGroup && g_pGameTypes->IsWorkshopMapGroup( gpGlobals->mapGroupName.ToCStr() ) )
{
if ( sv_workshop_allow_other_maps.GetBool() && ( bufSize >= 7 ) )
{ // Advertise support for other maps
Q_strncpy( buf, "wks:1,", 7 );
buf += 6;
bufSize -= 7;
}
CUtlVector< PublishedFileId_t > arrAdvertisedFileIds;
FOR_EACH_VEC( *mapsInGroup, i )
{
PublishedFileId_t id = DedicatedServerWorkshop().GetUGCMapPublishedFileID((*mapsInGroup)[i]);
CFmtStr szIdAsHexString( "%llx", id );
size_t len = szIdAsHexString.Length();
if ( bufSize <= len + 1 )
{
Warning( "GameData: Too many community maps installed, not advertising for map id \"%llu (0x%s)\"\n", id, szIdAsHexString.Access() );
continue;
}
Q_strncpy( buf, szIdAsHexString.Access(), len + 1 );
buf += len;
*( buf ++ ) = ',';
bufSize -= len + 1;
arrAdvertisedFileIds.AddToTail( id );
}
// Advertise maps that have been recently checked and downloaded from Workshop
if ( sv_workshop_allow_other_maps.GetBool() )
{
CUtlVector<const DedicatedServerUGCFileInfo_t *> arrInfoMaps;
DedicatedServerWorkshop().GetWorkshopMasWithValidUgcInformation( arrInfoMaps );
arrInfoMaps.Sort( Helper_SortWorkshopMapsMRU );
FOR_EACH_VEC( arrInfoMaps, iInfoMap )
{
PublishedFileId_t id = arrInfoMaps[iInfoMap]->fileId;
if ( arrAdvertisedFileIds.Find( id ) != arrAdvertisedFileIds.InvalidIndex() )
continue; // already advertised
CFmtStr szIdAsHexString( "%llx", id );
size_t len = szIdAsHexString.Length();
if ( bufSize <= len + 1 )
break; // Advertise only as much downloaded stuff as can fit
Q_strncpy( buf, szIdAsHexString.Access(), len + 1 );
buf += len;
*( buf ++ ) = ',';
bufSize -= len + 1;
}
}
}
}
// Trim the last comma if anything was written
if ( buf > bufBase )
buf[ -1 ] = 0;
}
// this returns true if they were already in the list or were successfully added
// returns false if they were not added (not allowed to be a caster)
bool AddAccountToActiveCasters( const CSteamID &steamID )
{
// first check if they are already in the list
bool bAlreadyAdded = false;
for ( int j = 0; j < CSGameRules()->m_arrTournamentActiveCasterAccounts.Count(); j++ )
{
if ( steamID.GetAccountID() == CSGameRules()->m_arrTournamentActiveCasterAccounts[ j ] )
{
// this caster is already in the list so skip adding them, but allow them
bAlreadyAdded = true;
break;
}
if ( CSGameRules()->m_arrTournamentActiveCasterAccounts[ j ] )
{
// already have an active caster, so don't allow another
return false;
}
}
if ( !bAlreadyAdded )
{
// not already added, so find an empty slot and put them in it
for (int j = 0; j < CSGameRules()->m_arrTournamentActiveCasterAccounts.Count(); j++ )
{
if ( CSGameRules()->m_arrTournamentActiveCasterAccounts[ j ] == 0 )
{
CSGameRules()->m_arrTournamentActiveCasterAccounts.Set( j, steamID.GetAccountID() );
if ( steamapicontext->SteamUser() && steamapicontext->SteamFriends() )
{
const char *pszName = steamapicontext->SteamFriends()->GetFriendPersonaName( steamID );
ConMsg( "Adding %s (ID:%d) to active caster list!\n", pszName, steamID.GetAccountID() );
}
else
{
ConMsg( "Adding ID:%d to active caster list!\n", steamID.GetAccountID() );
}
break;
}
}
}
return true;
}
// validate if player is a caster and is not playing in the current game, then add them to the active caster list
// returns false if they are not allow to be a caster
bool CServerGameDLL::ValidateAndAddActiveCaster( const CSteamID &steamID )
{
// check if they are a player in the current game. Note: players can be casters sometimes (and might be in the casters list below), but we don't want their voice data "public" when they are playing
for ( int i = 0; i < CCSGameRules::sm_QueuedServerReservation.account_ids().size(); i++ )
{
if ( steamID.GetAccountID() == CCSGameRules::sm_QueuedServerReservation.account_ids( i ) )
{
// this is a player
return false;
}
}
// they weren't in the player list, so now check the caster list
for ( int i = 0; i < CCSGameRules::sm_QueuedServerReservation.tournament_casters_account_ids().size(); i++ )
{
if ( steamID.GetAccountID() == CCSGameRules::sm_QueuedServerReservation.tournament_casters_account_ids( i ) )
{
// this is a caster
return AddAccountToActiveCasters( steamID );
}
}
if ( tv_allow_camera_man_steamid.GetString()[0] && engine->IsDedicatedServer() )
{
CSteamID steamidCameraMan( V_atoui64( tv_allow_camera_man_steamid.GetString() ) );
if ( steamidCameraMan.IsValid() && steamidCameraMan.BIndividualAccount() && steamidCameraMan.GetAccountID() &&
( steamidCameraMan.GetAccountID() == steamID.GetAccountID() ) )
{
return AddAccountToActiveCasters( steamID );
}
}
return false;
}
// Returns which encryption key to use for messages to be encrypted for TV
EncryptedMessageKeyType_t CServerGameDLL::GetMessageEncryptionKey( INetMessage *pMessage )
{
switch ( pMessage->GetType() )
{
case svc_VoiceData:
{
// check the voice data packets for being from an active caster and add the caster flag and use the public key
CSVCMsg_VoiceData_t *pVoiceData = ( CSVCMsg_VoiceData_t * ) pMessage;
CSteamID steamID( static_cast<uint64>( pVoiceData->xuid() ) );
if ( steamID.GetAccountID() )
{
for ( int j = 0; j < CSGameRules()->m_arrTournamentActiveCasterAccounts.Count(); j++ )
{
if ( steamID.GetAccountID() == CSGameRules()->m_arrTournamentActiveCasterAccounts[ j ] )
{
pVoiceData->set_caster( true );
return kEncryptedMessageKeyType_Public;
}
}
}
}
return kEncryptedMessageKeyType_Private;
case svc_UserMessage:
{
CSVCMsg_UserMessage_t *pUsrMessageHeader = ( CSVCMsg_UserMessage_t * ) pMessage;
switch ( pUsrMessageHeader->msg_type() )
{
case CS_UM_SayText:
{
CCSUsrMsg_SayText usrMsg;
if ( usrMsg.ParseFromArray( &pUsrMessageHeader->msg_data().at( 0 ), pUsrMessageHeader->msg_data().size() ) )
{
if ( usrMsg.textallchat() )
return kEncryptedMessageKeyType_Public;
}
}
return kEncryptedMessageKeyType_Private;
case CS_UM_SayText2:
{
CCSUsrMsg_SayText2 usrMsg;
if ( usrMsg.ParseFromArray( &pUsrMessageHeader->msg_data().at( 0 ), pUsrMessageHeader->msg_data().size() ) )
{
if ( usrMsg.textallchat() )
return kEncryptedMessageKeyType_Public;
}
}
return kEncryptedMessageKeyType_Private;
case CS_UM_TextMsg:
case CS_UM_RadioText:
case CS_UM_RawAudio:
case CS_UM_SendAudio:
return kEncryptedMessageKeyType_Private;
default:
return kEncryptedMessageKeyType_None;
}
}
return kEncryptedMessageKeyType_None;
case svc_EncryptedData:
default:
return kEncryptedMessageKeyType_None;
}
}
// If server game dll needs more time before server process quits then
// it should return true to hold game server reservation from this interface method.
// If this method returns false then the server process will clear the reservation
// and might shutdown to meet uptime or memory limit requirements.
bool CServerGameDLL::ShouldHoldGameServerReservation( float flTimeElapsedWithoutClients )
{
/** Removed for partner depot **/
return false; // let the server get unreserved
}
// Pure server validation failed for the given client, client supplied
// data is included in the payload
void CServerGameDLL::OnPureServerFileValidationFailure( edict_t *edictClient, const char *path, const char *fileName, uint32 crc, int32 hashType, int32 len, int packNumber, int packFileID )
{
/** Removed for partner depot **/
}
// Last chance validation on connect packet for the client, non-NULL return value
// causes the client connect to be aborted with the provided error
char const * CServerGameDLL::ClientConnectionValidatePreNetChan( bool bGameServer, char const *adr, int nAuthProtocol, uint64 ullSteamID )
{
/** Removed for partner depot **/
return NULL; // allow connections by default
}
// Network channel notification from engine to game server code
void CServerGameDLL::OnEngineClientNetworkEvent( edict_t *edictClient, uint64 ullSteamID, int nEventType, void *pvParam )
{
/** Removed for partner depot **/
}
// Game server notifying GC with its sync packet
void CServerGameDLL::EngineGotvSyncPacket( const CEngineGotvSyncPacket *pPkt )
{
/** Removed for partner depot **/
}
// GOTV client attempt redirect over SDR
bool CServerGameDLL::OnEngineClientProxiedRedirect( uint64 ullClient, const char *adrProxiedRedirect, const char *adrRegular )
{
/** Removed for partner depot **/
return false;
}
bool CServerGameDLL::LogForHTTPListeners( const char* szLogLine )
{
return GetServerLogHTTPDispatcher()->LogForHTTPListeners( szLogLine );
}
//-----------------------------------------------------------------------------
// Purpose: Called to apply lobby settings to a dedicated server
//-----------------------------------------------------------------------------
void CServerGameDLL::ApplyGameSettings( KeyValues *pKV )
{
if ( !pKV )
{
return;
}
if ( engine )
{
DevMsg( "CServerGameDLL::ApplyGameSettings game settings payload received:\n" );
KeyValuesDumpAsDevMsg( pKV, 1 );
const char* pMapName = NULL;
const char* pMapNameFromKV = pKV->GetString( "game/map" );
const char* pGameType = pKV->GetString( "game/type" );
const char* pGameMode = pKV->GetString( "game/mode" );
const char* pMapGroupName = pKV->GetString( "game/mapgroupname", NULL );
const char* pMapGroupNameToValidate = NULL; // pMapGroupName is ok to be NULL; this variable lets us easily use a non null pMapGroupName or gpGlobals->mapGroupName
if ( !IsValveDS() &&
pMapNameFromKV && StringHasPrefix( pMapNameFromKV, "workshop" ) &&
pMapGroupName && Q_stristr( pMapGroupName, "@workshop" ) )
{
// A community server is getting reserved by a client for a workshop map,
// retain our current workshop collection if we are hosting one to preserve
// map rotation process
pMapGroupName = engine->IsDedicatedServer() ? STRING( gpGlobals->mapGroupName ) : pMapGroupName;
}
if ( pMapGroupName && (pMapGroupName[0] != '\0') && !pMapNameFromKV )
{
// if we have a mapgroup name, then we don't care about any map name from the pKV and we just want the first map from the mapgroup
pMapName = g_pGameTypes->GetRandomMap( pMapGroupName );
pMapGroupNameToValidate = pMapGroupName;
}
else
{
pMapGroupNameToValidate = ( pMapGroupName && (pMapGroupName[0] != '\0') ) ? pMapGroupName : STRING( gpGlobals->mapGroupName );
}
// make sure we are not using a bogus mapgroup name
if ( pMapGroupNameToValidate && !StringIsEmpty( pMapGroupNameToValidate ) && !g_pGameTypes->IsValidMapGroupName( pMapGroupNameToValidate ) )
{
Warning( "ApplyGameSettings: Invalid mapgroup name %s\n", pMapGroupNameToValidate );
return;
}
// only use the map name from the pKV if there was no mapgroup name in the pKV
if ( !pMapName )
{
pMapName = pMapNameFromKV;
}
// For team games we add the prefix "team" to the game type. This is to
// eliminate team game lobbies from searches for QuickMatch and Custom Match
char *teamStr = "team";
const char *pTeamPrefix = Q_strstr( pGameType, teamStr);
if ( pTeamPrefix == pGameType )
{
pGameType += Q_strlen( teamStr );
}
if ( pMapName && pMapName[0] != '\0' )
{
// validate map exists in the mapgroup
if ( !g_pGameTypes->IsValidMapInMapGroup( pMapGroupNameToValidate, pMapName ) )
{
Warning( "ApplyGameSettings: Map %s not part of Mapgroup %s\n", pMapName, pMapGroupNameToValidate );
}
int extraSpectators = 2;
if ( ( pGameType && pGameType[0] != '\0' ) &&
( pGameMode && pGameMode[0] != '\0' ) )
{
// make sure the mapgroup is in this game type & mode
if ( !g_pGameTypes->IsValidMapGroupForTypeAndMode( pMapGroupNameToValidate, pGameType, pGameMode ) )
{
Warning( "ApplyGameSettings: MapGroup %s not part of type %s mode %s\n", pMapGroupNameToValidate, pGameType, pGameMode );
}
// Get the bot difficulty setting before it gets reverted.
ConVarRef cvCustomBotDiff( "custom_bot_difficulty" );
int customBotDiff = cvCustomBotDiff.GetInt();
/*
// FIXME[pmf]: We don't want to reset all replicated convars unless we also re-exec game.cfg on the server,
// otherwise we'll overwrite all the game configuration convars specified in game.cfg
// Reset server enforced convars
g_pCVar->RevertFlaggedConVars( FCVAR_REPLICATED );
*/
// Cheats were disabled; revert all cheat cvars to their default values.
// This must be done heading into multiplayer games because people can play
// demos etc and set cheat cvars with sv_cheats 0.
g_pCVar->RevertFlaggedConVars( FCVAR_CHEAT );
// we know that the loading screen data is correct, so let the loading screen know
//g_pGameTypes->SetLoadingScreenDataIsCorrect( true );
g_pGameTypes->SetRunMapWithDefaultGametype( false );
// Set game_type and game_mode convars.
g_pGameTypes->SetGameTypeAndMode( pGameType, pGameMode );
extern ConVar game_online;
if ( char const *szOnline = pKV->GetString( "system/network", NULL ) )
{
game_online.SetValue( ( !V_stricmp( szOnline, "LIVE" ) ) ? 1 : 0 );
}
extern ConVar game_public;
if ( char const *szAccess = pKV->GetString( "system/access", NULL ) )
{
game_public.SetValue( ( !V_stricmp( szAccess, "public" ) ) ? 1 : 0 );
}
#ifndef CLIENT_DLL
if ( engine->IsDedicatedServer() )
game_online.SetValue( 1 );
#endif
// Special case: set the custom bot difficulty for offline games
if ( !game_online.GetBool() )
{
g_pGameTypes->SetCustomBotDifficulty( customBotDiff );
}
// Make sure that correct number of slots is set for the engine
{
int iType, iMode;
if ( g_pGameTypes->GetGameModeAndTypeIntsFromStrings( pGameType, pGameMode, iType, iMode ) )
{
int iMaxPlayersForTypeMode = g_pGameTypes->GetMaxPlayersForTypeAndMode( iType, iMode );
pKV->SetInt( "members/numSlots", iMaxPlayersForTypeMode );
}
}
// Make sure the settings keys have extra spectator info
pKV->SetInt( "members/numExtraSpectatorSlots", extraSpectators );
}
CFmtStr command;
if ( pMapGroupName )
{
command.AppendFormat( "mapgroup %s\n", pMapGroupName );
}
command.AppendFormat( "nextlevel %s\n", pMapName ); // gamerules will clean it up when they construct for the next map
command.AppendFormat( "map %s reserved\n", pMapName );
Warning( "Executing server command:\n%s\n---\n", command.Access() );
engine->ServerCommand( command );
if ( engine->IsDedicatedServer() )
engine->ServerExecute();
}
}
}
const char * CServerGameClients::ClientNameHandler( uint64 xuid, const char *pchName )
{
CSteamID steamID( xuid );
// In tournament mode force names for the players according to the reservation
if ( steamID.IsValid() && steamID.BIndividualAccount() &&
CCSGameRules::sm_QueuedServerReservation.has_tournament_event() )
{
for ( int32 iTeam = 0; iTeam < CCSGameRules::sm_QueuedServerReservation.tournament_teams().size(); ++iTeam )
{
TournamentTeam const &ttTeam = CCSGameRules::sm_QueuedServerReservation.tournament_teams( iTeam );
for ( int32 iTeamPlayer = 0; iTeamPlayer < ttTeam.players().size(); ++iTeamPlayer )
{
TournamentPlayer const &ttPlayer = ttTeam.players( iTeamPlayer );
if ( ttPlayer.account_id() && ( ttPlayer.account_id() == steamID.GetAccountID() ) )
{
return ( ttPlayer.player_nick().c_str() );
}
}
}
}
// Throttle name changes from clients (hacked clients can set the name convar at any rate)
extern CCSPlayer *ToCSPlayer( CBaseEntity *pEntity );
if ( CCSPlayer* pPlayer = ToCSPlayer( CBasePlayer::GetPlayerBySteamID( steamID ) ) )
{
if ( !pPlayer->CanChangeName() )
return pPlayer->GetPlayerName();
}
// Account not resolved, use whatever name they provided
return pchName;
}
void CServerGameClients::ClientSvcUserMessage( edict_t *pEntity, int nType, int nPassthrough, uint32 cbSize, const void *pvBuffer )
{
CCSPlayer *pPlayer = ToCSPlayer( GetContainingEntity( pEntity ) );
if ( !pPlayer )
return;
switch ( nType )
{
case CS_UM_PlayerDecalDigitalSignature:
{
CCSUsrMsg_PlayerDecalDigitalSignature msg;
if ( msg.ParseFromArray( pvBuffer, cbSize ) )
pPlayer->SprayPaint( msg );
}
return;
}
}
+14
View File
@@ -0,0 +1,14 @@
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
//=============================================================================//
#ifndef CS_GAMEINTERFACE_H
#define CS_GAMEINTERFACE_H
#ifdef _WIN32
#pragma once
#endif
#endif // CS_GAMEINTERFACE_H
File diff suppressed because it is too large Load Diff
+509
View File
@@ -0,0 +1,509 @@
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose: The CS game stats header
//
// $NoKeywords: $
//=============================================================================//
#ifndef CS_GAMESTATS_H
#define CS_GAMESTATS_H
#ifdef _WIN32
#pragma once
#endif
#include "cs_blackmarket.h"
#include "GameStats.h"
#include "cs_gamestats_shared.h"
#include "GameEventListener.h"
#include "weapon_csbase.h"
#include "steamworks_gamestats_server.h"
// forward declares
class CBreakableProp;
const float cDisseminationTimeHigh = 0.25f; // Time interval for high priority stats sent to the player
const float cDisseminationTimeLow = 2.5f; // Time interval for medium priority stats sent to the player
#define BULLET_SUB_GROUP_MASK 0xF0000000
#define BULLET_ID_GROUP_MASK 0x000FFFFF
#define BULLET_RECOIL_MASK 0x0FF00000
#define RECOIL_BIT_SHIFT(val) (val<<20)
#define SUB_BULLET_BIT_SHIFT(val) (val << 28 )
//Helper enum table and conversion function to simplify bomb data recording. Update whenever new bomb-related events become relevant
enum CSBombEventName
{
BOMB_EVENT_NAME_NONE = 0, // 0 is an unknown event
BOMB_EVENT_NAME_FIRST,
BOMB_EVENT_NAME_PLANTED = BOMB_EVENT_NAME_FIRST,
BOMB_EVENT_NAME_DEFUSED,
BOMB_EVENT_NAME_MAX = BOMB_EVENT_NAME_DEFUSED, // number of BOMB_EVENT_NAMES
};
CSBombEventName BombEventNameFromString( const char* pEventName );
int GetCSLevelIndex( const char *pLevelName );
#if !defined( NO_STEAM )
uint32 GetPlayerID( CCSPlayer *pPlayer );
#endif
typedef struct
{
char szGameName[8];
byte iVersion;
char szMapName[32];
char ipAddr[4];
short port;
int serverid;
} gamestats_header_t;
typedef struct
{
gamestats_header_t header;
short iMinutesPlayed;
short iTerroristVictories[CS_NUM_LEVELS];
short iCounterTVictories[CS_NUM_LEVELS];
short iBlackMarketPurchases[WEAPON_MAX];
short iAutoBuyPurchases;
short iReBuyPurchases;
short iAutoBuyM4A1Purchases;
short iAutoBuyAK47Purchases;
short iAutoBuyFamasPurchases;
short iAutoBuyGalilPurchases;
short iAutoBuyGalilARPurchases;
short iAutoBuyVestHelmPurchases;
short iAutoBuyVestPurchases;
} cs_gamestats_t;
struct WeaponStats
{
int shots;
int hits;
int kills;
int damage;
};
static byte PackVelocityComponent( float f )
{
// Gets velocity components and sticks fits them into a byte by scaling them
// and clamping them
f = f / 2.0f;
if ( f > 127 ) f = 127;
if ( f < -128 ) f = -128;
return (byte) f;
}
static byte PackMovementComponent( bool bDucking, bool bInAir)
{
return (((byte)bDucking) << 1) | (byte)bInAir;
}
static uint32 PackMovementStatInternal( Vector vVelocity, bool bDucking, bool bInAir )
{
return ( (uint32)PackVelocityComponent( vVelocity.x ) << 24 ) |
( (uint32)PackVelocityComponent( vVelocity.y ) << 16 ) |
( (uint32)PackVelocityComponent( vVelocity.z ) << 8 ) |
( (uint32)PackMovementComponent( bDucking, bInAir ) );
}
static uint32 PackPlayerMovementStat( CCSPlayer *pPlayer )
{
Vector vAttackerVelocity;
pPlayer->GetVelocity( &vAttackerVelocity, NULL );
bool bInAir = FBitSet( pPlayer->GetFlags(), FL_ONGROUND ) ? false : true;
bool bDucking = FBitSet( pPlayer->GetFlags(), FL_DUCKING ) ? true : false;
return PackMovementStatInternal( vAttackerVelocity, bDucking, bInAir );
}
// Pack and convert Target Aim Angle component into 32bits
// OGS
static uint8 PackAimAngleComponent( float f )
{
// Pack component of QAngle into Byte. Angle is -180 -> 180, so there's no space for the full range.
// Make sure we're not getting weird values, rather have dumb data than broken game
if ( f > 180 ) f = 180;
if ( f < -180 ) f = -180;
// The greatest granularity comes from packing angle into ( 2 * Pi )/255 units.
// Effectively that's just a shift and stretch of the range: ( ( f + 180 )/ 360 ) * 255;
return (uint8) ( ( ( f + 180 ) / 360 ) * 255 );
}
static uint32 PackAimAngleStat( CCSPlayer *pPlayer )
{
QAngle vPlayerAimAngle = pPlayer->GetFinalAimAngle();
return ( (uint32)PackAimAngleComponent( (float)vPlayerAimAngle.z ) << 16 |
(uint32)PackAimAngleComponent( (float)vPlayerAimAngle.y ) << 8 |
(uint32)PackAimAngleComponent( (float)vPlayerAimAngle.x )
);
}
//=============================================================================
//
// OGS Gamestats
//
#if !defined( _GAMECONSOLE ) && !defined( NO_STEAM )
struct SWeaponShotData : public BaseStatData
{
SWeaponShotData( CCSPlayer *pPlayer, CWeaponCSBase* pWeapon, uint8 subBullet, uint8 round, uint8 iRecoilIndex )
{
Clear();
if ( pWeapon )
{
m_ui8WeaponID = (uint8)pWeapon->GetEconItemView()->GetItemIndex();
}
if ( pPlayer )
{
m_iUserID = GetPlayerID( pPlayer );
m_uiBulletID = pPlayer->GetBulletGroup();
m_vAttackerPos = pPlayer->GetAbsOrigin();
m_uAttackerMovement = PackPlayerMovementStat( pPlayer );
}
m_uiSubBulletID = subBullet;
m_RoundID = round;
m_uiRecoilIndex = iRecoilIndex;
}
void Clear()
{
m_iUserID = 0;
m_WeaponID = WEAPON_NONE;
m_ui8WeaponID = 0;
m_uiBulletID = 0;
m_uiSubBulletID = 0;
m_vAttackerPos.Init();
m_uAttackerMovement = 0;
m_RoundID = 0;
m_uiRecoilIndex = 0;
}
int m_iUserID;
CSWeaponID m_WeaponID;
uint8 m_ui8WeaponID;
uint32 m_uiBulletID;
uint8 m_uiSubBulletID;
uint8 m_uiRecoilIndex;
Vector m_vAttackerPos;
uint32 m_uAttackerMovement;
uint8 m_RoundID;
};
struct SWeaponHitData : public BaseStatData
{
SWeaponHitData( CCSPlayer *pCSTarget, const CTakeDamageInfo &info, uint8 subBullet, uint8 round, uint8 iRecoilIndex );
SWeaponHitData()
{
Clear();
}
// When any grenade explodes-- this is separate from any damage it may deal, which are also recorded as hits.
// Can fail! check the return!
bool InitAsGrenadeDetonation( class CBaseCSGrenadeProjectile *pGrenade, uint32 unBulletGroup );
// When a bomb is planted or defused, we want to collect data from each alive player, reporting their locations
bool InitAsBombEvent( CCSPlayer *pCSPlayer, class CPlantedC4 *pPlantedC4, uint32 unBulletGroup, uint8 nBombsite, CSBombEventName nBombEventName );
void Clear()
{
m_uiBulletID = 0;
m_uiSubBulletID = 0;
m_vAttackerPos.Init();
m_vTargetPos.Init();
m_uiDamage = 0;
m_HitRegion = 0;
m_RoundID = 0;
m_ui8WeaponID = 0;
m_ui64TargertID = 0;
m_ui64AttackerID = 0;
m_ui8Health = 0;
m_uAttackerMovement = 0;
m_uiRecoilIndex = 0;
}
void CompactBulletID()
{
m_uiBulletID = (m_uiBulletID & BULLET_ID_GROUP_MASK) | (SUB_BULLET_BIT_SHIFT(m_uiSubBulletID) & BULLET_SUB_GROUP_MASK) | (RECOIL_BIT_SHIFT(MIN(m_uiRecoilIndex, 255)) & BULLET_RECOIL_MASK);
}
// Data that gets sent to OGS
uint32 m_uiBulletID;
uint8 m_uiSubBulletID;
uint8 m_uiRecoilIndex;
Vector m_vAttackerPos;
Vector m_vTargetPos;
uint16 m_uiDamage;
uint8 m_HitRegion;
uint8 m_RoundID;
uint8 m_ui8Health;
uint8 m_ui8WeaponID;
uint32 m_ui64AttackerID;
uint32 m_ui64TargertID;
uint32 m_uAttackerMovement;
BEGIN_STAT_TABLE( "CSGOWeaponHitData" )
REGISTER_STAT_NAMED( m_ui8WeaponID, "WeaponID" )
REGISTER_STAT_NAMED( m_uiBulletID, "BulletID" )
REGISTER_STAT_NAMED( m_ui64AttackerID, "AttackerID" )
REGISTER_STAT_NAMED( (int)m_vAttackerPos.x, "AttackerX" )
REGISTER_STAT_NAMED( (int)m_vAttackerPos.y, "AttackerY" )
REGISTER_STAT_NAMED( (int)m_vAttackerPos.z, "AttackerZ" )
REGISTER_STAT_NAMED( (int)m_uAttackerMovement, "AttackerMovement" )
REGISTER_STAT_NAMED( m_ui64TargertID, "TargetID" )
REGISTER_STAT_NAMED( (int)m_vTargetPos.x, "TargetX" )
REGISTER_STAT_NAMED( (int)m_vTargetPos.y, "TargetY" )
REGISTER_STAT_NAMED( (int)m_vTargetPos.z, "TargetZ" )
REGISTER_STAT_NAMED( m_ui8Health, "Health" )
REGISTER_STAT_NAMED( m_uiDamage, "Damage" )
REGISTER_STAT_NAMED( m_HitRegion, "HitRegion" )
REGISTER_STAT_NAMED( m_RoundID, "Round" )
END_STAT_TABLE()
};
struct SWeaponMissData : public BaseStatData
{
SWeaponMissData( SWeaponShotData *data )
{
Clear();
if ( data )
{
m_ui64AttackerID = data->m_iUserID;
m_ui8WeaponID = data->m_ui8WeaponID;//data->m_WeaponID;
m_uiBulletID = data->m_uiBulletID;
m_uiRecoilIndex = data->m_uiRecoilIndex;
m_uiSubBulletID = data->m_uiSubBulletID;
m_vAttackerPos = data->m_vAttackerPos;
m_uAttackerMovement = data->m_uAttackerMovement;
m_RoundID = data->m_RoundID;
TimeSubmitted = data->TimeSubmitted;
}
}
void Clear()
{
m_ui8WeaponID = 0;
m_uiBulletID = 0;
m_uiSubBulletID = 0;
m_uiRecoilIndex = 0;
m_ui64AttackerID = 0;
m_vAttackerPos.Init();
m_uAttackerMovement = 0;
m_RoundID = 0;
}
void CompactBulletID()
{
m_uiBulletID = (m_uiBulletID & BULLET_ID_GROUP_MASK) | (SUB_BULLET_BIT_SHIFT(m_uiSubBulletID) & BULLET_SUB_GROUP_MASK) | (RECOIL_BIT_SHIFT(MIN(m_uiRecoilIndex, 255)) & BULLET_RECOIL_MASK);
}
uint8 m_ui8WeaponID;
uint32 m_uiBulletID;
uint8 m_uiSubBulletID;
uint8 m_uiRecoilIndex;
uint32 m_ui64AttackerID;
Vector m_vAttackerPos;
uint32 m_uAttackerMovement;
uint8 m_RoundID;
BEGIN_STAT_TABLE( "CSGOWeaponMissData" )
REGISTER_STAT_NAMED( m_ui8WeaponID, "WeaponID" )
REGISTER_STAT_NAMED( m_uiBulletID, "BulletID" )
REGISTER_STAT_NAMED( m_ui64AttackerID, "AttackerID" )
REGISTER_STAT_NAMED( (int)m_vAttackerPos.x, "AttackerX" )
REGISTER_STAT_NAMED( (int)m_vAttackerPos.y, "AttackerY" )
REGISTER_STAT_NAMED( (int)m_vAttackerPos.z, "AttackerZ" )
REGISTER_STAT_NAMED( (int)m_uAttackerMovement, "AttackerMovement" )
REGISTER_STAT_NAMED( m_RoundID, "Round" )
END_STAT_TABLE()
};
struct SMarketPurchases : public BaseStatData
{
SMarketPurchases( uint64 ulPlayerID, int iPrice, const char *pName, int round ) : ItemCost(iPrice)
{
m_nPlayerID = ulPlayerID;
if ( pName )
{
//Can we find a valid Item Definition?
if ( GetItemSchema()->GetItemDefinitionByName( pName ) )
{
ItemID = (uint)GetItemSchema()->GetItemDefinitionByName( pName )->GetDefinitionIndex();
}
else
{
//We don't know about you, you're probably equipment (armor, defuse kit)
//Use CSWeaponID instead (works for all equipment)
ItemID = WeaponIdFromString( pName ); //Returns WEAPON_NONE on failure to find string pName
}
}
else
{
ItemID = WEAPON_NONE;
}
// Can't buy 'none'. Investigate why we can't get a weapon ID from the given string.
Assert( ItemID != WEAPON_NONE );
m_iPurchaseCnt = 1;
m_niRound = round;
}
uint32 m_nPlayerID;
int ItemCost;
uint ItemID;
char m_iPurchaseCnt;
int m_niRound;
BEGIN_STAT_TABLE( "CSGOMarketPurchase" )
REGISTER_STAT_NAMED( m_nPlayerID, "AccountID" )
REGISTER_STAT( ItemCost )
REGISTER_STAT_NAMED( m_iPurchaseCnt, "PurchaseCount" )
REGISTER_STAT_NAMED( ItemID, "WeaponID" )
REGISTER_STAT_NAMED( m_niRound, "Round" )
END_STAT_TABLE()
};
typedef CUtlVector< SWeaponHitData* > CSGOWeaponHitData;
typedef CUtlVector< SWeaponMissData* > CSGOWeaponMissData;
typedef CUtlVector< SWeaponShotData* > CSGOWeaponShotsData;
typedef CUtlVector< SMarketPurchases* > CSGOMarketPurchaseData;
#endif // OGS Data
//=============================================================================
//
// CS Game Stats Class
//
class CCSGameStats : public CBaseGameStats, public CGameEventListener, public CAutoGameSystemPerFrame
#if !defined( _GAMECONSOLE )
, public IGameStatTracker
#endif
{
public:
// Constructor/Destructor.
CCSGameStats( void );
~CCSGameStats( void );
virtual void Clear( void );
virtual bool Init();
virtual void PreClientUpdate();
// Overridden events
virtual void Event_LevelInit( void );
virtual void Event_LevelShutdown( float flElapsed );
virtual void Event_ShotFired( CBasePlayer *pPlayer, CBaseCombatWeapon* pWeapon );
virtual void Event_ShotHit( CBasePlayer *pPlayer, const CTakeDamageInfo &info );
virtual void Event_PlayerKilled( CBasePlayer *pPlayer, const CTakeDamageInfo &info );
virtual void Event_PlayerKilled_PreWeaponDrop( CBasePlayer *pPlayer, const CTakeDamageInfo &info );
virtual void Event_PlayerConnected( CBasePlayer *pPlayer );
virtual void Event_PlayerDisconnected( CBasePlayer *pPlayer );
virtual void Event_WindowShattered( CBasePlayer *pPlayer );
virtual void Event_PlayerDamage( CBasePlayer *pBasePlayer, const CTakeDamageInfo &info );
virtual void Event_PlayerKilledOther( CBasePlayer *pAttacker, CBaseEntity *pVictim, const CTakeDamageInfo &info );
// CSS specific events
void Event_BombPlanted( CCSPlayer *pPlayer );
void Event_BombDefused( CCSPlayer *pPlayer );
void Event_BombExploded( CCSPlayer *pPlayer );
void Event_MoneyEarned( CCSPlayer *pPlayer, int moneyEarned );
void Event_MoneySpent( CCSPlayer* pPlayer, int moneySpent, const char *pItemName );
void Event_HostageRescued( CCSPlayer *pPlayer );
void Event_PlayerSprayedDecal( CCSPlayer*pPlayer );
void Event_AllHostagesRescued();
void Event_BreakProp( CCSPlayer *pPlayer, CBreakableProp *pProp );
void Event_PlayerDonatedWeapon (CCSPlayer* pPlayer);
void Event_PlayerDominatedOther( CCSPlayer* pAttacker, CCSPlayer* pVictim);
void Event_PlayerRevenge( CCSPlayer* pAttacker );
void Event_PlayerAvengedTeammate( CCSPlayer* pAttacker, CCSPlayer* pAvengedPlayer );
void Event_MVPEarned( CCSPlayer* pPlayer );
void Event_KnifeUse( CCSPlayer* pPlayer, bool bStab, int iDamage );
void RecordWeaponHit( SWeaponHitData* pHitData );
// Steamworks Gamestats
#if !defined( _GAMECONSOLE )
void UploadRoundStats( void );
virtual void SubmitGameStats( KeyValues *pKV );
virtual StatContainerList_t* GetStatContainerList( void );
bool AnyOGSDataToSubmit( void );
#endif
virtual void FireGameEvent( IGameEvent *event );
void UpdatePlayerRoundStats(int winner);
void DumpMatchWeaponMetrics();
const PlayerStats_t& FindPlayerStats( CBasePlayer *pPlayer ) const;
void ResetPlayerStats( CBasePlayer *pPlayer );
void ResetKillHistory( CBasePlayer *pPlayer );
void ResetRoundStats();
void ResetPlayerClassMatchStats();
void ClearOGSRoundStats();
const StatsCollection_t& GetTeamStats( int iTeamIndex ) const;
void ResetAllTeamStats();
void ResetAllStats();
void ResetWeaponStats();
void IncrementTeamStat( int iTeamIndex, int iStatIndex, int iAmount );
void CalcDominationAndRevenge( CCSPlayer *pAttacker, CCSPlayer *pVictim, int *piDeathFlags );
void CalculateOverkill(CCSPlayer* pAttacker, CCSPlayer* pVictim);
void PlayerKilled( CBasePlayer *pVictim, const CTakeDamageInfo &info );
void IncrementStat( CCSPlayer* pPlayer, CSStatType_t statId, int iValue, bool bPlayerOnly = false, bool bIncludeBotController = false );
void SendStatsToPlayer( CCSPlayer * pPlayer, int iMinStatPriority );
void CreateNewGameStatsSession( void );
protected:
void SetStat( CCSPlayer *pPlayer, CSStatType_t statId, int iValue );
void TrackKillStats( CCSPlayer *pAttacker, CCSPlayer *pVictim );
private:
PlayerStats_t m_aPlayerStats[MAX_PLAYERS+1]; // List of stats for each player for current life - reset after each death
StatsCollection_t m_aTeamStats[TEAM_MAXCOUNT - FIRST_GAME_TEAM];
float m_fDisseminationTimerLow; // how long since last medium priority stat update
float m_fDisseminationTimerHigh; // how long since last high priority stat update
int m_numberOfRoundsForDirectAverages;
int m_numberOfTerroristEntriesForDirectAverages;
int m_numberOfCounterTerroristEntriesForDirectAverages;
CUtlDict< CSStatType_t, short > m_PropStatTable;
WeaponStats m_weaponStats[WEAPON_MAX][WeaponMode_MAX];
// Steamworks Gamestats
#if !defined( _GAMECONSOLE ) && !defined( NO_STEAM )
CSGOWeaponHitData m_WeaponHitData;
CSGOWeaponMissData m_WeaponMissData;
CSGOWeaponShotsData m_WeaponShotData;
CSGOMarketPurchaseData m_MarketPurchases;
// A static list of all the stat containers, one for each data structure being tracked
static StatContainerList_t * s_StatLists;
#endif
};
extern CCSGameStats CCS_GameStats;
#endif // CS_GAMESTATS_H
+226
View File
@@ -0,0 +1,226 @@
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//
//=============================================================================//
#include "cbase.h"
#include "hltvdirector.h"
#include "igameevents.h"
// NOTE: This has to be the last file included!
#include "tier0/memdbgon.h"
class CCSHLTVDirector : public CHLTVDirector
{
public:
DECLARE_CLASS( CCSHLTVDirector, CHLTVDirector );
const char** GetModEvents();
void AddHLTVServer( IHLTVServer *hltv );
void CreateShotFromEvent( CHLTVGameEvent *event );
};
void CCSHLTVDirector::AddHLTVServer( IHLTVServer *hltv )
{
BaseClass::AddHLTVServer( hltv );
// mod specific events the director uses to find interesting shots
ListenForGameEvent( "hostage_rescued" );
ListenForGameEvent( "hostage_killed" );
ListenForGameEvent( "hostage_hurt" );
ListenForGameEvent( "hostage_follows" );
ListenForGameEvent( "bomb_pickup" );
ListenForGameEvent( "bomb_dropped" );
ListenForGameEvent( "bomb_exploded" );
ListenForGameEvent( "bomb_defused" );
ListenForGameEvent( "bomb_planted" );
ListenForGameEvent( "bomb_begindefuse" );
ListenForGameEvent( "bomb_beginplant" );
ListenForGameEvent( "vip_escaped" );
ListenForGameEvent( "vip_killed" );
}
void CCSHLTVDirector::CreateShotFromEvent( CHLTVGameEvent *event )
{
// show event at least for 2 more seconds after it occured
const char *name = event->m_Event->GetName();
IGameEvent *shot = NULL;
CBaseEntity *player = NULL;
if ( !Q_strcmp( "hostage_rescued", name ) ||
!Q_strcmp( "hostage_hurt", name ) ||
!Q_strcmp( "hostage_follows", name ) ||
!Q_strcmp( "hostage_killed", name ) )
{
player = UTIL_PlayerByUserId( event->m_Event->GetInt("userid") );
if ( !player )
return;
// shot player as primary, hostage as secondary target
shot = gameeventmanager->CreateEvent( "hltv_chase", true );
shot->SetInt( "target1", player->entindex() );
shot->SetInt( "target2", event->m_Event->GetInt("hostage") );
shot->SetFloat( "distance", 96.0f );
shot->SetInt( "theta", 40 );
shot->SetInt( "phi", 20 );
// shot 2 seconds after event
m_nNextShotTick = MIN( m_nNextShotTick, (event->m_Tick+TIME_TO_TICKS(2.0)) );
m_iPVSEntity = player->entindex();
}
else if ( !Q_strcmp( "bomb_beginplant", name ) ||
!Q_strcmp( "bomb_begindefuse", name ) )
{
player = UTIL_PlayerByUserId( event->m_Event->GetInt("userid") );
if ( !player )
return;
// chasecam
shot = gameeventmanager->CreateEvent( "hltv_chase", true );
if ( shot )
{
shot->SetInt( "target1", player->entindex() );
shot->SetInt( "target2", 0 );
shot->SetFloat( "distance", 500.0f );
shot->SetInt( "theta", 180 );
shot->SetInt( "phi", 45 );
shot->SetBool( "ineye", true );
// shot 3 seconds after pickup
m_nNextShotTick = MIN( m_nNextShotTick, (event->m_Tick+TIME_TO_TICKS(3.0)) );
m_iPVSEntity = player->entindex();
for ( int i = 0; i < m_HltvServers.Count(); ++i )
{
m_HltvServers[ i ].m_pHLTVServer->BroadcastEvent( shot );
}
gameeventmanager->FreeEvent( shot );
DevMsg("DrcCmd: %s\n", name );
return;
}
}
// let baseclass create a shot
BaseClass::CreateShotFromEvent( event );
}
const char** CCSHLTVDirector::GetModEvents()
{
// game events relayed to spectator clients
static const char *s_modevents[] =
{
"hltv_status",
"hltv_chat",
"player_connect",
"player_connect_full",
"player_disconnect",
"player_team",
"player_info",
"server_cvar",
"player_changename",
"teamplay_broadcast_audio",
"player_death",
"other_death",
"player_hurt",
"player_chat",
"round_start",
"round_end",
// additional CS:S events:
"bomb_planted",
"bomb_defused",
"bomb_beginplant",
"bomb_begindefuse",
"hostage_killed",
"hostage_hurt",
"begin_new_match",
// UI events
"cs_match_end_restart",
"cs_game_disconnected",
"announce_phase_end",
"round_mvp",
"server_spawn",
"player_spawn",
"hltv_status",
"cs_win_panel_round",
"endmatch_cmm_start_reveal_items",
"game_newmap",
"hostage_rescued",
"bomb_exploded",
"bomb_pickup",
"bomb_dropped",
"defuser_pickup",
"defuser_dropped",
"decoy_started",
"decoy_detonate",
"hegrenade_detonate",
"flashbang_detonate",
"smokegrenade_detonate",
"smokegrenade_expired",
"inferno_startburn",
"inferno_expire",
"bot_takeover",
"bomb_beep",
"weapon_fire",
"weapon_fire_on_empty",
"weapon_outofammo",
"weapon_reload",
"weapon_zoom",
"player_footstep",
"player_jump",
"player_blind",
"round_freeze_end",
"cs_win_panel_match",
"cs_pre_restart",
"tournament_reward",
"item_found",
"items_gifted",
"achievement_earned",
"round_announce_warmup",
"round_announce_last_round_half",
"round_announce_final",
"round_announce_match_point",
"round_poststart",
"buytime_ended",
"round_time_warning",
"dm_bonus_weapon_start",
"endmatch_mapvote_selecting_map",
"cs_round_start_beep",
"cs_round_final_beep",
"round_announce_match_start",
"seasoncoin_levelup",
"player_falldamage",
"hostage_rescued_all",
"round_officially_ended",
"round_prestart",
NULL
};
return s_modevents;
}
static CCSHLTVDirector s_HLTVDirector; // singleton
EXPOSE_SINGLE_INTERFACE_GLOBALVAR(CHLTVDirector, IHLTVDirector, INTERFACEVERSION_HLTVDIRECTOR, s_HLTVDirector );
CHLTVDirector* HLTVDirector()
{
return &s_HLTVDirector;
}
IGameSystem* HLTVDirectorSystem()
{
return &s_HLTVDirector;
}
+73
View File
@@ -0,0 +1,73 @@
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//
//=============================================================================//
// nav.h
// Data structures and constants for the Navigation Mesh system
// Author: Michael S. Booth (mike@turtlerockstudios.com), January 2003
#ifndef _CS_NAV_H_
#define _CS_NAV_H_
#include "nav.h"
/**
* Below are several constants used by the navigation system.
* @todo Move these into TheNavMesh singleton.
*/
const float BotRadius = 10.0f; ///< circular extent that contains bot
class CNavArea;
class CSNavNode;
#if 0
//--------------------------------------------------------------------------------------------------------------
/**
* Return true if given entity can be ignored when moving
*/
#define WALK_THRU_DOORS 0x01
#define WALK_THRU_BREAKABLES 0x02
#define WALK_THRU_TOGGLE_BRUSHES 0x04
#define WALK_THRU_EVERYTHING (WALK_THRU_DOORS | WALK_THRU_BREAKABLES | WALK_THRU_TOGGLE_BRUSHES)
inline bool IsEntityWalkable( CBaseEntity *entity, unsigned int flags )
{
if (FClassnameIs( entity, "worldspawn" ))
return false;
if (FClassnameIs( entity, "player" ))
return false;
// if we hit a door, assume its walkable because it will open when we touch it
if (FClassnameIs( entity, "prop_door*" ) || FClassnameIs( entity, "func_door*" ))
return (flags & WALK_THRU_DOORS) ? true : false;
// if we hit a clip brush, ignore it if it is not BRUSHSOLID_ALWAYS
if (FClassnameIs( entity, "func_brush" ))
{
CFuncBrush *brush = (CFuncBrush *)entity;
switch ( brush->m_iSolidity )
{
case CFuncBrush::BRUSHSOLID_ALWAYS:
return false;
case CFuncBrush::BRUSHSOLID_NEVER:
return true;
case CFuncBrush::BRUSHSOLID_TOGGLE:
return (flags & WALK_THRU_TOGGLE_BRUSHES) ? true : false;
}
}
// if we hit a breakable object, assume its walkable because we will shoot it when we touch it
if (FClassnameIs( entity, "func_breakable" ) && entity->GetHealth() && entity->m_takedamage == DAMAGE_YES)
return (flags & WALK_THRU_BREAKABLES) ? true : false;
if (FClassnameIs( entity, "func_breakable_surf" ) && entity->m_takedamage == DAMAGE_YES)
return (flags & WALK_THRU_BREAKABLES) ? true : false;
return false;
}
#endif
#endif // _CS_NAV_H_
+505
View File
@@ -0,0 +1,505 @@
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//
//=============================================================================//
// nav_area.cpp
// AI Navigation areas
// Author: Michael S. Booth (mike@turtlerockstudios.com), January 2003
#include "cbase.h"
#include "cs_nav_mesh.h"
#include "cs_nav_area.h"
#include "cs_gamerules.h"
#include "nav_pathfind.h"
#include "nav_colors.h"
#include "fmtstr.h"
#include "props_shared.h"
#include "func_breakablesurf.h"
#include "color.h"
#include "collisionutils.h"
#include "point_hiding_spot.h"
// memdbgon must be the last include file in a .cpp file!!!
#include <tier0/memdbgon.h>
#ifdef _WIN32
#pragma warning (disable:4701) // disable warning that variable *may* not be initialized
#endif
//--------------------------------------------------------------------------------------------------------------
/**
* Constructor used during normal runtime.
*/
CCSNavArea::CCSNavArea( void )
{
#ifndef PLATFORM_64BITS // TODO64: this is probably just alignment for performance, but perhaps the sizes are hardcoded somewhere
COMPILE_TIME_ASSERT( sizeof( CNavAreaCriticalData ) == 128 );
COMPILE_TIME_ASSERT( sizeof( CCSNavArea ) == 768 );
#endif
m_approachCount = 0;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Destructor
*/
CCSNavArea::~CCSNavArea()
{
}
void CCSNavArea::OnServerActivate( void )
{
CNavArea::OnServerActivate();
}
void CCSNavArea::OnRoundRestart( void )
{
CNavArea::OnRoundRestart();
}
void CCSNavArea::Save( CUtlBuffer &fileBuffer, unsigned int version ) const
{
CNavArea::Save( fileBuffer, version );
//
// Save the approach areas for this area
//
// save number of approach areas
fileBuffer.PutUnsignedChar(m_approachCount);
// save approach area info
for( int a=0; a<m_approachCount; ++a )
{
if (m_approach[a].here.area)
fileBuffer.PutUnsignedInt(m_approach[a].here.area->GetID());
else
fileBuffer.PutUnsignedInt(0);
if (m_approach[a].prev.area)
fileBuffer.PutUnsignedInt(m_approach[a].prev.area->GetID());
else
fileBuffer.PutUnsignedInt(0);
fileBuffer.PutUnsignedChar(m_approach[a].prevToHereHow);
if (m_approach[a].next.area)
fileBuffer.PutUnsignedInt(m_approach[a].next.area->GetID());
else
fileBuffer.PutUnsignedInt(0);
fileBuffer.PutUnsignedChar(m_approach[a].hereToNextHow);
}
}
NavErrorType CCSNavArea::Load( CUtlBuffer &fileBuffer, unsigned int version, unsigned int subVersion )
{
if ( version < 15 )
return LoadLegacy(fileBuffer, version, subVersion);
// load base class data
NavErrorType error = CNavArea::Load( fileBuffer, version, subVersion );
switch ( subVersion )
{
case 1:
//
// Load number of approach areas
//
m_approachCount = fileBuffer.GetUnsignedChar();
// load approach area info (IDs)
for( int a = 0; a < m_approachCount; ++a )
{
m_approach[a].here.id = fileBuffer.GetUnsignedInt();
m_approach[a].prev.id = fileBuffer.GetUnsignedInt();
m_approach[a].prevToHereHow = (NavTraverseType)fileBuffer.GetUnsignedChar();
m_approach[a].next.id = fileBuffer.GetUnsignedInt();
m_approach[a].hereToNextHow = (NavTraverseType)fileBuffer.GetUnsignedChar();
}
if ( !fileBuffer.IsValid() )
error = NAV_INVALID_FILE;
// fall through
case 0:
// legacy version
break;
default:
Warning( "Unknown NavArea sub-version number\n" );
error = NAV_INVALID_FILE;
}
return error;
}
NavErrorType CCSNavArea::PostLoad( void )
{
NavErrorType error = CNavArea::PostLoad();
// resolve approach area IDs
for ( int a = 0; a < m_approachCount; ++a )
{
m_approach[a].here.area = TheNavMesh->GetNavAreaByID( m_approach[a].here.id );
if (m_approach[a].here.id && m_approach[a].here.area == NULL)
{
Msg( "CNavArea::PostLoad: Corrupt navigation data. Missing Approach Area (here).\n" );
error = NAV_CORRUPT_DATA;
}
m_approach[a].prev.area = TheNavMesh->GetNavAreaByID( m_approach[a].prev.id );
if (m_approach[a].prev.id && m_approach[a].prev.area == NULL)
{
Msg( "CNavArea::PostLoad: Corrupt navigation data. Missing Approach Area (prev).\n" );
error = NAV_CORRUPT_DATA;
}
m_approach[a].next.area = TheNavMesh->GetNavAreaByID( m_approach[a].next.id );
if (m_approach[a].next.id && m_approach[a].next.area == NULL)
{
Msg( "CNavArea::PostLoad: Corrupt navigation data. Missing Approach Area (next).\n" );
error = NAV_CORRUPT_DATA;
}
}
return error;
}
void CCSNavArea::Draw( void ) const
{
CNavArea::Draw();
}
void CCSNavArea::CustomAnalysis( bool isIncremental /*= false */ )
{
ComputeApproachAreas();
}
//--------------------------------------------------------------------------------------------------------------
// return danger decay rate per second
float CCSNavArea::GetDangerDecayRate( void ) const
{
if ( CSGameRules()->IsPlayingGunGameTRBomb() )
{
// decay danger faster in this aggressive bomb planting mode
return 1.0f / 30.0f;
}
// one kill == 1.0, which we will forget about in two minutes
return 1.0f / 120.0f;
}
float CCSNavArea::GetEarliestOccupyTime( int teamID ) const
{
if ( CSGameRules()->IsPlayingCoopMission() )
return 0.0f;
return BaseClass::GetEarliestOccupyTime( teamID );
}
void CCSNavArea::UpdateBlocked( bool force /*= false*/, int teamID /*= TEAM_ANY */ )
{
if ( CSGameRules() && CSGameRules()->IsPlayingCoopMission() )
BaseClass::UpdateBlocked( force, teamID );
}
bool CCSNavArea::IsBlocked( int teamID, bool ignoreNavBlockers /*= false */ ) const
{
return ( CSGameRules() && CSGameRules()->IsPlayingCoopMission() ) ? BaseClass::IsBlocked( teamID, ignoreNavBlockers ) : false;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Load legacy navigation area from the file
*/
NavErrorType CCSNavArea::LoadLegacy( CUtlBuffer &fileBuffer, unsigned int version, unsigned int subVersion )
{
// load ID
m_id = fileBuffer.GetUnsignedInt();
// update nextID to avoid collisions
if (m_id >= m_nextID)
m_nextID = m_id+1;
// load attribute flags
if ( version <= 8 )
{
m_attributeFlags = fileBuffer.GetUnsignedChar();
}
else if ( version < 13 )
{
m_attributeFlags = fileBuffer.GetUnsignedShort();
}
else
{
m_attributeFlags = fileBuffer.GetInt();
}
// load extent of area
fileBuffer.Get( &m_nwCorner, 3*sizeof(float) );
fileBuffer.Get( &m_seCorner, 3*sizeof(float) );
if ( ( m_seCorner.x - m_nwCorner.x ) > 0.0f && ( m_seCorner.y - m_nwCorner.y ) > 0.0f )
{
m_invDxCorners = 1.0f / ( m_seCorner.x - m_nwCorner.x );
m_invDyCorners = 1.0f / ( m_seCorner.y - m_nwCorner.y );
}
else
{
m_invDxCorners = m_invDyCorners = 0;
DevWarning( "Degenerate Navigation Area #%d at setpos %g %g %g\n",
m_id, m_nwCorner.x, m_nwCorner.y, m_nwCorner.z );
}
// load heights of implicit corners
m_neZ = fileBuffer.GetFloat();
m_swZ = fileBuffer.GetFloat();
CheckWaterLevel();
// load connections (IDs) to adjacent areas
// in the enum order NORTH, EAST, SOUTH, WEST
for( int d=0; d<NUM_DIRECTIONS; d++ )
{
// load number of connections for this direction
unsigned int count = fileBuffer.GetUnsignedInt();
Assert( fileBuffer.IsValid() );
m_connect[d].EnsureCapacity( count );
for( unsigned int i=0; i<count; ++i )
{
NavConnect connect;
connect.id = fileBuffer.GetUnsignedInt();
Assert( fileBuffer.IsValid() );
// don't allow self-referential connections
if ( connect.id != m_id )
{
m_connect[d].AddToTail( connect );
}
}
}
//
// Load hiding spots
//
// load number of hiding spots
unsigned char hidingSpotCount = fileBuffer.GetUnsignedChar();
if (version == 1)
{
// load simple vector array
Vector pos;
for( int h=0; h<hidingSpotCount; ++h )
{
fileBuffer.Get( &pos, 3 * sizeof(float) );
// create new hiding spot and put on master list
HidingSpot *spot = TheNavMesh->CreateHidingSpot();
spot->SetPosition( pos );
spot->SetFlags( HidingSpot::IN_COVER );
m_hidingSpots.AddToTail( spot );
}
}
else
{
// load HidingSpot objects for this area
for( int h=0; h<hidingSpotCount; ++h )
{
// create new hiding spot and put on master list
HidingSpot *spot = TheNavMesh->CreateHidingSpot();
spot->Load( fileBuffer, version );
m_hidingSpots.AddToTail( spot );
}
}
if ( version < 15 )
{
//
// Load number of approach areas
//
m_approachCount = fileBuffer.GetUnsignedChar();
// load approach area info (IDs)
for( int a = 0; a < m_approachCount; ++a )
{
m_approach[a].here.id = fileBuffer.GetUnsignedInt();
m_approach[a].prev.id = fileBuffer.GetUnsignedInt();
m_approach[a].prevToHereHow = (NavTraverseType)fileBuffer.GetUnsignedChar();
m_approach[a].next.id = fileBuffer.GetUnsignedInt();
m_approach[a].hereToNextHow = (NavTraverseType)fileBuffer.GetUnsignedChar();
}
}
//
// Load encounter paths for this area
//
unsigned int count = fileBuffer.GetUnsignedInt();
if (version < 3)
{
// old data, read and discard
for( unsigned int e=0; e<count; ++e )
{
SpotEncounter encounter;
encounter.from.id = fileBuffer.GetUnsignedInt();
encounter.to.id = fileBuffer.GetUnsignedInt();
fileBuffer.Get( &encounter.path.from.x, 3 * sizeof(float) );
fileBuffer.Get( &encounter.path.to.x, 3 * sizeof(float) );
// read list of spots along this path
unsigned char spotCount = fileBuffer.GetUnsignedChar();
for( int s=0; s<spotCount; ++s )
{
fileBuffer.GetFloat();
fileBuffer.GetFloat();
fileBuffer.GetFloat();
fileBuffer.GetFloat();
}
}
return NAV_OK;
}
for( unsigned int e=0; e<count; ++e )
{
SpotEncounter *encounter = new SpotEncounter;
encounter->from.id = fileBuffer.GetUnsignedInt();
unsigned char dir = fileBuffer.GetUnsignedChar();
encounter->fromDir = static_cast<NavDirType>( dir );
encounter->to.id = fileBuffer.GetUnsignedInt();
dir = fileBuffer.GetUnsignedChar();
encounter->toDir = static_cast<NavDirType>( dir );
// read list of spots along this path
unsigned char spotCount = fileBuffer.GetUnsignedChar();
SpotOrder order;
for( int s=0; s<spotCount; ++s )
{
order.id = fileBuffer.GetUnsignedInt();
unsigned char t = fileBuffer.GetUnsignedChar();
order.t = (float)t/255.0f;
encounter->spots.AddToTail( order );
}
m_spotEncounters.AddToTail( encounter );
}
if (version < 5)
return NAV_OK;
//
// Load Place data
//
PlaceDirectory::IndexType entry = fileBuffer.GetUnsignedShort();
// convert entry to actual Place
SetPlace( placeDirectory.IndexToPlace( entry ) );
if ( version < 7 )
return NAV_OK;
// load ladder data
for ( int dir=0; dir<CNavLadder::NUM_LADDER_DIRECTIONS; ++dir )
{
count = fileBuffer.GetUnsignedInt();
for( unsigned int i=0; i<count; ++i )
{
NavLadderConnect connect;
connect.id = fileBuffer.GetUnsignedInt();
bool alreadyConnected = false;
FOR_EACH_VEC( m_ladder[dir], j )
{
if ( m_ladder[dir][j].id == connect.id )
{
alreadyConnected = true;
break;
}
}
if ( !alreadyConnected )
{
m_ladder[dir].AddToTail( connect );
}
}
}
if ( version < 8 )
return NAV_OK;
// load earliest occupy times
for( int i=0; i<MAX_NAV_TEAMS; ++i )
{
// no spot in the map should take longer than this to reach
m_earliestOccupyTime[i] = fileBuffer.GetFloat();
}
if ( version < 11 )
return NAV_OK;
// load light intensity
for ( int i=0; i<NUM_CORNERS; ++i )
{
m_lightIntensity[i] = fileBuffer.GetFloat();
}
if ( version < 16 )
return NAV_OK;
// load visibility information
unsigned int visibleAreaCount = fileBuffer.GetUnsignedInt();
m_potentiallyVisibleAreas.EnsureCapacity( visibleAreaCount );
for( unsigned int j=0; j<visibleAreaCount; ++j )
{
AreaBindInfo info;
info.id = fileBuffer.GetUnsignedInt();
info.attributes = fileBuffer.GetUnsignedChar();
m_potentiallyVisibleAreas.AddToTail( info );
}
// read area from which we inherit visibility
m_inheritVisibilityFrom.id = fileBuffer.GetUnsignedInt();
return NAV_OK;
}
CCSHidingSpot::~CCSHidingSpot()
{
if ( m_pOwningEntity )
m_pOwningEntity->DetachFromHidingSpot();
}
+107
View File
@@ -0,0 +1,107 @@
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//
//=============================================================================//
// nav_area.h
// Navigation areas
// Author: Michael S. Booth (mike@turtlerockstudios.com), January 2003
#ifndef _CS_NAV_AREA_H_
#define _CS_NAV_AREA_H_
#include "nav_area.h"
class CCSHidingSpot : public HidingSpot
{
public:
virtual ~CCSHidingSpot();
void SetOwningEntity( class CPointHidingSpot *pHidingSpotEnt );
protected:
CPointHidingSpot *m_pOwningEntity;
};
//-------------------------------------------------------------------------------------------------------------------
/**
* A CNavArea is a rectangular region defining a walkable area in the environment
*/
class CCSNavArea : public CNavArea
{
public:
DECLARE_CLASS( CCSNavArea, CNavArea );
CCSNavArea( void );
~CCSNavArea();
virtual void OnServerActivate( void ); // (EXTEND) invoked when map is initially loaded
virtual void OnRoundRestart( void ); // (EXTEND) invoked for each area when the round restarts
virtual void Draw( void ) const; // draw area for debugging & editing
virtual void Save( CUtlBuffer &fileBuffer, unsigned int version ) const; // (EXTEND)
virtual NavErrorType Load( CUtlBuffer &fileBuffer, unsigned int version, unsigned int subVersion ); // (EXTEND)
virtual NavErrorType PostLoad( void ); // (EXTEND) invoked after all areas have been loaded - for pointer binding, etc
virtual void CustomAnalysis( bool isIncremental = false ); // for game-specific analysis
virtual float GetDangerDecayRate( void ) const; // return danger decay rate per second
virtual float GetEarliestOccupyTime( int teamID ) const OVERRIDE; // returns the minimum time for someone of the given team to reach this spot from their spawn
// Use nav blockers in coop mode. There is a bug with these functions causing bots to lose
// their path at the start of rounds that is undiagnosed at the time of this comment. Coop needs nav blockers
// and doesn't (seem) to have any issues with blocked nav so let's leave it on for them
virtual void UpdateBlocked( bool force = false, int teamID = TEAM_ANY ) OVERRIDE;
// Updates the (un)blocked status of the nav area (throttled)
virtual bool IsBlocked( int teamID, bool ignoreNavBlockers = false ) const OVERRIDE;
//- approach areas ----------------------------------------------------------------------------------
struct ApproachInfo
{
NavConnect here; ///< the approach area
NavConnect prev; ///< the area just before the approach area on the path
NavConnect next; ///< the area just after the approach area on the path
uint16 prevToHereHow; // NavTraverseType
uint16 hereToNextHow;
};
const ApproachInfo *GetApproachInfo( int i ) const { return &m_approach[i]; }
int GetApproachInfoCount( void ) const { return m_approachCount; }
void ComputeApproachAreas( void ); ///< determine the set of "approach areas" - for map learning
//- player counting --------------------------------------------------------------------------------
void ClearPlayerCount( void ); ///< set the player count to zero
protected:
NavErrorType LoadLegacy( CUtlBuffer &fileBuffer, unsigned int version, unsigned int subVersion );
private:
//- approach areas ----------------------------------------------------------------------------------
enum { MAX_APPROACH_AREAS = 16 };
ApproachInfo m_approach[ MAX_APPROACH_AREAS ];
unsigned char m_approachCount;
unsigned char m_paddingToAlignTo128[ int( 0 - sizeof( CNavArea ) - ( sizeof( ApproachInfo ) * MAX_APPROACH_AREAS ) - sizeof( unsigned char ) ) & 127 ];
};
//--------------------------------------------------------------------------------------------------------------
//--------------------------------------------------------------------------------------------------------------
//
// Inlines
//
inline void CCSNavArea::ClearPlayerCount( void )
{
#ifndef PLATFORM_64BITS
COMPILE_TIME_ASSERT( sizeof( CCSNavArea ) == 768 ); // legacy 32-bit struct was 768 bytes, preserving for compatibility
#endif
for( int i=0; i<MAX_NAV_TEAMS; ++i )
{
m_playerCount[ i ] = 0;
}
}
#endif // _CS_NAV_AREA_H_
+69
View File
@@ -0,0 +1,69 @@
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//
//=============================================================================//
// nav_edit.cpp
// Implementation of Navigation Mesh edit mode
// Author: Michael Booth, 2003-2004
#include "cbase.h"
#include "nav_mesh.h"
#include "cs_nav_pathfind.h"
#include "cs_nav_node.h"
#include "nav_colors.h"
#include "Color.h"
#include "tier0/vprof.h"
#include "collisionutils.h"
ConVar nav_show_area_info( "nav_show_area_info", "0.5", FCVAR_GAMEDLL, "Duration in seconds to show nav area ID and attributes while editing" );
ConVar nav_snap_to_grid( "nav_snap_to_grid", "0", FCVAR_GAMEDLL, "Snap to the nav generation grid when creating new nav areas" );
ConVar nav_create_place_on_ground( "nav_create_place_on_ground", "0", FCVAR_GAMEDLL, "If true, nav areas will be placed flush with the ground when created by hand." );
#if DEBUG_NAV_NODES
extern ConVar nav_show_nodes;
#endif // DEBUG_NAV_NODES
//--------------------------------------------------------------------------------------------------------------
void EditNav_Precache(void *pUser)
{
CBaseEntity::PrecacheScriptSound( "Bot.EditSwitchOn" );
CBaseEntity::PrecacheScriptSound( "EDIT_TOGGLE_PLACE_MODE" );
CBaseEntity::PrecacheScriptSound( "Bot.EditSwitchOff" );
CBaseEntity::PrecacheScriptSound( "EDIT_PLACE_PICK" );
CBaseEntity::PrecacheScriptSound( "EDIT_DELETE" );
CBaseEntity::PrecacheScriptSound( "EDIT.ToggleAttribute" );
CBaseEntity::PrecacheScriptSound( "EDIT_SPLIT.MarkedArea" );
CBaseEntity::PrecacheScriptSound( "EDIT_SPLIT.NoMarkedArea" );
CBaseEntity::PrecacheScriptSound( "EDIT_MERGE.Enable" );
CBaseEntity::PrecacheScriptSound( "EDIT_MERGE.Disable" );
CBaseEntity::PrecacheScriptSound( "EDIT_MARK.Enable" );
CBaseEntity::PrecacheScriptSound( "EDIT_MARK.Disable" );
CBaseEntity::PrecacheScriptSound( "EDIT_MARK_UNNAMED.Enable" );
CBaseEntity::PrecacheScriptSound( "EDIT_MARK_UNNAMED.NoMarkedArea" );
CBaseEntity::PrecacheScriptSound( "EDIT_MARK_UNNAMED.MarkedArea" );
CBaseEntity::PrecacheScriptSound( "EDIT_CONNECT.AllDirections" );
CBaseEntity::PrecacheScriptSound( "EDIT_CONNECT.Added" );
CBaseEntity::PrecacheScriptSound( "EDIT_DISCONNECT.MarkedArea" );
CBaseEntity::PrecacheScriptSound( "EDIT_DISCONNECT.NoMarkedArea" );
CBaseEntity::PrecacheScriptSound( "EDIT_SPLICE.MarkedArea" );
CBaseEntity::PrecacheScriptSound( "EDIT_SPLICE.NoMarkedArea" );
CBaseEntity::PrecacheScriptSound( "EDIT_SELECT_CORNER.MarkedArea" );
CBaseEntity::PrecacheScriptSound( "EDIT_SELECT_CORNER.NoMarkedArea" );
CBaseEntity::PrecacheScriptSound( "EDIT_MOVE_CORNER.MarkedArea" );
CBaseEntity::PrecacheScriptSound( "EDIT_MOVE_CORNER.NoMarkedArea" );
CBaseEntity::PrecacheScriptSound( "EDIT_BEGIN_AREA.Creating" );
CBaseEntity::PrecacheScriptSound( "EDIT_BEGIN_AREA.NotCreating" );
CBaseEntity::PrecacheScriptSound( "EDIT_END_AREA.Creating" );
CBaseEntity::PrecacheScriptSound( "EDIT_END_AREA.NotCreating" );
CBaseEntity::PrecacheScriptSound( "EDIT_WARP_TO_MARK" );
}
#ifdef CSTRIKE_DLL
PRECACHE_REGISTER_FN( EditNav_Precache );
#endif
File diff suppressed because it is too large Load Diff
+269
View File
@@ -0,0 +1,269 @@
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//
//=============================================================================//
// nav_generate.cpp
// Auto-generate a Navigation Mesh by sampling the current map
// Author: Michael S. Booth (mike@turtlerockstudios.com), 2003
#include "cbase.h"
#include "util_shared.h"
#include "nav_mesh.h"
#include "cs_nav_area.h"
#include "cs_nav_node.h"
#include "cs_nav_pathfind.h"
#include "viewport_panel_names.h"
enum { MAX_BLOCKED_AREAS = 256 };
static unsigned int blockedID[ MAX_BLOCKED_AREAS ];
static int blockedIDCount = 0;
static float lastMsgTime = 0.0f;
//ConVar nav_slope_limit( "nav_slope_limit", "0.7", FCVAR_GAMEDLL, "The ground unit normal's Z component must be greater than this for nav areas to be generated." );
ConVar nav_restart_after_analysis( "nav_restart_after_analysis", "1", FCVAR_GAMEDLL, "When nav nav_restart_after_analysis finishes, restart the server. Turning this off can cause crashes, but is useful for incremental generation." );
//--------------------------------------------------------------------------------------------------------------
/**
* Shortest path cost, paying attention to "blocked" areas
*/
class ApproachAreaCost
{
public:
// HPE_TODO[pmf]: check that these new parameters are okay to be ignored
float operator() ( CNavArea *area, CNavArea *fromArea, const CNavLadder *ladder, const CFuncElevator *elevator, float length )
{
// check if this area is "blocked"
for( int i=0; i<blockedIDCount; ++i )
{
if (area->GetID() == blockedID[i])
{
return -1.0f;
}
}
if (fromArea == NULL)
{
// first area in path, no cost
return 0.0f;
}
else
{
// compute distance traveled along path so far
float dist;
if (ladder)
{
dist = ladder->m_length;
}
else
{
dist = (area->GetCenter() - fromArea->GetCenter()).Length();
}
float cost = dist + fromArea->GetCostSoFar();
return cost;
}
}
};
/*
* Determine the set of "approach areas".
* An approach area is an area representing a place where players
* move into/out of our local neighborhood of areas.
* @todo Optimize by search from eye outward and modifying pathfinder to treat all links as bi-directional
*/
void CCSNavArea::ComputeApproachAreas( void )
{
m_approachCount = 0;
if (nav_quicksave.GetBool())
return;
// use the center of the nav area as the "view" point
Vector eye = GetCenter();
if (TheNavMesh->GetGroundHeight( eye, &eye.z ) == false)
return;
// approximate eye position
if (GetAttributes() & NAV_MESH_CROUCH)
eye.z += 0.9f * HalfHumanHeight;
else
eye.z += 0.9f * HumanHeight;
enum { MAX_PATH_LENGTH = 256 };
CNavArea *path[ MAX_PATH_LENGTH ];
ApproachAreaCost cost;
enum SearchType
{
FROM_EYE, ///< start search from our eyepoint outward to farArea
TO_EYE, ///< start search from farArea beack towards our eye
SEARCH_FINISHED
};
//
// In order to *completely* enumerate all of the approach areas, we
// need to search from our eyepoint outward, as well as from outwards
// towards our eyepoint
//
for( int searchType = FROM_EYE; searchType != SEARCH_FINISHED; ++searchType )
{
//
// In order to enumerate all of the approach areas, we need to
// run the algorithm many times, once for each "far away" area
// and keep the union of the approach area sets
//
int it;
for( it = 0; it < TheNavAreas.Count(); ++it )
{
CNavArea *farArea = TheNavAreas[ it ];
blockedIDCount = 0;
// skip the small areas
const float minSize = 200.0f; // 150
Extent extent;
farArea->GetExtent(&extent);
if (extent.SizeX() < minSize || extent.SizeY() < minSize)
{
continue;
}
// if we can see 'farArea', try again - the whole point is to go "around the bend", so to speak
if (farArea->IsVisible( eye ))
{
continue;
}
//
// Keep building paths to farArea and blocking them off until we
// cant path there any more.
// As areas are blocked off, all exits will be enumerated.
//
while( m_approachCount < MAX_APPROACH_AREAS )
{
CNavArea *from, *to;
if (searchType == FROM_EYE)
{
// find another path *to* 'farArea'
// we must pathfind from us in order to pick up one-way paths OUT OF our area
from = this;
to = farArea;
}
else // TO_EYE
{
// find another path *from* 'farArea'
// we must pathfind to us in order to pick up one-way paths INTO our area
from = farArea;
to = this;
}
// build the actual path
if (NavAreaBuildPath( from, to, NULL, cost ) == false)
{
break;
}
// find number of areas on path
int count = 0;
CNavArea *area;
for( area = to; area; area = area->GetParent() )
{
++count;
}
if (count > MAX_PATH_LENGTH)
{
count = MAX_PATH_LENGTH;
}
// if the path is only two areas long, there can be no approach points
if (count <= 2)
{
break;
}
// build path starting from eye
int i = 0;
if (searchType == FROM_EYE)
{
for( area = to; i < count && area; area = area->GetParent() )
{
path[ count-i-1 ] = area;
++i;
}
}
else // TO_EYE
{
for( area = to; i < count && area; area = area->GetParent() )
{
path[ i++ ] = area;
}
}
// traverse path to find first area we cannot see (skip the first area)
for( i=1; i<count; ++i )
{
// if we see this area, continue on
if (path[i]->IsVisible( eye ))
{
continue;
}
// we can't see this area - mark this area as "blocked" and unusable by subsequent approach paths
if (blockedIDCount == MAX_BLOCKED_AREAS)
{
Msg( "Overflow computing approach areas for area #%d.\n", GetID());
return;
}
// if the area to be blocked is actually farArea, block the one just prior
// (blocking farArea will cause all subsequent pathfinds to fail)
int block = (path[i] == farArea) ? i-1 : i;
// dont block the start area, or all subsequence pathfinds will fail
if (block == 0)
{
continue;
}
blockedID[ blockedIDCount++ ] = path[ block ]->GetID();
// store new approach area if not already in set
int a;
for( a=0; a<m_approachCount; ++a )
{
if (m_approach[a].here.area == path[block-1])
{
break;
}
}
if (a == m_approachCount)
{
m_approach[ m_approachCount ].prev.area = (block >= 2) ? path[block-2] : NULL;
m_approach[ m_approachCount ].here.area = path[block-1];
m_approach[ m_approachCount ].prevToHereHow = path[block-1]->GetParentHow();
m_approach[ m_approachCount ].next.area = path[block];
m_approach[ m_approachCount ].hereToNextHow = path[block]->GetParentHow();
++m_approachCount;
}
// we are done with this path
break;
}
}
}
}
}
+473
View File
@@ -0,0 +1,473 @@
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//
//=============================================================================//
// NavMesh.cpp
// Implementation of Navigation Mesh interface
// Author: Michael S. Booth, 2003-2004
#include "cbase.h"
#include "filesystem.h"
#include "cs_nav_mesh.h"
#include "cs_nav_node.h"
#include "cs_nav_area.h"
#include "cs_nav_pathfind.h"
#include "cs_shareddefs.h"
#include "cs_gamerules.h"
#include "fmtstr.h"
#include "utlbuffer.h"
#include "bot_util.h"
#include "tier0/vprof.h"
#include "cs_bot_manager.h"
#ifndef CLIENT_DLL
#include "mapinfo.h"
#endif
class CFuncBlockDMSpawns : public CFuncBrush
{
DECLARE_CLASS( CFuncBlockDMSpawns, CFuncBrush );
void Spawn( void )
{
SetSolid( SOLID_BSP );
AddSolidFlags( FSOLID_NOT_SOLID );
SetModel( STRING( GetModelName() ) ); // set size and link into world
extern ConVar showtriggers;
if ( !showtriggers.GetInt() )
{
AddEffects( EF_NODRAW );
}
}
};
LINK_ENTITY_TO_CLASS( func_block_dm_spawns, CFuncBlockDMSpawns );
extern ConVar mp_randomspawn;
ConVar mp_guardian_target_site( "mp_guardian_target_site", "-1", FCVAR_RELEASE | FCVAR_GAMEDLL, "If set to the index of a bombsite, will cause random spawns to be only created near that site." );
//--------------------------------------------------------------------------------------------------------------
CSNavMesh::CSNavMesh( void )
{
m_desiredChickenCount = 0;
}
//--------------------------------------------------------------------------------------------------------------
CSNavMesh::~CSNavMesh()
{
}
CNavArea * CSNavMesh::CreateArea( void ) const
{
return new CCSNavArea;
}
//-------------------------------------------------------------------------
void CSNavMesh::BeginCustomAnalysis( bool bIncremental )
{
}
//-------------------------------------------------------------------------
// invoked when custom analysis step is complete
void CSNavMesh::PostCustomAnalysis( void )
{
}
//-------------------------------------------------------------------------
void CSNavMesh::EndCustomAnalysis()
{
}
//-------------------------------------------------------------------------
/**
* Returns sub-version number of data format used by derived classes
*/
unsigned int CSNavMesh::GetSubVersionNumber( void ) const
{
// 1: initial implementation - added ApproachArea data
return 1;
}
//-------------------------------------------------------------------------
/**
* Store custom mesh data for derived classes
*/
void CSNavMesh::SaveCustomData( CUtlBuffer &fileBuffer ) const
{
}
//-------------------------------------------------------------------------
/**
* Load custom mesh data for derived classes
*/
void CSNavMesh::LoadCustomData( CUtlBuffer &fileBuffer, unsigned int subVersion )
{
}
//--------------------------------------------------------------------------------------------------------------
/**
* Reset the Navigation Mesh to initial values
*/
void CSNavMesh::Reset( void )
{
m_refreshChickenTimer.Start( 5.0f );
m_refreshDMSpawnTimer.Start( 1.0f );
CNavMesh::Reset();
}
//--------------------------------------------------------------------------------------------------------------
/**
* Zero player counts in all areas
*/
void CSNavMesh::ClearPlayerCounts( void )
{
FOR_EACH_VEC( TheNavAreas, it )
{
CCSNavArea *area = (CCSNavArea*)TheNavAreas[ it ];
area->ClearPlayerCount();
}
}
//--------------------------------------------------------------------------------------------------------------
// Keep desired number of chickens alive in map
void CSNavMesh::MaintainChickenPopulation( void )
{
if ( m_desiredChickenCount > 0 && m_refreshChickenTimer.IsElapsed() )
{
m_refreshChickenTimer.Start( RandomFloat( 10.0f, 20.0f ) );
int actualCount = 0;
for( int i=0; i<m_chickenVector.Count(); ++i )
{
if ( m_chickenVector[i] != NULL )
{
++actualCount;
}
}
if ( actualCount < m_desiredChickenCount )
{
int need = m_desiredChickenCount - actualCount;
for( int k=0; k<need; ++k )
{
// find a good spot to spawn a chicken
CBaseEntity *chicken = NULL;
for( int attempts=0; attempts<10; ++attempts )
{
int which = RandomInt( 0, TheNavAreas.Count()-1 );
CNavArea *testArea = TheNavAreas[ which ];
const float tooSmall = 50.0f;
if ( testArea && testArea->GetSizeX() > tooSmall && testArea->GetSizeY() > tooSmall )
{
if ( !UTIL_IsVisibleToTeam( testArea->GetCenter(), TEAM_CT ) &&
!UTIL_IsVisibleToTeam( testArea->GetCenter(), TEAM_TERRORIST ) )
{
// don't spawn a chicken on top of another chicken
int n;
for( n=0; n<m_chickenVector.Count(); ++n )
{
if ( m_chickenVector[n] == NULL )
continue;
const float tooClose = 50.0f;
Vector between = m_chickenVector[n]->GetAbsOrigin() - testArea->GetCenter();
if ( between.IsLengthLessThan( tooClose ) )
break;
}
if ( n >= m_chickenVector.Count() )
{
// found a good spot - spawn a chicken here
chicken = CreateEntityByName( "chicken" );
if ( chicken )
{
chicken->SetAbsOrigin( testArea->GetCenter() );
DispatchSpawn( chicken );
m_chickenVector.AddToTail( chicken );
}
break;
}
}
}
}
if ( !chicken )
{
// couldn't spawn a chicken - try again later
return;
}
}
}
}
}
//--------------------------------------------------------------------------------------------------------------
void CSNavMesh::Update( void )
{
MaintainChickenPopulation();
MaintainDMSpawnPopulation();
CNavMesh::Update();
}
NavErrorType CSNavMesh::Load( void )
{
return CNavMesh::Load();
}
bool CSNavMesh::Save( void ) const
{
return CNavMesh::Save();
}
NavErrorType CSNavMesh::PostLoad( unsigned int version )
{
if ( CSGameRules()->IsPlayingGunGameDeathmatch() )
m_desiredChickenCount = 10;
else
m_desiredChickenCount = g_pMapInfo ? g_pMapInfo->m_iPetPopulation : 0;
m_chickenVector.RemoveAll();
if ( CSGameRules() && CSGameRules()->IsPlayingCooperativeGametype() )
{
// in Guardian mode, remove all of the mapper placed DM spawn points
CBaseEntity *pEntity = NULL;
while ( ( pEntity = gEntList.FindEntityByClassname( pEntity, "info_deathmatch_spawn" ) ) != NULL )
{
UTIL_Remove( pEntity );
}
}
ResetDMSpawns();
return CNavMesh::PostLoad(version);
}
CON_COMMAND( dm_reset_spawns, "" )
{
( dynamic_cast< CSNavMesh* >( TheNavMesh ) )->ResetDMSpawns();
DevMsg( "Manually resetting deathmatch spawns.\n");
}
void CSNavMesh::ResetDMSpawns( void )
{
FOR_EACH_VEC( m_DMSpawnVector, i )
{
UTIL_Remove( m_DMSpawnVector[i] );
}
m_desiredDMSpawns = 50;
m_consecutiveFailedAttempts = 0;
m_DMSpawnVector.RemoveAll();
MaintainDMSpawnPopulation();
}
void CSNavMesh::MaintainDMSpawnPopulation( void )
{
if ( mp_randomspawn.GetInt() == 0 )
return;
bool bDisableAutoGeneratedDMSpawns = ( g_pMapInfo ? g_pMapInfo->m_bDisableAutoGeneratedDMSpawns : false );
// HACK: Force this to run in guardian, even if the map explicitly placed dm spawns
if ( bDisableAutoGeneratedDMSpawns && !CSGameRules()->IsPlayingCoopGuardian() )
return;
if ( m_desiredDMSpawns > 0 && m_refreshDMSpawnTimer.IsElapsed() )
{
m_refreshDMSpawnTimer.Start( 1.0f );
int actualCount = 0;
for( int i=0; i<m_DMSpawnVector.Count(); ++i )
{
if ( m_DMSpawnVector[i] != NULL )
{
++actualCount;
}
}
// If the map isn't large enough to support as many spawns as m_desiredDMSpawns then shrink m_desiredDMSpawns to whatever we can get.
if ( m_consecutiveFailedAttempts >= 100 )
{
m_desiredDMSpawns = actualCount;
m_consecutiveFailedAttempts = 0;
Warning( "Giving up attempts to make more random spawn points. Current count: %i.\n", actualCount );
}
if ( actualCount < m_desiredDMSpawns )
{
// float calctime = Plat_FloatTime(); MEASURE PERF 1/2
int need = m_desiredDMSpawns - actualCount;
int spawnsToTry = max( 10, need );
for( int k=0; k < spawnsToTry; k++ )
{
// find a good spot to spawn a Deathmatch spawnpoint
CBaseEntity *DMSpawn = NULL;
m_consecutiveFailedAttempts++;
for( int attempts = 0; attempts < 10; attempts++ )
{
CNavArea *testArea = NULL;
if ( mp_guardian_target_site.GetInt() >= 0 )
{
const CCSBotManager::Zone *zone = TheCSBots()->GetZone( mp_guardian_target_site.GetInt() );
if ( zone )
{
testArea = zone->m_area[ RandomInt( 0, zone->m_areaCount - 1 ) ];
}
}
else
{
testArea = TheNavAreas[ RandomInt( 0, TheNavAreas.Count() - 1 ) ];
}
const float tooSmall = ( mp_guardian_target_site.GetInt() >= 0 ) ? 35.0f : 100.0f;
if ( testArea && testArea->GetSizeX() > tooSmall && testArea->GetSizeY() > tooSmall )
{
bool bBadNavArea = false;
// don't spawn a DMSpawn too close to another DMSpawn
for( int n = 0; n < m_DMSpawnVector.Count(); n++ )
{
if ( m_DMSpawnVector[n] == NULL )
continue;
float tooClose = ( mp_guardian_target_site.GetInt() >= 0 ) ? 50.0f : 300.0f;
Vector between = m_DMSpawnVector[n]->GetAbsOrigin() - testArea->GetCenter();
if ( between.IsLengthLessThan( tooClose ) )
{
bBadNavArea = true;
break;
}
}
if ( bBadNavArea )
continue;
if ( IsSpawnBlockedByTrigger( testArea->GetCenter() + Vector( 0, 0, 16 ) ) )
continue;
// check that we can path from the nav area to a ct spawner to confirm it isn't orphaned.
CBaseEntity *CTSpawn = gEntList.FindEntityByClassname( NULL, "info_player_counterterrorist" );
if ( CTSpawn )
{
CNavArea *CTSpawnArea = GetNearestNavArea( CTSpawn->GetAbsOrigin() );
ShortestPathCost cost;
bool bNotOrphaned = NavAreaBuildPath( testArea, CTSpawnArea, NULL, cost );
if ( !bNotOrphaned )
{
const char* szTSpawnEntName = "info_player_terrorist";
if ( CSGameRules()->IsPlayingCoopMission() )
szTSpawnEntName = "info_enemy_terrorist_spawn";
// double check that we can path from the nav area to a t spawner to confirm it isn't orphaned.
CBaseEntity *TSpawn = gEntList.FindEntityByClassname( NULL, szTSpawnEntName );
if ( TSpawn )
{
CNavArea *TSpawnArea = GetNearestNavArea( TSpawn->GetAbsOrigin() );
ShortestPathCost cost2;
bNotOrphaned = NavAreaBuildPath( testArea, TSpawnArea, NULL, cost2 );
if ( !bNotOrphaned )
continue;
}
}
}
// found a good spot - spawn a DMSpawn here
DMSpawn = CreateEntityByName( "info_deathmatch_spawn" );
if ( DMSpawn )
{
DMSpawn->SetAbsOrigin( testArea->GetCenter() + Vector( 0, 0, 16 ));
DispatchSpawn( DMSpawn );
m_DMSpawnVector.AddToTail( DMSpawn );
m_consecutiveFailedAttempts = 0;
// Msg( " %f :CREATED DM SPAWN %i.\n", gpGlobals->curtime, m_DMSpawnVector.Count() );
}
break;
}
}
}
// MEASURE PERF 2/2
// calctime = Plat_FloatTime() - calctime;
// Msg( "DM SPAWN SEARCH CALC TIME: %f\n", calctime );
}
}
}
bool CSNavMesh::IsSpawnBlockedByTrigger( Vector pos )
{
CBaseEntity *list[32];
Ray_t ray;
ray.Init( pos, pos + Vector( 1,1,1 ) );
int nCount = AllEdictsAlongRay( list, 1024, ray, 0 );
for ( int i = 0; i < nCount; i++ )
{
if ( FClassnameIs( list[i], "func_block_dm_spawns" ) )
return true;
}
return false;
}
int CSNavMesh::AllEdictsAlongRay( CBaseEntity **pList, int listMax, const Ray_t &ray, int flagMask )
{
CFlaggedEntitiesEnum rayEnum( pList, listMax, flagMask );
::partition->EnumerateElementsAlongRay( PARTITION_ENGINE_NON_STATIC_EDICTS, ray, false, &rayEnum );
return rayEnum.GetCount();
}
+86
View File
@@ -0,0 +1,86 @@
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//
//=============================================================================//
// cs_nav_mesh.h
// The Navigation Mesh interface
// Author: Michael S. Booth (mike@turtlerockstudios.com), January 2003
//
// Author: Michael S. Booth (mike@turtlerockstudios.com), 2003
//
// NOTE: The Navigation code uses Doxygen-style comments. If you run Doxygen over this code, it will
// auto-generate documentation. Visit www.doxygen.org to download the system for free.
//
#ifndef _CS_NAV_MESH_H_
#define _CS_NAV_MESH_H_
#include "filesystem.h"
#include "nav_mesh.h"
#include "cs_nav.h"
#include "nav_area.h"
#include "nav_colors.h"
class CNavArea;
class CCSNavArea;
class CBaseEntity;
//--------------------------------------------------------------------------------------------------------
/**
* The CSNavMesh is the global interface to the Navigation Mesh.
* @todo Make this an abstract base class interface, and derive mod-specific implementations.
*/
class CSNavMesh : public CNavMesh
{
public:
CSNavMesh( void );
virtual ~CSNavMesh();
virtual CNavArea *CreateArea( void ) const; // CNavArea factory
virtual unsigned int GetSubVersionNumber( void ) const; // returns sub-version number of data format used by derived classes
virtual void SaveCustomData( CUtlBuffer &fileBuffer ) const; // store custom mesh data for derived classes
virtual void LoadCustomData( CUtlBuffer &fileBuffer, unsigned int subVersion ); // load custom mesh data for derived classes
virtual void Reset( void ); ///< destroy Navigation Mesh data and revert to initial state
virtual void Update( void ); ///< invoked on each game frame
virtual NavErrorType Load( void ); // load navigation data from a file
virtual NavErrorType PostLoad( unsigned int version ); // (EXTEND) invoked after all areas have been loaded - for pointer binding, etc
virtual bool Save( void ) const; ///< store Navigation Mesh to a file
void ClearPlayerCounts( void ); ///< zero player counts in all areas
void ResetDMSpawns( void );
protected:
virtual void BeginCustomAnalysis( bool bIncremental );
virtual void PostCustomAnalysis( void ); // invoked when custom analysis step is complete
virtual void EndCustomAnalysis();
virtual bool IsMeshVisibilityGenerated( void ) const { return false; } // allow derived meshes to skip costly mesh visibility computation and storage
private:
void MaintainChickenPopulation( void );
int m_desiredChickenCount;
CountdownTimer m_refreshChickenTimer;
CUtlVector< CHandle< CBaseEntity > > m_chickenVector;
void MaintainDMSpawnPopulation( void );
int m_desiredDMSpawns;
int m_consecutiveFailedAttempts;
CountdownTimer m_refreshDMSpawnTimer;
CUtlVector< CHandle< CBaseEntity > > m_DMSpawnVector;
bool IsSpawnBlockedByTrigger( Vector pos );
int AllEdictsAlongRay( CBaseEntity **pList, int listMax, const Ray_t &ray, int flagMask );
};
#endif // _CS_NAV_MESH_H_
+383
View File
@@ -0,0 +1,383 @@
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//
//=============================================================================//
// nav_node.cpp
// AI Navigation Nodes
// Author: Michael S. Booth (mike@turtlerockstudios.com), January 2003
#include "cbase.h"
#include "cs_nav_node.h"
#include "nav_colors.h"
#include "nav_mesh.h"
NavDirType Opposite[ NUM_DIRECTIONS ] = { SOUTH, WEST, NORTH, EAST };
CSNavNode *CSNavNode::m_list = NULL;
unsigned int CSNavNode::m_listLength = 0;
unsigned int CSNavNode::m_nextID = 1;
ConVar nav_show_nodes( "nav_show_nodes", "0" );
//--------------------------------------------------------------------------------------------------------------
class LookAtTarget
{
public:
LookAtTarget( const Vector &target )
{
m_target = target;
}
bool operator()( CBasePlayer *player )
{
QAngle angles;
Vector to = m_target - player->GetAbsOrigin();
VectorAngles( to, angles );
player->SetLocalAngles( angles );
player->SnapEyeAngles( angles );
return true;
}
private:
Vector m_target;
};
//--------------------------------------------------------------------------------------------------------------
/**
* Constructor
*/
CSNavNode::CSNavNode( const Vector &pos, const Vector &normal, CSNavNode *parent )
{
m_pos = pos;
m_normal = normal;
m_id = m_nextID++;
int i;
for( i=0; i<NUM_DIRECTIONS; ++i )
{
m_to[ i ] = NULL;
}
for ( i=0; i<NUM_CORNERS; ++i )
{
m_crouch[ i ] = false;
}
m_visited = 0;
m_parent = parent;
m_next = m_list;
m_list = this;
m_listLength++;
m_isCovered = false;
m_area = NULL;
m_attributeFlags = 0;
if ( nav_show_nodes.GetBool() )
{
NDebugOverlay::Cross3D( m_pos, 10.0f, 128, 128, 128, true, 10.0f );
NDebugOverlay::Cross3D( m_pos, 10.0f, 255, 255, 255, false, 10.0f );
LookAtTarget lookAt( m_pos );
ForEachPlayer( lookAt );
}
}
//--------------------------------------------------------------------------------------------------------------
#if DEBUG_NAV_NODES
ConVar nav_show_node_id( "nav_show_node_id", "0" );
ConVar nav_test_node( "nav_test_node", "0" );
ConVar nav_test_node_crouch( "nav_test_node_crouch", "0" );
ConVar nav_test_node_crouch_dir( "nav_test_node_crouch_dir", "4" );
#endif // DEBUG_NAV_NODES
//--------------------------------------------------------------------------------------------------------------
void CSNavNode::Draw( void )
{
#if DEBUG_NAV_NODES
if ( !nav_show_nodes.GetBool() )
return;
int r = 0, g = 0, b = 0;
if ( m_isCovered )
{
if ( GetAttributes() & NAV_MESH_CROUCH )
{
b = 255;
}
else
{
r = 255;
}
}
else
{
if ( GetAttributes() & NAV_MESH_CROUCH )
{
b = 255;
}
g = 255;
}
NDebugOverlay::Cross3D( m_pos, 2, r, g, b, true, 0.1f );
if ( (!m_isCovered && nav_show_node_id.GetBool()) || (m_isCovered && nav_show_node_id.GetInt() < 0) )
{
char text[16];
Q_snprintf( text, sizeof( text ), "%d", m_id );
NDebugOverlay::Text( m_pos, text, true, 0.1f );
}
if ( (unsigned int)(nav_test_node.GetInt()) == m_id )
{
TheNavMesh->TestArea( this, 1, 1 );
nav_test_node.SetValue( 0 );
}
if ( (unsigned int)(nav_test_node_crouch.GetInt()) == m_id )
{
CheckCrouch();
nav_test_node_crouch.SetValue( 0 );
}
if ( GetAttributes() & NAV_MESH_CROUCH )
{
int i;
for( i=0; i<NUM_CORNERS; i++ )
{
if ( m_crouch[i] )
{
Vector2D dir;
CornerToVector2D( (NavCornerType)i, &dir );
const float scale = 3.0f;
Vector scaled( dir.x * scale, dir.y * scale, 0 );
NDebugOverlay::HorzArrow( m_pos, m_pos + scaled, 0.5, 0, 0, 255, 255, true, 0.1f );
}
}
}
#endif // DEBUG_NAV_NODES
}
//--------------------------------------------------------------------------------------------------------------
void CSNavNode::CheckCrouch( void )
{
CTraceFilterWalkableEntities filter( NULL, COLLISION_GROUP_PLAYER_MOVEMENT, WALK_THRU_EVERYTHING );
trace_t tr;
// Trace downward from duck height to find the max floor height for the node's surroundings
Vector mins( -HalfHumanWidth, -HalfHumanWidth, 0 );
Vector maxs( HalfHumanWidth, HalfHumanWidth, 0 );
Vector start( m_pos.x, m_pos.y, m_pos.z + VEC_DUCK_HULL_MAX.z - 0.1f );
UTIL_TraceHull(
start,
m_pos,
mins,
maxs,
MASK_PLAYERSOLID_BRUSHONLY,
&filter,
&tr );
Vector groundPos = tr.endpos;
if ( tr.startsolid && !tr.allsolid )
{
// Try going down out of the solid and re-check for the floor height
start.z -= tr.endpos.z - 0.1f;
UTIL_TraceHull(
start,
m_pos,
mins,
maxs,
MASK_PLAYERSOLID_BRUSHONLY,
&filter,
&tr );
groundPos = tr.endpos;
}
if ( tr.startsolid )
{
// we don't even have duck height clear. try a simple check to find floor height.
float x, y;
// Find the highest floor z - for a player to stand in this area, we need a full
// VEC_HULL_MAX.z of clearance above this height at all points.
float maxFloorZ = m_pos.z;
for( y = -HalfHumanWidth; y <= HalfHumanWidth + 0.1f; y += HalfHumanWidth )
{
for( x = -HalfHumanWidth; x <= HalfHumanWidth + 0.1f; x += HalfHumanWidth )
{
float floorZ;
if ( TheNavMesh->GetGroundHeight( m_pos, &floorZ ) )
{
maxFloorZ = MAX( maxFloorZ, floorZ + 0.1f );
}
}
}
groundPos.Init( m_pos.x, m_pos.y, maxFloorZ );
}
// For each direction, trace upwards from our best ground height to VEC_HULL_MAX.z to see if we have standing room.
for ( int i=0; i<NUM_CORNERS; ++i )
{
#if DEBUG_NAV_NODES
if ( nav_test_node_crouch_dir.GetInt() != NUM_CORNERS && i != nav_test_node_crouch_dir.GetInt() )
continue;
#endif // DEBUG_NAV_NODES
NavCornerType corner = (NavCornerType)i;
Vector2D cornerVec;
CornerToVector2D( corner, &cornerVec );
Vector actualGroundPos = groundPos; // we might need to adjust this if the tracehull failed above and we fell back to m_pos.z
// Build a mins/maxs pair for the HumanWidth x HalfHumanWidth box facing the appropriate direction
mins.Init();
maxs.Init( cornerVec.x * HalfHumanWidth, cornerVec.y * HalfHumanWidth, 0 );
// now make sure that mins is smaller than maxs
for ( int j=0; j<3; ++j )
{
if ( mins[j] > maxs[j] )
{
float tmp = mins[j];
mins[j] = maxs[j];
maxs[j] = tmp;
}
}
UTIL_TraceHull(
actualGroundPos + Vector( 0, 0, 0.1f ),
actualGroundPos + Vector( 0, 0, VEC_HULL_MAX.z - 0.2f ),
mins,
maxs,
MASK_PLAYERSOLID_BRUSHONLY,
&filter,
&tr );
actualGroundPos.z += tr.fractionleftsolid * VEC_HULL_MAX.z;
float maxHeight = actualGroundPos.z + VEC_DUCK_HULL_MAX.z;
for ( ; tr.startsolid && actualGroundPos.z <= maxHeight; actualGroundPos.z += 1.0f )
{
// In case we didn't find a good ground pos above, we could start in the ground. Move us up some.
UTIL_TraceHull(
actualGroundPos + Vector( 0, 0, 0.1f ),
actualGroundPos + Vector( 0, 0, VEC_HULL_MAX.z - 0.2f ),
mins,
maxs,
MASK_PLAYERSOLID_BRUSHONLY,
&filter,
&tr );
}
if (tr.startsolid || tr.fraction != 1.0f)
{
SetAttributes( NAV_MESH_CROUCH );
m_crouch[corner] = true;
}
#if DEBUG_NAV_NODES
if ( nav_show_nodes.GetBool() )
{
if ( nav_test_node_crouch_dir.GetInt() == i || nav_test_node_crouch_dir.GetInt() == NUM_CORNERS )
{
if ( tr.startsolid )
{
NDebugOverlay::Box( actualGroundPos, mins, maxs+Vector( 0, 0, VEC_HULL_MAX.z), 255, 0, 0, 10, 20.0f );
}
else if ( m_crouch[corner] )
{
NDebugOverlay::Box( actualGroundPos, mins, maxs+Vector( 0, 0, VEC_HULL_MAX.z), 0, 0, 255, 10, 20.0f );
}
else
{
NDebugOverlay::Box( actualGroundPos, mins, maxs+Vector( 0, 0, VEC_HULL_MAX.z), 0, 255, 0, 10, 10.0f );
}
}
}
#endif // DEBUG_NAV_NODES
}
}
//--------------------------------------------------------------------------------------------------------------
/**
* Create a connection FROM this node TO the given node, in the given direction
*/
void CSNavNode::ConnectTo( CSNavNode *node, NavDirType dir )
{
m_to[ dir ] = node;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Return node at given position.
* @todo Need a hash table to make this lookup fast
*/
CSNavNode *CSNavNode::GetNode( const Vector &pos )
{
const float tolerance = 0.45f * GenerationStepSize; // 1.0f
for( CSNavNode *node = m_list; node; node = node->m_next )
{
float dx = fabs( node->m_pos.x - pos.x );
float dy = fabs( node->m_pos.y - pos.y );
float dz = fabs( node->m_pos.z - pos.z );
if (dx < tolerance && dy < tolerance && dz < tolerance)
return node;
}
return NULL;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Return true if this node is bidirectionally linked to
* another node in the given direction
*/
BOOL CSNavNode::IsBiLinked( NavDirType dir ) const
{
if (m_to[ dir ] && m_to[ dir ]->m_to[ Opposite[dir] ] == this)
{
return true;
}
return false;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Return true if this node is the NW corner of a quad of nodes
* that are all bidirectionally linked.
*/
BOOL CSNavNode::IsClosedCell( void ) const
{
if (IsBiLinked( SOUTH ) &&
IsBiLinked( EAST ) &&
m_to[ EAST ]->IsBiLinked( SOUTH ) &&
m_to[ SOUTH ]->IsBiLinked( EAST ) &&
m_to[ EAST ]->m_to[ SOUTH ] == m_to[ SOUTH ]->m_to[ EAST ])
{
return true;
}
return false;
}
+135
View File
@@ -0,0 +1,135 @@
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//
//=============================================================================//
// nav_node.h
// Navigation Nodes are used when generating a Navigation Mesh by point sampling the map
// Author: Michael S. Booth (mike@turtlerockstudios.com), January 2003
#ifndef _CS_NAV_NODE_H_
#define _CS_NAV_NODE_H_
#include "cs_nav.h"
// If DEBUG_NAV_NODES is true, nav_show_nodes controls drawing node positions, and
// nav_show_node_id allows you to show the IDs of nodes that didn't get used to create areas.
#ifdef _DEBUG
#define DEBUG_NAV_NODES 1
#else
#define DEBUG_NAV_NODES 0
#endif
//--------------------------------------------------------------------------------------------------------------
/**
* Navigation Nodes.
* These Nodes encapsulate world locations, and ways to get from one location to an adjacent one.
* Note that these links are not necessarily commutative (falling off of a ledge, for example).
*/
class CSNavNode
{
public:
CSNavNode( const Vector &pos, const Vector &normal, CSNavNode *parent = NULL );
static CSNavNode *GetNode( const Vector &pos ); ///< return navigation node at the position, or NULL if none exists
CSNavNode *GetConnectedNode( NavDirType dir ) const; ///< get navigation node connected in given direction, or NULL if cant go that way
const Vector *GetPosition( void ) const;
const Vector *GetNormal( void ) const { return &m_normal; }
unsigned int GetID( void ) const { return m_id; }
static CSNavNode *GetFirst( void ) { return m_list; }
static unsigned int GetListLength( void ) { return m_listLength; }
CSNavNode *GetNext( void ) { return m_next; }
void Draw( void );
void ConnectTo( CSNavNode *node, NavDirType dir ); ///< create a connection FROM this node TO the given node, in the given direction
CSNavNode *GetParent( void ) const;
void MarkAsVisited( NavDirType dir ); ///< mark the given direction as having been visited
BOOL HasVisited( NavDirType dir ); ///< return TRUE if the given direction has already been searched
BOOL IsBiLinked( NavDirType dir ) const; ///< node is bidirectionally linked to another node in the given direction
BOOL IsClosedCell( void ) const; ///< node is the NW corner of a bi-linked quad of nodes
void Cover( void ) { m_isCovered = true; } ///< @todo Should pass in area that is covering
BOOL IsCovered( void ) const { return m_isCovered; } ///< return true if this node has been covered by an area
void AssignArea( CNavArea *area ); ///< assign the given area to this node
CNavArea *GetArea( void ) const; ///< return associated area
void SetAttributes( unsigned char bits ) { m_attributeFlags = bits; }
unsigned char GetAttributes( void ) const { return m_attributeFlags; }
private:
friend class CNavMesh;
void CheckCrouch( void );
Vector m_pos; ///< position of this node in the world
Vector m_normal; ///< surface normal at this location
CSNavNode *m_to[ NUM_DIRECTIONS ]; ///< links to north, south, east, and west. NULL if no link
unsigned int m_id; ///< unique ID of this node
unsigned char m_attributeFlags; ///< set of attribute bit flags (see NavAttributeType)
static CSNavNode *m_list; ///< the master list of all nodes for this map
static unsigned int m_listLength;
static unsigned int m_nextID;
CSNavNode *m_next; ///< next link in master list
// below are only needed when generating
unsigned char m_visited; ///< flags for automatic node generation. If direction bit is clear, that direction hasn't been explored yet.
CSNavNode *m_parent; ///< the node prior to this in the search, which we pop back to when this node's search is done (a stack)
bool m_isCovered; ///< true when this node is "covered" by a CNavArea
CNavArea *m_area; ///< the area this node is contained within
bool m_crouch[ NUM_CORNERS ];
};
//--------------------------------------------------------------------------------------------------------------
//
// Inlines
//
inline CSNavNode *CSNavNode::GetConnectedNode( NavDirType dir ) const
{
return m_to[ dir ];
}
inline const Vector *CSNavNode::GetPosition( void ) const
{
return &m_pos;
}
inline CSNavNode *CSNavNode::GetParent( void ) const
{
return m_parent;
}
inline void CSNavNode::MarkAsVisited( NavDirType dir )
{
m_visited |= (1 << dir);
}
inline BOOL CSNavNode::HasVisited( NavDirType dir )
{
if (m_visited & (1 << dir))
return true;
return false;
}
inline void CSNavNode::AssignArea( CNavArea *area )
{
m_area = area;
}
inline CNavArea *CSNavNode::GetArea( void ) const
{
return m_area;
}
#endif // _CS_NAV_NODE_H_
File diff suppressed because it is too large Load Diff
+246
View File
@@ -0,0 +1,246 @@
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//
//=============================================================================//
// nav_path.h
// Navigation Path encapsulation
// Author: Michael S. Booth (mike@turtlerockstudios.com), November 2003
#ifndef _CS_NAV_PATH_H_
#define _CS_NAV_PATH_H_
#include "nav_area.h"
#include "bot_util.h"
class CImprovLocomotor;
//--------------------------------------------------------------------------------------------------------
/**
* The CCSNavPath class encapsulates a path through space
*/
class CCSNavPath
{
public:
CCSNavPath( void )
{
m_segmentCount = 0;
}
struct PathSegment
{
CNavArea *area; ///< the area along the path
NavTraverseType how; ///< how to enter this area from the previous one
Vector pos; ///< our movement goal position at this point in the path
const CNavLadder *ladder; ///< if "how" refers to a ladder, this is it
};
const PathSegment * operator[] ( int i ) const { return (i >= 0 && i < m_segmentCount) ? &m_path[i] : NULL; }
const PathSegment *GetSegment( int i ) const { return (i >= 0 && i < m_segmentCount) ? &m_path[i] : NULL; }
int GetSegmentCount( void ) const { return m_segmentCount; }
const Vector &GetEndpoint( void ) const { return m_path[ m_segmentCount-1 ].pos; }
bool IsAtEnd( const Vector &pos ) const; ///< return true if position is at the end of the path
float GetLength( void ) const; ///< return length of path from start to finish
bool GetPointAlongPath( float distAlong, Vector *pointOnPath ) const; ///< return point a given distance along the path - if distance is out of path bounds, point is clamped to start/end
/// return the node index closest to the given distance along the path without going over - returns (-1) if error
int GetSegmentIndexAlongPath( float distAlong ) const;
bool IsValid( void ) const { return (m_segmentCount > 0); }
void Invalidate( void ) { m_segmentCount = 0; }
void Draw( const Vector &color = Vector( 1.0f, 0.3f, 0 ) ); ///< draw the path for debugging
/// compute closest point on path to given point
bool FindClosestPointOnPath( const Vector *worldPos, int startIndex, int endIndex, Vector *close ) const;
void Optimize( void );
/**
* Compute shortest path from 'start' to 'goal' via A* algorithm.
* If returns true, path was build to the goal position.
* If returns false, path may either be invalid (use IsValid() to check), or valid but
* doesn't reach all the way to the goal.
*/
template< typename CostFunctor >
bool Compute( const Vector &start, const Vector &goal, CostFunctor &costFunc )
{
Invalidate();
CNavArea *startArea = TheNavMesh->GetNearestNavArea( start + Vector( 0.0f, 0.0f, 1.0f ) );
if (startArea == NULL)
{
return false;
}
CNavArea *goalArea = TheNavMesh->GetNavArea( goal );
// if we are already in the goal area, build trivial path
if (startArea == goalArea)
{
BuildTrivialPath( start, goal );
return true;
}
// make sure path end position is on the ground
Vector pathEndPosition = goal;
if (goalArea)
{
pathEndPosition.z = goalArea->GetZ( pathEndPosition );
}
else
{
TheNavMesh->GetGroundHeight( pathEndPosition, &pathEndPosition.z );
}
//
// Compute shortest path to goal
//
CNavArea *closestArea;
bool pathResult = NavAreaBuildPath( startArea, goalArea, &goal, costFunc, &closestArea );
//
// Build path by following parent links
//
// get count
int count = 0;
CNavArea *area;
for( area = closestArea; area; area = area->GetParent() )
{
++count;
}
// save room for endpoint
if (count > MAX_PATH_SEGMENTS-1)
{
count = MAX_PATH_SEGMENTS-1;
}
if (count == 0)
{
return false;
}
if (count == 1)
{
BuildTrivialPath( start, goal );
return true;
}
// build path
m_segmentCount = count;
for( area = closestArea; count && area; area = area->GetParent() )
{
--count;
m_path[ count ].area = area;
m_path[ count ].how = area->GetParentHow();
}
// compute path positions
if (ComputePathPositions() == false)
{
//PrintIfWatched( "CNavPath::Compute: Error building path\n" );
Invalidate();
return false;
}
// append path end position
m_path[ m_segmentCount ].area = closestArea;
m_path[ m_segmentCount ].pos = pathEndPosition;
m_path[ m_segmentCount ].ladder = NULL;
m_path[ m_segmentCount ].how = NUM_TRAVERSE_TYPES;
++m_segmentCount;
return pathResult;
}
private:
enum { MAX_PATH_SEGMENTS = 256 };
PathSegment m_path[ MAX_PATH_SEGMENTS ];
int m_segmentCount;
bool ComputePathPositions( void ); ///< determine actual path positions
bool BuildTrivialPath( const Vector &start, const Vector &goal ); ///< utility function for when start and goal are in the same area
int FindNextOccludedNode( int anchor ); ///< used by Optimize()
};
//--------------------------------------------------------------------------------------------------------
/**
* Monitor improv movement and determine if it becomes stuck
*/
class CStuckMonitor
{
public:
CStuckMonitor( void );
void Reset( void );
void Update( CImprovLocomotor *improv );
bool IsStuck( void ) const { return m_isStuck; }
float GetDuration( void ) const { return (m_isStuck) ? m_stuckTimer.GetElapsedTime() : 0.0f; }
private:
bool m_isStuck; ///< if true, we are stuck
Vector m_stuckSpot; ///< the location where we became stuck
IntervalTimer m_stuckTimer; ///< how long we have been stuck
enum { MAX_VEL_SAMPLES = 5 };
float m_avgVel[ MAX_VEL_SAMPLES ];
int m_avgVelIndex;
int m_avgVelCount;
Vector m_lastCentroid;
float m_lastTime;
};
//--------------------------------------------------------------------------------------------------------
/**
* The CNavPathFollower class implements path following behavior
*/
class CNavPathFollower
{
public:
CNavPathFollower( void );
void SetImprov( CImprovLocomotor *improv ) { m_improv = improv; }
void SetPath( CCSNavPath *path ) { m_path = path; }
void Reset( void );
#define DONT_AVOID_OBSTACLES false
void Update( float deltaT, bool avoidObstacles = true ); ///< move improv along path
void Debug( bool status ) { m_isDebug = status; } ///< turn debugging on/off
bool IsStuck( void ) const { return m_stuckMonitor.IsStuck(); } ///< return true if improv is stuck
void ResetStuck( void ) { m_stuckMonitor.Reset(); }
float GetStuckDuration( void ) const { return m_stuckMonitor.GetDuration(); } ///< return how long we've been stuck
void FeelerReflexAdjustment( Vector *goalPosition, float height = -1.0f ); ///< adjust goal position if "feelers" are touched
private:
CImprovLocomotor *m_improv; ///< who is doing the path following
CCSNavPath *m_path; ///< the path being followed
int m_segmentIndex; ///< the point on the path the improv is moving towards
int m_behindIndex; ///< index of the node on the path just behind us
Vector m_goal; ///< last computed follow goal
bool m_isLadderStarted;
bool m_isDebug;
int FindOurPositionOnPath( Vector *close, bool local ) const; ///< return the closest point to our current position on current path
int FindPathPoint( float aheadRange, Vector *point, int *prevIndex ); ///< compute a point a fixed distance ahead along our path.
CStuckMonitor m_stuckMonitor;
};
#endif // _CS_NAV_PATH_H_
+65
View File
@@ -0,0 +1,65 @@
//========= Copyright Š 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//
//=============================================================================//
// nav_pathfind.h
// Path-finding mechanisms using the Navigation Mesh
// Author: Michael S. Booth (mike@turtlerockstudios.com), January 2003
#ifndef _CS_NAV_PATHFIND_H_
#define _CS_NAV_PATHFIND_H_
#include "nav_pathfind.h"
//--------------------------------------------------------------------------------------------------------------
/**
* Compute travel distance along shortest path from startPos to goalPos.
* Return -1 if can't reach endPos from goalPos.
*/
template< typename CostFunctor >
float NavAreaTravelDistance( const Vector &startPos, const Vector &goalPos, CostFunctor &costFunc )
{
CNavArea *startArea = TheNavMesh->GetNearestNavArea( startPos );
if (startArea == NULL)
{
return -1.0f;
}
// compute path between areas using given cost heuristic
CNavArea *goalArea = NULL;
if (NavAreaBuildPath( startArea, NULL, &goalPos, costFunc, &goalArea ) == false)
{
return -1.0f;
}
// compute distance along path
if (goalArea->GetParent() == NULL)
{
// both points are in the same area - return euclidean distance
return (goalPos - startPos).Length();
}
else
{
CNavArea *area;
float distance;
// goalPos is assumed to be inside goalArea (or very close to it) - skip to next area
area = goalArea->GetParent();
distance = (goalPos - area->GetCenter()).Length();
for( ; area->GetParent(); area = area->GetParent() )
{
distance += (area->GetCenter() - area->GetParent()->GetCenter()).Length();
}
// add in distance to startPos
distance += (startPos - area->GetCenter()).Length();
return distance;
}
}
#endif // _CS_NAV_PATHFIND_H_
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,717 @@
//========= Copyright 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose: CS's custom CPlayerResource
//
// $NoKeywords: $
//=============================================================================//
#include "cbase.h"
#include "cs_player.h"
#include "cs_simple_hostage.h"
#include "cs_player_resource.h"
#include "econ_game_account_client.h"
#include "weapon_c4.h"
#include <coordsize.h>
#include "cs_bot_manager.h"
#include "cs_gamerules.h"
#include "cs_bot.h"
#include "cs_team.h"
extern bool g_fGameOver;
// Datatable
IMPLEMENT_SERVERCLASS_ST(CCSPlayerResource, DT_CSPlayerResource)
SendPropInt( SENDINFO( m_iPlayerC4 ), 8, SPROP_UNSIGNED ),
SendPropInt( SENDINFO( m_iPlayerVIP ), 8, SPROP_UNSIGNED ),
SendPropArray3( SENDINFO_ARRAY3(m_bHostageAlive), SendPropInt( SENDINFO_ARRAY(m_bHostageAlive), 1, SPROP_UNSIGNED ) ),
SendPropArray3( SENDINFO_ARRAY3(m_isHostageFollowingSomeone), SendPropInt( SENDINFO_ARRAY(m_isHostageFollowingSomeone), 1, SPROP_UNSIGNED ) ),
SendPropArray3( SENDINFO_ARRAY3(m_iHostageEntityIDs), SendPropInt( SENDINFO_ARRAY(m_iHostageEntityIDs), -1, SPROP_UNSIGNED ) ),
SendPropVector( SENDINFO(m_bombsiteCenterA), -1, SPROP_COORD),
SendPropVector( SENDINFO(m_bombsiteCenterB), -1, SPROP_COORD),
SendPropArray3( SENDINFO_ARRAY3(m_hostageRescueX), SendPropInt( SENDINFO_ARRAY(m_hostageRescueX), COORD_INTEGER_BITS+1, 0 ) ),
SendPropArray3( SENDINFO_ARRAY3(m_hostageRescueY), SendPropInt( SENDINFO_ARRAY(m_hostageRescueY), COORD_INTEGER_BITS+1, 0 ) ),
SendPropArray3( SENDINFO_ARRAY3(m_hostageRescueZ), SendPropInt( SENDINFO_ARRAY(m_hostageRescueZ), COORD_INTEGER_BITS+1, 0 ) ),
SendPropArray3( SENDINFO_ARRAY3(m_iMVPs), SendPropInt( SENDINFO_ARRAY(m_iMVPs), COORD_INTEGER_BITS+1, SPROP_UNSIGNED ) ),
SendPropArray3( SENDINFO_ARRAY3(m_iArmor), SendPropInt( SENDINFO_ARRAY(m_iArmor), COORD_INTEGER_BITS+1, SPROP_UNSIGNED ) ),
SendPropArray3( SENDINFO_ARRAY3(m_bHasDefuser), SendPropInt( SENDINFO_ARRAY(m_bHasDefuser), 1, SPROP_UNSIGNED ) ),
SendPropArray3( SENDINFO_ARRAY3(m_bHasHelmet), SendPropInt( SENDINFO_ARRAY(m_bHasHelmet), 1, SPROP_UNSIGNED ) ),
SendPropArray3( SENDINFO_ARRAY3(m_iScore), SendPropInt( SENDINFO_ARRAY(m_iScore), 32) ),
SendPropArray3( SENDINFO_ARRAY3(m_iCompetitiveRanking), SendPropInt( SENDINFO_ARRAY(m_iCompetitiveRanking), 32) ),
SendPropArray3( SENDINFO_ARRAY3(m_iCompetitiveWins), SendPropInt( SENDINFO_ARRAY(m_iCompetitiveWins), 32) ),
SendPropArray3( SENDINFO_ARRAY3( m_iCompTeammateColor ), SendPropInt( SENDINFO_ARRAY( m_iCompTeammateColor ), 32 ) ),
#if CS_CONTROLLABLE_BOTS_ENABLED
SendPropArray3( SENDINFO_ARRAY3(m_bControllingBot), SendPropInt( SENDINFO_ARRAY(m_bControllingBot), 1, SPROP_UNSIGNED ) ),
SendPropArray3( SENDINFO_ARRAY3(m_iControlledPlayer), SendPropInt( SENDINFO_ARRAY(m_iControlledPlayer), 8, SPROP_UNSIGNED ) ),
SendPropArray3( SENDINFO_ARRAY3(m_iControlledByPlayer), SendPropInt( SENDINFO_ARRAY(m_iControlledByPlayer), 8, SPROP_UNSIGNED ) ),
#endif
SendPropArray3( SENDINFO_ARRAY3(m_iBotDifficulty), SendPropInt( SENDINFO_ARRAY(m_iBotDifficulty), 32) ),
SendPropArray3( SENDINFO_ARRAY3(m_szClan), SendPropStringT( SENDINFO_ARRAY(m_szClan) ) ),
SendPropArray3( SENDINFO_ARRAY3(m_iTotalCashSpent), SendPropInt( SENDINFO_ARRAY(m_iTotalCashSpent), 32) ),
SendPropArray3( SENDINFO_ARRAY3(m_iCashSpentThisRound), SendPropInt( SENDINFO_ARRAY(m_iCashSpentThisRound), 32) ),
SendPropArray3( SENDINFO_ARRAY3(m_nEndMatchNextMapVotes), SendPropInt( SENDINFO_ARRAY(m_nEndMatchNextMapVotes), 32) ),
SendPropBool( SENDINFO( m_bEndMatchNextMapAllVoted ) ),
SendPropArray3( SENDINFO_ARRAY3(m_nActiveCoinRank), SendPropInt( SENDINFO_ARRAY(m_nActiveCoinRank), 32) ),
SendPropArray3( SENDINFO_ARRAY3(m_nMusicID), SendPropInt( SENDINFO_ARRAY(m_nMusicID), 32) ),
// SendPropArray3( SENDINFO_ARRAY3(m_bIsAssassinationTarget), SendPropBool( SENDINFO_ARRAY(m_bIsAssassinationTarget), 32) ),
SendPropArray3( SENDINFO_ARRAY3(m_nPersonaDataPublicLevel), SendPropInt( SENDINFO_ARRAY(m_nPersonaDataPublicLevel), 32) ),
SendPropArray3( SENDINFO_ARRAY3(m_nPersonaDataPublicCommendsLeader), SendPropInt( SENDINFO_ARRAY(m_nPersonaDataPublicCommendsLeader), 32) ),
SendPropArray3( SENDINFO_ARRAY3(m_nPersonaDataPublicCommendsTeacher), SendPropInt( SENDINFO_ARRAY(m_nPersonaDataPublicCommendsTeacher), 32) ),
SendPropArray3( SENDINFO_ARRAY3(m_nPersonaDataPublicCommendsFriendly), SendPropInt( SENDINFO_ARRAY(m_nPersonaDataPublicCommendsFriendly), 32) ),
END_SEND_TABLE()
BEGIN_DATADESC( CCSPlayerResource )
// DEFINE_ARRAY( m_iPing, FIELD_INTEGER, MAX_PLAYERS+1 ),
// DEFINE_ARRAY( m_iPacketloss, FIELD_INTEGER, MAX_PLAYERS+1 ),
END_DATADESC()
LINK_ENTITY_TO_CLASS( cs_player_manager, CCSPlayerResource );
CCSPlayerResource::CCSPlayerResource( void )
{
m_bEndMatchNextMapAllVoted = false;
m_bPreferencesAssigned_T = false;
m_bPreferencesAssigned_CT = false;
memset( m_nAttemptedToGetColor, false, sizeof( m_nAttemptedToGetColor ) );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CCSPlayerResource::UpdatePlayerData( void )
{
int i;
m_iPlayerC4 = 0;
m_iPlayerVIP = 0;
int nTotalPlayingPlayers = 0;
for ( i = 1; i <= gpGlobals->maxClients; i++ )
{
int botDifficulty = -1;
CCSPlayer *pPlayer = (CCSPlayer*)UTIL_PlayerByIndex( i );
bool bSetValidRanking = false;
if ( pPlayer && pPlayer->IsConnected() )
{
if ( pPlayer->IsVIP() )
{
// we should only have one VIP
Assert( m_iPlayerVIP == 0 );
m_iPlayerVIP = i;
}
if ( pPlayer->HasC4() )
{
// we should only have one bomb
m_iPlayerC4 = i;
}
m_iMVPs.Set( i, pPlayer->GetNumMVPs() );
m_bHasDefuser.Set( i, pPlayer->HasDefuser() );
m_bHasHelmet.Set( i, pPlayer->m_bHasHelmet );
m_iArmor.Set( i, pPlayer->ArmorValue() );
m_iScore.Set( i, pPlayer->GetScore() );
m_iTotalCashSpent.Set( i, pPlayer->GetTotalCashSpent() );
m_iCashSpentThisRound.Set( i, pPlayer->GetCashSpentThisRound() );
m_szClan.Set(i, AllocPooledString( pPlayer->GetClanTag() ) );
m_nEndMatchNextMapVotes.Set( i, pPlayer->GetEndMatchNextMapVote() );
m_nActiveCoinRank.Set( i, pPlayer->GetRank( MEDAL_CATEGORY_SEASON_COIN ) );
m_nMusicID.Set( i, pPlayer->GetMusicID() );
// UpdateAssassinationTargets();
if ( CEconPersonaDataPublic const *pPublic = pPlayer->GetPersonaDataPublic() )
{
m_nPersonaDataPublicLevel.Set( i, pPublic->Obj().player_level() );
m_nPersonaDataPublicCommendsLeader.Set( i, pPublic->Obj().commendation().cmd_leader() );
m_nPersonaDataPublicCommendsTeacher.Set( i, pPublic->Obj().commendation().cmd_teaching() );
m_nPersonaDataPublicCommendsFriendly.Set( i, pPublic->Obj().commendation().cmd_friendly() );
}
else
{
m_nPersonaDataPublicLevel.Set( i, -1 );
m_nPersonaDataPublicCommendsLeader.Set( i, -1 );
m_nPersonaDataPublicCommendsTeacher.Set( i, -1 );
m_nPersonaDataPublicCommendsFriendly.Set( i, -1 );
}
if ( pPlayer->IsBot() )
{
CCSBot* pBot = dynamic_cast< CCSBot* >( pPlayer );
if ( pBot )
{
// Retrieve and store the bot's difficulty level
const BotProfile* pProfile = pBot->GetProfile();
if ( pProfile )
{
botDifficulty = pProfile->GetMaxDifficulty();
}
m_iCompTeammateColor.Set( i, -2 );
m_nAttemptedToGetColor[i] = true;
// SetPlayerTeammateColor( i, false );
}
}
else
{
if ( pPlayer->GetTeamNumber() != TEAM_SPECTATOR )
nTotalPlayingPlayers++;
SetPlayerTeammateColor( i, false );
}
if ( CSGameRules() && CSGameRules()->IsQueuedMatchmaking() )
{
for ( int k = 0; k < CCSGameRules::sm_QueuedServerReservation.rankings().size(); ++ k )
{
if ( CCSGameRules::sm_QueuedServerReservation.rankings( k ).account_id() == pPlayer->GetHumanPlayerAccountID() )
{
m_iCompetitiveRanking.Set( i, CCSGameRules::sm_QueuedServerReservation.rankings( k ).rank_id() );
m_iCompetitiveWins.Set( i, CCSGameRules::sm_QueuedServerReservation.rankings( k ).wins() );
bSetValidRanking = true;
break;
}
}
}
}
else
{
m_iMVPs.Set( i, 0 );
m_bHasDefuser.Set( i, false );
m_bHasHelmet.Set( i, false );
m_iArmor.Set( i, 0 );
m_szClan.Set( i, MAKE_STRING( "" ) );
m_nEndMatchNextMapVotes.Set( i, -1 );
m_nActiveCoinRank.Set( i, -1 );
m_nMusicID.Set( i, -1 );
m_bIsAssassinationTarget.Set( i, 0 );
m_nPersonaDataPublicLevel.Set( i, -1 );
m_nPersonaDataPublicCommendsLeader.Set( i, -1 );
m_nPersonaDataPublicCommendsTeacher.Set( i, -1 );
m_nPersonaDataPublicCommendsFriendly.Set( i, -1 );
m_iCompTeammateColor.Set( i, -1 );
m_nAttemptedToGetColor[i] = false;
}
if ( !bSetValidRanking )
{
m_iCompetitiveRanking.Set( i, 0 );
m_iCompetitiveWins.Set( i, 0 );
}
m_iBotDifficulty.Set( i, botDifficulty );
}
if ( g_fGameOver && nTotalPlayingPlayers > 0 && CSGameRules() && CSGameRules()->IsEndMatchVotingForNextMap() )
{
if ( m_bEndMatchNextMapAllVoted == false )
{
// ignore whether all players voted and just return whether or not we ran out of time
if ( CSGameRules()->m_eEndMatchMapVoteState == CSGameRules()->k_EEndMatchMapVoteState_VoteTimeEnded ||
CSGameRules()->m_eEndMatchMapVoteState == CSGameRules()->k_EEndMatchMapVoteState_AllPlayersVoted ||
CSGameRules()->m_eEndMatchMapVoteState == CSGameRules()->k_EEndMatchMapVoteState_SelectingWinner ||
CSGameRules()->m_eEndMatchMapVoteState == CSGameRules()->k_EEndMatchMapVoteState_SettingNextLevel ||
CSGameRules()->m_eEndMatchMapVoteState == CSGameRules()->k_EEndMatchMapVoteState_VoteAllDone )
{
m_bEndMatchNextMapAllVoted = true;
}
/* m_bEndMatchNextMapAllVoted = (CSGameRules()->m_eEndMatchMapVoteState == CSGameRules()->k_EEndMatchMapVoteState_VoteTimeEnded);*/
// int nNumVotes = 0;
// int nHighestSingleVoteSlot = 0;
//
// // this is done in three different places
// // TODO: make a function out of this
// int nVotes[MAX_ENDMATCH_VOTE_PANELS];
// for ( int i = 0; i < MAX_ENDMATCH_VOTE_PANELS; i++ )
// nVotes[i] = 0;
//
// for ( int i = 1; i <= MAX_PLAYERS; i++ )
// {
// int nMapSlot = m_nEndMatchNextMapVotes[i];
// if ( nMapSlot != -1 && nMapSlot < MAX_ENDMATCH_VOTE_PANELS )
// {
// nVotes[nMapSlot] += 1;
// nNumVotes += 1;
// }
// }
// //////
//
// for ( int i = 0; i < MAX_ENDMATCH_VOTE_PANELS + 1; i++ )
// {
// if ( nVotes[nHighestSingleVoteSlot] < nVotes[i] )
// nHighestSingleVoteSlot = i;
// }
//
// if ( nTotalPlayingPlayers > 0 )
// {
// float flnVotesToSucceed = (float)nTotalPlayingPlayers * 0.501f;
// int nVotesToSucceed = ceil( flnVotesToSucceed );
//
// if ( nNumVotes >= nTotalPlayingPlayers || nVotes[nHighestSingleVoteSlot] >= nVotesToSucceed || ( nextlevel.GetString() && *nextlevel.GetString() && engine->IsMapValid( nextlevel.GetString() ) ) )
// m_bEndMatchNextMapAllVoted = true;
// }
}
}
else
{
// if we aren't at the end of the game or we don't have any players, no one has voted
m_bEndMatchNextMapAllVoted = false;
}
int numHostages = g_Hostages.Count();
for ( i = 0; i < MAX_HOSTAGES; i++ )
{
if ( i >= numHostages )
{
// engine->Con_NPrintf( i, "Dead" );
m_bHostageAlive.Set( i, false );
m_isHostageFollowingSomeone.Set( i, false );
m_iHostageEntityIDs.Set( i, 0 );
continue;
}
CHostage* pHostage = g_Hostages[i];
m_bHostageAlive.Set( i, pHostage->IsRescuable() );
if ( pHostage->IsValid() )
{
m_iHostageEntityIDs.Set( i, pHostage->entindex() );
m_isHostageFollowingSomeone.Set( i, pHostage->IsFollowingSomeone() );
// engine->Con_NPrintf( i, "ID:%d Pos:(%.0f,%.0f,%.0f)", pHostage->entindex(), pHostage->GetAbsOrigin().x, pHostage->GetAbsOrigin().y, pHostage->GetAbsOrigin().z );
}
else
{
// engine->Con_NPrintf( i, "Invalid" );
}
}
if( !m_foundGoalPositions )
{
// We only need to update these once a map, but we need the client to know about them.
CBaseEntity* ent = NULL;
while ( ( ent = gEntList.FindEntityByClassname( ent, "func_bomb_target" ) ) != NULL )
{
const Vector &pos = ent->WorldSpaceCenter();
CNavArea *area = TheNavMesh->GetNearestNavArea( pos, true, 10000.0f, false, false );
const char *placeName = (area) ? TheNavMesh->PlaceToName( area->GetPlace() ) : NULL;
if ( placeName == NULL )
{
// The bomb site has no area or place name, so just choose A then B
if ( m_bombsiteCenterA.Get().IsZero() )
{
m_bombsiteCenterA = pos;
}
else
{
m_bombsiteCenterB = pos;
}
}
else
{
// The bomb site has a place name, so choose accordingly
if( FStrEq( placeName, "BombsiteA" ) || FStrEq( placeName, "Bombsite" ) )
{
m_bombsiteCenterA = pos;
}
else
{
m_bombsiteCenterB = pos;
}
}
m_foundGoalPositions = true;
}
int hostageRescue = 0;
while ( (( ent = gEntList.FindEntityByClassname( ent, "func_hostage_rescue" ) ) != NULL) && (hostageRescue < MAX_HOSTAGE_RESCUES) )
{
const Vector &pos = ent->WorldSpaceCenter();
m_hostageRescueX.Set( hostageRescue, (int) pos.x );
m_hostageRescueY.Set( hostageRescue, (int) pos.y );
m_hostageRescueZ.Set( hostageRescue, (int) pos.z );
hostageRescue++;
m_foundGoalPositions = true;
}
}
#if CS_CONTROLLABLE_BOTS_ENABLED
for ( int i=0; i < MAX_PLAYERS+1; i++ )
{
CCSPlayer *pPlayer = ToCSPlayer( UTIL_PlayerByIndex( i ) );
bool bControllingBot = false;
CCSPlayer *pControlledPlayer = NULL;
CCSPlayer *pControlledByPlayer = NULL;
if ( pPlayer && pPlayer->IsConnected() )
{
bControllingBot = pPlayer->IsControllingBot();
pControlledPlayer = pPlayer->GetControlledBot();
pControlledByPlayer = pPlayer->GetControlledByPlayer();
}
m_bControllingBot.Set( i, bControllingBot ? 1 : 0 );
m_iControlledPlayer.Set( i, pControlledPlayer ? pControlledPlayer->entindex() : 0 );
m_iControlledByPlayer.Set( i, pControlledByPlayer ? pControlledByPlayer->entindex() : 0 );
}
#endif
BaseClass::UpdatePlayerData();
}
void CCSPlayerResource::Spawn( void )
{
m_iPlayerC4 = 0;
m_iPlayerVIP = 0;
m_bombsiteCenterA.Init();
m_bombsiteCenterB.Init();
m_foundGoalPositions = false;
m_bPreferencesAssigned_CT = false;
m_bPreferencesAssigned_T = false;
memset( m_nAttemptedToGetColor, false, sizeof( m_nAttemptedToGetColor ) );
for ( int i=0; i < MAX_HOSTAGES; i++ )
{
m_bHostageAlive.Set( i, 0 );
m_isHostageFollowingSomeone.Set( i, 0 );
m_iHostageEntityIDs.Set(i, 0);
}
for ( int i=0; i < MAX_HOSTAGE_RESCUES; i++ )
{
m_hostageRescueX.Set( i, 0 );
m_hostageRescueY.Set( i, 0 );
m_hostageRescueZ.Set( i, 0 );
}
for ( int i=0; i < MAX_PLAYERS+1; i++ )
{
m_iMVPs.Set( i, 0 );
m_bHasDefuser.Set( i, false );
m_bHasHelmet.Set( i, false );
m_iArmor.Set( i, 0 );
m_iScore.Set( i, 0 );
m_iCompetitiveRanking.Set( i, 0 );
m_iCompetitiveWins.Set( i, 0 );
m_iCompTeammateColor.Set( i, -1 );
m_iBotDifficulty.Set( i, -1 );
m_szClan.Set( i, MAKE_STRING( "" ) );
m_nActiveCoinRank.Set( i, -1 );
m_nMusicID.Set( i, -1 );
m_bIsAssassinationTarget.Set( i, 0 );
m_nPersonaDataPublicLevel.Set( i, -1 );
m_nPersonaDataPublicCommendsLeader.Set( i, -1 );
m_nPersonaDataPublicCommendsTeacher.Set( i, -1 );
m_nPersonaDataPublicCommendsFriendly.Set( i, -1 );
}
m_bEndMatchNextMapAllVoted = false;
for ( int i=0; i < MAX_ENDMATCH_VOTE_PANELS+1; i++ )
{
m_nEndMatchNextMapVotes.Set( i, 0 );
}
BaseClass::Spawn();
}
const Vector CCSPlayerResource::GetBombsiteAPosition()
{
return m_bombsiteCenterA;
}
const Vector CCSPlayerResource::GetBombsiteBPosition()
{
return m_bombsiteCenterB;
}
const Vector CCSPlayerResource::GetHostageRescuePosition( int iIndex )
{
if ( iIndex < 0 || iIndex >= MAX_HOSTAGE_RESCUES )
return vec3_origin;
Vector ret;
ret.x = m_hostageRescueX[iIndex];
ret.y = m_hostageRescueY[iIndex];
ret.z = m_hostageRescueZ[iIndex];
return ret;
}
//--------------------------------------------------------------------------------------------------------
int CCSPlayerResource::GetCompTeammateColor( int iIndex )
{
CCSPlayer *pPlayer = ( CCSPlayer* )UTIL_PlayerByIndex( iIndex );
if ( !pPlayer )
return -1;
if ( pPlayer->IsBot() )
return -2;
return m_iCompTeammateColor[iIndex];
}
void CCSPlayerResource::ResetPlayerTeammateColor( int index )
{
CCSPlayer *pPlayer = ( CCSPlayer* )UTIL_PlayerByIndex( index );
if ( !pPlayer )
return;
if ( CSGameRules() && CSGameRules()->IsPlayingAnyCompetitiveStrictRuleset() && CSGameRules()->IsQueuedMatchmaking() )
return;
int nTeamNum = pPlayer->GetTeamNumber();
if ( nTeamNum > TEAM_SPECTATOR )
{
SetPlayerTeammateColor( index, true );
return;
}
m_iCompTeammateColor.Set( index, -1 );
}
void CCSPlayerResource::ForcePlayersPickColors()
{
m_bPreferencesAssigned_CT = true;
m_bPreferencesAssigned_T = true;
for ( int i = 1; i <= gpGlobals->maxClients; i++ )
m_nAttemptedToGetColor[i] = true;
}
void CCSPlayerResource::SetPlayerTeammateColor( int index, bool bReset )
{
CCSPlayer *pPlayer = ( CCSPlayer* )UTIL_PlayerByIndex( index );
if ( !pPlayer )
return;
m_nAttemptedToGetColor[index] = true;
if ( !CSGameRules() || !CSGameRules()->IsPlayingAnyCompetitiveStrictRuleset() )
{
m_iCompTeammateColor.Set( index, -1 );
return;
}
if ( pPlayer->IsBot() )
{
m_iCompTeammateColor.Set( index, -2 );
return;
}
int nTeamNum = pPlayer->GetTeamNumber();
if ( nTeamNum > TEAM_SPECTATOR )
{
if ( CSGameRules() && CSGameRules()->IsPlayingAnyCompetitiveStrictRuleset() )
{
// check to see if we have a color already
int idxThisPlayer = -1;
// don't use the QMM code
/*
if ( CSGameRules()->IsQueuedMatchmaking() )
{
CCSPlayer *pThisPlayer = ( CCSPlayer* )UTIL_PlayerByIndex( index );
CSteamID steamID;
pThisPlayer->GetSteamID( &steamID );
int numTotalPlayers = 0;
static ConVarRef sv_mmqueue_reservation( "sv_mmqueue_reservation" );
for ( char const *pszPrev = sv_mmqueue_reservation.GetString(), *pszNext = pszPrev;
( pszNext = strchr( pszPrev, '[' ) ) != NULL; pszPrev = pszNext + 1 )
{
uint32 uiAccountId = 0;
sscanf( pszNext, "[%x]", &uiAccountId );
if ( uiAccountId && ( steamID.GetAccountID() == uiAccountId ) )
{
idxThisPlayer = numTotalPlayers;
}
++numTotalPlayers;
}
}
*/
// let all players have at least one crack at getting their prefered color before we start assigning loser colors
if ( (nTeamNum == TEAM_TERRORIST && m_bPreferencesAssigned_T == false) ||
(nTeamNum == TEAM_CT && m_bPreferencesAssigned_CT == false) )
{
int nNumAttemptedToGetColor = 0;
for ( int i = 1; i <= gpGlobals->maxClients; i++ )
{
CCSPlayer *pOtherPlayer = ( CCSPlayer* )UTIL_PlayerByIndex( i );
if ( pOtherPlayer && pOtherPlayer->GetTeamNumber() == pPlayer->GetTeamNumber() )
{
if ( m_nAttemptedToGetColor[i] == true )
nNumAttemptedToGetColor++;
if ( nNumAttemptedToGetColor >= 5 )
{
if ( nTeamNum == TEAM_TERRORIST )
m_bPreferencesAssigned_T = true;
else
m_bPreferencesAssigned_CT = true;
break;
}
}
}
}
// Valve MM gives us the player index - does this work?
if ( idxThisPlayer > -1 )
{
m_iCompTeammateColor.Set( index, ( idxThisPlayer % 5 ) );
}
else if ( m_iCompTeammateColor[index] == -1 || bReset )//otherwise we have to do it ourselves
{
int nPreferredColor = pPlayer->GetTeammatePreferredColor( );
if ( nPreferredColor == -1 )
{
pPlayer->InitTeammatePreferredColor( );
nPreferredColor = pPlayer->GetTeammatePreferredColor( );
}
// we didn't initialize, so try again another time
if ( nPreferredColor == -1 )
return;
int nAssignedColor = m_iCompTeammateColor[index] > -1 ? m_iCompTeammateColor[index] : nPreferredColor;
bool bColorInUse = false;
for ( int ii = 0; ii < 5; ii++ )
{
nAssignedColor = nAssignedColor % 5;
bColorInUse = false;
for ( int j = 1; j <= gpGlobals->maxClients; j++ )
{
CCSPlayer *pOtherPlayer = ( CCSPlayer* )UTIL_PlayerByIndex( j );
if ( pOtherPlayer && pOtherPlayer->GetTeamNumber( ) == pPlayer->GetTeamNumber( ) )
{
if ( nAssignedColor == m_iCompTeammateColor[j] && pOtherPlayer != pPlayer )
{
// All players should get a crack at getting their prefered color before a
// previously connected player crawls up the color scale and nabs it first
if ( ( nTeamNum == TEAM_TERRORIST && m_bPreferencesAssigned_T == false) ||
( nTeamNum == TEAM_CT && m_bPreferencesAssigned_CT == false ) )
return;
bColorInUse = true;
nAssignedColor++;
break;
}
}
}
if ( bColorInUse == false )
break;
}
// somehow this failed
AssertMsg( !bColorInUse, "Trying to assign a color to a teammate, but all colors are already in use!" );
nAssignedColor = bColorInUse == false ? nAssignedColor : -1;
m_iCompTeammateColor.Set( index, nAssignedColor );
}
}
else
m_iCompTeammateColor.Set( index, -1 );
}
}
bool CCSPlayerResource::IsAssassinationTarget( int index ) const
{
return m_bIsAssassinationTarget[ index ];
}
bool Helper_DoesPlayerHaveAssassinateQuestForTeam( const CCSPlayer *pPlayer, int iTeamNum )
{
// If this player has an assassination quest targeting this team, prefer not to pick them as the target
CEconQuestDefinition *pQuest = GetItemSchema()->GetQuestDefinition( pPlayer->Inventory()->GetActiveQuestID() );
return ( pQuest && IsAssassinationQuest( pQuest ) && ( ( int ) pQuest->GetTargetTeam() == iTeamNum ) );
}
bool Helper_ValidateAssassinationTarget( const CCSPlayer *pCurrentAssassinationTarget, int iTeamNum )
{
// Validate current assassination target, pick new one if needed
if ( !pCurrentAssassinationTarget || !pCurrentAssassinationTarget->IsConnected() ||
pCurrentAssassinationTarget->GetTeamNumber() != iTeamNum || pCurrentAssassinationTarget->IsDead() ||
pCurrentAssassinationTarget->IsControllingBot() || Helper_DoesPlayerHaveAssassinateQuestForTeam( pCurrentAssassinationTarget, iTeamNum ) )
{
return false;
}
return true;
}
ConVar sv_assassination_target_ratio( "sv_assassination_target_ratio", "5" );
void CCSPlayerResource::UpdateAssassinationTargets( const CEconQuestDefinition * pQuest )
{
CCSTeam *pTeam = GetGlobalCSTeam( pQuest->GetTargetTeam() );
if ( !pTeam )
return;
// 1 out of X players is an assassination target, no less than 1 and more more than MAX_ASSASSINATION_TARGETS.
CUtlVector<CCSPlayer*> vecCandiates;
auto iTargetsNeeded = Min( Max( 1, pTeam->GetHumanMembers( &vecCandiates ) / Max( 1, sv_assassination_target_ratio.GetInt() ) ), 3 );
CUtlVector< CCSPlayer* > vecNotIdealPlayers;
FOR_EACH_VEC_BACK( vecCandiates, iter )
{
CCSPlayer* pCur = vecCandiates[ iter ];
// Validate current assassination targets, remove from candidate list
if ( pCur->IsAssassinationTarget() )
{
// Still valid, then reduce count of needed targets
if ( Helper_ValidateAssassinationTarget( pCur, pQuest->GetTargetTeam() ) )
{
iTargetsNeeded--;
}
else
{
// Prefer not to pick recently invalidated players
m_bIsAssassinationTarget.GetForModify( pCur->entindex() ) = false;
vecNotIdealPlayers.AddToHead( pCur );
}
vecCandiates.Remove( iter );
}
else if ( Helper_DoesPlayerHaveAssassinateQuestForTeam( pCur, GetTeamNumber() ) )
{
// Prefer not to pick players with this quest
vecCandiates.Remove( iter );
vecNotIdealPlayers.AddToTail( pCur );
}
}
while ( iTargetsNeeded-- > 0 )
{
CUtlVector< CCSPlayer* > &vecBucket = vecCandiates.Count() > 0 ? vecCandiates : vecNotIdealPlayers;
CCSPlayer *pTarget = vecBucket[ RandomInt( 0, vecBucket.Count() - 1 ) ];
vecBucket.FindAndFastRemove( pTarget );
m_bIsAssassinationTarget.GetForModify( pTarget->entindex() ) = true;
}
}
+104
View File
@@ -0,0 +1,104 @@
//========= Copyright 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose: CS's custom CPlayerResource
//
// $NoKeywords: $
//=============================================================================//
#ifndef CS_PLAYER_RESOURCE_H
#define CS_PLAYER_RESOURCE_H
#ifdef _WIN32
#pragma once
#endif
#include "player_resource.h"
extern Vector g_vecDefuserPosition;
extern CBaseEntity* g_pDefuserEntity;
class CCSPlayerResource : public CPlayerResource
{
DECLARE_CLASS( CCSPlayerResource, CPlayerResource );
public:
DECLARE_SERVERCLASS();
DECLARE_DATADESC();
CCSPlayerResource();
virtual void UpdatePlayerData( void );
virtual void Spawn( void );
const Vector GetBombsiteAPosition();
const Vector GetBombsiteBPosition();
const Vector GetHostageRescuePosition( int index );
bool EndMatchNextMapAllVoted( void ) { return m_bEndMatchNextMapAllVoted; }
int GetCompTeammateColor( int iIndex );
void ResetPlayerTeammateColor( int index );
void ForcePlayersPickColors( void );
void SetPlayerTeammateColor( int index, bool bReset );
bool IsAssassinationTarget( int index ) const;
void UpdateAssassinationTargets( const CEconQuestDefinition * pQuest );
protected:
CNetworkVar( int, m_iPlayerC4 ); // entity index of C4 carrier or 0
CNetworkVar( int, m_iPlayerVIP ); // entity index of VIP player or 0
CNetworkArray( bool, m_bHostageAlive, MAX_HOSTAGES );
CNetworkArray( bool, m_isHostageFollowingSomeone, MAX_HOSTAGES );
CNetworkArray( int, m_iHostageEntityIDs, MAX_HOSTAGES );
CNetworkVector( m_bombsiteCenterA );// Location of bombsite A
CNetworkVector( m_bombsiteCenterB );// Location of bombsite B
CNetworkArray( int, m_hostageRescueX, MAX_HOSTAGE_RESCUES );// Locations of all hostage rescue spots
CNetworkArray( int, m_hostageRescueY, MAX_HOSTAGE_RESCUES );
CNetworkArray( int, m_hostageRescueZ, MAX_HOSTAGE_RESCUES );
CNetworkArray( int, m_iMVPs, MAX_PLAYERS + 1 );
CNetworkArray( int, m_iArmor, MAX_PLAYERS + 1 );
CNetworkArray( bool, m_bHasDefuser, MAX_PLAYERS + 1);
CNetworkArray( bool, m_bHasHelmet, MAX_PLAYERS + 1);
CNetworkArray( int, m_iScore, MAX_PLAYERS + 1 );
CNetworkArray( int, m_iCompetitiveRanking, MAX_PLAYERS + 1 );
CNetworkArray( int, m_iCompetitiveWins, MAX_PLAYERS + 1 );
CNetworkArray( int, m_iCompTeammateColor, MAX_PLAYERS + 1 );
#if CS_CONTROLLABLE_BOTS_ENABLED
CNetworkArray( int, m_bControllingBot, MAX_PLAYERS+1 );
CNetworkArray( int, m_iControlledPlayer, MAX_PLAYERS+1 );
CNetworkArray( int, m_iControlledByPlayer, MAX_PLAYERS+1 );
#endif
CNetworkArray( int, m_iBotDifficulty, MAX_PLAYERS+1 ); // Difficulty level of a bot ( -1 if not applicable )
CNetworkArray( string_t, m_szClan, MAX_PLAYERS+1 );
CNetworkArray( int, m_iTotalCashSpent, MAX_PLAYERS + 1 );
CNetworkArray( int, m_iCashSpentThisRound, MAX_PLAYERS + 1 );
CNetworkArray( int, m_nEndMatchNextMapVotes, MAX_PLAYERS + 1 );
CNetworkVar( bool, m_bEndMatchNextMapAllVoted );
CNetworkArray( int, m_nActiveCoinRank, MAX_PLAYERS + 1 );
CNetworkArray( int, m_nMusicID, MAX_PLAYERS + 1 );
CNetworkArray( bool, m_bIsAssassinationTarget, MAX_PLAYERS + 1 );
CNetworkArray( int, m_nPersonaDataPublicLevel, MAX_PLAYERS + 1 );
CNetworkArray( int, m_nPersonaDataPublicCommendsLeader, MAX_PLAYERS + 1 );
CNetworkArray( int, m_nPersonaDataPublicCommendsTeacher, MAX_PLAYERS + 1 );
CNetworkArray( int, m_nPersonaDataPublicCommendsFriendly, MAX_PLAYERS + 1 );
bool m_nAttemptedToGetColor[MAX_PLAYERS + 1];
private:
bool m_foundGoalPositions;
bool m_bPreferencesAssigned_CT;
bool m_bPreferencesAssigned_T;
};
inline CCSPlayerResource* CSPlayerResource()
{
return static_cast<CCSPlayerResource*>(g_pPlayerResource);
}
#endif // CS_PLAYER_RESOURCE_H
+94
View File
@@ -0,0 +1,94 @@
//========= Copyright © 1996-2005, 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 "iservervehicle.h"
#include "cs_player.h"
// NOTE: This has to be the last file included!
#include "tier0/memdbgon.h"
static CMoveData g_MoveData;
CMoveData *g_pMoveData = &g_MoveData;
IPredictionSystem *IPredictionSystem::g_pPredictionSystems = NULL;
//-----------------------------------------------------------------------------
// Sets up the move data for TF2
//-----------------------------------------------------------------------------
class CCSPlayerMove : public CPlayerMove
{
DECLARE_CLASS( CCSPlayerMove, CPlayerMove );
public:
virtual void SetupMove( CBasePlayer *player, CUserCmd *ucmd, IMoveHelper *pHelper, CMoveData *move );
virtual void FinishMove( CBasePlayer *player, CUserCmd *ucmd, CMoveData *move );
};
// PlayerMove Interface
static CCSPlayerMove g_PlayerMove;
//-----------------------------------------------------------------------------
// Singleton accessor
//-----------------------------------------------------------------------------
CPlayerMove *PlayerMove()
{
return &g_PlayerMove;
}
//-----------------------------------------------------------------------------
// 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 CCSPlayerMove::SetupMove( CBasePlayer *player, CUserCmd *ucmd, IMoveHelper *pHelper, CMoveData *move )
{
player->AvoidPhysicsProps( ucmd );
BaseClass::SetupMove( player, ucmd, pHelper, move );
IServerVehicle *pVehicle = player->GetVehicle();
if (pVehicle && gpGlobals->frametime != 0)
{
pVehicle->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 CCSPlayerMove::FinishMove( CBasePlayer *player, CUserCmd *ucmd, CMoveData *move )
{
// Call the default FinishMove code.
BaseClass::FinishMove( player, ucmd, move );
IServerVehicle *pVehicle = player->GetVehicle();
if (pVehicle && gpGlobals->frametime != 0)
{
pVehicle->FinishMove( player, ucmd, move );
}
CCSPlayer *pPlayer = ToCSPlayer( player );
// Reset these... they get reset each frame.
pPlayer->m_bInBombZone = false;
pPlayer->m_bInBombZoneTrigger = false;
pPlayer->m_bInBuyZone = false;
pPlayer->m_bInHostageRescueZone = false;
pPlayer->m_bInNoDefuseArea = false;
}
+495
View File
@@ -0,0 +1,495 @@
//========= Copyright Valve Corporation,k All rights reserved. ============//
//
//
// Note: This code integrated then adapted from TF:
// //ValveGames/staging/src/game/server/tf/tf_pushentity.cpp
//
// $NoKeywords: $
//=============================================================================//
#include "cbase.h"
#include "pushentity.h"
#include "cs_player.h"
#include "collisionutils.h"
#include "cs_gamerules.h"
//#include "mathlib/mathlib.h"
class CCSPhysicsPushEntities : public CPhysicsPushedEntities
{
public:
DECLARE_CLASS( CCSPhysicsPushEntities, CPhysicsPushedEntities );
// Constructor/Destructor.
CCSPhysicsPushEntities();
~CCSPhysicsPushEntities();
protected:
// Speculatively checks to see if all entities in this list can be pushed
virtual bool SpeculativelyCheckRotPush( const RotatingPushMove_t &rotPushMove, CBaseEntity *pRoot ) OVERRIDE;
virtual bool SpeculativelyCheckLinearPush( const Vector &vecAbsPush ) OVERRIDE;
virtual void FinishRotPushedEntity( CBaseEntity *pPushedEntity, const RotatingPushMove_t &rotPushMove ) OVERRIDE;
private:
bool RotationPushCSPlayer( PhysicsPushedInfo_t &info, const Vector &vecAbsPush, const RotatingPushMove_t &rotPushMove, bool bRotationalPush, CBaseEntity *pRoot );
bool RotationCheckPush( PhysicsPushedInfo_t &info, bool bIgnoreTeammates );
bool LinearPushCSPlayer( PhysicsPushedInfo_t &info, const Vector &vecAbsPush, bool bRotationalPush );
bool LinearCheckPush( PhysicsPushedInfo_t &info, bool bIgnoreTeammates );
void EnsureValidPushWhileRiding( CBaseEntity *pBlocker, CBaseEntity *pPusher );
bool IsPlayerAABBIntersetingPusherOBB( CBaseEntity *pEntity, CBaseEntity *pRootEntity );
void MovePlayer( CBaseEntity *pBlocker, PhysicsPushedInfo_t &info, float flMoveScale, bool bPusherIsTrain, bool bIgnoreTeammates );
void FindNewPushDirection( Vector &vecCurrent, Vector &vecNormal, Vector &vecOutput );
float m_flPushDist;
Vector m_vecPushVector;
};
CCSPhysicsPushEntities s_CSPushedEntities;
CPhysicsPushedEntities *g_pPushedEntities = &s_CSPushedEntities;
//-----------------------------------------------------------------------------
// Purpose: Constructor.
//-----------------------------------------------------------------------------
CCSPhysicsPushEntities::CCSPhysicsPushEntities()
{
}
//-----------------------------------------------------------------------------
// Purpose: Destructor.
//-----------------------------------------------------------------------------
CCSPhysicsPushEntities::~CCSPhysicsPushEntities()
{
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool CCSPhysicsPushEntities::SpeculativelyCheckRotPush( const RotatingPushMove_t &rotPushMove, CBaseEntity *pRoot )
{
TM_ZONE_DEFAULT( TELEMETRY_LEVEL0 );
Vector vecAbsPush( 0.0f, 0.0f, 0.0f );
m_nBlocker = -1;
int nMovedCount = m_rgMoved.Count();
for ( int i = ( nMovedCount - 1 ); i >= 0; --i )
{
// Is the entity and CS Player?
CCSPlayer *pCSPlayer = NULL;
bool bPusherIsTrain = false;
if ( m_rgMoved[i].m_pEntity && m_rgMoved[i].m_pEntity->IsPlayer() )
{
pCSPlayer = ToCSPlayer( m_rgMoved[i].m_pEntity );
CBaseEntity* pPusher = m_rgPusher[ 0 ].m_pEntity->GetRootMoveParent();
bPusherIsTrain = pPusher && pPusher->IsBaseTrain();
}
// Special code to move the player away from the func_train.
// Only do this if it's a train pushing a player--otherwise use base class.
if ( pCSPlayer && bPusherIsTrain )
{
// Rotationally push the player!
ComputeRotationalPushDirection( m_rgMoved[ i ].m_pEntity, rotPushMove, &vecAbsPush, pRoot );
RotationPushCSPlayer( m_rgMoved[i], vecAbsPush, rotPushMove, true, pRoot );
}
else
{
// Keep this in sync with BaseClass::SpeculativelyCheckRotPush
ComputeRotationalPushDirection( m_rgMoved[i].m_pEntity, rotPushMove, &vecAbsPush, pRoot );
if ( !SpeculativelyCheckPush( m_rgMoved[i], vecAbsPush, true, pRoot ) )
{
m_nBlocker = i;
return false;
}
}
}
return true;
}
//-----------------------------------------------------------------------------
// Speculatively checks to see if all entities in this list can be pushed
//-----------------------------------------------------------------------------
bool CCSPhysicsPushEntities::SpeculativelyCheckLinearPush( const Vector &vecAbsPush )
{
TM_ZONE_DEFAULT( TELEMETRY_LEVEL0 );
m_nBlocker = -1;
int nMovedCount = m_rgMoved.Count();
for ( int i = ( nMovedCount - 1 ); i >= 0; --i )
{
// Is the entity and CS Player?
CCSPlayer *pCSPlayer = NULL;
bool bPusherIsTrain = false;
if ( m_rgMoved[i].m_pEntity && m_rgMoved[i].m_pEntity->IsPlayer() )
{
pCSPlayer = ToCSPlayer( m_rgMoved[i].m_pEntity );
CBaseEntity* pPusher = m_rgPusher[0].m_pEntity->GetRootMoveParent();
bPusherIsTrain = pPusher && pPusher->IsBaseTrain();
}
// Special code to move the player away from the func_train.
// Only do this if it's a train pushing a player--otherwise use base class.
if ( pCSPlayer && bPusherIsTrain )
{
// Linearly push the player!
LinearPushCSPlayer( m_rgMoved[i], vecAbsPush, false );
}
else
{
// Keep this in sync with BaseClass::SpeculativelyCheckLinearPush
if ( !SpeculativelyCheckPush( m_rgMoved[i], vecAbsPush, false, NULL ) )
{
m_nBlocker = i;
return false;
}
}
}
return true;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool CCSPhysicsPushEntities::RotationPushCSPlayer( PhysicsPushedInfo_t &info, const Vector &vecAbsPush, const RotatingPushMove_t &rotPushMove, bool bRotationalPush, CBaseEntity* pRoot )
{
const bool cbIgnoreTeammates = !CSGameRules() || !CSGameRules()->IsTeammateSolid();
Assert( cbIgnoreTeammates ); // This code doesn't behave if teammates are solid.
// Clear out the collision entity so that if we early out we don't send bogus collision data to the physics system.
info.m_Trace.m_pEnt = NULL;
// Look into doing a full engine->CM_Clear( trace)
// Get the player.
CCSPlayer *pPlayer = ToCSPlayer( info.m_pEntity );
if ( !pPlayer )
return false;
info.m_vecStartAbsOrigin = pPlayer->GetAbsOrigin();
// Get the player collision data.
CCollisionProperty *pCollisionPlayer = info.m_pEntity->CollisionProp();
if ( !pCollisionPlayer )
return false;
// Find the root object if in hierarchy.
CBaseEntity *pRootEntity = m_rgPusher[0].m_pEntity->GetRootMoveParent();
if ( !pRootEntity )
return false;
// This code doesn't at all match the code in AvoidPushawayProps, which is a bummer. It'd be
// great if this just got rolled into that. Unfortunately, doing so would also require
// making trains predictive--they are not currently.
if ( !pPlayer->GetGroundEntity() || pPlayer->GetGroundEntity()->GetRootMoveParent() != pRootEntity )
{
Vector vMinPushAway = vecAbsPush;
m_flPushDist = VectorNormalize( vMinPushAway );
m_vecPushVector = vMinPushAway;
Assert( !m_vecPushVector.IsZero() ); // Is our push vector legit?
}
else
{
SpeculativelyCheckPush( info, vecAbsPush, true, pRoot, cbIgnoreTeammates );
EnsureValidPushWhileRiding( pPlayer, pRootEntity );
}
return RotationCheckPush( info, cbIgnoreTeammates );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool CCSPhysicsPushEntities::RotationCheckPush( PhysicsPushedInfo_t &info, bool bIgnoreTeammates )
{
// Get the blocking and pushing entities.
CBaseEntity *pBlocker = info.m_pEntity;
CBaseEntity *pRootEntity = m_rgPusher[0].m_pEntity->GetRootMoveParent();
if ( !pBlocker || !pRootEntity )
return true;
int *pPusherHandles = ( int* )stackalloc( m_rgPusher.Count() * sizeof( int ) );
UnlinkPusherList( pPusherHandles );
for ( int iPushTry = 0; iPushTry < 3; ++iPushTry )
{
MovePlayer( pBlocker, info, 0.35f, pRootEntity->IsBaseTrain(), bIgnoreTeammates );
if ( IsPushedPositionValid( pBlocker, bIgnoreTeammates ) )
break;
}
RelinkPusherList( pPusherHandles );
// Is the blocked ground the push entity?
info.m_bPusherIsGround = false;
if ( pBlocker->GetGroundEntity() && pBlocker->GetGroundEntity()->GetRootMoveParent() == m_rgPusher[0].m_pEntity )
{
info.m_bPusherIsGround = true;
}
// Check to see if the player is in a good spot and attempt a move again if not - but only if it isn't being ridden on.
if ( !IsPushedPositionValid( pBlocker, bIgnoreTeammates ) )
{
// Try again is the player is still blocked.
DevMsg( 2, "Pushing rotation hard!\n" );
UnlinkPusherList( pPusherHandles );
MovePlayer( pBlocker, info, 1.0f, pRootEntity->IsBaseTrain(), bIgnoreTeammates );
RelinkPusherList( pPusherHandles );
}
// The player will never stop a train from moving in CS.
info.m_bBlocked = false;
return true;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool CCSPhysicsPushEntities::LinearPushCSPlayer( PhysicsPushedInfo_t &info, const Vector &vecAbsPush, bool bRotationalPush )
{
const bool cbIgnoreTeammates = !CSGameRules() || !CSGameRules()->IsTeammateSolid();
Assert( cbIgnoreTeammates ); // This code doesn't behave if teammates are solid.
// Clear out the collision entity so that if we early out we don't send bogus collision data to the physics system.
info.m_Trace.m_pEnt = NULL;
// Get the player.
CCSPlayer *pPlayer = ToCSPlayer( info.m_pEntity );
if ( !pPlayer )
return false;
info.m_vecStartAbsOrigin = pPlayer->GetAbsOrigin();
// Get the player collision data.
CCollisionProperty *pCollisionPlayer = info.m_pEntity->CollisionProp();
if ( !pCollisionPlayer )
return false;
// Find the root object if in hierarchy.
CBaseEntity *pRootEntity = m_rgPusher[0].m_pEntity->GetRootMoveParent();
if ( !pRootEntity )
return false;
// Get the pusher collision data.
CCollisionProperty *pCollisionPusher = pRootEntity->CollisionProp();
if ( !pCollisionPusher )
return false;
if ( !pPlayer->GetGroundEntity() || pPlayer->GetGroundEntity()->GetRootMoveParent() != pRootEntity )
{
m_vecPushVector = vecAbsPush;
m_flPushDist = VectorNormalize( m_vecPushVector );
}
else
{
// Try to get the base class first.
SpeculativelyCheckPush( info, vecAbsPush, false, NULL, cbIgnoreTeammates );
EnsureValidPushWhileRiding( pPlayer, pRootEntity );
}
return LinearCheckPush( info, cbIgnoreTeammates );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool CCSPhysicsPushEntities::LinearCheckPush( PhysicsPushedInfo_t &info, bool bIgnoreTeammates )
{
// Get the blocking and pushing entities.
CBaseEntity *pBlocker = info.m_pEntity;
CBaseEntity *pRootEntity = m_rgPusher[0].m_pEntity->GetRootMoveParent();
if ( !pBlocker || !pRootEntity )
return true;
// Unlink the pusher from the spatial partition and attempt a player move.
int *pPusherHandles = ( int* )stackalloc( m_rgPusher.Count() * sizeof( int ) );
UnlinkPusherList( pPusherHandles );
MovePlayer( pBlocker, info, 1.0f, pRootEntity->IsBaseTrain(), bIgnoreTeammates );
RelinkPusherList( pPusherHandles );
// Is the pusher the ground entity the blocker is standing on?
info.m_bPusherIsGround = false;
if ( pBlocker->GetGroundEntity() && pBlocker->GetGroundEntity()->GetRootMoveParent() == m_rgPusher[0].m_pEntity )
{
info.m_bPusherIsGround = true;
}
// Check to see if the player is in a good spot and attempt a move again if not - but only if it isn't being ridden on.
if ( !info.m_bPusherIsGround && !IsPushedPositionValid( pBlocker, bIgnoreTeammates ) )
{
// Try again is the player is still blocked.
DevMsg( 2, "Pushing linear hard!\n" );
UnlinkPusherList( pPusherHandles );
MovePlayer( pBlocker, info, 1.0f, pRootEntity->IsBaseTrain(), bIgnoreTeammates );
RelinkPusherList( pPusherHandles );
}
// The player will never stop a train from moving in CS.
info.m_bBlocked = false;
return true;
}
//-----------------------------------------------------------------------------
// Purpose: When riding atop a vehicle, try to ensure that the final push vector and
// push distance will land us in a valid location.
//-----------------------------------------------------------------------------
void CCSPhysicsPushEntities::EnsureValidPushWhileRiding( CBaseEntity *pBlocker, CBaseEntity *pPusher )
{
const bool cbIgnoreTeammates = !CSGameRules() || !CSGameRules()->IsTeammateSolid();
Assert( cbIgnoreTeammates ); // This code doesn't behave if teammates are solid.
TM_ZONE_DEFAULT( TELEMETRY_LEVEL3 );
m_vecPushVector.Zero();
m_flPushDist = 0.0f;
// Do we still have a collision?
if ( IsPushedPositionValid( pBlocker, cbIgnoreTeammates ) )
return;
const float cMaxDistToLookForPlacement = 72;
// Try nudging them upwards a bit.
if ( FindValidLocationUpwards( &m_flPushDist, pBlocker, cMaxDistToLookForPlacement, 1.1 ) )
{
m_vecPushVector.z = 1.0f;
}
else
{
// Try to push the player backwards along the direction of travel of the vehicle (and also up a bit)?
Vector vBackwardsAndUp( 0, 0, 1 );
Vector vTraceEndpoint = pPusher->GetAbsVelocity();
vTraceEndpoint.x = -vTraceEndpoint.x;
vTraceEndpoint.y = -vTraceEndpoint.y;
vTraceEndpoint.z = sqrt( vTraceEndpoint.x * vTraceEndpoint.x + vTraceEndpoint.y * vTraceEndpoint.y );
vTraceEndpoint = vTraceEndpoint.Normalized() * cMaxDistToLookForPlacement;
vTraceEndpoint += pBlocker->GetAbsOrigin();
Vector vDelta;
if ( FindValidLocationAlongVector( &vDelta, pBlocker, vTraceEndpoint, 1.1 ) )
{
m_vecPushVector = vDelta;
m_flPushDist = VectorNormalize( m_vecPushVector );
}
else
{
Assert( !"Failed to find a location upwards or backwards for a blocker, sadness!" );
}
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool CCSPhysicsPushEntities::IsPlayerAABBIntersetingPusherOBB( CBaseEntity *pEntity, CBaseEntity *pRootEntity )
{
// Get the player.
CCSPlayer *pPlayer = ToCSPlayer( pEntity );
if ( !pPlayer )
return false;
// Get the player collision data.
CCollisionProperty *pCollisionPlayer = pEntity->CollisionProp();
if ( !pCollisionPlayer )
return false;
// Get the pusher collision data.
CCollisionProperty *pCollisionPusher = pRootEntity->CollisionProp();
if ( !pCollisionPusher )
return false;
// Do we have a collision.
return IsOBBIntersectingOBB( pCollisionPlayer->GetCollisionOrigin(), pCollisionPlayer->GetCollisionAngles(), pCollisionPlayer->OBBMins(), pCollisionPlayer->OBBMaxs(),
pCollisionPusher->GetCollisionOrigin(), pCollisionPusher->GetCollisionAngles(), pCollisionPusher->OBBMins(), pCollisionPusher->OBBMaxs(),
0.0f );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CCSPhysicsPushEntities::FindNewPushDirection( Vector &vecCurrent, Vector &vecNormal, Vector &vecOutput )
{
// Determine how far along plane to slide based on incoming direction.
float flBackOff = DotProduct( vecCurrent, vecNormal );
for ( int iAxis = 0; iAxis < 3; ++iAxis )
{
float flDelta = vecNormal[iAxis] * flBackOff;
vecOutput[iAxis] = vecCurrent[iAxis] - flDelta;
}
// iterate once to make sure we aren't still moving through the plane
float flAdjust = DotProduct( vecOutput, vecNormal );
if( flAdjust < 0.0f )
{
vecOutput -= ( vecNormal * flAdjust );
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CCSPhysicsPushEntities::MovePlayer( CBaseEntity *pBlocker, PhysicsPushedInfo_t &info, float flMoveScale, bool bPusherIsTrain, bool bIgnoreTeammates )
{
// Find out how far we still need to move.
float flFractionLeft = 1.0f;
float flNewDist = m_flPushDist *flMoveScale;
Vector vecPush = m_vecPushVector;
// Find a new push vector.
Vector vecStart = pBlocker->GetAbsOrigin();
Vector logVecStart = vecStart;
Vector logVecEnd = vecStart;
int iSteps = 0;
vecStart.z += 4.0f;
for ( int iTest = 0; iTest < 4; ++iTest )
{
// Clear the trace entity.
Vector vecEnd = pBlocker->GetAbsOrigin() + ( flNewDist * vecPush );
TraceBlockerEntity( pBlocker, vecStart, vecEnd, bIgnoreTeammates, &info.m_Trace );
if ( info.m_Trace.fraction > 0.0f )
{
pBlocker->SetAbsOrigin( info.m_Trace.endpos );
logVecEnd = info.m_Trace.endpos;
iSteps = iTest + 1;
}
if ( info.m_Trace.fraction == 1.0f || !info.m_Trace.m_pEnt )
break;
// New test distance and position.
flFractionLeft = 1.0f - info.m_Trace.fraction;
flNewDist = flFractionLeft * flNewDist;
flNewDist = flNewDist * ( 1.0f + ( 1.0f - fabs( info.m_Trace.plane.normal.Dot( vecPush ) ) ) );
// Find the new push direction.
Vector vecTmp;
FindNewPushDirection( vecPush, info.m_Trace.plane.normal, vecTmp );
VectorCopy( vecTmp, vecPush );
}
Vector finalPushVec = logVecEnd - logVecStart;
DevMsg( 2, "Pushed player by %.2f over %d steps (push vector: %.2f, %.2f, %.2f)\n", finalPushVec.Length(), iSteps, finalPushVec.x, finalPushVec.y, finalPushVec.z );
}
//-----------------------------------------------------------------------------
// Causes all entities in the list to touch triggers from their prev position
//-----------------------------------------------------------------------------
void CCSPhysicsPushEntities::FinishRotPushedEntity( CBaseEntity *pPushedEntity, const RotatingPushMove_t &rotPushMove )
{
if ( !pPushedEntity->IsPlayer() )
{
QAngle angles = pPushedEntity->GetAbsAngles();
// only rotate YAW with pushing. Freely rotateable entities should either use VPHYSICS
// or be set up as children
angles.y += rotPushMove.amove.y;
pPushedEntity->SetAbsAngles( angles );
}
}
+108
View File
@@ -0,0 +1,108 @@
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose: Team management class. Contains all the details for a specific team
//
// $NoKeywords: $
//=============================================================================//
#include "cbase.h"
#include "cs_team.h"
#include "entitylist.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
// Datatable
IMPLEMENT_SERVERCLASS_ST(CCSTeam, DT_CSTeam)
END_SEND_TABLE()
LINK_ENTITY_TO_CLASS( cs_team_manager, CCSTeam );
//-----------------------------------------------------------------------------
// Purpose: Get a pointer to the specified TF team manager
//-----------------------------------------------------------------------------
CCSTeam *GetGlobalCSTeam( int iIndex )
{
return (CCSTeam*)GetGlobalTeam( iIndex );
}
//-----------------------------------------------------------------------------
// Purpose: Needed because this is an entity, but should never be used
//-----------------------------------------------------------------------------
void CCSTeam::Init( const char *pName, int iNumber )
{
BaseClass::Init( pName, iNumber );
// Only detect changes every half-second.
NetworkProp()->SetUpdateInterval( 0.75f );
}
CCSTeam::CCSTeam()
{
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
CCSTeam::~CCSTeam( void )
{
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CCSTeam::Precache( void )
{
}
//-----------------------------------------------------------------------------
// Purpose: Called every frame
//-----------------------------------------------------------------------------
void CCSTeam::Think( void )
{
BaseClass::Think();
}
void CCSTeam::OnRoundPreStart( void )
{
}
//------------------------------------------------------------------------------------------------------------------
// PLAYERS
//-----------------------------------------------------------------------------
// Purpose: Add the specified player to this team. Remove them from their current team, if any.
//-----------------------------------------------------------------------------
void CCSTeam::AddPlayer( CBasePlayer *pPlayer )
{
BaseClass::AddPlayer( pPlayer );
}
//-----------------------------------------------------------------------------
// Purpose: Clean up the player's objects when they leave
//-----------------------------------------------------------------------------
void CCSTeam::RemovePlayer( CBasePlayer *pPlayer )
{
BaseClass::RemovePlayer( pPlayer );
}
//------------------------------------------------------------------------------------------------------------------
// UTILITY FUNCS
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
CCSTeam* CCSTeam::GetEnemyTeam()
{
int iMyTeam = GetTeamNumber();
if ( iMyTeam == TEAM_CT )
return GetGlobalCSTeam( TEAM_TERRORIST );
else if ( iMyTeam == TEAM_TERRORIST )
return GetGlobalCSTeam( TEAM_CT );
else
return NULL;
}
+63
View File
@@ -0,0 +1,63 @@
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose: Team management class. Contains all the details for a specific team
//
// $NoKeywords: $
//=============================================================================//
#ifndef CS_TEAM_H
#define CS_TEAM_H
#ifdef _WIN32
#pragma once
#endif
#include "utlvector.h"
#include "team.h"
//-----------------------------------------------------------------------------
// Purpose: Team Manager
//-----------------------------------------------------------------------------
class CCSTeam : public CTeam
{
DECLARE_CLASS( CCSTeam, CTeam );
public:
CCSTeam();
virtual ~CCSTeam( void );
DECLARE_SERVERCLASS();
// Initialization
virtual void Init( const char *pName, int iNumber );
virtual void Precache( void );
virtual void Think( void );
void OnRoundPreStart( void );
//-----------------------------------------------------------------------------
// Players
//-----------------------------------------------------------------------------
virtual void AddPlayer( CBasePlayer *pPlayer );
virtual void RemovePlayer( CBasePlayer *pPlayer );
//-----------------------------------------------------------------------------
// Utility funcs
//-----------------------------------------------------------------------------
CCSTeam* GetEnemyTeam();
private:
// Used to distribute resources to a team
float m_flNextResourceTime;
int m_iLastUpdateSentAt;
};
extern CCSTeam *GetGlobalCSTeam( int iIndex );
#endif // TF_TEAM_H
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+423
View File
@@ -0,0 +1,423 @@
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============
//
// Purpose: CS-specific things to vote on
//
//=============================================================================
#ifndef CS_VOTEISSUES_H
#define CS_VOTEISSUES_H
#ifdef _WIN32
#pragma once
#endif
#include "vote_controller.h"
class CCSPlayer;
//=============================================================================
// do not re-order, stored in DB
enum
{
kVoteKickBanPlayerReason_Other,
kVoteKickBanPlayerReason_Cheating,
kVoteKickBanPlayerReason_Idle,
kVoteKickBanPlayerReason_Scamming,
};
uint32 GetKickBanPlayerReason( const char *pReasonString );
//=============
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
class CBaseCSIssue : public CBaseIssue
{
// Overrides to BaseIssue standard to this mod.
public:
CBaseCSIssue( const char *typeString, CVoteController *pVoteController ) : CBaseIssue( typeString, pVoteController )
{
}
virtual int GetVoteIssue( void ) { return VOTEISSUE_UNDEFINED; }
virtual const char *GetOtherTeamDisplayString() { return "#SFUI_otherteam_vote_unimplemented"; }
};
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
class CRestartGameIssue : public CBaseCSIssue
{
public:
CRestartGameIssue( CVoteController *pVoteController ) : CBaseCSIssue( VOTEISSUE_NAME_RESTARTGAME, pVoteController )
{
}
virtual void ExecuteCommand( void );
virtual bool IsEnabled( void );
virtual bool CanCallVote( int iEntIndex, const char *pszTypeString, const char *pszDetails, vote_create_failed_t &nFailCode, int &nTime );
virtual const char *GetDisplayString();
virtual void ListIssueDetails( CBasePlayer *forWhom );
virtual bool IsAllyRestrictedVote( void ){ return false; }
virtual const char *GetVotePassedString();
virtual int GetVoteIssue( void ) { return VOTEISSUE_RESTARTGAME; }
};
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
class CKickIssue : public CBaseCSIssue
{
public:
CKickIssue( CVoteController *pVoteController ) : CBaseCSIssue( VOTEISSUE_NAME_KICK, pVoteController ), m_bPlayerCrashed( false )
{
}
virtual void ExecuteCommand( void );
virtual bool IsEnabled( void );
virtual bool CanCallVote( int iEntIndex, const char *pszTypeString, const char *pszDetails, vote_create_failed_t &nFailCode, int &nTime );
virtual const char *GetDisplayString( void );
virtual void ListIssueDetails( CBasePlayer *pForWhom );
virtual const char *GetVotePassedString( void );
virtual bool IsAllyRestrictedVote( void ) { return true; }
virtual void OnVoteFailed( void );
virtual void OnVoteStarted( void );
virtual const char *GetDetailsString( void );
virtual const char *GetOtherTeamDisplayString();
virtual int GetVoteIssue( void ) { return VOTEISSUE_KICK; }
virtual bool IsEnabledInQueuedMatchmaking( void ) { return true; } // Query if the issue is supported in queued matchmaking mode
private:
void ExtractDataFromDetails( const char *pszDetails, CCSPlayer **pSubject, uint32 *pReason = NULL );
void NotifyGC( CCSPlayer *pSubject, bool bKickedSuccessfully, uint32 unReason );
CSteamID m_steamIDVoteCaller;
CSteamID m_steamIDtoBan;
bool m_bPlayerCrashed;
};
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
class CLoadBackupIssue : public CBaseCSIssue
{
public:
CLoadBackupIssue( CVoteController *pVoteController ) : CBaseCSIssue( VOTEISSUE_NAME_LOADBACKUP, pVoteController )
{
m_szPrevDetailsString[ 0 ] = 0;
m_szNiceName[ 0 ] = 0;
}
virtual void ExecuteCommand( void );
virtual bool IsEnabled( void );
virtual bool CanCallVote( int iEntIndex, const char *pszTypeString, const char *pszDetails, vote_create_failed_t &nFailCode, int &nTime );
virtual const char *GetDisplayString( void );
virtual void ListIssueDetails( CBasePlayer *pForWhom );
virtual const char *GetVotePassedString( void );
virtual bool IsAllyRestrictedVote( void ) { return false; }
virtual void OnVoteFailed( void );
virtual const char *GetDetailsString( void );
virtual float GetFailedVoteLockOutTime( void ) { return 1.0; }
virtual bool IsEnabledInQueuedMatchmaking( void ) { return true; } // Query if the issue is supported in queued matchmaking mode
virtual int GetVoteIssue( void ) { return VOTEISSUE_LOADBACKUP; }
private:
CUtlVector< char const * > m_arrStrings;
char m_szPrevDetailsString[MAX_PATH];
char m_szNiceName[MAX_PATH];
};
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
class CChangeLevelIssue : public CBaseCSIssue
{
public:
CChangeLevelIssue( CVoteController *pVoteController ) : CBaseCSIssue( VOTEISSUE_NAME_CHANGELEVEL, pVoteController )
{
}
virtual void ExecuteCommand( void );
virtual bool IsAllyRestrictedVote( void ){ return false; }
virtual bool IsEnabled( void );
virtual bool CanTeamCallVote( int iTeam ) const; // Can someone on the given team call this vote?
virtual bool CanCallVote( int iEntIndex, const char *pszTypeString, const char *pszDetails, vote_create_failed_t &nFailCode, int &nTime );
virtual const char *GetDisplayString( void );
virtual void ListIssueDetails( CBasePlayer *pForWhom );
virtual const char *GetVotePassedString( void );
virtual const char *GetDetailsString( void );
virtual bool IsYesNoVote( void );
virtual int GetVoteIssue( void ) { return VOTEISSUE_CHANGELEVEL; }
};
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
class CNextLevelIssue : public CBaseCSIssue
{
public:
CNextLevelIssue( CVoteController *pVoteController ) : CBaseCSIssue( VOTEISSUE_NAME_NEXTLEVEL, pVoteController )
{
}
virtual void ExecuteCommand( void );
virtual bool IsAllyRestrictedVote( void ){ return false; }
virtual bool IsEnabled( void );
virtual bool CanTeamCallVote( int iTeam ) const; // Can someone on the given team call this vote?
virtual bool CanCallVote( int iEntIndex, const char *pszTypeString, const char *pszDetails, vote_create_failed_t &nFailCode, int &nTime );
virtual const char *GetDisplayString( void );
virtual void ListIssueDetails( CBasePlayer *pForWhom );
virtual const char *GetVotePassedString( void );
virtual const char *GetDetailsString( void );
virtual bool IsYesNoVote( void );
virtual int GetNumberVoteOptions( void );
virtual int GetVoteIssue( void ) { return VOTEISSUE_NEXTLEVEL; }
private:
CUtlVector <const char *> m_IssueOptions;
};
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
class CScrambleTeams : public CBaseCSIssue
{
public:
CScrambleTeams( CVoteController *pVoteController ) : CBaseCSIssue( VOTEISSUE_NAME_SCRAMBLE, pVoteController )
{
}
virtual void ExecuteCommand( void );
virtual bool IsEnabled( void );
virtual bool CanCallVote( int iEntIndex, const char *pszTypeString, const char *pszDetails, vote_create_failed_t &nFailCode, int &nTime );
virtual const char *GetDisplayString( void );
virtual void ListIssueDetails( CBasePlayer *pForWhom );
virtual bool IsAllyRestrictedVote( void ){ return false; }
virtual const char *GetVotePassedString( void );
virtual int GetVoteIssue( void ) { return VOTEISSUE_SCRAMBLE; }
};
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
class CSwapTeams : public CBaseCSIssue
{
public:
CSwapTeams( CVoteController *pVoteController ) : CBaseCSIssue( VOTEISSUE_NAME_SWAPTEAMS, pVoteController )
{
}
virtual void ExecuteCommand( void );
virtual bool IsEnabled( void );
virtual bool CanCallVote( int iEntIndex, const char *pszTypeString, const char *pszDetails, vote_create_failed_t &nFailCode, int &nTime );
virtual const char *GetDisplayString( void );
virtual void ListIssueDetails( CBasePlayer *pForWhom );
virtual bool IsAllyRestrictedVote( void ){ return false; }
virtual const char *GetVotePassedString( void );
virtual int GetVoteIssue( void ) { return VOTEISSUE_SWAPTEAMS; }
};
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
class CPauseMatchIssue : public CBaseCSIssue
{
public:
CPauseMatchIssue( CVoteController *pVoteController ) : CBaseCSIssue( VOTEISSUE_NAME_PAUSEMATCH, pVoteController )
{
}
virtual void ExecuteCommand( void );
virtual bool IsEnabled( void );
virtual bool CanCallVote( int iEntIndex, const char *pszTypeString, const char *pszDetails, vote_create_failed_t &nFailCode, int &nTime );
virtual const char *GetDisplayString( void );
virtual bool ShouldIgnoreCreationTimer( void ) { return true; }
virtual void ListIssueDetails( CBasePlayer *pForWhom );
virtual int GetVotesRequiredToPass( void ){ return 1; }
virtual const char *GetVotePassedString( void );
virtual float GetCommandDelay( void ) { return 0.0; }
virtual bool IsEnabledDuringWarmup( void ) { return true; } // Can this vote be called during warmup?
virtual float GetFailedVoteLockOutTime( void ) { return 1.0; }
virtual bool IsEnabledInQueuedMatchmaking( void ) { return true; } // Query if the issue is supported in queued matchmaking mode
virtual int GetVoteIssue( void ) { return VOTEISSUE_PAUSEMATCH; }
};
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
class CUnpauseMatchIssue : public CBaseCSIssue
{
public:
CUnpauseMatchIssue( CVoteController *pVoteController ) : CBaseCSIssue( VOTEISSUE_NAME_UNPAUSEMATCH, pVoteController )
{
}
virtual void ExecuteCommand( void );
virtual bool IsEnabled( void );
virtual bool CanCallVote( int iEntIndex, const char *pszTypeString, const char *pszDetails, vote_create_failed_t &nFailCode, int &nTime );
virtual const char *GetDisplayString( void );
virtual void ListIssueDetails( CBasePlayer *pForWhom );
virtual bool ShouldIgnoreCreationTimer( void ) { return true; }
virtual bool IsUnanimousVoteToPass( void ) { return true; } // Requires all potential voters to pass
virtual bool IsEnabledDuringWarmup( void ) { return true; } // Can this vote be called during warmup?
virtual bool IsVoteCallExclusiveToSpectators( void ) { return true; } // Whether only spectators can call the vote
virtual const char *GetVotePassedString( void );
virtual float GetFailedVoteLockOutTime( void ) { return 1.0; }
virtual bool IsEnabledInQueuedMatchmaking( void ) { return true; } // Query if the issue is supported in queued matchmaking mode
virtual int GetVoteIssue( void ) { return VOTEISSUE_UNPAUSEMATCH; }
};
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
class CReadyForMatchIssue : public CBaseCSIssue
{
public:
CReadyForMatchIssue( CVoteController *pVoteController ) : CBaseCSIssue( VOTEISSUE_NAME_READYFORMATCH, pVoteController )
{
}
virtual void ExecuteCommand( void );
virtual bool IsEnabled( void );
virtual bool CanCallVote( int iEntIndex, const char *pszTypeString, const char *pszDetails, vote_create_failed_t &nFailCode, int &nTime );
virtual const char *GetDisplayString( void );
virtual void ListIssueDetails( CBasePlayer *pForWhom );
virtual bool ShouldIgnoreCreationTimer( void ) { return true; }
virtual bool IsUnanimousVoteToPass( void ) { return true; } // Requires all potential voters to pass
virtual bool IsEnabledDuringWarmup( void ) { return true; } // Can this vote be called during warmup?
virtual bool IsVoteCallExclusiveToSpectators( void ) { return true; } // Whether only spectators can call the vote
virtual const char *GetVotePassedString( void );
virtual float GetFailedVoteLockOutTime( void ) { return 1.0; }
virtual bool IsEnabledInQueuedMatchmaking( void ) { return true; } // Query if the issue is supported in queued matchmaking mode
virtual int GetVoteIssue( void ) { return VOTEISSUE_READYFORMATCH; }
};
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
class CNotReadyForMatchIssue : public CBaseCSIssue
{
public:
CNotReadyForMatchIssue( CVoteController *pVoteController ) : CBaseCSIssue( VOTEISSUE_NAME_NOTREADYFORMATCH, pVoteController )
{
}
virtual void ExecuteCommand( void );
virtual bool IsEnabled( void );
virtual bool CanCallVote( int iEntIndex, const char *pszTypeString, const char *pszDetails, vote_create_failed_t &nFailCode, int &nTime );
virtual const char *GetDisplayString( void );
virtual void ListIssueDetails( CBasePlayer *pForWhom );
virtual int GetVotesRequiredToPass( void ){ return 1; }
virtual float GetCommandDelay( void ) { return 0.0; }
virtual bool ShouldIgnoreCreationTimer( void ) { return true; }
virtual bool IsEnabledDuringWarmup( void ) { return true; } // Can this vote be called during warmup?
virtual const char *GetVotePassedString( void );
virtual float GetFailedVoteLockOutTime( void ) { return 1.0; }
virtual bool IsEnabledInQueuedMatchmaking( void ) { return true; } // Query if the issue is supported in queued matchmaking mode
virtual int GetVoteIssue( void ) { return VOTEISSUE_NOTREADYFORMATCH; }
};
class CStartTimeOutIssue : public CBaseCSIssue
{
public:
CStartTimeOutIssue( CVoteController *pVoteController ) : CBaseCSIssue( VOTEISSUE_NAME_STARTTIMEOUT, pVoteController )
{
}
virtual void ExecuteCommand( void );
virtual bool IsEnabled( void );
virtual bool CanCallVote( int iEntIndex, const char *pszTypeString, const char *pszDetails, vote_create_failed_t &nFailCode, int &nTime );
virtual bool CanTeamCallVote( int iTeam ) const; // Can someone on the given team call this vote?
virtual const char *GetDisplayString( void );
virtual void ListIssueDetails( CBasePlayer *pForWhom );
virtual bool IsAllyRestrictedVote( void ) { return true; }
virtual const char *GetVotePassedString( void );
virtual const char *GetOtherTeamDisplayString( ) { return "#SFUI_otherteam_vote_timeout"; }
virtual bool IsEnabledInQueuedMatchmaking( void ) { return true; } // Query if the issue is supported in queued matchmaking mode
virtual int GetVoteIssue( void ) { return VOTEISSUE_STARTTIMEOUT; }
virtual vote_create_failed_t MakeVoteFailErrorCodeForClients( vote_create_failed_t eDefaultFailCode );
};
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
class CSurrender : public CBaseCSIssue
{
public:
CSurrender( CVoteController *pVoteController ) : CBaseCSIssue( VOTEISSUE_NAME_SURRENDER, pVoteController )
{
}
virtual void ExecuteCommand( void );
virtual bool IsEnabled( void );
virtual bool CanCallVote( int iEntIndex, const char *pszTypeString, const char *pszDetails, vote_create_failed_t &nFailCode, int &nTime );
virtual bool CanTeamCallVote( int iTeam ) const;
virtual const char *GetDisplayString( void );
virtual const char *GetOtherTeamDisplayString();
virtual void ListIssueDetails( CBasePlayer *pForWhom );
virtual bool IsAllyRestrictedVote( void ){ return true; }
virtual const char *GetVotePassedString( void );
virtual bool IsEnabledInQueuedMatchmaking( void ) { return true; } // Query if the issue is supported in queued matchmaking mode
virtual int GetVoteIssue( void ) { return VOTEISSUE_SURRENDER; }
virtual bool IsUnanimousVoteToPass( void ) {return true; } // Requires all potential voters to pass
virtual vote_create_failed_t MakeVoteFailErrorCodeForClients( vote_create_failed_t eDefaultFailCode )
{
switch ( eDefaultFailCode )
{
case VOTE_FAILED_WAITINGFORPLAYERS:
case VOTE_FAILED_TEAM_CANT_CALL:
return VOTE_FAILED_TOO_EARLY_SURRENDER;
default:
return eDefaultFailCode;
}
}
};
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
class CQueuedMatchmakingRematch : public CBaseCSIssue
{
public:
CQueuedMatchmakingRematch( CVoteController *pVoteController ) : CBaseCSIssue( VOTEISSUE_NAME_REMATCH, pVoteController )
{
}
virtual void ExecuteCommand( void );
virtual void OnVoteFailed( void ); // The moment the vote fails, also has some time for feedback before the window goes away
virtual bool IsEnabled( void );
virtual bool CanCallVote( int iEntIndex, const char *pszTypeString, const char *pszDetails, vote_create_failed_t &nFailCode, int &nTime );
virtual const char *GetDisplayString( void );
virtual void ListIssueDetails( CBasePlayer *pForWhom );
virtual bool IsAllyRestrictedVote( void ){ return false; }
virtual const char *GetVotePassedString( void );
virtual int GetVoteIssue( void ) { return VOTEISSUE_REMATCH; }
virtual void OnVoteStarted( void );
virtual bool IsEnabledInQueuedMatchmaking( void ) { return true; } // Query if the issue is supported in queued matchmaking mode
virtual vote_create_failed_t MakeVoteFailErrorCodeForClients( vote_create_failed_t eDefaultFailCode ) { return VOTE_FAILED_REMATCH; }
public:
static bool IsTimeForRematchVote();
};
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
class CQueuedMatchmakingContinue : public CBaseCSIssue
{
public:
CQueuedMatchmakingContinue( CVoteController *pVoteController ) : CBaseCSIssue( VOTEISSUE_NAME_CONTINUE, pVoteController )
{
}
virtual void ExecuteCommand( void );
virtual void OnVoteFailed( void ); // The moment the vote fails, also has some time for feedback before the window goes away
virtual bool IsEnabled( void );
virtual bool CanCallVote( int iEntIndex, const char *pszTypeString, const char *pszDetails, vote_create_failed_t &nFailCode, int &nTime );
virtual const char *GetDisplayString( void );
virtual void ListIssueDetails( CBasePlayer *pForWhom );
virtual bool IsAllyRestrictedVote( void ){ return false; }
virtual const char *GetVotePassedString( void );
virtual int GetVoteIssue( void ) { return VOTEISSUE_CONTINUE; }
virtual void OnVoteStarted( void );
virtual bool IsEnabledInQueuedMatchmaking( void ) { return true; } // Query if the issue is supported in queued matchmaking mode
virtual bool IsUnanimousVoteToPass() { return true; } // Require all attending humans to vote
virtual vote_create_failed_t MakeVoteFailErrorCodeForClients( vote_create_failed_t eDefaultFailCode ) { return VOTE_FAILED_CONTINUE; }
};
#endif // CS_VOTEISSUES_H
+172
View File
@@ -0,0 +1,172 @@
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose: Bomb target area
//
//=============================================================================//
#include "cbase.h"
#include "cs_player.h"
#include "weapon_csbase.h"
#include "func_bomb_target.h"
#include "cs_player_resource.h"
#include "cs_gamerules.h"
LINK_ENTITY_TO_CLASS( func_bomb_target, CBombTarget );
BEGIN_DATADESC( CBombTarget )
DEFINE_FUNCTION( BombTargetTouch ),
DEFINE_FUNCTION( BombTargetUse ), //needed?
// Inputs
DEFINE_INPUTFUNC( FIELD_VOID, "BombExplode", OnBombExplode ),
DEFINE_INPUTFUNC( FIELD_VOID, "BombPlanted", OnBombPlanted ),
DEFINE_INPUTFUNC( FIELD_VOID, "BombDefused", OnBombDefused ),
// Outputs
DEFINE_OUTPUT( m_OnBombExplode, "BombExplode" ),
DEFINE_OUTPUT( m_OnBombPlanted, "BombPlanted" ),
DEFINE_OUTPUT( m_OnBombDefused, "BombDefused" ),
DEFINE_KEYFIELD( m_bIsHeistBombTarget, FIELD_BOOLEAN, "heistbomb" ),
DEFINE_KEYFIELD( m_szMountTarget, FIELD_STRING, "bomb_mount_target" ),
END_DATADESC()
CBombTarget::CBombTarget( void )
{
m_bIsHeistBombTarget = false;
m_bBombPlantedHere = false;
m_szMountTarget = NULL_STRING;
m_hInstructorHint = NULL;
}
void CBombTarget::Spawn()
{
InitTrigger();
SetTouch( &CBombTarget::BombTargetTouch );
SetUse( &CBombTarget::BombTargetUse );
//VisibilityMonitor_AddEntity( this, 1200.0f, NULL, NULL );
}
void CBombTarget::ReInitOnRoundStart( void )
{
if ( m_hInstructorHint.Get() )
{
CPointEntity *pEnt = static_cast< CPointEntity* >( m_hInstructorHint.Get() );
UTIL_Remove( pEnt );
m_hInstructorHint = NULL;
}
m_bBombPlantedHere = false;
//CCSPlayerResource *pCSPR = CSPlayerResource();
Vector bombA = Vector( 0, 0, 0 );
Vector bombB = Vector( 0, 0, 0 );
Vector vecTrigger = CollisionProp()->WorldSpaceCenter();
if ( CSPlayerResource() )
{
bombA = CSPlayerResource()->GetBombsiteAPosition();
bombB = CSPlayerResource()->GetBombsiteBPosition();
CPointEntity *pEnt;
if ( vecTrigger.DistTo( bombA ) > vecTrigger.DistTo( bombB ) )
{
pEnt = static_cast< CPointEntity* >( CreateEntityByName( "info_bomb_target_hint_B" ) );
}
else
{
pEnt = static_cast< CPointEntity* >( CreateEntityByName( "info_bomb_target_hint_A" ) );
}
if ( pEnt )
{
pEnt->Spawn();
trace_t tr;
UTIL_TraceLine( vecTrigger, vecTrigger + Vector ( 0, 0, -128 ), MASK_SOLID, this, COLLISION_GROUP_NONE, &tr );
Vector vecHint = tr.endpos + Vector ( 0, 0, 16 );
pEnt->SetAbsOrigin( vecHint );
pEnt->SetOwnerEntity( this );
pEnt->SetParent( this );
m_hInstructorHint = pEnt;
}
}
}
void CBombTarget::BombTargetTouch( CBaseEntity* pOther )
{
CCSPlayer *p = dynamic_cast< CCSPlayer* >( pOther );
if ( p )
{
p->m_bInBombZoneTrigger = true;
if ( p->HasC4() && m_bBombPlantedHere == false && (CSGameRules()->m_bBombPlanted == false || CSGameRules()->IsPlayingCoopMission()) )
{
p->m_bInBombZone = true;
p->m_iBombSiteIndex = entindex();
//bool bC4Active = p->GetActiveCSWeapon() && p->GetActiveCSWeapon()->GetCSWeaponID() == WEAPON_C4;
if ( !(p->m_iDisplayHistoryBits & DHF_IN_TARGET_ZONE) )
{
p->m_iDisplayHistoryBits |= DHF_IN_TARGET_ZONE;
}
}
}
}
void CBombTarget::BombTargetUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
{
//SUB_UseTargets( NULL, USE_TOGGLE, 0 );
DevMsg( 2, "BombTargetUse does nothing\n" );
}
// Relay to our outputs
void CBombTarget::OnBombExplode( inputdata_t &inputdata )
{
m_OnBombExplode.FireOutput(this, this);
}
// Relay to our outputs
void CBombTarget::OnBombPlanted( inputdata_t &inputdata )
{
m_OnBombPlanted.FireOutput(this, this);
m_bBombPlantedHere = true;
}
// Relay to our outputs
void CBombTarget::OnBombDefused( inputdata_t &inputdata )
{
m_OnBombDefused.FireOutput(this, this);
}
//-----------------------------------------------------------------------------
// Purpose: A generic target entity that gets replicated to the client for displaying a hint for the CS bomb targets
//-----------------------------------------------------------------------------
void CInfoInstructorHintBombTargetA::Spawn( void )
{
VisibilityMonitor_AddEntity( this, 5000.0f, NULL, NULL );
}
LINK_ENTITY_TO_CLASS( info_bomb_target_hint_A, CInfoInstructorHintBombTargetA );
BEGIN_DATADESC( CInfoInstructorHintBombTargetA )
END_DATADESC()
//-----------------------------------------------------------------------------
// Purpose: A generic target entity that gets replicated to the client for displaying a hint for the CS bomb targets
//-----------------------------------------------------------------------------
void CInfoInstructorHintBombTargetB::Spawn( void )
{
VisibilityMonitor_AddEntity( this, 5000.0f, NULL, NULL );
}
LINK_ENTITY_TO_CLASS( info_bomb_target_hint_B, CInfoInstructorHintBombTargetB );
BEGIN_DATADESC( CInfoInstructorHintBombTargetB )
END_DATADESC()
+76
View File
@@ -0,0 +1,76 @@
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose: Bomb Target Area ent
//
// $NoKeywords: $
//=============================================================================//
#include "cbase.h"
#include "triggers.h"
#include "cvisibilitymonitor.h"
//#include "cs_player_resource.h"
class CBombTarget : public CBaseTrigger
{
public:
DECLARE_CLASS( CBombTarget, CBaseTrigger );
DECLARE_DATADESC();
CBombTarget();
void Spawn();
virtual void ReInitOnRoundStart( void );
void EXPORT BombTargetTouch( CBaseEntity* pOther );
void EXPORT BombTargetUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value );
void OnBombExplode( inputdata_t &inputdata );
void OnBombPlanted( inputdata_t &inputdata );
void OnBombDefused( inputdata_t &inputdata );
bool IsHeistBombTarget( void ) { return m_bIsHeistBombTarget; }
const char *GetBombMountTarget( void ){ return STRING( m_szMountTarget ); }
private:
COutputEvent m_OnBombExplode; //Fired when the bomb explodes
COutputEvent m_OnBombPlanted; //Fired when the bomb is planted
COutputEvent m_OnBombDefused; //Fired when the bomb is defused
bool m_bIsHeistBombTarget;
bool m_bBombPlantedHere;
string_t m_szMountTarget;
EHANDLE m_hInstructorHint; // Hint that's used by the instructor system
};
//-----------------------------------------------------------------------------
// Purpose: A generic target entity that gets replicated to the client for displaying a hint for the CS bomb targets
//-----------------------------------------------------------------------------
class CInfoInstructorHintBombTargetA : public CPointEntity
{
public:
DECLARE_CLASS( CInfoInstructorHintBombTargetA, CPointEntity );
void Spawn( void );
virtual int UpdateTransmitState( void ) // set transmit filter to transmit always
{
return SetTransmitState( FL_EDICT_ALWAYS );
}
DECLARE_DATADESC();
};
//-----------------------------------------------------------------------------
// Purpose: A generic target entity that gets replicated to the client for displaying a hint for the CS bomb targets
//-----------------------------------------------------------------------------
class CInfoInstructorHintBombTargetB : public CPointEntity
{
public:
DECLARE_CLASS( CInfoInstructorHintBombTargetB, CPointEntity );
void Spawn( void );
virtual int UpdateTransmitState( void ) // set transmit filter to transmit always
{
return SetTransmitState( FL_EDICT_ALWAYS );
}
DECLARE_DATADESC();
};
+163
View File
@@ -0,0 +1,163 @@
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
//=============================================================================//
#include "cbase.h"
#include "triggers.h"
#include "cs_player.h"
#include "cs_team.h"
//======================================
// Buy zone
//
//
extern ConVar mp_logdetail;
class CBuyZone : public CBaseTrigger
{
public:
DECLARE_CLASS( CBuyZone, CBaseTrigger );
DECLARE_DATADESC();
CBuyZone();
void Spawn();
void EXPORT BuyZoneTouch( CBaseEntity* pOther );
void InputSetTeam_TerroristOnly( inputdata_t& inputdata );
void InputSetTeam_CTOnly( inputdata_t& inputdata );
void InputSetTeam_AllTeams( inputdata_t& inputdata );
void InputSetTeam_None( inputdata_t& inputdata );
// CBaseTrigger override
virtual void EndTouch( CBaseEntity *pOther ) OVERRIDE;
public:
int m_LegacyTeamNum;
};
LINK_ENTITY_TO_CLASS( func_buyzone, CBuyZone );
BEGIN_DATADESC( CBuyZone )
DEFINE_FUNCTION( BuyZoneTouch ),
// This is here to support maps that haven't updated to using "teamnum" yet.
DEFINE_INPUT( m_LegacyTeamNum, FIELD_INTEGER, "team" ),
DEFINE_INPUTFUNC( FIELD_VOID, "SetTeam_TerroristOnly", InputSetTeam_TerroristOnly ),
DEFINE_INPUTFUNC( FIELD_VOID, "SetTeam_CTOnly", InputSetTeam_CTOnly ),
DEFINE_INPUTFUNC( FIELD_VOID, "SetTeam_AllTeams", InputSetTeam_AllTeams ),
DEFINE_INPUTFUNC( FIELD_VOID, "SetTeam_None", InputSetTeam_None )
END_DATADESC()
CBuyZone::CBuyZone()
{
m_LegacyTeamNum = -1;
}
void CBuyZone::Spawn()
{
InitTrigger();
SetTouch( &CBuyZone::BuyZoneTouch );
// Support for legacy-style teamnums.
if ( m_LegacyTeamNum == 1 )
{
ChangeTeam( TEAM_TERRORIST );
}
else if ( m_LegacyTeamNum == 2 )
{
ChangeTeam( TEAM_CT );
}
}
void CBuyZone::BuyZoneTouch( CBaseEntity* pOther )
{
CCSPlayer *p = dynamic_cast< CCSPlayer* >( pOther );
if ( p )
{
int nZoneTeam = GetTeamNumber();
if ( nZoneTeam == -1 )
{
// -1 mean no team can use it
return;
}
// compare player team with buy zone team number
else if ( nZoneTeam == 0 || p->GetTeamNumber() == GetTeamNumber() )
{
p->m_bInBuyZone = true;
p->AutoBuyAmmo();
}
}
}
void CBuyZone::InputSetTeam_TerroristOnly( inputdata_t& inputdata )
{
ChangeTeam( TEAM_TERRORIST );
}
void CBuyZone::InputSetTeam_CTOnly( inputdata_t& inputdata )
{
ChangeTeam( TEAM_CT );
}
void CBuyZone::InputSetTeam_AllTeams( inputdata_t& inputdata )
{
// team 0 in this case means that everyone can use it
ChangeTeam( 0 );
}
void CBuyZone::InputSetTeam_None( inputdata_t& inputdata )
{
// team -1 means that no one can use it
ChangeTeam( -1 );
}
void CBuyZone::EndTouch( CBaseEntity *pOther )
{
BaseClass::EndTouch( pOther );
// Feature mostly for tournament organizers to aid match stat log parsing.
if ( mp_logdetail.GetInt() >= 3 && pOther->IsPlayer() )
{
if ( CCSPlayer *pCSPlayer = ToCSPlayer( pOther ) )
{
CUtlString strEquipment( "[ ");
for ( int i = 0; i < MAX_WEAPONS; ++i )
{
if ( CBaseCombatWeapon* pWeapon = pCSPlayer->GetWeapon( i ) )
strEquipment.Append( CFmtStr( "%s ", pWeapon->GetEconItemView() ? pWeapon->GetEconItemView()->GetItemDefinition()->GetDefinitionName() : pWeapon->GetName() ).Get() );
}
if ( pCSPlayer->HasDefuser() )
strEquipment.Append( "defuser " );
if ( pCSPlayer->ArmorValue() > 0 )
strEquipment.Append( CFmtStr( "kevlar(%d) ", pCSPlayer->ArmorValue() ).Get() );
if ( pCSPlayer->m_bHasHelmet )
strEquipment.Append( "helmet " );
if ( pCSPlayer->HasC4() )
strEquipment.Append( "C4 " );
strEquipment.Append( "]" );
UTIL_LogPrintf( "\"%s<%i><%s><%s>\" left buyzone with %s\n",
pCSPlayer->GetPlayerName(),
pCSPlayer->GetUserID(),
pCSPlayer->GetNetworkIDString(),
pCSPlayer->GetTeam()->GetName(),
strEquipment.Get() );
}
}
}
@@ -0,0 +1,107 @@
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
//=============================================================================//
#include "cbase.h"
#include "func_hostage_rescue.h"
//============================================================================
LINK_ENTITY_TO_CLASS( func_hostage_rescue, CHostageRescueZone );
BEGIN_DATADESC( CHostageRescueZone )
//Functions
DEFINE_FUNCTION( HostageRescueTouch ),
END_DATADESC()
void CHostageRescueZone::Spawn()
{
m_hInstructorHint = NULL;
InitTrigger();
SetTouch( &CHostageRescueZone::HostageRescueTouch );
VisibilityMonitor_AddEntity( this, 1600.0f, NULL, NULL );
}
void CHostageRescueZone::ReInitOnRoundStart( void )
{
if ( m_hInstructorHint.Get() )
{
CPointEntity *pEnt = static_cast< CPointEntity* >( m_hInstructorHint.Get() );
UTIL_Remove( pEnt );
m_hInstructorHint = NULL;
}
//CCSPlayerResource *pCSPR = CSPlayerResource();
Vector vecPosCur = Vector( 0, 0, 0 );
Vector vecPosNew = Vector( 0, 0, 0 );
Vector vecTrigger = CollisionProp()->WorldSpaceCenter();
if ( CSPlayerResource() )
{
vecPosCur = CSPlayerResource()->GetHostageRescuePosition( 0 );
for ( int i=0; i < MAX_HOSTAGE_RESCUES; i++ )
{
vecPosNew = CSPlayerResource()->GetHostageRescuePosition( MIN( i+1, MAX_HOSTAGE_RESCUES-1 ) );
if ( vecPosNew != vec3_origin )
{
if ( vecTrigger.DistTo( vecPosCur ) > vecTrigger.DistTo( vecPosNew ) )
{
vecPosCur = vecPosNew;
}
}
}
CPointEntity *pEnt = NULL;
if ( vecPosCur != vec3_origin )
{
pEnt = static_cast< CPointEntity* >( CreateEntityByName( "info_hostage_rescue_zone_hint" ) );
}
if ( pEnt )
{
pEnt->Spawn();
trace_t tr;
UTIL_TraceLine( vecPosCur, vecPosCur + Vector ( 0, 0, -128 ), MASK_SOLID, this, COLLISION_GROUP_NONE, &tr );
Vector vecHint = tr.endpos + Vector ( 0, 0, 16 );
pEnt->SetAbsOrigin( vecHint );
pEnt->SetOwnerEntity( this );
pEnt->SetParent( this );
m_hInstructorHint = pEnt;
}
}
}
void CHostageRescueZone::HostageRescueTouch( CBaseEntity *pOther )
{
if ( m_bDisabled == false )
{
variant_t emptyVariant;
pOther->AcceptInput( "OnRescueZoneTouch", NULL, NULL, emptyVariant, 0 );
}
}
//-----------------------------------------------------------------------------
// Purpose: A generic target entity that gets replicated to the client for displaying a hint for the hostage rescue zone
//-----------------------------------------------------------------------------
LINK_ENTITY_TO_CLASS( info_hostage_rescue_zone_hint, CInfoInstructorHintHostageRescueZone );
BEGIN_DATADESC( CInfoInstructorHintHostageRescueZone )
END_DATADESC()
void CInfoInstructorHintHostageRescueZone::Spawn( void )
{
VisibilityMonitor_AddEntity( this, 5000.0f, NULL, NULL );
}
@@ -0,0 +1,50 @@
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
//=============================================================================//
#ifndef HOSTAGERESCUEZONE_H
#define HOSTAGERESCUEZONE_H
#ifdef _WIN32
#pragma once
#endif
#include "cbase.h"
#include "triggers.h"
#include "cvisibilitymonitor.h"
#include "cs_shareddefs.h"
#include "cs_player_resource.h"
class CHostageRescueZone : public CBaseTrigger
{
public:
DECLARE_CLASS( CHostageRescueZone, CBaseTrigger );
DECLARE_DATADESC();
void CHostageRescue();
void Spawn();
virtual void ReInitOnRoundStart( void );
void HostageRescueTouch( CBaseEntity* pOther );
EHANDLE m_hInstructorHint; // Hint that's used by the instructor system
};
//-----------------------------------------------------------------------------
// Purpose: A generic target entity that gets replicated to the client for displaying a hint for the hostage rescue zone
//-----------------------------------------------------------------------------
class CInfoInstructorHintHostageRescueZone : public CPointEntity
{
public:
DECLARE_CLASS( CInfoInstructorHintHostageRescueZone, CPointEntity );
void Spawn( void );
virtual int UpdateTransmitState( void ) // set transmit filter to transmit always
{
return SetTransmitState( FL_EDICT_ALWAYS );
}
DECLARE_DATADESC();
};
#endif // HOSTAGERESCUEZONE_H
+32
View File
@@ -0,0 +1,32 @@
//========= Copyright © 2011-2011, Valve Corporation, All rights reserved. ============//
//
// Purpose: No defuse area
//
//=============================================================================//
#include "cbase.h"
#include "cs_player.h"
#include "func_no_defuse.h"
LINK_ENTITY_TO_CLASS( func_no_defuse, CNoDefuseArea );
BEGIN_DATADESC( CNoDefuseArea )
DEFINE_FUNCTION( NoDefuseAreaTouch ),
END_DATADESC()
void CNoDefuseArea::Spawn()
{
InitTrigger();
SetTouch( &CNoDefuseArea::NoDefuseAreaTouch );
}
void CNoDefuseArea::NoDefuseAreaTouch( CBaseEntity* pOther )
{
CCSPlayer *player = dynamic_cast< CCSPlayer* >( pOther );
// CT players are not allowed to defuse a bomb when they are in a no defuse zone.
// the is to help prevent bugs with CTs being able to defuse bombs through walls and floors etc.
if ( player )
{
player->m_bInNoDefuseArea = true;
}
}
+19
View File
@@ -0,0 +1,19 @@
//========= Copyright Š 2011-2011, Valve Corporation, All rights reserved. ============//
//
// Purpose: No defuse area ent
//
// $NoKeywords: $
//=============================================================================//
#include "cbase.h"
#include "triggers.h"
class CNoDefuseArea : public CBaseTrigger
{
public:
DECLARE_CLASS( CNoDefuseArea, CBaseTrigger );
DECLARE_DATADESC();
void Spawn();
void EXPORT NoDefuseAreaTouch( CBaseEntity* pOther );
};
File diff suppressed because it is too large Load Diff
+71
View File
@@ -0,0 +1,71 @@
#ifndef INCLUDED_funfact_cs
#define INCLUDED_funfact_cs
#pragma once
#include "cs_shareddefs.h"
struct FunFact
{
FunFact() :
id(-1),
szLocalizationToken(NULL),
iPlayer(0),
iData1(0),
iData2(0),
iData3(0),
fMagnitude(0.0f)
{}
int id;
const char* szLocalizationToken;
int iPlayer;
int iData1;
int iData2;
int iData3;
float fMagnitude;
};
typedef CUtlVector<FunFact> FunFactVector;
class FunFactEvaluator
{
DECLARE_CLASS_NOBASE( FunFactEvaluator );
public:
FunFactEvaluator( int id, const char* szLocalizationToken, float fCoolness ) :
m_id(id),
m_pLocalizationToken(szLocalizationToken),
m_fCoolness(fCoolness)
{}
virtual ~FunFactEvaluator() {}
int GetId() const { return m_id; }
const char* GetLocalizationToken() const { return m_pLocalizationToken; }
float GetCoolness() const { return m_fCoolness; }
virtual bool Evaluate( e_RoundEndReason iRoundResult, FunFactVector& results ) const = 0;
private:
int m_id;
const char* m_pLocalizationToken;
float m_fCoolness;
};
typedef FunFactEvaluator* (*funfactCreateFunc) (void);
class CFunFactHelper
{
public:
CFunFactHelper ( funfactCreateFunc createFunc )
{
m_pfnCreate = createFunc;
m_pNext = s_pFirst;
s_pFirst = this;
}
funfactCreateFunc m_pfnCreate;
CFunFactHelper *m_pNext;
static CFunFactHelper *s_pFirst;
};
#endif // INCLUDED_funfact_cs
+210
View File
@@ -0,0 +1,210 @@
#include "cbase.h"
#include "usermessages.h"
#include "funfactmgr_cs.h"
#include "cs_shareddefs.h"
const float kCooldownRatePlayer = 0.5f;
const float kCooldownRateFunFact = 0.2f;
const float kWeightPlayerCooldown = 0.7f;
const float kWeightFunFactCooldown = 1.0f;
const float kWeightCoolness = 1.2f;
const float kWeightRarity = 1.0f;
#define DEBUG_FUNFACT_SCORING 0
#if DEBUG_FUNFACT_SCORING
#include "cs_player.h"
#endif
//-----------------------------------------------------------------------------
// Purpose: constructor
//-----------------------------------------------------------------------------
CCSFunFactMgr::CCSFunFactMgr() :
CAutoGameSystemPerFrame( "CCSFunFactMgr" ),
m_funFactDatabase(0, 100, DefLessFunc(int) )
{
for ( int i = 0; i < ARRAYSIZE(m_playerCooldown); ++i )
{
m_playerCooldown[i] = 0.0f;
}
}
CCSFunFactMgr::~CCSFunFactMgr()
{
Shutdown();
}
//-----------------------------------------------------------------------------
// Purpose: Initializes the fun fact manager
//-----------------------------------------------------------------------------
bool CCSFunFactMgr::Init()
{
ListenForGameEvent( "player_connect" );
CFunFactHelper *pFunFactHelper = CFunFactHelper::s_pFirst;
// create database of all fun fact evaluators (and initial usage metrics)
while ( pFunFactHelper )
{
FunFactDatabaseEntry entry;
entry.fCooldown = 0.0f;
entry.iOccurrences = 0;
entry.pEvaluator = pFunFactHelper->m_pfnCreate();
m_funFactDatabase.Insert(entry.pEvaluator->GetId(), entry);
pFunFactHelper = pFunFactHelper->m_pNext;
}
for (int i = 0; i < ARRAYSIZE(m_playerCooldown); ++i)
{
m_playerCooldown[i] = 0.0f;
}
m_numRounds = 0;
return true;
}
//-----------------------------------------------------------------------------
// Purpose: Shuts down the fun fact manager
//-----------------------------------------------------------------------------
void CCSFunFactMgr::Shutdown()
{
FOR_EACH_MAP( m_funFactDatabase, iter )
{
delete m_funFactDatabase[iter].pEvaluator;
}
m_funFactDatabase.RemoveAll();
}
//-----------------------------------------------------------------------------
// Purpose: Per frame processing
//-----------------------------------------------------------------------------
void CCSFunFactMgr::Update( float frametime )
{
}
//-----------------------------------------------------------------------------
// Purpose: Listens for game events. Clears out map based stats and player based stats when necessary
//-----------------------------------------------------------------------------
void CCSFunFactMgr::FireGameEvent( IGameEvent *event )
{
const char *eventname = event->GetName();
if ( Q_strcmp( "player_connect", eventname ) == 0 )
{
int index = event->GetInt("index");// player slot (entity index-1)
ASSERT( index >= 0 && index <= MAX_PLAYERS );
if( index >= 0 && index <= MAX_PLAYERS )
{
m_playerCooldown[index] = 0.0f;
}
}
}
//-----------------------------------------------------------------------------
// Purpose: Finds the best fun fact to display and returns all necessary information through the parameters
//-----------------------------------------------------------------------------
bool CCSFunFactMgr::GetRoundEndFunFact( int iWinningTeam, e_RoundEndReason iRoundResult, FunFact& funfact )
{
//No fun fact for surrender
if ( iRoundResult == CTs_Surrender || iRoundResult == Terrorists_Surrender )
{
return false;
}
FunFactVector validFunFacts;
// Generate a vector of all valid fun facts for this round
FOR_EACH_MAP( m_funFactDatabase, i )
{
FunFact funFact;
if ( m_funFactDatabase[i].pEvaluator->Evaluate(iRoundResult, validFunFacts) )
{
m_funFactDatabase[i].iOccurrences++;
}
}
m_numRounds++;
if (validFunFacts.Count() == 0)
return false;
// pick the fun fact with the highest score
float fBestScore = -FLT_MAX;
int iFunFactIndex = -1;
#if DEBUG_FUNFACT_SCORING
Msg("Scoring fun facts:\n");
#endif
FOR_EACH_VEC(validFunFacts, i)
{
float fScore = ScoreFunFact(validFunFacts[i]);
#if DEBUG_FUNFACT_SCORING
char szPlayerName[64];
const FunFact& funfact = validFunFacts[i];
if (funfact.iPlayer > 0)
V_strncpy(szPlayerName, ToCSPlayer(UTIL_PlayerByIndex(funfact.iPlayer))->GetPlayerName(), sizeof(szPlayerName));
else
V_strcpy(szPlayerName, "");
Msg("(%5.4f) %s, %s, %i, %i, %i\n", fScore, funfact.szLocalizationToken, szPlayerName, funfact.iData1, funfact.iData2, funfact.iData3);
#endif
if (fScore > fBestScore)
{
fBestScore = fScore;
iFunFactIndex = i;
}
}
if (iFunFactIndex < 0)
return false;
funfact = validFunFacts[iFunFactIndex];
// decay player cooldowns
for (int i = 0; i < ARRAYSIZE(m_playerCooldown); ++i )
{
m_playerCooldown[i] *= (1.0f - kCooldownRatePlayer);
}
// decay funfact cooldowns
FOR_EACH_MAP(m_funFactDatabase, i)
{
m_funFactDatabase[i].fCooldown *= (1.0f - kCooldownRateFunFact);
}
// set player cooldown for player in funfact
m_playerCooldown[funfact.iPlayer] = 1.0f;
// set funfact cooldown for current funfact
m_funFactDatabase[m_funFactDatabase.Find(funfact.id)].fCooldown = 1.0f;
return true;
}
float CCSFunFactMgr::ScoreFunFact( const FunFact& funfact )
{
float fScore = 0.0f;
const FunFactDatabaseEntry& dbEntry = m_funFactDatabase[m_funFactDatabase.Find(funfact.id)];
// add the coolness score for the funfact
fScore += kWeightCoolness * dbEntry.pEvaluator->GetCoolness() * (1.0f + funfact.fMagnitude);
// subtract the cooldown for the funfact
fScore -= kWeightFunFactCooldown * dbEntry.fCooldown;
// subtract the cooldown for the player
fScore -= kWeightPlayerCooldown * m_playerCooldown[funfact.iPlayer];
// add the rarity bonus
fScore += kWeightRarity * powf((1.0f - (float)dbEntry.iOccurrences / m_numRounds), 4.0f);
return fScore;
}
+47
View File
@@ -0,0 +1,47 @@
#ifndef FUNFACTMGR_H
#define FUNFACTMGR_H
#ifdef _WIN32
#pragma once
#endif
#include "GameEventListener.h"
#include "funfact_cs.h"
#include "utlmap.h"
#include "cs_shareddefs.h"
class FunFactEvaluator;
class CCSFunFactMgr : public CAutoGameSystemPerFrame, public CGameEventListener
{
public:
CCSFunFactMgr();
~CCSFunFactMgr();
virtual bool Init();
virtual void Shutdown();
virtual void Update( float frametime );
bool GetRoundEndFunFact( int iWinningTeam, e_RoundEndReason iRoundResult, FunFact& funfact );
protected:
float ScoreFunFact( const FunFact& funfact );
void FireGameEvent( IGameEvent *event );
private:
// Weights for all players. Updated every round
// index 0 is for "all players" funfacts, and has the same cooldown behavior as for individual players
float m_playerCooldown[MAX_PLAYERS + 1];
struct FunFactDatabaseEntry
{
const FunFactEvaluator* pEvaluator;
int iOccurrences;
float fCooldown;
};
CUtlMap<int, FunFactDatabaseEntry> m_funFactDatabase;
int m_numRounds;
};
#endif
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,312 @@
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//
//=============================================================================//
// cs_simple_hostage.h
// Simple CS1.6 level hostage
// Author: Michael S. Booth, July 2004
#ifndef _CS_SIMPLE_HOSTAGE_H_
#define _CS_SIMPLE_HOSTAGE_H_
#include "nav_mesh.h"
#include "cs_nav_path.h"
#include "cs_nav_pathfind.h"
#include "improv_locomotor.h"
#include "cs_playeranimstate.h"
#include "ai_speech.h"
class CCSPlayer;
class CHostageExpresserShim : public CBaseCombatCharacter
{
public:
inline CAI_Expresser *GetExpresser( void ) { return m_pExpresser; }
inline const CAI_Expresser *GetMultiplayerExpresser( void ) const { return m_pExpresser; }
protected:
CAI_Expresser *m_pExpresser;
};
class CHostageCarriableProp : public CBaseAnimating
{
public:
DECLARE_CLASS( CHostageCarriableProp, CBaseAnimating );
DECLARE_DATADESC();
DECLARE_SERVERCLASS();
DECLARE_PREDICTABLE();
};
//----------------------------------------------------------------------------------------------------------------
/**
* A Counter-Strike Hostage
*/
class CHostage : public CAI_ExpresserHost< CHostageExpresserShim >, public CImprovLocomotor, public ICSPlayerAnimStateHelpers
{
public:
DECLARE_CLASS( CHostage, CHostageExpresserShim );
DECLARE_ENT_SCRIPTDESC();
DECLARE_SERVERCLASS();
DECLARE_DATADESC();
CHostage( void );
virtual ~CHostage();
public:
virtual void Spawn( void );
virtual void Precache();
int ObjectCaps( void ) { return (BaseClass::ObjectCaps() | FCAP_IMPULSE_USE); } // make hostage "useable"
virtual bool KeyValue( const char *szKeyName, const char *szValue );
virtual void PhysicsSimulate( void );
virtual int OnTakeDamage_Alive( const CTakeDamageInfo &info );
virtual void TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr );
virtual void Event_Killed( const CTakeDamageInfo &info );
virtual void Touch( CBaseEntity *other ); // in contact with "other"
void HostageRescueZoneTouch( inputdata_t &inputdata ); // invoked when hostage touches a rescue zone
void HostageThink( void ); // periodic update to initiate behaviors
void GiveCTUseBonus( CCSPlayer *rescuer ); // give bonus to CT's for talking to a hostage
void CheckForHostageAbuse( CCSPlayer *player ); // check for hostage-killer abuse
// queries
bool IsBeingCarried( void );
bool IsFollowingSomeone( void );
bool IsFollowing( const CBaseEntity *entity );
bool IsValid( void ) const;
bool IsRescuable( void ) const;
bool IsRescued( void ) const;
bool IsOnGround( void ) const;
bool IsVisible( const Vector &pos, bool testFOV = false ) const; // return true if hostage can see position
// hostage states
void Idle( void ); // stand idle
void Follow( CCSPlayer *leader ); // begin following "leader"
CCSPlayer *GetLeader( void ) const; // return our leader, or NULL
void DropHostage( Vector vecPosition, bool bIsRescued = false );
void FaceTowards( const Vector &target, float deltaT ); // rotate body to face towards "target"
void ApplyForce( const Vector &force ) { m_accel += force; } // apply a force to the hostage
unsigned int PhysicsSolidMaskForEntity() const;
Class_T Classify ( void ) { return CLASS_PLAYER_ALLY; }
public:
// begin CImprovLocomotor -----------------------------------------------------------------------------------------------------------------
virtual const Vector &GetCentroid( void ) const;
virtual const Vector &GetFeet( void ) const; // return position of "feet" - point below centroid of improv at feet level
virtual const Vector &GetEyes( void ) const;
virtual float GetMoveAngle( void ) const; // return direction of movement
virtual CNavArea *GetLastKnownArea( void ) const;
virtual bool GetSimpleGroundHeightWithFloor( const Vector &pos, float *height, Vector *normal = NULL ); // find "simple" ground height, treating current nav area as part of the floor
virtual void Crouch( void );
virtual void StandUp( void ); // "un-crouch"
virtual bool IsCrouching( void ) const;
virtual void Jump( void ); // initiate a jump
virtual bool IsJumping( void ) const;
virtual void Run( void ); // set movement speed to running
virtual void Walk( void ); // set movement speed to walking
virtual bool IsRunning( void ) const;
virtual void StartLadder( const CNavLadder *ladder, NavTraverseType how, const Vector &approachPos, const Vector &departPos ); // invoked when a ladder is encountered while following a path
virtual bool TraverseLadder( const CNavLadder *ladder, NavTraverseType how, const Vector &approachPos, const Vector &departPos, float deltaT ); // traverse given ladder
virtual bool IsUsingLadder( void ) const;
virtual void TrackPath( const Vector &pathGoal, float deltaT ); // move along path by following "pathGoal"
virtual void OnMoveToSuccess( const Vector &goal ); // invoked when an improv reaches its MoveTo goal
virtual void OnMoveToFailure( const Vector &goal, MoveToFailureType reason ); // invoked when an improv fails to reach a MoveTo goal
// end CImprovLocomotor -------------------------------------------------------------------------------------------------------------------
public:
virtual CWeaponCSBase* CSAnim_GetActiveWeapon();
virtual bool CSAnim_CanMove();
virtual void ModifyOrAppendCriteria( AI_CriteriaSet& set );
int GetHostageState( void ) { return m_nHostageState; }
uint32 GetHostageSpawnExclusionGroup() const { return m_uiHostageSpawnExclusionGroupMask; }
uint32 GetHostageSpawnRandomFactor() const { return m_nHostageSpawnRandomFactor; }
COutputEvent m_OnHostageBeginGrab;
COutputEvent m_OnFirstPickedUp;
COutputEvent m_OnDroppedNotRescued;
COutputEvent m_OnRescued;
protected:
virtual CAI_Expresser *CreateExpresser( void );
virtual void HostageUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value );
void SetHostageStartFollowingPlayer( CCSPlayer *pPlayer );
void SmoothlyDropHostageToGround( Vector vecPosition );
private:
uint32 m_uiHostageSpawnExclusionGroupMask; // Mask of hostage spawn exclusion groups
uint32 m_nHostageSpawnRandomFactor; // Additional weight when building random spawn locations
bool m_bRemove; // We've exceeded MAX_HOSTAGES; delete excess entities upon spawn.
float GetModifiedDamage( float flDamage, int nHitGroup );
IPlayerAnimState *m_PlayerAnimState;
IMPLEMENT_NETWORK_VAR_FOR_DERIVED( m_iMaxHealth );
IMPLEMENT_NETWORK_VAR_FOR_DERIVED( m_iHealth );
IMPLEMENT_NETWORK_VAR_FOR_DERIVED( m_lifeState );
IMPLEMENT_NETWORK_VAR_FOR_DERIVED( m_fFlags );
CNetworkVar( Vector, m_vel );
CNetworkVar( bool, m_isRescued ); // true if the hostage has been rescued
CNetworkVar( bool, m_jumpedThisFrame );
CNetworkVar( int, m_nHostageState );
CNetworkVar( EHANDLE, m_leader ); // the player we are following
void UpdateFollowing( float deltaT ); // do following behavior
int m_lastLeaderID;
CountdownTimer m_reuseTimer; // to throttle how often hostage can be used
bool m_hasBeenUsed; // flag to give first rescuer bonus money
Vector m_accel;
bool m_isRunning; // true if hostage move speed is to run (walk if false)
bool m_isCrouching; // true if hostage is crouching
CountdownTimer m_jumpTimer; // if zero, we can jump
bool m_isWaitingForLeader; // true if we are waiting for our rescuer to move
CCSNavPath m_path; // current path to follow
CountdownTimer m_repathTimer; // throttle pathfinder
CountdownTimer m_inhibitDoorTimer;
CNavPathFollower m_pathFollower; // path tracking mechanism
CountdownTimer m_inhibitObstacleAvoidanceTimer; // when active, turn off path following feelers
CNavArea *m_lastKnownArea; // last area we were in
void Wiggle( void ); // attempt to wiggle-out of begin stuck
CountdownTimer m_wiggleTimer; // for wiggling
NavRelativeDirType m_wiggleDirection;
bool m_isAdjusted; // hack for adjusting bounding box
// Info for defusing.
bool m_bHandsHaveBeenCut;
//bool m_bBeingGrabbed;
CHandle<CCSPlayer> m_pHostageGrabber;
float m_fLastGrabTime;
float m_flGrabbingLength; //How long does the defuse take? Depends on if a defuser was used
Vector m_vecPositionWhenStartedDroppingToGround; // Where was the hostage when he started dropping to ground
Vector m_vecGrabbedPos;
CNetworkVar( float, m_flRescueStartTime );
CNetworkVar( float, m_flGrabSuccessTime ); //What time did the grabbing succeed?
CNetworkVar( float, m_flDropStartTime ); //What time did the grabbing succeed?
void PushawayThink( void ); // pushes physics objects away from the hostage
void AvoidPhysicsProps( void ); // guides the hostage away from physics props
};
//--------------------------------------------------------------------------------------------------------------
/**
* Functor used with NavAreaBuildPath() for building Hostage paths.
* Once we hook up crouching and ladders, this can be removed and ShortestPathCost() can be used instead.
*/
class HostagePathCost
{
public:
float operator() ( CNavArea *area, CNavArea *fromArea, const CNavLadder *ladder, const CFuncElevator *elevator, float length )
{
if (fromArea == NULL)
{
// first area in path, no cost
return 0.0f;
}
else
{
// if this area isn't available to hostages, skip it
if ( area->GetAttributes() & NAV_MESH_NO_HOSTAGES )
{
return -1.0f;
}
// compute distance travelled along path so far
float dist;
if (ladder)
{
// can't traverse ladders
return -1.0f;
}
else
{
dist = (area->GetCenter() - fromArea->GetCenter()).Length();
}
float cost = dist + fromArea->GetCostSoFar();
if (area->GetAttributes() & NAV_MESH_CROUCH) // && !(area->GetAttributes() & NAV_MESH_JUMP))
{
// can't traverse areas that require crouching
return -1.0f;
}
// if this is a "jump" area, add penalty
if (area->GetAttributes() & NAV_MESH_JUMP)
{
const float jumpPenalty = 5.0f;
cost += jumpPenalty * dist;
}
return cost;
}
}
};
//--------------------------------------------------------------------------------------------------------------
// All the hostage entities.
extern CUtlVector< CHostage * > g_Hostages;
//--------------------------------------------------------------------------------------------------------------
/**
* Iterate over all active hostages in the game, invoking functor on each.
* If functor returns false, stop iteration and return false.
*/
template < typename Functor >
bool ForEachHostage( Functor &func )
{
for( int i=0; i<g_Hostages.Count(); ++i )
{
CHostage *hostage = g_Hostages[i];
if ( hostage == NULL || !hostage->IsValid() )
continue;
if ( func( hostage ) == false )
return false;
}
return true;
}
#endif // _CS_SIMPLE_HOSTAGE_H_
@@ -0,0 +1,21 @@
//====== Copyright © 1996-2004, Valve Corporation, All rights reserved. =======
//
// Purpose:
//
//=============================================================================
#include "cbase.h"
#include "info_view_parameters.h"
// NOTE: This has to be the last file included!
#include "tier0/memdbgon.h"
BEGIN_DATADESC( CInfoViewParameters )
DEFINE_KEYFIELD( m_nViewMode, FIELD_INTEGER, "ViewMode" )
END_DATADESC()
LINK_ENTITY_TO_CLASS( info_view_parameters, CInfoViewParameters );
@@ -0,0 +1,24 @@
//====== Copyright © 1996-2004, Valve Corporation, All rights reserved. =======
//
// Purpose:
//
//=============================================================================
#ifndef INFO_VIEW_PARAMETERS_H
#define INFO_VIEW_PARAMETERS_H
#ifdef _WIN32
#pragma once
#endif
class CInfoViewParameters : public CBaseEntity
{
public:
DECLARE_CLASS( CInfoViewParameters, CBaseEntity );
DECLARE_DATADESC();
int m_nViewMode;
};
#endif // INFO_VIEW_PARAMETERS_H
+187
View File
@@ -0,0 +1,187 @@
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//
//=============================================================================//
#include "cbase.h"
#include "items.h"
#include "cs_player.h"
#include "weapon_csbase.h"
#include "cs_ammodef.h"
//-----------------------------------------------------------------------------
class CItemAmmo : public CItem
{
public:
DECLARE_CLASS( CItemAmmo, CItem );
bool MyTouch( CBasePlayer *pBasePlayer )
{
CCSPlayer *pPlayer = dynamic_cast< CCSPlayer* >( pBasePlayer );
if ( !pPlayer )
{
Assert( false );
return false;
}
int ammoIndex = GetCSAmmoDef()->Index( GetAmmoName() );
if ( ammoIndex < 0 )
{
Assert( false );
return false;
}
pPlayer->GiveAmmo( GetCSAmmoDef()->GetBuySize( ammoIndex ), ammoIndex );
return true;
}
virtual const char * GetAmmoName( void ) const { return NULL; }
};
//-----------------------------------------------------------------------------
class CItemAmmo50AE : public CItemAmmo
{
public:
DECLARE_CLASS( CItemAmmo50AE, CItemAmmo );
virtual const char * GetAmmoName( void ) const { return BULLET_PLAYER_50AE; }
};
LINK_ENTITY_TO_CLASS( ammo_50ae, CItemAmmo50AE );
//-----------------------------------------------------------------------------
class CItemAmmo762MM : public CItemAmmo
{
public:
DECLARE_CLASS( CItemAmmo762MM, CItemAmmo );
virtual const char * GetAmmoName( void ) const { return BULLET_PLAYER_762MM; }
};
LINK_ENTITY_TO_CLASS( ammo_762mm, CItemAmmo762MM );
//-----------------------------------------------------------------------------
class CItemAmmo556MM : public CItemAmmo
{
public:
DECLARE_CLASS( CItemAmmo556MM, CItemAmmo );
virtual const char * GetAmmoName( void ) const { return BULLET_PLAYER_556MM; }
};
LINK_ENTITY_TO_CLASS( ammo_556mm, CItemAmmo556MM );
class CItemAmmo556MM_SMALL : public CItemAmmo
{
public:
DECLARE_CLASS( CItemAmmo556MM_SMALL, CItemAmmo );
virtual const char * GetAmmoName( void ) const { return BULLET_PLAYER_556MM_SMALL; }
};
LINK_ENTITY_TO_CLASS( ammo_556mm_small, CItemAmmo556MM_SMALL );
//-----------------------------------------------------------------------------
class CItemAmmo556MM_BOX : public CItemAmmo
{
public:
DECLARE_CLASS( CItemAmmo556MM_BOX, CItemAmmo );
virtual const char * GetAmmoName( void ) const { return BULLET_PLAYER_556MM_BOX; }
};
LINK_ENTITY_TO_CLASS( ammo_556mm_box, CItemAmmo556MM_BOX );
//-----------------------------------------------------------------------------
class CItemAmmo338MAG : public CItemAmmo
{
public:
DECLARE_CLASS( CItemAmmo338MAG, CItemAmmo );
virtual const char * GetAmmoName( void ) const { return BULLET_PLAYER_338MAG; }
};
LINK_ENTITY_TO_CLASS( ammo_338mag, CItemAmmo338MAG );
//-----------------------------------------------------------------------------
class CItemAmmo9MM : public CItemAmmo
{
public:
DECLARE_CLASS( CItemAmmo9MM, CItemAmmo );
virtual const char * GetAmmoName( void ) const { return BULLET_PLAYER_9MM; }
};
LINK_ENTITY_TO_CLASS( ammo_9mm, CItemAmmo9MM );
//-----------------------------------------------------------------------------
class CItemAmmoBuckshot : public CItemAmmo
{
public:
DECLARE_CLASS( CItemAmmoBuckshot, CItemAmmo );
virtual const char * GetAmmoName( void ) const { return BULLET_PLAYER_BUCKSHOT; }
};
LINK_ENTITY_TO_CLASS( ammo_buckshot, CItemAmmoBuckshot );
//-----------------------------------------------------------------------------
class CItemAmmo45ACP : public CItemAmmo
{
public:
DECLARE_CLASS( CItemAmmo45ACP, CItemAmmo );
virtual const char * GetAmmoName( void ) const { return BULLET_PLAYER_45ACP; }
};
LINK_ENTITY_TO_CLASS( ammo_45acp, CItemAmmo45ACP );
//-----------------------------------------------------------------------------
class CItemAmmo357SIG : public CItemAmmo
{
public:
DECLARE_CLASS( CItemAmmo357SIG, CItemAmmo );
virtual const char * GetAmmoName( void ) const { return BULLET_PLAYER_357SIG; }
};
LINK_ENTITY_TO_CLASS( ammo_357sig, CItemAmmo357SIG );
//-----------------------------------------------------------------------------
class CItemAmmo357SIG_P250 : public CItemAmmo
{
public:
DECLARE_CLASS( CItemAmmo357SIG_P250, CItemAmmo );
virtual const char * GetAmmoName( void ) const { return BULLET_PLAYER_357SIG_P250; }
};
LINK_ENTITY_TO_CLASS( ammo_357sig_p250, CItemAmmo357SIG_P250 );
//-----------------------------------------------------------------------------
class CItemAmmo357SIG_SMALL : public CItemAmmo
{
public:
DECLARE_CLASS( CItemAmmo357SIG_SMALL, CItemAmmo );
virtual const char * GetAmmoName( void ) const { return BULLET_PLAYER_357SIG_SMALL; }
};
LINK_ENTITY_TO_CLASS( ammo_357sig_small, CItemAmmo357SIG_SMALL );
//-----------------------------------------------------------------------------
class CItemAmmo357SIG_MIN : public CItemAmmo
{
public:
DECLARE_CLASS( CItemAmmo357SIG_MIN, CItemAmmo );
virtual const char * GetAmmoName( void ) const { return BULLET_PLAYER_357SIG_MIN; }
};
LINK_ENTITY_TO_CLASS( ammo_357sig_np_reserve, CItemAmmo357SIG_MIN );
//-----------------------------------------------------------------------------
class CItemAmmo57MM : public CItemAmmo
{
public:
DECLARE_CLASS( CItemAmmo57MM, CItemAmmo );
virtual const char * GetAmmoName( void ) const { return BULLET_PLAYER_57MM; }
};
LINK_ENTITY_TO_CLASS( ammo_57mm, CItemAmmo57MM );
//-----------------------------------------------------------------------------
+117
View File
@@ -0,0 +1,117 @@
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//
//=============================================================================//
#include "cbase.h"
#include "items.h"
#include "cs_player.h"
class CItemAssaultSuit : public CItem
{
void Spawn( void )
{
Precache( );
CItem::Spawn( );
}
void Precache( void )
{
PrecacheScriptSound( "BaseCombatCharacter.ItemPickup2" );
}
bool MyTouch( CBasePlayer *pBasePlayer )
{
CCSPlayer *pPlayer = dynamic_cast< CCSPlayer* >( pBasePlayer );
if ( !pPlayer )
{
Assert( false );
return false;
}
pPlayer->m_bHasHelmet = true;
pPlayer->SetArmorValue( 100 );
pPlayer->RecalculateCurrentEquipmentValue();
if ( pPlayer->IsAlive() )
{
CBroadcastRecipientFilter filter;
if (pPlayer->GetTeamNumber() == TEAM_CT)
{
// Play the CT Suit sound
EmitSound(filter, entindex(), "Player.EquipArmor_CT");
}
else
{
// Play the T Suit sound
EmitSound(filter, entindex(), "Player.EquipArmor_T");
}
//EmitSound( filter, entindex(), "BaseCombatCharacter.ItemPickup2" );
}
return true;
}
};
LINK_ENTITY_TO_CLASS( item_assaultsuit, CItemAssaultSuit );
class CItemHeavyAssaultSuit : public CItemAssaultSuit
{
// void Spawn( void )
// {
// Precache();
// CItem::Spawn();
// }
// void Precache( void )
// {
// PrecacheScriptSound( "BaseCombatCharacter.ItemPickup2" );
// }
bool MyTouch( CBasePlayer *pBasePlayer )
{
CCSPlayer *pPlayer = dynamic_cast< CCSPlayer* >( pBasePlayer );
if ( !pPlayer )
{
Assert( false );
return false;
}
pPlayer->m_bHasHelmet = true;
pPlayer->SetArmorValue( 200 );
pPlayer->RecalculateCurrentEquipmentValue();
pPlayer->m_bHasHeavyArmor = true;
if ( pPlayer->IsAlive() )
{
CBroadcastRecipientFilter filter;
if (pPlayer->GetTeamNumber() == TEAM_CT)
{
// Play the CT Suit sound
pPlayer->EmitSound("Player.EquipArmor_CT");
}
else
{
// Play the T Suit sound
pPlayer->EmitSound("Player.EquipArmor_T");
}
//EmitSound( filter, entindex(), "BaseCombatCharacter.ItemPickup2" );
}
return true;
}
};
LINK_ENTITY_TO_CLASS( item_heavyassaultsuit, CItemHeavyAssaultSuit );
+157
View File
@@ -0,0 +1,157 @@
//========= Copyright 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose: Defuser kit that drops from counter-strike CTS
//
//=============================================================================//
#include "cbase.h"
#include "items.h"
#include "cs_player.h"
#include "cs_gamerules.h"
#include "cs_entity_spotting.h"
class CItemDefuser : public CItem
{
public:
DECLARE_CLASS( CItemDefuser, CItem );
CItemDefuser();
~CItemDefuser();
void Spawn( void );
void Precache( void );
void DefuserTouch( CBaseEntity *pOther );
void ActivateThink( void );
DECLARE_DATADESC();
};
LINK_ENTITY_TO_CLASS( item_defuser, CItemDefuser );
LINK_ENTITY_TO_CLASS( item_cutters, CItemDefuser );
PRECACHE_REGISTER(item_defuser);
BEGIN_DATADESC( CItemDefuser )
//Functions
DEFINE_THINKFUNC( ActivateThink ),
DEFINE_ENTITYFUNC( DefuserTouch ),
END_DATADESC()
Vector g_vecDefuserPosition = vec3_origin;
CBaseEntity* g_pDefuserEntity = NULL;
CItemDefuser::CItemDefuser()
{
g_pDefuserEntity = this;
SetSpotRules( CCSEntitySpotting::SPOT_RULE_T | CCSEntitySpotting::SPOT_RULE_ALWAYS_SEEN_BY_CT );
}
CItemDefuser::~CItemDefuser()
{
if ( g_pDefuserEntity == this )
{
g_pDefuserEntity = NULL;
g_vecDefuserPosition = vec3_origin;
}
}
void CItemDefuser::Spawn( void )
{
Precache( );
SetModel( "models/weapons/w_defuser.mdl" );
BaseClass::Spawn();
#if !defined( CLIENT_DLL )
if ( mp_defuser_allocation.GetInt() == DefuserAllocation::Random )
{
IGameEvent * event = gameeventmanager->CreateEvent( "defuser_dropped" );
if ( event )
{
event->SetInt( "entityid", entindex() );
event->SetInt( "priority", 0 ); //defuser_dropped
gameeventmanager->FireEvent( event );
}
}
g_vecDefuserPosition = GetAbsOrigin();
#endif
SetNextThink( gpGlobals->curtime + 0.5f );
SetThink( &CItemDefuser::ActivateThink );
SetTouch( NULL );
}
void CItemDefuser::Precache( void )
{
PrecacheModel( "models/weapons/w_defuser.mdl" );
PrecacheScriptSound( "BaseCombatCharacter.ItemPickup2" );
}
void CItemDefuser::ActivateThink( void )
{
//since we can't stop the item from being touched while its in the air,
//activate 1 second after being dropped
SetTouch( &CItemDefuser::DefuserTouch );
SetThink( NULL );
}
void CItemDefuser::DefuserTouch( CBaseEntity *pOther )
{
if ( !pOther->IsPlayer() )
{
return;
}
//if( GetFlags() & FL_ONGROUND )
{
CCSPlayer *pPlayer = (CCSPlayer *)pOther;
if ( !pPlayer )
{
Assert( false );
return;
}
if( pPlayer->GetTeamNumber() == TEAM_CT && !pPlayer->HasDefuser() )
{
// [dwenger] Added for fun-fact support
pPlayer->GiveDefuser( true );
#if !defined( CLIENT_DLL )
if ( mp_defuser_allocation.GetInt() == DefuserAllocation::Random )
{
IGameEvent * event = gameeventmanager->CreateEvent( "defuser_pickup" );
if ( event )
{
event->SetInt( "entityid", entindex() );
event->SetInt( "userid", pPlayer->GetUserID() );
event->SetInt( "priority", 0 ); //defuser_pickup
gameeventmanager->FireEvent( event );
}
}
#endif
// if ( pPlayer->IsDead() == false )
// {
// CBroadcastRecipientFilter filter;
// EmitSound( filter, entindex(), "BaseCombatCharacter.ItemPickup2" );
// }
UTIL_Remove( this );
return;
}
}
}

Some files were not shown because too many files have changed in this diff Show More