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