MYST3: Implement loading game scripts from the executable

This commit is contained in:
Bastien Bouclet 2011-08-28 20:10:47 +02:00
parent 2a0d5bbb40
commit 8150baefca
10 changed files with 558 additions and 11 deletions

288
engines/myst3/database.cpp Normal file
View File

@ -0,0 +1,288 @@
/* Residual - A 3D game interpreter
*
* Residual 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 library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*
*/
#include "engines/myst3/database.h"
#include "common/file.h"
#include "common/debug.h"
#include "common/md5.h"
namespace Myst3 {
Database::Database(const Common::String &executable) :
_exePath(executable),
_currentRoomID(0),
_gameVersion(0) {
// Game versions database
static GameVersion versions[] = {
{ "1.22 French", "554612b239ff2d9a3364fa38e3f32b45", 0x486108 }
};
Common::File file;
file.open(_exePath);
// Check whether the game version is known
Common::String md5 = Common::computeStreamMD5AsString(file, 0);
for (uint i = 0; i < sizeof(versions) / sizeof(GameVersion); i++) {
if (md5.equals(versions[i].md5)) {
_gameVersion = &versions[i];
break;
}
}
if (_gameVersion != 0) {
debug("Initializing database from %s (%s)", _exePath.c_str(), _gameVersion->description);
} else {
error("Unknown game version: %s (md5: %s)", _exePath.c_str(), md5.c_str());
}
// Load the ages and rooms description
file.seek(_gameVersion->ageTableOffset - _baseOffset);
_ages = loadAges(file);
for (uint i = 0; i < _ages.size(); i++) {
file.seek(_ages[i].roomsOffset);
// Read the room offset table
Common::Array<uint32> roomsOffsets;
for (uint j = 0; j < _ages[i].roomCount; j++) {
uint32 offset = file.readUint32LE() - _baseOffset;
roomsOffsets.push_back(offset);
}
// Load the rooms
for (uint j = 0; j < roomsOffsets.size(); j++) {
file.seek(roomsOffsets[j]);
_ages[i].rooms.push_back(loadRoom(file));
}
}
file.close();
}
NodeData *Database::getNodeData(uint16 nodeID) {
for (uint i = 0; i < _currentRoom.size(); i++) {
if (_currentRoom[i].id == nodeID)
return &_currentRoom[i];
}
return 0;
}
void Database::loadRoomScripts(const uint8 roomID) {
if (roomID == _currentRoomID)
return;
_currentRoom.clear();
uint32 roomScriptsOffset = 0;
for (uint i = 0; i < _ages.size(); i++)
for (uint j = 0; j < _ages[i].rooms.size(); j++) {
if (_ages[i].rooms[j].id == roomID) {
roomScriptsOffset = _ages[i].rooms[j].scriptsOffset;
break;
}
}
if (!roomScriptsOffset)
return;
Common::File file;
file.open(_exePath);
file.seek(roomScriptsOffset);
_currentRoomID = roomID;
while (1) {
NodeData node = loadNode(file);
if (node.id <= 0)
break;
node.scripts = loadCondScripts(file);
node.hotspots = loadHotspots(file);
_currentRoom.push_back(node);
}
file.close();
}
NodeData Database::loadNode(Common::ReadStream &s) {
NodeData node;
node.id = s.readUint16LE();
return node;
}
Common::Array<CondScript> Database::loadCondScripts(Common::ReadStream &s) {
Common::Array<CondScript> scripts;
while (1) {
CondScript script = loadCondScript(s);
if (!script.condition)
break;
scripts.push_back(script);
}
return scripts;
}
Common::Array<HotSpot> Database::loadHotspots(Common::ReadStream &s) {
Common::Array<HotSpot> scripts;
while (1) {
HotSpot hotspot = loadHotspot(s);
if (!hotspot.condition)
break;
scripts.push_back(hotspot);
}
return scripts;
}
Common::Array<Opcode> Database::loadOpcodes(Common::ReadStream &s)
{
Common::Array<Opcode> script;
while(1){
Opcode opcode;
opcode.op = s.readByte();
uint8 count = s.readByte();
if(count == 0 && opcode.op == 0)
break;
for(int i = 0;i < count;i++){
uint16 value = s.readUint16LE();
opcode.args.push_back(value);
}
script.push_back(opcode);
}
return script;
}
CondScript Database::loadCondScript(Common::ReadStream & s)
{
CondScript script;
script.condition = s.readUint16LE();
if(!script.condition)
return script;
script.script = loadOpcodes(s);
return script;
}
HotSpot Database::loadHotspot(Common::ReadStream &s) {
HotSpot hotspot;
hotspot.condition = s.readUint16LE();
if (hotspot.condition == 0)
return hotspot;
if (hotspot.condition != -1) {
hotspot.rects = loadRects(s);
hotspot.unk2 = s.readUint16LE();
}
hotspot.script = loadOpcodes(s);
return hotspot;
}
Common::Array<PolarRect> Database::loadRects(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);
return rects;
}
Common::Array<AgeData> Database::loadAges(Common::ReadStream &s)
{
Common::Array<AgeData> ages;
for (uint i = 0; i < 10; i++) {
AgeData age;
age.id = s.readUint32LE();
age.disk = s.readUint32LE();
age.roomCount = s.readUint32LE();
age.roomsOffset = s.readUint32LE() - _baseOffset;
age.ageUnk1 = s.readUint32LE();
ages.push_back(age);
}
return ages;
}
RoomData Database::loadRoom(Common::ReadStream &s) {
RoomData room;
room.id = s.readByte();
room.roomUnk1 = s.readByte();
room.roomUnk2 = s.readByte();
room.roomUnk3 = s.readByte();
s.read(&room.name, 8);
room.scriptsOffset = s.readUint32LE();
room.ambSoundsOffset = s.readUint32LE();
room.unkOffset = s.readUint32LE();
room.roomUnk4 = s.readUint32LE();
room.roomUnk5 = s.readUint32LE();
if (room.scriptsOffset != 0)
room.scriptsOffset -= _baseOffset;
if (room.ambSoundsOffset != 0)
room.ambSoundsOffset -= _baseOffset;
if (room.unkOffset != 0)
room.unkOffset -= _baseOffset;
return room;
}
} /* namespace Myst3 */

112
engines/myst3/database.h Normal file
View File

@ -0,0 +1,112 @@
/* Residual - A 3D game interpreter
*
* Residual 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 library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*
*/
#ifndef DATABASE_H_
#define DATABASE_H_
#include "common/scummsys.h"
#include "engines/myst3/hotspot.h"
#include "common/str.h"
#include "common/array.h"
#include "common/stream.h"
namespace Myst3 {
struct NodeData
{
int16 id;
Common::Array<CondScript> scripts;
Common::Array<HotSpot> hotspots;
};
struct RoomData
{
uint8 id;
uint8 roomUnk1;
uint8 roomUnk2;
uint8 roomUnk3;
char name[8];
uint32 scriptsOffset;
uint32 ambSoundsOffset;
uint32 unkOffset;
uint32 roomUnk4;
uint32 roomUnk5;
};
struct AgeData
{
uint32 id;
uint32 disk;
uint32 roomCount;
uint32 roomsOffset;
uint32 ageUnk1;
Common::Array<RoomData> rooms;
};
class Database
{
public:
/**
* Initialize the database from an executable file
*/
Database(const Common::String & executable);
/**
* Loads a room's nodes into the database
*/
void loadRoomScripts(uint8 roomID);
/**
* Returns a node's hotspots and scripts from the currently loaded room
*/
NodeData *getNodeData(uint16 nodeID);
private:
struct GameVersion {
const char *description;
const char *md5;
const uint32 ageTableOffset;
};
static const uint32 _baseOffset = 0x400000;
Common::String _exePath;
GameVersion *_gameVersion;
Common::Array<AgeData> _ages;
uint16 _currentRoomID;
Common::Array<NodeData> _currentRoom;
Common::Array<AgeData> loadAges(Common::ReadStream &s);
RoomData loadRoom(Common::ReadStream &s);
NodeData loadNode(Common::ReadStream & s);
Common::Array<CondScript> loadCondScripts(Common::ReadStream & s);
Common::Array<Opcode> loadOpcodes(Common::ReadStream & s);
Common::Array<HotSpot> loadHotspots(Common::ReadStream & s);
Common::Array<PolarRect> loadRects(Common::ReadStream & s);
CondScript loadCondScript(Common::ReadStream & s);
HotSpot loadHotspot(Common::ReadStream & s);
};
} /* namespace Myst3 */
#endif /* DATABASE_H_ */

43
engines/myst3/hotspot.cpp Normal file
View File

@ -0,0 +1,43 @@
/* Residual - A 3D game interpreter
*
* Residual 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 library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*
*/
#include "engines/myst3/hotspot.h"
namespace Myst3 {
bool HotSpot::isPointInRects(const Common::Point & p)
{
for(uint j = 0;j < rects.size();j++){
Common::Rect rect = Common::Rect(
rects[j].centerHeading - rects[j].width / 2,
rects[j].centerPitch - rects[j].height / 2,
rects[j].centerHeading + rects[j].width / 2,
rects[j].centerPitch + rects[j].height / 2);
if(rect.contains(p)){
return true;
}
}
return false;
}
} /* namespace Myst3 */

60
engines/myst3/hotspot.h Normal file
View File

@ -0,0 +1,60 @@
/* Residual - A 3D game interpreter
*
* Residual 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 library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*
*/
#ifndef HOTSPOT_H_
#define HOTSPOT_H_
#include "common/rect.h"
#include "common/array.h"
namespace Myst3 {
struct Opcode {
uint8 op;
Common::Array<uint16> args;
};
struct CondScript {
uint16 condition;
Common::Array<Opcode> script;
};
struct PolarRect {
int16 centerPitch;
int16 centerHeading;
int16 height;
int16 width;
};
class HotSpot {
public:
int16 condition;
Common::Array<PolarRect> rects;
int16 unk2;
Common::Array<Opcode> script;
bool isPointInRects(const Common::Point &p);
};
} /* namespace Myst3 */
#endif /* HOTSPOT_H_ */

View File

@ -2,9 +2,11 @@ MODULE := engines/myst3
MODULE_OBJS := \
archive.o \
database.o \
detection.o \
directoryentry.o \
directorysubentry.o \
hotspot.o \
myst3.o \
room.o \
scene.o

View File

@ -30,9 +30,12 @@
#include "common/util.h"
#include "common/textconsole.h"
#include "gui/debugger.h"
#include "engines/engine.h"
#include "engines/myst3/myst3.h"
#include "engines/myst3/database.h"
#include "graphics/jpeg.h"
#include "graphics/conversion.h"
@ -48,29 +51,31 @@ namespace Myst3 {
Myst3Engine::Myst3Engine(OSystem *syst, int gameFlags) :
Engine(syst), _system(syst) {
_console = new GUI::Debugger();
}
Myst3Engine::~Myst3Engine() {
delete _console;
}
Common::Error Myst3Engine::run() {
const int w = 800;
const int h = 600;
const char archiveFileName[] = "MAISnodes.m3a";
const int roomID = 2;
const char archiveFileName[] = "LEISnodes.m3a";
int nodeID = 1;
if (!_archive.open(archiveFileName)) {
error("Unable to open archive");
}
Database db = Database("M3.exe");
db.loadRoomScripts(245); // LEIS
_system->setupScreen(w, h, false, true);
_system->showMouse(false);
_scene.init(w, h);
_room.load(_archive, roomID);
_scene.init(w, h);
_room.load(_archive, nodeID);
for(;;) {
// Process events
@ -81,6 +86,33 @@ Common::Error Myst3Engine::run() {
return Common::kNoError;
} else if (event.type == Common::EVENT_MOUSEMOVE) {
_scene.updateCamera(event.relMouse);
} else if (event.type == Common::EVENT_LBUTTONDOWN) {
Common::Point mouse = _scene.getMousePos();
NodeData *nodeData = db.getNodeData(nodeID);
for (uint j = 0; j < nodeData->hotspots.size(); j++) {
if (nodeData->hotspots[j].isPointInRects(mouse)) {
const Opcode &op = nodeData->hotspots[j].script[0];
debug("op %d, %d", op.op, op.args[0]);
if (op.op == 138) {
nodeID = op.args[0];
_room.unload();
_room.load(_archive, nodeID);
}
}
}
} else if (event.type == Common::EVENT_KEYDOWN) {
switch (event.kbd.keycode) {
case Common::KEYCODE_d:
if (event.kbd.flags & Common::KBD_CTRL) {
_console->attach();
_console->onFrame();
_scene.init(w, h);
}
break;
default:
break;
}
}
}
@ -93,6 +125,8 @@ Common::Error Myst3Engine::run() {
_system->delayMillis(10);
}
_room.unload();
_archive.close();
return Common::kNoError;

View File

@ -41,7 +41,7 @@ class Myst3Engine : public Engine {
protected:
// Engine APIs
virtual Common::Error run();
virtual GUI::Debugger *getDebugger() { return _console; }
public:
Myst3Engine(OSystem *syst, int gameFlags);
@ -49,6 +49,7 @@ public:
private:
OSystem *_system;
GUI::Debugger *_console;
Room _room;
Scene _scene;

View File

@ -26,6 +26,7 @@
#include "engines/myst3/room.h"
#include "common/debug.h"
#include "common/rect.h"
namespace Myst3 {
@ -79,8 +80,6 @@ void Room::dumpFaceMask(Archive &archive, uint16 index, int face) {
dataOffset = maskStream->readUint32LE();
headerOffset = maskStream->pos();
debug("%d %d %d %d", headerOffset, dataOffset, blockX, blockY);
if (dataOffset != 0) {
maskStream->seek(dataOffset, SEEK_SET);
@ -119,6 +118,12 @@ void Room::load(Archive &archive, uint16 index) {
}
}
void Room::unload() {
for (int i = 0; i < 6; i++) {
glDeleteTextures(1, &_cubeTextures[i]);
}
}
void Room::draw() {
// Size of the cube
float t = 1.0f;

View File

@ -51,6 +51,7 @@ class Room {
void setFaceTextureJPEG(int face, Graphics::JPEG *jpeg);
void draw();
void load(Archive &archive, uint16 index);
void unload();
void dumpFaceMask(Archive &archive, uint16 index, int face);
};

View File

@ -48,6 +48,7 @@ class Scene {
void clear();
void setupCamera();
void updateCamera(Common::Point &mouse);
Common::Point getMousePos() { return Common::Point(_cameraHeading, _cameraPitch); }
};
} // end of namespace Myst3