/* * Copyright (C) 2002,2003 Daniel Heck * * 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. * * $Id: game.cc,v 1.1 2003/01/23 22:36:04 dheck Exp $ */ #include "display.hh" #include "game.hh" #include "menus.hh" #include "options.hh" #include "video.hh" #include "sound.hh" #include "px/sdl.hh" #include #include using namespace enigma; using namespace px; using namespace std; //---------------------------------------- // Data types //---------------------------------------- namespace { class GameMenu : public gui::Menu { public: GameMenu(); private: bool on_event (const SDL_Event &e); void on_action(gui::Widget *w); gui::Widget *resume, *restart, *options, *abort; }; class Game { public: Game(); void run(LevelPack *lp, int ilevel); void quit(); void finish_level(); void restart_level(); private: // Private types. enum State { INGAME, // game is currently running LEVELFINISHED, // level finished; proceed to next one PLAYERDEAD, // player is dead; restart level shortly RESTARTGAME, // restart the current (new lives) RELOADLEVEL, // reload current level (resets objects) LEVELINFO, // display level info ABORT // quit the game immediately }; // Private methods. void handle_events(); void on_keydown(SDL_Event &e); void on_mousebutton(SDL_Event &e); void update_mouse_button_state(); void change_state(State newstate); void tick(double dtime); void show_menu(); bool load_level(int ilevel); // Private variables. State state; LevelPack *level_pack; unsigned icurrent_level; Uint32 last_tick_time; double level_finished_dtime; double actor_dead_dtime; px::Screen *screen; }; } GameMenu::GameMenu() : resume(new gui::TextButton("Resume Level", this)), restart(new gui::TextButton("Restart Level", this)), options(new gui::TextButton("Options", this)), abort(new gui::TextButton("Abort Level", this)) { add(resume, Rect(0,0,150,40)); add(restart, Rect(0,45,150,40)); add(options, Rect(0,90,150,40)); add(abort, Rect(0,135,150,40)); center(video::GetScreen()); } bool GameMenu::on_event (const SDL_Event &e) { if (e.type == SDL_MOUSEBUTTONDOWN && e.button.button == SDL_BUTTON_RIGHT) { Menu::quit(); return true; } return false; } void GameMenu::on_action(gui::Widget *w) { if (w == resume) { Menu::quit(); } else if (w == abort) { enigma::QuitGame(); Menu::quit(); } else if (w == restart) { enigma::RestartLevel(); Menu::quit(); } else if (w == options) { GUI_OptionsMenu(); Menu::quit(); } } //====================================================================== // MAIN PROGRAM //====================================================================== //---------------------------------------- // Level management //---------------------------------------- namespace enigma { vector LevelPacks; } // Run init_file to initialize the level list. void LevelPack::init() { string filename = enigma::FindDataFile(init_file); ifstream is(filename.c_str()); if (!is) { fprintf(stderr, "Couldn't load level pack %s.\n", filename.c_str()); return; } levels.clear(); string line; vector tokens; while (getline(is, line)) { split_copy(line, '|', back_inserter(tokens)); transform(tokens.begin(), tokens.end(), tokens.begin(), trim); if (tokens.size() == 2) levels.push_back(LevelInfo(tokens[0], tokens[1], "")); tokens.clear(); } } void enigma::AddLevelPack (const char *init_file, const char *name) { LevelPack *lp = new LevelPack(init_file, name); LevelPacks.push_back(lp); lp->init(); } //---------------------------------------------------------------------- // GAME IMPLEMENTATION //---------------------------------------------------------------------- Game::Game() : state(INGAME), icurrent_level(0), last_tick_time(0), screen (0) { } void Game::quit() { change_state(ABORT); } void Game::finish_level() { change_state(LEVELFINISHED); } void Game::restart_level() { change_state(RELOADLEVEL); } void Game::change_state(State newstate) { if (state == newstate) return; state = newstate; switch (state) { case LEVELFINISHED: level_finished_dtime=0; sound::PlaySound("finished"); display::GetStatusBar()->show_text("Level finished!", display::TEXT_STATIC); player::LevelFinished(); // remove player-controlled actors options::SetLevelFinished(level_pack->name, level_pack->levels[icurrent_level].filename, 2); // difficulty break; case PLAYERDEAD: actor_dead_dtime=0; // display::ShowText("You lost", display::TEXT_STATIC); break; default: break; } } void Game::tick(double dtime) { switch (state) { case RESTARTGAME: // Move the main actors to their respective starting positions player::NewGame(2); // two virtual players if( ! load_level(icurrent_level)) { change_state(ABORT); } else // load_level(icurrent_level); change_state(INGAME); break; case PLAYERDEAD: actor_dead_dtime += dtime; if (actor_dead_dtime <= 0.5) { world::Tick(dtime); display::Tick(dtime); display::Redraw(screen); handle_events(); } else { change_state (RESTARTGAME); } break; case RELOADLEVEL: load_level(icurrent_level); change_state(INGAME); break; case LEVELINFO: // TODO: show level information (name, author, etc.) change_state(INGAME); break; case LEVELFINISHED: level_finished_dtime += dtime; if (level_finished_dtime <= 1.5) { handle_events(); world::Tick(dtime); display::Tick(dtime); display::Redraw(screen); } else { unsigned next_level = 1+icurrent_level; if (next_level == level_pack->levels.size()) change_state(ABORT); else { while (level_pack->levels[next_level].filename == "todo") if (++next_level >= level_pack->levels.size()) break; load_level(next_level); change_state(LEVELINFO); } } break; case INGAME: if (player::AllActorsDead()) { change_state(PLAYERDEAD); } handle_events(); world::Tick(dtime); display::Tick(dtime); display::Redraw(screen); break; default: break; } } bool Game::load_level(int ilevel) { // FX_Fade(video::FADEOUT); vector &levels = level_pack->levels; icurrent_level=ilevel; if( ! world::Load(levels[icurrent_level].filename)) return false; GC gc(video::BackBuffer()); display::DrawAll(gc); ShowScreen(video::TM_PUSH_RANDOM, video::BackBuffer()); // FX_Fade(video::FADEIN); sdl::FlushEvents(); update_mouse_button_state(); last_tick_time = SDL_GetTicks(); return true; } void Game::run (LevelPack *lp, int ilevel) { screen = video::GetScreen(); class Avg { enum {N=20}; double dtimes[N]; int idx; public: Avg() : idx(0) { fill(dtimes, dtimes+N, 0.010); } void add(double dtime) { dtimes[idx++] = dtime; idx = idx % N; } double get() { double s=0; for (int i=0; i 0) ; //SDL_Delay(sleeptime); dtime=(SDL_GetTicks()-last_tick_time)/1000.0; double adtime = average_dtime.get(); average_dtime.add(dtime); if (abs(adtime-dtime)/adtime < 0.1) { // less than 10% deviation from average frame time? dtime = adtime; } if (dtime > 500.0) /* Time has done something strange, perhaps run backwards */ dtime = 0.0; else if (dtime > 0.5) dtime = 0.5; } video::ShowMouse(); } static void mouse_force(int xrel, int yrel) { px::V3 force(xrel, yrel,0); double f = length(force); if (f > 0) { force *= options::MouseSpeed; world::SetMouseForce(force); } } void Game::handle_events() { SDL_Event e; while (SDL_PollEvent(&e)) { switch (e.type) { case SDL_KEYDOWN: on_keydown(e); break; case SDL_MOUSEMOTION: mouse_force(e.motion.xrel, e.motion.yrel); break; case SDL_MOUSEBUTTONDOWN: case SDL_MOUSEBUTTONUP: on_mousebutton(e); break; case SDL_ACTIVEEVENT: if( e.active.gain == 0) show_menu(); break; case SDL_QUIT: change_state(ABORT); break; } } } void Game::on_mousebutton(SDL_Event &e) { if (e.button.state == SDL_PRESSED) { if (e.button.button == 1) { // left mousebutton -> activate first item in inventory player::ActivateItem(); } else if (e.button.button == 3) { // right mousebutton -> rotate inventory display::GetStatusBar()->hide_text(); player::RotateInventory(); } } update_mouse_button_state(); } void Game::update_mouse_button_state() { int b = SDL_GetMouseState(0, 0); player::InhibitPickup((b & SDL_BUTTON(1)) || (b & SDL_BUTTON(3))); } static void set_mousespeed(double spd) { if (spd > 0 && spd < 15) { display::StatusBar *sb = display::GetStatusBar(); char msg[200]; options::MouseSpeed = int(spd); sprintf(msg, "Mouse speed: %d", int(spd)); sb->show_text(msg, display::TEXT_2SECONDS); } } static void set_frictionfactor(double f) { if (f > 0 && f < 10) { display::StatusBar *sb = display::GetStatusBar(); char msg[200]; f = int(2*f)/2.0; options::FrictionFactor = f; sprintf(msg, "Friction: %g", f); sb->show_text(msg, display::TEXT_2SECONDS); } } void Game::on_keydown(SDL_Event &e) { switch (e.key.keysym.sym) { case SDLK_ESCAPE: show_menu(); break; case SDLK_LEFT: set_mousespeed(options::MouseSpeed - 1); break; case SDLK_RIGHT: set_mousespeed(options::MouseSpeed + 1); break; case SDLK_PAGEUP: set_frictionfactor (options::FrictionFactor+0.5); break; case SDLK_PAGEDOWN: set_frictionfactor (options::FrictionFactor-0.5); break; case SDLK_F10: { string fname = level_pack->levels[icurrent_level].filename + ".bmp"; video::Screenshot(fname.c_str()); } break; case SDLK_F3: player::Suicide(); // change_state(RELOADLEVEL); break; case SDLK_x: if (e.key.keysym.mod & KMOD_ALT) { change_state(ABORT); } break; default: break; } if (options::WizardMode > 0.0) { switch (e.key.keysym.sym) { case SDLK_f: options::ShowFPS = !options::ShowFPS; break; case SDLK_l: load_level(icurrent_level); break; case SDLK_t: // Darken the current screen; useful for debugging screen updates TintRect(screen, screen->size(), 0,0,0, 200); screen->update_all(); screen->flush_updates(); break; case SDLK_g: if (e.key.keysym.mod & KMOD_ALT) { display::ReloadModels(); load_level(icurrent_level); } break; case SDLK_1: ToggleFlag(display::SHOW_FLOOR); break; case SDLK_2: ToggleFlag(display::SHOW_ITEMS); break; case SDLK_3: ToggleFlag(display::SHOW_SHADES); break; case SDLK_4: ToggleFlag(display::SHOW_STONES); break; case SDLK_5: ToggleFlag(display::SHOW_SPRITES); break; default: break; } } } void Game::show_menu() { sdl::TempInputGrab grab(SDL_GRAB_OFF); video::ShowMouse(); GameMenu m; m.manage(screen); video::HideMouse(); update_mouse_button_state(); last_tick_time = SDL_GetTicks(); if (state != ABORT) display::RedrawAll(screen); } //---------------------------------------------------------------------- // VARIABLES / FUNCTIONS //---------------------------------------------------------------------- namespace { Game game_inst; } bool enigma::ConserveLevel = false; int enigma::Difficulty = 1; void enigma::StartGame (LevelPack *lp, unsigned levelidx) { if (lp->levels[levelidx].filename != "todo") game_inst.run (lp, levelidx); } void enigma::FinishLevel() { game_inst.finish_level(); } void enigma::RestartLevel() { game_inst.restart_level(); } void enigma::QuitGame() { game_inst.quit(); }