1 /*
2 ===========================================================================
3 Copyright (C) 1999-2005 Id Software, Inc.
4 Copyright (C) 2000-2006 Tim Angus
5
6 This file is part of Tremulous.
7
8 Tremulous is free software; you can redistribute it
9 and/or modify it under the terms of the GNU General Public License as
10 published by the Free Software Foundation; either version 2 of the License,
11 or (at your option) any later version.
12
13 Tremulous is distributed in the hope that it will be
14 useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
17
18 You should have received a copy of the GNU General Public License
19 along with Tremulous; if not, write to the Free Software
20 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
21 ===========================================================================
22 */
23
24 // cg_servercmds.c -- reliably sequenced text commands sent by the server
25 // these are processed at snapshot transition time, so there will definately
26 // be a valid snapshot this frame
27
28
29 #include "cg_local.h"
30
31 /*
32 =================
33 CG_ParseScores
34
35 =================
36 */
CG_ParseScores(void)37 static void CG_ParseScores( void )
38 {
39 int i;
40
41 cg.numScores = atoi( CG_Argv( 1 ) );
42
43 if( cg.numScores > MAX_CLIENTS )
44 cg.numScores = MAX_CLIENTS;
45
46 cg.teamScores[ 0 ] = atoi( CG_Argv( 2 ) );
47 cg.teamScores[ 1 ] = atoi( CG_Argv( 3 ) );
48
49 memset( cg.scores, 0, sizeof( cg.scores ) );
50
51 if( cg_debugRandom.integer )
52 CG_Printf( "cg.numScores: %d\n", cg.numScores );
53
54 for( i = 0; i < cg.numScores; i++ )
55 {
56 //
57 cg.scores[ i ].client = atoi( CG_Argv( i * 6 + 4 ) );
58 cg.scores[ i ].score = atoi( CG_Argv( i * 6 + 5 ) );
59 cg.scores[ i ].ping = atoi( CG_Argv( i * 6 + 6 ) );
60 cg.scores[ i ].time = atoi( CG_Argv( i * 6 + 7 ) );
61 cg.scores[ i ].weapon = atoi( CG_Argv( i * 6 + 8 ) );
62 cg.scores[ i ].upgrade = atoi( CG_Argv( i * 6 + 9 ) );
63
64 if( cg.scores[ i ].client < 0 || cg.scores[ i ].client >= MAX_CLIENTS )
65 cg.scores[ i ].client = 0;
66
67 cgs.clientinfo[ cg.scores[ i ].client ].score = cg.scores[ i ].score;
68 cgs.clientinfo[ cg.scores[ i ].client ].powerups = 0;
69
70 cg.scores[ i ].team = cgs.clientinfo[ cg.scores[ i ].client ].team;
71 }
72 }
73
74 /*
75 =================
76 CG_ParseTeamInfo
77
78 =================
79 */
CG_ParseTeamInfo(void)80 static void CG_ParseTeamInfo( void )
81 {
82 int i;
83 int client;
84
85 numSortedTeamPlayers = atoi( CG_Argv( 1 ) );
86
87 for( i = 0; i < numSortedTeamPlayers; i++ )
88 {
89 client = atoi( CG_Argv( i * 6 + 2 ) );
90
91 sortedTeamPlayers[ i ] = client;
92
93 cgs.clientinfo[ client ].location = atoi( CG_Argv( i * 6 + 3 ) );
94 cgs.clientinfo[ client ].health = atoi( CG_Argv( i * 6 + 4 ) );
95 cgs.clientinfo[ client ].armor = atoi( CG_Argv( i * 6 + 5 ) );
96 cgs.clientinfo[ client ].curWeapon = atoi( CG_Argv( i * 6 + 6 ) );
97 cgs.clientinfo[ client ].powerups = atoi( CG_Argv( i * 6 + 7 ) );
98 }
99 }
100
101
102 /*
103 ================
104 CG_ParseServerinfo
105
106 This is called explicitly when the gamestate is first received,
107 and whenever the server updates any serverinfo flagged cvars
108 ================
109 */
CG_ParseServerinfo(void)110 void CG_ParseServerinfo( void )
111 {
112 const char *info;
113 char *mapname;
114
115 info = CG_ConfigString( CS_SERVERINFO );
116 cgs.dmflags = atoi( Info_ValueForKey( info, "dmflags" ) );
117 cgs.teamflags = atoi( Info_ValueForKey( info, "teamflags" ) );
118 cgs.timelimit = atoi( Info_ValueForKey( info, "timelimit" ) );
119 cgs.maxclients = atoi( Info_ValueForKey( info, "sv_maxclients" ) );
120 mapname = Info_ValueForKey( info, "mapname" );
121 Com_sprintf( cgs.mapname, sizeof( cgs.mapname ), "maps/%s.bsp", mapname );
122 }
123
124 /*
125 ==================
126 CG_ParseWarmup
127 ==================
128 */
CG_ParseWarmup(void)129 static void CG_ParseWarmup( void )
130 {
131 const char *info;
132 int warmup;
133
134 info = CG_ConfigString( CS_WARMUP );
135
136 warmup = atoi( info );
137 cg.warmupCount = -1;
138
139 if( warmup == 0 && cg.warmup )
140 {
141 }
142
143 cg.warmup = warmup;
144 }
145
146 /*
147 ================
148 CG_SetConfigValues
149
150 Called on load to set the initial values from configure strings
151 ================
152 */
CG_SetConfigValues(void)153 void CG_SetConfigValues( void )
154 {
155 cgs.scores1 = atoi( CG_ConfigString( CS_SCORES1 ) );
156 cgs.scores2 = atoi( CG_ConfigString( CS_SCORES2 ) );
157
158 sscanf( CG_ConfigString( CS_BUILDPOINTS ),
159 "%d %d %d %d %d", &cgs.alienBuildPoints,
160 &cgs.alienBuildPointsTotal,
161 &cgs.humanBuildPoints,
162 &cgs.humanBuildPointsTotal,
163 &cgs.humanBuildPointsPowered );
164
165 sscanf( CG_ConfigString( CS_STAGES ), "%d %d %d %d %d %d", &cgs.alienStage, &cgs.humanStage,
166 &cgs.alienKills, &cgs.humanKills, &cgs.alienNextStageThreshold, &cgs.humanNextStageThreshold );
167 sscanf( CG_ConfigString( CS_SPAWNS ), "%d %d", &cgs.numAlienSpawns, &cgs.numHumanSpawns );
168
169 cgs.levelStartTime = atoi( CG_ConfigString( CS_LEVEL_START_TIME ) );
170 cg.warmup = atoi( CG_ConfigString( CS_WARMUP ) );
171 }
172
173
174 /*
175 =====================
176 CG_ShaderStateChanged
177 =====================
178 */
CG_ShaderStateChanged(void)179 void CG_ShaderStateChanged( void )
180 {
181 char originalShader[ MAX_QPATH ];
182 char newShader[ MAX_QPATH ];
183 char timeOffset[ 16 ];
184 const char *o;
185 char *n, *t;
186
187 o = CG_ConfigString( CS_SHADERSTATE );
188
189 while( o && *o )
190 {
191 n = strstr( o, "=" );
192
193 if( n && *n )
194 {
195 strncpy( originalShader, o, n - o );
196 originalShader[ n - o ] = 0;
197 n++;
198 t = strstr( n, ":" );
199
200 if( t && *t )
201 {
202 strncpy( newShader, n, t - n );
203 newShader[ t - n ] = 0;
204 }
205 else
206 break;
207
208 t++;
209 o = strstr( t, "@" );
210
211 if( o )
212 {
213 strncpy( timeOffset, t, o - t );
214 timeOffset[ o - t ] = 0;
215 o++;
216 trap_R_RemapShader( originalShader, newShader, timeOffset );
217 }
218 }
219 else
220 break;
221 }
222 }
223
224 /*
225 ================
226 CG_AnnounceAlienStageTransistion
227 ================
228 */
CG_AnnounceAlienStageTransistion(stage_t from,stage_t to)229 static void CG_AnnounceAlienStageTransistion( stage_t from, stage_t to )
230 {
231 if( cg.predictedPlayerState.stats[ STAT_PTEAM ] != PTE_ALIENS )
232 return;
233
234 trap_S_StartLocalSound( cgs.media.alienStageTransition, CHAN_ANNOUNCER );
235 CG_CenterPrint( "We have evolved!", 200, GIANTCHAR_WIDTH * 4 );
236 }
237
238 /*
239 ================
240 CG_AnnounceHumanStageTransistion
241 ================
242 */
CG_AnnounceHumanStageTransistion(stage_t from,stage_t to)243 static void CG_AnnounceHumanStageTransistion( stage_t from, stage_t to )
244 {
245 if( cg.predictedPlayerState.stats[ STAT_PTEAM ] != PTE_HUMANS )
246 return;
247
248 trap_S_StartLocalSound( cgs.media.humanStageTransition, CHAN_ANNOUNCER );
249 CG_CenterPrint( "Reinforcements have arrived!", 200, GIANTCHAR_WIDTH * 4 );
250 }
251
252 /*
253 ================
254 CG_ConfigStringModified
255
256 ================
257 */
CG_ConfigStringModified(void)258 static void CG_ConfigStringModified( void )
259 {
260 const char *str;
261 int num;
262
263 num = atoi( CG_Argv( 1 ) );
264
265 // get the gamestate from the client system, which will have the
266 // new configstring already integrated
267 trap_GetGameState( &cgs.gameState );
268
269 // look up the individual string that was modified
270 str = CG_ConfigString( num );
271
272 // do something with it if necessary
273 if( num == CS_MUSIC )
274 CG_StartMusic( );
275 else if( num == CS_SERVERINFO )
276 CG_ParseServerinfo( );
277 else if( num == CS_WARMUP )
278 CG_ParseWarmup( );
279 else if( num == CS_SCORES1 )
280 cgs.scores1 = atoi( str );
281 else if( num == CS_SCORES2 )
282 cgs.scores2 = atoi( str );
283 else if( num == CS_BUILDPOINTS )
284 sscanf( str, "%d %d %d %d %d", &cgs.alienBuildPoints,
285 &cgs.alienBuildPointsTotal,
286 &cgs.humanBuildPoints,
287 &cgs.humanBuildPointsTotal,
288 &cgs.humanBuildPointsPowered );
289 else if( num == CS_STAGES )
290 {
291 stage_t oldAlienStage = cgs.alienStage;
292 stage_t oldHumanStage = cgs.humanStage;
293
294 sscanf( str, "%d %d %d %d %d %d",
295 &cgs.alienStage, &cgs.humanStage,
296 &cgs.alienKills, &cgs.humanKills,
297 &cgs.alienNextStageThreshold, &cgs.humanNextStageThreshold );
298
299 if( cgs.alienStage != oldAlienStage )
300 CG_AnnounceAlienStageTransistion( oldAlienStage, cgs.alienStage );
301
302 if( cgs.humanStage != oldHumanStage )
303 CG_AnnounceHumanStageTransistion( oldHumanStage, cgs.humanStage );
304 }
305 else if( num == CS_SPAWNS )
306 sscanf( str, "%d %d", &cgs.numAlienSpawns, &cgs.numHumanSpawns );
307 else if( num == CS_LEVEL_START_TIME )
308 cgs.levelStartTime = atoi( str );
309 else if( num == CS_VOTE_TIME )
310 {
311 cgs.voteTime = atoi( str );
312 cgs.voteModified = qtrue;
313
314 if( cgs.voteTime )
315 trap_Cvar_Set( "ui_voteActive", "1" );
316 else
317 trap_Cvar_Set( "ui_voteActive", "0" );
318 }
319 else if( num == CS_VOTE_YES )
320 {
321 cgs.voteYes = atoi( str );
322 cgs.voteModified = qtrue;
323 }
324 else if( num == CS_VOTE_NO )
325 {
326 cgs.voteNo = atoi( str );
327 cgs.voteModified = qtrue;
328 }
329 else if( num == CS_VOTE_STRING )
330 Q_strncpyz( cgs.voteString, str, sizeof( cgs.voteString ) );
331 else if( num >= CS_TEAMVOTE_TIME && num <= CS_TEAMVOTE_TIME + 1 )
332 {
333 int cs_offset = num - CS_TEAMVOTE_TIME;
334
335 cgs.teamVoteTime[ cs_offset ] = atoi( str );
336 cgs.teamVoteModified[ cs_offset ] = qtrue;
337
338 if( cs_offset == 0 )
339 {
340 if( cgs.teamVoteTime[ cs_offset ] )
341 trap_Cvar_Set( "ui_humanTeamVoteActive", "1" );
342 else
343 trap_Cvar_Set( "ui_humanTeamVoteActive", "0" );
344 }
345 else if( cs_offset == 1 )
346 {
347 if( cgs.teamVoteTime[ cs_offset ] )
348 trap_Cvar_Set( "ui_alienTeamVoteActive", "1" );
349 else
350 trap_Cvar_Set( "ui_alienTeamVoteActive", "0" );
351 }
352 }
353 else if( num >= CS_TEAMVOTE_YES && num <= CS_TEAMVOTE_YES + 1 )
354 {
355 cgs.teamVoteYes[ num - CS_TEAMVOTE_YES ] = atoi( str );
356 cgs.teamVoteModified[ num - CS_TEAMVOTE_YES ] = qtrue;
357 }
358 else if( num >= CS_TEAMVOTE_NO && num <= CS_TEAMVOTE_NO + 1 )
359 {
360 cgs.teamVoteNo[ num - CS_TEAMVOTE_NO ] = atoi( str );
361 cgs.teamVoteModified[ num - CS_TEAMVOTE_NO ] = qtrue;
362 }
363 else if( num >= CS_TEAMVOTE_STRING && num <= CS_TEAMVOTE_STRING + 1 )
364 Q_strncpyz( cgs.teamVoteString[ num - CS_TEAMVOTE_STRING ], str, sizeof( cgs.teamVoteString ) );
365 else if( num == CS_INTERMISSION )
366 cg.intermissionStarted = atoi( str );
367 else if( num >= CS_MODELS && num < CS_MODELS+MAX_MODELS )
368 cgs.gameModels[ num - CS_MODELS ] = trap_R_RegisterModel( str );
369 else if( num >= CS_SHADERS && num < CS_SHADERS+MAX_GAME_SHADERS )
370 cgs.gameShaders[ num - CS_SHADERS ] = trap_R_RegisterShader( str );
371 else if( num >= CS_PARTICLE_SYSTEMS && num < CS_PARTICLE_SYSTEMS+MAX_GAME_PARTICLE_SYSTEMS )
372 cgs.gameParticleSystems[ num - CS_PARTICLE_SYSTEMS ] = CG_RegisterParticleSystem( (char *)str );
373 else if( num >= CS_SOUNDS && num < CS_SOUNDS+MAX_SOUNDS )
374 {
375 if( str[ 0 ] != '*' )
376 { // player specific sounds don't register here
377 cgs.gameSounds[ num - CS_SOUNDS ] = trap_S_RegisterSound( str, qfalse );
378 }
379 }
380 else if( num >= CS_PLAYERS && num < CS_PLAYERS+MAX_CLIENTS )
381 {
382 CG_NewClientInfo( num - CS_PLAYERS );
383 CG_BuildSpectatorString( );
384 }
385 else if( num == CS_FLAGSTATUS )
386 {
387 }
388 else if( num == CS_SHADERSTATE )
389 {
390 CG_ShaderStateChanged( );
391 }
392 }
393
394
395 /*
396 =======================
397 CG_AddToTeamChat
398
399 =======================
400 */
CG_AddToTeamChat(const char * str)401 static void CG_AddToTeamChat( const char *str )
402 {
403 int len;
404 char *p, *ls;
405 int lastcolor;
406 int chatHeight;
407
408 if( cg_teamChatHeight.integer < TEAMCHAT_HEIGHT )
409 chatHeight = cg_teamChatHeight.integer;
410 else
411 chatHeight = TEAMCHAT_HEIGHT;
412
413 if( chatHeight <= 0 || cg_teamChatTime.integer <= 0 )
414 {
415 // team chat disabled, dump into normal chat
416 cgs.teamChatPos = cgs.teamLastChatPos = 0;
417 return;
418 }
419
420 len = 0;
421
422 p = cgs.teamChatMsgs[cgs.teamChatPos % chatHeight];
423 *p = 0;
424
425 lastcolor = '7';
426
427 ls = NULL;
428 while( *str )
429 {
430 if( len > TEAMCHAT_WIDTH - 1 )
431 {
432 if( ls )
433 {
434 str -= ( p - ls );
435 str++;
436 p -= ( p - ls );
437 }
438
439 *p = 0;
440
441 cgs.teamChatMsgTimes[ cgs.teamChatPos % chatHeight ] = cg.time;
442
443 cgs.teamChatPos++;
444 p = cgs.teamChatMsgs[ cgs.teamChatPos % chatHeight ];
445 *p = 0;
446 *p++ = Q_COLOR_ESCAPE;
447 *p++ = lastcolor;
448 len = 0;
449 ls = NULL;
450 }
451
452 if( Q_IsColorString( str ) )
453 {
454 *p++ = *str++;
455 lastcolor = *str;
456 *p++ = *str++;
457 continue;
458 }
459
460 if( *str == ' ' )
461 ls = p;
462
463 *p++ = *str++;
464 len++;
465 }
466 *p = 0;
467
468 cgs.teamChatMsgTimes[ cgs.teamChatPos % chatHeight ] = cg.time;
469 cgs.teamChatPos++;
470
471 if( cgs.teamChatPos - cgs.teamLastChatPos > chatHeight )
472 cgs.teamLastChatPos = cgs.teamChatPos - chatHeight;
473 }
474
475
476
477 /*
478 ===============
479 CG_MapRestart
480
481 The server has issued a map_restart, so the next snapshot
482 is completely new and should not be interpolated to.
483
484 A tournement restart will clear everything, but doesn't
485 require a reload of all the media
486 ===============
487 */
CG_MapRestart(void)488 static void CG_MapRestart( void )
489 {
490 if( cg_showmiss.integer )
491 CG_Printf( "CG_MapRestart\n" );
492
493 CG_InitMarkPolys( );
494
495 // make sure the "3 frags left" warnings play again
496 cg.fraglimitWarnings = 0;
497
498 cg.timelimitWarnings = 0;
499
500 cg.intermissionStarted = qfalse;
501
502 cgs.voteTime = 0;
503
504 cg.mapRestart = qtrue;
505
506 CG_StartMusic( );
507
508 trap_S_ClearLoopingSounds( qtrue );
509
510 // we really should clear more parts of cg here and stop sounds
511
512 // play the "fight" sound if this is a restart without warmup
513 if( cg.warmup == 0 )
514 CG_CenterPrint( "FIGHT!", 120, GIANTCHAR_WIDTH * 2 );
515
516 trap_Cvar_Set( "cg_thirdPerson", "0" );
517 }
518
519 /*
520 =================
521 CG_RemoveChatEscapeChar
522 =================
523 */
CG_RemoveChatEscapeChar(char * text)524 static void CG_RemoveChatEscapeChar( char *text )
525 {
526 int i, l;
527
528 l = 0;
529 for( i = 0; text[ i ]; i++ )
530 {
531 if( text[ i ] == '\x19' )
532 continue;
533
534 text[ l++ ] = text[ i ];
535 }
536
537 text[ l ] = '\0';
538 }
539
540 /*
541 ===============
542 CG_SetUIVars
543
544 Set some cvars used by the UI
545 ===============
546 */
CG_SetUIVars(void)547 static void CG_SetUIVars( void )
548 {
549 int i;
550 char carriageCvar[ MAX_TOKEN_CHARS ];
551
552 *carriageCvar = 0;
553
554 //determine what the player is carrying
555 for( i = WP_NONE + 1; i < WP_NUM_WEAPONS; i++ )
556 {
557 if( BG_InventoryContainsWeapon( i, cg.snap->ps.stats ) &&
558 BG_FindPurchasableForWeapon( i ) )
559 strcat( carriageCvar, va( "W%d ", i ) );
560 }
561 for( i = UP_NONE + 1; i < UP_NUM_UPGRADES; i++ )
562 {
563 if( BG_InventoryContainsUpgrade( i, cg.snap->ps.stats ) &&
564 BG_FindPurchasableForUpgrade( i ) )
565 strcat( carriageCvar, va( "U%d ", i ) );
566 }
567 strcat( carriageCvar, "$" );
568
569 trap_Cvar_Set( "ui_carriage", carriageCvar );
570
571 trap_Cvar_Set( "ui_stages", va( "%d %d", cgs.alienStage, cgs.humanStage ) );
572 }
573
574
575 /*
576 ==============
577 CG_Menu
578 ==============
579 */
CG_Menu(int menu)580 void CG_Menu( int menu )
581 {
582 CG_SetUIVars( );
583
584 switch( menu )
585 {
586 case MN_TEAM: trap_SendConsoleCommand( "menu tremulous_teamselect\n" ); break;
587 case MN_A_CLASS: trap_SendConsoleCommand( "menu tremulous_alienclass\n" ); break;
588 case MN_H_SPAWN: trap_SendConsoleCommand( "menu tremulous_humanitem\n" ); break;
589 case MN_A_BUILD: trap_SendConsoleCommand( "menu tremulous_alienbuild\n" ); break;
590 case MN_H_BUILD: trap_SendConsoleCommand( "menu tremulous_humanbuild\n" ); break;
591 case MN_H_ARMOURY: trap_SendConsoleCommand( "menu tremulous_humanarmoury\n" ); break;
592
593 case MN_A_TEAMFULL:
594 trap_Cvar_Set( "ui_dialog", "The alien team has too many players. Please wait until "
595 "slots become available or join the human team." );
596 trap_SendConsoleCommand( "menu tremulous_alien_dialog\n" );
597 break;
598
599 case MN_H_TEAMFULL:
600 trap_Cvar_Set( "ui_dialog", "The human team has too many players. Please wait until "
601 "slots become available or join the alien team." );
602 trap_SendConsoleCommand( "menu tremulous_human_dialog\n" );
603 break;
604
605 case MN_H_NOROOM:
606 if( !cg_disableWarningDialogs.integer )
607 {
608 trap_Cvar_Set( "ui_dialog", "There is no room to build here. Move until the buildable turns "
609 "translucent green indicating a valid build location." );
610 trap_SendConsoleCommand( "menu tremulous_human_dialog\n" );
611 }
612 else
613 CG_Printf( "There is no room to build here\n" );
614
615 break;
616
617 case MN_H_NOPOWER:
618 if( !cg_disableWarningDialogs.integer )
619 {
620 trap_Cvar_Set( "ui_dialog", "There is no power remaining. Free up power by destroying existing "
621 "buildable objects." );
622 trap_SendConsoleCommand( "menu tremulous_human_dialog\n" );
623 }
624 else
625 CG_Printf( "There is no power remaining\n" );
626
627 break;
628
629 case MN_H_NOTPOWERED:
630 if( !cg_disableWarningDialogs.integer )
631 {
632 trap_Cvar_Set( "ui_dialog", "This buildable is not powered. Build a Reactor and/or Repeater in "
633 "order to power it." );
634 trap_SendConsoleCommand( "menu tremulous_human_dialog\n" );
635 }
636 else
637 CG_Printf( "This buildable is not powered\n" );
638
639 break;
640
641 case MN_H_NORMAL:
642 if( !cg_disableWarningDialogs.integer )
643 {
644 trap_Cvar_Set( "ui_dialog", "Cannot build on this surface. The surface is too steep or unsuitable "
645 "to build on. Please choose another site for this structure." );
646 trap_SendConsoleCommand( "menu tremulous_human_dialog\n" );
647 }
648 else
649 CG_Printf( "Cannot build on this surface\n" );
650
651 break;
652
653 case MN_H_REACTOR:
654 if( !cg_disableWarningDialogs.integer )
655 {
656 trap_Cvar_Set( "ui_dialog", "There can only be one Reactor. Destroy the existing one if you "
657 "wish to move it." );
658 trap_SendConsoleCommand( "menu tremulous_human_dialog\n" );
659 }
660 else
661 CG_Printf( "There can only be one Reactor\n" );
662
663 break;
664
665 case MN_H_REPEATER:
666 if( !cg_disableWarningDialogs.integer )
667 {
668 trap_Cvar_Set( "ui_dialog", "There is no power here. If available, a Repeater may be used to "
669 "transmit power to this location." );
670 trap_SendConsoleCommand( "menu tremulous_human_dialog\n" );
671 }
672 else
673 CG_Printf( "There is no power here\n" );
674
675 break;
676
677 case MN_H_NODCC:
678 if( !cg_disableWarningDialogs.integer )
679 {
680 trap_Cvar_Set( "ui_dialog", "There is no Defense Computer. A Defense Computer is needed to build "
681 "this." );
682 trap_SendConsoleCommand( "menu tremulous_human_dialog\n" );
683 }
684 else
685 CG_Printf( "There is no Defense Computer\n" );
686
687 break;
688
689 case MN_H_TNODEWARN:
690 if( !cg_disableWarningDialogs.integer )
691 {
692 trap_Cvar_Set( "ui_dialog", "WARNING: This Telenode will not be powered. Build near a power "
693 "structure to prevent seeing this message again." );
694 trap_SendConsoleCommand( "menu tremulous_human_dialog\n" );
695 }
696 else
697 CG_Printf( "This Telenode will not be powered\n" );
698
699 break;
700
701 case MN_H_RPTWARN:
702 if( !cg_disableWarningDialogs.integer )
703 {
704 trap_Cvar_Set( "ui_dialog", "WARNING: This Repeater will not be powered as there is no parent "
705 "Reactor providing power. Build a Reactor." );
706 trap_SendConsoleCommand( "menu tremulous_human_dialog\n" );
707 }
708 else
709 CG_Printf( "This Repeater will not be powered\n" );
710
711 break;
712
713 case MN_H_RPTWARN2:
714 if( !cg_disableWarningDialogs.integer )
715 {
716 trap_Cvar_Set( "ui_dialog", "This area already has power. A Repeater is not required here." );
717 trap_SendConsoleCommand( "menu tremulous_human_dialog\n" );
718 }
719 else
720 CG_Printf( "This area already has power\n" );
721
722 break;
723
724 case MN_H_NOSLOTS:
725 if( !cg_disableWarningDialogs.integer )
726 {
727 trap_Cvar_Set( "ui_dialog", "You have no room to carry this. Please sell any conflicting "
728 "upgrades before purchasing this item." );
729 trap_SendConsoleCommand( "menu tremulous_human_dialog\n" );
730 }
731 else
732 CG_Printf( "You have no room to carry this\n" );
733
734 break;
735
736 case MN_H_NOFUNDS:
737 if( !cg_disableWarningDialogs.integer )
738 {
739 trap_Cvar_Set( "ui_dialog", "Insufficient funds. You do not have enough credits to perform this "
740 "action." );
741 trap_SendConsoleCommand( "menu tremulous_human_dialog\n" );
742 }
743 else
744 CG_Printf( "Insufficient funds\n" );
745
746 break;
747
748 case MN_H_ITEMHELD:
749 if( !cg_disableWarningDialogs.integer )
750 {
751 trap_Cvar_Set( "ui_dialog", "You already hold this item. It is not possible to carry multiple items "
752 "of the same type." );
753 trap_SendConsoleCommand( "menu tremulous_human_dialog\n" );
754 }
755 else
756 CG_Printf( "You already hold this item\n" );
757
758 break;
759
760
761 //===============================
762
763
764 case MN_A_NOROOM:
765 if( !cg_disableWarningDialogs.integer )
766 {
767 trap_Cvar_Set( "ui_dialog", "There is no room to build here. Move until the structure turns "
768 "translucent green indicating a valid build location." );
769 trap_SendConsoleCommand( "menu tremulous_alien_dialog\n" );
770 }
771 else
772 CG_Printf( "There is no room to build here\n" );
773
774 break;
775
776 case MN_A_NOCREEP:
777 if( !cg_disableWarningDialogs.integer )
778 {
779 trap_Cvar_Set( "ui_dialog", "There is no creep here. You must build near existing Eggs or "
780 "the Overmind. Alien structures will not support themselves." );
781 trap_SendConsoleCommand( "menu tremulous_alien_dialog\n" );
782 }
783 else
784 CG_Printf( "There is no creep here\n" );
785
786 break;
787
788 case MN_A_NOOVMND:
789 if( !cg_disableWarningDialogs.integer )
790 {
791 trap_Cvar_Set( "ui_dialog", "There is no Overmind. An Overmind must be built to control "
792 "the structure you tried to place" );
793 trap_SendConsoleCommand( "menu tremulous_alien_dialog\n" );
794 }
795 else
796 CG_Printf( "There is no Overmind\n" );
797
798 break;
799
800 case MN_A_OVERMIND:
801 if( !cg_disableWarningDialogs.integer )
802 {
803 trap_Cvar_Set( "ui_dialog", "There can only be one Overmind. Destroy the existing one if you "
804 "wish to move it." );
805 trap_SendConsoleCommand( "menu tremulous_alien_dialog\n" );
806 }
807 else
808 CG_Printf( "There can only be one Overmind\n" );
809
810 break;
811
812 case MN_A_HOVEL:
813 if( !cg_disableWarningDialogs.integer )
814 {
815 trap_Cvar_Set( "ui_dialog", "There can only be one Hovel. Destroy the existing one if you "
816 "wish to move it." );
817 trap_SendConsoleCommand( "menu tremulous_alien_dialog\n" );
818 }
819 else
820 CG_Printf( "There can only be one Hovel\n" );
821
822 break;
823
824 case MN_A_NOASSERT:
825 if( !cg_disableWarningDialogs.integer )
826 {
827 trap_Cvar_Set( "ui_dialog", "The Overmind cannot control any more structures. Destroy existing "
828 "structures to build more." );
829 trap_SendConsoleCommand( "menu tremulous_alien_dialog\n" );
830 }
831 else
832 CG_Printf( "The Overmind cannot control any more structures\n" );
833
834 break;
835
836 case MN_A_SPWNWARN:
837 if( !cg_disableWarningDialogs.integer )
838 {
839 trap_Cvar_Set( "ui_dialog", "WARNING: This spawn will not be controlled by an Overmind. "
840 "Build an Overmind to prevent seeing this message again." );
841 trap_SendConsoleCommand( "menu tremulous_alien_dialog\n" );
842 }
843 else
844 CG_Printf( "This spawn will not be controlled by an Overmind\n" );
845
846 break;
847
848 case MN_A_NORMAL:
849 if( !cg_disableWarningDialogs.integer )
850 {
851 trap_Cvar_Set( "ui_dialog", "Cannot build on this surface. This surface is too steep or unsuitable "
852 "to build on. Please choose another site for this structure." );
853 trap_SendConsoleCommand( "menu tremulous_alien_dialog\n" );
854 }
855 else
856 CG_Printf( "Cannot build on this surface\n" );
857
858 break;
859
860 case MN_A_NOEROOM:
861 if( !cg_disableWarningDialogs.integer )
862 {
863 trap_Cvar_Set( "ui_dialog", "There is no room to evolve here. Move away from walls or other "
864 "nearby objects and try again." );
865 trap_SendConsoleCommand( "menu tremulous_alien_dialog\n" );
866 }
867 else
868 CG_Printf( "There is no room to evolve here\n" );
869
870 break;
871
872 case MN_A_TOOCLOSE:
873 if( !cg_disableWarningDialogs.integer )
874 {
875 trap_Cvar_Set( "ui_dialog", "This location is too close to the enemy to evolve. "
876 "Move away until you are no longer aware of the enemy's "
877 "presence and try again." );
878 trap_SendConsoleCommand( "menu tremulous_alien_dialog\n" );
879 }
880 else
881 CG_Printf( "This location is too close to the enemy to evolve\n" );
882
883 break;
884
885 case MN_A_NOOVMND_EVOLVE:
886 if( !cg_disableWarningDialogs.integer )
887 {
888 trap_Cvar_Set( "ui_dialog", "There is no Overmind. An Overmind must be built to allow "
889 "you to upgrade." );
890 trap_SendConsoleCommand( "menu tremulous_alien_dialog\n" );
891 }
892 else
893 CG_Printf( "There is no Overmind\n" );
894
895 break;
896
897 case MN_A_HOVEL_OCCUPIED:
898 if( !cg_disableWarningDialogs.integer )
899 {
900 trap_Cvar_Set( "ui_dialog", "This Hovel is occupied by another builder. Please find or build "
901 "another." );
902 trap_SendConsoleCommand( "menu tremulous_alien_dialog\n" );
903 }
904 else
905 CG_Printf( "This Hovel is occupied by another builder\n" );
906
907 break;
908
909 case MN_A_HOVEL_BLOCKED:
910 if( !cg_disableWarningDialogs.integer )
911 {
912 trap_Cvar_Set( "ui_dialog", "The exit to this Hovel is currently blocked. Please wait until it "
913 "becomes clear then try again." );
914 trap_SendConsoleCommand( "menu tremulous_alien_dialog\n" );
915 }
916 else
917 CG_Printf( "The exit to this Hovel is currently blocked\n" );
918
919 break;
920
921 case MN_A_HOVEL_EXIT:
922 if( !cg_disableWarningDialogs.integer )
923 {
924 trap_Cvar_Set( "ui_dialog", "The exit to this Hovel would always be blocked. Please choose "
925 "a more suitable location." );
926 trap_SendConsoleCommand( "menu tremulous_alien_dialog\n" );
927 }
928 else
929 CG_Printf( "The exit to this Hovel would always be blocked\n" );
930
931 break;
932
933 case MN_A_INFEST:
934 trap_Cvar_Set( "ui_currentClass", va( "%d %d", cg.snap->ps.stats[ STAT_PCLASS ],
935 cg.snap->ps.persistant[ PERS_CREDIT ] ) );
936 trap_SendConsoleCommand( "menu tremulous_alienupgrade\n" );
937 break;
938
939 default:
940 Com_Printf( "cgame: debug: no such menu %d\n", menu );
941 }
942 }
943
944 /*
945 =================
946 CG_ServerCommand
947
948 The string has been tokenized and can be retrieved with
949 Cmd_Argc() / Cmd_Argv()
950 =================
951 */
CG_ServerCommand(void)952 static void CG_ServerCommand( void )
953 {
954 const char *cmd;
955 char text[ MAX_SAY_TEXT ];
956
957 cmd = CG_Argv( 0 );
958
959 if( !cmd[ 0 ] )
960 {
961 // server claimed the command
962 return;
963 }
964
965 if( !strcmp( cmd, "cp" ) )
966 {
967 CG_CenterPrint( CG_Argv( 1 ), SCREEN_HEIGHT * 0.30, BIGCHAR_WIDTH );
968 return;
969 }
970
971 if( !strcmp( cmd, "cs" ) )
972 {
973 CG_ConfigStringModified( );
974 return;
975 }
976
977 if( !strcmp( cmd, "print" ) )
978 {
979 CG_Printf( "%s", CG_Argv( 1 ) );
980 return;
981 }
982
983 if( !strcmp( cmd, "chat" ) )
984 {
985 if( !cg_teamChatsOnly.integer )
986 {
987 trap_S_StartLocalSound( cgs.media.talkSound, CHAN_LOCAL_SOUND );
988 Q_strncpyz( text, CG_Argv( 1 ), MAX_SAY_TEXT );
989 CG_RemoveChatEscapeChar( text );
990 CG_Printf( "%s\n", text );
991 }
992
993 return;
994 }
995
996 if( !strcmp( cmd, "tchat" ) )
997 {
998 if( cg.snap->ps.stats[ STAT_PTEAM ] == PTE_ALIENS )
999 trap_S_StartLocalSound( cgs.media.alienTalkSound, CHAN_LOCAL_SOUND );
1000 else if( cg.snap->ps.stats[ STAT_PTEAM ] == PTE_HUMANS )
1001 trap_S_StartLocalSound( cgs.media.humanTalkSound, CHAN_LOCAL_SOUND );
1002 else
1003 trap_S_StartLocalSound( cgs.media.talkSound, CHAN_LOCAL_SOUND );
1004
1005 Q_strncpyz( text, CG_Argv( 1 ), MAX_SAY_TEXT );
1006 CG_RemoveChatEscapeChar( text );
1007 CG_AddToTeamChat( text );
1008 CG_Printf( "%s\n", text );
1009 return;
1010 }
1011
1012 if( !strcmp( cmd, "scores" ) )
1013 {
1014 CG_ParseScores( );
1015 return;
1016 }
1017
1018 if( !strcmp( cmd, "tinfo" ) )
1019 {
1020 CG_ParseTeamInfo( );
1021 return;
1022 }
1023
1024 if( !strcmp( cmd, "map_restart" ) )
1025 {
1026 CG_MapRestart( );
1027 return;
1028 }
1029
1030 if( Q_stricmp( cmd, "remapShader" ) == 0 )
1031 {
1032 if( trap_Argc( ) == 4 )
1033 trap_R_RemapShader( CG_Argv( 1 ), CG_Argv( 2 ), CG_Argv( 3 ) );
1034 }
1035
1036 // clientLevelShot is sent before taking a special screenshot for
1037 // the menu system during development
1038 if( !strcmp( cmd, "clientLevelShot" ) )
1039 {
1040 cg.levelShot = qtrue;
1041 return;
1042 }
1043
1044 //the server has triggered a menu
1045 if( !strcmp( cmd, "servermenu" ) )
1046 {
1047 if( trap_Argc( ) == 2 && !cg.demoPlayback )
1048 CG_Menu( atoi( CG_Argv( 1 ) ) );
1049
1050 return;
1051 }
1052
1053 //the server thinks this client should close all menus
1054 if( !strcmp( cmd, "serverclosemenus" ) )
1055 {
1056 trap_SendConsoleCommand( "closemenus\n" );
1057 return;
1058 }
1059
1060 //poison cloud effect needs to be reliable
1061 if( !strcmp( cmd, "poisoncloud" ) )
1062 {
1063 cg.poisonedTime = cg.time;
1064
1065 if( CG_IsParticleSystemValid( &cg.poisonCloudPS ) )
1066 {
1067 cg.poisonCloudPS = CG_SpawnNewParticleSystem( cgs.media.poisonCloudPS );
1068 CG_SetAttachmentCent( &cg.poisonCloudPS->attachment, &cg.predictedPlayerEntity );
1069 CG_AttachToCent( &cg.poisonCloudPS->attachment );
1070 }
1071
1072 return;
1073 }
1074
1075 if( !strcmp( cmd, "weaponswitch" ) )
1076 {
1077 CG_Printf( "client weaponswitch\n" );
1078 if( trap_Argc( ) == 2 )
1079 {
1080 cg.weaponSelect = atoi( CG_Argv( 1 ) );
1081 cg.weaponSelectTime = cg.time;
1082 }
1083
1084 return;
1085 }
1086
1087 // server requests a ptrc
1088 if( !strcmp( cmd, "ptrcrequest" ) )
1089 {
1090 int code = CG_ReadPTRCode( );
1091
1092 trap_SendClientCommand( va( "ptrcverify %d", code ) );
1093 return;
1094 }
1095
1096 // server issues a ptrc
1097 if( !strcmp( cmd, "ptrcissue" ) )
1098 {
1099 if( trap_Argc( ) == 2 )
1100 {
1101 int code = atoi( CG_Argv( 1 ) );
1102
1103 CG_WritePTRCode( code );
1104 }
1105
1106 return;
1107 }
1108
1109 // reply to ptrcverify
1110 if( !strcmp( cmd, "ptrcconfirm" ) )
1111 {
1112 trap_SendConsoleCommand( "menu ptrc_popmenu\n" );
1113
1114 return;
1115 }
1116
1117 CG_Printf( "Unknown client game command: %s\n", cmd );
1118 }
1119
1120
1121 /*
1122 ====================
1123 CG_ExecuteNewServerCommands
1124
1125 Execute all of the server commands that were received along
1126 with this this snapshot.
1127 ====================
1128 */
CG_ExecuteNewServerCommands(int latestSequence)1129 void CG_ExecuteNewServerCommands( int latestSequence )
1130 {
1131 while( cgs.serverCommandSequence < latestSequence )
1132 {
1133 if( trap_GetServerCommand( ++cgs.serverCommandSequence ) )
1134 CG_ServerCommand( );
1135 }
1136 }
1137