Level::FileFormat Level::get_file_format(const Filename& filename) { if (! ::exists(filename.get_rootful().c_str())) return FORMAT_NOTHING; std::ifstream file(filename.get_rootful().c_str(), std::ios::binary); // the length check before the read() was necessary for me on Linux // to get the Debugger past this, it got stuck on read() when nothing // was wrong. file.seekg (0, std::ios::end); if (file.tellg() < 8) { file.close(); return FORMAT_NOTHING; } file.seekg(0, std::ios::beg); unsigned char buf[8]; file.read((char*) &buf, 8); file.close(); // A binary file has two-byte numbers in big endian // for rate, lixes, required, seconds at the beginning. // Neither should be > 0x00FF. If all of them are, // this is an ASCII file which shouldn't have '\0' chars. if (buf[0] == '\0' || buf[2] == '\0' || buf[4] == '\0' || buf[6] == '\0') return FORMAT_BINARY; // This isn't a binary file. Is it a Lemmini file? // Lemmini files start with "# LVL". else if (buf[0] == '#' && buf[1] == ' ' && buf[2] == 'L' && buf[3] == 'V') return FORMAT_LEMMINI; else return FORMAT_LIX; }
void Level::load_from_file(const Filename& filename) { clear(); status = GOOD; level_filename = filename.get_rootless(); FileFormat fmt = get_file_format(filename); if (fmt == FORMAT_BINARY) { // load an original .LVL file from L1/ONML/... load_from_binary(filename); } else if (fmt == FORMAT_LEMMINI) { // load an .INI file from Lemmini load_from_lemmini(filename); } else { // load the regular Lix format std::vector <IO::Line> lines; if (IO::fill_vector_from_file(lines, filename.get_rootful())) { load_from_vector(lines); } else status = BAD_FILE_NOT_FOUND; } load_finalize(); }
void LevelMetaData::read_metadata_binary(const Filename& fn) { std::ifstream file(fn.get_rootful().c_str(), std::ios::binary); // see level_bi.cpp for documentation of the L1 format file.seekg(0x2); initial = read_two_bytes_levelbi(file); required = read_two_bytes_levelbi(file); name_english = read_level_name_bytes(file); file.close(); }
bool dir_exists(const Filename& fn) { std::string dir = fn.get_rootful(); if (dir.size() > 0 && dir[dir.size() - 1] == '/') dir.erase(--dir.end()); // Allegro's file_exits sucks, you have to check twice like the following // if you want to find all directories, no matter whether FA_ARCH or not const bool a = file_exists(dir.c_str(), FA_ALL, 0); const bool b = file_exists(dir.c_str(), FA_ALL & ~ FA_DIREC, 0); return a && ! b; }
void LevelMetaData::read_metadata_lix(const Filename& fn) { std::vector <IO::Line> lines; if (!IO::fill_vector_from_file(lines, fn.get_rootful())) { return; } // File exists for (IO::LineIt i = lines.begin(); i != lines.end(); ++i) { if (i->text1 == gloB->level_built) built = i->text2; if (i->text1 == gloB->level_name_german) name_german = i->text2; if (i->text1 == gloB->level_name_english) name_english = i->text2; if (i->text1 == gloB->level_initial) initial = i->nr1; if (i->text1 == gloB->level_required) required = i->nr1; } }
// Dateisuchfunktionen in verschiedenen Variationen void find_files( const Filename& fn_where, const std::string& what, DoStr dostr, void* from ) { std::string where = fn_where.get_rootful(); al_ffblk info; std::string search_for = where + what; if (al_findfirst(search_for.c_str(), &info, FA_RDONLY | FA_HIDDEN | FA_LABEL | FA_ARCH) == 0) { do { // Gefundene Datei zum Vektor hinzuf�gen Filename fn_result(where + info.name); dostr(fn_result, from); } while (al_findnext(&info) == 0); al_findclose(&info); } }
void LevelMetaData::read_metadata_lemmini(const Filename& fn) { std::ifstream file(fn.get_rootful().c_str()); if (! file.good()) return; // File exists std::string s; while (file >> s) { if (s == "name") { file >> s; // parse the "="; s.clear(); char c; while (file.get(c)) { if (c == ' ' && s.empty()); // discard spaces before name else if (c == '\n' || c == '\r') break; // done reading name else s += c; } name_english = s; } else if (s == "numLemmings") {
void find_dirs(const Filename& fn_where, DoStr dostr, void* from) { al_ffblk info; std::string where = fn_where.get_rootful(); if (where[where.size()-1] != '/') where += '/'; if (where[where.size()-1] != '*') where += '*'; if (al_findfirst(where.c_str(), &info, FA_RDONLY | FA_HIDDEN | FA_LABEL | FA_DIREC | FA_ARCH) == 0) { do { // Gefundenes Verzeichnis hinzuf�gen if ((info.attrib & FA_DIREC) == FA_DIREC && info.name[0] != '.') { std::string s = where; s.resize(s.size() -1 ); // * von der Maske abschnibbeln s += info.name; s += '/'; Filename fn_result(s); dostr(fn_result, from); } } while (al_findnext(&info) == 0); al_findclose(&info); } }
void find_tree (const Filename& fn_where, const std::string& what, DoStr dostr, void* from) { std::string where = fn_where.get_rootful(); // Nach Verzeichnissen suchen al_ffblk info; if (where[where.size()-1] != '/') where += '/'; std::string search_for = where + '*'; if (al_findfirst(search_for.c_str(), &info, FA_RDONLY | FA_HIDDEN | FA_LABEL | FA_DIREC | FA_ARCH) == 0) { do { // Dies nur f�r jedes Verzeichnis au�er . und .. ausf�hren: // Neue Suche mit gleichem Vektor im gefundenen Verzeichnis if ((info.attrib & FA_DIREC) == FA_DIREC && info.name[0] != '.') { Filename fn_recurs(where + info.name + '/'); find_tree(fn_recurs, what, dostr, from); } } while (al_findnext(&info) == 0); al_findclose(&info); } // Nach Dateien suchen, die dem Suchkriterium entsprechen Filename fn_where_with_slash(where); find_files(fn_where_with_slash, what, dostr, from); }
Cutbit::Cutbit(const Filename& filename, const bool cut) : bitmap (0), xl (0), yl (0), x_frames(1), y_frames(1) { // Angegebene Datei als Bild laden. // Wenn dies kein Bild ist, Fehlermeldung schreiben und abbrechen. bitmap = load_bitmap(filename.get_rootful().c_str(), 0); if (!bitmap) { std::string str = Language::log_bitmap_bad; str += " "; str += filename.get_rootless(); Log::log(Log::ERROR, str); return; } if (cut) cut_bitmap(); else { xl = bitmap->w; yl = bitmap->h; } }
void Replay::load_from_file(const Filename& fn) { clear(); level_filename = fn; // Standardwert: Annahme, Level in Replaydatei unsigned long vm = 0; // version_min erst spaeter setzen wegen add() std::vector <IO::Line> lines; if (!IO::fill_vector_from_file(lines, fn.get_rootful())) { file_not_found = true; holds_level = false; return; } for (IO::LineIt i = lines.begin(); i != lines.end(); ++i) switch(i->type) { case '$': if (i->text1 == gloB->replay_built_required) built_required = i->text2; else if (i->text1 == gloB->replay_permu ) permu = Permu (i->text2); else if (i->text1 == gloB->replay_level_filename) { // We changed the names of the directories on 2012-04-12. Probably // a year from this time on, there shouldn't be any important // replays with the old path anymore. Then, remove this erase() // to finally allow a directory (levels-dir)/levels/ in theory. std::string filestr = i->text2; if (filestr.substr(0, 7) == "levels/") filestr.erase(0, 7); level_filename = Filename( gloB->dir_levels.get_dir_rootless() + filestr); } break; case '#': if (i->text1 == gloB->replay_version_min ) vm = i->nr1; break; case '+': if (i->text1 == gloB->replay_player || i->text1 == gloB->replay_friend) { add_player(i->nr1, LixEn::string_to_style(i->text2), i->text3); if (i->text1 == gloB->replay_player) player_local = i->nr1; } break; case '!': { Data d; d.update = i->nr1; // d.player ist zwar ein char, aber wir lesen ja d.player = i->nr2; // nicht aktiv longs ein, sondern weisen nur zu. d.what = i->nr3; if (i->text1 == gloB->replay_spawnint ) d.action = SPAWNINT; else if (i->text1 == gloB->replay_skill ) d.action = SKILL; else if (i->text1 == gloB->replay_assign ) d.action = ASSIGN; else if (i->text1 == gloB->replay_assign_legacy) d.action = ASSIGN; else if (i->text1 == gloB->replay_aim ) d.action = AIM; else if (i->text1 == gloB->replay_nuke ) d.action = NUKE; add(d); break; } default: break; } // Variablen nach dem Laden zuweisen, damit add() nichts kaputtmacht version_min = vm; // check whether the pointed-to level exists, otherwise use itself // as a fallback level Level pointedto(level_filename); if (pointedto.get_status() == Level::BAD_FILE_NOT_FOUND || pointedto.get_status() == Level::BAD_EMPTY) { level_filename = fn; } // load the replay file itself as a level, to see whether there's a level // in the file itself. This is important e.g. for the extract button. Level itself(fn); if (itself.get_status() == Level::BAD_FILE_NOT_FOUND || itself.get_status() == Level::BAD_EMPTY) { holds_level = false; } else { holds_level = true; if (level_filename == fn) { built_required = Level::get_built(level_filename); } } }
void Replay::save_to_file(const Filename& s, const Level* const lev) { bool save_level_into_file = holds_level || level_filename == gloB->empty_filename || lev != 0; // We currently override the above check, and will always save a level // into the replay, thus have the replay never point back into the level // tree. save_level_into_file = true; std::ofstream file(s.get_rootful().c_str()); // Also override NOT saving the filename. Always save the filename right // now, and use the level in the replay as a fallback if there is nothing // at the pointed-to level position. if (true || !save_level_into_file) { built_required = Level::get_built(level_filename); // Write the path to the level, but omit the leading (dir-levels)/ // This if is just against a crash in networking games. if (level_filename.get_rootless().size() > gloB->dir_levels.get_dir_rootless().size()) { file << IO::LineDollar(gloB->replay_level_filename, level_filename.get_rootless().substr( gloB->dir_levels.get_dir_rootless().size())) << IO::LineDollar(gloB->replay_built_required, built_required); } } file << IO::LineHash(gloB->replay_version_min, version_min) << std::endl; // Alle Spieler notieren. for (std::set <Player> ::const_iterator itr = players.begin(); itr != players.end(); ++itr) file << IO::LinePlus(itr->number == player_local ? gloB->replay_player : gloB->replay_friend, itr->number, LixEn::style_to_string(itr->style), itr->name); if (players.size() > 1) { std::ostringstream pstr; pstr << permu; file << IO::LineDollar(gloB->replay_permu, pstr.str()) << std::endl; } file << std::endl; // Die einzelnen Aktionen schreiben for (It itr = data.begin(); itr != data.end(); ++itr) { file << IO::LineBang(itr->update, itr->player, itr->action == Replay::SPAWNINT ? gloB->replay_spawnint : itr->action == Replay::SKILL ? gloB->replay_skill : itr->action == Replay::ASSIGN ? gloB->replay_assign : itr->action == Replay::AIM ? gloB->replay_aim : itr->action == Replay::NUKE ? gloB->replay_nuke : Language::cancel, itr->what); } if (save_level_into_file) { file << std::endl; file << (lev ? *lev : Level(level_filename)); } file.close(); }
void Level::save_to_file(const Filename& filename) const { std::ofstream file(filename.get_rootful().c_str()); file << *this; file.close(); }
void Level::load_from_binary(const Filename& filename) { std::ifstream file(filename.get_rootful().c_str(), std::ios::binary); if (!file.good()) { status = BAD_FILE_NOT_FOUND; return; } // ==GLOBALS=============================================================== // BYTES 0x0000 to 0x0001 // Release Rate : 0x0000 is slowest, 0x00FA is fastest // 0x00FA ist 250 im Dezimalsystem. 99 ist die hoechste Rate. If the value // is abstruse, it'll be corrected to > 1 and < some upper bound // later in load_from(). spawnint_slow = 4 + Help::even(99 - read_two_bytes_levelbi(file)) / 2; // BYTES 0x0002 to 0x0003 // Num of lemmings : maximum 0x0072. 0x0010 would be 16 lemmings. initial = read_two_bytes_levelbi(file); // BYTES 0x0004 to 0x0005 // Num to rescue : should be less than or equal to number of lemmings required = read_two_bytes_levelbi(file); // BYTES 0x0006 to 0x0007 // Time Limit : max 0x00FF, 0x0001 to 0x0009 works best seconds = read_two_bytes_levelbi(file) * 60; // BYTES 0x0008 to 0x0017 (2 bytes each, only lower byte is used) // Num of skills : max 0x00FA. order is Climber, Floater, Bomber, // Blocker,Builder, Basher, Miner, Digger for (int i = 0; i < 8; ++i) { LixEn::Ac ac = LixEn::NOTHING; switch (i) { case 0: ac = LixEn::CLIMBER; break; // 0x0008 und 09 case 1: ac = LixEn::FLOATER; break; // 0x000A ... case 2: ac = LixEn::EXPLODER; break; // 0x000C case 3: ac = LixEn::BLOCKER; break; // 0x000E case 4: ac = LixEn::BUILDER; break; // 0x0010 case 5: ac = LixEn::BASHER; break; // 0x0012 case 6: ac = LixEn::MINER; break; // 0x0014 ... case 7: ac = LixEn::DIGGER; break; // 0x0016 und 17 } skills[ac] = read_two_bytes_use_only_second_byte_levelbi(file); // skill slots with 0 skills will be culled in the finalize function legacy_ac_vec.push_back(ac); } // ORIGHACK: If a two-player level is loaded, make the given calculation // for the time, as the result (5 -> 2 minutes) is a nice overtime for // a multiplayer game. The overtime starts counting after the first player // is out of lixes, but has some saved. // Also: Use knockback exploders instead of L1-style exploders. if (filename.get_rootful().find("network/") != std::string::npos) { if (skills.find(LixEn::EXPLODER) != skills.end()) { skills [LixEn::EXPLODER2] = skills[LixEn::EXPLODER]; skills.erase(LixEn::EXPLODER); } seconds = (seconds / 2) - 30; if (seconds <= 0) seconds = 15; } // BYTES 0x0018 to 0x0019 // Start screen xpos : 0x0000 to 0x04F0. is rounded to nearest multiple // of 8. // We will multiply everything by 2 later, as L++ uses double the resol. start_x = read_two_bytes_levelbi(file); size_x = 1600; size_y = 160; // BYTES 0x001A to 0x001B // Normal Graphic Set: 0x0000 is dirt, 0x0001 is fire, 0x0002 is squasher, // 0x0003 is pillar,0x0004 is crystal, 0x0005 is brick, // 0x0006 is rock, 0x0007 is snow and 0x0008 is bubble. // Bei mir zusaetzlich: 0x0009 is holiday int graphics_set = read_two_bytes_use_only_second_byte_levelbi(file); // ORIGHACK: Bei Levels aus den Verzeichnissen zu ONML oder Holiday // entsprechend um 5 erhoehen bzw. auf 9 setzen. const std::string& filestr = filename.get_rootful(); if (filestr.find("ONML/") != std::string::npos || filestr.find("onml/") != std::string::npos || filestr.find("ore Lemmings/") != std::string::npos) graphics_set += 5; if (filestr.find("oliday") != std::string::npos) graphics_set = 9; // BYTES 0x001C to 0x001D // Extended Graphic Set: corresponds to VGASPEC?.DAT int spec_graphics = read_two_bytes_use_only_second_byte_levelbi(file); if (spec_graphics != 0) { Pos ter; ter.x = 304; ter.y = 0; ter.ob = ObjLib::get_orig_vgaspec(spec_graphics); if (ter.ob) pos[Object::TERRAIN].push_back(ter); else status = BAD_IMAGE; } // BYTES 0x001E to 0x001F // [04:14:50] <Mindless> "BYTES 0x001E to 0x001F" in level_bi.cpp // [04:15:37] <Mindless> that's 0x0000 in normal levels, 0xffff in // "Inroducing SuperLemming!" in which the gameplay is double speed // No support for this in L++ (yet). read_two_bytes_levelbi(file); // wegwerfen // ==OBJECTS=============================================================== // BYTES 0x0020 to 0x011F (8 byte blocks) // each 8 byte block starting at byte 0x0020 represents an interactive // object. there can be a maximum of 32 objects. write 0x00 to fill bytes // up to 0x0120 if there are less than 32 objects. for (int loop = 0; loop < 32; ++loop) { bool add = false; // Design der Funktion hier: vorzeitiges continue Pos spe; // fuehrte zu: zu wenige Werte werden ausgelesen // x pos : min 0xFFF8, max 0x0638. 0xFFF8 = -24, 0x0001 = -16, // 0x0008= -8, 0x0010 = 0, 0x0018 = 8, ... , 0x0638 = 1576 // note: should be multiples of 8 spe.x = read_two_bytes_levelbi(file); if (spe.x != 0) add = true; // 0x0001 ist -16; also 0x0000 wohl nix if (spe.x == 1) spe.x -= 1; if (spe.x >= 0xFFF8) spe.x -= 0x10000; spe.x -= 0x10; // y pos : min 0xFFD7, max 0x009F. 0xFFD7 = -41, 0xFFF8 = -8, // 0xFFFF = -1, 0x0000 = 0, ... , 0x009F = 159. // note: can be any value in the specified range spe.y = read_two_bytes_levelbi(file); if (spe.y >= 0xFFD7) spe.y -= 0x10000; // L1 has a bug where special objects must be at a certain multiple // in x-direction, I think it's 8. I think there is no such condition // in y-direction. So, let's disable this: the following line. // spe.y -= spe.y % 4; // obj id : min 0x0000, max 0x000F. the object id is different in each // graphics set, however 0x0000 is always an exit and 0x0001 is // always a start. see appendix a for full object listings int spe_id = read_two_bytes_levelbi(file); // modifier : first byte can be 80 (do not overwrite existing terrain) // or 40 (must have terrain underneath to be visible). 00 specifies // always draw full graphic. second byte can be 8F (display graphic // upside-down) or 0F (display graphic normally) // Wird von L++ nicht unterstuetzt read_two_bytes_levelbi(file); // wegwerfen if (add) { spe.ob = ObjLib::get_orig_special(graphics_set, spe_id); if (spe.ob) { // Do not load/show the waving green and blue flags. // L++ features a better way of showing the goal ownership. if (spe.ob->type != Object::DECO || spe.ob->subtype != 1) pos[spe.ob->type].push_back(spe); } else status = BAD_IMAGE; } } // ==TERRAIN=============================================================== // BYTES 0x0120 to 0x075F (4 byte blocks) // each 4 byte block starting at byte 0x0120 represents a terrain object. // there can be a maximum of 400 terrain objects. Write 0xFF fill the bytes // up to byte 0x0760 if need be. for (int loop = 0; loop < 400; ++loop) { Pos ter; bool add = true; // x ter : min 0x0000, max 0x063F. 0x0000 = -16, 0x0008 = -8, // 0x0010 = 0, 0x063f = 1583. // note: the xter also contains modifiers. the first nibble can be... ter.x = read_two_bytes_levelbi(file); int first_nibble = ter.x / 0x1000; ter.x %= 0x1000; // Erstes Nibble entfernen ter.x -= 16; if (ter.x > 0x700) add = false; //ter.x -= 0x1000; // Sir Edmund, ... // y ter : 9-bit value. min 0xEF0, max 0x518. 0xEF0 = -38, // 0xEF8 = -37, 0x020 = 0, 0x028 = 1, 0x030 = 2, 0x038 = 3, ... , // 0x518 = 159 // note: the yter value bleeds into the next value since it is 9bits. // terrain id: min 0x00, max 0x3F. // not all graphic sets have all 64 graphics. ter.y = read_two_bytes_levelbi(file); int terrain_id = ter.y % 0x0080; // Terrain-ID sind 7 Bits davon //if (ter.y >= 0xEF00) ter.y -= 0x10000; if (ter.y >= 0xE000) ter.y -= 0x10080; ter.y -= 0x0200; ter.y /= 0x0080; // Hintere 7 Bits abschnibbeln if (terrain_id == 0x7F) add = false; // Write 0xFF fill the bytes up to byte 0x0760 if need be. // Terrain-ID hat 7 Bits mit Maximum 0x3F, also ist 0x7F der Wert, // den die Terrain-ID annimmt, wenn das Objekt nicht existiert. if (add) { ter.ob = ObjLib::get_orig_terrain(graphics_set, terrain_id); // note: the xter also contains modifiers. the first nibble can be // 8 (don't overwrite existing terr., 4 (display upside-down), or // 2 (remove terrain instead add). you can add them together. // 0 indicates normal. // eg: 0xC011 means draw at xter=1, do not overwirte, upside-down. ter.noow = first_nibble & 8; ter.mirr = first_nibble & 4; ter.dark = first_nibble & 2; if (ter.ob) pos[Object::TERRAIN].push_back(ter); else status = BAD_IMAGE; } } // ==STEEL AREAS=========================================================== // BYTES 0x0760 to 0x07DF (4 byte blocks) // // x pos : 9-bit value. min 0x000, max 0xC78. 0x000 = -16, 0x008 = -12, // 0x010 = -8, 0x018 = -4, ... , 0xC78 = 1580. // note: each hex value represents 4 pixels. since it is 9 bit value it // bleeds into the next attribute. // // y pos : min 0x00, max 0x27. 0x00 = 0, 0x01 = 4, 0x02 = 8, ... , // 0x27 = 156 // note: each hex value represents 4 pixels // // area : min 0x00, max 0xFF. the first nibble is the x-size, from 0 - F. // each value represents 4 pixels. the second nibble is the y-size. // 0x00 = (4,4), 0x11 = (8,8), 0x7F = (32,64), 0x23 = (12,16) // // eg: 00 9F 52 00 = put steel at (-12,124) width = 24, height = 12 // // each 4 byte block starting at byte 0x0760 represents a steel area which // the lemmings cannot bash through. The first three bytes are given above, // and the last byte is always 00.. what a waste of space considering how // compact they made the first 3 bytes! write 0x00 to fill each byte up to // 0x07E0 if need be. // for (int loop = 0x0760; loop < 0x07E0; ++loop) read_one_byte(file); name_english = read_level_name_bytes(file); file.close(); // Finalisieren: Level auf tatsaechliche Groesse bringen, denn L++ hat // alles doppelt so gross wie normal size_x *= 2; size_y *= 2; start_x *= 2; for (int type = Object::TERRAIN; type != Object::MAX; ++type) for (std::list <Pos> ::iterator itr = pos[type].begin(); itr != pos[type].end(); ++itr) { itr->x *= 2; itr->y *= 2; } load_finalize_binary_or_lemmini(filename); }
void Level::load_finalize_binary_or_lemmini(const Filename& filename) { built = Date("0"); start_manual = true; // Ueble Machenschaften, die den Level gar nicht so sehr wie das Original // darstellen, sondern dafuer viel schoener! Links und rechts den Level // abschneiden, wenn der Platz nicht gebraucht wird. int min_x = size_x; int max_x = 0; // Minimum und Maximum finden for (int type = Object::TERRAIN; type != Object::MAX; ++type) for (std::list <Pos> ::iterator itr = pos[type].begin(); itr != pos[type].end(); ++itr) { if (itr->dark) continue; const int ix = itr->x + itr->ob->selbox_x; const int ix2 = itr->x + itr->ob->selbox_x + itr->ob->selbox_xl; if (ix < min_x) min_x = ix; if (ix2 > max_x) max_x = ix2; } if (min_x < 0) min_x = 0; if (max_x > size_x) max_x = size_x; // Nun alles entsprechend verschieben size_x = max_x - min_x; start_x -= min_x; for (int type = Object::TERRAIN; type != Object::MAX; ++type) for (std::list <Pos> ::iterator itr = pos[type].begin(); itr != pos[type].end(); ++itr) { itr->x -= min_x; } const std::string& filestr = filename.get_rootful(); // ORIGHACK: In multiplayer levels, the hatch direction should point // towards the center because torus_x can't be set. if (filestr.find("network/") != std::string::npos && pos[Object::HATCH].size() > 1) { for (std::list <Pos> ::iterator hatch = pos[Object::HATCH].begin(); hatch != pos[Object::HATCH].end(); ++hatch) if (hatch->x + hatch->ob->get_trigger_x() >= size_x / 2) hatch->rot = 1; } // ORIGHACK: In 2-player levels, the goal order should be set in such // a way that the distance for both players to get to their goal is // the same. This is only done for 1. Lemmings, as in 2. ONML, the // goal order is already as intended. if (filestr.find("network/2player/1. Lemmings") != std::string::npos && pos[Object::HATCH].size() == 2 && pos[Object::GOAL].size() == 2) { const Pos& h1 = * pos[Object::HATCH].begin(); const Pos& h2 = *++pos[Object::HATCH].begin(); const Pos& g1 = * pos[Object::GOAL ].begin(); const Pos& g2 = *++pos[Object::GOAL ].begin(); double dist_cur = Help::hypot(h1.x, h1.y, g1.x, g1.y) + Help::hypot(h2.x, h2.y, g2.x, g2.y); double dist_swap = Help::hypot(h1.x, h1.y, g2.x, g2.y) + Help::hypot(h2.x, h2.y, g1.x, g1.y); if (dist_swap > dist_cur) pos[Object::GOAL].reverse(); } // ORIGHACK: In 2-player levels, if there's one hatch only, player 0 goes // to the right and player 1 to the left. Thus, put goal 0 to the right. if ((filestr.find("network/2player/1. Lemmings") != std::string::npos || filestr.find("network/2player/2. ONML") != std::string::npos) && pos[Object::HATCH].size() == 1 && pos[Object::GOAL].size() == 2) { const Pos& g1 = * pos[Object::GOAL ].begin(); const Pos& g2 = *++pos[Object::GOAL ].begin(); if (g2.x > g1.x) pos[Object::GOAL].reverse(); } }
void Level::load_from_lemmini(const Filename& filename) { typedef std::map <std::string, LemminiLine> Lines; Lines lines; std::ifstream file(filename.get_rootful().c_str()); if (! file.good()) { status = BAD_FILE_NOT_FOUND; return; } // safely parse into lines, see other/file/io.cpp for comments. // ignore lines starting with #. std::string s; char c; while (file.get(c)) { if (c != '\n' && c != '\r') s += c; else if (! s.empty()) { if (s[0] != '#') { LemminiLine ll(s); lines[ll.name] = ll; } s.clear(); } } // No newline at end of file shall not matter if (! s.empty() && s[0] != '#') { LemminiLine ll(s); lines[ll.name] = ll; } file.close(); // parse the contents of the LemminiLines name_english = lines["name"].str; size_x = 2 * 1600; size_y = 2 * 160; start_x = lines["xPos"].nr; seconds = std::max(lines["timeLimit"].nr * 60, lines["timeLimitSeconds"].nr); initial = lines["numLemmings"].nr; required = lines["numToRescue"].nr; spawnint_slow = 4 + Help::even(99 - lines["releaseRate"].nr) / 2; spawnint_fast = 4; skills[LixEn::CLIMBER] = lines["numClimbers"].nr; skills[LixEn::FLOATER] = lines["numFloaters"].nr; skills[LixEn::EXPLODER] = lines["numBombers"].nr; skills[LixEn::BLOCKER] = lines["numBlockers"].nr; skills[LixEn::BUILDER] = lines["numBuilders"].nr; skills[LixEn::BASHER] = lines["numBashers"].nr; skills[LixEn::MINER] = lines["numMiners"].nr; skills[LixEn::DIGGER] = lines["numDiggers"].nr; // skill slots with 0 skills will be culled in the finalize function legacy_ac_vec.push_back(LixEn::CLIMBER); legacy_ac_vec.push_back(LixEn::FLOATER); legacy_ac_vec.push_back(LixEn::EXPLODER); legacy_ac_vec.push_back(LixEn::BLOCKER); legacy_ac_vec.push_back(LixEn::BUILDER); legacy_ac_vec.push_back(LixEn::BASHER); legacy_ac_vec.push_back(LixEn::MINER); legacy_ac_vec.push_back(LixEn::DIGGER); int graphics_set = 0; std::string sty = lines["style"].str; Help::string_to_nice_case(sty); if (sty == "Dirt") graphics_set = 0; else if (sty == "Fire") graphics_set = 1; else if (sty == "Marble") graphics_set = 2; else if (sty == "Pillar") graphics_set = 3; else if (sty == "Crystal") graphics_set = 4; else if (sty == "Brick") graphics_set = 5; else if (sty == "Rock") graphics_set = 6; else if (sty == "Snow") graphics_set = 7; else if (sty == "Bubble") graphics_set = 8; else if (sty == "Special") graphics_set = 9; // Parse the terrain int piece_nr = 0; while (true) { std::ostringstream piece_str; piece_str << "terrain_" << piece_nr; Lines::const_iterator itr = lines.find(piece_str.str()); if (itr == lines.end()) break; else { Pos ter; ter.ob = ObjLib::get_orig_terrain(graphics_set, itr->second.nr); ter.x = itr->second.x; ter.y = itr->second.y; ter.noow = itr->second.flags & 8; ter.mirr = itr->second.flags & 4; ter.dark = itr->second.flags & 2; if (ter.ob) pos[Object::TERRAIN].push_back(ter); else status = BAD_IMAGE; } ++piece_nr; } // parse the special objects piece_nr = 0; while (true) { std::ostringstream piece_str; piece_str << "object_" << piece_nr; Lines::const_iterator itr = lines.find(piece_str.str()); if (itr == lines.end()) break; else { Pos ter; ter.ob = ObjLib::get_orig_special(graphics_set, itr->second.nr); ter.x = itr->second.x; ter.y = itr->second.y; if (ter.ob) { // Do not load/show the waving green and blue flags. if (ter.ob->type != Object::DECO || ter.ob->subtype != 1) pos[ter.ob->type].push_back(ter); } else status = BAD_IMAGE; } ++piece_nr; } load_finalize_binary_or_lemmini(filename); }