shithub: candycrisis

ref: 13aedf6a9faab2fe48c45278cd8280c76ec3816b
dir: /Source/main.cpp/

View raw version
// main.c

//
//                      CANDY CRISIS SDL
//
// FEATURES:
// � Runs natively on any platform with support for SDL. Developed 
//   primarily around the lame Mac Carbon SDL. Can compile and run
//   with gcc/mingw32 on Windows and gcc on Linux.
//
// ETC:
// ��Game functionality unchanged.
// � Removed all Mac OS-specific concepts from the game, i.e.:
//   - GWorlds replaced with SDL_Surfaces.
//   - Rects and Points replaced with MRects and MPoints. (same contents)
// ��All resources moved into external files in a folder called
//   "CandyCrisisResources." Graphics are all JPG and PNG, opened
//   with SDL_image. Sounds are WAV. Music is still MOD type.
// � Using the following Open Source projects:
//   SDL, SDL_image (for graphics)
//   libjpeg (used by SDL_image)
//   libpng (used by SDL_image)
//   zlib (used by libpng)
// � Using fmod for sound; replaces MikMod
// ��Wrote a utility library, SDLU, to pick up slack in the SDL 
//   implementation and to help SDL mesh with a Mac-centric universe.
// ��New registration code algorithm based on a fast string hash.
//

//
//                      CANDY CRISIS X
//
// FEATURES:
// � Runs natively on Mac OS X. Developed around Mac OS X
// Public Beta 1H39 and 2E14.
// Updated 3/25/2001 for Mac OS X 4K78--OS X 10.0.
// Updated 8/25/2001 for Mac OS X 10.0.4.
//
// ETC:
// ��Game functionality unchanged.
// ��Zerius Sound System scrapped, replaced with LibMikMod. I'm
// very unhappy with performance relative to ZSS, but on G3s and up,
// performance should not be a major concern. If only I could get
// the source to ZSS so it could be Carbonized! 
// ��Everything runs in one window now, instead of having
// one window per interface element. This was necessary 
// because OS X wanted to put drop shadows around everything
// and it looked pretty weird. This was also a personal pet
// peeve that I never had the motivation to fix until now.
// ��Controls dialog is super Aqua savvy, using Theme Text 
// and Theme Buttons.
// ��A couple of kludges added, to work around OS X bugs. Ugh.
// � Found bug which was causing blitter to draw larger dirty rects
// than necessary (top/left of dirty rect was always 0/0). Not sure 
// if it ever shipped like that or if this is something I changed 
// post-Candy Crisis 1.0.
// ��Changed cursor management since OS X cursors don't know how to
// hide and show themselves properly.
//
// UNRESOLVED:
// ��Stopped getting Out of Memory reports. I wonder if any of the
// Candy Crisis cleanups affected this...?
// ��OS X displays a line of garbage when you try to put up a totally
// blank cursor. I'm not going to spend too long analyzing this; it's
// not my bug.
// 


//                    CANDY CRISIS 1.0 UPDATE
//
// FEATURES:
// � Rebranded "Candy Crisis" at the request of Mars Candy Co.
// Many, many graphical changes as a result. (New logo thanks 
// to Bob Frasure.)
// � "Controls" button in main menu per many user requests.
// � Slightly improved error reporting. 
// � Option-key at startup to turn on "allow background tasks" 
// or "don't change resolutions." (Don't change resolutions
// requires DrawSprocket 1.7.)
//
// NONCRITICAL:
// � Fixed bug in 2P mode where game would say "Player 1 got
//  best combo!" when Player 2 really got it, and vice versa. 
// � Changed Magic Skittle ratio to 1/19 instead of 1/17, after
// watching Brett get tons of Magic Skittles at work. Hmm.
// ��Replaced RandomBefore with less hacked-up code, because
// Magic Skittles STILL seemed to be coming up more often than
// expected. That seemed to take care of it.
// ��Fixed rare bug where, after losing, the game would sometimes
// get stuck until you explicitly chose "end game." (Would manifest
// more often on a slow computer and/or when Background Tasks were
// activated.)
//
// ETC:
// � Antipiracy measures.
// ��Game fonts all loaded at startup time instead of dynamically,
// in an attempt to reduce the number of GWorlds which are created,
// then torn down, during the game (which could have been potentially
// fragmenting the heap, though I doubt this was a real problem).
//
// UNRESOLVED:
// ��Still a handful of people who get Out of Memory when they 
// try to pause a game. Damn. Hopefully now I'll at least know
// where they're dying (though I highly suspect it's InitGWorld,
// which without a stack crawl is pretty much useless info...)
// One guy says this is fixed by deleting prefs. Huh??
//

//
//                     2.0.2 UPDATE
//
// FEATURES:
// � When you continue, your score is now rolled back to what
// it was when you first started the round. This prevents people
// from racking up high scores by continuing over and over again
// on the highest board.
// � You can now clear the high score tables to their default
// values by holding delete while clicking "high scores" on the
// main screen.
//
// ETC:
// ��Added small picture to the controls dialog so people know
// which color is Player 1, and which is Player 2.
// ��Holding option while warping causes a CPU/CPU match to
// occur.
// � Changed in-game registration URL to:
// http://emulation.net/s2.com/register.html
//
// CRITICAL:
// � Fixed minor memory corruption when a bomb hits floor or
// gray Skittle. Could potentially have corrupted 3 tiles of
// opponent's board.
// ��Fixed bug where potential combo data would not be cleared
// when choosing "End Game" and then starting a new game, which
// led to really weird corruptions of potential combo data.
// ��Fixed bug where holding down button after end-credits rolled
// would cause the pause dialog to pop up on a zero-gamma screen
// (whoops).
// 
// NONCRITICAL:
// ��Fixed bug where dropping bomb would not display associated
// points.
// ��Fixed bug where dropping bomb on floor/gray Skittle would
// reward the player for "killing" empty squares, making it
// score 100x(9-number of grays in 3x3 area) as opposed to
// the correct 100x(number of blobs in 3x3 area).
// ��Occasionally, when Best Combo got corrupted, it would show
// the ending credits instead of displaying the Best Combo. Now
// there is code to ensure that the level # of the Best Combo 
// structure is in bounds. (If it isn't, the best combo is
// assumed to be corrupt, and it is deleted. Not the most 
// optimal solution, but what can I do?)
// ��If you started the tutorial, ended it, then viewed the best
// combo, you'd see a speech balloon appear for one frame.
// ��Fixed bug where bringing up InputSprocket dialog would 
// unload ics8's used to draw key caps (damn InputSprocket bugs).
// Does this only affect ISp < 1.7?
// � Fixed bug where bringing up InputSprocket dialog would not
// update game windows behind it after it got closed.
//
// UNRESOLVED:
// � Slow loading time issue seems to only be affecting an
// incredible minority of people. It's being caused by QuickTime
// decompressing JPEGs. I think it's not a Skittles 2 issue.
// ��One guy says if he quits the game, reopens it, starts a game,
// then pauses it, he gets an Out of Memory condition. He's running
// 8.6-D clean. Hmm.
//

//
//                     2.0.1 UPDATE
//
// FEATURES:
//ʥ�Best Combo
// ��New bg for level 8
// ��New sfx for continue sound (requested by Nathan Lamont)
//
// ETC:
// ��High score dialog enhanced to support Best Combo stuff
// ��Tutorial suggests pressing esc to set up keys now
// ��More aggressive AI for intellect>18 (does not percieve
// 1-level zap as advantageous)
//
// CRITICAL:
// ��Fix for out-of-bounds array read (->crash) inside 
// ZapScoreDisplay.
// ��Fixed bug where falling Skittles (in DropBlobs) would
// occasionally have their bottom half lopped off. Tough to
// see while in motion but totally obvious in screenshots.
// ��Fixed InputSprocket icons in ISpConfigure dialog
// ��Workaround for System 7 bug, where setting the 
// cursor while gamma is faded causes solid black cursor.
//
// NONCRITICAL:
// ��Fixed bug where char fading on blobs that were
// in motion would cause crap to appear for one frame.
// Only apparent on slow Macs or in screenshots. Otherwise
// appeared as flicker and easily dismissed.
// ��Cmd-tab inside High Scores or Game Over screen
// no longer leaves a white box (empty window) open
// and will not switch out with 0 gamma
// ��Fixed DrawSprocket bug on Macs that can't do 640x480
// (i.e. PowerBook G3 is stuck at 1024x768). The window
// would be drawn in lower-right hand corner instead of
// centered.
// ��Fixed bug in multipliers for getting multiple 
// colors at once. (Should have been *3/*6/*12/*24, was
// actually *3/*9/*21/*45! Ouch!)
// � Hitting cmd-Q at high score dialog would allow
// empty high score name to be added to high score table.
//
// UNRESOLVED:
// ��A few users report very slow loading times between
// levels and during loading sequence. Problem tends to
// be alleviated by turning on VM. Can't reproduce here.
// 

//
//                 2.0.0 INITIAL RELEASE
//

#if _WIN32
#include <windows.h>
#include <io.h> // for _chdir
#endif

#include "SDL.h"
#include "SDLU.h"
#include "SDL_image.h"

#include "main.h"

#include <string.h>
#include <stdlib.h>

#include "hiscore.h"
#include "control.h"
#include "players.h"
#include "gworld.h"
#include "graphics.h"
#include "grays.h"
#include "soundfx.h"
#include "next.h"
#include "random.h"
#include "victory.h"
#include "score.h"
#include "graymonitor.h"
#include "music.h"
#include "gameticks.h"
#include "level.h"
#include "opponent.h"
#include "keyselect.h"
#include "blitter.h"
#include "prefs.h"
#include "tweak.h"
#include "zap.h"
#include "pause.h"
#include "tutorial.h"
#include "RegAlgorithm.h"


SDL_Surface* frontSurface;
signed char  nextA[2], nextB[2], nextM[2], nextG[2], colorA[2], colorB[2],
	         blobX[2], blobY[2], blobR[2], blobSpin[2], speed[2], role[2], halfway[2],
	         control[2], dropping[2], magic[2], grenade[2], anim[2];
int          chain[2];
long         blobTime[2], startTime, endTime;
MBoolean     finished = false, pauseKey = false, showStartMenu = true;
signed char  grid[2][kGridAcross][kGridDown], suction[2][kGridAcross][kGridDown], charred[2][kGridAcross][kGridDown], glow[2][kGridAcross][kGridDown];
MRect        playerWindowZRect, playerWindowRect[2];
MBoolean     playerWindowVisible[2] = { true, true };
KeyList      hitKey[2];
int          backgroundID = -1;
MPoint       blobWindow[8][2];
MBoolean     playerIsRegistered = false;
char         registeredName[64] = "";
char         registeredKey[18] = ""; // size is strange just to perplex hackers
void         (*DoFullRepaint)() = NoPaint;
MBoolean     needsRefresh = false;

static char  candyCrisisResources[512];

int main(int argc, char *argv[])
{
	argc, argv;
	
	Initialize( );	
	if( IsRegistered( ) ) exit(0);

	LoadPrefs( );
	
	ReserveMonitor( );	
	ShowTitle( );

	if( !IsRegistered( ) ) 
	{
		SDLU_SetBrightness( 1.0 );
		SharewareNotice( 15*30 );
		SDLU_SetBrightness( 0.0 );
	}
	
	ChooseMusic( 13 );
	
	while( !finished )
	{
		if( showStartMenu )
		{
			GameStartMenu( );
			showStartMenu = false;
		}
		
		if( !finished )
		{
			DoFullRepaint = NeedRefresh;
			CheckKeys( );
			HandlePlayers( );
			UpdateOpponent( );
			UpdateBalloon( );
			UpdateSound( );
			DoFullRepaint = NoPaint;
			
			if( needsRefresh )
			{
				RefreshAll();
				needsRefresh = false;
			}
			
			if( !showStartMenu && pauseKey )
			{
				FreezeGameTickCount( );
				PauseMusic( );
				MaskRect( &playerWindowRect[0] );
				MaskRect( &playerWindowRect[1] );
				WaitForRelease( );
				
				HandleDialog( kPauseDialog );
								
				WaitForRelease( );
				RefreshPlayerWindow( 0 );
				RefreshPlayerWindow( 1 );
				ResumeMusic( );
				UnfreezeGameTickCount( );
			}
		}
	}
	
	SavePrefs( );
	ReleaseMonitor( );
	
	return 0;
}

void NoPaint( void )
{
}

void MaskRect( MRect *r )
{
	SDL_Rect sdlRect;
	SDLU_MRectToSDLRect( r, &sdlRect );
	SDLU_BlitFrontSurface( backdropSurface, &sdlRect, &sdlRect );
}

void RefreshPlayerWindow( short player )
{
	MRect fullUpdate = {0, 0, kGridDown * kBlobVertSize, kGridAcross * kBlobHorizSize };
	
	if( control[player] == kNobodyControl )
	{
		MaskRect( &playerWindowRect[player] );
	}
	else
	{
		SetUpdateRect( player, &fullUpdate );
		UpdatePlayerWindow( player );
	}
}

void NeedRefresh()
{
	needsRefresh = true;
}

void RefreshAll( void )
{	
	DrawBackdrop( );

	ShowGrayMonitor( 0 );
	ShowGrayMonitor( 1 );

	RefreshNext( 0 );
	RefreshNext( 1 );

	RefreshPlayerWindow( 0 );
	RefreshPlayerWindow( 1 );

	DrawFrozenOpponent( );
	DrawStage( );

	ShowScore( 0 );
	ShowScore( 1 );
}

void Error( const char* extra )
{
#if TARGET_API_MAC_CARBON
	Str255 myString, extraP;
	
	CopyCStringToPascal( extra, extraP );
	ReleaseMonitor( );
	GetIndString( myString, 131, errUnknown );
	ParamText( myString, extraP, "\p", "\p" );
	Alert( dFatalErrorAlert, NULL );
	ExitToShell( );
#else
	char message[256];
	sprintf( message, "Sorry, a critical error has occurred. Please report the following error message:\n    %s", extra );
	#if WIN32
		MessageBox( NULL, message, "Candy Crisis", MB_OK );
	#else
		fprintf(stderr, "Candy Crisis: %s\n", message);
	#endif
	exit(0);
#endif
}

void WaitForRelease( void )
{	
	do
	{
		SDLU_Yield();
	}
	while( AnyKeyIsPressed( ) || SDLU_Button() );
}

MBoolean AnyKeyIsPressed( void )
{
	int index;
	int arraySize;
	unsigned char* pressedKeys;
				                 
	SDLU_PumpEvents();          
	pressedKeys = SDL_GetKeyState( &arraySize );
	
	// Only check ASCII keys. (Reason: some extended keys, like NUMLOCK or CAPSLOCK,
	// can be on all the time even if a key really isn't depressed.)
	if( arraySize > 128 ) arraySize = 128;
	
	for( index = 0; index < arraySize; index++ )
	{
		if( pressedKeys[index] ) 
		{
			return true;
		}
	}

	return false;
}

MBoolean ControlKeyIsPressed( void )
{
	int arraySize;
	unsigned char* pressedKeys;
				                 
	SDLU_PumpEvents();          
	pressedKeys = SDL_GetKeyState( &arraySize );

	return pressedKeys[ SDLK_LCTRL ] || pressedKeys[ SDLK_RCTRL ];
}

MBoolean OptionKeyIsPressed( void )
{
	int arraySize;
	unsigned char* pressedKeys;
				                 
	SDLU_PumpEvents();          
	pressedKeys = SDL_GetKeyState( &arraySize );

	return pressedKeys[ SDLK_LALT ] || pressedKeys[ SDLK_RALT ];
}

void RetrieveResources( void )
{
	                            OpeningProgress( 0, 10 );
	InitSound( );				OpeningProgress( 1, 10 );

	InitBackdrop( );			OpeningProgress( 2, 10 );

	GetBlobGraphics( );			OpeningProgress( 3, 10 );
	
	InitNext( );				OpeningProgress( 4, 10 );
	
	InitScore( );				OpeningProgress( 5, 10 );

	InitRegistration();
	InitGrayMonitors( );		OpeningProgress( 6, 10 );
	
	InitOpponent( );			OpeningProgress( 7, 10 );

	InitStage( );   // must run after backdrop window is open
	InitGameTickCount( );

	InitPlayers( ); // must run after backdrop window is open
	InitFont( ); 
	InitZapStyle( );// must run after fonts are inited
					            OpeningProgress( 8, 10 );
	
	InitBlitter( ); // must run after player windows are open
	InitPlayerWorlds( );  		OpeningProgress( 9, 10 );
	
	InitVictory( );	// must run after fonts are inited			
	InitTweak( );				OpeningProgress( 10, 10 );
}


void CenterRectOnScreen( MRect *rect, double locationX, double locationY )
{
	MPoint dest = {0,0};
	
	dest.h = (short)(locationX * (640 - (rect->right - rect->left)));
	dest.h &= ~3;
	dest.v = (short)(locationY * (480 - (rect->bottom - rect->top)));

	OffsetMRect( rect, -rect->left, -rect->top );
	OffsetMRect( rect, dest.h, dest.v );
}

void ReserveMonitor( void )
{
	SDL_Surface* icon;
	SDL_Surface* mask;
	
	icon = LoadPICTAsSurface( 10000, 16 );
	mask = LoadPICTAsSurface( 10001, 1 );
	SDL_WM_SetIcon( icon, (Uint8*) mask->pixels );
	SDL_FreeSurface( icon );
	SDL_FreeSurface( mask );

	SDL_ShowCursor( SDL_DISABLE );
	
#if TARGET_API_MAC_CARBON
	frontSurface = SDL_SetVideoMode( 640, 480, 15, SDL_SWSURFACE );
#else
	frontSurface = SDL_SetVideoMode( 640, 480, 16, SDL_SWSURFACE | SDL_FULLSCREEN );
#endif

	SDL_WM_SetCaption( "Candy Crisis", "CandyCrisis" );
}

void ReleaseMonitor( void )
{
	// frontSurface is released by SDL_Quit... we are not supposed to kill it
}

int Warp( void )
{
	return 8;
}

const char* QuickResourceName( const char* prefix, int id, const char* extension )
{
	static char name[512];
	if( id ) 
	{
		sprintf( name, "%s%s_%d%s", candyCrisisResources, prefix, id, extension );
	}
	else
	{
		sprintf( name, "%s%s%s", candyCrisisResources, prefix, extension );
	}
	
	return name;
}

void Initialize( void )
{	
#if _WIN32
    HMODULE module;
    char    name[MAX_PATH+1], *lastBackslash;
   
    module = GetModuleHandle( NULL );
    GetModuleFileName( module, name, MAX_PATH );
    lastBackslash = strrchr( name, '\\' );
    if( lastBackslash != NULL )
    {
        *lastBackslash = '\0';
        strcpy( candyCrisisResources, name );
        strcat( candyCrisisResources, "\\CandyCrisisResources\\" );
    }
#endif
#if TARGET_API_MAC_CARBON
	strcpy( candyCrisisResources, ":CandyCrisisResources:" );
#endif
#ifdef linux
	strcpy( candyCrisisResources, "CandyCrisisResources/" );
#endif

	if( SDL_Init( SDL_INIT_VIDEO | SDL_INIT_AUDIO ) < 0 )
	{
		Error( "SDL_Init failed" );
	}
	
	atexit( SDL_Quit );
	
	SDL_SetEventFilter( SDLU_EventFilter );
}

void LaunchURL( const char* url )
{
#if TARGET_API_MAC_CARBON
	OSStatus err = -1;
	ICInstance inst;
	long startSel;
	long endSel;

	if( ICStart != NULL )
	{
		err = ICStart( &inst, 'Skit' );
		if (err == noErr)
		{
			startSel = 0;
			endSel = strlen(url);
			err = ICLaunchURL( inst, "\p", url, strlen(url), &startSel, &endSel );
			ICStop(inst);
		}
	}
#else
    SDL_WM_IconifyWindow();
	ShellExecute( NULL, "open", url, "", "c:\\", SW_SHOWNORMAL );
	WaitForRegainFocus();
#endif
}

void QuickFadeIn( MRGBColor *color )
{
	color; // is unused

#ifndef TARGET_API_MAC_CARBON
	long  c;
	float percent;

	for( percent=0.0f; percent<1.0f; percent += 0.04f )
	{
		c = MTickCount( ); 
		SDLU_SetBrightness( percent );
		while( c == MTickCount( ) ) 
		{  
			SDLU_Yield(); 
		}
	}
	
	SDLU_SetBrightness( percent );
#endif
}

void QuickFadeOut( MRGBColor *color )
{
	color; // is unused

#ifndef TARGET_API_MAC_CARBON
	long   c;
	float  percent;

	for( percent=1.0f; percent>0.0f; percent -= 0.04f )
	{
		c = MTickCount( ); 
		SDLU_SetBrightness( percent );
 		while( c == MTickCount( ) )
 		{
 			SDLU_Yield(); 
 		}
	}
	
	SDLU_SetBrightness( percent );
#endif
}

MBoolean FileExists( const char* name )
{
	FILE* f = fopen( name, "rb" );
	if( f == NULL )
	{
		return false;
	}
	
	fclose( f );
	return true;
}


void WaitForRegainFocus()
{
    do
    {  
        SDLU_PumpEvents();
        SDL_Delay(50);
    }
    while( !SDLU_IsForeground() );    

	DoFullRepaint();
}


void InitRegistration()
{
	playerIsRegistered = ValidateCode( registeredName, registeredKey );
}


MBoolean IsRegistered()
{
	return playerIsRegistered;
}