ref: 1ae6334bfaeb53c301a553e45733ad2a50b74689
dir: /Source/main.cpp/
// 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;
}