/* ******************************************************************************** Copyright 2006, 2007 Ben Ruyl This file is part of Sokoban 3D. Sokoban 3D 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. Sokoban 3D 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 Sokoban 3D; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA ******************************************************************************** */ #include // Header File For Windows #include // Header File For The OpenGL32 Library #include // Header File For The GLu32 Library #include "main.h" #include "texture.h" #include "sokomain.h" #include "gamemain.h" #include "menumain.h" #include "jpeg.h" #include "text.h" // Header File For NeHeGL #include "wglext.h" #include "time.h" #include "fmod.h" #include "fmod_errors.h" #include "cINI.h" #include "LevelloadUnit.h" #ifndef CDS_FULLSCREEN // CDS_FULLSCREEN Is Not Defined By Some #define CDS_FULLSCREEN 4 // Compilers. By Defining It This Way, #endif #include "ARB_MULTISAMPLE.h" // We Can Avoid Errors #include "language.h" bool SupportsDepthClamp = false; FMOD_RESULT result; FMOD_SYSTEM * systema; FMOD_SOUND * FMusicStream; FMOD_CHANNEL * FMusicChannel; bool LoadNextSong(void); string stAppDirectory; bool DEV_MODE = false; GL_Window* g_window; Keys* g_keys; TGameOptions FGameOptions; typedef char* (* LPGETSTRING)(int); typedef void (* LPGETMENUPOS)(float[5]); typedef float (* LPGETMENURIGHT)(void); //typedef const char* GetString(int aID); wstring FLanguage[50]; bool RTL = false; // if this is true, everything is read from right to left LPGETSTRING GetString; LPGETMENUPOS GetMenuPos; LPGETMENURIGHT GetMenuRight; float FMenuRight = 0.0f; vector FLangFiles; vector FLevelsPlayed; vector FMusicNames; //FSOUND_SAMPLE* FMusicChannels[MAX_SOUND]; int FMusicCount; int FMusicPos; int FCorruptedMusicCount = 0; //FMUSIC_MODULE* FSoundChannels[MAX_SOUND]; int GameState; float start = 0; float MenuPos[5]; struct // Create A Structure For The Timer Information { __int64 frequency; // Timer Frequency float resolution; // Timer Resolution unsigned long mm_timer_start; // Multimedia Timer Start Value unsigned long mm_timer_elapsed; // Multimedia Timer Elapsed Time bool performance_timer; // Using The Performance Timer? __int64 performance_timer_start; // Performance Timer Start Value __int64 performance_timer_elapsed; // Performance Timer Elapsed Time } timer; PFNWGLSWAPINTERVALEXTPROC wglSwapIntervalEXT = NULL; PFNWGLGETSWAPINTERVALEXTPROC wglGetSwapIntervalEXT = NULL; FMOD_RESULT F_CALLBACK onEnd( FMOD_CHANNEL * channel, FMOD_CHANNEL_CALLBACKTYPE type, int command, unsigned int commanddata1, unsigned int commanddata2) { FMOD_Sound_Release(FMusicStream); // free the previous song LoadNextSong(); // if (LoadNextSong()) return FMOD_OK; } bool LoadNextSong(void) { bool ResultOK = false; while (ResultOK == false && FGameOptions.FMusicEnabled) { FMusicPos++; if (FMusicPos >= FMusicNames.size()) FMusicPos = 0; string stSoundPath = stAppDirectory + "Data/music/" + FMusicNames[FMusicPos]; result = FMOD_System_CreateStream(systema, stSoundPath.c_str(), FMOD_DEFAULT, 0, &FMusicStream); if (result == FMOD_OK) result = FMOD_System_PlaySound(systema, FMOD_CHANNEL_FREE, FMusicStream, false, &FMusicChannel); FMOD_Channel_SetVolume(FMusicChannel, 0.4f); ResultOK = result == FMOD_OK; if (!ResultOK) { FCorruptedMusicCount++; if (FCorruptedMusicCount == FMusicNames.size()) FGameOptions.FMusicEnabled = false; // all the files are corrupted } } FMOD_Channel_SetCallback(FMusicChannel, FMOD_CHANNEL_CALLBACKTYPE_END, onEnd, 0); return ResultOK; } // by steinsoft.org bool checkExtension(const char* extensions, const char* checkFor) { char *extension; char *endOfStr; int idx = 0; endOfStr = (char*)checkFor + strlen(checkFor); while (checkFor < endOfStr) { idx = strcspn(checkFor, " "); if ( (strlen(extensions) == idx) && (strncmp(extensions, checkFor, idx) == 0)) return true; checkFor += (idx + 1); } return false; } bool InitVSync() { if (checkExtension((char*)glGetString(GL_EXTENSIONS), "WGL_EXT_swap_control")) { wglSwapIntervalEXT = (PFNWGLSWAPINTERVALEXTPROC) wglGetProcAddress("wglSwapIntervalEXT"); wglGetSwapIntervalEXT = (PFNWGLGETSWAPINTERVALEXTPROC) wglGetProcAddress("wglGetSwapIntervalEXT"); return true; } return false;// WGL_EXT_swap_control not supported } bool SetVSync(bool VSync) { if(!wglSwapIntervalEXT) return false; wglSwapIntervalEXT(VSync); return true; } bool GetVSync(bool* VSync) { if(!wglGetSwapIntervalEXT) return false;//VSynce value is not valid... *VSync = wglGetSwapIntervalEXT(); return true; } void TimerInit(void) // Initialize Our Timer (Get It Ready) { memset(&timer, 0, sizeof(timer)); // Clear Our Timer Structure // Check To See If A Performance Counter Is Available // If One Is Available The Timer Frequency Will Be Updated if (!QueryPerformanceFrequency((LARGE_INTEGER *) &timer.frequency)) { // No Performace Counter Available timer.performance_timer = FALSE; // Set Performance Timer To FALSE timer.mm_timer_start = timeGetTime(); // Use timeGetTime() To Get Current Time timer.resolution = 1.000f/1000.0f; // Set Our Timer Resolution To .001f timer.frequency = 1000; // Set Our Timer Frequency To 1000 timer.mm_timer_elapsed = timer.mm_timer_start; // Set The Elapsed Time To The Current Time } else { // Performance Counter Is Available, Use It Instead Of The Multimedia Timer // Get The Current Time And Store It In performance_timer_start QueryPerformanceCounter((LARGE_INTEGER *) &timer.performance_timer_start); timer.performance_timer = TRUE; // Set Performance Timer To TRUE // Calculate The Timer Resolution Using The Timer Frequency timer.resolution = (float) (((double)1.0000f)/((double)timer.frequency)); // Set The Elapsed Time To The Current Time timer.performance_timer_elapsed = timer.performance_timer_start; } } float TimerGetTime() // Get Time In Milliseconds { __int64 time; // time Will Hold A 64 Bit Integer if (timer.performance_timer) // Are We Using The Performance Timer? { QueryPerformanceCounter((LARGE_INTEGER *) &time); // Grab The Current Performance Time // Return The Current Time Minus The Start Time Multiplied By The Resolution And 1000 (To Get MS) return ( (float) ( time - timer.performance_timer_start) * timer.resolution)*1000.000f; } else { // Return The Current Time Minus The Start Time Multiplied By The Resolution And 1000 (To Get MS) return( (float) ( timeGetTime() - timer.mm_timer_start) * timer.resolution)*1000.000f; } } void SaveSettings(void) { FILE* inifile = fopen("config.ini", "wt"); if (inifile) { fprintf(inifile, "*** WARNING: THIS FILE WILL BE REWRITTEN WHEN OPTIONS CHANGE. ANY NON-VALID DATA WILL BE REMOVED ***\n"); fprintf(inifile, "[General]\n"); fprintf(inifile, "Music=%d\n", (int)FGameOptions.FMusicEnabled); fprintf(inifile, "FullScreen=%d\n", (int)FGameOptions.FFullScreen); fprintf(inifile, "Anisotropic Filtering=%d\n", FGameOptions.FAnisotropicFilter); fprintf(inifile, "Language=%s\n", FGameOptions.FLangName); fprintf(inifile, "Antialias=%d\n", FGameOptions.FSamples); fprintf(inifile, "Resolution=%d\n", FGameOptions.FScreenResolution); fprintf(inifile, "VSync=%d\n", (int)FGameOptions.FVSyncEnabled); fclose(inifile); } /* HKEY hk; if (RegCreateKeyEx(HKEY_CURRENT_USER, "SOFTWARE\\Soko3D", 0, NULL, REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL, &hk, 0)) { // MessageBox (HWND_DESKTOP, "Could not create the registry key", "Error", // MB_OK | MB_ICONEXCLAMATION); } DWORD dwData; RegSetValueEx(hk, "Language", 0, REG_SZ, (LPBYTE)&FGameOptions.FLangName, sizeof(FGameOptions.FLangName)); dwData = DWORD(FGameOptions.FMusicEnabled); RegSetValueEx(hk, "Music", 0, REG_DWORD, (LPBYTE)&dwData, sizeof(DWORD)); dwData = DWORD(FGameOptions.FSamples); RegSetValueEx(hk, "Samples", 0, REG_DWORD, (LPBYTE)&dwData, sizeof(DWORD)); dwData = DWORD(FGameOptions.FVSyncEnabled); RegSetValueEx(hk, "VSync", 0, REG_DWORD, (LPBYTE)&dwData, sizeof(DWORD)); dwData = DWORD(FGameOptions.FAnisotropicFilter); RegSetValueEx(hk, "Anisotropic filtering", 0, REG_DWORD, (LPBYTE)&dwData, sizeof(DWORD)); dwData = DWORD(FGameOptions.FWindowed); RegSetValueEx(hk, "Windowed", 0, REG_DWORD, (LPBYTE)&dwData, sizeof(DWORD)); dwData = DWORD(FGameOptions.FScreenResolution); RegSetValueEx(hk, "Resolution", 0, REG_DWORD, (LPBYTE)&dwData, sizeof(DWORD)); RegCloseKey(hk);*/ } void LoadSettings(void) { /* HKEY hk; // changed to CURRENT_USER to allow guests on computer to configure if (RegCreateKeyEx(HKEY_CURRENT_USER, "SOFTWARE\\Soko3D", 0, NULL, REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL, &hk, 0)) { // MessageBox (HWND_DESKTOP, "Could not create the registry key", "Error", // MB_OK | MB_ICONEXCLAMATION); } DWORD dwData; strcpy(FGameOptions.FLangName, "English.txt"); // dwData = strlen(FGameOptions.FLangName);// sizeof(FGameOptions.FLangName); // DWORD MusicEn = 1; dwData = sizeof(FGameOptions.FLangName); RegQueryValueEx(hk, "Language", 0, NULL, (LPBYTE)&FGameOptions.FLangName, &dwData); FGameOptions.FMusicEnabled = true; dwData = sizeof(DWORD); DWORD MusicEn = 1; RegQueryValueEx(hk, "Music", 0, 0, (LPBYTE)&MusicEn, &dwData); FGameOptions.FMusicEnabled = (bool)MusicEn; FGameOptions.FSamples = 0; dwData = sizeof(DWORD); DWORD dwSamples = 0; RegQueryValueEx(hk, "Samples", 0, 0, (LPBYTE)&dwSamples, &dwData); FGameOptions.FSamples = dwSamples; FGameOptions.FWindowed = false; dwData = sizeof(DWORD); DWORD dwWindowed = 0; RegQueryValueEx(hk, "Windowed", 0, 0, (LPBYTE)&dwWindowed, &dwData); FGameOptions.FWindowed = (bool)dwWindowed; FGameOptions.FAnisotropicFilter = 1; dwData = sizeof(DWORD); DWORD dwFilter = 1; RegQueryValueEx(hk, "Anisotropic filtering", 0, 0, (LPBYTE)&dwFilter, &dwData); FGameOptions.FAnisotropicFilter = dwFilter; FGameOptions.FScreenResolution = 1; // 800 * 600 standard dwData = sizeof(DWORD); DWORD dwRes = 1; RegQueryValueEx(hk, "Resolution", 0, 0, (LPBYTE)&dwRes, &dwData); FGameOptions.FScreenResolution = dwRes; FGameOptions.FVSyncEnabled = true; dwData = sizeof(DWORD); DWORD VSyncEn = DWORD(FGameOptions.FVSyncEnabled); RegQueryValueEx(hk, "VSync", 0, 0, (LPBYTE)&VSyncEn, &dwData); FGameOptions.FVSyncEnabled = (bool)VSyncEn; RegCloseKey(hk);*/ // NEW CIni ini; int* val = 0; FGameOptions.FMusicEnabled = true; if ( ini.ReadValue("config.ini", "General", "Music", vtInt,(void**) &val) ) { FGameOptions.FMusicEnabled = *val; delete val; } FGameOptions.FFullScreen = true; if ( ini.ReadValue("config.ini", "General", "FullScreen", vtInt, (void**) &val) ) { FGameOptions.FFullScreen = (bool) *val; delete val; } FGameOptions.FAnisotropicFilter = 1; if ( ini.ReadValue("config.ini", "General", "Anisotropic Filtering", vtInt, (void**) &val) ) { FGameOptions.FAnisotropicFilter = *val; delete val; } FGameOptions.FScreenResolution = 1; if ( ini.ReadValue("config.ini", "General", "Resolution", vtInt, (void**) &val) ) { FGameOptions.FScreenResolution = *val; delete val; } FGameOptions.FSamples = 0; if ( ini.ReadValue("config.ini", "General", "Antialias", vtInt, (void**) &val) ) { FGameOptions.FSamples = *val; delete val; } FGameOptions.FVSyncEnabled = true; if ( ini.ReadValue("config.ini", "General", "VSync", vtInt, (void**) &val) ) { FGameOptions.FVSyncEnabled = (bool) *val; delete val; } char* str = 0; strcpy(FGameOptions.FLangName, "English.txt"); if ( ini.ReadValue("config.ini", "General", "Language", vtString, (void**) &str) ) { strcpy(FGameOptions.FLangName, str); //delete [] str; } } string GetFileExtension(string str) { char result[30]; //char * pch = strrchr(str.c_str(), '.'); //memcpy(result, pch, strlen(str) + str - pch); size_t pos; pos = str.find_last_of('.'); string ext = str.substr(pos+1); return ext; } char* ExtractFilePath(void) { char fullpath[MAX_PATH]; GetModuleFileName(NULL, fullpath, sizeof(fullpath)); DWORD dwSize; LPSTR szDirectory; dwSize = strrchr(fullpath, '\\') - fullpath + 2; szDirectory = (LPSTR) malloc(dwSize); lstrcpynA(szDirectory, fullpath, dwSize); return szDirectory; } void EnumerateDir(string folderpath, vector & outputfolderlist) { HANDLE hList; TCHAR szDir[MAX_PATH+1]; WIN32_FIND_DATA FileData; // Get the proper directory path sprintf(szDir, "%s*", folderpath.c_str ()); // Get the first file hList = FindFirstFile(szDir, &FileData); if (hList == INVALID_HANDLE_VALUE) { //outputfolderlist.resize(0); } else { // Traverse through the directory structure while (FindNextFile(hList, &FileData)) { // Check the object is a directory or not if (FileData.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) {} else { if ((strcmp(FileData.cFileName, ".") != 0) && (strcmp(FileData.cFileName, "..") != 0)) { outputfolderlist.push_back (FileData.cFileName); } } } } FindClose(hList); } void SaveLevelsPlayedInfo(void) { FILE * pFile; // create a new file and write the data string path = stAppDirectory + "Data/misc/Levelsplayed.txt"; pFile = fopen(path.c_str(), "wt"); if (pFile != NULL) { fprintf(pFile, "%d\n", FLevelsPlayed.size()); for (int i = 0; i < FLevelsPlayed.size(); i++) { fprintf(pFile, "%s\n", FLevelsPlayed[i].FLevelName.c_str()); fprintf(pFile, "%d\n", FLevelsPlayed[i].FLevelNumbers.size()); for (int j = 0; j < FLevelsPlayed[i].FLevelNumbers.size(); j++) { fprintf(pFile, "%d\n", FLevelsPlayed[i].FLevelNumbers[j]); } } fclose(pFile); } } void LoadLevelsPlayedInfo(void) { FILE * pFile; string path = stAppDirectory + "Data/misc/Levelsplayed.txt"; pFile = fopen(path.c_str(), "rt"); int numberoflevelsets, numberoflevels, dummyint; char dummy1[MAX_PATH]; FLevelsPlayed.resize(0); if (pFile != NULL) { fscanf(pFile, "%d\n", &numberoflevelsets); FLevelsPlayed.resize(numberoflevelsets); for (int i = 0; i < numberoflevelsets; i++) { fgets(dummy1, MAX_PATH, pFile); // fscanf(pFile, "%s", &dummy1); this one stops after a space! FLevelsPlayed[i].FLevelName = dummy1; FLevelsPlayed[i].FLevelName.resize(FLevelsPlayed[i].FLevelName.size() - 1); fscanf(pFile, "%d\n", &numberoflevels); FLevelsPlayed[i].FLevelNumbers.resize(numberoflevels); for (int j = 0; j < numberoflevels; j++) { fscanf(pFile, "%d\n", &dummyint); FLevelsPlayed[i].FLevelNumbers[j] = dummyint; } } fclose(pFile); } } // This is Windows specific! In Windows the default wchar_t width is 2, on *nix systems it is 4 // The files are written with width 2 bool LoadLanguage(string path) { bool success = false; wchar_t dummy[1000]; FILE * pFile = fopen(path.c_str(), "rb"); if (pFile != NULL) { wchar_t dummy2[1000]; /* for (int i = 0; i < 4; i++) fscanf (pFile, "%f\n", &MenuPos[i]); fscanf(pFile, "%f\n", &FMenuRight);*/ // fread(&MenuPos, 4, 5, pFile); // fread() // setlocale(LC_ALL,""); float menuposx; for (int z = 0; z < 5; z++) { fread(&menuposx, sizeof(float), 1, pFile); // read the menupos MenuPos[z] = menuposx; } fread(&menuposx, sizeof(float), 1, pFile); FMenuRight = menuposx; fread(dummy2, sizeof(wchar_t), 1, pFile); // read the unicode identifier for (int i = 0; i < 46; i++) { // fgets(dummy2, 1000, pFile); // wchar_t dummy1[1000]; // mbstowcs(dummy, dummy2, 1000); fgetws(dummy, 1000, pFile); FLanguage[i] = dummy; // 2: one for the line break, one for the return FLanguage[i].resize(FLanguage[i].length() - 2); /* int test = FLanguage[i].length(); wstring hoi = FLanguage[i]; // hoi.resize(test); int z = 0; for (int k = 0; k < test; k++) hoi[k] = FLanguage[i][test - 1 - k]; FLanguage[i] = hoi; */ // fread(dummy2, 2, 1, pFile); // read the unicode identifier // FLanguage[i] = GetString(i); } if (FLanguage[0] == L"Hebrew") // fixme RTL = true; else RTL = false; success = true; fclose(pFile); } return success; } BOOL Initialize (GL_Window* window, Keys* keys) // Any GL Init Code & User Initialiazation Goes Here { g_window = window; g_keys = keys; glClearColor (0.0f, 0.0f, 0.0f, 0.5f); // Black Background glClearDepth (1.0f); // Depth Buffer Setup glDepthFunc (GL_LEQUAL); // The Type Of Depth Testing (Less Or Equal) glEnable (GL_DEPTH_TEST); // Enable Depth Testing glShadeModel (GL_SMOOTH); // Select Smooth Shading // glHint (GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); // Set Perspective Calculations To Most Accurate // glHint(GL_POINT_SMOOTH_HINT, GL_NICEST); // glHint(GL_POLYGON_SMOOTH_HINT, GL_NICEST); glCullFace(GL_BACK); glEnable(GL_CULL_FACE); // glEnable(GL_LINE_SMOOTH); // glEnable(GL_POINT_SMOOTH); glEnable(GL_TEXTURE_2D); glEnable(GL_MULTISAMPLE_ARB); // enable anti-alias stAppDirectory = ExtractFilePath(); LoadLanguage(stAppDirectory + "Data/languages/" + FGameOptions.FLangName); BuildFont(); result = FMOD_System_Create(&systema); // Create the sound handle. if (result != FMOD_OK) { exit(-1); } result = FMOD_System_Init(systema, 10, FMOD_INIT_NORMAL, 0); // Initialize FMOD. if (result != FMOD_OK) { exit(-1); } TimerInit(); InitVSync(); if (FGameOptions.FVSyncEnabled) SetVSync(true); else SetVSync(false); SupportsDepthClamp = checkExtension((char*)glGetString(GL_EXTENSIONS), "GL_DEPTH_CLAMP_NV"); if (SupportsDepthClamp) glEnable(GL_DEPTH_CLAMP_NV); if (!strstr((char*)glGetString(GL_EXTENSIONS), "GL_EXT_texture_filter_anisotropic")) printf("Warning: Anisotropic filter not supported\n"); else { float maximumAnisotropy; glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &maximumAnisotropy); printf("Max anisotropy: %f\n", maximumAnisotropy); } // requires Opengl 1.2 glLightModelf(GL_LIGHT_MODEL_COLOR_CONTROL,GL_SEPARATE_SPECULAR_COLOR); texture[0] = loadJPEGTexture(stAppDirectory + "Data/textures/lostvalley_north.jpg"); texture[3] = loadJPEGTexture(stAppDirectory + "Data/textures/lostvalley_east.jpg"); texture[2] = loadJPEGTexture(stAppDirectory + "Data/textures/lostvalley_south.jpg"); texture[1] = loadJPEGTexture(stAppDirectory + "Data/textures/lostvalley_west.jpg"); texture[4] = loadJPEGTexture(stAppDirectory + "Data/textures/lostvalley_up.jpg"); texture[5] = loadJPEGTexture(stAppDirectory + "Data/textures/lostvalley_down.jpg"); texture[6] = LoadTGA1(stAppDirectory + "Data/textures/grass.tga"); texture[7] = LoadTGA1(stAppDirectory + "Data/textures/Crate1.tga"); texture[9] = LoadTGA1(stAppDirectory + "Data/textures/wall.tga"); texture[10] = LoadTGA1(stAppDirectory + "Data/textures/grasscrate.tga"); texture[11] = LoadTGA1(stAppDirectory + "Data/textures/cursor.tga"); texture[12] = LoadTGA1(stAppDirectory + "Data/textures/Particle1.tga"); texture[13] = LoadTGA1(stAppDirectory + "Data/textures/background.tga"); texture[14] = LoadTGA1(stAppDirectory + "Data/textures/cursor_high.tga"); texture[15] = LoadTGA1(stAppDirectory + "Data/textures/Particle2.tga"); texture[16] = LoadTGA1(stAppDirectory + "Data/textures/up.tga"); texture[17] = LoadTGA1(stAppDirectory + "Data/textures/down.tga"); texture[18] = LoadTGA1(stAppDirectory + "Data/textures/fog2.tga"); LoadLevelsPlayedInfo(); FLevelNames.resize(0); EnumerateDir(stAppDirectory + "Data/levels/", FLevelNames); EnumerateDir(stAppDirectory + "Data/music/", FMusicNames); EnumerateDir(stAppDirectory + "Data/languages/", FLangFiles); if (FMusicNames.size() == 0) FGameOptions.FMusicEnabled = false; FMusicPos = -1; LoadNextSong(); GameState = GS_MENU; MenuGameState = GSM_MAINMENU; MENU_Initialize(); SOKO_Init(); return TRUE; // Return TRUE (Initialization Successful) } void Deinitialize (void) // Any User DeInitialization Goes Here { our_font.clean(); } void Update (DWORD milliseconds) // Perform Motion Updates Here { /* if (g_keys->keyDown [VK_ESCAPE] == TRUE) // Is ESC Being Pressed? { TerminateApplication (g_window); // Terminate The Program }*/ // while(TimerGetTime() < start + 33.33333f) {} float curtime = TimerGetTime() ; if (curtime > start + 10.000f) { start = curtime; if (GameState == GS_GAME) SOKO_Update(); else if (GameState == GS_MENU) MENU_Update(0); FMOD_System_Update(systema); mode = RENDER; Draw(); } } void Draw (void) { if (mode == RENDER) framescount++; if (GameState == GS_GAME) { SOKO_Draw(); } else if (GameState == GS_MENU) MENU_Draw(); }