Moving level loading into the package
Signed-off-by: Hans Kokx <hans.d.kokx@gmail.com>
This commit is contained in:
@@ -1,7 +1,5 @@
|
|||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:wolf_3d_data/wolf_3d_data.dart';
|
import 'package:wolf_3d_data/wolf_3d_data.dart';
|
||||||
import 'package:wolf_dart/features/map/wolf_level.dart';
|
|
||||||
import 'package:wolf_dart/features/map/wolf_map_parser.dart';
|
|
||||||
|
|
||||||
class WolfMap {
|
class WolfMap {
|
||||||
/// The fully parsed and decompressed levels from the game files.
|
/// The fully parsed and decompressed levels from the game files.
|
||||||
@@ -24,7 +22,7 @@ class WolfMap {
|
|||||||
final vswap = await rootBundle.load("assets/VSWAP.WL1");
|
final vswap = await rootBundle.load("assets/VSWAP.WL1");
|
||||||
|
|
||||||
// 2. Parse the data using the parser we just built
|
// 2. Parse the data using the parser we just built
|
||||||
final parsedLevels = WolfMapParser.parseMaps(
|
final parsedLevels = WLParser.parseMaps(
|
||||||
mapHead,
|
mapHead,
|
||||||
gameMaps,
|
gameMaps,
|
||||||
isShareware: true,
|
isShareware: true,
|
||||||
@@ -48,7 +46,7 @@ class WolfMap {
|
|||||||
final vswap = await rootBundle.load("assets/VSWAP.WL6");
|
final vswap = await rootBundle.load("assets/VSWAP.WL6");
|
||||||
|
|
||||||
// 2. Parse the data using the parser we just built
|
// 2. Parse the data using the parser we just built
|
||||||
final parsedLevels = WolfMapParser.parseMaps(mapHead, gameMaps);
|
final parsedLevels = WLParser.parseMaps(mapHead, gameMaps);
|
||||||
final parsedTextures = WLParser.parseWalls(vswap);
|
final parsedTextures = WLParser.parseWalls(vswap);
|
||||||
final parsedSprites = WLParser.parseSprites(vswap);
|
final parsedSprites = WLParser.parseSprites(vswap);
|
||||||
|
|
||||||
|
|||||||
@@ -1,187 +0,0 @@
|
|||||||
import 'dart:convert';
|
|
||||||
import 'dart:typed_data';
|
|
||||||
|
|
||||||
import 'package:wolf_dart/features/entities/map_objects.dart';
|
|
||||||
import 'package:wolf_dart/features/map/wolf_level.dart';
|
|
||||||
|
|
||||||
abstract class WolfMapParser {
|
|
||||||
/// Parses MAPHEAD and GAMEMAPS to extract the raw level data.
|
|
||||||
static List<WolfLevel> parseMaps(
|
|
||||||
ByteData mapHead,
|
|
||||||
ByteData gameMaps, {
|
|
||||||
bool isShareware = true,
|
|
||||||
}) {
|
|
||||||
List<WolfLevel> levels = [];
|
|
||||||
|
|
||||||
// 1. READ MAPHEAD
|
|
||||||
// The very first 16-bit word in MAPHEAD is the RLEW tag (usually 0xABCD)
|
|
||||||
// We will need this later for decompression!
|
|
||||||
int rlewTag = mapHead.getUint16(0, Endian.little);
|
|
||||||
|
|
||||||
// MAPHEAD contains up to 100 levels.
|
|
||||||
// Starting at byte 2, there are 100 32-bit integers representing
|
|
||||||
// the byte offset of each level's header inside GAMEMAPS.
|
|
||||||
for (int i = 0; i < 100; i++) {
|
|
||||||
int mapOffset = mapHead.getUint32(2 + (i * 4), Endian.little);
|
|
||||||
|
|
||||||
// An offset of 0 means the level doesn't exist (end of the list)
|
|
||||||
if (mapOffset == 0) continue;
|
|
||||||
|
|
||||||
// 2. READ GAMEMAPS HEADER
|
|
||||||
// Jump to the offset in GAMEMAPS to read the 38-byte Level Header
|
|
||||||
|
|
||||||
// Pointers to the compressed data for the 3 planes (Walls, Objects, Extra)
|
|
||||||
int plane0Offset = gameMaps.getUint32(mapOffset + 0, Endian.little);
|
|
||||||
int plane1Offset = gameMaps.getUint32(mapOffset + 4, Endian.little);
|
|
||||||
// Plane 2 (offset + 8) is usually unused in standard Wolf3D
|
|
||||||
|
|
||||||
// Lengths of the compressed data for each plane
|
|
||||||
int plane0Length = gameMaps.getUint16(mapOffset + 12, Endian.little);
|
|
||||||
int plane1Length = gameMaps.getUint16(mapOffset + 14, Endian.little);
|
|
||||||
|
|
||||||
// Map Name (16 bytes of ASCII text)
|
|
||||||
List<int> nameBytes = [];
|
|
||||||
for (int n = 0; n < 16; n++) {
|
|
||||||
int charCode = gameMaps.getUint8(mapOffset + 22 + n);
|
|
||||||
if (charCode == 0) break; // Null terminator
|
|
||||||
nameBytes.add(charCode);
|
|
||||||
}
|
|
||||||
String name = ascii.decode(nameBytes);
|
|
||||||
|
|
||||||
// 3. EXTRACT AND DECOMPRESS BOTH PLANES
|
|
||||||
|
|
||||||
// --- PLANE 0: WALLS ---
|
|
||||||
final compressedWallData = gameMaps.buffer.asUint8List(
|
|
||||||
plane0Offset,
|
|
||||||
plane0Length,
|
|
||||||
);
|
|
||||||
Uint16List carmackExpandedWalls = _expandCarmack(compressedWallData);
|
|
||||||
List<int> flatWallGrid = _expandRlew(carmackExpandedWalls, rlewTag);
|
|
||||||
|
|
||||||
// --- PLANE 1: OBJECTS (NEW) ---
|
|
||||||
final compressedObjectData = gameMaps.buffer.asUint8List(
|
|
||||||
plane1Offset,
|
|
||||||
plane1Length,
|
|
||||||
);
|
|
||||||
Uint16List carmackExpandedObjects = _expandCarmack(compressedObjectData);
|
|
||||||
List<int> flatObjectGrid = _expandRlew(carmackExpandedObjects, rlewTag);
|
|
||||||
|
|
||||||
for (int i = 0; i < flatObjectGrid.length; i++) {
|
|
||||||
int id = flatObjectGrid[i];
|
|
||||||
|
|
||||||
// Handle the 'secret' pushwalls (Logic check)
|
|
||||||
if (id == MapObject.pushwallTrigger) {
|
|
||||||
// In Wolf3D, ID 98 means the wall at this same index in Plane 0 is pushable.
|
|
||||||
// You might want to mark this in your engine state.
|
|
||||||
}
|
|
||||||
|
|
||||||
// Filter out invalid IDs for Shareware to prevent crashes
|
|
||||||
if (isShareware && !MapObject.isSharewareCompatible(id)) {
|
|
||||||
flatObjectGrid[i] = 0; // Turn unknown objects into empty space
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
List<List<int>> wallGrid = [];
|
|
||||||
List<List<int>> objectGrid = [];
|
|
||||||
|
|
||||||
for (int y = 0; y < 64; y++) {
|
|
||||||
List<int> wallRow = [];
|
|
||||||
List<int> objectRow = [];
|
|
||||||
for (int x = 0; x < 64; x++) {
|
|
||||||
wallRow.add(flatWallGrid[y * 64 + x]);
|
|
||||||
objectRow.add(flatObjectGrid[y * 64 + x]);
|
|
||||||
}
|
|
||||||
wallGrid.add(wallRow);
|
|
||||||
objectGrid.add(objectRow);
|
|
||||||
}
|
|
||||||
|
|
||||||
levels.add(
|
|
||||||
WolfLevel(
|
|
||||||
name: name,
|
|
||||||
wallGrid: wallGrid,
|
|
||||||
objectGrid: objectGrid,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return levels;
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- ALGORITHM 1: CARMACK EXPANSION ---
|
|
||||||
static Uint16List _expandCarmack(Uint8List compressed) {
|
|
||||||
ByteData data = ByteData.sublistView(compressed);
|
|
||||||
|
|
||||||
// The first 16-bit word is the total length of the expanded data in BYTES.
|
|
||||||
int expandedLengthBytes = data.getUint16(0, Endian.little);
|
|
||||||
int expandedLengthWords = expandedLengthBytes ~/ 2;
|
|
||||||
Uint16List expanded = Uint16List(expandedLengthWords);
|
|
||||||
|
|
||||||
int inIdx = 2; // Skip the length word we just read
|
|
||||||
int outIdx = 0;
|
|
||||||
|
|
||||||
while (outIdx < expandedLengthWords && inIdx < compressed.length) {
|
|
||||||
int word = data.getUint16(inIdx, Endian.little);
|
|
||||||
inIdx += 2;
|
|
||||||
|
|
||||||
int highByte = word >> 8;
|
|
||||||
int lowByte = word & 0xFF;
|
|
||||||
|
|
||||||
// 0xA7 and 0xA8 are the Carmack Pointer Tags
|
|
||||||
if (highByte == 0xA7 || highByte == 0xA8) {
|
|
||||||
if (lowByte == 0) {
|
|
||||||
// Exception Rule: If the length (lowByte) is 0, it's not a pointer.
|
|
||||||
// It's literally just the tag byte followed by another byte.
|
|
||||||
int nextByte = data.getUint8(inIdx++);
|
|
||||||
expanded[outIdx++] = (nextByte << 8) | highByte;
|
|
||||||
} else if (highByte == 0xA7) {
|
|
||||||
// 0xA7 = Near Pointer (look back a few spaces)
|
|
||||||
int offset = data.getUint8(inIdx++);
|
|
||||||
int copyFrom = outIdx - offset;
|
|
||||||
for (int i = 0; i < lowByte; i++) {
|
|
||||||
expanded[outIdx++] = expanded[copyFrom++];
|
|
||||||
}
|
|
||||||
} else if (highByte == 0xA8) {
|
|
||||||
// 0xA8 = Far Pointer (absolute offset from the very beginning)
|
|
||||||
int offset = data.getUint16(inIdx, Endian.little);
|
|
||||||
inIdx += 2;
|
|
||||||
for (int i = 0; i < lowByte; i++) {
|
|
||||||
expanded[outIdx++] = expanded[offset++];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Normal, uncompressed word
|
|
||||||
expanded[outIdx++] = word;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return expanded;
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- ALGORITHM 2: RLEW EXPANSION ---
|
|
||||||
static List<int> _expandRlew(Uint16List carmackExpanded, int rlewTag) {
|
|
||||||
// The first word is the expanded length in BYTES
|
|
||||||
int expandedLengthBytes = carmackExpanded[0];
|
|
||||||
int expandedLengthWords = expandedLengthBytes ~/ 2;
|
|
||||||
List<int> rlewExpanded = List<int>.filled(expandedLengthWords, 0);
|
|
||||||
|
|
||||||
int inIdx = 1; // Skip the length word
|
|
||||||
int outIdx = 0;
|
|
||||||
|
|
||||||
while (outIdx < expandedLengthWords && inIdx < carmackExpanded.length) {
|
|
||||||
int word = carmackExpanded[inIdx++];
|
|
||||||
|
|
||||||
if (word == rlewTag) {
|
|
||||||
// We found an RLEW tag!
|
|
||||||
// The next word is the count, the word after that is the value.
|
|
||||||
int count = carmackExpanded[inIdx++];
|
|
||||||
int value = carmackExpanded[inIdx++];
|
|
||||||
for (int i = 0; i < count; i++) {
|
|
||||||
rlewExpanded[outIdx++] = value;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Normal word
|
|
||||||
rlewExpanded[outIdx++] = word;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return rlewExpanded;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,30 +1,4 @@
|
|||||||
# This file configures the static analysis results for your project (errors,
|
include: package:flutter_lints/flutter.yaml
|
||||||
# warnings, and lints).
|
|
||||||
#
|
|
||||||
# This enables the 'recommended' set of lints from `package:lints`.
|
|
||||||
# This set helps identify many issues that may lead to problems when running
|
|
||||||
# or consuming Dart code, and enforces writing Dart using a single, idiomatic
|
|
||||||
# style and format.
|
|
||||||
#
|
|
||||||
# If you want a smaller set of lints you can change this to specify
|
|
||||||
# 'package:lints/core.yaml'. These are just the most critical lints
|
|
||||||
# (the recommended set includes the core lints).
|
|
||||||
# The core lints are also what is used by pub.dev for scoring packages.
|
|
||||||
|
|
||||||
include: package:lints/recommended.yaml
|
formatter:
|
||||||
|
trailing_commas: preserve
|
||||||
# Uncomment the following section to specify additional rules.
|
|
||||||
|
|
||||||
# linter:
|
|
||||||
# rules:
|
|
||||||
# - camel_case_types
|
|
||||||
|
|
||||||
# analyzer:
|
|
||||||
# exclude:
|
|
||||||
# - path/to/excluded/files/**
|
|
||||||
|
|
||||||
# For more information about the core and recommended set of lints, see
|
|
||||||
# https://dart.dev/go/core-lints
|
|
||||||
|
|
||||||
# For additional information about configuring this file, see
|
|
||||||
# https://dart.dev/guides/language/analysis-options
|
|
||||||
|
|||||||
11
packages/wolf_3d_data/lib/src/classes/game_version.dart
Normal file
11
packages/wolf_3d_data/lib/src/classes/game_version.dart
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
enum GameVersion {
|
||||||
|
shareware("WL1"),
|
||||||
|
retail("WL6"),
|
||||||
|
spearOfDestinyDemo("SDM"),
|
||||||
|
spearOfDestiny("SOD"),
|
||||||
|
;
|
||||||
|
|
||||||
|
final String fileExtension;
|
||||||
|
|
||||||
|
const GameVersion(this.fileExtension);
|
||||||
|
}
|
||||||
@@ -5,7 +5,7 @@ class WolfLevel {
|
|||||||
final Sprite wallGrid;
|
final Sprite wallGrid;
|
||||||
final Sprite objectGrid;
|
final Sprite objectGrid;
|
||||||
|
|
||||||
WolfLevel({
|
const WolfLevel({
|
||||||
required this.name,
|
required this.name,
|
||||||
required this.wallGrid,
|
required this.wallGrid,
|
||||||
required this.objectGrid,
|
required this.objectGrid,
|
||||||
81
packages/wolf_3d_data/lib/src/classes/wolfenstein_data.dart
Normal file
81
packages/wolf_3d_data/lib/src/classes/wolfenstein_data.dart
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
import 'dart:typed_data';
|
||||||
|
|
||||||
|
import 'package:wolf_3d_data/src/wl_parser.dart';
|
||||||
|
|
||||||
|
import 'game_version.dart';
|
||||||
|
import 'sprite.dart';
|
||||||
|
import 'wolf_level.dart';
|
||||||
|
|
||||||
|
class WolfensteinData {
|
||||||
|
final GameVersion version;
|
||||||
|
|
||||||
|
// 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._({
|
||||||
|
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]!;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,8 @@
|
|||||||
|
import 'dart:convert';
|
||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
|
|
||||||
|
import 'package:wolf_3d_data/src/classes/wolf_level.dart';
|
||||||
|
|
||||||
import 'classes/sprite.dart';
|
import 'classes/sprite.dart';
|
||||||
|
|
||||||
class WLParser {
|
class WLParser {
|
||||||
@@ -110,6 +113,153 @@ class WLParser {
|
|||||||
cmdOffset += 6; // Move to the next 6-byte instruction
|
cmdOffset += 6; // Move to the next 6-byte instruction
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Parses MAPHEAD and GAMEMAPS to extract the raw level data.
|
||||||
|
static List<WolfLevel> parseMaps(
|
||||||
|
ByteData mapHead,
|
||||||
|
ByteData gameMaps, {
|
||||||
|
bool isShareware = true,
|
||||||
|
}) {
|
||||||
|
List<WolfLevel> levels = [];
|
||||||
|
int rlewTag = mapHead.getUint16(0, Endian.little);
|
||||||
|
|
||||||
|
for (int i = 0; i < 100; i++) {
|
||||||
|
int mapOffset = mapHead.getUint32(2 + (i * 4), Endian.little);
|
||||||
|
if (mapOffset == 0) continue;
|
||||||
|
|
||||||
|
int plane0Offset = gameMaps.getUint32(mapOffset + 0, Endian.little);
|
||||||
|
int plane1Offset = gameMaps.getUint32(mapOffset + 4, Endian.little);
|
||||||
|
|
||||||
|
int plane0Length = gameMaps.getUint16(mapOffset + 12, Endian.little);
|
||||||
|
int plane1Length = gameMaps.getUint16(mapOffset + 14, Endian.little);
|
||||||
|
|
||||||
|
List<int> nameBytes = [];
|
||||||
|
for (int n = 0; n < 16; n++) {
|
||||||
|
int charCode = gameMaps.getUint8(mapOffset + 22 + n);
|
||||||
|
if (charCode == 0) break;
|
||||||
|
nameBytes.add(charCode);
|
||||||
|
}
|
||||||
|
String name = ascii.decode(nameBytes);
|
||||||
|
|
||||||
|
// --- DECOMPRESS PLANES ---
|
||||||
|
final compressedWallData = gameMaps.buffer.asUint8List(
|
||||||
|
plane0Offset,
|
||||||
|
plane0Length,
|
||||||
|
);
|
||||||
|
Uint16List carmackExpandedWalls = _expandCarmack(compressedWallData);
|
||||||
|
List<int> flatWallGrid = _expandRlew(carmackExpandedWalls, rlewTag);
|
||||||
|
|
||||||
|
final compressedObjectData = gameMaps.buffer.asUint8List(
|
||||||
|
plane1Offset,
|
||||||
|
plane1Length,
|
||||||
|
);
|
||||||
|
Uint16List carmackExpandedObjects = _expandCarmack(compressedObjectData);
|
||||||
|
List<int> flatObjectGrid = _expandRlew(carmackExpandedObjects, rlewTag);
|
||||||
|
|
||||||
|
// --- BUILD GRIDS ---
|
||||||
|
List<List<int>> wallGrid = [];
|
||||||
|
List<List<int>> objectGrid = [];
|
||||||
|
|
||||||
|
for (int y = 0; y < 64; y++) {
|
||||||
|
List<int> wallRow = [];
|
||||||
|
List<int> objectRow = [];
|
||||||
|
for (int x = 0; x < 64; x++) {
|
||||||
|
wallRow.add(flatWallGrid[y * 64 + x]);
|
||||||
|
objectRow.add(flatObjectGrid[y * 64 + x]);
|
||||||
|
}
|
||||||
|
wallGrid.add(wallRow);
|
||||||
|
objectGrid.add(objectRow);
|
||||||
|
}
|
||||||
|
|
||||||
|
levels.add(
|
||||||
|
WolfLevel(
|
||||||
|
name: name,
|
||||||
|
wallGrid: wallGrid,
|
||||||
|
objectGrid: objectGrid,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return levels;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- ALGORITHM 1: CARMACK EXPANSION ---
|
||||||
|
static Uint16List _expandCarmack(Uint8List compressed) {
|
||||||
|
ByteData data = ByteData.sublistView(compressed);
|
||||||
|
|
||||||
|
// The first 16-bit word is the total length of the expanded data in BYTES.
|
||||||
|
int expandedLengthBytes = data.getUint16(0, Endian.little);
|
||||||
|
int expandedLengthWords = expandedLengthBytes ~/ 2;
|
||||||
|
Uint16List expanded = Uint16List(expandedLengthWords);
|
||||||
|
|
||||||
|
int inIdx = 2; // Skip the length word we just read
|
||||||
|
int outIdx = 0;
|
||||||
|
|
||||||
|
while (outIdx < expandedLengthWords && inIdx < compressed.length) {
|
||||||
|
int word = data.getUint16(inIdx, Endian.little);
|
||||||
|
inIdx += 2;
|
||||||
|
|
||||||
|
int highByte = word >> 8;
|
||||||
|
int lowByte = word & 0xFF;
|
||||||
|
|
||||||
|
// 0xA7 and 0xA8 are the Carmack Pointer Tags
|
||||||
|
if (highByte == 0xA7 || highByte == 0xA8) {
|
||||||
|
if (lowByte == 0) {
|
||||||
|
// Exception Rule: If the length (lowByte) is 0, it's not a pointer.
|
||||||
|
// It's literally just the tag byte followed by another byte.
|
||||||
|
int nextByte = data.getUint8(inIdx++);
|
||||||
|
expanded[outIdx++] = (nextByte << 8) | highByte;
|
||||||
|
} else if (highByte == 0xA7) {
|
||||||
|
// 0xA7 = Near Pointer (look back a few spaces)
|
||||||
|
int offset = data.getUint8(inIdx++);
|
||||||
|
int copyFrom = outIdx - offset;
|
||||||
|
for (int i = 0; i < lowByte; i++) {
|
||||||
|
expanded[outIdx++] = expanded[copyFrom++];
|
||||||
|
}
|
||||||
|
} else if (highByte == 0xA8) {
|
||||||
|
// 0xA8 = Far Pointer (absolute offset from the very beginning)
|
||||||
|
int offset = data.getUint16(inIdx, Endian.little);
|
||||||
|
inIdx += 2;
|
||||||
|
for (int i = 0; i < lowByte; i++) {
|
||||||
|
expanded[outIdx++] = expanded[offset++];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Normal, uncompressed word
|
||||||
|
expanded[outIdx++] = word;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return expanded;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- ALGORITHM 2: RLEW EXPANSION ---
|
||||||
|
static List<int> _expandRlew(Uint16List carmackExpanded, int rlewTag) {
|
||||||
|
// The first word is the expanded length in BYTES
|
||||||
|
int expandedLengthBytes = carmackExpanded[0];
|
||||||
|
int expandedLengthWords = expandedLengthBytes ~/ 2;
|
||||||
|
List<int> rlewExpanded = List<int>.filled(expandedLengthWords, 0);
|
||||||
|
|
||||||
|
int inIdx = 1; // Skip the length word
|
||||||
|
int outIdx = 0;
|
||||||
|
|
||||||
|
while (outIdx < expandedLengthWords && inIdx < carmackExpanded.length) {
|
||||||
|
int word = carmackExpanded[inIdx++];
|
||||||
|
|
||||||
|
if (word == rlewTag) {
|
||||||
|
// We found an RLEW tag!
|
||||||
|
// The next word is the count, the word after that is the value.
|
||||||
|
int count = carmackExpanded[inIdx++];
|
||||||
|
int value = carmackExpanded[inIdx++];
|
||||||
|
for (int i = 0; i < count; i++) {
|
||||||
|
rlewExpanded[outIdx++] = value;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Normal word
|
||||||
|
rlewExpanded[outIdx++] = word;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return rlewExpanded;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Helper class to parse and store redundant VSWAP header data
|
/// Helper class to parse and store redundant VSWAP header data
|
||||||
|
|||||||
@@ -3,5 +3,7 @@
|
|||||||
/// More dartdocs go here.
|
/// More dartdocs go here.
|
||||||
library;
|
library;
|
||||||
|
|
||||||
|
export 'src/classes/game_version.dart' show GameVersion;
|
||||||
export 'src/classes/sprite.dart' hide Matrix;
|
export 'src/classes/sprite.dart' hide Matrix;
|
||||||
|
export 'src/classes/wolf_level.dart' show WolfLevel;
|
||||||
export 'src/wl_parser.dart' show WLParser;
|
export 'src/wl_parser.dart' show WLParser;
|
||||||
|
|||||||
Reference in New Issue
Block a user