summaryrefslogtreecommitdiff
path: root/game/server/cstrike/bot/cs_bot_radio.cpp
blob: 08acb4e08166c93ec7f24b7f2fcde9970fe20633 (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
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: 
//
// $NoKeywords: $
//=============================================================================//

// Author: Michael S. Booth ([email protected]), 2003

#include "cbase.h"
#include "cs_bot.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
		const Vector *pos = GetRandomSpotAtPlace( place );
		if (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 - RADIO_START_1 );
	}
	else if (event > RADIO_START_2 && event < RADIO_START_3)
	{
		HandleMenu_Radio2( event - RADIO_START_2 );
	}
	else
	{
		HandleMenu_Radio3( event - RADIO_START_3 );
	}
}


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

	UserMessageBegin ( filter, "RawAudio" );
		WRITE_BYTE( pitch );
		WRITE_BYTE( entindex() );
		WRITE_FLOAT( duration );
		WRITE_STRING( voiceFilename );
	MessageEnd();

	GetChatter()->ResetRadioSilenceDuration();

	m_voiceEndTimestamp = gpGlobals->curtime + duration;
}