shithub: candycrisis

ref: 56f1e92bcab1dad60dad8791428b84a0a4c0453b
dir: /src/tutorial.c/

View raw version
// tutorial.c

#include "SDLU.h"

#include "main.h"
#include "tutorial.h"
#include "level.h"
#include "font.h"
#include "pause.h"
#include "gworld.h"
#include "graphics.h"
#include "control.h"
#include "blitter.h"
#include "gameticks.h"
#include "soundfx.h"
#include "opponent.h"
#include "keyselect.h"

AutoPattern tutorialPattern[] =
{
	{ kMessage,        0,   0,   "Welcome to the\nCandy Crisis\ntutorial!" },
	{ kIdleTicks,      180, 0,   NULL },
	{ kMessage,        0,   0,   "I'll be your guide\nthroughout the\ntutorial. Let's\nget started!" },
	{ kRetrieve,       1,   1,   NULL },
	{ kIdleTicks,      240, 0,   NULL },	
	{ kMessage,        0,   0,   "When you start the\ngame, you'll find a\npair of candies\nfalling from the sky." },
	{ kIdleTicks,      240, 0,   NULL },	
	{ kMessage,        0,   0,   "Your goal is to\nkeep these candies\nfrom overflowing the\nboard!" },
	{ kBlockUntilLand, 0,   0,   NULL },
	{ kRetrieve,       2,   2,   NULL },
	{ kIdleTicks,      60,  0,   NULL },
	{ kMessage,        0,   0,   "You control the\ncandy by moving\nthem left and right." },
	{ kIdleTicks,      120, 0,   NULL },
	{ kPosition,       0,   0,   NULL },
	{ kIdleTicks,      120, 0,   NULL },
	{ kBlockUntilDrop, 0,   0,   NULL },
	{ kMessage,        0,   0,   "Press '~~'\nto make the pieces\nmove left." },
	{ kRetrieve,       3,   3,   NULL },
	{ kIdleTicks,      60,  0,   NULL },
	{ kPosition,       5,   0,   NULL },
	{ kIdleTicks,      90,  0,   NULL },
	{ kMessage,        0,   0,  "Use '||' to\nmake the pieces\nmove right." },
	{ kIdleTicks,      180, 0,   NULL },
	{ kBlockUntilDrop, 0,   0,   NULL },
	{ kMessage,        0,   0,   "You can also\nmake the pieces\nrotate around each\nother." },
	{ kRetrieve,       4,   3,   NULL },
	{ kIdleTicks,      180, 0,   NULL },
	{ kSpin,           0,   0,   NULL },
	{ kIdleTicks,      30,  0,   NULL },
	{ kSpin,           0,   0,   NULL },
	{ kMessage,        0,   0,   "Press '{{'\nto make the pieces\nrotate." },
	{ kIdleTicks,      30,  0,   NULL },
	{ kSpin,           0,   0,   NULL },
	{ kIdleTicks,      30,  0,   NULL },
	{ kSpin,           0,   0,   NULL },
	{ kIdleTicks,      30,  0,   NULL },
	{ kSpin,           0,   0,   NULL },
	{ kIdleTicks,      30,  0,   NULL },
	{ kSpin,           0,   0,   NULL },
	{ kIdleTicks,      30,  0,   NULL },
	{ kPosition,       5,   0,   NULL },
	{ kIdleTicks,      30,  0,   NULL },
	{ kMessage,        0,   0,   "Also, '``'\ncauses the candy\nto drop faster." },
	{ kIdleTicks,      180, 0,   NULL },	
	{ kBlockUntilDrop, 0,   0,   NULL },	
	{ kMessage,        0,   0,   "To pause the game\nor adjust settings,\npress 'esc.'" },
	{ kIdleTicks,      280, 0,   NULL },	
	{ kMessage,        0,   0,   "The candy in\nthis game is made\nfrom a highly\nunstable substance!" },
	{ kRetrieve,       2,   2,   NULL },
	{ kIdleTicks,      200, 0,   NULL },
	{ kMessage,        0,   0,   "So when four\npieces of the same\ncolor come into\ncontact..." },
	{ kPosition,       1,   0,   NULL },
	{ kIdleTicks,      180, 0,   NULL },
	{ kMessage,        0,   0,   "... they vaporize!" },
	{ kIdleTicks,      30,  0,   NULL },
	{ kBlockUntilDrop, 0,   0,   NULL },	
	{ kIdleTicks,      60,  0,   NULL },
	{ kMessage,        0,   0,   "Let's see that\nonce again." },
	{ kRetrieve,       1,   1,   NULL },
	{ kIdleTicks,      120, 0,   NULL },
	{ kBlockUntilDrop, 0,   0,   NULL },	
	{ kMessage,        0,   0,   "Pop!" },
	{ kIdleTicks,      120, 0,   NULL },	
	{ kMessage,        0,   0,   "You can even get\nfive or more\npieces to pop\nall at the same\ntime!" },
	{ kRetrieve,       4,   4,   NULL },
	{ kIdleTicks,      30,  0,   NULL },
	{ kSpin,           0,   0,   NULL },
	{ kPosition,       4,   0,   NULL },
	{ kIdleTicks,      30,  0,   NULL },
	{ kBlockUntilDrop, 0,   0,   NULL },	
	{ kRetrieve,       4,   4,   NULL },
	{ kIdleTicks,      30,  0,   NULL },
	{ kSpin,           0,   0,   NULL },
	{ kPosition,       3,   0,   NULL },
	{ kIdleTicks,      30,  0,   NULL },
	{ kBlockUntilDrop, 0,   0,   NULL },	
	{ kRetrieve,       4,   4,   NULL },
	{ kIdleTicks,      30,  0,   NULL },
	{ kPosition,       4,   0,   NULL },
	{ kBlockUntilDrop, 0,   0,   NULL },	
	{ kMessage,        0,   0,   "Pop!\n\nTechniques like this\ncan earn lots of\nbonus points." },
	{ kIdleTicks,      180, 0,   NULL },
	{ kMessage,        0,   0,   "You can also pop\nmore than one color\nat once." },
	{ kRetrieve,       5,   5,   NULL },
	{ kIdleTicks,      30,  0,   NULL },
	{ kPosition,       4,   0,   NULL },
	{ kIdleTicks,      30,  0,   NULL },
	{ kBlockUntilDrop, 0,   0,   NULL },
	{ kRetrieve,       6,   5,   NULL },
	{ kIdleTicks,      30,  0,   NULL },
	{ kSpin,           0,   0,   NULL },
	{ kIdleTicks,      30,  0,   NULL },
	{ kPosition,       3,   0,   NULL },
	{ kIdleTicks,      30,  0,   NULL },
	{ kBlockUntilDrop, 0,   0,   NULL },
	{ kRetrieve,       3,   5,   NULL },
	{ kIdleTicks,      30,  0,   NULL },
	{ kSpin,           0,   0,   NULL },
	{ kIdleTicks,      10,  0,   NULL },
	{ kSpin,           0,   0,   NULL },
	{ kIdleTicks,      10,  0,   NULL },
	{ kSpin,           0,   0,   NULL },
	{ kIdleTicks,      30,  0,   NULL },
	{ kPosition,       5,   0,   NULL },
	{ kIdleTicks,      30,  0,   NULL },
	{ kMessage,        0,   0,   "All right!" },
	{ kBlockUntilDrop, 0,   0,   NULL },
	{ kRetrieve,       0,   6,   NULL },
	{ kIdleTicks,      30,  0,   NULL },
	{ kSpin,           0,   0,   NULL },
	{ kIdleTicks,      30,  0,   NULL },
	{ kMessage,        0,   0,   "You can even set\nup devastating\nchain reactions..." },
	{ kBlockUntilDrop, 0,   0,   NULL },
	{ kRetrieve,       0,   6,   NULL },
	{ kIdleTicks,      90,  0,   NULL },
	{ kBlockUntilDrop, 0,   0,   NULL },
	{ kRetrieve,       6,   6,   NULL },
	{ kIdleTicks,      60,  0,   NULL },
	{ kBlockUntilDrop, 0,   0,   NULL },
	{ kRetrieve,       0,   0,   NULL },
	{ kIdleTicks,      30,  0,   NULL },
	{ kPosition,       1,   0,   NULL },
	{ kMessage,        0,   0,   "... like this!" },
	{ kIdleTicks,      30,  0,   NULL },
	{ kBlockUntilDrop, 0,   0,   NULL },
	{ kIdleTicks,      30,  0,   NULL },
	{ kMessage,        0,   0,   "Tricky, isn't it?" },
	{ kRetrieve,       1,   2,   NULL },
	{ kIdleTicks,      60,  0,   NULL },
	{ kSpin,           0,   0,   NULL },
	{ kIdleTicks,      30,  0,   NULL },
	{ kBlockUntilDrop, 0,   0,   NULL },
	{ kRetrieve,       1,   2,   NULL },
	{ kIdleTicks,      60,  0,   NULL },
	{ kSpin,           0,   0,   NULL },
	{ kIdleTicks,      30,  0,   NULL },
	{ kBlockUntilDrop, 0,   0,   NULL },
	{ kRetrieve,       3,   1,   NULL },
	{ kIdleTicks,      60,  0,   NULL },
	{ kBlockUntilDrop, 0,   0,   NULL },
	{ kRetrieve,       3,   2,   NULL },
	{ kIdleTicks,      30,  0,   NULL },
	{ kPosition,       3,   0,   NULL },
	{ kMessage,        0,   0,  "Let's see one more\nexample of a chain\nreaction." },
	{ kIdleTicks,      30,  0,   NULL },
	{ kBlockUntilDrop, 0,   0,   NULL },
	{ kRetrieve,       1,   2,   NULL },
	{ kIdleTicks,      60,  0,   NULL },
	{ kSpin,           0,   0,   NULL },
	{ kIdleTicks,      30,  0,   NULL },
	{ kBlockUntilDrop, 0,   0,   NULL },	
	{ kRetrieve,       5,   5,   NULL },
	{ kIdleTicks,      30,  0,   NULL },
	{ kPosition,       1,   0,   NULL },
	{ kIdleTicks,      30,  0,   NULL },
	{ kBlockUntilDrop, 0,   0,   NULL },	
	{ kRetrieve,       5,   3,   NULL },
	{ kSpin,           0,   0,   NULL },
	{ kIdleTicks,      10,  0,   NULL },
	{ kSpin,           0,   0,   NULL },
	{ kIdleTicks,      10,  0,   NULL },
	{ kPosition,       1,   0,   NULL },
	{ kIdleTicks,      30,  0,   NULL },
	{ kBlockUntilDrop, 0,   0,   NULL },	
	{ kRetrieve,       3,   5,   NULL },
	{ kIdleTicks,      30,  0,   NULL },
	{ kPosition,       0,   0,   NULL },
	{ kIdleTicks,      30,  0,   NULL },
	{ kSpin,           0,   0,   NULL },
	{ kIdleTicks,      10,  0,   NULL },
	{ kBlockUntilDrop, 0,   0,   NULL },	
	{ kRetrieve,       3,   3,   NULL },
	{ kIdleTicks,      30,  0,   NULL },
	{ kPosition,       0,   0,   NULL },
	{ kIdleTicks,      20,  0,   NULL },
	{ kBlockUntilDrop, 0,   0,   NULL },	
	{ kIdleTicks,      90,  0,   NULL }, 
	{ kMessage,        0,   0,   "There's one more\nthing you need to\nknow about..." },
	{ kIdleTicks,      180, 0,   NULL },
	{ kMessage,        0,   0,   "Watch out for the\nsee-through candy!" },
	{ kIdleTicks,      60,  0,   NULL },
	{ kPunish,         18,  0,   NULL },
	{ kRetrieve,       1,   1,   NULL },
	{ kIdleTicks,      120, 0,   NULL },
	{ kSpin,           0,   0,   NULL },
	{ kPosition,       0,   0,   NULL },
	{ kIdleTicks,      20,  0,   NULL },
	{ kBlockUntilDrop, 0,   0,   NULL },	
	{ kMessage,        0,   0,   "When your opponent\nvaporizes a group of\ncandies, they also\nsend some transparent\npieces to you!" },
	{ kRetrieve,       1,   1,   NULL },
	{ kIdleTicks,      90,  0,   NULL },
	{ kSpin,           0,   0,   NULL },
	{ kPosition,       4,   0,   NULL },
	{ kIdleTicks,      30,  0,   NULL },
	{ kBlockUntilDrop, 0,   0,   NULL },	
	{ kRetrieve,       1,   1,   NULL },
	{ kIdleTicks,      60,  0,   NULL },
	{ kSpin,           0,   0,   NULL },
	{ kIdleTicks,      90,  0,   NULL },
	{ kMessage,        0,   0,   "You can get rid of\nthese pieces by\nvaporizing something\nnext to them." },
	{ kIdleTicks,      150, 0,   NULL },
	{ kBlockUntilDrop, 0,   0,   NULL },	
	{ kIdleTicks,      60,  0,   NULL },
	{ kRetrieve,       1,   2,   NULL },
	{ kMessage,        0,   0,   "There are also\nsome bonus items\nwhich can come\nin handy." },
	{ kIdleTicks,      20,  0,   NULL },
	{ kSpin,           0,   0,   NULL },
	{ kPosition,       0,   0,   NULL },
	{ kIdleTicks,      20,  0,   NULL },
	{ kBlockUntilDrop, 0,   0,   NULL },	
	{ kRetrieve,       3,   1,   NULL },
	{ kIdleTicks,      20,  0,   NULL },
	{ kSpin,           0,   0,   NULL },
	{ kIdleTicks,      10,  0,   NULL },
	{ kSpin,           0,   0,   NULL },
	{ kIdleTicks,      10,  0,   NULL },
	{ kPosition,       0,   0,   NULL },
	{ kIdleTicks,      20,  0,   NULL },
	{ kBlockUntilDrop, 0,   0,   NULL },	
	{ kRetrieve,       1,   3,   NULL },
	{ kIdleTicks,      10,  0,   NULL },
	{ kPosition,       1,   0,   NULL },
	{ kIdleTicks,      15,  0,   NULL },
	{ kBlockUntilDrop, 0,   0,   NULL },	
	{ kMessage,        0,   0,   "One of these bonus\nitems is the Crazy\nCandy!" },
	{ kRetrieve,       -1,  2,   NULL },
	{ kIdleTicks,      15,  0,   NULL },
	{ kSpin,           0,   0,   NULL },
	{ kIdleTicks,      10,  0,   NULL },
	{ kSpin,           0,   0,   NULL },
	{ kIdleTicks,      100, 0,   NULL },
	{ kBlockUntilDrop, 0,   0,   NULL },	
	{ kMessage,        0,   0,   "The Crazy Candy\ntakes on the\ncolor of one of\nits neighbors." },
	{ kIdleTicks,      60,  0,   NULL },
	{ kRetrieve,       3,   4,   NULL },
	{ kIdleTicks,      5,   0,   NULL },
	{ kSpin,           0,   0,   NULL },
	{ kPosition,       4,   0,   NULL },
	{ kIdleTicks,      5,   0,   NULL },
	{ kBlockUntilDrop, 0,   0,   NULL },	
	{ kRetrieve,       1,   5,   NULL },
	{ kIdleTicks,      5,   0,   NULL },
	{ kSpin,           0,   0,   NULL },
	{ kIdleTicks,      5,   0,   NULL },
	{ kSpin,           0,   0,   NULL },
	{ kIdleTicks,      5,   0,   NULL },
	{ kSpin,           0,   0,   NULL },
	{ kIdleTicks,      5,   0,   NULL },
	{ kPosition,       1,   0,   NULL },
	{ kBlockUntilDrop, 0,   0,   NULL },	
	{ kMessage,        0,   0,   "Another useful bonus\nitem is the bomb!" },
	{ kRetrieve,       2,   3,   NULL },
	{ kIdleTicks,      5,   0,   NULL },
	{ kPosition,       0,   0,   NULL },
	{ kBlockUntilDrop, 0,   0,   NULL },	
	{ kRetrieve,       0,   3,   NULL },
	{ kIdleTicks,      5,   0,   NULL },
	{ kBlockUntilDrop, 0,   0,   NULL },	
	{ kRetrieve,       3,   2,   NULL },
	{ kIdleTicks,      5,   0,   NULL },
	{ kPosition,       5,   0,   NULL },
	{ kIdleTicks,      5,   0,   NULL },
	{ kSpin,           0,   0,   NULL },
	{ kBlockUntilDrop, 0,   0,   NULL },	
	{ kRetrieve,       4,   3,   NULL },
	{ kIdleTicks,      5,   0,   NULL },
	{ kPosition,       3,   0,   NULL },
	{ kBlockUntilDrop, 0,   0,   NULL },	
	{ kRetrieve,       2,   3,   NULL },
	{ kIdleTicks,      5,   0,   NULL },
	{ kSpin,           0,   0,   NULL },
	{ kIdleTicks,      5,   0,   NULL },
	{ kSpin,           0,   0,   NULL },
	{ kIdleTicks,      5,   0,   NULL },
	{ kPosition,       5,   0,   NULL },
	{ kBlockUntilDrop, 0,   0,   NULL },	
	{ kRetrieve, kBombBottom, kBombTop, NULL },
	{ kMessage,        0,   0,   "When the bomb lands\non one color\nof candy, all\npieces of that\ncolor will\nvaporize!" },
	{ kIdleTicks,      100, 0,   NULL },
	{ kPosition,       4,   0,   NULL },
	{ kIdleTicks,      120, 0,   NULL },
	{ kBlockUntilDrop, 0,   0,   NULL },
	{ kIdleTicks,      200, 0,   NULL },
	{ kMessage,        0,   0,   "Now you're ready\nto play Candy Crisis.\nGood luck!" },
	{ kIdleTicks,      270, 0,   NULL },

	{ kComplete,       0,   0,   NULL }
};

MRect           balloonRect = {0, 0, 208, 236};
SkittlesFontPtr balloonFont;
MPoint          balloonPt;
char*           balloonChar;
char            balloonMsg[256];
MTicks          balloonTime, tutorialTime;
SDL_Surface*    balloonSurface = NULL;

void InitTutorial( void )
{
	// Balloon font
	balloonFont = GetFont( picBalloonFont );
	
	// Balloon backbuffer
	if( balloonSurface == NULL )
	{
		SDL_Rect surfaceRect = { 0, 0, backdropSurface->w, backdropSurface->h }; 
		balloonSurface = SDLU_InitSurface( &surfaceRect, 32 );
	}
	
	// Set up auto pattern
	autoPattern = tutorialPattern;	
	tutorialTime = 0;
}

void EndTutorial( void )
{
	QuickFadeOut( NULL );
	
	showStartMenu = true;
}

static int CalculateBalloonWidth( char *message )
{
	int maxWidth = 40;
	int currentWidth = 0;
	
	for( ;; )
	{
		char in = *message++;
		
		switch(in)
		{
			case 0:
				return (currentWidth > maxWidth)? currentWidth: maxWidth;
				
			case '\n':
				maxWidth = (currentWidth > maxWidth)? currentWidth: maxWidth;
				currentWidth = 0;
				break;
			
			default:
				currentWidth += balloonFont->width[(uint8_t) in];
				break;
		}
	}
}

static int CalculateBalloonHeight( char *message )
{
	int lines = 2;
	char *scan = message;
	
	while( *scan ) lines += (*scan++ == '\n');

	return lines * 20;
}

void StopBalloon( void )
{
	balloonTime = 0x7FFFFFFF;
}

void StartBalloon( const char *message )
{
	MPoint      balloonTip, balloonFill;
	int         replace;
	const char* match[] = { "~~", "||", "``", "{{" };
	char*       search;
	SDL_Rect    balloonSDLRect, balloonContentsSDLRect;
	MRect       balloonContentsRect;
	
	SDL_strlcpy( balloonMsg, message, sizeof(balloonMsg) );
	for( replace=0; replace<4; replace++ )
	{
		search = SDL_strstr( balloonMsg, match[replace] );
		if( search )
		{
			char temp[256];
			
			search[0] = '%';
			search[1] = 's';
			SDL_snprintf( temp, sizeof(temp), balloonMsg, SDL_GetKeyName( playerKeys[1][replace] ) );
			SDL_strlcpy( balloonMsg, temp, sizeof(balloonMsg) );
		}
	}
	
	// Erase previous balloons
	SDLU_MRectToSDLRect( &balloonRect, &balloonSDLRect );
	SDLU_BlitFrontSurface( backdropSurface, &balloonSDLRect, &balloonSDLRect );

	// Draw empty balloon outline
	SDLU_AcquireSurface( balloonSurface );

	balloonRect.left = balloonRect.right - 25 - CalculateBalloonWidth ( balloonMsg );
	balloonRect.top = balloonRect.bottom - 25 - CalculateBalloonHeight( balloonMsg );

	SDLU_MRectToSDLRect( &balloonRect, &balloonSDLRect );
	SDLU_BlitSurface( backdropSurface, &balloonSDLRect,
	                  balloonSurface,  &balloonSDLRect  );
	
	balloonContentsRect = balloonRect;
	balloonContentsRect.bottom -= 25;
		
	SurfaceGetEdges( balloonSurface, &balloonContentsRect );
	SDL_FillRect( balloonSurface, 
				  SDLU_MRectToSDLRect( &balloonContentsRect, &balloonContentsSDLRect ), 
				  SDL_MapRGB( balloonSurface->format, 0xFF, 0xFF, 0xFF ) );
	SurfaceCurveEdges( balloonSurface, &balloonContentsRect );
	
	balloonTip.v = balloonContentsRect.bottom - 2;
	balloonTip.h = balloonContentsRect.right - 60;
	balloonFill = balloonTip;

	SurfaceBlitCharacter( balloonFont, '\x01', &balloonFill,  0,   0,   0,  0 );
	SurfaceBlitCharacter( balloonFont, '\x02', &balloonTip, 255, 255, 255,  0 );
	
	SDLU_ReleaseSurface( balloonSurface );

	// Blit empty balloon to screen
	SDLU_MRectToSDLRect( &balloonRect, &balloonSDLRect );
	SDLU_BlitFrontSurface( balloonSurface, &balloonSDLRect, &balloonSDLRect );
	
	balloonPt.h = balloonRect.left + 10;
	balloonPt.v = balloonRect.top + 10;
	balloonChar = balloonMsg;
	balloonTime = GameTickCount( );
	
	OpponentChatter( true );
}

void UpdateBalloon( void )
{
	SDL_Rect balloonSDLRect;
	
	if( control[0] != kAutoControl ) return;
	if( GameTickCount() < balloonTime ) return;
	
	if( balloonChar )
	{
		unsigned char in = *balloonChar++;
				
		switch( in )
		{
			case 0:
				OpponentChatter( false );
				balloonChar = NULL;
				balloonTime += 120;
				break;
				
			case '\n':
				balloonPt.h = balloonRect.left + 10;
				balloonPt.v += 20;
				break;
				
			default:
				if( balloonFont->width[in] > 0 )
				{
					SDLU_AcquireSurface( balloonSurface );
					SurfaceBlitCharacter( balloonFont, in, &balloonPt, 0, 0, 0, 0 );
					SDLU_ReleaseSurface( balloonSurface );
					
					SDLU_MRectToSDLRect( &balloonRect, &balloonSDLRect );
					SDLU_BlitFrontSurface( balloonSurface, &balloonSDLRect, &balloonSDLRect );

					balloonTime += 2;
				}
				break;			
		}	
	}
	else
	{
		SDLU_MRectToSDLRect( &balloonRect, &balloonSDLRect );
		SDLU_BlitFrontSurface( backdropSurface, &balloonSDLRect, &balloonSDLRect );
		                    
		StopBalloon();
	}
}