1300 lines
34 KiB
C++
1300 lines
34 KiB
C++
/*
|
|
* Modification History
|
|
*
|
|
* 2007-September-25 Jason Rohrer
|
|
* Created.
|
|
*/
|
|
|
|
|
|
|
|
#include <math.h>
|
|
#include <signal.h>
|
|
#include <stdlib.h>
|
|
#include <time.h>
|
|
#include <unistd.h>
|
|
|
|
|
|
// for memcpy
|
|
#include <string.h>
|
|
|
|
|
|
// let SDL override our main function with SDLMain
|
|
#include <SDL/SDL_main.h>
|
|
|
|
// must do this before SDL include to prevent WinMain linker errors on win32
|
|
int mainFunction( int inArgCount, char **inArgs );
|
|
|
|
int main( int inArgCount, char **inArgs ) {
|
|
return mainFunction( inArgCount, inArgs );
|
|
}
|
|
|
|
|
|
#include <SDL/SDL.h>
|
|
|
|
#include "blowUp.h"
|
|
#include "World.h"
|
|
#include "map.h"
|
|
#include "score.h"
|
|
#include "common.h"
|
|
#include "musicPlayer.h"
|
|
|
|
#include "minorGems/system/Time.h"
|
|
#include "minorGems/system/Thread.h"
|
|
#include "minorGems/util/stringUtils.h"
|
|
#include "minorGems/util/SettingsManager.h"
|
|
|
|
|
|
|
|
// size of game image
|
|
int width = 100;
|
|
int height = 12;
|
|
// area above game image for score
|
|
int scoreHeight = getScoreHeight();
|
|
|
|
// size of game image plus scoreboard
|
|
int totalImageHeight = height + scoreHeight;
|
|
|
|
char paused = false;
|
|
char canPause = false;
|
|
|
|
|
|
// size of screen for fullscreen mode
|
|
int screenWidth = 640;
|
|
int screenHeight = 480;
|
|
//int screenWidth = 100;
|
|
//int screenHeight = 16;
|
|
|
|
// blow-up factor when projecting game onto screen
|
|
// Max defined by image size vs screen size.
|
|
// This is the cap for in-game user-directed blowup adjustment.
|
|
int maxBlowUpFactor;
|
|
|
|
// current blow-up setting
|
|
int blowUpFactor;
|
|
|
|
|
|
// step to take when user hits blowUp key
|
|
int blowUpStep = -1;
|
|
// flag to force update of entire screen
|
|
int blowUpChanged = true;
|
|
|
|
|
|
char fullScreen = true;
|
|
|
|
|
|
// lock down to 15 fps
|
|
int lockedFrameRate = 15;
|
|
|
|
|
|
// target length of game
|
|
int gameTime = 5 * 60;
|
|
//int gameTime = 30;
|
|
|
|
|
|
// life time that passes per frame
|
|
double timeDelta = - ( (double)width / ( gameTime * lockedFrameRate ) );
|
|
//double timeDelta = -0.0;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// the joystick to read from, or NULL if there's no available joystick
|
|
SDL_Joystick *joystick;
|
|
|
|
|
|
|
|
// catch an interrupt signal
|
|
void catch_int(int sig_num) {
|
|
printf( "Quiting...\n" );
|
|
SDL_Quit();
|
|
exit( 0 );
|
|
signal( SIGINT, catch_int );
|
|
}
|
|
|
|
|
|
|
|
char getKeyDown( int inKeyCode ) {
|
|
SDL_PumpEvents();
|
|
Uint8 *keys = SDL_GetKeyState( NULL );
|
|
return keys[ inKeyCode ] == SDL_PRESSED;
|
|
}
|
|
|
|
|
|
|
|
char getHatDown( Uint8 inHatPosition ) {
|
|
if( joystick == NULL ) {
|
|
return false;
|
|
}
|
|
|
|
SDL_JoystickUpdate();
|
|
|
|
Uint8 hatPosition = SDL_JoystickGetHat( joystick, 0 );
|
|
|
|
if( hatPosition & inHatPosition ) {
|
|
return true;
|
|
}
|
|
else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
|
|
int joyThreshold = 25000;
|
|
|
|
char getJoyPushed( Uint8 inHatPosition ) {
|
|
|
|
Sint16 x = SDL_JoystickGetAxis(joystick, 0);
|
|
Sint16 y = SDL_JoystickGetAxis(joystick, 1);
|
|
|
|
switch( inHatPosition ) {
|
|
case SDL_HAT_DOWN:
|
|
return y > joyThreshold;
|
|
break;
|
|
case SDL_HAT_UP:
|
|
return y < -joyThreshold;
|
|
break;
|
|
case SDL_HAT_LEFT:
|
|
return x < - joyThreshold;
|
|
break;
|
|
case SDL_HAT_RIGHT:
|
|
return x > joyThreshold;
|
|
break;
|
|
default:
|
|
return false;
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
// returns true if hit, returns false if user quits before hitting
|
|
char waitForKeyOrButton() {
|
|
SDL_Event event;
|
|
|
|
while( true ) {
|
|
while( SDL_WaitEvent( &event ) ) {
|
|
switch( event.type ) {
|
|
case SDL_JOYHATMOTION:
|
|
case SDL_JOYBUTTONDOWN:
|
|
case SDL_JOYBUTTONUP:
|
|
case SDL_KEYDOWN:
|
|
case SDL_KEYUP:
|
|
return true;
|
|
break;
|
|
// watch for quit event
|
|
case SDL_QUIT:
|
|
return false;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
|
|
Uint32 *gameImage;
|
|
|
|
|
|
|
|
// flips back buffer onto screen (or updates rect)
|
|
void flipScreen( SDL_Surface *inScreen ) {
|
|
|
|
// unlock the screen if necessary
|
|
if( SDL_MUSTLOCK( inScreen ) ) {
|
|
SDL_UnlockSurface( inScreen );
|
|
}
|
|
|
|
if( ( inScreen->flags & SDL_DOUBLEBUF ) == SDL_DOUBLEBUF ) {
|
|
// need to flip buffer
|
|
SDL_Flip( inScreen );
|
|
}
|
|
else if( !blowUpChanged ) {
|
|
// just update center
|
|
|
|
// small area in center that we actually draw in, black around it
|
|
int yOffset = ( inScreen->h - totalImageHeight * blowUpFactor ) / 2;
|
|
int xOffset = ( inScreen->w - width * blowUpFactor ) / 2;
|
|
|
|
SDL_UpdateRect( inScreen, xOffset, yOffset,
|
|
width * blowUpFactor,
|
|
totalImageHeight * blowUpFactor );
|
|
}
|
|
else {
|
|
// update the whole thing
|
|
SDL_UpdateRect( inScreen, 0, 0, inScreen->w, inScreen->h );
|
|
|
|
// reset flag
|
|
blowUpChanged = false;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void lockScreen( SDL_Surface * inScreen ) {
|
|
// check if we need to lock the screen
|
|
if( SDL_MUSTLOCK( inScreen ) ) {
|
|
if( SDL_LockSurface( inScreen ) < 0 ) {
|
|
printf( "Couldn't lock screen: %s\n", SDL_GetError() );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void flipGameImageOntoScreen( SDL_Surface *inScreen ) {
|
|
|
|
if( blowUpChanged
|
|
&&
|
|
( inScreen->flags & SDL_DOUBLEBUF ) == SDL_DOUBLEBUF ) {
|
|
|
|
// blow up size has changed, and we are double-buffered
|
|
// flip onto screen an additional time.
|
|
// This will cause us to black-out the background in both buffers.
|
|
|
|
lockScreen( inScreen );
|
|
|
|
// when blow-up factor changes:
|
|
// clear screen to prepare for next draw,
|
|
// which will be bigger or smaller
|
|
SDL_FillRect( inScreen, NULL, 0x00000000 );
|
|
|
|
blowupOntoScreen( gameImage,
|
|
width, totalImageHeight, blowUpFactor, inScreen );
|
|
|
|
flipScreen( inScreen );
|
|
}
|
|
|
|
lockScreen( inScreen );
|
|
if( blowUpChanged ) {
|
|
SDL_FillRect( inScreen, NULL, 0x00000000 );
|
|
}
|
|
blowupOntoScreen( gameImage,
|
|
width, totalImageHeight, blowUpFactor, inScreen );
|
|
|
|
flipScreen( inScreen );
|
|
|
|
// we've handled any blow up change
|
|
blowUpChanged = false;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
SDL_Surface *screen = NULL;
|
|
|
|
|
|
// play a complete game, from title screen to end, on screen
|
|
// returns false if player quits
|
|
char playGame();
|
|
|
|
|
|
|
|
void createScreen() {
|
|
if( screen != NULL ) {
|
|
// destroy old one first
|
|
SDL_FreeSurface( screen );
|
|
}
|
|
|
|
int displayW = width * blowUpFactor;
|
|
int displayH = totalImageHeight * blowUpFactor;
|
|
|
|
|
|
Uint32 flags = SDL_HWSURFACE | SDL_DOUBLEBUF;
|
|
if( fullScreen ) {
|
|
flags = flags | SDL_FULLSCREEN;
|
|
|
|
// use letterbox mode in full screen
|
|
displayW = screenWidth;
|
|
displayH = screenHeight;
|
|
}
|
|
|
|
|
|
|
|
screen = SDL_SetVideoMode( displayW, displayH,
|
|
32,
|
|
flags );
|
|
|
|
if ( screen == NULL ) {
|
|
printf( "Couldn't set %dx%dx32 video mode: %s\n", displayW,
|
|
displayH,
|
|
SDL_GetError() );
|
|
}
|
|
}
|
|
|
|
|
|
|
|
int mainFunction( int inArgCount, char **inArgs ) {
|
|
|
|
// let catch_int handle interrupt (^c)
|
|
signal( SIGINT, catch_int );
|
|
|
|
|
|
Uint32 initFlags =
|
|
SDL_INIT_VIDEO | SDL_INIT_JOYSTICK | SDL_INIT_NOPARACHUTE;
|
|
|
|
#ifdef __mac__
|
|
// SDL_Init is dreadfully slow if we try to init JOYSTICK on MacOSX
|
|
// not sure why---maybe it's searching all devices or something like
|
|
// that.
|
|
// Couldn't find anything online about this.
|
|
initFlags =
|
|
SDL_INIT_VIDEO | SDL_INIT_NOPARACHUTE;
|
|
#endif
|
|
|
|
if( SDL_Init( initFlags ) < 0 ) {
|
|
printf( "Couldn't initialize SDL: %s\n", SDL_GetError() );
|
|
return -1;
|
|
}
|
|
|
|
SDL_ShowCursor( SDL_DISABLE );
|
|
|
|
|
|
// read screen size from settings
|
|
char widthFound = false;
|
|
int readWidth = SettingsManager::getIntSetting( "screenWidth",
|
|
&widthFound );
|
|
char heightFound = false;
|
|
int readHeight = SettingsManager::getIntSetting( "screenHeight",
|
|
&heightFound );
|
|
|
|
if( widthFound && heightFound ) {
|
|
// override hard-coded defaults
|
|
screenWidth = readWidth;
|
|
screenHeight = readHeight;
|
|
}
|
|
|
|
printf( "Screen dimensions for fullscreen mode: %dx%d\n",
|
|
screenWidth, screenHeight );
|
|
|
|
// set here, since screenWidth may have changed from default
|
|
maxBlowUpFactor = screenWidth / width;
|
|
|
|
// default to max
|
|
blowUpFactor = maxBlowUpFactor;
|
|
|
|
|
|
char fullscreenFound = false;
|
|
int readFullscreen = SettingsManager::getIntSetting( "fullscreen",
|
|
&fullscreenFound );
|
|
if( fullscreenFound ) {
|
|
fullScreen = readFullscreen;
|
|
}
|
|
|
|
printf( "Starting game in " );
|
|
if( fullScreen ) {
|
|
printf( "fullscreen" );
|
|
}
|
|
else {
|
|
printf( "windowed" );
|
|
}
|
|
printf( " mode.\n" );
|
|
|
|
|
|
|
|
createScreen();
|
|
|
|
|
|
// try to open joystick
|
|
int numJoysticks = SDL_NumJoysticks();
|
|
printf( "Found %d joysticks\n", numJoysticks );
|
|
|
|
if( numJoysticks > 0 ) {
|
|
// open first one by default
|
|
joystick = SDL_JoystickOpen( 0 );
|
|
|
|
if( joystick == NULL ) {
|
|
printf( "Couldn't open joystick 0: %s\n", SDL_GetError() );
|
|
}
|
|
int numHats = SDL_JoystickNumHats( joystick );
|
|
|
|
if( numHats <= 0 ) {
|
|
printf( "No d-pad found on joystick\n" );
|
|
SDL_JoystickClose( joystick );
|
|
joystick = NULL;
|
|
}
|
|
}
|
|
else {
|
|
joystick = NULL;
|
|
}
|
|
|
|
|
|
|
|
#ifdef __mac__
|
|
// make sure working directory is the same as the directory
|
|
// that the app resides in
|
|
// this is especially important on the mac platform, which
|
|
// doesn't set a proper working directory for double-clicked
|
|
// app bundles
|
|
|
|
// arg 0 is the path to the app executable
|
|
char *appDirectoryPath = stringDuplicate( inArgs[ 0 ] );
|
|
|
|
printf( "Mac: app path %s\n", appDirectoryPath );
|
|
|
|
char *appNamePointer = strstr( appDirectoryPath,
|
|
"Passage.app" );
|
|
|
|
if( appNamePointer != NULL ) {
|
|
// terminate full app path to get parent directory
|
|
appNamePointer[0] = '\0';
|
|
|
|
printf( "Mac: changing working dir to %s\n", appDirectoryPath );
|
|
chdir( appDirectoryPath );
|
|
}
|
|
|
|
delete [] appDirectoryPath;
|
|
#endif
|
|
|
|
|
|
|
|
|
|
// load graphics only once
|
|
loadWorldGraphics();
|
|
|
|
|
|
setMusicLoudness( 0 );
|
|
startMusic( "music.tga" );
|
|
|
|
// keep playing until player quits
|
|
while( playGame() ) {
|
|
}
|
|
|
|
stopMusic();
|
|
|
|
|
|
destroyWorldGraphics();
|
|
|
|
|
|
if( joystick != NULL ) {
|
|
SDL_JoystickClose( joystick );
|
|
}
|
|
|
|
SDL_Quit();
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/*
|
|
// TEMP
|
|
#include "minorGems/graphics/converters/TGAImageConverter.h"
|
|
|
|
#include "minorGems/io/file/File.h"
|
|
|
|
#include "minorGems/io/file/FileInputStream.h"
|
|
// END TEMP
|
|
*/
|
|
|
|
|
|
char playGame() {
|
|
|
|
|
|
int currentSpriteIndex = 2;
|
|
|
|
double playerX, playerY;
|
|
|
|
double maxPlayerX = 0;
|
|
|
|
int score = 0;
|
|
|
|
|
|
// the gem that marks chests containing points
|
|
int specialGem = time( NULL ) % 4;
|
|
|
|
int exploreScore = 0;
|
|
int exploreSubPoints = 0;
|
|
// make sure explore score never goes down
|
|
int maxExploreScore = 0;
|
|
|
|
int chestScore = 0;
|
|
|
|
// track whether we ever met the spouse
|
|
// separate from World's haveMetSpouse()
|
|
char knowSpouse = false;
|
|
|
|
|
|
|
|
initWorld();
|
|
initScore();
|
|
|
|
|
|
|
|
// room at top for score
|
|
int totalGameImagePixels = width * totalImageHeight;
|
|
|
|
gameImage = new Uint32[ totalGameImagePixels ];
|
|
|
|
int i;
|
|
|
|
// fill with black
|
|
for( i=0; i<totalGameImagePixels; i++ ) {
|
|
gameImage[i] = 0;
|
|
}
|
|
|
|
|
|
// first, fill the whole thing with black
|
|
// SDL_FillRect( screen, NULL, 0x00000000 );
|
|
|
|
|
|
|
|
|
|
double dX = 0;
|
|
double dY = 0;
|
|
|
|
|
|
char done = false;
|
|
|
|
double maxWorldX = 0;
|
|
double minWorldX = 0;
|
|
|
|
|
|
// start player position
|
|
playerX = getTileWidth();
|
|
maxPlayerX = playerX;
|
|
|
|
dX = playerX;
|
|
playerY = 2 + height/2;
|
|
dY = 0;
|
|
|
|
setPlayerPosition( (int)playerX, (int)playerY );
|
|
setPlayerSpriteFrame( currentSpriteIndex );
|
|
|
|
|
|
double lastFrameTimeStamp = Time::getCurrentTime();
|
|
|
|
// use to slow player motion after spouse has died
|
|
char movingThisFrame = true;
|
|
|
|
|
|
// first, flip title onto screen
|
|
Image *titleImage = readTGA( "title.tga" );
|
|
int numTitlePixels = titleImage->getWidth() * titleImage->getHeight();
|
|
|
|
double *titleRed = titleImage->getChannel( 0 );
|
|
double *titleGreen = titleImage->getChannel( 1 );
|
|
double *titleBlue = titleImage->getChannel( 2 );
|
|
|
|
Uint32 *titlePixels = new Uint32[ numTitlePixels ];
|
|
|
|
for( int i=0; i<numTitlePixels; i++ ) {
|
|
titlePixels[ i ] =
|
|
(unsigned char )( titleRed[i] * 255 ) << 16
|
|
|
|
|
(unsigned char )( titleGreen[i] * 255 ) << 8
|
|
|
|
|
(unsigned char )( titleBlue[i] * 255 );
|
|
}
|
|
|
|
// fill screen with title
|
|
memcpy( gameImage, titlePixels, 4 * numTitlePixels );
|
|
|
|
|
|
flipGameImageOntoScreen( screen );
|
|
|
|
|
|
char hit = waitForKeyOrButton();
|
|
|
|
if( !hit ) {
|
|
// no key pressed...
|
|
// maybe they closed the window
|
|
// count as a "quit"
|
|
done = true;
|
|
}
|
|
else {
|
|
// they pressed a key to start the game
|
|
|
|
// return to start
|
|
restartMusic();
|
|
|
|
// turn up music
|
|
setMusicLoudness( 1.0 );
|
|
}
|
|
|
|
|
|
|
|
|
|
// fill with black to cover up title
|
|
for( i=0; i<totalGameImagePixels; i++ ) {
|
|
gameImage[i] = 0;
|
|
}
|
|
|
|
|
|
|
|
int frameCount = 0;
|
|
unsigned long startTime = time( NULL );
|
|
|
|
char quit = false;
|
|
|
|
int titleFadeFrame = 0;
|
|
int numTitleFadeFrames = 200;
|
|
|
|
char stepDX = true;
|
|
|
|
|
|
/*
|
|
// TEMP
|
|
// generate mural image
|
|
printf( "Writing mural...\n" );
|
|
|
|
int muralWidth = 4437;
|
|
int muralHeight = 12;
|
|
|
|
|
|
int numStrips = 16;
|
|
int stripWidth = muralWidth / numStrips;
|
|
|
|
int betweenStripSkip = 1;
|
|
|
|
|
|
int yOffset = 160;
|
|
|
|
Image mural( stripWidth,
|
|
( muralHeight + betweenStripSkip) * numStrips,
|
|
3, true );
|
|
|
|
double *muralR = mural.getChannel( 0 );
|
|
double *muralG = mural.getChannel( 1 );
|
|
double *muralB = mural.getChannel( 2 );
|
|
|
|
|
|
for( int s=0; s<numStrips; s ++ ) {
|
|
|
|
for( int x=0; x<stripWidth; x++ ) {
|
|
for( int y=0; y<muralHeight; y++ ) {
|
|
int worldX = x + s * stripWidth;
|
|
|
|
Uint32 sample =
|
|
sampleFromWorld( worldX, y + yOffset, 1 );
|
|
|
|
int muralY = s * (muralHeight + betweenStripSkip) + y;
|
|
|
|
int index = muralY * stripWidth + x;
|
|
|
|
muralR[ index ] = ( (sample >> 16) & 0xFF ) / 255.0;
|
|
muralG[ index ] = ( (sample >> 8) & 0xFF ) / 255.0;
|
|
muralB[ index ] = ( sample & 0xFF ) / 255.0;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
File tgaFile( NULL, "mural.tga" );
|
|
FileOutputStream tgaStream( &tgaFile );
|
|
|
|
TGAImageConverter converter;
|
|
|
|
converter.formatImage( &mural, &tgaStream );
|
|
|
|
// END TEMP
|
|
*/
|
|
|
|
|
|
|
|
while( !done ) {
|
|
|
|
if( getKeyDown( SDLK_s ) ) {
|
|
stepDX = false;
|
|
}
|
|
else {
|
|
stepDX = true;
|
|
}
|
|
|
|
// trick:
|
|
// dX advances linearly, which results in smooth motion
|
|
// instead of single-pixel tick-tick-tick
|
|
// However, this results in smudgy sampling of sprites, which
|
|
// makes them hard to see
|
|
// If we map our sloping dX, which looks like this: /
|
|
// to a constant between integers with a steep slope up to the next
|
|
// integer, like this: _/
|
|
// Then we get to look at unsmudged sprites between steps, but
|
|
// still see a smoothed-out tick
|
|
|
|
double drawDX = floor( dX );
|
|
double floorWidth = 0.5;
|
|
|
|
if( dX - drawDX > floorWidth ) {
|
|
// slope
|
|
double slopeValue =
|
|
( dX - drawDX - floorWidth ) / ( 1 - floorWidth );
|
|
|
|
// us trig function to avoid jagged transition from floor to
|
|
// slope to next floor
|
|
|
|
drawDX += -0.5 * cos( slopeValue * M_PI ) + 0.5;
|
|
}
|
|
|
|
if( !stepDX ) {
|
|
// disable smoothed stepping
|
|
drawDX = dX;
|
|
}
|
|
|
|
|
|
for( int y=0; y<height; y++ ) {
|
|
for( int x=0; x<width; x++ ) {
|
|
|
|
//int worldX = (int)( pow( x / 2.0, 1.4 ) );
|
|
|
|
// compression based on distance of pixel column from player
|
|
double trueDistanceFromPlayer = drawDX + x - playerX;
|
|
double maxDistance = width - 1;
|
|
|
|
// cap it so that we don't force tan into infinity below
|
|
double cappedDistanceFromPlayer = trueDistanceFromPlayer;
|
|
if( cappedDistanceFromPlayer > maxDistance ) {
|
|
cappedDistanceFromPlayer = maxDistance;
|
|
}
|
|
if( cappedDistanceFromPlayer < -maxDistance ) {
|
|
cappedDistanceFromPlayer = -maxDistance;
|
|
}
|
|
|
|
// the world position we will sample from
|
|
double worldX = x;
|
|
|
|
// zone around player where no x compression happens
|
|
int noCompressZone = 10;
|
|
|
|
|
|
if( trueDistanceFromPlayer > noCompressZone ) {
|
|
|
|
worldX =
|
|
x +
|
|
//(width/8) *
|
|
// use true distance as a factor so that compression
|
|
// continues at constant rate after we pass capped
|
|
// distance
|
|
// otherwise, compression stops after capped distance
|
|
// Still... constant rate looks weird, so avoid
|
|
// it by not letting it pass capped distance
|
|
trueDistanceFromPlayer / 2 *
|
|
( pow( tan( ( ( cappedDistanceFromPlayer -
|
|
noCompressZone ) /
|
|
(double)( width - noCompressZone ) ) *
|
|
M_PI / 2 ), 2) );
|
|
/*
|
|
trueDistanceFromPlayer / 2 *
|
|
( tan( ( cappedDistanceFromPlayer /
|
|
(double)( width - 0.5 ) ) * M_PI / 2 ) );
|
|
*/
|
|
// simpler formula
|
|
// actually, this does not approach 0 as
|
|
// cappedDistanceFromPlayer approaches 0, so use tan
|
|
// instead
|
|
// ( trueDistanceFromPlayer / 2 ) * 100
|
|
// / pow( ( width - cappedDistanceFromPlayer ), 1.6 );
|
|
}
|
|
else if( trueDistanceFromPlayer < - noCompressZone ) {
|
|
worldX =
|
|
x +
|
|
//(width/8) *
|
|
trueDistanceFromPlayer / 2 *
|
|
( pow( tan( ( ( - cappedDistanceFromPlayer -
|
|
noCompressZone ) /
|
|
(double)( width - noCompressZone ) ) *
|
|
M_PI / 2 ), 2) );
|
|
/*
|
|
trueDistanceFromPlayer / 2 *
|
|
( tan( ( - cappedDistanceFromPlayer /
|
|
(double)( width - 0.5 ) ) * M_PI / 2 ) );
|
|
*/
|
|
//( trueDistanceFromPlayer / 2 ) * 50
|
|
/// ( width + cappedDistanceFromPlayer );
|
|
}
|
|
else {
|
|
// inside no-compresison zone
|
|
worldX = x;
|
|
}
|
|
|
|
|
|
//int worldX = x;
|
|
|
|
worldX += drawDX;
|
|
|
|
if( worldX > maxWorldX ) {
|
|
maxWorldX = worldX;
|
|
}
|
|
if( worldX < minWorldX ) {
|
|
minWorldX = worldX;
|
|
}
|
|
|
|
|
|
int worldY = (int)floor( y + dY );
|
|
|
|
|
|
// linear interpolation of two samples for worldX
|
|
int intWorldX = (int)floor( worldX );
|
|
|
|
double bWeight = worldX - intWorldX;
|
|
double aWeight = 1 - bWeight;
|
|
|
|
|
|
Uint32 sampleA =
|
|
sampleFromWorld( intWorldX, worldY, aWeight );
|
|
Uint32 sampleB =
|
|
sampleFromWorld( intWorldX + 1, worldY, bWeight );
|
|
|
|
|
|
|
|
Uint32 combined = sampleB + sampleA;
|
|
|
|
|
|
gameImage[ ( y + scoreHeight ) * width + x ] = combined;
|
|
|
|
}
|
|
}
|
|
|
|
drawScore( gameImage, width, height, score );
|
|
|
|
|
|
if( isPlayerDead() ) {
|
|
// fade to title screen
|
|
double titleWeight =
|
|
titleFadeFrame / (double)( numTitleFadeFrames - 1 );
|
|
double gameWeight = 1 - titleWeight;
|
|
|
|
// wipe from left to right during fade
|
|
int wipePosition = (int)( titleWeight * width );
|
|
|
|
// fade out music while we do it
|
|
setMusicLoudness( 1.0 - titleWeight );
|
|
|
|
|
|
|
|
for( i=0; i<totalGameImagePixels; i++ ) {
|
|
|
|
|
|
Uint32 gamePixel = gameImage[i];
|
|
|
|
unsigned char gameRed = gamePixel >> 16 & 0xFF;
|
|
unsigned char gameGreen = gamePixel >> 8 & 0xFF;
|
|
unsigned char gameBlue = gamePixel & 0xFF;
|
|
|
|
Uint32 titlePixel = titlePixels[i];
|
|
|
|
unsigned char titleRed = titlePixel >> 16 & 0xFF;
|
|
unsigned char titleGreen = titlePixel >> 8 & 0xFF;
|
|
unsigned char titleBlue = titlePixel & 0xFF;
|
|
|
|
unsigned char red =
|
|
(unsigned char)(
|
|
gameWeight * gameRed + titleWeight * titleRed );
|
|
unsigned char green =
|
|
(unsigned char)(
|
|
gameWeight * gameGreen + titleWeight * titleGreen );
|
|
unsigned char blue =
|
|
(unsigned char)(
|
|
gameWeight * gameBlue + titleWeight * titleBlue );
|
|
|
|
|
|
int x = i % width;
|
|
if( x <= wipePosition ) {
|
|
gameImage[i] = red << 16 | green << 8 | blue;
|
|
}
|
|
|
|
}
|
|
|
|
if( !paused ) {
|
|
|
|
titleFadeFrame ++;
|
|
}
|
|
|
|
|
|
if( titleFadeFrame == numTitleFadeFrames ) {
|
|
done = true;
|
|
}
|
|
}
|
|
|
|
|
|
flipGameImageOntoScreen( screen );
|
|
|
|
|
|
// done with frame
|
|
double newTimestamp = Time::getCurrentTime();
|
|
|
|
double frameTime = newTimestamp - lastFrameTimeStamp;
|
|
|
|
double extraTime = 1.0 / lockedFrameRate - frameTime;
|
|
|
|
if( extraTime > 0 ) {
|
|
Thread::staticSleep( (int)( extraTime * 1000 ) );
|
|
}
|
|
|
|
// start timing next frame
|
|
lastFrameTimeStamp = Time::getCurrentTime();
|
|
|
|
|
|
|
|
int spouseX, spouseY;
|
|
getSpousePosition( &spouseX, &spouseY );
|
|
|
|
|
|
int moveDelta = 1;
|
|
|
|
if( isPlayerDead() ) {
|
|
// stop moving
|
|
moveDelta = 0;
|
|
}
|
|
|
|
if( knowSpouse && isSpouseDead() ) {
|
|
|
|
// player moves slower
|
|
// toggle motion on this frame
|
|
movingThisFrame = ( frameCount % 2 == 0 );
|
|
}
|
|
|
|
if( getKeyDown( SDLK_p ) && canPause ) {
|
|
paused = true;
|
|
}
|
|
if( getKeyDown( SDLK_o ) ) {
|
|
paused = false;
|
|
}
|
|
if( paused && getKeyDown( SDLK_i ) ) {
|
|
stepAnimations();
|
|
}
|
|
|
|
|
|
if( getKeyDown( SDLK_b ) ) {
|
|
// adjust blowup factor
|
|
blowUpFactor += blowUpStep;
|
|
|
|
if( blowUpFactor > maxBlowUpFactor ) {
|
|
blowUpStep *= -1;
|
|
blowUpFactor = maxBlowUpFactor - 1;
|
|
}
|
|
|
|
if( blowUpFactor < 1 ) {
|
|
blowUpStep *= -1;
|
|
blowUpFactor = 2;
|
|
}
|
|
|
|
if( fullScreen ) {
|
|
// force redraw of whole screen
|
|
blowUpChanged = true;
|
|
}
|
|
else {
|
|
// create a new screen using the new size
|
|
createScreen();
|
|
}
|
|
|
|
}
|
|
|
|
if( getKeyDown( SDLK_f ) ) {
|
|
// toggle fullscreen mode
|
|
fullScreen = ! fullScreen;
|
|
|
|
// create a new screen surface (and destroy old one)
|
|
createScreen();
|
|
}
|
|
|
|
|
|
|
|
if( getKeyDown( SDLK_LEFT ) || getJoyPushed( SDL_HAT_LEFT ) ) {
|
|
char notBlocked =
|
|
!isBlocked( (int)( playerX - moveDelta ), (int)playerY );
|
|
|
|
// spouse and character move, and are blocked, together
|
|
if( haveMetSpouse() &&
|
|
isBlocked( spouseX - moveDelta, spouseY ) ) {
|
|
notBlocked = false;
|
|
}
|
|
|
|
if( movingThisFrame && notBlocked ) {
|
|
|
|
playerX -= moveDelta;
|
|
|
|
if( playerX < 0 ) {
|
|
// undo
|
|
playerX += moveDelta;
|
|
}
|
|
else {
|
|
// update screen position
|
|
dX -= moveDelta;
|
|
|
|
// pick sprite frame based on position in world
|
|
if( ( (int)playerX / 2 ) % 2 == 0 ) {
|
|
currentSpriteIndex = 6;
|
|
}
|
|
else {
|
|
currentSpriteIndex = 7;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if( getKeyDown( SDLK_RIGHT ) || getJoyPushed( SDL_HAT_RIGHT )) {
|
|
char notBlocked =
|
|
!isBlocked( (int)( playerX + moveDelta ), (int)playerY );
|
|
|
|
// spouse and character move, and are blocked, together
|
|
if( haveMetSpouse() &&
|
|
isBlocked( spouseX + moveDelta, spouseY ) ) {
|
|
notBlocked = false;
|
|
}
|
|
|
|
if( movingThisFrame && notBlocked ) {
|
|
|
|
dX += moveDelta;
|
|
|
|
playerX += moveDelta;
|
|
|
|
// pick sprite frame based on position in world
|
|
if( ( (int)playerX / 2 ) % 2 == 0 ) {
|
|
currentSpriteIndex = 3;
|
|
}
|
|
else {
|
|
currentSpriteIndex = 2;
|
|
}
|
|
}
|
|
}
|
|
else if( getKeyDown( SDLK_UP ) || getJoyPushed( SDL_HAT_UP ) ) {
|
|
char notBlocked =
|
|
!isBlocked( (int)playerX, (int)( playerY - moveDelta ) );
|
|
|
|
// spouse and character move, and are blocked, together
|
|
if( haveMetSpouse() &&
|
|
isBlocked( spouseX, spouseY - moveDelta ) ) {
|
|
notBlocked = false;
|
|
}
|
|
|
|
if( movingThisFrame && notBlocked ) {
|
|
|
|
playerY -= moveDelta;
|
|
|
|
if( playerY < 0 ) {
|
|
// undo
|
|
playerY += moveDelta;
|
|
}
|
|
else {
|
|
// update screen position
|
|
dY -= moveDelta;
|
|
|
|
// pick sprite frame based on position in world
|
|
if( ( (int)playerY / 2 ) % 2 == 0 ) {
|
|
currentSpriteIndex = 0;
|
|
}
|
|
else {
|
|
currentSpriteIndex = 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if( getKeyDown( SDLK_DOWN ) || getJoyPushed( SDL_HAT_DOWN )) {
|
|
char notBlocked =
|
|
!isBlocked( (int)playerX, (int)( playerY + moveDelta ) );
|
|
|
|
// spouse and character move, and are blocked, together
|
|
if( haveMetSpouse() &&
|
|
isBlocked( spouseX, spouseY + moveDelta ) ) {
|
|
notBlocked = false;
|
|
}
|
|
|
|
if( movingThisFrame && notBlocked ) {
|
|
|
|
dY += moveDelta;
|
|
|
|
playerY += moveDelta;
|
|
|
|
// pick sprite frame based on position in world
|
|
if( ( (int)playerY / 2 ) % 2 == 0 ) {
|
|
currentSpriteIndex = 5;
|
|
}
|
|
else {
|
|
currentSpriteIndex = 4;
|
|
}
|
|
}
|
|
}
|
|
|
|
setPlayerPosition( (int)playerX, (int)playerY );
|
|
setPlayerSpriteFrame( currentSpriteIndex );
|
|
|
|
// may change after we set player position
|
|
getSpousePosition( &spouseX, &spouseY );
|
|
|
|
|
|
// check for events to quit
|
|
SDL_Event event;
|
|
while( SDL_PollEvent(&event) ) {
|
|
switch( event.type ) {
|
|
case SDL_KEYDOWN:
|
|
switch( event.key.keysym.sym ) {
|
|
case SDLK_q:
|
|
case SDLK_ESCAPE:
|
|
done = true;
|
|
quit = true;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
case SDL_QUIT:
|
|
done = true;
|
|
quit = true;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
//t +=0.25;
|
|
frameCount ++;
|
|
|
|
// other animations run independent of whether player is moving
|
|
if( !paused && frameCount % 6 == 0 ) {
|
|
stepAnimations();
|
|
}
|
|
|
|
if( ! isPlayerDead() && ! paused ) {
|
|
// player position on screen inches forward
|
|
dX += timeDelta;
|
|
}
|
|
|
|
double age = ( playerX - dX ) / width;
|
|
|
|
setCharacterAges( age );
|
|
|
|
if( age >= 0.85 ) {
|
|
dieSpouse();
|
|
}
|
|
if( age >= 0.95 ) {
|
|
diePlayer();
|
|
}
|
|
|
|
|
|
if( isChest( (int)playerX, (int)playerY ) == CHEST_CLOSED ) {
|
|
|
|
openChest( (int)playerX, (int)playerY );
|
|
|
|
int chestX, chestY;
|
|
|
|
getChestCenter( (int)playerX, (int)playerY, &chestX, &chestY );
|
|
|
|
if( getChestCode( (int)playerX, (int)playerY ) &
|
|
0x01 << specialGem ) {
|
|
|
|
// reward player
|
|
chestScore += 100;
|
|
|
|
|
|
startPrizeAnimation( chestX, chestY );
|
|
}
|
|
else {
|
|
startDustAnimation( chestX, chestY );
|
|
}
|
|
|
|
}
|
|
|
|
|
|
int distanceFromSpouse = (int) sqrt( pow( spouseX - playerX, 2 ) +
|
|
pow( spouseY - playerY, 2 ) );
|
|
|
|
|
|
if( ! haveMetSpouse() &&
|
|
! isSpouseDead() &&
|
|
distanceFromSpouse < 10 ) {
|
|
|
|
meetSpouse();
|
|
|
|
knowSpouse = true;
|
|
|
|
startHeartAnimation(
|
|
(int)( ( spouseX - playerX ) / 2 + playerX ),
|
|
(int)( ( spouseY - playerY ) / 2 + playerY ) - 2 );
|
|
}
|
|
|
|
|
|
// stop after player has gone off right end of screen
|
|
if( playerX - dX > width ) {
|
|
dX = playerX - width;
|
|
}
|
|
|
|
int exploreDelta = 0;
|
|
|
|
if( playerX > maxPlayerX ) {
|
|
exploreDelta = (int)( playerX - maxPlayerX );
|
|
maxPlayerX = playerX;
|
|
}
|
|
|
|
int spouseExploreFactor = 2;
|
|
|
|
if( haveMetSpouse() ) {
|
|
// exploring worth more
|
|
exploreDelta *= spouseExploreFactor;
|
|
}
|
|
|
|
exploreSubPoints += exploreDelta;
|
|
|
|
|
|
exploreScore = (int)( exploreSubPoints / 10 );
|
|
|
|
if( haveMetSpouse() ) {
|
|
// show explore score contribution in jumps
|
|
exploreScore =
|
|
( exploreScore / spouseExploreFactor )
|
|
* spouseExploreFactor;
|
|
// note:
|
|
// this can cause our score to go down (to the previous
|
|
// jump) as we transition from not having a spouse to
|
|
// having one.
|
|
// we fix this below with maxExploreScore
|
|
}
|
|
|
|
if( exploreScore < maxExploreScore ) {
|
|
// don't let it go down from max
|
|
exploreScore = maxExploreScore;
|
|
}
|
|
|
|
|
|
score = chestScore + exploreScore;
|
|
if( exploreScore > maxExploreScore ) {
|
|
maxExploreScore = exploreScore;
|
|
}
|
|
|
|
|
|
}
|
|
|
|
unsigned long netTime = time( NULL ) - startTime;
|
|
double frameRate = frameCount / (double)netTime;
|
|
|
|
printf( "Max world x = %f\n", maxWorldX );
|
|
printf( "Min world x = %f\n", minWorldX );
|
|
|
|
printf( "Frame rate = %f fps (%d frames)\n",
|
|
frameRate, frameCount );
|
|
|
|
printf( "Game time = %d:%d\n",
|
|
(int)netTime / 60, (int)netTime % 60 );
|
|
|
|
fflush( stdout );
|
|
|
|
|
|
delete titleImage;
|
|
|
|
delete [] gameImage;
|
|
delete [] titlePixels;
|
|
|
|
|
|
destroyWorld();
|
|
destroyScore();
|
|
|
|
|
|
if( quit ) {
|
|
return false;
|
|
}
|
|
else {
|
|
return true;
|
|
}
|
|
|
|
}
|
|
|