/* * Copyright (C) 2002 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: enigma.cc,v 1.1 2003/01/05 19:56:39 dheck Exp $ */ #include "config.h" #include "options.hh" #include "enigma.hh" #include "display.hh" #include "editor.hh" #include "world.hh" #include "player.hh" #include "lua.hh" #include "sound.hh" #include "system.hh" #include "gui.hh" #include "video.hh" #include "menus.hh" #include "px/px.hh" #include "px/cache.hh" #include "SDL.h" #include "getopt.h" #include #include #include #include #include #include #include using namespace std; using namespace px; using namespace enigma; namespace { void flush_events() { SDL_Event e; while (SDL_PollEvent(&e)) ; } inline px::Screen *get_screen() { return video::GetScreen(); } } Direction enigma::reverse(Direction d) { Direction rdir[] = { EAST, NORTH, WEST, SOUTH }; return d==NODIR ? NODIR : rdir[d]; } string enigma::to_suffix(Direction d) { static char *sfx[] = { "", "-w", "-s", "-e", "-n" }; return sfx[d+1]; } //====================================================================== // MENUS //====================================================================== namespace { using gui::TextButton; class GameMenu : public gui::Menu { public: GameMenu() : resume(new TextButton("Resume Level", this)), restart(new TextButton("Restart Level", this)), options(new TextButton("Options", this)), abort(new 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()); } private: bool on_event (const SDL_Event &e) { if (e.type == SDL_MOUSEBUTTONDOWN && e.button.button == SDL_BUTTON_RIGHT) { Menu::quit(); return true; } return false; } void 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(); } } gui::Widget *resume, *restart, *options, *abort; }; class FontAlloc { public: Font *acquire(const std::string &name) { string fname = enigma::FindDataFile(string("fonts/") + name + ".png"); string dname = enigma::FindDataFile(string("fonts/") + name + ".bmf"); return px::LoadBitmapFont(fname.c_str(), dname.c_str()); } void release(Font *f) { delete f; } }; class ImageAlloc { public: Surface *acquire(const std::string &name) { return px::LoadImage(name.c_str()); } void release(Surface *s) { delete s; } }; typedef cache::Cache ImageCache; } //====================================================================== // MAIN PROGRAM //====================================================================== namespace { vector args; // List of command line arguments. } vector enigma::LevelPacks; //---------------------------------------------------------------------- // Level management //---------------------------------------------------------------------- // 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 //---------------------------------------------------------------------- namespace { class Game { public: Game(); void run(LevelPack *lp, int ilevel); void quit() { change_state(ABORT); } void finish_level() { change_state(LEVELFINISHED); } void restart_level() { change_state(RELOADLEVEL); } private: // Private types. enum State { /* The game is currently running. */ INGAME, /* This level has been completed, proceed to the next one. */ LEVELFINISHED, /* Player's marble is dead. Wait a little, then restart the level. */ PLAYERDEAD, /* Restart the game. */ RESTARTGAME, /* Reload the current level (this automatically resets every object in the landscape). */ RELOADLEVEL, /* The level info screen before entering a new level. */ LEVELINFO, /* Leave the game immediately. */ ABORT }; // Private methods. void handle_events(); void on_keydown(SDL_Event &e); void on_mousebutton(SDL_Event &e); void change_state(State newstate); void tick(double dtime); void show_menu(); bool load_level(int ilevel); // Private variables. State state; enum InputState { I_NORMAL, I_MESSAGE, I_COMMAND } input_state; LevelPack *level_pack; unsigned icurrent_level; Uint32 last_tick_time; double level_finished_dtime; double actor_dead_dtime; }; } Game::Game() : state(INGAME), input_state(I_NORMAL), icurrent_level(0), last_tick_time(0) { } void Game::change_state(State newstate) { if (state == newstate) return; state = newstate; switch (state) { case LEVELFINISHED: level_finished_dtime=0; 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(get_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(get_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(get_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); flush_events(); last_tick_time = SDL_GetTicks(); return true; } void Game::run (LevelPack *lp, int ilevel) { level_pack = lp; icurrent_level = ilevel; video::TempInputGrab grab(SDL_GRAB_ON); video::HideMouse(); // sound::PlayMusic("/sound/Emilie.xm"); sound::FadeoutMusic(); sound::StopMusic(); double dtime=0; state = RESTARTGAME; while (state != ABORT) { last_tick_time=SDL_GetTicks(); tick(dtime); int sleeptime = 10 - (SDL_GetTicks()-last_tick_time); if (sleeptime > 0) SDL_Delay(sleeptime); dtime=(SDL_GetTicks()-last_tick_time)/1000.0; 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(); player::InhibitPickup(true); } else if (e.button.button == 3) { // right mousebutton -> rotate inventory display::GetStatusBar()->hide_text(); player::RotateInventory(); player::InhibitPickup(true); } } else { // mouse button released -> allow picking up items int b = SDL_GetMouseState(0, 0); if (!(b & SDL_BUTTON(1)) && !(b & SDL_BUTTON(3))) { player::InhibitPickup(false); } } } static void set_mousespeed(double spd) { if (spd > 0 && spd < 10) { 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_mousedamping(double d) { if (d > 0 && d < 100) { display::StatusBar *sb = display::GetStatusBar(); char msg[200]; options::MouseDamping = int(d); sprintf(msg, "Mouse damping: %d", int(d)); 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_UP: set_mousedamping(options::MouseDamping + 2); break; case SDLK_DOWN: set_mousedamping(options::MouseDamping - 2); 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(get_screen(), get_screen()->size(), 0,0,0, 200); get_screen()->update_all(); get_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() { Screen *scr = get_screen(); // TintRect(scr, scr->size(), 0,0,0, 150); // scr->update_all(); video::TempInputGrab grab(SDL_GRAB_OFF); video::ShowMouse(); GameMenu m; m.manage(scr); video::HideMouse(); last_tick_time = SDL_GetTicks(); if (state != ABORT) display::RedrawAll(get_screen()); } namespace { Game game_inst; } bool enigma::ConserveLevel = false; 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(); } //---------------------------------------------------------------------- // Data path //---------------------------------------------------------------------- namespace { class DataPathList { public: DataPathList(const string &pathspec=DEFAULT_DATA_PATH) { set_path(pathspec); } void set_path(const string &pathspec) { path=pathspec; datapaths.clear(); split_copy(pathspec, ':', back_inserter(datapaths)); for_each(datapaths.begin(), datapaths.end(), &sysdep::expand_path); } const string &get_path() const { return path; } bool find_file(const string &filename, string &dest) const { for (unsigned i=0; i datapaths; }; DataPathList datapaths; } string enigma::GetDataPath() { return datapaths.get_path(); } void enigma::SetDataPath(const string &p) { datapaths.set_path(p); } string enigma::FindDataFile(const string &filename) { string found_file; if (!datapaths.find_file(filename, found_file)) { fprintf(stderr, "File not found: %s\n", filename.c_str()); return filename; } return found_file; } string enigma::FindDataFile(const string &path, const string &filename) { return FindDataFile(path+"/"+filename); } //---------------------------------------------------------------------- // Resource management //---------------------------------------------------------------------- namespace { cache::Cache font_cache; cache::Cache image_cache; } px::Font * enigma::LoadFont(const char *name) { string png = string("fonts/") + name + ".png"; string bmf = string("fonts/") + name + ".bmf"; return px::LoadBitmapFont(FindDataFile(png).c_str(), FindDataFile(bmf).c_str()); } px::Font * enigma::GetFont(const char *name) { return font_cache.get(name); } px::Surface * enigma::LoadImage(const char *name) { string filename = FindDataFile(string("gfx/") + name + ".png"); return px::LoadImage(filename.c_str()); } px::Surface * enigma::GetImage(const char *name) { string filename = FindDataFile(string("gfx/") + name + ".png"); Surface *s = image_cache.get(filename); assert(s); return s; } //---------------------------------------------------------------------- // Startup //---------------------------------------------------------------------- static void usage() { printf("Available command line options for Enigma:\n\n" " --nosound Disable music and sound\n" " --nomusic Disable music\n" " --window Run in a window; do not enter fullscreen mode\n" " --help -h Show this help\n" " --version Print the executable's version number\n" " --8bpp Use 256 color mode\n" "\n"); } static void init() { lua::Init(); // Run initialization scripts lua::Dofile("init.lua"); lua::Dofile("levels/index.lua"); // Load preferences if (!options::Load()) { fprintf(stderr, "Error in configuration file.\n"); } // Evaluate command line arguments bool nosound_flag = false; bool nomusic_flag = false; bool show_help = false; bool show_version = false; for (unsigned i=0; i < ::args.size(); ++i) { string& arg = ::args[i]; if (arg == "--help" || arg == "-h") show_help = true; else if (arg == "--nosound") nosound_flag = true; else if (arg == "--nomusic") nomusic_flag = true; else if (arg == "--version") show_version = true; else if (arg == "--window") options::FullScreen = false; else if (arg == "--wizard") options::WizardMode = true; else if (arg == "--8bpp") options::BitsPerPixel = 8; else show_help = true; // unknown argument } if (show_help || show_version) { printf("Enigma v%s\n",VERSION); if (show_help) usage(); exit(0); } int sdl_flags = SDL_INIT_VIDEO | SDL_INIT_NOPARACHUTE; if (!nosound_flag) sdl_flags |= SDL_INIT_AUDIO; if (SDL_Init(sdl_flags) < 0) { fprintf(stderr, "Couldn't init SDL: %s\n", SDL_GetError()); exit(1); } if (nosound_flag) sound::DisableSound(); else if (nomusic_flag) sound::DisableMusic(); video::Init(); video::SetPalette("enigma.pal"); display::Init(); sound::Init(); world::Init(); video::SetMouseCursor(enigma::LoadImage("cur-magic"), 4, 4); video::ShowMouse(); } static void shutdown() { video::Shutdown(); display::Shutdown(); world::Shutdown(); options::Save(); delete_sequence(enigma::LevelPacks.begin(), enigma::LevelPacks.end()); } int main(int argc, char** argv) { #ifdef MACOSX // In Mac OS X, applications are self-contained bundles, // e.g. directories like "Enigma.app". Resources are // placed in those bundles under "Enigma.app/Contents/Resources", // the main executable would be "Enigma.app/Contents/MacOS/enigma". // Here, we get the executable name, clip off the last bit, chdir into it, // then chdir to ../Resources. The original SDL implementation chdirs to // "../../..", i.e. the directory the bundle is placed in. This break // the self-containedness. char parentdir[1024]; char *c; strncpy ( parentdir, argv[0], sizeof(parentdir) ); c = (char*) parentdir; while (*c != '\0') /* go to end */ c++; while (*c != '/') /* back up to parent */ c--; *c++ = '\0'; /* cut off last part (binary name) */ chdir (parentdir); /* chdir to the binary app's parent */ chdir ("../Resources/"); /* chdir to the .app's parent */ #endif //MACOSX copy(argv+1, argv+argc, back_inserter(::args)); init(); GUI_MainMenu(LevelPacks[0], 0); shutdown(); return 0; }