///////////////////////////////////////////////////////////////////// // XLogical - A puzzle game // // Copyright (C) 2000 Neil Brown, Tom Warkentin // // This program 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. // // This program 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 this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. // or at the website: http://www.gnu.org // //////////////////////////////////////////////////////////////////////// // Language Includes #include #include #include #include #ifndef WIN32 #include #ifndef USE_GETLOGIN #include #endif #endif // Application Includes #include "audio.h" #include "gamelogic.h" #include "graph.h" #include "globals.h" #include "ball.h" #include "menus.h" #include "modlist.h" #include "music_files.h" #include "text.h" #include "maps.h" #include "properties.h" #include "sound_files.h" #include "gpl.h" //#define DEBUG_FUNC //#define DEBUG 2 //#define SHOW_FPS // Optimization of draws #define REFRESH_INTERVAL 50 // Rate for blinking items on the screen #define BLINK_RATE 5 // How many frames to skip before // we check for a new mod to update // the text #define MOD_UPDATE_SKIP 50 // We'll scroll once every SCROLL_FREQ frames #define SCROLL_FREQ 1 // Space for text at the top #define TOP_OFFSET 50 // For end credits scrolling #define CREDITS_SCROLL_BASE 95 #define CREDITS_SCROLL_SKIP 1 #define CREDITS_SCROLL_HEIGHT 330 #define CREDITS_SCROLL_SPACING 5 // For end credits scrolling #define LICENSE_SCROLL_BASE 340 #define LICENSE_SCROLL_SKIP 1 #define LICENSE_SCROLL_HEIGHT 75 #define LICENSE_SCROLL_SPACING 1 // bonus life stuff const ulong kBonusLifeScoreStep = 10000; // bonus life every this often ulong gNextBonusLifeScore = kBonusLifeScoreStep; // state of startup static bool gEnteringHiScore = false; static int gNamePos = 0; static int gTitleState = PRE_LOADING; static long kBonusStep = 20L; static bool gFullRefreshNeeded = false; // To count the time we're in a menu so we don't lose it // from our timers ulong gMenuEntryTime = 0; // To save the time left when a level is over ulong spareTime = 0; // For the screen text stuffs int scrFontWidths[] = { 12, 9, 18, 13, 10, 17, // % 17, 8, 13, 14, 13, 15, 11, 18, 7, 29, // / 22, 8, 16, 15, 16, 17, // 5 17, 13, 17, 16, 8, 9, 13, 18, // = 14, 18, 19, 18, 17, 21, // C 19, 14, 14, 22, 14, 9, // I 11, 15, 14, 22, 19, 21, 17, 22, // Q 18, 16, 17, 15, 18, 21, // W 17, 13, 14, 14, 28, 15 }; // Some Local functions void draw_help( void ); void draw_score( void ); void scroll_info( void ); void scroll_mod_info( void ); void refresh_scroll_info( void ); // in game music list Cmodlist gModList; bool gInGame = false; // Class instances to scroll some text CText modScroller; CText infoScroller; CText scrFont; void check_bonus_life() { while (currentGame->score > gNextBonusLifeScore) { currentGame->livesLeft += 1; gNextBonusLifeScore += kBonusLifeScoreStep; audioDriver->play_sound( soundFiles[SOUND_BONUS_LIFE] ); } } Cgame::Cgame( const char *fn ) { #ifdef DEBUG_FUNC cerr << __FILE__ << " Cgame::constructor( )" << endl; #endif // Did we have a level filename? if (fn) { levelFile = strdup( fn ); } else { levelFile = NULL; } currentMap = gameLevels.end( ); playerName = new char[32+1]; strcpy( playerName, "nobody" ); #ifdef WIN32 encryptName = new char[32+1]; strcpy( encryptName, "nobody" ); #else encryptName = new char[L_cuserid+1]; #ifdef USE_GETLOGIN char *login = getlogin(); if (login) { strcpy( encryptName, getlogin( ) ); } else { strcpy( encryptName, "nobody" ); } #else struct passwd *p = getpwuid( geteuid() ); if (p) { strcpy( encryptName, p->pw_name ); } else { strcpy( encryptName, "nobody" ); } #endif #endif // Set the initial state gameState = MAP_START; gNextBonusLifeScore = kBonusLifeScoreStep; score = 0; bonus = 0; livesLeft = 3; modScroller.set_bounds( 32, 256, 40 ); modScroller.set_string((char *) " " ); infoScroller.set_bounds( 384, 607, 40 ); infoScroller.set_string((char *) " " ); read_hiscores( ); scrFont.set_font_info( BMP_FONT_3, scrFontWidths, 26, 2, ' ', '`' ); infoScroller.set_string((char *) " " ); } Cgame::~Cgame( void ) { #ifdef DEBUG_FUNC cerr << __FILE__ << " Cgame::destructor( )" << endl; #endif write_hiscores( ); delete [] playerName; delete [] encryptName; } void Cgame::reset_game( void ) { #ifdef DEBUG_FUNC cerr << __FILE__ << " Cgame::reset_game( )" << endl; #endif gNextBonusLifeScore = kBonusLifeScoreStep; score = 0; bonus = 0; livesLeft = 3; warningPlayed = false; } void add_new_ball( void ) { #ifdef DEBUG_FUNC cerr << __FILE__ << " : add_new_ball( )" << endl; #endif dir_t dir = UP; Cball *b = NULL; #if 0 static int done = 0; if( done > 4 ) { return; } #endif // Check to see if there's a ball on the current path if( CURMAP->startPathActive ) { return; } // Check to see if we're at max moving balls if( ! CURMAP->can_move_ball( ) ) { return; } // Get our new ball class b = new Cball; CURMAP->ballStartTime = graphDriver->graph_get_time(); // Assign color to new ball b->set_color( CURMAP->nextColor ); switch( CURMAP->nextColor ) { default: case C1: b->set_pixmap_num( BMP_BALL_C1 ); break; case C2: b->set_pixmap_num( BMP_BALL_C2 ); break; case C3: b->set_pixmap_num( BMP_BALL_C3 ); break; case C4: b->set_pixmap_num( BMP_BALL_C4 ); break; } // Choose the next color for the map CURMAP->nextColor = (color_t)(rand( ) % 4 + 1); switch( CURMAP->tiles[ CURMAP->startTile]->exitFlags ) { case EXIT_UP: dir = UP; break; case EXIT_DOWN: dir = DOWN; break; case EXIT_LEFT: dir = LEFT; break; case EXIT_RIGHT: dir = RIGHT; break; default: break; } CURMAP->startPathActive = 1; CURMAP->tiles[ CURMAP->startTile ]->add_ball( b, dir, 0 ); b->doMidPoint = false; // special case for start track audioDriver->play_sound( soundFiles[SOUND_NEW_BALL] ); // Add a new moving ball CURMAP->move_ball( ); #if 0 done++; #endif } void setup_new_map( void ) { int row; int col; int index = 0; // Clear the background buffer graphDriver->graph_clear_rect_perm( 0, 0, graphDriver->graph_main_width( ), graphDriver->graph_main_height( ) ); // Fake it out in case the background is the same graphDriver->graph_set_background( BMP_SPINDARK ); // Now draw the real background graphDriver->graph_set_background( CURMAP->bgPixmap ); // Draw the bases for everything for( row = 0; row < CURMAP->ySize; row++ ) { for( col = 0; col < CURMAP->xSize; col++ ) { CURMAP->tiles[index]->draw_under( col * CURMAP->tileSize, row * CURMAP->tileSize + TOP_OFFSET ); index++; } } } //////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////// // PLAY STATE FUNCTIONS //////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////// void play_click_func( int x, int y, int button ) { #ifdef DEBUG_FUNC cerr << __FILE__ << " : play_click_func( "; cerr << x << "," << y << "," << button << " )" << endl; #endif int row; int col; int relX, relY; int index; // Find our row and column row = (y - TOP_OFFSET) / CURMAP->tileSize; col = x / CURMAP->tileSize; // Get the click position relative to the tile origin relX = x % CURMAP->tileSize; relY = (y - TOP_OFFSET) % CURMAP->tileSize; // If we're outside the map area, bail if( relY < 0 ) { return; } // Get the vector index for our tile index = row * CURMAP->xSize + col; // Call that tile's click function CURMAP->tiles[index]->click_func( relX, relY, button ); } void play_loop_func( void ) { #ifdef DEBUG_FUNC cerr << __FILE__ << " : play_loop_func( )" << endl; #endif int status = 1; int ret = 0; ulong timeNow = graphDriver->graph_get_time(); vector< Ctile * >::iterator it; int finishedSpinner = 1; static int scroll_update = 0; if (gFullRefreshNeeded) { graphDriver->graph_set_background( BMP_SPINDARK ); graphDriver->graph_set_background( BMP_BACK_GAME ); full_refresh(); graphDriver->graph_refresh(); gFullRefreshNeeded = false; } #ifdef SHOW_FPS static ulong timestamp = 0; static int framecounter = 0; if( timeNow - timestamp > 0 ) { char fps[10]; sprintf( fps, "%d FPS", framecounter ); graphDriver->graph_clear_rect( 10, 490, 50, 10 ); modScroller.render_string( fps, 10, 490 ); framecounter = 0; timestamp = timeNow; } else { framecounter++; } #endif // Adjust timers to account for the time that was spent in // menus if (gMenuEntryTime > 0) { CURMAP->startTime += timeNow - gMenuEntryTime; CURMAP->ballStartTime += timeNow - gMenuEntryTime; if (CURMAP->nextPatternTime > 0) { CURMAP->nextPatternTime += timeNow - gMenuEntryTime; } gMenuEntryTime = 0; } // Check to see if we need to update the pattern if ((CURMAP->nextPatternTime != 0) && (CURMAP->nextPatternTime < timeNow)) { CURMAP->nextPatternTime = 0; CURMAP->new_pattern(); audioDriver->play_sound( soundFiles[SOUND_NEW_PATTERN] ); } // If we're just waiting for the last spinner to finish, we can do // a slimmed-down loop if( currentGame->gameState == FINISH_WAIT ) { // Loop over the map tiles and do our stuff. for( it = CURMAP->tiles.begin( ); it != CURMAP->tiles.end( ); it++ ) { if( (*it)->get_type( ) != BLANK_TILE ) { // Update Animations (*it)->anim_loop( ); // Move the balls we have to (*it)->move_balls( ); } // Check for unfinished ( "finishing" ) spinners if( (*it)->get_type( ) == SPINNER ) { if( ! TILE_SPINNER( *it )->check_complete( ) ) { ret ++; } } } // Were there no unfinished spinners left? if( ret == CURMAP->spinnersOnMap ) { // Nope - we can move on currentGame->gameState = MAP_COMPLETE; switch_to_intermission(); audioDriver->play_sound( soundFiles[SOUND_FINISH_LEVEL] ); } play_draw_map( ); // Refresh the display //graphDriver->graph_refresh( ); return; } // Loop over the map tiles and do our stuff. for( it = CURMAP->tiles.begin( ); it != CURMAP->tiles.end( ); it++ ) { if( (*it)->get_type( ) != BLANK_TILE ) { // Update Animations (*it)->anim_loop( ); // Move the balls we have to (*it)->move_balls( ); } } // Loop while spinners are still "finishing" while( status > 0 ) { // Reset our status status = 0; for( it = CURMAP->tiles.begin( ); it != CURMAP->tiles.end( ); it++ ) { if( (*it)->get_type( ) == SPINNER ) { ret = TILE_SPINNER( *it )->check_complete( ); switch( ret ) { case FILL_SOLID: currentGame->score = currentGame->score + (SOLID_POINTS * finishedSpinner); break; case FILL_ORDER: currentGame->score += ORDER_POINTS * finishedSpinner; break; case FILL_PATTERN: currentGame->score += PATTERN_POINTS * finishedSpinner; break; case FILL_COMPLETED: currentGame->score += SOLID_POINTS / 10; break; default: break; } } // We finished a spinner - refresh everything if( ret ) { finishedSpinner++; } status += ret; ret = 0; // TBD - here's where we check for combos } } check_bonus_life(); // Did we complete all the spinners on the map? if( CURMAP->spinnersFinished == CURMAP->spinnersOnMap ) { currentGame->gameState = FINISH_WAIT; if (gMenuEntryTime != 0) { spareTime = CURMAP->startTime + CURMAP->mapTimeLimit - timeNow + (timeNow - gMenuEntryTime); } else { spareTime = CURMAP->startTime + CURMAP->mapTimeLimit - timeNow; } // Add our spare time to the bonus if (CURMAP->haveMapTimer) { currentGame->bonus += spareTime / kTicksInSecond; } else { currentGame->bonus = 0; } return; } // Check to see if we're over ball time for the start path. // We only need to check this if the start path is active if( CURMAP->startPathActive && (CURMAP->ballStartTime > 0) ) { if( CURMAP->ballStartTime + CURMAP->ballTimeLimit <= timeNow ) { CURMAP->reset( ); currentGame->gameState = BALL_TIMEOUT; currentGame->livesLeft -= 1; switch_to_intermission(); return; } } // Check to see if we're over map time if( CURMAP->haveMapTimer ) { long timeLeft = CURMAP->startTime + CURMAP->mapTimeLimit - timeNow; if( CURMAP->startTime + CURMAP->mapTimeLimit <= timeNow ) { CURMAP->reset( ); currentGame->gameState = MAP_TIMEOUT; currentGame->livesLeft -= 1; switch_to_intermission(); return; } else if ((timeLeft <= 60*(long)kTicksInSecond) && !currentGame->warningPlayed) { audioDriver->play_sound( soundFiles[SOUND_TIME_WARNING] ); currentGame->warningPlayed = true; } } // Add a new ball if it looks like we need one add_new_ball( ); // Redraw the map play_draw_map( ); // Do some scrolly stuff if( scroll_update++ % SCROLL_FREQ == 0 ) { // Clear the scroll area graphDriver->graph_clear_rect( 0, 40, graphDriver->graph_main_width(), 10 ); // Draw the scroll boundaries graphDriver->graph_draw_pixmap( BMP_SCROLL_BLOCK, 0, 0, 0, 40, 32, 10, 0 ); graphDriver->graph_draw_pixmap( BMP_SCROLL_BLOCK, 0, 0, 256, 40, 128, 10, 0 ); graphDriver->graph_draw_pixmap( BMP_SCROLL_BLOCK, 0, 0, 607, 40, 32, 10, 0 ); scroll_mod_info( ); scroll_info( ); } // print out our score draw_score( ); } void play_key_press_func( keysyms keyval ) { #if DEBUG & 1 cerr << __FILE__ << " : play_key_press_func( keyval=" << (int)keyval << " )" << endl; #endif switch( keyval ) { case eEsc: // Clear the game board for the menu graphDriver->graph_clear_rect( 0, 0, graphDriver->graph_main_width( ), graphDriver->graph_main_height( ) ); gMenuEntryTime = graphDriver->graph_get_time(); get_game_menu()->Start( graphDriver->graph_main_width(), graphDriver->graph_main_height(), full_refresh, switch_to_playing_no_music_change ); break; #ifdef DEBUG case eN: currentGame->currentMap++; CURMAP->reset( ); setup_new_map( ); full_refresh( ); break; #endif case eRight: case eDown: // switch music to next song in user list audioDriver->play_music( gModList.next().c_str() ); break; case eLeft: case eUp: // switch music to previous song in user list audioDriver->play_music( gModList.previous().c_str() ); break; #ifdef DEBUG case eQ: exit( 0 ); break; #endif default: break; } } void play_draw_map( void ) { #ifdef DEBUG_FUNC cerr << __FILE__ << " : play_draw_map( )" << endl; #endif ulong timeUsed; ulong checkTime; float newSpeed; static ulong savedTime = 0; int row; int col; int index = 0; // Now loop through the Clevel_map object and draw the balls index = 0; for( row = 0; row < CURMAP->ySize; row++ ) { for( col = 0; col < CURMAP->xSize; col++ ) { if( ! CURMAP->tiles[index]->balls.empty( ) ) { CURMAP->tiles[index]->draw_balls( col * CURMAP->tileSize, row * CURMAP->tileSize + TOP_OFFSET ); } index++; } } // Refresh the correct tiles index = 0; for( row = 0; row < CURMAP->ySize; row++ ) { for( col = 0; col < CURMAP->xSize; col++ ) { // Check to see if we should draw this tile switch( CURMAP->tiles[index]->get_type( ) ) { case PAINTER: case BLOCKER: case SPINNER: case PATTERN_DISP: case ORDER_DISP: case TELEPORT: case ONEWAY: case NEXT_DISP: case MOVE_COUNTER: case BALL_TIMER: case TIME_DISP: case START: CURMAP->tiles[index]->draw_over( col * CURMAP->tileSize, row * CURMAP->tileSize + TOP_OFFSET ); break; default: break; } index++; } } // speed throttling code checkTime = graphDriver->graph_get_time( ); if( checkTime < savedTime ) { // we wrapped around the 1 second mark timeUsed = (1000 - savedTime) + checkTime; } else { timeUsed = checkTime - savedTime; } newSpeed = BALL_SPEED_MAX * ((float)CURMAP->ballSpdPct / 100.0 ) * ((float)timeUsed / (float)REFRESH_INTERVAL ); if( (newSpeed < BALL_SPEED_MAX) && (newSpeed > 0) ) { CURMAP->ballSpd = newSpeed; } savedTime = checkTime; } void reload_func( void ) { gFullRefreshNeeded = true; } void full_refresh( void ) { #ifdef DEBUG_FUNC cerr << __FILE__ << " : full_refresh( )" << endl; #endif int row; int col; int index = 0; // Paint the background onto the screen graphDriver->graph_refresh( ); // Now loop through the Clevel_map object and draw the balls index = 0; // Draw the bases for everything for( row = 0; row < CURMAP->ySize; row++ ) { for( col = 0; col < CURMAP->xSize; col++ ) { CURMAP->tiles[index]->draw_under( col * CURMAP->tileSize, row * CURMAP->tileSize + TOP_OFFSET ); index++; } } index = 0; for( row = 0; row < CURMAP->ySize; row++ ) { for( col = 0; col < CURMAP->xSize; col++ ) { if( CURMAP->tiles[index]->get_type( ) != BLANK_TILE ) { CURMAP->tiles[index]->draw_balls( col * CURMAP->tileSize, row * CURMAP->tileSize + TOP_OFFSET ); } index++; } } // Now loop through the Clevel_map object and draw anything // that goes on top of balls index = 0; for( row = 0; row < CURMAP->ySize; row++ ) { for( col = 0; col < CURMAP->xSize; col++ ) { if( CURMAP->tiles[index]->get_type( ) != BLANK_TILE ) { CURMAP->tiles[index]->draw_over( col * CURMAP->tileSize, row * CURMAP->tileSize + TOP_OFFSET ); } index++; } } } ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// // START STATE FUNCTIONS ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// void start_click_func( int x, int y, int button ) { #ifdef DEBUG_FUNC cerr << __FILE__ << " : start_click_func( "; cerr << x << "," << y << "," << button << " )" << endl; #endif if (gTitleState > POST_LOADING) { get_main_menu()->Start( graphDriver->graph_main_width(), graphDriver->graph_main_height(), start_draw_map, switch_to_intro_no_music_change ); } } void start_loop_func( void ) { #ifdef DEBUG_FUNC1 cerr << __FILE__ << " : start_loop_func( )" << endl; #endif static char *maps = NULL; class Clevel_map *tMap; char *singleMap; int i; switch( gTitleState ) { case PRE_LOADING: { // Set the background image graphDriver->graph_set_background( BMP_TITLE_TEXT_4 ); // Draw main title graphDriver->graph_draw_perm( BMP_TITLE_TEXT_1, 0, 0, 50, 100, 0, 0 ); graphDriver->graph_erase_rect( 0,0, graphDriver->graph_main_width( ), graphDriver->graph_main_height( ) ); // Draw things once start_draw_map( ); // Draw "loading" text graphDriver->graph_draw( BMP_TITLE_TEXT_3, 80, 210 ); // Clear the text area graphDriver->graph_clear_rect( 90, 290, 470, 70 ); if( (currentGame->levelFile != NULL) && (strlen( currentGame->levelFile) > 0) ) { #if DEBUG & 2 cerr << currentGame->levelFile << endl; #endif gTitleState = LOADING_FILE; } else { gTitleState = LOADING_BUILTIN; maps = maptext; } break; } case LOADING_BUILTIN: case LOADING_FILE: { static bool userMusicLoaded = false; if (!userMusicLoaded) { init_in_game_music(); userMusicLoaded = true; } if( gTitleState == LOADING_BUILTIN ) { // grab the next map singleMap = get_next_builtin_map( maps ); if( singleMap != NULL ) { // Move the counter forward maps += strlen( singleMap ); } } else { // Grab the next map from the file singleMap = get_next_file_map( ); } // Load the map if we got one if( singleMap != NULL ) { // Load up the level tMap = load_a_level( singleMap ); // Free up our space delete [] singleMap; // If we got a valid map in, add it if( tMap != NULL ) { #if DEBUG & 2 cerr << "Level: " << tMap->mapName; cerr << "\t\t" << currentGame->playerName; cerr << " = " << encrypt( tMap->mapName, currentGame->encryptName ) << endl; #endif // Insert the level into the level list gameLevels.insert( gameLevels.end( ), tMap ); // This is a little hackish - may get changed when we do the // "GAME OVER" stuff // point our CURMAP stuff so set_start_path( ) works currentGame->currentMap = gameLevels.end( ); currentGame->currentMap--; // This has to happen after currentMap is assigned. // This is due to the use of CURMAP everywhere. // Bad design I guess. Won't make that mistake again... // ( Yeah right...) tMap->tiles[tMap->startTile]->set_start_path( exit_to_dir( CURMAP->tiles[tMap->startTile]->exitFlags ) ); } else { // We're done loading gTitleState = POST_LOADING; } // Move the lines up for( i = 0; i < 4; i++ ) { graphDriver->graph_copy_area( 100, // SRC_ULX 300 + i * 10 + 10, // SRC_ULY 100, // DST_ULX 300 + i * 10, // DST_ULY 450, // WIDTH 10 ); // HEIGHT } // Clear the bottom line graphDriver->graph_clear_rect( 100, 340, 450, 10 ); // Render the map name modScroller.render_string( tMap->mapName, 100, 340 ); } else { // Out of map text gTitleState = POST_LOADING; } break; } case POST_LOADING: { // No more levels to load // If we added at least one level, reset the levels to the start. if( gameLevels.size( ) > 0 ) { // Set our first level and get on with it currentGame->currentMap = gameLevels.begin( ); } else { // No point in playing if we didn't get any levels cerr << "ERROR: No valid levels!" << endl; clean_exit( 1 ); } graphDriver->graph_set_key_press_func( start_key_press_func ); // Go to the "end" gTitleState = 999; graphDriver->graph_draw_perm( BMP_TITLE_TEXT_2, 0, 0, 50, 300, 0, 0 ); break; } default: { graphDriver->graph_erase_rect( 0, 0, 600, 500 ); graphDriver->graph_erase_rect( 0,0, graphDriver->graph_main_width( ), graphDriver->graph_main_height( ) ); break; } } } void start_draw_map( void ) { #if DEBUG & 1 cerr << __FILE__ << " : start_draw_map( )" << endl; #endif } void start_key_press_func( keysyms keyval ) { #ifdef DEBUG_FUNC cerr << __FILE__ << " : start_key_press_func( keyval=" << (int)keyval << " )" << endl; #endif switch( keyval ) { #ifdef DEBUG case eQ: exit( 0 ); break; #endif case eEsc: get_main_menu()->Start( graphDriver->graph_main_width(), graphDriver->graph_main_height(), start_draw_map, switch_to_intro_no_music_change ); break; default: break; } } ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// // INTERMISSION STATE FUNCTIONS ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// void intermission_click_func( int x, int y, int button ) { #ifdef DEBUG_FUNC cerr << __FILE__ << " : intermission_click_func( "; cerr << x << "," << y << "," << button << " )" << endl; #endif // Refresh the info scroller refresh_scroll_info( ); switch( currentGame->gameState ) { case MAP_COMPLETE: if (currentGame->bonus == 0) { // Pop to the next level currentGame->currentMap++; // Is there a next level? if( currentGame->currentMap == gameLevels.end( ) ) { // Set our state currentGame->gameState = WON; if (currentGame->is_hiscore()) { gEnteringHiScore = true; gNamePos = 0; *(currentGame->playerName) = 0; } // Clear the screen to black graphDriver->graph_clear_rect( 0, 0, graphDriver->graph_main_width(), graphDriver->graph_main_height() ); // End the game currentGame->livesLeft = -1; // Reset the maps currentGame->currentMap = gameLevels.begin( ); audioDriver->play_music( musicFiles[MUSIC_WON_GAME] ); break; } CURMAP->reset(); currentGame->gameState = MAP_START; } break; case GAME_OVER: // Put us back at the beginning if (!gEnteringHiScore) { CURMAP->reset( ); currentGame->currentMap = gameLevels.begin( ); CURMAP->reset( ); switch_to_high_scores(); } break; case HIGH_SCORES: switch_to_intro(); get_main_menu()->Start( graphDriver->graph_main_width(), graphDriver->graph_main_height(), start_draw_map, switch_to_intro_no_music_change ); break; case HELP_MAIN: case ABOUT: switch_to_intro_no_music_change(); get_main_menu()->Start( graphDriver->graph_main_width(), graphDriver->graph_main_height(), start_draw_map, switch_to_intro_no_music_change ); break; case HELP_GAME: switch_to_playing_no_music_change(); get_game_menu()->Start( graphDriver->graph_main_width(), graphDriver->graph_main_height(), start_draw_map, switch_to_playing_no_music_change ); break; case WON: if (!gEnteringHiScore) { // Reset the levels and update the high score table CURMAP->reset( ); currentGame->currentMap = gameLevels.begin( ); currentGame->reset_game( ); switch_to_high_scores(); } break; default: if (currentGame->livesLeft <= 0) { switch_to_game_over(); } else { // Reset the current map CURMAP->reset( ); setup_new_map( ); // Restore the play loop stuff switch_to_playing(); full_refresh(); // Maybe need a switch currentGame->gameState = PLAYING; } break; } } void intermission_loop_func( void ) { #ifdef DEBUG_FUNC cerr << __FILE__ << " : intermission_loop_func( )" << endl; #endif // Now redraw the map intermission_draw_screen( ); } void intermission_key_press_func( keysyms keyval ) { #if DEBUG & 1 cerr << __FILE__ << " : intermission_key_press_func( keyval=" << (int)keyval << " )" << endl; #endif switch( keyval ) { #ifdef DEBUG case eQ: exit( 0 ); break; #endif case eEsc: switch( currentGame->gameState ) { case GAME_OVER: if (!gEnteringHiScore) { // Put us back at the beginning CURMAP->reset( ); currentGame->currentMap = gameLevels.begin( ); CURMAP->reset( ); switch_to_high_scores(); } break; case HIGH_SCORES: switch_to_intro(); get_main_menu()->Start( graphDriver->graph_main_width(), graphDriver->graph_main_height(), start_draw_map, switch_to_intro_no_music_change ); break; case ABOUT: switch_to_intro_no_music_change(); get_main_menu()->Start( graphDriver->graph_main_width(), graphDriver->graph_main_height(), start_draw_map, switch_to_intro_no_music_change ); break; case WON: if (!gEnteringHiScore) { // Reset the levels and update the high score table CURMAP->reset( ); currentGame->currentMap = gameLevels.begin( ); currentGame->reset_game( ); switch_to_high_scores(); } break; default: if ((currentGame->gameState != MAP_COMPLETE) || (currentGame->bonus == 0)) { get_intermission_menu()->Start( graphDriver->graph_main_width(), graphDriver->graph_main_height(), intermission_draw_screen, switch_to_intermission_no_music_change ); } break; } break; case eSpace: // Refresh the info scroller refresh_scroll_info( ); switch( currentGame->gameState ) { case MAP_COMPLETE: if (currentGame->bonus == 0) { // Pop to the next level currentGame->currentMap++; // Is there a next level? if( currentGame->currentMap == gameLevels.end( ) ) { // Set our state currentGame->gameState = WON; // Clear the screen to black graphDriver->graph_clear_rect( 0, 0, graphDriver->graph_main_width(), graphDriver->graph_main_height() ); // End the game currentGame->livesLeft = -1; // Reset the maps currentGame->currentMap = gameLevels.begin( ); audioDriver->play_music( musicFiles[MUSIC_WON_GAME] ); break; } // Reset the current map CURMAP->reset( ); // Maybe need a switch currentGame->gameState = MAP_START; } break; case GAME_OVER: if (!gEnteringHiScore) { // Put us back at the beginning CURMAP->reset( ); currentGame->currentMap = gameLevels.begin( ); CURMAP->reset( ); switch_to_high_scores(); } break; case HIGH_SCORES: switch_to_intro(); get_main_menu()->Start( graphDriver->graph_main_width(), graphDriver->graph_main_height(), start_draw_map, switch_to_intro_no_music_change ); break; case HELP_MAIN: case ABOUT: switch_to_intro_no_music_change(); get_main_menu()->Start( graphDriver->graph_main_width(), graphDriver->graph_main_height(), start_draw_map, switch_to_intro_no_music_change ); break; case HELP_GAME: switch_to_playing_no_music_change(); get_game_menu()->Start( graphDriver->graph_main_width(), graphDriver->graph_main_height(), start_draw_map, switch_to_playing_no_music_change ); break; case WON: if (!gEnteringHiScore) { // Reset the levels and update the high score table CURMAP->reset( ); currentGame->currentMap = gameLevels.begin( ); currentGame->reset_game( ); switch_to_high_scores(); } break; default: if (currentGame->livesLeft <= 0) { CURMAP->reset( ); switch_to_game_over(); } else { // Reset the current map CURMAP->reset( ); // May not need this setup_new_map( ); // Restore the play loop stuff switch_to_playing(); // Draw the current map full_refresh( ); // Maybe need a switch currentGame->gameState = PLAYING; } break; } break; case eBackSpace: if (gEnteringHiScore) { gNamePos -= 1; if (gNamePos < 0) { gNamePos = 0; } currentGame->playerName[gNamePos] = 0; } break; case eEnter: if (gEnteringHiScore) { currentGame->update_hiscores(); gEnteringHiScore = false; } default: if (gEnteringHiScore && (keyval >= eA) && (keyval <= eZ)) { if (gNamePos < 10) { currentGame->playerName[gNamePos] = keysym_to_char( keyval ); gNamePos += 1; currentGame->playerName[gNamePos] = 0; } } break; } } void intermission_draw_screen( void ) { #ifdef DEBUG_FUNC cerr << __FILE__ << " : intermission_draw_screen( )" << endl; #endif char cstr[50]; static int switchFlag = 0; static int blinker = 0; int i,j; static int scrollPixSkip = 0; static int renderLine = 0; static int blankLine = 0; static int scrollPixSkipAbout = 0; static int renderLineAbout = 0; static int blankLineAbout = 0; static const char *credits[] = { "XLOGICAL", " ", " ", " ", "DEVELOPERS", "TOM WARKENTIN", "NEIL BROWN", " ", " ", " ", "RENDERED GRAPHICS", "SLOANE MUSCROFT", " ", " ", " ", " ", "THANKS TO ALL THE FOLKS AT", "RAINBOW ARTS WHO PRODUCED THIS", "GREAT GAME FOR THE AMIGA", " ", "...WHEREVER THEY ARE NOW", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", "NOTHING TO SEE HERE....", " ", " ", " ", "MOVE ALONG...", "" // Must have a null at the end }; if( switchFlag++ % BLINK_RATE == 0 ) { if (blinker) { blinker = 0; } else { blinker = 1; } } switch( currentGame->gameState ) { case BALL_TIMEOUT: // Clear the screen to black graphDriver->graph_clear_rect( 0, 0, graphDriver->graph_main_width(), graphDriver->graph_main_height() ); // Blink on and off here. if( blinker ) { scrFont.render_string_center((char *) "BALL TIMEOUT", 0, PLAYWIDTH, 50 ); } break; case MAP_TIMEOUT: // Clear the screen to black graphDriver->graph_clear_rect( 0, 0, graphDriver->graph_main_width(), graphDriver->graph_main_height() ); if( blinker ) { scrFont.render_string_center((char *) "OUT OF TIME", 0, PLAYWIDTH, 50 ); } break; case WON: // Blink our congrats if( blinker ) { scrFont.render_string_center((char *) "CONGRATULATIONS!! YOU WON", 0, PLAYWIDTH, 50 ); } else { // Clear the blinker area graphDriver->graph_clear_rect( 0, 50, graphDriver->graph_main_width(), scrFont.get_height( ) ); } if (gEnteringHiScore) { scrFont.render_string_center((char *) "ENTER YOUR NAME", 0, PLAYWIDTH, 200); static int wonBlinkCount = 3; static bool wonOnOffFlag = true; if (wonBlinkCount <= 0) { wonBlinkCount = 3; wonOnOffFlag = !wonOnOffFlag; } else { wonBlinkCount -= 1; } // clear the name area graphDriver->graph_clear_rect( 0, 250, graphDriver->graph_main_width(), scrFont.get_height( ) ); char buf[32+1]; sprintf( buf, "%s", currentGame->playerName ); scrFont.render_string_center(buf, 0, PLAYWIDTH, 250); if (wonOnOffFlag) { int len = scrFont.stringLen( buf ); int x = (PLAYWIDTH - len ) / 2 + len; scrFont.render_string((char *) "-", x, 250 ); } } else { // Move the text up by our CREDITS_SCROLL_SKIP. graphDriver->graph_copy_area( 0, CREDITS_SCROLL_BASE, 0, CREDITS_SCROLL_BASE - CREDITS_SCROLL_SKIP, PLAYWIDTH, CREDITS_SCROLL_HEIGHT ); // Decrement the pixel rows to skip before drawing scrollPixSkip += CREDITS_SCROLL_SKIP; // Now draw the lines of text we can see if( credits[renderLine][0] != '\0' ) { // We're not on the last line yet so keep drawing stuff // Draw our line of text starting at the bottom scrFont.render_string_center( (char *)credits[renderLine], 0, PLAYWIDTH, CREDITS_SCROLL_BASE + CREDITS_SCROLL_HEIGHT - scrollPixSkip ); } // See if we're at the height of the text + spacing yet if( scrollPixSkip >= scrFont.get_height( ) + CREDITS_SCROLL_SPACING ) { // Yep, we are. Reset our pixel skip scrollPixSkip = 0; if( credits[renderLine][0] != '\0' ) { // Increment our rendering line if we're not at the end. renderLine++; } else { // We're at the last line - count render lines until // we've scrolled everything and we can redraw blankLine++; if( blankLine > ( CREDITS_SCROLL_HEIGHT / scrFont.get_height( ) ) ) { renderLine = 0; } } } // Now erase the top where we copied over stuff graphDriver->graph_clear_rect( 0, CREDITS_SCROLL_BASE - CREDITS_SCROLL_SKIP, PLAYWIDTH, CREDITS_SCROLL_SKIP ); // Now erase the bottom where we drew too much graphDriver->graph_clear_rect( 0, CREDITS_SCROLL_BASE + CREDITS_SCROLL_HEIGHT - 1, PLAYWIDTH, scrFont.get_height( ) ); } break; case MAP_COMPLETE: // Clear the screen to black graphDriver->graph_clear_rect( 0, 0, graphDriver->graph_main_width(), graphDriver->graph_main_height() ); // Do some scoring here..... scrFont.render_string_center((char *) "WELL DONE", 0, PLAYWIDTH, 5 ); scrFont.render_string_center((char *) "LEVEL COMPLETED", 0, PLAYWIDTH, 50 ); scrFont.render_string((char *) "SPINNERS", 50, 150 ); sprintf( cstr, "%d", CURMAP->spinnersOnMap ); scrFont.render_string_rjust( cstr, PLAYWIDTH - 50 , 150 ); if( CURMAP->haveMapTimer ) { scrFont.render_string((char *) "SECONDS LEFT", 50, 200 ); sprintf( cstr, "%ld", spareTime / kTicksInSecond ); scrFont.render_string_rjust( cstr, PLAYWIDTH - 50, 200 ); scrFont.render_string((char *) "TIME BONUS", 50, 250 ); sprintf( cstr, "%ld", currentGame->bonus ); scrFont.render_string_rjust( cstr, PLAYWIDTH - 50, 250 ); if (currentGame->bonus > 0) { if (currentGame->bonus > kBonusStep) { currentGame->score += kBonusStep; currentGame->bonus -= kBonusStep; } else { currentGame->score += currentGame->bonus; currentGame->bonus = 0; } check_bonus_life(); } else { currentGame->bonus = 0; } } else { scrFont.render_string((char *) "NO TIME LIMIT", 50, 200 ); scrFont.render_string((char *) "NO TIME BONUS", 50, 250 ); currentGame->bonus = 0; } scrFont.render_string((char *) "SCORE", 50, 300 ); sprintf( cstr, "%ld", currentGame->score ); scrFont.render_string_rjust( cstr, PLAYWIDTH - 50, 300 ); break; case HELP_MAIN: case HELP_GAME: draw_help(); break; case MAP_START: // Clear the screen to black graphDriver->graph_clear_rect( 0, 0, graphDriver->graph_main_width(), graphDriver->graph_main_height() ); sprintf( cstr, "%s", (*currentGame->currentMap)->mapName ); scrFont.render_string_center( cstr, 5, PLAYWIDTH - 5, 20 ); scrFont.render_string((char *) "PASSWORD" , 50, 100 ); sprintf( cstr, "%s", encrypt( (*currentGame->currentMap)->mapName, currentGame->encryptName ) ); scrFont.render_string_rjust( cstr, PLAYWIDTH - 50, 100 ); scrFont.render_string((char *) "SPINNERS" , 50, 150 ); sprintf( cstr, "%d", CURMAP->spinnersOnMap ); scrFont.render_string_rjust( cstr, PLAYWIDTH - 50, 150 ); if( CURMAP->haveMapTimer ) { long totalSecs = CURMAP->mapTimeLimit / kTicksInSecond; long mins = totalSecs / 60; long secs = totalSecs % 60; scrFont.render_string((char *) "MAP TIME" , 50, 200 ); sprintf( cstr, "%02ld:%02ld", mins, secs ); scrFont.render_string_rjust( cstr, PLAYWIDTH - 50, 200 ); } else { scrFont.render_string((char *) "NO TIME LIMIT" , 50, 200 ); } { long totalSecs = CURMAP->ballTimeLimit / kTicksInSecond; long mins = totalSecs / 60; long secs = totalSecs % 60; scrFont.render_string((char *) "BALL TIME" , 50, 250 ); sprintf( cstr, "%02ld:%02ld", mins, secs ); scrFont.render_string_rjust( cstr, PLAYWIDTH - 50, 250 ); } scrFont.render_string((char *) "LIVES LEFT" , 50, 300 ); sprintf( cstr, "%d", currentGame->livesLeft ); scrFont.render_string_rjust( cstr, PLAYWIDTH - 50, 300 ); break; case GAME_OVER: // Clear the screen to black graphDriver->graph_clear_rect( 0, 0, graphDriver->graph_main_width(), graphDriver->graph_main_height() ); scrFont.render_string_center((char *) "GAME OVER", 0, PLAYWIDTH, 50 ); if (gEnteringHiScore) { scrFont.render_string_center((char *) "ENTER YOUR NAME", 0, PLAYWIDTH, 200); static int blinkCount = 3; static bool onOffFlag = true; if (blinkCount <= 0) { blinkCount = 3; onOffFlag = !onOffFlag; } else { blinkCount -= 1; } char buf[32+1]; sprintf( buf, "%s", currentGame->playerName ); scrFont.render_string_center(buf, 0, PLAYWIDTH, 250); if (onOffFlag) { int len = scrFont.stringLen( buf ); int x = (PLAYWIDTH - len ) / 2 + len; scrFont.render_string((char *) "-", x, 250 ); } } break; case HIGH_SCORES: // Clear the screen to black graphDriver->graph_clear_rect( 0, 0, graphDriver->graph_main_width(), graphDriver->graph_main_height() ); scrFont.render_string_center((char *) "HIGH SCORES", 0, PLAYWIDTH, 10 ); // Draw the high scores for( i = 0; i < NUM_HI_SCORES; i++ ) { scrFont.render_string( (char *)currentGame->hiscores[i].name.c_str( ), 50, 60 + i * 35 ); sprintf( cstr, "%ld", currentGame->hiscores[i].score ); scrFont.render_string_rjust( cstr, PLAYWIDTH - 50, 60 + i * 35 ); } break; case ABOUT: scrFont.render_string_center((char *) "ABOUT XLOGICAL", 0, PLAYWIDTH, 30 ); j = 100; scrFont.render_string((char *) "CODERS", 50, j ); scrFont.render_string_rjust((char *) "NEIL BROWN", PLAYWIDTH - 50, j ); j += 35; scrFont.render_string_rjust((char *) "TOM WARKENTIN", PLAYWIDTH - 50, j ); j += 35; j += 35; scrFont.render_string((char *) "GRAPHICS", 50, j ); scrFont.render_string_rjust((char *) "SLOANE MUSCROFT", PLAYWIDTH - 50, j ); modScroller.render_string_center((char *) "XLOGICAL V1.0 COPYRIGHT (C) 2000 NEIL BROWN, TOM WARKENTIN", 0, PLAYWIDTH, 280 ); modScroller.render_string_center((char *) "XLOGICAL COMES WITH ABSOLUTELY NO WARRANTY;", 0, PLAYWIDTH, 290 ); modScroller.render_string_center((char *) "XLOGICAL IS DISTRIBUTED UNDER THE GNU PUBLIC LICENSE", 0, PLAYWIDTH, 300 ); graphDriver->graph_draw_pixmap( BMP_SCROLL_BLOCK, 0, 0, 64, LICENSE_SCROLL_BASE - 10 , 176, 2, 1 ); graphDriver->graph_draw_pixmap( BMP_SCROLL_BLOCK, 0, 0, 395, LICENSE_SCROLL_BASE - 10 , 180, 2, 1 ); modScroller.render_string_center((char *) "GNU PUBLIC LICENSE", 0, PLAYWIDTH, LICENSE_SCROLL_BASE - 13 ); // Move the text up by our CREDITS_SCROLL_SKIP. graphDriver->graph_copy_area( 0, LICENSE_SCROLL_BASE, 0, LICENSE_SCROLL_BASE - LICENSE_SCROLL_SKIP, PLAYWIDTH, LICENSE_SCROLL_HEIGHT ); // Decrement the pixel rows to skip before drawing scrollPixSkipAbout += LICENSE_SCROLL_SKIP; // Now draw the lines of text we can see if( gpl[renderLineAbout][0] != '\0' ) { // We're not on the last line yet so keep drawing stuff // Draw our line of text starting at the bottom modScroller.render_string_center( (char *)gpl[renderLineAbout], 0, PLAYWIDTH, LICENSE_SCROLL_BASE + LICENSE_SCROLL_HEIGHT - scrollPixSkipAbout ); } // See if we're at the height of the text + spacing yet if( scrollPixSkipAbout >= modScroller.get_height( ) + LICENSE_SCROLL_SPACING ) { // Yep, we are. Reset our pixel skip scrollPixSkipAbout = 0; if( gpl[renderLineAbout][0] != '\0' ) { // Increment our rendering line if we're not at the end. renderLineAbout++; } else { // We're at the last line - count render lines until // we've scrolled everything and we can redraw blankLineAbout++; if( blankLineAbout > ( LICENSE_SCROLL_HEIGHT / modScroller.get_height( ) ) ) { renderLineAbout = 0; } } } // Now erase the top where we copied over stuff graphDriver->graph_clear_rect( 0, LICENSE_SCROLL_BASE - LICENSE_SCROLL_SKIP, PLAYWIDTH, LICENSE_SCROLL_SKIP ); // Now erase the bottom where we drew too much graphDriver->graph_clear_rect( 0, LICENSE_SCROLL_BASE + LICENSE_SCROLL_HEIGHT - 1, PLAYWIDTH, modScroller.get_height( ) ); graphDriver->graph_draw_pixmap( BMP_SCROLL_BLOCK, 0, 0, 64, LICENSE_SCROLL_BASE + LICENSE_SCROLL_HEIGHT + 3 , 256, 2, 1 ); graphDriver->graph_draw_pixmap( BMP_SCROLL_BLOCK, 0, 0, 320, LICENSE_SCROLL_BASE + LICENSE_SCROLL_HEIGHT + 3 , 256, 2, 1 ); break; default: // Clear the screen to black graphDriver->graph_clear_rect( 0, 0, graphDriver->graph_main_width(), graphDriver->graph_main_height() ); scrFont.render_string_center((char *) "HOW DID YOU GET HERE???", 0, PLAYWIDTH, 40 ); break; } scrFont.render_string_center((char *) "PRESS SPACE TO CONTINUE", 0, PLAYWIDTH, 440 ); } void draw_help( void ) { int xoff; int yoff; // Clear the screen to black graphDriver->graph_clear_rect( 0, 0, graphDriver->graph_main_width(), graphDriver->graph_main_height() ); scrFont.render_string_center((char *) "HOW TO PLAY", 0, PLAYWIDTH, 5 ); xoff = 10; yoff = 50; graphDriver->graph_draw( BMP_BACK_GAME, xoff, yoff ); graphDriver->graph_draw( BMP_TELEPORT_BASE, xoff, yoff ); graphDriver->graph_draw( BMP_TELEPORT_UD, xoff, yoff ); modScroller.render_string((char *) "-- Teleporter --", xoff+70, yoff+5 ); modScroller.render_string((char *) "The ball will be", xoff+70, yoff+15 ); modScroller.render_string((char *) "transported to", xoff+70, yoff+ 25 ); modScroller.render_string((char *) "the matching", xoff+70, yoff+35); modScroller.render_string((char *) "teleporter tile.", xoff+70, yoff+45 ); yoff += 70 ; graphDriver->graph_draw( BMP_BACK_GAME, xoff, yoff ); graphDriver->graph_draw( BMP_BALLMOVE5_5, xoff + 12, yoff + 5 ); modScroller.render_string((char *) "--Moving Balls--", xoff+70, yoff+5 ); modScroller.render_string((char *) "Colored bars show", xoff+70, yoff+15 ); modScroller.render_string((char *) "how many balls are", xoff+70, yoff+ 25 ); modScroller.render_string((char *) "in motion. When it", xoff+70, yoff+35); modScroller.render_string((char *) "is full, no more", xoff+70, yoff+45 ); modScroller.render_string((char *) "balls can be moved.", xoff+70, yoff+55 ); yoff += 70 ; graphDriver->graph_draw( BMP_BACK_GAME, xoff, yoff ); graphDriver->graph_draw( BMP_PATTERN, xoff, yoff ); graphDriver->graph_draw( BMP_GEM_C1, xoff + 23, yoff + 4 ); graphDriver->graph_draw( BMP_GEM_C2, xoff + 43, yoff + 23 ); graphDriver->graph_draw( BMP_GEM_C3, xoff + 23, yoff + 42 ); graphDriver->graph_draw( BMP_GEM_C4, xoff + 2, yoff + 23 ); modScroller.render_string((char *) "--Pattern--", xoff+70, yoff+5 ); modScroller.render_string((char *) "Shows the colors", xoff+70, yoff+15 ); modScroller.render_string((char *) "of the balls in", xoff+70, yoff+ 25 ); modScroller.render_string((char *) "a spinner for it", xoff+70, yoff+35); modScroller.render_string((char *) "to be completed.", xoff+70, yoff+45 ); xoff = 235; yoff = 50 ; graphDriver->graph_draw( BMP_BACK_GAME, xoff, yoff ); graphDriver->graph_draw( BMP_ORDER, xoff, yoff ); graphDriver->graph_draw( BMP_GEM_C1, xoff + 23, yoff + 4 ); graphDriver->graph_draw( BMP_GEM_C2, xoff + 23, yoff + 23 ); graphDriver->graph_draw( BMP_GEM_C3, xoff + 23, yoff + 42 ); modScroller.render_string((char *) "--Order--", xoff+70, yoff+5 ); modScroller.render_string((char *) "Shows which", xoff+70, yoff+15 ); modScroller.render_string((char *) "color balls", xoff+70, yoff+ 25 ); modScroller.render_string((char *) "to fill the", xoff+70, yoff+35); modScroller.render_string((char *) "spinner with", xoff+70, yoff+45 ); modScroller.render_string((char *) "first.", xoff+70, yoff+55 ); yoff += 70 ; graphDriver->graph_draw( BMP_BACK_GAME, xoff, yoff ); graphDriver->graph_draw( BMP_NEXT_BASE, xoff, yoff ); graphDriver->graph_draw( BMP_GEM_C1, xoff + 24, yoff + 24 ); modScroller.render_string((char *) "--Next Ball--", xoff+70, yoff+5 ); modScroller.render_string((char *) "Shows what the", xoff+70, yoff+15 ); modScroller.render_string((char *) "color of the", xoff+70, yoff+ 25 ); modScroller.render_string((char *) "next ball will", xoff+70, yoff+35); modScroller.render_string((char *) "be.", xoff+70, yoff+45); yoff += 70 ; graphDriver->graph_draw( BMP_BACK_GAME, xoff, yoff ); graphDriver->graph_draw( BMP_TIMER, xoff, yoff ); modScroller.render_string((char *) "--Map Timer--", xoff+70, yoff+5 ); modScroller.render_string((char *) "Counts down", xoff+70, yoff+15 ); modScroller.render_string((char *) "the time left", xoff+70, yoff+ 25 ); modScroller.render_string((char *) "to complete", xoff+70, yoff+35); modScroller.render_string((char *) "the map.", xoff+70, yoff+45); xoff = 420; yoff = 50 ; graphDriver->graph_draw( BMP_BACK_GAME, xoff, yoff ); graphDriver->graph_draw( BMP_STARTTIMER_00, xoff + 7, yoff + 8 ); modScroller.render_string((char *) "--Ball Timer--", xoff+70, yoff+5 ); modScroller.render_string((char *) "Indicates how", xoff+70, yoff+15 ); modScroller.render_string((char *) "much time you", xoff+70, yoff+ 25 ); modScroller.render_string((char *) "have to get", xoff+70, yoff+ 35 ); modScroller.render_string((char *) "the ball off the", xoff+70, yoff+45); modScroller.render_string((char *) "starting track.", xoff+70, yoff+55 ); yoff += 70 ; graphDriver->graph_draw( BMP_BACK_GAME, xoff, yoff ); graphDriver->graph_draw( BMP_BLOCK_BASE, xoff, yoff ); graphDriver->graph_draw( BMP_GEM_C1, xoff + 24, yoff + 24 ); modScroller.render_string((char *) "--Blocker--", xoff+70, yoff+5 ); modScroller.render_string((char *) "Only balls that", xoff+70, yoff+15 ); modScroller.render_string((char *) "match the", xoff+70, yoff+ 25 ); modScroller.render_string((char *) "blocker's color", xoff+70, yoff+35); modScroller.render_string((char *) "may pass.", xoff+70, yoff+45); yoff += 70 ; graphDriver->graph_draw( BMP_BACK_GAME, xoff, yoff ); graphDriver->graph_draw( BMP_PAINT_BASE, xoff, yoff ); graphDriver->graph_draw( BMP_GEM_C1, xoff + 24, yoff + 24 ); modScroller.render_string((char *) "--Painter--", xoff+70, yoff+5 ); modScroller.render_string((char *) "Changes the color", xoff+70, yoff+15 ); modScroller.render_string((char *) "of the ball to", xoff+70, yoff+ 25 ); modScroller.render_string((char *) "match", xoff+70, yoff+35); yoff += 70 ; xoff = 10; yoff = 265 ; graphDriver->graph_draw( BMP_BACK_GAME, xoff, yoff ); graphDriver->graph_draw( BMP_NEXT_BASE, xoff, yoff ); graphDriver->graph_draw( BMP_ARROW_R, xoff + 23, yoff + 23 ); modScroller.render_string((char *) "--Oneway--", xoff+70, yoff+5 ); modScroller.render_string((char *) "Balls may only", xoff+70, yoff+15 ); modScroller.render_string((char *) "travel in the", xoff+70, yoff+ 25 ); modScroller.render_string((char *) "direction of the", xoff+70, yoff+35); modScroller.render_string((char *) "arrow.", xoff+70, yoff+45 ); xoff = 235; yoff = 265 ; graphDriver->graph_draw( BMP_BACK_GAME, xoff, yoff ); graphDriver->graph_draw( BMP_SPIN_0, xoff, yoff ); modScroller.render_string((char *) "--Spinner--", xoff+70, yoff+5 ); modScroller.render_string((char *) "Must be filled with balls of the", xoff+70, yoff+15 ); modScroller.render_string((char *) "same color or following the order", xoff+70, yoff+ 25 ); modScroller.render_string((char *) "and pattern tiles. Right-click", xoff+70, yoff+35); modScroller.render_string((char *) "to spin. Left-click on a ball to", xoff+70, yoff+45 ); modScroller.render_string((char *) "eject it.", xoff+70, yoff+55 ); xoff = 230; yoff = 340 ; modScroller.render_string((char *) "[ESC] - Bring up the menu", xoff, yoff ); modScroller.render_string((char *) "[LEFT] - Play Previous MOD", xoff, yoff+10 ); modScroller.render_string((char *) "[RIGHT] - Play Next MOD", xoff, yoff+20 ); yoff = 375; modScroller.render_string_center((char *) "The object of the game is to fill up the spinners on the", 0, PLAYWIDTH, yoff ); yoff += 10; modScroller.render_string_center((char *) "map with balls of the same color. If there is a pattern", 0, PLAYWIDTH, yoff ); yoff += 10; modScroller.render_string_center((char *) "indicator on the level, the spinners must match that", 0, PLAYWIDTH, yoff ); yoff += 10; modScroller.render_string_center((char *) "pattern instead. If there is an order indicator", 0, PLAYWIDTH, yoff ); yoff += 10; modScroller.render_string_center((char *) "then the spinners must be filled with balls matching", 0, PLAYWIDTH, yoff ); yoff += 10; modScroller.render_string_center((char *) "the colors in the order indicator until it is empty.", 0, PLAYWIDTH, yoff ); yoff += 10; modScroller.render_string_center((char *) "", 0, PLAYWIDTH, yoff ); } void draw_score( void ) { #ifdef DEBUG_FUNC cerr << __FILE__ << " : draw_score( )" << endl; #endif char scoreStr[ 24 ]; // Clear the score spot graphDriver->graph_clear_rect( 500, 5, 139, 35 ); // Draw the score sprintf( scoreStr, "%8ld", currentGame->score ); scrFont.render_string( (char *)&scoreStr, 500, 5 ); } void scroll_info( void ) { #ifdef DEBUG_FUNC cerr << __FILE__ << " : scroll_info( )" << endl; #endif static int first = 1; if( first ) { refresh_scroll_info( ); first = 0; } infoScroller.draw( ); } void refresh_scroll_info( void ) { #ifdef DEBUG_FUNC cerr << __FILE__ << " : refresh_scroll_info( )" << endl; #endif char *info = new char [300]; sprintf( info, "LEVEL: %s -- PASSWORD: %s -- LIVES LEFT: %d", (*currentGame->currentMap)->mapName, encrypt((*currentGame->currentMap)->mapName, currentGame->encryptName ), currentGame->livesLeft ); infoScroller.set_string( info ); } void scroll_mod_info( void ) { #ifdef DEBUG_FUNC cerr << __FILE__ << " : scroll_mod_info( )" << endl; #endif static char *modInfo = NULL; static long modChecksum = 0; static bool noModInfoSet = false; static int skip = MOD_UPDATE_SKIP; // Skip a few cycles before updating text // We don't need to be dead-on, and it saves // a lot of time if( skip-- > 0 ) { modScroller.draw( ); return; } skip = MOD_UPDATE_SKIP; // Get the mod name if(audioDriver->get_current_music_name() != NULL) { noModInfoSet = false; // If the checksum doesn't match, then it's a new mod if(modChecksum != get_cksum( audioDriver->get_current_music_name() )) { modChecksum = get_cksum( audioDriver->get_current_music_name() ); delete [] modInfo; modInfo = new char [ strlen( audioDriver->get_current_music_name() ) + 100 ]; sprintf( modInfo, "MODULE [%s] -- MODULE TYPE: [%s]", audioDriver->get_current_music_name(), audioDriver->get_current_music_type() ); modScroller.set_string( modInfo ); modScroller.draw( ); } else { modScroller.draw( ); } } else { if (!noModInfoSet) { modScroller.set_string((char *) "NO MODULE INFO" ); noModInfoSet = true; } modScroller.draw( ); } } void switch_to_game_over( void ) { #ifdef DEBUG_FUNC cout << "switch_to_game_over" << endl; #endif gInGame = false; currentGame->gameState = GAME_OVER; if (currentGame->is_hiscore()) { gEnteringHiScore = true; gNamePos = 0; *(currentGame->playerName) = 0; } audioDriver->play_music( musicFiles[MUSIC_END_GAME] ); } void switch_to_high_scores( void ) { #ifdef DEBUG_FUNC cout << "switch_to_high_scores" << endl; #endif gInGame = false; currentGame->gameState = HIGH_SCORES; currentGame->write_hiscores(); graphDriver->graph_set_click_func( intermission_click_func ); graphDriver->graph_set_key_press_func( intermission_key_press_func ); graphDriver->graph_set_loop_func( intermission_loop_func ); audioDriver->play_music( musicFiles[MUSIC_HIGH_SCORE] ); } void switch_to_intro( void ) { #ifdef DEBUG_FUNC cout << "switch_to_intro" << endl; #endif gInGame = false; graphDriver->graph_set_click_func( start_click_func ); graphDriver->graph_set_key_press_func( start_key_press_func ); graphDriver->graph_set_loop_func( start_loop_func ); audioDriver->play_music( musicFiles[MUSIC_INTRO] ); // Set the background image graphDriver->graph_set_background( BMP_TITLE_TEXT_4 ); // Draw main title graphDriver->graph_draw_perm( BMP_TITLE_TEXT_1, 0, 0, 50, 100, 0, 0 ); graphDriver->graph_draw_perm( BMP_TITLE_TEXT_2, 0, 0, 50, 300, 0, 0 ); graphDriver->graph_erase_rect( 0,0, graphDriver->graph_main_width( ), graphDriver->graph_main_height( ) ); } void switch_to_intro_no_music_change( void ) { #ifdef DEBUG_FUNC cout << "switch_to_intro" << endl; #endif gInGame = false; graphDriver->graph_set_click_func( start_click_func ); graphDriver->graph_set_key_press_func( start_key_press_func ); graphDriver->graph_set_loop_func( start_loop_func ); // Set the background image graphDriver->graph_set_background( BMP_TITLE_TEXT_4 ); // Draw main title graphDriver->graph_draw_perm( BMP_TITLE_TEXT_1, 0, 0, 50, 100, 0, 0 ); graphDriver->graph_draw_perm( BMP_TITLE_TEXT_2, 0, 0, 50, 300, 0, 0 ); graphDriver->graph_erase_rect( 0,0, graphDriver->graph_main_width( ), graphDriver->graph_main_height( ) ); } void switch_to_intermission( void ) { #ifdef DEBUG_FUNC cout << "switch_to_intermission" << endl; #endif gInGame = false; graphDriver->graph_set_click_func( intermission_click_func ); graphDriver->graph_set_key_press_func( intermission_key_press_func ); graphDriver->graph_set_loop_func( intermission_loop_func ); audioDriver->play_music( musicFiles[MUSIC_PRE_GAME] ); } void switch_to_intermission_no_music_change( void ) { #ifdef DEBUG_FUNC cout << "switch_to_intermission_no_music_change" << endl; #endif gInGame = false; graphDriver->graph_set_click_func( intermission_click_func ); graphDriver->graph_set_key_press_func( intermission_key_press_func ); graphDriver->graph_set_loop_func( intermission_loop_func ); } void switch_to_playing( void ) { #ifdef DEBUG_FUNC cout << "switch_to_playing" << endl; #endif gInGame = true; gMenuEntryTime = 0; CURMAP->reset(); graphDriver->graph_set_click_func( play_click_func ); graphDriver->graph_set_key_press_func( play_key_press_func ); graphDriver->graph_set_loop_func( play_loop_func ); full_refresh(); audioDriver->play_music( gModList.next().c_str() ); } void switch_to_playing_no_music_change( void ) { #ifdef DEBUG_FUNC cout << "switch_to_playing_no_music_change" << endl; #endif gInGame = true; graphDriver->graph_set_click_func( play_click_func ); graphDriver->graph_set_key_press_func( play_key_press_func ); graphDriver->graph_set_loop_func( play_loop_func ); full_refresh(); } void init_in_game_music() { if (atoi(properties::instance().get( "audio.enabled" ).c_str()) > 0) { gModList.init(); gModList.randomize(); } } void music_finished() { if (gInGame) { // we are in game - so play the next song in the modlist audioDriver->play_music( gModList.next().c_str() ); } else { // we are not in game - so replay the current song audioDriver->play_current_music(); } } // $Log: gamelogic.cpp,v $ // Revision 1.8 2001-08-01 00:59:40 tom // Changed blinker code to use if statement. Visual C++ didn't like the ~ // operator. // // Revision 1.7 2001/07/31 20:54:52 tom // Changed system time functions to use time function provided by Cgraph // class instead of using OS system calls. This should make it easier // to port to other operating systems, e.g. BSD. // // Revision 1.6 2001/03/15 09:39:31 tom // added some logic to handle video mode changes on the fly // // Revision 1.5 2001/02/19 03:03:53 tom // hard coded names and paths of builtin music and sound files to be // consistent with how image files are compiled into the executable and // to make the properties/ini file simpler to port between Unix and WIN32 // // Revision 1.4 2001/02/18 09:04:14 tom // - changed background tile to be a background jpg image // - added prompt for user name when high score achieved // - moved finding user defined in game music to load phase so that game // screen comes up faster when a large number of user defined dirs exist // - fixed a bug on "RESTART LEVEL" menu item where it would take you to the // intermission screen but as soon as game play started, it was "GAME OVER". // The menu item now changes to "END GAME" if no lives are left. // // Revision 1.3 2001/02/17 07:18:21 tom // got xlogical working in windows finally - with the following limitations: // - music is broken (disabled in this checkin) // - no user specified mod directories for in game music // - high scores will be saved as user "nobody" // // Revision 1.2 2001/02/16 20:59:53 tom // did some WIN32 porting work on the code... just a few more files to go. // // Revision 1.1 2001/01/20 17:32:27 brown // Working toward Windows integration // // Revision 1.44 2000/11/19 03:23:47 brown // Fixed lost ball bug - found teleporter bug but it's not fixed yet // // Revision 1.43 2000/11/09 22:06:33 tom // cleaned up compiler warnings // // Revision 1.42 2000/11/07 07:40:21 tom // fixed infinite negative bonus bug // // Revision 1.41 2000/10/17 05:15:42 tom // fixed typo in "BROWN" on about screen // // Revision 1.40 2000/10/14 03:09:29 tom // commented out defining of debug mode // // Revision 1.39 2000/10/13 04:41:11 tom // fixed core dump when no module info is available. // // Revision 1.38 2000/10/08 23:28:56 tom // commented out debug defines // // Revision 1.37 2000/10/08 21:14:02 brown // Fixed blocker problem // Fixed painter tile so the right bitmaps is drawn // // Revision 1.36 2000/10/08 19:12:57 tom // fixed help screen // // Revision 1.35 2000/10/08 18:23:09 brown // Fixed the editor crash when the level file didn't exist // Added help menu // // Revision 1.34 2000/10/08 16:45:56 tom // added time warning sound // // Revision 1.33 2000/10/08 16:07:28 tom // fixed some little annoyances // // Revision 1.32 2000/10/08 07:08:01 tom // ifdef'd quick quit keypress code and next level jump keypress code // // Revision 1.31 2000/10/08 05:41:00 tom // fixed check score potential feature // // Revision 1.30 2000/10/08 05:37:44 tom // added new pattern sound effect // // Revision 1.29 2000/10/08 05:30:18 tom // - added map reset calls all over the place // - added sound effect for finishing level // // Revision 1.28 2000/10/08 04:04:07 tom // added bonus life scoring // // Revision 1.27 2000/10/08 02:53:47 brown // Fixed the level editor and some graphics // // Revision 1.26 2000/10/07 19:26:20 brown // Added BALL_SPEED_PCT to the parser // Fixed debug info output // // Revision 1.25 2000/10/07 19:21:35 tom // ifdef'd printing of level file being loaded and some other debug stuff // // Revision 1.24 2000/10/07 19:12:18 brown // Speed-throttling code completed // // Revision 1.22 2000/10/07 05:41:19 tom // made hi-score file a hard coded absolute path // // Revision 1.21 2000/10/06 19:29:04 brown // Added autoconf stuff // Added GPL header to files // Added WON scroller // Added GPL to about screen // // Revision 1.20 2000/10/03 02:51:15 brown // Random refresh changes. // // Revision 1.19 2000/10/03 01:46:04 tom // fixed refresh bug when cancelling/resuming game from play menu // // Revision 1.18 2000/10/02 06:00:00 tom // fixed some remaining refresh and music restart bugs // // Revision 1.17 2000/10/02 02:23:47 brown // Random fixes // // Revision 1.16 2000/10/01 22:16:25 tom // added about screen // // Revision 1.15 2000/10/01 21:41:42 tom // fixed refresh flickering during transitions // // Revision 1.14 2000/10/01 21:29:22 tom // fixed music restarting in the intermission screens when you enter/exit // the menu repeatedly // // Revision 1.13 2000/10/01 21:07:53 tom // made use of music_finished callback to play next user song or to replay // current song depending on what state the program is in. // // Revision 1.12 2000/10/01 20:53:15 brown // More background fixes // // Revision 1.11 2000/10/01 19:35:53 brown // Background fixes // // Revision 1.10 2000/10/01 19:28:01 tom // - removed all references to CFont // - fixed password entry menu item // // Revision 1.9 2000/10/01 18:52:27 brown // Fixed some background stuff and the title screen // // Revision 1.8 2000/10/01 18:41:34 tom // fixed music starting over when returning from menu // // Revision 1.7 2000/10/01 18:27:01 tom // added new ball sound effect and code to play it // // Revision 1.6 2000/10/01 08:08:14 tom // finished implementing the SDL_mixer audio driver // // Revision 1.5 2000/10/01 05:00:24 tom // put in infrastructure for abstract audio drivers and started to code // SDL_mixer support. // // Revision 1.4 2000/10/01 03:43:32 brown // Added FPS counter // // Revision 1.3 2000/09/30 17:33:15 brown // Fixed timer drawing and order/pattern refreshes // // Revision 1.2 2000/09/30 15:52:12 brown // Fixed score display, text scrolling, window height, ball redraws // // Revision 1.1.1.1 2000/09/28 02:17:52 tom // imported sources // // Revision 1.72 2000/07/23 18:29:52 brown // Fixed problem where quitting the current level didn't reset levels to the // beginning // // Revision 1.71 2000/07/23 16:50:27 brown // Fixed level timer changing while paused // Added "finish spinner" animation // Fixed level ending before last spinner finished ( sortof ) // // Revision 1.70 2000/04/19 03:20:26 tom // ifdef'd the code that uses getlogin() in favor of getpwuid( geteuid() ). // // Revision 1.69 2000/04/17 21:29:53 tom // Fixed core dumps and music & sound playing problems. (xlogical now // requires libmikmod 3.1.9) // // Revision 1.68 2000/03/28 07:29:59 tom // ifdef'd printing of debug information. // // Revision 1.67 2000/02/23 07:28:58 tom // Fixed a bonus calculating bug. // // Revision 1.66 2000/02/21 23:06:34 brown // Updated a CRAPLOAD of graphics, fixed fonts and scrolling // // Revision 1.65 2000/02/13 07:32:46 tom // Moved writing of high score file into clean_exit() // // Revision 1.64 2000/02/13 07:24:15 tom // made high score reading ifstream not use the new operator (no more memory // leak and it will close the file when the object goes out of scope). // // Revision 1.63 2000/02/13 07:16:36 tom // - changed level names to include spaces // - made it so that player cannot advance intermission screens until bonus // is finished being moved to the player's score // // Revision 1.62 2000/02/13 06:27:18 tom // Fixed new ball timer bug and high score update bug. // // Revision 1.61 2000/02/13 03:44:49 brown // Start tile changes, as well as some font scroll changes // // Revision 1.60 2000/02/13 00:16:45 tom // Made title screen pop up menu when mouse is clicked, implemented reset high // score menu item. // // Revision 1.59 2000/02/12 23:47:09 tom // Fixed writing of hiscore file. // // Revision 1.58 2000/02/12 23:23:46 tom // Fixed next level transition when using mouse click to advance from // intermission screens. // // Revision 1.57 2000/02/12 23:12:45 tom // Fixed "Spinners" text being printed off the edge of the screen. // print_normal() wasn't fixed... // // Revision 1.56 2000/02/12 22:54:50 tom // Did a bunch of menu fixing up... and got rid of menu stuff in graph_gtk. // // Revision 1.55 2000/02/12 21:23:13 brown // Fix // // Revision 1.54 2000/02/12 21:07:07 brown // // Bunch of new backgrounds // // Revision 1.53 2000/02/12 20:38:33 tom // Fixed up // // Revision 1.52 2000/02/07 03:53:09 brown // General graphics changes with some logic here and there // // Revision 1.51 2000/01/23 09:04:31 tom // - made threaded sound loop conditionally compiled (now disabled by default) // // Revision 1.50 2000/01/23 06:48:07 tom // Disabled key press events until levels are loaded. // // Revision 1.49 2000/01/23 06:11:36 tom // - changed menu stuff to take 1 call back that is called to restore the // graphDriver state and do whatever else is necessary to switch states. // - created centralized state switching routines to make it easier to hook in // mod changing, etc. // - xlogical now supports intro.mod, pregame.mod, ingame.mod, & user mods // (still need to hook in highscore.mod and endgame.mod) // // Revision 1.48 2000/01/22 19:35:27 brown // High score changes etc. // // Revision 1.47 2000/01/10 02:53:47 brown // Bunch of changes - fixed start track, fixed order and pattern, fixed some // backgrounds. // // Revision 1.46 2000/01/09 02:26:16 brown // Quite a few fixes - speedup for the level loading, passwords work etc // // Revision 1.45 2000/01/06 02:41:58 brown // Some font stuff. Fixed some other intermission goodies // // Revision 1.44 2000/01/05 03:47:33 brown // Fixed some endgame stuff, scrollers, and hi-score menu entry goodies // // Revision 1.43 2000/01/02 03:33:04 tom // Added levels 70 & 71. // // Revision 1.42 2000/01/01 21:51:18 brown // Mucho changes - fixed level loading and game startup screens // // Revision 1.41 1999/12/28 07:36:20 tom // Fixed menu refresh problems. // // Revision 1.40 1999/12/25 08:18:33 tom // Added "Id" and "Log" CVS keywords to source code. //