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
|
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
#ifndef AI_SPEECH_H
#define AI_SPEECH_H
#include "utlmap.h"
#include "soundflags.h"
#include "AI_ResponseSystem.h"
#include "utldict.h"
#if defined( _WIN32 )
#pragma once
#endif
class KeyValues;
class AI_CriteriaSet;
//-----------------------------------------------------------------------------
// Purpose: Used to share a global resource or prevent a system stepping on
// own toes.
//-----------------------------------------------------------------------------
class CAI_TimedSemaphore
{
public:
CAI_TimedSemaphore()
: m_ReleaseTime( 0 )
{
m_hCurrentTalker = NULL;
}
void Acquire( float time, CBaseEntity *pTalker ) { m_ReleaseTime = gpGlobals->curtime + time; m_hCurrentTalker = pTalker; }
void Release() { m_ReleaseTime = 0; m_hCurrentTalker = NULL; }
// Current owner of the semaphore is always allowed to talk
bool IsAvailable( CBaseEntity *pTalker ) const { return ((gpGlobals->curtime > m_ReleaseTime) || (m_hCurrentTalker == pTalker)); }
float GetReleaseTime() const { return m_ReleaseTime; }
CBaseEntity *GetOwner() { return m_hCurrentTalker; }
private:
float m_ReleaseTime;
EHANDLE m_hCurrentTalker;
};
//-----------------------------------------------------------------------------
extern CAI_TimedSemaphore g_AIFriendliesTalkSemaphore;
extern CAI_TimedSemaphore g_AIFoesTalkSemaphore;
#define GetSpeechSemaphore( pNpc ) (((pNpc)->IsPlayerAlly()) ? &g_AIFriendliesTalkSemaphore : &g_AIFoesTalkSemaphore )
//-----------------------------------------------------------------------------
// Basic speech system types
//-----------------------------------------------------------------------------
//-------------------------------------
// Constants
const float AIS_DEF_MIN_DELAY = 2.8; // Minimum amount of time an NPCs will wait after someone has spoken before considering speaking again
const float AIS_DEF_MAX_DELAY = 3.2; // Maximum amount of time an NPCs will wait after someone has spoken before considering speaking again
const float AIS_NO_DELAY = 0;
const soundlevel_t AIS_DEF_SNDLVL = SNDLVL_TALKING;
#define AI_NULL_CONCEPT NULL
#define AI_NULL_SENTENCE NULL
// Sentence prefix constants
#define AI_SP_SPECIFIC_SENTENCE '!'
#define AI_SP_WAVFILE '^'
#define AI_SP_SCENE_GROUP '='
#define AI_SP_SPECIFIC_SCENE '?'
#define AI_SPECIFIC_SENTENCE(str_constant) "!" str_constant
#define AI_WAVFILE(str_constant) "^" str_constant
// @Note (toml 09-12-02): as scene groups are not currently implemented, the string is a semi-colon delimited list
#define AI_SCENE_GROUP(str_constant) "=" str_constant
#define AI_SPECIFIC_SCENE(str_constant) "?" str_constant
// Designer overriding modifiers
#define AI_SPECIFIC_SCENE_MODIFIER "scene:"
//-------------------------------------
//-------------------------------------
// An id that represents the core meaning of a spoken phrase,
// eventually to be mapped to a sentence group or scene
typedef const char *AIConcept_t;
inline bool CompareConcepts( AIConcept_t c1, AIConcept_t c2 )
{
return ( (void *)c1 == (void *)c2 || ( c1 && c2 && Q_stricmp( c1, c2 ) == 0 ) );
}
//-------------------------------------
// Specifies and stores the base timing and attentuation values for concepts
//
class AI_Response;
//-----------------------------------------------------------------------------
// CAI_Expresser
//
// Purpose: Provides the functionality of going from abstract concept ("hello")
// to specific sentence/scene/wave
//
//-------------------------------------
// Sink supports behavior control and receives notifications of internal events
class CAI_ExpresserSink
{
public:
virtual void OnSpokeConcept( AIConcept_t concept, AI_Response *response ) {};
virtual void OnStartSpeaking() {}
virtual bool UseSemaphore() { return true; }
};
struct ConceptHistory_t
{
DECLARE_SIMPLE_DATADESC();
ConceptHistory_t(float timeSpoken = -1 )
: timeSpoken( timeSpoken ), response( NULL )
{
}
ConceptHistory_t( const ConceptHistory_t& src );
ConceptHistory_t& operator = ( const ConceptHistory_t& src );
~ConceptHistory_t();
float timeSpoken;
AI_Response *response;
};
//-------------------------------------
class CAI_Expresser : public IResponseFilter
{
public:
CAI_Expresser( CBaseFlex *pOuter = NULL );
~CAI_Expresser();
// --------------------------------
bool Connect( CAI_ExpresserSink *pSink ) { m_pSink = pSink; return true; }
bool Disconnect( CAI_ExpresserSink *pSink ) { m_pSink = NULL; return true;}
void TestAllResponses();
// --------------------------------
bool Speak( AIConcept_t concept, const char *modifiers = NULL, char *pszOutResponseChosen = NULL, size_t bufsize = 0, IRecipientFilter *filter = NULL );
// These two methods allow looking up a response and dispatching it to be two different steps
bool SpeakFindResponse( AI_Response &response, AIConcept_t concept, const char *modifiers = NULL );
bool SpeakDispatchResponse( AIConcept_t concept, AI_Response &response, IRecipientFilter *filter = NULL );
float GetResponseDuration( AI_Response &response );
virtual int SpeakRawSentence( const char *pszSentence, float delay, float volume = VOL_NORM, soundlevel_t soundlevel = SNDLVL_TALKING, CBaseEntity *pListener = NULL );
bool SemaphoreIsAvailable( CBaseEntity *pTalker );
float GetSemaphoreAvailableTime( CBaseEntity *pTalker );
// --------------------------------
virtual bool IsSpeaking();
bool CanSpeak();
bool CanSpeakAfterMyself();
float GetTimeSpeechComplete() const { return m_flStopTalkTime; }
void BlockSpeechUntil( float time );
// --------------------------------
bool CanSpeakConcept( AIConcept_t concept );
bool SpokeConcept( AIConcept_t concept );
float GetTimeSpokeConcept( AIConcept_t concept ); // returns -1 if never
void SetSpokeConcept( AIConcept_t concept, AI_Response *response, bool bCallback = true );
void ClearSpokeConcept( AIConcept_t concept );
// --------------------------------
void SetVoicePitch( int voicePitch ) { m_voicePitch = voicePitch; }
int GetVoicePitch() const;
void NoteSpeaking( float duration, float delay = 0 );
// Force the NPC to release the semaphore & clear next speech time
void ForceNotSpeaking( void );
protected:
CAI_TimedSemaphore *GetMySpeechSemaphore( CBaseEntity *pNpc );
bool SpeakRawScene( const char *pszScene, float delay, AI_Response *response, IRecipientFilter *filter = NULL );
// This will create a fake .vcd/CChoreoScene to wrap the sound to be played
bool SpeakAutoGeneratedScene( char const *soundname, float delay );
void DumpHistories();
void SpeechMsg( CBaseEntity *pFlex, PRINTF_FORMAT_STRING const char *pszFormat, ... );
// --------------------------------
CAI_ExpresserSink *GetSink() { return m_pSink; }
private:
// --------------------------------
virtual bool IsValidResponse( ResponseType_t type, const char *pszValue );
// --------------------------------
CAI_ExpresserSink *m_pSink;
// --------------------------------
//
// Speech concept data structures
//
CUtlDict< ConceptHistory_t, int > m_ConceptHistories;
// --------------------------------
//
// Speaking states
//
float m_flStopTalkTime; // when in the future that I'll be done saying this sentence.
float m_flStopTalkTimeWithoutDelay; // same as the above, but minus the delay before other people can speak
float m_flBlockedTalkTime;
int m_voicePitch; // pitch of voice for this head
float m_flLastTimeAcceptedSpeak; // because speech may not be blocked until NoteSpeaking called by scene ent, this handles in-think blocking
DECLARE_SIMPLE_DATADESC();
// --------------------------------
//
public:
virtual void SetOuter( CBaseFlex *pOuter ) { m_pOuter = pOuter; }
CBaseFlex * GetOuter() { return m_pOuter; }
const CBaseFlex * GetOuter() const { return m_pOuter; }
private:
CHandle<CBaseFlex> m_pOuter;
};
class CMultiplayer_Expresser : public CAI_Expresser
{
public:
CMultiplayer_Expresser( CBaseFlex *pOuter = NULL );
//~CMultiplayer_Expresser();
virtual bool IsSpeaking();
void AllowMultipleScenes();
void DisallowMultipleScenes();
private:
bool m_bAllowMultipleScenes;
};
//-----------------------------------------------------------------------------
//
// An NPC base class to assist a branch of the inheritance graph
// in utilizing CAI_Expresser
//
template <class BASE_NPC>
class CAI_ExpresserHost : public BASE_NPC, protected CAI_ExpresserSink
{
DECLARE_CLASS_NOFRIEND( CAI_ExpresserHost, BASE_NPC );
public:
virtual void NoteSpeaking( float duration, float delay );
virtual bool Speak( AIConcept_t concept, const char *modifiers = NULL, char *pszOutResponseChosen = NULL, size_t bufsize = 0, IRecipientFilter *filter = NULL );
// These two methods allow looking up a response and dispatching it to be two different steps
bool SpeakFindResponse( AI_Response& response, AIConcept_t concept, const char *modifiers = NULL );
bool SpeakDispatchResponse( AIConcept_t concept, AI_Response& response );
virtual void PostSpeakDispatchResponse( AIConcept_t concept, AI_Response& response ) { return; }
float GetResponseDuration( AI_Response& response );
float GetTimeSpeechComplete() const { return this->GetExpresser()->GetTimeSpeechComplete(); }
bool IsSpeaking() { return this->GetExpresser()->IsSpeaking(); }
bool CanSpeak() { return this->GetExpresser()->CanSpeak(); }
bool CanSpeakAfterMyself() { return this->GetExpresser()->CanSpeakAfterMyself(); }
void SetSpokeConcept( AIConcept_t concept, AI_Response *response, bool bCallback = true ) { this->GetExpresser()->SetSpokeConcept( concept, response, bCallback ); }
float GetTimeSpokeConcept( AIConcept_t concept ) { return this->GetExpresser()->GetTimeSpokeConcept( concept ); }
bool SpokeConcept( AIConcept_t concept ) { return this->GetExpresser()->SpokeConcept( concept ); }
protected:
int PlaySentence( const char *pszSentence, float delay, float volume = VOL_NORM, soundlevel_t soundlevel = SNDLVL_TALKING, CBaseEntity *pListener = NULL );
virtual void ModifyOrAppendCriteria( AI_CriteriaSet& set );
virtual IResponseSystem *GetResponseSystem();
// Override of base entity response input handler
virtual void DispatchResponse( const char *conceptName );
};
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
template <class BASE_NPC>
inline void CAI_ExpresserHost<BASE_NPC>::NoteSpeaking( float duration, float delay )
{
this->GetExpresser()->NoteSpeaking( duration, delay );
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
template <class BASE_NPC>
inline bool CAI_ExpresserHost<BASE_NPC>::Speak( AIConcept_t concept, const char *modifiers /*= NULL*/, char *pszOutResponseChosen /*=NULL*/, size_t bufsize /* = 0 */, IRecipientFilter *filter /* = NULL */ )
{
AssertOnce( this->GetExpresser()->GetOuter() == this );
return this->GetExpresser()->Speak( concept, modifiers, pszOutResponseChosen, bufsize, filter );
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
template <class BASE_NPC>
inline int CAI_ExpresserHost<BASE_NPC>::PlaySentence( const char *pszSentence, float delay, float volume, soundlevel_t soundlevel, CBaseEntity *pListener )
{
return this->GetExpresser()->SpeakRawSentence( pszSentence, delay, volume, soundlevel, pListener );
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
extern void CAI_ExpresserHost_NPC_DoModifyOrAppendCriteria( CAI_BaseNPC *pSpeaker, AI_CriteriaSet& criteriaSet );
template <class BASE_NPC>
inline void CAI_ExpresserHost<BASE_NPC>::ModifyOrAppendCriteria( AI_CriteriaSet& criteriaSet )
{
BaseClass::ModifyOrAppendCriteria( criteriaSet );
if ( this->MyNPCPointer() )
{
CAI_ExpresserHost_NPC_DoModifyOrAppendCriteria( this->MyNPCPointer(), criteriaSet );
}
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
template <class BASE_NPC>
inline IResponseSystem *CAI_ExpresserHost<BASE_NPC>::GetResponseSystem()
{
extern IResponseSystem *g_pResponseSystem;
// Expressive NPC's use the general response system
return g_pResponseSystem;
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
template <class BASE_NPC>
inline bool CAI_ExpresserHost<BASE_NPC>::SpeakFindResponse( AI_Response& response, AIConcept_t concept, const char *modifiers /*= NULL*/ )
{
return this->GetExpresser()->SpeakFindResponse( response, concept, modifiers );
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
template <class BASE_NPC>
inline bool CAI_ExpresserHost<BASE_NPC>::SpeakDispatchResponse( AIConcept_t concept, AI_Response& response )
{
if ( this->GetExpresser()->SpeakDispatchResponse( concept, response ) )
{
PostSpeakDispatchResponse( concept, response );
return true;
}
return false;
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
template <class BASE_NPC>
inline float CAI_ExpresserHost<BASE_NPC>::GetResponseDuration( AI_Response& response )
{
return this->GetExpresser()->GetResponseDuration( response );
}
//-----------------------------------------------------------------------------
// Override of base entity response input handler
//-----------------------------------------------------------------------------
template <class BASE_NPC>
inline void CAI_ExpresserHost<BASE_NPC>::DispatchResponse( const char *conceptName )
{
Speak( (AIConcept_t)conceptName );
}
//-----------------------------------------------------------------------------
#endif // AI_SPEECH_H
|