Moved data loading to parser. Added remaining shareware data.
Signed-off-by: Hans Kokx <hans.d.kokx@gmail.com>
This commit is contained in:
BIN
assets/shareware/AUDIOHED.WL1
Normal file
BIN
assets/shareware/AUDIOHED.WL1
Normal file
Binary file not shown.
BIN
assets/shareware/AUDIOT.WL1
Normal file
BIN
assets/shareware/AUDIOT.WL1
Normal file
Binary file not shown.
BIN
assets/shareware/CONFIG.WL1
Normal file
BIN
assets/shareware/CONFIG.WL1
Normal file
Binary file not shown.
BIN
assets/shareware/VGADICT.WL1
Normal file
BIN
assets/shareware/VGADICT.WL1
Normal file
Binary file not shown.
BIN
assets/shareware/VGAGRAPH.WL1
Normal file
BIN
assets/shareware/VGAGRAPH.WL1
Normal file
Binary file not shown.
BIN
assets/shareware/VGAHEAD.WL1
Normal file
BIN
assets/shareware/VGAHEAD.WL1
Normal file
Binary file not shown.
@@ -1,7 +1,7 @@
|
||||
import 'dart:math' as math;
|
||||
|
||||
import 'package:wolf_3d_data/wolf_3d_data.dart';
|
||||
import 'package:wolf_dart/features/map/door.dart';
|
||||
import 'package:wolf_dart/features/entities/door.dart';
|
||||
|
||||
class DoorManager {
|
||||
// Key is '$x,$y'
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:wolf_3d_data/wolf_3d_data.dart';
|
||||
|
||||
class WolfMap {
|
||||
/// The fully parsed and decompressed levels from the game files.
|
||||
final List<WolfLevel> levels;
|
||||
final List<Sprite> textures;
|
||||
final List<Sprite> sprites;
|
||||
|
||||
// A private constructor so we can only instantiate this from the async loader
|
||||
WolfMap._(
|
||||
this.levels,
|
||||
this.textures,
|
||||
this.sprites,
|
||||
);
|
||||
|
||||
/// Asynchronously loads the map files and parses them into a new WolfMap instance.
|
||||
static Future<WolfMap> loadShareware() async {
|
||||
// 1. Load the binary data
|
||||
final mapHead = await rootBundle.load("assets/MAPHEAD.WL1");
|
||||
final gameMaps = await rootBundle.load("assets/GAMEMAPS.WL1");
|
||||
final vswap = await rootBundle.load("assets/VSWAP.WL1");
|
||||
|
||||
// 2. Parse the data using the parser we just built
|
||||
final parsedLevels = WLParser.parseMaps(
|
||||
mapHead,
|
||||
gameMaps,
|
||||
isShareware: true,
|
||||
);
|
||||
final parsedTextures = WLParser.parseWalls(vswap);
|
||||
final parsedSprites = WLParser.parseSprites(vswap);
|
||||
|
||||
// 3. Return the populated instance!
|
||||
return WolfMap._(
|
||||
parsedLevels,
|
||||
parsedTextures,
|
||||
parsedSprites,
|
||||
);
|
||||
}
|
||||
|
||||
/// Asynchronously loads the map files and parses them into a new WolfMap instance.
|
||||
static Future<WolfMap> loadRetail() async {
|
||||
// 1. Load the binary data
|
||||
final mapHead = await rootBundle.load("assets/MAPHEAD.WL6");
|
||||
final gameMaps = await rootBundle.load("assets/GAMEMAPS.WL6");
|
||||
final vswap = await rootBundle.load("assets/VSWAP.WL6");
|
||||
|
||||
// 2. Parse the data using the parser we just built
|
||||
final parsedLevels = WLParser.parseMaps(mapHead, gameMaps);
|
||||
final parsedTextures = WLParser.parseWalls(vswap);
|
||||
final parsedSprites = WLParser.parseSprites(vswap);
|
||||
|
||||
// 3. Return the populated instance!
|
||||
return WolfMap._(
|
||||
parsedLevels,
|
||||
parsedTextures,
|
||||
parsedSprites,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ import 'dart:math' as math;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/scheduler.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:wolf_3d_data/wolf_3d_data.dart';
|
||||
import 'package:wolf_dart/classes/coordinate_2d.dart';
|
||||
import 'package:wolf_dart/features/difficulty/difficulty.dart';
|
||||
@@ -13,7 +14,6 @@ import 'package:wolf_dart/features/entities/entity_registry.dart';
|
||||
import 'package:wolf_dart/features/entities/map_objects.dart';
|
||||
import 'package:wolf_dart/features/entities/pushwall_manager.dart';
|
||||
import 'package:wolf_dart/features/input/input_manager.dart';
|
||||
import 'package:wolf_dart/features/map/wolf_map.dart';
|
||||
import 'package:wolf_dart/features/player/player.dart';
|
||||
import 'package:wolf_dart/features/renderer/raycast_painter.dart';
|
||||
import 'package:wolf_dart/features/renderer/weapon_painter.dart';
|
||||
@@ -44,8 +44,9 @@ class _WolfRendererState extends State<WolfRenderer>
|
||||
|
||||
late Ticker _gameLoop;
|
||||
final FocusNode _focusNode = FocusNode();
|
||||
late WolfMap gameMap;
|
||||
late WolfensteinData gameData;
|
||||
late Level currentLevel;
|
||||
late WolfLevel activeLevel;
|
||||
|
||||
final double fov = math.pi / 3;
|
||||
|
||||
@@ -64,15 +65,22 @@ class _WolfRendererState extends State<WolfRenderer>
|
||||
}
|
||||
|
||||
Future<void> _initGame(bool isShareware) async {
|
||||
gameMap = isShareware
|
||||
? await WolfMap.loadShareware()
|
||||
: await WolfMap.loadRetail();
|
||||
currentLevel = gameMap.levels[0].wallGrid;
|
||||
gameData = await WLParser.loadAsync(
|
||||
(filename) => rootBundle.load('assets/retail/$filename'),
|
||||
);
|
||||
|
||||
print('Detected Game Version: ${gameData.version.name}');
|
||||
print('Loaded ${gameData.levels.length} levels!');
|
||||
print('Loaded ${gameData.vgaImages.length} images!');
|
||||
|
||||
// Get the first level out of the data class
|
||||
activeLevel = gameData.levels.first;
|
||||
|
||||
// Set up your grids directly from the active level
|
||||
currentLevel = activeLevel.wallGrid;
|
||||
final Level objectLevel = activeLevel.objectGrid;
|
||||
|
||||
doorManager.initDoors(currentLevel);
|
||||
|
||||
final Level objectLevel = gameMap.levels[0].objectGrid;
|
||||
|
||||
pushwallManager.initPushwalls(currentLevel, objectLevel);
|
||||
|
||||
for (int y = 0; y < 64; y++) {
|
||||
@@ -108,7 +116,7 @@ class _WolfRendererState extends State<WolfRenderer>
|
||||
x + 0.5,
|
||||
y + 0.5,
|
||||
widget.difficulty,
|
||||
gameMap.sprites.length,
|
||||
gameData.sprites.length,
|
||||
isSharewareMode: isShareware,
|
||||
);
|
||||
|
||||
@@ -354,7 +362,7 @@ class _WolfRendererState extends State<WolfRenderer>
|
||||
entity.x,
|
||||
entity.y,
|
||||
widget.difficulty,
|
||||
gameMap.sprites.length,
|
||||
gameData.sprites.length,
|
||||
);
|
||||
|
||||
if (droppedAmmo != null) {
|
||||
@@ -403,7 +411,7 @@ class _WolfRendererState extends State<WolfRenderer>
|
||||
}
|
||||
|
||||
if (widget.showSpriteGallery) {
|
||||
return SpriteGallery(sprites: gameMap.sprites);
|
||||
return SpriteGallery(sprites: gameData.sprites);
|
||||
}
|
||||
|
||||
return Scaffold(
|
||||
@@ -429,12 +437,12 @@ class _WolfRendererState extends State<WolfRenderer>
|
||||
),
|
||||
painter: RaycasterPainter(
|
||||
map: currentLevel,
|
||||
textures: gameMap.textures,
|
||||
textures: gameData.walls,
|
||||
player: player,
|
||||
fov: fov,
|
||||
doorOffsets: doorManager.getOffsetsForRenderer(),
|
||||
entities: entities,
|
||||
sprites: gameMap.sprites,
|
||||
sprites: gameData.sprites,
|
||||
activePushwall: pushwallManager.activePushwall,
|
||||
),
|
||||
),
|
||||
@@ -451,9 +459,9 @@ class _WolfRendererState extends State<WolfRenderer>
|
||||
child: CustomPaint(
|
||||
painter: WeaponPainter(
|
||||
sprite:
|
||||
gameMap.sprites[player.currentWeapon
|
||||
gameData.sprites[player.currentWeapon
|
||||
.getCurrentSpriteIndex(
|
||||
gameMap.sprites.length,
|
||||
gameData.sprites.length,
|
||||
)],
|
||||
),
|
||||
),
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
enum GameVersion {
|
||||
shareware("WL1"),
|
||||
retail("WL6"),
|
||||
spearOfDestinyDemo("SDM"),
|
||||
spearOfDestiny("SOD"),
|
||||
spearOfDestinyDemo("SDM"),
|
||||
;
|
||||
|
||||
final String fileExtension;
|
||||
|
||||
13
packages/wolf_3d_data/lib/src/classes/image.dart
Normal file
13
packages/wolf_3d_data/lib/src/classes/image.dart
Normal file
@@ -0,0 +1,13 @@
|
||||
import 'dart:typed_data';
|
||||
|
||||
class VgaImage {
|
||||
final int width;
|
||||
final int height;
|
||||
final Uint8List pixels; // 8-bit paletted pixel data
|
||||
|
||||
VgaImage({
|
||||
required this.width,
|
||||
required this.height,
|
||||
required this.pixels,
|
||||
});
|
||||
}
|
||||
6
packages/wolf_3d_data/lib/src/classes/sound.dart
Normal file
6
packages/wolf_3d_data/lib/src/classes/sound.dart
Normal file
@@ -0,0 +1,6 @@
|
||||
import 'dart:typed_data';
|
||||
|
||||
class PcmSound {
|
||||
final Uint8List bytes;
|
||||
PcmSound(this.bytes);
|
||||
}
|
||||
@@ -1,81 +1,19 @@
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:wolf_3d_data/src/wl_parser.dart';
|
||||
|
||||
import 'game_version.dart';
|
||||
import 'sprite.dart';
|
||||
import 'wolf_level.dart';
|
||||
import 'package:wolf_3d_data/wolf_3d_data.dart';
|
||||
|
||||
class WolfensteinData {
|
||||
final GameVersion version;
|
||||
final List<Sprite> walls;
|
||||
final List<Sprite> sprites;
|
||||
final List<PcmSound> sounds;
|
||||
final List<WolfLevel> levels;
|
||||
final List<VgaImage> vgaImages;
|
||||
|
||||
// Raw file data references
|
||||
final ByteData _vswap;
|
||||
final ByteData _mapHead;
|
||||
final ByteData _gameMaps;
|
||||
|
||||
// Backing fields for lazy loading
|
||||
List<Sprite>? _walls;
|
||||
List<Sprite>? _sprites;
|
||||
List<WolfLevel>? _levels;
|
||||
|
||||
WolfensteinData._({
|
||||
const WolfensteinData({
|
||||
required this.version,
|
||||
required ByteData vswap,
|
||||
required ByteData mapHead,
|
||||
required ByteData gameMaps,
|
||||
}) : _vswap = vswap,
|
||||
_mapHead = mapHead,
|
||||
_gameMaps = gameMaps;
|
||||
|
||||
/// Initializes the data from a map of filenames to their byte contents.
|
||||
/// Automatically detects the game version from the file extensions.
|
||||
factory WolfensteinData.fromFiles(Map<String, ByteData> files) {
|
||||
if (files.isEmpty) throw ArgumentError('File map cannot be empty');
|
||||
|
||||
// 1. Detect Game Version from the first file's extension
|
||||
final sampleFilename = files.keys.first.toUpperCase();
|
||||
final extension = sampleFilename.split('.').last;
|
||||
|
||||
final version = GameVersion.values.firstWhere(
|
||||
(v) => v.fileExtension == extension,
|
||||
orElse: () =>
|
||||
throw FormatException('Unsupported file extension: $extension'),
|
||||
);
|
||||
|
||||
// 2. Extract the required files using the detected extension
|
||||
final vswap = _getFile(files, 'VSWAP.$extension');
|
||||
final mapHead = _getFile(files, 'MAPHEAD.$extension');
|
||||
final gameMaps = _getFile(files, 'GAMEMAPS.$extension');
|
||||
|
||||
return WolfensteinData._(
|
||||
version: version,
|
||||
vswap: vswap,
|
||||
mapHead: mapHead,
|
||||
gameMaps: gameMaps,
|
||||
);
|
||||
}
|
||||
|
||||
// --- Lazy Getters ---
|
||||
|
||||
List<Sprite> get walls => _walls ??= WLParser.parseWalls(_vswap);
|
||||
|
||||
List<Sprite> get sprites => _sprites ??= WLParser.parseSprites(_vswap);
|
||||
|
||||
List<WolfLevel> get levels => _levels ??= WLParser.parseMaps(
|
||||
_mapHead,
|
||||
_gameMaps,
|
||||
isShareware: version == GameVersion.shareware,
|
||||
);
|
||||
|
||||
// --- Helpers ---
|
||||
|
||||
static ByteData _getFile(Map<String, ByteData> files, String name) {
|
||||
// Case-insensitive lookup
|
||||
final key = files.keys.firstWhere(
|
||||
(k) => k.toUpperCase() == name.toUpperCase(),
|
||||
orElse: () => throw FormatException('Missing required file: $name'),
|
||||
);
|
||||
return files[key]!;
|
||||
}
|
||||
required this.walls,
|
||||
required this.sprites,
|
||||
required this.sounds,
|
||||
required this.levels,
|
||||
required this.vgaImages,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,11 +1,79 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:wolf_3d_data/src/classes/game_version.dart';
|
||||
import 'package:wolf_3d_data/src/classes/image.dart';
|
||||
import 'package:wolf_3d_data/src/classes/sound.dart';
|
||||
import 'package:wolf_3d_data/src/classes/wolf_level.dart';
|
||||
import 'package:wolf_3d_data/src/classes/wolfenstein_data.dart';
|
||||
|
||||
import 'classes/sprite.dart';
|
||||
|
||||
class WLParser {
|
||||
abstract class WLParser {
|
||||
/// Asynchronously discovers the game version and loads all necessary files.
|
||||
/// Provide a [fileFetcher] callback (e.g., Flutter's rootBundle.load) that
|
||||
/// takes a filename and returns its ByteData.
|
||||
static Future<WolfensteinData> loadAsync(
|
||||
Future<ByteData> Function(String filename) fileFetcher,
|
||||
) async {
|
||||
GameVersion? detectedVersion;
|
||||
ByteData? vswap;
|
||||
|
||||
// 1. Probe the data source to figure out which version we have
|
||||
for (final version in GameVersion.values) {
|
||||
try {
|
||||
vswap = await fileFetcher('VSWAP.${version.fileExtension}');
|
||||
detectedVersion = version;
|
||||
break; // We found the version!
|
||||
} catch (_) {
|
||||
// File wasn't found, try the next version extension
|
||||
}
|
||||
}
|
||||
|
||||
if (detectedVersion == null || vswap == null) {
|
||||
throw Exception(
|
||||
'Could not locate a valid VSWAP file for any game version.',
|
||||
);
|
||||
}
|
||||
|
||||
final ext = detectedVersion.fileExtension;
|
||||
|
||||
// 2. Now that we know the version, confidently load the rest of the files
|
||||
return load(
|
||||
version: detectedVersion,
|
||||
vswap: vswap,
|
||||
mapHead: await fileFetcher('MAPHEAD.$ext'),
|
||||
gameMaps: await fileFetcher('GAMEMAPS.$ext'),
|
||||
vgaDict: await fileFetcher('VGADICT.$ext'),
|
||||
vgaHead: await fileFetcher('VGAHEAD.$ext'),
|
||||
vgaGraph: await fileFetcher('VGAGRAPH.$ext'),
|
||||
);
|
||||
}
|
||||
|
||||
/// Parses all raw ByteData upfront and returns a fully populated
|
||||
/// WolfensteinData object. By using named parameters, the compiler
|
||||
/// guarantees no files are missing or misnamed.
|
||||
static WolfensteinData load({
|
||||
required GameVersion version,
|
||||
required ByteData vswap,
|
||||
required ByteData mapHead,
|
||||
required ByteData gameMaps,
|
||||
required ByteData vgaDict,
|
||||
required ByteData vgaHead,
|
||||
required ByteData vgaGraph,
|
||||
}) {
|
||||
final isShareware = version == GameVersion.shareware;
|
||||
|
||||
return WolfensteinData(
|
||||
version: version,
|
||||
walls: parseWalls(vswap),
|
||||
sprites: parseSprites(vswap),
|
||||
sounds: parseSounds(vswap).map((bytes) => PcmSound(bytes)).toList(),
|
||||
levels: parseMaps(mapHead, gameMaps, isShareware: isShareware),
|
||||
vgaImages: parseVgaImages(vgaDict, vgaHead, vgaGraph),
|
||||
);
|
||||
}
|
||||
|
||||
/// Extracts the 64x64 wall textures from VSWAP.WL1
|
||||
static List<Sprite> parseWalls(ByteData vswap) {
|
||||
final header = _VswapHeader(vswap);
|
||||
@@ -114,6 +182,44 @@ class WLParser {
|
||||
}
|
||||
}
|
||||
|
||||
/// Extracts and decodes the UI graphics, Title Screens, and Menu items
|
||||
static List<VgaImage> parseVgaImages(
|
||||
ByteData vgaDict,
|
||||
ByteData vgaHead,
|
||||
ByteData vgaGraph,
|
||||
) {
|
||||
// 1. Get all raw decompressed chunks using the Huffman algorithm
|
||||
List<Uint8List> rawChunks = _parseVgaRaw(vgaDict, vgaHead, vgaGraph);
|
||||
|
||||
// 2. Chunk 0 is the Picture Table (Dimensions for all images)
|
||||
// It contains consecutive 16-bit widths and 16-bit heights
|
||||
ByteData picTable = ByteData.sublistView(rawChunks[0]);
|
||||
int numPics = picTable.lengthInBytes ~/ 4;
|
||||
|
||||
List<VgaImage> images = [];
|
||||
|
||||
// 3. In Wolf3D, Chunk 1 and 2 are fonts. Pictures start at Chunk 3!
|
||||
int picStartIndex = 3;
|
||||
|
||||
for (int i = 0; i < numPics; i++) {
|
||||
int width = picTable.getUint16(i * 4, Endian.little);
|
||||
int height = picTable.getUint16(i * 4 + 2, Endian.little);
|
||||
|
||||
// Safety check: ensure we don't read out of bounds
|
||||
if (picStartIndex + i < rawChunks.length) {
|
||||
images.add(
|
||||
VgaImage(
|
||||
width: width,
|
||||
height: height,
|
||||
pixels: rawChunks[picStartIndex + i],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return images;
|
||||
}
|
||||
|
||||
/// Parses MAPHEAD and GAMEMAPS to extract the raw level data.
|
||||
static List<WolfLevel> parseMaps(
|
||||
ByteData mapHead,
|
||||
@@ -260,6 +366,128 @@ class WLParser {
|
||||
}
|
||||
return rlewExpanded;
|
||||
}
|
||||
|
||||
/// Extracts decompressed VGA data chunks (UI, Fonts, Pictures)
|
||||
static List<Uint8List> _parseVgaRaw(
|
||||
ByteData vgaDict,
|
||||
ByteData vgaHead,
|
||||
ByteData vgaGraph,
|
||||
) {
|
||||
List<Uint8List> vgaChunks = [];
|
||||
|
||||
// 1. Parse the Huffman Dictionary from VGADICT
|
||||
List<_HuffmanNode> dict = [];
|
||||
for (int i = 0; i < 255; i++) {
|
||||
dict.add(
|
||||
_HuffmanNode(
|
||||
vgaDict.getUint16(i * 4, Endian.little),
|
||||
vgaDict.getUint16(i * 4 + 2, Endian.little),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// 2. Read VGAHEAD to get the offsets for VGAGRAPH
|
||||
List<int> offsets = [];
|
||||
int numChunks = vgaHead.lengthInBytes ~/ 3;
|
||||
int lastOffset = -1;
|
||||
|
||||
for (int i = 0; i < numChunks; i++) {
|
||||
int offset =
|
||||
vgaHead.getUint8(i * 3) |
|
||||
(vgaHead.getUint8(i * 3 + 1) << 8) |
|
||||
(vgaHead.getUint8(i * 3 + 2) << 16);
|
||||
|
||||
if (offset == 0x00FFFFFF) break;
|
||||
|
||||
// --- SAFETY FIX ---
|
||||
// 1. Offsets cannot point beyond the file length.
|
||||
// 2. Offsets must not go backward (this means we hit the junk padding).
|
||||
if (offset > vgaGraph.lengthInBytes || offset < lastOffset) {
|
||||
break;
|
||||
}
|
||||
|
||||
offsets.add(offset);
|
||||
lastOffset = offset;
|
||||
}
|
||||
|
||||
// 3. Decompress the chunks from VGAGRAPH
|
||||
for (int i = 0; i < offsets.length; i++) {
|
||||
int offset = offsets[i];
|
||||
|
||||
// --- BOUNDARY CHECK ---
|
||||
// If the offset is exactly at the end of the file, or doesn't leave
|
||||
// enough room for a 4-byte length header, it's an empty chunk.
|
||||
if (offset + 4 > vgaGraph.lengthInBytes) {
|
||||
vgaChunks.add(Uint8List(0));
|
||||
continue;
|
||||
}
|
||||
|
||||
// The first 4 bytes of a compressed chunk specify its decompressed size
|
||||
int expandedLength = vgaGraph.getUint32(offset, Endian.little);
|
||||
|
||||
if (expandedLength == 0) {
|
||||
vgaChunks.add(Uint8List(0));
|
||||
continue;
|
||||
}
|
||||
|
||||
// Decompress starting immediately after the 4-byte length header.
|
||||
// We pass the exact slice of bytes remaining so we don't read out of bounds.
|
||||
Uint8List expandedData = _expandHuffman(
|
||||
vgaGraph.buffer.asUint8List(
|
||||
vgaGraph.offsetInBytes + offset + 4,
|
||||
vgaGraph.lengthInBytes - (offset + 4),
|
||||
),
|
||||
dict,
|
||||
expandedLength,
|
||||
);
|
||||
|
||||
vgaChunks.add(expandedData);
|
||||
}
|
||||
|
||||
return vgaChunks;
|
||||
}
|
||||
|
||||
// --- ALGORITHM 3: HUFFMAN EXPANSION ---
|
||||
static Uint8List _expandHuffman(
|
||||
Uint8List compressed,
|
||||
List<_HuffmanNode> dict,
|
||||
int expandedLength,
|
||||
) {
|
||||
Uint8List expanded = Uint8List(expandedLength);
|
||||
|
||||
int outIdx = 0;
|
||||
int byteIdx = 0;
|
||||
int bitMask = 1;
|
||||
int currentNode = 254; // id Software's Huffman root is always node 254
|
||||
|
||||
while (outIdx < expandedLength && byteIdx < compressed.length) {
|
||||
// Read the current bit (LSB to MSB)
|
||||
int bit = (compressed[byteIdx] & bitMask) == 0 ? 0 : 1;
|
||||
|
||||
// Advance to the next bit/byte
|
||||
bitMask <<= 1;
|
||||
if (bitMask > 128) {
|
||||
bitMask = 1;
|
||||
byteIdx++;
|
||||
}
|
||||
|
||||
// Traverse the tree
|
||||
int nextVal = bit == 0 ? dict[currentNode].bit0 : dict[currentNode].bit1;
|
||||
|
||||
if (nextVal < 256) {
|
||||
// If the value is < 256, we've hit a leaf node (an actual character byte!)
|
||||
expanded[outIdx++] = nextVal;
|
||||
currentNode =
|
||||
254; // Reset to the root of the tree for the next character
|
||||
} else {
|
||||
// If the value is >= 256, it's a pointer to the next internal node.
|
||||
// Node indexes are offset by 256.
|
||||
currentNode = nextVal - 256;
|
||||
}
|
||||
}
|
||||
|
||||
return expanded;
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper class to parse and store redundant VSWAP header data
|
||||
@@ -278,3 +506,10 @@ class _VswapHeader {
|
||||
(i) => vswap.getUint32(6 + (i * 4), Endian.little),
|
||||
);
|
||||
}
|
||||
|
||||
class _HuffmanNode {
|
||||
final int bit0;
|
||||
final int bit1;
|
||||
|
||||
_HuffmanNode(this.bit0, this.bit1);
|
||||
}
|
||||
|
||||
@@ -4,6 +4,9 @@
|
||||
library;
|
||||
|
||||
export 'src/classes/game_version.dart' show GameVersion;
|
||||
export 'src/classes/image.dart' show VgaImage;
|
||||
export 'src/classes/sound.dart' show PcmSound;
|
||||
export 'src/classes/sprite.dart' hide Matrix;
|
||||
export 'src/classes/wolf_level.dart' show WolfLevel;
|
||||
export 'src/classes/wolfenstein_data.dart' show WolfensteinData;
|
||||
export 'src/wl_parser.dart' show WLParser;
|
||||
|
||||
@@ -19,7 +19,8 @@ dev_dependencies:
|
||||
flutter:
|
||||
uses-material-design: true
|
||||
assets:
|
||||
- assets/
|
||||
- assets/retail/
|
||||
- assets/shareware/
|
||||
|
||||
workspace:
|
||||
- packages/wolf_3d_data
|
||||
|
||||
Reference in New Issue
Block a user