Initial commit
This commit is contained in:
896
engines/myst3/database.cpp
Normal file
896
engines/myst3/database.cpp
Normal file
@@ -0,0 +1,896 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "engines/myst3/database.h"
|
||||
|
||||
#include "common/archive.h"
|
||||
#include "common/debug.h"
|
||||
#include "common/substream.h"
|
||||
|
||||
namespace Myst3 {
|
||||
|
||||
/**
|
||||
* An abstract node transformation.
|
||||
*
|
||||
* Subclasses can read data related to a node from a stream in read,
|
||||
* and transform a node using that data in apply.
|
||||
*/
|
||||
class NodeTransform {
|
||||
public :
|
||||
virtual ~NodeTransform() {};
|
||||
|
||||
virtual void read(Common::SeekableReadStream *file) = 0;
|
||||
virtual void apply(NodePtr &node) = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* A node transformation that reads hotspots and scripts,
|
||||
* and adds them to a node.
|
||||
*/
|
||||
class NodeTransformAddHotspots : public NodeTransform {
|
||||
public :
|
||||
NodeTransformAddHotspots();
|
||||
|
||||
void read(Common::SeekableReadStream *file) override;
|
||||
void apply(NodePtr &node) override;
|
||||
|
||||
private:
|
||||
int32 _zipBitIndex;
|
||||
Common::Array<CondScript> _scripts;
|
||||
Common::Array<HotSpot> _hotspots;
|
||||
};
|
||||
|
||||
/**
|
||||
* A node transformation that reads scripts, and adds them to a node's
|
||||
* sound scripts.
|
||||
*/
|
||||
class NodeTransformAddSoundScripts : public NodeTransform {
|
||||
public :
|
||||
void read(Common::SeekableReadStream *file) override;
|
||||
void apply(NodePtr &node) override;
|
||||
|
||||
private:
|
||||
Common::Array<CondScript> _scripts;
|
||||
};
|
||||
|
||||
/**
|
||||
* A node transformation that reads scripts, and adds them to a node's
|
||||
* background sound scripts.
|
||||
*/
|
||||
class NodeTransformAddBackgroundSoundScripts : public NodeTransform {
|
||||
public :
|
||||
void read(Common::SeekableReadStream *file) override;
|
||||
void apply(NodePtr &node) override;
|
||||
|
||||
private:
|
||||
Common::Array<CondScript> _scripts;
|
||||
};
|
||||
|
||||
/**
|
||||
* Walks through a stream of nodes. For each encountered node, a NodeTransformer is called.
|
||||
*/
|
||||
class NodeWalker {
|
||||
public :
|
||||
NodeWalker(NodeTransform *transform);
|
||||
~NodeWalker();
|
||||
|
||||
void read(Common::SeekableReadStream *file, Common::Array<NodePtr> &allNodes, bool createMissingSharedNodes);
|
||||
|
||||
private:
|
||||
NodeTransform *_transform;
|
||||
};
|
||||
|
||||
/**
|
||||
* A collection of functions used to read script related data
|
||||
*/
|
||||
class ScriptData {
|
||||
public:
|
||||
static Common::Array<CondScript> readCondScripts(Common::SeekableReadStream &s);
|
||||
static Common::Array<Opcode> readOpcodes(Common::ReadStream &s);
|
||||
static Common::Array<HotSpot> readHotspots(Common::ReadStream &s);
|
||||
static Common::Array<PolarRect> readRects(Common::ReadStream &s);
|
||||
static CondScript readCondScript(Common::SeekableReadStream &s);
|
||||
static HotSpot readHotspot(Common::ReadStream &s);
|
||||
|
||||
private:
|
||||
ScriptData() {};
|
||||
};
|
||||
|
||||
Common::Array<PolarRect> ScriptData::readRects(Common::ReadStream &s) {
|
||||
Common::Array<PolarRect> rects;
|
||||
|
||||
bool lastRect = false;
|
||||
do {
|
||||
PolarRect rect;
|
||||
rect.centerPitch = s.readUint16LE();
|
||||
rect.centerHeading = s.readUint16LE();
|
||||
rect.width = s.readUint16LE();
|
||||
rect.height = s.readUint16LE();
|
||||
|
||||
if (rect.width < 0) {
|
||||
rect.width = -rect.width;
|
||||
} else {
|
||||
lastRect = true;
|
||||
}
|
||||
|
||||
rects.push_back(rect);
|
||||
} while (!lastRect && !s.eos());
|
||||
|
||||
return rects;
|
||||
}
|
||||
|
||||
Common::Array<Opcode> ScriptData::readOpcodes(Common::ReadStream &s) {
|
||||
Common::Array<Opcode> script;
|
||||
|
||||
while (!s.eos()) {
|
||||
Opcode opcode;
|
||||
uint16 code = s.readUint16LE();
|
||||
|
||||
opcode.op = code & 0xff;
|
||||
uint8 count = code >> 8;
|
||||
if (count == 0 && opcode.op == 0)
|
||||
break;
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
int16 value = s.readSint16LE();
|
||||
opcode.args.push_back(value);
|
||||
}
|
||||
|
||||
script.push_back(opcode);
|
||||
}
|
||||
|
||||
return script;
|
||||
}
|
||||
|
||||
CondScript ScriptData::readCondScript(Common::SeekableReadStream &s) {
|
||||
CondScript script;
|
||||
script.condition = s.readUint16LE();
|
||||
if(!script.condition)
|
||||
return script;
|
||||
|
||||
// WORKAROUND: Original data bug in MATO 32765
|
||||
// The script data for node MATO 32765 is missing its first two bytes
|
||||
// of data, resulting in incorrect opcodes being read
|
||||
|
||||
// Original disassembly:
|
||||
// init 0 > c[v565 != 0]
|
||||
// op 115, ifVarInRange ( )
|
||||
// op 45, inventoryAddBack ( )
|
||||
// op 53, varSetValue ( vSunspotColor 4090 )
|
||||
// op 53, varSetValue ( vSunspotRadius 40 )
|
||||
// op 33, waterEffectSetWave ( 100 80 )
|
||||
// op 32, waterEffectSetAttenuation ( 359 )
|
||||
// op 31, waterEffectSetSpeed ( 15 )
|
||||
|
||||
// Fixed disassembly
|
||||
// init 0 > c[v1 != 0]
|
||||
// op 53, varSetValue ( vSunspotIntensity 45 )
|
||||
// op 53, varSetValue ( vSunspotColor 4090 )
|
||||
// op 53, varSetValue ( vSunspotRadius 40 )
|
||||
// op 33, waterEffectSetWave ( 100 80 )
|
||||
// op 32, waterEffectSetAttenuation ( 359 )
|
||||
// op 31, waterEffectSetSpeed ( 15 )
|
||||
|
||||
if (script.condition == 565) {
|
||||
script.condition = 1;
|
||||
s.seek(-2, SEEK_CUR);
|
||||
}
|
||||
// END WORKAROUND
|
||||
|
||||
script.script = readOpcodes(s);
|
||||
|
||||
return script;
|
||||
}
|
||||
|
||||
Common::Array<CondScript> ScriptData::readCondScripts(Common::SeekableReadStream &s) {
|
||||
Common::Array<CondScript> scripts;
|
||||
|
||||
while (!s.eos()) {
|
||||
CondScript script = readCondScript(s);
|
||||
|
||||
if (!script.condition)
|
||||
break;
|
||||
|
||||
scripts.push_back(script);
|
||||
}
|
||||
|
||||
return scripts;
|
||||
}
|
||||
|
||||
HotSpot ScriptData::readHotspot(Common::ReadStream &s) {
|
||||
HotSpot hotspot;
|
||||
|
||||
hotspot.condition = s.readUint16LE();
|
||||
|
||||
if (hotspot.condition == 0)
|
||||
return hotspot;
|
||||
|
||||
if (hotspot.condition != -1) {
|
||||
hotspot.rects = readRects(s);
|
||||
hotspot.cursor = s.readUint16LE();
|
||||
}
|
||||
|
||||
hotspot.script = readOpcodes(s);
|
||||
|
||||
return hotspot;
|
||||
}
|
||||
|
||||
Common::Array<HotSpot> ScriptData::readHotspots(Common::ReadStream &s) {
|
||||
Common::Array<HotSpot> scripts;
|
||||
|
||||
while (!s.eos()) {
|
||||
HotSpot hotspot = readHotspot(s);
|
||||
|
||||
if (!hotspot.condition)
|
||||
break;
|
||||
|
||||
scripts.push_back(hotspot);
|
||||
}
|
||||
|
||||
return scripts;
|
||||
}
|
||||
|
||||
NodeTransformAddHotspots::NodeTransformAddHotspots() : _zipBitIndex(-1) {
|
||||
}
|
||||
|
||||
void NodeTransformAddHotspots::read(Common::SeekableReadStream *file) {
|
||||
_zipBitIndex++;
|
||||
_scripts = ScriptData::readCondScripts(*file);
|
||||
_hotspots = ScriptData::readHotspots(*file);
|
||||
}
|
||||
|
||||
void NodeTransformAddHotspots::apply(NodePtr &node) {
|
||||
node->zipBitIndex = _zipBitIndex;
|
||||
node->scripts.push_back(_scripts);
|
||||
node->hotspots.push_back(_hotspots);
|
||||
}
|
||||
|
||||
void NodeTransformAddSoundScripts::read(Common::SeekableReadStream *file) {
|
||||
_scripts = ScriptData::readCondScripts(*file);
|
||||
}
|
||||
|
||||
void NodeTransformAddSoundScripts::apply(NodePtr &node) {
|
||||
node->soundScripts.push_back(_scripts);
|
||||
}
|
||||
|
||||
void NodeTransformAddBackgroundSoundScripts::read(Common::SeekableReadStream *file) {
|
||||
_scripts = ScriptData::readCondScripts(*file);
|
||||
}
|
||||
|
||||
void NodeTransformAddBackgroundSoundScripts::apply(NodePtr &node) {
|
||||
node->backgroundSoundScripts.push_back(_scripts);
|
||||
}
|
||||
|
||||
NodeWalker::NodeWalker(NodeTransform *transform) : _transform(transform) {
|
||||
}
|
||||
|
||||
void NodeWalker::read(Common::SeekableReadStream *file, Common::Array<NodePtr> &allNodes, bool createMissingSharedNodes) {
|
||||
while (!file->eos()) {
|
||||
int16 id = file->readUint16LE();
|
||||
|
||||
// End of list
|
||||
if (id == 0)
|
||||
break;
|
||||
|
||||
if (id < -10)
|
||||
error("Unimplemented node list command");
|
||||
|
||||
if (id > 0) {
|
||||
// Normal node, find the node if existing
|
||||
NodePtr node;
|
||||
for (uint i = 0; i < allNodes.size(); i++)
|
||||
if (allNodes[i]->id == id) {
|
||||
node = allNodes[i];
|
||||
break;
|
||||
}
|
||||
|
||||
// Node not found, create a new one
|
||||
if (!node) {
|
||||
node = NodePtr(new NodeData());
|
||||
node->id = id;
|
||||
allNodes.push_back(node);
|
||||
}
|
||||
|
||||
_transform->read(file);
|
||||
_transform->apply(node);
|
||||
} else {
|
||||
// Several nodes sharing the same scripts
|
||||
// Find the node ids the script applies to
|
||||
Common::Array<int16> scriptNodeIds;
|
||||
|
||||
if (id == -10)
|
||||
do {
|
||||
id = file->readUint16LE();
|
||||
if (id < 0) {
|
||||
uint16 end = file->readUint16LE();
|
||||
for (int i = -id; i <= end; i++)
|
||||
scriptNodeIds.push_back(i);
|
||||
|
||||
} else if (id > 0) {
|
||||
scriptNodeIds.push_back(id);
|
||||
}
|
||||
} while (id);
|
||||
else
|
||||
for (int i = 0; i < -id; i++) {
|
||||
scriptNodeIds.push_back(file->readUint16LE());
|
||||
}
|
||||
|
||||
// Load the script
|
||||
_transform->read(file);
|
||||
|
||||
// Add the script to each matching node
|
||||
for (uint i = 0; i < scriptNodeIds.size(); i++) {
|
||||
NodePtr node;
|
||||
|
||||
// Find the current node if existing
|
||||
for (uint j = 0; j < allNodes.size(); j++) {
|
||||
if (allNodes[j]->id == scriptNodeIds[i]) {
|
||||
node = allNodes[j];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!node) {
|
||||
if (createMissingSharedNodes) {
|
||||
// Node not found, create a new one
|
||||
node = NodePtr(new NodeData());
|
||||
node->id = scriptNodeIds[i];
|
||||
allNodes.push_back(node);
|
||||
} else {
|
||||
// Node not found, skip it
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
_transform->apply(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NodeWalker::~NodeWalker() {
|
||||
delete _transform;
|
||||
}
|
||||
|
||||
static const RoomData roomsXXXX[] = {
|
||||
{ kRoomShared, "XXXX" }
|
||||
};
|
||||
|
||||
static const RoomData roomsINTR[] = {
|
||||
{ kRoomIntro, "INTR" }
|
||||
};
|
||||
|
||||
static const RoomData roomsTOHO[] = {
|
||||
{ kRoomTomahnaStart, "TOHO" }
|
||||
};
|
||||
|
||||
static const RoomData roomsTOHB[] = {
|
||||
{ kRoomTomahnaReturn, "TOHB" }
|
||||
};
|
||||
|
||||
static const RoomData roomsLE[] = {
|
||||
{ kJnaninStart, "LEIS" },
|
||||
{ kRoomLeos, "LEOS" },
|
||||
{ kRoomLeet, "LEET" },
|
||||
{ kRoomLelt, "LELT" },
|
||||
{ kRoomLemt, "LEMT" },
|
||||
{ kRoomLeof, "LEOF" }
|
||||
};
|
||||
|
||||
static const RoomData roomsLI[] = {
|
||||
{ kRoomEdannaStart, "LIDR" },
|
||||
{ kRoomLisw, "LISW" },
|
||||
{ kRoomLifo, "LIFO" },
|
||||
{ kRoomLisp, "LISP" },
|
||||
{ kRoomLine, "LINE" }
|
||||
};
|
||||
|
||||
static const RoomData roomsEN[] = {
|
||||
{ kRoomVoltaicStart, "ENSI" },
|
||||
{ kRoomEnpp, "ENPP" },
|
||||
{ kRoomEnem, "ENEM" },
|
||||
{ kRoomEnlc, "ENLC" },
|
||||
{ kRoomEndd, "ENDD" },
|
||||
{ kRoomEnch, "ENCH" },
|
||||
{ kRoomEnli, "ENLI" }
|
||||
};
|
||||
|
||||
static const RoomData roomsNA[] = {
|
||||
{ kRoomNarayan, "NACH" }
|
||||
};
|
||||
|
||||
static const RoomData roomsMENU[] = {
|
||||
{ kRoomMenu, "MENU" },
|
||||
{ kRoomJournals, "JRNL" },
|
||||
{ kRoomDemo, "DEMO" },
|
||||
{ kRoomAtix, "ATIX" }
|
||||
};
|
||||
|
||||
static const RoomData roomsMA[] = {
|
||||
{ kRoomAmateriaStart, "MACA" },
|
||||
{ kRoomMais, "MAIS" },
|
||||
{ kRoomMall, "MALL" },
|
||||
{ kRoomMass, "MASS" },
|
||||
{ kRoomMaww, "MAWW" },
|
||||
{ kRoomMato, "MATO" }
|
||||
};
|
||||
|
||||
static const RoomData roomsLOGO[] = {
|
||||
{ kLogo, "LOGO" }
|
||||
};
|
||||
|
||||
const AgeData Database::_ages[] = {
|
||||
{ 1, 0, 1, roomsXXXX, 0 },
|
||||
{ 2, 1, 1, roomsINTR, 0 },
|
||||
{ 3, 2, 1, roomsTOHO, 0 },
|
||||
{ 4, 4, 1, roomsTOHB, 0 },
|
||||
{ 5, 2, 6, roomsLE, 1 },
|
||||
{ 6, 4, 5, roomsLI, 2 },
|
||||
{ 7, 3, 7, roomsEN, 3 },
|
||||
{ 8, 3, 1, roomsNA, 4 },
|
||||
{ 9, 0, 4, roomsMENU, 0 },
|
||||
{ 10, 1, 6, roomsMA, 5 },
|
||||
{ 11, 0, 1, roomsLOGO, 0 }
|
||||
};
|
||||
|
||||
Database::Database(const Common::Platform platform, const Common::Language language, const uint32 localizationType) :
|
||||
_platform(platform),
|
||||
_language(language),
|
||||
_localizationType(localizationType),
|
||||
_soundIdMin(0),
|
||||
_soundIdMax(0) {
|
||||
_datFile = SearchMan.createReadStreamForMember("myst3.dat");
|
||||
if (!_datFile) {
|
||||
error("Unable to find 'myst3.dat'");
|
||||
}
|
||||
|
||||
uint magic = _datFile->readUint32LE();
|
||||
if (magic != MKTAG('M', 'Y', 'S', 'T')) {
|
||||
error("'myst3.dat' is invalid");
|
||||
}
|
||||
|
||||
uint version = _datFile->readUint32LE();
|
||||
if (version != kDatVersion) {
|
||||
error("Incorrect 'myst3.dat' version. Expected '%d', found '%d'", kDatVersion, version);
|
||||
}
|
||||
|
||||
bool isWindowMacVersion = _platform == Common::kPlatformWindows || _platform == Common::kPlatformMacintosh;
|
||||
bool isXboxVersion = _platform == Common::kPlatformXbox;
|
||||
|
||||
readScriptIndex(_datFile, isWindowMacVersion); // Main scripts
|
||||
readScriptIndex(_datFile, isWindowMacVersion && _localizationType == kLocMulti6); // Menu scripts 6 languages version
|
||||
readScriptIndex(_datFile, isWindowMacVersion && _localizationType == kLocMulti2); // Menu scripts 2 languages CD version
|
||||
readScriptIndex(_datFile, isWindowMacVersion && _localizationType == kLocMonolingual); // Menu scripts english CD version
|
||||
readScriptIndex(_datFile, isXboxVersion); // Main scripts Xbox version
|
||||
readScriptIndex(_datFile, isXboxVersion && _localizationType != kLocMonolingual); // Menu scripts PAL Xbox version
|
||||
readScriptIndex(_datFile, isXboxVersion && _localizationType == kLocMonolingual); // Menu scripts NTSC Xbox version
|
||||
readSoundNames(_datFile, isWindowMacVersion); // Sound names
|
||||
readSoundNames(_datFile, isXboxVersion); // Sound names Xbox
|
||||
|
||||
_roomScriptsStartOffset = _datFile->pos();
|
||||
|
||||
Common::SeekableReadStream *initScriptStream = getRoomScriptStream("INIT", kScriptTypeNodeInit);
|
||||
_nodeInitScript = ScriptData::readOpcodes(*initScriptStream);
|
||||
delete initScriptStream;
|
||||
|
||||
Common::SeekableReadStream *cuesStream = getRoomScriptStream("INIT", kScriptTypeAmbientCue);
|
||||
loadAmbientCues(cuesStream);
|
||||
delete cuesStream;
|
||||
|
||||
preloadCommonRooms();
|
||||
initializeZipBitIndexTable();
|
||||
|
||||
if (isWindowMacVersion && _localizationType == kLocMulti2) {
|
||||
patchLanguageMenu();
|
||||
}
|
||||
}
|
||||
|
||||
Database::~Database() {
|
||||
delete _datFile;
|
||||
}
|
||||
|
||||
void Database::preloadCommonRooms() {
|
||||
for (uint i = 0; i < ARRAYSIZE(_ages); i++) {
|
||||
const AgeData &age = _ages[i];
|
||||
|
||||
for (uint j = 0; j < age.roomCount; j++) {
|
||||
const RoomData &room = age.rooms[j];
|
||||
|
||||
if (isCommonRoom(room.id, age.id)) {
|
||||
Common::Array<NodePtr> nodes = readRoomScripts(&room);
|
||||
_roomNodesCache.setVal(RoomKey(room.id, age.id), nodes);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Common::Array<NodePtr> Database::getRoomNodes(uint32 roomID, uint32 ageID) const {
|
||||
Common::Array<NodePtr> nodes;
|
||||
|
||||
if (_roomNodesCache.contains(RoomKey(roomID, ageID))) {
|
||||
nodes = _roomNodesCache.getVal(RoomKey(roomID, ageID));
|
||||
} else {
|
||||
const RoomData *data = findRoomData(roomID, ageID);
|
||||
nodes = readRoomScripts(data);
|
||||
}
|
||||
|
||||
return nodes;
|
||||
}
|
||||
|
||||
Common::Array<uint16> Database::listRoomNodes(uint32 roomID, uint32 ageID) {
|
||||
Common::Array<NodePtr> nodes;
|
||||
Common::Array<uint16> list;
|
||||
|
||||
nodes = getRoomNodes(roomID, ageID);
|
||||
|
||||
for (uint i = 0; i < nodes.size(); i++) {
|
||||
list.push_back(nodes[i]->id);
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
NodePtr Database::getNodeData(uint16 nodeID, uint32 roomID, uint32 ageID) {
|
||||
Common::Array<NodePtr> nodes = getRoomNodes(roomID, ageID);
|
||||
|
||||
for (uint i = 0; i < nodes.size(); i++) {
|
||||
if (nodes[i]->id == nodeID)
|
||||
return nodes[i];
|
||||
}
|
||||
|
||||
return NodePtr();
|
||||
}
|
||||
|
||||
void Database::initializeZipBitIndexTable() {
|
||||
int16 zipBit = 0;
|
||||
for (uint i = 0; i < ARRAYSIZE(_ages); i++) {
|
||||
for (uint j = 0; j < _ages[i].roomCount; j++) {
|
||||
_roomZipBitIndex.setVal(_ages[i].rooms[j].id, zipBit);
|
||||
|
||||
// Add the highest zip-bit index for the current room
|
||||
// to get the zip-bit index for the next room
|
||||
int16 maxZipBitForRoom = 0;
|
||||
Common::Array<NodePtr> nodes = readRoomScripts(&_ages[i].rooms[j]);
|
||||
for (uint k = 0; k < nodes.size(); k++) {
|
||||
maxZipBitForRoom = MAX(maxZipBitForRoom, nodes[k]->zipBitIndex);
|
||||
}
|
||||
|
||||
zipBit += maxZipBitForRoom + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int32 Database::getNodeZipBitIndex(uint16 nodeID, uint32 roomID, uint32 ageID) {
|
||||
if (!_roomZipBitIndex.contains(roomID)) {
|
||||
error("Unable to find zip-bit index for room %d", roomID);
|
||||
}
|
||||
|
||||
Common::Array<NodePtr> nodes = getRoomNodes(roomID, ageID);
|
||||
|
||||
for (uint i = 0; i < nodes.size(); i++) {
|
||||
if (nodes[i]->id == nodeID) {
|
||||
return _roomZipBitIndex[roomID] + nodes[i]->zipBitIndex;
|
||||
}
|
||||
}
|
||||
|
||||
error("Unable to find zip-bit index for node (%d, %d)", nodeID, roomID);
|
||||
}
|
||||
|
||||
const RoomData *Database::findRoomData(uint32 roomID, uint32 ageID) const {
|
||||
for (uint i = 0; i < ARRAYSIZE(_ages); i++) {
|
||||
if (_ages[i].id == ageID) {
|
||||
for (uint j = 0; j < _ages[i].roomCount; j++) {
|
||||
if (_ages[i].rooms[j].id == roomID) {
|
||||
return &_ages[i].rooms[j];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
error("No room with ID %d in age %d", roomID, ageID);
|
||||
}
|
||||
|
||||
Common::Array<NodePtr> Database::readRoomScripts(const RoomData *room) const {
|
||||
Common::Array<NodePtr> nodes;
|
||||
|
||||
// Load the node scripts
|
||||
Common::SeekableReadStream *scriptsStream = getRoomScriptStream(room->name, kScriptTypeNode);
|
||||
if (scriptsStream) {
|
||||
NodeWalker scriptWalker = NodeWalker(new NodeTransformAddHotspots());
|
||||
scriptWalker.read(scriptsStream, nodes, true);
|
||||
|
||||
delete scriptsStream;
|
||||
}
|
||||
|
||||
// Load the ambient sound scripts, if any
|
||||
Common::SeekableReadStream *ambientSoundsStream = getRoomScriptStream(room->name, kScriptTypeAmbientSound);
|
||||
if (ambientSoundsStream) {
|
||||
NodeWalker scriptWalker = NodeWalker(new NodeTransformAddSoundScripts());
|
||||
scriptWalker.read(ambientSoundsStream, nodes, false);
|
||||
|
||||
delete ambientSoundsStream;
|
||||
}
|
||||
|
||||
Common::SeekableReadStream *backgroundSoundsStream = getRoomScriptStream(room->name, kScriptTypeBackgroundSound);
|
||||
if (backgroundSoundsStream) {
|
||||
NodeWalker scriptWalker = NodeWalker(new NodeTransformAddBackgroundSoundScripts());
|
||||
scriptWalker.read(backgroundSoundsStream, nodes, false);
|
||||
|
||||
delete backgroundSoundsStream;
|
||||
}
|
||||
|
||||
patchNodeScripts(room, nodes);
|
||||
|
||||
return nodes;
|
||||
}
|
||||
|
||||
void Database::patchNodeScripts(const RoomData *room, Common::Array<NodePtr> &nodes) const {
|
||||
if (strcmp(room->name, "LEOF") == 0) {
|
||||
// The room LEOF does not have a script to set default water effect
|
||||
// parameters when entering a node. As a result, the pool of water
|
||||
// mainly visible in LEOF 23 uses the last set water effect parameters.
|
||||
// If the player comes from the top of the tower, the water effect is
|
||||
// barely visible.
|
||||
// As a workaround we insert default water effect settings in node
|
||||
// 32765 which get applied for each node in the room.
|
||||
// The new script disassembles as follow:
|
||||
|
||||
// node: LEOF 32765
|
||||
// init 0 > c[v1 != 0] (true)
|
||||
// op 33, waterEffectSetWave ( 100 100 )
|
||||
// op 32, waterEffectSetAttenuation ( 360 )
|
||||
// op 31, waterEffectSetSpeed ( 12 )
|
||||
|
||||
Opcode waterEffectSetWave;
|
||||
waterEffectSetWave.op = 33;
|
||||
waterEffectSetWave.args.push_back(100);
|
||||
waterEffectSetWave.args.push_back(100);
|
||||
|
||||
Opcode waterEffectSetAttenuation;
|
||||
waterEffectSetAttenuation.op = 32;
|
||||
waterEffectSetAttenuation.args.push_back(360);
|
||||
|
||||
Opcode waterEffectSetSpeed;
|
||||
waterEffectSetSpeed.op = 31;
|
||||
waterEffectSetSpeed.args.push_back(12);
|
||||
|
||||
CondScript waterEffectScript;
|
||||
waterEffectScript.condition = 1;
|
||||
waterEffectScript.script.push_back(waterEffectSetWave);
|
||||
waterEffectScript.script.push_back(waterEffectSetAttenuation);
|
||||
waterEffectScript.script.push_back(waterEffectSetSpeed);
|
||||
|
||||
NodePtr node32765 = NodePtr(new NodeData());
|
||||
node32765->id = 32765;
|
||||
node32765->scripts.push_back(waterEffectScript);
|
||||
|
||||
nodes.push_back(node32765);
|
||||
}
|
||||
}
|
||||
|
||||
bool Database::isCommonRoom(uint32 roomID, uint32 ageID) const {
|
||||
return roomID == kRoomShared || roomID == kRoomMenu || roomID == kRoomJournals;
|
||||
}
|
||||
|
||||
void Database::cacheRoom(uint32 roomID, uint32 ageID) {
|
||||
if (_roomNodesCache.contains(RoomKey(roomID, ageID))) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove old rooms from cache and add the new one
|
||||
for (NodesCache::iterator it = _roomNodesCache.begin(); it != _roomNodesCache.end(); it++) {
|
||||
if (!isCommonRoom(it->_key.roomID, it->_key.ageID)) {
|
||||
_roomNodesCache.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
const RoomData *currentRoomData = findRoomData(roomID, ageID);
|
||||
|
||||
if (!currentRoomData)
|
||||
return;
|
||||
|
||||
_roomNodesCache.setVal(RoomKey(roomID, ageID), readRoomScripts(currentRoomData));
|
||||
}
|
||||
|
||||
Common::String Database::getRoomName(uint32 roomID, uint32 ageID) const {
|
||||
const RoomData *data = findRoomData(roomID, ageID);
|
||||
return data->name;
|
||||
}
|
||||
|
||||
RoomKey Database::getRoomKey(const char *name) {
|
||||
for (uint i = 0; i < ARRAYSIZE(_ages); i++)
|
||||
for (uint j = 0; j < _ages[i].roomCount; j++) {
|
||||
if (scumm_stricmp(_ages[i].rooms[j].name, name) == 0) {
|
||||
return RoomKey(_ages[i].rooms[j].id, _ages[i].id);
|
||||
}
|
||||
}
|
||||
|
||||
return RoomKey(0, 0);
|
||||
}
|
||||
|
||||
uint32 Database::getAgeLabelId(uint32 ageID) {
|
||||
for (uint i = 0; i < ARRAYSIZE(_ages); i++)
|
||||
if (_ages[i].id == ageID)
|
||||
return _ages[i].labelId;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
Common::String Database::getSoundName(uint32 id) {
|
||||
return _soundNames.getVal(id);
|
||||
}
|
||||
|
||||
void Database::loadAmbientCues(Common::ReadStream *s) {
|
||||
_ambientCues.clear();
|
||||
|
||||
while (!s->eos()) {
|
||||
uint16 id = s->readUint16LE();
|
||||
|
||||
if (!id)
|
||||
break;
|
||||
|
||||
AmbientCue cue;
|
||||
cue.id = id;
|
||||
cue.minFrames = s->readUint16LE();
|
||||
cue.maxFrames = s->readUint16LE();
|
||||
|
||||
while (1) {
|
||||
uint16 track = s->readUint16LE();
|
||||
|
||||
if (!track)
|
||||
break;
|
||||
|
||||
cue.tracks.push_back(track);
|
||||
}
|
||||
|
||||
_ambientCues[id] = cue;
|
||||
}
|
||||
}
|
||||
|
||||
const AmbientCue &Database::getAmbientCue(uint16 id) {
|
||||
if (!_ambientCues.contains(id))
|
||||
error("Unable to find an ambient cue with id %d", id);
|
||||
|
||||
return _ambientCues.getVal(id);
|
||||
}
|
||||
|
||||
void Database::readScriptIndex(Common::SeekableReadStream *stream, bool load) {
|
||||
uint count = stream->readUint32LE();
|
||||
for (uint i = 0; i < count; i++) {
|
||||
|
||||
RoomScripts roomScripts;
|
||||
|
||||
char roomName[5];
|
||||
stream->read(roomName, sizeof(roomName));
|
||||
roomName[4] = '\0';
|
||||
|
||||
roomScripts.room = Common::String(roomName);
|
||||
roomScripts.type = (ScriptType) stream->readUint32LE();
|
||||
roomScripts.offset = stream->readUint32LE();
|
||||
roomScripts.size = stream->readUint32LE();
|
||||
|
||||
if (load) {
|
||||
_roomScriptsIndex.push_back(roomScripts);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Database::readSoundNames(Common::SeekableReadStream *stream, bool load) {
|
||||
uint count = stream->readUint32LE();
|
||||
for (uint i = 0; i < count; i++) {
|
||||
|
||||
uint id = stream->readUint32LE();
|
||||
|
||||
char soundName[32];
|
||||
stream->read(soundName, sizeof(soundName));
|
||||
soundName[31] = '\0';
|
||||
|
||||
if (load) {
|
||||
_soundNames[id] = Common::String(soundName);
|
||||
|
||||
if (_soundIdMin == 0 || id < _soundIdMin) {
|
||||
_soundIdMin = id;
|
||||
}
|
||||
|
||||
if (_soundIdMax == 0 || id > _soundIdMax) {
|
||||
_soundIdMax = id;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Common::SeekableReadStream *Database::getRoomScriptStream(const char *room, ScriptType scriptType) const {
|
||||
for (uint i = 0; i < _roomScriptsIndex.size(); i++) {
|
||||
if (_roomScriptsIndex[i].room.equalsIgnoreCase(room)
|
||||
&& _roomScriptsIndex[i].type == scriptType) {
|
||||
uint32 startOffset = _roomScriptsStartOffset + _roomScriptsIndex[i].offset;
|
||||
uint32 size = _roomScriptsIndex[i].size;
|
||||
|
||||
return new Common::SeekableSubReadStream(_datFile, startOffset, startOffset + size);
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool Database::areRoomsScriptsEqual(uint32 roomID1, uint32 ageID1, uint32 roomID2, uint32 ageID2, ScriptType scriptType) {
|
||||
const RoomData *data1 = findRoomData(roomID1, ageID1);
|
||||
const RoomData *data2 = findRoomData(roomID2, ageID2);
|
||||
|
||||
int32 startOffset1 = -1;
|
||||
int32 startOffset2 = -1;
|
||||
for (uint i = 0; i < _roomScriptsIndex.size(); i++) {
|
||||
if (_roomScriptsIndex[i].room.equalsIgnoreCase(data1->name)
|
||||
&& _roomScriptsIndex[i].type == scriptType) {
|
||||
startOffset1 = _roomScriptsStartOffset + _roomScriptsIndex[i].offset;
|
||||
}
|
||||
|
||||
if (_roomScriptsIndex[i].room.equalsIgnoreCase(data2->name)
|
||||
&& _roomScriptsIndex[i].type == scriptType) {
|
||||
startOffset2 = _roomScriptsStartOffset + _roomScriptsIndex[i].offset;
|
||||
}
|
||||
}
|
||||
|
||||
return startOffset1 == startOffset2;
|
||||
}
|
||||
|
||||
int16 Database::getGameLanguageCode() const {
|
||||
// The monolingual versions of the game always use 0 as the language code
|
||||
if (_localizationType == kLocMonolingual) {
|
||||
return kEnglish;
|
||||
}
|
||||
|
||||
switch (_language) {
|
||||
case Common::FR_FRA:
|
||||
return kFrench;
|
||||
case Common::DE_DEU:
|
||||
return kGerman;
|
||||
case Common::IT_ITA:
|
||||
return kItalian;
|
||||
case Common::ES_ESP:
|
||||
return kSpanish;
|
||||
case Common::EN_ANY:
|
||||
return kEnglish;
|
||||
default:
|
||||
return kOther;
|
||||
}
|
||||
}
|
||||
|
||||
void Database::patchLanguageMenu() {
|
||||
// The menu scripts in 'myst3.dat" for the non English CD versions come from the French version
|
||||
// The scripts for the other languages only differ by the value set for AudioLanguage variable
|
||||
// when the language selection is not English.
|
||||
// This function patches the language selection script to set the appropriate value based
|
||||
// on the detected game language.
|
||||
|
||||
// Script disassembly:
|
||||
// hotspot 5 > c[v1 != 0] (true)
|
||||
// rect > pitch: 373 heading: 114 width: 209 height: 28
|
||||
// op 206, soundPlayVolume ( 795 5 )
|
||||
// op 53, varSetValue ( vLanguageAudio 2 ) // <= The second argument of this opcode is patched
|
||||
// op 194, runPuzzle1 ( 18 )
|
||||
// op 194, runPuzzle1 ( 19 )
|
||||
|
||||
NodePtr languageMenu = getNodeData(530, kRoomMenu, 9);
|
||||
languageMenu->hotspots[5].script[1].args[1] = getGameLanguageCode();
|
||||
}
|
||||
|
||||
} // End of namespace Myst3
|
||||
Reference in New Issue
Block a user