summaryrefslogtreecommitdiff
path: root/game/server/cstrike/bot/cs_bot_chatter.h
blob: 47ba8020e7bd74c1d7746e9a60163480a6cecac8 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: Bot radio chatter system
//
// $NoKeywords: $
//=============================================================================//

// Author: Michael S. Booth ([email protected]), 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
const Vector *GetRandomSpotAtPlace( Place place );

//----------------------------------------------------------------------------------------------------
/**
 * 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
	// It is a best practice to always have a virtual destructor in an interface
	// class. Otherwise if the derived classes have destructors they will not be
	// called.
	virtual ~BotMeme() {}
	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

	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

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;
};
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;
}

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();
		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 );
	~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 );

	void EnemySpotted( void );									///< report enemy sightings
	void KilledMyEnemy( int victimID );
	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

	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 );

	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( 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
};

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 );
}




#endif // CS_BOT_CHATTER_H