1 /*
2 ===========================================================================
3 Copyright (C) 2000-2006 Tim Angus
4 
5 This file is part of Tremulous.
6 
7 Tremulous is free software; you can redistribute it
8 and/or modify it under the terms of the GNU General Public License as
9 published by the Free Software Foundation; either version 2 of the License,
10 or (at your option) any later version.
11 
12 Tremulous is distributed in the hope that it will be
13 useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 GNU General Public License for more details.
16 
17 You should have received a copy of the GNU General Public License
18 along with Tremulous; if not, write to the Free Software
19 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
20 ===========================================================================
21 */
22 
23 // cg_tutorial.c -- the tutorial system
24 
25 #include "cg_local.h"
26 
27 typedef struct
28 {
29   char      *command;
30   char      *humanName;
31   keyNum_t  keys[ 2 ];
32 } bind_t;
33 
34 static bind_t bindings[ ] =
35 {
36   { "+button2",       "Activate Upgrade",       { -1, -1 } },
37   { "+speed",         "Run/Walk",               { -1, -1 } },
38   { "boost",          "Sprint",                 { -1, -1 } },
39   { "+moveup",        "Jump",                   { -1, -1 } },
40   { "+movedown",      "Crouch",                 { -1, -1 } },
41   { "+zoom",          "ZoomView",               { -1, -1 } },
42   { "+attack",        "Primary Attack",         { -1, -1 } },
43   { "+button5",       "Secondary Attack",       { -1, -1 } },
44   { "reload",         "Reload",                 { -1, -1 } },
45   { "buy ammo",       "Buy Ammo",               { -1, -1 } },
46   { "itemact medkit", "Use Medkit",             { -1, -1 } },
47   { "+button7",       "Use Structure/Evolve",   { -1, -1 } },
48   { "deconstruct",    "Deconstruct Structure",  { -1, -1 } },
49   { "weapprev",       "Previous Upgrade",       { -1, -1 } },
50   { "weapnext",       "Next Upgrade",           { -1, -1 } }
51 };
52 
53 static const int numBindings = sizeof( bindings ) / sizeof( bind_t );
54 
55 /*
56 =================
57 CG_GetBindings
58 =================
59 */
CG_GetBindings(void)60 static void CG_GetBindings( void )
61 {
62   int   i, j, numKeys;
63   char  buffer[ MAX_STRING_CHARS ];
64 
65   for( i = 0; i < numBindings; i++ )
66   {
67     bindings[ i ].keys[ 0 ] = bindings[ i ].keys[ 1 ] = K_NONE;
68     numKeys = 0;
69 
70     for( j = 0; j < K_LAST_KEY; j++ )
71     {
72       trap_Key_GetBindingBuf( j, buffer, MAX_STRING_CHARS );
73 
74       if( buffer[ 0 ] == 0 )
75         continue;
76 
77       if( !Q_stricmp( buffer, bindings[ i ].command ) )
78       {
79         bindings[ i ].keys[ numKeys++ ] = j;
80 
81         if( numKeys > 1 )
82           break;
83       }
84     }
85   }
86 }
87 
88 /*
89 ===============
90 CG_KeyNameForCommand
91 ===============
92 */
CG_KeyNameForCommand(const char * command)93 static const char *CG_KeyNameForCommand( const char *command )
94 {
95   int         i, j;
96   static char buffer[ MAX_STRING_CHARS ];
97   int         firstKeyLength;
98 
99   buffer[ 0 ] = '\0';
100 
101   for( i = 0; i < numBindings; i++ )
102   {
103     if( !Q_stricmp( command, bindings[ i ].command ) )
104     {
105       if( bindings[ i ].keys[ 0 ] != K_NONE )
106       {
107         trap_Key_KeynumToStringBuf( bindings[ i ].keys[ 0 ],
108             buffer, MAX_STRING_CHARS );
109         firstKeyLength = strlen( buffer );
110 
111         for( j = 0; j < firstKeyLength; j++ )
112           buffer[ j ] = toupper( buffer[ j ] );
113 
114         if( bindings[ i ].keys[ 1 ] != K_NONE )
115         {
116           Q_strcat( buffer, MAX_STRING_CHARS, " or " );
117           trap_Key_KeynumToStringBuf( bindings[ i ].keys[ 1 ],
118               buffer + strlen( buffer ), MAX_STRING_CHARS - strlen( buffer ) );
119 
120           for( j = firstKeyLength + 4; j < strlen( buffer ); j++ )
121             buffer[ j ] = toupper( buffer[ j ] );
122         }
123       }
124       else
125       {
126         Q_strncpyz( buffer, va( "\"%s\"", bindings[ i ].humanName ),
127             MAX_STRING_CHARS );
128         Q_strcat( buffer, MAX_STRING_CHARS, " (unbound)" );
129       }
130 
131       return buffer;
132     }
133   }
134 
135   return "";
136 }
137 
138 #define MAX_TUTORIAL_TEXT 4096
139 
140 /*
141 ===============
142 CG_BuildableInRange
143 ===============
144 */
CG_BuildableInRange(playerState_t * ps)145 static qboolean CG_BuildableInRange( playerState_t *ps )
146 {
147   vec3_t        view, point;
148   trace_t       trace;
149   entityState_t *es;
150 
151   AngleVectors( cg.refdefViewAngles, view, NULL, NULL );
152   VectorMA( cg.refdef.vieworg, 64, view, point );
153   CG_Trace( &trace, cg.refdef.vieworg, NULL, NULL,
154             point, ps->clientNum, MASK_SHOT );
155 
156   es = &cg_entities[ trace.entityNum ].currentState;
157 
158   if( es->eType == ET_BUILDABLE &&
159       ps->stats[ STAT_PTEAM ] == BG_FindTeamForBuildable( es->modelindex ) )
160     return qtrue;
161   else
162     return qfalse;
163 }
164 
165 /*
166 ===============
167 CG_AlienBuilderText
168 ===============
169 */
CG_AlienBuilderText(char * text,playerState_t * ps)170 static void CG_AlienBuilderText( char *text, playerState_t *ps )
171 {
172   buildable_t buildable = ps->stats[ STAT_BUILDABLE ] & ~SB_VALID_TOGGLEBIT;
173 
174   if( buildable > BA_NONE )
175   {
176     Q_strcat( text, MAX_TUTORIAL_TEXT,
177         va( "Press %s to place the %s\n",
178           CG_KeyNameForCommand( "+attack" ),
179           BG_FindHumanNameForBuildable( buildable ) ) );
180 
181     Q_strcat( text, MAX_TUTORIAL_TEXT,
182         va( "Press %s to cancel placing the %s\n",
183           CG_KeyNameForCommand( "+button5" ),
184           BG_FindHumanNameForBuildable( buildable ) ) );
185   }
186   else
187   {
188     Q_strcat( text, MAX_TUTORIAL_TEXT,
189         va( "Press %s to build a structure\n",
190           CG_KeyNameForCommand( "+attack" ) ) );
191 
192     if( CG_BuildableInRange( ps ) )
193     {
194       Q_strcat( text, MAX_TUTORIAL_TEXT,
195           va( "Press %s to destroy this structure\n",
196             CG_KeyNameForCommand( "deconstruct" ) ) );
197     }
198   }
199 
200   if( ps->stats[ STAT_PCLASS ] == PCL_ALIEN_BUILDER0_UPG )
201   {
202     if( ( ps->stats[ STAT_BUILDABLE ] & ~SB_VALID_TOGGLEBIT ) == BA_NONE )
203     {
204       Q_strcat( text, MAX_TUTORIAL_TEXT,
205           va( "Press %s to swipe\n",
206             CG_KeyNameForCommand( "+button5" ) ) );
207     }
208 
209     Q_strcat( text, MAX_TUTORIAL_TEXT,
210         va( "Press %s to launch a projectile\n",
211           CG_KeyNameForCommand( "+button2" ) ) );
212 
213     Q_strcat( text, MAX_TUTORIAL_TEXT,
214         va( "Press %s to walk on walls\n",
215           CG_KeyNameForCommand( "+movedown" ) ) );
216   }
217 }
218 
219 /*
220 ===============
221 CG_AlienLevel0Text
222 ===============
223 */
CG_AlienLevel0Text(char * text,playerState_t * ps)224 static void CG_AlienLevel0Text( char *text, playerState_t *ps )
225 {
226   Q_strcat( text, MAX_TUTORIAL_TEXT,
227       "Touch a human to damage it\n" );
228 
229   Q_strcat( text, MAX_TUTORIAL_TEXT,
230       va( "Press %s to walk on walls\n",
231         CG_KeyNameForCommand( "+movedown" ) ) );
232 }
233 
234 /*
235 ===============
236 CG_AlienLevel1Text
237 ===============
238 */
CG_AlienLevel1Text(char * text,playerState_t * ps)239 static void CG_AlienLevel1Text( char *text, playerState_t *ps )
240 {
241   Q_strcat( text, MAX_TUTORIAL_TEXT,
242       "Touch a human to grab it\n" );
243 
244   Q_strcat( text, MAX_TUTORIAL_TEXT,
245       va( "Press %s to swipe\n",
246         CG_KeyNameForCommand( "+attack" ) ) );
247 
248   if( ps->stats[ STAT_PCLASS ] == PCL_ALIEN_LEVEL1_UPG )
249   {
250     Q_strcat( text, MAX_TUTORIAL_TEXT,
251         va( "Press %s to spray poisonous gas\n",
252           CG_KeyNameForCommand( "+button5" ) ) );
253   }
254 
255   Q_strcat( text, MAX_TUTORIAL_TEXT,
256       va( "Press %s to walk on walls\n",
257         CG_KeyNameForCommand( "+movedown" ) ) );
258 }
259 
260 /*
261 ===============
262 CG_AlienLevel2Text
263 ===============
264 */
CG_AlienLevel2Text(char * text,playerState_t * ps)265 static void CG_AlienLevel2Text( char *text, playerState_t *ps )
266 {
267   Q_strcat( text, MAX_TUTORIAL_TEXT,
268       va( "Press %s to bite\n",
269         CG_KeyNameForCommand( "+attack" ) ) );
270 
271   if( ps->stats[ STAT_PCLASS ] == PCL_ALIEN_LEVEL2_UPG )
272   {
273     Q_strcat( text, MAX_TUTORIAL_TEXT,
274         va( "Press %s to invoke an electrical attack\n",
275           CG_KeyNameForCommand( "+button5" ) ) );
276   }
277 
278   Q_strcat( text, MAX_TUTORIAL_TEXT,
279       va( "Hold down %s then touch a wall to wall jump\n",
280         CG_KeyNameForCommand( "+moveup" ) ) );
281 }
282 
283 /*
284 ===============
285 CG_AlienLevel3Text
286 ===============
287 */
CG_AlienLevel3Text(char * text,playerState_t * ps)288 static void CG_AlienLevel3Text( char *text, playerState_t *ps )
289 {
290   Q_strcat( text, MAX_TUTORIAL_TEXT,
291       va( "Press %s to bite\n",
292         CG_KeyNameForCommand( "+attack" ) ) );
293 
294   if( ps->stats[ STAT_PCLASS ] == PCL_ALIEN_LEVEL3_UPG )
295   {
296     Q_strcat( text, MAX_TUTORIAL_TEXT,
297         va( "Press %s to launch a projectile\n",
298           CG_KeyNameForCommand( "+button2" ) ) );
299   }
300 
301   Q_strcat( text, MAX_TUTORIAL_TEXT,
302       va( "Hold down and release %s to pounce\n",
303         CG_KeyNameForCommand( "+button5" ) ) );
304 }
305 
306 /*
307 ===============
308 CG_AlienLevel4Text
309 ===============
310 */
CG_AlienLevel4Text(char * text,playerState_t * ps)311 static void CG_AlienLevel4Text( char *text, playerState_t *ps )
312 {
313   Q_strcat( text, MAX_TUTORIAL_TEXT,
314       va( "Press %s to swipe\n",
315         CG_KeyNameForCommand( "+attack" ) ) );
316 
317   Q_strcat( text, MAX_TUTORIAL_TEXT,
318       va( "Hold down and release %s to charge\n",
319         CG_KeyNameForCommand( "+button5" ) ) );
320 }
321 
322 /*
323 ===============
324 CG_HumanCkitText
325 ===============
326 */
CG_HumanCkitText(char * text,playerState_t * ps)327 static void CG_HumanCkitText( char *text, playerState_t *ps )
328 {
329   buildable_t buildable = ps->stats[ STAT_BUILDABLE ] & ~SB_VALID_TOGGLEBIT;
330 
331   if( buildable > BA_NONE )
332   {
333     Q_strcat( text, MAX_TUTORIAL_TEXT,
334         va( "Press %s to place the %s\n",
335           CG_KeyNameForCommand( "+attack" ),
336           BG_FindHumanNameForBuildable( buildable ) ) );
337 
338     Q_strcat( text, MAX_TUTORIAL_TEXT,
339         va( "Press %s to cancel placing the %s\n",
340           CG_KeyNameForCommand( "+button5" ),
341           BG_FindHumanNameForBuildable( buildable ) ) );
342   }
343   else
344   {
345     Q_strcat( text, MAX_TUTORIAL_TEXT,
346         va( "Press %s to build a structure\n",
347           CG_KeyNameForCommand( "+attack" ) ) );
348 
349     if( CG_BuildableInRange( ps ) )
350     {
351       Q_strcat( text, MAX_TUTORIAL_TEXT,
352           va( "Press %s to destroy this structure\n",
353             CG_KeyNameForCommand( "deconstruct" ) ) );
354     }
355   }
356 }
357 
358 /*
359 ===============
360 CG_HumanText
361 ===============
362 */
CG_HumanText(char * text,playerState_t * ps)363 static void CG_HumanText( char *text, playerState_t *ps )
364 {
365   char      *name;
366   int       ammo, clips;
367   upgrade_t upgrade = UP_NONE;
368 
369   if( cg.weaponSelect <= 32 )
370     name = cg_weapons[ cg.weaponSelect ].humanName;
371   else if( cg.weaponSelect > 32 )
372   {
373     name = cg_upgrades[ cg.weaponSelect - 32 ].humanName;
374     upgrade = cg.weaponSelect - 32;
375   }
376 
377   BG_UnpackAmmoArray( ps->weapon, ps->ammo, ps->powerups, &ammo, &clips );
378 
379   if( !ammo && !clips && !BG_FindInfinteAmmoForWeapon( ps->weapon ) )
380   {
381     //no ammo
382     switch( ps->weapon )
383     {
384       case WP_MACHINEGUN:
385       case WP_CHAINGUN:
386       case WP_SHOTGUN:
387       case WP_FLAMER:
388         Q_strcat( text, MAX_TUTORIAL_TEXT,
389             va( "Find an Armoury and press %s for more ammo\n",
390               CG_KeyNameForCommand( "buy ammo" ) ) );
391         break;
392 
393       case WP_LAS_GUN:
394       case WP_PULSE_RIFLE:
395       case WP_MASS_DRIVER:
396       case WP_LUCIFER_CANNON:
397         Q_strcat( text, MAX_TUTORIAL_TEXT,
398             va( "Find a Reactor or Repeater and press %s for more ammo\n",
399               CG_KeyNameForCommand( "buy ammo" ) ) );
400         break;
401 
402       default:
403         break;
404     }
405   }
406   else
407   {
408     switch( ps->weapon )
409     {
410       case WP_BLASTER:
411       case WP_MACHINEGUN:
412       case WP_SHOTGUN:
413       case WP_LAS_GUN:
414       case WP_CHAINGUN:
415       case WP_PULSE_RIFLE:
416       case WP_FLAMER:
417         Q_strcat( text, MAX_TUTORIAL_TEXT,
418             va( "Press %s to fire the %s\n",
419               CG_KeyNameForCommand( "+attack" ),
420               BG_FindHumanNameForWeapon( ps->weapon ) ) );
421         break;
422 
423       case WP_MASS_DRIVER:
424         Q_strcat( text, MAX_TUTORIAL_TEXT,
425             va( "Press %s to fire the %s\n",
426               CG_KeyNameForCommand( "+attack" ),
427               BG_FindHumanNameForWeapon( ps->weapon ) ) );
428 
429         Q_strcat( text, MAX_TUTORIAL_TEXT,
430             va( "Hold %s to zoom\n",
431               CG_KeyNameForCommand( "+zoom" ) ) );
432         break;
433 
434       case WP_PAIN_SAW:
435         Q_strcat( text, MAX_TUTORIAL_TEXT,
436             va( "Hold %s to activate the %s\n",
437               CG_KeyNameForCommand( "+attack" ),
438               BG_FindHumanNameForWeapon( ps->weapon ) ) );
439         break;
440 
441       case WP_LUCIFER_CANNON:
442         Q_strcat( text, MAX_TUTORIAL_TEXT,
443             va( "Hold and release %s to fire a charged shot\n",
444               CG_KeyNameForCommand( "+attack" ) ) );
445 
446         Q_strcat( text, MAX_TUTORIAL_TEXT,
447             va( "Press %s to fire the %s\n",
448               CG_KeyNameForCommand( "+button5" ),
449               BG_FindHumanNameForWeapon( ps->weapon ) ) );
450         break;
451 
452       case WP_HBUILD:
453       case WP_HBUILD2:
454         CG_HumanCkitText( text, ps );
455         break;
456 
457       default:
458         break;
459     }
460   }
461 
462   Q_strcat( text, MAX_TUTORIAL_TEXT,
463       va( "Press %s and ",
464           CG_KeyNameForCommand( "weapprev" ) ) );
465   Q_strcat( text, MAX_TUTORIAL_TEXT,
466       va( "%s to select an upgrade\n",
467           CG_KeyNameForCommand( "weapnext" ) ) );
468 
469   if( upgrade == UP_NONE ||
470       ( upgrade > UP_NONE && BG_FindUsableForUpgrade( upgrade ) ) )
471   {
472     Q_strcat( text, MAX_TUTORIAL_TEXT,
473         va( "Press %s to use the %s\n",
474             CG_KeyNameForCommand( "+button2" ),
475             name ) );
476   }
477 
478   if( ps->stats[ STAT_HEALTH ] <= 35 &&
479       BG_InventoryContainsUpgrade( UP_MEDKIT, ps->stats ) )
480   {
481     Q_strcat( text, MAX_TUTORIAL_TEXT,
482         va( "Press %s to use your %s\n",
483           CG_KeyNameForCommand( "itemact medkit" ),
484           BG_FindHumanNameForUpgrade( UP_MEDKIT ) ) );
485   }
486 
487   Q_strcat( text, MAX_TUTORIAL_TEXT,
488       va( "Press %s to use a structure\n",
489         CG_KeyNameForCommand( "+button7" ) ) );
490 }
491 
492 /*
493 ===============
494 CG_SpectatorText
495 ===============
496 */
CG_SpectatorText(char * text,playerState_t * ps)497 static void CG_SpectatorText( char *text, playerState_t *ps )
498 {
499   if( ps->pm_flags & PMF_FOLLOW )
500   {
501     Q_strcat( text, MAX_TUTORIAL_TEXT,
502         va( "Press %s to return to free spectator mode\n",
503           CG_KeyNameForCommand( "+button2" ) ) );
504 
505     if( CG_PlayerCount( ) > 1 )
506     {
507       Q_strcat( text, MAX_TUTORIAL_TEXT,
508           va( "Press %s or ",
509             CG_KeyNameForCommand( "weapprev" ) ) );
510       Q_strcat( text, MAX_TUTORIAL_TEXT,
511           va( "%s to change player\n",
512             CG_KeyNameForCommand( "weapnext" ) ) );
513     }
514   }
515   else if( ps->pm_type == PM_SPECTATOR )
516   {
517     Q_strcat( text, MAX_TUTORIAL_TEXT,
518         va( "Press %s to join a team\n",
519           CG_KeyNameForCommand( "+attack" ) ) );
520 
521     if( CG_PlayerCount( ) > 0 )
522     {
523       Q_strcat( text, MAX_TUTORIAL_TEXT,
524           va( "Press %s to enter spectator follow mode\n",
525             CG_KeyNameForCommand( "+button2" ) ) );
526     }
527   }
528   else
529   {
530     Q_strcat( text, MAX_TUTORIAL_TEXT,
531         va( "Press %s to spawn\n",
532           CG_KeyNameForCommand( "+attack" ) ) );
533   }
534 }
535 
536 /*
537 ===============
538 CG_TutorialText
539 
540 Returns context help for the current class/weapon
541 ===============
542 */
CG_TutorialText(void)543 const char *CG_TutorialText( void )
544 {
545   playerState_t *ps;
546   static char   text[ MAX_TUTORIAL_TEXT ];
547 
548   CG_GetBindings( );
549 
550   text[ 0 ] = '\0';
551   ps = &cg.snap->ps;
552 
553   if( !cg.intermissionStarted && !cg.demoPlayback )
554   {
555     if( ps->persistant[ PERS_TEAM ] == TEAM_SPECTATOR ||
556         ps->pm_flags & PMF_FOLLOW )
557     {
558       CG_SpectatorText( text, ps );
559     }
560     else if( ps->stats[ STAT_HEALTH ] > 0 )
561     {
562       switch( ps->stats[ STAT_PCLASS ] )
563       {
564         case PCL_ALIEN_BUILDER0:
565         case PCL_ALIEN_BUILDER0_UPG:
566           CG_AlienBuilderText( text, ps );
567           break;
568 
569         case PCL_ALIEN_LEVEL0:
570           CG_AlienLevel0Text( text, ps );
571           break;
572 
573         case PCL_ALIEN_LEVEL1:
574         case PCL_ALIEN_LEVEL1_UPG:
575           CG_AlienLevel1Text( text, ps );
576           break;
577 
578         case PCL_ALIEN_LEVEL2:
579         case PCL_ALIEN_LEVEL2_UPG:
580           CG_AlienLevel2Text( text, ps );
581           break;
582 
583         case PCL_ALIEN_LEVEL3:
584         case PCL_ALIEN_LEVEL3_UPG:
585           CG_AlienLevel3Text( text, ps );
586           break;
587 
588         case PCL_ALIEN_LEVEL4:
589           CG_AlienLevel4Text( text, ps );
590           break;
591 
592         case PCL_HUMAN:
593           CG_HumanText( text, ps );
594           break;
595 
596         default:
597           break;
598       }
599 
600       if( ps->stats[ STAT_PTEAM ] == PTE_ALIENS &&
601           BG_UpgradeClassAvailable( ps ) )
602       {
603         Q_strcat( text, MAX_TUTORIAL_TEXT,
604             va( "Press %s to evolve\n",
605               CG_KeyNameForCommand( "+button7" ) ) );
606       }
607     }
608 
609     Q_strcat( text, MAX_TUTORIAL_TEXT, "Press ESC for the menu" );
610   }
611 
612   return text;
613 }
614