diff options
Diffstat (limited to 'src/cgame/cg_tutorial.c')
-rw-r--r-- | src/cgame/cg_tutorial.c | 663 |
1 files changed, 663 insertions, 0 deletions
diff --git a/src/cgame/cg_tutorial.c b/src/cgame/cg_tutorial.c new file mode 100644 index 0000000..5ad29b7 --- /dev/null +++ b/src/cgame/cg_tutorial.c @@ -0,0 +1,663 @@ +/* +=========================================================================== +Copyright (C) 2000-2006 Tim Angus + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +// cg_tutorial.c -- the tutorial system + +#include "cg_local.h" + +typedef struct +{ + char *command; + char *humanName; + keyNum_t keys[ 2 ]; +} bind_t; + +static bind_t bindings[ ] = +{ + { "+button2", "Activate Upgrade", { -1, -1 } }, + { "+speed", "Run/Walk", { -1, -1 } }, + { "boost", "Sprint", { -1, -1 } }, + { "+moveup", "Jump", { -1, -1 } }, + { "+movedown", "Crouch", { -1, -1 } }, + { "+attack", "Primary Attack", { -1, -1 } }, + { "+button5", "Secondary Attack", { -1, -1 } }, + { "reload", "Reload", { -1, -1 } }, + { "buy ammo", "Buy Ammo", { -1, -1 } }, + { "itemact medkit", "Use Medkit", { -1, -1 } }, + { "+button7", "Use Structure/Evolve", { -1, -1 } }, + { "deconstruct", "Deconstruct Structure", { -1, -1 } }, + { "weapprev", "Previous Upgrade", { -1, -1 } }, + { "weapnext", "Next Upgrade", { -1, -1 } } +}; + +static const int numBindings = sizeof( bindings ) / sizeof( bind_t ); + +/* +================= +CG_GetBindings +================= +*/ +static void CG_GetBindings( void ) +{ + int i, j, numKeys; + char buffer[ MAX_STRING_CHARS ]; + + for( i = 0; i < numBindings; i++ ) + { + bindings[ i ].keys[ 0 ] = bindings[ i ].keys[ 1 ] = K_NONE; + numKeys = 0; + + for( j = 0; j < K_LAST_KEY; j++ ) + { + trap_Key_GetBindingBuf( j, buffer, MAX_STRING_CHARS ); + + if( buffer[ 0 ] == 0 ) + continue; + + if( !Q_stricmp( buffer, bindings[ i ].command ) ) + { + bindings[ i ].keys[ numKeys++ ] = j; + + if( numKeys > 1 ) + break; + } + } + } +} + +/* +=============== +CG_KeyNameForCommand +=============== +*/ +static const char *CG_KeyNameForCommand( const char *command ) +{ + int i, j; + static char buffer[ MAX_STRING_CHARS ]; + int firstKeyLength; + + buffer[ 0 ] = '\0'; + + for( i = 0; i < numBindings; i++ ) + { + if( !Q_stricmp( command, bindings[ i ].command ) ) + { + if( bindings[ i ].keys[ 0 ] != K_NONE ) + { + trap_Key_KeynumToStringBuf( bindings[ i ].keys[ 0 ], + buffer, MAX_STRING_CHARS ); + firstKeyLength = strlen( buffer ); + + for( j = 0; j < firstKeyLength; j++ ) + buffer[ j ] = toupper( buffer[ j ] ); + + if( bindings[ i ].keys[ 1 ] != K_NONE ) + { + Q_strcat( buffer, MAX_STRING_CHARS, " or " ); + trap_Key_KeynumToStringBuf( bindings[ i ].keys[ 1 ], + buffer + strlen( buffer ), MAX_STRING_CHARS - strlen( buffer ) ); + + for( j = firstKeyLength + 4; j < strlen( buffer ); j++ ) + buffer[ j ] = toupper( buffer[ j ] ); + } + } + else + { + Q_strncpyz( buffer, va( "\"%s\"", bindings[ i ].humanName ), + MAX_STRING_CHARS ); + Q_strcat( buffer, MAX_STRING_CHARS, " (unbound)" ); + } + + return buffer; + } + } + + return ""; +} + +#define MAX_TUTORIAL_TEXT 4096 + +/* +=============== +CG_BuildableInRange +=============== +*/ +static entityState_t *CG_BuildableInRange( playerState_t *ps, float *healthFraction ) +{ + vec3_t view, point; + trace_t trace; + entityState_t *es; + int health; + + AngleVectors( cg.refdefViewAngles, view, NULL, NULL ); + VectorMA( cg.refdef.vieworg, 64, view, point ); + CG_Trace( &trace, cg.refdef.vieworg, NULL, NULL, + point, ps->clientNum, MASK_SHOT ); + + es = &cg_entities[ trace.entityNum ].currentState; + + if( healthFraction ) + { + health = es->generic1 & B_HEALTH_MASK; + *healthFraction = (float)health / B_HEALTH_MASK; + } + + if( es->eType == ET_BUILDABLE && + ps->stats[ STAT_PTEAM ] == BG_FindTeamForBuildable( es->modelindex ) ) + return es; + else + return NULL; +} + +/* +=============== +CG_AlienBuilderText +=============== +*/ +static void CG_AlienBuilderText( char *text, playerState_t *ps ) +{ + buildable_t buildable = ps->stats[ STAT_BUILDABLE ] & ~SB_VALID_TOGGLEBIT; + entityState_t *es; + + if( buildable > BA_NONE ) + { + Q_strcat( text, MAX_TUTORIAL_TEXT, + va( "Press %s to place the %s\n", + CG_KeyNameForCommand( "+attack" ), + BG_FindHumanNameForBuildable( buildable ) ) ); + + Q_strcat( text, MAX_TUTORIAL_TEXT, + va( "Press %s to cancel placing the %s\n", + CG_KeyNameForCommand( "+button5" ), + BG_FindHumanNameForBuildable( buildable ) ) ); + } + else + { + Q_strcat( text, MAX_TUTORIAL_TEXT, + va( "Press %s to build a structure\n", + CG_KeyNameForCommand( "+attack" ) ) ); + + if( ( es = CG_BuildableInRange( ps, NULL ) ) ) + { + if( cgs.markDeconstruct ) + { + if( es->generic1 & B_MARKED_TOGGLEBIT ) + { + Q_strcat( text, MAX_TUTORIAL_TEXT, + va( "Press %s to unmark this structure\n", + CG_KeyNameForCommand( "deconstruct" ) ) ); + } + else + { + Q_strcat( text, MAX_TUTORIAL_TEXT, + va( "Press %s to mark this structure\n", + CG_KeyNameForCommand( "deconstruct" ) ) ); + } + } + else + { + Q_strcat( text, MAX_TUTORIAL_TEXT, + va( "Press %s to destroy this structure\n", + CG_KeyNameForCommand( "deconstruct" ) ) ); + } + } + } + + if( ps->stats[ STAT_PCLASS ] == PCL_ALIEN_BUILDER0_UPG ) + { + if( ( ps->stats[ STAT_BUILDABLE ] & ~SB_VALID_TOGGLEBIT ) == BA_NONE ) + { + Q_strcat( text, MAX_TUTORIAL_TEXT, + va( "Press %s to swipe\n", + CG_KeyNameForCommand( "+button5" ) ) ); + } + + Q_strcat( text, MAX_TUTORIAL_TEXT, + va( "Press %s to launch a projectile\n", + CG_KeyNameForCommand( "+button2" ) ) ); + + Q_strcat( text, MAX_TUTORIAL_TEXT, + va( "Press %s to walk on walls\n", + CG_KeyNameForCommand( "+movedown" ) ) ); + } +} + +/* +=============== +CG_AlienLevel0Text +=============== +*/ +static void CG_AlienLevel0Text( char *text, playerState_t *ps ) +{ + Q_strcat( text, MAX_TUTORIAL_TEXT, + "Touch a human to damage it\n" ); + + Q_strcat( text, MAX_TUTORIAL_TEXT, + va( "Press %s to walk on walls\n", + CG_KeyNameForCommand( "+movedown" ) ) ); +} + +/* +=============== +CG_AlienLevel1Text +=============== +*/ +static void CG_AlienLevel1Text( char *text, playerState_t *ps ) +{ + Q_strcat( text, MAX_TUTORIAL_TEXT, + "Touch a human to grab it\n" ); + + Q_strcat( text, MAX_TUTORIAL_TEXT, + va( "Press %s to swipe\n", + CG_KeyNameForCommand( "+attack" ) ) ); + + if( ps->stats[ STAT_PCLASS ] == PCL_ALIEN_LEVEL1_UPG ) + { + Q_strcat( text, MAX_TUTORIAL_TEXT, + va( "Press %s to spray poisonous gas\n", + CG_KeyNameForCommand( "+button5" ) ) ); + } + + Q_strcat( text, MAX_TUTORIAL_TEXT, + va( "Press %s to walk on walls\n", + CG_KeyNameForCommand( "+movedown" ) ) ); +} + +/* +=============== +CG_AlienLevel2Text +=============== +*/ +static void CG_AlienLevel2Text( char *text, playerState_t *ps ) +{ + Q_strcat( text, MAX_TUTORIAL_TEXT, + va( "Press %s to bite\n", + CG_KeyNameForCommand( "+attack" ) ) ); + + if( ps->stats[ STAT_PCLASS ] == PCL_ALIEN_LEVEL2_UPG ) + { + Q_strcat( text, MAX_TUTORIAL_TEXT, + va( "Press %s to invoke an electrical attack\n", + CG_KeyNameForCommand( "+button5" ) ) ); + } + + Q_strcat( text, MAX_TUTORIAL_TEXT, + va( "Hold down %s then touch a wall to wall jump\n", + CG_KeyNameForCommand( "+moveup" ) ) ); +} + +/* +=============== +CG_AlienLevel3Text +=============== +*/ +static void CG_AlienLevel3Text( char *text, playerState_t *ps ) +{ + Q_strcat( text, MAX_TUTORIAL_TEXT, + va( "Press %s to bite\n", + CG_KeyNameForCommand( "+attack" ) ) ); + + if( ps->stats[ STAT_PCLASS ] == PCL_ALIEN_LEVEL3_UPG ) + { + Q_strcat( text, MAX_TUTORIAL_TEXT, + va( "Press %s to launch a projectile\n", + CG_KeyNameForCommand( "+button2" ) ) ); + } + + Q_strcat( text, MAX_TUTORIAL_TEXT, + va( "Hold down and release %s to pounce\n", + CG_KeyNameForCommand( "+button5" ) ) ); +} + +/* +=============== +CG_AlienLevel4Text +=============== +*/ +static void CG_AlienLevel4Text( char *text, playerState_t *ps ) +{ + Q_strcat( text, MAX_TUTORIAL_TEXT, + va( "Press %s to swipe\n", + CG_KeyNameForCommand( "+attack" ) ) ); + + Q_strcat( text, MAX_TUTORIAL_TEXT, + va( "Hold down and release %s to charge\n", + CG_KeyNameForCommand( "+button5" ) ) ); +} + +/* +=============== +CG_HumanCkitText +=============== +*/ +static void CG_HumanCkitText( char *text, playerState_t *ps ) +{ + buildable_t buildable = ps->stats[ STAT_BUILDABLE ] & ~SB_VALID_TOGGLEBIT; + float health; + + if( buildable > BA_NONE ) + { + Q_strcat( text, MAX_TUTORIAL_TEXT, + va( "Press %s to place the %s\n", + CG_KeyNameForCommand( "+attack" ), + BG_FindHumanNameForBuildable( buildable ) ) ); + + Q_strcat( text, MAX_TUTORIAL_TEXT, + va( "Press %s to cancel placing the %s\n", + CG_KeyNameForCommand( "+button5" ), + BG_FindHumanNameForBuildable( buildable ) ) ); + } + else + { + Q_strcat( text, MAX_TUTORIAL_TEXT, + va( "Press %s to build a structure\n", + CG_KeyNameForCommand( "+attack" ) ) ); + + if( CG_BuildableInRange( ps, &health ) ) + { + if( health < 1.0f ) + { + Q_strcat( text, MAX_TUTORIAL_TEXT, + va( "Hold %s to repair this structure\n", + CG_KeyNameForCommand( "+button5" ) ) ); + } + + Q_strcat( text, MAX_TUTORIAL_TEXT, + va( "Press %s to destroy this structure\n", + CG_KeyNameForCommand( "deconstruct" ) ) ); + } + } +} + +/* +=============== +CG_HumanText +=============== +*/ +static void CG_HumanText( char *text, playerState_t *ps ) +{ + char *name; + int ammo, clips; + upgrade_t upgrade = UP_NONE; + + if( cg.weaponSelect <= 32 ) + name = cg_weapons[ cg.weaponSelect ].humanName; + else if( cg.weaponSelect > 32 ) + { + name = cg_upgrades[ cg.weaponSelect - 32 ].humanName; + upgrade = cg.weaponSelect - 32; + } + + BG_UnpackAmmoArray( ps->weapon, ps->ammo, ps->powerups, &ammo, &clips ); + + if( !ammo && !clips && !BG_FindInfinteAmmoForWeapon( ps->weapon ) ) + { + //no ammo + switch( ps->weapon ) + { + case WP_MACHINEGUN: + case WP_CHAINGUN: + case WP_SHOTGUN: + case WP_FLAMER: + Q_strcat( text, MAX_TUTORIAL_TEXT, + va( "Find an Armoury and press %s for more ammo\n", + CG_KeyNameForCommand( "buy ammo" ) ) ); + break; + + case WP_LAS_GUN: + case WP_PULSE_RIFLE: + case WP_MASS_DRIVER: + case WP_LUCIFER_CANNON: + Q_strcat( text, MAX_TUTORIAL_TEXT, + va( "Find a Reactor or Repeater and press %s for more ammo\n", + CG_KeyNameForCommand( "buy ammo" ) ) ); + break; + + default: + break; + } + } + else + { + switch( ps->weapon ) + { + case WP_BLASTER: + case WP_MACHINEGUN: + case WP_SHOTGUN: + case WP_LAS_GUN: + case WP_CHAINGUN: + case WP_PULSE_RIFLE: + case WP_FLAMER: + Q_strcat( text, MAX_TUTORIAL_TEXT, + va( "Press %s to fire the %s\n", + CG_KeyNameForCommand( "+attack" ), + BG_FindHumanNameForWeapon( ps->weapon ) ) ); + break; + + case WP_MASS_DRIVER: + Q_strcat( text, MAX_TUTORIAL_TEXT, + va( "Press %s to fire the %s\n", + CG_KeyNameForCommand( "+attack" ), + BG_FindHumanNameForWeapon( ps->weapon ) ) ); + + Q_strcat( text, MAX_TUTORIAL_TEXT, + va( "Hold %s to zoom\n", + CG_KeyNameForCommand( "+button5" ) ) ); + break; + + case WP_PAIN_SAW: + Q_strcat( text, MAX_TUTORIAL_TEXT, + va( "Hold %s to activate the %s\n", + CG_KeyNameForCommand( "+attack" ), + BG_FindHumanNameForWeapon( ps->weapon ) ) ); + break; + + case WP_LUCIFER_CANNON: + Q_strcat( text, MAX_TUTORIAL_TEXT, + va( "Hold and release %s to fire a charged shot\n", + CG_KeyNameForCommand( "+attack" ) ) ); + + Q_strcat( text, MAX_TUTORIAL_TEXT, + va( "Press %s to fire the %s\n", + CG_KeyNameForCommand( "+button5" ), + BG_FindHumanNameForWeapon( ps->weapon ) ) ); + break; + + case WP_HBUILD: + case WP_HBUILD2: + CG_HumanCkitText( text, ps ); + break; + + default: + break; + } + } + + Q_strcat( text, MAX_TUTORIAL_TEXT, + va( "Press %s and ", + CG_KeyNameForCommand( "weapprev" ) ) ); + Q_strcat( text, MAX_TUTORIAL_TEXT, + va( "%s to select an upgrade\n", + CG_KeyNameForCommand( "weapnext" ) ) ); + + if( upgrade == UP_NONE || + ( upgrade > UP_NONE && BG_FindUsableForUpgrade( upgrade ) ) ) + { + Q_strcat( text, MAX_TUTORIAL_TEXT, + va( "Press %s to use the %s\n", + CG_KeyNameForCommand( "+button2" ), + name ) ); + } + + if( ps->stats[ STAT_HEALTH ] <= 35 && + BG_InventoryContainsUpgrade( UP_MEDKIT, ps->stats ) ) + { + Q_strcat( text, MAX_TUTORIAL_TEXT, + va( "Press %s to use your %s\n", + CG_KeyNameForCommand( "itemact medkit" ), + BG_FindHumanNameForUpgrade( UP_MEDKIT ) ) ); + } + + Q_strcat( text, MAX_TUTORIAL_TEXT, + va( "Press %s to use a structure\n", + CG_KeyNameForCommand( "+button7" ) ) ); + + Q_strcat( text, MAX_TUTORIAL_TEXT, + va( "Press %s to sprint\n", + CG_KeyNameForCommand( "boost" ) ) ); +} + +/* +=============== +CG_SpectatorText +=============== +*/ +static void CG_SpectatorText( char *text, playerState_t *ps ) +{ + if( cgs.clientinfo[ cg.clientNum ].team != PTE_NONE ) + { + Q_strcat( text, MAX_TUTORIAL_TEXT, + va( "Press %s to spawn\n", CG_KeyNameForCommand( "+attack" ) ) ); + } + else + { + Q_strcat( text, MAX_TUTORIAL_TEXT, + va( "Press %s to join a team\n", CG_KeyNameForCommand( "+attack" ) ) ); + } + + if( ps->pm_flags & PMF_FOLLOW ) + { + Q_strcat( text, MAX_TUTORIAL_TEXT, + va( "Press %s to stop following\n", + CG_KeyNameForCommand( "+button2" ) ) ); + + Q_strcat( text, MAX_TUTORIAL_TEXT, + va( "Press %s or ", CG_KeyNameForCommand( "weapprev" ) ) ); + Q_strcat( text, MAX_TUTORIAL_TEXT, + va( "%s to change player\n", CG_KeyNameForCommand( "weapnext" ) ) ); + } + else + { + Q_strcat( text, MAX_TUTORIAL_TEXT, + va( "Press %s to follow a %s\n", CG_KeyNameForCommand( "+button2" ), + ( cgs.clientinfo[ cg.clientNum ].team == PTE_NONE ) + ? "player" : "teammate" ) ); + } +} + +/* +=============== +CG_TutorialText + +Returns context help for the current class/weapon +=============== +*/ +const char *CG_TutorialText( void ) +{ + playerState_t *ps; + static char text[ MAX_TUTORIAL_TEXT ]; + + CG_GetBindings( ); + + text[ 0 ] = '\0'; + ps = &cg.snap->ps; + + if( !cg.intermissionStarted && !cg.demoPlayback ) + { + if( ps->persistant[ PERS_TEAM ] == TEAM_SPECTATOR || + ps->pm_flags & PMF_FOLLOW ) + { + CG_SpectatorText( text, ps ); + } + else if( ps->stats[ STAT_HEALTH ] > 0 ) + { + switch( ps->stats[ STAT_PCLASS ] ) + { + case PCL_ALIEN_BUILDER0: + case PCL_ALIEN_BUILDER0_UPG: + CG_AlienBuilderText( text, ps ); + break; + + case PCL_ALIEN_LEVEL0: + CG_AlienLevel0Text( text, ps ); + break; + + case PCL_ALIEN_LEVEL1: + case PCL_ALIEN_LEVEL1_UPG: + CG_AlienLevel1Text( text, ps ); + break; + + case PCL_ALIEN_LEVEL2: + case PCL_ALIEN_LEVEL2_UPG: + CG_AlienLevel2Text( text, ps ); + break; + + case PCL_ALIEN_LEVEL3: + case PCL_ALIEN_LEVEL3_UPG: + CG_AlienLevel3Text( text, ps ); + break; + + case PCL_ALIEN_LEVEL4: + CG_AlienLevel4Text( text, ps ); + break; + + case PCL_HUMAN: + CG_HumanText( text, ps ); + break; + + default: + break; + } + + if( ps->stats[ STAT_PTEAM ] == PTE_ALIENS ) + { + entityState_t *es = CG_BuildableInRange( ps, NULL ); + + if( ps->stats[ STAT_STATE ] & SS_HOVELING ) + { + Q_strcat( text, MAX_TUTORIAL_TEXT, + va( "Press %s to exit the hovel\n", + CG_KeyNameForCommand( "+button7" ) ) ); + } + else if( es && es->modelindex == BA_A_HOVEL && + es->generic1 & B_SPAWNED_TOGGLEBIT && + ( ps->stats[ STAT_PCLASS ] == PCL_ALIEN_BUILDER0 || + ps->stats[ STAT_PCLASS ] == PCL_ALIEN_BUILDER0_UPG ) ) + { + Q_strcat( text, MAX_TUTORIAL_TEXT, + va( "Press %s to enter the hovel\n", + CG_KeyNameForCommand( "+button7" ) ) ); + } + else if( BG_UpgradeClassAvailable( ps ) ) + { + Q_strcat( text, MAX_TUTORIAL_TEXT, + va( "Press %s to evolve\n", + CG_KeyNameForCommand( "+button7" ) ) ); + } + } + } + + Q_strcat( text, MAX_TUTORIAL_TEXT, "Press ESC for the menu" ); + } + + return text; +} |