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