/* * * levelloadjj2.cpp * * 28th June 2010: Created levelloadjj2.cpp from parts of levelload.cpp * 29th June 2010: Renamed levelloadjj2.cpp to jj2levelload.cpp * * Part of the OpenJazz project * * * Copyright (c) 2005-2010 Alister Thomson * * OpenJazz is distributed under the terms of * the GNU General Public License, version 2.0 * * 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * */ /* * Deals with the loading of JJ2 level data. * */ #include "jj2event/jj2event.h" #include "jj2level.h" #include "game/game.h" #include "io/file.h" #include "io/gfx/font.h" #include "io/gfx/sprite.h" #include "io/gfx/video.h" #include "io/sound.h" #include "menu/menu.h" #include "player/jj2levelplayer.h" #include "loop.h" #include "util.h" #include #define SKEY 254 /* Sprite colour key */ void JJ2Level::loadSprite (File* file, Sprite* sprite) { // TODO: Load sprites from JJ2 files unsigned char* pixels; int pos, maskOffset; int width, height; // Load dimensions width = file->loadShort() << 2; height = file->loadShort(); file->seek(2, false); maskOffset = file->loadShort(); pos = file->loadShort() << 2; // Sprites can be either masked or not masked. if (maskOffset) { // Masked height++; // Skip to mask file->seek(maskOffset, false); // Find the end of the data pos += file->tell() + ((width >> 2) * height); // Read scrambled, masked pixel data pixels = file->loadPixels(width * height, SKEY); sprite->setPixels(pixels, width, height, SKEY); delete[] pixels; file->seek(pos, true); } else if (width) { // Not masked // Read scrambled pixel data pixels = file->loadPixels(width * height); sprite->setPixels(pixels, width, height, SKEY); delete[] pixels; } return; } int JJ2Level::loadSprites (char * fileName) { // TODO: Load sprites from JJ2 files File* mainFile = NULL; File* specFile = NULL; int count; // Open fileName try { specFile = new File(fileName, false); } catch (int e) { return e; } // This function loads all the sprites, not fust those in fileName try { mainFile = new File(F_MAINCHAR, false); } catch (int e) { delete specFile; return e; } sprites = specFile->loadShort(); // Include space in the sprite set for the blank sprite at the end spriteSet = new Sprite[sprites + 1]; // Read horizontal offsets for (count = 0; count < sprites; count++) spriteSet[count].xOffset = specFile->loadChar() << 2; // Read vertical offsets for (count = 0; count < sprites; count++) spriteSet[count].yOffset = specFile->loadChar(); // Skip to where the sprites start in mainchar.000 mainFile->seek(2, true); // Loop through all the sprites to be loaded for (count = 0; count < sprites; count++) { if (specFile->loadChar() == 0xFF) { // Go to the next sprite/file indicator specFile->seek(1, false); if (mainFile->loadChar() == 0xFF) { // Go to the next sprite/file indicator mainFile->seek(1, false); /* Both fileName and mainchar.000 have file indicators, so create a blank sprite */ spriteSet[count].clearPixels(); continue; } else { // Return to the start of the sprite mainFile->seek(-1, false); // Load the individual sprite data loadSprite(mainFile, spriteSet + count); } } else { // Return to the start of the sprite specFile->seek(-1, false); // Go to the main file's next sprite/file indicator mainFile->seek(2, false); // Load the individual sprite data loadSprite(specFile, spriteSet + count); } // Check if the next sprite exists // If not, create blank sprites for the remainder if (specFile->tell() >= specFile->getSize()) { for (count++; count < sprites; count++) { spriteSet[count].clearPixels(); } } } delete mainFile; delete specFile; // Include a blank sprite at the end spriteSet[sprites].clearPixels(); spriteSet[sprites].xOffset = 0; spriteSet[sprites].yOffset = 0; return E_NONE; } int JJ2Level::loadTiles (char* fileName) { File* file; unsigned char* aBuffer; unsigned char* bBuffer; unsigned char* dBuffer; unsigned char* tileBuffer; int aCLength, bCLength, cCLength, dCLength; int aLength, bLength, cLength, dLength; int count, x, y; int maxTiles; int tiles; // Thanks to Neobeo for working out the most of the .j2t format try { file = new File(fileName, false); } catch (int e) { return e; } // Skip to version indicator file->seek(220, true); maxTiles = file->loadShort(); if (maxTiles == 0x201) maxTiles = 4096; else maxTiles = 1024; // Skip to compressed block lengths file->seek(8, false); aCLength = file->loadInt(); aLength = file->loadInt(); bCLength = file->loadInt(); bLength = file->loadInt(); cCLength = file->loadInt(); cLength = file->loadInt(); dCLength = file->loadInt(); dLength = file->loadInt(); aBuffer = file->loadLZ(aCLength, aLength); bBuffer = file->loadLZ(bCLength, bLength); file->seek(cCLength, false); // Don't need this block dBuffer = file->loadLZ(dCLength, dLength); delete file; // Load the palette for (count = 0; count < 256; count++) { palette[count].r = aBuffer[count << 2]; palette[count].g = aBuffer[(count << 2) + 1]; palette[count].b = aBuffer[(count << 2) + 2]; } // Load tiles tiles = createShort(aBuffer + 1024); tileBuffer = new unsigned char[tiles << 10]; for (count = 0; count < tiles; count++) { memcpy(tileBuffer + (count << 10), bBuffer + createInt(aBuffer + 1028 + (maxTiles << 1) + (count << 2)), 1024); } tileSet = createSurface(tileBuffer, TTOI(1), TTOI(tiles)); SDL_SetColorKey(tileSet, SDL_SRCCOLORKEY, 0); // Flip tiles for (count = 0; count < tiles * 32; count++) { for (x = 0; x < 16; x++) { y = tileBuffer[(count * 32) + x]; tileBuffer[(count * 32) + x] = tileBuffer[(count * 32) + 31 - x]; tileBuffer[(count * 32) + 31 - x] = y; } } flippedTileSet = createSurface(tileBuffer, TTOI(1), TTOI(tiles)); SDL_SetColorKey(flippedTileSet, SDL_SRCCOLORKEY, 0); delete[] tileBuffer; // Load mask mask = new char[tiles << 10]; // Unpack bits for (count = 0; count < tiles; count++) { for (y = 0; y < 32; y++) { for (x = 0; x < 32; x++) mask[(count << 10) + (y << 5) + x] = (dBuffer[createInt(aBuffer + 1028 + (maxTiles * 18) + (count << 2)) + (y << 2) + (x >> 3)] >> (x & 7)) & 1; } } flippedMask = new char[tiles << 10]; // Unpack bits for (count = 0; count < tiles; count++) { for (y = 0; y < 32; y++) { for (x = 0; x < 32; x++) flippedMask[(count << 10) + (y << 5) + x] = (dBuffer[createInt(aBuffer + 1028 + (maxTiles * 22) + (count << 2)) + (y << 2) + (x >> 3)] >> (x & 7)) & 1; } } delete[] dBuffer; delete[] bBuffer; delete[] aBuffer; /* Uncomment the code below if you want to see the mask instead of the tile graphics during gameplay */ /*if (SDL_MUSTLOCK(tileSet)) SDL_LockSurface(tileSet); if (SDL_MUSTLOCK(flippedTileSet)) SDL_LockSurface(flippedTileSet); for (count = 0; count < tiles; count++) { for (y = 0; y < 32; y++) { for (x = 0; x < 32; x++) { if (mask[(count << 10) + (y << 5) + x] == 1) ((char *)(tileSet->pixels))[(count << 10) + (y << 5) + x] = 43; if (flippedMask[(count << 10) + (y << 5) + x] == 1) ((char *)(flippedTileSet->pixels))[(count << 10) + (y << 5) + x] = 88; } } } if (SDL_MUSTLOCK(tileSet)) SDL_UnlockSurface(tileSet); if (SDL_MUSTLOCK(flippedTileSet)) SDL_UnlockSurface(flippedTileSet);*/ return tiles | (maxTiles << 16); } int JJ2Level::load (char *fileName, unsigned char diff, bool checkpoint) { File *file; unsigned char *buffer; char *string; unsigned char* aBuffer; unsigned char* bBuffer; unsigned char* cBuffer; unsigned char* dBuffer; int aCLength, bCLength, cCLength, dCLength; int aLength, bLength, cLength, dLength; int tiles; int count, x, y; unsigned char tileQuad[8]; short int* quadRefs; int flags, width, pitch, height; int worldNum; unsigned char startX, startY; // Thanks to Neobeo for working out the most of the .j2l format try { font = new Font(false); } catch (int e) { throw e; } difficulty = diff; // Open JJ2 level file try { file = new File(fileName, false); } catch (int e) { delete font; return e; } // Load level name file->seek(188, true); string = (char *)file->loadBlock(32); // Show loading screen video.setPalette(menuPalette); clearScreen(0); x = (canvasW >> 1) - ((strlen(string) + 6) << 2); x = fontmn2->showString("LOADING ", x - 60, (canvasH >> 1) - 16); fontmn2->showString(string, x, (canvasH >> 1) - 16); delete[] string; if (::loop(NORMAL_LOOP) == E_QUIT) return E_QUIT; // Skip to compressed block lengths file->seek(230, true); aCLength = file->loadInt(); aLength = file->loadInt(); bCLength = file->loadInt(); bLength = file->loadInt(); cCLength = file->loadInt(); cLength = file->loadInt(); dCLength = file->loadInt(); dLength = file->loadInt(); aBuffer = file->loadLZ(aCLength, aLength); bBuffer = file->loadLZ(bCLength, bLength); cBuffer = file->loadLZ(cCLength, cLength); dBuffer = file->loadLZ(dCLength, dLength); delete file; // Load tile set from given file tiles = loadTiles((char *)aBuffer + 51); if (tiles < 0) { delete[] dBuffer; delete[] cBuffer; delete[] bBuffer; delete[] aBuffer; delete font; return tiles; } TSF = tiles >> 28; tiles = tiles & 0xFFFF; // Next level string = (char *)aBuffer + 115; if (fileExists(string)) nextLevel = createString(string); else nextLevel = createString(string, ".j2l"); // Music file string = (char *)aBuffer + 179; if (fileExists(string)) musicFile = createString(string); else musicFile = createString(string, ".j2b"); // Create layers quadRefs = (short int *)dBuffer; for (count = 0; count < LAYERS; count++) { flags = aBuffer[8403 + (count << 2)]; width = createInt(aBuffer + 8403 + 48 + (count << 2)); pitch = createInt(aBuffer + 8403 + 80 + (count << 2)); height = createInt(aBuffer + 8403 + 112 + (count << 2)); if (aBuffer[8403 + 40 + count]) { layers[count] = new JJ2Layer(width, height, flags); for (y = 0; y < height; y++) { for (x = 0; x < width; x++) { if ((x & 3) == 0) memcpy(tileQuad, cBuffer + (quadRefs[x >> 2] << 3), 8); layers[count]->setTile(x, y, createShort(tileQuad + ((x & 3) << 1)), TSF? -tiles: tiles); } quadRefs += pitch >> 2; } } else { // No tile data layers[count] = new JJ2Layer(); } } layer = layers[3]; width = layer->getWidth(); height = layer->getHeight(); // Load events startX = 1; startY = 1; mods = new JJ2Modifier *[height]; *mods = new JJ2Modifier[width * height]; events = NULL; for (y = 0; y < height; y++) { mods[y] = *mods + (y * width); for (x = 0; x < width; x++) { count = bBuffer[((y * width) + x) << 2]; if ((count < 33) || (count == 230)) { mods[y][x].type = count; mods[y][x].property = bBuffer[(((y * width) + x) << 2) + 1]; if (count == 29) { // Jazz start pos startX = x; startY = y; } } else { mods[y][x].type = 0; events = new JJ2Event(events, x, y, bBuffer + (((y * width) + x) << 2)); } } } delete[] dBuffer; delete[] cBuffer; delete[] bBuffer; delete[] aBuffer; // Open JJ1 level file for remaining data // Use first level worldNum = 0; string = createFileName(F_LEVEL, 0, worldNum); try { file = new File(string, false); } catch (int e) { // Failed to load first level, so try loading first Christmas level worldNum = 50; string[8] = '5'; try { file = new File(string, false); } catch (int e) { delete[] string; if (events) delete events; delete[] *mods; delete[] mods; for (count = 0; count < LAYERS; count++) delete layers[count]; delete[] flippedMask; delete[] mask; delete[] musicFile; delete[] nextLevel; SDL_FreeSurface(flippedTileSet); SDL_FreeSurface(tileSet); delete font; return e; } } delete[] string; // Load sprite set from corresponding Sprites.### string = createFileName(F_SPRITES, worldNum); count = loadSprites(string); delete[] string; if (count < 0) { delete file; if (events) delete events; delete[] *mods; delete[] mods; for (x = 0; x < LAYERS; x++) delete layers[x]; delete[] flippedMask; delete[] mask; delete[] musicFile; delete[] nextLevel; SDL_FreeSurface(flippedTileSet); SDL_FreeSurface(tileSet); delete font; return count; } // Skip to tile and event reference data file->seek(39, true); // Skip tile references file->skipRLE(); // A mysterious block of mystery file->skipRLE(); // Skip mask data file->skipRLE(); // Skip special event path file->skipRLE(); // Skip event set file->skipRLE(); // Yet more doubtless essential data file->skipRLE(); // Load animation set buffer = file->loadRLE(128 << 6); // Create animation set based on that data for (count = 0; count < 128; count++) { animSet[count].setData(buffer[(count << 6) + 6], buffer[count << 6], buffer[(count << 6) + 1], buffer[(count << 6) + 3], buffer[(count << 6) + 4], buffer[(count << 6) + 2], buffer[(count << 6) + 5]); for (y = 0; y < buffer[(count << 6) + 6]; y++) { // Get frame x = buffer[(count << 6) + 7 + y]; if (x > sprites) x = sprites; // Assign sprite and vertical offset animSet[count].setFrame(y, true); animSet[count].setFrameData(spriteSet + x, buffer[(count << 6) + 26 + y], buffer[(count << 6) + 45 + y]); } } delete[] buffer; // At general data // There's a a whole load of unknown data around here // Like another one of those pesky RLE blocks file->skipRLE(); // And 217 bytes of DOOM file->seek(217, false); // Load sound map x = file->tell(); for (count = 0; count < 32; count++) { file->seek(x + (count * 9), true); string = file->loadString(); soundMap[count] = -1; // Search for matching sound for (y = 0; (y < nSounds) && (soundMap[count] == -1); y++) { if (!strcmp(string, sounds[y].name)) soundMap[count] = y; } delete[] string; } // Skip file names file->seek(x + 366, true); // Skip the players' initial coordinates file->seek(4, false); // Skip next level numbers file->seek(2, false); // Thanks to Doubble Dutch for the water level bytes file->seek(4, false); waterLevelTarget = ITOF(file->loadShort()); waterLevel = waterLevelTarget - F8; waterLevelSpeed = 0; // Thanks to Feline and the JCS94 team for the next bits: file->seek(3, false); // Now at "Section 15" // Load player's animation set references buffer = file->loadRLE(PANIMS * 2); string = new char[PANIMS + 3]; for (x = 0; x < PANIMS; x++) string[x + 3] = buffer[x << 1]; delete[] buffer; if (gameMode) { string[0] = MTL_P_ANIMS; string[1] = MT_P_ANIMS; string[2] = 0; game->send((unsigned char *)string); } // Set the players' initial values if (game) { if (!checkpoint) game->setCheckpoint(startX, startY); for (count = 0; count < nPlayers; count++) game->resetPlayer(players + count, LT_JJ2LEVEL, string + 3); } else { localPlayer->reset(LT_JJ2LEVEL, string + 3, startX, startY); } delete[] string; // And that's us done! delete file; // Set the tick at which the level will end, though this is not used endTime = (5 - difficulty) * 2 * 60 * 1000; // Adjust panel fonts to use bonus level palette panelBigFont->mapPalette(0, 32, 64, 8); panelSmallFont->mapPalette(75, 5, 64, 8); return E_NONE; }