Initial commit

This commit is contained in:
2026-02-02 04:50:13 +01:00
commit 5b11698731
22592 changed files with 7677434 additions and 0 deletions

3
engines/myst3/POTFILES Normal file
View File

@@ -0,0 +1,3 @@
engines/myst3/detection.cpp
engines/myst3/metaengine.cpp
engines/myst3/myst3.cpp

206
engines/myst3/ambient.cpp Normal file
View File

@@ -0,0 +1,206 @@
/* 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/ambient.h"
#include "engines/myst3/database.h"
#include "engines/myst3/myst3.h"
#include "engines/myst3/state.h"
#include "engines/myst3/sound.h"
namespace Myst3 {
Ambient::Ambient(Myst3Engine *vm) :
_vm(vm),
_cueStartTick(0) {
_cueSheet.reset();
}
Ambient::~Ambient() {
}
void Ambient::playCurrentNode(uint32 volume, uint32 fadeOutDelay) {
if (!fadeOutDelay) fadeOutDelay = 1;
uint32 node = _vm->_state->getLocationNode();
uint32 room = _vm->_state->getLocationRoom();
uint32 age = _vm->_state->getLocationAge();
// Load sound descriptors
loadNode(node, room, age);
// Adjust volume
scaleVolume(volume);
// Play sounds
applySounds(fadeOutDelay);
}
void Ambient::scaleVolume(uint32 volume) {
for (uint i = 0; i < _sounds.size(); i++)
_sounds[i].volume = _sounds[i].volume * volume / 100;
}
void Ambient::loadNode(uint32 node, uint32 room, uint32 age) {
_sounds.clear();
_cueSheet.reset();
if (!node)
node = _vm->_state->getLocationNode();
_vm->_state->setAmbiantPreviousFadeOutDelay(_vm->_state->getAmbiantFadeOutDelay());
_scriptAge = age;
_scriptRoom = room;
_vm->runAmbientScripts(node);
if (_sounds.size() == 0)
_vm->runAmbientScripts(32766);
}
void Ambient::addSound(uint32 id, int32 volume, int32 heading, int32 headingAngle, int32 u1, int32 fadeOutDelay) {
if (!volume)
volume = 1;
AmbientSound s;
if (volume >= 0) {
s.volume = volume;
s.volumeFlag = 0;
} else {
s.volume = -volume;
s.volumeFlag = 1;
}
s.id = id;
s.heading = heading;
s.headingAngle = headingAngle;
s.u1 = u1;
s.fadeOutDelay = fadeOutDelay;
_sounds.push_back(s);
}
void Ambient::setCueSheet(uint32 id, int32 volume, int32 heading, int32 headingAngle) {
_cueSheet.reset();
if (volume >= 0) {
_cueSheet.volume = volume;
_cueSheet.volumeFlag = 0;
} else {
_cueSheet.volume = -volume;
_cueSheet.volumeFlag = 1;
}
_cueSheet.id = id;
_cueSheet.heading = heading;
_cueSheet.headingAngle = headingAngle;
}
uint16 Ambient::delayForCue(uint32 id) {
const AmbientCue &cue = _vm->_db->getAmbientCue(id);
// Return a delay in frames inside the bounds
return _vm->_rnd->getRandomNumberRng(cue.minFrames, cue.maxFrames);
}
uint32 Ambient::nextCueSound(uint32 id) {
static uint32 lastId = 0;
const AmbientCue &cue = _vm->_db->getAmbientCue(id);
// Only one sound, no way it can be different from the previous one
if (cue.tracks.size() == 1) {
return cue.tracks[0];
}
// Make sure the new random sound is different from the last one
uint32 soundId;
do {
uint index = _vm->_rnd->getRandomNumber(cue.tracks.size() - 1);
soundId = cue.tracks[index];
} while (soundId == lastId);
lastId = soundId;
return soundId;
}
void Ambient::updateCue() {
if (_cueSheet.id) {
if (!_cueStartTick) {
_cueStartTick = _vm->_state->getTickCount() + delayForCue(_cueSheet.id);
}
if (_vm->_state->getTickCount() >= _cueStartTick) {
_cueStartTick = 0;
uint32 soundId = nextCueSound(_cueSheet.id);
uint heading;
if (_cueSheet.heading == 32766) {
heading = _vm->_rnd->getRandomNumberRng(0, 359);
} else {
heading = _cueSheet.heading;
}
_vm->_sound->playCue(soundId, _cueSheet.volume, heading, _cueSheet.headingAngle);
}
}
}
void Ambient::applySounds(uint32 fadeOutDelay) {
// Reset the random sounds
_cueStartTick = 0;
if (!_cueSheet.id) {
_vm->_sound->stopCue(fadeOutDelay);
}
// Age all sounds
_vm->_sound->age();
// Setup the selected sounds
for (uint i = 0; i < _sounds.size(); i++) {
const AmbientSound &sound = _sounds[i];
bool existingChannel;
SoundChannel *channel = _vm->_sound->getChannelForSound(sound.id, kAmbient, &existingChannel);
// The sound was already playing
if (!existingChannel) {
uint volume = 0;
// This check allows for proper fade in (when volumeFlag is 0) for sounds
// eg. when raising the elevator/platform to reach the main control panel
// for the resonating rings (the harmonic frequency sounds should fade in)
if (sound.volumeFlag)
volume = sound.volume;
channel->play(sound.id, volume, sound.heading, sound.headingAngle, true, kAmbient);
}
if (channel->_playing) {
channel->fade(sound.volume, sound.heading, sound.headingAngle, fadeOutDelay);
channel->_age = 0;
channel->_ambientFadeOutDelay = sound.fadeOutDelay;
}
}
// Fade out old playing ambient sounds
_vm->_sound->fadeOutOldSounds(fadeOutDelay);
}
} // End of namespace Myst3

85
engines/myst3/ambient.h Normal file
View File

@@ -0,0 +1,85 @@
/* 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/>.
*
*/
#ifndef AMBIENT_H_
#define AMBIENT_H_
#include "common/array.h"
namespace Myst3 {
class Myst3Engine;
class Ambient {
public:
Ambient(Myst3Engine *vm);
virtual ~Ambient();
void playCurrentNode(uint32 volume, uint32 fadeOutDelay);
void loadNode(uint32 node, uint32 room, uint32 age);
void applySounds(uint32 fadeOutDelay);
void scaleVolume(uint32 volume);
void addSound(uint32 id, int32 volume, int32 heading, int32 headingAngle, int32 u1, int32 fadeOutDelay);
void setCueSheet(uint32 id, int32 volume, int32 heading, int32 headingAngle);
void updateCue();
uint32 _scriptAge;
uint32 _scriptRoom;
private:
Myst3Engine *_vm;
struct AmbientSound {
uint32 id;
//char name[32];
int32 volume;
int32 volumeFlag;
int32 heading;
int32 headingAngle;
int32 u1;
int32 fadeOutDelay;
void reset() {
id = 0;
volume = 0;
volumeFlag = 0;
heading = 0;
headingAngle = 0;
u1 = 0;
fadeOutDelay = 0;
}
};
uint16 delayForCue(uint32 id);
uint32 nextCueSound(uint32 id);
Common::Array<AmbientSound> _sounds;
AmbientSound _cueSheet;
uint32 _cueStartTick;
};
} // End of namespace Myst3
#endif // AMBIENT_H_

302
engines/myst3/archive.cpp Normal file
View File

@@ -0,0 +1,302 @@
/* 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/archive.h"
#include "common/debug.h"
#include "common/memstream.h"
#include "common/substream.h"
namespace Myst3 {
void Archive::decryptHeader(Common::SeekableReadStream &inStream, Common::WriteStream &outStream) {
static const uint32 addKey = 0x3C6EF35F;
static const uint32 multKey = 0x0019660D;
inStream.seek(0);
uint32 size = inStream.readUint32LE();
bool encrypted = size > 1000000;
inStream.seek(0);
if (encrypted) {
uint32 decryptedSize = size ^ addKey;
uint32 currentKey = 0;
for (uint i = 0; i < decryptedSize; i++) {
currentKey += addKey;
outStream.writeUint32LE(inStream.readUint32LE() ^ currentKey);
currentKey *= multKey;
}
} else {
for (uint i = 0; i < size; i++) {
outStream.writeUint32LE(inStream.readUint32LE());
}
}
}
static Common::String readFixedString(Common::ReadStream &stream, uint32 length) {
Common::String value;
for (uint i = 0; i < length; i++) {
value += stream.readByte();
}
return value;
}
static uint32 readUint24(Common::ReadStream &stream) {
uint32 value = stream.readUint16LE();
value |= stream.readByte() << 16;
return value;
}
Archive::DirectorySubEntry Archive::readSubEntry(Common::ReadStream &stream) {
DirectorySubEntry subEntry;
subEntry.offset = stream.readUint32LE();
subEntry.size = stream.readUint32LE();
uint16 metadataSize = stream.readUint16LE();
subEntry.face = stream.readByte();
subEntry.type = static_cast<ResourceType>(stream.readByte());
subEntry.metadata.resize(metadataSize);
for (uint i = 0; i < metadataSize; i++) {
subEntry.metadata[i] = stream.readUint32LE();
}
return subEntry;
}
Archive::DirectoryEntry Archive::readEntry(Common::ReadStream &stream) {
DirectoryEntry entry;
if (_roomName.empty()) {
entry.roomName = readFixedString(stream, 4);
} else {
entry.roomName = _roomName;
}
entry.index = readUint24(stream);
byte subItemCount = stream.readByte();
entry.subentries.resize(subItemCount);
for (uint i = 0; i < subItemCount; i++) {
entry.subentries[i] = readSubEntry(stream);
}
return entry;
}
void Archive::readDirectory() {
Common::MemoryWriteStreamDynamic buf(DisposeAfterUse::YES);
decryptHeader(_file, buf);
Common::MemoryReadStream directory(buf.getData(), buf.size());
_directorySize = directory.readUint32LE();
while (directory.pos() + 4 < directory.size()) {
_directory.push_back(readEntry(directory));
}
}
void Archive::visit(ArchiveVisitor &visitor) {
visitor.visitArchive(*this);
for (uint i = 0; i < _directory.size(); i++) {
visitor.visitDirectoryEntry(_directory[i]);
for (uint j = 0; j < _directory[i].subentries.size(); j++) {
visitor.visitDirectorySubEntry(_directory[i].subentries[j]);
}
}
}
Common::SeekableReadStream *Archive::dumpToMemory(uint32 offset, uint32 size) {
_file.seek(offset);
return _file.readStream(size);
}
uint32 Archive::copyTo(uint32 offset, uint32 size, Common::WriteStream &out) {
Common::SeekableSubReadStream subStream(&_file, offset, offset + size);
subStream.seek(0);
return out.writeStream(&subStream);
}
const Archive::DirectoryEntry *Archive::getEntry(const Common::String &room, uint32 index) const {
for (uint i = 0; i < _directory.size(); i++) {
const DirectoryEntry &entry = _directory[i];
if (entry.index == index && entry.roomName == room) {
return &entry;
}
}
return nullptr;
}
ResourceDescription Archive::getDescription(const Common::String &room, uint32 index, uint16 face,
ResourceType type) {
const DirectoryEntry *entry = getEntry(room, index);
if (!entry) {
return ResourceDescription();
}
for (uint i = 0; i < entry->subentries.size(); i++) {
const DirectorySubEntry &subentry = entry->subentries[i];
if (subentry.face == face && subentry.type == type) {
return ResourceDescription(this, subentry);
}
}
return ResourceDescription();
}
ResourceDescriptionArray Archive::listFilesMatching(const Common::String &room, uint32 index, uint16 face,
ResourceType type) {
const DirectoryEntry *entry = getEntry(room, index);
if (!entry) {
return ResourceDescriptionArray();
}
ResourceDescriptionArray list;
for (uint i = 0; i < entry->subentries.size(); i++) {
const DirectorySubEntry &subentry = entry->subentries[i];
if (subentry.face == face && subentry.type == type) {
list.push_back(ResourceDescription(this, subentry));
}
}
return list;
}
bool Archive::open(const char *fileName, const char *room) {
// If the room name is not provided, it is assumed that
// we are opening a multi-room archive
if (room) {
_roomName = room;
}
if (_file.open(fileName)) {
readDirectory();
return true;
}
return false;
}
void Archive::close() {
_directorySize = 0;
_roomName.clear();
_directory.clear();
_file.close();
}
ResourceDescription::ResourceDescription() :
_archive(nullptr),
_subentry(nullptr) {
}
ResourceDescription::ResourceDescription(Archive *archive, const Archive::DirectorySubEntry &subentry) :
_archive(archive),
_subentry(&subentry) {
}
Common::SeekableReadStream *ResourceDescription::getData() const {
return _archive->dumpToMemory(_subentry->offset, _subentry->size);
}
ResourceDescription::SpotItemData ResourceDescription::getSpotItemData() const {
assert(_subentry->type == Archive::kSpotItem || _subentry->type == Archive::kLocalizedSpotItem);
SpotItemData spotItemData;
spotItemData.u = _subentry->metadata[0];
spotItemData.v = _subentry->metadata[1];
return spotItemData;
}
ResourceDescription::VideoData ResourceDescription::getVideoData() const {
VideoData videoData;
if (_subentry->type == Archive::kMovie || _subentry->type == Archive::kMultitrackMovie) {
videoData.v1.setValue(0, static_cast<int32>(_subentry->metadata[0]) * 0.000001f);
videoData.v1.setValue(1, static_cast<int32>(_subentry->metadata[1]) * 0.000001f);
videoData.v1.setValue(2, static_cast<int32>(_subentry->metadata[2]) * 0.000001f);
videoData.v2.setValue(0, static_cast<int32>(_subentry->metadata[3]) * 0.000001f);
videoData.v2.setValue(1, static_cast<int32>(_subentry->metadata[4]) * 0.000001f);
videoData.v2.setValue(2, static_cast<int32>(_subentry->metadata[5]) * 0.000001f);
videoData.u = static_cast<int32>(_subentry->metadata[6]);
videoData.v = static_cast<int32>(_subentry->metadata[7]);
videoData.width = static_cast<int32>(_subentry->metadata[8]);
videoData.height = static_cast<int32>(_subentry->metadata[9]);
}
return videoData;
}
uint32 ResourceDescription::getMiscData(uint index) const {
assert(_subentry->type == Archive::kNumMetadata || _subentry->type == Archive::kTextMetadata);
if (index == 0) {
return _subentry->offset;
} else if (index == 1) {
return _subentry->size;
} else {
return _subentry->metadata[index - 2];
}
}
Common::String ResourceDescription::getTextData(uint index) const {
assert(_subentry->type == Archive::kTextMetadata);
uint8 key = 35;
uint8 cnt = 0;
uint8 decrypted[89];
memset(decrypted, 0, sizeof(decrypted));
uint8 *out = &decrypted[0];
while (cnt / 4u < (_subentry->metadata.size() + 2) && cnt < 89) {
// XORed text stored in little endian 32 bit words
*out++ = (getMiscData(cnt / 4) >> (8 * (3 - (cnt % 4)))) ^ key++;
cnt++;
}
// decrypted contains a null separated string array
// extract the wanted one
cnt = 0;
int i = 0;
while (cnt < index) {
if (!decrypted[i])
cnt++;
i++;
}
Common::String text((const char *)&decrypted[i]);
return text;
}
ArchiveVisitor::~ArchiveVisitor() {
}
} // End of namespace Myst3

150
engines/myst3/archive.h Normal file
View File

@@ -0,0 +1,150 @@
/* 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/>.
*
*/
#ifndef MYST3_ARCHIVE_H
#define MYST3_ARCHIVE_H
#include "common/array.h"
#include "common/file.h"
#include "math/vector3d.h"
namespace Myst3 {
class ArchiveVisitor;
class ResourceDescription;
typedef Common::Array<ResourceDescription> ResourceDescriptionArray;
class Archive {
public:
enum ResourceType {
kCubeFace = 0,
kWaterEffectMask = 1,
kLavaEffectMask = 2,
kMagneticEffectMask = 3,
kShieldEffectMask = 4,
kSpotItem = 5,
kFrame = 6,
kRawData = 7,
kMovie = 8,
kStillMovie = 10,
kText = 11,
kTextMetadata = 12,
kNumMetadata = 13,
kLocalizedSpotItem = 69,
kLocalizedFrame = 70,
kMultitrackMovie = 72,
kDialogMovie = 74
};
struct DirectorySubEntry {
uint32 offset;
uint32 size;
byte face;
ResourceType type;
Common::Array<uint32> metadata;
DirectorySubEntry() : offset(0), size(0), face(0), type(kCubeFace) {}
};
struct DirectoryEntry {
Common::String roomName;
uint32 index;
Common::Array<DirectorySubEntry> subentries;
DirectoryEntry() : index(0) {}
};
ResourceDescription getDescription(const Common::String &room, uint32 index, uint16 face,
ResourceType type);
ResourceDescriptionArray listFilesMatching(const Common::String &room, uint32 index, uint16 face,
ResourceType type);
Common::SeekableReadStream *dumpToMemory(uint32 offset, uint32 size);
uint32 copyTo(uint32 offset, uint32 size, Common::WriteStream &out);
void visit(ArchiveVisitor &visitor);
bool open(const char *fileName, const char *room);
void close();
const Common::String &getRoomName() const { return _roomName; }
uint32 getDirectorySize() const { return _directorySize; }
private:
Common::String _roomName;
Common::File _file;
uint32 _directorySize;
Common::Array<DirectoryEntry> _directory;
void decryptHeader(Common::SeekableReadStream &inStream, Common::WriteStream &outStream);
void readDirectory();
DirectorySubEntry readSubEntry(Common::ReadStream &stream);
DirectoryEntry readEntry(Common::ReadStream &stream);
const DirectoryEntry *getEntry(const Common::String &room, uint32 index) const;
};
class ResourceDescription {
public:
struct SpotItemData {
uint32 u;
uint32 v;
};
struct VideoData {
Math::Vector3d v1;
Math::Vector3d v2;
int32 u;
int32 v;
int32 width;
int32 height;
};
ResourceDescription();
ResourceDescription(Archive *archive, const Archive::DirectorySubEntry &subentry);
bool isValid() const { return _archive && _subentry; }
Common::SeekableReadStream *getData() const;
uint16 getFace() const { return _subentry->face; }
Archive::ResourceType getType() const { return _subentry->type; }
SpotItemData getSpotItemData() const;
VideoData getVideoData() const;
uint32 getMiscData(uint index) const;
Common::String getTextData(uint index) const;
private:
Archive *_archive;
const Archive::DirectorySubEntry *_subentry;
};
class ArchiveVisitor {
public:
virtual ~ArchiveVisitor();
virtual void visitArchive(Archive &archive) {}
virtual void visitDirectoryEntry(Archive::DirectoryEntry &directoryEntry) {}
virtual void visitDirectorySubEntry(Archive::DirectorySubEntry &directorySubEntry) {}
};
} // End of namespace Myst3
#endif

View File

@@ -0,0 +1,3 @@
# This file is included from the main "configure" script
# add_engine [name] [desc] [build-by-default] [subengines] [base games] [deps] [components]
add_engine myst3 "Myst 3" yes "" "" "16bit 3d highres jpeg bink" "tinygl"

480
engines/myst3/console.cpp Normal file
View File

@@ -0,0 +1,480 @@
/* 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/console.h"
#include "engines/myst3/archive.h"
#include "engines/myst3/database.h"
#include "engines/myst3/effects.h"
#include "engines/myst3/inventory.h"
#include "engines/myst3/script.h"
#include "engines/myst3/state.h"
namespace Myst3 {
Console::Console(Myst3Engine *vm) : GUI::Debugger(), _vm(vm) {
registerCmd("infos", WRAP_METHOD(Console, Cmd_Infos));
registerCmd("lookAt", WRAP_METHOD(Console, Cmd_LookAt));
registerCmd("initScript", WRAP_METHOD(Console, Cmd_InitScript));
registerCmd("var", WRAP_METHOD(Console, Cmd_Var));
registerCmd("listNodes", WRAP_METHOD(Console, Cmd_ListNodes));
registerCmd("run", WRAP_METHOD(Console, Cmd_Run));
registerCmd("runOp", WRAP_METHOD(Console, Cmd_RunOp));
registerCmd("go", WRAP_METHOD(Console, Cmd_Go));
registerCmd("extract", WRAP_METHOD(Console, Cmd_Extract));
registerCmd("fillInventory", WRAP_METHOD(Console, Cmd_FillInventory));
registerCmd("dumpArchive", WRAP_METHOD(Console, Cmd_DumpArchive));
registerCmd("dumpMasks", WRAP_METHOD(Console, Cmd_DumpMasks));
}
Console::~Console() {
}
void Console::describeScript(const Common::Array<Opcode> &script) {
for (uint j = 0; j < script.size(); j++) {
debugPrintf("%s", _vm->_scriptEngine->describeOpcode(script[j]).c_str());
}
}
bool Console::Cmd_Infos(int argc, const char **argv) {
uint16 nodeId = _vm->_state->getLocationNode();
uint32 roomId = _vm->_state->getLocationRoom();
uint32 ageID = _vm->_state->getLocationAge();
if (argc >= 2) {
nodeId = atoi(argv[1]);
}
if (argc >= 3) {
RoomKey roomKey = _vm->_db->getRoomKey(argv[2]);
if (roomKey.roomID == 0 || roomKey.ageID == 0) {
debugPrintf("Unknown room name %s\n", argv[2]);
return true;
}
roomId = roomKey.roomID;
ageID = roomKey.ageID;
}
NodePtr nodeData = _vm->_db->getNodeData(nodeId, roomId, ageID);
if (!nodeData) {
debugPrintf("No node with id %d\n", nodeId);
return true;
}
Common::String roomName = _vm->_db->getRoomName(roomId, ageID);
debugPrintf("node: %s %d ", roomName.c_str(), nodeId);
for (uint i = 0; i < nodeData->scripts.size(); i++) {
debugPrintf("\ninit %d > %s (%s)\n", i,
_vm->_state->describeCondition(nodeData->scripts[i].condition).c_str(),
_vm->_state->evaluate(nodeData->scripts[i].condition) ? "true" : "false");
describeScript(nodeData->scripts[i].script);
}
for (uint i = 0; i < nodeData->hotspots.size(); i++) {
debugPrintf("\nhotspot %d > %s (%s)\n", i,
_vm->_state->describeCondition(nodeData->hotspots[i].condition).c_str(),
_vm->_state->evaluate(nodeData->hotspots[i].condition) ? "true" : "false");
for(uint j = 0; j < nodeData->hotspots[i].rects.size(); j++) {
PolarRect &rect = nodeData->hotspots[i].rects[j];
debugPrintf(" rect > pitch: %d heading: %d width: %d height: %d\n",
rect.centerPitch, rect.centerHeading, rect.width, rect.height);
}
describeScript(nodeData->hotspots[i].script);
}
for (uint i = 0; i < nodeData->soundScripts.size(); i++) {
debugPrintf("\nsound %d > %s (%s)\n", i,
_vm->_state->describeCondition(nodeData->soundScripts[i].condition).c_str(),
_vm->_state->evaluate(nodeData->soundScripts[i].condition) ? "true" : "false");
describeScript(nodeData->soundScripts[i].script);
}
for (uint i = 0; i < nodeData->backgroundSoundScripts.size(); i++) {
debugPrintf("\nbackground sound %d > %s (%s)\n", i,
_vm->_state->describeCondition(nodeData->backgroundSoundScripts[i].condition).c_str(),
_vm->_state->evaluate(nodeData->backgroundSoundScripts[i].condition) ? "true" : "false");
describeScript(nodeData->backgroundSoundScripts[i].script);
}
return true;
}
bool Console::Cmd_LookAt(int argc, const char **argv) {
if (argc != 1 && argc != 3) {
debugPrintf("Usage :\n");
debugPrintf("lookAt pitch heading\n");
return true;
}
float pitch = _vm->_state->getLookAtPitch();
float heading = _vm->_state->getLookAtHeading();
debugPrintf("pitch: %d heading: %d\n", (int)pitch, (int)heading);
if (argc >= 3){
_vm->_state->lookAt(atof(argv[1]), atof(argv[2]));
return false;
}
return true;
}
bool Console::Cmd_InitScript(int argc, const char **argv) {
describeScript(_vm->_db->getNodeInitScript());
return true;
}
bool Console::Cmd_Var(int argc, const char **argv) {
if (argc != 2 && argc != 3) {
debugPrintf("Usage :\n");
debugPrintf("var variable : Display var value\n");
debugPrintf("var variable value : Change var value\n");
return true;
}
uint16 var = atoi(argv[1]);
if (var < 1 || var > 2047) {
debugPrintf("Variable out of range %d\n", var);
return true;
}
uint32 value = _vm->_state->getVar(var);
if (argc == 3) {
value = atoi(argv[2]);
_vm->_state->setVar(var, value);
}
debugPrintf("%s: %d\n", _vm->_state->describeVar(var).c_str(), value);
return true;
}
bool Console::Cmd_ListNodes(int argc, const char **argv) {
uint32 roomID = _vm->_state->getLocationRoom();
uint32 ageID = _vm->_state->getLocationAge();
if (argc == 2) {
RoomKey roomKey = _vm->_db->getRoomKey(argv[1]);
if (roomKey.roomID == 0 || roomKey.ageID == 0) {
debugPrintf("Unknown room name %s\n", argv[1]);
return true;
}
roomID = roomKey.roomID;
ageID = roomKey.ageID;
}
debugPrintf("Nodes:\n");
Common::Array<uint16> list = _vm->_db->listRoomNodes(roomID, ageID);
for (uint i = 0; i < list.size(); i++) {
debugPrintf("%d\n", list[i]);
}
return true;
}
bool Console::Cmd_Run(int argc, const char **argv) {
uint16 nodeId = _vm->_state->getLocationNode();
uint32 roomId = _vm->_state->getLocationRoom();
uint32 ageId = _vm->_state->getLocationAge();
if (argc >= 2) {
nodeId = atoi(argv[1]);
}
if (argc >= 3) {
RoomKey roomKey = _vm->_db->getRoomKey(argv[2]);
if (roomKey.roomID == 0 || roomKey.ageID == 0) {
debugPrintf("Unknown room name %s\n", argv[2]);
return true;
}
roomId = roomKey.roomID;
ageId = roomKey.ageID;
}
_vm->runScriptsFromNode(nodeId, roomId, ageId);
return false;
}
bool Console::Cmd_RunOp(int argc, const char **argv) {
if (argc < 2) {
debugPrintf("Usage :\n");
debugPrintf("runOp [opcode] [argument 1] [argument 2] ... : Run specified command\n");
return true;
}
Opcode op;
op.op = atoi(argv[1]);
for (int i = 2; i < argc; i++) {
op.args.push_back(atoi(argv[i]));
}
debugPrintf("Running opcode :\n");
debugPrintf("%s\n", _vm->_scriptEngine->describeOpcode(op).c_str());
_vm->_scriptEngine->runSingleOp(op);
return false;
}
bool Console::Cmd_Go(int argc, const char **argv) {
if (argc != 3) {
debugPrintf("Usage :\n");
debugPrintf("go [room name] [node id] : Go to node\n");
return true;
}
RoomKey roomKey = _vm->_db->getRoomKey(argv[1]);
if (roomKey.roomID == 0 || roomKey.ageID == 0) {
debugPrintf("Unknown room name %s\n", argv[1]);
return true;
}
uint16 nodeId = atoi(argv[2]);
_vm->_state->setLocationNextAge(roomKey.ageID);
_vm->_state->setLocationNextRoom(roomKey.roomID);
_vm->_state->setLocationNextNode(nodeId);
_vm->goToNode(0, kTransitionFade);
return false;
}
bool Console::Cmd_Extract(int argc, const char **argv) {
if (argc != 5) {
debugPrintf("Extract a file from the game's archives\n");
debugPrintf("Usage :\n");
debugPrintf("extract [room] [node id] [face number] [object type]\n");
return true;
}
// Room names are uppercase
Common::String room = Common::String(argv[1]);
room.toUppercase();
uint16 id = atoi(argv[2]);
uint16 face = atoi(argv[3]);
Archive::ResourceType type = (Archive::ResourceType) atoi(argv[4]);
ResourceDescription desc = _vm->getFileDescription(room, id, face, type);
if (!desc.isValid()) {
debugPrintf("File with room %s, id %d, face %d and type %d does not exist\n", room.c_str(), id, face, type);
return true;
}
Common::SeekableReadStream *s = desc.getData();
Common::Path filename(Common::String::format("node%s_%d_face%d.%d", room.c_str(), id, face, type));
Common::DumpFile f;
f.open(filename);
uint8 *buf = new uint8[s->size()];
s->read(buf, s->size());
f.write(buf, s->size());
delete[] buf;
f.close();
delete s;
debugPrintf("File '%s' successfully written\n", filename.toString(Common::Path::kNativeSeparator).c_str());
return true;
}
bool Console::Cmd_FillInventory(int argc, const char **argv) {
_vm->_inventory->addAll();
return false;
}
class DumpingArchiveVisitor : public ArchiveVisitor {
public:
DumpingArchiveVisitor() :
_archive(nullptr),
_currentDirectoryEntry(nullptr) {
}
void visitArchive(Archive &archive) override {
_archive = &archive;
}
void visitDirectoryEntry(Archive::DirectoryEntry &directoryEntry) override {
_currentDirectoryEntry = &directoryEntry;
}
void visitDirectorySubEntry(Archive::DirectorySubEntry &directorySubEntry) override {
assert(_currentDirectoryEntry);
Common::Path fileName;
switch (directorySubEntry.type) {
case Archive::kNumMetadata:
case Archive::kTextMetadata:
return; // These types are pure metadata and can't be extracted
case Archive::kCubeFace:
case Archive::kSpotItem:
case Archive::kLocalizedSpotItem:
case Archive::kFrame:
fileName = Common::Path(Common::String::format("dump/%s-%d-%d.jpg", _currentDirectoryEntry->roomName.c_str(), _currentDirectoryEntry->index, directorySubEntry.face));
break;
case Archive::kWaterEffectMask:
fileName = Common::Path(Common::String::format("dump/%s-%d-%d.mask", _currentDirectoryEntry->roomName.c_str(), _currentDirectoryEntry->index, directorySubEntry.face));
break;
case Archive::kMovie:
case Archive::kStillMovie:
case Archive::kDialogMovie:
case Archive::kMultitrackMovie:
fileName = Common::Path(Common::String::format("dump/%s-%d.bik", _currentDirectoryEntry->roomName.c_str(), _currentDirectoryEntry->index));
break;
default:
fileName = Common::Path(Common::String::format("dump/%s-%d-%d.%d", _currentDirectoryEntry->roomName.c_str(), _currentDirectoryEntry->index, directorySubEntry.face, directorySubEntry.type));
break;
}
debug("Extracted %s", fileName.toString(Common::Path::kNativeSeparator).c_str());
Common::DumpFile outFile;
if (!outFile.open(fileName, true))
error("Unable to open file '%s' for writing", fileName.toString(Common::Path::kNativeSeparator).c_str());
_archive->copyTo(directorySubEntry.offset, directorySubEntry.size, outFile);
outFile.close();
}
private:
Archive *_archive;
const Archive::DirectoryEntry *_currentDirectoryEntry;
};
bool Console::Cmd_DumpArchive(int argc, const char **argv) {
if (argc != 2) {
debugPrintf("Extract all the files from a game archive.\n");
debugPrintf("The destination folder, named 'dump', must exist.\n");
debugPrintf("Usage :\n");
debugPrintf("dumpArchive [file name]\n");
return true;
}
// Is the archive multi-room
Common::String temp = Common::String(argv[1]);
temp.toUppercase();
bool multiRoom = !temp.hasSuffix(".M3A");
if (!multiRoom) {
temp = Common::String(argv[1], 4);
temp.toUppercase();
}
Archive archive;
if (!archive.open(argv[1], multiRoom ? nullptr : temp.c_str())) {
debugPrintf("Can't open archive with name '%s'\n", argv[1]);
return true;
}
DumpingArchiveVisitor dumper;
archive.visit(dumper);
archive.close();
return true;
}
bool Console::Cmd_DumpMasks(int argc, const char **argv) {
if (argc != 1 && argc != 2) {
debugPrintf("Extract the masks of the faces of a cube node.\n");
debugPrintf("The destination folder, named 'dump', must exist.\n");
debugPrintf("Usage :\n");
debugPrintf("dumpMasks [node]\n");
return true;
}
uint16 nodeId = _vm->_state->getLocationNode();
if (argc >= 2) {
nodeId = atoi(argv[1]);
}
debugPrintf("Extracting masks for node %d:\n", nodeId);
for (uint i = 0; i < 6; i++) {
bool water = dumpFaceMask(nodeId, i, Archive::kWaterEffectMask);
if (water)
debugPrintf("Face %d: water OK\n", i);
bool effect2 = dumpFaceMask(nodeId, i, Archive::kLavaEffectMask);
if (effect2)
debugPrintf("Face %d: effect 2 OK\n", i);
bool magnet = dumpFaceMask(nodeId, i, Archive::kMagneticEffectMask);
if (magnet)
debugPrintf("Face %d: magnet OK\n", i);
if (!water && !effect2 && !magnet)
debugPrintf("Face %d: No mask found\n", i);
}
return true;
}
bool Console::dumpFaceMask(uint16 index, int face, Archive::ResourceType type) {
ResourceDescription maskDesc = _vm->getFileDescription("", index, face, type);
if (!maskDesc.isValid())
return false;
Common::SeekableReadStream *maskStream = maskDesc.getData();
Effect::FaceMask *mask = Effect::loadMask(maskStream);
delete maskStream;
Common::DumpFile outFile;
Common::Path fileName(Common::String::format("dump/%d-%d.masku_%d", index, face, type));
outFile.open(fileName);
outFile.write(mask->surface->getPixels(), mask->surface->pitch * mask->surface->h);
outFile.close();
delete mask;
return true;
}
} // End of namespace Myst3

63
engines/myst3/console.h Normal file
View File

@@ -0,0 +1,63 @@
/* 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/>.
*
*/
#ifndef CONSOLE_H_
#define CONSOLE_H_
#include "common/debug.h"
#include "gui/debugger.h"
#include "engines/myst3/myst3.h"
namespace Myst3 {
class Myst3Engine;
struct Opcode;
class Console : public GUI::Debugger {
public:
Console(Myst3Engine *vm);
virtual ~Console();
private:
Myst3Engine *_vm;
void describeScript(const Common::Array<Opcode> &script);
bool dumpFaceMask(uint16 index, int face, Archive::ResourceType type);
bool Cmd_Infos(int argc, const char **argv);
bool Cmd_LookAt(int argc, const char **argv);
bool Cmd_InitScript(int argc, const char **argv);
bool Cmd_Var(int argc, const char **argv);
bool Cmd_ListNodes(int argc, const char **argv);
bool Cmd_Run(int argc, const char **argv);
bool Cmd_RunOp(int argc, const char **argv);
bool Cmd_Go(int argc, const char **argv);
bool Cmd_Extract(int argc, const char **argv);
bool Cmd_DumpArchive(int argc, const char **argv);
bool Cmd_DumpMasks(int argc, const char **argv);
bool Cmd_FillInventory(int argc, const char **argv);
};
} // End of namespace Myst3
#endif // CONSOLE_H_

3
engines/myst3/credits.pl Normal file
View File

@@ -0,0 +1,3 @@
begin_section("Myst 3");
add_person("Bastien Bouclet", "bgK", "");
end_section();

255
engines/myst3/cursor.cpp Normal file
View File

@@ -0,0 +1,255 @@
/* 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/archive.h"
#include "engines/myst3/cursor.h"
#include "engines/myst3/gfx.h"
#include "engines/myst3/myst3.h"
#include "engines/myst3/scene.h"
#include "engines/myst3/state.h"
#include "graphics/surface.h"
#include "image/bmp.h"
namespace Myst3 {
struct CursorData {
uint32 nodeID;
uint16 hotspotX;
uint16 hotspotY;
float transparency;
float transparencyXbox;
};
static const CursorData availableCursors[] = {
{ 1000, 8, 8, 0.25f, 0.00f }, // Default cursor
{ 1001, 8, 8, 0.50f, 0.50f }, // On top of inventory item
{ 1002, 8, 8, 0.50f, 0.50f }, // Drag cursor
{ 1003, 1, 5, 0.50f, 0.50f },
{ 1004, 14, 5, 0.50f, 0.50f },
{ 1005, 16, 14, 0.50f, 0.50f },
{ 1006, 16, 14, 0.50f, 0.50f },
{ 1007, 8, 8, 0.55f, 0.55f },
{ 1000, 8, 8, 0.25f, 0.00f }, // Default cursor
{ 1001, 8, 8, 0.50f, 0.50f },
{ 1011, 16, 16, 0.50f, 0.50f },
{ 1000, 6, 1, 0.50f, 0.50f },
{ 1000, 8, 8, 0.00f, 0.25f } // Invisible cursor
};
Cursor::Cursor(Myst3Engine *vm) :
_vm(vm),
_position(vm->_scene->getCenter()),
_hideLevel(0),
_lockedAtCenter(false) {
// The cursor is manually scaled
_scaled = false;
_isConstrainedToWindow = false;
// Load available cursors
loadAvailableCursors();
// Set default cursor
changeCursor(8);
}
void Cursor::loadAvailableCursors() {
assert(_textures.empty());
// Load available cursors
for (uint i = 0; i < ARRAYSIZE(availableCursors); i++) {
// Check if a cursor sharing the same texture has already been loaded
if (_textures.contains(availableCursors[i].nodeID)) continue;
// Load the cursor bitmap
ResourceDescription cursorDesc = _vm->getFileDescription("GLOB", availableCursors[i].nodeID, 0, Archive::kRawData);
if (!cursorDesc.isValid())
error("Cursor %d does not exist", availableCursors[i].nodeID);
Common::SeekableReadStream *bmpStream = cursorDesc.getData();
Image::BitmapDecoder bitmapDecoder;
if (!bitmapDecoder.loadStream(*bmpStream))
error("Could not decode Myst III bitmap");
const Graphics::Surface *surfaceBGRA = bitmapDecoder.getSurface();
Graphics::Surface *surfaceRGBA = surfaceBGRA->convertTo(Texture::getRGBAPixelFormat());
delete bmpStream;
// Apply the colorkey for transparency
for (int y = 0; y < surfaceRGBA->h; y++) {
byte *pixels = (byte *)(surfaceRGBA->getBasePtr(0, y));
for (int x = 0; x < surfaceRGBA->w; x++) {
byte *r = pixels + 0;
byte *g = pixels + 1;
byte *b = pixels + 2;
byte *a = pixels + 3;
if (*r == 0 && *g == 0xFF && *b == 0 && *a == 0xFF) {
*g = 0;
*a = 0;
}
pixels += 4;
}
}
// Create and store the texture
_textures.setVal(availableCursors[i].nodeID, _vm->_gfx->createTexture2D(surfaceRGBA));
surfaceRGBA->free();
delete surfaceRGBA;
}
}
Cursor::~Cursor() {
// Free cursors textures
for (TextureMap::iterator it = _textures.begin(); it != _textures.end(); it++) {
delete it->_value;
}
}
void Cursor::changeCursor(uint32 index) {
if (index >= ARRAYSIZE(availableCursors))
return;
if (_vm->getPlatform() == Common::kPlatformXbox) {
// The cursor is hidden when it is not hovering hotspots
if ((index == 0 || index == 8) && _vm->_state->getViewType() != kCube)
index = 12;
}
_currentCursorID = index;
}
float Cursor::getTransparencyForId(uint32 cursorId) {
assert(cursorId < ARRAYSIZE(availableCursors));
if (_vm->getPlatform() == Common::kPlatformXbox) {
return availableCursors[cursorId].transparencyXbox;
} else {
return availableCursors[cursorId].transparency;
}
}
void Cursor::lockPosition(bool lock) {
if (_lockedAtCenter == lock)
return;
_lockedAtCenter = lock;
g_system->lockMouse(lock);
Common::Point center = _vm->_scene->getCenter();
if (_lockedAtCenter) {
// Locking, just move the cursor at the center of the screen
_position = center;
} else {
// Unlocking, warp the actual mouse position to the cursor
g_system->warpMouse(center.x, center.y);
}
}
void Cursor::updatePosition(const Common::Point &mouse) {
if (!_lockedAtCenter) {
_position = mouse;
} else {
_position = _vm->_scene->getCenter();
}
}
Common::Point Cursor::getPosition(bool scaled) {
if (scaled) {
Common::Rect viewport = _vm->_gfx->viewport();
// The rest of the engine expects 640x480 coordinates
Common::Point scaledPosition = _position;
scaledPosition.x -= viewport.left;
scaledPosition.y -= viewport.top;
scaledPosition.x = CLIP<int16>(scaledPosition.x, 0, viewport.width());
scaledPosition.y = CLIP<int16>(scaledPosition.y, 0, viewport.height());
scaledPosition.x *= Renderer::kOriginalWidth / (float) viewport.width();
scaledPosition.y *= Renderer::kOriginalHeight / (float) viewport.height();
return scaledPosition;
} else {
return _position;
}
}
void Cursor::draw() {
assert(_currentCursorID < ARRAYSIZE(availableCursors));
const CursorData &cursor = availableCursors[_currentCursorID];
Texture *texture = _textures[cursor.nodeID];
if (!texture) {
error("No texture for cursor with id %d", cursor.nodeID);
}
// Rect where to draw the cursor
Common::Rect viewport = _vm->_gfx->viewport();
float scale = MIN(
viewport.width() / (float) Renderer::kOriginalWidth,
viewport.height() / (float) Renderer::kOriginalHeight
);
Common::Rect screenRect = Common::Rect(texture->width * scale, texture->height * scale);
screenRect.translate(_position.x - cursor.hotspotX * scale, _position.y - cursor.hotspotY * scale);
// Texture rect
Common::Rect textureRect = Common::Rect(texture->width, texture->height);
float transparency = 1.0f;
int32 varTransparency = _vm->_state->getCursorTransparency();
if (_lockedAtCenter || varTransparency == 0) {
if (varTransparency >= 0)
transparency = varTransparency / 100.0f;
else
transparency = getTransparencyForId(_currentCursorID);
}
_vm->_gfx->drawTexturedRect2D(screenRect, textureRect, texture, transparency);
}
void Cursor::setVisible(bool show) {
if (show)
_hideLevel = MAX<int32>(0, --_hideLevel);
else
_hideLevel++;
}
bool Cursor::isVisible() {
return !_hideLevel && !_vm->_state->getCursorHidden() && !_vm->_state->getCursorLocked();
}
void Cursor::getDirection(float &pitch, float &heading) {
if (_lockedAtCenter) {
pitch = _vm->_state->getLookAtPitch();
heading = _vm->_state->getLookAtHeading();
} else {
_vm->_scene->screenPosToDirection(_position, pitch, heading);
}
}
} // End of namespace Myst3

80
engines/myst3/cursor.h Normal file
View File

@@ -0,0 +1,80 @@
/* 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/>.
*
*/
#ifndef CURSOR_H_
#define CURSOR_H_
#include "common/hashmap.h"
#include "common/rect.h"
#include "engines/myst3/gfx.h"
namespace Myst3 {
class Myst3Engine;
class Texture;
class Cursor : public Drawable {
public:
Cursor(Myst3Engine *vm);
virtual ~Cursor();
void changeCursor(uint32 index);
bool isPositionLocked() { return _lockedAtCenter; }
void lockPosition(bool lock);
/**
* Get the mouse cursor position
*
* By default it is in 640x480 equivalent coordinates
*
* @param scaled When false the position is in actual game screen coordinates.
* @return
*/
Common::Point getPosition(bool scaled = true);
void updatePosition(const Common::Point &mouse);
void getDirection(float &pitch, float &heading);
void draw() override;
void setVisible(bool show);
bool isVisible();
private:
Myst3Engine *_vm;
uint32 _currentCursorID;
int32 _hideLevel;
/** Position of the cursor */
Common::Point _position;
typedef Common::HashMap<uint32, Texture *> TextureMap;
TextureMap _textures;
bool _lockedAtCenter;
void loadAvailableCursors();
float getTransparencyForId(uint32 cursorId);
};
} // End of namespace Myst3
#endif // CURSOR_H_

896
engines/myst3/database.cpp Normal file
View 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

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

@@ -0,0 +1,282 @@
/* 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/>.
*
*/
#ifndef DATABASE_H_
#define DATABASE_H_
#include "engines/myst3/hotspot.h"
#include "engines/myst3/detection.h"
#include "common/scummsys.h"
#include "common/str.h"
#include "common/language.h"
#include "common/platform.h"
#include "common/ptr.h"
#include "common/array.h"
#include "common/hashmap.h"
#include "common/stream.h"
namespace Myst3 {
enum MystLanguage {
kEnglish = 0,
kOther = 1, // Dutch, Japanese or Polish
kDutch = 1,
kFrench = 2,
kGerman = 3,
kItalian = 4,
kSpanish = 5
};
enum NodeID {
kNodeSharedInit = 1,
kNodeLogoPlay = 1,
kNodeMenuNewGame = 98,
kNodeMenuMain = 100,
kNodeMenuLoadGame = 200,
kNodeMenuSaveGame = 300
};
enum RoomID {
kRoomShared = 101,
kRoomIntro = 201,
kRoomTomahnaStart = 301,
kRoomTomahnaReturn = 401,
kJnaninStart = 501,
kRoomLeos = 502,
kRoomLeet = 503,
kRoomLelt = 504,
kRoomLemt = 505,
kRoomLeof = 506,
kRoomEdannaStart = 601,
kRoomLisw = 602,
kRoomLifo = 603,
kRoomLisp = 604,
kRoomLine = 605,
kRoomVoltaicStart = 701,
kRoomEnpp = 703,
kRoomEnem = 704,
kRoomEnlc = 705,
kRoomEndd = 706,
kRoomEnch = 707,
kRoomEnli = 708,
kRoomNarayan = 801,
kRoomMenu = 901,
kRoomJournals = 902,
kRoomDemo = 903,
kRoomAtix = 904,
kRoomAmateriaStart = 1001,
kRoomMais = 1002,
kRoomMall = 1003,
kRoomMass = 1004,
kRoomMaww = 1005,
kRoomMato = 1006,
kLogo = 1101
};
struct NodeData {
int16 id;
int16 zipBitIndex;
Common::Array<CondScript> scripts;
Common::Array<HotSpot> hotspots;
Common::Array<CondScript> soundScripts;
Common::Array<CondScript> backgroundSoundScripts;
};
// Nodes are using ref counting pointers since they can be
// deleted by a script they own
typedef Common::SharedPtr<NodeData> NodePtr;
struct RoomData {
uint32 id;
const char *name;
};
struct RoomKey {
uint16 ageID;
uint16 roomID;
RoomKey(uint16 room, uint16 age) : roomID(room), ageID(age) {};
bool operator==(const RoomKey &k) const {
return ageID == k.ageID && roomID == k.roomID;
}
};
struct AgeData {
uint32 id;
uint32 disk;
uint32 roomCount;
const RoomData *rooms;
uint32 labelId;
};
struct AmbientCue {
uint16 id;
uint16 minFrames;
uint16 maxFrames;
Common::Array<uint16> tracks;
};
/**
* Script types stored in 'myst3.dat'
*/
enum ScriptType {
kScriptTypeNode,
kScriptTypeAmbientSound,
kScriptTypeBackgroundSound,
kScriptTypeNodeInit,
kScriptTypeAmbientCue
};
/**
* A script index entry in the 'myst3.dat' file
*/
struct RoomScripts {
Common::String room;
ScriptType type;
uint offset;
uint size;
};
class Myst3Engine;
class Database {
public:
Database(const Common::Platform platform, const Common::Language language, const uint32 localizationType);
~Database();
/**
* Loads a room's nodes into the database cache
*/
void cacheRoom(uint32 roomID, uint32 ageID);
/**
* Tells if a room is a common room
*
* Common rooms are always in the cache
*/
bool isCommonRoom(uint32 roomID, uint32 ageID) const;
/**
* Returns a node's hotspots and scripts from the currently loaded room
*/
NodePtr getNodeData(uint16 nodeID, uint32 roomID, uint32 ageID);
/**
* Returns a node's zip id, as used by savestates
*/
int32 getNodeZipBitIndex(uint16 nodeID, uint32 roomID, uint32 ageID);
/**
* Returns the generic node init script
*/
const Common::Array<Opcode>& getNodeInitScript() { return _nodeInitScript; }
/**
* Returns the name of the currently loaded room
*/
Common::String getRoomName(uint32 roomID, uint32 ageID) const;
/**
* Returns the id of a room from its name
*/
RoomKey getRoomKey(const char *name);
/**
* Returns the list of the nodes of a room
*/
Common::Array<uint16> listRoomNodes(uint32 roomID, uint32 ageID);
/**
* Returns an age's label id, to be used with AGES 1000 metadata
*/
uint32 getAgeLabelId(uint32 ageID);
/**
* Retrieve the file name of a sound from its id
*/
Common::String getSoundName(uint32 id);
/** Get the sound variable id range */
uint32 getSoundIdMin() const { return _soundIdMin; }
uint32 getSoundIdMax() const { return _soundIdMax; }
/**
* Retrieve an ambient cue from its id
*/
const AmbientCue &getAmbientCue(uint16 id);
int16 getGameLanguageCode() const;
/** Check if the scripts for two rooms are identical */
bool areRoomsScriptsEqual(uint32 roomID1, uint32 ageID1, uint32 roomID2, uint32 ageID2, ScriptType scriptType);
private:
struct RoomKeyHash {
uint operator()(const RoomKey &v) const {
return v.ageID + (v.roomID << 16);
}
};
typedef Common::HashMap<RoomKey, Common::Array<NodePtr>, RoomKeyHash> NodesCache;
const Common::Platform _platform;
const Common::Language _language;
const uint32 _localizationType;
static const AgeData _ages[];
NodesCache _roomNodesCache;
Common::Array<Opcode> _nodeInitScript;
uint32 _soundIdMin;
uint32 _soundIdMax;
Common::HashMap<uint32, Common::String> _soundNames;
Common::HashMap<uint16, AmbientCue> _ambientCues;
Common::HashMap<uint32, int16> _roomZipBitIndex;
// 'myst3.dat' cached data
static const uint kDatVersion = 3;
Common::SeekableReadStream *_datFile;
Common::Array<RoomScripts> _roomScriptsIndex;
int32 _roomScriptsStartOffset;
const RoomData *findRoomData(uint32 roomID, uint32 ageID) const;
Common::Array<NodePtr> getRoomNodes(uint32 roomID, uint32 ageID) const;
Common::Array<NodePtr> readRoomScripts(const RoomData *room) const;
void preloadCommonRooms();
void initializeZipBitIndexTable();
void patchLanguageMenu();
void patchNodeScripts(const RoomData *room, Common::Array<NodePtr> &nodes) const;
// 'myst3.dat' read methods
void readScriptIndex(Common::SeekableReadStream *stream, bool load);
void readSoundNames(Common::SeekableReadStream *stream, bool load);
void loadAmbientCues(Common::ReadStream *s);
Common::SeekableReadStream *getRoomScriptStream(const char *room, ScriptType scriptType) const;
};
} // End of namespace Myst3
#endif // DATABASE_H_

295
engines/myst3/detection.cpp Normal file
View File

@@ -0,0 +1,295 @@
/* 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 "common/translation.h"
#include "engines/advancedDetector.h"
#include "engines/myst3/detection.h"
#include "engines/myst3/myst3.h"
namespace Myst3 {
static const PlainGameDescriptor myst3Games[] = {
{ "myst3", "Myst III: Exile" },
{ nullptr, nullptr }
};
static const DebugChannelDef debugFlagList[] = {
{Myst3::kDebugVariable, "Variable", "Track Variable Accesses"},
{Myst3::kDebugSaveLoad, "SaveLoad", "Track Save/Load Function"},
{Myst3::kDebugScript, "Script", "Track Script Execution"},
{Myst3::kDebugNode, "Node", "Track Node Changes"},
DEBUG_CHANNEL_END
};
static const char *const directoryGlobs[] = {
"bin",
"M3Data",
"MYST3BIN",
"TEXT",
"Myst III Spanish", // For the DVD original layout
"Dutch", // For the CD multilingual original layout
"Spanish",
nullptr
};
#define MYST3ENTRY(lang, langFile, md5lang, extra, flags) \
{ \
{ \
"myst3", \
extra, \
{ \
{ "RSRC.m3r", 0, "a2c8ed69800f60bf5667e5c76a88e481", 1223862 }, \
{ langFile, 0, md5lang, AD_NO_SIZE }, \
}, \
lang, \
Common::kPlatformWindows, \
ADGF_NO_FLAGS, \
GUIO_NONE \
}, \
flags \
},
#define MYST3ENTRY_DVD(lang, langFile, md5lang, extra, flags) \
{ \
{ \
"myst3", \
extra, \
{ \
{ "RSRC.m3r", 0, "a2c8ed69800f60bf5667e5c76a88e481", 1223862 }, \
{ "ENGLISH.m3t", 0, "74726de866c0594d3f2a05ff754c973d", 3407120 }, \
{ langFile, 0, md5lang, AD_NO_SIZE }, \
}, \
lang, \
Common::kPlatformWindows, \
ADGF_NO_FLAGS, \
GUIO_NONE \
}, \
flags \
},
#define MYST3ENTRY_XBOX(lang, langFile, md5lang) \
{ \
{ \
"myst3", \
0, \
{ \
{ "RSRC.m3r", 0, "3de23eb5a036a62819186105478f9dde", 1226192 }, \
{ langFile, 0, md5lang, AD_NO_SIZE }, \
}, \
lang, \
Common::kPlatformXbox, \
ADGF_UNSTABLE, \
GUIO_NONE \
}, \
kLocMulti6 \
},
static const Myst3GameDescription gameDescriptions[] = {
// Initial US release (English only) v1.0
MYST3ENTRY(Common::EN_ANY, "ENGLISH.m3t", "19dcba1074f235ec2119313242d891de", nullptr, kLocMonolingual)
// Initial US release (English only) v1.22
MYST3ENTRY(Common::EN_ANY, "ENGLISH.m3t", "3ca92b097c4319a2ace7fd6e911d6b0f", nullptr, kLocMonolingual)
// European releases (Country language + English) (1.2)
MYST3ENTRY(Common::NL_NLD, "DUTCH.m3u", "0e8019cfaeb58c2de00ac114cf122220", nullptr, kLocMulti2)
MYST3ENTRY(Common::FR_FRA, "FRENCH.m3u", "3a7e270c686806dfc31c2091e09c03ec", nullptr, kLocMulti2)
MYST3ENTRY(Common::DE_DEU, "GERMAN.m3u", "1b2fa162a951fa4ed65617dd3f0c8a53", nullptr, kLocMulti2) // #1323, andrews05
MYST3ENTRY(Common::IT_ITA, "ITALIAN.m3u", "906645a87ac1cbbd2b88c277c2b4fda2", nullptr, kLocMulti2) // #1323, andrews05
MYST3ENTRY(Common::ES_ESP, "SPANISH.m3u", "28003569d9536cbdf6020aee8e9bcd15", nullptr, kLocMulti2) // #1323, goodoldgeorge
MYST3ENTRY(Common::PL_POL, "POLISH.m3u", "8075e4e822e100ec79a5842a530dbe24", nullptr, kLocMulti2)
// Russian release (Russian only) (1.2)
MYST3ENTRY(Common::RU_RUS, "ENGLISH.m3t", "57d36d8610043fda554a0708d71d2681", nullptr, kLocMonolingual)
// Hebrew release (Hebrew only) (1.2 - Patched using the patch CD)
MYST3ENTRY(Common::HE_ISR, "HEBREW.m3u", "16fbbe420fed366249a8d44a759f966c", nullptr, kLocMonolingual) // #1348, BLooperZ
// Japanese release (1.2)
MYST3ENTRY(Common::JA_JPN, "JAPANESE.m3u", "21bbd040bcfadd13b9dc84360c3de01d", nullptr, kLocMulti2)
MYST3ENTRY(Common::JA_JPN, "JAPANESE.m3u", "1e7c3156417978a1187fa6bc0e2cfafc", "Subtitles only", kLocMulti2)
// Multilingual CD release (1.21)
MYST3ENTRY(Common::EN_ANY, "ENGLISH.m3u", "b62ca55aa17724cddbbcc78cba988337", nullptr, kLocMulti6)
MYST3ENTRY(Common::FR_FRA, "FRENCH.m3u", "73519070cba1c7bea599adbddeae304f", nullptr, kLocMulti6)
MYST3ENTRY(Common::NL_NLD, "DUTCH.m3u", "c4a8d8fb0eb3fecb9c435a8517bc1f9a", nullptr, kLocMulti6)
MYST3ENTRY(Common::DE_DEU, "GERMAN.m3u", "5b3be343dd20f03ebdf16381b873f035", nullptr, kLocMulti6)
MYST3ENTRY(Common::IT_ITA, "ITALIAN.m3u", "73db43aac3fe8671e2c4e227977fbb61", nullptr, kLocMulti6)
MYST3ENTRY(Common::ES_ESP, "SPANISH.m3u", "55ceb165dad02211ef2d25946c3aac8e", nullptr, kLocMulti6)
// Multilingual CD release (1.21, original layout)
{
{
"myst3",
nullptr,
{
{ "RSRC.m3r", 0, "a2c8ed69800f60bf5667e5c76a88e481", 1223862 },
{ "SPANISH.m3u", 0, "55ceb165dad02211ef2d25946c3aac8e", 2604702 }, // Use the spanish language file
{ "DUTCH.m3u", 0, "c4a8d8fb0eb3fecb9c435a8517bc1f9a", 2607925 }, // and the dutch one to exclude Multi2 versions
},
Common::UNK_LANG,
Common::kPlatformWindows,
ADGF_NO_FLAGS,
GUIO_NONE
},
kLocMulti6 | kLayoutCD
},
{
// Chinese (Simplified) CD release (1.22LC)
{
"myst3",
MetaEngineDetection::GAME_NOT_IMPLEMENTED, // Lacks OVER101.m3o file
{
{ "RSRC.m3r", 0, "a2c8ed69800f60bf5667e5c76a88e481", 1223862 },
{ "localized.m3t", 0, "3a9f299f8d061ce3d2862d985edb84e3", 2341588 },
{ "ENGLISHjp.m3t", 0, "19dcba1074f235ec2119313242d891de", 5658925 },
},
Common::ZH_CHN,
Common::kPlatformWindows,
ADGF_UNSUPPORTED,
GUIO_NONE
},
kLocMulti2 // CHS, English
},
// Japanese DVD release (1.24) (TRAC report #14298)
MYST3ENTRY(Common::JA_JPN, "JAPANESE.m3u", "5c18c9c124ff92d2b95ae5d128228f7b", "DVD", kLocMulti2)
// DVD releases (1.27)
MYST3ENTRY_DVD(Common::EN_ANY, "ENGLISH.m3u", "e200b416f43e70fee76148a80d195d5c", "DVD", kLocMulti6)
MYST3ENTRY_DVD(Common::FR_FRA, "FRENCH.m3u", "5679ce65c5e9af8899835ef9af398f1a", "DVD", kLocMulti6)
MYST3ENTRY_DVD(Common::NL_NLD, "DUTCH.m3u", "2997afdb4306c573153fdbb391ed2fff", "DVD", kLocMulti6)
MYST3ENTRY_DVD(Common::DE_DEU, "GERMAN.m3u", "09f32e6ceb414463e8fc22ca1a9564d3", "DVD", kLocMulti6)
MYST3ENTRY_DVD(Common::IT_ITA, "ITALIAN.m3u", "51fb02f6bf37dde811d7cde648365260", "DVD", kLocMulti6)
MYST3ENTRY_DVD(Common::ES_ESP, "SPANISH.m3u", "e27e610fe8ce35223a3239ff170a85ec", "DVD", kLocMulti6)
// DVD release (1.27, original layout)
{
{
"myst3",
"DVD",
{
{ "RSRC.m3r", 0, "a2c8ed69800f60bf5667e5c76a88e481", 1223862 },
{ "ENGLISH.m3t", 0, "74726de866c0594d3f2a05ff754c973d", 3407120 },
{ "language.m3u", 0, "e27e610fe8ce35223a3239ff170a85ec", 2604318 }, // Use the spanish language file
},
Common::UNK_LANG,
Common::kPlatformWindows,
ADGF_NO_FLAGS,
GUIO_NONE
},
kLocMulti6 | kLayoutDVD
},
// Myst 3 Xbox (PAL)
MYST3ENTRY_XBOX(Common::EN_ANY, "ENGLISHX.m3t", "c4d012ab02b8ca7d0c7e79f4dbd4e676")
MYST3ENTRY_XBOX(Common::FR_FRA, "FRENCHX.m3t", "94c9dcdec8794751e4d773776552751a")
MYST3ENTRY_XBOX(Common::DE_DEU, "GERMANX.m3t", "b9b66fcd5d4fbb95ac2d7157577991a5")
MYST3ENTRY_XBOX(Common::IT_ITA, "ITALIANX.m3t", "3ca266019eba68123f6b7cae57cfc200")
MYST3ENTRY_XBOX(Common::ES_ESP, "SPANISHX.m3t", "a9aca36ccf6709164249f3fb6b1ef148")
// Myst 3 Xbox (RUS)
MYST3ENTRY_XBOX(Common::RU_RUS, "ENGLISHX.m3t", "18cb50f5c5317586a128ca9eb3e03279")
{
// Myst 3 PS2 (NTSC-U/C)
{
"myst3",
_s("PS2 version is not yet supported"),
AD_ENTRY1s("RSRC.m3r", "c60d37bfd3bb8b0bee143018447bb460", 346618151),
Common::UNK_LANG,
Common::kPlatformPS2,
ADGF_UNSUPPORTED,
GUIO_NONE
},
0
},
{
// Myst 3 PS2 (PAL)
{
"myst3",
_s("PS2 version is not yet supported"),
AD_ENTRY1s("RSRC.m3r", "f0e0c502f77157e6b5272686c661ea75", 91371793),
Common::UNK_LANG,
Common::kPlatformPS2,
ADGF_UNSUPPORTED,
GUIO_NONE
},
0
},
{ AD_TABLE_END_MARKER, 0 }
};
class Myst3MetaEngineDetection : public AdvancedMetaEngineDetection<Myst3GameDescription> {
public:
Myst3MetaEngineDetection() : AdvancedMetaEngineDetection(gameDescriptions, myst3Games) {
_guiOptions = GUIO5(GUIO_NOMIDI, GUIO_NOSFX, GUIO_NOSPEECH, GUIO_NOSUBTITLES, GAMEOPTION_WIDESCREEN_MOD);
_maxScanDepth = 3;
_directoryGlobs = directoryGlobs;
}
const char *getEngineName() const override {
return "Myst III";
}
const char *getName() const override {
return "myst3";
}
const char *getOriginalCopyright() const override {
return "Myst III Exile (C) Presto Studios";
}
const DebugChannelDef *getDebugChannels() const override {
return debugFlagList;
}
DetectedGame toDetectedGame(const ADDetectedGame &adGame, ADDetectedGameExtraInfo *extraInfo) const override {
DetectedGame game = AdvancedMetaEngineDetection::toDetectedGame(adGame);
// The AdvancedDetector model only allows specifying a single supported
// game language. The 10th anniversary edition Myst III is multilanguage.
// Here we amend the detected games to set the list of supported languages.
if ((reinterpret_cast<const Myst3::Myst3GameDescription *>(
adGame.desc)->flags & Myst3::kGameLayoutTypeMask) != Myst3::kLayoutFlattened) {
game.appendGUIOptions(Common::getGameGUIOptionsDescriptionLanguage(Common::EN_ANY));
game.appendGUIOptions(Common::getGameGUIOptionsDescriptionLanguage(Common::DE_DEU));
game.appendGUIOptions(Common::getGameGUIOptionsDescriptionLanguage(Common::ES_ESP));
game.appendGUIOptions(Common::getGameGUIOptionsDescriptionLanguage(Common::FR_FRA));
game.appendGUIOptions(Common::getGameGUIOptionsDescriptionLanguage(Common::IT_ITA));
game.appendGUIOptions(Common::getGameGUIOptionsDescriptionLanguage(Common::IT_ITA));
game.appendGUIOptions(Common::getGameGUIOptionsDescriptionLanguage(Common::NL_NLD));
}
return game;
}
};
} // End of namespace Myst3
REGISTER_PLUGIN_STATIC(MYST3_DETECTION, PLUGIN_TYPE_ENGINE_DETECTION, Myst3::Myst3MetaEngineDetection);

54
engines/myst3/detection.h Normal file
View File

@@ -0,0 +1,54 @@
/* 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/>.
*
*/
#ifndef MYST3_DETECTION_H
#define MYST3_DETECTION_H
#include "engines/advancedDetector.h"
namespace Myst3 {
static const uint32 kGameLocalizationTypeMask = 0xff;
enum GameLocalizationType {
kLocMonolingual = 0,
kLocMulti2 = 1,
kLocMulti6 = 2,
};
static const uint32 kGameLayoutTypeMask = 0xff << 8;
enum GameLayoutType {
kLayoutFlattened = 0,
kLayoutCD = 1 << 8,
kLayoutDVD = 2 << 8,
};
struct Myst3GameDescription {
AD_GAME_DESCRIPTION_HELPERS(desc);
ADGameDescription desc;
uint32 flags;
};
#define GAMEOPTION_WIDESCREEN_MOD GUIO_GAMEOPTIONS1
} // End of namespace Myst3
#endif // MYST3_DETECTION_H

797
engines/myst3/effects.cpp Normal file
View File

@@ -0,0 +1,797 @@
/* 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 "engines/myst3/effects.h"
#include "engines/myst3/gfx.h"
#include "engines/myst3/myst3.h"
#include "engines/myst3/state.h"
#include "engines/myst3/sound.h"
#include "graphics/surface.h"
namespace Myst3 {
Effect::FaceMask::FaceMask() :
surface(nullptr) {
for (uint i = 0; i < 10; i++) {
for (uint j = 0; j < 10; j++) {
block[i][j] = false;
}
}
}
Effect::FaceMask::~FaceMask() {
if (surface) {
surface->free();
}
delete surface;
}
Common::Rect Effect::FaceMask::getBlockRect(uint x, uint y) {
Common::Rect rect = Common::Rect(64, 64);
rect.translate(x * 64, y * 64);
return rect;
}
Effect::Effect(Myst3Engine *vm) :
_vm(vm) {
}
Effect::~Effect() {
for (FaceMaskMap::iterator it = _facesMasks.begin(); it != _facesMasks.end(); it++) {
delete it->_value;
}
}
bool Effect::loadMasks(const Common::String &room, uint32 id, Archive::ResourceType type) {
bool isFrame = _vm->_state->getViewType() == kFrame;
// Load the mask of each face
for (uint i = 0; i < 6; i++) {
ResourceDescription desc = _vm->getFileDescription(room, id, i + 1, type);
if (desc.isValid()) {
Common::SeekableReadStream *data = desc.getData();
// Check if we are overriding an existing mask
delete _facesMasks[i];
_facesMasks[i] = loadMask(data);
// Frame masks are vertically flipped for some reason
if (isFrame) {
_vm->_gfx->flipVertical(_facesMasks[i]->surface);
}
delete data;
}
}
if (_facesMasks.empty())
return false;
return true;
}
Effect::FaceMask *Effect::loadMask(Common::SeekableReadStream *maskStream) {
FaceMask *mask = new FaceMask();
mask->surface = new Graphics::Surface();
mask->surface->create(640, 640, Graphics::PixelFormat::createFormatCLUT8());
uint32 headerOffset = 0;
uint32 dataOffset = 0;
while (headerOffset < 400) {
int blockX = (headerOffset / sizeof(dataOffset)) % 10;
int blockY = (headerOffset / sizeof(dataOffset)) / 10;
maskStream->seek(headerOffset, SEEK_SET);
dataOffset = maskStream->readUint32LE();
headerOffset = maskStream->pos();
if (dataOffset != 0) {
maskStream->seek(dataOffset, SEEK_SET);
for(int i = 63; i >= 0; i--) {
int x = 0;
byte numValues = maskStream->readByte();
for (int j = 0; j < numValues; j++) {
byte repeat = maskStream->readByte();
byte value = maskStream->readByte();
for (int k = 0; k < repeat; k++) {
((uint8*)mask->surface->getPixels())[((blockY * 64) + i) * 640 + blockX * 64 + x] = value;
x++;
}
// If a block has at least one non zero value, mark it as active
if (value != 0) {
mask->block[blockX][blockY] = true;
}
}
}
}
}
return mask;
}
Common::Rect Effect::getUpdateRectForFace(uint face) {
FaceMask *mask = _facesMasks.getVal(face);
if (!mask)
error("No mask for face %d", face);
Common::Rect rect;
// Build a rectangle containing all the active effect blocks
for (uint i = 0; i < 10; i++) {
for (uint j = 0; j < 10; j++) {
if (mask->block[i][j]) {
if (rect.isEmpty()) {
rect = FaceMask::getBlockRect(i, j);
} else {
rect.extend(FaceMask::getBlockRect(i, j));
}
}
}
}
return rect;
}
WaterEffect::WaterEffect(Myst3Engine *vm) :
Effect(vm),
_lastUpdate(0),
_step(0) {
}
WaterEffect::~WaterEffect() {
}
WaterEffect *WaterEffect::create(Myst3Engine *vm, uint32 id) {
WaterEffect *s = new WaterEffect(vm);
if (!s->loadMasks("", id, Archive::kWaterEffectMask)) {
delete s;
return nullptr;
}
return s;
}
bool WaterEffect::isRunning() {
return _vm->_state->getWaterEffectActive()
&& _vm->_state->getWaterEffectRunning();
}
bool WaterEffect::update() {
if (!isRunning()) {
return false;
}
if (g_system->getMillis() - _lastUpdate >= 1000 / (uint32)_vm->_state->getWaterEffectSpeed()) {
_lastUpdate = g_system->getMillis();
_step++;
if (_step > _vm->_state->getWaterEffectMaxStep())
_step = 0;
float position = _step / (float)_vm->_state->getWaterEffectMaxStep();
doStep(position, _vm->_state->getViewType() == kFrame);
return true;
}
return false;
}
void WaterEffect::doStep(float position, bool isFrame) {
double timeOffset;
double frequency;
double ampl;
timeOffset = position * 2 * M_PI;
frequency = _vm->_state->getWaterEffectFrequency() * 0.1;
ampl = _vm->_state->getWaterEffectAmpl() / 10.0 / 2.0;
for (uint i = 0; i < 640; i++) {
double ampl1;
if (i < 320)
ampl1 = i / 320 + 1.0;
else
ampl1 = (640 - i) / 320 + 1.0;
_bottomDisplacement[i] = sin(i / 640.0 * frequency * 2 * M_PI + timeOffset) / 2 * ampl1 * ampl;
}
// FIXME: The original sets this to WaterEffectAttenuation, which causes
// glitches here
uint32 attenuation = 640;
for (uint i = 0; i < attenuation; i++) {
double ampl2 = attenuation / (attenuation - i + 1.0);
int8 value = sin(i / 640.0 * frequency * 2 * M_PI * ampl2 + timeOffset) / 2 * 1.0 / ampl2 * ampl;
if (!isFrame) {
_verticalDisplacement[i] = value;
} else {
_verticalDisplacement[attenuation - 1 - i] = value;
}
}
for (uint i = 0; i < 640; i++) {
double ampl3 = sin(i / 640.0 * frequency * 2 * M_PI + timeOffset) / 2.0;
_horizontalDisplacements[0][i] = ampl3 * 1.25 * ampl + 0.5;
_horizontalDisplacements[1][i] = ampl3 * 1.00 * ampl + 0.5;
_horizontalDisplacements[2][i] = ampl3 * 0.75 * ampl + 0.5;
_horizontalDisplacements[3][i] = ampl3 * 0.50 * ampl + 0.5;
_horizontalDisplacements[4][i] = ampl3 * 0.25 * ampl + 0.5;
}
}
void WaterEffect::applyForFace(uint face, Graphics::Surface *src, Graphics::Surface *dst) {
if (!isRunning()) {
return;
}
FaceMask *mask = _facesMasks.getVal(face);
if (!mask)
error("No mask for face %d", face);
apply(src, dst, mask->surface, face == 1, _vm->_state->getWaterEffectAmpl());
}
void WaterEffect::apply(Graphics::Surface *src, Graphics::Surface *dst, Graphics::Surface *mask, bool bottomFace, int32 waterEffectAmpl) {
int32 waterEffectAttenuation = _vm->_state->getWaterEffectAttenuation();
int32 waterEffectAmplOffset = _vm->_state->getWaterEffectAmplOffset();
int8 *hDisplacement = nullptr;
int8 *vDisplacement = nullptr;
if (bottomFace) {
hDisplacement = _bottomDisplacement;
vDisplacement = _bottomDisplacement;
} else {
vDisplacement = _verticalDisplacement;
}
uint32 *dstPtr = (uint32 *)dst->getPixels();
byte *maskPtr = (byte *)mask->getPixels();
for (int y = 0; y < dst->h; y++) {
if (!bottomFace) {
uint32 strength = (320 * (9 - y / 64)) / waterEffectAttenuation;
if (strength > 4)
strength = 4;
hDisplacement = _horizontalDisplacements[strength];
}
for (int x = 0; x < dst->w; x++) {
int8 maskValue = *maskPtr;
if (maskValue != 0) {
int8 xOffset = hDisplacement[x];
int8 yOffset = vDisplacement[y];
if (maskValue < 8) {
maskValue -= waterEffectAmplOffset;
if (maskValue < 0) {
maskValue = 0;
}
if (xOffset >= 0) {
if (xOffset > maskValue)
xOffset = maskValue;
} else {
if (-xOffset > maskValue)
xOffset = -maskValue;
}
if (yOffset >= 0) {
if (yOffset > maskValue)
yOffset = maskValue;
} else {
if (-yOffset > maskValue)
yOffset = -maskValue;
}
}
uint32 srcValue1 = *(uint32 *) src->getBasePtr(x + xOffset, y + yOffset);
uint32 srcValue2 = *(uint32 *) src->getBasePtr(x, y);
#ifdef SCUMM_BIG_ENDIAN
*dstPtr = 0x000000FF | ((0x7F7F7F00 & (srcValue1 >> 1)) + (0x7F7F7F00 & (srcValue2 >> 1)));
#else
*dstPtr = 0xFF000000 | ((0x007F7F7F & (srcValue1 >> 1)) + (0x007F7F7F & (srcValue2 >> 1)));
#endif
}
maskPtr++;
dstPtr++;
}
}
}
LavaEffect::LavaEffect(Myst3Engine *vm) :
Effect(vm),
_lastUpdate(0),
_step(0) {
}
LavaEffect::~LavaEffect() {
}
LavaEffect *LavaEffect::create(Myst3Engine *vm, uint32 id) {
LavaEffect *s = new LavaEffect(vm);
if (!s->loadMasks("", id, Archive::kLavaEffectMask)) {
delete s;
return nullptr;
}
return s;
}
bool LavaEffect::update() {
if (!_vm->_state->getLavaEffectActive()) {
return false;
}
if (g_system->getMillis() - _lastUpdate >= 1000 / (uint32)_vm->_state->getLavaEffectSpeed()) {
_lastUpdate = g_system->getMillis();
_step += _vm->_state->getLavaEffectStepSize();
doStep(_step, _vm->_state->getLavaEffectAmpl() / 10);
if (_step > 256)
_step -= 256;
return true;
}
return false;
}
void LavaEffect::doStep(int32 position, float ampl) {
for (uint i = 0; i < 256; i++) {
_displacement[i] = (sin((i + position) * 2 * M_PI / 256.0) + 1.0) * ampl;
}
}
void LavaEffect::applyForFace(uint face, Graphics::Surface *src, Graphics::Surface *dst) {
if (!_vm->_state->getLavaEffectActive()) {
return;
}
FaceMask *mask = _facesMasks.getVal(face);
if (!mask)
error("No mask for face %d", face);
uint32 *dstPtr = (uint32 *)dst->getPixels();
byte *maskPtr = (byte *)mask->surface->getPixels();
for (int y = 0; y < dst->h; y++) {
for (int x = 0; x < dst->w; x++) {
uint8 maskValue = *maskPtr;
if (maskValue != 0) {
int32 xOffset= _displacement[(maskValue + y) % 256];
int32 yOffset = _displacement[maskValue % 256];
int32 maxOffset = (maskValue >> 6) & 0x3;
if (yOffset > maxOffset) {
yOffset = maxOffset;
}
if (xOffset > maxOffset) {
xOffset = maxOffset;
}
// uint32 srcValue1 = *(uint32 *)src->getBasePtr(x + xOffset, y + yOffset);
// uint32 srcValue2 = *(uint32 *)src->getBasePtr(x, y);
//
// *dstPtr = 0xFF000000 | ((0x007F7F7F & (srcValue1 >> 1)) + (0x007F7F7F & (srcValue2 >> 1)));
// TODO: The original does "blending" as above, but strangely
// this looks more like the original rendering
*dstPtr = *(uint32 *)src->getBasePtr(x + xOffset, y + yOffset);
}
maskPtr++;
dstPtr++;
}
}
}
MagnetEffect::MagnetEffect(Myst3Engine *vm) :
Effect(vm),
_lastSoundId(0),
_lastTime(0),
_position(0),
_lastAmpl(0),
_shakeStrength(nullptr) {
}
MagnetEffect::~MagnetEffect() {
delete _shakeStrength;
}
MagnetEffect *MagnetEffect::create(Myst3Engine *vm, uint32 id) {
if (!vm->_state->getMagnetEffectSound()) {
return nullptr;
}
MagnetEffect *s = new MagnetEffect(vm);
s->loadMasks("", id, Archive::kMagneticEffectMask);
return s;
}
bool MagnetEffect::update() {
int32 soundId = _vm->_state->getMagnetEffectSound();
if (!soundId) {
// The effect is no longer active
_lastSoundId = 0;
_vm->_state->setMagnetEffectUnk3(0);
delete _shakeStrength;
_shakeStrength = nullptr;
return false;
}
if (soundId != _lastSoundId) {
// The sound changed since last update
_lastSoundId = soundId;
ResourceDescription desc = _vm->getFileDescription("", _vm->_state->getMagnetEffectNode(), 0, Archive::kRawData);
if (!desc.isValid())
error("Magnet effect support file %d does not exist", _vm->_state->getMagnetEffectNode());
delete _shakeStrength;
_shakeStrength = desc.getData();
}
int32 soundPosition = _vm->_sound->playedFrames(soundId);
if (_shakeStrength && soundPosition >= 0) {
// Update the shake amplitude according to the position in the playing sound.
// This has no in-game effect (same as original) due to var 122 being 0.
_shakeStrength->seek(soundPosition, SEEK_SET);
_vm->_state->setMagnetEffectUnk3(_shakeStrength->readByte());
// Update the vertical displacements
float ampl = (_vm->_state->getMagnetEffectUnk1() + _vm->_state->getMagnetEffectUnk3())
/ (float)_vm->_state->getMagnetEffectUnk2();
if (ampl != _lastAmpl) {
for (uint i = 0; i < 256; i++) {
_verticalDisplacement[i] = sin(i * 2 * M_PI / 255.0) * ampl;
}
_lastAmpl = ampl;
}
// Update the position in the effect cycle
uint32 time = g_system->getMillis();
if (_lastTime) {
_position += (float)_vm->_state->getMagnetEffectSpeed() * (time - _lastTime) / 1000 / 10;
while (_position > 1.0) {
_position -= 1.0;
}
}
_lastTime = time;
} else {
_vm->_state->setMagnetEffectUnk3(0);
}
return true;
}
void MagnetEffect::applyForFace(uint face, Graphics::Surface *src, Graphics::Surface *dst) {
FaceMask *mask = _facesMasks.getVal(face);
if (!mask)
error("No mask for face %d", face);
apply(src, dst, mask->surface, _position * 256.0);
}
void MagnetEffect::apply(Graphics::Surface *src, Graphics::Surface *dst, Graphics::Surface *mask, int32 position) {
uint32 *dstPtr = (uint32 *)dst->getPixels();
byte *maskPtr = (byte *)mask->getPixels();
for (int y = 0; y < dst->h; y++) {
for (int x = 0; x < dst->w; x++) {
uint8 maskValue = *maskPtr;
if (maskValue != 0) {
int32 displacement = _verticalDisplacement[(maskValue + position) % 256];
int32 displacedY = CLIP<int32>(y + displacement, 0, src->h - 1);
uint32 srcValue1 = *(uint32 *) src->getBasePtr(x, displacedY);
uint32 srcValue2 = *(uint32 *) src->getBasePtr(x, y);
#ifdef SCUMM_BIG_ENDIAN
*dstPtr = 0x000000FF | ((0x7F7F7F00 & (srcValue1 >> 1)) + (0x7F7F7F00 & (srcValue2 >> 1)));
#else
*dstPtr = 0xFF000000 | ((0x007F7F7F & (srcValue1 >> 1)) + (0x007F7F7F & (srcValue2 >> 1)));
#endif
}
maskPtr++;
dstPtr++;
}
}
}
ShakeEffect::ShakeEffect(Myst3Engine *vm) :
Effect(vm),
_lastTick(0),
_magnetEffectShakeStep(0),
_pitchOffset(0),
_headingOffset(0) {
}
ShakeEffect::~ShakeEffect() {
}
ShakeEffect *ShakeEffect::create(Myst3Engine *vm) {
if (vm->_state->getShakeEffectAmpl() == 0) {
return nullptr;
}
return new ShakeEffect(vm);
}
bool ShakeEffect::update() {
// Check if the effect is active
int32 ampl = _vm->_state->getShakeEffectAmpl();
if (ampl == 0) {
return false;
}
// Check if the effect needs to be updated
uint tick = _vm->_state->getTickCount();
if (tick < _lastTick + _vm->_state->getShakeEffectTickPeriod()) {
return false;
}
if (_vm->_state->getMagnetEffectUnk3()) {
// If the magnet effect is also active, use its parameters
float magnetEffectAmpl = (_vm->_state->getMagnetEffectUnk1() + _vm->_state->getMagnetEffectUnk3()) / 32.0;
float shakeEffectAmpl;
if (_magnetEffectShakeStep >= 2) {
shakeEffectAmpl = ampl;
} else {
shakeEffectAmpl = -ampl;
}
_pitchOffset = shakeEffectAmpl / 200.0 * magnetEffectAmpl;
if (_magnetEffectShakeStep >= 1 && _magnetEffectShakeStep <= 2) {
shakeEffectAmpl = ampl;
} else {
shakeEffectAmpl = -ampl;
}
_headingOffset = shakeEffectAmpl / 200.0 * magnetEffectAmpl;
_magnetEffectShakeStep++;
_magnetEffectShakeStep %= 3;
} else {
// Shake effect only
uint randomAmpl;
randomAmpl = _vm->_rnd->getRandomNumberRng(0, ampl);
_pitchOffset = (randomAmpl - ampl / 2.0) / 100.0;
randomAmpl = _vm->_rnd->getRandomNumberRng(0, ampl);
_headingOffset = (randomAmpl - ampl / 2.0) / 100.0;
}
_lastTick = tick;
return true;
}
void ShakeEffect::applyForFace(uint face, Graphics::Surface* src, Graphics::Surface* dst) {
}
RotationEffect::RotationEffect(Myst3Engine *vm) :
Effect(vm),
_lastUpdate(0),
_headingOffset(0) {
}
RotationEffect::~RotationEffect() {
}
RotationEffect *RotationEffect::create(Myst3Engine *vm) {
if (vm->_state->getRotationEffectSpeed() == 0) {
return nullptr;
}
return new RotationEffect(vm);
}
bool RotationEffect::update() {
// Check if the effect is active
int32 speed = _vm->_state->getRotationEffectSpeed();
if (speed == 0) {
return false;
}
if (_lastUpdate != 0) {
_headingOffset = speed * (g_system->getMillis() - _lastUpdate) / 1000.0;
}
_lastUpdate = g_system->getMillis();
return true;
}
void RotationEffect::applyForFace(uint face, Graphics::Surface* src, Graphics::Surface* dst) {
}
bool ShieldEffect::loadPattern() {
// Read the shield effect support data
ResourceDescription desc = _vm->getFileDescription("NARA", 10000, 0, Archive::kRawData);
if (!desc.isValid()) {
return false;
}
Common::SeekableReadStream *stream = desc.getData();
if (stream->size() != 4096) {
error("Incorrect shield effect support file size %d", (int)stream->size());
}
stream->read(_pattern, 4096);
delete stream;
return true;
}
ShieldEffect::ShieldEffect(Myst3Engine *vm):
Effect(vm),
_lastTick(0),
_amplitude(1.0),
_amplitudeIncrement(1.0 / 64.0) {
}
ShieldEffect::~ShieldEffect() {
}
ShieldEffect *ShieldEffect::create(Myst3Engine *vm, uint32 id) {
uint32 room = vm->_state->getLocationRoom();
uint32 node = vm->_state->getLocationNode();
// This effect can only be found on Narayan cube nodes
if (room != kRoomNarayan || node >= 100)
return nullptr;
ShieldEffect *s = new ShieldEffect(vm);
if (!s->loadPattern()) {
delete s;
return nullptr; // We don't have the effect file
}
bool outerShieldUp = vm->_state->getOuterShieldUp();
bool innerShieldUp = vm->_state->getInnerShieldUp();
int32 saavedroStatus = vm->_state->getSaavedroStatus();
bool hasMasks = false;
int32 innerShieldMaskNode = 0;
if (innerShieldUp) {
innerShieldMaskNode = node + 100;
}
if (outerShieldUp) {
hasMasks |= s->loadMasks("NARA", node + 300, Archive::kShieldEffectMask);
if (saavedroStatus == 2) {
innerShieldMaskNode = node + 200;
}
}
if (innerShieldMaskNode) {
hasMasks |= s->loadMasks("NARA", innerShieldMaskNode, Archive::kShieldEffectMask);
}
if (innerShieldMaskNode && innerShieldUp && node > 6) {
hasMasks |= s->loadMasks("NARA", node + 100, Archive::kShieldEffectMask);
}
if (!hasMasks) {
delete s;
return nullptr;
}
return s;
}
bool ShieldEffect::update() {
if (_vm->_state->getTickCount() == _lastTick)
return false;
_lastTick = _vm->_state->getTickCount();
// Update the amplitude, varying between 1.0 and 4.0
_amplitude += _amplitudeIncrement;
if (_amplitude >= 4.0) {
_amplitude = 4.0;
_amplitudeIncrement = -1.0 / 64.0;
} else if (_amplitude <= 1.0) {
_amplitude = 1.0;
_amplitudeIncrement = 1.0 / 64.0;
}
// Update the support data
for (uint i = 0; i < ARRAYSIZE(_pattern); i++) {
_pattern[i] += 2; // Intentional overflow
}
// Update the displacement offsets
for (uint i = 0; i < 256; i++) {
_displacement[i] = (sin(i * 2 * M_PI / 255.0) + 1.0) * _amplitude;
}
return true;
}
void ShieldEffect::applyForFace(uint face, Graphics::Surface *src, Graphics::Surface *dst) {
if (!_vm->_state->getShieldEffectActive()) {
return;
}
FaceMask *mask = _facesMasks.getVal(face);
if (!mask)
error("No mask for face %d", face);
uint32 *dstPtr = (uint32 *)dst->getPixels();
byte *maskPtr = (byte *)mask->surface->getPixels();
for (int y = 0; y < dst->h; y++) {
for (int x = 0; x < dst->w; x++) {
uint8 maskValue = *maskPtr;
if (maskValue != 0) {
int32 yOffset = _displacement[_pattern[(y % 64) * 64 + (x % 64)]];
if (yOffset > maskValue) {
yOffset = maskValue;
}
*dstPtr = *(uint32 *)src->getBasePtr(x, y + yOffset);
}
maskPtr++;
dstPtr++;
}
}
}
} // End of namespace Myst3

200
engines/myst3/effects.h Normal file
View File

@@ -0,0 +1,200 @@
/* 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/>.
*
*/
#ifndef EFFECTS_H_
#define EFFECTS_H_
#include "common/hashmap.h"
#include "common/rect.h"
#include "engines/myst3/archive.h"
namespace Graphics {
struct Surface;
}
namespace Myst3 {
class Myst3Engine;
class Effect {
public:
struct FaceMask {
FaceMask();
~FaceMask();
static Common::Rect getBlockRect(uint x, uint y);
Graphics::Surface *surface;
bool block[10][10];
};
virtual ~Effect();
virtual bool update() = 0;
virtual void applyForFace(uint face, Graphics::Surface *src, Graphics::Surface *dst) = 0;
bool hasFace(uint face) { return _facesMasks.contains(face); }
Common::Rect getUpdateRectForFace(uint face);
// Public and static for use by the debug console
static FaceMask *loadMask(Common::SeekableReadStream *maskStream);
protected:
Effect(Myst3Engine *vm);
bool loadMasks(const Common::String &room, uint32 id, Archive::ResourceType type);
Myst3Engine *_vm;
typedef Common::HashMap<uint, FaceMask *> FaceMaskMap;
FaceMaskMap _facesMasks;
};
class WaterEffect : public Effect {
public:
static WaterEffect *create(Myst3Engine *vm, uint32 id);
virtual ~WaterEffect();
bool update();
void applyForFace(uint face, Graphics::Surface *src, Graphics::Surface *dst);
protected:
WaterEffect(Myst3Engine *vm);
void doStep(float position, bool isFrame);
void apply(Graphics::Surface *src, Graphics::Surface *dst, Graphics::Surface *mask,
bool bottomFace, int32 waterEffectAmpl);
uint32 _lastUpdate;
int32 _step;
int8 _bottomDisplacement[640];
int8 _verticalDisplacement[640];
int8 _horizontalDisplacements[5][640];
private:
bool isRunning();
};
class LavaEffect : public Effect {
public:
static LavaEffect *create(Myst3Engine *vm, uint32 id);
virtual ~LavaEffect();
bool update();
void applyForFace(uint face, Graphics::Surface *src, Graphics::Surface *dst);
protected:
LavaEffect(Myst3Engine *vm);
void doStep(int32 position, float ampl);
uint32 _lastUpdate;
int32 _step;
int32 _displacement[256];
};
class MagnetEffect : public Effect {
public:
static MagnetEffect *create(Myst3Engine *vm, uint32 id);
virtual ~MagnetEffect();
bool update();
void applyForFace(uint face, Graphics::Surface *src, Graphics::Surface *dst);
protected:
MagnetEffect(Myst3Engine *vm);
void apply(Graphics::Surface *src, Graphics::Surface *dst, Graphics::Surface *mask, int32 position);
int32 _lastSoundId;
Common::SeekableReadStream *_shakeStrength;
uint32 _lastTime;
float _position;
float _lastAmpl;
int32 _verticalDisplacement[256];
};
class ShakeEffect : public Effect {
public:
static ShakeEffect *create(Myst3Engine *vm);
virtual ~ShakeEffect();
bool update();
void applyForFace(uint face, Graphics::Surface *src, Graphics::Surface *dst);
float getPitchOffset() { return _pitchOffset; }
float getHeadingOffset() { return _headingOffset; }
protected:
ShakeEffect(Myst3Engine *vm);
uint32 _lastTick;
uint _magnetEffectShakeStep;
float _pitchOffset;
float _headingOffset;
};
class RotationEffect : public Effect {
public:
static RotationEffect *create(Myst3Engine *vm);
virtual ~RotationEffect();
bool update();
void applyForFace(uint face, Graphics::Surface *src, Graphics::Surface *dst);
float getHeadingOffset() { return _headingOffset; }
protected:
RotationEffect(Myst3Engine *vm);
uint32 _lastUpdate;
float _headingOffset;
};
class ShieldEffect : public Effect {
public:
static ShieldEffect *create(Myst3Engine *vm, uint32 id);
virtual ~ShieldEffect();
bool update();
void applyForFace(uint face, Graphics::Surface *src, Graphics::Surface *dst);
protected:
ShieldEffect(Myst3Engine *vm);
bool loadPattern();
uint32 _lastTick;
float _amplitude;
float _amplitudeIncrement;
uint8 _pattern[4096];
int32 _displacement[256];
};
} // End of namespace Myst3
#endif // EFFECTS_H_

306
engines/myst3/gfx.cpp Normal file
View File

@@ -0,0 +1,306 @@
/* 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/gfx.h"
#include "engines/util.h"
#include "common/config-manager.h"
#include "graphics/renderer.h"
#include "graphics/surface.h"
#if defined(USE_OPENGL_GAME) || defined(USE_OPENGL_SHADERS)
#include "graphics/opengl/context.h"
#endif
#include "math/glmath.h"
namespace Myst3 {
const float Renderer::cubeVertices[] = {
// S T X Y Z
0.0f, 1.0f, -320.0f, -320.0f, -320.0f,
1.0f, 1.0f, 320.0f, -320.0f, -320.0f,
0.0f, 0.0f, -320.0f, 320.0f, -320.0f,
1.0f, 0.0f, 320.0f, 320.0f, -320.0f,
0.0f, 1.0f, 320.0f, -320.0f, -320.0f,
1.0f, 1.0f, -320.0f, -320.0f, -320.0f,
0.0f, 0.0f, 320.0f, -320.0f, 320.0f,
1.0f, 0.0f, -320.0f, -320.0f, 320.0f,
0.0f, 1.0f, 320.0f, -320.0f, 320.0f,
1.0f, 1.0f, -320.0f, -320.0f, 320.0f,
0.0f, 0.0f, 320.0f, 320.0f, 320.0f,
1.0f, 0.0f, -320.0f, 320.0f, 320.0f,
0.0f, 1.0f, 320.0f, -320.0f, -320.0f,
1.0f, 1.0f, 320.0f, -320.0f, 320.0f,
0.0f, 0.0f, 320.0f, 320.0f, -320.0f,
1.0f, 0.0f, 320.0f, 320.0f, 320.0f,
0.0f, 1.0f, -320.0f, -320.0f, 320.0f,
1.0f, 1.0f, -320.0f, -320.0f, -320.0f,
0.0f, 0.0f, -320.0f, 320.0f, 320.0f,
1.0f, 0.0f, -320.0f, 320.0f, -320.0f,
0.0f, 1.0f, 320.0f, 320.0f, 320.0f,
1.0f, 1.0f, -320.0f, 320.0f, 320.0f,
0.0f, 0.0f, 320.0f, 320.0f, -320.0f,
1.0f, 0.0f, -320.0f, 320.0f, -320.0f
};
Renderer::Renderer(OSystem *system)
: _system(system),
_font(nullptr) {
// Compute the cube faces Axis Aligned Bounding Boxes
for (uint i = 0; i < ARRAYSIZE(_cubeFacesAABB); i++) {
for (uint j = 0; j < 4; j++) {
_cubeFacesAABB[i].expand(Math::Vector3d(cubeVertices[5 * (4 * i + j) + 2], cubeVertices[5 * (4 * i + j) + 3], cubeVertices[5 * (4 * i + j) + 4]));
}
}
}
Renderer::~Renderer() {
}
void Renderer::initFont(const Graphics::Surface *surface) {
_font = createTexture2D(surface);
}
void Renderer::freeFont() {
if (_font) {
delete _font;
_font = nullptr;
}
}
Texture *Renderer::copyScreenshotToTexture() {
Graphics::Surface *surface = getScreenshot();
Texture *texture = createTexture2D(surface);
surface->free();
delete surface;
return texture;
}
Common::Rect Renderer::getFontCharacterRect(uint8 character) {
uint index = 0;
if (character == ' ')
index = 0;
else if (character >= '0' && character <= '9')
index = 1 + character - '0';
else if (character >= 'A' && character <= 'Z')
index = 1 + 10 + character - 'A';
else if (character == '|')
index = 1 + 10 + 26;
else if (character == '/')
index = 2 + 10 + 26;
else if (character == ':')
index = 3 + 10 + 26;
return Common::Rect(16 * index, 0, 16 * (index + 1), 32);
}
Common::Rect Renderer::viewport() const {
return _screenViewport;
}
void Renderer::computeScreenViewport() {
int32 screenWidth = _system->getWidth();
int32 screenHeight = _system->getHeight();
if (ConfMan.getBool("widescreen_mod")) {
_screenViewport = Common::Rect(screenWidth, screenHeight);
} else {
// Aspect ratio correction
int32 viewportWidth = MIN<int32>(screenWidth, screenHeight * kOriginalWidth / kOriginalHeight);
int32 viewportHeight = MIN<int32>(screenHeight, screenWidth * kOriginalHeight / kOriginalWidth);
_screenViewport = Common::Rect(viewportWidth, viewportHeight);
// Pillarboxing
_screenViewport.translate((screenWidth - viewportWidth) / 2,
(screenHeight - viewportHeight) / 2);
}
}
Math::Matrix4 Renderer::makeProjectionMatrix(float fov) const {
static const float nearClipPlane = 1.0;
static const float farClipPlane = 10000.0;
float aspectRatio = kOriginalWidth / (float) kFrameHeight;
float xmaxValue = nearClipPlane * tan(fov * M_PI / 360.0);
float ymaxValue = xmaxValue / aspectRatio;
return Math::makeFrustumMatrix(-xmaxValue, xmaxValue, -ymaxValue, ymaxValue, nearClipPlane, farClipPlane);
}
void Renderer::setupCameraPerspective(float pitch, float heading, float fov) {
_projectionMatrix = makeProjectionMatrix(fov);
_modelViewMatrix = Math::Matrix4(180.0f - heading, pitch, 0.0f, Math::EO_YXZ);
Math::Matrix4 proj = _projectionMatrix;
Math::Matrix4 model = _modelViewMatrix;
proj.transpose();
model.transpose();
_mvpMatrix = proj * model;
_frustum.setup(_mvpMatrix);
_mvpMatrix.transpose();
}
bool Renderer::isCubeFaceVisible(uint face) {
assert(face < 6);
return _frustum.isInside(_cubeFacesAABB[face]);
}
void Renderer::flipVertical(Graphics::Surface *s) {
for (int y = 0; y < s->h / 2; ++y) {
// Flip the lines
byte *line1P = (byte *)s->getBasePtr(0, y);
byte *line2P = (byte *)s->getBasePtr(0, s->h - y - 1);
for (int x = 0; x < s->pitch; ++x)
SWAP(line1P[x], line2P[x]);
}
}
Renderer *createRenderer(OSystem *system) {
Common::String rendererConfig = ConfMan.get("renderer");
Graphics::RendererType desiredRendererType = Graphics::Renderer::parseTypeCode(rendererConfig);
Graphics::RendererType matchingRendererType = Graphics::Renderer::getBestMatchingAvailableType(desiredRendererType,
#if defined(USE_OPENGL_GAME)
Graphics::kRendererTypeOpenGL |
#endif
#if defined(USE_OPENGL_SHADERS)
Graphics::kRendererTypeOpenGLShaders |
#endif
#if defined(USE_TINYGL)
Graphics::kRendererTypeTinyGL |
#endif
0);
bool isAccelerated = matchingRendererType != Graphics::kRendererTypeTinyGL;
uint width;
uint height = Renderer::kOriginalHeight;
if (ConfMan.getBool("widescreen_mod")) {
width = Renderer::kOriginalWidth * Renderer::kOriginalHeight / Renderer::kFrameHeight;
} else {
width = Renderer::kOriginalWidth;
}
if (isAccelerated) {
initGraphics3d(width, height);
} else {
initGraphics(width, height, nullptr);
}
#if defined(USE_OPENGL_SHADERS)
if (matchingRendererType == Graphics::kRendererTypeOpenGLShaders) {
return CreateGfxOpenGLShader(system);
}
#endif
#if defined(USE_OPENGL_GAME)
if (matchingRendererType == Graphics::kRendererTypeOpenGL) {
return CreateGfxOpenGL(system);
}
#endif
#if defined(USE_TINYGL)
if (matchingRendererType == Graphics::kRendererTypeTinyGL) {
return CreateGfxTinyGL(system);
}
#endif
/* We should never end up here, getBestMatchingRendererType would have failed before */
error("Unable to create a renderer");
}
void Renderer::renderDrawable(Drawable *drawable, Window *window) {
if (drawable->isConstrainedToWindow()) {
selectTargetWindow(window, drawable->is3D(), drawable->isScaled());
} else {
selectTargetWindow(nullptr, drawable->is3D(), drawable->isScaled());
}
drawable->draw();
}
void Renderer::renderDrawableOverlay(Drawable *drawable, Window *window) {
// Overlays are always 2D
if (drawable->isConstrainedToWindow()) {
selectTargetWindow(window, drawable->is3D(), drawable->isScaled());
} else {
selectTargetWindow(nullptr, drawable->is3D(), drawable->isScaled());
}
drawable->drawOverlay();
}
void Renderer::renderWindow(Window *window) {
renderDrawable(window, window);
}
void Renderer::renderWindowOverlay(Window *window) {
renderDrawableOverlay(window, window);
}
Drawable::Drawable() :
_isConstrainedToWindow(true),
_is3D(false),
_scaled(true) {
}
Common::Point Window::getCenter() const {
Common::Rect frame = getPosition();
return Common::Point((frame.left + frame.right) / 2, (frame.top + frame.bottom) / 2);
}
Common::Point Window::screenPosToWindowPos(const Common::Point &screen) const {
Common::Rect frame = getPosition();
return Common::Point(screen.x - frame.left, screen.y - frame.top);
}
Common::Point Window::scalePoint(const Common::Point &screen) const {
Common::Rect viewport = getPosition();
Common::Rect originalViewport = getOriginalPosition();
Common::Point scaledPosition = screen;
scaledPosition.x -= viewport.left;
scaledPosition.y -= viewport.top;
scaledPosition.x = CLIP<int16>(scaledPosition.x, 0, viewport.width());
scaledPosition.y = CLIP<int16>(scaledPosition.y, 0, viewport.height());
if (_scaled) {
scaledPosition.x *= originalViewport.width() / (float) viewport.width();
scaledPosition.y *= originalViewport.height() / (float) viewport.height();
}
return scaledPosition;
}
const Graphics::PixelFormat Texture::getRGBAPixelFormat() {
return Graphics::PixelFormat::createFormatRGBA32();
}
} // End of namespace Myst3

203
engines/myst3/gfx.h Normal file
View File

@@ -0,0 +1,203 @@
/* 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/>.
*
*/
#ifndef GFX_H_
#define GFX_H_
#include "common/rect.h"
#include "common/system.h"
#include "math/frustum.h"
#include "math/matrix4.h"
#include "math/vector3d.h"
namespace Myst3 {
class Renderer;
class Drawable {
public:
Drawable();
virtual ~Drawable() {}
virtual void draw() {}
virtual void drawOverlay() {}
/** Should the drawable be drawn inside the active window, or is it allowed to draw on the entire screen? */
bool isConstrainedToWindow() const { return _isConstrainedToWindow; }
/** Whether to setup the renderer state for 2D or 3D when processing the drawable */
bool is3D() const { return _is3D; }
/** Whether to scale the drawable to a size equivalent to the original engine or to draw it at its native size */
bool isScaled() const { return _scaled; }
protected:
bool _isConstrainedToWindow;
bool _is3D;
bool _scaled;
};
/**
* Game screen window
*
* A window represents a game screen pane.
* It allows abstracting the rendering position from the behavior.
*/
class Window : public Drawable {
public:
/**
* Get the window position in screen coordinates
*/
virtual Common::Rect getPosition() const = 0;
/**
* Get the window position in original (640x480) screen coordinates
*/
virtual Common::Rect getOriginalPosition() const = 0;
/**
* Get the window center in screen coordinates
*/
Common::Point getCenter() const;
/**
* Convert screen coordinates to window coordinates
*/
Common::Point screenPosToWindowPos(const Common::Point &screen) const;
/**
* Transform a point from screen coordinates to scaled window coordinates
*/
Common::Point scalePoint(const Common::Point &screen) const;
};
class Texture {
public:
virtual ~Texture() {}
uint width;
uint height;
Graphics::PixelFormat format;
virtual void update(const Graphics::Surface *surface) = 0;
virtual void updatePartial(const Graphics::Surface *surface, const Common::Rect &rect) = 0;
static const Graphics::PixelFormat getRGBAPixelFormat();
};
class Renderer {
public:
Renderer(OSystem *system);
virtual ~Renderer();
virtual void init() = 0;
virtual void clear() = 0;
/**
* Swap the buffers, making the drawn screen visible
*/
virtual void flipBuffer() { }
virtual void initFont(const Graphics::Surface *surface);
virtual void freeFont();
virtual Texture *createTexture3D(const Graphics::Surface *surface) = 0;
virtual Texture *createTexture2D(const Graphics::Surface *surface) { return createTexture3D(surface); }
virtual void drawRect2D(const Common::Rect &rect, uint8 a, uint8 r, uint8 g, uint8 b) = 0;
virtual void drawTexturedRect2D(const Common::Rect &screenRect, const Common::Rect &textureRect, Texture *texture,
float transparency = -1.0, bool additiveBlending = false) = 0;
virtual void drawTexturedRect3D(const Math::Vector3d &topLeft, const Math::Vector3d &bottomLeft,
const Math::Vector3d &topRight, const Math::Vector3d &bottomRight,
Texture *texture) = 0;
virtual void drawCube(Texture **textures) = 0;
virtual void draw2DText(const Common::String &text, const Common::Point &position) = 0;
virtual Graphics::Surface *getScreenshot() = 0;
virtual Texture *copyScreenshotToTexture();
/** Render a Drawable in the specified window */
void renderDrawable(Drawable *drawable, Window *window);
/** Render a Drawable overlay in the specified window */
void renderDrawableOverlay(Drawable *drawable, Window *window);
/** Render the main Drawable of a Window */
void renderWindow(Window *window);
/** Render the main Drawable overlay of a Window */
void renderWindowOverlay(Window *window);
Common::Rect viewport() const;
/**
* Select the window where to render
*
* This also sets the viewport
*/
virtual void selectTargetWindow(Window *window, bool is3D, bool scaled) = 0;
void setupCameraPerspective(float pitch, float heading, float fov);
bool isCubeFaceVisible(uint face);
Math::Matrix4 getMvpMatrix() const { return _mvpMatrix; }
void flipVertical(Graphics::Surface *s);
static const int kOriginalWidth = 640;
static const int kOriginalHeight = 480;
static const int kTopBorderHeight = 30;
static const int kBottomBorderHeight = 90;
static const int kFrameHeight = 360;
void computeScreenViewport();
protected:
OSystem *_system;
Texture *_font;
Common::Rect _screenViewport;
Math::Matrix4 _projectionMatrix;
Math::Matrix4 _modelViewMatrix;
Math::Matrix4 _mvpMatrix;
Math::Frustum _frustum;
static const float cubeVertices[5 * 6 * 4];
Math::AABB _cubeFacesAABB[6];
Common::Rect getFontCharacterRect(uint8 character);
Math::Matrix4 makeProjectionMatrix(float fov) const;
};
Renderer *CreateGfxOpenGL(OSystem *system);
Renderer *CreateGfxOpenGLShader(OSystem *system);
Renderer *CreateGfxTinyGL(OSystem *system);
Renderer *createRenderer(OSystem *system);
} // End of namespace Myst3
#endif // GFX_H_

View File

@@ -0,0 +1,330 @@
/* 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 "common/rect.h"
#include "common/textconsole.h"
#if defined(USE_OPENGL_GAME)
#include "graphics/opengl/context.h"
#include "graphics/surface.h"
#include "engines/myst3/gfx.h"
#include "engines/myst3/gfx_opengl.h"
#include "engines/myst3/gfx_opengl_texture.h"
namespace Myst3 {
Renderer *CreateGfxOpenGL(OSystem *system) {
return new OpenGLRenderer(system);
}
OpenGLRenderer::OpenGLRenderer(OSystem *system) :
Renderer(system) {
}
OpenGLRenderer::~OpenGLRenderer() {
}
Texture *OpenGLRenderer::createTexture3D(const Graphics::Surface *surface) {
return new OpenGLTexture(surface);
}
void OpenGLRenderer::init() {
debug("Initializing OpenGL Renderer");
computeScreenViewport();
// Check the available OpenGL extensions
if (!OpenGLContext.NPOTSupported) {
warning("GL_ARB_texture_non_power_of_two is not available.");
}
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glDisable(GL_LIGHTING);
glEnable(GL_TEXTURE_2D);
glEnable(GL_DEPTH_TEST);
}
void OpenGLRenderer::clear() {
glClearColor(0.f, 0.f, 0.f, 1.f); // Solid black
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
}
void OpenGLRenderer::selectTargetWindow(Window *window, bool is3D, bool scaled) {
if (!window) {
// No window found ...
if (scaled) {
// ... in scaled mode draw in the original game screen area
Common::Rect vp = viewport();
glViewport(vp.left, _system->getHeight() - vp.top - vp.height(), vp.width(), vp.height());
} else {
// ... otherwise, draw on the whole screen
glViewport(0, 0, _system->getWidth(), _system->getHeight());
}
} else {
// Found a window, draw inside it
Common::Rect vp = window->getPosition();
glViewport(vp.left, _system->getHeight() - vp.top - vp.height(), vp.width(), vp.height());
}
if (is3D) {
glMatrixMode(GL_PROJECTION);
glLoadMatrixf(_projectionMatrix.getData());
glMatrixMode(GL_MODELVIEW);
glLoadMatrixf(_modelViewMatrix.getData());
} else {
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
if (!window) {
if (scaled) {
glOrtho(0.0, kOriginalWidth, kOriginalHeight, 0.0, -1.0, 1.0);
} else {
glOrtho(0.0, _system->getWidth(), _system->getHeight(), 0.0, -1.0, 1.0);
}
} else {
if (scaled) {
Common::Rect originalRect = window->getOriginalPosition();
glOrtho(0.0, originalRect.width(), originalRect.height(), 0.0, -1.0, 1.0);
} else {
Common::Rect vp = window->getPosition();
glOrtho(0.0, vp.width(), vp.height(), 0.0, -1.0, 1.0);
}
}
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}
}
void OpenGLRenderer::drawRect2D(const Common::Rect &rect, uint8 a, uint8 r, uint8 g, uint8 b) {
glDisable(GL_TEXTURE_2D);
glColor4ub(r, g, b, a);
if (a != 255) {
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
}
glBegin(GL_TRIANGLE_STRIP);
glVertex3f(rect.left, rect.bottom, 0.0f);
glVertex3f(rect.right, rect.bottom, 0.0f);
glVertex3f(rect.left, rect.top, 0.0f);
glVertex3f(rect.right, rect.top, 0.0f);
glEnd();
glDisable(GL_BLEND);
}
void OpenGLRenderer::drawTexturedRect2D(const Common::Rect &screenRect, const Common::Rect &textureRect,
Texture *texture, float transparency, bool additiveBlending) {
OpenGLTexture *glTexture = static_cast<OpenGLTexture *>(texture);
const float tLeft = textureRect.left / (float)glTexture->internalWidth;
const float tWidth = textureRect.width() / (float)glTexture->internalWidth;
const float tTop = textureRect.top / (float)glTexture->internalHeight;
const float tHeight = textureRect.height() / (float)glTexture->internalHeight;
float sLeft = screenRect.left;
float sTop = screenRect.top;
float sRight = sLeft + screenRect.width();
float sBottom = sTop + screenRect.height();
if (glTexture->upsideDown) {
SWAP(sTop, sBottom);
}
if (transparency >= 0.0) {
if (additiveBlending) {
glBlendFunc(GL_SRC_ALPHA, GL_ONE);
} else {
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
}
glEnable(GL_BLEND);
} else {
transparency = 1.0;
}
glEnable(GL_TEXTURE_2D);
glColor4f(1.0f, 1.0f, 1.0f, transparency);
glDepthMask(GL_FALSE);
glBindTexture(GL_TEXTURE_2D, glTexture->id);
glBegin(GL_TRIANGLE_STRIP);
glTexCoord2f(tLeft, tTop + tHeight);
glVertex3f(sLeft + 0, sBottom, 1.0f);
glTexCoord2f(tLeft + tWidth, tTop + tHeight);
glVertex3f(sRight, sBottom, 1.0f);
glTexCoord2f(tLeft, tTop);
glVertex3f(sLeft + 0, sTop + 0, 1.0f);
glTexCoord2f(tLeft + tWidth, tTop);
glVertex3f(sRight, sTop + 0, 1.0f);
glEnd();
glDisable(GL_BLEND);
glDepthMask(GL_TRUE);
}
void OpenGLRenderer::draw2DText(const Common::String &text, const Common::Point &position) {
OpenGLTexture *glFont = static_cast<OpenGLTexture *>(_font);
// The font only has uppercase letters
Common::String textToDraw = text;
textToDraw.toUppercase();
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glEnable(GL_TEXTURE_2D);
glDepthMask(GL_FALSE);
glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
glBindTexture(GL_TEXTURE_2D, glFont->id);
int x = position.x;
int y = position.y;
for (uint i = 0; i < textToDraw.size(); i++) {
Common::Rect textureRect = getFontCharacterRect(textToDraw[i]);
int w = textureRect.width();
int h = textureRect.height();
float cw = textureRect.width() / (float)glFont->internalWidth;
float ch = textureRect.height() / (float)glFont->internalHeight;
float cx = textureRect.left / (float)glFont->internalWidth;
float cy = textureRect.top / (float)glFont->internalHeight;
glBegin(GL_QUADS);
glTexCoord2f(cx, cy + ch);
glVertex3f(x, y, 1.0f);
glTexCoord2f(cx + cw, cy + ch);
glVertex3f(x + w, y, 1.0f);
glTexCoord2f(cx + cw, cy);
glVertex3f(x + w, y + h, 1.0f);
glTexCoord2f(cx, cy);
glVertex3f(x, y + h, 1.0f);
glEnd();
x += textureRect.width() - 3;
}
glDisable(GL_TEXTURE_2D);
glDisable(GL_BLEND);
glDepthMask(GL_TRUE);
}
void OpenGLRenderer::drawFace(uint face, Texture *texture) {
OpenGLTexture *glTexture = static_cast<OpenGLTexture *>(texture);
// Used fragment of the texture
const float w = glTexture->width / (float) glTexture->internalWidth;
const float h = glTexture->height / (float) glTexture->internalHeight;
glBindTexture(GL_TEXTURE_2D, glTexture->id);
glBegin(GL_TRIANGLE_STRIP);
for (uint i = 0; i < 4; i++) {
glTexCoord2f(w * cubeVertices[5 * (4 * face + i) + 0], h * cubeVertices[5 * (4 * face + i) + 1]);
glVertex3f(cubeVertices[5 * (4 * face + i) + 2], cubeVertices[5 * (4 * face + i) + 3], cubeVertices[5 * (4 * face + i) + 4]);
}
glEnd();
}
void OpenGLRenderer::drawCube(Texture **textures) {
glEnable(GL_TEXTURE_2D);
glDepthMask(GL_FALSE);
for (uint i = 0; i < 6; i++) {
drawFace(i, textures[i]);
}
glDepthMask(GL_TRUE);
}
void OpenGLRenderer::drawTexturedRect3D(const Math::Vector3d &topLeft, const Math::Vector3d &bottomLeft,
const Math::Vector3d &topRight, const Math::Vector3d &bottomRight, Texture *texture) {
OpenGLTexture *glTexture = static_cast<OpenGLTexture *>(texture);
const float w = glTexture->width / (float)glTexture->internalWidth;
const float h = glTexture->height / (float)glTexture->internalHeight;
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glEnable(GL_BLEND);
glDepthMask(GL_FALSE);
glBindTexture(GL_TEXTURE_2D, glTexture->id);
glBegin(GL_TRIANGLE_STRIP);
glTexCoord2f(0, 0);
glVertex3f(-topLeft.x(), topLeft.y(), topLeft.z());
glTexCoord2f(0, h);
glVertex3f(-bottomLeft.x(), bottomLeft.y(), bottomLeft.z());
glTexCoord2f(w, 0);
glVertex3f(-topRight.x(), topRight.y(), topRight.z());
glTexCoord2f(w, h);
glVertex3f(-bottomRight.x(), bottomRight.y(), bottomRight.z());
glEnd();
glDisable(GL_BLEND);
glDepthMask(GL_TRUE);
}
Graphics::Surface *OpenGLRenderer::getScreenshot() {
Common::Rect screen = viewport();
Graphics::Surface *s = new Graphics::Surface();
s->create(screen.width(), screen.height(), Texture::getRGBAPixelFormat());
g_system->presentBuffer();
glReadPixels(screen.left, screen.top, screen.width(), screen.height(), GL_RGBA, GL_UNSIGNED_BYTE, s->getPixels());
flipVertical(s);
return s;
}
Texture *OpenGLRenderer::copyScreenshotToTexture() {
OpenGLTexture *texture = new OpenGLTexture();
Common::Rect screen = viewport();
texture->copyFromFramebuffer(screen);
return texture;
}
} // End of namespace Myst3
#endif

View File

@@ -0,0 +1,64 @@
/* 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/>.
*
*/
#ifndef GFX_OPENGL_H_
#define GFX_OPENGL_H_
#include "common/rect.h"
#include "common/system.h"
#include "math/vector3d.h"
#include "engines/myst3/gfx.h"
namespace Myst3 {
class OpenGLRenderer : public Renderer {
public:
OpenGLRenderer(OSystem *_system);
virtual ~OpenGLRenderer();
void init() override;
void clear() override;
void selectTargetWindow(Window *window, bool is3D, bool scaled) override;
Texture *createTexture3D(const Graphics::Surface *surface) override;
void drawRect2D(const Common::Rect &rect, uint8 a, uint8 r, uint8 g, uint8 b) override;
virtual void drawTexturedRect2D(const Common::Rect &screenRect, const Common::Rect &textureRect, Texture *texture,
float transparency = -1.0, bool additiveBlending = false) override;
virtual void drawTexturedRect3D(const Math::Vector3d &topLeft, const Math::Vector3d &bottomLeft,
const Math::Vector3d &topRight, const Math::Vector3d &bottomRight,
Texture *texture) override;
void drawCube(Texture **textures) override;
void draw2DText(const Common::String &text, const Common::Point &position) override;
Graphics::Surface *getScreenshot() override;
Texture *copyScreenshotToTexture() override;
private:
void drawFace(uint face, Texture *texture);
};
} // End of namespace Myst3
#endif // GFX_H_

View File

@@ -0,0 +1,397 @@
/* 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/>.
*
*/
// Matrix calculations taken from the glm library
// Which is covered by the MIT license
// And has this additional copyright note:
/* Copyright (c) 2005 - 2012 G-Truc Creation
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*/
#include "common/rect.h"
#include "common/textconsole.h"
#if defined(USE_OPENGL_SHADERS)
#include "graphics/surface.h"
#include "math/glmath.h"
#include "math/vector2d.h"
#include "math/rect2d.h"
#include "math/quat.h"
#include "graphics/opengl/shader.h"
#include "engines/myst3/gfx.h"
#include "engines/myst3/gfx_opengl_texture.h"
#include "engines/myst3/gfx_opengl_shaders.h"
namespace Myst3 {
Renderer *CreateGfxOpenGLShader(OSystem *system) {
return new ShaderRenderer(system);
}
static const GLfloat boxVertices[] = {
// XS YT
0.0, 0.0,
1.0, 0.0,
0.0, 1.0,
1.0, 1.0,
};
void ShaderRenderer::setupQuadEBO() {
unsigned short quadIndices[6 * 100];
unsigned short start = 0;
for (unsigned short *p = quadIndices; p < &quadIndices[6 * 100]; p += 6) {
p[0] = p[3] = start++;
p[1] = start++;
p[2] = p[4] = start++;
p[5] = start++;
}
_quadEBO = OpenGL::Shader::createBuffer(GL_ELEMENT_ARRAY_BUFFER, sizeof(quadIndices), quadIndices, GL_STATIC_DRAW);
}
Math::Vector2d ShaderRenderer::scaled(float x, float y) const {
return Math::Vector2d(x / _currentViewport.width(), y / _currentViewport.height());
}
ShaderRenderer::ShaderRenderer(OSystem *system) :
Renderer(system),
_prevText(""),
_prevTextPosition(0,0),
_currentViewport(kOriginalWidth, kOriginalHeight),
_boxShader(nullptr),
_cubeShader(nullptr),
_rect3dShader(nullptr),
_textShader(nullptr),
_boxVBO(0),
_cubeVBO(0),
_rect3dVBO(0),
_textVBO(0),
_quadEBO(0) {
}
ShaderRenderer::~ShaderRenderer() {
OpenGL::Shader::freeBuffer(_boxVBO);
OpenGL::Shader::freeBuffer(_cubeVBO);
OpenGL::Shader::freeBuffer(_rect3dVBO);
OpenGL::Shader::freeBuffer(_textVBO);
OpenGL::Shader::freeBuffer(_quadEBO);
delete _boxShader;
delete _cubeShader;
delete _rect3dShader;
delete _textShader;
}
Texture *ShaderRenderer::createTexture3D(const Graphics::Surface *surface) {
return new OpenGLTexture(surface);
}
void ShaderRenderer::init() {
debug("Initializing OpenGL Renderer with shaders");
computeScreenViewport();
glEnable(GL_DEPTH_TEST);
static const char* attributes[] = { "position", "texcoord", nullptr };
_boxShader = OpenGL::Shader::fromFiles("myst3_box", attributes);
_boxVBO = OpenGL::Shader::createBuffer(GL_ARRAY_BUFFER, sizeof(boxVertices), boxVertices);
_boxShader->enableVertexAttribute("position", _boxVBO, 2, GL_FLOAT, GL_TRUE, 2 * sizeof(float), 0);
_boxShader->enableVertexAttribute("texcoord", _boxVBO, 2, GL_FLOAT, GL_TRUE, 2 * sizeof(float), 0);
_cubeShader = OpenGL::Shader::fromFiles("myst3_cube", attributes);
_cubeVBO = OpenGL::Shader::createBuffer(GL_ARRAY_BUFFER, sizeof(cubeVertices), cubeVertices);
_cubeShader->enableVertexAttribute("texcoord", _cubeVBO, 2, GL_FLOAT, GL_TRUE, 5 * sizeof(float), 0);
_cubeShader->enableVertexAttribute("position", _cubeVBO, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), 2 * sizeof(float));
_rect3dShader = OpenGL::Shader::fromFiles("myst3_cube", attributes);
_rect3dVBO = OpenGL::Shader::createBuffer(GL_ARRAY_BUFFER, 20 * sizeof(float), nullptr);
_rect3dShader->enableVertexAttribute("texcoord", _rect3dVBO, 2, GL_FLOAT, GL_TRUE, 5 * sizeof(float), 0);
_rect3dShader->enableVertexAttribute("position", _rect3dVBO, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), 2 * sizeof(float));
_textShader = OpenGL::Shader::fromFiles("myst3_text", attributes);
_textVBO = OpenGL::Shader::createBuffer(GL_ARRAY_BUFFER, 100 * 16 * sizeof(float), nullptr, GL_DYNAMIC_DRAW);
_textShader->enableVertexAttribute("texcoord", _textVBO, 2, GL_FLOAT, GL_TRUE, 4 * sizeof(float), 0);
_textShader->enableVertexAttribute("position", _textVBO, 2, GL_FLOAT, GL_TRUE, 4 * sizeof(float), 2 * sizeof(float));
setupQuadEBO();
}
void ShaderRenderer::clear() {
glClearColor(0.f, 0.f, 0.f, 1.f); // Solid black
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
}
void ShaderRenderer::selectTargetWindow(Window *window, bool is3D, bool scaled) {
if (!window) {
// No window found ...
if (scaled) {
// ... in scaled mode draw in the original game screen area
Common::Rect vp = viewport();
glViewport(vp.left, _system->getHeight() - vp.top - vp.height(), vp.width(), vp.height());
_currentViewport = Common::Rect(kOriginalWidth, kOriginalHeight);
} else {
// ... otherwise, draw on the whole screen
glViewport(0, 0, _system->getWidth(), _system->getHeight());
_currentViewport = Common::Rect(_system->getWidth(), _system->getHeight());
}
} else {
// Found a window, draw inside it
Common::Rect vp = window->getPosition();
glViewport(vp.left, _system->getHeight() - vp.top - vp.height(), vp.width(), vp.height());
if (scaled) {
_currentViewport = window->getOriginalPosition();
} else {
_currentViewport = vp;
}
}
}
void ShaderRenderer::drawRect2D(const Common::Rect &rect, uint8 a, uint8 r, uint8 g, uint8 b) {
_boxShader->use();
_boxShader->setUniform("textured", false);
_boxShader->setUniform("color", Math::Vector4d(r / 255.0, g / 255.0, b / 255.0, a / 255.0));
_boxShader->setUniform("verOffsetXY", scaled(rect.left, rect.top));
_boxShader->setUniform("verSizeWH", scaled(rect.width(), rect.height()));
glDepthMask(GL_FALSE);
if (a != 255) {
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
}
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
glDisable(GL_BLEND);
glDepthMask(GL_TRUE);
}
void ShaderRenderer::drawTexturedRect2D(const Common::Rect &screenRect, const Common::Rect &textureRect,
Texture *texture, float transparency, bool additiveBlending) {
OpenGLTexture *glTexture = static_cast<OpenGLTexture *>(texture);
const float tLeft = textureRect.left / (float)glTexture->internalWidth;
const float tWidth = textureRect.width() / (float)glTexture->internalWidth;
const float tTop = textureRect.top / (float)glTexture->internalHeight;
const float tHeight = textureRect.height() / (float)glTexture->internalHeight;
const float sLeft = screenRect.left;
const float sTop = screenRect.top;
const float sWidth = screenRect.width();
const float sHeight = screenRect.height();
if (transparency >= 0.0) {
if (additiveBlending) {
glBlendFunc(GL_SRC_ALPHA, GL_ONE);
} else {
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
}
glEnable(GL_BLEND);
} else {
transparency = 1.0;
}
_boxShader->use();
_boxShader->setUniform("textured", true);
_boxShader->setUniform("color", Math::Vector4d(1.0f, 1.0f, 1.0f, transparency));
_boxShader->setUniform("verOffsetXY", scaled(sLeft, sTop));
_boxShader->setUniform("verSizeWH", scaled(sWidth, sHeight));
_boxShader->setUniform("texOffsetXY", Math::Vector2d(tLeft, tTop));
_boxShader->setUniform("texSizeWH", Math::Vector2d(tWidth, tHeight));
_boxShader->setUniform("flipY", glTexture->upsideDown);
glDepthMask(GL_FALSE);
glBindTexture(GL_TEXTURE_2D, glTexture->id);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
glDisable(GL_BLEND);
glDepthMask(GL_TRUE);
}
void ShaderRenderer::draw2DText(const Common::String &text, const Common::Point &position) {
OpenGLTexture *glFont = static_cast<OpenGLTexture *>(_font);
// The font only has uppercase letters
Common::String textToDraw = text;
textToDraw.toUppercase();
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glDisable(GL_DEPTH_TEST);
glDepthMask(GL_FALSE);
if (_prevText != textToDraw || _prevTextPosition != position) {
_prevText = textToDraw;
_prevTextPosition = position;
float x = position.x / (float) _currentViewport.width();
float y = position.y / (float) _currentViewport.height();
float *bufData = new float[16 * textToDraw.size()];
float *cur = bufData;
for (uint i = 0; i < textToDraw.size(); i++) {
Common::Rect textureRect = getFontCharacterRect(textToDraw[i]);
float w = textureRect.width() / (float) _currentViewport.width();
float h = textureRect.height() / (float) _currentViewport.height();
float cw = textureRect.width() / (float)glFont->internalWidth;
float ch = textureRect.height() / (float)glFont->internalHeight;
float cx = textureRect.left / (float)glFont->internalWidth;
float cy = textureRect.top / (float)glFont->internalHeight;
const float charData[] = {
cx, cy + ch, x, y,
cx + cw, cy + ch, x + w, y,
cx + cw, cy, x + w, y + h,
cx, cy, x, y + h,
};
memcpy(cur, charData, 16 * sizeof(float));
cur += 16;
x += (textureRect.width() - 3) / (float) _currentViewport.width();
}
glBindBuffer(GL_ARRAY_BUFFER, _textVBO);
glBufferSubData(GL_ARRAY_BUFFER, 0, textToDraw.size() * 16 * sizeof(float), bufData);
delete[] bufData;
}
_textShader->use();
glBindTexture(GL_TEXTURE_2D, glFont->id);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _quadEBO);
glDrawElements(GL_TRIANGLES, 6 * textToDraw.size(), GL_UNSIGNED_SHORT, nullptr);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
glDisable(GL_BLEND);
glEnable(GL_DEPTH_TEST);
glDepthMask(GL_TRUE);
}
void ShaderRenderer::drawCube(Texture **textures) {
OpenGLTexture *texture0 = static_cast<OpenGLTexture *>(textures[0]);
glDepthMask(GL_FALSE);
_cubeShader->use();
_cubeShader->setUniform1f("texScale", texture0->width / (float) texture0->internalWidth);
_cubeShader->setUniform("mvpMatrix", _mvpMatrix);
glBindTexture(GL_TEXTURE_2D, static_cast<OpenGLTexture *>(textures[0])->id);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
glBindTexture(GL_TEXTURE_2D, static_cast<OpenGLTexture *>(textures[1])->id);
glDrawArrays(GL_TRIANGLE_STRIP, 4, 4);
glBindTexture(GL_TEXTURE_2D, static_cast<OpenGLTexture *>(textures[2])->id);
glDrawArrays(GL_TRIANGLE_STRIP, 8, 4);
glBindTexture(GL_TEXTURE_2D, static_cast<OpenGLTexture *>(textures[3])->id);
glDrawArrays(GL_TRIANGLE_STRIP, 12, 4);
glBindTexture(GL_TEXTURE_2D, static_cast<OpenGLTexture *>(textures[4])->id);
glDrawArrays(GL_TRIANGLE_STRIP, 16, 4);
glBindTexture(GL_TEXTURE_2D, static_cast<OpenGLTexture *>(textures[5])->id);
glDrawArrays(GL_TRIANGLE_STRIP, 20, 4);
glDepthMask(GL_TRUE);
}
void ShaderRenderer::drawTexturedRect3D(const Math::Vector3d &topLeft, const Math::Vector3d &bottomLeft,
const Math::Vector3d &topRight, const Math::Vector3d &bottomRight, Texture *texture) {
OpenGLTexture *glTexture = static_cast<OpenGLTexture *>(texture);
const float w = glTexture->width / (float)glTexture->internalWidth;
const float h = glTexture->height / (float)glTexture->internalHeight;
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glEnable(GL_BLEND);
glDepthMask(GL_FALSE);
glBindTexture(GL_TEXTURE_2D, glTexture->id);
const GLfloat vertices[] = {
// S T X Y Z
0, 0, -topLeft.x(), topLeft.y(), topLeft.z(),
0, h, -bottomLeft.x(), bottomLeft.y(), bottomLeft.z(),
w, 0, -topRight.x(), topRight.y(), topRight.z(),
w, h, -bottomRight.x(), bottomRight.y(), bottomRight.z(),
};
_rect3dShader->use();
_rect3dShader->setUniform1f("texScale", 1.0f);
_rect3dShader->setUniform("mvpMatrix", _mvpMatrix);
glBindBuffer(GL_ARRAY_BUFFER, _rect3dVBO);
glBufferSubData(GL_ARRAY_BUFFER, 0, 20 * sizeof(float), vertices);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
glDisable(GL_BLEND);
glDepthMask(GL_TRUE);
}
Graphics::Surface *ShaderRenderer::getScreenshot() {
Common::Rect screen = viewport();
Graphics::Surface *s = new Graphics::Surface();
s->create(screen.width(), screen.height(), Texture::getRGBAPixelFormat());
g_system->presentBuffer();
glReadPixels(screen.left, screen.top, screen.width(), screen.height(), GL_RGBA, GL_UNSIGNED_BYTE, s->getPixels());
flipVertical(s);
return s;
}
Texture *ShaderRenderer::copyScreenshotToTexture() {
OpenGLTexture *texture = new OpenGLTexture();
Common::Rect screen = viewport();
texture->copyFromFramebuffer(screen);
return texture;
}
} // End of namespace Myst3
#endif

View File

@@ -0,0 +1,83 @@
/* 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/>.
*
*/
#ifndef GFX_OPENGL_SHADERS_H_
#define GFX_OPENGL_SHADERS_H_
#include "common/rect.h"
#include "math/rect2d.h"
#include "graphics/opengl/shader.h"
#include "engines/myst3/gfx.h"
namespace Myst3 {
class ShaderRenderer : public Renderer {
public:
ShaderRenderer(OSystem *_system);
virtual ~ShaderRenderer();
void init() override;
void clear() override;
void selectTargetWindow(Window *window, bool is3D, bool scaled) override;
Texture *createTexture3D(const Graphics::Surface *surface) override;
void drawRect2D(const Common::Rect &rect, uint8 a, uint8 r, uint8 g, uint8 b) override;
virtual void drawTexturedRect2D(const Common::Rect &screenRect, const Common::Rect &textureRect, Texture *texture,
float transparency = -1.0, bool additiveBlending = false) override;
virtual void drawTexturedRect3D(const Math::Vector3d &topLeft, const Math::Vector3d &bottomLeft,
const Math::Vector3d &topRight, const Math::Vector3d &bottomRight,
Texture *texture) override;
void drawCube(Texture **textures) override;
void draw2DText(const Common::String &text, const Common::Point &position) override;
Graphics::Surface *getScreenshot() override;
Texture *copyScreenshotToTexture() override;
private:
void setupQuadEBO();
Math::Vector2d scaled(float x, float y) const;
OpenGL::Shader *_boxShader;
OpenGL::Shader *_cubeShader;
OpenGL::Shader *_rect3dShader;
OpenGL::Shader *_textShader;
GLuint _boxVBO;
GLuint _cubeVBO;
GLuint _rect3dVBO;
GLuint _textVBO;
GLuint _quadEBO;
Common::Rect _currentViewport;
Common::String _prevText;
Common::Point _prevTextPosition;
};
} // End of namespace Myst3
#endif

View File

@@ -0,0 +1,149 @@
/* 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 "common/scummsys.h"
#if defined(USE_OPENGL_GAME) || defined(USE_OPENGL_SHADERS)
#include "engines/myst3/gfx_opengl_texture.h"
#include "graphics/opengl/context.h"
namespace Myst3 {
// From Bit Twiddling Hacks
static uint32 upperPowerOfTwo(uint32 v) {
v--;
v |= v >> 1;
v |= v >> 2;
v |= v >> 4;
v |= v >> 8;
v |= v >> 16;
v++;
return v;
}
OpenGLTexture::OpenGLTexture() :
internalFormat(0),
sourceFormat(0),
internalWidth(0),
internalHeight(0),
upsideDown(false) {
glGenTextures(1, &id);
}
OpenGLTexture::OpenGLTexture(const Graphics::Surface *surface) {
width = surface->w;
height = surface->h;
format = surface->format;
upsideDown = false;
// Pad the textures if non power of two support is unavailable
if (OpenGLContext.NPOTSupported) {
internalHeight = height;
internalWidth = width;
} else {
internalHeight = upperPowerOfTwo(height);
internalWidth = upperPowerOfTwo(width);
}
if (format.bytesPerPixel == 4) {
assert(surface->format == getRGBAPixelFormat());
internalFormat = GL_RGBA;
sourceFormat = GL_UNSIGNED_BYTE;
} else if (format.bytesPerPixel == 2) {
internalFormat = GL_RGB;
sourceFormat = GL_UNSIGNED_SHORT_5_6_5;
} else
error("Unknown pixel format");
glGenTextures(1, &id);
glBindTexture(GL_TEXTURE_2D, id);
glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, internalWidth, internalHeight, 0, internalFormat, sourceFormat, nullptr);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
// TODO: If non power of two textures are unavailable this clamping
// has no effect on the padded sides (resulting in white lines on the edges)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
update(surface);
}
OpenGLTexture::~OpenGLTexture() {
glDeleteTextures(1, &id);
}
void OpenGLTexture::update(const Graphics::Surface *surface) {
updatePartial(surface, Common::Rect(surface->w, surface->h));
}
void OpenGLTexture::updateTexture(const Graphics::Surface *surface, const Common::Rect &rect) {
assert(surface->format == format);
glBindTexture(GL_TEXTURE_2D, id);
if (OpenGLContext.unpackSubImageSupported) {
const Graphics::Surface subArea = surface->getSubArea(rect);
glPixelStorei(GL_UNPACK_ROW_LENGTH, surface->pitch / surface->format.bytesPerPixel);
glTexSubImage2D(GL_TEXTURE_2D, 0, rect.left, rect.top, subArea.w, subArea.h, internalFormat, sourceFormat, const_cast<void *>(subArea.getPixels()));
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
} else {
// GL_UNPACK_ROW_LENGTH is not supported, don't bother and do a full texture update
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, surface->w, surface->h, internalFormat, sourceFormat, const_cast<void *>(surface->getPixels()));
}
}
void OpenGLTexture::updatePartial(const Graphics::Surface *surface, const Common::Rect &rect) {
updateTexture(surface, rect);
}
void OpenGLTexture::copyFromFramebuffer(const Common::Rect &screen) {
internalFormat = GL_RGB;
width = screen.width();
height = screen.height();
upsideDown = true;
// Pad the textures if non power of two support is unavailable
if (OpenGLContext.NPOTSupported) {
internalHeight = height;
internalWidth = width;
} else {
internalHeight = upperPowerOfTwo(height);
internalWidth = upperPowerOfTwo(width);
}
glBindTexture(GL_TEXTURE_2D, id);
glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, internalWidth, internalHeight, 0, internalFormat, GL_UNSIGNED_BYTE, nullptr);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
g_system->presentBuffer();
glCopyTexImage2D(GL_TEXTURE_2D, 0, internalFormat, screen.left, screen.top, internalWidth, internalHeight, 0);
}
} // End of namespace Myst3
#endif

View File

@@ -0,0 +1,57 @@
/* 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/>.
*
*/
#ifndef GFX_OPENGL_TEXTURE_H
#define GFX_OPENGL_TEXTURE_H
#include "graphics/surface.h"
#include "graphics/opengl/system_headers.h"
#include "common/textconsole.h"
#include "engines/myst3/gfx.h"
namespace Myst3 {
class OpenGLTexture : public Texture {
public:
OpenGLTexture(const Graphics::Surface *surface);
OpenGLTexture();
virtual ~OpenGLTexture();
void update(const Graphics::Surface *surface) override;
void updatePartial(const Graphics::Surface *surface, const Common::Rect &rect) override;
void copyFromFramebuffer(const Common::Rect &screen);
GLuint id;
GLuint internalFormat;
GLuint sourceFormat;
uint32 internalWidth;
uint32 internalHeight;
bool upsideDown;
private:
void updateTexture(const Graphics::Surface *surface, const Common::Rect &rect);
};
} // End of namespace Myst3
#endif

View File

@@ -0,0 +1,287 @@
/* 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 "common/config-manager.h"
#include "common/rect.h"
#include "common/textconsole.h"
#include "graphics/surface.h"
#include "graphics/tinygl/tinygl.h"
#include "math/vector2d.h"
#include "math/glmath.h"
#include "engines/myst3/gfx.h"
#include "engines/myst3/gfx_tinygl.h"
#include "engines/myst3/gfx_tinygl_texture.h"
namespace Myst3 {
Renderer *CreateGfxTinyGL(OSystem *system) {
return new TinyGLRenderer(system);
}
TinyGLRenderer::TinyGLRenderer(OSystem *system) :
Renderer(system) {
}
TinyGLRenderer::~TinyGLRenderer() {
TinyGL::destroyContext();
}
Texture *TinyGLRenderer::createTexture2D(const Graphics::Surface *surface) {
return new TinyGLTexture2D(surface);
}
Texture *TinyGLRenderer::createTexture3D(const Graphics::Surface *surface) {
return new TinyGLTexture3D(surface);
}
void TinyGLRenderer::init() {
debug("Initializing Software 3D Renderer");
computeScreenViewport();
TinyGL::createContext(kOriginalWidth, kOriginalHeight, g_system->getScreenFormat(), 512, false, ConfMan.getBool("dirtyrects"));
tglMatrixMode(TGL_PROJECTION);
tglLoadIdentity();
tglMatrixMode(TGL_MODELVIEW);
tglLoadIdentity();
tglDisable(TGL_LIGHTING);
tglEnable(TGL_TEXTURE_2D);
tglEnable(TGL_DEPTH_TEST);
}
void TinyGLRenderer::clear() {
tglClearColor(0.f, 0.f, 0.f, 1.f); // Solid black
tglClear(TGL_COLOR_BUFFER_BIT | TGL_DEPTH_BUFFER_BIT);
tglColor4f(1.0f, 1.0f, 1.0f, 1.0f);
}
void TinyGLRenderer::selectTargetWindow(Window *window, bool is3D, bool scaled) {
if (!window) {
// No window found ...
if (scaled) {
// ... in scaled mode draw in the original game screen area
_viewport = viewport();
} else {
// ... otherwise, draw on the whole screen
_viewport = Common::Rect(_system->getWidth(), _system->getHeight());
}
} else {
// Found a window, draw inside it
_viewport = window->getPosition();
}
tglViewport(_viewport.left, _system->getHeight() - _viewport.top - _viewport.height(), _viewport.width(), _viewport.height());
if (is3D) {
tglMatrixMode(TGL_PROJECTION);
tglLoadMatrixf(_projectionMatrix.getData());
tglMatrixMode(TGL_MODELVIEW);
tglLoadMatrixf(_modelViewMatrix.getData());
} else {
tglMatrixMode(TGL_PROJECTION);
tglLoadIdentity();
if (!window) {
if (scaled) {
tglOrthof(0, kOriginalWidth, kOriginalHeight, 0, -1, 1);
} else {
tglOrthof(0, _system->getWidth(), _system->getHeight(), 0, -1, 1);
}
} else {
if (scaled) {
Common::Rect originalRect = window->getOriginalPosition();
tglOrthof(0, originalRect.width(), originalRect.height(), 0, -1, 1);
} else {
Common::Rect vp = window->getPosition();
tglOrthof(0, vp.width(), vp.height(), 0, -1, 1);
}
}
tglMatrixMode(TGL_MODELVIEW);
tglLoadIdentity();
}
}
void TinyGLRenderer::drawRect2D(const Common::Rect &rect, uint8 a, uint8 r, uint8 g, uint8 b) {
tglDisable(TGL_TEXTURE_2D);
tglColor4ub(r, g, b, a);
if (a != 255) {
tglEnable(TGL_BLEND);
tglBlendFunc(TGL_SRC_ALPHA, TGL_ONE_MINUS_SRC_ALPHA);
}
tglBegin(TGL_TRIANGLE_STRIP);
tglVertex3f(rect.left, rect.bottom, 0.0f);
tglVertex3f(rect.right, rect.bottom, 0.0f);
tglVertex3f(rect.left, rect.top, 0.0f);
tglVertex3f(rect.right, rect.top, 0.0f);
tglEnd();
tglDisable(TGL_BLEND);
}
void TinyGLRenderer::drawTexturedRect2D(const Common::Rect &screenRect, const Common::Rect &textureRect,
Texture *texture, float transparency, bool additiveBlending) {
TinyGLTexture2D *glTexture = static_cast<TinyGLTexture2D *>(texture);
const float sLeft = screenRect.left;
const float sTop = screenRect.top;
const float sWidth = screenRect.width();
const float sHeight = screenRect.height();
if (transparency >= 0.0) {
if (additiveBlending) {
tglBlendFunc(TGL_SRC_ALPHA, TGL_ONE);
} else {
tglBlendFunc(TGL_SRC_ALPHA, TGL_ONE_MINUS_SRC_ALPHA);
}
tglEnable(TGL_BLEND);
} else {
transparency = 1.0;
}
tglEnable(TGL_TEXTURE_2D);
tglDepthMask(TGL_FALSE);
// HACK: tglBlit is not affected by the viewport, so we offset the draw coordinates here
TinyGL::BlitTransform transform(sLeft + _viewport.left, sTop + _viewport.top);
transform.sourceRectangle(textureRect.left, textureRect.top, sWidth, sHeight);
transform.tint(transparency);
tglBlit(glTexture->getBlitTexture(), transform);
tglDisable(TGL_BLEND);
tglDepthMask(TGL_TRUE);
}
void TinyGLRenderer::draw2DText(const Common::String &text, const Common::Point &position) {
TinyGLTexture2D *glFont = static_cast<TinyGLTexture2D *>(_font);
// The font only has uppercase letters
Common::String textToDraw = text;
textToDraw.toUppercase();
tglEnable(TGL_BLEND);
tglBlendFunc(TGL_SRC_ALPHA, TGL_ONE_MINUS_SRC_ALPHA);
tglEnable(TGL_TEXTURE_2D);
tglDepthMask(TGL_FALSE);
tglColor4f(1.0f, 1.0f, 1.0f, 1.0f);
int x = position.x;
int y = position.y;
for (uint i = 0; i < textToDraw.size(); i++) {
Common::Rect textureRect = getFontCharacterRect(textToDraw[i]);
int w = textureRect.width();
int h = textureRect.height();
TinyGL::BlitTransform transform(x, y);
transform.sourceRectangle(textureRect.left, textureRect.top, w, h);
transform.flip(true, false);
tglBlit(glFont->getBlitTexture(), transform);
x += textureRect.width() - 3;
}
tglDisable(TGL_TEXTURE_2D);
tglDisable(TGL_BLEND);
tglDepthMask(TGL_TRUE);
}
void TinyGLRenderer::drawFace(uint face, Texture *texture) {
TinyGLTexture3D *glTexture = static_cast<TinyGLTexture3D *>(texture);
tglBindTexture(TGL_TEXTURE_2D, glTexture->id);
tglBegin(TGL_TRIANGLE_STRIP);
for (uint i = 0; i < 4; i++) {
tglTexCoord2f(cubeVertices[5 * (4 * face + i) + 0], cubeVertices[5 * (4 * face + i) + 1]);
tglVertex3f(cubeVertices[5 * (4 * face + i) + 2], cubeVertices[5 * (4 * face + i) + 3], cubeVertices[5 * (4 * face + i) + 4]);
}
tglEnd();
}
void TinyGLRenderer::drawCube(Texture **textures) {
tglEnable(TGL_TEXTURE_2D);
tglDepthMask(TGL_FALSE);
for (uint i = 0; i < 6; i++) {
drawFace(i, textures[i]);
}
tglDepthMask(TGL_TRUE);
}
void TinyGLRenderer::drawTexturedRect3D(const Math::Vector3d &topLeft, const Math::Vector3d &bottomLeft,
const Math::Vector3d &topRight, const Math::Vector3d &bottomRight, Texture *texture) {
TinyGLTexture3D *glTexture = static_cast<TinyGLTexture3D *>(texture);
tglBlendFunc(TGL_SRC_ALPHA, TGL_ONE_MINUS_SRC_ALPHA);
tglEnable(TGL_BLEND);
tglDepthMask(TGL_FALSE);
tglBindTexture(TGL_TEXTURE_2D, glTexture->id);
tglBegin(TGL_TRIANGLE_STRIP);
tglTexCoord2f(0, 0);
tglVertex3f(-topLeft.x(), topLeft.y(), topLeft.z());
tglTexCoord2f(0, 1);
tglVertex3f(-bottomLeft.x(), bottomLeft.y(), bottomLeft.z());
tglTexCoord2f(1, 0);
tglVertex3f(-topRight.x(), topRight.y(), topRight.z());
tglTexCoord2f(1, 1);
tglVertex3f(-bottomRight.x(), bottomRight.y(), bottomRight.z());
tglEnd();
tglDisable(TGL_BLEND);
tglDepthMask(TGL_TRUE);
}
Graphics::Surface *TinyGLRenderer::getScreenshot() {
return TinyGL::copyFromFrameBuffer(Texture::getRGBAPixelFormat());
}
void TinyGLRenderer::flipBuffer() {
Common::List<Common::Rect> dirtyAreas;
TinyGL::presentBuffer(dirtyAreas);
Graphics::Surface glBuffer;
TinyGL::getSurfaceRef(glBuffer);
if (!dirtyAreas.empty()) {
for (Common::List<Common::Rect>::iterator itRect = dirtyAreas.begin(); itRect != dirtyAreas.end(); ++itRect) {
g_system->copyRectToScreen(glBuffer.getBasePtr((*itRect).left, (*itRect).top), glBuffer.pitch,
(*itRect).left, (*itRect).top, (*itRect).width(), (*itRect).height());
}
}
}
} // End of namespace Myst3

View File

@@ -0,0 +1,70 @@
/* 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/>.
*
*/
#ifndef GFX_TINYGL_H_
#define GFX_TINYGL_H_
#include "common/rect.h"
#include "common/system.h"
#include "math/vector3d.h"
#include "engines/myst3/gfx.h"
#include "graphics/tinygl/tinygl.h"
namespace Myst3 {
class TinyGLRenderer : public Renderer {
public:
TinyGLRenderer(OSystem *_system);
virtual ~TinyGLRenderer();
void init() override;
void clear() override;
void selectTargetWindow(Window *window, bool is3D, bool scaled) override;
Texture *createTexture2D(const Graphics::Surface *surface) override;
Texture *createTexture3D(const Graphics::Surface *surface) override;
void drawRect2D(const Common::Rect &rect, uint8 a, uint8 r, uint8 g, uint8 b) override;
virtual void drawTexturedRect2D(const Common::Rect &screenRect, const Common::Rect &textureRect, Texture *texture,
float transparency = -1.0, bool additiveBlending = false) override;
virtual void drawTexturedRect3D(const Math::Vector3d &topLeft, const Math::Vector3d &bottomLeft,
const Math::Vector3d &topRight, const Math::Vector3d &bottomRight,
Texture *texture) override;
void drawCube(Texture **textures) override;
void draw2DText(const Common::String &text, const Common::Point &position) override;
Graphics::Surface *getScreenshot() override;
void flipBuffer() override;
private:
void drawFace(uint face, Texture *texture);
Common::Rect _viewport;
};
} // End of namespace Myst3
#endif // GFX_H_

View File

@@ -0,0 +1,96 @@
/* 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/gfx_tinygl_texture.h"
#include "graphics/tinygl/tinygl.h"
namespace Myst3 {
TinyGLTexture2D::TinyGLTexture2D(const Graphics::Surface *surface) {
width = surface->w;
height = surface->h;
format = surface->format;
_blitImage = tglGenBlitImage();
update(surface);
}
TinyGLTexture2D::~TinyGLTexture2D() {
tglDeleteBlitImage(_blitImage);
}
void TinyGLTexture2D::update(const Graphics::Surface *surface) {
tglUploadBlitImage(_blitImage, *surface, 0, false);
}
void TinyGLTexture2D::updatePartial(const Graphics::Surface *surface, const Common::Rect &rect) {
// FIXME: TinyGL does not support partial texture update
update(surface);
}
TinyGL::BlitImage *TinyGLTexture2D::getBlitTexture() const {
return _blitImage;
}
TinyGLTexture3D::TinyGLTexture3D(const Graphics::Surface *surface) {
width = surface->w;
height = surface->h;
format = surface->format;
if (format.bytesPerPixel == 4) {
internalFormat = TGL_RGBA;
sourceFormat = TGL_UNSIGNED_BYTE;
} else if (format.bytesPerPixel == 2) {
internalFormat = TGL_RGB;
sourceFormat = TGL_UNSIGNED_SHORT_5_6_5;
} else
error("Unknown pixel format");
tglGenTextures(1, &id);
tglBindTexture(TGL_TEXTURE_2D, id);
tglTexImage2D(TGL_TEXTURE_2D, 0, internalFormat, width, height, 0, internalFormat, sourceFormat, nullptr);
tglTexParameteri(TGL_TEXTURE_2D, TGL_TEXTURE_MIN_FILTER, TGL_LINEAR);
tglTexParameteri(TGL_TEXTURE_2D, TGL_TEXTURE_MAG_FILTER, TGL_LINEAR);
// NOTE: TinyGL doesn't have issues with white lines so doesn't need use TGL_CLAMP_TO_EDGE
tglTexParameteri(TGL_TEXTURE_2D, TGL_TEXTURE_WRAP_S, TGL_REPEAT);
tglTexParameteri(TGL_TEXTURE_2D, TGL_TEXTURE_WRAP_T, TGL_REPEAT);
update(surface);
}
TinyGLTexture3D::~TinyGLTexture3D() {
tglDeleteTextures(1, &id);
}
void TinyGLTexture3D::update(const Graphics::Surface *surface) {
tglBindTexture(TGL_TEXTURE_2D, id);
tglTexImage2D(TGL_TEXTURE_2D, 0, internalFormat, width, height, 0,
internalFormat, sourceFormat, const_cast<void *>(surface->getPixels())); // TESTME: Not sure if it works.
}
void TinyGLTexture3D::updatePartial(const Graphics::Surface *surface, const Common::Rect &rect) {
// FIXME: TinyGL does not support partial texture update
update(surface);
}
} // End of namespace Myst3

View File

@@ -0,0 +1,63 @@
/* 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/>.
*
*/
#ifndef GFX_TINYGL_TEXTURE_H
#define GFX_TINYGL_TEXTURE_H
#include "common/textconsole.h"
#include "graphics/surface.h"
#include "graphics/tinygl/tinygl.h"
#include "engines/myst3/gfx.h"
namespace Myst3 {
class TinyGLTexture2D : public Texture {
public:
TinyGLTexture2D(const Graphics::Surface *surface);
virtual ~TinyGLTexture2D();
TinyGL::BlitImage *getBlitTexture() const;
void update(const Graphics::Surface *surface) override;
void updatePartial(const Graphics::Surface *surface, const Common::Rect &rect) override;
private:
TinyGL::BlitImage *_blitImage;
};
class TinyGLTexture3D : public Texture {
public:
TinyGLTexture3D(const Graphics::Surface *surface);
virtual ~TinyGLTexture3D();
void update(const Graphics::Surface *surface) override;
void updatePartial(const Graphics::Surface *surface, const Common::Rect &rect) override;
TGLuint id;
TGLuint internalFormat;
TGLuint sourceFormat;
};
} // End of namespace Myst3
#endif

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

@@ -0,0 +1,174 @@
/* 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/hotspot.h"
#include "engines/myst3/scene.h"
#include "engines/myst3/state.h"
#include "common/config-manager.h"
#include "math/ray.h"
namespace Myst3 {
static void polarRectTo3dRect(const PolarRect &polarRect,
Math::Vector3d &topLeft, Math::Vector3d &topRight,
Math::Vector3d &bottomLeft, Math::Vector3d &bottomRight) {
static const float scale = 50.0;
Math::Vector3d direction = Scene::directionToVector(polarRect.centerPitch, 90.0 - polarRect.centerHeading) * scale;
Math::Vector3d u = Math::Vector3d(direction.z(), 0.0, -direction.x());
u.normalize();
Math::Vector3d v = Math::Vector3d::crossProduct(direction, u);
v.normalize();
Math::Vector3d sizeU = u * polarRect.width / 90.0 * scale;
Math::Vector3d sizeV = v * polarRect.height / 90.0 * scale;
topRight = direction + sizeV + sizeU;
bottomRight = direction - sizeV + sizeU;
bottomLeft = direction - sizeV - sizeU;
topLeft = direction + sizeV - sizeU;
}
bool static rayIntersectsRect(const Math::Ray &ray, const Math::Vector3d &topLeft, const Math::Vector3d &topRight,
const Math::Vector3d &bottomLeft, const Math::Vector3d &bottomRight) {
// Orthogonal basis in rectangle coordinates
Math::Vector3d topRectDir = topRight - topLeft;
Math::Vector3d leftRectDir = bottomLeft - topLeft;
Math::Vector3d n = Math::Vector3d::crossProduct(topRectDir, leftRectDir);
float nDotDir = Math::Vector3d::dotProduct(n, ray.getDirection());
if (ABS(nDotDir) < 1e-6) {
// The ray is coplanar with the rectangle
return false;
}
// Solution to the system (intersection of line with plane):
// Line equation: V = ray.origin + t * ray.direction
// Plane equation: dot(n, V) = 0
float t = -Math::Vector3d::dotProduct(n, ray.getOrigin() - topLeft) / nDotDir;
if (t < 0.0) {
// The intersection is not in the ray direction
return false;
}
// Intersection point in world coordinates
Math::Vector3d intersection = ray.getOrigin() + ray.getDirection() * t;
// Intersection point in 2D rect coordinates
Math::Vector3d intersect2D = intersection - topLeft;
float u = Math::Vector3d::dotProduct(intersect2D, topRectDir);
float v = Math::Vector3d::dotProduct(intersect2D, leftRectDir);
// Intersection inside the rectangle
return (u >= 0.0 && u <= Math::Vector3d::dotProduct(topRectDir, topRectDir)
&& v >= 0.0 && v <= Math::Vector3d::dotProduct(leftRectDir, leftRectDir));
}
HotSpot::HotSpot() :
condition(0),
cursor(0) {
}
int32 HotSpot::isPointInRectsCube(float pitch, float heading) {
for (uint j = 0; j < rects.size(); j++) {
Math::Ray ray = Math::Ray(Math::Vector3d(), Scene::directionToVector(pitch, 90.0 - heading));
Math::Vector3d topLeft, topRight, bottomLeft, bottomRight;
polarRectTo3dRect(rects[j], topLeft, topRight, bottomLeft, bottomRight);
if (rayIntersectsRect(ray, topLeft, topRight, bottomLeft, bottomRight)) {
return j;
}
}
return -1;
}
int32 HotSpot::isPointInRectsFrame(GameState *state, const Common::Point &p) {
for (uint j = 0; j < rects.size(); j++) {
int16 x = rects[j].centerPitch;
int16 y = rects[j].centerHeading;
int16 w = rects[j].width;
int16 h = rects[j].height;
if (y < 0) {
x = state->getVar(x);
y = state->getVar(-y);
h = -h;
}
Common::Rect rect = Common::Rect(w, h);
rect.translate(x, y);
if (rect.contains(p))
return j;
}
return -1;
}
bool HotSpot::isEnabled(GameState *state, uint16 var) {
if (!state->evaluate(condition))
return false;
if (isZip()) {
if (!ConfMan.getBool("zip_mode") || !isZipDestinationAvailable(state)) {
return false;
}
}
if (var == 0)
return cursor <= 13;
else
return cursor == var;
}
int32 HotSpot::isZipDestinationAvailable(GameState *state) {
assert(isZip() && script.size() != 0);
uint16 node;
uint16 room = state->getLocationRoom();
uint32 age = state->getLocationAge();
// Get the zip destination from the script
Opcode op = script[0];
switch (op.op) {
case 140:
case 142:
node = op.args[0];
break;
case 141:
case 143:
node = op.args[1];
room = op.args[0];
break;
default:
error("Expected zip action");
}
return state->isZipDestinationAvailable(node, room, age);
}
} // End of namespace Myst3

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

@@ -0,0 +1,69 @@
/* 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/>.
*
*/
#ifndef HOTSPOT_H_
#define HOTSPOT_H_
#include "common/rect.h"
#include "common/array.h"
namespace Myst3 {
class GameState;
struct Opcode {
uint8 op;
Common::Array<int16> args;
};
struct CondScript {
uint16 condition;
Common::Array<Opcode> script;
};
struct PolarRect {
int16 centerPitch;
int16 centerHeading;
int16 height;
int16 width;
};
class HotSpot {
public:
HotSpot();
int16 condition;
Common::Array<PolarRect> rects;
int16 cursor;
Common::Array<Opcode> script;
int32 isPointInRectsCube(float pitch, float heading);
int32 isPointInRectsFrame(GameState *state, const Common::Point &p);
bool isEnabled(GameState *state, uint16 var = 0);
private:
bool isZip() { return cursor == 7; }
int32 isZipDestinationAvailable(GameState *state);
};
} // End of namespace Myst3
#endif // HOTSPOT_H_

392
engines/myst3/inventory.cpp Normal file
View File

@@ -0,0 +1,392 @@
/* 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/inventory.h"
#include "engines/myst3/cursor.h"
#include "engines/myst3/database.h"
#include "engines/myst3/scene.h"
#include "engines/myst3/state.h"
namespace Myst3 {
const Inventory::ItemData Inventory::_availableItems[8] = {
{ 0, 41, 47, 481 },
{ 41, 38, 50, 480 },
{ 79, 38, 49, 279 },
{ 117, 34, 48, 277 },
{ 151, 35, 44, 345 },
{ 186, 35, 44, 398 },
{ 221, 35, 44, 447 },
{ 0, 0, 0, 0 }
};
Inventory::Inventory(Myst3Engine *vm) :
Window(),
_vm(vm),
_texture(nullptr) {
_scaled = !_vm->isWideScreenModEnabled();
initializeTexture();
}
Inventory::~Inventory() {
delete _texture;
}
void Inventory::initializeTexture() {
Graphics::Surface *s = _vm->loadTexture(1204);
_texture = _vm->_gfx->createTexture2D(s);
s->free();
delete s;
}
bool Inventory::isMouseInside() {
Common::Point mouse = _vm->_cursor->getPosition(false);
return getPosition().contains(mouse);
}
void Inventory::draw() {
if (_vm->isWideScreenModEnabled()) {
// Draw a black background to cover the main game frame
Common::Rect screen = _vm->_gfx->viewport();
_vm->_gfx->drawRect2D(Common::Rect(screen.width(), Renderer::kBottomBorderHeight), 0xFF, 0x00, 0x00, 0x00);
}
uint16 hoveredItemVar = hoveredItem();
for (ItemList::const_iterator it = _inventory.begin(); it != _inventory.end(); it++) {
int32 state = _vm->_state->getVar(it->var);
// Don't draw if the item is being dragged or is hidden
if (state == -1 || state == 0)
continue;
const ItemData &item = getData(it->var);
Common::Rect textureRect = Common::Rect(item.textureWidth,
item.textureHeight);
textureRect.translate(item.textureX, 0);
bool itemHighlighted = it->var == hoveredItemVar || state == 2;
if (itemHighlighted)
textureRect.translate(0, _texture->height / 2);
_vm->_gfx->drawTexturedRect2D(it->rect, textureRect, _texture);
}
}
void Inventory::reset() {
_inventory.clear();
reflow();
updateState();
}
void Inventory::addItem(uint16 var, bool atEnd) {
// Only add objects once to the inventory
if (!hasItem(var)) {
_vm->_state->setVar(var, 1);
InventoryItem i;
i.var = var;
if (atEnd) {
_inventory.push_back(i);
} else {
_inventory.push_front(i);
}
reflow();
updateState();
}
}
void Inventory::removeItem(uint16 var) {
_vm->_state->setVar(var, 0);
for (ItemList::iterator it = _inventory.begin(); it != _inventory.end(); it++) {
if (it->var == var) {
_inventory.erase(it);
break;
}
}
reflow();
updateState();
}
void Inventory::addAll() {
for (uint i = 0; _availableItems[i].var; i++)
addItem(_availableItems[i].var, true);
}
bool Inventory::hasItem(uint16 var) {
for (ItemList::iterator it = _inventory.begin(); it != _inventory.end(); it++) {
if (it->var == var)
return true;
}
return false;
}
const Inventory::ItemData &Inventory::getData(uint16 var) {
for (uint i = 0; _availableItems[i].var; i++) {
if (_availableItems[i].var == var)
return _availableItems[i];
}
return _availableItems[7];
}
void Inventory::reflow() {
uint16 itemCount = 0;
uint16 totalWidth = 0;
for (uint i = 0; _availableItems[i].var; i++) {
if (hasItem(_availableItems[i].var)) {
totalWidth += _availableItems[i].textureWidth;
itemCount++;
}
}
if (itemCount >= 2)
totalWidth += 9 * (itemCount - 1);
uint16 left;
if (_vm->isWideScreenModEnabled()) {
Common::Rect screen = _vm->_gfx->viewport();
left = (screen.width() - totalWidth) / 2;
} else {
left = (Renderer::kOriginalWidth - totalWidth) / 2;
}
for (ItemList::iterator it = _inventory.begin(); it != _inventory.end(); it++) {
const ItemData &item = getData(it->var);
uint16 top = (Renderer::kBottomBorderHeight - item.textureHeight) / 2;
it->rect = Common::Rect(item.textureWidth, item.textureHeight);
it->rect.translate(left, top);
left += item.textureWidth;
if (itemCount >= 2)
left += 9;
}
}
uint16 Inventory::hoveredItem() {
Common::Point mouse = _vm->_cursor->getPosition(false);
mouse = scalePoint(mouse);
for (ItemList::const_iterator it = _inventory.begin(); it != _inventory.end(); it++) {
if(it->rect.contains(mouse))
return it->var;
}
return 0;
}
void Inventory::useItem(uint16 var) {
switch (var) {
case 277: // Atrus
closeAllBooks();
_vm->_state->setJournalAtrusState(2);
openBook(9, kRoomJournals, 100);
break;
case 279: // Saavedro
closeAllBooks();
_vm->_state->setJournalSaavedroState(2);
openBook(9, kRoomJournals, 200);
break;
case 480: // Tomahna
closeAllBooks();
_vm->_state->setBookStateTomahna(2);
openBook(8, kRoomNarayan, 220);
break;
case 481: // Releeshahn
closeAllBooks();
_vm->_state->setBookStateReleeshahn(2);
openBook(9, kRoomJournals, 300);
break;
case 345:
_vm->dragSymbol(345, 1002);
break;
case 398:
_vm->dragSymbol(398, 1001);
break;
case 447:
_vm->dragSymbol(447, 1000);
break;
default:
debug("Used inventory item %d which is not implemented", var);
}
}
void Inventory::closeAllBooks() {
if (_vm->_state->getJournalAtrusState())
_vm->_state->setJournalAtrusState(1);
if (_vm->_state->getJournalSaavedroState())
_vm->_state->setJournalSaavedroState(1);
if (_vm->_state->getBookStateTomahna())
_vm->_state->setBookStateTomahna(1);
if (_vm->_state->getBookStateReleeshahn())
_vm->_state->setBookStateReleeshahn(1);
}
void Inventory::openBook(uint16 age, uint16 room, uint16 node) {
if (!_vm->_state->getBookSavedNode()) {
_vm->_state->setBookSavedAge(_vm->_state->getLocationAge());
_vm->_state->setBookSavedRoom(_vm->_state->getLocationRoom());
_vm->_state->setBookSavedNode(_vm->_state->getLocationNode());
}
_vm->_state->setLocationNextAge(age);
_vm->_state->setLocationNextRoom(room);
_vm->goToNode(node, kTransitionFade);
}
void Inventory::addSaavedroChapter(uint16 var) {
_vm->_state->setVar(var, 1);
_vm->_state->setJournalSaavedroState(2);
_vm->_state->setJournalSaavedroChapter(var - 285);
_vm->_state->setJournalSaavedroPageInChapter(0);
openBook(9, kRoomJournals, 200);
}
void Inventory::loadFromState() {
Common::Array<uint16> items = _vm->_state->getInventory();
_inventory.clear();
for (uint i = 0; i < items.size(); i++)
addItem(items[i], true);
}
void Inventory::updateState() {
Common::Array<uint16> items;
for (ItemList::iterator it = _inventory.begin(); it != _inventory.end(); it++)
items.push_back(it->var);
_vm->_state->updateInventory(items);
}
Common::Rect Inventory::getPosition() const {
Common::Rect screen = _vm->_gfx->viewport();
Common::Rect frame;
if (_vm->isWideScreenModEnabled()) {
frame = Common::Rect(screen.width(), Renderer::kBottomBorderHeight);
Common::Rect scenePosition = _vm->_scene->getPosition();
int16 top = CLIP<int16>(screen.height() - frame.height(), 0, scenePosition.bottom);
frame.translate(0, top);
} else {
frame = Common::Rect(screen.width(), screen.height() * Renderer::kBottomBorderHeight / Renderer::kOriginalHeight);
frame.translate(screen.left, screen.top + screen.height() * (Renderer::kTopBorderHeight + Renderer::kFrameHeight) / Renderer::kOriginalHeight);
}
return frame;
}
Common::Rect Inventory::getOriginalPosition() const {
Common::Rect originalPosition = Common::Rect(Renderer::kOriginalWidth, Renderer::kBottomBorderHeight);
originalPosition.translate(0, Renderer::kTopBorderHeight + Renderer::kFrameHeight);
return originalPosition;
}
void Inventory::updateCursor() {
uint16 item = hoveredItem();
if (item > 0) {
_vm->_cursor->changeCursor(1);
} else {
_vm->_cursor->changeCursor(8);
}
}
DragItem::DragItem(Myst3Engine *vm, uint id):
_vm(vm),
_texture(nullptr),
_frame(1) {
// Draw on the whole screen
_isConstrainedToWindow = false;
_scaled = !_vm->isWideScreenModEnabled();
ResourceDescription movieDesc = _vm->getFileDescription("DRAG", id, 0, Archive::kStillMovie);
if (!movieDesc.isValid())
error("Movie %d does not exist", id);
// Load the movie
_movieStream = movieDesc.getData();
_bink.loadStream(_movieStream);
_bink.setOutputPixelFormat(Texture::getRGBAPixelFormat());
_bink.start();
const Graphics::Surface *frame = _bink.decodeNextFrame();
_texture = _vm->_gfx->createTexture2D(frame);
}
DragItem::~DragItem() {
delete _texture;
}
void DragItem::drawOverlay() {
Common::Rect textureRect = Common::Rect(_texture->width, _texture->height);
_vm->_gfx->drawTexturedRect2D(getPosition(), textureRect, _texture, 0.99f);
}
void DragItem::setFrame(uint16 frame) {
if (frame != _frame) {
_frame = frame;
_bink.seekToFrame(frame - 1);
const Graphics::Surface *s = _bink.decodeNextFrame();
_texture->update(s);
}
}
Common::Rect DragItem::getPosition() {
Common::Rect viewport;
Common::Point mouse;
if (_scaled) {
viewport = Common::Rect(Renderer::kOriginalWidth, Renderer::kOriginalHeight);
mouse = _vm->_cursor->getPosition(true);
} else {
viewport = _vm->_gfx->viewport();
mouse = _vm->_cursor->getPosition(false);
}
uint posX = CLIP<uint>(mouse.x, _texture->width / 2, viewport.width() - _texture->width / 2);
uint posY = CLIP<uint>(mouse.y, _texture->height / 2, viewport.height() - _texture->height / 2);
// Proper scaling code - similar to cursor scaling code (see: Cursor::draw())
float scale = MIN(
viewport.width() / (float) Renderer::kOriginalWidth,
viewport.height() / (float) Renderer::kOriginalHeight
);
Common::Rect screenRect = Common::Rect::center(posX, posY, _texture->width * scale, _texture->height * scale);
return screenRect;
}
} // End of namespace Myst3

121
engines/myst3/inventory.h Normal file
View File

@@ -0,0 +1,121 @@
/* 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/>.
*
*/
#ifndef INVENTORY_H_
#define INVENTORY_H_
#include "common/list.h"
#include "common/memstream.h"
#include "common/rect.h"
#include "engines/myst3/gfx.h"
#include "video/bink_decoder.h"
namespace Myst3 {
class Myst3Engine;
class Texture;
class Inventory : public Window {
public:
Inventory(Myst3Engine *vm);
virtual ~Inventory();
// Window API
Common::Rect getPosition() const override;
Common::Rect getOriginalPosition() const override;
void loadFromState();
void updateState();
void addItem(uint16 var, bool atEnd);
void addSaavedroChapter(uint16 var);
void addAll();
void removeItem(uint16 var);
void reset();
/** Is the mouse inside the inventory area */
bool isMouseInside();
/** Change the cursor when it is hovering an item */
void updateCursor();
void reflow();
uint16 hoveredItem();
void useItem(uint16 var);
void draw() override;
private:
struct InventoryItem {
uint16 var;
Common::Rect rect;
};
typedef Common::List<InventoryItem> ItemList;
struct ItemData {
uint16 textureX;
uint16 textureWidth;
uint16 textureHeight;
uint16 var;
};
static const ItemData _availableItems[8];
const ItemData &getData(uint16 var);
Myst3Engine *_vm;
Texture *_texture;
ItemList _inventory;
void initializeTexture();
bool hasItem(uint16 var);
void openBook(uint16 age, uint16 room, uint16 node);
void closeAllBooks();
};
class DragItem : public Drawable {
public:
DragItem(Myst3Engine *vm, uint id);
~DragItem();
void drawOverlay() override;
void setFrame(uint16 frame);
private:
Myst3Engine *_vm;
Common::SeekableReadStream *_movieStream;
Video::BinkDecoder _bink;
uint16 _frame;
Texture *_texture;
Common::Rect getPosition();
};
} // End of namespace Myst3
#endif // INVENTORY_H_

1022
engines/myst3/menu.cpp Normal file

File diff suppressed because it is too large Load Diff

218
engines/myst3/menu.h Normal file
View File

@@ -0,0 +1,218 @@
/* 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/>.
*
*/
#ifndef MENU_H_
#define MENU_H_
#include "engines/myst3/gfx.h"
#include "common/events.h"
#include "common/hashmap.h"
#include "common/memstream.h"
#include "common/ptr.h"
#include "common/rect.h"
#include "common/savefile.h"
#include "common/str-array.h"
#include "video/bink_decoder.h"
namespace Myst3 {
class Myst3Engine;
class SpotItemFace;
class GameState;
enum DialogType {
kConfirmNewGame,
kConfirmLoadGame,
kConfirmOverwrite,
kConfirmEraseSavedGame,
kErrorEraseSavedGame,
kConfirmQuit
};
class Menu : public Drawable {
public:
Menu(Myst3Engine *vm);
virtual ~Menu();
/** Indicates if the player is currently in a menu node */
bool isOpen() const;
/**
* Handle an event for the menu
*
* @return true if the event was handled
*/
virtual bool handleInput(const Common::KeyState &e) = 0;
void updateMainMenu(uint16 action);
void goToNode(uint16 node);
/**
* Capture a save thumbnail
*/
Graphics::Surface *captureThumbnail();
/**
* Capture and save a thumbnail
* thumbnail can be obtain from borrowSaveThumbnail()
*/
void generateSaveThumbnail();
/**
* Get the current save thumbnail
*
* Only valid while the menu is open
*/
Graphics::Surface *borrowSaveThumbnail();
virtual void saveLoadAction(uint16 action, uint16 item) = 0;
virtual void setSaveLoadSpotItem(uint16 id, SpotItemFace *spotItem);
protected:
Myst3Engine *_vm;
Common::ScopedPtr<Graphics::Surface, Graphics::SurfaceDeleter> _saveThumbnail;
SpotItemFace *_saveLoadSpotItem;
Common::String _saveLoadAgeName;
uint dialogIdFromType(DialogType type);
uint16 dialogConfirmValue();
uint16 dialogSaveValue();
Graphics::Surface *createThumbnail(Graphics::Surface *big);
Common::String getAgeLabel(GameState *gameState);
};
class PagingMenu : public Menu {
public:
PagingMenu(Myst3Engine *vm);
virtual ~PagingMenu();
void draw() override;
bool handleInput(const Common::KeyState &e) override;
void saveLoadAction(uint16 action, uint16 item) override;
private:
Common::StringArray _saveLoadFiles;
Common::String _saveName;
bool _saveDrawCaret;
int32 _saveCaretCounter;
static const uint kCaretSpeed = 25;
void loadMenuOpen();
void loadMenuSelect(uint16 item);
void loadMenuLoad();
void loadMenuChangePage();
void saveMenuOpen();
void saveMenuSelect(uint16 item);
void saveMenuChangePage();
void saveMenuSave();
void saveLoadErase();
void saveLoadUpdateVars();
Common::String prepareSaveNameForDisplay(const Common::String &name);
};
class AlbumMenu : public Menu {
public:
AlbumMenu(Myst3Engine *vm);
virtual ~AlbumMenu();
void draw() override;
bool handleInput(const Common::KeyState &e) override;
void saveLoadAction(uint16 action, uint16 item) override;
void setSaveLoadSpotItem(uint16 id, SpotItemFace *spotItem) override;
private:
static const uint16 kAlbumThumbnailWidth = 100;
static const uint16 kAlbumThumbnailHeight = 56;
// This map does not own its elements
Common::HashMap<int, SpotItemFace *> _albumSpotItems;
Common::String _saveLoadTime;
void loadMenuOpen();
void loadMenuSelect();
void loadMenuLoad();
void saveMenuOpen();
void saveMenuSave();
void setSavesAvailable();
Common::String getSaveNameTemplate();
Common::HashMap<int, Common::String> listSaveFiles();
void loadSaves();
};
class Dialog : public Drawable {
public:
Dialog(Myst3Engine *vm, uint id);
virtual ~Dialog();
void draw() override;
virtual int16 update() = 0;
protected:
Common::Rect getPosition() const;
Myst3Engine *_vm;
Video::BinkDecoder _bink;
Texture *_texture;
uint _buttonCount;
};
class ButtonsDialog : public Dialog {
public:
ButtonsDialog(Myst3Engine *vm, uint id);
virtual ~ButtonsDialog();
void draw() override;
int16 update() override;
private:
Common::Point getRelativeMousePosition() const;
uint16 _previousframe;
uint16 _frameToDisplay;
Common::Rect _buttons[3];
void loadButtons();
};
class GamepadDialog : public Dialog {
public:
GamepadDialog(Myst3Engine *vm, uint id);
virtual ~GamepadDialog();
int16 update() override;
};
} // End of namespace Myst3
#endif // MENU_H_

View File

@@ -0,0 +1,187 @@
/* 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/advancedDetector.h"
#include "engines/myst3/database.h"
#include "engines/myst3/gfx.h"
#include "engines/myst3/state.h"
#include "engines/myst3/detection.h"
#include "common/config-manager.h"
#include "common/savefile.h"
#include "common/translation.h"
#include "graphics/scaler.h"
namespace Myst3{
static const ADExtraGuiOptionsMap optionsList[] = {
{
GAMEOPTION_WIDESCREEN_MOD,
{
_s("Widescreen mod"),
_s("Enable widescreen rendering in fullscreen mode."),
"widescreen_mod",
false,
0,
0
}
},
AD_EXTRA_GUI_OPTIONS_TERMINATOR
};
class Myst3MetaEngine : public AdvancedMetaEngine<Myst3GameDescription> {
public:
const char *getName() const override {
return "myst3";
}
const ADExtraGuiOptionsMap *getAdvancedExtraGuiOptions() const override {
return optionsList;
}
bool hasFeature(MetaEngineFeature f) const override {
return (f == kSupportsListSaves) ||
(f == kSupportsDeleteSave) ||
(f == kSupportsLoadingDuringStartup) ||
(f == kSavesSupportMetaInfo) ||
(f == kSavesSupportThumbnail) ||
(f == kSavesSupportCreationDate) ||
(f == kSavesSupportPlayTime);
}
SaveStateList listSaves(const char *target) const override {
Common::Platform platform = Common::parsePlatform(ConfMan.get("platform", target));
Common::StringArray filenames = Saves::list(g_system->getSavefileManager(), platform);
SaveStateList saveList;
for (uint32 i = 0; i < filenames.size(); i++)
// Since slots are ignored when saving, we always return slot 0
// as an unused slot to optimise the autosave process
saveList.push_back(SaveStateDescriptor(this, i + 1, filenames[i]));
return saveList;
}
SaveStateDescriptor getSaveDescription(const char *target, int slot) const {
SaveStateList saves = listSaves(target);
SaveStateDescriptor description;
for (uint32 i = 0; i < saves.size(); i++) {
if (saves[i].getSaveSlot() == slot) {
description = saves[i];
}
}
return description;
}
SaveStateDescriptor querySaveMetaInfos(const char *target, int slot) const override {
SaveStateDescriptor saveInfos = getSaveDescription(target, slot);
if (saveInfos.getDescription().empty()) {
// Unused slot
return SaveStateDescriptor();
}
// Open save
Common::InSaveFile *saveFile = g_system->getSavefileManager()->openForLoading(saveInfos.getDescription());
if (!saveFile) {
warning("Unable to open file %s for reading, slot %d", saveInfos.getDescription().encode().c_str(), slot);
return SaveStateDescriptor();
}
// Read state data
Common::Serializer s = Common::Serializer(saveFile, nullptr);
GameState::StateData data;
data.syncWithSaveGame(s);
// Read and resize the thumbnail
Graphics::Surface *saveThumb = GameState::readThumbnail(saveFile);
Graphics::Surface *guiThumb = GameState::resizeThumbnail(saveThumb, kThumbnailWidth, kThumbnailHeight1);
saveThumb->free();
delete saveThumb;
// Set metadata
saveInfos.setThumbnail(guiThumb);
saveInfos.setPlayTime(data.secondsPlayed * 1000);
if (data.saveYear != 0) {
saveInfos.setSaveDate(data.saveYear, data.saveMonth, data.saveDay);
saveInfos.setSaveTime(data.saveHour, data.saveMinute);
}
if (data.saveDescription != "") {
saveInfos.setDescription(data.saveDescription);
}
if (s.getVersion() >= 150) {
saveInfos.setAutosave(data.isAutosave);
}
delete saveFile;
return saveInfos;
}
bool removeSaveState(const char *target, int slot) const override {
SaveStateDescriptor saveInfos = getSaveDescription(target, slot);
return g_system->getSavefileManager()->removeSavefile(saveInfos.getDescription());
}
int getMaximumSaveSlot() const override {
return 999;
}
Common::Error createInstance(OSystem *syst, Engine **engine, const Myst3GameDescription *desc) const override;
// TODO: Add getSavegameFile()
};
Common::Error Myst3MetaEngine::createInstance(OSystem *syst, Engine **engine, const Myst3GameDescription *desc) const {
*engine = new Myst3Engine(syst,desc);
return Common::kNoError;
}
Common::Platform Myst3Engine::getPlatform() const {
return _gameDescription->desc.platform;
}
Common::Language Myst3Engine::getGameLanguage() const {
return _gameDescription->desc.language;
}
uint32 Myst3Engine::getGameLocalizationType() const {
return _gameDescription->flags & kGameLocalizationTypeMask;
}
uint32 Myst3Engine::getGameLayoutType() const {
return _gameDescription->flags & kGameLayoutTypeMask;
}
} // End of namespace Myst3
#if PLUGIN_ENABLED_DYNAMIC(MYST3)
REGISTER_PLUGIN_DYNAMIC(MYST3, PLUGIN_TYPE_ENGINE, Myst3::Myst3MetaEngine);
#else
REGISTER_PLUGIN_STATIC(MYST3, PLUGIN_TYPE_ENGINE, Myst3::Myst3MetaEngine);
#endif

46
engines/myst3/module.mk Normal file
View File

@@ -0,0 +1,46 @@
MODULE := engines/myst3
MODULE_OBJS := \
ambient.o \
archive.o \
console.o \
cursor.o \
database.o \
effects.o \
gfx.o \
gfx_opengl.o \
gfx_opengl_shaders.o \
gfx_opengl_texture.o \
hotspot.o \
inventory.o \
menu.o \
metaengine.o \
movie.o \
myst3.o \
node.o \
nodecube.o \
nodeframe.o \
puzzles.o \
scene.o \
script.o \
sound.o \
state.o \
subtitles.o \
transition.o
ifdef USE_TINYGL
MODULE_OBJS += \
gfx_tinygl.o \
gfx_tinygl_texture.o
endif
# This module can be built as a plugin
ifeq ($(ENABLE_MYST3), DYNAMIC_PLUGIN)
PLUGIN := 1
endif
# Include common rules
include $(srcdir)/rules.mk
# Detection objects
DETECT_OBJS += $(MODULE)/detection.o

584
engines/myst3/movie.cpp Normal file
View File

@@ -0,0 +1,584 @@
/* 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/movie.h"
#include "engines/myst3/ambient.h"
#include "engines/myst3/myst3.h"
#include "engines/myst3/sound.h"
#include "engines/myst3/state.h"
#include "engines/myst3/subtitles.h"
#include "common/config-manager.h"
namespace Myst3 {
Movie::Movie(Myst3Engine *vm, uint16 id) :
_vm(vm),
_id(id),
_posU(0),
_posV(0),
_startFrame(0),
_endFrame(0),
_texture(nullptr),
_force2d(false),
_forceOpaque(false),
_subtitles(nullptr),
_volume(0),
_additiveBlending(false),
_transparency(100) {
ResourceDescription binkDesc = _vm->getFileDescription("", id, 0, Archive::kMultitrackMovie);
if (!binkDesc.isValid())
binkDesc = _vm->getFileDescription("", id, 0, Archive::kDialogMovie);
if (!binkDesc.isValid())
binkDesc = _vm->getFileDescription("", id, 0, Archive::kStillMovie);
if (!binkDesc.isValid())
binkDesc = _vm->getFileDescription("", id, 0, Archive::kMovie);
// Check whether the video is optional
bool optional = false;
if (_vm->_state->hasVarMovieOptional()) {
optional = _vm->_state->getMovieOptional();
_vm->_state->setMovieOptional(0);
}
if (!binkDesc.isValid()) {
if (!optional)
error("Movie %d does not exist", id);
else
return;
}
loadPosition(binkDesc.getVideoData());
Common::SeekableReadStream *binkStream = binkDesc.getData();
_bink.setSoundType(Audio::Mixer::kSFXSoundType);
_bink.loadStream(binkStream);
_bink.setOutputPixelFormat(Texture::getRGBAPixelFormat());
if (binkDesc.getType() == Archive::kMultitrackMovie || binkDesc.getType() == Archive::kDialogMovie) {
uint language = ConfMan.getInt("audio_language");
_bink.setAudioTrack(language);
}
if (ConfMan.getBool("subtitles"))
_subtitles = Subtitles::create(_vm, id);
// Clear the subtitles override anyway, so that it does not end up
// being used by the another movie at some point.
_vm->_state->setMovieOverrideSubtitles(0);
}
void Movie::loadPosition(const ResourceDescription::VideoData &videoData) {
static const float scale = 50.0f;
_is3D = _vm->_state->getViewType() == kCube;
assert(!_texture);
Math::Vector3d planeDirection = videoData.v1;
planeDirection.normalize();
Math::Vector3d u;
u.set(planeDirection.z(), 0.0f, -planeDirection.x());
u.normalize();
Math::Vector3d v = Math::Vector3d::crossProduct(planeDirection, u);
v.normalize();
Math::Vector3d planeOrigin = planeDirection * scale;
float left = (videoData.u - 320) * 0.003125f;
float right = (videoData.u + videoData.width - 320) * 0.003125f;
float top = (320 - videoData.v) * 0.003125f;
float bottom = (320 - videoData.v - videoData.height) * 0.003125f;
Math::Vector3d vLeft = scale * left * u;
Math::Vector3d vRight = scale * right * u;
Math::Vector3d vTop = scale * top * v;
Math::Vector3d vBottom = scale * bottom * v;
_pTopLeft = planeOrigin + vTop + vLeft;
_pBottomLeft = planeOrigin + vBottom + vLeft;
_pBottomRight = planeOrigin + vBottom + vRight;
_pTopRight = planeOrigin + vTop + vRight;
_posU = videoData.u;
_posV = videoData.v;
}
void Movie::draw2d() {
Common::Rect screenRect = Common::Rect(_bink.getWidth(), _bink.getHeight());
screenRect.translate(_posU, _posV);
Common::Rect textureRect = Common::Rect(_bink.getWidth(), _bink.getHeight());
if (_forceOpaque)
_vm->_gfx->drawTexturedRect2D(screenRect, textureRect, _texture);
else
_vm->_gfx->drawTexturedRect2D(screenRect, textureRect, _texture, (float) _transparency / 100, _additiveBlending);
}
void Movie::draw3d() {
_vm->_gfx->drawTexturedRect3D(_pTopLeft, _pBottomLeft, _pTopRight, _pBottomRight, _texture);
}
void Movie::draw() {
if (_force2d)
return;
if (_is3D) {
draw3d();
} else {
draw2d();
}
}
void Movie::drawOverlay() {
if (_force2d)
draw2d();
if (_subtitles) {
_subtitles->setFrame(adjustFrameForRate(_bink.getCurFrame(), false));
_vm->_gfx->renderWindowOverlay(_subtitles);
}
}
void Movie::drawNextFrameToTexture() {
const Graphics::Surface *frame = _bink.decodeNextFrame();
if (frame) {
if (_texture)
_texture->update(frame);
else if (_is3D)
_texture = _vm->_gfx->createTexture3D(frame);
else
_texture = _vm->_gfx->createTexture2D(frame);
}
}
int32 Movie::adjustFrameForRate(int32 frame, bool dataToBink) {
// The scripts give frame numbers for a framerate of 15 im/s
// adjust the frame number according to the actual framerate
if (_bink.getFrameRate().toInt() != 15) {
Common::Rational rational;
if (dataToBink) {
rational = _bink.getFrameRate() * frame / 15;
} else {
rational = 15 * frame / _bink.getFrameRate();
}
frame = rational.toInt();
}
return frame;
}
void Movie::setStartFrame(int32 v) {
_startFrame = adjustFrameForRate(v, true);
}
void Movie::setEndFrame(int32 v) {
_endFrame = adjustFrameForRate(v, true);
}
void Movie::pause(bool p) {
_bink.pauseVideo(p);
}
Movie::~Movie() {
if (_texture)
delete _texture;
delete _subtitles;
}
void Movie::setForce2d(bool b) {
_force2d = b;
if (_force2d) {
if (_is3D)
delete _texture;
_is3D = false;
}
}
ScriptedMovie::ScriptedMovie(Myst3Engine *vm, uint16 id) :
Movie(vm, id),
_condition(0),
_conditionBit(0),
_startFrameVar(0),
_endFrameVar(0),
_posUVar(0),
_posVVar(0),
_nextFrameReadVar(0),
_nextFrameWriteVar(0),
_playingVar(0),
_enabled(false),
_disableWhenComplete(false),
_scriptDriven(false),
_isLastFrame(false),
_soundHeading(0),
_soundAttenuation(0),
_volumeVar(0),
_loop(false),
_transparencyVar(0) {
_bink.start();
}
void ScriptedMovie::draw() {
if (!_enabled)
return;
Movie::draw();
}
void ScriptedMovie::drawOverlay() {
if (!_enabled)
return;
Movie::drawOverlay();
}
void ScriptedMovie::update() {
if (_startFrameVar) {
_startFrame = _vm->_state->getVar(_startFrameVar);
}
if (_endFrameVar) {
_endFrame = _vm->_state->getVar(_endFrameVar);
}
if (!_endFrame) {
_endFrame = _bink.getFrameCount();
}
if (_posUVar) {
_posU = _vm->_state->getVar(_posUVar);
}
if (_posVVar) {
_posV = _vm->_state->getVar(_posVVar);
}
if (_transparencyVar) {
_transparency = _vm->_state->getVar(_transparencyVar);
}
bool newEnabled;
if (_conditionBit) {
newEnabled = (_vm->_state->getVar(_condition) & (1 << (_conditionBit - 1))) != 0;
} else {
newEnabled = _vm->_state->evaluate(_condition);
}
if (newEnabled != _enabled) {
_enabled = newEnabled;
if (newEnabled) {
if (_disableWhenComplete
|| _bink.getCurFrame() < _startFrame
|| _bink.getCurFrame() >= _endFrame
|| _bink.endOfVideo()) {
_bink.seekToFrame(_startFrame);
_isLastFrame = false;
}
if (!_scriptDriven)
_bink.pauseVideo(false);
drawNextFrameToTexture();
} else {
// Make sure not to pause the video twice. VideoDecoder handles pause levels.
// The video may have already been paused if _disableWhenComplete is set.
if (!_bink.isPaused()) {
_bink.pauseVideo(true);
}
}
}
if (_enabled) {
updateVolume();
if (_nextFrameReadVar) {
int32 nextFrame = _vm->_state->getVar(_nextFrameReadVar);
if (nextFrame > 0 && nextFrame <= (int32)_bink.getFrameCount()) {
// Are we changing frame?
if (_bink.getCurFrame() != nextFrame - 1) {
// Don't seek if we just want to display the next frame
if (_bink.getCurFrame() + 1 != nextFrame - 1) {
_bink.seekToFrame(nextFrame - 1);
}
drawNextFrameToTexture();
}
_vm->_state->setVar(_nextFrameReadVar, 0);
_isLastFrame = false;
}
}
if (!_scriptDriven && (_bink.needsUpdate() || _isLastFrame)) {
bool complete = false;
if (_isLastFrame) {
_isLastFrame = false;
if (_loop) {
_bink.seekToFrame(_startFrame);
drawNextFrameToTexture();
} else {
complete = true;
}
} else {
drawNextFrameToTexture();
_isLastFrame = _bink.getCurFrame() == (_endFrame - 1);
}
if (_nextFrameWriteVar) {
_vm->_state->setVar(_nextFrameWriteVar, _bink.getCurFrame() + 1);
}
if (_disableWhenComplete && complete) {
_bink.pauseVideo(true);
if (_playingVar) {
_vm->_state->setVar(_playingVar, 0);
} else {
_enabled = 0;
_vm->_state->setVar(_condition & 0x7FF, 0);
}
}
}
}
}
void ScriptedMovie::updateVolume() {
int32 volume;
if (_volumeVar) {
volume = _vm->_state->getVar(_volumeVar);
} else {
volume = _volume;
}
int32 mixerVolume, balance;
_vm->_sound->computeVolumeBalance(volume, _soundHeading, _soundAttenuation, &mixerVolume, &balance);
_bink.setVolume(mixerVolume);
_bink.setBalance(balance);
}
ScriptedMovie::~ScriptedMovie() {
}
SimpleMovie::SimpleMovie(Myst3Engine *vm, uint16 id) :
Movie(vm, id),
_synchronized(false) {
_startFrame = 1;
_endFrame = _bink.getFrameCount();
_startEngineTick = _vm->_state->getTickCount();
}
void SimpleMovie::play() {
playStartupSound();
_bink.setEndFrame(_endFrame - 1);
_bink.setVolume(_volume * Audio::Mixer::kMaxChannelVolume / 100);
if (_bink.getCurFrame() < _startFrame - 1) {
_bink.seekToFrame(_startFrame - 1);
}
_bink.start();
}
void SimpleMovie::update() {
uint16 scriptStartFrame = _vm->_state->getMovieScriptStartFrame();
if (scriptStartFrame && _bink.getCurFrame() > scriptStartFrame) {
uint16 script = _vm->_state->getMovieScript();
// The control variables are reset before running the script because
// some scripts set up another movie triggered script
_vm->_state->setMovieScriptStartFrame(0);
_vm->_state->setMovieScript(0);
_vm->runScriptsFromNode(script);
}
uint16 ambiantStartFrame = _vm->_state->getMovieAmbiantScriptStartFrame();
if (ambiantStartFrame && _bink.getCurFrame() > ambiantStartFrame) {
// Fixes bug #16491 for missing ambient sounds while a simpleMovie is playing
// eg. the burning fire after Saavedro throws the lamp at the curtain in Tomahna (during the intro at Atrus's office)
// and the screaming squee after trapping it in Edanna.
// This fix is essentially replicating the code from ambientReloadCurrentNode() script command.
// In these movies (SimpleMovie), a frame number (ambiantStartFrame) is set (before they start)
// upon which the ambient sounds scripts should be re-evaluated,
// because a condition and related variables to that condition will have changed
// and therefore different ambient sounds should be played.
//
// NOTE: The value set for var MovieAmbiantScript (181) is not a node id -- so calling runAmbientScripts() here with it as argument does not work.
// Instead, we just reload the current node's ambient scripts and then applySounds using the value of MovieAmbiantScript as fadeOutDelay.
_vm->_ambient->loadNode(0, 0, 0);
_vm->_ambient->applySounds(_vm->_state->valueOrVarValue(_vm->_state->getMovieAmbiantScript()));
_vm->_state->setMovieAmbiantScriptStartFrame(0);
_vm->_state->setMovieAmbiantScript(0);
}
if (!_synchronized) {
// Play the movie according to the bink file framerate
if (_bink.needsUpdate()) {
drawNextFrameToTexture();
}
} else {
// Draw a movie frame each two engine frames
int targetFrame = (_vm->_state->getTickCount() - _startEngineTick) >> 1;
if (_bink.getCurFrame() < targetFrame) {
drawNextFrameToTexture();
}
}
}
bool SimpleMovie::endOfVideo() {
if (!_synchronized) {
return _bink.getTime() >= (uint)_bink.getEndTime().msecs();
} else {
int tickBasedEndFrame = (_vm->_state->getTickCount() - _startEngineTick) >> 1;
return tickBasedEndFrame >= _endFrame;
}
}
void SimpleMovie::playStartupSound() {
int32 soundId = _vm->_state->getMovieStartSoundId();
if (soundId) {
uint32 volume = _vm->_state->getMovieStartSoundVolume();
uint32 heading = _vm->_state->getMovieStartSoundHeading();
uint32 attenuation = _vm->_state->getMovieStartSoundAttenuation();
_vm->_sound->playEffect(soundId, volume, heading, attenuation);
_vm->_state->setMovieStartSoundId(0);
}
}
void SimpleMovie::refreshAmbientSounds() {
uint32 engineFrames = _bink.getFrameCount() * 2;
_vm->_ambient->playCurrentNode(100, engineFrames);
}
SimpleMovie::~SimpleMovie() {
}
ProjectorMovie::ProjectorMovie(Myst3Engine *vm, uint16 id, Graphics::Surface *background) :
ScriptedMovie(vm, id),
_background(background),
_frame(nullptr) {
_enabled = true;
for (uint i = 0; i < kBlurIterations; i++) {
_blurTableX[i] = (uint8)(sin(2 * (float)M_PI * i / (float)kBlurIterations) * 256.0);
_blurTableY[i] = (uint8)(cos(2 * (float)M_PI * i / (float)kBlurIterations) * 256.0);
}
}
ProjectorMovie::~ProjectorMovie() {
if (_frame) {
_frame->free();
delete _frame;
}
if (_background) {
_background->free();
delete _background;
}
}
void ProjectorMovie::update() {
if (!_frame) {
// First call, get the alpha channel from the bink file
const Graphics::Surface *frame = _bink.decodeNextFrame();
_frame = new Graphics::Surface();
_frame->copyFrom(*frame);
}
uint16 focus = _vm->_state->getProjectorBlur() / 10;
uint16 zoom = _vm->_state->getProjectorZoom();
uint16 backgroundX = (_vm->_state->getProjectorX() - zoom / 2) / 10;
uint16 backgroundY = (_vm->_state->getProjectorY() - zoom / 2) / 10;
float delta = zoom / 10.0 / _frame->w;
// For each pixel in the target image
for (int i = 0; i < _frame->h; i++) {
byte *dst = (byte *)_frame->getBasePtr(0, i);
for (int j = 0; j < _frame->w; j++) {
uint8 depth;
uint16 r = 0, g = 0, b = 0;
uint32 srcX = (uint32)(backgroundX + j * delta);
uint32 srcY = (uint32)(backgroundY + i * delta);
byte *src = (byte *)_background->getBasePtr(srcX, srcY);
// Get the depth from the background
depth = *(src + 3);
// Compute the blur level from the focus point and the depth of the current point
uint8 blurLevel = abs(focus - depth) + 1;
// No need to compute the effect for transparent pixels
byte a = *(dst + 3);
if (a != 0) {
// The blur effect is done by mixing the color components from the pixel at (srcX, srcY)
// and other pixels on the same row / column
uint cnt = 0;
for (uint k = 0; k < kBlurIterations; k++) {
uint32 blurX = srcX + ((uint32) (blurLevel * _blurTableX[k] * delta) >> 12); // >> 12 = / 256 / 16
uint32 blurY = srcY + ((uint32) (blurLevel * _blurTableY[k] * delta) >> 12);
if (blurX < 1024 && blurY < 1024) {
byte *blur = (byte *)_background->getBasePtr(blurX, blurY);
r += *blur++;
g += *blur++;
b += *blur;
cnt++;
}
}
// Divide the components by the number of pixels used in the blur effect
r /= cnt;
g /= cnt;
b /= cnt;
}
// Draw the new pixel
*dst++ = r;
*dst++ = g;
*dst++ = b;
dst++; // Keep the alpha channel from the previous frame
}
}
if (_texture)
_texture->update(_frame);
else if (_is3D)
_texture = _vm->_gfx->createTexture3D(_frame);
else
_texture = _vm->_gfx->createTexture2D(_frame);
}
} // End of namespace Myst3

189
engines/myst3/movie.h Normal file
View File

@@ -0,0 +1,189 @@
/* 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/>.
*
*/
#ifndef MOVIE_H_
#define MOVIE_H_
#include "engines/myst3/gfx.h"
#include "engines/myst3/node.h"
#include "math/vector3d.h"
#include "video/bink_decoder.h"
namespace Myst3 {
class Myst3Engine;
class Texture;
class Subtitles;
class Movie : public Drawable {
public:
Movie(Myst3Engine *vm, uint16 id);
virtual ~Movie();
void draw() override;
void drawOverlay() override;
/** Increase or decrease the movie's pause level by one */
void pause(bool pause);
uint16 getId() { return _id; }
bool isVideoLoaded() {return _bink.isVideoLoaded(); }
void setPosU(int32 v) { _posU = v; }
void setPosV(int32 v) { _posV = v; }
void setForce2d(bool b);
void setForceOpaque(bool b) { _forceOpaque = b; }
void setStartFrame(int32 v);
void setEndFrame(int32 v);
void setVolume(int32 v) { _volume = v; }
protected:
Myst3Engine *_vm;
uint16 _id;
Subtitles *_subtitles;
Math::Vector3d _pTopLeft;
Math::Vector3d _pBottomLeft;
Math::Vector3d _pBottomRight;
Math::Vector3d _pTopRight;
bool _force2d;
bool _forceOpaque;
int32 _posU;
int32 _posV;
Video::BinkDecoder _bink;
Texture *_texture;
int32 _startFrame;
int32 _endFrame;
int32 _volume;
bool _additiveBlending;
int32 _transparency;
int32 adjustFrameForRate(int32 frame, bool dataToBink);
void loadPosition(const ResourceDescription::VideoData &videoData);
void drawNextFrameToTexture();
void draw2d();
void draw3d();
};
class ScriptedMovie : public Movie {
public:
ScriptedMovie(Myst3Engine *vm, uint16 id);
virtual ~ScriptedMovie();
void draw() override;
void drawOverlay() override;
virtual void update();
void setEndFrameVar(uint16 v) { _endFrameVar = v; }
void setNextFrameReadVar(uint16 v) { _nextFrameReadVar = v; }
void setNextFrameWriteVar(uint16 v) { _nextFrameWriteVar = v; }
void setPlayingVar(uint16 v) { _playingVar = v; }
void setPosUVar(uint16 v) { _posUVar = v; }
void setPosVVar(uint16 v) { _posVVar = v; }
void setVolumeVar(uint16 v) { _volumeVar = v; }
void setStartFrameVar(uint16 v) { _startFrameVar = v; }
void setCondition(int16 condition) { _condition = condition; }
void setConditionBit(int16 cb) { _conditionBit = cb; }
void setDisableWhenComplete(bool upd) { _disableWhenComplete = upd; }
void setLoop(bool loop) { _loop = loop; }
void setScriptDriven(bool b) { _scriptDriven = b; }
void setSoundHeading(uint16 v) { _soundHeading = v; }
void setSoundAttenuation(uint16 v) { _soundAttenuation = v; }
void setAdditiveBlending(bool b) { _additiveBlending = b; }
void setTransparency(int32 v) { _transparency = v; }
void setTransparencyVar(uint16 v) { _transparencyVar = v; }
protected:
bool _enabled;
bool _loop;
bool _disableWhenComplete;
bool _scriptDriven;
bool _isLastFrame;
int16 _condition;
uint16 _conditionBit;
uint16 _startFrameVar;
uint16 _endFrameVar;
uint16 _posUVar;
uint16 _posVVar;
uint16 _volumeVar;
uint32 _soundHeading;
uint32 _soundAttenuation;
uint16 _nextFrameReadVar;
uint16 _nextFrameWriteVar;
uint16 _playingVar;
uint16 _transparencyVar;
void updateVolume();
};
class SimpleMovie : public Movie {
public:
SimpleMovie(Myst3Engine *vm, uint16 id);
virtual ~SimpleMovie();
void update();
bool endOfVideo();
void playStartupSound();
void refreshAmbientSounds();
void setSynchronized(bool b) { _synchronized = b; }
void play();
private:
bool _synchronized;
uint _startEngineTick;
};
// Used by the projectors on J'nanin, see puzzle #14
class ProjectorMovie : public ScriptedMovie {
public:
ProjectorMovie(Myst3Engine *vm, uint16 id, Graphics::Surface *background);
virtual ~ProjectorMovie();
void update();
private:
Graphics::Surface *_background;
Graphics::Surface *_frame;
static const uint kBlurIterations = 30;
uint8 _blurTableX[kBlurIterations];
uint8 _blurTableY[kBlurIterations];
};
} // End of namespace Myst3
#endif // MOVIE_H_

2019
engines/myst3/myst3.cpp Normal file

File diff suppressed because it is too large Load Diff

255
engines/myst3/myst3.h Normal file
View File

@@ -0,0 +1,255 @@
/* 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/>.
*
*/
#ifndef MYST3_ENGINE_H
#define MYST3_ENGINE_H
#include "engines/engine.h"
#include "common/array.h"
#include "common/ptr.h"
#include "common/system.h"
#include "common/random.h"
#include "engines/myst3/archive.h"
namespace Graphics {
struct Surface;
class FrameLimiter;
}
namespace Common {
struct Event;
}
namespace Myst3 {
// Engine Debug Flags
enum {
kDebugVariable = 1,
kDebugSaveLoad,
kDebugNode,
kDebugScript,
};
enum TransitionType {
kTransitionFade = 1,
kTransitionNone,
kTransitionZip,
kTransitionLeftToRight,
kTransitionRightToLeft
};
class Archive;
class Console;
class Drawable;
class GameState;
class HotSpot;
class Cursor;
class Inventory;
class Database;
class Scene;
class Script;
class SpotItemFace;
class SunSpot;
class Renderer;
class Menu;
class Node;
class Sound;
class Ambient;
class ScriptedMovie;
class ShakeEffect;
class RotationEffect;
class Transition;
struct NodeData;
struct Myst3GameDescription;
typedef Common::SharedPtr<NodeData> NodePtr;
class Myst3Engine : public Engine {
protected:
// Engine APIs
Common::Error run() override;
void syncSoundSettings() override;
void pauseEngineIntern(bool pause) override;
public:
GameState *_state;
Scene *_scene;
Cursor *_cursor;
Inventory *_inventory;
Renderer *_gfx;
Menu *_menu;
Database *_db;
Sound *_sound;
Ambient *_ambient;
Common::RandomSource *_rnd;
// Used by the projectors on J'nanin, see puzzle #14
Graphics::Surface *_projectorBackground;
Myst3Engine(OSystem *syst, const Myst3GameDescription *version);
virtual ~Myst3Engine();
bool hasFeature(EngineFeature f) const override;
Common::Platform getPlatform() const;
Common::Language getGameLanguage() const;
uint32 getGameLocalizationType() const;
uint32 getGameLayoutType() const;
bool isTextLanguageEnglish() const;
bool isWideScreenModEnabled() const;
bool canSaveGameStateCurrently(Common::U32String *msg = nullptr) override;
bool canLoadGameStateCurrently(Common::U32String *msg = nullptr) override;
Common::Error loadGameState(int slot) override;
Common::Error loadGameState(Common::String fileName, TransitionType transition);
Common::Error saveGameState(int slot, const Common::String &desc, bool isAutosave = false) override;
Common::Error saveGameState(const Common::String &desc, const Graphics::Surface *thumbnail, bool isAutosave);
ResourceDescription getFileDescription(const Common::String &room, uint32 index, uint16 face,
Archive::ResourceType type);
ResourceDescriptionArray listFilesMatching(const Common::String &room, uint32 index, uint16 face,
Archive::ResourceType type);
Graphics::Surface *loadTexture(uint16 id);
static Graphics::Surface *decodeJpeg(const ResourceDescription *jpegDesc);
void goToNode(uint16 nodeID, TransitionType transition);
void loadNode(uint16 nodeID, uint32 roomID = 0, uint32 ageID = 0);
void unloadNode();
void loadNodeCubeFaces(uint16 nodeID);
void loadNodeFrame(uint16 nodeID);
void loadNodeMenu(uint16 nodeID);
void setupTransition();
void drawTransition(TransitionType transitionType);
void dragItem(uint16 statusVar, uint16 movie, uint16 frame, uint16 hoverFrame, uint16 itemVar);
void dragSymbol(uint16 var, uint16 id);
int16 openDialog(uint16 id);
void runNodeInitScripts();
void runNodeBackgroundScripts();
void runScriptsFromNode(uint16 nodeID, uint32 roomID = 0, uint32 ageID = 0);
void runBackgroundSoundScriptsFromNode(uint16 nodeID, uint32 roomID = 0, uint32 ageID = 0);
void runAmbientScripts(uint32 node);
void loadMovie(uint16 id, uint16 condition, bool resetCond, bool loop);
void playMovieGoToNode(uint16 movie, uint16 node);
void playMovieFullFrame(uint16 movie);
void playSimpleMovie(uint16 id, bool fullframe = false, bool refreshAmbientSounds = false);
void removeMovie(uint16 id);
void setMovieLooping(uint16 id, bool loop);
void addSpotItem(uint16 id, int16 condition, bool fade);
SpotItemFace *addMenuSpotItem(uint16 id, int16 condition, const Common::Rect &rect);
void loadNodeSubtitles(uint32 id);
void addSunSpot(uint16 pitch, uint16 heading, uint16 intensity,
uint16 color, uint16 var, bool varControlledIntensity, uint16 radius);
SunSpot computeSunspotsIntensity(float pitch, float heading);
void setMenuAction(uint16 action) { _menuAction = action; }
void animateDirectionChange(float pitch, float heading, uint16 scriptTicks);
void getMovieLookAt(uint16 id, bool start, float &pitch, float &heading);
void drawFrame(bool noSwap = false);
void processInput(bool interactive);
void processEventForKeyboardState(const Common::Event &event);
void processEventForGamepad(const Common::Event &event);
void updateInputState();
bool inputValidatePressed();
bool inputEscapePressed();
bool inputSpacePressed();
bool inputTilePressed();
void settingsInitDefaults();
void settingsLoadToVars();
void settingsApplyFromVars();
private:
OSystem *_system;
const Myst3GameDescription *_gameDescription;
Node *_node;
Common::Array<Archive *> _archivesCommon;
Archive *_archiveNode;
Script *_scriptEngine;
Common::Array<ScriptedMovie *> _movies;
Common::Array<SunSpot *> _sunspots;
Common::Array<Drawable *> _drawables;
uint16 _menuAction;
// Used by Amateria's magnetic rings
ShakeEffect *_shakeEffect;
// Used by Voltaic's spinning gears
RotationEffect *_rotationEffect;
Graphics::FrameLimiter *_frameLimiter;
Transition *_transition;
bool _inputSpacePressed;
bool _inputEnterPressed;
bool _inputEscapePressed;
bool _inputEscapePressedNotConsumed;
bool _inputTildePressed;
bool _interactive;
uint32 _backgroundSoundScriptLastRoomId;
uint32 _backgroundSoundScriptLastAgeId;
/**
* When the widescreen mode is active, the user can manually hide
* the inventory by clicking on an unused inventory space.
* This allows interacting with the scene portion that is below
* the inventory.
*/
bool _inventoryManualHide;
HotSpot *getHoveredHotspot(NodePtr nodeData, uint16 var = 0);
void updateCursor();
bool checkDatafiles();
bool addArchive(const Common::String &file, bool mandatory);
void openArchives();
void closeArchives();
bool isInventoryVisible();
void interactWithHoveredElement();
friend class Console;
};
} // end of namespace Myst3
#endif

472
engines/myst3/node.cpp Normal file
View File

@@ -0,0 +1,472 @@
/* 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 "engines/myst3/effects.h"
#include "engines/myst3/node.h"
#include "engines/myst3/myst3.h"
#include "engines/myst3/state.h"
#include "engines/myst3/subtitles.h"
#include "common/config-manager.h"
#include "common/debug.h"
#include "common/rect.h"
namespace Myst3 {
void Face::setTextureFromJPEG(const ResourceDescription *jpegDesc) {
_bitmap = Myst3Engine::decodeJpeg(jpegDesc);
if (_is3D) {
_texture = _vm->_gfx->createTexture3D(_bitmap);
} else {
_texture = _vm->_gfx->createTexture2D(_bitmap);
}
// Set the whole texture as dirty
addTextureDirtyRect(Common::Rect(_bitmap->w, _bitmap->h));
}
Face::Face(Myst3Engine *vm, bool is3D) :
_vm(vm),
_is3D(is3D),
_textureDirty(true),
_texture(nullptr),
_bitmap(nullptr),
_finalBitmap(nullptr) {
}
void Face::addTextureDirtyRect(const Common::Rect &rect) {
if (!_textureDirty) {
_textureDirtyRect = rect;
} else {
_textureDirtyRect.extend(rect);
}
_textureDirty = true;
}
void Face::uploadTexture() {
if (_textureDirty) {
if (_finalBitmap)
_texture->updatePartial(_finalBitmap, _textureDirtyRect);
else
_texture->updatePartial(_bitmap, _textureDirtyRect);
_textureDirty = false;
}
}
Face::~Face() {
_bitmap->free();
delete _bitmap;
_bitmap = nullptr;
if (_finalBitmap) {
_finalBitmap->free();
delete _finalBitmap;
}
if (_texture) {
delete _texture;
}
}
Node::Node(Myst3Engine *vm, uint16 id) :
_vm(vm),
_id(id),
_subtitles(nullptr) {
for (uint i = 0; i < ARRAYSIZE(_faces); i++)
_faces[i] = nullptr;
}
void Node::initEffects() {
resetEffects();
if (_vm->_state->getViewType() == kMenu) {
// The node init script does not clear the magnet effect state.
// Here we ignore effects on menu nodes so we don't try to
// to load the magnet effect when opening the main menu on Amateria.
return;
}
if (_vm->_state->getWaterEffects()) {
Effect *effect = WaterEffect::create(_vm, _id);
if (effect) {
_effects.push_back(effect);
_vm->_state->setWaterEffectActive(true);
}
}
Effect *effect = MagnetEffect::create(_vm, _id);
if (effect) {
_effects.push_back(effect);
_vm->_state->setMagnetEffectActive(true);
}
effect = LavaEffect::create(_vm, _id);
if (effect) {
_effects.push_back(effect);
_vm->_state->setLavaEffectActive(true);
}
effect = ShieldEffect::create(_vm, _id);
if (effect) {
_effects.push_back(effect);
_vm->_state->setShieldEffectActive(true);
}
}
void Node::resetEffects() {
for (uint i = 0; i < _effects.size(); i++) {
delete _effects[i];
}
_effects.clear();
}
Node::~Node() {
for (uint i = 0; i < _spotItems.size(); i++) {
delete _spotItems[i];
}
_spotItems.clear();
resetEffects();
_vm->_state->setWaterEffectActive(false);
_vm->_state->setMagnetEffectActive(false);
_vm->_state->setLavaEffectActive(false);
_vm->_state->setShieldEffectActive(false);
for (int i = 0; i < 6; i++) {
delete _faces[i];
}
delete _subtitles;
}
void Node::loadSpotItem(uint16 id, int16 condition, bool fade) {
SpotItem *spotItem = new SpotItem(_vm);
spotItem->setCondition(condition);
spotItem->setFade(fade);
spotItem->setFadeVar(abs(condition));
for (int i = 0; i < 6; i++) {
ResourceDescriptionArray spotItemImages = _vm->listFilesMatching("", id, i + 1, Archive::kLocalizedSpotItem);
if (spotItemImages.empty())
spotItemImages = _vm->listFilesMatching("", id, i + 1, Archive::kSpotItem);
for (uint j = 0; j < spotItemImages.size(); j++) {
const ResourceDescription &image = spotItemImages[j];
ResourceDescription::SpotItemData spotItemData = image.getSpotItemData();
SpotItemFace *spotItemFace = new SpotItemFace(_faces[i], spotItemData.u, spotItemData.v);
spotItemFace->loadData(&image);
// SpotItems with an always true conditions cannot be undrawn.
// Draw them now to make sure the "non drawn backups" for other, potentially
// overlapping SpotItems have them drawn.
if (condition == 1) {
spotItemFace->draw();
}
spotItem->addFace(spotItemFace);
}
}
_spotItems.push_back(spotItem);
}
SpotItemFace *Node::loadMenuSpotItem(int16 condition, const Common::Rect &rect) {
SpotItem *spotItem = new SpotItem(_vm);
spotItem->setCondition(condition);
spotItem->setFade(false);
spotItem->setFadeVar(abs(condition));
SpotItemFace *spotItemFace = new SpotItemFace(_faces[0], rect.left, rect.top);
spotItemFace->initBlack(rect.width(), rect.height());
spotItem->addFace(spotItemFace);
_spotItems.push_back(spotItem);
return spotItemFace;
}
void Node::loadSubtitles(uint32 id) {
_subtitles = Subtitles::create(_vm, id);
}
bool Node::hasSubtitlesToDraw() {
if (!_subtitles || _vm->_state->getSpotSubtitle() <= 0)
return false;
if (!_vm->isTextLanguageEnglish() && _vm->_state->getLocationRoom() == kRoomNarayan) {
// The words written on the walls in Narayan are always in English.
// Show the subtitles regardless of the "subtitles" setting if the game language is not English.
return true;
}
return ConfMan.getBool("subtitles");
}
void Node::drawOverlay() {
if (hasSubtitlesToDraw()) {
uint subId = _vm->_state->getSpotSubtitle();
_subtitles->setFrame(15 * subId + 1);
_vm->_gfx->renderWindowOverlay(_subtitles);
}
}
void Node::update() {
// First undraw ...
for (uint i = 0; i < _spotItems.size(); i++) {
_spotItems[i]->updateUndraw();
}
// ... then redraw
for (uint i = 0; i < _spotItems.size(); i++) {
_spotItems[i]->updateDraw();
}
bool needsUpdate = false;
for (uint i = 0; i < _effects.size(); i++) {
needsUpdate |= _effects[i]->update();
}
// Apply the effects for all the faces
for (uint faceId = 0; faceId < 6; faceId++) {
Face *face = _faces[faceId];
if (face == nullptr)
continue; // No such face in this node
if (!isFaceVisible(faceId)) {
continue; // This face is not currently visible
}
uint effectsForFace = 0;
for (uint i = 0; i < _effects.size(); i++) {
if (_effects[i]->hasFace(faceId))
effectsForFace++;
}
if (effectsForFace == 0)
continue;
if (!needsUpdate && !face->isTextureDirty())
continue;
// Alloc the target surface if necessary
if (!face->_finalBitmap) {
face->_finalBitmap = new Graphics::Surface();
}
face->_finalBitmap->copyFrom(*face->_bitmap);
if (effectsForFace == 1) {
_effects[0]->applyForFace(faceId, face->_bitmap, face->_finalBitmap);
face->addTextureDirtyRect(_effects[0]->getUpdateRectForFace(faceId));
} else if (effectsForFace == 2) {
// TODO: Keep the same temp surface to avoid heap fragmentation ?
Graphics::Surface *tmp = new Graphics::Surface();
tmp->copyFrom(*face->_bitmap);
_effects[0]->applyForFace(faceId, face->_bitmap, tmp);
_effects[1]->applyForFace(faceId, tmp, face->_finalBitmap);
tmp->free();
delete tmp;
face->addTextureDirtyRect(_effects[0]->getUpdateRectForFace(faceId));
face->addTextureDirtyRect(_effects[1]->getUpdateRectForFace(faceId));
} else {
error("Unable to render more than 2 effects per faceId (%d)", effectsForFace);
}
}
}
SpotItem::SpotItem(Myst3Engine *vm) :
_vm(vm) {
}
SpotItem::~SpotItem() {
for (uint i = 0; i < _faces.size(); i++) {
delete _faces[i];
}
}
void SpotItem::updateUndraw() {
for (uint i = 0; i < _faces.size(); i++) {
if (!_vm->_state->evaluate(_condition) && _faces[i]->isDrawn()) {
_faces[i]->undraw();
}
}
}
void SpotItem::updateDraw() {
for (uint i = 0; i < _faces.size(); i++) {
if (_enableFade) {
uint16 newFadeValue = _vm->_state->getVar(_fadeVar);
if (_faces[i]->getFadeValue() != newFadeValue) {
_faces[i]->setFadeValue(newFadeValue);
_faces[i]->setDrawn(false);
}
}
if (_vm->_state->evaluate(_condition) && !_faces[i]->isDrawn()) {
if (_enableFade)
_faces[i]->fadeDraw();
else
_faces[i]->draw();
}
}
}
SpotItemFace::SpotItemFace(Face *face, uint16 posX, uint16 posY):
_face(face),
_posX(posX),
_posY(posY),
_drawn(false),
_bitmap(nullptr),
_notDrawnBitmap(nullptr),
_fadeValue(0) {
}
SpotItemFace::~SpotItemFace() {
if (_bitmap) {
_bitmap->free();
delete _bitmap;
_bitmap = nullptr;
}
if (_notDrawnBitmap) {
_notDrawnBitmap->free();
delete _notDrawnBitmap;
_notDrawnBitmap = nullptr;
}
}
void SpotItemFace::initBlack(uint16 width, uint16 height) {
if (_bitmap) {
_bitmap->free();
}
_bitmap = new Graphics::Surface();
_bitmap->create(width, height, Texture::getRGBAPixelFormat());
initNotDrawn(width, height);
_drawn = false;
}
void SpotItemFace::loadData(const ResourceDescription *jpegDesc) {
// Convert active SpotItem image to raw data
_bitmap = Myst3Engine::decodeJpeg(jpegDesc);
initNotDrawn(_bitmap->w, _bitmap->h);
}
void SpotItemFace::updateData(const Graphics::Surface *surface) {
assert(_bitmap && surface);
assert(surface->format == Texture::getRGBAPixelFormat());
_bitmap->free();
_bitmap->copyFrom(*surface);
_drawn = false;
}
void SpotItemFace::clear() {
memset(_bitmap->getPixels(), 0, _bitmap->pitch * _bitmap->h);
_drawn = false;
}
void SpotItemFace::initNotDrawn(uint16 width, uint16 height) {
// Copy not drawn SpotItem image from face
_notDrawnBitmap = new Graphics::Surface();
_notDrawnBitmap->create(width, height, Texture::getRGBAPixelFormat());
for (uint i = 0; i < height; i++) {
memcpy(_notDrawnBitmap->getBasePtr(0, i), _face->_bitmap->getBasePtr(_posX, _posY + i), width * 4);
}
}
Common::Rect SpotItemFace::getFaceRect() const {
assert(_bitmap);
Common::Rect r = Common::Rect(_bitmap->w, _bitmap->h);
r.translate(_posX, _posY);
return r;
}
void SpotItemFace::draw() {
for (int i = 0; i < _bitmap->h; i++) {
memcpy(_face->_bitmap->getBasePtr(_posX, _posY + i), _bitmap->getBasePtr(0, i), _bitmap->w * 4);
}
_drawn = true;
_face->addTextureDirtyRect(getFaceRect());
}
void SpotItemFace::undraw() {
for (int i = 0; i < _notDrawnBitmap->h; i++) {
memcpy(_face->_bitmap->getBasePtr(_posX, _posY + i), _notDrawnBitmap->getBasePtr(0, i), _notDrawnBitmap->w * 4);
}
_drawn = false;
_face->addTextureDirtyRect(getFaceRect());
}
void SpotItemFace::fadeDraw() {
uint16 fadeValue = CLIP<uint16>(_fadeValue, 0, 100);
for (int i = 0; i < _bitmap->h; i++) {
byte *ptrND = (byte *)_notDrawnBitmap->getBasePtr(0, i);
byte *ptrD = (byte *)_bitmap->getBasePtr(0, i);
byte *ptrDest = (byte *)_face->_bitmap->getBasePtr(_posX, _posY + i);
for (int j = 0; j < _bitmap->w; j++) {
byte rND = *ptrND++;
byte gND = *ptrND++;
byte bND = *ptrND++;
ptrND++; // Alpha
byte rD = *ptrD++;
byte gD = *ptrD++;
byte bD = *ptrD++;
ptrD++; // Alpha
// TODO: optimize ?
*ptrDest++ = rND * (100 - fadeValue) / 100 + rD * fadeValue / 100;
*ptrDest++ = gND * (100 - fadeValue) / 100 + gD * fadeValue / 100;
*ptrDest++ = bND * (100 - fadeValue) / 100 + bD * fadeValue / 100;
ptrDest++; // Alpha
}
}
_drawn = true;
_face->addTextureDirtyRect(getFaceRect());
}
} // end of namespace Myst3

161
engines/myst3/node.h Normal file
View File

@@ -0,0 +1,161 @@
/* 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/>.
*
*/
#ifndef MYST3_ROOM_H
#define MYST3_ROOM_H
#include "engines/myst3/archive.h"
#include "engines/myst3/gfx.h"
#include "common/array.h"
#include "common/rect.h"
#include "graphics/surface.h"
namespace Myst3 {
class Texture;
class Myst3Engine;
class Subtitles;
class Effect;
class Face {
public:
Graphics::Surface *_bitmap;
Graphics::Surface *_finalBitmap;
Texture *_texture;
Face(Myst3Engine *vm, bool is3D = false);
~Face();
void setTextureFromJPEG(const ResourceDescription *jpegDesc);
void addTextureDirtyRect(const Common::Rect &rect);
bool isTextureDirty() { return _textureDirty; }
void uploadTexture();
private:
bool _textureDirty;
Common::Rect _textureDirtyRect;
Myst3Engine *_vm;
bool _is3D;
};
class SpotItemFace {
public:
SpotItemFace(Face *face, uint16 posX, uint16 posY);
~SpotItemFace();
void initBlack(uint16 width, uint16 height);
void loadData(const ResourceDescription *jpegDesc);
void updateData(const Graphics::Surface *surface);
void clear();
void draw();
void undraw();
void fadeDraw();
bool isDrawn() { return _drawn; }
void setDrawn(bool drawn) { _drawn = drawn; }
uint16 getFadeValue() { return _fadeValue; }
void setFadeValue(uint16 value) { _fadeValue = value; }
Common::Rect getFaceRect() const;
private:
Face *_face;
bool _drawn;
uint16 _fadeValue;
uint16 _posX;
uint16 _posY;
Graphics::Surface *_bitmap;
Graphics::Surface *_notDrawnBitmap;
void initNotDrawn(uint16 width, uint16 height);
};
class SpotItem {
public:
SpotItem(Myst3Engine *vm);
~SpotItem();
void setCondition(int16 condition) { _condition = condition; }
void setFade(bool fade) { _enableFade = fade; }
void setFadeVar(uint16 var) { _fadeVar = var; }
void addFace(SpotItemFace *face) { _faces.push_back(face); }
void updateUndraw();
void updateDraw();
private:
Myst3Engine *_vm;
int16 _condition;
uint16 _fadeVar;
bool _enableFade;
Common::Array<SpotItemFace *> _faces;
};
class SunSpot {
public:
uint16 pitch;
uint16 heading;
float intensity;
uint32 color;
uint16 var;
bool variableIntensity;
float radius;
};
class Node : public Drawable {
public:
Node(Myst3Engine *vm, uint16 id);
~Node() override;
void initEffects();
void resetEffects();
void update();
void drawOverlay() override;
void loadSpotItem(uint16 id, int16 condition, bool fade);
SpotItemFace *loadMenuSpotItem(int16 condition, const Common::Rect &rect);
void loadSubtitles(uint32 id);
bool hasSubtitlesToDraw();
protected:
virtual bool isFaceVisible(uint faceId) = 0;
Myst3Engine *_vm;
uint16 _id;
Face *_faces[6];
Common::Array<SpotItem *> _spotItems;
Subtitles *_subtitles;
Common::Array<Effect *> _effects;
};
} // end of namespace Myst3
#endif

View File

@@ -0,0 +1,68 @@
/* 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/archive.h"
#include "engines/myst3/nodecube.h"
#include "engines/myst3/myst3.h"
#include "common/debug.h"
namespace Myst3 {
NodeCube::NodeCube(Myst3Engine *vm, uint16 id) :
Node(vm, id) {
_is3D = true;
for (int i = 0; i < 6; i++) {
ResourceDescription jpegDesc = _vm->getFileDescription("", id, i + 1, Archive::kCubeFace);
if (!jpegDesc.isValid())
error("Face %d does not exist", id);
_faces[i] = new Face(_vm, true);
_faces[i]->setTextureFromJPEG(&jpegDesc);
}
}
NodeCube::~NodeCube() {
}
void NodeCube::draw() {
// Update the OpenGL textures if needed
for (uint i = 0; i < 6; i++) {
if (_faces[i]->isTextureDirty() && isFaceVisible(i)) {
_faces[i]->uploadTexture();
}
}
Texture *textures[6];
for (uint i = 0; i < 6; i++) {
textures[i] = _faces[i]->_texture;
}
_vm->_gfx->drawCube(textures);
}
bool NodeCube::isFaceVisible(uint faceId) {
return _vm->_gfx->isCubeFaceVisible(faceId);
}
} // End of namespace Myst3

42
engines/myst3/nodecube.h Normal file
View File

@@ -0,0 +1,42 @@
/* 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/>.
*
*/
#ifndef NODECUBE_H_
#define NODECUBE_H_
#include "engines/myst3/node.h"
namespace Myst3 {
class NodeCube: public Node {
public:
NodeCube(Myst3Engine *vm, uint16 id);
virtual ~NodeCube();
void draw() override;
protected:
bool isFaceVisible(uint faceId) override;
};
} // End of namespace Myst3
#endif // NODECUBE_H_

View File

@@ -0,0 +1,70 @@
/* 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/archive.h"
#include "engines/myst3/myst3.h"
#include "engines/myst3/nodeframe.h"
#include "engines/myst3/scene.h"
#include "engines/myst3/state.h"
namespace Myst3 {
NodeFrame::NodeFrame(Myst3Engine *vm, uint16 id) :
Node(vm, id) {
ResourceDescription jpegDesc = _vm->getFileDescription("", id, 1, Archive::kLocalizedFrame);
if (!jpegDesc.isValid())
jpegDesc = _vm->getFileDescription("", id, 0, Archive::kFrame);
if (!jpegDesc.isValid())
jpegDesc = _vm->getFileDescription("", id, 1, Archive::kFrame);
if (!jpegDesc.isValid())
error("Frame %d does not exist", id);
_faces[0] = new Face(_vm);
_faces[0]->setTextureFromJPEG(&jpegDesc);
}
NodeFrame::~NodeFrame() {
}
void NodeFrame::draw() {
Common::Rect screenRect;
// Size and position of the frame
if (_vm->_state->getViewType() == kMenu) {
screenRect = Common::Rect(Renderer::kOriginalWidth, Renderer::kOriginalHeight);
} else {
screenRect = Common::Rect(Renderer::kOriginalWidth, Renderer::kFrameHeight);
}
// Used fragment of texture
Common::Rect textureRect = Common::Rect(screenRect.width(), screenRect.height());
// Update the OpenGL texture if needed
_faces[0]->uploadTexture();
// Draw
_vm->_gfx->drawTexturedRect2D(screenRect, textureRect, _faces[0]->_texture);
}
} // End of namespace Myst3

42
engines/myst3/nodeframe.h Normal file
View File

@@ -0,0 +1,42 @@
/* 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/>.
*
*/
#ifndef NODEFRAME_H_
#define NODEFRAME_H_
#include "engines/myst3/node.h"
namespace Myst3 {
class NodeFrame : public Node {
public:
NodeFrame(Myst3Engine *vm, uint16 id);
virtual ~NodeFrame();
void draw() override;
protected:
bool isFaceVisible(uint faceId) override { return true; }
};
} // End of namespace Myst3
#endif // NODEFRAME_H_

1625
engines/myst3/puzzles.cpp Normal file

File diff suppressed because it is too large Load Diff

99
engines/myst3/puzzles.h Normal file
View File

@@ -0,0 +1,99 @@
/* 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/>.
*
*/
#ifndef PUZZLES_H_
#define PUZZLES_H_
#include "common/scummsys.h"
#include "graphics/surface.h"
namespace Myst3 {
class Myst3Engine;
class Puzzles {
public:
Puzzles(Myst3Engine *vm);
virtual ~Puzzles();
void run(uint16 id, uint16 arg0 = 0, uint16 arg1 = 0, uint16 arg2 = 0);
private:
Myst3Engine *_vm;
typedef int32 SymbolCodeSolution[4];
struct PegCombination {
uint16 movie;
bool pegs[5];
uint16 pegFrames[3];
uint16 expireFrame;
};
void leversBall(int16 var);
void tesla(int16 movie, int16 var, int16 move);
void resonanceRingControl();
void resonanceRingsLaunchBall();
void resonanceRingsLights();
void pinball(int16 var);
const PegCombination *_pinballFindCombination(uint16 var, const PegCombination pegs[], uint16 size);
void weightDrag(uint16 var, uint16 movie);
void journalSaavedro(int16 move);
int16 _journalSaavedroLastPageLastChapterValue();
uint16 _journalSaavedroGetNode(uint16 chapter);
uint16 _journalSaavedroPageCount(uint16 chapter);
bool _journalSaavedroHasChapter(uint16 chapter);
uint16 _journalSaavedroNextChapter(uint16 chapter, bool forward);
void journalAtrus(uint16 node, uint16 var);
void mainMenu(uint16 action);
void projectorLoadBitmap(uint16 bitmap);
void projectorAddSpotItem(uint16 bitmap, uint16 x, uint16 y);
void projectorUpdateCoordinates();
void symbolCodesInit(uint16 var, uint16 posX, uint16 posY);
void symbolCodesClick(int16 var);
bool _symbolCodesCheckSolution(uint16 var, const SymbolCodeSolution &solution);
int32 _symbolCodesFound();
void railRoadSwitchs();
void rollercoaster();
void settingsSave();
void updateSoundScriptTimer();
void checkCanSave();
void _drawForVarHelper(int16 var, int32 startValue, int32 endValue);
void _drawXTicks(uint16 ticks);
};
} // End of namespace Myst3
#endif // PUZZLES_H_

227
engines/myst3/scene.cpp Normal file
View File

@@ -0,0 +1,227 @@
/* 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 "common/config-manager.h"
#include "engines/myst3/scene.h"
#include "engines/myst3/gfx.h"
#include "engines/myst3/myst3.h"
#include "engines/myst3/node.h"
#include "engines/myst3/state.h"
#include "math/vector2d.h"
#include "math/utils.h"
namespace Myst3 {
Scene::Scene(Myst3Engine *vm) :
Window(),
_vm(vm),
_mouseSpeed(50) {
updateMouseSpeed();
}
void Scene::updateCamera(Common::Point &mouse) {
float pitch = _vm->_state->getLookAtPitch();
float heading = _vm->_state->getLookAtHeading();
if (!_vm->_state->getCursorLocked()) {
float speed = 25 / (float)(200 - _mouseSpeed);
// Adjust the speed according to the resolution
Common::Rect screen = _vm->_gfx->viewport();
speed *= Renderer::kOriginalHeight / (float) screen.height();
if (ConfMan.getBool("mouse_inverted")) {
pitch += mouse.y * speed;
} else {
pitch -= mouse.y * speed;
}
heading += mouse.x * speed;
}
// Keep heading within allowed values
if (_vm->_state->isCameraLimited()) {
float minHeading = _vm->_state->getMinHeading();
float maxHeading = _vm->_state->getMaxHeading();
if (minHeading < maxHeading) {
heading = CLIP(heading, minHeading, maxHeading);
} else {
if (heading < minHeading && heading > maxHeading) {
uint distToMin = (uint)ABS(heading - minHeading);
uint distToMax = (uint)ABS(heading - maxHeading);
if (distToMin > distToMax)
heading = maxHeading;
else
heading = minHeading;
}
}
}
// Keep heading in 0..360 range
if (heading > 360.0f)
heading -= 360.0f;
else if (heading < 0.0f)
heading += 360.0f;
// Keep pitch within allowed values
float minPitch = _vm->_state->getCameraMinPitch();
float maxPitch = _vm->_state->getCameraMaxPitch();
if (_vm->_state->isCameraLimited()) {
minPitch = _vm->_state->getMinPitch();
maxPitch = _vm->_state->getMaxPitch();
}
pitch = CLIP(pitch, minPitch, maxPitch);
_vm->_state->lookAt(pitch, heading);
_vm->_state->setCameraPitch((int32)pitch);
_vm->_state->setCameraHeading((int32)heading);
}
void Scene::drawSunspotFlare(const SunSpot &s) {
Common::Rect frame = Common::Rect(Renderer::kOriginalWidth, Renderer::kFrameHeight);
uint8 a = (uint8)(s.intensity * s.radius);
uint8 r = (s.color >> 16) & 0xFF;
uint8 g = (s.color >> 8) & 0xFF;
uint8 b = (s.color >> 0) & 0xFF;
_vm->_gfx->selectTargetWindow(this, false, true);
_vm->_gfx->drawRect2D(frame, a, r, g, b);
}
Math::Vector3d Scene::directionToVector(float pitch, float heading) {
Math::Vector3d v;
float radHeading = Math::deg2rad(heading);
float radPitch = Math::deg2rad(pitch);
v.setValue(0, cos(radPitch) * cos(radHeading));
v.setValue(1, sin(radPitch));
v.setValue(2, cos(radPitch) * sin(radHeading));
return v;
}
float Scene::distanceToZone(float spotHeading, float spotPitch, float spotRadius, float heading, float pitch) {
Math::Vector3d vLookAt = directionToVector(pitch, heading);
Math::Vector3d vSun = directionToVector(spotPitch, spotHeading);
float dotProduct = Math::Vector3d::dotProduct(vLookAt, -vSun);
float distance = (0.05 * spotRadius - (dotProduct + 1.0) * 90) / (0.05 * spotRadius);
return CLIP<float>(distance, 0.0, 1.0);
}
void Scene::updateMouseSpeed() {
_mouseSpeed = ConfMan.getInt("mouse_speed");
}
Common::Rect Scene::getPosition() const {
Common::Rect screen = _vm->_gfx->viewport();
Common::Rect frame;
if (_vm->isWideScreenModEnabled()) {
int32 viewportWidth = Renderer::kOriginalWidth;
int32 viewportHeight;
if (_vm->_state->getViewType() == kMenu) {
viewportHeight = Renderer::kOriginalHeight;
} else {
viewportHeight = Renderer::kFrameHeight;
}
// Aspect ratio correction
frame = Common::Rect(MIN<int32>(screen.width(), screen.height() * viewportWidth / viewportHeight),
MIN<int32>(screen.height(), screen.width() * viewportHeight / viewportWidth));
// Pillarboxing
uint left = (screen.width() - frame.width()) / 2;
uint top;
if (_vm->_state->getViewType() == kMenu) {
top = (screen.height() - frame.height()) / 2;
} else {
top = (screen.height() - frame.height()) * Renderer::kTopBorderHeight / (Renderer::kTopBorderHeight + Renderer::kBottomBorderHeight);
}
frame.translate(left, top);
} else {
if (_vm->_state->getViewType() != kMenu) {
frame = Common::Rect(screen.width(), screen.height() * Renderer::kFrameHeight / Renderer::kOriginalHeight);
frame.translate(screen.left, screen.top + screen.height() * Renderer::kTopBorderHeight / Renderer::kOriginalHeight);
} else {
frame = screen;
}
}
return frame;
}
Common::Rect Scene::getOriginalPosition() const {
Common::Rect originalPosition;
if (_vm->_state->getViewType() != kMenu) {
originalPosition = Common::Rect(Renderer::kOriginalWidth, Renderer::kFrameHeight);
originalPosition.translate(0, Renderer::kTopBorderHeight);
} else {
originalPosition = Common::Rect(Renderer::kOriginalWidth, Renderer::kOriginalHeight);
}
return originalPosition;
}
void Scene::screenPosToDirection(const Common::Point &screen, float &pitch, float &heading) const {
Common::Rect frame = getPosition();
// Screen coords to window coords
Common::Point pos = screenPosToWindowPos(screen);
// Window coords to normalized coords
Math::Vector4d in;
in.x() = pos.x * 2 / (float) frame.width() - 1.0;
in.y() = 1.0 - pos.y * 2 / (float) frame.height();
in.z() = 1.0;
in.w() = 1.0;
// Normalized coords to direction
Math::Matrix4 A = _vm->_gfx->getMvpMatrix();
A.inverse();
Math::Vector4d out = A.transform(in);
Math::Vector3d direction(out.x(), out.y(), out.z());
direction.normalize();
// 3D coords to polar coords
Math::Vector2d horizontalProjection = Math::Vector2d(direction.x(), direction.z());
horizontalProjection.normalize();
pitch = 90 - Math::Angle::arcCosine(direction.y()).getDegrees();
heading = Math::Angle::arcCosine(horizontalProjection.getY()).getDegrees();
if (horizontalProjection.getX() > 0.0)
heading = 360 - heading;
}
} // end of namespace Myst3

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

@@ -0,0 +1,60 @@
/* 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/>.
*
*/
#ifndef MYST3_SCENE_H
#define MYST3_SCENE_H
#include "common/rect.h"
#include "engines/myst3/gfx.h"
namespace Myst3 {
class Myst3Engine;
class SunSpot;
class Scene : public Window {
private:
Myst3Engine *_vm;
uint _mouseSpeed;
public:
Scene(Myst3Engine *vm);
// Window API
Common::Rect getPosition() const override;
Common::Rect getOriginalPosition() const override;
void updateCamera(Common::Point &mouse);
void updateMouseSpeed();
void screenPosToDirection(const Common::Point &screen, float &pitch, float &heading) const;
static Math::Vector3d directionToVector(float pitch, float heading);
void drawSunspotFlare(const SunSpot &s);
float distanceToZone(float spotHeading, float spotPitch, float spotRadius, float heading, float pitch);
};
} // end of namespace Myst3
#endif

2905
engines/myst3/script.cpp Normal file

File diff suppressed because it is too large Load Diff

327
engines/myst3/script.h Normal file
View File

@@ -0,0 +1,327 @@
/* 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/>.
*
*/
#ifndef SCRIPT_H_
#define SCRIPT_H_
#include "common/array.h"
namespace Myst3 {
class Myst3Engine;
class Puzzles;
struct Opcode;
#define DECLARE_OPCODE(x) void x(Context &c, const Opcode &cmd)
class Script {
public:
Script(Myst3Engine *vm);
virtual ~Script();
bool run(const Common::Array<Opcode> *script);
void runSingleOp(const Opcode &op);
const Common::String describeOpcode(const Opcode &opcode);
private:
struct Context {
bool endScript;
bool result;
const Common::Array<Opcode> *script;
Common::Array<Opcode>::const_iterator op;
Common::Array<Opcode>::const_iterator whileStart;
};
typedef void (Script::*CommandProc)(Context &c, const Opcode &cmd);
enum ArgumentType {
kUnknown = 'u',
kVar = 'v',
kValue = 'i',
kEvalValue = 'e',
kCondition = 'c'
};
struct Command {
Command() : op(0), proc(nullptr), desc(nullptr), signature(nullptr) { }
Command(uint16 o, CommandProc p, const char *d, const char *s) : op(o), proc(p), desc(d), signature(s) { }
uint16 op;
CommandProc proc;
const char *desc;
const char *signature;
};
Myst3Engine *_vm;
Puzzles *_puzzles;
Common::Array<Command> _commands;
const Command &findCommand(uint16 op);
const Command &findCommandByProc(CommandProc proc);
const Common::String describeCommand(uint16 op);
const Common::String describeArgument(char type, int16 value);
void shiftCommands(uint16 base, int32 value);
void runOp(Context &c, const Opcode &op);
void goToElse(Context &c);
void runScriptForVarDrawTicksHelper(uint16 var, int32 startValue, int32 endValue, uint16 script, int32 numTicks);
DECLARE_OPCODE(badOpcode);
DECLARE_OPCODE(uselessOpcode);
DECLARE_OPCODE(nodeCubeInit);
DECLARE_OPCODE(nodeCubeInitIndex);
DECLARE_OPCODE(nodeFrameInit);
DECLARE_OPCODE(nodeFrameInitCond);
DECLARE_OPCODE(nodeFrameInitIndex);
DECLARE_OPCODE(nodeMenuInit);
DECLARE_OPCODE(stopWholeScript);
DECLARE_OPCODE(spotItemAdd);
DECLARE_OPCODE(spotItemAddCond);
DECLARE_OPCODE(spotItemAddCondFade);
DECLARE_OPCODE(spotItemAddMenu);
DECLARE_OPCODE(movieInitLooping);
DECLARE_OPCODE(movieInitCondLooping);
DECLARE_OPCODE(movieInitCond);
DECLARE_OPCODE(movieInitPreloadLooping);
DECLARE_OPCODE(movieInitCondPreloadLooping);
DECLARE_OPCODE(movieInitCondPreload);
DECLARE_OPCODE(movieInitFrameVar);
DECLARE_OPCODE(movieInitFrameVarPreload);
DECLARE_OPCODE(movieInitOverridePosition);
DECLARE_OPCODE(movieInitScriptedPosition);
DECLARE_OPCODE(movieInitCondScriptedPosition);
DECLARE_OPCODE(movieRemove);
DECLARE_OPCODE(movieRemoveAll);
DECLARE_OPCODE(movieSetLooping);
DECLARE_OPCODE(movieSetNotLooping);
DECLARE_OPCODE(waterEffectSetSpeed);
DECLARE_OPCODE(waterEffectSetAttenuation);
DECLARE_OPCODE(waterEffectSetWave);
DECLARE_OPCODE(shakeEffectSet);
DECLARE_OPCODE(sunspotAdd);
DECLARE_OPCODE(sunspotAddIntensity);
DECLARE_OPCODE(sunspotAddVarIntensity);
DECLARE_OPCODE(sunspotAddIntensityColor);
DECLARE_OPCODE(sunspotAddVarIntensityColor);
DECLARE_OPCODE(sunspotAddIntensityRadius);
DECLARE_OPCODE(sunspotAddVarIntensityRadius);
DECLARE_OPCODE(sunspotAddIntColorRadius);
DECLARE_OPCODE(sunspotAddVarIntColorRadius);
DECLARE_OPCODE(inventoryAddFront);
DECLARE_OPCODE(inventoryAddBack);
DECLARE_OPCODE(inventoryRemove);
DECLARE_OPCODE(inventoryReset);
DECLARE_OPCODE(inventoryAddSaavChapter);
DECLARE_OPCODE(varSetZero);
DECLARE_OPCODE(varSetOne);
DECLARE_OPCODE(varSetTwo);
DECLARE_OPCODE(varSetOneHundred);
DECLARE_OPCODE(varSetValue);
DECLARE_OPCODE(varToggle);
DECLARE_OPCODE(varSetOneIfNotZero);
DECLARE_OPCODE(varOpposite);
DECLARE_OPCODE(varAbsolute);
DECLARE_OPCODE(varDereference);
DECLARE_OPCODE(varReferenceSetZero);
DECLARE_OPCODE(varReferenceSetValue);
DECLARE_OPCODE(varRandRange);
DECLARE_OPCODE(polarToRectSimple);
DECLARE_OPCODE(polarToRect);
DECLARE_OPCODE(varSetDistanceToZone);
DECLARE_OPCODE(varSetMinDistanceToZone);
DECLARE_OPCODE(varRemoveBits);
DECLARE_OPCODE(varToggleBits);
DECLARE_OPCODE(varCopy);
DECLARE_OPCODE(varSetBitsFromVar);
DECLARE_OPCODE(varSetBits);
DECLARE_OPCODE(varApplyMask);
DECLARE_OPCODE(varSwap);
DECLARE_OPCODE(varIncrement);
DECLARE_OPCODE(varIncrementMax);
DECLARE_OPCODE(varIncrementMaxLooping);
DECLARE_OPCODE(varAddValueMaxLooping);
DECLARE_OPCODE(varDecrement);
DECLARE_OPCODE(varDecrementMin);
DECLARE_OPCODE(varDecrementMinLooping);
DECLARE_OPCODE(varAddValueMax);
DECLARE_OPCODE(varSubValueMin);
DECLARE_OPCODE(varZeroRange);
DECLARE_OPCODE(varCopyRange);
DECLARE_OPCODE(varSetRange);
DECLARE_OPCODE(varIncrementMaxTen);
DECLARE_OPCODE(varAddValue);
DECLARE_OPCODE(varArrayAddValue);
DECLARE_OPCODE(varAddVarValue);
DECLARE_OPCODE(varSubValue);
DECLARE_OPCODE(varSubVarValue);
DECLARE_OPCODE(varModValue);
DECLARE_OPCODE(varMultValue);
DECLARE_OPCODE(varMultVarValue);
DECLARE_OPCODE(varDivValue);
DECLARE_OPCODE(varDivVarValue);
DECLARE_OPCODE(varCrossMultiplication);
DECLARE_OPCODE(varMinValue);
DECLARE_OPCODE(varClipValue);
DECLARE_OPCODE(varClipChangeBound);
DECLARE_OPCODE(varAbsoluteSubValue);
DECLARE_OPCODE(varAbsoluteSubVar);
DECLARE_OPCODE(varRatioToPercents);
DECLARE_OPCODE(varRotateValue3);
DECLARE_OPCODE(ifElse);
DECLARE_OPCODE(ifCondition);
DECLARE_OPCODE(ifCond1AndCond2);
DECLARE_OPCODE(ifCond1OrCond2);
DECLARE_OPCODE(ifOneVarSetInRange);
DECLARE_OPCODE(ifVarEqualsValue);
DECLARE_OPCODE(ifVarNotEqualsValue);
DECLARE_OPCODE(ifVar1EqualsVar2);
DECLARE_OPCODE(ifVar1NotEqualsVar2);
DECLARE_OPCODE(ifVarSupEqValue);
DECLARE_OPCODE(ifVarInfEqValue);
DECLARE_OPCODE(ifVarInRange);
DECLARE_OPCODE(ifVarNotInRange);
DECLARE_OPCODE(ifVar1SupEqVar2);
DECLARE_OPCODE(ifVar1SupVar2);
DECLARE_OPCODE(ifVar1InfEqVar2);
DECLARE_OPCODE(ifVarHasAllBitsSet);
DECLARE_OPCODE(ifVarHasNoBitsSet);
DECLARE_OPCODE(ifVarHasSomeBitsSet);
DECLARE_OPCODE(ifHeadingInRange);
DECLARE_OPCODE(ifPitchInRange);
DECLARE_OPCODE(ifHeadingPitchInRect);
DECLARE_OPCODE(ifMouseIsInRect);
DECLARE_OPCODE(leverDrag);
DECLARE_OPCODE(leverDragPositions);
DECLARE_OPCODE(leverDragXY);
DECLARE_OPCODE(itemDrag);
DECLARE_OPCODE(runScriptWhileDragging);
DECLARE_OPCODE(chooseNextNode);
DECLARE_OPCODE(goToNodeTransition);
DECLARE_OPCODE(goToNodeTrans2);
DECLARE_OPCODE(goToNodeTrans1);
DECLARE_OPCODE(goToRoomNode);
DECLARE_OPCODE(zipToNode);
DECLARE_OPCODE(zipToRoomNode);
DECLARE_OPCODE(drawTransition);
DECLARE_OPCODE(reloadNode);
DECLARE_OPCODE(redrawFrame);
DECLARE_OPCODE(moviePlay);
DECLARE_OPCODE(moviePlaySynchronized);
DECLARE_OPCODE(moviePlayFullFrame);
DECLARE_OPCODE(moviePlayFullFrameTrans);
DECLARE_OPCODE(moviePlayChangeNode);
DECLARE_OPCODE(moviePlayChangeNodeTrans);
DECLARE_OPCODE(lookAt);
DECLARE_OPCODE(lookAtInXFrames);
DECLARE_OPCODE(lookAtMovieStart);
DECLARE_OPCODE(lookAtMovieStartInXFrames);
DECLARE_OPCODE(cameraLimitMovement);
DECLARE_OPCODE(cameraFreeMovement);
DECLARE_OPCODE(cameraLookAt);
DECLARE_OPCODE(cameraLookAtVar);
DECLARE_OPCODE(cameraGetLookAt);
DECLARE_OPCODE(lookAtMovieStartImmediate);
DECLARE_OPCODE(cameraSetFOV);
DECLARE_OPCODE(changeNode);
DECLARE_OPCODE(changeNodeRoom);
DECLARE_OPCODE(changeNodeRoomAge);
DECLARE_OPCODE(drawXTicks);
DECLARE_OPCODE(drawWhileCond);
DECLARE_OPCODE(whileStart);
DECLARE_OPCODE(whileEnd);
DECLARE_OPCODE(runScriptWhileCond);
DECLARE_OPCODE(runScriptWhileCondEachXFrames);
DECLARE_OPCODE(runScriptForVar);
DECLARE_OPCODE(runScriptForVarEachXFrames);
DECLARE_OPCODE(runScriptForVarStartVar);
DECLARE_OPCODE(runScriptForVarStartVarEachXFrames);
DECLARE_OPCODE(runScriptForVarEndVar);
DECLARE_OPCODE(runScriptForVarEndVarEachXFrames);
DECLARE_OPCODE(runScriptForVarStartEndVar);
DECLARE_OPCODE(runScriptForVarStartEndVarEachXFrames);
DECLARE_OPCODE(drawFramesForVar);
DECLARE_OPCODE(drawFramesForVarEachTwoFrames);
DECLARE_OPCODE(drawFramesForVarStartEndVarEachTwoFrames);
DECLARE_OPCODE(runScript);
DECLARE_OPCODE(runScriptWithVar);
DECLARE_OPCODE(runCommonScript);
DECLARE_OPCODE(runCommonScriptWithVar);
DECLARE_OPCODE(runPuzzle1);
DECLARE_OPCODE(runPuzzle2);
DECLARE_OPCODE(runPuzzle3);
DECLARE_OPCODE(runPuzzle4);
DECLARE_OPCODE(ambientLoadNode);
DECLARE_OPCODE(ambientReloadCurrentNode);
DECLARE_OPCODE(ambientPlayCurrentNode);
DECLARE_OPCODE(ambientApply);
DECLARE_OPCODE(ambientApplyWithFadeDelay);
DECLARE_OPCODE(soundPlayBadClick);
DECLARE_OPCODE(soundPlayBlocking);
DECLARE_OPCODE(soundPlay);
DECLARE_OPCODE(soundPlayVolume);
DECLARE_OPCODE(soundPlayVolumeDirection);
DECLARE_OPCODE(soundPlayVolumeDirectionAtt);
DECLARE_OPCODE(soundStopEffect);
DECLARE_OPCODE(soundFadeOutEffect);
DECLARE_OPCODE(soundPlayLooping);
DECLARE_OPCODE(soundPlayFadeInOut);
DECLARE_OPCODE(soundChooseNext);
DECLARE_OPCODE(soundRandomizeNext);
DECLARE_OPCODE(soundChooseNextAfterOther);
DECLARE_OPCODE(soundRandomizeNextAfterOther);
DECLARE_OPCODE(ambientSetFadeOutDelay);
DECLARE_OPCODE(ambientAddSound1);
DECLARE_OPCODE(ambientAddSound2);
DECLARE_OPCODE(ambientAddSound3);
DECLARE_OPCODE(ambientAddSound4);
DECLARE_OPCODE(ambientAddSound5);
DECLARE_OPCODE(ambientSetCue1);
DECLARE_OPCODE(ambientSetCue2);
DECLARE_OPCODE(ambientSetCue3);
DECLARE_OPCODE(ambientSetCue4);
DECLARE_OPCODE(runAmbientScriptNode);
DECLARE_OPCODE(runAmbientScriptNodeRoomAge);
DECLARE_OPCODE(runSoundScriptNode);
DECLARE_OPCODE(runSoundScriptNodeRoom);
DECLARE_OPCODE(runSoundScriptNodeRoomAge);
DECLARE_OPCODE(soundStopMusic);
DECLARE_OPCODE(movieSetStartupSound);
DECLARE_OPCODE(movieSetStartupSoundVolume);
DECLARE_OPCODE(movieSetStartupSoundVolumeH);
DECLARE_OPCODE(drawOneFrame);
DECLARE_OPCODE(cursorHide);
DECLARE_OPCODE(cursorShow);
DECLARE_OPCODE(cursorSet);
DECLARE_OPCODE(cursorLock);
DECLARE_OPCODE(cursorUnlock);
DECLARE_OPCODE(dialogOpen);
DECLARE_OPCODE(newGame);
};
} // End of namespace Myst3
#endif // SCRIPT_H_

View File

@@ -0,0 +1,13 @@
in vec2 Texcoord;
OUTPUT
uniform UBOOL textured;
uniform vec4 color;
uniform sampler2D tex;
void main() {
outColor = color;
if (UBOOL_TEST(textured))
outColor = outColor * texture(tex, Texcoord);
}

View File

@@ -0,0 +1,25 @@
in vec2 position;
in vec2 texcoord;
uniform vec2 texOffsetXY;
uniform vec2 texSizeWH;
uniform vec2 verOffsetXY;
uniform vec2 verSizeWH;
uniform UBOOL flipY;
out vec2 Texcoord;
void main() {
Texcoord = texOffsetXY + texcoord * texSizeWH;
// Coordinates are [0.0;1.0], transform to [-1.0; 1.0]
vec2 pos = verOffsetXY + position * verSizeWH;
pos.x = pos.x * 2.0 - 1.0;
pos.y = -1.0 * (pos.y * 2.0 - 1.0);
if (UBOOL_TEST(flipY)) {
pos.y *= -1.0;
}
gl_Position = vec4(pos, 0.0, 1.0);
}

View File

@@ -0,0 +1,9 @@
in vec2 Texcoord;
OUTPUT
uniform sampler2D tex;
void main() {
outColor = texture(tex, Texcoord);
}

View File

@@ -0,0 +1,13 @@
in vec3 position;
in vec2 texcoord;
uniform float texScale;
uniform mat4 mvpMatrix;
out vec2 Texcoord;
void main() {
Texcoord = texcoord * texScale;
gl_Position = mvpMatrix * vec4(position, 1.0);
}

View File

@@ -0,0 +1,9 @@
in vec2 Texcoord;
OUTPUT
uniform sampler2D tex;
void main() {
outColor = texture(tex, Texcoord);
}

View File

@@ -0,0 +1,15 @@
in vec2 position;
in vec2 texcoord;
out vec2 Texcoord;
void main() {
Texcoord = texcoord;
// Coordinates are [0.0;1.0], transform to [-1.0; 1.0]
vec2 pos = position;
pos.x = pos.x * 2.0 - 1.0;
pos.y = -1.0 * (pos.y * 2.0 - 1.0);
gl_Position = vec4(pos, 0.0, 1.0);
}

678
engines/myst3/sound.cpp Normal file
View File

@@ -0,0 +1,678 @@
/* 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/ambient.h"
#include "engines/myst3/database.h"
#include "engines/myst3/myst3.h"
#include "engines/myst3/sound.h"
#include "engines/myst3/state.h"
#include "audio/audiostream.h"
#include "audio/decoders/asf.h"
#include "audio/decoders/mp3.h"
#include "audio/decoders/wave.h"
#include "common/archive.h"
#include "common/config-manager.h"
namespace Myst3 {
Sound::Sound(Myst3Engine *vm) :
_vm(vm) {
for (uint i = 0; i < kNumChannels; i++)
_channels[i] = new SoundChannel(_vm);
}
Sound::~Sound() {
for (uint i = 0; i < kNumChannels; i++)
delete _channels[i];
}
void Sound::playEffect(uint32 id, uint32 volume, uint16 heading, uint16 attenuation) {
id = _vm->_state->valueOrVarValue(id);
SoundChannel *channel = getChannelForSound(id, kEffect);
channel->play(id, volume, heading, attenuation, false, kEffect);
}
void Sound::playEffectLooping(uint32 id, uint32 volume, uint16 heading, uint16 attenuation) {
id = _vm->_state->valueOrVarValue(id);
bool alreadyPlaying;
SoundChannel *channel = getChannelForSound(id, kEffect, &alreadyPlaying);
if (!alreadyPlaying) {
channel->play(id, volume, heading, attenuation, true, kEffect);
}
}
void Sound::playEffectFadeInOut(uint32 id, uint32 volume, uint16 heading, uint16 attenuation,
uint32 fadeInDuration, uint32 playDuration, uint32 fadeOutDuration) {
SoundChannel *channel = getChannelForSound(id, kEffect);
channel->play(id, fadeInDuration == 0 ? volume : 0, heading, attenuation, true, kEffect);
uint32 effectiveVolume = channel->adjustVolume(volume);
if (channel->_playing) {
channel->_fadeArrayPosition = 0;
channel->_fadeDurations[0] = 0;
channel->_fadeDurations[1] = 0;
channel->_fadeDurations[2] = playDuration;
channel->_fadeDurations[3] = fadeOutDuration;
channel->_fadeVolumes[0] = 0;
channel->_fadeVolumes[1] = effectiveVolume;
channel->_fadeVolumes[2] = effectiveVolume;
channel->_fadeVolumes[3] = 0;
channel->fade(effectiveVolume, heading, attenuation, fadeInDuration);
channel->_hasFadeArray = true;
}
}
void Sound::stopEffect(uint32 id, uint32 fadeDuration) {
bool found;
SoundChannel *channel = getChannelForSound(id, kEffect, &found);
if (found) {
channel->fadeOut(fadeDuration);
}
}
void Sound::stopMusic(uint32 fadeDelay) {
for (uint i = 0; i < kNumChannels; i++) {
SoundChannel *channel = _channels[i];
if (channel->_type == kMusic && channel->_playing)
channel->fadeOut(fadeDelay);
}
}
void Sound::resetSoundVars() {
uint32 minId = _vm->_db->getSoundIdMin();
uint32 maxId = _vm->_db->getSoundIdMax();
if (minId == 0 || maxId == 0) {
return;
}
for (uint32 id = minId; id <= maxId; id++) {
_vm->_state->setVar(id, 0);
}
}
void Sound::playCue(uint32 id, uint32 volume, uint16 heading, uint16 attenuation) {
SoundChannel *channel = _channels[13];
channel->play(id, volume, heading, attenuation, false, kCue);
}
void Sound::stopCue(uint32 fadeDelay) {
SoundChannel *channel = _channels[13];
channel->fadeOut(fadeDelay);
}
SoundChannel *Sound::getChannelForSound(uint32 id, SoundType type, bool *found) {
// Channel number 13 is reserved for cue sounds
// if the sound is already playing, return that channel
for (uint i = 0; i < kNumChannels - 1; i++)
if (_channels[i]->_id == id && (_channels[i]->_type == type || type == kAny ) && _channels[i]->_playing) {
if (found) *found = true;
return _channels[i];
}
// else return the channel with the oldest sound
SoundChannel *oldest = _channels[0];
for (uint i = 0; i < kNumChannels - 1; i++) {
if (_channels[i]->_age > oldest->_age) {
oldest = _channels[i];
}
}
if (found) *found = false;
return oldest;
}
void Sound::update() {
for (uint i = 0; i < kNumChannels; i++)
_channels[i]->update();
_vm->runBackgroundSoundScriptsFromNode(_vm->_state->getLocationNode());
_vm->_ambient->updateCue();
}
void Sound::age() {
for (uint i = 0; i < kNumChannels; i++)
_channels[i]->age(99);
}
void Sound::fadeOutOldSounds(uint32 fadeDelay) {
for (uint i = 0; i < kNumChannels; i++) {
if (_channels[i]->_playing && _channels[i]->_type == kAmbient && _channels[i]->_age == 1) {
uint32 delay = _channels[i]->_ambientFadeOutDelay;
if (_vm->_state->getAmbientOverrideFadeOutDelay() || delay == 0)
delay = fadeDelay;
_channels[i]->fadeOut(delay);
}
}
_vm->_state->setAmbientOverrideFadeOutDelay(false);
}
void Sound::compute3DVolumes(int32 heading, uint angle, int32 *left, int32 *right) {
// This table contains the left and right volume values for the cardinal directions
static const struct {
int32 angle;
int32 left;
int32 right;
} volumes[] = {
{ -180, 50, 50 },
{ -90, 100, 0 },
{ 0, 100, 100 },
{ 90, 0, 100 },
{ 180, 50, 50 }
};
#if 0
// This is the equivalent volume table for the xbox version, with 4.0 surround
static const struct {
int32 angle;
int32 frontLeft;
int32 frontRight;
int32 backLeft;
int32 backRight;
} surroundVolumes[] = {
{ -180, 0, 0, 100, 100 },
{ -135, 0, 0, 100, 0 },
{ -90, 100, 0, 100, 0 },
{ -40, 100, 0, 0, 0 },
{ 0, 100, 100, 0, 0 },
{ 40, 0, 100, 0, 0 },
{ 90, 0, 100, 0, 100 },
{ 135, 0, 0, 0, 100 },
{ 180, 0, 0, 100, 100 },
};
#endif
if (angle) {
// Compute the distance to the sound source
int32 headingDistance = heading - _vm->_state->getLookAtHeading();
// Make sure to use the shortest direction
while (ABS(headingDistance) > 180) {
if (headingDistance > 0) {
headingDistance -= 360;
} else {
headingDistance += 360;
}
}
// Find the appropriate quadrant
uint quadrant = 0;
while (headingDistance < volumes[quadrant].angle || headingDistance > volumes[quadrant + 1].angle)
quadrant++;
float positionInQuadrant = (headingDistance - volumes[quadrant].angle)
/ (float)(volumes[quadrant + 1].angle - volumes[quadrant].angle);
// Compute the left and right volumes using linear interpolation from the cardinal directions
*left = volumes[quadrant].left + (volumes[quadrant + 1].left - volumes[quadrant].left) * positionInQuadrant;
*right = volumes[quadrant].right + (volumes[quadrant + 1].right - volumes[quadrant].right) * positionInQuadrant;
// Add the base sound level
*left += (100 - angle) * (100 - *left) / 100;
*right += (100 - angle) * (100 - *right) / 100;
} else {
*left = 100;
*right = 100;
}
}
void Sound::computeVolumeBalance(int32 volume, int32 heading, uint attenuation, int32 *mixerVolume, int32 *balance) {
int32 left, right;
_vm->_sound->compute3DVolumes(heading, attenuation, &left, &right);
*mixerVolume = MAX(left, right) * volume * Audio::Mixer::kMaxChannelVolume / 100 / 100;
// Compute balance from the left and right volumes
if (left == right) {
*balance = 0;
} else if (left > right) {
*balance = -127 * (left - right) / left;
} else {
*balance = 127 * (right - left) / right;
}
}
int32 Sound::playedFrames(uint32 id) {
bool soundPlaying;
SoundChannel *channel = getChannelForSound(id, kAny, &soundPlaying);
if (!soundPlaying) {
return -1;
}
return channel->playedFrames();
}
bool Sound::isPlaying(uint32 id) {
bool soundPlaying;
getChannelForSound(id, kAny, &soundPlaying);
return soundPlaying;
}
void Sound::setupNextSound(SoundNextCommand command, int16 controlVar, int16 startSoundId, int16 soundCount,
int32 soundMinDelay, int32 soundMaxDelay, int32 controlSoundId, int32 controlSoundMaxPosition) {
bool playSeveralSounds = _vm->_state->getSoundNextMultipleSounds();
_vm->_state->setSoundNextMultipleSounds(false);
_vm->_state->setSoundNextIsChoosen(false);
_vm->_state->setSoundNextId(0);
_vm->_state->setSoundNextIsLast(false);
uint32 controlLastTick = _vm->_state->getVar(controlVar);
int32 playingSoundId = _vm->_state->getVar(controlVar + 1) >> 16;
int32 soundDelay = _vm->_state->getVar(controlVar + 1) & 0xFFFF;
if (!controlLastTick) {
if (!playSeveralSounds) {
for (int16 i = startSoundId; i < startSoundId + soundCount; i++) {
int16 soundVarValue = _vm->_state->getVar(i);
if (soundVarValue)
return;
}
}
soundDelay = _vm->_rnd->getRandomNumberRng(soundMinDelay, soundMaxDelay);
_vm->_state->setVar(controlVar, 1);
_vm->_state->setVar(controlVar + 1, soundDelay | (playingSoundId << 16));
return;
}
uint currentTick = _vm->_state->getTickCount();
if (currentTick == controlLastTick) {
return;
}
if (currentTick < controlLastTick) {
soundDelay = 0;
} else if (currentTick > controlLastTick + 10) {
soundDelay -= 10;
} else {
soundDelay -= currentTick - controlLastTick;
}
if (soundDelay < 0) {
soundDelay = 0;
}
if (soundDelay) {
_vm->_state->setVar(controlVar, currentTick);
_vm->_state->setVar(controlVar + 1, soundDelay | (playingSoundId << 16));
return;
}
bool shouldPlaySound;
if (command == kRandom || command == kNext) {
shouldPlaySound = true;
} else {
int32 controlSoundPosition = playedFrames(controlSoundId);
shouldPlaySound = controlSoundPosition >= 0 && controlSoundPosition <= controlSoundMaxPosition;
}
if (!shouldPlaySound) {
return;
}
switch (command) {
case kRandom:
case kRandomIfOtherStarting: {
if (soundCount == 1) {
playingSoundId = startSoundId;
} else {
int32 newSoundId;
do {
newSoundId = _vm->_rnd->getRandomNumberRng(startSoundId, startSoundId + soundCount - 1);
} while (newSoundId == playingSoundId);
playingSoundId = newSoundId;
}
}
break;
case kNext:
case kNextIfOtherStarting: {
if (!playingSoundId) {
playingSoundId = startSoundId;
} else {
playingSoundId++;
}
if (playingSoundId == startSoundId + soundCount - 1) {
_vm->_state->setSoundNextIsLast(true);
}
if (playingSoundId >= startSoundId + soundCount)
playingSoundId = startSoundId;
}
break;
}
_vm->_state->setVar(controlVar, 0);
_vm->_state->setVar(controlVar + 1, soundDelay | (playingSoundId << 16));
_vm->_state->setVar(playingSoundId, 2);
_vm->_state->setSoundNextIsChoosen(true);
_vm->_state->setSoundNextId(playingSoundId);
}
SoundChannel::SoundChannel(Myst3Engine *vm) :
_vm(vm),
_playing(false),
_fading(false),
_id(0),
_stream(nullptr),
_age(0),
_ambientFadeOutDelay(0),
_volume(0),
_heading(0),
_headingAngle(0),
_fadeLastTick(0),
_fadeDuration(0),
_fadeTargetVolume(0),
_fadeSourceVolume(0),
_fadeTargetAttenuation(0),
_fadeSourceAttenuation(0),
_fadeTargetHeading(0),
_fadeSourceHeading(0),
_stopWhenSilent(true),
_hasFadeArray(false),
_fadeArrayPosition(0),
_fadePosition(0),
_type(kAny) {
}
SoundChannel::~SoundChannel() {
}
void SoundChannel::play(uint32 id, uint32 volume, uint16 heading, uint16 attenuation, bool loop, SoundType type) {
stop();
// Load the name of the sound from its id
_name = _vm->_db->getSoundName(id);
// Set the sound type
if (_vm->_state->getVar(id) != 2) {
_type = type;
} else {
_type = kMusic;
}
// Set the sound parameters
_volume = adjustVolume(volume);
_heading = heading;
_headingAngle = attenuation;
// Open the file to a stream
Audio::RewindableAudioStream *plainStream = makeAudioStream(_name);
if (!plainStream)
return;
// Get the sound's length
Audio::SeekableAudioStream *seekableStream = dynamic_cast<Audio::SeekableAudioStream *>(plainStream);
if (seekableStream) {
_length = seekableStream->getLength();
}
_stream = Audio::makeLoopingAudioStream(plainStream, loop ? 0 : 1);
// Play the sound
g_system->getMixer()->playStream(mixerSoundType(), &_handle, _stream);
setVolume3D(volume, heading, attenuation);
// Update state
_id = id;
_age = 0;
_playing = true;
_stopWhenSilent = false;
_vm->_state->setVar(id, 1);
}
Audio::Mixer::SoundType SoundChannel::mixerSoundType() {
switch (_type) {
case kCue:
case kEffect:
return Audio::Mixer::kSFXSoundType;
case kAmbient:
case kMusic:
return Audio::Mixer::kMusicSoundType;
default:
error("Impossible");
}
}
uint32 SoundChannel::adjustVolume(uint32 volume) {
if (_type == kMusic)
return volume * 100 / 75;
else
return volume;
}
Audio::RewindableAudioStream *SoundChannel::makeAudioStream(const Common::String &name) const {
Common::String folder = Common::String(name.c_str(), 4);
Common::Path filename(Common::String::format("M3Data/%s/%s", folder.c_str(), name.c_str()));
Common::SeekableReadStream *s = SearchMan.createReadStreamForMember(filename);
bool isMP3 = false;
bool isWMA = false;
if (!s)
s = SearchMan.createReadStreamForMember(filename.append(".wav"));
if (!s) {
s = SearchMan.createReadStreamForMember(filename.append(".mp3"));
if (s) isMP3 = true;
}
if (!s) {
s = SearchMan.createReadStreamForMember(filename.append(".wma"));
if (s) isWMA = true;
}
if (!s)
error("Unable to open sound file '%s'", filename.toString().c_str());
if (isMP3) {
#ifdef USE_MAD
return Audio::makeMP3Stream(s, DisposeAfterUse::YES);
#else
warning("Unable to play sound '%s', MP3 support is not compiled in.", filename.toString().c_str());
delete s;
return NULL;
#endif
} else if (isWMA) {
return Audio::makeASFStream(s, DisposeAfterUse::YES);
} else {
return Audio::makeWAVStream(s, DisposeAfterUse::YES);
}
}
void SoundChannel::update() {
if (!_playing)
return; // Nothing to update
if (!_fading)
setVolume3D(_volume, _heading, _headingAngle);
else
updateFading();
_playing = g_system->getMixer()->isSoundHandleActive(_handle);
if (!_playing || (_stopWhenSilent && !_volume)) {
stop();
}
if (!_playing)
return;
}
void SoundChannel::stop() {
_playing = g_system->getMixer()->isSoundHandleActive(_handle);
if (_playing) {
g_system->getMixer()->stopHandle(_handle);
_playing = false;
}
if (_id != 0) {
_vm->_state->setVar(_id, 0);
_id = 0;
}
_age = 99;
_fading = false;
_stopWhenSilent = true;
_hasFadeArray = false;
_stream = nullptr;
_length = Audio::Timestamp();
}
void SoundChannel::setVolume3D(uint32 volume, uint16 heading, uint16 attenuation) {
int32 mixerVolume, balance;
_vm->_sound->computeVolumeBalance(volume, heading, attenuation, &mixerVolume, &balance);
g_system->getMixer()->setChannelVolume(_handle, mixerVolume);
g_system->getMixer()->setChannelBalance(_handle, balance);
}
void SoundChannel::fadeOut(uint32 fadeDelay) {
if (fadeDelay == 0) {
stop();
} else {
fade(0, -1, 0, fadeDelay);
}
}
void SoundChannel::fade(uint32 targetVolume, int32 targetHeading, int32 targetAttenuation, uint32 fadeDelay) {
_fading = true;
_hasFadeArray = false;
_fadeDuration = fadeDelay;
_fadePosition = 0;
_fadeLastTick = 0;
_fadeSourceVolume = _volume;
_fadeTargetVolume = targetVolume;
if (!targetVolume)
_stopWhenSilent = true;
if (targetHeading < 0) {
_fadeSourceHeading = _heading;
_fadeTargetHeading = _heading;
_fadeSourceAttenuation = _headingAngle;
_fadeTargetAttenuation = _headingAngle;
} else {
_fadeSourceAttenuation = _headingAngle;
_fadeTargetAttenuation = targetAttenuation;
_fadeSourceHeading = _heading;
_fadeTargetHeading = targetHeading;
_fadeSourceHeading -= 360;
while (ABS(targetHeading - _fadeSourceHeading) > 180) {
_fadeSourceHeading += 360;
}
}
}
void SoundChannel::age(uint32 maxAge) {
_age++;
_age = CLIP<uint32>(_age, 0, maxAge);
}
void SoundChannel::updateFading() {
uint tick = _vm->_state->getTickCount();
if (tick == _fadeLastTick) {
return; // We already updated fading this tick
}
_fadeLastTick = tick;
_fadePosition++;
if (_fadePosition <= _fadeDuration) {
// Fading in progress, compute the new channel parameters
_volume = _fadeSourceVolume + _fadePosition * (_fadeTargetVolume - _fadeSourceVolume) / _fadeDuration;
_heading = _fadeSourceHeading + _fadePosition * (_fadeTargetHeading - _fadeSourceHeading) / _fadeDuration;
_headingAngle = _fadeSourceAttenuation + _fadePosition * (_fadeTargetAttenuation - _fadeSourceAttenuation) / _fadeDuration;
} else {
if (!_hasFadeArray) {
// The fading is complete
_fading = false;
} else {
// This step of the fade array is complete, find the next one
do {
_fadeArrayPosition++;
} while (_fadeArrayPosition < 4 && !_fadeDurations[_fadeArrayPosition]);
if (_fadeArrayPosition < 4) {
// Setup the new fading step
_fadePosition = 0;
_fadeDuration = _fadeDurations[_fadeArrayPosition];
_fadeSourceVolume = _volume;
_fadeTargetVolume = _fadeVolumes[_fadeArrayPosition];
if (!_fadeTargetVolume) {
_stopWhenSilent = true;
}
} else {
// No more steps
_hasFadeArray = false;
_fading = false;
_stopWhenSilent = true;
_volume = 0;
}
}
}
setVolume3D(_volume, _heading, _headingAngle);
}
uint32 SoundChannel::playedFrames() {
uint32 length = _length.msecs();
if (!length) {
warning("Unable to retrieve length for sound %d", _id);
return 0;
}
uint32 elapsed = g_system->getMixer()->getSoundElapsedTime(_handle);
// Don't count completed loops in
while (elapsed > length) {
elapsed -= length;
}
return elapsed * 30 / 1000;
}
} // End of namespace Myst3

148
engines/myst3/sound.h Normal file
View File

@@ -0,0 +1,148 @@
/* 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/>.
*
*/
#ifndef SOUND_H_
#define SOUND_H_
#include "audio/audiostream.h"
#include "audio/mixer.h"
#include "common/str.h"
namespace Myst3 {
class Myst3Engine;
enum SoundType {
kAny,
kAmbient,
kCue,
kEffect,
kMusic
};
enum SoundNextCommand {
kRandom,
kNext,
kRandomIfOtherStarting,
kNextIfOtherStarting
};
class SoundChannel {
public:
SoundChannel(Myst3Engine *vm);
virtual ~SoundChannel();
void play(uint32 id, uint32 volume, uint16 heading, uint16 attenuation, bool loop, SoundType type);
void setVolume3D(uint32 volume, uint16 heading, uint16 attenuation);
void fade(uint32 targetVolume, int32 targetHeading, int32 targetAttenuation, uint32 fadeDelay);
void fadeOut(uint32 fadeDelay);
void update();
void stop();
void age(uint32 maxAge);
uint32 playedFrames();
uint32 adjustVolume(uint32 volume);
uint32 _id;
bool _playing;
bool _stopWhenSilent;
bool _fading;
SoundType _type;
uint32 _age;
uint32 _ambientFadeOutDelay;
uint _fadeLastTick;
int32 _fadeDuration; // In frames (@30 fps)
int32 _fadePosition;
int32 _fadeSourceVolume;
int32 _fadeTargetVolume;
int32 _fadeSourceHeading;
int32 _fadeTargetHeading;
int32 _fadeSourceAttenuation;
int32 _fadeTargetAttenuation;
bool _hasFadeArray;
uint32 _fadeArrayPosition;
uint32 _fadeDurations[4];
uint32 _fadeVolumes[4];
private:
Myst3Engine *_vm;
Common::String _name;
uint32 _volume;
int32 _heading;
uint32 _headingAngle;
Audio::AudioStream *_stream;
Audio::SoundHandle _handle;
Audio::Timestamp _length;
Audio::RewindableAudioStream *makeAudioStream(const Common::String &name) const;
void updateFading();
Audio::Mixer::SoundType mixerSoundType();
};
class Sound {
public:
Sound(Myst3Engine *vm);
virtual ~Sound();
SoundChannel *getChannelForSound(uint32 id, SoundType type, bool *found = nullptr);
void playEffect(uint32 id, uint32 volume, uint16 heading = 0, uint16 attenuation = 0);
void playEffectLooping(uint32 id, uint32 volume, uint16 heading = 0, uint16 attenuation = 0);
void playEffectFadeInOut(uint32 id, uint32 volume, uint16 heading, uint16 attenuation,
uint32 fadeInDuration, uint32 playDuration, uint32 fadeOutDuration);
void stopEffect(uint32 id, uint32 fadeDuration);
void playCue(uint32 id, uint32 volume, uint16 heading, uint16 attenuation);
void stopCue(uint32 fadeDelay);
void stopMusic(uint32 fadeDelay);
bool isPlaying(uint32 id);
int32 playedFrames(uint32 id);
void update();
void age();
void fadeOutOldSounds(uint32 fadeDelay);
void computeVolumeBalance(int32 volume, int32 heading, uint attenuation, int32 *mixerVolume, int32 *balance);
void setupNextSound(SoundNextCommand command, int16 controlVar, int16 startSoundId, int16 soundCount,
int32 soundMinDelay, int32 soundMaxDelay, int32 controlSoundId = 0, int32 controlSoundMaxPosition = 0);
void resetSoundVars();
private:
static const uint kNumChannels = 14;
Myst3Engine *_vm;
SoundChannel *_channels[kNumChannels];
void compute3DVolumes(int32 heading, uint angle, int32 *left, int32 *right);
};
} // End of namespace Myst3
#endif // SOUND_H_

842
engines/myst3/state.cpp Normal file
View File

@@ -0,0 +1,842 @@
/* 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/state.h"
#include "engines/myst3/database.h"
#include "engines/myst3/gfx.h"
#include "common/debug-channels.h"
#include "common/ptr.h"
#include "common/savefile.h"
#include "graphics/surface.h"
namespace Myst3 {
GameState::StateData::StateData() {
version = GameState::kSaveVersion;
gameRunning = true;
tickCount = 0;
nextSecondsUpdate = 0;
secondsPlayed = 0;
dword_4C2C44 = 0;
dword_4C2C48 = 0;
dword_4C2C4C = 0;
dword_4C2C50 = 0;
dword_4C2C54 = 0;
dword_4C2C58 = 0;
dword_4C2C5C = 0;
dword_4C2C60 = 0;
currentNodeType = 0;
lookatPitch = 0;
lookatHeading = 0;
lookatFOV = 0;
pitchOffset = 0;
headingOffset = 0;
limitCubeCamera = 0;
minPitch = 0;
maxPitch = 0;
minHeading = 0;
maxHeading = 0;
dword_4C2C90 = 0;
for (uint i = 0; i < 2048; i++)
vars[i] = 0;
vars[0] = 0;
vars[1] = 1;
inventoryCount = 0;
for (uint i = 0; i < 7; i++)
inventoryList[i] = 0;
for (uint i = 0; i < 64; i++)
zipDestinations[i] = 0;
saveDay = 0;
saveMonth = 0;
saveYear = 0;
saveHour = 0;
saveMinute = 0;
isAutosave = false;
}
GameState::GameState(const Common::Platform platform, Database *database):
_platform(platform),
_db(database) {
#define VAR(var, x, unk) _varDescriptions.setVal(#x, VarDescription(var, #x, unk));
VAR(14, CursorTransparency, false)
VAR(47, ProjectorAngleX, false)
VAR(48, ProjectorAngleY, false)
VAR(49, ProjectorAngleZoom, false)
VAR(50, ProjectorAngleBlur, false)
VAR(51, DraggedWeight, false)
VAR(57, DragEnded, false)
VAR(58, DragLeverSpeed, false)
VAR(59, DragPositionFound, false)
VAR(60, DragLeverPositionChanged, false)
VAR(61, LocationAge, false)
VAR(62, LocationRoom, false)
VAR(63, LocationNode, false)
VAR(64, BookSavedAge, false)
VAR(65, BookSavedRoom, false)
VAR(66, BookSavedNode, false)
VAR(67, MenuSavedAge, false)
VAR(68, MenuSavedRoom, false)
VAR(69, MenuSavedNode, false)
VAR(70, SecondsCountdown, false)
VAR(71, TickCountdown, false)
// Counters, unused by the game scripts
VAR(76, CounterUnk76, false)
VAR(77, CounterUnk77, false)
VAR(78, CounterUnk78, false)
VAR(79, SweepEnabled, false)
VAR(80, SweepValue, false)
VAR(81, SweepStep, false)
VAR(82, SweepMin, false)
VAR(83, SweepMax, false)
VAR(84, InputMousePressed, false)
VAR(88, InputEscapePressed, false)
VAR(89, InputTildePressed, false)
VAR(90, InputSpacePressed, false)
VAR(92, HotspotActiveRect, false)
VAR(93, WaterEffectRunning, false)
VAR(94, WaterEffectActive, false)
VAR(95, WaterEffectSpeed, false)
VAR(96, WaterEffectAttenuation, false)
VAR(97, WaterEffectFrequency, false)
VAR(98, WaterEffectAmpl, false)
VAR(99, WaterEffectMaxStep, false)
VAR(100, WaterEffectAmplOffset, false)
VAR(101, LavaEffectActive, false)
VAR(102, LavaEffectSpeed, false)
VAR(103, LavaEffectAmpl, false)
VAR(104, LavaEffectStepSize, false)
VAR(105, MagnetEffectActive, false)
VAR(106, MagnetEffectSpeed, false)
VAR(107, MagnetEffectUnk1, false)
VAR(108, MagnetEffectUnk2, false)
VAR(109, MagnetEffectSound, false)
VAR(110, MagnetEffectNode, false)
VAR(111, MagnetEffectUnk3, false)
VAR(112, ShakeEffectAmpl, false)
VAR(113, ShakeEffectTickPeriod, false)
VAR(114, RotationEffectSpeed, false)
VAR(115, SunspotIntensity, false)
VAR(116, SunspotColor, false)
VAR(117, SunspotRadius, false)
VAR(119, AmbiantFadeOutDelay, false)
VAR(120, AmbiantPreviousFadeOutDelay, false)
VAR(121, AmbientOverrideFadeOutDelay, false)
VAR(122, SoundScriptsSuspended, false)
VAR(124, SoundNextMultipleSounds, false)
VAR(125, SoundNextIsChoosen, false)
VAR(126, SoundNextId, false)
VAR(127, SoundNextIsLast, false)
VAR(128, SoundScriptsTimer, false)
VAR(129, SoundScriptsPaused, false)
VAR(130, SoundScriptFadeOutDelay, false)
VAR(131, CursorLocked, false)
VAR(132, CursorHidden, false)
VAR(136, CameraPitch, false)
VAR(137, CameraHeading, false)
VAR(140, CameraMinPitch, false)
VAR(141, CameraMaxPitch, false)
VAR(142, MovieStartFrame, false)
VAR(143, MovieEndFrame, false)
VAR(144, MovieVolume1, false)
VAR(145, MovieVolume2, false)
VAR(146, MovieOverrideSubtitles, false)
VAR(149, MovieConditionBit, false)
VAR(150, MoviePreloadToMemory, false)
VAR(151, MovieScriptDriven, false)
VAR(152, MovieNextFrameSetVar, false)
VAR(153, MovieNextFrameGetVar, false)
VAR(154, MovieStartFrameVar, false)
VAR(155, MovieEndFrameVar, false)
VAR(156, MovieForce2d, false)
VAR(157, MovieVolumeVar, false)
VAR(158, MovieSoundHeading, false)
VAR(159, MoviePanningStrenght, false)
VAR(160, MovieSynchronized, false)
// We ignore this, and never skip frames
VAR(161, MovieNoFrameSkip, false)
// Only play the audio track. This is used in TOHO 3 only.
// Looks like it works fine without any specific implementation
VAR(162, MovieAudioOnly, false)
VAR(163, MovieOverrideCondition, false)
VAR(164, MovieUVar, false)
VAR(165, MovieVVar, false)
VAR(166, MovieOverridePosition, false)
VAR(167, MovieOverridePosU, false)
VAR(168, MovieOverridePosV, false)
VAR(169, MovieScale, false)
VAR(170, MovieAdditiveBlending, false)
VAR(171, MovieTransparency, false)
VAR(172, MovieTransparencyVar, false)
VAR(173, MoviePlayingVar, false)
VAR(174, MovieStartSoundId, false)
VAR(175, MovieStartSoundVolume, false)
VAR(176, MovieStartSoundHeading, false)
VAR(177, MovieStartSoundAttenuation, false)
VAR(178, MovieUseBackground, false)
VAR(179, CameraSkipAnimation, false)
VAR(180, MovieAmbiantScriptStartFrame, false)
VAR(181, MovieAmbiantScript, false)
VAR(182, MovieScriptStartFrame, false)
VAR(183, MovieScript, false)
VAR(185, CameraMoveSpeed, false)
// We always allow missing SpotItem data
VAR(186, SpotItemAllowMissing, false)
VAR(187, TransitionSound, false)
VAR(188, TransitionSoundVolume, false)
VAR(189, LocationNextNode, false)
VAR(190, LocationNextRoom, false)
VAR(191, LocationNextAge, false)
VAR(195, BallPosition, false)
VAR(196, BallFrame, false)
VAR(197, BallLeverLeft, false)
VAR(198, BallLeverRight, false)
VAR(228, BallDoorOpen, false)
VAR(243, ProjectorX, false)
VAR(244, ProjectorY, false)
VAR(245, ProjectorZoom, false)
VAR(246, ProjectorBlur, false)
VAR(247, ProjectorAngleXOffset, false)
VAR(248, ProjectorAngleYOffset, false)
VAR(249, ProjectorAngleZoomOffset, false)
VAR(250, ProjectorAngleBlurOffset, false)
VAR(277, JournalAtrusState, false)
VAR(279, JournalSaavedroState, false)
VAR(280, JournalSaavedroClosed, false)
VAR(281, JournalSaavedroOpen, false)
VAR(282, JournalSaavedroLastPage, false)
VAR(283, JournalSaavedroChapter, false)
VAR(284, JournalSaavedroPageInChapter, false)
VAR(329, TeslaAllAligned, false)
VAR(330, TeslaTopAligned, false)
VAR(331, TeslaMiddleAligned, false)
VAR(332, TeslaBottomAligned, false)
VAR(333, TeslaMovieStart, false)
// Amateria ambient sound / movie counters (XXXX 1001 and XXXX 1002)
VAR(406, AmateriaSecondsCounter, false)
VAR(407, AmateriaTicksCounter, false)
VAR(444, ResonanceRingsSolved, false)
VAR(460, PinballRemainingPegs, false)
VAR(475, OuterShieldUp, false)
VAR(476, InnerShieldUp, false)
VAR(479, SaavedroStatus, false)
VAR(480, BookStateTomahna, false)
VAR(481, BookStateReleeshahn, false)
VAR(489, SymbolCode2Solved, false)
VAR(495, SymbolCode1AllSolved, false)
VAR(496, SymbolCode1CurrentSolved, false)
VAR(497, SymbolCode1TopSolved, false)
VAR(502, SymbolCode1LeftSolved, false)
VAR(507, SymbolCode1RightSolved, false)
VAR(540, SoundVoltaicUnk540, false)
VAR(587, SoundEdannaUnk587, false)
VAR(627, SoundAmateriaUnk627, false)
VAR(930, SoundAmateriaUnk930, false)
VAR(1031, SoundEdannaUnk1031, false)
VAR(1146, SoundVoltaicUnk1146, false)
VAR(1322, ZipModeEnabled, false)
VAR(1323, SubtitlesEnabled, false)
VAR(1324, WaterEffects, false)
VAR(1325, TransitionSpeed, false)
VAR(1326, MouseSpeed, false)
VAR(1327, DialogResult, false)
VAR(1395, HotspotIgnoreClick, false)
VAR(1396, HotspotHovered, false)
VAR(1397, SpotSubtitle, false)
// Override node from which effect masks are loaded
// This is only used in LEIS x75, but is useless
// since all the affected nodes have the same effect masks
VAR(1398, EffectsOverrideMaskNode, false)
VAR(1399, DragLeverLimited, false)
VAR(1400, DragLeverLimitMin, false)
VAR(1401, DragLeverLimitMax, false)
// Mouse unk
VAR(6, Unk6, true)
// Backup var for opcodes 245, 246 => find usage
VAR(13, Unk13, true)
// ???
VAR(147, MovieUnk147, true)
VAR(148, MovieUnk148, true)
if (_platform != Common::kPlatformXbox) {
VAR(1337, MenuEscapePressed, false)
VAR(1338, MenuNextAction, false)
VAR(1339, MenuLoadBack, false)
VAR(1340, MenuSaveBack, false)
VAR(1341, MenuSaveAction, false)
VAR(1342, MenuOptionsBack, false)
VAR(1350, MenuSaveLoadPageLeft, false)
VAR(1351, MenuSaveLoadPageRight, false)
VAR(1352, MenuSaveLoadSelectedItem, false)
VAR(1353, MenuSaveLoadCurrentPage, false)
// Menu stuff does not look like it's too useful
VAR(1361, Unk1361, true)
VAR(1362, Unk1362, true)
VAR(1363, Unk1363, true)
VAR(1374, OverallVolume, false)
VAR(1377, MusicVolume, false)
VAR(1380, MusicFrequency, false)
VAR(1393, LanguageAudio, false)
VAR(1394, LanguageText, false)
VAR(1406, ShieldEffectActive, false)
} else {
shiftVariables(927, 1);
shiftVariables(1031, 2);
shiftVariables(1395, -22);
VAR(1340, MenuSavesAvailable, false)
VAR(1341, MenuNextAction, false)
VAR(1342, MenuLoadBack, false)
VAR(1343, MenuSaveBack, false)
VAR(1344, MenuSaveAction, false)
VAR(1345, MenuOptionsBack, false)
VAR(1346, MenuSelectedSave, false)
VAR(1384, MovieOptional, false)
VAR(1386, VibrationEnabled, false)
VAR(1430, GamePadActionPressed, false)
VAR(1431, GamePadDownPressed, false)
VAR(1432, GamePadUpPressed, false)
VAR(1433, GamePadLeftPressed, false)
VAR(1434, GamePadRightPressed, false)
VAR(1435, GamePadCancelPressed, false)
VAR(1437, DragWithDirectionKeys, false)
VAR(1438, MenuAttractCountDown, false)
VAR(1439, ShieldEffectActive, false)
VAR(1445, StateCanSave, false)
}
#undef VAR
newGame();
}
GameState::~GameState() {
}
void GameState::syncFloat(Common::Serializer &s, float &val,
Common::Serializer::Version minVersion, Common::Serializer::Version maxVersion) {
static const float precision = 10000.0;
if (s.isLoading()) {
int32 saved = 0;
s.syncAsSint32LE(saved, minVersion, maxVersion);
val = saved / precision;
} else {
int32 toSave = static_cast<int32>(val * precision);
s.syncAsSint32LE(toSave, minVersion, maxVersion);
}
}
Common::Error GameState::StateData::syncWithSaveGame(Common::Serializer &s) {
if (!s.syncVersion(kSaveVersion))
return Common::Error(Common::kUnknownError, Common::String::format("This savegame (v%d) is too recent (max %d) please get a newer version of ScummVM", s.getVersion(), kSaveVersion));
s.syncAsUint32LE(gameRunning);
s.syncAsUint32LE(tickCount);
s.syncAsUint32LE(nextSecondsUpdate);
s.syncAsUint32LE(secondsPlayed);
s.syncAsUint32LE(dword_4C2C44);
s.syncAsUint32LE(dword_4C2C48);
s.syncAsUint32LE(dword_4C2C4C);
s.syncAsUint32LE(dword_4C2C50);
s.syncAsUint32LE(dword_4C2C54);
s.syncAsUint32LE(dword_4C2C58);
s.syncAsUint32LE(dword_4C2C5C);
s.syncAsUint32LE(dword_4C2C60);
s.syncAsUint32LE(currentNodeType);
// The original engine (v148) saved the raw IEE754 data,
// we save decimal data as fixed point instead to be achieve portability
if (s.getVersion() < 149) {
s.syncBytes((byte*) &lookatPitch, sizeof(float));
s.syncBytes((byte*) &lookatHeading, sizeof(float));
s.syncBytes((byte*) &lookatFOV, sizeof(float));
s.syncBytes((byte*) &pitchOffset, sizeof(float));
s.syncBytes((byte*) &headingOffset, sizeof(float));
} else {
syncFloat(s, lookatPitch);
syncFloat(s, lookatHeading);
syncFloat(s, lookatFOV);
syncFloat(s, pitchOffset);
syncFloat(s, headingOffset);
}
s.syncAsUint32LE(limitCubeCamera);
if (s.getVersion() < 149) {
s.syncBytes((byte*) &minPitch, sizeof(float));
s.syncBytes((byte*) &maxPitch, sizeof(float));
s.syncBytes((byte*) &minHeading, sizeof(float));
s.syncBytes((byte*) &maxHeading, sizeof(float));
} else {
syncFloat(s, minPitch);
syncFloat(s, maxPitch);
syncFloat(s, minHeading);
syncFloat(s, maxHeading);
}
s.syncAsUint32LE(dword_4C2C90);
for (uint i = 0; i < 2048; i++)
s.syncAsSint32LE(vars[i]);
s.syncAsUint32LE(inventoryCount);
for (uint i = 0; i < 7; i++)
s.syncAsUint32LE(inventoryList[i]);
for (uint i = 0; i < 64; i++)
s.syncAsUint32LE(zipDestinations[i]);
s.syncAsByte(saveDay, 149);
s.syncAsByte(saveMonth, 149);
s.syncAsUint16LE(saveYear, 149);
s.syncAsByte(saveHour, 149);
s.syncAsByte(saveMinute, 149);
s.syncString(saveDescription, 149);
s.syncAsUint32LE(isAutosave, 150);
return Common::kNoError;
}
const Graphics::PixelFormat GameState::getThumbnailSavePixelFormat() {
return Graphics::PixelFormat::createFormatBGRA32(false);
}
Graphics::Surface *GameState::readThumbnail(Common::ReadStream *inStream) {
Graphics::Surface *thumbnail = new Graphics::Surface();
thumbnail->create(kThumbnailWidth, kThumbnailHeight, getThumbnailSavePixelFormat());
inStream->read((byte *)thumbnail->getPixels(), kThumbnailWidth * kThumbnailHeight * 4);
thumbnail->convertToInPlace(Texture::getRGBAPixelFormat());
return thumbnail;
}
void GameState::writeThumbnail(Common::WriteStream *outStream, const Graphics::Surface *thumbnail) {
assert(thumbnail->format == Texture::getRGBAPixelFormat());
assert(thumbnail && thumbnail->w == kThumbnailWidth && thumbnail->h == kThumbnailHeight);
Graphics::Surface *converted = thumbnail->convertTo(getThumbnailSavePixelFormat());
outStream->write((byte *)converted->getPixels(), kThumbnailWidth * kThumbnailHeight * 4);
converted->free();
delete converted;
}
Graphics::Surface *GameState::resizeThumbnail(Graphics::Surface *big, uint width, uint height) {
assert(big->format.bytesPerPixel == 4);
Graphics::Surface *small = new Graphics::Surface();
small->create(width, height, big->format);
uint32 *dst = (uint32 *)small->getPixels();
for (int i = 0; i < small->h; i++) {
for (int j = 0; j < small->w; j++) {
uint32 srcX = big->w * j / small->w;
uint32 srcY = big->h * i / small->h;
uint32 *src = (uint32 *)big->getBasePtr(srcX, srcY);
// Copy RGBA pixel
*dst++ = *src;
}
}
return small;
}
void GameState::newGame() {
_data = StateData();
_lastTickStartTime = g_system->getMillis();
}
Common::Error GameState::load(Common::InSaveFile *saveFile) {
Common::Serializer s = Common::Serializer(saveFile, nullptr);
Common::Error loadError = _data.syncWithSaveGame(s);
_data.gameRunning = true;
if (loadError.getCode() != Common::kNoError) {
return loadError;
}
return Common::kNoError;
}
Common::Error GameState::save(Common::OutSaveFile *saveFile, const Common::String &description, const Graphics::Surface *thumbnail, bool isAutosave) {
Common::Serializer s = Common::Serializer(nullptr, saveFile);
// Update save creation info
TimeDate t;
g_system->getTimeAndDate(t);
_data.saveYear = t.tm_year + 1900;
_data.saveMonth = t.tm_mon + 1;
_data.saveDay = t.tm_mday;
_data.saveHour = t.tm_hour;
_data.saveMinute = t.tm_min;
_data.saveDescription = description;
_data.isAutosave = isAutosave;
_data.gameRunning = false;
Common::Error saveError = _data.syncWithSaveGame(s);
if (saveError.getCode() != Common::kNoError) {
return saveError;
}
writeThumbnail(saveFile, thumbnail);
_data.gameRunning = true;
return Common::kNoError;
}
Common::String GameState::formatSaveTime() {
if (_data.saveYear == 0)
return "";
// TODO: Check the Xbox NTSC version, maybe it uses that strange MM/DD/YYYY format
return Common::String::format("%02d/%02d/%02d %02d:%02d",
_data.saveDay, _data.saveMonth, _data.saveYear,
_data.saveHour, _data.saveMinute);
}
Common::Array<uint16> GameState::getInventory() {
Common::Array<uint16> items;
for (uint i = 0; i < _data.inventoryCount; i++)
items.push_back(_data.inventoryList[i]);
return items;
}
void GameState::updateInventory(const Common::Array<uint16> &items) {
for (uint i = 0; i < 7; i++)
_data.inventoryList[i] = 0;
for (uint i = 0; i < items.size(); i++)
_data.inventoryList[i] = items[i];
_data.inventoryCount = items.size();
}
void GameState::checkRange(uint16 var) {
if (var < 1 || var > 2047)
error("Variable out of range %d", var);
}
const GameState::VarDescription GameState::findDescription(uint16 var) {
for (VarMap::const_iterator it = _varDescriptions.begin(); it != _varDescriptions.end(); it++) {
if (it->_value.var == var) {
return it->_value;
}
}
return VarDescription();
}
void GameState::shiftVariables(uint16 base, int32 value) {
for (VarMap::iterator it = _varDescriptions.begin(); it != _varDescriptions.end(); it++) {
if (it->_value.var >= base) {
it->_value.var += value;
}
}
}
int32 GameState::getVar(uint16 var) {
checkRange(var);
return _data.vars[var];
}
void GameState::setVar(uint16 var, int32 value) {
checkRange(var);
if (DebugMan.isDebugChannelEnabled(kDebugVariable)) {
const VarDescription &d = findDescription(var);
if (d.name && d.unknown) {
warning("A script is writing to the unimplemented engine-mapped var %d (%s)", var, d.name);
}
}
_data.vars[var] = value;
}
bool GameState::evaluate(int16 condition) {
uint16 unsignedCond = abs(condition);
uint16 var = unsignedCond & 2047;
int32 varValue = getVar(var);
int32 targetValue = (unsignedCond >> 11) - 1;
if (targetValue >= 0) {
if (condition >= 0)
return varValue == targetValue;
else
return varValue != targetValue;
} else {
if (condition >= 0)
return varValue != 0;
else
return varValue == 0;
}
}
int32 GameState::valueOrVarValue(int16 value) {
if (value < 0)
return getVar(-value);
return value;
}
int32 GameState::engineGet(const Common::String &varName) {
if (!_varDescriptions.contains(varName))
error("The engine is trying to access an undescribed var (%s)", varName.c_str());
const VarDescription &d = _varDescriptions.getVal(varName);
return _data.vars[d.var];
}
void GameState::engineSet(const Common::String &varName, int32 value) {
if (!_varDescriptions.contains(varName))
error("The engine is trying to access an undescribed var (%s)", varName.c_str());
const VarDescription &d = _varDescriptions.getVal(varName);
_data.vars[d.var] = value;
}
const Common::String GameState::describeVar(uint16 var) {
const VarDescription &d = findDescription(var);
if (d.name) {
return Common::String::format("v%s", d.name);
} else {
return Common::String::format("v%d", var);
}
}
const Common::String GameState::describeCondition(int16 condition) {
uint16 unsignedCond = abs(condition);
uint16 var = unsignedCond & 2047;
int16 value = (unsignedCond >> 11) - 1;
return Common::String::format("c[%s %s %d]",
describeVar(var).c_str(),
(condition >= 0 && value >= 0) || (condition < 0 && value < 0) ? "==" : "!=",
value >= 0 ? value : 0);
}
void GameState::limitCubeCamera(float minPitch, float maxPitch, float minHeading, float maxHeading) {
_data.limitCubeCamera = true;
_data.minPitch = minPitch;
_data.maxPitch = maxPitch;
_data.minHeading = minHeading;
_data.maxHeading = maxHeading;
}
void GameState::updateFrameCounters() {
if (!_data.gameRunning)
return;
uint32 currentTime = g_system->getMillis();
int32 timeToNextTick = _lastTickStartTime + kTickDuration - currentTime;
if (timeToNextTick <= 0) {
_data.tickCount++;
updateTickCounters();
_lastTickStartTime = currentTime + timeToNextTick;
}
if (currentTime > _data.nextSecondsUpdate || ABS<int32>(_data.nextSecondsUpdate - currentTime) > 2000) {
_data.secondsPlayed++;
_data.nextSecondsUpdate = currentTime + 1000;
int32 secondsCountdown = getSecondsCountdown();
if (secondsCountdown > 0)
setSecondsCountdown(--secondsCountdown);
if (getAmateriaSecondsCounter() > 0)
setAmateriaSecondsCounter(getAmateriaSecondsCounter() - 1);
if (getSoundScriptsTimer() > 0)
setSoundScriptsTimer(getSoundScriptsTimer() - 1);
if (hasVarMenuAttractCountDown() && getMenuAttractCountDown() > 0)
setMenuAttractCountDown(getMenuAttractCountDown() - 1);
}
}
void GameState::updateTickCounters() {
int32 tickCountdown = getTickCountdown();
if (tickCountdown > 0)
setTickCountdown(--tickCountdown);
if (getAmateriaTicksCounter() > 0)
setAmateriaTicksCounter(getAmateriaTicksCounter() - 1);
if (getSweepEnabled()) {
if (getSweepValue() + getSweepStep() > getSweepMax()) {
setSweepValue(getSweepMax());
if (getSweepStep() > 0) {
setSweepStep(-getSweepStep());
}
} else if (getSweepValue() + getSweepStep() < getSweepMin()) {
setSweepValue(getSweepMin());
if (getSweepStep() < 0) {
setSweepStep(-getSweepStep());
}
} else {
setSweepValue(getSweepValue() + getSweepStep());
}
}
}
uint GameState::getTickCount() const {
return _data.tickCount;
}
void GameState::pauseEngine(bool pause) {
if (!pause) {
_lastTickStartTime = g_system->getMillis();
}
}
bool GameState::isZipDestinationAvailable(uint16 node, uint16 room, uint32 age) {
int32 zipBitIndex = _db->getNodeZipBitIndex(node, room, age);
int32 arrayIndex = zipBitIndex / 32;
assert(arrayIndex < 64);
return (_data.zipDestinations[arrayIndex] & (1 << (zipBitIndex % 32))) != 0;
}
void GameState::markNodeAsVisited(uint16 node, uint16 room, uint32 age) {
int32 zipBitIndex = _db->getNodeZipBitIndex(node, room, age);
int32 arrayIndex = zipBitIndex / 32;
assert(arrayIndex < 64);
_data.zipDestinations[arrayIndex] |= 1 << (zipBitIndex % 32);
}
Common::String Saves::buildName(const char *name, Common::Platform platform) {
const char *format;
if (platform == Common::kPlatformXbox) {
format = "%s.m3x";
} else {
format = "%s.m3s";
}
return Common::String::format(format, name);
}
struct AutosaveFirstComparator {
bool operator()(const Common::String &x, const Common::String &y) const {
if (x.hasPrefixIgnoreCase("autosave.")) {
return true;
}
if (y.hasPrefixIgnoreCase("autosave.")) {
return false;
}
return x < y;
}
};
Common::StringArray Saves::list(Common::SaveFileManager *saveFileManager, Common::Platform platform) {
Common::String searchPattern = Saves::buildName("*", platform);
Common::StringArray filenames = saveFileManager->listSavefiles(searchPattern);
// The saves are sorted alphabetically
Common::sort(filenames.begin(), filenames.end(), AutosaveFirstComparator());
return filenames;
}
} // End of namespace Myst3

446
engines/myst3/state.h Normal file
View File

@@ -0,0 +1,446 @@
/* 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/>.
*
*/
#ifndef VARIABLES_H_
#define VARIABLES_H_
#include "common/hashmap.h"
#include "common/hash-str.h"
#include "common/savefile.h"
#include "common/serializer.h"
#include "engines/myst3/myst3.h"
namespace Myst3 {
class Database;
// View type
enum ViewType {
kCube = 1,
kFrame = 2,
kMenu = 3
};
#define DECLARE_VAR(name) \
void set##name(int32 value) { engineSet(#name, value); } \
int32 get##name() { return engineGet(#name); } \
bool hasVar##name() { return _varDescriptions.contains(#name); }
class GameState {
public:
GameState(const Common::Platform platform, Database *database);
virtual ~GameState();
void newGame();
Common::Error load(Common::InSaveFile *saveFile);
Common::Error save(Common::OutSaveFile *saveFile, const Common::String &description, const Graphics::Surface *thumbnail, bool isAutosave);
int32 getVar(uint16 var);
void setVar(uint16 var, int32 value);
bool evaluate(int16 condition);
int32 valueOrVarValue(int16 value);
const Common::String describeVar(uint16 var);
const Common::String describeCondition(int16 condition);
DECLARE_VAR(CursorTransparency)
DECLARE_VAR(ProjectorAngleX)
DECLARE_VAR(ProjectorAngleY)
DECLARE_VAR(ProjectorAngleZoom)
DECLARE_VAR(ProjectorAngleBlur)
DECLARE_VAR(DraggedWeight)
DECLARE_VAR(DragEnded)
DECLARE_VAR(DragLeverSpeed)
DECLARE_VAR(DragPositionFound)
DECLARE_VAR(DragLeverPositionChanged)
DECLARE_VAR(LocationAge)
DECLARE_VAR(LocationRoom)
DECLARE_VAR(LocationNode)
DECLARE_VAR(BookSavedAge)
DECLARE_VAR(BookSavedRoom)
DECLARE_VAR(BookSavedNode)
DECLARE_VAR(MenuSavedAge)
DECLARE_VAR(MenuSavedRoom)
DECLARE_VAR(MenuSavedNode)
DECLARE_VAR(SecondsCountdown)
DECLARE_VAR(TickCountdown)
DECLARE_VAR(SweepEnabled)
DECLARE_VAR(SweepValue)
DECLARE_VAR(SweepStep)
DECLARE_VAR(SweepMin)
DECLARE_VAR(SweepMax)
DECLARE_VAR(InputMousePressed)
DECLARE_VAR(InputEscapePressed)
DECLARE_VAR(InputTildePressed)
DECLARE_VAR(InputSpacePressed)
DECLARE_VAR(HotspotActiveRect)
DECLARE_VAR(WaterEffectRunning)
DECLARE_VAR(WaterEffectActive)
DECLARE_VAR(WaterEffectSpeed)
DECLARE_VAR(WaterEffectAttenuation)
DECLARE_VAR(WaterEffectFrequency)
DECLARE_VAR(WaterEffectAmpl)
DECLARE_VAR(WaterEffectMaxStep)
DECLARE_VAR(WaterEffectAmplOffset)
DECLARE_VAR(LavaEffectActive)
DECLARE_VAR(LavaEffectSpeed)
DECLARE_VAR(LavaEffectAmpl)
DECLARE_VAR(LavaEffectStepSize)
DECLARE_VAR(MagnetEffectActive)
DECLARE_VAR(MagnetEffectSpeed)
DECLARE_VAR(MagnetEffectUnk1)
DECLARE_VAR(MagnetEffectUnk2)
DECLARE_VAR(MagnetEffectSound)
DECLARE_VAR(MagnetEffectNode)
DECLARE_VAR(MagnetEffectUnk3)
DECLARE_VAR(ShakeEffectAmpl)
DECLARE_VAR(ShakeEffectTickPeriod)
DECLARE_VAR(RotationEffectSpeed)
DECLARE_VAR(SunspotIntensity)
DECLARE_VAR(SunspotColor)
DECLARE_VAR(SunspotRadius)
DECLARE_VAR(AmbiantFadeOutDelay)
DECLARE_VAR(AmbiantPreviousFadeOutDelay)
DECLARE_VAR(AmbientOverrideFadeOutDelay)
DECLARE_VAR(SoundScriptsSuspended)
DECLARE_VAR(SoundNextMultipleSounds)
DECLARE_VAR(SoundNextIsChoosen)
DECLARE_VAR(SoundNextId)
DECLARE_VAR(SoundNextIsLast)
DECLARE_VAR(SoundScriptsTimer)
DECLARE_VAR(SoundScriptsPaused)
DECLARE_VAR(SoundScriptFadeOutDelay)
DECLARE_VAR(CursorLocked)
DECLARE_VAR(CursorHidden)
DECLARE_VAR(CameraPitch)
DECLARE_VAR(CameraHeading)
DECLARE_VAR(CameraMinPitch)
DECLARE_VAR(CameraMaxPitch)
DECLARE_VAR(MovieStartFrame)
DECLARE_VAR(MovieEndFrame)
DECLARE_VAR(MovieVolume1)
DECLARE_VAR(MovieVolume2)
DECLARE_VAR(MovieOverrideSubtitles)
DECLARE_VAR(MovieConditionBit)
DECLARE_VAR(MoviePreloadToMemory)
DECLARE_VAR(MovieScriptDriven)
DECLARE_VAR(MovieNextFrameSetVar)
DECLARE_VAR(MovieNextFrameGetVar)
DECLARE_VAR(MovieStartFrameVar)
DECLARE_VAR(MovieEndFrameVar)
DECLARE_VAR(MovieForce2d)
DECLARE_VAR(MovieVolumeVar)
DECLARE_VAR(MovieSoundHeading)
DECLARE_VAR(MoviePanningStrenght)
DECLARE_VAR(MovieSynchronized)
DECLARE_VAR(MovieOverrideCondition)
DECLARE_VAR(MovieUVar)
DECLARE_VAR(MovieVVar)
DECLARE_VAR(MovieOverridePosition)
DECLARE_VAR(MovieOverridePosU)
DECLARE_VAR(MovieOverridePosV)
DECLARE_VAR(MovieAdditiveBlending)
DECLARE_VAR(MovieTransparency)
DECLARE_VAR(MovieTransparencyVar)
DECLARE_VAR(MoviePlayingVar)
DECLARE_VAR(MovieStartSoundId)
DECLARE_VAR(MovieStartSoundVolume)
DECLARE_VAR(MovieStartSoundHeading)
DECLARE_VAR(MovieStartSoundAttenuation)
DECLARE_VAR(MovieUseBackground)
DECLARE_VAR(CameraSkipAnimation)
DECLARE_VAR(MovieAmbiantScriptStartFrame)
DECLARE_VAR(MovieAmbiantScript)
DECLARE_VAR(MovieScriptStartFrame)
DECLARE_VAR(MovieScript)
DECLARE_VAR(CameraMoveSpeed)
DECLARE_VAR(TransitionSound)
DECLARE_VAR(TransitionSoundVolume)
DECLARE_VAR(LocationNextNode)
DECLARE_VAR(LocationNextRoom)
DECLARE_VAR(LocationNextAge)
DECLARE_VAR(BallPosition)
DECLARE_VAR(BallFrame)
DECLARE_VAR(BallLeverLeft)
DECLARE_VAR(BallLeverRight)
DECLARE_VAR(BallDoorOpen)
DECLARE_VAR(ProjectorX)
DECLARE_VAR(ProjectorY)
DECLARE_VAR(ProjectorZoom)
DECLARE_VAR(ProjectorBlur)
DECLARE_VAR(ProjectorAngleXOffset)
DECLARE_VAR(ProjectorAngleYOffset)
DECLARE_VAR(ProjectorAngleZoomOffset)
DECLARE_VAR(ProjectorAngleBlurOffset)
DECLARE_VAR(JournalAtrusState)
DECLARE_VAR(JournalSaavedroState)
DECLARE_VAR(JournalSaavedroClosed)
DECLARE_VAR(JournalSaavedroOpen)
DECLARE_VAR(JournalSaavedroLastPage)
DECLARE_VAR(JournalSaavedroChapter)
DECLARE_VAR(JournalSaavedroPageInChapter)
DECLARE_VAR(TeslaAllAligned)
DECLARE_VAR(TeslaTopAligned)
DECLARE_VAR(TeslaMiddleAligned)
DECLARE_VAR(TeslaBottomAligned)
DECLARE_VAR(TeslaMovieStart)
DECLARE_VAR(AmateriaSecondsCounter)
DECLARE_VAR(AmateriaTicksCounter)
DECLARE_VAR(ResonanceRingsSolved)
DECLARE_VAR(PinballRemainingPegs)
DECLARE_VAR(OuterShieldUp)
DECLARE_VAR(InnerShieldUp)
DECLARE_VAR(SaavedroStatus)
DECLARE_VAR(BookStateTomahna)
DECLARE_VAR(BookStateReleeshahn)
DECLARE_VAR(SymbolCode2Solved)
DECLARE_VAR(SymbolCode1AllSolved)
DECLARE_VAR(SymbolCode1CurrentSolved)
DECLARE_VAR(SymbolCode1TopSolved)
DECLARE_VAR(SymbolCode1LeftSolved)
DECLARE_VAR(SymbolCode1RightSolved)
DECLARE_VAR(SoundVoltaicUnk540)
DECLARE_VAR(SoundEdannaUnk587)
DECLARE_VAR(SoundAmateriaUnk627)
DECLARE_VAR(SoundAmateriaUnk930)
DECLARE_VAR(SoundEdannaUnk1031)
DECLARE_VAR(SoundVoltaicUnk1146)
DECLARE_VAR(ZipModeEnabled)
DECLARE_VAR(SubtitlesEnabled)
DECLARE_VAR(WaterEffects)
DECLARE_VAR(TransitionSpeed)
DECLARE_VAR(MouseSpeed)
DECLARE_VAR(DialogResult)
DECLARE_VAR(MenuEscapePressed)
DECLARE_VAR(MenuNextAction)
DECLARE_VAR(MenuLoadBack)
DECLARE_VAR(MenuSaveBack)
DECLARE_VAR(MenuSaveAction)
DECLARE_VAR(MenuOptionsBack)
DECLARE_VAR(MenuSaveLoadPageLeft)
DECLARE_VAR(MenuSaveLoadPageRight)
DECLARE_VAR(MenuSaveLoadSelectedItem)
DECLARE_VAR(MenuSaveLoadCurrentPage)
DECLARE_VAR(OverallVolume)
DECLARE_VAR(MusicVolume)
DECLARE_VAR(MusicFrequency)
DECLARE_VAR(LanguageAudio)
DECLARE_VAR(LanguageText)
DECLARE_VAR(HotspotIgnoreClick)
DECLARE_VAR(HotspotHovered)
DECLARE_VAR(SpotSubtitle)
DECLARE_VAR(DragLeverLimited)
DECLARE_VAR(DragLeverLimitMin)
DECLARE_VAR(DragLeverLimitMax)
DECLARE_VAR(ShieldEffectActive)
// Xbox specific variables
DECLARE_VAR(GamePadActionPressed)
DECLARE_VAR(GamePadDownPressed)
DECLARE_VAR(GamePadUpPressed)
DECLARE_VAR(GamePadLeftPressed)
DECLARE_VAR(GamePadRightPressed)
DECLARE_VAR(GamePadCancelPressed)
DECLARE_VAR(DragWithDirectionKeys)
DECLARE_VAR(MenuSavesAvailable)
DECLARE_VAR(MenuSelectedSave)
DECLARE_VAR(MenuAttractCountDown)
DECLARE_VAR(MovieOptional)
DECLARE_VAR(VibrationEnabled)
DECLARE_VAR(StateCanSave)
void updateFrameCounters();
uint getTickCount() const;
/** Ensture the counters are correct when the engine is paused or resumed */
void pauseEngine(bool pause);
ViewType getViewType() { return static_cast<ViewType>(_data.currentNodeType); }
void setViewType(ViewType t) { _data.currentNodeType = t; }
float getLookAtFOV() { return _data.lookatFOV; }
void setLookAtFOV(float fov) { _data.lookatFOV = fov; }
float getLookAtPitch() { return _data.lookatPitch; }
float getLookAtHeading() { return _data.lookatHeading; }
void lookAt(float pitch, float heading) { _data.lookatPitch = pitch; _data.lookatHeading = heading; }
void limitCubeCamera(float minPitch, float maxPitch, float minHeading, float maxHeading);
void freeCubeCamera() { _data.limitCubeCamera = false; }
bool isCameraLimited() { return _data.limitCubeCamera != 0; }
float getMinPitch() { return _data.minPitch; }
float getMaxPitch() { return _data.maxPitch; }
float getMinHeading() { return _data.minHeading; }
float getMaxHeading() { return _data.maxHeading; }
void markNodeAsVisited(uint16 node, uint16 room, uint32 age);
bool isZipDestinationAvailable(uint16 node, uint16 room, uint32 age);
Common::String formatSaveTime();
Common::Array<uint16> getInventory();
void updateInventory(const Common::Array<uint16> &items);
struct StateData {
uint32 version;
uint32 gameRunning;
uint32 tickCount;
uint32 nextSecondsUpdate;
uint32 secondsPlayed;
uint32 dword_4C2C44;
uint32 dword_4C2C48;
uint32 dword_4C2C4C;
uint32 dword_4C2C50;
uint32 dword_4C2C54;
uint32 dword_4C2C58;
uint32 dword_4C2C5C;
uint32 dword_4C2C60;
uint32 currentNodeType;
float lookatPitch;
float lookatHeading;
float lookatFOV;
float pitchOffset;
float headingOffset;
uint32 limitCubeCamera;
float minPitch;
float maxPitch;
float minHeading;
float maxHeading;
uint32 dword_4C2C90;
int32 vars[2048];
uint32 inventoryCount;
uint32 inventoryList[7];
uint32 zipDestinations[64];
uint8 saveDay;
uint8 saveMonth;
uint16 saveYear;
uint8 saveHour;
uint8 saveMinute;
Common::String saveDescription;
bool isAutosave;
StateData();
Common::Error syncWithSaveGame(Common::Serializer &s);
};
static const Graphics::PixelFormat getThumbnailSavePixelFormat();
static Graphics::Surface *readThumbnail(Common::ReadStream *inStream);
static void writeThumbnail(Common::WriteStream *outStream, const Graphics::Surface *thumbnail);
static Graphics::Surface *resizeThumbnail(Graphics::Surface *big, uint width, uint height);
static const uint kThumbnailWidth = 240;
static const uint kThumbnailHeight = 135;
private:
const Common::Platform _platform;
Database *_db;
static const uint32 kSaveVersion = 150;
StateData _data;
static const uint32 kTickDuration = 1000 / 30;
uint32 _lastTickStartTime;
struct VarDescription {
VarDescription() : var(0), name(0), unknown(0) {}
VarDescription(uint16 v, const char *n, bool u) : var(v), name(n), unknown(u) {}
uint16 var;
const char *name;
bool unknown;
};
typedef Common::HashMap<Common::String, VarDescription> VarMap;
VarMap _varDescriptions;
void checkRange(uint16 var);
const VarDescription findDescription(uint16 var);
void shiftVariables(uint16 base, int32 value);
int32 engineGet(const Common::String &varName);
void engineSet(const Common::String &varName, int32 value);
static void syncFloat(Common::Serializer &s, float &val,
Common::Serializer::Version minVersion = 0,
Common::Serializer::Version maxVersion = Common::Serializer::kLastVersion);
void updateTickCounters();
};
/**
* Save files related utility functions
*/
struct Saves {
/** Build a save file name according to the game platform */
static Common::String buildName(const char *name, Common::Platform platform);
/** List all the save file names for the game platform, sorted alphabetically */
static Common::StringArray list(Common::SaveFileManager *saveFileManager, Common::Platform platform);
};
} // End of namespace Myst3
#endif // VARIABLES_H_

566
engines/myst3/subtitles.cpp Normal file
View File

@@ -0,0 +1,566 @@
/* 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/subtitles.h"
#include "engines/myst3/myst3.h"
#include "engines/myst3/scene.h"
#include "engines/myst3/state.h"
#include "common/archive.h"
#include "graphics/fontman.h"
#include "graphics/font.h"
#include "graphics/fonts/ttf.h"
#include "video/bink_decoder.h"
namespace Myst3 {
class FontSubtitles : public Subtitles {
public:
FontSubtitles(Myst3Engine *vm);
virtual ~FontSubtitles();
protected:
void loadResources() override;
bool loadSubtitles(int32 id) override;
void drawToTexture(const Phrase *phrase) override;
private:
void loadCharset(int32 id);
void createTexture();
void readPhrases(const ResourceDescription *desc);
static Common::String fakeBidiProcessing(const Common::String &phrase);
const Graphics::Font *_font;
Graphics::Surface *_surface;
float _scale;
uint8 *_charset;
};
FontSubtitles::FontSubtitles(Myst3Engine *vm) :
Subtitles(vm),
_font(nullptr),
_surface(nullptr),
_scale(1.0),
_charset(nullptr) {
}
FontSubtitles::~FontSubtitles() {
if (_surface) {
_surface->free();
delete _surface;
}
delete _font;
delete[] _charset;
}
void FontSubtitles::loadResources() {
// We draw the subtitles in the adequate resolution so that they are not
// scaled up. This is the scale factor of the current resolution
// compared to the original
_scale = getPosition().width() / (float) getOriginalPosition().width();
#ifdef USE_FREETYPE2
const char *ttfFile;
if (_fontFace == "Arial Narrow") {
// Use the TTF font provided by the game if TTF support is available
ttfFile = "arir67w.ttf";
} else if (_fontFace == "MS Gothic") {
// The Japanese font has to be supplied by the user
ttfFile = "msgothic.ttf";
} else if (_fontFace == "Arial2") {
// The Hebrew font has to be supplied by the user
ttfFile = "hebrew.ttf";
} else {
error("Unknown subtitles font face '%s'", _fontFace.c_str());
}
Common::SeekableReadStream *s = SearchMan.createReadStreamForMember(ttfFile);
if (s) {
_font = Graphics::loadTTFFont(s, DisposeAfterUse::YES, _fontSize * _scale);
} else {
warning("Unable to load the subtitles font '%s'", ttfFile);
}
#endif
}
void FontSubtitles::loadCharset(int32 id) {
ResourceDescription fontCharset = _vm->getFileDescription("CHAR", id, 0, Archive::kRawData);
// Load the font charset if any
if (fontCharset.isValid()) {
Common::SeekableReadStream *data = fontCharset.getData();
_charset = new uint8[data->size()];
data->read(_charset, data->size());
delete data;
}
}
bool FontSubtitles::loadSubtitles(int32 id) {
// No game-provided charset for the Japanese version
if (_fontCharsetCode == 0) {
loadCharset(1100);
}
int32 overridenId = checkOverridenId(id);
ResourceDescription desc = loadText(overridenId, overridenId != id);
if (!desc.isValid())
return false;
readPhrases(&desc);
if (_vm->getGameLanguage() == Common::HE_ISR) {
for (uint i = 0; i < _phrases.size(); i++) {
_phrases[i].string = fakeBidiProcessing(_phrases[i].string);
}
}
return true;
}
void FontSubtitles::readPhrases(const ResourceDescription *desc) {
Common::SeekableReadStream *crypted = desc->getData();
// Read the frames and associated text offsets
while (true) {
Phrase s;
s.frame = crypted->readUint32LE();
s.offset = crypted->readUint32LE();
if (!s.frame)
break;
_phrases.push_back(s);
}
// Read and decrypt the frames subtitles
for (uint i = 0; i < _phrases.size(); i++) {
crypted->seek(_phrases[i].offset);
uint8 key = 35;
while (true) {
uint8 c = crypted->readByte() ^ key++;
if (c >= 32 && _charset)
c = _charset[c - 32];
if (!c)
break;
_phrases[i].string += c;
}
}
delete crypted;
}
static bool isPunctuation(char c) {
return c == '.' || c == ',' || c == '\"' || c == '!' || c == '?';
}
Common::String FontSubtitles::fakeBidiProcessing(const Common::String &phrase) {
// The Hebrew subtitles are stored in logical order:
// .ABC DEF GHI
// This line should be rendered in visual order as:
// .IHG FED CBA
// Notice how the dot is on the left both in logical and visual order. This is
// because it is in left to right order while the Hebrew characters are in right to
// left order. Text rendering code needs to apply what is called the BiDirectional
// algorithm to know which parts of an input string are LTR or RTL and how to render
// them. This is a quite complicated algorithm. Fortunately the subtitles in Myst III
// only require very specific BiDi processing. The punctuation signs at the beginning of
// each line need to be moved to the end so that they are visually to the left once
// the string is rendered from right to left.
// This method works around the need to implement proper BiDi processing
// by exploiting that fact.
uint punctuationCounter = 0;
while (punctuationCounter < phrase.size() && isPunctuation(phrase[punctuationCounter])) {
punctuationCounter++;
}
Common::String output = Common::String(phrase.c_str() + punctuationCounter);
for (uint i = 0; i < punctuationCounter; i++) {
output += phrase[i];
}
// Also reverse the string so that it is in visual order.
// This is necessary because our text rendering code does not actually support RTL.
for (int i = 0, j = output.size() - 1; i < j; i++, j--) {
char c = output[i];
output.setChar(output[j], i);
output.setChar(c, j);
}
return output;
}
void FontSubtitles::createTexture() {
// Create a surface to draw the subtitles on
// Use RGB 565 to allow use of BDF fonts
if (!_surface) {
uint16 width = Renderer::kOriginalWidth * _scale;
uint16 height = _surfaceHeight * _scale;
// Make sure the width is even. Some graphics drivers have trouble reading from
// surfaces with an odd width (Mesa 18 on Intel).
width &= ~1;
_surface = new Graphics::Surface();
_surface->create(width, height, Graphics::PixelFormat(2, 5, 6, 5, 0, 11, 5, 0, 0));
}
if (!_texture) {
_texture = _vm->_gfx->createTexture2D(_surface);
}
}
/** Return an encoding from a GDI Charset as provided to CreateFont */
static Common::CodePage getEncodingFromCharsetCode(uint32 gdiCharset) {
static const struct {
uint32 charset;
Common::CodePage encoding;
} codepages[] = {
{ 128, Common::kWindows932 }, // SHIFTJIS_CHARSET
{ 177, Common::kWindows1255 }, // HEBREW_CHARSET
{ 204, Common::kWindows1251 }, // RUSSIAN_CHARSET
{ 238, Common::kMacCentralEurope } // EASTEUROPE_CHARSET
};
for (uint i = 0; i < ARRAYSIZE(codepages); i++) {
if (gdiCharset == codepages[i].charset) {
return codepages[i].encoding;
}
}
error("Unknown font charset code '%d'", gdiCharset);
}
void FontSubtitles::drawToTexture(const Phrase *phrase) {
const Graphics::Font *font;
if (_font)
font = _font;
else
font = FontMan.getFontByUsage(Graphics::FontManager::kLocalizedFont);
if (!font)
error("No available font");
if (!_texture || !_surface) {
createTexture();
}
// Draw the new text
memset(_surface->getPixels(), 0, _surface->pitch * _surface->h);
if (_fontCharsetCode == 0) {
font->drawString(_surface, phrase->string, 0, _singleLineTop * _scale, _surface->w, 0xFFFFFFFF, Graphics::kTextAlignCenter, 0, false);
} else {
Common::CodePage encoding = getEncodingFromCharsetCode(_fontCharsetCode);
Common::U32String unicode = Common::U32String(phrase->string, encoding);
font->drawString(_surface, unicode, 0, _singleLineTop * _scale, _surface->w, 0xFFFFFFFF, Graphics::kTextAlignCenter, 0, false);
}
// Update the texture
_texture->update(_surface);
}
class MovieSubtitles : public Subtitles {
public:
MovieSubtitles(Myst3Engine *vm);
virtual ~MovieSubtitles();
protected:
void loadResources() override;
bool loadSubtitles(int32 id) override;
void drawToTexture(const Phrase *phrase) override;
private:
ResourceDescription loadMovie(int32 id, bool overriden);
void readPhrases(const ResourceDescription *desc);
Video::BinkDecoder _bink;
};
MovieSubtitles::MovieSubtitles(Myst3Engine *vm) :
Subtitles(vm) {
}
MovieSubtitles::~MovieSubtitles() {
}
void MovieSubtitles::readPhrases(const ResourceDescription *desc) {
Common::SeekableReadStream *frames = desc->getData();
// Read the frames
uint index = 0;
while (true) {
Phrase s;
s.frame = frames->readUint32LE();
s.offset = index;
if (!s.frame)
break;
_phrases.push_back(s);
index++;
}
delete frames;
}
ResourceDescription MovieSubtitles::loadMovie(int32 id, bool overriden) {
ResourceDescription desc;
if (overriden) {
desc = _vm->getFileDescription("IMGR", 200000 + id, 0, Archive::kMovie);
} else {
desc = _vm->getFileDescription("", 200000 + id, 0, Archive::kMovie);
}
return desc;
}
bool MovieSubtitles::loadSubtitles(int32 id) {
int32 overridenId = checkOverridenId(id);
ResourceDescription phrases = loadText(overridenId, overridenId != id);
ResourceDescription movie = loadMovie(overridenId, overridenId != id);
if (!phrases.isValid() || !movie.isValid())
return false;
readPhrases(&phrases);
// Load the movie
Common::SeekableReadStream *movieStream = movie.getData();
_bink.loadStream(movieStream);
_bink.setOutputPixelFormat(Texture::getRGBAPixelFormat());
_bink.start();
return true;
}
void MovieSubtitles::loadResources() {
}
void MovieSubtitles::drawToTexture(const Phrase *phrase) {
_bink.seekToFrame(phrase->offset);
const Graphics::Surface *surface = _bink.decodeNextFrame();
if (!_texture) {
_texture = _vm->_gfx->createTexture2D(surface);
} else {
_texture->update(surface);
}
}
Subtitles::Subtitles(Myst3Engine *vm) :
Window(),
_vm(vm),
_texture(nullptr),
_frame(-1) {
_scaled = !_vm->isWideScreenModEnabled();
}
Subtitles::~Subtitles() {
freeTexture();
}
void Subtitles::loadFontSettings(int32 id) {
// Load font settings
const ResourceDescription fontNums = _vm->getFileDescription("NUMB", id, 0, Archive::kNumMetadata);
if (!fontNums.isValid())
error("Unable to load font settings values");
_fontSize = fontNums.getMiscData(0);
_fontBold = fontNums.getMiscData(1);
_surfaceHeight = fontNums.getMiscData(2);
_singleLineTop = fontNums.getMiscData(3);
_line1Top = fontNums.getMiscData(4);
_line2Top = fontNums.getMiscData(5);
_surfaceTop = fontNums.getMiscData(6);
_fontCharsetCode = fontNums.getMiscData(7);
if (_fontCharsetCode > 0) {
_fontCharsetCode = 128; // The Japanese subtitles are encoded in CP 932 / Shift JIS
}
if (_vm->getGameLanguage() == Common::HE_ISR) {
// The Hebrew subtitles are encoded in CP 1255, but the game data does not specify the appropriate encoding
_fontCharsetCode = 177;
}
if (_fontCharsetCode < 0) {
_fontCharsetCode = -_fontCharsetCode; // Negative values are GDI charset codes
}
ResourceDescription fontText = _vm->getFileDescription("TEXT", id, 0, Archive::kTextMetadata);
if (!fontText.isValid())
error("Unable to load font face");
_fontFace = fontText.getTextData(0);
}
int32 Subtitles::checkOverridenId(int32 id) {
// Subtitles may be overridden using a variable
if (_vm->_state->getMovieOverrideSubtitles()) {
id = _vm->_state->getMovieOverrideSubtitles();
_vm->_state->setMovieOverrideSubtitles(0);
}
return id;
}
ResourceDescription Subtitles::loadText(int32 id, bool overriden) {
ResourceDescription desc;
if (overriden) {
desc = _vm->getFileDescription("IMGR", 100000 + id, 0, Archive::kText);
} else {
desc = _vm->getFileDescription("", 100000 + id, 0, Archive::kText);
}
return desc;
}
void Subtitles::setFrame(int32 frame) {
const Phrase *phrase = nullptr;
uint phraseIdx = 0;
for (uint i = 0; i < _phrases.size(); ++i) {
if (_phrases[i].frame > frame)
break;
phrase = &_phrases[i];
phraseIdx = i;
}
if (!phrase
|| (phraseIdx == _phrases.size() - 1 && phrase->string.empty())) {
freeTexture();
return;
}
if (phrase->frame == _frame) {
return;
}
_frame = phrase->frame;
drawToTexture(phrase);
}
void Subtitles::drawOverlay() {
if (!_texture)
return;
Common::Rect viewport;
if (_scaled) {
viewport = Common::Rect(Renderer::kOriginalWidth, Renderer::kOriginalHeight);
} else {
viewport = _vm->_gfx->viewport();
}
float scale = MIN(
viewport.width() / (float) Renderer::kOriginalWidth,
viewport.height() / (float) Renderer::kOriginalHeight
);
Common::Rect screen = _vm->_gfx->viewport();
Common::Rect bottomBorder = Common::Rect(Renderer::kOriginalWidth * scale, _surfaceHeight * scale);
bottomBorder.translate(0, _surfaceTop);
if (_vm->isWideScreenModEnabled()) {
// Draw a black background to cover the main game frame
_vm->_gfx->drawRect2D(Common::Rect(screen.width(), Renderer::kBottomBorderHeight), 0xFF, 0x00, 0x00, 0x00);
// Center the subtitles in the screen
bottomBorder.translate((screen.width() - Renderer::kOriginalWidth * scale) / 2, 0);
}
Common::Rect textureRect = Common::Rect(_texture->width, _texture->height);
_vm->_gfx->drawTexturedRect2D(bottomBorder, textureRect, _texture);
}
Subtitles *Subtitles::create(Myst3Engine *vm, uint32 id) {
Subtitles *s;
if (vm->getPlatform() == Common::kPlatformXbox) {
s = new MovieSubtitles(vm);
} else {
s = new FontSubtitles(vm);
}
s->loadFontSettings(1100);
if (!s->loadSubtitles(id)) {
delete s;
return nullptr;
}
s->loadResources();
return s;
}
void Subtitles::freeTexture() {
if (_texture) {
delete _texture;
_texture = nullptr;
}
}
Common::Rect Subtitles::getPosition() const {
Common::Rect screen = _vm->_gfx->viewport();
Common::Rect frame;
if (_vm->isWideScreenModEnabled()) {
frame = Common::Rect(screen.width(), Renderer::kBottomBorderHeight);
Common::Rect scenePosition = _vm->_scene->getPosition();
int16 top = CLIP<int16>(screen.height() - frame.height(), 0, scenePosition.bottom);
frame.translate(0, top);
} else {
frame = Common::Rect(screen.width(), screen.height() * Renderer::kBottomBorderHeight / Renderer::kOriginalHeight);
frame.translate(screen.left, screen.top + screen.height() * (Renderer::kTopBorderHeight + Renderer::kFrameHeight) / Renderer::kOriginalHeight);
}
return frame;
}
Common::Rect Subtitles::getOriginalPosition() const {
Common::Rect originalPosition = Common::Rect(Renderer::kOriginalWidth, Renderer::kBottomBorderHeight);
originalPosition.translate(0, Renderer::kTopBorderHeight + Renderer::kFrameHeight);
return originalPosition;
}
} // End of namespace Myst3

85
engines/myst3/subtitles.h Normal file
View File

@@ -0,0 +1,85 @@
/* 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/>.
*
*/
#ifndef SUBTITLES_H_
#define SUBTITLES_H_
#include "engines/myst3/gfx.h"
#include "common/array.h"
namespace Myst3 {
class Myst3Engine;
class ResourceDescription;
class Subtitles : public Window {
public:
static Subtitles *create(Myst3Engine *vm, uint32 id);
virtual ~Subtitles();
// Window API
Common::Rect getPosition() const override;
Common::Rect getOriginalPosition() const override;
void setFrame(int32 frame);
void drawOverlay() override;
protected:
struct Phrase {
uint32 offset;
int32 frame;
Common::String string;
};
Subtitles(Myst3Engine *vm);
void loadFontSettings(int32 id);
virtual void loadResources() = 0;
virtual bool loadSubtitles(int32 id) = 0;
virtual void drawToTexture(const Phrase *phrase) = 0;
void freeTexture();
int32 checkOverridenId(int32 id);
ResourceDescription loadText(int32 id, bool overriden);
Myst3Engine *_vm;
Common::Array<Phrase> _phrases;
int32 _frame;
Texture *_texture;
// Font settings
Common::String _fontFace;
uint _fontSize;
bool _fontBold;
uint _surfaceHeight;
uint _singleLineTop;
uint _line1Top;
uint _line2Top;
uint _surfaceTop;
int32 _fontCharsetCode;
};
} // End of namespace Myst3
#endif // SUBTITLES_H_

View File

@@ -0,0 +1,176 @@
/* 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 "common/events.h"
#include "common/config-manager.h"
#include "engines/myst3/transition.h"
#include "engines/myst3/sound.h"
#include "engines/myst3/state.h"
#include "graphics/surface.h"
#include "graphics/framelimiter.h"
namespace Myst3 {
Transition::Transition(Myst3Engine *vm) :
_vm(vm),
_type(kTransitionNone),
_sourceScreenshot(nullptr),
_frameLimiter(new Graphics::FrameLimiter(g_system, ConfMan.getInt("engine_speed"))) {
// Capture a screenshot of the source node
int durationTicks = computeDuration();
if (durationTicks) {
_sourceScreenshot = _vm->_gfx->copyScreenshotToTexture();
}
}
Transition::~Transition() {
delete _sourceScreenshot;
delete _frameLimiter;
}
int Transition::computeDuration() {
int durationTicks = 30 * (100 - ConfMan.getInt("transition_speed")) / 100;
if (_type == kTransitionZip) {
durationTicks >>= 1;
}
return durationTicks;
}
void Transition::playSound() {
if (_vm->_state->getTransitionSound()) {
_vm->_sound->playEffect(_vm->_state->getTransitionSound(), _vm->_state->getTransitionSoundVolume());
}
_vm->_state->setTransitionSound(0);
}
void Transition::draw(TransitionType type) {
_type = type;
// Play the transition sound
playSound();
int durationTicks = computeDuration();
// Got any transition to draw?
if (!_sourceScreenshot || type == kTransitionNone || durationTicks == 0) {
return;
}
// Capture a screenshot of the destination node
_vm->drawFrame(true);
Texture *targetScreenshot = _vm->_gfx->copyScreenshotToTexture();
// Compute the start and end frames for the animation
int startTick = _vm->_state->getTickCount();
uint endTick = startTick + durationTicks;
// Draw on the full screen
_vm->_gfx->selectTargetWindow(nullptr, false, false);
// Draw each step until completion
int completion = 0;
while ((_vm->_state->getTickCount() <= endTick || completion < 100) && !_vm->shouldQuit()) {
_frameLimiter->startFrame();
completion = CLIP<int>(100 * (_vm->_state->getTickCount() - startTick) / durationTicks, 0, 100);
_vm->_gfx->clear();
drawStep(targetScreenshot, _sourceScreenshot, completion);
_vm->_gfx->flipBuffer();
_frameLimiter->delayBeforeSwap();
g_system->updateScreen();
_vm->_state->updateFrameCounters();
Common::Event event;
while (_vm->getEventManager()->pollEvent(event)) {
// Ignore all the events happening during transitions, so that the view does not move
// between the initial transition screen shoot and the first frame drawn after the transition.
// However, keep updating the keyboard state so we don't end up in
// an unbalanced state where the engine believes keys are still
// pressed while they are not.
_vm->processEventForKeyboardState(event);
if (_vm->_state->hasVarGamePadUpPressed()) {
_vm->processEventForGamepad(event);
}
}
}
delete targetScreenshot;
delete _sourceScreenshot;
_sourceScreenshot = nullptr;
}
void Transition::drawStep(Texture *targetTexture, Texture *sourceTexture, uint completion) {
Common::Rect viewport = _vm->_gfx->viewport();
switch (_type) {
case kTransitionNone:
break;
case kTransitionFade:
case kTransitionZip: {
Common::Rect textureRect = Common::Rect(sourceTexture->width, sourceTexture->height);
_vm->_gfx->drawTexturedRect2D(viewport, textureRect, sourceTexture);
_vm->_gfx->drawTexturedRect2D(viewport, textureRect, targetTexture, completion / 100.0);
}
break;
case kTransitionLeftToRight: {
int16 transitionX = (viewport.width() * (100 - completion)) / 100;
Common::Rect sourceTextureRect(0, 0, transitionX, sourceTexture->height);
Common::Rect sourceScreenRect(sourceTextureRect.width(), sourceTextureRect.height());
sourceScreenRect.translate(viewport.left, viewport.top);
Common::Rect targetTextureRect(transitionX, 0, targetTexture->width, targetTexture->height);
Common::Rect targetScreenRect(targetTextureRect.width(), targetTextureRect.height());
targetScreenRect.translate(viewport.left + transitionX, viewport.top);
_vm->_gfx->drawTexturedRect2D(sourceScreenRect, sourceTextureRect, sourceTexture);
_vm->_gfx->drawTexturedRect2D(targetScreenRect, targetTextureRect, targetTexture);
}
break;
case kTransitionRightToLeft: {
int16 transitionX = viewport.width() * completion / 100;
Common::Rect sourceTextureRect(transitionX, 0, sourceTexture->width, sourceTexture->height);
Common::Rect sourceScreenRect(sourceTextureRect.width(), sourceTextureRect.height());
sourceScreenRect.translate(viewport.left + transitionX, viewport.top);
Common::Rect targetTextureRect(0, 0, transitionX, targetTexture->height);
Common::Rect targetScreenRect(targetTextureRect.width(), targetTextureRect.height());
targetScreenRect.translate(viewport.left, viewport.top);
_vm->_gfx->drawTexturedRect2D(sourceScreenRect, sourceTextureRect, sourceTexture);
_vm->_gfx->drawTexturedRect2D(targetScreenRect, targetTextureRect, targetTexture);
}
break;
}
}
} // End of namespace Myst3

View File

@@ -0,0 +1,57 @@
/* 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/>.
*
*/
#ifndef TRANSITION_H_
#define TRANSITION_H_
#include "engines/myst3/myst3.h"
#include "engines/myst3/gfx.h"
namespace Graphics {
class FrameLimiter;
}
namespace Myst3 {
class Transition {
public:
Transition(Myst3Engine *vm);
virtual ~Transition();
void draw(TransitionType type);
private:
void drawStep(Texture *targetTexture, Texture *sourceTexture, uint completion);
int computeDuration();
void playSound();
Myst3Engine *_vm;
Graphics::FrameLimiter *_frameLimiter;
TransitionType _type;
Texture *_sourceScreenshot;
};
} // End of namespace Myst3
#endif // TRANSITION_H_