diff --git a/lib/features/map/vswap_parser.dart b/lib/features/map/vswap_parser.dart index 9ee3a00..b502a6e 100644 --- a/lib/features/map/vswap_parser.dart +++ b/lib/features/map/vswap_parser.dart @@ -39,4 +39,65 @@ class VswapParser { return textures; } + + /// Extracts the compiled scaled sprites from VSWAP.WL1 + static List> parseSprites(ByteData vswap) { + int chunks = vswap.getUint16(0, Endian.little); + int spriteStart = vswap.getUint16(2, Endian.little); + int soundStart = vswap.getUint16(4, Endian.little); + + List offsets = []; + for (int i = 0; i < chunks; i++) { + offsets.add(vswap.getUint32(6 + (i * 4), Endian.little)); + } + + List> sprites = []; + + // Sprites are located between the walls and the sounds + for (int i = spriteStart; i < soundStart; i++) { + int offset = offsets[i]; + if (offset == 0) continue; // Some chunks are empty placeholders + + // Initialize the 64x64 grid with 255 (The Magenta Transparency Color!) + Matrix sprite = List.generate(64, (_) => List.filled(64, 255)); + + int leftPix = vswap.getUint16(offset, Endian.little); + int rightPix = vswap.getUint16(offset + 2, Endian.little); + + // Read the offsets for each vertical column of the sprite + List colOffsets = []; + for (int x = leftPix; x <= rightPix; x++) { + colOffsets.add( + vswap.getUint16(offset + 4 + ((x - leftPix) * 2), Endian.little), + ); + } + + for (int x = leftPix; x <= rightPix; x++) { + int colOffset = colOffsets[x - leftPix]; + if (colOffset == 0) continue; + + int cmdOffset = offset + colOffset; + + // Execute the column drawing commands + while (true) { + int endY = vswap.getUint16(cmdOffset, Endian.little); + if (endY == 0) break; // 0 marks the end of the column + endY ~/= 2; // Wolf3D stores Y coordinates multiplied by 2 + + int pixelOfs = vswap.getUint16(cmdOffset + 2, Endian.little); + + int startY = vswap.getUint16(cmdOffset + 4, Endian.little); + startY ~/= 2; + + for (int y = startY; y < endY; y++) { + // The Carmack 286 Hack: pixelOfs + y gives the exact byte address! + sprite[x][y] = vswap.getUint8(offset + pixelOfs + y); + } + cmdOffset += 6; // Move to the next 6-byte instruction + } + } + sprites.add(sprite); + } + return sprites; + } } diff --git a/lib/features/map/wolf_map.dart b/lib/features/map/wolf_map.dart index 83d958f..73dbf4b 100644 --- a/lib/features/map/wolf_map.dart +++ b/lib/features/map/wolf_map.dart @@ -8,9 +8,14 @@ class WolfMap { /// The fully parsed and decompressed levels from the game files. final List levels; final List> textures; + final List> sprites; // A private constructor so we can only instantiate this from the async loader - WolfMap._(this.levels, this.textures); + WolfMap._( + this.levels, + this.textures, + this.sprites, + ); /// Asynchronously loads the map files and parses them into a new WolfMap instance. static Future load() async { @@ -22,8 +27,13 @@ class WolfMap { // 2. Parse the data using the parser we just built final parsedLevels = WolfMapParser.parseMaps(mapHead, gameMaps); final parsedTextures = VswapParser.parseWalls(vswap); + final parsedSprites = VswapParser.parseSprites(vswap); // 3. Return the populated instance! - return WolfMap._(parsedLevels, parsedTextures); + return WolfMap._( + parsedLevels, + parsedTextures, + parsedSprites, + ); } }