/* Copyright (C) 2004 by James Gregory Part of the GalaxyHack project This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY. See the COPYING file for more details. */ #include "Globals.h" #include "Group.h" #include "MainMenu.h" #include "SetupBattle.h" #include "PreBattle.h" #include "RTS.h" #include "Score.h" #include "ForceSelect.h" #include "LookupTables.h" #include "Stuff.h" #include #include #include #include #include using std::cout; using std::endl; using std::ofstream; using std::runtime_error; using std::terminate; using std::istringstream; using std::istream_iterator; using std::find; using std::getline; //Global just to this file: GameState* game = 0; int fpsState = 0; string userHomePath; int lastSwitchTime; //set in init // Function declarations ////////////////////////////////// void FindHomePath(); bool DealWithArgs(int argc, char* argv[], vector& preloadSides); void LoadSettings(char* argv[]); void SaveSettings(); void GameInit(char* argv[]); bool GameMain(); void PollInput(); void DealWithEvent(SDL_Event& event); void FPSCounter(); void GameShutdown(bool saveSettings = true); void ExceptionShutdown(runtime_error e); int main(int argc, char* argv[]) { vector preloadSides; try { if (!DealWithArgs(argc, argv, preloadSides)) return 0; GameInit(argv); if (preloadSides.size()) { for (int i = 0; i != preloadSides.size(); ++i) { sides.push_back(Side(preloadSides[i])); sides[i].FilesToSDStruct(); sides[i].LoadData(false); } gsTo = GST_PreBattle; PreBattle::pbState = PBS_PreBattle; } } catch(runtime_error e) { const char* error = e.what(); WriteLog(error); GameShutdown(false); terminate(); } try { while (GameMain()); } catch(runtime_error e) { ExceptionShutdown(e); } GameShutdown(); return 0; } void FindHomePath() { #ifndef WIN32 char *home = 0; home = getenv("HOME"); if (home) { userHomePath = home; userHomePath += "/.galaxyhack/"; } #endif } bool DealWithArgs(int argc, char* argv[], vector& preloadSides) { if (argc > 1) { bool validArgs = 1; for (int i = 1; i != argc; ++i) { string theArg = argv[i]; if (theArg == "-driver" && argc > i + 1) { globalSettings.videoDriver = argv[i+1]; cout << "Ignoring settings file and using video driver " + globalSettings.videoDriver << endl << endl; ++i; } else if (theArg == "-x" && argc > i + 2) { preloadSides.push_back(argv[i+1]); preloadSides.push_back(argv[i+2]); i +=2; } else if (theArg == "-b" && argc > i + 1) { globalSettings.batch = true; string framesString = argv[i+1]; string::const_iterator begin = framesString.begin(); string::const_iterator end = framesString.end(); globalSettings.maxFrames = IterToInt(begin, end); #ifndef WIN32 cout << "Batch mode initiated, max frames set to " + globalSettings.maxFrames << endl << endl; #endif ++i; } else if (theArg == "-u" && argc > i + 1) { string speedString = argv[i+1]; string::const_iterator begin = speedString.begin(); string::const_iterator end = speedString.end(); worldUpdateInterval = IterToInt(begin, end); #ifndef WIN32 cout << "World update interval set to" + worldUpdateInterval << endl << endl; #endif ++i; } else if (theArg == "-r" && argc > i + 1) { string randomString = argv[i+1]; string::const_iterator begin = randomString.begin(); string::const_iterator end = randomString.end(); globalSettings.randomSeed = IterToInt(begin, end); #ifndef WIN32 cout << "Random seed set to" + globalSettings.randomSeed << endl << endl; #endif ++i; } else validArgs = 0; } if (validArgs == 0) { cout << "Usage: ./galaxyhack [OPTIONS]" << endl << endl; cout << "GalaxyHack - AI script based computer game" << endl << endl; cout << "Options:" << endl; cout << " -driver : use the specified video driver" << endl << endl; cout << " \"x11\" is the default but runs rather slowly." << endl; cout << " \"dga\" will make the game run faster, but requires root permissions (run rootperms.sh) and only works with certain video cards." << endl << endl; cout << " -x : skip menu screen and immediately start a battle between the two fleets given" << endl; cout << " -b : batch mode, see manual for details" << endl; cout << " -u : world update interval, see manual for details" << endl; cout << " -r : set the seed for the random number generator" << endl << endl; cout << "A number of other options can be changed by editing ~/.galaxyhack/settings.dat" << endl << endl; return false; } } return true; } void LoadSettings(char* argv[]) { #ifndef WIN32 string settingsPath = userHomePath + "settings.dat"; if (!DoesFileExist(settingsPath)) settingsPath = "settings.dat"; #else string settingsPath = "settings.dat"; #endif if (!DoesFileExist(settingsPath)) { #ifndef WIN32 string error = "Couldn't find settings.dat in $HOME/.galaxyhack or in present working directory"; #else string error = "Couldn't find settings.dat"; #endif throw runtime_error(error.c_str()); } string inputStr; FileToString(settingsPath, inputStr); istringstream input(inputStr); istream_iterator iter = input; istream_iterator fileEnd = istream_iterator(); #ifndef WIN32 cout << "Using " + settingsPath + ":" << endl << endl; cout << inputStr; #endif //commander iter = find(iter, fileEnd, ':'); input.ignore(); getline(input, globalSettings.commander); iter = input; //data path iter = find(iter, fileEnd, ':'); input.ignore(); getline(input, globalSettings.bdp); if (globalSettings.bdp == "pwd") globalSettings.bdp = ""; else if (globalSettings.bdp[globalSettings.bdp.size() -1] != '/') globalSettings.bdp += '/'; iter = input; //video driver iter = find(iter, fileEnd, ':'); input.ignore(); //may have been set on command line if (globalSettings.videoDriver == "") getline(input, globalSettings.videoDriver); iter = input; //fullscreen iter = find(iter, fileEnd, ':'); ++iter; globalSettings.fullScreen = static_cast(IterToInt(iter, fileEnd)); //screen resolution iter = find(iter, fileEnd, ':'); ++iter; globalSettings.screenWidth = IterToInt(iter, fileEnd); iter = find(iter, fileEnd, ':'); ++iter; globalSettings.screenHeight = IterToInt(iter, fileEnd); //music iter = find(iter, fileEnd, ':'); ++iter; globalSettings.bMusic = static_cast(IterToInt(iter, fileEnd)); //disable sound iter = find(iter, fileEnd, ':'); ++iter; globalSettings.disableSound = static_cast(IterToInt(iter, fileEnd)); //default pics iter = find(iter, fileEnd, ':'); input.ignore(); getline(input, globalSettings.defaultCSPic); iter = input; iter = find(iter, fileEnd, ':'); input.ignore(); getline(input, globalSettings.defaultFrPic); iter = input; iter = find(iter, fileEnd, ':'); input.ignore(); getline(input, globalSettings.defaultSSPic); iter = input; //remembered fleets for (int i = 0; i != maxPlayers; ++i) { iter = find(iter, fileEnd, ':'); input.ignore(); string tempStr; getline(input, tempStr); globalSettings.rememberFleets.push_back(tempStr); iter = input; } iter = find(iter, fileEnd, ':'); ++iter; globalSettings.howGreenIsGreen = IterToInt(iter, fileEnd); oldGlobalSettings = globalSettings; if (globalSettings.batch) { globalSettings.disableSound = true; globalSettings.dontWriteSound = true; worldUpdateInterval = 0; } } void SaveSettings() { #ifndef WIN32 namespace fs = boost::filesystem; fs::path userHomePathPath(userHomePath); if (!fs::exists(userHomePathPath)) fs::create_directory(userHomePathPath); #endif string settingsStr = userHomePath + "settings.dat"; ofstream output(settingsStr.c_str(), std::ios::trunc | std::ios::out); output << "- there must be exactly one space between each colon and the setting that follows" << endl; output << endl; output << "Commander: " << globalSettings.commander << endl; if (globalSettings.bdp.size()) output << "Base data path: " << globalSettings.bdp << endl << endl; else output << "Base data path: pwd" << endl << endl; output << "Video driver: " << globalSettings.videoDriver << endl; output << "Fullscreen: " << globalSettings.fullScreen << endl; output << "Screen width: " << globalSettings.screenWidth << endl; output << "Screen height: " << globalSettings.screenHeight << endl << endl; output << "Music: " << globalSettings.bMusic << endl; if (globalSettings.dontWriteSound) output << "Disable sound: " << oldGlobalSettings.disableSound << endl << endl; else output << "Disable sound: " << globalSettings.disableSound << endl << endl; output << "Default cap ship pic: " << globalSettings.defaultCSPic << endl; output << "Default frigate pic: " << globalSettings.defaultFrPic << endl; output << "Default small ship pic: " << globalSettings.defaultSSPic << endl << endl; for (int i = 0; i != globalSettings.rememberFleets.size(); ++i) output << "Fleet " << i + 1 << ": " << globalSettings.rememberFleets[i] << endl; output << endl; output << "How green is green: " << globalSettings.howGreenIsGreen << endl; } void GameInit(char* argv[]) { namespace fs = boost::filesystem; FindHomePath(); LoadSettings(argv); string tmpStr = "SDL_VIDEODRIVER=" + globalSettings.videoDriver; char* sillyness = c_strNC(tmpStr); putenv(sillyness); JSDL.Init(); SetupLookupTables(); LoadStandardGraphics(); lastSwitchTime = SDL_GetTicks(); } bool GameMain() { now = SDL_GetTicks(); int timePerFrame; //1000ms/60fps = 16 ish, though this will get rounded to 20 if (worldUpdateInterval > 0) timePerFrame = 16; //1000ms/20fps = 50 else timePerFrame = 50; if (now - lastSwitchTime > timePerFrame) { skipDisplayFrame = false; lastSwitchTime = now; } else skipDisplayFrame = true; PollInput(); UpdateWindows(); if (gsTo == gsCurrent) game->Main(); else try { SafeDelete(game); if (gsTo == GST_Reload) gsTo = gsCurrent; switch (gsTo) { case GST_MainMenu: game = new MainMenu::MainMenu_State; break; case GST_SetupBattle: game = new SetupBattle::SetupBattle_State; break; case GST_PreBattle: game = new PreBattle::PreBattle_State; break; case GST_Battle: game = new RTS::RTS_State; break; case GST_Score: game = new Score::Score_State; break; case GST_ForceSelect: game = new ForceSelect::ForceSelect_State; break; case GST_TheOS: return false; break; } //make sure menus have a starting event to force them to update screen before user //input in spite of SDL_WaitEvent SDL_Event event; event.type = SDL_USEREVENT; SDL_PushEvent(&event); //make sure we don't get stuck in poll event loop before display is updated on fast computers lastSwitchTime -= 1000; gsCurrent = gsTo; } catch(runtime_error e) { if (gsTo != GST_MainMenu) { sides.clear(); KillAllWindows(); const char* error = e.what(); WriteLog(error); globalErrorString = error; if (gsCurrent == GST_MainMenu) gsCurrent = GST_TheOS; gsTo = GST_MainMenu; } else throw runtime_error(e); } if (fpsState == 1) FPSCounter(); if (!globalSettings.batch) { static SDL_Rect mouseRect = {0, 0, genPictures[GENPIC_CURSOR]->w, genPictures[GENPIC_CURSOR]->h}; int mx, my; SDL_GetMouseState(&mx, &my); mouseRect.x = mx; mouseRect.y = my; JSDL.Blt(genPictures[GENPIC_CURSOR], mouseRect); } //this does two things: //1. in windowed mode, it prevents it going so fast //it actually slows down to a crawl, I think maybe //because blits start having to wait for other ones to finish //2. In full screen mode, it means if you have the game //set to go more than 60fps it won't have a limit of the //monitor refresh rate if (!skipDisplayFrame) JSDL.Flip(); return true; } void PollInput() { if (globalSettings.batch) return; SDL_Event event; if (gsCurrent == GST_MainMenu || gsCurrent == GST_SetupBattle || gsCurrent == GST_Score || gsCurrent == GST_ForceSelect) { int result = SDL_WaitEvent(&event); if (!result) throw runtime_error("Error when waiting for event"); DealWithEvent(event); return; } while (SDL_PollEvent(&event)) DealWithEvent(event); } void DealWithEvent(SDL_Event& event) { switch (event.type) { case SDL_MOUSEBUTTONDOWN: if (game && !WinMouseD(event.button.button, event.button.x, event.button.y)) game->MouseD(event.button.button, event.button.x, event.button.y); break; case SDL_MOUSEBUTTONUP: if (game) game->MouseU(event.button.button, event.button.x, event.button.y); break; case SDL_MOUSEMOTION: WinMouseM(event.motion.state, event.motion.x, event.motion.y); if (game) game->MouseM(event.motion.state, event.motion.x, event.motion.y); break; case SDL_KEYDOWN: if (WinKeyboard(event.key.keysym)) break; switch(event.key.keysym.sym) { case SDLK_F4: CycleGroupInfoType(); break; case SDLK_F8: if (fpsState == false) fpsState = true; else fpsState = false; break; } if (game) game->Keyboard(event.key.keysym); break; /* FIXME this doesn't work at all case SDL_ACTIVEEVENT: if (event.active.state & SDL_APPACTIVE && event.active.gain == 0) { while (1) { if (SDL_PollEvent(&event)) { if (event.active.state == SDL_APPACTIVE && event.active.gain == 1) break; } SDL_Delay(500); } } break; */ case SDL_QUIT: gsTo = GST_TheOS; break; default: break; } } void FPSCounter() { static int fpsCounter = 0; static int fpsSave = 0; static unsigned int fpsTimer = SDL_GetTicks(); if (now - fpsTimer > 1000) { fpsSave = fpsCounter; fpsCounter = 0; fpsTimer = now; } else ++fpsCounter; char output[60]; sprintf(output, "FPS (sort of): %d", fpsSave); normalFonts.BlitString(50, globalSettings.screenHeight - 20, 0, output); } void GameShutdown(bool saveSettings) { if (saveSettings) SaveSettings(); ClearStandardGraphics(); JSDL.Shutdown(); //should already be deleted unless an exception has been thrown SafeDelete(game); } void ExceptionShutdown(runtime_error e) { const string eWhat = e.what(); const string errorLog = "Exception shutdown due to: " + eWhat; WriteLog(errorLog.c_str()); if (globalSettings.batch) { GameShutdown(); terminate(); } if (JSDL.screen->locked) JSDL.UnlockBack(); const string errorWin = "Major problem:\n" + eWhat + "\n\nClick the left mouse button to exit"; CreateInfoString(errorWin); list::reverse_iterator iter = myWindows.rbegin(); iter->DrawSelf(); JSDL.ForceFlip(); while (1) { SDL_Event event; while (SDL_PollEvent(&event)) { if (event.type == SDL_MOUSEBUTTONDOWN || event.type == SDL_KEYDOWN) { GameShutdown(); terminate(); } } } }