/* * Copyright (C) 2002,2003,2004 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: oxyd.cc,v 1.86 2004/05/22 13:04:30 dheck Exp $ */ /* * This files contains the interface between Enigma and the original * Oxyd games. It is responsible for converting level descriptions or * sound effects. */ #include "oxyd.hh" #include "sound.hh" #include "lua.hh" #include "server.hh" #include "player.hh" #include "main.hh" #include #include #include #include "oxyd_internal.hh" #define SOUND sound::GetEngine() using namespace std; using namespace enigma; using world::Stone; using world::MakeStone; using world::Item; using world::MakeItem; using namespace oxyd; using OxydLib::Level; /* -------------------- Helper functions -------------------- */ namespace { enigma::Direction direction_oxyd2enigma (OxydLib::Direction odir) { switch (odir) { case Direction_Up: return NORTH; break; case Direction_Down: return SOUTH; break; case Direction_Left: return WEST; break; case Direction_Right: return EAST; break; default : fprintf(stderr, "Unknown OxydLib-direction %i!\n", int(odir)); return NODIR; } } GameType to_gametype (OxydLib::OxydVersion ver) { GameType typ = GAMET_UNKNOWN; switch (ver) { case OxydVersion_Oxyd1: typ = GAMET_OXYD1; break; case OxydVersion_OxydMagnum: case OxydVersion_OxydMagnumGold: typ = GAMET_OXYDMAGNUM; break; case OxydVersion_OxydExtra: typ = GAMET_OXYDEXTRA; break; case OxydVersion_PerOxyd: typ = GAMET_PEROXYD; break; default : assert(0); break; } return typ; } void dumpUnknownObjects (const OxydLib::Level& level) { set stones, items, floors; const Grid &sgrid = level.getGrid (GridType_Pieces); for (unsigned y=0; yint_attrib("code")) stones.insert(code); const Grid &igrid = level.getGrid (GridType_Objects); for (unsigned y=0; yint_attrib("code")) items.insert(code); const Grid &fgrid = level.getGrid (GridType_Objects); for (unsigned y=0; yint_attrib("code")) floors.insert(code); if (!stones.empty()) { enigma::Log << "Unknown stones:"; for (set::iterator i = stones.begin(); i != stones.end(); ++i) enigma::Log << ' ' << *i; enigma::Log << endl; } if (!items.empty()) { enigma::Log << "Unknown items:"; for (set::iterator i = items.begin(); i != items.end(); ++i) enigma::Log << ' ' << *i; enigma::Log << endl; } if (!floors.empty()) { enigma::Log << "Unknown floors:"; for (set::iterator i = floors.begin(); i != floors.end(); ++i) enigma::Log << ' ' << *i; enigma::Log << endl; } } GridLoc to_gridloc (const SignalLocation &a) { assert (a.getGridType() >= GridType_First && a.getGridType() <= GridType_Last); static GridLayer tab[3] = { GRID_FLOOR, GRID_STONES, GRID_ITEMS }; return GridLoc (tab[a.getGridType()], GridPos(a.getX(), a.getY())); } string patchfile_name (enigma::GameType t, int index, bool twoplayers) { string patchfile = "levels/patches/"; switch (t) { case GAMET_OXYD1: patchfile += "ox1_"; break; case GAMET_PEROXYD: patchfile += "pox_"; break; case GAMET_OXYDMAGNUM: patchfile += "oxm_"; break; case GAMET_OXYDEXTRA: patchfile += "oxe_"; break; default: assert (0); } if (twoplayers) patchfile += px::strf ("%03d.lua", index+101); else patchfile += px::strf ("%03d.lua", index+1); return patchfile; } bool parse_gridpos (CommandString &cs, int levelw, int levelh, GridPos &p) { int pos = cs.get_int (0, levelw * levelh - 1, -1); if (pos != -1) { p.x = pos % levelw; p.y = pos / levelw; return true; } return false; } } /* -------------------- CommandString implementation -------------------- */ int CommandString::get_int (int min, int max, int dflt) { int val = 0; bool error = false; int c = get_char(); if (c != '(') error = true; else { bool positive = true; c = get_char(); if (c == '+') { c = get_char(); } else if (c == '-') { positive = false; c = get_char(); } if (isdigit(c)) { val = 0; do { val = 10*val + (c - '0'); c = get_char(); } while (isdigit(c)); if (!positive) val = -val; } else error = true; } return (error || val > max || val < min) ? dflt : val; } /* -------------------- OxydLoader -------------------- */ OxydLoader::OxydLoader (const Level &level_, const LoaderConfig config_) : level (level_), config (config_) { harmless_medi = false; } void OxydLoader::load () { // Prepare Enigma game engine world::PrepareLevel (level.getWidth(), level.getHeight()); if (config.twoplayers) server::TwoPlayerGame = true; display::ResizeGameArea (20, 11); if (level.getScrolling()) SetFollowMode (display::FOLLOW_SCROLLING); else SetFollowMode (display::FOLLOW_SCREEN); // Populate Enigma game load_floor (); load_items (); load_stones (); scramble_puzzles (); load_actors (); connect_signals (); connect_rubberbands (); parse_specials (); // Debugging output dumpUnknownObjects(level); } Stone * OxydLoader::make_laser (int type) { px::Assert (type >= 0 && type <= 2, "Oxyd supports only three different lasers per level"); const Laser& laser = level.getLaser(type); enigma::Direction dir = direction_oxyd2enigma(laser.getDir()); bool on = laser.getOn(); Stone *st = 0; if (dir != NODIR) { string lasername("st-laser"); lasername += to_suffix(dir); st = MakeStone(lasername.c_str()); st->set_attrib("on", Value(on)); // OnOffStone attribute } return st; } Stone *OxydLoader::make_timer (int x, int y) { const OscillatorMap &oscillators = level.getOscillators(config.gamemode); Stone *st = world::MakeStone ("st-timer"); st->set_attrib("interval", Value(0.2)); Block block(x, y); OscillatorMap::const_iterator i = oscillators.find(block); if (i != oscillators.end()) { const Oscillator &o = i->second; st->set_attrib("on", Value(o.getOn())); double interval = (1 + o.getPeriod()) * config.timer_factor; st->set_attrib("interval", Value(interval)); } return st; } Stone *OxydLoader::make_stone (int type, int x, int y) { world::Stone *st = 0; if (type == 0) { // ignore } else if (type >= 0x01 && type <= 0x10) { // Oxyd stones of different colors char color[2] = "0"; color[0] += (type-1) / 2; st = world::MakeStone("st-oxyd"); st->set_attrib("color", color); st->set_attrib("flavor", config.oxyd_flavor); } else if (type == config.id_timer) { st = make_timer (x, y); } else if (type >= config.id_laser1 && type < config.id_laser1 + 3) { st = make_laser (type - config.id_laser1); } else { // No special case -> get Stone from map const char *name = config.stonetable[type]; if (name == 0) { Log << px::strf("Unknown stone %X\n", type); st = world::MakeStone ("st-dummy"); st->set_attrib("code", type); } else if (name[0] != '\0') { // ignore if name=="" st = world::MakeStone (name); } } return st; } static std::string convert_encoding (const std::string &t) { std::string tt = t; for (size_t i = 0; i 128) Log << "Unknown character in Oxyd text: " << int(c) << ' '; break; } } Log << ">> '" << t << "'\n"; Log << ">> '" << tt << "'\n"; return tt; } Item *OxydLoader::make_item (int type) { using namespace world; Item *it = 0; OxydLib::Language lang = Language_English; std::string localelang = px::GetLanguageCode (px::DefaultMessageLocale()); if (localelang == "de") lang = Language_German; else if (localelang == "fr") lang = Language_French; switch (type) { case 0x00: break; // ignore case 0x02: // note 1 { it = MakeItem (world::it_document); string text = convert_encoding(level.getNoteText(0, lang)); it->set_attrib ("text", text.c_str()); } break; case 0x03: // note 2 { it = MakeItem (world::it_document); string text = convert_encoding(level.getNoteText(1, lang)); it->set_attrib ("text", text.c_str()); } break; default: { ItemID id = config.itemtable[type]; if (id == it_INVALID) { Log << px::strf ("Unknown item %X\n",type); it = MakeItem (world::it_dummy); it->set_attrib("code", type); } else it = MakeItem (id); } } return it; } void OxydLoader::load_floor () { using namespace world; const Grid &grid = level.getGrid (GridType_Surfaces); for (unsigned y=0; yset_attrib("code", code); } else { fl = MakeFloor(name); } SetFloor (GridPos(x, y), fl); } } } void OxydLoader::load_items () { const Grid &grid = level.getGrid (GridType_Objects); for (unsigned y=0; yset_attrib ("player", Value(0.0)); break; case MarbleType_White: ac = MakeActor (world::ac_whiteball); ac->set_attrib ("player", Value(1.0)); break; case MarbleType_Meditation: if (have_black_marble && !level.getHarmlessMeditationMarbles()) { // # example: Oxyd Extra #28 ac = MakeActor (world::ac_killerball); // ac->set_attrib ("player", Value(0.0)); ac->set_attrib ("mouseforce", Value (1.0)); ac->set_attrib ("controllers", Value (3.0)); } else { ac = MakeActor (world::ac_meditation); nmeditationmarbles += 1; if (config.twoplayers && (nmeditationmarbles % 2) == 0) ac->set_attrib("player", Value(1.0)); else ac->set_attrib ("player", Value(0.0)); } if (minfo.is_default(MI_FORCE)) { ac->set_attrib("mouseforce", Value(1.0)); } else { ac->set_attrib("mouseforce", Value(minfo.get_value(MI_FORCE) / 32.0)); // just a guess } break; case MarbleType_Jack: ac = MakeActor (world::ac_top); if (!minfo.is_default(MI_FORCE)) { double force = minfo.get_value(MI_FORCE) / 4; // just a guess ac->set_attrib("force", Value(force) ); enigma::Log << "Set jack force to " << force << endl; } if (!minfo.is_default(MI_RANGE)) { double range = minfo.get_value(MI_RANGE) / 32.0; // value seems to contain distance in pixels ac->set_attrib("range", Value(range) ); enigma::Log << "Set jack range to " << range << endl; } break; case MarbleType_Rotor: { ac = MakeActor (world::ac_rotor); double force = minfo.get_value (MI_FORCE, 30) * 0.3; double range = minfo.get_value (MI_RANGE, 100) / 32.0; int gohome = minfo.get_value (MI_GOHOME, 1); ac->set_attrib ("force", force); ac->set_attrib ("range", range); ac->set_attrib ("gohome", Value (gohome)); enigma::Log << "rotor force " << force << endl; enigma::Log << "rotor range " << range << endl; break; } case MarbleType_Horse: { ac = MakeActor (world::ac_horse); int levelw = level.getWidth(); if (!minfo.is_default(MI_HORSETARGET1)) { int targetpos = minfo.get_value(MI_HORSETARGET1); ac->set_attrib("target1", px::strf("%d %d", targetpos % levelw, int(targetpos / levelw))); } if (!minfo.is_default(MI_HORSETARGET2)) { int targetpos = minfo.get_value(MI_HORSETARGET2); ac->set_attrib("target2", px::strf("%d %d", targetpos % levelw, int(targetpos / levelw))); } if (!minfo.is_default(MI_HORSETARGET3)) { int targetpos = minfo.get_value(MI_HORSETARGET3); ac->set_attrib("target3", px::strf("%d %d", targetpos % levelw, int(targetpos / levelw))); } if (!minfo.is_default(MI_HORSETARGET4)) { int targetpos = minfo.get_value(MI_HORSETARGET4); ac->set_attrib("target4", px::strf("%d %d", targetpos % levelw, int(targetpos / levelw))); } break; } case MarbleType_Bug: ac = MakeActor (world::ac_bug); break; default: enigma::Log << "Unhandled actor type " << int(marble.getMarbleType()) << endl; break; // case MarbleType_LifeSpitter: // case MarbleType_DynamiteHolder: // break; } if (ac) world::AddActor (x, y, ac); m_actors.push_back (ac); } } void OxydLoader::connect_rubberbands () { GameMode game_mode = config.gamemode; int num_rubberbands = level.getNumRubberBands(game_mode); for (int i=0; i senders; level.getSenders(&senders); set::const_iterator senderIter = senders.begin(); set::const_iterator senderEnd = senders.end(); for (; senderIter != senderEnd; ++senderIter) { const SignalLocation &sender = *senderIter; int nrec = level.getNumRecipients(sender); for (int irec=0; irec &special = level.getSpecialItems(); vector numberlist; int levelw = level.getWidth(); int levelh = level.getHeight(); for (size_t i=0; igetLevel(i).empty()) { level_index[nlevels] = i; nlevels++; } } Log << "Levelpack '" << get_name() << "' has " << nlevels << " levels." << endl; } int LevelPack_Oxyd::get_revision_number (size_t /*index*/) const { // Revision is always 1 (never DECREASE the revision number! ) return 1; } int LevelPack_Oxyd::get_preview_version() const { // Increment this to invalidate auto-saved previews for oxyd level // packs never DECREASE the preview version! return 1; } int LevelPack_Oxyd::get_default_SoundSet() const { return m_datfile->getVersion() + 2; } bool LevelPack_Oxyd::needs_twoplayers() const { return m_twoplayers; } bool LevelPack_Oxyd::may_have_previews() const { return false; } GameType LevelPack_Oxyd::get_gametype() const { return to_gametype(m_datfile->getVersion()); } GameMode LevelPack_Oxyd::get_gamemode() const { return (server::GetDifficulty() == DIFFICULTY_EASY ? GameMode_Easy : GameMode_Hard); } string LevelPack_Oxyd::get_name() const { static const char *names1p[] = { "Oxyd 1", "Oxyd magnum", "Magnum Gold", "Per.Oxyd", "Oxyd extra" }; static const char *names2p[] = { "Oxyd 1 (2p)", "", "", "Per.Oxyd (2p)", "Oxyd extra (2p)" }; OxydVersion v = get_version(); return level_index[0]>99 ? names2p[v] : names1p[v]; } void LevelPack_Oxyd::load_level (size_t index) { px::Assert (index < size(), "Invalid level index"); // Prepare level data string msg; Level level; if (!parseLevel (m_datfile->getLevel(level_index[index]), &level, &msg)) { throw levels::XLevelLoading(msg); } // Load level load (level); // Apply patch file string patchname = patchfile_name (get_gametype(), index, m_twoplayers); string patchfile; if (enigma::FindFile (patchname, patchfile)) { if (lua::Dofile (lua::LevelState(), patchname) != 0) { string err = string("While executing '")+patchname+"':\n"+lua::LastError(lua::LevelState()); throw levels::XLevelLoading(err); } } // Good to go! world::InitWorld(); } const LevelInfo & LevelPack_Oxyd::get_info (size_t index) const { index = level_index[index]-m_index_start; static LevelInfo info; info.type = get_gametype(); info.filename = px::strf ("Import %s %d", get_name().c_str(), index); info.name = px::strf ("%s #%d", get_name().c_str(), index+1); info.author = "Dongleware"; info.revision = get_revision_number(index); info.has_easymode = has_easymode(index); // info.par_time_easy = info.par_time_normal = -1; // @@@ read from score file ? // info.par_time_easy_by = info.par_time_hard_by = ""; return info; } /* -------------------- Oxyd extra level pack -------------------- */ LP_OxydExtra::LP_OxydExtra (DatFile *dat) : LevelPack_Oxyd (OxydVersion_OxydExtra, dat, 0, 99, false) { } void LP_OxydExtra::load (const Level &level) { LoaderConfig c (needs_twoplayers(), get_gamemode(), oxydextra_floor_map, oxydextra_item_map, oxydextra_stone_map); // c.id_timer = 0x2e; c.id_laser1 = 0x3e; PerOxydLoader (level, c).load(); } /* -------------------- Oxyd magnum level pack -------------------- */ LP_OxydMagnum::LP_OxydMagnum(OxydVersion version, DatFile *dat) : LevelPack_Oxyd (version, dat, 0, (version==OxydVersion_OxydMagnumGold) ? 120 : 99, false) { } void LP_OxydMagnum::load (const Level &level) { LoaderConfig c (needs_twoplayers(), get_gamemode(), oxydmag_floor_map, oxydmag_item_map, oxydmag_stone_map); c.id_timer = 0x33; c.id_laser1 = 0x44; PerOxydLoader (level, c).load(); // Add a yinyang item if a white marble is present if (world::CountActorsOfKind (world::ac_whiteball) > 0) player::AddYinYang(); } /* -------------------- MarbleInfo -------------------- */ MarbleInfo::MarbleInfo (const Marble& marble) { const string& data = marble.getData(server::GetDifficulty() == DIFFICULTY_EASY ? GameMode_Easy : GameMode_Hard); size_t from = 0; int idx = 0; while (from != string::npos) { size_t par_open = data.find('(', from); from = string::npos; if (par_open != string::npos) { size_t par_close = data.find(')', par_open); if (par_close != string::npos) { from = par_close; if (par_close == par_open+1) { value[idx++] = DEFAULT_VALUE; } else { value[idx++] = atoi(data.substr(par_open+1, par_close-par_open-1).c_str()); } } else { Log << "Error in MarbleInfo: missing closing parenthesis" << endl; } } } for (; idx games; int active_soundset = 1; // 1: enigma 2..: OxydVersion+2; typedef std::map SoundMap; SoundMap soundfx_map; } /* -------------------- Functions -------------------- */ void oxyd::Init() { games.clear(); games.resize(OxydVersion_Count); games[OxydVersion_Oxyd1] = new GameInfo (OxydVersion_Oxyd1, "Oxyd 1", "oxyd1ibm.dat"); games[OxydVersion_OxydMagnum] = new GameInfo (OxydVersion_OxydMagnum, "Oxyd magnum", "oxydmibm.dat"); games[OxydVersion_OxydMagnumGold] = new GameInfo(OxydVersion_OxydMagnumGold, "Oxyd magnum gold", "oxydmgg.dat"); games[OxydVersion_OxydExtra] = new GameInfo(OxydVersion_OxydExtra, "Oxyd extra", "oxydex.dat"); games[OxydVersion_PerOxyd] = new GameInfo(OxydVersion_PerOxyd, "Per.Oxyd", "peroxyd.dat"); } void oxyd::Shutdown() { px::delete_sequence(games.begin(), games.end()); } bool oxyd::FoundOxyd (OxydVersion ver) { return games[ver]->is_present(); } void oxyd::ChangeSoundset (int sound_set, int default_sound_set) { static int last_default_sound_set = 1; // if called without knowing default sound set // take last default or default to enigma // (e.g when called from option menu from inside game) if (default_sound_set == -1) default_sound_set = last_default_sound_set; else last_default_sound_set = default_sound_set; if (sound_set == 0) { // use specific soundset for each levelpack sound_set = default_sound_set; } if (sound_set == active_soundset) { return; } // reset to enigma soundset soundfx_map.clear(); active_soundset = 1; sound::ClearCache(); if (sound_set == 1) { // enigma -> no mapping lua::SetSoundTable ("Enigma"); return; } OxydVersion ver = OxydVersion(sound_set-2); GameInfo& gi = *(games[ver]); if (!gi.is_present()) return; // not installed -> use enigma soundset if (ver == OxydVersion_OxydMagnum || ver == OxydVersion_OxydMagnumGold) { lua::SetSoundTable ("Oxydm"); } else { lua::SetSoundTable ("Oxyd"); } active_soundset = sound_set; static const char *oxydsounds[] = { "OXBLOOP.SDD", "OXBOING.SDD", "OXBOLD.SDD", "OXCRACK.SDD", "OXCRASH1.SDD", "OXCRASH2.SDD", "OXCUT.SDD", "OXDROP.SDD", "OXEXIT.SDD", "OXFINITO.SDD", "OXINTRO.SDD", "OXINVENT.SDD", "OXINVROT.SDD", "OXJUMP.SDD", "OXKLICK1.SDD", "OXKLICK2.SDD", "OXKLICK3.SDD", "OXKLICK4.SDD", "OXKLICK5.SDD", "OXKLICK6.SDD", "OXKLIRR.SDD", "OXLASER.SDD", "OXMAGIC.SDD", "OXMAGIC2.SDD", "OXMAGIC3.SDD", "OXMAGIC4.SDD", "OXMATSCH.SDD", "OXMEMCL.SDD", "OXMEMOK.SDD", "OXMEMOP.SDD", "OXMONEY.SDD", "OXMOTOR.SDD", "OXMOVE.SDD", "OXPULLER.SDD", "OXSWOFF.SDD", "OXSWON.SDD", "OXTHIEF.SDD", "OXTRANS.SDD", "OXTURN.SDD", "OXUNTITL.SDD", "OXWOUOU.SDD", 0 }; OxydLib::DatFile *datfile = gi.getDatfile(); for (int i=0; oxydsounds[i]; ++i) { string chunkname = oxydsounds[i]; const OxydLib::ByteVec *snddata = datfile->getChunk(chunkname); if (snddata) { enigma::Log << "Loaded sound file " << chunkname<< "\n"; sound::SoundData snd; snd.buf.assign (snddata->begin(), snddata->end() - 4); // snd.buf = *snddata; snd.freq = 6274; snd.signedp = true; snd.samplesize = 1; snd.nchannels = 1; sound::DefineSound (oxydsounds[i], snd); } } }