#include "global.h" /* ----------------------------------------------------------------------------- File: StepMania.cpp Desc: Entry point for program. Copyright (c) 2001-2002 by the person(s) listed below. All rights reserved. Chris Danford ----------------------------------------------------------------------------- */ #include "StepMania.h" // // Rage global classes // #include "RageLog.h" #include "RageDisplay.h" #include "RageTextureManager.h" #include "RageSoundManager.h" #include "RageInput.h" #include "RageTimer.h" #include "RageException.h" #include "RageMath.h" #include "arch/arch.h" #include "arch/LoadingWindow/LoadingWindow.h" #include "arch/ErrorDialog/ErrorDialog.h" #include "time.h" #include "SDL_utils.h" // // StepMania global classes // #include "ThemeManager.h" #include "NoteSkinManager.h" #include "PrefsManager.h" #include "SongManager.h" #include "PrefsManager.h" #include "GameState.h" #include "AnnouncerManager.h" #include "ScreenManager.h" #include "GameManager.h" #include "FontManager.h" #include "InputFilter.h" #include "InputMapper.h" #include "InputQueue.h" #include "SongCacheIndex.h" /* This is also a global class; we own it. */ ArchHooks *HOOKS = NULL; #ifdef DEBUG #pragma comment(lib, "SDL-1.2.5/lib/SDLmaind.lib") #else #pragma comment(lib, "SDL-1.2.5/lib/SDLmain.lib") #endif #pragma comment(lib, "opengl32.lib") #pragma comment(lib, "glu32.Lib") #ifdef WIN32 HWND g_hWndMain = NULL; #endif static bool g_bHasFocus = true; static void ChangeToDirOfExecutable(const char *argv0) { /* Make sure the current directory is the root program directory * We probably shouldn't do this; rather, we should know where things * are and use paths as needed, so we don't depend on the binary being * in the same place as "Songs" ... */ if( !DoesFileExist("Songs") ) { // strip off executable name CString dir = argv0; unsigned n = dir.find_last_of("/\\"); if (n != dir.npos) dir.erase(n); chdir( dir.GetString() ); } } void ApplyGraphicOptions() { bool bNeedReload = false; bNeedReload |= DISPLAY->SetVideoMode( PREFSMAN->m_bWindowed, PREFSMAN->m_iDisplayWidth, PREFSMAN->m_iDisplayHeight, PREFSMAN->m_iDisplayColorDepth, PREFSMAN->m_iRefreshRate, PREFSMAN->m_bVsync ); bNeedReload |= TEXTUREMAN->SetPrefs( PREFSMAN->m_iTextureColorDepth, PREFSMAN->m_iUnloadTextureDelaySeconds, PREFSMAN->m_iMaxTextureResolution ); if( bNeedReload ) TEXTUREMAN->ReloadAll(); } void ExitGame() { SDL_Event *event; event = (SDL_Event *) malloc(sizeof(SDL_Event)); event->type = SDL_QUIT; SDL_PushEvent(event); } void ResetGame() { GAMESTATE->Reset(); PREFSMAN->ReadGamePrefsFromDisk(); INPUTMAPPER->ReadMappingsFromDisk(); GAMESTATE->m_bPlayersCanJoin = true; for( int p=0; pDoesNoteSkinExist( NOTESKIN->GetCurNoteSkinName(pn) ) ) { CStringArray asNoteSkinNames; NOTESKIN->GetNoteSkinNames( asNoteSkinNames ); NOTESKIN->SwitchNoteSkin( pn, asNoteSkinNames[0] ); } } if( !THEME->DoesThemeExist( THEME->GetCurThemeName() ) ) { CString sGameName = GAMESTATE->GetCurrentGameDef()->m_szName; if( THEME->DoesThemeExist( sGameName ) ) THEME->SwitchTheme( sGameName ); else THEME->SwitchTheme( "default" ); } PREFSMAN->SaveGamePrefsToDisk(); SCREENMAN->SetNewScreen( THEME->GetMetric("Common","InitialScreen") ); } static void GameLoop(); #include "SDL_utils.h" #include "SDL_image.h" #include "SDL_rotozoom.h" #include "StepMania.xpm" /* icon */ static void SetIcon() { SDL_Surface *srf = IMG_ReadXPMFromArray(icon); SDL_SetColorKey( srf, SDL_SRCCOLORKEY, SDL_MapRGB(srf->format, 0xFF, 0, 0xFF)); /* Windows icons are 32x32 and SDL can't resize them for us, which * causes mask corruption. (Actually, the above icon *is* 32x32; * this is here just in case it changes.) */ ConvertSDLSurface(srf, srf->w, srf->h, 32, 0xFF000000, 0x00FF0000, 0x0000FF00, 0x000000FF); zoomSurface(srf, 32, 32); SDL_SetAlpha( srf, SDL_SRCALPHA, SDL_ALPHA_OPAQUE ); SDL_WM_SetIcon(srf, NULL /* derive from alpha */); SDL_FreeSurface(srf); } static bool ChangeAppPri() { if(PREFSMAN->m_iBoostAppPriority == 0) return false; /* If -1 and this is a debug build, don't. It makes the debugger sluggish. */ #ifdef DEBUG if(PREFSMAN->m_iBoostAppPriority == -1) return false; #endif return true; } static void BoostAppPri() { if(!ChangeAppPri()) return; #ifdef WIN32 /* We just want a slight boost, so we don't skip needlessly if something happens * in the background. We don't really want to be high-priority--above normal should * be enough. However, ABOVE_NORMAL_PRIORITY_CLASS is only supported in Win2000 * and later. */ OSVERSIONINFO version; version.dwOSVersionInfoSize=sizeof(version); if(!GetVersionEx(&version)) { LOG->Warn(werr_ssprintf(GetLastError(), "GetVersionEx failed")); return; } #ifndef ABOVE_NORMAL_PRIORITY_CLASS #define ABOVE_NORMAL_PRIORITY_CLASS 0x00008000 #endif DWORD pri = HIGH_PRIORITY_CLASS; if(version.dwMajorVersion >= 5) pri = ABOVE_NORMAL_PRIORITY_CLASS; /* Be sure to boost the app, not the thread, to make sure the * sound thread stays higher priority than the main thread. */ SetPriorityClass(GetCurrentProcess(), pri); #endif } static void RestoreAppPri() { if(!ChangeAppPri()) return; #ifdef WIN32 SetPriorityClass(GetCurrentProcess(), NORMAL_PRIORITY_CLASS); #endif } int main(int argc, char* argv[]) { /* Set up arch hooks first. This may set up crash handling. */ HOOKS = MakeArchHooks(); ChangeToDirOfExecutable(argv[0]); /* Set this up second. Do this early, since it's needed for RageException::Throw. * Do it after ChangeToDirOfExecutable, so the log ends up in the right place. */ LOG = new RageLog(); #ifdef DEBUG LOG->ShowConsole(); #endif /* Whew--we should be able to crash safely now! */ atexit(SDL_Quit); /* Clean up on exit */ /* Fire up the SDL, but don't actually start any subsystems. */ int SDL_flags = 0; #ifdef WIN32 /* We use our own error handler in Windows. */ SDL_flags |= SDL_INIT_NOPARACHUTE; #endif SDL_Init(SDL_flags); atexit(SDL_Quit); CString g_sErrorString = ""; #ifndef DEBUG try{ #endif /* Initialize the SDL library. */ if( SDL_InitSubSystem(SDL_INIT_VIDEO) < 0 ) RageException::Throw( "Couldn't initialize SDL: %s", SDL_GetError() ); SetIcon(); LoadingWindow *loading_window = MakeLoadingWindow(); /* This might be using SDL, so reset the caption. */ SDL_WM_SetCaption("StepMania", "StepMania"); loading_window->Paint(); // changed to use time. GetTimeSinceStart is silly because it always return 0! -Chris srand( time(NULL) ); // seed number generator HOOKS->DumpDebugInfo(); // // Create game objects // GAMESTATE = new GameState; PREFSMAN = new PrefsManager; GAMEMAN = new GameManager; THEME = new ThemeManager; NOTESKIN = new NoteSkinManager; SOUNDMAN = new RageSoundManager(PREFSMAN->m_sSoundDrivers); SOUNDMAN->SetPrefs(PREFSMAN->m_fSoundVolume); ANNOUNCER = new AnnouncerManager; INPUTFILTER = new InputFilter; INPUTMAPPER = new InputMapper; INPUTQUEUE = new InputQueue; SONGINDEX = new SongCacheIndex; /* depends on SONGINDEX: */ SONGMAN = new SongManager( loading_window ); // this takes a long time to load delete loading_window; // destroy this before init'ing Display PREFSMAN->ReadGlobalPrefsFromDisk( true ); DISPLAY = new RageDisplay( PREFSMAN->m_bWindowed, PREFSMAN->m_iDisplayWidth, PREFSMAN->m_iDisplayHeight, PREFSMAN->m_iDisplayColorDepth, PREFSMAN->m_iRefreshRate, PREFSMAN->m_bVsync ); TEXTUREMAN = new RageTextureManager( PREFSMAN->m_iTextureColorDepth, PREFSMAN->m_iUnloadTextureDelaySeconds, PREFSMAN->m_iMaxTextureResolution ); /* Grab the window manager specific information */ SDL_SysWMinfo info; SDL_VERSION(&info.version); if ( SDL_GetWMInfo(&info) < 0 ) RageException::Throw( "SDL_GetWMInfo failed" ); #ifdef WIN32 g_hWndMain = info.window; #endif INPUTMAN = new RageInput(); // These things depend on the TextureManager, so do them after! FONT = new FontManager; SCREENMAN = new ScreenManager; /* People may want to do something else while songs are loading, so do * this after loading songs. */ BoostAppPri(); ResetGame(); if( DISPLAY->IsSoftwareRenderer() ) SCREENMAN->Prompt( SM_None, "OpenGL hardware acceleration\n" "was not detected.\n\n" "StepMania will use the Microsoft\n" "software OpenGL renderer.\n" "However, the game is not playable\n" "with this software renderer.\n" "Please install the latest video\n" "driver from your graphics card vendor\n" "to enable OpenGL hardware acceleration." ); /* Run the main loop. */ GameLoop(); PREFSMAN->SaveGlobalPrefsToDisk(); PREFSMAN->SaveGamePrefsToDisk(); #ifndef DEBUG } catch( RageException e ) { g_sErrorString = e.what(); } #endif SAFE_DELETE( SCREENMAN ); SAFE_DELETE( INPUTQUEUE ); SAFE_DELETE( INPUTMAPPER ); SAFE_DELETE( INPUTFILTER ); SAFE_DELETE( SONGMAN ); SAFE_DELETE( SONGINDEX ); SAFE_DELETE( PREFSMAN ); SAFE_DELETE( GAMESTATE ); SAFE_DELETE( GAMEMAN ); SAFE_DELETE( NOTESKIN ); SAFE_DELETE( THEME ); SAFE_DELETE( ANNOUNCER ); SAFE_DELETE( INPUTMAN ); SAFE_DELETE( SOUNDMAN ); SAFE_DELETE( FONT ); SAFE_DELETE( TEXTUREMAN ); SAFE_DELETE( DISPLAY ); SAFE_DELETE( LOG ); SAFE_DELETE( HOOKS ); if( g_sErrorString != "" ) { // throw up a pretty error dialog ErrorDialog *d = MakeErrorDialog(); d->SetErrorText(g_sErrorString); d->ShowError(); delete d; } return 0; } /* Returns true if the key has been handled and should be discarded, false if * the key should be sent on to screens. */ bool HandleGlobalInputs( DeviceInput DeviceI, InputEventType type, GameInput GameI, MenuInput MenuI, StyleInput StyleI ) { switch( MenuI.button ) { case MENU_BUTTON_OPERATOR: if( type == IET_FIRST_PRESS ) return true; /* Global operator key, to get quick access to the options menu. Don't * do this if we're on a "system menu", which includes the editor * (to prevent quitting without storing changes). */ if( !GAMESTATE->m_bIsOnSystemMenu ) { SCREENMAN->SystemMessage( "OPERATOR" ); SCREENMAN->SetNewScreen( "ScreenOptionsMenu" ); } return true; case MENU_BUTTON_COIN: /* Handle a coin insertion. */ if( type == IET_FIRST_PRESS ) return true; switch( PREFSMAN->m_CoinMode ) { case PrefsManager::COIN_FREE: // fall through case PrefsManager::COIN_HOME: // fall through case PrefsManager::COIN_PAY: GAMESTATE->m_iCoins++; SCREENMAN->RefreshCreditsMessages(); SOUNDMAN->PlayOnce( THEME->GetPathTo("Sounds","insert coin") ); return true; default: ASSERT(0); return true; } } if(DeviceI == DeviceInput(DEVICE_KEYBOARD, SDLK_F4)) { if(type != IET_FIRST_PRESS) return true; if( INPUTFILTER->IsBeingPressed( DeviceInput(DEVICE_KEYBOARD, SDLK_RALT)) || INPUTFILTER->IsBeingPressed( DeviceInput(DEVICE_KEYBOARD, SDLK_LALT)) ) { // pressed Alt+F4 SDL_Event *event; event = (SDL_Event *) malloc(sizeof(SDL_Event)); event->type = SDL_QUIT; SDL_PushEvent(event); return true; } else { // pressed just F4 PREFSMAN->m_bWindowed = !PREFSMAN->m_bWindowed; ApplyGraphicOptions(); return true; } } if(DeviceI == DeviceInput(DEVICE_KEYBOARD, SDLK_F5)) // F5 conflicts with editor and AutoSync { if(type != IET_FIRST_PRESS) return true; // pressed F6. Save Screenshot. CString sPath; for( int i=0; i<1000; i++ ) { sPath = ssprintf("screen%04d.bmp",i); if( !DoesFileExist(sPath) ) break; } DISPLAY->SaveScreenshot( sPath ); SCREENMAN->SystemMessage( "Saved screenshot: " + sPath ); return true; } if(DeviceI == DeviceInput(DEVICE_KEYBOARD, SDLK_RETURN)) { if( INPUTFILTER->IsBeingPressed(DeviceInput(DEVICE_KEYBOARD, SDLK_RALT)) || INPUTFILTER->IsBeingPressed(DeviceInput(DEVICE_KEYBOARD, SDLK_LALT)) ) { if(type != IET_FIRST_PRESS) return true; /* alt-enter */ PREFSMAN->m_bWindowed = !PREFSMAN->m_bWindowed; ApplyGraphicOptions(); return true; } } return false; } static void HandleInputEvents(float fDeltaTime) { INPUTFILTER->Update( fDeltaTime ); static InputEventArray ieArray; ieArray.clear(); // empty the array INPUTFILTER->GetInputEvents( ieArray ); for( unsigned i=0; iDeviceToGame( DeviceI, GameI ); if( GameI.IsValid() && type == IET_FIRST_PRESS ) INPUTQUEUE->RememberInput( GameI ); if( GameI.IsValid() ) { INPUTMAPPER->GameToMenu( GameI, MenuI ); INPUTMAPPER->GameToStyle( GameI, StyleI ); } // HACK: Numlock is read is being pressed if the NumLock light is on. // Filter out all NumLock repeat messages if( DeviceI.device == DEVICE_KEYBOARD && DeviceI.button == SDLK_NUMLOCK && type != IET_FIRST_PRESS ) continue; // skip if( HandleGlobalInputs(DeviceI, type, GameI, MenuI, StyleI ) ) continue; // skip SCREENMAN->Input( DeviceI, type, GameI, MenuI, StyleI ); } } static void GameLoop() { RageTimer timer; while(1) { // process all queued events SDL_Event event; while(SDL_PollEvent(&event)) { if(INPUTMAN->FeedSDLEvent(event)) continue; /* it took care of it */ switch(event.type) { case SDL_QUIT: LOG->Trace("SDL_QUIT: shutting down"); return; /* exit the main loop */ case SDL_VIDEORESIZE: DISPLAY->ResolutionChanged(event.resize.w, event.resize.h); break; case SDL_ACTIVEEVENT: { /* We don't care about mouse focus. */ if(event.active.state == SDL_APPMOUSEFOCUS) break; Uint8 i = SDL_GetAppState(); g_bHasFocus = i&SDL_APPINPUTFOCUS && i&SDL_APPACTIVE; LOG->Trace("App %s focus (%i%i)", g_bHasFocus? "has":"doesn't have", i&SDL_APPINPUTFOCUS, i&SDL_APPACTIVE); if(g_bHasFocus) BoostAppPri(); else RestoreAppPri(); } } } /* * Update */ float fDeltaTime = timer.GetDeltaTime(); if( INPUTFILTER->IsBeingPressed( DeviceInput(DEVICE_KEYBOARD, SDLK_TAB) ) ) { if( INPUTFILTER->IsBeingPressed( DeviceInput(DEVICE_KEYBOARD, SDLK_BACKQUOTE) ) ) fDeltaTime = 0; /* both; stop time */ else fDeltaTime *= 4; } else if( INPUTFILTER->IsBeingPressed( DeviceInput(DEVICE_KEYBOARD, SDLK_BACKQUOTE) ) ) fDeltaTime /= 4; TEXTUREMAN->Update( fDeltaTime ); SCREENMAN->Update( fDeltaTime ); SOUNDMAN->Update( fDeltaTime ); /* Important: Process input AFTER updaing game logic, or input will be acting on song beat from last frame */ HandleInputEvents( fDeltaTime ); /* * Render */ DISPLAY->Clear(); SCREENMAN->Draw(); // draw the game DISPLAY->Flip(); if(g_bHasFocus) SDL_Delay( 0 ); // give some time to other processes and threads else SDL_Delay( 2 ); // give more time to other processes and threads, but not so much that we skip sound } }