initial
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -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
@@ -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
@@ -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 );
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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() );
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
|
||||
@@ -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
@@ -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
|
||||
//
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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.
|
||||
};
|
||||
@@ -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[];
|
||||
@@ -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 );
|
||||
|
||||
@@ -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
|
||||
@@ -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 )
|
||||
{
|
||||
|
||||
}
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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_
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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_
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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_
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
@@ -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_
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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 );
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
@@ -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
|
||||
@@ -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()
|
||||
@@ -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();
|
||||
};
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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 );
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
@@ -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 );
|
||||
|
||||
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user