From 4f9182507afb365638b33341fb797d82c3a50df0 Mon Sep 17 00:00:00 2001 From: WinterGrascph Date: Sat, 7 May 2016 20:53:35 +0200 Subject: DM: Implement Dungeon.dat file parsing, add relevant structures --- engines/dm/dungeonman.cpp | 227 ++++++++++++++++++++++++++++++++++++++++++++-- engines/dm/dungeonman.h | 125 ++++++++++++++++++++++++- engines/dm/gfx.cpp | 2 +- 3 files changed, 341 insertions(+), 13 deletions(-) diff --git a/engines/dm/dungeonman.cpp b/engines/dm/dungeonman.cpp index 292ca023e7..e33895db5a 100644 --- a/engines/dm/dungeonman.cpp +++ b/engines/dm/dungeonman.cpp @@ -1,20 +1,49 @@ #include "dungeonman.h" #include "common/file.h" +#include "common/memstream.h" + namespace DM { -DungeonMan::DungeonMan(DMEngine *dmEngine) : _vm(dmEngine), _dungeonDataSize(0), _dungeonData(NULL) {} +// TODO: refactor direction into a class +int8 dirIntoStepCountEast[4] = {0 /* North */, 1 /* East */, 0 /* West */, -1 /* South */}; +int8 dirIntoStepCountNorth[4] = {-1 /* North */, 0 /* East */, 1 /* West */, 0 /* South */}; + +void turnDirRight(direction &dir) { dir = (direction)((dir + 1) & 3); } + + +} + +using namespace DM; + + +void DungeonMan::mapCoordsAfterRelMovement(direction dir, uint16 stepsForward, uint16 stepsRight, uint16 &posX, uint16 &posY) { + posX += dirIntoStepCountEast[dir] * stepsForward; + posY += dirIntoStepCountNorth[dir] * stepsForward; + turnDirRight(dir); + posX += dirIntoStepCountEast[dir] * stepsRight; + posY += dirIntoStepCountNorth[dir] * stepsRight; +} + +DungeonMan::DungeonMan(DMEngine *dmEngine) : _vm(dmEngine), _rawDunFileData(NULL), _maps(NULL), _rawMapData(NULL) {} DungeonMan::~DungeonMan() { - delete[] _dungeonData; + delete[] _rawDunFileData; + delete[] _maps; + delete[] _dunData.dunMapsFirstColumnIndex; + delete[] _dunData.dunColumnsCumulativeSquareThingCount; + delete[] _dunData.squareFirstThings; + delete[] _dunData.dunTextData; + delete[] _dunData.dungeonMapData; } -void DungeonMan::loadDungeonFile() { +void DungeonMan::decompressDungeonFile() { Common::File f; f.open("Dungeon.dat"); if (f.readUint16BE() == 0x8104) { - _dungeonDataSize = f.readUint32BE(); - _dungeonData = new byte[_dungeonDataSize]; + _rawDunFileDataSize = f.readUint32BE(); + if (_rawDunFileData) delete[] _rawDunFileData; + _rawDunFileData = new byte[_rawDunFileDataSize]; f.readUint16BE(); byte common[4]; for (uint16 i = 0; i < 4; ++i) @@ -29,7 +58,7 @@ void DungeonMan::loadDungeonFile() { uint16 wordBuff = f.readUint16BE(); uint8 bitsLeftInByte = 8; byte byteBuff = f.readByte(); - while (uncompIndex < _dungeonDataSize) { + while (uncompIndex < _rawDunFileDataSize) { while (bitsUsedInWord != 0) { uint8 shiftVal; if (f.eos()) { @@ -49,18 +78,198 @@ void DungeonMan::loadDungeonFile() { bitsUsedInWord -= shiftVal; } if (((wordBuff >> 15) & 1) == 0) { - _dungeonData[uncompIndex++] = common[(wordBuff >> 13) & 3]; + _rawDunFileData[uncompIndex++] = common[(wordBuff >> 13) & 3]; bitsUsedInWord += 3; } else if (((wordBuff >> 14) & 3) == 2) { - _dungeonData[uncompIndex++] = lessCommon[(wordBuff >> 10) & 15]; + _rawDunFileData[uncompIndex++] = lessCommon[(wordBuff >> 10) & 15]; bitsUsedInWord += 6; } else if (((wordBuff >> 14) & 3) == 3) { - _dungeonData[uncompIndex++] = (wordBuff >> 6) & 255; + _rawDunFileData[uncompIndex++] = (wordBuff >> 6) & 255; bitsUsedInWord += 10; } } + } else { + // TODO: if the dungeon is uncompressed, read it here } f.close(); } + +uint8 gAdditionalThingCounts[16] = { + 0, /* Door */ + 0, /* Teleporter */ + 0, /* Text String */ + 0, /* Sensor */ + 75, /* Group */ + 100, /* Weapon */ + 120, /* Armour */ + 0, /* Scroll */ + 5, /* Potion */ + 0, /* Container */ + 140, /* Junk */ + 0, /* Unused */ + 0, /* Unused */ + 0, /* Unused */ + 60, /* Projectile */ + 50 /* Explosion */ +}; // @ G0236_auc_Graphic559_AdditionalThingCounts + +// TODO: refactor THINGS into classes +unsigned char gThingDataByteCount[16] = { + 4, /* Door */ + 6, /* Teleporter */ + 4, /* Text String */ + 8, /* Sensor */ + 16, /* Group */ + 4, /* Weapon */ + 4, /* Armour */ + 4, /* Scroll */ + 4, /* Potion */ + 8, /* Container */ + 4, /* Junk */ + 0, /* Unused */ + 0, /* Unused */ + 0, /* Unused */ + 8, /* Projectile */ + 4 /* Explosion */ +}; // @ G0235_auc_Graphic559_ThingDataByteCount + +const Thing Thing::specThingNone(0, 0, 0); + + +void DungeonMan::loadDungeonFile() { + if (_messages.newGame) + decompressDungeonFile(); + + Common::MemoryReadStream dunDataStream(_rawDunFileData, _fileHeader.rawMapDataSize, DisposeAfterUse::NO); + + // initialize _fileHeader + _fileHeader.dungeonId = _fileHeader.ornamentRandomSeed = dunDataStream.readUint16BE(); + _fileHeader.rawMapDataSize = dunDataStream.readUint16BE(); + _fileHeader.mapCount = dunDataStream.readByte(); + dunDataStream.readByte(); // discard 1 byte + _fileHeader.textDataWordCount = dunDataStream.readUint16BE(); + uint16 partyPosition = dunDataStream.readUint16BE(); + _fileHeader.partyStartDir = (direction)((partyPosition >> 10) & 3); + _fileHeader.partyStartPosY = (partyPosition >> 5) & 0x1F; + _fileHeader.partyStartPosX = (partyPosition >> 0) & 0x1F; + _fileHeader.squareFirstThingCount = dunDataStream.readUint16BE(); + for (uint16 i = 0; i < kThingTypeTotal; ++i) + _fileHeader.thingCounts[i] = dunDataStream.readUint16BE(); + + // init party position and mapindex + if (_messages.newGame) { + _dunData.partyDir = _fileHeader.partyStartDir; + _dunData.partyPosX = _fileHeader.partyStartPosX; + _dunData.partyPosY = _fileHeader.partyStartPosY; + _dunData.currMapIndex = 0; + } + + // load map data + if (_maps) delete[] _maps; + + _maps = new Map[_fileHeader.mapCount]; + for (uint16 i = 0; i < _fileHeader.mapCount; ++i) { + _maps[i].rawDunDataOffset = dunDataStream.readUint16BE(); + dunDataStream.readUint32BE(); // discard 4 bytes + _maps[i].offsetMapX = dunDataStream.readByte(); + _maps[i].offsetMapY = dunDataStream.readByte(); + + uint16 tmp = dunDataStream.readUint16BE(); + _maps[i].height = tmp >> 11; + _maps[i].width = (tmp >> 6) & 0x1F; + _maps[i].level = tmp & 0x1F; // Only used in DMII + + tmp = dunDataStream.readUint16BE(); + _maps[i].randFloorOrnCount = tmp >> 12; + _maps[i].floorOrnCount = (tmp >> 8) & 0xF; + _maps[i].randWallOrnCount = (tmp >> 4) & 0xF; + _maps[i].wallOrnCount = tmp & 0xF; + + tmp = dunDataStream.readUint16BE(); + _maps[i].difficulty = tmp >> 12; + _maps[i].creatureTypeCount = (tmp >> 4) & 0xF; + _maps[i].doorOrnCount = tmp & 0xF; + + tmp = dunDataStream.readUint16BE(); + _maps[i].doorSet1 = (tmp >> 12) & 0xF; + _maps[i].doorSet0 = (tmp >> 8) & 0xF; + _maps[i].wallSet = (tmp >> 4) & 0xF; + _maps[i].floorSet = tmp & 0xF; + } + + // TODO: ??? is this - begin + if (_dunData.dunMapsFirstColumnIndex) delete[] _dunData.dunMapsFirstColumnIndex; + + _dunData.dunMapsFirstColumnIndex = new uint16[_fileHeader.mapCount]; + uint16 columCount = 0; + for (uint16 i = 0; i < _fileHeader.mapCount; ++i) { + _dunData.dunMapsFirstColumnIndex[i] = columCount; + columCount += _maps[i].width + 1; + } + _dunData.dunColumCount = columCount; + // TODO: ??? is this - end + + if (_messages.newGame) // TODO: what purpose does this serve? + _fileHeader.squareFirstThingCount += 300; + + // TODO: ??? is this - begin + if (_dunData.dunColumnsCumulativeSquareThingCount) + delete[] _dunData.dunColumnsCumulativeSquareThingCount; + _dunData.dunColumnsCumulativeSquareThingCount = new uint16[columCount]; + for (uint16 i = 0; i < columCount; ++i) + _dunData.dunColumnsCumulativeSquareThingCount[i] = dunDataStream.readUint16BE(); + // TODO: ??? is this - end + + // TODO: ??? is this - begin + if (_dunData.squareFirstThings) + delete[] _dunData.squareFirstThings; + _dunData.squareFirstThings = new Thing[_fileHeader.squareFirstThingCount]; + for (uint16 i = 0; i < _fileHeader.squareFirstThingCount; ++i) { + uint16 tmp = dunDataStream.readUint16BE(); + _dunData.squareFirstThings[i].cell = tmp >> 14; + _dunData.squareFirstThings[i].type = (tmp >> 10) & 0xF; + _dunData.squareFirstThings[i].index = tmp & 0x1FF; + } + if (_messages.newGame) + for (uint16 i = 0; i < 300; ++i) + _dunData.squareFirstThings[i] = Thing::specThingNone; + + // TODO: ??? is this - end + + // load text data + if (_dunData.dunTextData) + delete[] _dunData.dunTextData; + _dunData.dunTextData = new uint16[_fileHeader.textDataWordCount]; + for (uint16 i = 0; i < _fileHeader.textDataWordCount; ++i) + _dunData.dunTextData[i] = dunDataStream.readUint16BE(); + + // TODO: ??? what this + if (_messages.newGame) + _dunData.eventMaximumCount = 100; + + // load 'Things' + // TODO: implement load things + // this is a temporary workaround to seek to raw map data + for (uint16 i = 0; i < kThingTypeTotal; ++i) + dunDataStream.skip(_fileHeader.thingCounts[i] * gThingDataByteCount[i]); + + _rawMapData = _rawDunFileData + dunDataStream.pos(); + + if (_dunData.dungeonMapData) delete[] _dunData.dungeonMapData; + + if (_messages.restartGameRequest) { + uint8 mapCount = _fileHeader.mapCount; + _dunData.dungeonMapData = new byte**[_dunData.dunColumCount + mapCount]; + byte **colFirstSquares = _dunData.dungeonMapData[mapCount]; + for (uint8 i = 0; i < mapCount; ++i) { + _dunData.dungeonMapData[i] = colFirstSquares; + byte *square = _rawMapData + _maps[i].rawDunDataOffset; + *colFirstSquares++ = square; + for (uint16 w = 0; w <= _maps[i].width; ++w) { + square += _maps[w].height + 1; + *colFirstSquares++ = square; + } + } + } } \ No newline at end of file diff --git a/engines/dm/dungeonman.h b/engines/dm/dungeonman.h index b820bb2b58..b26d56fc4c 100644 --- a/engines/dm/dungeonman.h +++ b/engines/dm/dungeonman.h @@ -5,16 +5,135 @@ namespace DM { +class DungeonMan; + +enum ThingType { + kPartyThingType = -1, // @ CM1_THING_TYPE_PARTY, special value + kDoorThingType = 0, + kTeleporterThingType = 1, + kTextstringType = 2, + kSensorThingType = 3, + kGroupThingType = 4, + kWeaponThingType = 5, + kArmourThingType = 6, + kScrollThingType = 7, + kPotionThingType = 8, + kContainerThingType = 9, + kJunkThingType = 10, + kProjectileThingType = 14, + kExplosionThingType = 15, + kThingTypeTotal = 16 // +1 than the last +}; // @ C[00..15]_THING_TYPE_... + + +class DungeonFileHeader { + friend class DungeonMan; + + uint16 dungeonId; // @ G0526_ui_DungeonID + // equal to dungeonId + uint16 ornamentRandomSeed; + uint32 rawMapDataSize; + uint8 mapCount; + uint16 textDataWordCount; + direction partyStartDir; // @ InitialPartyLocation + uint16 partyStartPosX, partyStartPosY; + uint16 squareFirstThingCount; // @ SquareFirstThingCount + uint16 thingCounts[16]; // @ ThingCount[16] +}; // @ DUNGEON_HEADER + +class Thing { + friend class DungeonMan; + + static const Thing specThingNone; + + Thing(uint8 cell, uint8 type, uint8 index) : cell(cell), type(type), index(index) {} + Thing() {} + + uint8 cell; + uint8 type; + uint8 index; +}; // @ THING + +class DungeonData { + friend class DungeonMan; + + direction partyDir; // @ G0308_i_PartyDirection + uint16 partyPosX; // @ G0306_i_PartyMapX + uint16 partyPosY; // @ G0307_i_PartyMapY + uint8 currMapIndex; // @ G0309_i_PartyMapIndex + + // I have no idea the heck is this + uint16 *dunMapsFirstColumnIndex = NULL; // @ G0281_pui_DungeonMapsFirstColumnIndex + uint16 dunColumCount; // @ G0282_ui_DungeonColumnCount + + // I have no idea the heck is this + uint16 *dunColumnsCumulativeSquareThingCount = NULL; // @ G0280_pui_DungeonColumnsCumulativeSquareThingCount + Thing *squareFirstThings = NULL; // @ G0283_pT_SquareFirstThings + uint16 *dunTextData = NULL; // @ G0260_pui_DungeonTextData + + byte *rawThingData[16] = {NULL}; // @ G0284_apuc_ThingData + + byte ***dungeonMapData = NULL; // @ G0279_pppuc_DungeonMapData + + uint16 eventMaximumCount; // @ G0369_ui_EventMaximumCount +}; // @ AGGREGATE + +struct Messages { + friend class DungeonMan; + +private: + bool newGame = true; // @ G0298_B_NewGame + bool restartGameRequest = false; // @ G0523_B_RestartGameRequested +}; // @ AGGREGATE + +class Map { + friend class DungeonMan; + + uint32 rawDunDataOffset; + uint8 offsetMapX, offsetMapY; + + uint8 level; // only used in DMII + uint8 width, height; // THESRE ARE INCLUSIVE BOUNDARIES + // orn short for Ornament + uint8 wallOrnCount; /* May be used in a Sensor on a Wall or closed Fake Wall square */ + uint8 randWallOrnCount; /* Used only on some Wall squares and some closed Fake Wall squares */ + uint8 floorOrnCount; /* May be used in a Sensor on a Pit, open Fake Wall, Corridor or Teleporter square */ + uint8 randFloorOrnCount; /* Used only on some Corridor squares and some open Fake Wall squares */ + + uint8 doorOrnCount; + uint8 creatureTypeCount; + uint8 difficulty; + + uint8 floorSet, wallSet, doorSet0, doorSet1; + +}; // @ MAP + + + class DungeonMan { DMEngine *_vm; - uint32 _dungeonDataSize; - byte *_dungeonData; + + uint32 _rawDunFileDataSize; + byte *_rawDunFileData; // @ ??? + DungeonFileHeader _fileHeader; // @ G0278_ps_DungeonHeader + + DungeonData _dunData; // @ NONE + Map *_maps; // @ G0277_ps_DungeonMaps + // does not have to be freed + byte *_rawMapData; // @ G0276_puc_DungeonRawMapData + Messages _messages; // @ NONE + DungeonMan(const DungeonMan &other); // no implementation on purpose void operator=(const DungeonMan &rhs); // no implementation on purpose + + void mapCoordsAfterRelMovement(direction dir, uint16 stepsForward, uint16 stepsRight, uint16 &posX, uint16 &posY); // @ F0150_DUNGEON_UpdateMapCoordinatesAfterRelativeMovement + + void decompressDungeonFile(); // @ F0455_FLOPPY_DecompressDungeon public: DungeonMan(DMEngine *dmEngine); ~DungeonMan(); - void loadDungeonFile(); + // TODO: this does stuff other than load the file! + void loadDungeonFile(); // @ F0434_STARTEND_IsLoadDungeonSuccessful_CPSC }; } diff --git a/engines/dm/gfx.cpp b/engines/dm/gfx.cpp index 0ae3b251fd..0c2efc6b71 100644 --- a/engines/dm/gfx.cpp +++ b/engines/dm/gfx.cpp @@ -39,7 +39,7 @@ Frame gCeilingFrame = {0, 223, 0, 28, 0, 0}; Frame gFloorFrame = {0, 223, 66, 135, 0, 0}; extern Viewport gDefultViewPort = {0, 0}; -extern Viewport gDungeonViewport = {0, 64}; +extern Viewport gDungeonViewport = {0, 64}; // TODO: I guessed the numbers } -- cgit v1.2.3