Initial commit

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

4
engines/zvision/POTFILES Normal file
View File

@@ -0,0 +1,4 @@
engines/zvision/detection_tables.h
engines/zvision/file/save_manager.cpp
engines/zvision/metaengine.cpp
engines/zvision/zvision.cpp

View File

@@ -0,0 +1,92 @@
/* 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 ZVISION_FOCUS_LIST_H
#define ZVISION_FOCUS_LIST_H
#include "common/array.h"
namespace ZVision {
/**
* FILO array of unique members
*
* Tracks redraw order of layered graphical elements.
* When an element has current focus, it is reshuffled to the top of the pile.
* When redrawing, start with last (bottom) element of list and finish with first (top)
* Used to:
* - ensure scrolling menus are drawn in the order in which they last had mouse focus.
* - ensure most recently updated subtitle is drawn atop all others.
*/
template<class T>
class FocusList : public Common::Array<T> {
private:
typedef uint size_type;
public:
/**
* Move unique entry to front of list; add to list if not already present.
* Sequence of all remaining members remains unchanged.
*/
void set(const T currentFocus) {
if (!this->size())
this->push_back(currentFocus);
else {
if (this->front() != currentFocus) {
Common::Array<T> buffer;
while (this->size() > 0) {
if (this->back() != currentFocus)
buffer.push_back(this->back());
this->pop_back();
}
this->push_back(currentFocus);
while (buffer.size() > 0) {
this->push_back(buffer.back());
buffer.pop_back();
}
}
}
}
/**
* Remove unique entry, if present.
* Sequence of all remaining members remains unchanged.
*/
void remove(const T value) {
if (this->size()) {
Common::Array<T> buffer;
while (this->size() > 0) {
if (this->back() != value)
buffer.push_back(this->back());
this->pop_back();
}
while (buffer.size() > 0) {
this->push_back(buffer.back());
buffer.pop_back();
}
}
}
};
} // End of namespace ZVision
#endif

View File

@@ -0,0 +1,122 @@
/* 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 "zvision/common/scroller.h"
namespace ZVision {
LinearScroller::LinearScroller(const int16 activePos, const int16 idlePos, const int16 period) :
_pos(idlePos),
_prevPos(idlePos),
_activePos(activePos),
_idlePos(idlePos),
_deltaPos((int16)(activePos - idlePos)),
_period(period) {
}
LinearScroller::~LinearScroller() {
}
bool LinearScroller::update(uint32 deltatime) {
_prevPos = _pos;
if (_period != 0) {
int16 targetPos;
float dPos = 0;
if (_active)
targetPos = _activePos;
else
targetPos = _idlePos;
if (_pos != targetPos) {
dPos = (float)((int32)deltatime * (int32)_deltaPos) / _period;
if ((int16)dPos == 0) {
if (_deltaPos > 0)
dPos = 1;
else
dPos = -1;
}
}
if (!_active)
dPos = -dPos;
_pos += (int16)dPos;
if ((dPos == 0) || ((dPos > 0) && (_pos > targetPos)) || ((dPos < 0) && (_pos < targetPos)))
_pos = targetPos;
_moving = (_pos != targetPos);
} else {
if (_active)
_pos = _activePos;
else
_pos = _idlePos;
_moving = false;
}
return (_pos != _prevPos); // True if redraw necessary
}
void LinearScroller::reset() {
setActive(false);
_pos = _idlePos;
}
void LinearScroller::setActive(bool active) {
_active = active;
}
bool LinearScroller::isMoving() {
return _moving;
}
Scroller::Scroller(const Common::Point &activePos, const Common::Point &idlePos, int16 period) :
_xScroller(activePos.x, idlePos.x, period),
_yScroller(activePos.y, idlePos.y, period) {
_pos.x = _xScroller._pos;
_pos.y = _yScroller._pos;
}
Scroller::~Scroller() {
}
void Scroller::reset() {
_xScroller.reset();
_yScroller.reset();
}
void Scroller::setActive(bool active) {
_xScroller.setActive(active);
_yScroller.setActive(active);
}
bool Scroller::isMoving() {
return _xScroller.isMoving() || _yScroller.isMoving();
}
bool Scroller::update(uint32 deltatime) {
bool redraw = false;
if (_xScroller.update(deltatime))
redraw = true;
if (_yScroller.update(deltatime))
redraw = true;
_pos.x = _xScroller._pos;
_pos.y = _yScroller._pos;
return (redraw);
}
} // End of namespace ZVision

View File

@@ -0,0 +1,84 @@
/* 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 ZVISION_SCROLLER_H
#define ZVISION_SCROLLER_H
#include "common/rect.h"
#include "zvision/zvision.h"
namespace ZVision {
/**
* Automatically scroll a GUI menu or similar graphical element between an active and an idle position
* Movement in one dimension; idle & active positions specified as int16.
* Movement is at constant speed determined by period, specified in ms.
* If active/idle status is changed mid-transition, will scroll from current position to the appropriate position.
* LinearScroller also be used to reversibly scroll animation frames.
*/
class LinearScroller {
public:
LinearScroller(const int16 activePos, const int16 idlePos, const int16 period = 500);
~LinearScroller();
void reset(); ///< Set idle and immediately jump to idle position
bool update(uint32 deltatime); ///< Calculate updated position of scrolled graphics; return true if redraw is necessary.
void setActive(bool active); ///< Set active or idle & scroll at set speed from current position to that position.
bool isMoving();
int16 getPos();
int16 _pos;
int16 _prevPos;
private:
bool _active = false;
bool _moving = false;
const int16 _activePos;
const int16 _idlePos;
const int16 _deltaPos;
const int16 _period;
};
/**
* Automatically scroll a GUI menu or similar graphical element between an active and an idle position
* Movement in two dimensions; idle & active positions specified as Common::Point
* Movement is at constant speed determined by period, specified in ms.
* If active/idle status is changed mid-transition, will scroll from current position to the appropriate position.
*/
class Scroller {
public:
Scroller(const Common::Point &activePos, const Common::Point &idlePos, const int16 period = 500);
~Scroller();
void reset(); ///< Set idle and immediately jump to idle position
bool update(uint32 deltatime); ///< Calculate updated position of scrolled graphics; return true if redraw is necessary.
void setActive(bool active); ///< Set active or idle & scroll at set speed from current position to that position.
bool isMoving();
Common::Point _pos;
private:
LinearScroller _xScroller, _yScroller;
};
} // End of namespace ZVision
#endif

View File

@@ -0,0 +1,3 @@
# This file is included from the main "configure" script
# add_engine [name] [desc] [build-by-default] [subengines] [base games] [deps] [components]
add_engine zvision "Z-Vision" yes "" "" "freetype2 16bit highres" "midi truemotion1 mpeg2"

View File

@@ -0,0 +1,65 @@
/* 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"
#include "common/system.h"
#include "zvision/core/clock.h"
namespace ZVision {
Clock::Clock(OSystem *system)
: _system(system),
_lastTime(system->getMillis()),
_deltaTime(0),
_pausedTime(0),
_paused(false) {
}
void Clock::update() {
uint32 currentTime = _system->getMillis();
_deltaTime = (currentTime - _lastTime);
if (_paused) {
_deltaTime -= (currentTime - _pausedTime);
}
if (_deltaTime < 0) {
_deltaTime = 0;
}
_lastTime = currentTime;
}
void Clock::start() {
if (_paused) {
_lastTime = _system->getMillis();
_paused = false;
}
}
void Clock::stop() {
if (!_paused) {
_pausedTime = _system->getMillis();
_paused = true;
}
}
} // End of namespace ZVision

View File

@@ -0,0 +1,83 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef ZVISION_CLOCK_H
#define ZVISION_CLOCK_H
#include "common/types.h"
class OSystem;
namespace ZVision {
/* Class for handling frame to frame deltaTime while keeping track of time pauses/un-pauses */
class Clock {
public:
Clock(OSystem *system);
private:
OSystem *_system;
uint32 _lastTime;
int32 _deltaTime;
uint32 _pausedTime;
bool _paused;
public:
/**
* Updates _deltaTime with the difference between the current time and
* when the last update() was called.
*/
void update();
/**
* Get the delta time since the last frame. (The time between update() calls)
*
* @return Delta time since the last frame (in milliseconds)
*/
uint32 getDeltaTime() const {
return _deltaTime;
}
/**
* Get the time from the program starting to the last update() call
*
* @return Time from program start to last update() call (in milliseconds)
*/
uint32 getLastMeasuredTime() {
return _lastTime;
}
/**
* Un-pause the clock.
* Has no effect if the clock is already un-paused.
*/
void start();
/**
* Pause the clock. Any future delta times will take this pause into account.
* Has no effect if the clock is already paused.
*/
void stop();
};
} // End of namespace ZVision
#endif

View File

@@ -0,0 +1,370 @@
/* 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 "audio/mixer.h"
#include "common/bufferedstream.h"
#include "common/file.h"
#include "common/scummsys.h"
#include "common/system.h"
#include "gui/debugger.h"
#include "zvision/zvision.h"
#include "zvision/core/console.h"
#include "zvision/graphics/render_manager.h"
#include "zvision/graphics/cursors/cursor.h"
#include "zvision/scripting/script_manager.h"
#include "zvision/sound/zork_raw.h"
#include "zvision/text/string_manager.h"
#include "zvision/video/zork_avi_decoder.h"
namespace ZVision {
Console::Console(ZVision *engine) : GUI::Debugger(), _engine(engine) {
registerCmd("loadvideo", WRAP_METHOD(Console, cmdLoadVideo));
registerCmd("loadsound", WRAP_METHOD(Console, cmdLoadSound));
registerCmd("raw2wav", WRAP_METHOD(Console, cmdRawToWav));
registerCmd("setrenderstate", WRAP_METHOD(Console, cmdSetRenderState));
registerCmd("generaterendertable", WRAP_METHOD(Console, cmdGenerateRenderTable));
registerCmd("setpanoramafov", WRAP_METHOD(Console, cmdSetPanoramaFoV));
registerCmd("setpanoramascale", WRAP_METHOD(Console, cmdSetPanoramaScale));
registerCmd("location", WRAP_METHOD(Console, cmdLocation));
registerCmd("dumpfile", WRAP_METHOD(Console, cmdDumpFile));
registerCmd("dumpfiles", WRAP_METHOD(Console, cmdDumpFiles));
registerCmd("dumpimage", WRAP_METHOD(Console, cmdDumpImage));
registerCmd("statevalue", WRAP_METHOD(Console, cmdStateValue));
registerCmd("stateflag", WRAP_METHOD(Console, cmdStateFlag));
}
bool Console::cmdLoadVideo(int argc, const char **argv) {
if (argc != 2) {
debugPrintf("Use %s <fileName> to load a video to the screen\n", argv[0]);
return true;
}
ZorkAVIDecoder videoDecoder;
if (videoDecoder.loadFile(argv[1])) {
_engine->playVideo(videoDecoder);
}
return true;
}
bool Console::cmdLoadSound(int argc, const char **argv) {
if (!Common::File::exists(argv[1])) {
debugPrintf("File does not exist\n");
return true;
}
if (argc == 2) {
Audio::AudioStream *soundStream = makeRawZorkStream(argv[1], _engine);
Audio::SoundHandle handle;
_engine->_mixer->playStream(Audio::Mixer::kPlainSoundType, &handle, soundStream, -1, 100, 0, DisposeAfterUse::YES, false, false);
} else if (argc == 4) {
int isStereo = atoi(argv[3]);
Common::File *file = new Common::File();
if (!file->open(argv[1])) {
warning("File not found: %s", argv[1]);
delete file;
return true;
}
Audio::AudioStream *soundStream = makeRawZorkStream(file, atoi(argv[2]), isStereo == 0 ? false : true);
Audio::SoundHandle handle;
_engine->_mixer->playStream(Audio::Mixer::kPlainSoundType, &handle, soundStream, -1, 100, 0, DisposeAfterUse::YES, false, false);
} else {
debugPrintf("Use %s <fileName> [<rate> <isStereo: 1 or 0>] to load a sound\n", argv[0]);
return true;
}
return true;
}
bool Console::cmdRawToWav(int argc, const char **argv) {
if (argc != 3) {
debugPrintf("Use %s <rawFilePath> <wavFileName> to dump a .RAW file to .WAV\n", argv[0]);
return true;
}
Common::File file;
if (!file.open(argv[1])) {
warning("File not found: %s", argv[1]);
return true;
}
Audio::AudioStream *audioStream = makeRawZorkStream(argv[1], _engine);
Common::DumpFile output;
output.open(argv[2]);
output.writeUint32BE(MKTAG('R', 'I', 'F', 'F'));
output.writeUint32LE(file.size() * 2 + 36);
output.writeUint32BE(MKTAG('W', 'A', 'V', 'E'));
output.writeUint32BE(MKTAG('f', 'm', 't', ' '));
output.writeUint32LE(16);
output.writeUint16LE(1);
uint16 numChannels;
if (audioStream->isStereo()) {
numChannels = 2;
output.writeUint16LE(2);
} else {
numChannels = 1;
output.writeUint16LE(1);
}
output.writeUint32LE(audioStream->getRate());
output.writeUint32LE(audioStream->getRate() * numChannels * 2);
output.writeUint16LE(numChannels * 2);
output.writeUint16LE(16);
output.writeUint32BE(MKTAG('d', 'a', 't', 'a'));
output.writeUint32LE(file.size() * 2);
int16 *buffer = new int16[file.size()];
audioStream->readBuffer(buffer, file.size());
#ifndef SCUMM_LITTLE_ENDIAN
for (int i = 0; i < file.size(); ++i)
buffer[i] = TO_LE_16(buffer[i]);
#endif
output.write(buffer, file.size() * 2);
delete[] buffer;
return true;
}
bool Console::cmdSetRenderState(int argc, const char **argv) {
if (argc != 2) {
debugPrintf("Use %s <RenderState: panorama, tilt, flat> to change the current render state\n", argv[0]);
return true;
}
Common::String renderState(argv[1]);
if (renderState.matchString("panorama", true))
_engine->getRenderManager()->getRenderTable()->setRenderState(RenderTable::PANORAMA);
else if (renderState.matchString("tilt", true))
_engine->getRenderManager()->getRenderTable()->setRenderState(RenderTable::TILT);
else if (renderState.matchString("flat", true))
_engine->getRenderManager()->getRenderTable()->setRenderState(RenderTable::FLAT);
else
debugPrintf("Use %s <RenderState: panorama, tilt, flat> to change the current render state\n", argv[0]);
return true;
}
bool Console::cmdGenerateRenderTable(int argc, const char **argv) {
_engine->getRenderManager()->getRenderTable()->generateRenderTable();
return true;
}
bool Console::cmdSetPanoramaFoV(int argc, const char **argv) {
if (argc != 2) {
debugPrintf("Use %s <fieldOfView> to change the current panorama field of view\n", argv[0]);
return true;
}
_engine->getRenderManager()->getRenderTable()->setPanoramaFoV(atof(argv[1]));
return true;
}
bool Console::cmdSetPanoramaScale(int argc, const char **argv) {
if (argc != 2) {
debugPrintf("Use %s <scale> to change the current panorama scale\n", argv[0]);
return true;
}
_engine->getRenderManager()->getRenderTable()->setPanoramaScale(atof(argv[1]));
return true;
}
bool Console::cmdLocation(int argc, const char **argv) {
Location curLocation = _engine->getScriptManager()->getCurrentLocation();
Common::String scrFile = Common::String::format("%c%c%c%c.scr", curLocation.world, curLocation.room, curLocation.node, curLocation.view);
debugPrintf("Current location: world '%c', room '%c', node '%c', view '%c', offset %d, script %s\n",
curLocation.world, curLocation.room, curLocation.node, curLocation.view, curLocation.offset, scrFile.c_str());
if (argc != 6) {
debugPrintf("Use %s <char: world> <char: room> <char:node> <char:view> <int: x offset> to change your location\n", argv[0]);
return true;
}
_engine->getScriptManager()->changeLocation(*(argv[1]), *(argv[2]), *(argv[3]), *(argv[4]), atoi(argv[5]));
return true;
}
void dumpFile(Common::SeekableReadStream *s, const Common::Path &outName) {
byte *buffer = new byte[s->size()];
s->read(buffer, s->size());
Common::DumpFile dumpFile;
dumpFile.open(outName);
dumpFile.write(buffer, s->size());
dumpFile.flush();
dumpFile.close();
delete[] buffer;
}
bool Console::cmdDumpFile(int argc, const char **argv) {
if (argc != 2) {
debugPrintf("Use %s <fileName> to dump a file\n", argv[0]);
return true;
}
Common::File f;
if (!f.open(argv[1])) {
warning("File not found: %s", argv[1]);
return true;
}
dumpFile(&f, argv[1]);
return true;
}
bool Console::cmdDumpFiles(int argc, const char **argv) {
Common::Path fileName;
Common::SeekableReadStream *in;
if (argc != 2) {
debugPrintf("Use %s <file extension> to dump all files with a specific extension\n", argv[0]);
return true;
}
Common::ArchiveMemberList fileList;
Common::Path pattern;
pattern = Common::Path(Common::String::format("*.%s", argv[1]));
SearchMan.listMatchingMembers(fileList, pattern);
for (auto &file : fileList) {
fileName = file.get()->getFileName();
debugPrintf("Dumping %s\n", fileName.toString().c_str());
in = file.get()->createReadStream();
if (in)
dumpFile(in, fileName);
else
debugPrintf("Failed to dump!");
delete in;
}
return true;
}
bool Console::cmdDumpImage(int argc, const char **argv) {
if (argc != 2) {
debugPrintf("Use %s <TGA/TGZ name> to dump a Z-Vision TGA/TGZ image into a regular BMP image\n", argv[0]);
return true;
}
Common::Path fileName = Common::Path(argv[1], Common::Path::kNativeSeparator);
Common::String baseName(fileName.baseName());
if (!baseName.hasSuffix(".tga")) {
debugPrintf("%s is not an image file", argv[1]);
}
Common::File f;
if (!f.open(fileName)) {
warning("File not found: %s", argv[1]);
return true;
}
Graphics::Surface surface;
_engine->getRenderManager()->readImageToSurface(fileName, surface, false);
// Open file
Common::DumpFile out;
baseName.setChar('b', baseName.size() - 3);
baseName.setChar('m', baseName.size() - 2);
baseName.setChar('p', baseName.size() - 1);
out.open(fileName.getParent().appendComponent(baseName));
// Write BMP header
out.writeByte('B');
out.writeByte('M');
out.writeUint32LE(surface.h * surface.pitch + 54);
out.writeUint32LE(0);
out.writeUint32LE(54);
out.writeUint32LE(40);
out.writeUint32LE(surface.w);
out.writeUint32LE(surface.h);
out.writeUint16LE(1);
out.writeUint16LE(16);
out.writeUint32LE(0);
out.writeUint32LE(0);
out.writeUint32LE(0);
out.writeUint32LE(0);
out.writeUint32LE(0);
out.writeUint32LE(0);
// Write pixel data to BMP
out.write(surface.getPixels(), surface.pitch * surface.h);
out.flush();
out.close();
surface.free();
return true;
}
bool Console::cmdStateValue(int argc, const char **argv) {
if (argc < 2) {
debugPrintf("Use %s <valuenum> to show the value of a state variable\n", argv[0]);
debugPrintf("Use %s <valuenum> <newvalue> to set the value of a state variable\n", argv[0]);
return true;
}
int valueNum = atoi(argv[1]);
int newValue = (argc > 2) ? atoi(argv[2]) : -1;
if (argc == 2)
debugPrintf("[%d] = %d\n", valueNum, _engine->getScriptManager()->getStateValue(valueNum));
else if (argc == 3)
_engine->getScriptManager()->setStateValue(valueNum, newValue);
return true;
}
bool Console::cmdStateFlag(int argc, const char **argv) {
if (argc < 2) {
debugPrintf("Use %s <flagnum> to show the value of a state flag\n", argv[0]);
debugPrintf("Use %s <flagnum> <newvalue> to set the value of a state flag\n", argv[0]);
return true;
}
int valueNum = atoi(argv[1]);
int newValue = (argc > 2) ? atoi(argv[2]) : -1;
if (argc == 2)
debugPrintf("[%d] = %d\n", valueNum, _engine->getScriptManager()->getStateFlag(valueNum));
else if (argc == 3)
_engine->getScriptManager()->setStateFlag(valueNum, newValue);
return true;
}
} // End of namespace ZVision

View File

@@ -0,0 +1,55 @@
/* 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 ZVISION_CONSOLE_H
#define ZVISION_CONSOLE_H
#include "gui/debugger.h"
namespace ZVision {
class ZVision;
class Console : public GUI::Debugger {
public:
Console(ZVision *engine);
~Console() override {}
private:
ZVision *_engine;
bool cmdLoadVideo(int argc, const char **argv);
bool cmdLoadSound(int argc, const char **argv);
bool cmdRawToWav(int argc, const char **argv);
bool cmdSetRenderState(int argc, const char **argv);
bool cmdGenerateRenderTable(int argc, const char **argv);
bool cmdSetPanoramaFoV(int argc, const char **argv);
bool cmdSetPanoramaScale(int argc, const char **argv);
bool cmdLocation(int argc, const char **argv);
bool cmdDumpFile(int argc, const char **argv);
bool cmdDumpFiles(int argc, const char **argv);
bool cmdDumpImage(int argc, const char **argv);
bool cmdStateValue(int argc, const char **argv);
bool cmdStateFlag(int argc, const char **argv);
};
} // End of namespace ZVision
#endif

View File

@@ -0,0 +1,493 @@
/* 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 "audio/mixer.h"
#include "common/config-manager.h"
#include "common/events.h"
#include "common/scummsys.h"
#include "common/stream.h"
#include "common/system.h"
#include "common/rational.h"
#include "engines/util.h"
#include "zvision/detection.h"
#include "zvision/zvision.h"
#include "zvision/core/console.h"
#include "zvision/graphics/cursors/cursor_manager.h"
#include "zvision/graphics/render_manager.h"
#include "zvision/scripting/script_manager.h"
#include "zvision/scripting/menu.h"
#include "zvision/sound/zork_raw.h"
#include "zvision/text/string_manager.h"
#include "zvision/text/subtitle_manager.h"
namespace ZVision {
void ZVision::pushKeyToCheatBuf(uint8 key) {
for (int i = 0; i < KEYBUF_SIZE - 1; i++)
_cheatBuffer[i] = _cheatBuffer[i + 1];
_cheatBuffer[KEYBUF_SIZE - 1] = key;
}
bool ZVision::checkCode(const char *code) {
int codeLen = strlen(code);
if (codeLen > KEYBUF_SIZE)
return false;
for (int i = 0; i < codeLen; i++)
if (code[i] != _cheatBuffer[KEYBUF_SIZE - codeLen + i] && code[i] != '?')
return false;
return true;
}
uint8 ZVision::getBufferedKey(uint8 pos) {
if (pos >= KEYBUF_SIZE)
return 0;
else
return _cheatBuffer[KEYBUF_SIZE - pos - 1];
}
void ZVision::cheatCodes(uint8 key) {
Location loc = _scriptManager->getCurrentLocation();
// Do not process cheat codes while in the game menus
if (loc.world == 'g' && loc.room == 'j')
return;
pushKeyToCheatBuf(key);
if (getGameId() == GID_GRANDINQUISITOR) {
if (checkCode("IMNOTDEAF")) {
// Unknown cheat
_subtitleManager->showDebugMsg(Common::String::format("IMNOTDEAF cheat or debug, not implemented"));
}
if (checkCode("3100OPB")) {
_subtitleManager->showDebugMsg(Common::String::format("Current location: %c%c%c%c",
_scriptManager->getStateValue(StateKey_World),
_scriptManager->getStateValue(StateKey_Room),
_scriptManager->getStateValue(StateKey_Node),
_scriptManager->getStateValue(StateKey_View)));
}
if (checkCode("KILLMENOW")) {
_scriptManager->changeLocation('g', 'j', 'd', 'e', 0);
_scriptManager->setStateValue(2201, 35);
}
if (checkCode("MIKESPANTS")) {
_scriptManager->changeLocation('g', 'j', 't', 'm', 0);
}
// There are 3 more cheats in script files:
// - "WHOAMI": gjcr.scr
// - "HUISOK": hp1e.scr
// - "EAT ME": uh1f.scr
} else if (getGameId() == GID_NEMESIS) {
if (checkCode("CHLOE")) {
_scriptManager->changeLocation('t', 'm', '2', 'g', 0);
_scriptManager->setStateValue(224, 1);
}
if (checkCode("77MASSAVE")) {
_subtitleManager->showDebugMsg(Common::String::format("Current location: %c%c%c%c",
_scriptManager->getStateValue(StateKey_World),
_scriptManager->getStateValue(StateKey_Room),
_scriptManager->getStateValue(StateKey_Node),
_scriptManager->getStateValue(StateKey_View)));
}
if (checkCode("IDKFA")) {
_scriptManager->changeLocation('t', 'w', '3', 'f', 0);
_scriptManager->setStateValue(249, 1);
}
if (checkCode("309NEWDORMA")) {
_scriptManager->changeLocation('g', 'j', 'g', 'j', 0);
}
if (checkCode("HELLOSAILOR")) {
Audio::AudioStream *soundStream;
if (loc == "vb10") {
soundStream = makeRawZorkStream("v000hpta.raw", this);
} else {
soundStream = makeRawZorkStream("v000hnta.raw", this);
}
Audio::SoundHandle handle;
_mixer->playStream(Audio::Mixer::kPlainSoundType, &handle, soundStream);
}
}
if (checkCode("FRAME")) {
Common::String fpsStr = Common::String::format("FPS: %d", getFPS());
_subtitleManager->showDebugMsg(fpsStr);
}
if (checkCode("COMPUTERARCH"))
_subtitleManager->showDebugMsg("COMPUTERARCH: var-viewer not implemented");
// This cheat essentially toggles the GOxxxx cheat below
if (checkCode("XYZZY"))
_scriptManager->setStateValue(StateKey_DebugCheats, 1 - _scriptManager->getStateValue(StateKey_DebugCheats));
if (_scriptManager->getStateValue(StateKey_DebugCheats) == 1)
if (checkCode("GO????"))
_scriptManager->changeLocation(getBufferedKey(3),
getBufferedKey(2),
getBufferedKey(1),
getBufferedKey(0), 0);
// Show the Venus screen when "?" or "/" is pressed while inside the temple world
if (_scriptManager->getStateValue(StateKey_VenusEnable) == 1)
if (getBufferedKey(0) == 0xBF && _scriptManager->getStateValue(StateKey_World) == 't')
_scriptManager->changeLocation('g', 'j', 'h', 'e', 0);
}
void ZVision::processEvents() {
while (_eventMan->pollEvent(_event)) {
switch (_event.type) {
case Common::EVENT_LBUTTONDOWN:
_cursorManager->cursorDown(true);
_menu->onMouseDown(_event.mouse);
if (!_menu->inMenu() || !_widescreen) {
_scriptManager->setStateValue(StateKey_LMouse, 1);
_scriptManager->addEvent(_event);
}
break;
case Common::EVENT_LBUTTONUP:
_cursorManager->cursorDown(false);
_menu->onMouseUp(_event.mouse);
if (!_menu->inMenu() || !_widescreen) {
_scriptManager->setStateValue(StateKey_LMouse, 0);
_scriptManager->addEvent(_event);
}
break;
case Common::EVENT_RBUTTONDOWN:
_cursorManager->cursorDown(true);
if (!_menu->inMenu() || !_widescreen) {
_scriptManager->setStateValue(StateKey_RMouse, 1);
if (getGameId() == GID_NEMESIS)
_scriptManager->inventoryCycle();
}
break;
case Common::EVENT_RBUTTONUP:
_cursorManager->cursorDown(false);
if (!_menu->inMenu() || !_widescreen)
_scriptManager->setStateValue(StateKey_RMouse, 0);
break;
case Common::EVENT_MOUSEMOVE:
onMouseMove(_event.mouse);
break;
case Common::EVENT_CUSTOM_ENGINE_ACTION_START:
switch ((ZVisionAction)_event.customType) {
case kZVisionActionLeft:
case kZVisionActionRight:
if (_renderManager->getRenderTable()->getRenderState() == RenderTable::PANORAMA)
_keyboardVelocity = (_event.customType == kZVisionActionLeft ?
-_scriptManager->getStateValue(StateKey_KbdRotateSpeed) :
_scriptManager->getStateValue(StateKey_KbdRotateSpeed)) * 2;
break;
case kZVisionActionUp:
case kZVisionActionDown:
if (_renderManager->getRenderTable()->getRenderState() == RenderTable::TILT)
_keyboardVelocity = (_event.customType == kZVisionActionUp ?
-_scriptManager->getStateValue(StateKey_KbdRotateSpeed) :
_scriptManager->getStateValue(StateKey_KbdRotateSpeed)) * 2;
break;
case kZVisionActionSave:
if (_menu->getEnable(kMainMenuSave))
_scriptManager->changeLocation('g', 'j', 's', 'e', 0);
break;
case kZVisionActionRestore:
if (_menu->getEnable(kMainMenuLoad))
_scriptManager->changeLocation('g', 'j', 'r', 'e', 0);
break;
case kZVisionActionPreferences:
if (_menu->getEnable(kMainMenuPrefs))
_scriptManager->changeLocation('g', 'j', 'p', 'e', 0);
break;
case kZVisionActionQuit:
if (_menu->getEnable(kMainMenuExit)) {
if (ConfMan.hasKey("confirm_exit") && ConfMan.getBool("confirm_exit"))
quit(true);
else
quit(false);
}
break;
case kZVisionActionShowFPS: {
Common::String fpsStr = Common::String::format("FPS: %d", getFPS());
_subtitleManager->showDebugMsg(fpsStr);
}
break;
default:
break;
}
break;
case Common::EVENT_CUSTOM_ENGINE_ACTION_END:
switch ((ZVisionAction)_event.customType) {
case kZVisionActionLeft:
case kZVisionActionRight:
if (_renderManager->getRenderTable()->getRenderState() == RenderTable::PANORAMA)
_keyboardVelocity = 0;
break;
case kZVisionActionUp:
case kZVisionActionDown:
if (_renderManager->getRenderTable()->getRenderState() == RenderTable::TILT)
_keyboardVelocity = 0;
break;
default:
break;
}
break;
case Common::EVENT_KEYDOWN: {
uint8 vkKey = getZvisionKey(_event.kbd.keycode);
_scriptManager->setStateValue(StateKey_KeyPress, vkKey);
_scriptManager->addEvent(_event);
cheatCodes(vkKey);
}
break;
case Common::EVENT_KEYUP:
_scriptManager->addEvent(_event);
break;
default:
break;
}
}
}
void ZVision::onMouseMove(const Common::Point &pos) {
debugC(6, kDebugEvent, "ZVision::onMouseMove()");
_menu->onMouseMove(pos);
Common::Point imageCoord(_renderManager->screenSpaceToImageSpace(pos));
Common::Rect workingArea = _renderManager->getWorkingArea();
bool cursorWasChanged = false;
// Graph of the function governing rotation velocity:
//
// |---------------- working window ------------------|
// ^ |---------|
// | |
// +Max velocity | rotation screen edge offset
// | /|
// | / |
// | / |
// | / |
// | / |
// | / |
// | / |
// | / |
// | / |
// Zero velocity |______________________________ ______________________________/_________|__________________________>
// | Position -> | /
// | | /
// | | /
// | | /
// | | /
// | | /
// | | /
// | | /
// | | /
// -Max velocity | |/
// |
// |
// ^
// Clip the horizontal mouse position to the working window
debugC(6, kDebugEvent, "Mouse pos.x, %d, clipping with %d+1, %d+1", pos.x, workingArea.left, workingArea.right);
Common::Point clippedPos = pos;
clippedPos.x = CLIP<int16>(pos.x, workingArea.left + 1, workingArea.right - 1);
if (workingArea.contains(clippedPos) && !_menu->inMenu()) {
cursorWasChanged = _scriptManager->onMouseMove(clippedPos, imageCoord);
RenderTable::RenderState renderState = _renderManager->getRenderTable()->getRenderState();
switch (renderState) {
case RenderTable::PANORAMA:
if (clippedPos.x >= workingArea.left && clippedPos.x < workingArea.left + ROTATION_SCREEN_EDGE_OFFSET) {
int16 mspeed = _scriptManager->getStateValue(StateKey_RotateSpeed) >> 4;
if (mspeed <= 0)
mspeed = 25;
_mouseVelocity = MIN(((Common::Rational(mspeed, ROTATION_SCREEN_EDGE_OFFSET) * (clippedPos.x - workingArea.left)) - mspeed).toInt(), -1);
_cursorManager->changeCursor(CursorIndex_Left);
cursorWasChanged = true;
} else if (clippedPos.x <= workingArea.right && clippedPos.x > workingArea.right - ROTATION_SCREEN_EDGE_OFFSET) {
int16 mspeed = _scriptManager->getStateValue(StateKey_RotateSpeed) >> 4;
if (mspeed <= 0)
mspeed = 25;
_mouseVelocity = MAX((Common::Rational(mspeed, ROTATION_SCREEN_EDGE_OFFSET) * (clippedPos.x - workingArea.right + ROTATION_SCREEN_EDGE_OFFSET)).toInt(), 1);
_cursorManager->changeCursor(CursorIndex_Right);
cursorWasChanged = true;
} else
_mouseVelocity = 0;
break;
case RenderTable::TILT:
if (clippedPos.y >= workingArea.top && clippedPos.y < workingArea.top + ROTATION_SCREEN_EDGE_OFFSET) {
int16 mspeed = _scriptManager->getStateValue(StateKey_RotateSpeed) >> 4;
if (mspeed <= 0)
mspeed = 25;
_mouseVelocity = MIN(((Common::Rational(mspeed, ROTATION_SCREEN_EDGE_OFFSET) * (pos.y - workingArea.top)) - mspeed).toInt(), -1);
_cursorManager->changeCursor(CursorIndex_UpArr);
cursorWasChanged = true;
} else if (clippedPos.y <= workingArea.bottom && clippedPos.y > workingArea.bottom - ROTATION_SCREEN_EDGE_OFFSET) {
int16 mspeed = _scriptManager->getStateValue(StateKey_RotateSpeed) >> 4;
if (mspeed <= 0)
mspeed = 25;
_mouseVelocity = MAX((Common::Rational(MAX_ROTATION_SPEED, ROTATION_SCREEN_EDGE_OFFSET) * (pos.y - workingArea.bottom + ROTATION_SCREEN_EDGE_OFFSET)).toInt(), 1);
_cursorManager->changeCursor(CursorIndex_DownArr);
cursorWasChanged = true;
} else
_mouseVelocity = 0;
break;
case RenderTable::FLAT:
default:
_mouseVelocity = 0;
break;
}
} else
_mouseVelocity = 0;
if (!cursorWasChanged)
_cursorManager->changeCursor(CursorIndex_Idle);
}
uint8 ZVision::getZvisionKey(Common::KeyCode scummKeyCode) {
if (scummKeyCode >= Common::KEYCODE_a && scummKeyCode <= Common::KEYCODE_z)
return 0x41 + scummKeyCode - Common::KEYCODE_a;
if (scummKeyCode >= Common::KEYCODE_0 && scummKeyCode <= Common::KEYCODE_9)
return 0x30 + scummKeyCode - Common::KEYCODE_0;
if (scummKeyCode >= Common::KEYCODE_F1 && scummKeyCode <= Common::KEYCODE_F15)
return 0x70 + scummKeyCode - Common::KEYCODE_F1;
if (scummKeyCode >= Common::KEYCODE_KP0 && scummKeyCode <= Common::KEYCODE_KP9)
return 0x60 + scummKeyCode - Common::KEYCODE_KP0;
switch (scummKeyCode) {
case Common::KEYCODE_BACKSPACE:
return 0x8;
case Common::KEYCODE_TAB:
return 0x9;
case Common::KEYCODE_CLEAR:
return 0xC;
case Common::KEYCODE_RETURN:
return 0xD;
case Common::KEYCODE_CAPSLOCK:
return 0x14;
case Common::KEYCODE_ESCAPE:
return 0x1B;
case Common::KEYCODE_SPACE:
return 0x20;
case Common::KEYCODE_PAGEUP:
return 0x21;
case Common::KEYCODE_PAGEDOWN:
return 0x22;
case Common::KEYCODE_END:
return 0x23;
case Common::KEYCODE_HOME:
return 0x24;
case Common::KEYCODE_LEFT:
return 0x25;
case Common::KEYCODE_UP:
return 0x26;
case Common::KEYCODE_RIGHT:
return 0x27;
case Common::KEYCODE_DOWN:
return 0x28;
case Common::KEYCODE_PRINT:
return 0x2A;
case Common::KEYCODE_INSERT:
return 0x2D;
case Common::KEYCODE_DELETE:
return 0x2E;
case Common::KEYCODE_HELP:
return 0x2F;
case Common::KEYCODE_KP_MULTIPLY:
return 0x6A;
case Common::KEYCODE_KP_PLUS:
return 0x6B;
case Common::KEYCODE_KP_MINUS:
return 0x6D;
case Common::KEYCODE_KP_PERIOD:
return 0x6E;
case Common::KEYCODE_KP_DIVIDE:
return 0x6F;
case Common::KEYCODE_NUMLOCK:
return 0x90;
case Common::KEYCODE_SCROLLOCK:
return 0x91;
case Common::KEYCODE_LSHIFT:
return 0xA0;
case Common::KEYCODE_RSHIFT:
return 0xA1;
case Common::KEYCODE_LCTRL:
return 0xA2;
case Common::KEYCODE_RCTRL:
return 0xA3;
case Common::KEYCODE_MENU:
return 0xA5;
case Common::KEYCODE_LEFTBRACKET:
return 0xDB;
case Common::KEYCODE_RIGHTBRACKET:
return 0xDD;
case Common::KEYCODE_SEMICOLON:
return 0xBA;
case Common::KEYCODE_BACKSLASH:
return 0xDC;
case Common::KEYCODE_QUOTE:
return 0xDE;
case Common::KEYCODE_SLASH:
return 0xBF;
case Common::KEYCODE_TILDE:
return 0xC0;
case Common::KEYCODE_COMMA:
return 0xBC;
case Common::KEYCODE_PERIOD:
return 0xBE;
case Common::KEYCODE_MINUS:
return 0xBD;
case Common::KEYCODE_PLUS:
return 0xBB;
default:
return 0;
}
return 0;
}
bool ZVision::quit(bool askFirst, bool streaming) {
debugC(1, kDebugEvent, "ZVision::quit()");
if (askFirst)
if (!_subtitleManager->askQuestion(_stringManager->getTextLine(StringManager::ZVISION_STR_EXITPROMT), streaming, true)) {
debugC(1, kDebugEvent, "~ZVision::quit()");
return false;
}
//quitGame();
_breakMainLoop = true;
debugC(1, kDebugEvent, "~ZVision::quit()");
return true;
}
} // End of namespace ZVision

View File

@@ -0,0 +1,6 @@
begin_section("Z-Vision");
add_person("Adrian Astley", "RichieSams", "");
add_person("Filippos Karapetis", "bluegr", "");
add_person("Anton Yarcev", "Zidane", "");
add_person("Thomas N McEwan", "tnm23", "Widescreen mod, HQ panoramas, 3D audio enhancement");
end_section();

View File

@@ -0,0 +1,76 @@
/* 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 "base/plugins.h"
#include "common/debug-channels.h"
#include "common/scummsys.h"
#include "common/str-array.h"
#include "engines/advancedDetector.h"
#include "zvision/detection.h"
#include "zvision/detection_tables.h"
namespace ZVision {
static const DebugChannelDef debugFlagList[] = {
{ ZVision::kDebugScript, "Script", "Show debug messages for script file parsing" },
{ ZVision::kDebugLoop, "Loop", "Show debug messages for main game logic cycle" },
{ ZVision::kDebugPuzzle, "Puzzle", "Show debug messages for puzzle processing" },
{ ZVision::kDebugAction, "Action", "Show debug messages for action processing" },
{ ZVision::kDebugControl, "Control", "Show debug messages for control processing" },
{ ZVision::kDebugEffect, "Effect", "Show debug messages for effect processing" },
{ ZVision::kDebugGraphics, "Graphics", "Show debug messages for graphics compositing & rendering" },
{ ZVision::kDebugVideo, "Video", "Show debug messages for video decoding & playback" },
{ ZVision::kDebugSound, "Sound", "Show debug messages for sound & music" },
{ ZVision::kDebugSubtitle, "Subtitle", "Show debug messages for subtitles" },
{ ZVision::kDebugFile, "File", "Show debug messages for file search & load operations" },
{ ZVision::kDebugMouse, "Mouse", "Print coordinates of mouse clicks"},
{ ZVision::kDebugAssign, "Assign", "Print new slotkey values when changed by an assignment action"},
{ ZVision::kDebugEvent, "Event", "Show debug messages for event processing"},
DEBUG_CHANNEL_END
};
class ZVisionMetaEngineDetection : public AdvancedMetaEngineDetection<ZVision::ZVisionGameDescription> {
public:
ZVisionMetaEngineDetection() : AdvancedMetaEngineDetection(ZVision::gameDescriptions, ZVision::zVisionGames) {
_maxScanDepth = 2;
_directoryGlobs = ZVision::directoryGlobs;
}
const char *getName() const override {
return "zvision";
}
const char *getEngineName() const override {
return "Z-Vision";
}
const char *getOriginalCopyright() const override {
return "Z-Vision (C) 1996 Activision";
}
const DebugChannelDef *getDebugChannels() const override {
return debugFlagList;
}
};
} // End of namespace ZVision
REGISTER_PLUGIN_STATIC(ZVISION_DETECTION, PLUGIN_TYPE_ENGINE_DETECTION, ZVision::ZVisionMetaEngineDetection);

View File

@@ -0,0 +1,69 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef ZVISION_DETECTION_H
#define ZVISION_DETECTION_H
#include "engines/advancedDetector.h"
namespace ZVision {
enum ZVisionDebugChannels {
kDebugScript = 1,
kDebugLoop,
kDebugPuzzle,
kDebugAction,
kDebugControl,
kDebugEffect,
kDebugGraphics,
kDebugVideo,
kDebugSound,
kDebugSubtitle,
kDebugFile,
kDebugMouse,
kDebugAssign,
kDebugEvent
};
enum ZVisionGameId {
GID_NONE = 0,
GID_NEMESIS = 1,
GID_GRANDINQUISITOR = 2
};
struct ZVisionGameDescription {
AD_GAME_DESCRIPTION_HELPERS(desc);
ADGameDescription desc;
ZVisionGameId gameId;
};
#define GAMEOPTION_ORIGINAL_SAVELOAD GUIO_GAMEOPTIONS1
#define GAMEOPTION_DOUBLE_FPS GUIO_GAMEOPTIONS2
#define GAMEOPTION_ENABLE_VENUS GUIO_GAMEOPTIONS3
#define GAMEOPTION_DISABLE_ANIM_WHILE_TURNING GUIO_GAMEOPTIONS4
#define GAMEOPTION_USE_HIRES_MPEG_MOVIES GUIO_GAMEOPTIONS5
#define GAMEOPTION_ENABLE_WIDESCREEN GUIO_GAMEOPTIONS6
#define GAMEOPTION_HQ_PANORAMA GUIO_GAMEOPTIONS7
} // End of namespace ZVision
#endif // ZVISION_DETECTION_H

View File

@@ -0,0 +1,268 @@
/* 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 ZVISION_DETECTION_TABLES_H
#define ZVISION_DETECTION_TABLES_H
#include "common/translation.h"
namespace ZVision {
static const PlainGameDescriptor zVisionGames[] = {
{ "znemesis", "Zork Nemesis: The Forbidden Lands" },
{ "zgi", "Zork: Grand Inquisitor" },
{ 0, 0 }
};
static const char *const directoryGlobs[] = {
"znemscr",
"taunts", // zgi mac
0
};
static const ZVisionGameDescription gameDescriptions[] = {
{
// Zork Nemesis English version
{
"znemesis",
0,
{
{ "CSCR.ZFS", 0, "88226e51a205d2e50c67a5237f3bd5f2", 2397741 },
{ "ASCR.ZFS", 0, "9a1e1a48a56cf12a22bad2d2e47f6c92", 917452 },
AD_LISTEND
},
Common::EN_ANY,
Common::kPlatformDOS,
ADGF_NO_FLAGS,
GUIO6(GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_DOUBLE_FPS, GAMEOPTION_ENABLE_VENUS, GAMEOPTION_DISABLE_ANIM_WHILE_TURNING, GAMEOPTION_ENABLE_WIDESCREEN, GAMEOPTION_HQ_PANORAMA)
},
GID_NEMESIS
},
{
// Zork Nemesis French version
{
"znemesis",
0,
AD_ENTRY2s("CSCR.ZFS", "f04113357b4748c13efcb58b4629887c", 2577873,
"NEMESIS.STR", "333bcb17bbb7f57cae742fbbe44f56f3", 9219),
Common::FR_FRA,
Common::kPlatformDOS,
ADGF_NO_FLAGS,
GUIO6(GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_DOUBLE_FPS, GAMEOPTION_ENABLE_VENUS, GAMEOPTION_DISABLE_ANIM_WHILE_TURNING, GAMEOPTION_ENABLE_WIDESCREEN, GAMEOPTION_HQ_PANORAMA)
},
GID_NEMESIS
},
{
// Zork Nemesis German version
{
"znemesis",
0,
AD_ENTRY2s("CSCR.ZFS", "f04113357b4748c13efcb58b4629887c", 2577873,
"NEMESIS.STR", "3d1a12b907751653866cffc6d4dfb331", 9505),
Common::DE_DEU,
Common::kPlatformDOS,
ADGF_NO_FLAGS,
GUIO6(GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_DOUBLE_FPS, GAMEOPTION_ENABLE_VENUS, GAMEOPTION_DISABLE_ANIM_WHILE_TURNING, GAMEOPTION_ENABLE_WIDESCREEN, GAMEOPTION_HQ_PANORAMA)
},
GID_NEMESIS
},
{
// Zork Nemesis Italian version
{
"znemesis",
0,
AD_ENTRY2s("CSCR.ZFS", "f04113357b4748c13efcb58b4629887c", 2577873,
"NEMESIS.STR", "7c568feca8d9f9ae855c47183612c305", 9061),
Common::IT_ITA,
Common::kPlatformDOS,
ADGF_NO_FLAGS,
GUIO6(GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_DOUBLE_FPS, GAMEOPTION_ENABLE_VENUS, GAMEOPTION_DISABLE_ANIM_WHILE_TURNING, GAMEOPTION_ENABLE_WIDESCREEN, GAMEOPTION_HQ_PANORAMA)
},
GID_NEMESIS
},
{
// Zork Nemesis Korean version
{
"znemesis",
0,
{
{ "CSCR.ZFS", 0, "88226e51a205d2e50c67a5237f3bd5f2", 2397741 },
{ "ASCR.ZFS", 0, "127f59f96be3d13eafac665eeede080d", 765413 },
AD_LISTEND
},
Common::KO_KOR,
Common::kPlatformDOS,
ADGF_NO_FLAGS,
GUIO6(GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_DOUBLE_FPS, GAMEOPTION_ENABLE_VENUS, GAMEOPTION_DISABLE_ANIM_WHILE_TURNING, GAMEOPTION_ENABLE_WIDESCREEN, GAMEOPTION_HQ_PANORAMA)
},
GID_NEMESIS
},
{
// Zork Nemesis English Mac version
// Bugreport #11755
{
"znemesis",
MetaEngineDetection::GAME_NOT_IMPLEMENTED, // Reason for being unsupported
{
{ "CSCR.ZFS", 0, "d:ce26cbb17bfbaa774742b3187262a7c0", 2597635 },
{ "ASCR.ZFS", 0, "d:5ee98db1bf73983eb8148da231342085", 929931 },
AD_LISTEND
},
Common::EN_ANY,
Common::kPlatformMacintosh,
ADGF_UNSUPPORTED,
GUIO6(GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_DOUBLE_FPS, GAMEOPTION_ENABLE_VENUS, GAMEOPTION_DISABLE_ANIM_WHILE_TURNING, GAMEOPTION_ENABLE_WIDESCREEN, GAMEOPTION_HQ_PANORAMA)
},
GID_NEMESIS
},
{
// Zork Nemesis English demo version
{
"znemesis",
"Demo",
AD_ENTRY1s("SCRIPTS.ZFS", "64f1e881394e9462305104f99513c833", 380539),
Common::EN_ANY,
Common::kPlatformWindows,
ADGF_DEMO,
GUIO6(GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_DOUBLE_FPS, GAMEOPTION_ENABLE_VENUS, GAMEOPTION_DISABLE_ANIM_WHILE_TURNING, GAMEOPTION_ENABLE_WIDESCREEN, GAMEOPTION_HQ_PANORAMA)
},
GID_NEMESIS
},
{
// Zork Grand Inquisitor English CD version
{
"zgi",
"CD",
AD_ENTRY1s("SCRIPTS.ZFS", "81efd40ecc3d22531e211368b779f17f", 8336944),
Common::EN_ANY,
Common::kPlatformWindows,
ADGF_NO_FLAGS,
GUIO5(GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_DOUBLE_FPS, GAMEOPTION_DISABLE_ANIM_WHILE_TURNING, GAMEOPTION_ENABLE_WIDESCREEN, GAMEOPTION_HQ_PANORAMA)
},
GID_GRANDINQUISITOR
},
{
// Zork Grand Inquisitor French CD version, reported by ulrichh on IRC
{
"zgi",
"CD",
AD_ENTRY1s("SCRIPTS.ZFS", "4d1ec4ade7ecc9ee9ec591d43ca3d213", 8338133),
Common::FR_FRA,
Common::kPlatformWindows,
ADGF_NO_FLAGS,
GUIO5(GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_DOUBLE_FPS, GAMEOPTION_DISABLE_ANIM_WHILE_TURNING, GAMEOPTION_ENABLE_WIDESCREEN, GAMEOPTION_HQ_PANORAMA)
},
GID_GRANDINQUISITOR
},
{
// Zork Grand Inquisitor German CD version, reported by breit in bug #6760
{
"zgi",
"CD",
AD_ENTRY1s("SCRIPTS.ZFS", "b7ac7e331b9b7f884590b0b325b560c8", 8338133),
Common::DE_DEU,
Common::kPlatformWindows,
ADGF_NO_FLAGS,
GUIO5(GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_DOUBLE_FPS, GAMEOPTION_DISABLE_ANIM_WHILE_TURNING, GAMEOPTION_ENABLE_WIDESCREEN, GAMEOPTION_HQ_PANORAMA)
},
GID_GRANDINQUISITOR
},
{
// Zork Grand Inquisitor Spanish CD version, reported by dianiu in bug #6764
{
"zgi",
"CD",
AD_ENTRY1s("SCRIPTS.ZFS", "5cdc4b99c1134053af135aae71326fd1", 8338141),
Common::ES_ESP,
Common::kPlatformWindows,
ADGF_NO_FLAGS,
GUIO5(GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_DOUBLE_FPS, GAMEOPTION_DISABLE_ANIM_WHILE_TURNING, GAMEOPTION_ENABLE_WIDESCREEN, GAMEOPTION_HQ_PANORAMA)
},
GID_GRANDINQUISITOR
},
{
// Zork Grand Inquisitor Mac CD version, reported by macca8 in bug #11756
{
"zgi",
"CD",
AD_ENTRY2s("SCRIPTS.ZFS", "81efd40ecc3d22531e211368b779f17f", 8336944,
"G0LPH10P.RAW", "c0b1f28b1cd1aaeb83c1a3985401bb14", 24462),
Common::EN_ANY,
Common::kPlatformMacintosh,
ADGF_NO_FLAGS,
GUIO5(GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_DOUBLE_FPS, GAMEOPTION_DISABLE_ANIM_WHILE_TURNING, GAMEOPTION_ENABLE_WIDESCREEN, GAMEOPTION_HQ_PANORAMA)
},
GID_GRANDINQUISITOR
},
{
// Zork Grand Inquisitor English DVD version
{
"zgi",
"DVD",
AD_ENTRY1s("SCRIPTS.ZFS", "03157a3399513bfaaf8dc6d5ab798b36", 8433326),
Common::EN_ANY,
Common::kPlatformWindows,
ADGF_DVD,
#if defined(USE_MPEG2) && defined(USE_A52)
GUIO6(GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_DOUBLE_FPS, GAMEOPTION_DISABLE_ANIM_WHILE_TURNING, GAMEOPTION_USE_HIRES_MPEG_MOVIES, GAMEOPTION_ENABLE_WIDESCREEN, GAMEOPTION_HQ_PANORAMA)
#else
GUIO5(GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_DOUBLE_FPS, GAMEOPTION_DISABLE_ANIM_WHILE_TURNING, GAMEOPTION_ENABLE_WIDESCREEN, GAMEOPTION_HQ_PANORAMA)
#endif
},
GID_GRANDINQUISITOR
},
{
// Zork Grand Inquisitor English demo version
{
"zgi",
"Demo",
AD_ENTRY1s("SCRIPTS.ZFS", "71a2494fd2fb999347deb13401e9b998", 304239),
Common::EN_ANY,
Common::kPlatformWindows,
ADGF_DEMO,
GUIO5(GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_DOUBLE_FPS, GAMEOPTION_DISABLE_ANIM_WHILE_TURNING, GAMEOPTION_ENABLE_WIDESCREEN, GAMEOPTION_HQ_PANORAMA)
},
GID_GRANDINQUISITOR
},
{
AD_TABLE_END_MARKER,
GID_NONE
}
};
} // End of namespace ZVision
#endif

View File

@@ -0,0 +1,266 @@
/* 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/debug.h"
#include "zvision/file/file_manager.h"
#include "zvision/file/zfs_archive.h"
namespace ZVision {
const char* genExcluded[] {"*.dll", "*.ini", "*.exe", "*.isu", "*.inf", "*path*.txt", "r.svr", "*.zix", "*.hlp", "*.gid"};
const char* zgiAlternates[] {
"c000h01q.raw", "cm00h01q.raw", "dm00h01q.raw", "e000h01q.raw", "em00h11p.raw", "em00h50q.raw", "gjnph65p.raw",
"gjnph72p.raw", "h000h01q.raw", "m000h01q.raw", "p000h01q.raw", "q000h01q.raw", "sw00h01q.raw", "t000h01q.raw",
"u000h01q.raw"
};
FileManager::FileManager(ZVision *engine) {
}
Common::File *FileManager::open(const Common::Path &filePath, bool allowSrc) {
debugC(5, kDebugFile, "FileManager::open()");
Common::File *file = new Common::File();
Common::File *out = nullptr;
Common::String fileName = filePath.baseName();
bool open = false;
bool altFound = false;
bool altOpen = false;
bool found = SearchMan.hasFile(filePath);
if(found) {
debugC(5, kDebugFile,"File %s found", fileName.c_str());
open = file->open(filePath);
if(open)
debugC(5, kDebugFile,"File %s opened", fileName.c_str());
}
if (allowSrc) {
Common::File *altFile = new Common::File();
Common::String altName = fileName;
altName.setChar('s', altName.size() - 3);
altName.setChar('r', altName.size() - 2);
altName.setChar('c', altName.size() - 1);
Common::Path altPath = filePath.getParent().appendComponent(altName);
altFound = SearchMan.hasFile(altPath);
if (altFound) {
debugC(5, kDebugFile,"Alternate file %s found", altName.c_str());
altOpen = altFile->open(altPath);
if (altOpen)
debugC(5, kDebugFile,"Alternate file %s opened", altName.c_str());
}
if(altOpen) {
if(open)
out = file->size() < altFile->size() ? altFile : file;
else
out = altFile;
}
else if(open)
out = file;
else {
if (found && altFound)
warning("Found file %s and alternate file %s but unable to open either", fileName.c_str(), altName.c_str());
else if (found)
warning("Found file %s but unable to open; alternate file %s not found", fileName.c_str(), altName.c_str());
else if (altFound)
warning("File %s not found; alternate file %s found but but unable to open", fileName.c_str(), altName.c_str());
else
warning("Unable to find file %s or alternate file %s", fileName.c_str(), altName.c_str());
}
if (out == altFile)
debugC(5, kDebugFile,"Returning alternate file %s", altName.c_str());
else {
if(altOpen)
altFile->close();
delete altFile;
}
}
else {
if(open)
out = file;
else if (found)
warning("File %s found, but unable to open", fileName.c_str());
else
warning("File %s not found", fileName.c_str());
}
if (out == file)
debugC(5, kDebugFile,"Returning file %s", fileName.c_str());
else {
if(open)
file->close();
delete file;
}
return out;
}
bool FileManager::exists(Common::Path filePath, bool allowSrc) {
Common::File file;
if (file.exists(filePath))
return true;
else if (allowSrc) {
if (file.exists(srcPath(filePath)))
return true;
}
return false;
}
Common::Path FileManager::srcPath(Common::Path filePath) {
Common::String name = filePath.baseName();
name.setChar('s', name.size() - 3);
name.setChar('r', name.size() - 2);
name.setChar('c', name.size() - 1);
return filePath.getParent().appendComponent(name);
}
bool FileManager::loadZix(const Common::Path &zixPath, const Common::FSNode &gameDataDir) {
Common::File zixFile;
if (!zixFile.open(zixPath))
return false;
Common::String line;
// Skip first block
while (!zixFile.eos()) {
line = zixFile.readLine();
if (line.matchString("----------*", true))
break;
}
if (zixFile.eos())
error("Corrupt ZIX file: %s", zixPath.toString(Common::Path::kNativeSeparator).c_str());
uint8 archives = 0;
// Parse subdirectories & archives
debugC(1, kDebugFile, "Parsing list of subdirectories & archives in %s", zixPath.toString(Common::Path::kNativeSeparator).c_str());
while (!zixFile.eos()) {
line = zixFile.readLine();
line.trim();
if (line.matchString("----------*", true))
break;
else if (line.matchString("DIR:*", true) || line.matchString("CD0:*", true) || line.matchString("CD1:*", true) || line.matchString("CD2:*", true)) {
line = Common::String(line.c_str() + 5);
for (uint i = 0; i < line.size(); i++)
if (line[i] == '\\')
line.setChar('/', i);
// Check if NEMESIS.ZIX/MEDIUM.ZIX refers to the znemesis folder, and
// check the game root folder instead
if (line.hasPrefix("znemesis/"))
line = Common::String(line.c_str() + 9);
// Check if INQUIS.ZIX refers to the ZGI folder, and check the game
// root folder instead
if (line.hasPrefix("zgi/"))
line = Common::String(line.c_str() + 4);
if (line.hasPrefix("zgi_e/"))
line = Common::String(line.c_str() + 6);
if (line.size() && line[0] == '.')
line.deleteChar(0);
if (line.size() && line[0] == '/')
line.deleteChar(0);
if (line.size() && line.hasSuffix("/"))
line.deleteLastChar();
Common::Path path(line, '/');
if (line.matchString("*.zfs", true)) {
if (!SearchMan.hasArchive(line)) {
path = path.getLastComponent(); //We are using the search manager in "flat" mode, so only filenames are needed
debugC(1, kDebugFile, "Adding archive %s to search manager.", path.toString().c_str());
Common::Archive *arc;
arc = new ZfsArchive(path);
SearchMan.add(line, arc);
}
}
else {
debugC(1, kDebugFile, "Adding directory %s to search manager.", path.toString().c_str());
SearchMan.addSubDirectoryMatching(gameDataDir,path.toString());
}
archives++;
}
}
if (zixFile.eos())
error("Corrupt ZIX file: %s", zixPath.toString(Common::Path::kNativeSeparator).c_str());
//Parse files
debugC(1, kDebugFile, "Parsing list of individual resource files in %s", zixPath.toString(Common::Path::kNativeSeparator).c_str());
while (!zixFile.eos()) {
line = zixFile.readLine();
line.trim();
uint dr = 0;
char buf[32];
if (sscanf(line.c_str(), "%u %s", &dr, buf) == 2) {
if (dr <= archives && dr > 0) {
Common::String name(buf);
bool exclude = false;
bool allowSrc = false;
for (auto excName : genExcluded)
if(name.matchString(excName, true)) {
exclude = true;
break;
}
for (auto altName : zgiAlternates)
if(name.matchString(altName, true)) {
allowSrc = true;
break;
}
if (!exclude) {
Common::Path path(name);
// No need to add file, just verify that it exists
if (allowSrc) {
Common::Path altPath = srcPath(path);
if (!SearchMan.hasFile(path) && !SearchMan.hasFile(altPath))
warning("Missing files %s and/or %s", path.toString().c_str(), altPath.toString().c_str());
else if (SearchMan.hasFile(path))
debugC(5, kDebugFile, "File found: %s", path.toString().c_str());
else
debugC(5, kDebugFile, "Alternate file found: %s", altPath.toString().c_str());
}
else {
if (!SearchMan.hasFile(path))
warning("Missing file %s", path.toString().c_str());
else
debugC(5, kDebugFile, "File found: %s", path.toString().c_str());
}
if (name.matchString("*.zfs", true))
if (!SearchMan.hasArchive(name)) {
Common::Path path_(path);
debugC(kDebugFile, "Adding archive %s to search manager.", path.toString().c_str());
Common::Archive *arc;
arc = new ZfsArchive(path_);
SearchMan.add(name, arc);
}
}
}
}
}
return true;
}
} // End of namespace Zvision

View File

@@ -0,0 +1,46 @@
/* 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/file.h"
#include "common/path.h"
#include "zvision/zvision.h"
#ifndef ZVISION_FILE_MANAGER
#define ZVISION_FILE_MANAGER
namespace ZVision {
class FileManager {
public:
FileManager(ZVision *engine);
~FileManager() {};
bool loadZix(const Common::Path &zixPath, const Common::FSNode &gameDataDir);
Common::File *open(const Common::Path &fileName, bool allowSrc=true); // Wrapper to automatically handle loading of files which may be empty & have an alternate .src file
bool exists(Common::Path filePath, bool allowSrc=true); // Wrapper to automatically handle checking existence of files which may be empty & have an alternate .src file
private:
Common::Path srcPath(Common::Path filePath);
};
} // End of namespace ZVision
#endif

View File

@@ -0,0 +1,101 @@
/* 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"
#include "zvision/file/lzss_read_stream.h"
namespace ZVision {
LzssReadStream::LzssReadStream(Common::SeekableReadStream *source)
: _source(source),
// It's convention to set the starting cursor position to blockSize - 16
_windowCursor(0x0FEE),
_eosFlag(false) {
// All values up to _windowCursor inits by 0x20
memset(_window, 0x20, _windowCursor);
memset(_window + _windowCursor, 0, BLOCK_SIZE - _windowCursor);
}
uint32 LzssReadStream::decompressBytes(byte *destination, uint32 numberOfBytes) {
uint32 destinationCursor = 0;
while (destinationCursor < numberOfBytes) {
byte flagbyte = _source->readByte();
if (_source->eos())
break;
uint mask = 1;
for (int i = 0; i < 8; ++i) {
if ((flagbyte & mask) == mask) {
byte data = _source->readByte();
if (_source->eos()) {
return destinationCursor;
}
_window[_windowCursor] = data;
destination[destinationCursor++] = data;
// Increment and wrap the window cursor
_windowCursor = (_windowCursor + 1) & 0xFFF;
} else {
byte low = _source->readByte();
if (_source->eos()) {
return destinationCursor;
}
byte high = _source->readByte();
if (_source->eos()) {
return destinationCursor;
}
uint16 length = (high & 0xF) + 2;
uint16 offset = low | ((high & 0xF0) << 4);
for (int j = 0; j <= length; ++j) {
byte temp = _window[(offset + j) & 0xFFF];
_window[_windowCursor] = temp;
destination[destinationCursor++] = temp;
_windowCursor = (_windowCursor + 1) & 0xFFF;
}
}
mask = mask << 1;
}
}
return destinationCursor;
}
bool LzssReadStream::eos() const {
return _eosFlag;
}
uint32 LzssReadStream::read(void *dataPtr, uint32 dataSize) {
uint32 bytesRead = decompressBytes(static_cast<byte *>(dataPtr), dataSize);
if (bytesRead < dataSize) {
// Flag that we're at EOS
_eosFlag = true;
}
return dataSize;
}
} // End of namespace ZVision

View File

@@ -0,0 +1,70 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef ZVISION_LZSS_STREAM_H
#define ZVISION_LZSS_STREAM_H
#include "common/array.h"
#include "common/stream.h"
namespace Common {
class SeekableReadStream;
}
namespace ZVision {
class LzssReadStream : public Common::ReadStream {
public:
/**
* A class that decompresses LZSS data and implements ReadStream for easy access
* to the decompiled data.
*
* @param source The source data
*/
LzssReadStream(Common::SeekableReadStream *source);
private:
enum {
BLOCK_SIZE = 0x1000
};
private:
Common::SeekableReadStream *_source;
byte _window[BLOCK_SIZE];
uint _windowCursor;
bool _eosFlag;
public:
bool eos() const override;
uint32 read(void *dataPtr, uint32 dataSize) override;
private:
/**
* Decompress the next <numberOfBytes> from the source stream. Or until EOS
*
* @param numberOfBytes How many bytes to decompress. This is a count of source bytes, not destination bytes
*/
uint32 decompressBytes(byte *destination, uint32 numberOfBytes);
};
}
#endif

View File

@@ -0,0 +1,275 @@
/* 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/file.h"
#include "common/scummsys.h"
#include "common/system.h"
#include "common/translation.h"
#include "graphics/surface.h"
#include "graphics/thumbnail.h"
#include "gui/message.h"
#include "gui/saveload.h"
#include "zvision/zvision.h"
#include "zvision/file/save_manager.h"
#include "zvision/graphics/render_manager.h"
#include "zvision/scripting/script_manager.h"
namespace ZVision {
const uint32 SaveManager::SAVEGAME_ID = MKTAG('Z', 'E', 'N', 'G');
bool SaveManager::scummVMSaveLoadDialog(bool isSave) {
GUI::SaveLoadChooser *dialog;
Common::String desc;
int slot;
if (isSave) {
dialog = new GUI::SaveLoadChooser(_("Save game:"), _("Save"), true);
slot = dialog->runModalWithCurrentTarget();
desc = dialog->getResultString();
if (desc.empty()) {
// create our own description for the saved game, the user didn't enter it
desc = dialog->createDefaultSaveDescription(slot);
}
if (desc.size() > 28)
desc = Common::String(desc.c_str(), 28);
} else {
dialog = new GUI::SaveLoadChooser(_("Restore game:"), _("Restore"), false);
slot = dialog->runModalWithCurrentTarget();
}
delete dialog;
if (slot < 0)
return false;
if (!isSave) {
Common::ErrorCode result = loadGame(slot).getCode();
return (result == Common::kNoError);
}
saveGame(slot, desc, false);
return true;
}
void SaveManager::saveGame(uint slot, const Common::String &saveName, bool useSaveBuffer) {
if (!_tempSave && useSaveBuffer)
return;
Common::SaveFileManager *saveFileManager = g_system->getSavefileManager();
Common::OutSaveFile *file = saveFileManager->openForSaving(_engine->getSaveStateName(slot));
writeSaveGameHeader(file, saveName, useSaveBuffer);
if (useSaveBuffer)
file->write(_tempSave->getData(), _tempSave->size());
else
_engine->getScriptManager()->serialize(file);
file->finalize();
delete file;
if (useSaveBuffer)
flushSaveBuffer();
_lastSaveTime = g_system->getMillis();
}
void SaveManager::writeSaveGameHeader(Common::OutSaveFile *file, const Common::String &saveName, bool useSaveBuffer) {
file->writeUint32BE(SAVEGAME_ID);
// Write version
file->writeByte(SAVE_VERSION);
// Write savegame name
file->writeString(saveName);
file->writeByte(0);
// Save the game thumbnail
if (useSaveBuffer)
file->write(_tempThumbnail->getData(), _tempThumbnail->size());
else
Graphics::saveThumbnail(*file);
// Write out the save date/time
TimeDate td;
g_system->getTimeAndDate(td);
file->writeSint16LE(td.tm_year + 1900);
file->writeSint16LE(td.tm_mon + 1);
file->writeSint16LE(td.tm_mday);
file->writeSint16LE(td.tm_hour);
file->writeSint16LE(td.tm_min);
file->writeUint32LE(g_engine->getTotalPlayTime() / 1000);
}
Common::Error SaveManager::loadGame(int slot) {
Common::SeekableReadStream *saveFile = NULL;
if (slot < 0) {
// Restart game, used by ZGI death screen only
_engine->getScriptManager()->initialize(true);
return Common::kNoError;
}
saveFile = getSlotFile(slot);
if (!saveFile)
return Common::kPathDoesNotExist;
// Read the header
SaveGameHeader header;
if (!readSaveGameHeader(saveFile, header))
return Common::kUnknownError;
ScriptManager *scriptManager = _engine->getScriptManager();
// Update the state table values
scriptManager->deserialize(saveFile);
delete saveFile;
if (_engine->getGameId() == GID_NEMESIS) {
// Zork Nemesis has no in-game option to select panorama quality or animation options
// We set them here to ensure loaded games don't override current game configuration
scriptManager->setStateValue(StateKey_HighQuality, ConfMan.getBool("highquality"));
scriptManager->setStateValue(StateKey_NoTurnAnim, ConfMan.getBool("noanimwhileturning"));
if (scriptManager->getCurrentLocation() == "tv2f") {
// WORKAROUND for script bug #6793: location tv2f (stairs) has two states:
// one at the top of the stairs, and one at the bottom. When the player
// goes to the bottom of the stairs, the screen changes, and hotspot
// 4652 (exit opposite the stairs) is enabled. However, the variable that
// controls the state (2408) is reset when the player goes down the stairs.
// Furthermore, the room's initialization script disables the stair exit
// control (4652). This leads to an impossible situation, where all the
// exit controls are disabled, and the player can't more anywhere. Thus,
// when loading a game in that room, we check for that impossible
// situation, which only occurs after the player has moved down the stairs,
// and fix it here by setting the correct background, and enabling the
// stair exit hotspot.
if ((scriptManager->getStateFlag(2411) & Puzzle::DISABLED) &&
(scriptManager->getStateFlag(2408) & Puzzle::DISABLED) &&
(scriptManager->getStateFlag(4652) & Puzzle::DISABLED)) {
_engine->getRenderManager()->setBackgroundImage("tv2fb21c.tga");
scriptManager->unsetStateFlag(4652, Puzzle::DISABLED);
}
}
}
g_engine->setTotalPlayTime(header.playTime * 1000);
return Common::kNoError;
}
bool SaveManager::readSaveGameHeader(Common::InSaveFile *in, SaveGameHeader &header, bool skipThumbnail) {
header.saveYear = 0;
header.saveMonth = 0;
header.saveDay = 0;
header.saveHour = 0;
header.saveMinutes = 0;
header.playTime = 0;
header.saveName.clear();
header.thumbnail = nullptr;
header.version = 0;
uint32 tag = in->readUint32BE();
// Check if it's original savegame than fill header structure
if (tag == MKTAG('Z', 'N', 'S', 'G')) {
header.saveName = "Original Save";
header.version = SAVE_ORIGINAL;
in->seek(-4, SEEK_CUR);
return true;
}
if (tag != SAVEGAME_ID) {
warning("File is not a Z-Vision saved game. Aborting load");
return false;
}
// Read in the version
header.version = in->readByte();
// Check that the save version isn't newer than this binary
if (header.version > SAVE_VERSION) {
uint tempVersion = header.version;
GUI::MessageDialog dialog(
Common::U32String::format(
_("This saved game uses version %u, but this engine only "
"supports up to version %d. You will need an updated version "
"of the engine to use this saved game."), tempVersion, SAVE_VERSION
));
dialog.runModal();
}
// Read in the save name
char ch;
while ((ch = (char)in->readByte()) != '\0')
header.saveName += ch;
// Get the thumbnail
if (!Graphics::loadThumbnail(*in, header.thumbnail, skipThumbnail)) {
return false;
}
// Read in save date/time
header.saveYear = in->readSint16LE();
header.saveMonth = in->readSint16LE();
header.saveDay = in->readSint16LE();
header.saveHour = in->readSint16LE();
header.saveMinutes = in->readSint16LE();
if (header.version >= 2) {
header.playTime = in->readUint32LE();
}
return true;
}
Common::SeekableReadStream *SaveManager::getSlotFile(uint slot) {
Common::SeekableReadStream *saveFile = g_system->getSavefileManager()->openForLoading(_engine->getSaveStateName(slot));
if (saveFile == NULL) {
// Try to load standard save file
Common::Path filename;
if (_engine->getGameId() == GID_GRANDINQUISITOR)
filename = Common::Path(Common::String::format("inqsav%u.sav", slot));
else if (_engine->getGameId() == GID_NEMESIS)
filename = Common::Path(Common::String::format("nemsav%u.sav", slot));
Common::File *tmpFile = new Common::File;
if (!tmpFile->open(filename))
delete tmpFile;
else
saveFile = tmpFile;
}
return saveFile;
}
void SaveManager::prepareSaveBuffer() {
delete _tempThumbnail;
_tempThumbnail = new Common::MemoryWriteStreamDynamic(DisposeAfterUse::YES);
Graphics::saveThumbnail(*_tempThumbnail);
delete _tempSave;
_tempSave = new Common::MemoryWriteStreamDynamic(DisposeAfterUse::YES);
_engine->getScriptManager()->serialize(_tempSave);
}
void SaveManager::flushSaveBuffer() {
delete _tempThumbnail;
_tempThumbnail = NULL;
delete _tempSave;
_tempSave = NULL;
}
} // End of namespace ZVision

View File

@@ -0,0 +1,103 @@
/* 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 ZVISION_SAVE_MANAGER_H
#define ZVISION_SAVE_MANAGER_H
#include "common/memstream.h"
#include "common/savefile.h"
namespace Common {
class String;
}
namespace Graphics {
struct Surface;
}
namespace ZVision {
class ZVision;
struct SaveGameHeader {
byte version;
Common::String saveName;
Graphics::Surface *thumbnail;
int16 saveYear, saveMonth, saveDay;
int16 saveHour, saveMinutes;
uint32 playTime;
};
class SaveManager {
public:
SaveManager(ZVision *engine) : _engine(engine), _tempSave(NULL), _tempThumbnail(NULL), _lastSaveTime(0) {}
~SaveManager() {
flushSaveBuffer();
}
uint32 getLastSaveTime() const {
return _lastSaveTime;
}
private:
ZVision *_engine;
uint32 _lastSaveTime;
static const uint32 SAVEGAME_ID;
enum {
SAVE_ORIGINAL = 0,
SAVE_VERSION = 2
};
Common::MemoryWriteStreamDynamic *_tempThumbnail;
Common::MemoryWriteStreamDynamic *_tempSave;
public:
/**
* Copies the data from the last auto-save into a new save file. We
* can't use the current state data because the save menu *IS* a room.
* The file is named using ZVision::generateSaveFileName(slot)
*
* @param slot The save slot this save pertains to. Must be [1, 20]
* @param saveName The internal name for this save. This is NOT the name of the actual save file.
*/
void saveGame(uint slot, const Common::String &saveName, bool useSaveBuffer);
/**
* Loads the state data from the save file that slot references. Uses
* ZVision::generateSaveFileName(slot) to get the save file name.
*
* @param slot The save slot to load. Must be [1, 20]
*/
Common::Error loadGame(int slot);
Common::SeekableReadStream *getSlotFile(uint slot);
bool readSaveGameHeader(Common::SeekableReadStream *in, SaveGameHeader &header, bool skipThumbnail = true);
void prepareSaveBuffer();
void flushSaveBuffer();
bool scummVMSaveLoadDialog(bool isSave);
private:
void writeSaveGameHeader(Common::OutSaveFile *file, const Common::String &saveName, bool useSaveBuffer);
};
} // End of namespace ZVision
#endif

View File

@@ -0,0 +1,156 @@
/* 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/debug.h"
#include "common/file.h"
#include "common/memstream.h"
#include "common/scummsys.h"
#include "zvision/detection.h"
#include "zvision/file/zfs_archive.h"
namespace ZVision {
ZfsArchive::ZfsArchive(const Common::Path &fileName) : _fileName(fileName) {
Common::File zfsFile;
memset(&_header, 0, sizeof(_header));
if (!zfsFile.open(_fileName)) {
warning("ZFSArchive::ZFSArchive(): Could not find the archive file");
return;
}
readHeaders(&zfsFile);
debugC(1, kDebugFile, "ZfsArchive::ZfsArchive(%s): Located %d files", _fileName.toString(Common::Path::kNativeSeparator).c_str(), _entryHeaders.size());
}
ZfsArchive::ZfsArchive(const Common::Path &fileName, Common::SeekableReadStream *stream) : _fileName(fileName) {
readHeaders(stream);
debugC(1, kDebugFile, "ZfsArchive::ZfsArchive(%s): Located %d files", _fileName.toString(Common::Path::kNativeSeparator).c_str(), _entryHeaders.size());
}
ZfsArchive::~ZfsArchive() {
debugC(1, kDebugFile, "ZfsArchive Destructor Called");
ZfsEntryHeaderMap::iterator it = _entryHeaders.begin();
for (; it != _entryHeaders.end(); ++it) {
delete it->_value;
}
}
void ZfsArchive::readHeaders(Common::SeekableReadStream *stream) {
// Don't do a straight struct cast since we can't guarantee endianness
_header.magic = stream->readUint32LE();
_header.unknown1 = stream->readUint32LE();
_header.maxNameLength = stream->readUint32LE();
_header.filesPerBlock = stream->readUint32LE();
_header.fileCount = stream->readUint32LE();
_header.xorKey[0] = stream->readByte();
_header.xorKey[1] = stream->readByte();
_header.xorKey[2] = stream->readByte();
_header.xorKey[3] = stream->readByte();
_header.fileSectionOffset = stream->readUint32LE();
uint32 nextOffset;
do {
// Read the offset to the next block
nextOffset = stream->readUint32LE();
// Read in each entry header
for (uint32 i = 0; i < _header.filesPerBlock; ++i) {
ZfsEntryHeader entryHeader;
entryHeader.name = readEntryName(stream);
entryHeader.offset = stream->readUint32LE();
entryHeader.id = stream->readUint32LE();
entryHeader.size = stream->readUint32LE();
entryHeader.time = stream->readUint32LE();
entryHeader.unknown = stream->readUint32LE();
if (entryHeader.size != 0)
_entryHeaders[entryHeader.name] = new ZfsEntryHeader(entryHeader);
}
// Seek to the next block of headers
stream->seek(nextOffset);
} while (nextOffset != 0);
}
Common::String ZfsArchive::readEntryName(Common::SeekableReadStream *stream) const {
// Entry Names are at most 16 bytes and are null padded
char buffer[16];
stream->read(buffer, 16);
return Common::String(buffer);
}
bool ZfsArchive::hasFile(const Common::Path &path) const {
Common::String name = path.toString();
return _entryHeaders.contains(name);
}
int ZfsArchive::listMembers(Common::ArchiveMemberList &list) const {
int matches = 0;
for (ZfsEntryHeaderMap::const_iterator it = _entryHeaders.begin(); it != _entryHeaders.end(); ++it) {
list.push_back(Common::ArchiveMemberList::value_type(new Common::GenericArchiveMember(it->_value->name, *this)));
matches++;
}
return matches;
}
const Common::ArchiveMemberPtr ZfsArchive::getMember(const Common::Path &path) const {
if (!hasFile(path))
return Common::ArchiveMemberPtr();
return Common::ArchiveMemberPtr(new Common::GenericArchiveMember(path, *this));
}
Common::SeekableReadStream *ZfsArchive::createReadStreamForMember(const Common::Path &path) const {
Common::String name = path.toString();
if (!_entryHeaders.contains(name)) {
return 0;
}
ZfsEntryHeader *entryHeader = _entryHeaders[name];
Common::File zfsArchive;
zfsArchive.open(_fileName);
zfsArchive.seek(entryHeader->offset);
// This *HAS* to be malloc (not new[]) because MemoryReadStream uses free() to free the memory
byte *buffer = (byte *)malloc(entryHeader->size);
zfsArchive.read(buffer, entryHeader->size);
// Decrypt the data in place
if (_header.xorKey[0] + _header.xorKey[1] + _header.xorKey[2] + _header.xorKey[3] != 0)
unXor(buffer, entryHeader->size, _header.xorKey);
return new Common::MemoryReadStream(buffer, entryHeader->size, DisposeAfterUse::YES);
}
void ZfsArchive::unXor(byte *buffer, uint32 length, const byte *xorKey) const {
for (uint32 i = 0; i < length; ++i)
buffer[i] ^= xorKey[i % 4];
}
} // End of namespace ZVision

View File

@@ -0,0 +1,124 @@
/* 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 ZVISION_ZFS_ARCHIVE_H
#define ZVISION_ZFS_ARCHIVE_H
#include "common/archive.h"
#include "common/hashmap.h"
#include "common/hash-str.h"
namespace Common {
class String;
}
namespace ZVision {
struct ZfsHeader {
uint32 magic;
uint32 unknown1;
uint32 maxNameLength;
uint32 filesPerBlock;
uint32 fileCount;
uint8 xorKey[4];
uint32 fileSectionOffset;
};
struct ZfsEntryHeader {
Common::String name;
uint32 offset;
uint32 id;
uint32 size;
uint32 time;
uint32 unknown;
};
typedef Common::HashMap<Common::String, ZfsEntryHeader *, Common::IgnoreCase_Hash, Common::IgnoreCase_EqualTo> ZfsEntryHeaderMap;
class ZfsArchive : public Common::Archive {
public:
ZfsArchive(const Common::Path &fileName);
ZfsArchive(const Common::Path &fileName, Common::SeekableReadStream *stream);
~ZfsArchive() override;
/**
* Check if a member with the given name is present in the Archive.
* Patterns are not allowed, as this is meant to be a quick File::exists()
* replacement.
*/
bool hasFile(const Common::Path &path) const override;
/**
* Add all members of the Archive to list.
* Must only append to list, and not remove elements from it.
*
* @return The number of names added to list
*/
int listMembers(Common::ArchiveMemberList &list) const override;
/**
* Returns a ArchiveMember representation of the given file.
*/
const Common::ArchiveMemberPtr getMember(const Common::Path &path) const override;
/**
* Create a stream bound to a member with the specified name in the
* archive. If no member with this name exists, 0 is returned.
*
* @return The newly created input stream
*/
Common::SeekableReadStream *createReadStreamForMember(const Common::Path &path) const override;
private:
const Common::Path _fileName;
ZfsHeader _header;
ZfsEntryHeaderMap _entryHeaders;
/**
* Parses the zfs file into file entry headers that can be used later
* to get the entry data.
*
* @param stream The contents of the zfs file
*/
void readHeaders(Common::SeekableReadStream *stream);
/**
* Entry names are contained within a 16 byte block. This reads the block
* and converts it the name to a Common::String
*
* @param stream The zfs file stream
* @return The entry file name
*/
Common::String readEntryName(Common::SeekableReadStream *stream) const;
/**
* ZFS file entries can be encrypted using XOR encoding. This method
* decodes the buffer in place using the supplied xorKey.
*
* @param buffer The data to decode
* @param length Length of buffer
*/
void unXor(byte *buffer, uint32 length, const byte *xorKey) const;
};
} // End of namespace ZVision
#endif

View File

@@ -0,0 +1,93 @@
/* 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/file.h"
#include "common/scummsys.h"
#include "common/str.h"
#include "zvision/graphics/cursors/cursor.h"
namespace ZVision {
ZorkCursor::ZorkCursor()
: _width(0),
_height(0),
_hotspotX(0),
_hotspotY(0) {
}
ZorkCursor::ZorkCursor(ZVision *engine, const Common::Path &fileName)
: _width(0),
_height(0),
_hotspotX(0),
_hotspotY(0) {
Common::File file;
if (!file.open(fileName))
error("Cursor file %s does not exist", fileName.toString().c_str());
uint32 magic = file.readUint32BE();
if (magic != MKTAG('Z', 'C', 'R', '1')) {
warning("%s is not a Zork Cursor file", fileName.toString().c_str());
return;
}
_hotspotX = file.readUint16LE();
_hotspotY = file.readUint16LE();
_width = file.readUint16LE();
_height = file.readUint16LE();
uint dataSize = _width * _height * sizeof(uint16);
_surface.create(_width, _height, engine->_resourcePixelFormat);
uint32 bytesRead = file.read(_surface.getPixels(), dataSize);
assert(bytesRead == dataSize);
#ifndef SCUMM_LITTLE_ENDIAN
int16 *buffer = (int16 *)_surface.getPixels();
for (uint32 i = 0; i < dataSize / 2; ++i)
buffer[i] = FROM_LE_16(buffer[i]);
#endif
}
ZorkCursor::ZorkCursor(const ZorkCursor &other) {
_width = other._width;
_height = other._height;
_hotspotX = other._hotspotX;
_hotspotY = other._hotspotY;
_surface.copyFrom(other._surface);
}
ZorkCursor &ZorkCursor::operator=(const ZorkCursor &other) {
_width = other._width;
_height = other._height;
_hotspotX = other._hotspotX;
_hotspotY = other._hotspotY;
_surface.free();
_surface.copyFrom(other._surface);
return *this;
}
ZorkCursor::~ZorkCursor() {
_surface.free();
}
} // End of namespace ZVision

View File

@@ -0,0 +1,77 @@
/* 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 ZVISION_CURSOR_H
#define ZVISION_CURSOR_H
#include "graphics/surface.h"
#include "zvision/zvision.h"
namespace Common {
class String;
}
namespace ZVision {
/**
* Utility class to parse and hold cursor data
* Modeled off Graphics::Cursor
*/
class ZorkCursor {
public:
ZorkCursor();
ZorkCursor(ZVision *engine, const Common::Path &fileName);
ZorkCursor(const ZorkCursor &other);
~ZorkCursor();
private:
uint16 _width;
uint16 _height;
uint16 _hotspotX;
uint16 _hotspotY;
Graphics::Surface _surface;
public:
ZorkCursor &operator=(const ZorkCursor &other);
uint16 getWidth() const {
return _width;
}
uint16 getHeight() const {
return _height;
}
uint16 getHotspotX() const {
return _hotspotX;
}
uint16 getHotspotY() const {
return _hotspotY;
}
byte getKeyColor() const {
return 0;
}
const byte *getSurface() const {
return (const byte *)_surface.getPixels();
}
};
} // End of namespace ZVision
#endif

View File

@@ -0,0 +1,153 @@
/* 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"
#include "common/system.h"
#include "graphics/cursorman.h"
#include "graphics/pixelformat.h"
#include "zvision/zvision.h"
#include "zvision/graphics/cursors/cursor_manager.h"
namespace ZVision {
const char *CursorManager::_cursorNames[NUM_CURSORS] = { "active", "arrow", "backward", "downarrow", "forward", "handpt", "handpu", "hdown", "hleft",
"hright", "hup", "idle", "leftarrow", "rightarrow", "suggest_surround", "suggest_tilt", "turnaround", "zuparrow"
};
const char *CursorManager::_zgiCursorFileNames[NUM_CURSORS] = { "g0gbc011.zcr", "g0gac011.zcr", "g0gac021.zcr", "g0gac031.zcr", "g0gac041.zcr", "g0gac051.zcr", "g0gac061.zcr", "g0gac071.zcr", "g0gac081.zcr",
"g0gac091.zcr", "g0gac101.zcr", "g0gac011.zcr", "g0gac111.zcr", "g0gac121.zcr", "g0gac131.zcr", "g0gac141.zcr", "g0gac151.zcr", "g0gac161.zcr"
};
const char *CursorManager::_zNemCursorFileNames[NUM_CURSORS] = { "00act", "arrow", "back", "down", "forw", "handpt", "handpu", "hdown", "hleft",
"hright", "hup", "00idle", "left", "right", "ssurr", "stilt", "turn", "up"
};
CursorManager::CursorManager(ZVision *engine, const Graphics::PixelFormat &pixelFormat)
: _engine(engine),
_pixelFormat(pixelFormat),
_cursorIsPushed(false),
_item(0),
_lastitem(0),
_currentCursor(CursorIndex_Idle) {
for (int i = 0; i < NUM_CURSORS; i++) {
if (_engine->getGameId() == GID_NEMESIS) {
Common::Path name;
if (i == 1) {
// Cursors "arrowa.zcr" and "arrowb.zcr" are missing
_cursors[i][0] = _cursors[i][1] = ZorkCursor();
continue;
}
name = Common::Path(Common::String::format("%sa.zcr", _zNemCursorFileNames[i]));
_cursors[i][0] = ZorkCursor(_engine, name); // Up cursor
name = Common::Path(Common::String::format("%sb.zcr", _zNemCursorFileNames[i]));
_cursors[i][1] = ZorkCursor(_engine, name); // Down cursor
} else if (_engine->getGameId() == GID_GRANDINQUISITOR) {
_cursors[i][0] = ZorkCursor(_engine, _zgiCursorFileNames[i]); // Up cursor
char buffer[25];
memset(buffer, 0, 25);
strncpy(buffer, _zgiCursorFileNames[i], 24);
buffer[3] += 2;
_cursors[i][1] = ZorkCursor(_engine, buffer); // Down cursor
}
}
}
void CursorManager::setItemID(int id) {
if (id != _item) {
if (id) {
Common::Path file;
if (_engine->getGameId() == GID_NEMESIS) {
file = Common::Path(Common::String::format("%2.2d%s%c.zcr", id, "idle", 'a'));
_cursors[NUM_CURSORS][0] = ZorkCursor(_engine, file);
file = Common::Path(Common::String::format("%2.2d%s%c.zcr", id, "idle", 'b'));
_cursors[NUM_CURSORS][1] = ZorkCursor(_engine, file);
file = Common::Path(Common::String::format("%2.2d%s%c.zcr", id, "act", 'a'));
_cursors[NUM_CURSORS + 1][0] = ZorkCursor(_engine, file);
file = Common::Path(Common::String::format("%2.2d%s%c.zcr", id, "act", 'b'));
_cursors[NUM_CURSORS + 1][0] = ZorkCursor(_engine, file);
} else if (_engine->getGameId() == GID_GRANDINQUISITOR) {
file = Common::Path(Common::String::format("g0b%cc%2.2x1.zcr", 'a' , id));
_cursors[NUM_CURSORS][0] = ZorkCursor(_engine, file);
file = Common::Path(Common::String::format("g0b%cc%2.2x1.zcr", 'c' , id));
_cursors[NUM_CURSORS][1] = ZorkCursor(_engine, file);
file = Common::Path(Common::String::format("g0b%cc%2.2x1.zcr", 'b' , id));
_cursors[NUM_CURSORS + 1][0] = ZorkCursor(_engine, file);
file = Common::Path(Common::String::format("g0b%cc%2.2x1.zcr", 'd' , id));
_cursors[NUM_CURSORS + 1][1] = ZorkCursor(_engine, file);
} else
return;
}
_item = id;
changeCursor(CursorIndex_Idle);
}
}
void CursorManager::initialize() {
changeCursor(_cursors[CursorIndex_Idle][_cursorIsPushed]);
showMouse(true);
}
void CursorManager::changeCursor(const ZorkCursor &cursor) {
CursorMan.replaceCursor(cursor.getSurface(), cursor.getWidth(), cursor.getHeight(), cursor.getHotspotX(), cursor.getHotspotY(), cursor.getKeyColor(), false, &_pixelFormat);
}
void CursorManager::cursorDown(bool pushed) {
if (_cursorIsPushed == pushed)
return;
_cursorIsPushed = pushed;
changeCursor(_cursors[_currentCursor][_cursorIsPushed]);
}
void CursorManager::changeCursor(int id) {
if (_item && (id == CursorIndex_Active ||
id == CursorIndex_Idle ||
id == CursorIndex_HandPu)) {
if (id == CursorIndex_Idle) {
id = CursorIndex_ItemIdle;
} else {
id = CursorIndex_ItemAct;
}
}
if (_currentCursor != id || ((id == CursorIndex_ItemAct || id == CursorIndex_ItemIdle) && _lastitem != _item)) {
_currentCursor = id;
_lastitem = _item;
changeCursor(_cursors[_currentCursor][_cursorIsPushed]);
}
}
int CursorManager::getCursorId(const Common::String &name) {
for (int i = 0; i < NUM_CURSORS; i++) {
if (name.equals(_cursorNames[i])) {
return i;
}
}
return CursorIndex_Idle;
}
void CursorManager::showMouse(bool vis) {
CursorMan.showMouse(vis);
}
} // End of namespace ZVision

View File

@@ -0,0 +1,132 @@
/* 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 ZVISION_CURSOR_MANAGER_H
#define ZVISION_CURSOR_MANAGER_H
#include "common/str.h"
#include "zvision/graphics/cursors/cursor.h"
namespace Graphics {
struct PixelFormat;
}
namespace ZVision {
class ZVision;
/**
* Mostly usable cursors
*/
enum CursorIndex {
CursorIndex_Active = 0,
CursorIndex_DownArr = 3,
CursorIndex_HandPu = 6,
CursorIndex_Idle = 11,
CursorIndex_Left = 12,
CursorIndex_Right = 13,
CursorIndex_UpArr = 17,
CursorIndex_ItemIdle = 18,
CursorIndex_ItemAct = 19
};
/**
* Class to manage cursor changes. The actual changes have to be done
* through CursorMan. Otherwise the cursor will disappear after GMM
* or debug console.
* TODO: Figure out a way to get rid of the extraneous data copying due to having to use CursorMan
*/
class CursorManager {
public:
CursorManager(ZVision *engine, const Graphics::PixelFormat &pixelFormat);
private:
static const int NUM_CURSORS = 18;
// 18 default cursors in up/down states, +2 for items idle/act cursors
ZorkCursor _cursors[NUM_CURSORS + 2][2];
ZVision *_engine;
const Graphics::PixelFormat _pixelFormat;
bool _cursorIsPushed;
int _item;
int _lastitem;
int _currentCursor;
static const char *_cursorNames[];
static const char *_zgiCursorFileNames[];
static const char *_zNemCursorFileNames[];
public:
/** Creates the idle cursor and shows it */
void initialize();
/**
* Change cursor to specified cursor ID. If item setted to not 0 and cursor id idle/acrive/handpu change cursor to item.
*
* @param id Wanted cursor id.
*/
void changeCursor(int id);
/**
* Return founded id for string contains cursor name
*
* @param name Cursor name
* @return Id of cursor or idle cursor id if not found
*/
int getCursorId(const Common::String &name);
/**
* Load cursor for item by id, and try to change cursor to item cursor if it's not 0
*
* @param id Item id or 0 for no item cursor
*/
void setItemID(int id);
/**
* Change the cursor to a certain push state. If the cursor is already in the specified push state, nothing will happen.
*
* @param pushed Should the cursor be pushed (true) or not pushed (false) (Another way to say it: down or up)
*/
void cursorDown(bool pushed);
/**
* Show or hide mouse cursor.
*
* @param vis Should the cursor be showed (true) or hide (false)
*/
void showMouse(bool vis);
private:
/**
* Calls CursorMan.replaceCursor() using the data in cursor
*
* @param cursor The cursor to show
*/
void changeCursor(const ZorkCursor &cursor);
};
} // End of namespace ZVision
#endif

View File

@@ -0,0 +1,170 @@
/* 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"
#include "zvision/zvision.h"
#include "zvision/graphics/render_manager.h"
#include "zvision/graphics/effects/fog.h"
#include "zvision/scripting/script_manager.h"
namespace ZVision {
FogFx::FogFx(ZVision *engine, uint32 key, Common::Rect region, bool ported, EffectMap *Map, const Common::Path &clouds):
GraphicsEffect(engine, key, region, ported) {
_map = Map;
_r = 0;
_g = 0;
_b = 0;
_pos = 0;
if (SearchMan.hasFile(clouds))
_engine->getRenderManager()->readImageToSurface(clouds, _fog);
else
_engine->getRenderManager()->readImageToSurface("cloud.tga", _fog);
_mp.resize(_fog.h);
for (int16 i = 0; i < _fog.h; i++) {
_mp[i].resize(_fog.w);
for (int16 j = 0; j < _fog.w; j++)
_mp[i][j] = true;
}
for (uint8 i = 0; i < 32; i++)
_colorMap[i] = 0;
}
FogFx::~FogFx() {
if (_map)
delete _map;
for (uint16 i = 0; i < _mp.size(); i++)
_mp[i].clear();
_mp.clear();
}
const Graphics::Surface *FogFx::draw(const Graphics::Surface &srcSubRect) {
_surface.copyFrom(srcSubRect);
EffectMap::iterator it = _map->begin();
uint32 cnt = 0;
for (uint16 j = 0; j < _surface.h; j++) {
uint16 *lineBuf = (uint16 *)_surface.getBasePtr(0, j);
for (uint16 i = 0; i < _surface.w; i++) {
if (it->inEffect) {
// Not 100% equivalent, but looks nice and not buggy
uint8 sr, sg, sb;
_engine->_resourcePixelFormat.colorToRGB(lineBuf[i], sr, sg, sb);
uint16 fogColor = *(uint16 *)_fog.getBasePtr((i + _pos) % _fog.w, j);
uint8 dr, dg, db;
_engine->_resourcePixelFormat.colorToRGB(_colorMap[fogColor & 0x1F], dr, dg, db);
uint16 fr = dr + sr;
if (fr > 255)
fr = 255;
uint16 fg = dg + sg;
if (fg > 255)
fg = 255;
uint16 fb = db + sb;
if (fb > 255)
fb = 255;
lineBuf[i] = _engine->_resourcePixelFormat.RGBToColor(fr, fg, fb);
}
cnt++;
if (cnt >= it->count) {
it++;
cnt = 0;
}
if (it == _map->end())
break;
}
if (it == _map->end())
break;
}
return &_surface;
}
void FogFx::update() {
_pos += _engine->getScriptManager()->getStateValue(StateKey_EF9_Speed);
_pos %= _fog.w;
debugC(2, kDebugEffect, "Updating fog effect");
uint8 dr = _engine->getScriptManager()->getStateValue(StateKey_EF9_R);
uint8 dg = _engine->getScriptManager()->getStateValue(StateKey_EF9_G);
uint8 db = _engine->getScriptManager()->getStateValue(StateKey_EF9_B);
dr = CLIP((int)dr, 0, 31);
dg = CLIP((int)dg, 0, 31);
db = CLIP((int)db, 0, 31);
if (dr != _r || dg != _g || db != _b) {
if (_r > dr)
_r--;
else if (_r < dr)
_r++;
if (_g > dg)
_g--;
else if (_g < dg)
_g++;
if (_b > db)
_b--;
else if (_b < db)
_b++;
// Not 100% equivalent, but looks nice and not buggy
_colorMap[31] = _engine->_resourcePixelFormat.RGBToColor(_r << 3, _g << 3, _b << 3);
for (uint8 i = 0; i < 31; i++) {
float perc = (float)i / 31.0;
uint8 cr = (uint8)((float)_r * perc);
uint8 cg = (uint8)((float)_g * perc);
uint8 cb = (uint8)((float)_b * perc);
_colorMap[i] = _engine->_resourcePixelFormat.RGBToColor(cr << 3, cg << 3, cb << 3);
}
}
for (uint16 j = 0; j < _fog.h; j++) {
uint16 *pix = (uint16 *)_fog.getBasePtr(0, j);
for (uint16 i = 0; i < _fog.w; i++) {
if (_mp[j][i]) {
if ((pix[i] & 0x1F) == 0x1F) {
pix[i]--;
_mp[j][i] = false;
} else
pix[i]++;
} else {
if ((pix[i] & 0x1F) == 0) {
pix[i]++;
_mp[j][i] = true;
} else
pix[i]--;
}
}
}
}
} // End of namespace ZVision

View File

@@ -0,0 +1,52 @@
/* 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 ZVISION_FOG_H
#define ZVISION_FOG_H
#include "zvision/graphics/graphics_effect.h"
namespace ZVision {
class ZVision;
// Used by Zork: Nemesis for the mixing chamber gas effect in the gas puzzle (location tt5e, when the blinds are down)
class FogFx : public GraphicsEffect {
public:
FogFx(ZVision *engine, uint32 key, Common::Rect region, bool ported, EffectMap *Map, const Common::Path &clouds);
~FogFx() override;
const Graphics::Surface *draw(const Graphics::Surface &srcSubRect) override;
void update() override;
private:
EffectMap *_map;
Graphics::Surface _fog;
uint8 _r, _g, _b;
int32 _pos;
Common::Array< Common::Array< bool > > _mp;
uint16 _colorMap[32];
};
} // End of namespace ZVision
#endif // ZVISION_FOG_H

View File

@@ -0,0 +1,106 @@
/* 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"
#include "zvision/zvision.h"
#include "zvision/graphics/render_manager.h"
#include "zvision/graphics/effects/light.h"
namespace ZVision {
LightFx::LightFx(ZVision *engine, uint32 key, Common::Rect region, bool ported, EffectMap *Map, int8 delta, int8 minD, int8 maxD):
GraphicsEffect(engine, key, region, ported) {
_map = Map;
_delta = delta;
_up = true;
_pos = 0;
_minD = minD;
if (_minD < -delta)
_minD = -delta;
_maxD = maxD;
if (_maxD > delta)
_maxD = delta;
}
LightFx::~LightFx() {
if (_map)
delete _map;
}
const Graphics::Surface *LightFx::draw(const Graphics::Surface &srcSubRect) {
_surface.copyFrom(srcSubRect);
EffectMap::iterator it = _map->begin();
uint32 cnt = 0;
uint32 dcolor = 0;
if (_pos < 0) {
uint8 cc = ((-_pos) & 0x1F) << 3;
dcolor = _engine->_resourcePixelFormat.RGBToColor(cc, cc, cc);
} else {
uint8 cc = (_pos & 0x1F) << 3;
dcolor = _engine->_resourcePixelFormat.RGBToColor(cc, cc, cc);
}
for (uint16 j = 0; j < _surface.h; j++) {
uint16 *lineBuf = (uint16 *)_surface.getBasePtr(0, j);
for (uint16 i = 0; i < _surface.w; i++) {
if (it->inEffect) {
if (_pos < 0) {
lineBuf[i] -= dcolor;
} else {
lineBuf[i] += dcolor;
}
}
cnt++;
if (cnt >= it->count) {
it++;
cnt = 0;
}
if (it == _map->end())
break;
}
if (it == _map->end())
break;
}
return &_surface;
}
void LightFx::update() {
if (_up)
_pos++;
else
_pos--;
if (_pos <= _minD) {
_up = !_up;
_pos = _minD;
} else if (_pos >= _maxD) {
_up = !_up;
_pos = _maxD;
}
}
} // End of namespace ZVision

View File

@@ -0,0 +1,52 @@
/* 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 LIGHTFX_H_INCLUDED
#define LIGHTFX_H_INCLUDED
#include "zvision/graphics/graphics_effect.h"
namespace ZVision {
class ZVision;
class LightFx : public GraphicsEffect {
public:
LightFx(ZVision *engine, uint32 key, Common::Rect region, bool ported, EffectMap *Map, int8 delta, int8 minD = -127, int8 maxD = 127);
~LightFx() override;
const Graphics::Surface *draw(const Graphics::Surface &srcSubRect) override;
void update() override;
private:
EffectMap *_map;
int32 _delta;
bool _up;
int32 _pos;
int8 _minD;
int8 _maxD;
};
} // End of namespace ZVision
#endif // LIGHTFX_H_INCLUDED

View File

@@ -0,0 +1,142 @@
/* 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"
#include "zvision/zvision.h"
#include "zvision/graphics/render_manager.h"
#include "zvision/graphics/effects/wave.h"
namespace ZVision {
WaveFx::WaveFx(ZVision *engine, uint32 key, Common::Rect region, bool ported, int16 frames, int16 centerX, int16 centerY, float ampl, float waveln, float spd):
GraphicsEffect(engine, key, region, ported) {
_frame = 0;
_frameCount = frames;
_ampls.resize(_frameCount);
_halfWidth = _region.width() / 2;
_halfHeight = _region.height() / 2;
int32 frmsize = _halfWidth * _halfHeight;
float phase = 0;
int16 quarterWidth = _halfWidth / 2;
int16 quarterHeight = _halfHeight / 2;
for (int16 i = 0; i < _frameCount; i++) {
_ampls[i].resize(frmsize);
for (int16 y = 0; y < _halfHeight; y++)
for (int16 x = 0; x < _halfWidth; x++) {
int16 dx = (x - quarterWidth);
int16 dy = (y - quarterHeight);
_ampls[i][x + y * _halfWidth] = (int8)(ampl * sin(sqrt(dx * dx / (float)centerX + dy * dy / (float)centerY) / (-waveln * 3.1415926) + phase));
}
phase += spd;
}
}
WaveFx::~WaveFx() {
for (uint16 i = 0; i < _ampls.size(); i++)
_ampls[i].clear();
_ampls.clear();
}
const Graphics::Surface *WaveFx::draw(const Graphics::Surface &srcSubRect) {
for (int16 y = 0; y < _halfHeight; y++) {
uint16 *abc = (uint16 *)_surface.getBasePtr(0, y);
uint16 *abc2 = (uint16 *)_surface.getBasePtr(0, _halfHeight + y);
uint16 *abc3 = (uint16 *)_surface.getBasePtr(_halfWidth, y);
uint16 *abc4 = (uint16 *)_surface.getBasePtr(_halfWidth, _halfHeight + y);
for (int16 x = 0; x < _halfWidth; x++) {
int8 amnt = _ampls[_frame][x + _halfWidth * y];
int16 nX = x + amnt;
int16 nY = y + amnt;
if (nX < 0)
nX = 0;
if (nX >= _region.width())
nX = _region.width() - 1;
if (nY < 0)
nY = 0;
if (nY >= _region.height())
nY = _region.height() - 1;
*abc = *(const uint16 *)srcSubRect.getBasePtr(nX, nY);
nX = x + amnt + _halfWidth;
nY = y + amnt;
if (nX < 0)
nX = 0;
if (nX >= _region.width())
nX = _region.width() - 1;
if (nY < 0)
nY = 0;
if (nY >= _region.height())
nY = _region.height() - 1;
*abc3 = *(const uint16 *)srcSubRect.getBasePtr(nX, nY);
nX = x + amnt;
nY = y + amnt + _halfHeight;
if (nX < 0)
nX = 0;
if (nX >= _region.width())
nX = _region.width() - 1;
if (nY < 0)
nY = 0;
if (nY >= _region.height())
nY = _region.height() - 1;
*abc2 = *(const uint16 *)srcSubRect.getBasePtr(nX, nY);
nX = x + amnt + _halfWidth;
nY = y + amnt + _halfHeight;
if (nX < 0)
nX = 0;
if (nX >= _region.width())
nX = _region.width() - 1;
if (nY < 0)
nY = 0;
if (nY >= _region.height())
nY = _region.height() - 1;
*abc4 = *(const uint16 *)srcSubRect.getBasePtr(nX, nY);
abc++;
abc2++;
abc3++;
abc4++;
}
}
return &_surface;
}
void WaveFx::update() {
_frame = (_frame + 1) % _frameCount;
}
} // End of namespace ZVision

View File

@@ -0,0 +1,50 @@
/* 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 WAVEFX_H_INCLUDED
#define WAVEFX_H_INCLUDED
#include "common/array.h"
#include "zvision/graphics/graphics_effect.h"
namespace ZVision {
class ZVision;
class WaveFx : public GraphicsEffect {
public:
WaveFx(ZVision *engine, uint32 key, Common::Rect region, bool ported, int16 frames, int16 centerX, int16 centerY, float ampl, float waveln, float spd);
~WaveFx() override;
const Graphics::Surface *draw(const Graphics::Surface &srcSubRect) override;
void update() override;
private:
int16 _frame;
int16 _frameCount;
int16 _halfWidth, _halfHeight;
Common::Array< Common::Array< int8 > > _ampls;
};
} // End of namespace ZVision
#endif // WAVEFX_H_INCLUDED

View File

@@ -0,0 +1,84 @@
/* 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 GRAPHICS_EFFECT_H_INCLUDED
#define GRAPHICS_EFFECT_H_INCLUDED
#include "common/list.h"
#include "common/rect.h"
#include "graphics/surface.h"
#include "zvision/zvision.h"
namespace ZVision {
class ZVision;
class GraphicsEffect {
public:
GraphicsEffect(ZVision *engine, uint32 key, Common::Rect region, bool ported) : _engine(engine), _key(key), _region(region), _ported(ported) {
_surface.create(_region.width(), _region.height(), _engine->_resourcePixelFormat);
}
virtual ~GraphicsEffect() {}
uint32 getKey() {
return _key;
}
Common::Rect getRegion() {
return _region;
}
// If true, effect is applied to the current background image prior to panoramic warping
// If false, effect is applied to the effects buffer, which corresponds directly to the working window
bool isPort() {
return _ported;
}
// Make a copy of supplied surface, draw effect on it, then return that altered surface
virtual const Graphics::Surface *draw(const Graphics::Surface &srcSubRect) {
return &_surface;
}
virtual void update() {}
protected:
ZVision *_engine;
uint32 _key;
Common::Rect _region;
bool _ported;
Graphics::Surface _surface;
// Static member functions
public:
};
struct EffectMapUnit {
uint32 count;
bool inEffect;
};
typedef Common::List<EffectMapUnit> EffectMap;
} // End of namespace ZVision
#endif // GRAPHICS_EFFECT_H_INCLUDED

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,376 @@
/* 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 ZVISION_RENDER_MANAGER_H
#define ZVISION_RENDER_MANAGER_H
#include "common/hashmap.h"
#include "common/rect.h"
#include "graphics/framelimiter.h"
#include "graphics/managed_surface.h"
#include "graphics/screen.h"
#include "graphics/surface.h"
#include "zvision/graphics/graphics_effect.h"
#include "zvision/graphics/render_table.h"
#include "zvision/text/truetype_font.h"
class OSystem;
namespace Common {
class String;
class SeekableReadStream;
}
namespace Video {
class VideoDecoder;
}
namespace ZVision {
class RenderManager {
public:
RenderManager(ZVision *engine, const ScreenLayout layout, const Graphics::PixelFormat pixelFormat, bool doubleFPS, bool widescreen = false);
~RenderManager();
typedef Common::List<GraphicsEffect *> EffectsList;
private:
ZVision *_engine;
OSystem *_system;
const Graphics::PixelFormat _pixelFormat;
const ScreenLayout _layout;
bool _hiRes = false;
Graphics::FrameLimiter _frameLimiter;
/**
* A Rectangle representing the screen/window resolution.
*/
Common::Rect _screenArea;
Common::Rect _HDscreenArea = Common::Rect(800, 600);
Common::Rect _HDscreenAreaWide = Common::Rect(720, 377);
Common::Point _textOffset; //Position vector of text area origin relative to working window origin
/**
* A Rectangle placed inside _screenArea All in-game coordinates
* are given in this coordinate space. Also, all images are clipped to the
* edges of this Rectangle
*/
Common::Rect _workingArea;
Common::Point _workingAreaCenter; //Center of the working area in working area coordinates
/**
Managed surface representing physical screen; dirty rectangles will be handled automatically by this from now on
*/
Graphics::Screen _screen;
/** A buffer for background image that's being used to create the background */
Graphics::Surface _currentBackgroundImage;
Common::Rect _backgroundDirtyRect;
/**
* The x1 or y1 offset of the subRectangle of the background that is currently displayed on the screen
* It will be x1 if PANORAMA, or y1 if TILT
*/
int16 _backgroundOffset;
/** The width of the current background image */
uint16 _backgroundWidth;
/** The height of the current background image */
uint16 _backgroundHeight;
// A buffer that holds the portion of the background that is used to render the final image
// If it's a normal scene, the pixels will be blitted directly to the screen
// If it's a panorma / tilt scene, the pixels will be first warped to _warpedSceneSurface
Graphics::Surface _backgroundSurface;
Graphics::ManagedSurface _workingManagedSurface;
Common::Rect _backgroundSurfaceDirtyRect;
//TODO: Migrate this functionality to SubtitleManager to improve encapsulation
//*
// Buffer for drawing subtitles & other messages
Graphics::Surface _textSurface;
Graphics::ManagedSurface _textManagedSurface;
Common::Rect _textSurfaceDirtyRect;
//*/
// Rectangle for subtitles & other messages
Common::Rect _textArea; //NB Screen coordinates
Common::Rect _textLetterbox; //Section of text area outside working window, to be filled with black when blanked
Common::Rect _textOverlay; //Section of text area to be filled with colorkey when blanked (may potentially intersect text letterbox area if screen/window is wider than working area!)
// Buffer for drawing menu
Graphics::Surface _menuSurface;
Graphics::ManagedSurface _menuManagedSurface;
Common::Rect _menuSurfaceDirtyRect; //subrectangle of menu area outside working area
// Rectangle for menu area
Common::Rect _menuArea; //Screen coordinates
Common::Rect _menuLetterbox; //Section of menu area to be filled with black when blanked
Common::Rect _menuOverlay; //Section of menu area to be filled with colorkey when blanked (may potentially intersect menu letterbox area if screen/window is wider than working area!)
//Buffer for streamed video playback
//*
Graphics::ManagedSurface _vidManagedSurface;
/*/
Graphics::Surface _vidSurface;
//*/
//Area of streamed video playback
Common::Rect _vidArea;
// A buffer used for apply graphics effects
Graphics::Surface _effectSurface;
// A buffer to store the result of the panorama / tilt warps
Graphics::Surface _warpedSceneSurface;
/** Used to warp the background image */
RenderTable _renderTable;
// Visual effects list
EffectsList _effects;
//Pointer to currently active backbuffer output surface
Graphics::Surface *_outputSurface;
bool _doubleFPS;
bool _widescreen;
public:
void initialize(bool hiRes = false);
/**
* Renders the scene to the screen
* Returns true if screen was updated
* If streamMode is set true, all background processing is skipped and the previous framebuffer is used
*/
bool renderSceneToScreen(bool immediate = false, bool overlayOnly = false, bool preStream = false);
Graphics::ManagedSurface &getVidSurface(Common::Rect dstRect); //dstRect is defined relative to working area origin
const Common::Rect &getMenuArea() const {
return _menuArea;
}
const Common::Rect &getWorkingArea() const {
return _workingArea;
}
/**
* Blits the image or a portion of the image to the background.
*
* @param fileName Name of the image file
* @param destinationX X position where the image should be put. Coords are in working window space, not screen space!
* @param destinationY Y position where the image should be put. Coords are in working window space, not screen space!
*/
void renderImageToBackground(const Common::Path &fileName, int16 destinationX, int16 destinationY);
/**
* Blits the image or a portion of the image to the background.
*
* @param fileName Name of the image file
* @param destX X position where the image should be put. Coords are in working window space, not screen space!
* @param destY Y position where the image should be put. Coords are in working window space, not screen space!
* @param colorkey Transparent color
*/
void renderImageToBackground(const Common::Path &fileName, int16 destX, int16 destY, uint32 colorkey);
/**
* Blits the image or a portion of the image to the background.
*
* @param fileName Name of the image file
* @param destX X position where the image should be put. Coords are in working window space, not screen space!
* @param destY Y position where the image should be put. Coords are in working window space, not screen space!
* @param keyX X position of transparent color
* @param keyY Y position of transparent color
*/
void renderImageToBackground(const Common::Path &fileName, int16 destX, int16 destY, int16 keyX, int16 keyY);
/**
* Sets the current background image to be used by the RenderManager and immediately
* blits it to the screen. (It won't show up until the end of the frame)
*
* @param fileName The name of the image file
*/
void setBackgroundImage(const Common::Path &fileName);
/**
* Set the background position (_backgroundOffset). If the current RenderState is PANORAMA, the offset
* will be in the horizontal direction. If the current RenderState is TILT, the offset will be in the
* vertical direction.
*
* This method will not render anything on the screen. So if nothing else is called that renders the
* background, the change won't be seen until next frame.
*
* @param offset The amount to offset the background
*/
void setBackgroundPosition(int offset);
/**
* Converts a point in screen coordinate space to image coordinate space
*
* @param point Point in screen coordinate space
* @return Point in image coordinate space
*/
const Common::Point screenSpaceToImageSpace(const Common::Point &point);
// Return pointer of RenderTable object
RenderTable *getRenderTable();
// Return current background offset
uint32 getCurrentBackgroundOffset();
/**
* Creates a copy of surface and transposes the data.
*
* Note: The user is responsible for calling free() on the returned surface
* and then deleting it
*
* @param surface The data to be transposed
* @return A copy of the surface with the data transposed
*/
static Graphics::Surface *tranposeSurface(const Graphics::Surface *surface);
// Scale buffer (nearest)
void scaleBuffer(const void *src, void *dst, uint32 srcWidth, uint32 srcHeight, byte bytesPerPixel, uint32 dstWidth, uint32 dstHeight);
/**
* Blit from one surface to another surface
*
* @param src Source surface
* @param srcRect Rectangle defining area of source surface to blit; if this rectangle is empty or not supplied, entire source surface is blitted
* @param dst Destination surface
* @param x Destination surface x coordinate
* @param y Destination surface y coordinate
*/
void blitSurfaceToSurface(const Graphics::Surface &src, Common::Rect srcRect, Graphics::Surface &dst, int _x, int _y);
void blitSurfaceToSurface(const Graphics::Surface &src, Common::Rect srcRect, Graphics::Surface &dst, int _x, int _y, uint32 colorkey);
void blitSurfaceToSurface(const Graphics::Surface &src, Graphics::Surface &dst, int _x, int _y) {blitSurfaceToSurface(src, Common::Rect(src.w, src.h), dst, _x, _y);}
void blitSurfaceToSurface(const Graphics::Surface &src, Graphics::Surface &dst, int _x, int _y, uint32 colorkey) {blitSurfaceToSurface(src, Common::Rect(src.w, src.h), dst, _x, _y, colorkey);}
// Blitting surface-to-background methods
void blitSurfaceToBkg(const Graphics::Surface &src, int x, int y, int32 colorkey = -1);
// Blitting surface-to-background methods with scale
void blitSurfaceToBkgScaled(const Graphics::Surface &src, const Common::Rect &dstRect, int32 colorkey = -1);
/**
* Blit from source surface to menu area
*
* @param src Source surface
* @param x x coordinate relative to menu area origin
* @param y y coordinate relative to menu area origin
*/
void blitSurfaceToMenu(const Graphics::Surface &src, int16 x, int16 y, int32 colorkey = 0);
/**
* Blit from source surface to text area
*
* @param src Source surface
* @param x x coordinate relative to text area origin
* @param y y coordinate relative to text area origin
*/
void blitSurfaceToText(const Graphics::Surface &src, int16 x, int16 y, int32 colorkey = 0);
// Return background size
Common::Point getBkgSize();
// Return portion of background as new surface
Graphics::Surface *getBkgRect(Common::Rect &rect);
// Load image into new surface
Graphics::Surface *loadImage(const Common::Path &file);
Graphics::Surface *loadImage(const Common::Path &file, bool transposed);
// Clear whole/area of menu backbuffer
void clearMenuSurface(bool force = false, int32 colorkey = -1);
// Clear whole/area of subtitle backbuffer
void clearTextSurface(bool force = false, int32 colorkey = -1);
// Copy needed portion of background surface to workingArea surface
void prepareBackground();
/**
* Reads an image file pixel data into a Surface buffer. Also, if the image
* is transposed, it will un-transpose the pixel data. The function will
* call destination::create() if the dimensions of destination do not match
* up with the dimensions of the image.
*
* @param fileName The name of a .tga file
* @param destination A reference to the Surface to store the pixel data in
*/
void readImageToSurface(const Common::Path &fileName, Graphics::Surface &destination);
/**
* Reads an image file pixel data into a Surface buffer. Also, if the image
* is transposed, it will un-transpose the pixel data. The function will
* call destination::create() if the dimensions of destination do not match
* up with the dimensions of the image.
*
* @param fileName The name of a .tga file
* @param destination A reference to the Surface to store the pixel data in
* @param transposed Transpose flag
*/
void readImageToSurface(const Common::Path &fileName, Graphics::Surface &destination, bool transposed);
// Add visual effect to effects list
void addEffect(GraphicsEffect *_effect);
// Delete effect(s) by ID (ID equal to slot of action:region that create this effect)
void deleteEffect(uint32 ID);
// Create "mask" for effects - (color +/- depth) will be selected as not transparent. Like color selection
// xy - base color
// depth - +/- of base color
// rect - rectangle where select pixels
// minD - if not NULL will receive real bottom border of depth
// maxD - if not NULL will receive real top border of depth
EffectMap *makeEffectMap(const Common::Point &xy, int16 depth, const Common::Rect &rect, int8 *minD, int8 *maxD);
// Create "mask" for effects by simple transparent color
EffectMap *makeEffectMap(const Graphics::Surface &surf, uint16 transp);
// Return background rectangle in screen coordinates
Common::Rect transformBackgroundSpaceRectToScreenSpace(const Common::Rect &src);
// Mark whole background surface as dirty
void markDirty();
/*
// Fill background surface by color
void bkgFill(uint8 r, uint8 g, uint8 b);
*/
void checkBorders();
void rotateTo(int16 to, int16 time);
void updateRotation();
void upscaleRect(Common::Rect &rect);
};
} // End of namespace ZVision
#endif

View File

@@ -0,0 +1,345 @@
/* 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/scummsys.h"
#include "common/system.h"
#include "math/utils.h"
#include "zvision/graphics/render_table.h"
#include "zvision/scripting/script_manager.h"
namespace ZVision {
RenderTable::RenderTable(ZVision *engine, uint16 numColumns, uint16 numRows, const Graphics::PixelFormat &pixelFormat)
: _engine(engine),
_system(engine->_system),
_numRows(numRows),
_numColumns(numColumns),
_renderState(FLAT),
_pixelFormat(pixelFormat) {
assert(numRows != 0 && numColumns != 0);
_internalBuffer = new FilterPixel[numRows * numColumns];
memset(&_panoramaOptions, 0, sizeof(_panoramaOptions));
memset(&_tiltOptions, 0, sizeof(_tiltOptions));
_halfRows = floor((_numRows - 1) / 2);
_halfColumns = floor((_numColumns - 1) / 2);
_halfWidth = (float)_numColumns / 2.0f - 0.5f;
_halfHeight = (float)_numRows / 2.0f - 0.5f;
}
RenderTable::~RenderTable() {
delete[] _internalBuffer;
}
void RenderTable::setRenderState(RenderState newState) {
_renderState = newState;
switch (newState) {
case PANORAMA:
_panoramaOptions.verticalFOV = Math::deg2rad<float>(27.0f);
_panoramaOptions.linearScale = 0.55f;
_panoramaOptions.reverse = false;
_panoramaOptions.zeroPoint = 0;
break;
case TILT:
_tiltOptions.verticalFOV = Math::deg2rad<float>(27.0f);
_tiltOptions.linearScale = 0.65f;
_tiltOptions.reverse = false;
break;
case FLAT:
// Intentionally left empty
break;
default:
break;
}
}
const Common::Point RenderTable::convertWarpedCoordToFlatCoord(const Common::Point &point) {
// If we're outside the range of the RenderTable, no warping is happening. Return the maximum image coords
if (point.x >= (int16)_numColumns || point.y >= (int16)_numRows || point.x < 0 || point.y < 0) {
int16 x = CLIP<int16>(point.x, 0, (int16)_numColumns);
int16 y = CLIP<int16>(point.y, 0, (int16)_numRows);
return Common::Point(x, y);
}
uint32 index = point.y * _numColumns + point.x;
Common::Point newPoint(point);
newPoint.x += (_internalBuffer[index]._xDir ? _internalBuffer[index]._src.right : _internalBuffer[index]._src.left);
newPoint.y += (_internalBuffer[index]._yDir ? _internalBuffer[index]._src.bottom : _internalBuffer[index]._src.top);
return newPoint;
}
// Disused at present; potentially useful for future rendering efficient improvements.
/*/
void RenderTable::mutateImage(uint16 *sourceBuffer, uint16 *destBuffer, uint32 destWidth, const Common::Rect &subRect) {
uint32 destOffset = 0;
uint32 sourceXIndex = 0;
uint32 sourceYIndex = 0;
if(highQuality) {
// TODO - convert to high quality pixel filtering
for (int16 y = subRect.top; y < subRect.bottom; ++y) {
uint32 sourceOffset = y * _numColumns;
for (int16 x = subRect.left; x < subRect.right; ++x) {
uint32 normalizedX = x - subRect.left;
uint32 index = sourceOffset + x;
// RenderTable only stores offsets from the original coordinates
sourceYIndex = y + _internalBuffer[index]._src.top;
sourceXIndex = x + _internalBuffer[index]._src.left;
destBuffer[destOffset + normalizedX] = sourceBuffer[sourceYIndex * _numColumns + sourceXIndex];
}
destOffset += destWidth;
}
}
else {
for (int16 y = subRect.top; y < subRect.bottom; ++y) {
uint32 sourceOffset = y * _numColumns;
for (int16 x = subRect.left; x < subRect.right; ++x) {
uint32 normalizedX = x - subRect.left;
uint32 index = sourceOffset + x;
// RenderTable only stores offsets from the original coordinates
sourceYIndex = y + _internalBuffer[index]._src.top;
sourceXIndex = x + _internalBuffer[index]._src.left;
destBuffer[destOffset + normalizedX] = sourceBuffer[sourceYIndex * _numColumns + sourceXIndex];
}
destOffset += destWidth;
}
}
}
// */
void RenderTable::mutateImage(Graphics::Surface *dstBuf, Graphics::Surface *srcBuf, bool highQuality) {
uint32 destOffset = 0;
uint32 sourceOffset = 0;
uint16 *sourceBuffer = (uint16 *)srcBuf->getPixels();
uint16 *destBuffer = (uint16 *)dstBuf->getPixels();
if (highQuality != _highQuality) {
_highQuality = highQuality;
generateRenderTable();
}
uint32 mutationTime = _system->getMillis();
if (_highQuality) {
// Apply bilinear interpolation
for (int16 y = 0; y < srcBuf->h; ++y) {
sourceOffset = y * _numColumns;
for (int16 x = 0; x < srcBuf->w; ++x) {
const FilterPixel &curP = _internalBuffer[sourceOffset + x];
const uint32 srcIndexYT = y + curP._src.top;
const uint32 srcIndexYB = y + curP._src.bottom;
const uint32 srcIndexXL = x + curP._src.left;
const uint32 srcIndexXR = x + curP._src.right;
uint32 rTL, rTR, rBL, rBR;
uint32 gTL, gTR, gBL, gBR;
uint32 bTL, bTR, bBL, bBR;
splitColor(sourceBuffer[srcIndexYT * _numColumns + srcIndexXL], rTL, gTL, bTL);
splitColor(sourceBuffer[srcIndexYT * _numColumns + srcIndexXR], rTR, gTR, bTR);
splitColor(sourceBuffer[srcIndexYB * _numColumns + srcIndexXL], rBL, gBL, bBL);
splitColor(sourceBuffer[srcIndexYB * _numColumns + srcIndexXR], rBR, gBR, bBR);
const uint32 rF = curP._fTL * rTL + curP._fTR * rTR + curP._fBL * rBL + curP._fBR * rBR;
const uint32 gF = curP._fTL * gTL + curP._fTR * gTR + curP._fBL * gBL + curP._fBR * gBR;
const uint32 bF = curP._fTL * bTL + curP._fTR * bTR + curP._fBL * bBL + curP._fBR * bBR;
destBuffer[destOffset] = mergeColor(rF, gF, bF);
destOffset++;
}
}
} else {
// Apply nearest-neighbour interpolation
for (int16 y = 0; y < srcBuf->h; ++y) {
sourceOffset = y * _numColumns;
for (int16 x = 0; x < srcBuf->w; ++x) {
const uint32 index = sourceOffset + x;
// RenderTable only stores offsets from the original coordinates
const uint32 srcIndexX = x + (_internalBuffer[index]._xDir ? _internalBuffer[index]._src.right : _internalBuffer[index]._src.left);
const uint32 srcIndexY = y + (_internalBuffer[index]._yDir ? _internalBuffer[index]._src.bottom : _internalBuffer[index]._src.top);
destBuffer[destOffset] = sourceBuffer[srcIndexY * _numColumns + srcIndexX];
destOffset++;
}
}
}
mutationTime = _system->getMillis() - mutationTime;
debugC(5, kDebugGraphics, "\tPanorama mutation time %dms, %s quality", mutationTime, _highQuality ? "high" : "low");
}
void RenderTable::generateRenderTable() {
switch (_renderState) {
case RenderTable::PANORAMA: {
generateLookupTable(false);
break;
}
case RenderTable::TILT:
generateLookupTable(true);
break;
case RenderTable::FLAT:
// Intentionally left empty
break;
default:
break;
}
}
void RenderTable::generateLookupTable(bool tilt) {
debugC(1, kDebugGraphics, "Generating %s lookup table.", tilt ? "tilt" : "panorama");
debugC(5, kDebugGraphics, "_halfWidth %f, _halfHeight %f", _halfWidth, _halfHeight);
debugC(5, kDebugGraphics, "_halfRows %d, _halfColumns %d", _halfRows, _halfColumns);
uint32 generationTime = _system->getMillis();
float cosAlpha, polarCoordInCylinderCoords, cylinderRadius, xOffset, yOffset;
uint32 indexTL, indexBL, indexTR, indexBR;
auto outerLoop = [&](uint & polarCoord, float & halfPolarSize, float & scale) {
// polarCoord is the coordinate of the working window pixel parallel to the direction of camera rotation
// halfPolarSize is the distance from the central axis to the outermost working window pixel in the direction of camera rotation
// alpha represents the angle in the direction of camera rotation between the view axis and the centre of a pixel at the given polar coordinate
const float alpha = atan(((float)polarCoord - halfPolarSize) / cylinderRadius);
// To map the polar coordinate to the cylinder surface coordinates, we just need to calculate the arc length
// We also scale it by linearScale
polarCoordInCylinderCoords = (cylinderRadius * scale * alpha) + halfPolarSize;
cosAlpha = cos(alpha);
};
auto innerLoop = [&](uint & polarCoord, uint & linearCoord, float & halfLinearSize, float & polarOffset, float & linearOffset) {
// To calculate linear coordinate in cylinder coordinates, we can do similar triangles comparison,
// comparing the triangle from the center to the screen and from the center to the edge of the cylinder
const float linearCoordInCylinderCoords = halfLinearSize + ((float)linearCoord - halfLinearSize) * cosAlpha;
linearOffset = linearCoordInCylinderCoords - linearCoord;
polarOffset = polarCoordInCylinderCoords - polarCoord;
_internalBuffer[indexTL] = FilterPixel(xOffset, yOffset, _highQuality);
// Transformation is both horizontally and vertically symmetrical about the camera axis,
// We can thus save on trigonometric calculations by computing one quarter of the transformation matrix and then mirroring it in both X & Y:
_internalBuffer[indexBL] = _internalBuffer[indexTL];
_internalBuffer[indexBL].flipV();
_internalBuffer[indexTR] = _internalBuffer[indexTL];
_internalBuffer[indexTR].flipH();
_internalBuffer[indexBR] = _internalBuffer[indexBL];
_internalBuffer[indexBR].flipH();
};
if (tilt) {
cylinderRadius = (_halfWidth + 0.5f) / tan(_tiltOptions.verticalFOV);
_tiltOptions.gap = cylinderRadius * atan2((float)(_halfHeight / cylinderRadius), 1.0f) * _tiltOptions.linearScale;
for (uint y = 0; y <= _halfRows; ++y) {
outerLoop(y, _halfHeight, _tiltOptions.linearScale);
const uint32 columnIndexTL = y * _numColumns;
const uint32 columnIndexBL = (_numRows - (y + 1)) * _numColumns;
const uint32 columnIndexTR = columnIndexTL + (_numColumns - 1);
const uint32 columnIndexBR = columnIndexBL + (_numColumns - 1);
for (uint x = 0; x <= _halfColumns; ++x) {
indexTL = columnIndexTL + x;
indexBL = columnIndexBL + x;
indexTR = columnIndexTR - x;
indexBR = columnIndexBR - x;
innerLoop(y, x, _halfWidth, yOffset, xOffset);
}
}
} else {
cylinderRadius = (_halfHeight + 0.5f) / tan(_panoramaOptions.verticalFOV);
for (uint x = 0; x <= _halfColumns; ++x) {
const uint32 columnIndexL = x;
const uint32 columnIndexR = (_numColumns - 1) - x;
uint32 rowIndexT = 0;
uint32 rowIndexB = _numColumns * (_numRows - 1);
outerLoop(x, _halfWidth, _panoramaOptions.linearScale);
for (uint y = 0; y <= _halfRows; ++y) {
indexTL = rowIndexT + columnIndexL;
indexBL = rowIndexB + columnIndexL;
indexTR = rowIndexT + columnIndexR;
indexBR = rowIndexB + columnIndexR;
innerLoop(x, y, _halfHeight, xOffset, yOffset);
rowIndexT += _numColumns;
rowIndexB -= _numColumns;
}
}
}
generationTime = _system->getMillis() - generationTime;
debugC(1, kDebugGraphics, "Render table generated, %s quality", _highQuality ? "high" : "low");
debugC(1, kDebugGraphics, "\tRender table generation time %dms", generationTime);
}
void RenderTable::setPanoramaFoV(float fov) {
assert(fov > 0.0f);
_panoramaOptions.verticalFOV = Math::deg2rad<float>(fov);
}
void RenderTable::setPanoramaScale(float scale) {
assert(scale > 0.0f);
_panoramaOptions.linearScale = scale;
}
void RenderTable::setPanoramaReverse(bool reverse) {
_panoramaOptions.reverse = reverse;
}
bool RenderTable::getPanoramaReverse() {
return _panoramaOptions.reverse;
}
void RenderTable::setPanoramaZeroPoint(uint16 point) {
_panoramaOptions.zeroPoint = point;
}
uint16 RenderTable::getPanoramaZeroPoint() {
return _panoramaOptions.zeroPoint;
}
void RenderTable::setTiltFoV(float fov) {
assert(fov > 0.0f);
_tiltOptions.verticalFOV = Math::deg2rad<float>(fov);
}
void RenderTable::setTiltScale(float scale) {
assert(scale > 0.0f);
_tiltOptions.linearScale = scale;
}
void RenderTable::setTiltReverse(bool reverse) {
_tiltOptions.reverse = reverse;
}
float RenderTable::getTiltGap() {
return _tiltOptions.gap;
}
float RenderTable::getAngle() {
switch (_renderState) {
case TILT:
return Math::rad2deg<float>(_tiltOptions.verticalFOV);
case PANORAMA:
return Math::rad2deg<float>(_panoramaOptions.verticalFOV);
default:
return 1.0f;
}
}
float RenderTable::getLinscale() {
switch (_renderState) {
case TILT:
return _tiltOptions.linearScale;
case PANORAMA:
return _panoramaOptions.linearScale;
default:
return 1.0f;
}
}
} // End of namespace ZVision

View File

@@ -0,0 +1,169 @@
/* 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 ZVISION_RENDER_TABLE_H
#define ZVISION_RENDER_TABLE_H
#include "common/rect.h"
#include "graphics/surface.h"
#include "zvision/zvision.h"
class OSystem;
namespace ZVision {
class FilterPixel {
public:
// Bitfields representing sequential direction of contraction
bool _xDir = false; // false left, true right
bool _yDir = false; // false up, true down
Common::Rect _src = Common::Rect(0, 0); // Coordinates of four panorama image pixels around actual working window pixel
float _fX, _fY, _fTL, _fTR, _fBL, _fBR;
FilterPixel() {}
FilterPixel(float x, float y, bool highQuality = false) {
_src.left = int16(floor(x));
_src.right = int16(ceil(x));
_src.top = int16(floor(y));
_src.bottom = int16(ceil(y));
if (highQuality) {
_fX = x - (float)_src.left;
_fY = y - (float)_src.top;
_fTL = (1 - _fX) * (1 - _fY);
_fTR = _fX * (1 - _fY);
_fBL = (1 - _fX) * _fY;
_fBR = _fX * _fY;
} else {
// Nearest neighbour
_xDir = (x - _src.left) > 0.5f;
_yDir = (y - _src.top) > 0.5f;
}
}
~FilterPixel() {}
inline void flipH() {
_src.left = -_src.left;
_src.right = -_src.right;
}
inline void flipV() {
_src.top = -_src.top;
_src.bottom = -_src.bottom;
}
};
class RenderTable {
public:
RenderTable(ZVision *engine, uint16 numRows, uint16 numColumns, const Graphics::PixelFormat &pixelFormat);
~RenderTable();
// Common::Point testPixel = Common::Point(255,0);
public:
enum RenderState {
PANORAMA,
TILT,
FLAT
};
private:
ZVision *_engine;
OSystem *_system;
uint16 _numRows, _numColumns, _halfRows, _halfColumns; // Working area width, height; half width, half height, in whole pixels
float _halfWidth, _halfHeight; // Centre axis to midpoint of outermost pixel
FilterPixel *_internalBuffer;
RenderState _renderState;
bool _highQuality = false;
const Graphics::PixelFormat _pixelFormat;
inline void splitColor(uint16 &color, uint32 &r, uint32 &g, uint32 &b) const {
// NB Left & right shifting unnecessary for interpolating & recombining, so not bothering in order to save cycles
r = color & 0x001f;
g = color & 0x03e0;
b = color & 0x7c00;
}
inline uint16 mergeColor(const uint32 &r, const uint32 &g, const uint32 &b) const {
// NB Red uses the lowest bits in RGB555 and so doesn't need its fractional bits masked away after averaging
return r | (g & 0x03e0) | (b & 0x7c00);
}
struct {
float verticalFOV; // Radians
float linearScale;
bool reverse;
uint16 zeroPoint;
} _panoramaOptions;
struct {
float verticalFOV; // Radians
float linearScale;
bool reverse;
float gap;
} _tiltOptions;
public:
RenderState getRenderState() {
return _renderState;
}
void setRenderState(RenderState newState);
const Common::Point convertWarpedCoordToFlatCoord(const Common::Point &point); // input point in working area coordinates
// void mutateImage(uint16 *sourceBuffer, uint16 *destBuffer, uint32 destWidth, const Common::Rect &subRect);
void mutateImage(Graphics::Surface *dstBuf, Graphics::Surface *srcBuf, bool filter = false);
template <typename I>
Common::String pixelToBinary(const I &pixel, bool splitColors = true) const {
uint8 bits = sizeof(pixel) << 3;
Common::String str("0b");
I spaceMask = 0;
for (uint8 i = 0; i < 3; i++)
spaceMask = (spaceMask << 5) + 0x10;
for (I mask = 0x01 << (bits - 1); mask; mask >>= 1) {
if (splitColors && (spaceMask & mask))
str += " ";
str += mask & pixel ? "1" : "0";
}
return str;
}
void generateRenderTable();
void setPanoramaFoV(float fov); // Degrees
void setPanoramaScale(float scale);
void setPanoramaReverse(bool reverse);
void setPanoramaZeroPoint(uint16 point);
uint16 getPanoramaZeroPoint();
bool getPanoramaReverse();
void setTiltFoV(float fov); // Degrees
void setTiltScale(float scale);
void setTiltReverse(bool reverse);
float getTiltGap();
float getAngle();
float getLinscale();
private:
void generateLookupTable(bool tilt = false);
void generatePanoramaLookupTable();
void generateTiltLookupTable();
};
} // End of namespace ZVision
#endif

View File

@@ -0,0 +1,418 @@
/* 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 "backends/keymapper/action.h"
#include "backends/keymapper/keymapper.h"
#include "backends/keymapper/standard-actions.h"
#include "common/savefile.h"
#include "common/scummsys.h"
#include "common/system.h"
#include "common/translation.h"
#include "engines/advancedDetector.h"
#include "zvision/detection.h"
#include "zvision/zvision.h"
#include "zvision/file/save_manager.h"
#include "zvision/scripting/script_manager.h"
namespace ZVision {
static const ADExtraGuiOptionsMap optionsList[] = {
{
GAMEOPTION_ORIGINAL_SAVELOAD,
{
_s("Use original save/load screens"),
_s("Use the original save/load screens instead of the ScummVM ones"),
"originalsaveload",
false,
0,
0
}
},
{
GAMEOPTION_DOUBLE_FPS,
{
_s("Double FPS"),
_s("Increase framerate from 30 to 60 FPS"),
"doublefps",
false,
0,
0
}
},
{
GAMEOPTION_ENABLE_VENUS,
{
_s("Enable Venus"),
_s("Enable the Venus help system"),
"venusenabled",
true,
0,
0
}
},
{
GAMEOPTION_DISABLE_ANIM_WHILE_TURNING,
{
_s("Disable animation while turning"),
_s("Disable animation while turning in panorama mode"),
"noanimwhileturning",
false,
0,
0
}
},
{
GAMEOPTION_USE_HIRES_MPEG_MOVIES,
{
_s("Use high resolution MPEG video"),
_s("Use MPEG video from the DVD version instead of lower resolution AVI"),
"mpegmovies",
true,
0,
0
}
},
{
GAMEOPTION_ENABLE_WIDESCREEN,
{
_s("Enable widescreen support"),
_s("Rearrange placement of menus & subtitles so as to make better use of modern wide aspect ratio displays"),
"widescreen",
true,
0,
0
}
},
{
GAMEOPTION_HQ_PANORAMA,
{
_s("Enable high quality panoramas"),
_s("Apply bilinear filtering to panoramic backgrounds"),
"highquality",
true,
0,
0
}
},
AD_EXTRA_GUI_OPTIONS_TERMINATOR
};
ZVisionGameId ZVision::getGameId() const {
return _gameDescription->gameId;
}
Common::Language ZVision::getLanguage() const {
return _gameDescription->desc.language;
}
uint32 ZVision::getFeatures() const {
return _gameDescription->desc.flags;
}
} // End of namespace ZVision
class ZVisionMetaEngine : public AdvancedMetaEngine<ZVision::ZVisionGameDescription> {
public:
const char *getName() const override {
return "zvision";
}
const ADExtraGuiOptionsMap *getAdvancedExtraGuiOptions() const override {
return ZVision::optionsList;
}
bool hasFeature(MetaEngineFeature f) const override;
Common::KeymapArray initKeymaps(const char *target) const override;
Common::Error createInstance(OSystem *syst, Engine **engine, const ZVision::ZVisionGameDescription *desc) const override;
SaveStateList listSaves(const char *target) const override;
int getMaximumSaveSlot() const override;
bool removeSaveState(const char *target, int slot) const override;
SaveStateDescriptor querySaveMetaInfos(const char *target, int slot) const override;
};
bool ZVisionMetaEngine::hasFeature(MetaEngineFeature f) const {
return
(f == kSupportsListSaves) ||
(f == kSupportsLoadingDuringStartup) ||
(f == kSupportsDeleteSave) ||
(f == kSavesSupportMetaInfo) ||
(f == kSavesSupportThumbnail) ||
(f == kSavesSupportCreationDate) ||
(f == kSavesSupportPlayTime) ||
(f == kSimpleSavesNames);
}
bool ZVision::ZVision::hasFeature(EngineFeature f) const {
return
(f == kSupportsReturnToLauncher) ||
(f == kSupportsLoadingDuringRuntime) ||
(f == kSupportsSavingDuringRuntime);
}
Common::Error ZVision::ZVision::loadGameState(int slot) {
return _saveManager->loadGame(slot);
}
Common::Error ZVision::ZVision::saveGameState(int slot, const Common::String &desc, bool isAutosave) {
_saveManager->saveGame(slot, desc, false);
return Common::kNoError;
}
bool ZVision::ZVision::canLoadGameStateCurrently(Common::U32String *msg) {
return !_videoIsPlaying;
}
bool ZVision::ZVision::canSaveGameStateCurrently(Common::U32String *msg) {
Location currentLocation = _scriptManager->getCurrentLocation();
return !_videoIsPlaying && currentLocation.world != 'g' && !(currentLocation.room == 'j' || currentLocation.room == 'a');
}
Common::KeymapArray ZVisionMetaEngine::initKeymaps(const char *target) const {
using namespace Common;
using namespace ZVision;
Keymap *mainKeymap = new Keymap(Keymap::kKeymapTypeGame, mainKeymapId, "Z-Vision");
Action *act;
act = new Action(kStandardActionLeftClick, _("Left click"));
act->setLeftClickEvent();
act->addDefaultInputMapping("MOUSE_LEFT");
act->addDefaultInputMapping("JOY_A");
mainKeymap->addAction(act);
act = new Action(kStandardActionRightClick, _("Right click"));
act->setRightClickEvent();
act->addDefaultInputMapping("MOUSE_RIGHT");
act->addDefaultInputMapping("JOY_B");
mainKeymap->addAction(act);
Keymap *gameKeymap = new Keymap(Keymap::kKeymapTypeGame, gameKeymapId, "Z-Vision - Game");
act = new Action(kStandardActionMoveUp, _("Look up"));
act->setCustomEngineActionEvent(kZVisionActionUp);
act->addDefaultInputMapping("UP");
act->addDefaultInputMapping("JOY_UP");
gameKeymap->addAction(act);
act = new Action(kStandardActionMoveDown, _("Look down"));
act->setCustomEngineActionEvent(kZVisionActionDown);
act->addDefaultInputMapping("DOWN");
act->addDefaultInputMapping("JOY_DOWN");
gameKeymap->addAction(act);
act = new Action(kStandardActionMoveLeft, _("Turn left"));
act->setCustomEngineActionEvent(kZVisionActionLeft);
act->addDefaultInputMapping("LEFT");
act->addDefaultInputMapping("JOY_LEFT");
gameKeymap->addAction(act);
act = new Action(kStandardActionMoveRight, _("Turn right"));
act->setCustomEngineActionEvent(kZVisionActionRight);
act->addDefaultInputMapping("RIGHT");
act->addDefaultInputMapping("JOY_RIGHT");
gameKeymap->addAction(act);
act = new Action("FPS", _("Show FPS"));
act->setCustomEngineActionEvent(kZVisionActionShowFPS);
act->addDefaultInputMapping("F10");
gameKeymap->addAction(act);
act = new Action("HELP", _("Help"));
act->setKeyEvent(KEYCODE_F1);
act->addDefaultInputMapping("F1");
act->addDefaultInputMapping("JOY_LEFT_TRIGGER");
gameKeymap->addAction(act);
act = new Action("INV", _("Inventory"));
act->setKeyEvent(KEYCODE_F5);
act->addDefaultInputMapping("F5");
act->addDefaultInputMapping("JOY_LEFT_SHOULDER");
gameKeymap->addAction(act);
act = new Action("SPELL", _("Spellbook"));
act->setKeyEvent(KEYCODE_F6);
act->addDefaultInputMapping("F6");
act->addDefaultInputMapping("JOY_RIGHT_SHOULDER");
gameKeymap->addAction(act);
act = new Action("SCORE", _("Score"));
act->setKeyEvent(KEYCODE_F7);
act->addDefaultInputMapping("F7");
act->addDefaultInputMapping("JOY_RIGHT_TRIGGER");
gameKeymap->addAction(act);
act = new Action("AWAY", _("Put away object"));
act->setKeyEvent(KEYCODE_F8);
act->addDefaultInputMapping("F8");
act->addDefaultInputMapping("JOY_X");
gameKeymap->addAction(act);
act = new Action("COIN", _("Extract coin"));
act->setKeyEvent(KEYCODE_F9);
act->addDefaultInputMapping("F9");
act->addDefaultInputMapping("JOY_Y");
gameKeymap->addAction(act);
act = new Action(kStandardActionSave, _("Save"));
act->setCustomEngineActionEvent(kZVisionActionSave);
act->addDefaultInputMapping("C+s");
gameKeymap->addAction(act);
act = new Action(kStandardActionLoad, _("Restore"));
act->setCustomEngineActionEvent(kZVisionActionRestore);
act->addDefaultInputMapping("C+r");
gameKeymap->addAction(act);
act = new Action("QUIT", _("Quit"));
act->setCustomEngineActionEvent(kZVisionActionQuit);
act->addDefaultInputMapping("C+q");
gameKeymap->addAction(act);
act = new Action(kStandardActionOpenSettings, _("Preferences"));
act->setCustomEngineActionEvent(kZVisionActionPreferences);
act->addDefaultInputMapping("C+p");
gameKeymap->addAction(act);
Keymap *cutscenesKeymap = new Keymap(Keymap::kKeymapTypeGame, cutscenesKeymapId, "Z-Vision - Cutscenes");
act = new Action(kStandardActionSkip, _("Skip cutscene"));
act->setCustomEngineActionEvent(kZVisionActionSkipCutscene);
act->addDefaultInputMapping("SPACE");
act->addDefaultInputMapping("JOY_Y");
cutscenesKeymap->addAction(act);
act = new Action("QUIT", _("Quit"));
act->setCustomEngineActionEvent(kZVisionActionQuit);
act->addDefaultInputMapping("C+q");
cutscenesKeymap->addAction(act);
KeymapArray keymaps(3);
keymaps[0] = mainKeymap;
keymaps[1] = gameKeymap;
keymaps[2] = cutscenesKeymap;
return keymaps;
}
Common::Error ZVisionMetaEngine::createInstance(OSystem *syst, Engine **engine, const ZVision::ZVisionGameDescription *desc) const {
*engine = new ZVision::ZVision(syst,desc);
return Common::kNoError;
}
SaveStateList ZVisionMetaEngine::listSaves(const char *target) const {
Common::SaveFileManager *saveFileMan = g_system->getSavefileManager();
ZVision::SaveGameHeader header;
Common::String pattern = target;
pattern += ".###";
Common::StringArray filenames;
filenames = saveFileMan->listSavefiles(pattern.c_str());
SaveStateList saveList;
// We only use readSaveGameHeader() here, which doesn't need an engine callback
ZVision::SaveManager *zvisionSaveMan = new ZVision::SaveManager(NULL);
for (Common::StringArray::const_iterator file = filenames.begin(); file != filenames.end(); file++) {
// Obtain the last 3 digits of the filename, since they correspond to the save slot
int slotNum = atoi(file->c_str() + file->size() - 3);
if (slotNum >= 0 && slotNum <= 999) {
Common::InSaveFile *in = saveFileMan->openForLoading(file->c_str());
if (in) {
if (zvisionSaveMan->readSaveGameHeader(in, header)) {
saveList.push_back(SaveStateDescriptor(this, slotNum, header.saveName));
}
delete in;
}
}
}
delete zvisionSaveMan;
// Sort saves based on slot number.
Common::sort(saveList.begin(), saveList.end(), SaveStateDescriptorSlotComparator());
return saveList;
}
int ZVisionMetaEngine::getMaximumSaveSlot() const {
return 999;
}
bool ZVisionMetaEngine::removeSaveState(const char *target, int slot) const {
Common::SaveFileManager *saveFileMan = g_system->getSavefileManager();
return saveFileMan->removeSavefile(Common::String::format("%s.%03u", target, slot));
}
SaveStateDescriptor ZVisionMetaEngine::querySaveMetaInfos(const char *target, int slot) const {
Common::String filename = Common::String::format("%s.%03u", target, slot);
Common::InSaveFile *in = g_system->getSavefileManager()->openForLoading(filename.c_str());
if (in) {
ZVision::SaveGameHeader header;
// We only use readSaveGameHeader() here, which doesn't need an engine callback
ZVision::SaveManager *zvisionSaveMan = new ZVision::SaveManager(NULL);
bool successfulRead = zvisionSaveMan->readSaveGameHeader(in, header, false);
delete zvisionSaveMan;
delete in;
if (successfulRead) {
SaveStateDescriptor desc(this, slot, header.saveName);
desc.setThumbnail(header.thumbnail);
if (header.version >= 1) {
int day = header.saveDay;
int month = header.saveMonth;
int year = header.saveYear;
desc.setSaveDate(year, month, day);
int hour = header.saveHour;
int minutes = header.saveMinutes;
desc.setSaveTime(hour, minutes);
}
if (header.version >= 2) {
desc.setPlayTime(header.playTime * 1000);
}
return desc;
}
}
return SaveStateDescriptor();
}
#if PLUGIN_ENABLED_DYNAMIC(ZVISION)
REGISTER_PLUGIN_DYNAMIC(ZVISION, PLUGIN_TYPE_ENGINE, ZVisionMetaEngine);
#else
REGISTER_PLUGIN_STATIC(ZVISION, PLUGIN_TYPE_ENGINE, ZVisionMetaEngine);
#endif

67
engines/zvision/module.mk Normal file
View File

@@ -0,0 +1,67 @@
MODULE := engines/zvision
MODULE_OBJS := \
common/scroller.o \
core/clock.o \
core/console.o \
core/events.o \
file/lzss_read_stream.o \
file/file_manager.o \
file/save_manager.o \
file/zfs_archive.o \
graphics/cursors/cursor.o \
graphics/cursors/cursor_manager.o \
graphics/effects/fog.o \
graphics/effects/light.o \
graphics/effects/wave.o \
graphics/render_manager.o \
graphics/render_table.o \
metaengine.o \
scripting/actions.o \
scripting/control.o \
scripting/controls/fist_control.o \
scripting/controls/hotmov_control.o \
scripting/controls/input_control.o \
scripting/controls/lever_control.o \
scripting/controls/paint_control.o \
scripting/controls/push_toggle_control.o \
scripting/controls/safe_control.o \
scripting/controls/save_control.o \
scripting/controls/slot_control.o \
scripting/controls/titler_control.o \
scripting/effects/animation_effect.o \
scripting/effects/distort_effect.o \
scripting/effects/music_effect.o \
scripting/effects/region_effect.o \
scripting/effects/syncsound_effect.o \
scripting/effects/timer_effect.o \
scripting/effects/ttytext_effect.o \
scripting/inventory.o \
scripting/menu.o \
scripting/scr_file_handling.o \
scripting/script_manager.o \
sound/midi.o \
sound/volume_manager.o \
sound/zork_raw.o \
text/string_manager.o \
text/subtitle_manager.o \
text/text.o \
text/truetype_font.o \
video/rlf_decoder.o \
video/video.o \
video/zork_avi_decoder.o \
zvision.o
MODULE_DIRS += \
engines/zvision
# This module can be built as a plugin
ifeq ($(ENABLE_ZVISION), DYNAMIC_PLUGIN)
PLUGIN := 1
endif
# Include common rules
include $(srcdir)/rules.mk
# Detection objects
DETECT_OBJS += $(MODULE)/detection.o

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,446 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef ZVISION_ACTIONS_H
#define ZVISION_ACTIONS_H
#include "common/path.h"
#include "common/rect.h"
#include "common/str.h"
namespace ZVision {
// Forward declaration of ZVision. This file is included before ZVision is declared
class ZVision;
class ScriptManager;
class ValueSlot;
/**
* The base class that represents any action that a Puzzle can take.
* This class is purely virtual.
*/
class ResultAction {
public:
ResultAction(ZVision *engine, int32 slotkey);
virtual ~ResultAction() {}
/**
* This is called by the script system whenever a Puzzle's criteria are found to be true.
* It should execute any necessary actions and return a value indicating whether the script
* system should continue to test puzzles. In 99% of cases this will be 'true'.
*
* @param engine A pointer to the base engine so the ResultAction can access all the necessary methods
* @return Should the script system continue to test any remaining puzzles (true) or immediately break and go on to the next frame (false)
*/
virtual bool execute() = 0;
protected:
ZVision *_engine;
ScriptManager *_scriptManager;
int32 _slotKey;
};
class ActionAdd : public ResultAction {
public:
ActionAdd(ZVision *engine, int32 slotkey, const Common::String &line);
~ActionAdd();
bool execute() override;
private:
uint32 _key;
ValueSlot *_value = NULL;
};
class ActionAssign : public ResultAction {
public:
ActionAssign(ZVision *engine, int32 slotkey, const Common::String &line);
~ActionAssign() override;
bool execute() override;
private:
uint32 _key;
ValueSlot *_value = NULL;
};
class ActionAttenuate : public ResultAction {
public:
ActionAttenuate(ZVision *engine, int32 slotkey, const Common::String &line);
bool execute() override;
private:
uint32 _key;
int32 _attenuation;
};
class ActionChangeLocation : public ResultAction {
public:
ActionChangeLocation(ZVision *engine, int32 slotkey, const Common::String &line);
bool execute() override;
private:
char _world;
char _room;
char _node;
char _view;
uint32 _offset;
};
class ActionCrossfade : public ResultAction {
public:
ActionCrossfade(ZVision *engine, int32 slotkey, const Common::String &line);
bool execute() override;
private:
uint32 _keyOne;
uint32 _keyTwo;
int32 _oneStartVolume;
int32 _twoStartVolume;
int32 _oneEndVolume;
int32 _twoEndVolume;
int32 _timeInMillis;
};
class ActionCursor : public ResultAction {
public:
ActionCursor(ZVision *engine, int32 slotkey, const Common::String &line);
bool execute() override;
private:
uint8 _action;
};
class ActionDelayRender : public ResultAction {
public:
ActionDelayRender(ZVision *engine, int32 slotkey, const Common::String &line);
bool execute() override;
private:
uint32 _framesToDelay;
};
class ActionDisableControl : public ResultAction {
public:
ActionDisableControl(ZVision *engine, int32 slotkey, const Common::String &line);
bool execute() override;
private:
uint32 _key;
};
class ActionDisplayMessage : public ResultAction {
public:
ActionDisplayMessage(ZVision *engine, int32 slotkey, const Common::String &line);
bool execute() override;
private:
int16 _control;
int16 _msgid;
};
class ActionDissolve : public ResultAction {
public:
ActionDissolve(ZVision *engine);
bool execute() override;
};
class ActionDistort : public ResultAction {
public:
ActionDistort(ZVision *engine, int32 slotkey, const Common::String &line);
~ActionDistort() override;
bool execute() override;
private:
int16 _distSlot;
int16 _speed;
float _startAngle;
float _endAngle;
float _startLineScale;
float _endLineScale;
};
class ActionEnableControl : public ResultAction {
public:
ActionEnableControl(ZVision *engine, int32 slotkey, const Common::String &line);
bool execute() override;
private:
uint32 _key;
};
class ActionFlushMouseEvents : public ResultAction {
public:
ActionFlushMouseEvents(ZVision *engine, int32 slotkey);
bool execute() override;
};
class ActionInventory : public ResultAction {
public:
ActionInventory(ZVision *engine, int32 slotkey, const Common::String &line);
bool execute() override;
private:
int8 _type;
int32 _key;
};
class ActionKill : public ResultAction {
public:
ActionKill(ZVision *engine, int32 slotkey, const Common::String &line);
bool execute() override;
private:
uint32 _key;
uint32 _type;
};
class ActionMenuBarEnable : public ResultAction {
public:
ActionMenuBarEnable(ZVision *engine, int32 slotkey, const Common::String &line);
bool execute() override;
private:
uint16 _menus;
};
class ActionMusic : public ResultAction {
public:
ActionMusic(ZVision *engine, int32 slotkey, const Common::String &line, bool global);
~ActionMusic() override;
bool execute() override;
private:
Common::Path _fileName;
bool _loop;
ValueSlot *_volume;
bool _universe;
bool _midi;
int8 _note;
int8 _prog;
};
class ActionPanTrack : public ResultAction {
public:
ActionPanTrack(ZVision *engine, int32 slotkey, const Common::String &line);
~ActionPanTrack() override;
bool execute() override;
private:
int32 _pos; // Sound source position; NB in panoramas (all original game scripts), this is specified as the X background coordinate; otherwise it is specified in azimuth degrees.
uint8 _mag; // Magnitude of effect (not used by original game scripts); 255 for fully directional sound, 0 for fully ambient
bool _resetMusicNode; // If true (default, original game scripts have no concept of this), associated music slot value is reset to a value of 2 upon creation of this object.
// This seems necessary to ensure all original game pan-track effects load correctly, though it is still unclear exactly what the original intent of these values was.
// So far, best guess for music slotkey values is: 0 = has never been loaded, 1 = loaded and actively playing now, 2 = has loaded & played & then subsequently been killed.
// Since there is literally nothing in the game scripts that sets some of these values to 2, and certain pan_tracks require it to be 2 for the puzzle that creates them to trigger, the original game engine code must have set these values to 2 manually somehow upon conditions being met to allow a pan_track to be created?
bool _staticScreen; // Used by auxiliary scripts to apply directionality to audio in static screens; not used in original game scripts.
bool _resetMixerOnDelete; // Unnecessary and should be set false for original scripts; useful in some cases in extra scripts to avoid brief volume spikes on location changes
uint32 _musicSlot;
};
class ActionPlayAnimation : public ResultAction {
public:
ActionPlayAnimation(ZVision *engine, int32 slotkey, const Common::String &line);
~ActionPlayAnimation() override;
bool execute() override;
private:
Common::Path _fileName;
uint32 _x;
uint32 _y;
uint32 _x2;
uint32 _y2;
uint32 _start;
uint32 _end;
int32 _mask;
int32 _framerate;
int32 _loopCount;
};
class ActionPlayPreloadAnimation : public ResultAction {
public:
ActionPlayPreloadAnimation(ZVision *engine, int32 slotkey, const Common::String &line);
bool execute() override;
private:
uint32 _controlKey;
uint32 _x1;
uint32 _y1;
uint32 _x2;
uint32 _y2;
uint _startFrame;
uint _endFrame;
uint _loopCount;
};
class ActionPreloadAnimation : public ResultAction {
public:
ActionPreloadAnimation(ZVision *engine, int32 slotkey, const Common::String &line);
~ActionPreloadAnimation() override;
bool execute() override;
private:
Common::Path _fileName;
int32 _mask;
int32 _framerate;
};
class ActionPreferences : public ResultAction {
public:
ActionPreferences(ZVision *engine, int32 slotkey, const Common::String &line);
bool execute() override;
private:
bool _save;
};
class ActionQuit : public ResultAction {
public:
ActionQuit(ZVision *engine, int32 slotkey) : ResultAction(engine, slotkey) {}
bool execute() override;
};
class ActionRegion : public ResultAction {
public:
ActionRegion(ZVision *engine, int32 slotkey, const Common::String &line);
~ActionRegion() override;
bool execute() override;
private:
Common::String _art;
Common::String _custom;
Common::Rect _rect;
uint16 _delay;
uint16 _type;
uint16 _unk1;
uint16 _unk2;
};
// Only used by ZGI (locations cd6e, cd6k, dg2f, dg4e, dv1j)
class ActionUnloadAnimation : public ResultAction {
public:
ActionUnloadAnimation(ZVision *engine, int32 slotkey, const Common::String &line);
bool execute() override;
private:
uint32 _key;
};
class ActionRandom : public ResultAction {
public:
ActionRandom(ZVision *engine, int32 slotkey, const Common::String &line);
~ActionRandom() override;
bool execute() override;
private:
ValueSlot *_max;
};
class ActionRestoreGame : public ResultAction {
public:
ActionRestoreGame(ZVision *engine, int32 slotkey, const Common::String &line);
bool execute() override;
private:
Common::Path _fileName;
};
class ActionRotateTo : public ResultAction {
public:
ActionRotateTo(ZVision *engine, int32 slotkey, const Common::String &line);
bool execute() override;
private:
int32 _toPos;
int32 _time;
};
class ActionSetPartialScreen : public ResultAction {
public:
ActionSetPartialScreen(ZVision *engine, int32 slotkey, const Common::String &line);
bool execute() override;
private:
uint _x;
uint _y;
Common::Path _fileName;
int32 _backgroundColor;
};
class ActionSetScreen : public ResultAction {
public:
ActionSetScreen(ZVision *engine, int32 slotkey, const Common::String &line);
bool execute() override;
private:
Common::Path _fileName;
};
class ActionStop : public ResultAction {
public:
ActionStop(ZVision *engine, int32 slotkey, const Common::String &line);
bool execute() override;
private:
uint32 _key;
};
class ActionStreamVideo : public ResultAction {
public:
ActionStreamVideo(ZVision *engine, int32 slotkey, const Common::String &line);
bool execute() override;
private:
Common::Path _fileName;
uint _x1;
uint _y1;
uint _x2;
uint _y2;
uint _flags;
bool _skippable;
};
class ActionSyncSound : public ResultAction {
public:
ActionSyncSound(ZVision *engine, int32 slotkey, const Common::String &line);
bool execute() override;
private:
int _syncto;
Common::Path _fileName;
};
class ActionTimer : public ResultAction {
public:
ActionTimer(ZVision *engine, int32 slotkey, const Common::String &line);
~ActionTimer() override;
bool execute() override;
private:
ValueSlot *_time;
};
class ActionTtyText : public ResultAction {
public:
ActionTtyText(ZVision *engine, int32 slotkey, const Common::String &line);
~ActionTtyText() override;
bool execute() override;
private:
Common::Path _filename;
uint32 _delay;
Common::Rect _r;
};
} // End of namespace ZVision
#endif

View File

@@ -0,0 +1,140 @@
/* 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/debug.h"
#include "common/scummsys.h"
#include "common/stream.h"
#include "zvision/detection.h"
#include "zvision/zvision.h"
#include "zvision/graphics/render_manager.h"
#include "zvision/scripting/control.h"
#include "zvision/scripting/script_manager.h"
namespace ZVision {
void Control::parseFlatControl(ZVision *engine) {
debugC(1, kDebugGraphics, "Setting render state to FLAT");
engine->getRenderManager()->getRenderTable()->setRenderState(RenderTable::FLAT);
}
void Control::parsePanoramaControl(ZVision *engine, Common::SeekableReadStream &stream) {
debugC(1, kDebugGraphics, "Setting render state to PANORAMA");
RenderTable *renderTable = engine->getRenderManager()->getRenderTable();
renderTable->setRenderState(RenderTable::PANORAMA);
// Loop until we find the closing brace
Common::String line = stream.readLine();
engine->getScriptManager()->trimCommentsAndWhiteSpace(&line);
while (!stream.eos() && !line.contains('}')) {
if (line.matchString("angle*", true)) {
float fov;
if (sscanf(line.c_str(), "angle(%f)", &fov) == 1)
renderTable->setPanoramaFoV(fov);
} else if (line.matchString("linscale*", true)) {
float scale;
if (sscanf(line.c_str(), "linscale(%f)", &scale) == 1)
renderTable->setPanoramaScale(scale);
} else if (line.matchString("reversepana*", true)) {
uint reverse = 0;
sscanf(line.c_str(), "reversepana(%u)", &reverse);
if (reverse == 1) {
renderTable->setPanoramaReverse(true);
}
} else if (line.matchString("zeropoint*", true)) {
uint point;
if (sscanf(line.c_str(), "zeropoint(%u)", &point) == 1)
renderTable->setPanoramaZeroPoint(point);
}
line = stream.readLine();
engine->getScriptManager()->trimCommentsAndWhiteSpace(&line);
}
renderTable->generateRenderTable();
}
// Only used in Zork Nemesis, handles tilt controls (ZGI doesn't have a tilt view)
void Control::parseTiltControl(ZVision *engine, Common::SeekableReadStream &stream) {
debugC(1, kDebugGraphics, "Setting render state to TILT");
RenderTable *renderTable = engine->getRenderManager()->getRenderTable();
renderTable->setRenderState(RenderTable::TILT);
// Loop until we find the closing brace
Common::String line = stream.readLine();
engine->getScriptManager()->trimCommentsAndWhiteSpace(&line);
while (!stream.eos() && !line.contains('}')) {
if (line.matchString("angle*", true)) {
float fov;
if (sscanf(line.c_str(), "angle(%f)", &fov) == 1)
renderTable->setTiltFoV(fov);
} else if (line.matchString("linscale*", true)) {
float scale;
if (sscanf(line.c_str(), "linscale(%f)", &scale) == 1)
renderTable->setTiltScale(scale);
} else if (line.matchString("reversepana*", true)) {
uint reverse = 0;
sscanf(line.c_str(), "reversepana(%u)", &reverse);
if (reverse == 1) {
renderTable->setTiltReverse(true);
}
}
line = stream.readLine();
engine->getScriptManager()->trimCommentsAndWhiteSpace(&line);
}
renderTable->generateRenderTable();
}
void Control::getParams(const Common::String &inputStr, Common::String &parameter, Common::String &values) {
const char *chrs = inputStr.c_str();
uint lbr;
for (lbr = 0; lbr < inputStr.size(); lbr++)
if (chrs[lbr] == '(')
break;
if (lbr >= inputStr.size())
return;
uint rbr;
for (rbr = lbr + 1; rbr < inputStr.size(); rbr++)
if (chrs[rbr] == ')')
break;
if (rbr >= inputStr.size())
return;
parameter = Common::String(chrs, chrs + lbr);
values = Common::String(chrs + lbr + 1, chrs + rbr);
}
void Control::setVenus() {
if (_venusId >= 0)
if (_engine->getScriptManager()->getStateValue(_venusId) > 0)
_engine->getScriptManager()->setStateValue(StateKey_Venus, _venusId);
}
} // End of namespace ZVision

View File

@@ -0,0 +1,146 @@
/* 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 ZVISION_CONTROL_H
#define ZVISION_CONTROL_H
#include "common/keyboard.h"
#include "common/str.h"
namespace Common {
class SeekableReadStream;
struct Point;
class WriteStream;
}
namespace ZVision {
class ZVision;
/**
* The base class for all Controls.
*
* Controls are the things that the user interacts with. Ex: A lever on the door
*/
class Control {
public:
enum ControlType {
CONTROL_UNKNOW,
CONTROL_INPUT,
CONTROL_PUSHTGL,
CONTROL_SLOT,
CONTROL_LEVER,
CONTROL_SAVE,
CONTROL_SAFE,
CONTROL_FIST,
CONTROL_TITLER,
CONTROL_HOTMOV,
CONTROL_PAINT
};
Control(ZVision *engine, uint32 key, ControlType type) : _engine(engine), _key(key), _type(type), _venusId(-1) {}
virtual ~Control() {}
uint32 getKey() {
return _key;
}
ControlType getType() {
return _type;
}
virtual void focus() {}
virtual void unfocus() {}
/**
* Called when LeftMouse is pushed. Default is NOP.
*
* @param screenSpacePos The position of the mouse in screen space
* @param backgroundImageSpacePos The position of the mouse in background image space
*/
virtual bool onMouseDown(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos) {
return false;
}
/**
* Called when LeftMouse is lifted. Default is NOP.
*
* @param screenSpacePos The position of the mouse in screen space
* @param backgroundImageSpacePos The position of the mouse in background image space
*/
virtual bool onMouseUp(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos) {
return false;
}
/**
* Called on every MouseMove. Default is NOP.
*
* @param screenSpacePos The position of the mouse in screen space
* @param backgroundImageSpacePos The position of the mouse in background image space
* @return Was the cursor changed?
*/
virtual bool onMouseMove(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos) {
return false;
}
/**
* Called when a key is pressed. Default is NOP.
*
* @param keycode The key that was pressed
*/
virtual bool onKeyDown(Common::KeyState keyState) {
return false;
}
/**
* Called when a key is released. Default is NOP.
*
* @param keycode The key that was pressed
*/
virtual bool onKeyUp(Common::KeyState keyState) {
return false;
}
/**
* Processes the node given the deltaTime since last frame. Default is NOP.
*
* @param deltaTimeInMillis The number of milliseconds that have passed since last frame
* @return If true, the node can be deleted after process() finishes
*/
virtual bool process(uint32 deltaTimeInMillis) {
return false;
}
void setVenus();
protected:
ZVision *_engine;
uint32 _key;
int32 _venusId;
void getParams(const Common::String &inputStr, Common::String &parameter, Common::String &values);
// Static member functions
public:
static void parseFlatControl(ZVision *engine);
static void parsePanoramaControl(ZVision *engine, Common::SeekableReadStream &stream);
static void parseTiltControl(ZVision *engine, Common::SeekableReadStream &stream);
private:
ControlType _type;
};
} // End of namespace ZVision
#endif

View File

@@ -0,0 +1,304 @@
/* 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/file.h"
#include "common/scummsys.h"
#include "common/stream.h"
#include "common/system.h"
#include "graphics/surface.h"
#include "video/video_decoder.h"
#include "zvision/zvision.h"
#include "zvision/graphics/render_manager.h"
#include "zvision/graphics/cursors/cursor_manager.h"
#include "zvision/scripting/script_manager.h"
#include "zvision/scripting/controls/fist_control.h"
#include "zvision/video/rlf_decoder.h"
namespace ZVision {
FistControl::FistControl(ZVision *engine, uint32 key, Common::SeekableReadStream &stream)
: Control(engine, key, CONTROL_FIST) {
_cursor = CursorIndex_Idle;
_animation = NULL;
_soundKey = 0;
_fiststatus = 0;
_order = 0;
_fistnum = 0;
_animationId = 0;
clearFistArray(_fistsUp);
clearFistArray(_fistsDwn);
_numEntries = 0;
_entries.clear();
_anmRect = Common::Rect();
// Loop until we find the closing brace
Common::String line = stream.readLine();
_engine->getScriptManager()->trimCommentsAndWhiteSpace(&line);
Common::String param;
Common::String values;
getParams(line, param, values);
while (!stream.eos() && !line.contains('}')) {
if (param.matchString("sound_key", true)) {
_soundKey = atoi(values.c_str());
} else if (param.matchString("cursor", true)) {
_cursor = _engine->getCursorManager()->getCursorId(values);
} else if (param.matchString("descfile", true)) {
readDescFile(Common::Path(values));
} else if (param.matchString("animation_id", true)) {
_animationId = atoi(values.c_str());
} else if (param.matchString("venus_id", true)) {
_venusId = atoi(values.c_str());
}
line = stream.readLine();
_engine->getScriptManager()->trimCommentsAndWhiteSpace(&line);
getParams(line, param, values);
}
}
FistControl::~FistControl() {
if (_animation)
delete _animation;
clearFistArray(_fistsUp);
clearFistArray(_fistsDwn);
_entries.clear();
}
bool FistControl::process(uint32 deltaTimeInMillis) {
if (_engine->getScriptManager()->getStateFlag(_key) & Puzzle::DISABLED)
return false;
if (_animation && _animation->isPlaying()) {
if (_animation->endOfVideo()) {
_animation->stop();
_engine->getScriptManager()->setStateValue(_animationId, 2);
return false;
}
if (_animation->needsUpdate()) {
const Graphics::Surface *frameData = _animation->decodeNextFrame();
if (frameData)
// WORKAROUND: Ignore the target frame dimensions for the finger animations.
// The target dimensions specify an area smaller than expected, thus if we
// scale the finger videos to fit these dimensions, they are not aligned
// correctly. Not scaling these videos yields a result identical to the
// original. Fixes bug #6784.
_engine->getRenderManager()->blitSurfaceToBkg(*frameData, _anmRect.left, _anmRect.top);
}
}
return false;
}
bool FistControl::onMouseMove(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos) {
if (_engine->getScriptManager()->getStateFlag(_key) & Puzzle::DISABLED)
return false;
if (mouseIn(screenSpacePos, backgroundImageSpacePos) >= 0) {
_engine->getCursorManager()->changeCursor(_cursor);
return true;
}
return false;
}
bool FistControl::onMouseUp(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos) {
if (_engine->getScriptManager()->getStateFlag(_key) & Puzzle::DISABLED)
return false;
int fistNumber = mouseIn(screenSpacePos, backgroundImageSpacePos);
if (fistNumber >= 0) {
setVenus();
uint32 oldStatus = _fiststatus;
_fiststatus ^= (1 << fistNumber);
for (int i = 0; i < _numEntries; i++)
if (_entries[i]._bitsStrt == oldStatus && _entries[i]._bitsEnd == _fiststatus) {
if (_animation) {
_animation->stop();
_animation->seekToFrame(_entries[i]._anmStrt);
_animation->setEndFrame(_entries[i]._anmEnd);
_animation->start();
}
_engine->getScriptManager()->setStateValue(_animationId, 1);
_engine->getScriptManager()->setStateValue(_soundKey, _entries[i]._sound);
break;
}
_engine->getScriptManager()->setStateValue(_key, _fiststatus);
}
return false;
}
void FistControl::readDescFile(const Common::Path &fileName) {
Common::File file;
if (!file.open(fileName)) {
warning("Desc file %s could could be opened", fileName.toString().c_str());
return;
}
Common::String line;
Common::String param;
Common::String values;
while (!file.eos()) {
line = file.readLine();
getFistParams(line, param, values);
if (param.matchString("animation_id", true)) {
// Not used
} else if (param.matchString("animation", true)) {
_animation = _engine->loadAnimation(Common::Path(values));
} else if (param.matchString("anim_rect", true)) {
int left, top, right, bottom;
if (sscanf(values.c_str(), "%d %d %d %d", &left, &top, &right, &bottom) == 4)
_anmRect = Common::Rect(left, top, right, bottom);
} else if (param.matchString("num_fingers", true)) {
if (sscanf(values.c_str(), "%d", &_fistnum) == 1) {
_fistsUp.resize(_fistnum);
_fistsDwn.resize(_fistnum);
}
} else if (param.matchString("entries", true)) {
if (sscanf(values.c_str(), "%d", &_numEntries) == 1)
_entries.resize(_numEntries);
} else if (param.matchString("eval_order_ascending", true)) {
sscanf(values.c_str(), "%d", &_order);
} else if (param.matchString("up_hs_num_*", true)) {
int fist, num;
num = atoi(values.c_str());
if (sscanf(param.c_str(), "up_hs_num_%d", &fist) == 1)
_fistsUp[fist].resize(num);
} else if (param.matchString("up_hs_*", true)) {
int16 fist, box, x1, y1, x2, y2;
if (sscanf(param.c_str(), "up_hs_%hd_%hd", &fist, &box) == 2) {
if (sscanf(values.c_str(), "%hd %hd %hd %hd", &x1, &y1, &x2, &y2) == 4)
(_fistsUp[fist])[box] = Common::Rect(x1, y1, x2, y2);
}
} else if (param.matchString("down_hs_num_*", true)) {
int fist, num;
num = atoi(values.c_str());
if (sscanf(param.c_str(), "down_hs_num_%d", &fist) == 1)
_fistsDwn[fist].resize(num);
} else if (param.matchString("down_hs_*", true)) {
int16 fist, box, x1, y1, x2, y2;
if (sscanf(param.c_str(), "down_hs_%hd_%hd", &fist, &box) == 2) {
if (sscanf(values.c_str(), "%hd %hd %hd %hd", &x1, &y1, &x2, &y2) == 4)
(_fistsDwn[fist])[box] = Common::Rect(x1, y1, x2, y2);
}
} else {
int entry, start, end, sound;
char bitsStart[33];
char bitsEnd[33];
entry = atoi(param.c_str());
if (sscanf(values.c_str(), "%s %s %d %d (%d)", bitsStart, bitsEnd, &start, &end, &sound) == 5) {
_entries[entry]._bitsStrt = readBits(bitsStart);
_entries[entry]._bitsEnd = readBits(bitsEnd);
_entries[entry]._anmStrt = start;
_entries[entry]._anmEnd = end;
_entries[entry]._sound = sound;
}
}
}
file.close();
}
void FistControl::clearFistArray(Common::Array< Common::Array<Common::Rect> > &arr) {
for (uint i = 0; i < arr.size(); i++)
arr[i].clear();
arr.clear();
}
uint32 FistControl::readBits(const char *str) {
uint32 bfield = 0;
int len = strlen(str);
for (int i = 0; i < len; i++)
if (str[i] != '0')
bfield |= (1 << i);
return bfield;
}
int FistControl::mouseIn(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos) {
if (_order) {
for (int i = 0; i < _fistnum; i++) {
if (((_fiststatus >> i) & 1) == 1) {
for (uint j = 0; j < _fistsDwn[i].size(); j++)
if ((_fistsDwn[i])[j].contains(backgroundImageSpacePos))
return i;
} else {
for (uint j = 0; j < _fistsUp[i].size(); j++)
if ((_fistsUp[i])[j].contains(backgroundImageSpacePos))
return i;
}
}
} else {
for (int i = _fistnum - 1; i >= 0; i--) {
if (((_fiststatus >> i) & 1) == 1) {
for (uint j = 0; j < _fistsDwn[i].size(); j++)
if ((_fistsDwn[i])[j].contains(backgroundImageSpacePos))
return i;
} else {
for (uint j = 0; j < _fistsUp[i].size(); j++)
if ((_fistsUp[i])[j].contains(backgroundImageSpacePos))
return i;
}
}
}
return -1;
}
void FistControl::getFistParams(const Common::String &inputStr, Common::String &parameter, Common::String &values) {
const char *chrs = inputStr.c_str();
uint lbr;
for (lbr = 0; lbr < inputStr.size(); lbr++)
if (chrs[lbr] == ':')
break;
if (lbr >= inputStr.size())
return;
uint rbr;
for (rbr = lbr + 1; rbr < inputStr.size(); rbr++)
if (chrs[rbr] == '~')
break;
if (rbr >= inputStr.size())
return;
parameter = Common::String(chrs, chrs + lbr);
values = Common::String(chrs + lbr + 1, chrs + rbr);
}
} // End of namespace ZVision

View File

@@ -0,0 +1,82 @@
/* 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 ZVISION_FIST_CONTROL_H
#define ZVISION_FIST_CONTROL_H
#include "common/array.h"
#include "common/rect.h"
#include "zvision/scripting/control.h"
namespace Video {
class VideoDecoder;
}
namespace ZVision {
// Only used in Zork Nemesis, handles the door lock puzzle with the skeletal fingers (td9e)
class FistControl : public Control {
public:
FistControl(ZVision *engine, uint32 key, Common::SeekableReadStream &stream);
~FistControl() override;
private:
uint32 _fiststatus;
int _fistnum;
int16 _cursor;
int _order;
Common::Array< Common::Array<Common::Rect> > _fistsUp;
Common::Array< Common::Array<Common::Rect> > _fistsDwn;
int32 _numEntries;
struct entries {
uint32 _bitsStrt;
uint32 _bitsEnd;
int32 _anmStrt;
int32 _anmEnd;
int32 _sound;
};
Common::Array<entries> _entries;
Video::VideoDecoder *_animation;
Common::Rect _anmRect;
int32 _soundKey;
int32 _animationId;
public:
bool onMouseUp(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos) override;
bool onMouseMove(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos) override;
bool process(uint32 deltaTimeInMillis) override;
private:
void readDescFile(const Common::Path &fileName);
void clearFistArray(Common::Array< Common::Array<Common::Rect> > &arr);
uint32 readBits(const char *str);
int mouseIn(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos);
void getFistParams(const Common::String &inputStr, Common::String &parameter, Common::String &values);
};
} // End of namespace ZVision
#endif

View File

@@ -0,0 +1,184 @@
/* 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/file.h"
#include "common/scummsys.h"
#include "common/stream.h"
#include "common/system.h"
#include "graphics/surface.h"
#include "video/video_decoder.h"
#include "zvision/zvision.h"
#include "zvision/graphics/render_manager.h"
#include "zvision/graphics/cursors/cursor_manager.h"
#include "zvision/scripting/script_manager.h"
#include "zvision/scripting/controls/hotmov_control.h"
namespace ZVision {
HotMovControl::HotMovControl(ZVision *engine, uint32 key, Common::SeekableReadStream &stream)
: Control(engine, key, CONTROL_HOTMOV) {
_animation = NULL;
_cycle = 0;
_frames.clear();
_cyclesCount = 0;
_framesCount = 0;
_engine->getScriptManager()->setStateValue(_key, 0);
// Loop until we find the closing brace
Common::String line = stream.readLine();
_engine->getScriptManager()->trimCommentsAndWhiteSpace(&line);
Common::String param;
Common::String values;
getParams(line, param, values);
while (!stream.eos() && !line.contains('}')) {
if (param.matchString("hs_frame_list", true)) {
readHsFile(Common::Path(values));
} else if (param.matchString("rectangle", true)) {
int x;
int y;
int width;
int height;
if (sscanf(values.c_str(), "%d %d %d %d", &x, &y, &width, &height) == 4)
_rectangle = Common::Rect(x, y, width, height);
} else if (param.matchString("num_frames", true)) {
_framesCount = atoi(values.c_str());
} else if (param.matchString("num_cycles", true)) {
_cyclesCount = atoi(values.c_str());
} else if (param.matchString("animation", true)) {
char filename[64];
if (sscanf(values.c_str(), "%s", filename) == 1) {
values = Common::String(filename);
_animation = _engine->loadAnimation(Common::Path(values));
_animation->start();
}
} else if (param.matchString("venus_id", true)) {
_venusId = atoi(values.c_str());
}
line = stream.readLine();
_engine->getScriptManager()->trimCommentsAndWhiteSpace(&line);
getParams(line, param, values);
}
}
HotMovControl::~HotMovControl() {
if (_animation)
delete _animation;
_frames.clear();
}
bool HotMovControl::process(uint32 deltaTimeInMillis) {
if (_engine->getScriptManager()->getStateFlag(_key) & Puzzle::DISABLED)
return false;
if (_cycle < _cyclesCount) {
if (_animation && _animation->endOfVideo()) {
_cycle++;
if (_cycle == _cyclesCount) {
_engine->getScriptManager()->setStateValue(_key, 2);
return false;
}
_animation->rewind();
}
if (_animation && _animation->needsUpdate()) {
const Graphics::Surface *frameData = _animation->decodeNextFrame();
if (frameData)
_engine->getRenderManager()->blitSurfaceToBkgScaled(*frameData, _rectangle);
}
}
return false;
}
bool HotMovControl::onMouseMove(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos) {
if (_engine->getScriptManager()->getStateFlag(_key) & Puzzle::DISABLED)
return false;
if (!_animation)
return false;
if (_cycle < _cyclesCount) {
if (_frames[_animation->getCurFrame()].contains(backgroundImageSpacePos)) {
_engine->getCursorManager()->changeCursor(CursorIndex_Active);
return true;
}
}
return false;
}
bool HotMovControl::onMouseUp(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos) {
if (_engine->getScriptManager()->getStateFlag(_key) & Puzzle::DISABLED)
return false;
if (!_animation)
return false;
if (_cycle < _cyclesCount) {
if (_frames[_animation->getCurFrame()].contains(backgroundImageSpacePos)) {
setVenus();
_engine->getScriptManager()->setStateValue(_key, 1);
return true;
}
}
return false;
}
void HotMovControl::readHsFile(const Common::Path &fileName) {
if (_framesCount == 0)
return;
Common::File file;
if (!file.open(fileName)) {
warning("HS file %s could could be opened", fileName.toString().c_str());
return;
}
Common::String line;
_frames.resize(_framesCount);
while (!file.eos()) {
line = file.readLine();
int frame;
int x;
int y;
int width;
int height;
if (sscanf(line.c_str(), "%d:%d %d %d %d~", &frame, &x, &y, &width, &height) == 5) {
if (frame >= 0 && frame < _framesCount)
_frames[frame] = Common::Rect(x, y, width, height);
}
}
file.close();
}
} // End of namespace ZVision

View File

@@ -0,0 +1,60 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef ZVISION_HOTMOV_CONTROL_H
#define ZVISION_HOTMOV_CONTROL_H
#include "common/array.h"
#include "common/path.h"
#include "common/rect.h"
#include "zvision/scripting/control.h"
namespace Video {
class VideoDecoder;
}
namespace ZVision {
// Only used in Zork Nemesis, handles movies where the player needs to click on something (mj7g, vw3g)
class HotMovControl : public Control {
public:
HotMovControl(ZVision *engine, uint32 key, Common::SeekableReadStream &stream);
~HotMovControl() override;
private:
int32 _framesCount;
int32 _cycle;
int32 _cyclesCount;
Video::VideoDecoder *_animation;
Common::Rect _rectangle;
Common::Array<Common::Rect> _frames;
public:
bool onMouseUp(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos) override;
bool onMouseMove(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos) override;
bool process(uint32 deltaTimeInMillis) override;
private:
void readHsFile(const Common::Path &fileName);
};
} // End of namespace ZVision
#endif

View File

@@ -0,0 +1,271 @@
/* 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 "backends/keymapper/keymap.h"
#include "common/rect.h"
#include "common/scummsys.h"
#include "common/str.h"
#include "common/stream.h"
#include "common/system.h"
#include "video/video_decoder.h"
#include "zvision/zvision.h"
#include "zvision/graphics/render_manager.h"
#include "zvision/graphics/cursors/cursor_manager.h"
#include "zvision/scripting/script_manager.h"
#include "zvision/scripting/controls/input_control.h"
#include "zvision/text/string_manager.h"
namespace ZVision {
InputControl::InputControl(ZVision *engine, uint32 key, Common::SeekableReadStream &stream)
: Control(engine, key, CONTROL_INPUT),
_background(0),
_nextTabstop(0),
_focused(false),
_textChanged(false),
_enterPressed(false),
_readOnly(false),
_txtWidth(0),
_animation(NULL) {
// Loop until we find the closing brace
Common::String line = stream.readLine();
_engine->getScriptManager()->trimCommentsAndWhiteSpace(&line);
Common::String param;
Common::String values;
getParams(line, param, values);
while (!stream.eos() && !line.contains('}')) {
if (param.matchString("rectangle", true)) {
int x1, y1, x2, y2;
if (sscanf(values.c_str(), "%d %d %d %d", &x1, &y1, &x2, &y2) == 4)
_textRectangle = Common::Rect(x1, y1, x2, y2);
} else if (param.matchString("aux_hotspot", true)) {
int x1, y1, x2, y2;
if (sscanf(values.c_str(), "%d %d %d %d", &x1, &y1, &x2, &y2) == 4)
_headerRectangle = Common::Rect(x1, y1, x2, y2);
} else if (param.matchString("string_init", true)) {
uint fontFormatNumber;
if (sscanf(values.c_str(), "%u", &fontFormatNumber) == 1)
_stringInit.readAllStyles(_engine->getStringManager()->getTextLine(fontFormatNumber));
} else if (param.matchString("chooser_init_string", true)) {
uint fontFormatNumber;
if (sscanf(values.c_str(), "%u", &fontFormatNumber) == 1)
_stringChooserInit.readAllStyles(_engine->getStringManager()->getTextLine(fontFormatNumber));
} else if (param.matchString("next_tabstop", true)) {
sscanf(values.c_str(), "%u", &_nextTabstop);
} else if (param.matchString("cursor_dimensions", true)) {
// Ignore, use the dimensions in the animation file
} else if (param.matchString("cursor_animation_frames", true)) {
// Ignore, use the frame count in the animation file
} else if (param.matchString("cursor_animation", true)) {
char fileName[25];
if (sscanf(values.c_str(), "%24s %*u", fileName) == 1) {
_animation = _engine->loadAnimation(fileName);
_animation->start();
}
} else if (param.matchString("focus", true)) {
_focused = true;
_engine->getScriptManager()->setFocusControlKey(_key);
} else if (param.matchString("venus_id", true)) {
_venusId = atoi(values.c_str());
}
line = stream.readLine();
_engine->getScriptManager()->trimCommentsAndWhiteSpace(&line);
getParams(line, param, values);
}
_maxTxtWidth = _textRectangle.width();
if (_animation)
_maxTxtWidth -= _animation->getWidth();
}
InputControl::~InputControl() {
_background->free();
delete _background;
unfocus();
}
void InputControl::focus() {
if (!_readOnly) {
_engine->getGameKeymap()->setEnabled(false);
}
_focused = true;
_textChanged = true;
}
void InputControl::unfocus() {
if (!_readOnly) {
_engine->getGameKeymap()->setEnabled(true);
}
_focused = false;
_textChanged = true;
}
bool InputControl::onMouseUp(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos) {
if (_engine->getScriptManager()->getStateFlag(_key) & Puzzle::DISABLED)
return false;
if (_textRectangle.contains(backgroundImageSpacePos)) {
if (!_readOnly) {
// Save
_engine->getScriptManager()->focusControl(_key);
setVenus();
} else {
// Restore
if (_currentInputText.size()) {
setVenus();
_enterPressed = true;
}
}
}
return false;
}
bool InputControl::onMouseMove(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos) {
if (_engine->getScriptManager()->getStateFlag(_key) & Puzzle::DISABLED)
return false;
if (_textRectangle.contains(backgroundImageSpacePos)) {
if (!_readOnly) {
// Save
_engine->getCursorManager()->changeCursor(CursorIndex_Active);
return true;
} else {
// Restore
if (_currentInputText.size()) {
_engine->getCursorManager()->changeCursor(CursorIndex_Active);
_engine->getScriptManager()->focusControl(_key);
return true;
}
}
}
return false;
}
bool InputControl::onKeyDown(Common::KeyState keyState) {
if (_engine->getScriptManager()->getStateFlag(_key) & Puzzle::DISABLED)
return false;
if (!_focused) {
return false;
}
if (keyState.keycode == Common::KEYCODE_BACKSPACE) {
if (!_readOnly) {
_currentInputText.deleteLastChar();
_textChanged = true;
}
} else if (keyState.keycode == Common::KEYCODE_RETURN) {
_enterPressed = true;
} else if (keyState.keycode == Common::KEYCODE_TAB) {
unfocus();
// Focus the next input control
_engine->getScriptManager()->focusControl(_nextTabstop);
// Don't process this event for other controls
return true;
} else {
if (!_readOnly) {
// Otherwise, append the new character to the end of the current text
uint16 asciiValue = keyState.ascii;
// We only care about text values
if (asciiValue >= 32 && asciiValue <= 126) {
_currentInputText += (char)asciiValue;
_textChanged = true;
}
}
}
return false;
}
bool InputControl::process(uint32 deltaTimeInMillis) {
if (_engine->getScriptManager()->getStateFlag(_key) & Puzzle::DISABLED)
return false;
if (!_background) {
_background = _engine->getRenderManager()->getBkgRect(_textRectangle);
}
// First see if we need to render the text
if (_textChanged) {
// Blit the text using the RenderManager
Graphics::Surface txt;
txt.copyFrom(*_background);
int32 oldTxtWidth = _txtWidth;
if (!_readOnly || !_focused)
_txtWidth = _engine->getTextRenderer()->drawText(_currentInputText, _stringInit, txt);
else
_txtWidth = _engine->getTextRenderer()->drawText(_currentInputText, _stringChooserInit, txt);
if (_readOnly || _txtWidth <= _maxTxtWidth)
_engine->getRenderManager()->blitSurfaceToBkg(txt, _textRectangle.left, _textRectangle.top);
else {
// Assume the last character caused the overflow.
_currentInputText.deleteLastChar();
_txtWidth = oldTxtWidth;
}
txt.free();
}
if (_animation && !_readOnly && _focused) {
if (_animation->endOfVideo())
_animation->rewind();
if (_animation->needsUpdate()) {
const Graphics::Surface *srf = _animation->decodeNextFrame();
int16 xx = _textRectangle.left + _txtWidth;
if (xx >= _textRectangle.left + (_textRectangle.width() - (int16)_animation->getWidth()))
xx = _textRectangle.left + _textRectangle.width() - (int16)_animation->getWidth();
_engine->getRenderManager()->blitSurfaceToBkg(*srf, xx, _textRectangle.top);
}
}
_textChanged = false;
return false;
}
bool InputControl::enterPress() {
if (_enterPressed) {
_enterPressed = false;
return true;
}
return false;
}
void InputControl::setText(const Common::String &_str) {
_currentInputText = _str;
_textChanged = true;
}
const Common::String InputControl::getText() {
return _currentInputText;
}
void InputControl::setReadOnly(bool readonly) {
_readOnly = readonly;
}
} // End of namespace ZVision

View File

@@ -0,0 +1,74 @@
/* 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 ZVISION_INPUT_CONTROL_H
#define ZVISION_INPUT_CONTROL_H
#include "common/rect.h"
#include "zvision/scripting/control.h"
#include "zvision/text/string_manager.h"
#include "zvision/text/text.h"
namespace Video {
class VideoDecoder;
}
namespace ZVision {
class InputControl : public Control {
public:
InputControl(ZVision *engine, uint32 key, Common::SeekableReadStream &stream);
~InputControl() override;
private:
Graphics::Surface *_background;
Common::Rect _textRectangle;
Common::Rect _headerRectangle;
TextStyleState _stringInit;
TextStyleState _stringChooserInit;
uint32 _nextTabstop;
bool _focused;
Common::String _currentInputText;
bool _textChanged;
bool _enterPressed;
bool _readOnly;
int16 _txtWidth;
int16 _maxTxtWidth;
Video::VideoDecoder *_animation;
public:
void focus() override;
void unfocus() override;
bool onMouseUp(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos) override;
bool onMouseMove(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos) override;
bool onKeyDown(Common::KeyState keyState) override;
bool process(uint32 deltaTimeInMillis) override;
void setText(const Common::String &_str);
const Common::String getText();
bool enterPress();
void setReadOnly(bool);
};
} // End of namespace ZVision
#endif

View File

@@ -0,0 +1,352 @@
/* 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/debug.h"
#include "common/file.h"
#include "common/scummsys.h"
#include "common/stream.h"
#include "common/system.h"
#include "common/tokenizer.h"
#include "graphics/surface.h"
#include "video/video_decoder.h"
#include "zvision/zvision.h"
#include "zvision/graphics/render_manager.h"
#include "zvision/graphics/cursors/cursor_manager.h"
#include "zvision/scripting/script_manager.h"
#include "zvision/scripting/controls/lever_control.h"
namespace ZVision {
LeverControl::LeverControl(ZVision *engine, uint32 key, Common::SeekableReadStream &stream)
: Control(engine, key, CONTROL_LEVER),
_frameInfo(0),
_frameCount(0),
_startFrame(0),
_currentFrame(0),
_lastRenderedFrame(0),
_mouseIsCaptured(false),
_isReturning(false),
_accumulatedTime(0),
_returnRoutesCurrentFrame(0),
_animation(NULL),
_cursor(CursorIndex_Active),
_mirrored(false) {
// Loop until we find the closing brace
Common::String line = stream.readLine();
_engine->getScriptManager()->trimCommentsAndWhiteSpace(&line);
Common::String param;
Common::String values;
getParams(line, param, values);
while (!stream.eos() && !line.contains('}')) {
if (param.matchString("descfile", true)) {
char levFileName[25];
if (sscanf(values.c_str(), "%24s", levFileName) == 1)
parseLevFile(levFileName);
} else if (param.matchString("cursor", true)) {
char cursorName[25];
if (sscanf(values.c_str(), "%24s", cursorName) == 1)
_cursor = _engine->getCursorManager()->getCursorId(Common::String(cursorName));
}
line = stream.readLine();
_engine->getScriptManager()->trimCommentsAndWhiteSpace(&line);
getParams(line, param, values);
}
renderFrame(_currentFrame);
}
LeverControl::~LeverControl() {
if (_animation)
delete _animation;
delete[] _frameInfo;
}
void LeverControl::parseLevFile(const Common::Path &fileName) {
debugC(2, kDebugControl, "LeverControl::parseLevFile(%s)", fileName.toString().c_str());
Common::File file;
if (!file.open(fileName)) {
warning("LEV file %s could could be opened", fileName.toString().c_str());
return;
}
Common::String line;
Common::String param;
Common::String values;
int id = 0;
while (!file.eos()) {
line = file.readLine();
getLevParams(line, param, values);
if (param.matchString("animation_id", true)) {
sscanf(values.c_str(), "%d", &id);
debugC(2, kDebugControl, "Lever animation ID: %d", id);
} else if (param.matchString("filename", true)) {
_animation = _engine->loadAnimation(Common::Path(values));
} else if (param.matchString("skipcolor", true)) {
// Not used
} else if (param.matchString("anim_coords", true)) {
int left, top, right, bottom;
if (sscanf(values.c_str(), "%d %d %d %d", &left, &top, &right, &bottom) == 4) {
_animationCoords.left = left;
_animationCoords.top = top;
_animationCoords.right = right;
_animationCoords.bottom = bottom;
}
} else if (param.matchString("mirrored", true)) {
uint mirrored;
if (sscanf(values.c_str(), "%u", &mirrored) == 1)
_mirrored = mirrored == 0 ? false : true;
} else if (param.matchString("frames", true)) {
if (sscanf(values.c_str(), "%u", &_frameCount) == 1)
_frameInfo = new FrameInfo[_frameCount];
} else if (param.matchString("elsewhere", true)) {
// Not used
} else if (param.matchString("out_of_control", true)) {
// Not used
} else if (param.matchString("start_pos", true)) {
if (sscanf(values.c_str(), "%u", &_startFrame) == 1)
_currentFrame = _startFrame;
} else if (param.matchString("hotspot_deltas", true)) {
uint x;
uint y;
if (sscanf(values.c_str(), "%u %u", &x, &y) == 2) {
_hotspotDelta.x = x;
_hotspotDelta.y = y;
}
} else if (param.matchString("venus_id", true)) {
_venusId = atoi(values.c_str());
} else {
uint frameNumber;
uint x, y;
line.toLowercase();
if (sscanf(line.c_str(), "%u:%u %u", &frameNumber, &x, &y) == 3) {
_frameInfo[frameNumber].hotspot.left = x;
_frameInfo[frameNumber].hotspot.top = y;
_frameInfo[frameNumber].hotspot.right = x + _hotspotDelta.x;
_frameInfo[frameNumber].hotspot.bottom = y + _hotspotDelta.y;
}
Common::StringTokenizer tokenizer(line, " ^=()~");
tokenizer.nextToken();
tokenizer.nextToken();
Common::String token = tokenizer.nextToken();
while (!tokenizer.empty()) {
if (token == "d") {
token = tokenizer.nextToken();
uint angle;
uint toFrame;
if (sscanf(token.c_str(), "%u,%u", &toFrame, &angle) == 2)
_frameInfo[frameNumber].paths.push_back(PathSegment(angle, toFrame));
} else if (token.hasPrefix("p")) {
// Format: P(<from> to <to>)
tokenizer.nextToken();
tokenizer.nextToken();
token = tokenizer.nextToken();
uint to = atoi(token.c_str());
_frameInfo[frameNumber].returnRoute.push_back(to);
}
token = tokenizer.nextToken();
}
}
// Don't read lines in this place because last will not be parsed.
}
// WORKAROUND for a script bug in Zork: Nemesis, room tz2e (orrery)
// Animation coordinates for left hand lever do not properly align with background image
switch (id) {
case 2926:
_animationCoords.bottom -= 4;
_animationCoords.right += 1;
_animationCoords.left -= 1;
_animationCoords.top += 1;
break;
default:
break;
}
// Cycle through all unit direction vectors in path segments & determine step distance
debugC(3, kDebugControl, "Setting step distances");
for (uint frame=0; frame < _frameCount; frame++) {
debugC(3, kDebugControl, "Frame %d", frame);
for (auto &iter : _frameInfo[frame].paths) {
uint destFrame = iter.toFrame;
Common::Point deltaPos = _frameInfo[destFrame].hotspot.origin() - _frameInfo[frame].hotspot.origin();
Math::Vector2d deltaPosVector((float)deltaPos.x, (float)deltaPos.y);
iter.distance *= deltaPosVector.getMagnitude();
debugC(3, kDebugControl, "\tdeltaPos = %d,%d, Distance %f", deltaPos.x, deltaPos.y, iter.distance);
}
}
debugC(2, kDebugControl, "LeverControl::~parseLevFile()");
}
bool LeverControl::onMouseDown(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos) {
if (_engine->getScriptManager()->getStateFlag(_key) & Puzzle::DISABLED)
return false;
if (_frameInfo[_currentFrame].hotspot.contains(backgroundImageSpacePos)) {
setVenus();
_mouseIsCaptured = true;
_gripOffset = backgroundImageSpacePos - _frameInfo[_currentFrame].hotspot.origin();
}
return false;
}
bool LeverControl::onMouseUp(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos) {
if (_engine->getScriptManager()->getStateFlag(_key) & Puzzle::DISABLED)
return false;
if (_mouseIsCaptured) {
_mouseIsCaptured = false;
_engine->getScriptManager()->setStateValue(_key, _currentFrame);
_isReturning = true;
_returnRoutesCurrentProgress = _frameInfo[_currentFrame].returnRoute.begin();
_returnRoutesCurrentFrame = _currentFrame;
}
_gripOffset = Common::Point(0,0);
return false;
}
bool LeverControl::onMouseMove(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos) {
if (_engine->getScriptManager()->getStateFlag(_key) & Puzzle::DISABLED)
return false;
bool cursorWasChanged = false;
if (_mouseIsCaptured) {
uint nextFrame = _currentFrame;
do {
Common::Point gripOrigin = _frameInfo[_currentFrame].hotspot.origin() + _gripOffset;
debugC(1, kDebugControl, "LeverControl::onMouseMove() screenPos = %d,%d, imagePos = %d,%d, gripOrigin = %d,%d", screenSpacePos.x, screenSpacePos.y, backgroundImageSpacePos.x, backgroundImageSpacePos.y, gripOrigin.x, gripOrigin.y);
Common::Point deltaPos = backgroundImageSpacePos - gripOrigin;
nextFrame = getNextFrame(deltaPos);
if (nextFrame != _currentFrame) {
_currentFrame = nextFrame;
_engine->getScriptManager()->setStateValue(_key, _currentFrame);
}
} while (nextFrame != _currentFrame);
if (_lastRenderedFrame != _currentFrame)
renderFrame(_currentFrame);
_engine->getCursorManager()->changeCursor(_cursor);
cursorWasChanged = true;
} else if (_frameInfo[_currentFrame].hotspot.contains(backgroundImageSpacePos)) {
_engine->getCursorManager()->changeCursor(_cursor);
cursorWasChanged = true;
}
return cursorWasChanged;
}
uint LeverControl::getNextFrame(Common::Point &deltaPos) {
Math::Vector2d movement((float)deltaPos.x, (float)deltaPos.y);
for (auto &iter : _frameInfo[_currentFrame].paths) {
debugC(1, kDebugControl, "\tPossible step = %f,%f, angle = %d, distance %f", iter.direction.getX(), iter.direction.getY(), (uint)Math::rad2deg(iter.angle), iter.distance);
if (movement.dotProduct(iter.direction) >= iter.distance/2) {
return iter.toFrame;
}
}
return _currentFrame;
}
bool LeverControl::process(uint32 deltaTimeInMillis) {
if (_engine->getScriptManager()->getStateFlag(_key) & Puzzle::DISABLED)
return false;
if (_isReturning) {
_accumulatedTime += deltaTimeInMillis;
while (_accumulatedTime >= _returnFramePeriod) {
_accumulatedTime -= _returnFramePeriod;
if (_returnRoutesCurrentFrame == *_returnRoutesCurrentProgress) {
_returnRoutesCurrentProgress++;
}
if (_returnRoutesCurrentProgress == _frameInfo[_currentFrame].returnRoute.end()) {
_isReturning = false;
_currentFrame = _returnRoutesCurrentFrame;
return false;
}
uint toFrame = *_returnRoutesCurrentProgress;
if (_returnRoutesCurrentFrame < toFrame) {
_returnRoutesCurrentFrame++;
} else if (_returnRoutesCurrentFrame > toFrame) {
_returnRoutesCurrentFrame--;
}
_engine->getScriptManager()->setStateValue(_key, _returnRoutesCurrentFrame);
renderFrame(_returnRoutesCurrentFrame);
}
}
return false;
}
void LeverControl::renderFrame(uint frameNumber) {
_lastRenderedFrame = frameNumber;
if (frameNumber != 0 && frameNumber < _lastRenderedFrame && _mirrored)
frameNumber = (_frameCount * 2) - frameNumber - 1;
const Graphics::Surface *frameData;
_animation->seekToFrame(frameNumber);
frameData = _animation->decodeNextFrame();
if (frameData)
_engine->getRenderManager()->blitSurfaceToBkgScaled(*frameData, _animationCoords);
}
void LeverControl::getLevParams(const Common::String &inputStr, Common::String &parameter, Common::String &values) {
const char *chrs = inputStr.c_str();
uint lbr;
for (lbr = 0; lbr < inputStr.size(); lbr++)
if (chrs[lbr] == ':')
break;
if (lbr >= inputStr.size())
return;
uint rbr;
for (rbr = lbr + 1; rbr < inputStr.size(); rbr++)
if (chrs[rbr] == '~')
break;
if (rbr >= inputStr.size())
return;
parameter = Common::String(chrs, chrs + lbr);
values = Common::String(chrs + lbr + 1, chrs + rbr);
}
} // End of namespace ZVision

View File

@@ -0,0 +1,96 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef ZVISION_LEVER_CONTROL_H
#define ZVISION_LEVER_CONTROL_H
#include "common/list.h"
#include "common/path.h"
#include "common/rect.h"
#include "math/vector2d.h"
#include "zvision/scripting/control.h"
namespace Video {
class VideoDecoder;
}
namespace ZVision {
// Only used in Zork Nemesis, handles draggable levers (te2e, tm7e, tp2e, tt2e, tz2e)
class LeverControl : public Control {
public:
LeverControl(ZVision *engine, uint32 key, Common::SeekableReadStream &stream);
~LeverControl() override;
private:
struct PathSegment {
PathSegment(uint a, uint t) : angle(Math::deg2rad<float>(a)), toFrame(t), direction(cos(angle), -sin(angle)) {}
float angle; // Radians
uint toFrame;
Math::Vector2d direction; // NB unit vector upon initialisation
float distance = 1.0f;
};
struct FrameInfo {
Common::Rect hotspot;
Common::List<PathSegment> paths;
Common::List<uint> returnRoute;
};
private:
Video::VideoDecoder *_animation;
int _cursor;
Common::Rect _animationCoords;
bool _mirrored;
uint _frameCount;
uint _startFrame;
Common::Point _hotspotDelta;
FrameInfo *_frameInfo;
uint _currentFrame;
uint _lastRenderedFrame;
bool _mouseIsCaptured;
bool _isReturning;
Common::Point _gripOffset;
Common::List<uint>::iterator _returnRoutesCurrentProgress;
uint _returnRoutesCurrentFrame;
const uint8 _returnFramePeriod = 60; // milliseconds
uint32 _accumulatedTime;
public:
bool onMouseDown(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos) override;
bool onMouseUp(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos) override;
bool onMouseMove(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos) override;
bool process(uint32 deltaTimeInMillis) override;
private:
void parseLevFile(const Common::Path &fileName);
void renderFrame(uint frameNumber);
void getLevParams(const Common::String &inputStr, Common::String &parameter, Common::String &values);
uint getNextFrame(Common::Point &deltaPos);
};
} // End of namespace ZVision
#endif

View File

@@ -0,0 +1,216 @@
/* 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"
#include "common/stream.h"
#include "zvision/zvision.h"
#include "zvision/graphics/cursors/cursor_manager.h"
#include "zvision/graphics/render_manager.h"
#include "zvision/scripting/script_manager.h"
#include "zvision/scripting/controls/paint_control.h"
namespace ZVision {
PaintControl::PaintControl(ZVision *engine, uint32 key, Common::SeekableReadStream &stream)
: Control(engine, key, CONTROL_PAINT) {
_cursor = CursorIndex_Active;
_paint = NULL;
_bkg = NULL;
_brush = NULL;
_colorKey = 0;
_mouseDown = false;
// Loop until we find the closing brace
Common::String line = stream.readLine();
_engine->getScriptManager()->trimCommentsAndWhiteSpace(&line);
Common::String param;
Common::String values;
getParams(line, param, values);
while (!stream.eos() && !line.contains('}')) {
if (param.matchString("rectangle", true)) {
int x;
int y;
int width;
int height;
if (sscanf(values.c_str(), "%d %d %d %d", &x, &y, &width, &height) == 4)
_rectangle = Common::Rect(x, y, width + x, height + y);
} else if (param.matchString("cursor", true)) {
_cursor = _engine->getCursorManager()->getCursorId(values);
} else if (param.matchString("brush_file", true)) {
_brush = _engine->getRenderManager()->loadImage(Common::Path(values), false);
} else if (param.matchString("venus_id", true)) {
_venusId = atoi(values.c_str());
} else if (param.matchString("paint_file", true)) {
_paint = _engine->getRenderManager()->loadImage(Common::Path(values), false);
} else if (param.matchString("eligible_objects", true)) {
char buf[256];
memset(buf, 0, 256);
strncpy(buf, values.c_str(), 255);
char *curpos = buf;
char *strend = buf + strlen(buf);
while (true) {
char *st = curpos;
if (st >= strend)
break;
while (*curpos != ' ' && curpos < strend)
curpos++;
*curpos = 0;
curpos++;
int obj = atoi(st);
_eligibleObjects.push_back(obj);
}
}
line = stream.readLine();
_engine->getScriptManager()->trimCommentsAndWhiteSpace(&line);
getParams(line, param, values);
}
if (_paint) {
_colorKey = _paint->format.RGBToColor(255, 0, 255);
_bkg = new Graphics::Surface;
_bkg->create(_rectangle.width(), _rectangle.height(), _paint->format);
_bkg->fillRect(Common::Rect(_rectangle.width(), _rectangle.height()), _colorKey);
Graphics::Surface *tmp = new Graphics::Surface;
tmp->create(_rectangle.width(), _rectangle.height(), _paint->format);
_engine->getRenderManager()->blitSurfaceToSurface(*_paint, _rectangle, *tmp, 0, 0);
_paint->free();
delete _paint;
_paint = tmp;
}
}
PaintControl::~PaintControl() {
// Clear the state value back to 0
//_engine->getScriptManager()->setStateValue(_key, 0);
if (_paint) {
_paint->free();
delete _paint;
}
if (_brush) {
_brush->free();
delete _brush;
}
if (_bkg) {
_bkg->free();
delete _bkg;
}
}
bool PaintControl::onMouseUp(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos) {
if (_engine->getScriptManager()->getStateFlag(_key) & Puzzle::DISABLED)
return false;
_mouseDown = false;
return false;
}
bool PaintControl::onMouseDown(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos) {
if (_engine->getScriptManager()->getStateFlag(_key) & Puzzle::DISABLED)
return false;
if (_rectangle.contains(backgroundImageSpacePos)) {
int mouseItem = _engine->getScriptManager()->getStateValue(StateKey_InventoryItem);
if (eligeblity(mouseItem)) {
setVenus();
_mouseDown = true;
}
}
return false;
}
bool PaintControl::onMouseMove(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos) {
if (_engine->getScriptManager()->getStateFlag(_key) & Puzzle::DISABLED)
return false;
if (_rectangle.contains(backgroundImageSpacePos)) {
int mouseItem = _engine->getScriptManager()->getStateValue(StateKey_InventoryItem);
if (eligeblity(mouseItem)) {
_engine->getCursorManager()->changeCursor(_cursor);
if (_mouseDown) {
Common::Rect bkgRect = paint(backgroundImageSpacePos);
if (!bkgRect.isEmpty()) {
Common::Rect imgRect = bkgRect;
imgRect.translate(-_rectangle.left, -_rectangle.top);
Graphics::Surface imgUpdate = _bkg->getSubArea(imgRect);
_engine->getRenderManager()->blitSurfaceToBkg(imgUpdate, bkgRect.left, bkgRect.top, _colorKey);
}
}
return true;
}
}
return false;
}
bool PaintControl::eligeblity(int itemId) {
for (Common::List<int>::iterator it = _eligibleObjects.begin(); it != _eligibleObjects.end(); it++)
if (*it == itemId)
return true;
return false;
}
Common::Rect PaintControl::paint(const Common::Point &point) {
Common::Rect paintRect = Common::Rect(_brush->w, _brush->h);
paintRect.moveTo(point);
paintRect.clip(_rectangle);
if (!paintRect.isEmpty()) {
Common::Rect brushRect = paintRect;
brushRect.translate(-point.x, -point.y);
Common::Rect bkgRect = paintRect;
bkgRect.translate(-_rectangle.left, -_rectangle.top);
for (int yy = 0; yy < brushRect.height(); yy++) {
uint16 *mask = (uint16 *)_brush->getBasePtr(brushRect.left, brushRect.top + yy);
uint16 *from = (uint16 *)_paint->getBasePtr(bkgRect.left, bkgRect.top + yy);
uint16 *to = (uint16 *)_bkg->getBasePtr(bkgRect.left, bkgRect.top + yy);
for (int xx = 0; xx < brushRect.width(); xx++) {
if (*mask != 0)
*(to + xx) = *(from + xx);
mask++;
}
}
}
return paintRect;
}
} // End of namespace ZVision

View File

@@ -0,0 +1,89 @@
/* 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 ZVISION_PAINT_CONTROL_H
#define ZVISION_PAINT_CONTROL_H
#include "common/list.h"
#include "common/rect.h"
#include "graphics/surface.h"
#include "zvision/scripting/control.h"
namespace ZVision {
// Only used in Zork Nemesis, handles the painting puzzle screen in Lucien's room in Irondune (ch4g)
class PaintControl : public Control {
public:
PaintControl(ZVision *engine, uint32 key, Common::SeekableReadStream &stream);
~PaintControl() override;
/**
* @param screenSpacePos The position of the mouse in screen space
* @param backgroundImageSpacePos The position of the mouse in background image space
*/
bool onMouseUp(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos) override;
/**
* @param screenSpacePos The position of the mouse in screen space
* @param backgroundImageSpacePos The position of the mouse in background image space
*/
bool onMouseDown(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos) override;
/**
* Called on every MouseMove. Tests if the mouse is inside _hotspot, and if so, sets the cursor.
*
* @param engine The base engine
* @param screenSpacePos The position of the mouse in screen space
* @param backgroundImageSpacePos The position of the mouse in background image space
* @return Was the cursor changed?
*/
bool onMouseMove(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos) override;
bool process(uint32 deltaTimeInMillis) override {
return false;
};
private:
/**
* The area that will trigger the event
* This is in image space coordinates, NOT screen space
*/
uint32 _colorKey;
Graphics::Surface *_paint;
Graphics::Surface *_bkg;
Graphics::Surface *_brush;
Common::List<int> _eligibleObjects;
int _cursor;
Common::Rect _rectangle;
bool _mouseDown;
bool eligeblity(int itemId);
Common::Rect paint(const Common::Point &point);
};
} // End of namespace ZVision
#endif

View File

@@ -0,0 +1,142 @@
/* 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"
#include "common/stream.h"
#include "zvision/zvision.h"
#include "zvision/graphics/cursors/cursor_manager.h"
#include "zvision/scripting/script_manager.h"
#include "zvision/scripting/controls/push_toggle_control.h"
namespace ZVision {
PushToggleControl::PushToggleControl(ZVision *engine, uint32 key, Common::SeekableReadStream &stream)
: Control(engine, key, CONTROL_PUSHTGL),
_countTo(2),
_cursor(CursorIndex_Active),
_event(Common::EVENT_LBUTTONUP) {
_hotspots.clear();
// Loop until we find the closing brace
Common::String line = stream.readLine();
_engine->getScriptManager()->trimCommentsAndWhiteSpace(&line);
Common::String param;
Common::String values;
getParams(line, param, values);
while (!stream.eos() && !line.contains('}')) {
if (param.matchString("*_hotspot", true)) {
uint x;
uint y;
uint width;
uint height;
if (sscanf(values.c_str(), "%u,%u,%u,%u", &x, &y, &width, &height) == 4)
_hotspots.push_back(Common::Rect(x, y, x + width + 1, y + height + 1));
} else if (param.matchString("cursor", true)) {
_cursor = _engine->getCursorManager()->getCursorId(values);
} else if (param.matchString("animation", true)) {
// Not used
} else if (param.matchString("sound", true)) {
// Not used
} else if (param.matchString("count_to", true)) {
sscanf(values.c_str(), "%u", &_countTo);
} else if (param.matchString("mouse_event", true)) {
if (values.equalsIgnoreCase("up")) {
_event = Common::EVENT_LBUTTONUP;
} else if (values.equalsIgnoreCase("down")) {
_event = Common::EVENT_LBUTTONDOWN;
} else if (values.equalsIgnoreCase("double")) {
// Not used
}
} else if (param.matchString("venus_id", true)) {
_venusId = atoi(values.c_str());
}
line = stream.readLine();
_engine->getScriptManager()->trimCommentsAndWhiteSpace(&line);
getParams(line, param, values);
}
if (_hotspots.size() == 0) {
warning("Push_toggle %u was parsed incorrectly", key);
}
}
PushToggleControl::~PushToggleControl() {
_hotspots.clear();
}
bool PushToggleControl::onMouseUp(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos) {
if (_engine->getScriptManager()->getStateFlag(_key) & Puzzle::DISABLED)
return false;
if (_event != Common::EVENT_LBUTTONUP)
return false;
if (contain(backgroundImageSpacePos)) {
setVenus();
int32 val = _engine->getScriptManager()->getStateValue(_key);
val = (val + 1) % _countTo;
_engine->getScriptManager()->setStateValue(_key, val);
return true;
}
return false;
}
bool PushToggleControl::onMouseDown(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos) {
if (_engine->getScriptManager()->getStateFlag(_key) & Puzzle::DISABLED)
return false;
if (_event != Common::EVENT_LBUTTONDOWN)
return false;
if (contain(backgroundImageSpacePos)) {
setVenus();
int32 val = _engine->getScriptManager()->getStateValue(_key);
val = (val + 1) % _countTo;
_engine->getScriptManager()->setStateValue(_key, val);
return true;
}
return false;
}
bool PushToggleControl::onMouseMove(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos) {
if (_engine->getScriptManager()->getStateFlag(_key) & Puzzle::DISABLED)
return false;
if (contain(backgroundImageSpacePos)) {
_engine->getCursorManager()->changeCursor(_cursor);
return true;
}
return false;
}
bool PushToggleControl::contain(const Common::Point &point) {
for (uint i = 0; i < _hotspots.size(); i++)
if (_hotspots[i].contains(point))
return true;
return false;
}
} // End of namespace ZVision

View File

@@ -0,0 +1,79 @@
/* 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 ZVISION_PUSH_TOGGLE_CONTROL_H
#define ZVISION_PUSH_TOGGLE_CONTROL_H
#include "common/array.h"
#include "common/events.h"
#include "common/rect.h"
#include "zvision/scripting/control.h"
namespace ZVision {
class PushToggleControl : public Control {
public:
PushToggleControl(ZVision *engine, uint32 key, Common::SeekableReadStream &stream);
~PushToggleControl() override;
/**
* Called when LeftMouse is pushed. Default is NOP.
*
* @param screenSpacePos The position of the mouse in screen space
* @param backgroundImageSpacePos The position of the mouse in background image space
*/
bool onMouseDown(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos) override;
/**
* Called when LeftMouse is lifted. Calls ScriptManager::setStateValue(_key, 1);
*
* @param screenSpacePos The position of the mouse in screen space
* @param backgroundImageSpacePos The position of the mouse in background image space
*/
bool onMouseUp(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos) override;
/**
* Called on every MouseMove. Tests if the mouse is inside _hotspot, and if so, sets the cursor.
*
* @param engine The base engine
* @param screenSpacePos The position of the mouse in screen space
* @param backgroundImageSpacePos The position of the mouse in background image space
* @return Was the cursor changed?
*/
bool onMouseMove(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos) override;
private:
/**
* The area that will trigger the event
* This is in image space coordinates, NOT screen space
*/
Common::Array<Common::Rect> _hotspots;
/** The cursor to use when hovering over _hotspot */
int _cursor;
/** Button maximal values count */
uint _countTo;
Common::EventType _event;
bool contain(const Common::Point &point);
};
} // End of namespace ZVision
#endif

View File

@@ -0,0 +1,177 @@
/* 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/file.h"
#include "common/scummsys.h"
#include "common/stream.h"
#include "common/system.h"
#include "common/tokenizer.h"
#include "graphics/surface.h"
#include "video/video_decoder.h"
#include "zvision/zvision.h"
#include "zvision/graphics/render_manager.h"
#include "zvision/graphics/cursors/cursor_manager.h"
#include "zvision/scripting/script_manager.h"
#include "zvision/scripting/controls/safe_control.h"
namespace ZVision {
SafeControl::SafeControl(ZVision *engine, uint32 key, Common::SeekableReadStream &stream)
: Control(engine, key, CONTROL_SAFE) {
_statesCount = 0;
_curState = 0;
_animation = NULL;
_innerRaduis = 0;
_innerRadiusSqr = 0;
_outerRadius = 0;
_outerRadiusSqr = 0;
_zeroPointer = 0;
_startPointer = 0;
_targetFrame = 0;
// Loop until we find the closing brace
Common::String line = stream.readLine();
_engine->getScriptManager()->trimCommentsAndWhiteSpace(&line);
Common::String param;
Common::String values;
getParams(line, param, values);
while (!stream.eos() && !line.contains('}')) {
if (param.matchString("animation", true)) {
_animation = _engine->loadAnimation(Common::Path(values));
_animation->start();
} else if (param.matchString("rectangle", true)) {
int x;
int y;
int width;
int height;
if (sscanf(values.c_str(), "%d %d %d %d", &x, &y, &width, &height) == 4)
_rectangle = Common::Rect(x, y, width, height);
} else if (param.matchString("num_states", true)) {
_statesCount = atoi(values.c_str());
} else if (param.matchString("center", true)) {
int x;
int y;
if (sscanf(values.c_str(), "%d %d", &x, &y) == 2)
_center = Common::Point(x, y);
} else if (param.matchString("dial_inner_radius", true)) {
_innerRaduis = atoi(values.c_str());
_innerRadiusSqr = _innerRaduis * _innerRaduis;
} else if (param.matchString("radius", true)) {
_outerRadius = atoi(values.c_str());
_outerRadiusSqr = _outerRadius * _outerRadius;
} else if (param.matchString("zero_radians_offset", true)) {
_zeroPointer = atoi(values.c_str());
} else if (param.matchString("pointer_offset", true)) {
_startPointer = atoi(values.c_str());
_curState = _startPointer;
} else if (param.matchString("cursor", true)) {
// Not used
} else if (param.matchString("mirrored", true)) {
// Not used
} else if (param.matchString("venus_id", true)) {
_venusId = atoi(values.c_str());
}
line = stream.readLine();
_engine->getScriptManager()->trimCommentsAndWhiteSpace(&line);
getParams(line, param, values);
}
if (_animation)
_animation->seekToFrame(_curState);
}
SafeControl::~SafeControl() {
if (_animation)
delete _animation;
}
bool SafeControl::process(uint32 deltaTimeInMillis) {
if (_engine->getScriptManager()->getStateFlag(_key) & Puzzle::DISABLED)
return false;
if (_animation && _animation->getCurFrame() != _targetFrame && _animation->needsUpdate()) {
// If we're past the target frame, move back one
if (_animation->getCurFrame() > _targetFrame)
_animation->seekToFrame(_animation->getCurFrame() - 1);
const Graphics::Surface *frameData = _animation->decodeNextFrame();
if (_animation->getCurFrame() == _targetFrame)
_engine->getScriptManager()->setStateValue(_key, _curState);
if (frameData)
_engine->getRenderManager()->blitSurfaceToBkg(*frameData, _rectangle.left, _rectangle.top);
}
return false;
}
bool SafeControl::onMouseMove(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos) {
if (_engine->getScriptManager()->getStateFlag(_key) & Puzzle::DISABLED)
return false;
if (_rectangle.contains(backgroundImageSpacePos)) {
int32 mR = backgroundImageSpacePos.sqrDist(_center);
if (mR <= _outerRadiusSqr && mR >= _innerRadiusSqr) {
_engine->getCursorManager()->changeCursor(CursorIndex_Active);
return true;
}
}
return false;
}
bool SafeControl::onMouseUp(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos) {
if (_engine->getScriptManager()->getStateFlag(_key) & Puzzle::DISABLED)
return false;
if (_rectangle.contains(backgroundImageSpacePos)) {
int32 mR = backgroundImageSpacePos.sqrDist(_center);
if (mR <= _outerRadiusSqr && mR >= _innerRadiusSqr) {
setVenus();
Common::Point tmp = backgroundImageSpacePos - _center;
// Coverity complains about the order of arguments here,
// but changing that breaks the Zork Nemesis safe puzzle.
float dd = atan2((float)tmp.x, (float)tmp.y) * 57.29578;
int16 dp_state = 360 / _statesCount;
int16 m_state = (_statesCount - ((((int16)dd + 540) % 360) / dp_state)) % _statesCount;
int16 tmp2 = (m_state + _curState - _zeroPointer + _statesCount - 1) % _statesCount;
if (_animation)
_animation->seekToFrame((_curState + _statesCount - _startPointer) % _statesCount);
_curState = (_statesCount * 2 + tmp2) % _statesCount;
_targetFrame = (_curState + _statesCount - _startPointer) % _statesCount;
return true;
}
}
return false;
}
} // End of namespace ZVision

View File

@@ -0,0 +1,63 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef ZVISION_SAFE_CONTROL_H
#define ZVISION_SAFE_CONTROL_H
#include "common/list.h"
#include "common/rect.h"
#include "zvision/scripting/control.h"
namespace Video {
class VideoDecoder;
}
namespace ZVision {
// Only used in Zork Nemesis, handles the safe in the Asylum (ac4g)
class SafeControl : public Control {
public:
SafeControl(ZVision *engine, uint32 key, Common::SeekableReadStream &stream);
~SafeControl() override;
private:
int16 _statesCount;
int16 _curState;
Video::VideoDecoder *_animation;
Common::Point _center;
Common::Rect _rectangle;
int16 _innerRaduis;
int32 _innerRadiusSqr;
int16 _outerRadius;
int32 _outerRadiusSqr;
int16 _zeroPointer;
int16 _startPointer;
int16 _targetFrame;
public:
bool onMouseUp(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos) override;
bool onMouseMove(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos) override;
bool process(uint32 deltaTimeInMillis) override;
};
} // End of namespace ZVision
#endif

View File

@@ -0,0 +1,118 @@
/* 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"
#include "common/str.h"
#include "common/stream.h"
#include "zvision/zvision.h"
#include "zvision/file/save_manager.h"
#include "zvision/scripting/script_manager.h"
#include "zvision/scripting/controls/input_control.h"
#include "zvision/scripting/controls/save_control.h"
#include "zvision/text/string_manager.h"
#include "zvision/text/subtitle_manager.h"
namespace ZVision {
SaveControl::SaveControl(ZVision *engine, uint32 key, Common::SeekableReadStream &stream)
: Control(engine, key, CONTROL_SAVE),
_saveControl(false) {
// Loop until we find the closing brace
Common::String line = stream.readLine();
_engine->getScriptManager()->trimCommentsAndWhiteSpace(&line);
Common::String param;
Common::String values;
getParams(line, param, values);
while (!stream.eos() && !line.contains('}')) {
if (param.matchString("savebox", true)) {
int saveId;
int inputId;
if (sscanf(values.c_str(), "%d %d", &saveId, &inputId) == 2) {
saveElement elmnt;
elmnt.inputKey = inputId;
elmnt.saveId = saveId;
elmnt.exist = false;
_inputs.push_back(elmnt);
}
} else if (param.matchString("control_type", true)) {
if (values.contains("save"))
_saveControl = true;
else
_saveControl = false;
}
line = stream.readLine();
_engine->getScriptManager()->trimCommentsAndWhiteSpace(&line);
getParams(line, param, values);
}
for (saveElmntList::iterator iter = _inputs.begin(); iter != _inputs.end(); ++iter) {
Control *ctrl = _engine->getScriptManager()->getControl(iter->inputKey);
if (ctrl && ctrl->getType() == Control::CONTROL_INPUT) {
InputControl *inp = (InputControl *)ctrl;
inp->setReadOnly(!_saveControl);
Common::SeekableReadStream *save = _engine->getSaveManager()->getSlotFile(iter->saveId);
if (save) {
SaveGameHeader header;
if (_engine->getSaveManager()->readSaveGameHeader(save, header)) {
inp->setText(header.saveName);
iter->exist = true;
}
delete save;
}
}
}
}
bool SaveControl::process(uint32 deltaTimeInMillis) {
for (saveElmntList::iterator iter = _inputs.begin(); iter != _inputs.end(); ++iter) {
Control *ctrl = _engine->getScriptManager()->getControl(iter->inputKey);
if (ctrl && ctrl->getType() == Control::CONTROL_INPUT) {
InputControl *inp = (InputControl *)ctrl;
if (inp->enterPress()) {
if (_saveControl) {
if (inp->getText().size() > 0) {
bool toSave = true;
if (iter->exist)
if (!_engine->getSubtitleManager()->askQuestion(_engine->getStringManager()->getTextLine(StringManager::ZVISION_STR_SAVEEXIST)))
toSave = false;
if (toSave) {
_engine->getSaveManager()->saveGame(iter->saveId, inp->getText(), true);
_engine->getSubtitleManager()->delayedMessage(_engine->getStringManager()->getTextLine(StringManager::ZVISION_STR_SAVED), 2000);
_engine->getScriptManager()->changeLocation(_engine->getScriptManager()->getLastMenuLocation());
}
} else {
_engine->getSubtitleManager()->timedMessage(_engine->getStringManager()->getTextLine(StringManager::ZVISION_STR_SAVEEMPTY), 2000);
}
} else {
_engine->getSaveManager()->loadGame(iter->saveId);
return true;
}
break;
}
}
}
return false;
}
} // End of namespace ZVision

View File

@@ -0,0 +1,53 @@
/* 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 ZVISION_SAVE_CONTROL_H
#define ZVISION_SAVE_CONTROL_H
#include "common/list.h"
#include "zvision/scripting/control.h"
namespace ZVision {
class SaveControl : public Control {
public:
SaveControl(ZVision *engine, uint32 key, Common::SeekableReadStream &stream);
private:
struct saveElement {
int saveId;
int inputKey;
bool exist;
};
typedef Common::List<saveElement> saveElmntList;
saveElmntList _inputs;
bool _saveControl;
public:
bool process(uint32 deltaTimeInMillis) override;
};
} // End of namespace ZVision
#endif

View File

@@ -0,0 +1,213 @@
/* 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"
#include "common/stream.h"
#include "zvision/zvision.h"
#include "zvision/graphics/cursors/cursor_manager.h"
#include "zvision/graphics/render_manager.h"
#include "zvision/scripting/script_manager.h"
#include "zvision/scripting/controls/slot_control.h"
namespace ZVision {
SlotControl::SlotControl(ZVision *engine, uint32 key, Common::SeekableReadStream &stream)
: Control(engine, key, CONTROL_SLOT),
_cursor(CursorIndex_Active),
_distanceId('0') {
_renderedItem = 0;
_bkg = NULL;
// Loop until we find the closing brace
Common::String line = stream.readLine();
_engine->getScriptManager()->trimCommentsAndWhiteSpace(&line);
Common::String param;
Common::String values;
getParams(line, param, values);
while (!stream.eos() && !line.contains('}')) {
if (param.matchString("hotspot", true)) {
int x;
int y;
int width;
int height;
if (sscanf(values.c_str(), "%d %d %d %d", &x, &y, &width, &height) == 4)
_hotspot = Common::Rect(x, y, width, height);
} else if (param.matchString("rectangle", true)) {
int x;
int y;
int width;
int height;
if (sscanf(values.c_str(), "%d %d %d %d", &x, &y, &width, &height) == 4)
_rectangle = Common::Rect(x, y, width, height);
} else if (param.matchString("cursor", true)) {
_cursor = _engine->getCursorManager()->getCursorId(values);
} else if (param.matchString("distance_id", true)) {
sscanf(values.c_str(), "%c", &_distanceId);
} else if (param.matchString("venus_id", true)) {
_venusId = atoi(values.c_str());
} else if (param.matchString("eligible_objects", true)) {
char buf[256];
memset(buf, 0, 256);
strncpy(buf, values.c_str(), 255);
char *curpos = buf;
char *strend = buf + strlen(buf);
while (true) {
char *st = curpos;
if (st >= strend)
break;
while (*curpos != ' ' && curpos < strend)
curpos++;
*curpos = 0;
curpos++;
int obj = atoi(st);
_eligibleObjects.push_back(obj);
}
}
line = stream.readLine();
_engine->getScriptManager()->trimCommentsAndWhiteSpace(&line);
getParams(line, param, values);
}
if (_hotspot.isEmpty() || _rectangle.isEmpty()) {
warning("Slot %u was parsed incorrectly", key);
}
}
SlotControl::~SlotControl() {
// Clear the state value back to 0
//_engine->getScriptManager()->setStateValue(_key, 0);
if (_bkg)
delete _bkg;
}
bool SlotControl::onMouseUp(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos) {
if (_engine->getScriptManager()->getStateFlag(_key) & Puzzle::DISABLED)
return false;
if (_hotspot.contains(backgroundImageSpacePos)) {
setVenus();
int item = _engine->getScriptManager()->getStateValue(_key);
int mouseItem = _engine->getScriptManager()->getStateValue(StateKey_InventoryItem);
if (item != 0) {
if (mouseItem != 0) {
if (eligeblity(mouseItem)) {
_engine->getScriptManager()->inventoryDrop(mouseItem);
_engine->getScriptManager()->inventoryAdd(item);
_engine->getScriptManager()->setStateValue(_key, mouseItem);
}
} else {
_engine->getScriptManager()->inventoryAdd(item);
_engine->getScriptManager()->setStateValue(_key, 0);
}
} else if (mouseItem == 0) {
if (eligeblity(0)) {
_engine->getScriptManager()->inventoryDrop(0);
_engine->getScriptManager()->setStateValue(_key, 0);
}
} else if (eligeblity(mouseItem)) {
_engine->getScriptManager()->setStateValue(_key, mouseItem);
_engine->getScriptManager()->inventoryDrop(mouseItem);
}
}
return false;
}
bool SlotControl::onMouseMove(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos) {
if (_engine->getScriptManager()->getStateFlag(_key) & Puzzle::DISABLED)
return false;
if (_hotspot.contains(backgroundImageSpacePos)) {
_engine->getCursorManager()->changeCursor(_cursor);
return true;
}
return false;
}
bool SlotControl::process(uint32 deltaTimeInMillis) {
if (_engine->getScriptManager()->getStateFlag(_key) & Puzzle::DISABLED)
return false;
if (_engine->canRender()) {
int curItem = _engine->getScriptManager()->getStateValue(_key);
if (curItem != _renderedItem) {
if (_renderedItem != 0 && curItem == 0) {
_engine->getRenderManager()->blitSurfaceToBkg(*_bkg, _rectangle.left, _rectangle.top);
_renderedItem = curItem;
} else {
if (_renderedItem == 0) {
if (_bkg)
delete _bkg;
_bkg = _engine->getRenderManager()->getBkgRect(_rectangle);
} else {
_engine->getRenderManager()->blitSurfaceToBkg(*_bkg, _rectangle.left, _rectangle.top);
}
char buf[16];
if (_engine->getGameId() == GID_NEMESIS)
Common::sprintf_s(buf, "%d%cobj.tga", curItem, _distanceId);
else
Common::sprintf_s(buf, "g0z%cu%2.2x1.tga", _distanceId, curItem);
Graphics::Surface *srf = _engine->getRenderManager()->loadImage(buf);
int16 drawx = _rectangle.left;
int16 drawy = _rectangle.top;
if (_rectangle.width() > srf->w)
drawx = _rectangle.left + (_rectangle.width() - srf->w) / 2;
if (_rectangle.height() > srf->h)
drawy = _rectangle.top + (_rectangle.height() - srf->h) / 2;
_engine->getRenderManager()->blitSurfaceToBkg(*srf, drawx, drawy, 0);
delete srf;
_renderedItem = curItem;
}
}
}
return false;
}
bool SlotControl::eligeblity(int itemId) {
for (Common::List<int>::iterator it = _eligibleObjects.begin(); it != _eligibleObjects.end(); it++)
if (*it == itemId)
return true;
return false;
}
} // End of namespace ZVision

View File

@@ -0,0 +1,81 @@
/* 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 ZVISION_SLOT_CONTROL_H
#define ZVISION_SLOT_CONTROL_H
#include "common/list.h"
#include "common/rect.h"
#include "graphics/surface.h"
#include "zvision/scripting/control.h"
namespace ZVision {
class SlotControl : public Control {
public:
SlotControl(ZVision *engine, uint32 key, Common::SeekableReadStream &stream);
~SlotControl() override;
/**
* Called when LeftMouse is lifted. Calls ScriptManager::setStateValue(_key, 1);
*
* @param screenSpacePos The position of the mouse in screen space
* @param backgroundImageSpacePos The position of the mouse in background image space
*/
bool onMouseUp(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos) override;
/**
* Called on every MouseMove. Tests if the mouse is inside _hotspot, and if so, sets the cursor.
*
* @param engine The base engine
* @param screenSpacePos The position of the mouse in screen space
* @param backgroundImageSpacePos The position of the mouse in background image space
* @return Was the cursor changed?
*/
bool onMouseMove(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos) override;
bool process(uint32 deltaTimeInMillis) override;
private:
/**
* The area that will trigger the event
* This is in image space coordinates, NOT screen space
*/
Common::Rect _rectangle;
Common::Rect _hotspot;
int _cursor;
char _distanceId;
int _renderedItem;
Common::List<int> _eligibleObjects;
bool eligeblity(int itemId);
Graphics::Surface *_bkg;
/** The cursor to use when hovering over _hotspot */
Common::String _hoverCursor;
};
} // End of namespace ZVision
#endif

View File

@@ -0,0 +1,103 @@
/* 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"
#include "common/stream.h"
#include "zvision/zvision.h"
#include "zvision/graphics/render_manager.h"
#include "zvision/scripting/script_manager.h"
#include "zvision/scripting/controls/titler_control.h"
#include "zvision/text/text.h"
namespace ZVision {
TitlerControl::TitlerControl(ZVision *engine, uint32 key, Common::SeekableReadStream &stream)
: Control(engine, key, CONTROL_TITLER) {
_surface = NULL;
_curString = -1;
// Loop until we find the closing brace
Common::String line = stream.readLine();
_engine->getScriptManager()->trimCommentsAndWhiteSpace(&line);
Common::String param;
Common::String values;
getParams(line, param, values);
while (!stream.eos() && !line.contains('}')) {
if (param.matchString("string_resource_file", true)) {
readStringsFile(Common::Path(values));
} else if (param.matchString("rectangle", true)) {
int x;
int y;
int x2;
int y2;
if (sscanf(values.c_str(), "%d %d %d %d", &x, &y, &x2, &y2) == 4)
_rectangle = Common::Rect(x, y, x2, y2);
}
line = stream.readLine();
_engine->getScriptManager()->trimCommentsAndWhiteSpace(&line);
getParams(line, param, values);
}
if (!_rectangle.isEmpty()) {
_surface = new Graphics::Surface;
_surface->create(_rectangle.width(), _rectangle.height(), _engine->_resourcePixelFormat);
_surface->fillRect(Common::Rect(_surface->w, _surface->h), 0);
}
}
TitlerControl::~TitlerControl() {
if (_surface) {
_surface->free();
delete _surface;
}
}
void TitlerControl::setString(int strLine) {
if (strLine != _curString && strLine >= 0 && strLine < (int)_strings.size()) {
_surface->fillRect(Common::Rect(_surface->w, _surface->h), 0);
_engine->getTextRenderer()->drawTextWithWordWrapping(_strings[strLine], *_surface);
_engine->getRenderManager()->blitSurfaceToBkg(*_surface, _rectangle.left, _rectangle.top);
_curString = strLine;
}
}
void TitlerControl::readStringsFile(const Common::Path &fileName) {
Common::File file;
if (!file.open(fileName)) {
warning("String_resource_file %s could could be opened", fileName.toString().c_str());
return;
}
_strings.clear();
while (!file.eos()) {
Common::String line = readWideLine(file).encode();
_strings.push_back(line);
}
file.close();
}
} // End of namespace ZVision

View File

@@ -0,0 +1,55 @@
/* 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 ZVISION_TITLER_CONTROL_H
#define ZVISION_TITLER_CONTROL_H
#include "common/array.h"
#include "common/file.h"
#include "common/path.h"
#include "common/rect.h"
#include "common/stream.h"
#include "graphics/surface.h"
#include "zvision/scripting/control.h"
namespace ZVision {
// Only used in Zork Nemesis, handles the death screen with the Restore/Exit buttons
class TitlerControl : public Control {
public:
TitlerControl(ZVision *engine, uint32 key, Common::SeekableReadStream &stream);
~TitlerControl() override;
void setString(int strLine);
private:
Common::Array< Common::String > _strings;
Common::Rect _rectangle;
int16 _curString;
Graphics::Surface *_surface;
void readStringsFile(const Common::Path &fileName);
};
} // End of namespace ZVision
#endif

View File

@@ -0,0 +1,213 @@
/* 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"
#include "graphics/surface.h"
#include "video/video_decoder.h"
#include "zvision/zvision.h"
#include "zvision/graphics/render_manager.h"
#include "zvision/scripting/script_manager.h"
#include "zvision/scripting/effects/animation_effect.h"
namespace ZVision {
AnimationEffect::AnimationEffect(ZVision *engine, uint32 controlKey, const Common::Path &fileName, int32 mask, int32 frate, bool disposeAfterUse)
: ScriptingEffect(engine, controlKey, SCRIPTING_EFFECT_ANIM),
_disposeAfterUse(disposeAfterUse),
_mask(mask),
_animation(NULL) {
_animation = engine->loadAnimation(fileName);
if (frate > 0) {
_frmDelayOverride = (int32)(1000.0 / frate);
// WORKAROUND: We do not allow the engine to delay more than 66 msec
// per frame (15fps max)
if (_frmDelayOverride > 66)
_frmDelayOverride = 66;
} else {
_frmDelayOverride = 0;
}
}
AnimationEffect::~AnimationEffect() {
if (_animation)
delete _animation;
_engine->getScriptManager()->setStateValue(_key, 2);
PlayNodes::iterator it = _playList.begin();
if (it != _playList.end()) {
_engine->getScriptManager()->setStateValue((*it).slot, 2);
if ((*it)._scaled) {
(*it)._scaled->free();
delete(*it)._scaled;
}
}
_playList.clear();
}
bool AnimationEffect::process(uint32 deltaTimeInMillis) {
ScriptManager *scriptManager = _engine->getScriptManager();
RenderManager *renderManager = _engine->getRenderManager();
RenderTable::RenderState renderState = renderManager->getRenderTable()->getRenderState();
bool isPanorama = (renderState == RenderTable::PANORAMA);
int16 velocity = _engine->getMouseVelocity() + _engine->getKeyboardVelocity();
// Do not update animation nodes in panoramic mode while turning, if the user
// has set this option
if (scriptManager->getStateValue(StateKey_NoTurnAnim) == 1 && isPanorama && velocity)
return false;
PlayNodes::iterator it = _playList.begin();
if (it != _playList.end()) {
playnode *nod = &(*it);
if (nod->_curFrame == -1) {
// The node is just beginning playback
nod->_curFrame = nod->start;
_animation->start();
_animation->seekToFrame(nod->start);
_animation->setEndFrame(nod->stop);
nod->_delay = deltaTimeInMillis; // Force the frame to draw
if (nod->slot)
scriptManager->setStateValue(nod->slot, 1);
} else if (_animation->endOfVideo()) {
// The node has reached the end; check if we need to loop
nod->loop--;
if (nod->loop == 0) {
if (nod->slot >= 0)
scriptManager->setStateValue(nod->slot, 2);
if (nod->_scaled) {
nod->_scaled->free();
delete nod->_scaled;
}
_playList.erase(it);
return _disposeAfterUse;
}
nod->_curFrame = nod->start;
_animation->seekToFrame(nod->start);
}
// Check if we need to draw a frame
bool needsUpdate = false;
if (_frmDelayOverride == 0) {
// If not overridden, use the VideoDecoder's check
needsUpdate = _animation->needsUpdate();
} else {
// Otherwise, implement our own timing
nod->_delay -= deltaTimeInMillis;
if (nod->_delay <= 0) {
nod->_delay += _frmDelayOverride;
needsUpdate = true;
}
}
if (needsUpdate) {
const Graphics::Surface *frame = _animation->decodeNextFrame();
if (frame) {
int dstw;
int dsth;
if (isPanorama) {
dstw = nod->pos.height();
dsth = nod->pos.width();
} else {
dstw = nod->pos.width();
dsth = nod->pos.height();
}
// We only scale down the animation to fit its frame, not up, otherwise we
// end up with distorted animations - e.g. the armor visor in location cz1e
// in Nemesis (one of the armors inside Irondune), or the planet in location
// aa10 in Nemesis (Juperon, outside the asylum). We do allow scaling up only
// when a simple 2x filter is requested (e.g. the alchemists and cup sequence
// in Nemesis)
if (frame->w > dstw || frame->h > dsth || (frame->w == dstw / 2 && frame->h == dsth / 2)) {
if (nod->_scaled)
if (nod->_scaled->w != dstw || nod->_scaled->h != dsth) {
nod->_scaled->free();
delete nod->_scaled;
nod->_scaled = NULL;
}
if (!nod->_scaled) {
nod->_scaled = new Graphics::Surface;
nod->_scaled->create(dstw, dsth, frame->format);
}
renderManager->scaleBuffer(frame->getPixels(), nod->_scaled->getPixels(), frame->w, frame->h, frame->format.bytesPerPixel, dstw, dsth);
frame = nod->_scaled;
}
if (isPanorama) {
Graphics::Surface *transposed = RenderManager::tranposeSurface(frame);
renderManager->blitSurfaceToBkg(*transposed, nod->pos.left, nod->pos.top, _mask);
transposed->free();
delete transposed;
} else {
renderManager->blitSurfaceToBkg(*frame, nod->pos.left, nod->pos.top, _mask);
}
}
}
}
return false;
}
void AnimationEffect::addPlayNode(int32 slot, int x, int y, int x2, int y2, int startFrame, int endFrame, int loops) {
playnode nod;
nod.loop = loops;
nod.pos = Common::Rect(x, y, x2 + 1, y2 + 1);
nod.start = startFrame;
nod.stop = CLIP<int>(endFrame, 0, _animation->getFrameCount() - 1);
nod.slot = slot;
nod._curFrame = -1;
nod._delay = 0;
nod._scaled = NULL;
_playList.push_back(nod);
}
bool AnimationEffect::stop() {
PlayNodes::iterator it = _playList.begin();
if (it != _playList.end()) {
_engine->getScriptManager()->setStateValue((*it).slot, 2);
if ((*it)._scaled) {
(*it)._scaled->free();
delete(*it)._scaled;
}
}
_playList.clear();
// We don't need to delete, it's may be reused
return false;
}
} // End of namespace ZVision

View File

@@ -0,0 +1,79 @@
/* 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 ZVISION_ANIMATION_NODE_H
#define ZVISION_ANIMATION_NODE_H
#include "common/list.h"
#include "common/path.h"
#include "common/rect.h"
#include "zvision/scripting/scripting_effect.h"
namespace Graphics {
struct Surface;
}
namespace Video {
class VideoDecoder;
}
namespace ZVision {
class ZVision;
class AnimationEffect : public ScriptingEffect {
public:
AnimationEffect(ZVision *engine, uint32 controlKey, const Common::Path &fileName, int32 mask, int32 frate, bool disposeAfterUse = true);
~AnimationEffect() override;
struct playnode {
Common::Rect pos;
int32 slot;
int32 start;
int32 stop;
int32 loop;
int32 _curFrame;
int32 _delay;
Graphics::Surface *_scaled;
};
private:
typedef Common::List<playnode> PlayNodes;
PlayNodes _playList;
int32 _mask;
bool _disposeAfterUse;
Video::VideoDecoder *_animation;
int32 _frmDelayOverride;
public:
bool process(uint32 deltaTimeInMillis) override;
void addPlayNode(int32 slot, int x, int y, int x2, int y2, int startFrame, int endFrame, int loops = 1);
bool stop() override;
};
} // End of namespace ZVision
#endif

View File

@@ -0,0 +1,108 @@
/* 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"
#include "common/stream.h"
#include "zvision/zvision.h"
#include "zvision/graphics/render_manager.h"
#include "zvision/graphics/render_table.h"
#include "zvision/scripting/script_manager.h"
#include "zvision/scripting/effects/distort_effect.h"
namespace ZVision {
DistortNode::DistortNode(ZVision *engine, uint32 key, int16 speed, float startAngle, float endAngle, float startLineScale, float endLineScale)
: ScriptingEffect(engine, key, SCRIPTING_EFFECT_DISTORT) {
_angle = _engine->getRenderManager()->getRenderTable()->getAngle();
_linScale = _engine->getRenderManager()->getRenderTable()->getLinscale();
_speed = speed;
_incr = true;
_startAngle = startAngle;
_endAngle = endAngle;
_startLineScale = startLineScale;
_endLineScale = endLineScale;
_curFrame = 1.0;
_diffAngle = endAngle - startAngle;
_diffLinScale = endLineScale - startLineScale;
_frmSpeed = (float)speed / 15.0;
_frames = (int)ceil((5.0 - _frmSpeed * 2.0) / _frmSpeed);
if (_frames <= 0)
_frames = 1;
if (_key != StateKey_NotSet)
_engine->getScriptManager()->setStateValue(_key, 1);
}
DistortNode::~DistortNode() {
setParams(_angle, _linScale);
}
bool DistortNode::process(uint32 deltaTimeInMillis) {
float updTime = deltaTimeInMillis / (1000.0 / 60.0);
if (_incr)
_curFrame += updTime;
else
_curFrame -= updTime;
if (_curFrame < 1.0) {
_curFrame = 1.0;
_incr = true;
} else if (_curFrame > _frames) {
_curFrame = _frames;
_incr = false;
}
float diff = (1.0 / (5.0 - (_curFrame * _frmSpeed))) / (5.0 - _frmSpeed);
setParams(_startAngle + diff * _diffAngle, _startLineScale + diff * _diffLinScale);
return false;
}
void DistortNode::setParams(float angl, float linScale) {
RenderTable *table = _engine->getRenderManager()->getRenderTable();
switch (table->getRenderState()) {
case RenderTable::PANORAMA: {
table->setPanoramaFoV(angl);
table->setPanoramaScale(linScale);
table->generateRenderTable();
_engine->getRenderManager()->markDirty();
break;
}
case RenderTable::TILT: {
table->setTiltFoV(angl);
table->setTiltScale(linScale);
table->generateRenderTable();
_engine->getRenderManager()->markDirty();
break;
}
default:
break;
}
}
} // End of namespace ZVision

View File

@@ -0,0 +1,62 @@
/* 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 ZVISION_DISTORT_NODE_H
#define ZVISION_DISTORT_NODE_H
#include "zvision/scripting/scripting_effect.h"
namespace ZVision {
class ZVision;
class DistortNode : public ScriptingEffect {
public:
DistortNode(ZVision *engine, uint32 key, int16 speed, float startAngle, float endAngle, float startLineScale, float endLineScale);
~DistortNode() override;
bool process(uint32 deltaTimeInMillis) override;
private:
int16 _speed;
float _startAngle;
float _endAngle;
float _startLineScale;
float _endLineScale;
float _frmSpeed;
float _diffAngle;
float _diffLinScale;
bool _incr;
int16 _frames;
float _curFrame;
float _angle;
float _linScale;
private:
void setParams(float angl, float linScale);
};
} // End of namespace ZVision
#endif

View File

@@ -0,0 +1,290 @@
/* 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 "audio/decoders/wave.h"
#include "common/debug.h"
#include "common/file.h"
#include "common/scummsys.h"
#include "common/stream.h"
#include "math/utils.h"
#include "math/angle.h"
#include "zvision/zvision.h"
#include "zvision/graphics/render_manager.h"
#include "zvision/scripting/script_manager.h"
#include "zvision/scripting/effects/music_effect.h"
#include "zvision/sound/midi.h"
#include "zvision/sound/volume_manager.h"
#include "zvision/sound/zork_raw.h"
namespace ZVision {
void MusicNodeBASE::setDirection(Math::Angle azimuth, uint8 magnitude) {
if (_engine->getScriptManager()->getStateValue(StateKey_Qsound) >= 1) {
_azimuth = azimuth;
_directionality = magnitude;
_balance = ((int)(127 * _azimuth.getSine()) * _directionality) / 255;
} else
setBalance(0);
updateMixer();
}
void MusicNodeBASE::setBalance(int8 balance) {
_balance = balance;
_azimuth.setDegrees(0);
_directionality = 255;
updateMixer();
}
void MusicNodeBASE::updateMixer() {
if (_engine->getScriptManager()->getStateValue(StateKey_Qsound) >= 1)
_volumeOut = _engine->getVolumeManager()->convert(_volume, _azimuth, _directionality); // Apply game-specific volume profile and then attenuate according to azimuth
else
_volumeOut = _engine->getVolumeManager()->convert(_volume); // Apply game-specific volume profile and ignore azimuth
outputMixer();
}
MusicNode::MusicNode(ZVision *engine, uint32 key, Common::Path &filename, bool loop, uint8 volume)
: MusicNodeBASE(engine, key, SCRIPTING_EFFECT_AUDIO) {
_loop = loop;
_volume = volume;
_balance = 0;
_fade = false;
_fadeStartVol = volume;
_fadeEndVol = 0;
_fadeTime = 0;
_fadeElapsed = 0;
_sub = 0;
_stereo = false;
_loaded = false;
Audio::RewindableAudioStream *audioStream = NULL;
if (filename.baseName().contains(".wav")) {
Common::File *file = new Common::File();
if (file->open(filename)) {
audioStream = Audio::makeWAVStream(file, DisposeAfterUse::YES);
}
} else {
audioStream = makeRawZorkStream(filename, _engine);
}
if (audioStream) {
_stereo = audioStream->isStereo();
if (_loop) {
Audio::LoopingAudioStream *loopingAudioStream = new Audio::LoopingAudioStream(audioStream, 0, DisposeAfterUse::YES);
_engine->_mixer->playStream(Audio::Mixer::kPlainSoundType, &_handle, loopingAudioStream, -1, _volume);
} else {
_engine->_mixer->playStream(Audio::Mixer::kPlainSoundType, &_handle, audioStream, -1, _volume);
}
if (_key != StateKey_NotSet) {
debugC(3, kDebugSound, "setting musicnode state value to 1");
_engine->getScriptManager()->setStateValue(_key, 1);
}
// Change filename.raw into filename.sub
Common::String subname = filename.baseName();
subname.setChar('s', subname.size() - 3);
subname.setChar('u', subname.size() - 2);
subname.setChar('b', subname.size() - 1);
Common::Path subpath(filename.getParent().appendComponent(subname));
if (SearchMan.hasFile(subpath))
_sub = _engine->getSubtitleManager()->create(subpath, _handle); // NB automatic subtitle!
_loaded = true;
updateMixer();
}
debugC(3, kDebugSound, "MusicNode: %d created", _key);
}
MusicNode::~MusicNode() {
if (_loaded)
_engine->_mixer->stopHandle(_handle);
if (_key != StateKey_NotSet)
_engine->getScriptManager()->setStateValue(_key, 2);
if (_sub)
_engine->getSubtitleManager()->destroy(_sub);
debugC(3, kDebugSound, "MusicNode: %d destroyed", _key);
}
void MusicNode::outputMixer() {
_engine->_mixer->setChannelBalance(_handle, _balance);
_engine->_mixer->setChannelVolume(_handle, _volumeOut);
}
void MusicNode::setFade(int32 time, uint8 target) {
_fadeStartVol = _volume;
_fadeEndVol = target;
_fadeElapsed = 0;
_fadeTime = time <= 0 ? 0 : (uint32)time;
_fade = true;
}
bool MusicNode::process(uint32 deltaTimeInMillis) {
if (!_loaded || ! _engine->_mixer->isSoundHandleActive(_handle))
return stop();
else {
if (_fade) {
debugC(3, kDebugSound, "Fading music, endVol %d, startVol %d, current %d, fade time %d, elapsed time %dms", _fadeEndVol, _fadeStartVol, _volume, _fadeTime, _fadeElapsed);
uint8 _newvol = 0;
_fadeElapsed += deltaTimeInMillis;
if ((_fadeTime <= 0) | (_fadeElapsed >= _fadeTime)) {
_newvol = _fadeEndVol;
_fade = false;
} else {
if (_fadeEndVol > _fadeStartVol)
_newvol = _fadeStartVol + (_fadeElapsed * (_fadeEndVol - _fadeStartVol)) / _fadeTime;
else
_newvol = _fadeStartVol - (_fadeElapsed * (_fadeStartVol - _fadeEndVol)) / _fadeTime;
}
if (_volume != _newvol)
setVolume(_newvol);
}
}
return false;
}
void MusicNode::setVolume(uint8 newVolume) {
if (_loaded) {
debugC(4, kDebugSound, "Changing volume of music node %d from %d to %d", _key, _volume, newVolume);
_volume = newVolume;
updateMixer();
}
}
PanTrackNode::PanTrackNode(ZVision *engine, uint32 key, uint32 slot, int16 pos, uint8 mag, bool resetMixerOnDelete, bool staticScreen)
: ScriptingEffect(engine, key, SCRIPTING_EFFECT_PANTRACK),
_slot(slot),
_sourcePos(0),
_viewPos(0),
_mag(mag),
_width(0),
_pos(pos),
_staticScreen(staticScreen),
_resetMixerOnDelete(resetMixerOnDelete) {
debugC(3, kDebugSound, "Created PanTrackNode, key %d, slot %d", _key, _slot);
process(0); // Try to set pan value for music node immediately
}
PanTrackNode::~PanTrackNode() {
debugC(1, kDebugSound, "Deleting PanTrackNode, key %d, slot %d", _key, _slot);
ScriptManager *scriptManager = _engine->getScriptManager();
ScriptingEffect *fx = scriptManager->getSideFX(_slot);
if (fx && fx->getType() == SCRIPTING_EFFECT_AUDIO && _resetMixerOnDelete) {
debugC(1, kDebugSound, "Resetting mixer, slot %d", _slot);
MusicNodeBASE *mus = (MusicNodeBASE *)fx;
mus->setBalance(0);
} else
debugC(1, kDebugSound, "NOT resetting mixer, slot %d", _slot);
}
bool PanTrackNode::process(uint32 deltaTimeInMillis) {
debugC(3, kDebugSound, "Processing PanTrackNode, key %d", _key);
ScriptManager *scriptManager = _engine->getScriptManager();
ScriptingEffect *fx = scriptManager->getSideFX(_slot);
if (fx && fx->getType() == SCRIPTING_EFFECT_AUDIO) {
MusicNodeBASE *mus = (MusicNodeBASE *)fx;
if (!_staticScreen)
// Original game scripted behaviour
switch (_engine->getRenderManager()->getRenderTable()->getRenderState()) {
case RenderTable::PANORAMA:
debugC(3, kDebugSound, "PanTrackNode in panorama mode");
_width = _engine->getRenderManager()->getBkgSize().x;
if (_width) {
_sourcePos.setDegrees(360 * _pos / _width);
_viewPos.setDegrees(360 * scriptManager->getStateValue(StateKey_ViewPos) / _width);
} else {
warning("Encountered zero background width whilst processing PanTrackNode in panoramic mode!");
}
break;
case RenderTable::FLAT:
case RenderTable::TILT:
default:
debugC(3, kDebugSound, "PanTrackNode in FLAT/TILT mode");
_sourcePos.setDegrees(0);
_viewPos.setDegrees(0);
break;
} else {
// Used for auxiliary scripts only
_sourcePos.setDegrees(_pos);
_viewPos.setDegrees(0);
}
Math::Angle azimuth;
azimuth = _sourcePos - _viewPos;
debugC(3, kDebugSound, "soundPos: %f, _viewPos: %f, azimuth: %f, width %d", _sourcePos.getDegrees(), _viewPos.getDegrees(), azimuth.getDegrees(), _width);
// azimuth is sound source position relative to player, clockwise from centre of camera axis to front when viewed top-down
mus->setDirection(azimuth, _mag);
}
return false;
}
MusicMidiNode::MusicMidiNode(ZVision *engine, uint32 key, uint8 program, uint8 note, uint8 volume)
: MusicNodeBASE(engine, key, SCRIPTING_EFFECT_AUDIO) {
_volume = volume;
_prog = program;
_noteNumber = note;
_pan = 0;
_chan = _engine->getMidiManager()->getFreeChannel();
if (_chan >= 0) {
updateMixer();
_engine->getMidiManager()->setProgram(_chan, _prog);
_engine->getMidiManager()->noteOn(_chan, _noteNumber, _volume);
}
if (_key != StateKey_NotSet)
_engine->getScriptManager()->setStateValue(_key, 1);
}
MusicMidiNode::~MusicMidiNode() {
if (_chan >= 0) {
_engine->getMidiManager()->noteOff(_chan);
}
if (_key != StateKey_NotSet)
_engine->getScriptManager()->setStateValue(_key, 2);
}
void MusicMidiNode::setFade(int32 time, uint8 target) {
}
bool MusicMidiNode::process(uint32 deltaTimeInMillis) {
return false;
}
void MusicMidiNode::setVolume(uint8 newVolume) {
if (_chan >= 0) {
_volume = newVolume;
updateMixer();
}
}
void MusicMidiNode::outputMixer() {
_engine->getMidiManager()->setBalance(_chan, _balance);
_engine->getMidiManager()->setPan(_chan, _pan);
_engine->getMidiManager()->setVolume(_chan, _volumeOut);
}
} // End of namespace ZVision

View File

@@ -0,0 +1,146 @@
/* 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 ZVISION_MUSIC_NODE_H
#define ZVISION_MUSIC_NODE_H
#include "audio/mixer.h"
#include "math/angle.h"
#include "zvision/scripting/scripting_effect.h"
#include "zvision/sound/volume_manager.h"
#include "zvision/text/subtitle_manager.h"
namespace Common {
class String;
}
namespace ZVision {
class MusicNodeBASE : public ScriptingEffect {
public:
MusicNodeBASE(ZVision *engine, uint32 key, ScriptingEffectType type) : ScriptingEffect(engine, key, type) {}
~MusicNodeBASE() override {}
/**
* Decrement the timer by the delta time. If the timer is finished, set the status
* in _globalState and let this node be deleted
*
* @param deltaTimeInMillis The number of milliseconds that have passed since last frame
* @return If true, the node can be deleted after process() finishes
*/
bool process(uint32 deltaTimeInMillis) override = 0;
virtual void setVolume(uint8 volume) = 0;
uint8 getVolume() {
return _volume;
}
virtual void setFade(int32 time, uint8 target) = 0;
virtual void setBalance(int8 balance); // NB Overrides effects of setDirection()
void setDirection(Math::Angle azimuth, uint8 magnitude = 255); // NB Overrides effects of setBalance()
protected:
void updateMixer();
virtual void outputMixer() = 0;
uint8 _volume = 0;
int8 _balance = 0;
Math::Angle _azimuth;
uint8 _directionality; // 0 = fully ambient, 255 = fully directional
uint8 _volumeOut = 0;
};
class MusicNode : public MusicNodeBASE {
public:
MusicNode(ZVision *engine, uint32 key, Common::Path &file, bool loop, uint8 volume);
~MusicNode() override;
/**
* Decrement the timer by the delta time. If the timer is finished, set the status
* in _globalState and let this node be deleted
*
* @param deltaTimeInMillis The number of milliseconds that have passed since last frame
* @return If true, the node can be deleted after process() finishes
*/
bool process(uint32 deltaTimeInMillis) override;
void setVolume(uint8 volume) override;
void setFade(int32 time, uint8 target) override;
private:
void outputMixer() override;
bool _loop;
bool _fade;
uint8 _fadeStartVol;
uint8 _fadeEndVol;
uint32 _fadeTime;
uint32 _fadeElapsed; // Cumulative time since fade start
bool _stereo;
Audio::SoundHandle _handle;
uint16 _sub;
bool _loaded;
};
// Only used by Zork: Nemesis, for the flute and piano puzzles (tj4e and ve6f, as well as vr)
class MusicMidiNode : public MusicNodeBASE {
public:
MusicMidiNode(ZVision *engine, uint32 key, uint8 program, uint8 note, uint8 volume);
~MusicMidiNode() override;
/**
* Decrement the timer by the delta time. If the timer is finished, set the status
* in _globalState and let this node be deleted
*
* @param deltaTimeInMillis The number of milliseconds that have passed since last frame
* @return If true, the node can be deleted after process() finishes
*/
bool process(uint32 deltaTimeInMillis) override;
void setVolume(uint8 volume) override;
void setFade(int32 time, uint8 target) override;
private:
void outputMixer() override;
int8 _chan;
uint8 _noteNumber;
int8 _pan;
uint8 _prog;
};
class PanTrackNode : public ScriptingEffect {
public:
PanTrackNode(ZVision *engine, uint32 key, uint32 slot, int16 pos, uint8 mag = 255, bool resetMixerOnDelete = false, bool staticScreen = false);
~PanTrackNode() override;
bool process(uint32 deltaTimeInMillis) override;
private:
uint32 _slot;
int16 _width, _pos;
Math::Angle _sourcePos, _viewPos;
uint8 _mag;
bool _resetMixerOnDelete;
bool _staticScreen;
};
} // End of namespace ZVision
#endif

View File

@@ -0,0 +1,53 @@
/* 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"
#include "zvision/zvision.h"
#include "zvision/graphics/render_manager.h"
#include "zvision/scripting/script_manager.h"
#include "zvision/scripting/effects/region_effect.h"
namespace ZVision {
RegionNode::RegionNode(ZVision *engine, uint32 key, GraphicsEffect *effect, uint32 delay)
: ScriptingEffect(engine, key, SCRIPTING_EFFECT_REGION) {
_effect = effect;
_delay = delay;
_timeLeft = 0;
}
RegionNode::~RegionNode() {
_engine->getRenderManager()->deleteEffect(_key);
}
bool RegionNode::process(uint32 deltaTimeInMillis) {
_timeLeft -= deltaTimeInMillis;
if (_timeLeft <= 0) {
_timeLeft = _delay;
if (_effect)
_effect->update();
}
return false;
}
} // End of namespace ZVision

View File

@@ -0,0 +1,55 @@
/* 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 ZVISION_REGION_NODE_H
#define ZVISION_REGION_NODE_H
#include "graphics/surface.h"
#include "zvision/graphics/graphics_effect.h"
#include "zvision/scripting/scripting_effect.h"
namespace ZVision {
class ZVision;
class RegionNode : public ScriptingEffect {
public:
RegionNode(ZVision *engine, uint32 key, GraphicsEffect *effect, uint32 delay);
~RegionNode() override;
/**
* Decrement the timer by the delta time. If the timer is finished, set the status
* in _globalState and let this node be deleted
*
* @param deltaTimeInMillis The number of milliseconds that have passed since last frame
* @return If true, the node can be deleted after process() finishes
*/
bool process(uint32 deltaTimeInMillis) override;
private:
int32 _timeLeft;
uint32 _delay;
GraphicsEffect *_effect;
};
} // End of namespace ZVision
#endif

View File

@@ -0,0 +1,80 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "audio/decoders/wave.h"
#include "common/file.h"
#include "common/scummsys.h"
#include "common/stream.h"
#include "zvision/zvision.h"
#include "zvision/graphics/render_manager.h"
#include "zvision/scripting/script_manager.h"
#include "zvision/scripting/effects/syncsound_effect.h"
#include "zvision/sound/zork_raw.h"
namespace ZVision {
SyncSoundNode::SyncSoundNode(ZVision *engine, uint32 key, Common::Path &filename, int32 syncto)
: ScriptingEffect(engine, key, SCRIPTING_EFFECT_AUDIO) {
_syncto = syncto;
_sub = 0;
Audio::RewindableAudioStream *audioStream = NULL;
if (filename.baseName().contains(".wav")) {
Common::File *file = new Common::File();
if (file->open(filename)) {
audioStream = Audio::makeWAVStream(file, DisposeAfterUse::YES);
}
} else {
audioStream = makeRawZorkStream(filename, _engine);
}
_engine->_mixer->playStream(Audio::Mixer::kPlainSoundType, &_handle, audioStream);
Common::String subname = filename.baseName();
subname.setChar('s', subname.size() - 3);
subname.setChar('u', subname.size() - 2);
subname.setChar('b', subname.size() - 1);
Common::Path subpath(filename.getParent().appendComponent(subname));
if (SearchMan.hasFile(subpath))
_sub = _engine->getSubtitleManager()->create(subpath, _handle); // NB automatic subtitle!
}
SyncSoundNode::~SyncSoundNode() {
_engine->_mixer->stopHandle(_handle);
if (_sub)
_engine->getSubtitleManager()->destroy(_sub);
}
bool SyncSoundNode::process(uint32 deltaTimeInMillis) {
if (! _engine->_mixer->isSoundHandleActive(_handle))
return stop();
else {
if (_engine->getScriptManager()->getSideFX(_syncto) == NULL)
return stop();
}
return false;
}
} // End of namespace ZVision

View File

@@ -0,0 +1,55 @@
/* 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 ZVISION_SYNCSOUND_NODE_H
#define ZVISION_SYNCSOUND_NODE_H
#include "audio/mixer.h"
#include "zvision/scripting/scripting_effect.h"
#include "zvision/text/subtitle_manager.h"
namespace Common {
class String;
}
namespace ZVision {
class SyncSoundNode : public ScriptingEffect {
public:
SyncSoundNode(ZVision *engine, uint32 key, Common::Path &file, int32 syncto);
~SyncSoundNode() override;
/**
* Decrement the timer by the delta time. If the timer is finished, set the status
* in _globalState and let this node be deleted
*
* @param deltaTimeInMillis The number of milliseconds that have passed since last frame
* @return If true, the node can be deleted after process() finishes
*/
bool process(uint32 deltaTimeInMillis) override;
private:
int32 _syncto;
Audio::SoundHandle _handle;
uint16 _sub;
};
} // End of namespace ZVision
#endif

View File

@@ -0,0 +1,77 @@
/* 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"
#include "common/stream.h"
#include "zvision/zvision.h"
#include "zvision/scripting/script_manager.h"
#include "zvision/scripting/effects/timer_effect.h"
namespace ZVision {
TimerNode::TimerNode(ZVision *engine, uint32 key, uint timeInSeconds)
: ScriptingEffect(engine, key, SCRIPTING_EFFECT_TIMER) {
_timeLeft = 0;
if (_engine->getGameId() == GID_NEMESIS)
_timeLeft = timeInSeconds * 1000;
else if (_engine->getGameId() == GID_GRANDINQUISITOR)
_timeLeft = timeInSeconds * 100;
if (_key != StateKey_NotSet)
_engine->getScriptManager()->setStateValue(_key, 1);
}
TimerNode::~TimerNode() {
if (_key != StateKey_NotSet)
_engine->getScriptManager()->setStateValue(_key, 2);
int32 timeLeft = _timeLeft / (_engine->getGameId() == GID_NEMESIS ? 1000 : 100);
if (timeLeft > 0)
_engine->getScriptManager()->setStateValue(_key, timeLeft); // If timer was stopped by stop or kill
}
bool TimerNode::process(uint32 deltaTimeInMillis) {
_timeLeft -= deltaTimeInMillis;
if (_timeLeft <= 0)
return stop();
return false;
}
bool TimerNode::stop() {
if (_key != StateKey_NotSet)
_engine->getScriptManager()->setStateValue(_key, 2);
return true;
}
void TimerNode::serialize(Common::WriteStream *stream) {
stream->writeUint32BE(MKTAG('T', 'I', 'M', 'R'));
stream->writeUint32LE(8); // size
stream->writeUint32LE(_key);
stream->writeUint32LE(_timeLeft);
}
void TimerNode::deserialize(Common::SeekableReadStream *stream) {
_timeLeft = stream->readUint32LE();
}
} // End of namespace ZVision

View File

@@ -0,0 +1,58 @@
/* 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 ZVISION_TIMER_NODE_H
#define ZVISION_TIMER_NODE_H
#include "zvision/scripting/scripting_effect.h"
namespace ZVision {
class ZVision;
class TimerNode : public ScriptingEffect {
public:
TimerNode(ZVision *engine, uint32 key, uint timeInSeconds);
~TimerNode() override;
/**
* Decrement the timer by the delta time. If the timer is finished, set the status
* in _globalState and let this node be deleted
*
* @param deltaTimeInMillis The number of milliseconds that have passed since last frame
* @return If true, the node can be deleted after process() finishes
*/
bool process(uint32 deltaTimeInMillis) override;
void serialize(Common::WriteStream *stream) override;
void deserialize(Common::SeekableReadStream *stream) override;
inline bool needsSerialization() override {
return true;
}
bool stop() override;
private:
int32 _timeLeft;
};
} // End of namespace ZVision
#endif

View File

@@ -0,0 +1,199 @@
/* 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/file.h"
#include "common/scummsys.h"
#include "common/stream.h"
#include "common/unicode-bidi.h"
#include "zvision/zvision.h"
#include "zvision/graphics/render_manager.h"
#include "zvision/scripting/script_manager.h"
#include "zvision/scripting/effects/ttytext_effect.h"
#include "zvision/text/text.h"
namespace ZVision {
ttyTextNode::ttyTextNode(ZVision *engine, uint32 key, const Common::Path &file, const Common::Rect &r, int32 delay) :
ScriptingEffect(engine, key, SCRIPTING_EFFECT_TTYTXT),
_fnt(engine) {
_delay = delay;
_r = r;
_txtpos = 0;
_nexttime = 0;
_dx = 0;
_dy = 0;
_lineStartPos = 0;
_startX = 0;
Common::File *infile = new Common::File;
infile->open(file);
if (infile) {
while (!infile->eos()) {
Common::U32String asciiLine = readWideLine(*infile);
if (asciiLine.empty()) {
continue;
}
_txtbuf += asciiLine;
}
delete infile;
}
_isRTL = Common::convertBiDiU32String(_txtbuf).visual != _txtbuf;
_img.create(_r.width(), _r.height(), _engine->_resourcePixelFormat);
_state._sharp = true;
_state.readAllStyles(_txtbuf.encode());
_state.updateFontWithTextState(_fnt);
_engine->getScriptManager()->setStateValue(_key, 1);
}
ttyTextNode::~ttyTextNode() {
_engine->getScriptManager()->setStateValue(_key, 2);
_img.free();
}
bool ttyTextNode::process(uint32 deltaTimeInMillis) {
_nexttime -= deltaTimeInMillis;
if (_nexttime < 0) {
if (_txtpos < _txtbuf.size()) {
if (_txtbuf[_txtpos] == '<') {
int32 start = _txtpos;
int32 end = 0;
int16 ret = 0;
while (_txtbuf[_txtpos] != '>' && _txtpos < _txtbuf.size())
_txtpos++;
end = _txtpos;
if (start != -1) {
if ((end - start - 1) > 0) {
Common::String buf = _txtbuf.substr(start + 1, end - start - 1);
ret = _state.parseStyle(buf, buf.size());
}
}
if (ret & (TEXT_CHANGE_FONT_TYPE | TEXT_CHANGE_FONT_STYLE)) {
_state.updateFontWithTextState(_fnt);
} else if (ret & TEXT_CHANGE_NEWLINE) {
newline();
}
if (ret & TEXT_CHANGE_HAS_STATE_BOX) {
Common::String buf;
buf = Common::String::format("%d", _engine->getScriptManager()->getStateValue(_state._statebox));
if (_isRTL) {
int16 currDx = _dx + _fnt.getStringWidth(buf);
_dx = _r.width() - currDx;
_isRTL = false;
for (uint8 j = 0; j < buf.size(); j++)
outchar(buf[j]);
_isRTL = true;
_dx = currDx;
} else {
for (uint8 j = 0; j < buf.size(); j++)
outchar(buf[j]);
}
}
_txtpos++;
_lineStartPos = _txtpos;
_startX = _dx;
} else {
uint32 pos = _lineStartPos;
int16 dx = _startX;
while (pos < _txtbuf.size() && _txtbuf[pos] != '<') {
uint16 chr = _txtbuf[pos];
if (chr == ' ') {
uint32 i = pos + 1;
uint16 width = _fnt.getCharWidth(chr);
while (i < _txtbuf.size() && _txtbuf[i] != ' ' && _txtbuf[i] != '<') {
uint16 uchr = _txtbuf[i];
width += _fnt.getCharWidth(uchr);
i++;
}
if (dx + width > _r.width())
break;
}
dx += _fnt.getCharWidth(chr);
pos++;
}
Common::U32String lineBuffer = Common::convertBiDiU32String(_txtbuf.substr(_lineStartPos, pos - _lineStartPos)).visual;
if (pos == _txtpos)
newline();
else if (_isRTL)
outchar(lineBuffer[pos - _txtpos - 1]);
else
outchar(lineBuffer[_txtpos - _lineStartPos]);
_txtpos++;
}
_nexttime = _delay;
_engine->getRenderManager()->blitSurfaceToBkg(_img, _r.left, _r.top);
} else
return stop();
}
return false;
}
void ttyTextNode::scroll() {
int32 scrl = 0;
while (_dy - scrl > _r.height() - _fnt.getFontHeight())
scrl += _fnt.getFontHeight();
int8 *pixels = (int8 *)_img.getPixels();
for (uint16 h = scrl; h < _img.h; h++)
memcpy(pixels + _img.pitch * (h - scrl), pixels + _img.pitch * h, _img.pitch);
_img.fillRect(Common::Rect(0, _img.h - scrl, _img.w, _img.h), 0);
_dy -= scrl;
}
void ttyTextNode::newline() {
_dy += _fnt.getFontHeight();
_dx = 0;
_lineStartPos = _txtpos + 1;
_startX = _dx;
}
void ttyTextNode::outchar(uint16 chr) {
uint32 clr = _engine->_resourcePixelFormat.RGBToColor(_state._red, _state._green, _state._blue);
if (_dx + _fnt.getCharWidth(chr) > _r.width())
newline();
if (_dy + _fnt.getFontHeight() >= _r.height())
scroll();
if (_isRTL)
_fnt.drawChar(&_img, chr, _r.width() - _dx - _fnt.getCharWidth(chr), _dy, clr);
else
_fnt.drawChar(&_img, chr, _dx, _dy, clr);
_dx += _fnt.getCharWidth(chr);
}
} // End of namespace ZVision

View File

@@ -0,0 +1,74 @@
/* 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 ZVISION_TTYTEXT_NODE_H
#define ZVISION_TTYTEXT_NODE_H
#include "common/rect.h"
#include "graphics/surface.h"
#include "zvision/scripting/scripting_effect.h"
#include "zvision/text/text.h"
#include "zvision/text/truetype_font.h"
namespace Common {
class String;
}
namespace ZVision {
class ttyTextNode : public ScriptingEffect {
public:
ttyTextNode(ZVision *engine, uint32 key, const Common::Path &file, const Common::Rect &r, int32 delay);
~ttyTextNode() override;
/**
* Decrement the timer by the delta time. If the timer is finished, set the status
* in _globalState and let this node be deleted
*
* @param deltaTimeInMillis The number of milliseconds that have passed since last frame
* @return If true, the node can be deleted after process() finishes
*/
bool process(uint32 deltaTimeInMillis) override;
private:
Common::Rect _r;
TextStyleState _state;
StyledTTFont _fnt;
Common::U32String _txtbuf;
uint32 _txtpos;
uint32 _lineStartPos;
bool _isRTL;
int32 _delay;
int32 _nexttime;
Graphics::Surface _img;
int16 _dx;
int16 _startX;
int16 _dy;
private:
void newline();
void scroll();
void outchar(uint16 chr);
};
} // End of namespace ZVision
#endif

View File

@@ -0,0 +1,120 @@
/* 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"
#include "zvision/scripting/script_manager.h"
namespace ZVision {
int8 ScriptManager::inventoryGetCount() {
return getStateValue(StateKey_Inv_Cnt_Slot);
}
void ScriptManager::inventorySetCount(int8 cnt) {
setStateValue(StateKey_Inv_Cnt_Slot, cnt);
}
int16 ScriptManager::inventoryGetItem(int8 id) {
if (id < 49 && id >= 0)
return getStateValue(StateKey_Inv_1_Slot + id);
return -1;
}
void ScriptManager::inventorySetItem(int8 id, int16 item) {
if (id < 49 && id >= 0)
setStateValue(StateKey_Inv_1_Slot + id, item);
}
void ScriptManager::inventoryAdd(int16 item) {
int8 cnt = inventoryGetCount();
if (cnt < 49) {
bool notExist = true;
if (cnt == 0) {
inventorySetItem(0, 0);
inventorySetCount(1); // we needed empty item for cycle code
cnt = 1;
}
for (int8 cur = 0; cur < cnt; cur++)
if (inventoryGetItem(cur) == item) {
notExist = false;
break;
}
if (notExist) {
for (int8 i = cnt; i > 0; i--)
inventorySetItem(i, inventoryGetItem(i - 1));
inventorySetItem(0, item);
setStateValue(StateKey_InventoryItem, item);
inventorySetCount(cnt + 1);
}
}
}
void ScriptManager::inventoryDrop(int16 item) {
int8 itemCount = inventoryGetCount();
// if items in inventory > 0
if (itemCount != 0) {
int8 index = 0;
// finding needed item
while (index < itemCount) {
if (inventoryGetItem(index) == item)
break;
index++;
}
// if item in the inventory
if (itemCount != index) {
// shift all items left with rewrite founded item
for (int8 v = index; v < itemCount - 1 ; v++)
inventorySetItem(v, inventoryGetItem(v + 1));
// del last item
inventorySetItem(itemCount - 1, 0);
inventorySetCount(inventoryGetCount() - 1);
setStateValue(StateKey_InventoryItem, inventoryGetItem(0));
}
}
}
void ScriptManager::inventoryCycle() {
int8 itemCount = inventoryGetCount();
int8 curItem = inventoryGetItem(0);
if (itemCount > 1) {
for (int8 i = 0; i < itemCount - 1; i++)
inventorySetItem(i, inventoryGetItem(i + 1));
inventorySetItem(itemCount - 1, curItem);
setStateValue(StateKey_InventoryItem, inventoryGetItem(0));
}
}
} // End of namespace ZVision

View File

@@ -0,0 +1,592 @@
/* 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 "zvision/detection.h"
#include "zvision/graphics/render_manager.h"
#include "zvision/scripting/menu.h"
namespace ZVision {
enum {
kFocusNone = -1,
kFocusItems = 0,
kFocusMagic = 1,
kFocusMain = 2
};
MenuManager::MenuManager(ZVision *engine, const Common::Rect menuArea, const MenuParams params) :
_engine(engine),
_params(params),
_menuBarFlag(0xFFFF),
_menuArea(menuArea),
_menuOrigin(menuArea.origin()),
_menuTriggerArea(_menuOrigin, _menuArea.width(), _params.triggerHeight),
_mainScroller(params.activePos, params.idlePos, params.period) {
_enableFlags.set_size(6);
for (int8 i = 0; i < 4; i++) {
// Generate button hotspot areas
_menuHotspots[i] = Common::Rect(_params.wxButs[i][1], _menuArea.top, _params.wxButs[i][1] + _params.wxButs[i][0], _menuArea.bottom);
// Initialise button animation frames
_mainFrames[i] = _params.idleFrame;
}
for (int i = 0; i < 4; i++)
_buttonAnim[i] = new LinearScroller(_params.activeFrame, _params.idleFrame, _params.buttonPeriod);
setFocus(kFocusNone); // Ensure focus list is initialised
_mainArea = Common::Rect(_params.wMain, _hMainMenu);
_mainArea.moveTo(_menuOrigin + _mainScroller._pos);
}
MenuManager::~MenuManager() {
for (int i = 0; i < 4; i++)
delete _buttonAnim[i];
_mainBack.free();
}
void MenuManager::setEnable(uint16 flags) {
static const uint16 flagMasks[6] = {0x8, 0x4, 0x2, 0x1, 0x100, 0x200}; // Enum order: save,restore,prefs,quit,items,magic
_menuBarFlag = flags;
for (uint i = 0; i <= 5; i++) {
if (_menuBarFlag & flagMasks[i])
_enableFlags.set(i);
else
_enableFlags.unset(i);
}
}
void MenuManager::onMouseUp(const Common::Point &pos) {
if (_menuFocus.front() == kFocusMain) {
_mouseOnItem = mouseOverMain(pos);
if (_mouseOnItem == _mainClicked)
// Activate clicked action from main menu
switch (_mouseOnItem) {
case kMainMenuSave:
_engine->getScriptManager()->changeLocation('g', 'j', 's', 'e', 0);
setFocus(kFocusNone);
_mainScroller.reset();
_redraw = true;
break;
case kMainMenuLoad:
_engine->getScriptManager()->changeLocation('g', 'j', 'r', 'e', 0);
setFocus(kFocusNone);
_mainScroller.reset();
_redraw = true;
break;
case kMainMenuPrefs:
_engine->getScriptManager()->changeLocation('g', 'j', 'p', 'e', 0);
setFocus(kFocusNone);
_mainScroller.reset();
_redraw = true;
break;
case kMainMenuExit:
_engine->quit(true);
break;
default:
break;
}
}
_mainClicked = -1;
}
void MenuManager::onMouseDown(const Common::Point &pos) {
if (_menuFocus.front() == kFocusMain) {
_mouseOnItem = mouseOverMain(pos);
// Show clicked graphic
if ((_mouseOnItem >= 0) && (_mouseOnItem < 4))
if (_enableFlags.get(_mouseOnItem)) {
_mainClicked = _mouseOnItem;
_redraw = true;
}
}
}
void MenuManager::onMouseMove(const Common::Point &pos) {
bool nowInMenu = inMenu(pos);
if (nowInMenu != _prevInMenu)
_redraw = true;
_prevInMenu = nowInMenu;
int lastItem = _mouseOnItem;
switch (_menuFocus.front()) {
case kFocusMain:
// Inform game scripting engine that mouse is in main menu
if (_engine->getScriptManager()->getStateValue(StateKey_MenuState) != 2)
_engine->getScriptManager()->setStateValue(StateKey_MenuState, 2);
_mouseOnItem = mouseOverMain(pos);
break;
case kFocusNone:
// Inform game scripting engine that mouse is not in any menu
if (_engine->getScriptManager()->getStateValue(StateKey_MenuState) != 0)
_engine->getScriptManager()->setStateValue(StateKey_MenuState, 0);
_mouseOnItem = -1;
break;
}
_mainScroller.setActive(_menuFocus.front() == kFocusMain);
// Update button animation status
for (int i = 0; i < 4; i++)
if (_menuFocus[0] == kFocusMain && _mouseOnItem == i)
_buttonAnim[i]->setActive(true);
else
_buttonAnim[i]->setActive(false);
if (lastItem != _mouseOnItem)
_redraw = true;
}
int MenuManager::mouseOverMain(const Common::Point &pos) {
// Common::Rect mainHotspot(28,_hSideMenu);
// mainHotspot.moveTo(mainOrigin + _mainScroller._pos);
for (int8 i = 0; i < 4; i++) {
if (_enableFlags.get(i) && _menuHotspots[i].contains(pos))
return i;
}
return -1;
}
void MenuManager::process(uint32 deltatime) {
if (_mainScroller.update(deltatime)) {
_mainArea.moveTo(_menuOrigin + _mainScroller._pos);
for (int i = 0; i < 4; i++)
_menuHotspots[i].moveTo(_menuOrigin + Common::Point(_params.wxButs[i][1], _mainScroller._pos.y));
_redraw = true;
}
// Update button highlight animation frame
for (int i = 0; i < 4; i++)
if (_buttonAnim[i]->update(deltatime)) {
_mainFrames[i] = _buttonAnim[i]->_pos;
_redraw = true;
}
if (_redraw) {
_engine->getRenderManager()->clearMenuSurface();
redrawAll();
_redraw = false;
}
}
void MenuNemesis::redrawAll() {
redrawMain();
}
void MenuManager::redrawMain() {
// Draw menu background
_engine->getRenderManager()->blitSurfaceToMenu(_mainBack, _mainScroller._pos.x, _mainScroller._pos.y, 0);
// Draw buttons
if (_menuFocus.front() == kFocusMain)
for (int8 i = 0; i < 4; i++) {
if (_enableFlags.get(i) && (_mainFrames[i] >= 0)) {
if (_mainClicked == i)
_engine->getRenderManager()->blitSurfaceToMenu(_mainButtons[i][_params.clickedFrame], _params.wxButs[i][1], _mainScroller._pos.y, 0);
else
_engine->getRenderManager()->blitSurfaceToMenu(_mainButtons[i][_mainFrames[i]], _params.wxButs[i][1], _mainScroller._pos.y, 0);
}
}
_clean = false;
}
void MenuManager::setFocus(int8 currentFocus) {
_menuFocus.set(currentFocus);
assert(_menuFocus.size() <= 4);
}
MenuZGI::MenuZGI(ZVision *engine, const Common::Rect menuArea) :
MenuManager(engine, menuArea, zgiParams),
_itemsScroller(Common::Point(0, 0), Common::Point(_wSideMenuTab - _wSideMenu, 0), _sideMenuPeriod),
_magicScroller(Common::Point(-_wSideMenu, 0), Common::Point(-_wSideMenuTab, 0), _sideMenuPeriod),
_itemsOrigin(menuArea.left, menuArea.top),
_magicOrigin(menuArea.right, menuArea.top) {
_magicArea = Common::Rect(_magicOrigin + _magicScroller._pos, _wSideMenu, _hSideMenu);
_itemsArea = Common::Rect(_itemsOrigin + _itemsScroller._pos, _wSideMenu, _hSideMenu);
// Buffer main menu background
_engine->getRenderManager()->readImageToSurface("gmzau031.tga", _mainBack, false);
char buf[24];
for (int i = 0; i < 4; i++) {
// Buffer menu buttons
Common::sprintf_s(buf, "gmzmu%2.2x1.tga", i);
_engine->getRenderManager()->readImageToSurface(buf, _mainButtons[i][0], false);
Common::sprintf_s(buf, "gmznu%2.2x1.tga", i);
_engine->getRenderManager()->readImageToSurface(buf, _mainButtons[i][1], false);
}
for (int i = 1; i < 4; i++) {
// Buffer full menu backgrounds
Common::sprintf_s(buf, "gmzau%2.2x1.tga", i);
_engine->getRenderManager()->readImageToSurface(buf, _menuBack[i - 1], false);
}
for (int i = 0; i < 50; i++) {
_items[i][0] = NULL;
_items[i][1] = NULL;
_itemId[i] = 0;
}
for (int i = 0; i < 12; i++) {
_magic[i][0] = NULL;
_magic[i][1] = NULL;
_magicId[i] = 0;
}
// Initialise focus sequence
setFocus(kFocusMain);
setFocus(kFocusMagic);
setFocus(kFocusItems);
setFocus(kFocusNone);
}
MenuZGI::~MenuZGI() {
for (int i = 0; i < 3; i++) {
_menuBack[i].free();
}
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 2; j++)
_mainButtons[i][j].free();
}
for (int i = 0; i < 50; i++) {
if (_items[i][0]) {
_items[i][0]->free();
delete _items[i][0];
}
if (_items[i][1]) {
_items[i][1]->free();
delete _items[i][1];
}
}
for (int i = 0; i < 12; i++) {
if (_magic[i][0]) {
_magic[i][0]->free();
delete _magic[i][0];
}
if (_magic[i][1]) {
_magic[i][1]->free();
delete _magic[i][1];
}
}
}
bool MenuZGI::inMenu(const Common::Point &pos) const {
return _menuTriggerArea.contains(pos) || (_menuFocus.front() != kFocusNone);
}
void MenuZGI::onMouseUp(const Common::Point &pos) {
if (inMenu(pos))
// _redraw = true;
switch (_menuFocus.front()) {
case kFocusItems:
if (_enableFlags.get(kItemsMenu)) {
int itemCount = _engine->getScriptManager()->getStateValue(StateKey_Inv_TotalSlots);
if (itemCount == 0)
itemCount = 20;
int i = mouseOverItem(pos, itemCount);
if (i != -1) {
int32 mouseItem = _engine->getScriptManager()->getStateValue(StateKey_InventoryItem);
if (mouseItem >= 0 && mouseItem < 0xE0) {
_engine->getScriptManager()->inventoryDrop(mouseItem);
_engine->getScriptManager()->inventoryAdd(_engine->getScriptManager()->getStateValue(StateKey_Inv_StartSlot + i));
_engine->getScriptManager()->setStateValue(StateKey_Inv_StartSlot + i, mouseItem);
_redraw = true;
}
}
}
break;
case kFocusMagic:
if (_enableFlags.get(kMagicMenu)) {
int i = mouseOverMagic(pos);
if (i != -1) {
uint itemnum = _engine->getScriptManager()->getStateValue(StateKey_Spell_1 + i);
if (itemnum != 0) {
if (_engine->getScriptManager()->getStateValue(StateKey_Reversed_Spellbooc) == 1)
itemnum = 0xEE + i;
else
itemnum = 0xE0 + i;
}
if (itemnum)
if (_engine->getScriptManager()->getStateValue(StateKey_InventoryItem) == 0 || _engine->getScriptManager()->getStateValue(StateKey_InventoryItem) >= 0xE0)
_engine->getScriptManager()->setStateValue(StateKey_Active_Spell, itemnum);
}
}
break;
case kFocusMain:
MenuManager::onMouseUp(pos);
break;
default:
break;
}
}
void MenuZGI::onMouseMove(const Common::Point &pos) {
if (!inMenu(pos)) {
_mainScroller.reset();
_magicScroller.reset();
_itemsScroller.reset();
}
// Set focus to topmost layer of menus that mouse is currently over
for (uint8 i = 0; i < _menuFocus.size(); i++) {
switch (_menuFocus[i]) {
case kFocusItems:
if (_itemsArea.contains(pos)) {
setFocus(kFocusItems);
i = _menuFocus.size() + 1;
}
break;
case kFocusMagic:
if (_magicArea.contains(pos)) {
setFocus(kFocusMagic);
i = _menuFocus.size() + 1;
}
break;
case kFocusMain:
if (_mainArea.contains(pos)) {
setFocus(kFocusMain);
i = _menuFocus.size() + 1;
}
break;
default:
setFocus(kFocusNone);
break;
}
}
_itemsScroller.setActive(_menuFocus.front() == kFocusItems);
_magicScroller.setActive(_menuFocus.front() == kFocusMagic);
if (_menuFocus.front() != kFocusNone) {
switch (_menuFocus.front()) {
case kFocusItems:
if (_enableFlags.get(kItemsMenu)) {
int itemCount = _engine->getScriptManager()->getStateValue(StateKey_Inv_TotalSlots);
if (itemCount == 0)
itemCount = 20;
else if (itemCount > 50)
itemCount = 50;
int lastItem = _mouseOnItem;
_mouseOnItem = mouseOverItem(pos, itemCount);
if (lastItem != _mouseOnItem)
if (_engine->getScriptManager()->getStateValue(StateKey_Inv_StartSlot + _mouseOnItem) || _engine->getScriptManager()->getStateValue(StateKey_Inv_StartSlot + lastItem))
_redraw = true;
}
break;
case kFocusMagic:
if (_enableFlags.get(kMagicMenu)) {
int lastItem = _mouseOnItem;
_mouseOnItem = mouseOverMagic(pos);
if (lastItem != _mouseOnItem)
if (_engine->getScriptManager()->getStateValue(StateKey_Spell_1 + _mouseOnItem) || _engine->getScriptManager()->getStateValue(StateKey_Spell_1 + lastItem))
_redraw = true;
}
break;
case kFocusMain:
break;
}
}
MenuManager::onMouseMove(pos);
}
int MenuZGI::mouseOverItem(const Common::Point &pos, int itemCount) {
int itemWidth = (_wSideMenu - 28) / itemCount;
Common::Rect itemHotspot = Common::Rect(28, _hSideMenu);
itemHotspot.moveTo(_itemsOrigin + _itemsScroller._pos);
for (int i = 0; i < itemCount; i++) {
if (itemHotspot.contains(pos))
return i;
itemHotspot.translate(itemWidth, 0);
}
return -1;
}
int MenuZGI::mouseOverMagic(const Common::Point &pos) {
Common::Rect magicHotspot(28, _hSideMenu);
magicHotspot.moveTo(_magicOrigin + _magicScroller._pos);
magicHotspot.translate(28, 0); // Offset from end of menu
for (int i = 0; i < 12; i++) {
if (magicHotspot.contains(pos))
return i;
magicHotspot.translate(_magicWidth, 0);
}
return -1;
}
void MenuZGI::process(uint32 deltatime) {
if (_itemsScroller.update(deltatime)) {
_itemsArea.moveTo(_itemsOrigin + _itemsScroller._pos);
_redraw = true;
}
if (_magicScroller.update(deltatime)) {
_magicArea.moveTo(_magicOrigin + _magicScroller._pos);
_redraw = true;
}
MenuManager::process(deltatime);
}
void MenuZGI::redrawAll() {
if (MenuManager::inMenu())
for (int8 i = _menuFocus.size() - 1; i >= 0; i--)
switch (_menuFocus[i]) {
case kFocusItems:
if (_enableFlags.get(kItemsMenu)) {
redrawItems();
}
break;
case kFocusMagic:
if (_enableFlags.get(kMagicMenu)) {
redrawMagic();
}
break;
case kFocusMain:
redrawMain();
break;
default:
break;
}
}
void MenuZGI::redrawMagic() {
const int16 yOrigin = _menuArea.width();
_engine->getRenderManager()->blitSurfaceToMenu(_menuBack[kFocusMagic], yOrigin + _magicScroller._pos.x, 0, 0);
for (int i = 0; i < 12; i++) {
bool inrect = false;
if (_mouseOnItem == i)
inrect = true;
uint curItemId = _engine->getScriptManager()->getStateValue(StateKey_Spell_1 + i);
if (curItemId) {
if (_engine->getScriptManager()->getStateValue(StateKey_Reversed_Spellbooc) == 1)
curItemId = 0xEE + i;
else
curItemId = 0xE0 + i;
}
if (curItemId != 0) {
if (_itemId[i] != curItemId) {
char buf[16];
Common::sprintf_s(buf, "gmzwu%2.2x1.tga", curItemId);
_magic[i][0] = _engine->getRenderManager()->loadImage(buf, false);
Common::sprintf_s(buf, "gmzxu%2.2x1.tga", curItemId);
_magic[i][1] = _engine->getRenderManager()->loadImage(buf, false);
_magicId[i] = curItemId;
}
if (inrect)
_engine->getRenderManager()->blitSurfaceToMenu(*_magic[i][1], yOrigin + _magicScroller._pos.x + 28 + _magicWidth * i, 0, 0);
else
_engine->getRenderManager()->blitSurfaceToMenu(*_magic[i][0], yOrigin + _magicScroller._pos.x + 28 + _magicWidth * i, 0, 0);
} else {
if (_magic[i][0]) {
_magic[i][0]->free();
delete _magic[i][0];
_magic[i][0] = NULL;
}
if (_magic[i][1]) {
_magic[i][1]->free();
delete _magic[i][1];
_magic[i][1] = NULL;
}
_magicId[i] = 0;
}
}
_clean = false;
}
void MenuZGI::redrawItems() {
_engine->getRenderManager()->blitSurfaceToMenu(_menuBack[kFocusItems], _itemsScroller._pos.x, 0, 0);
int itemCount = _engine->getScriptManager()->getStateValue(StateKey_Inv_TotalSlots);
if (itemCount == 0)
itemCount = 20;
else if (itemCount > 50)
itemCount = 50;
int itemWidth = (_wSideMenu - 28) / itemCount;
for (int i = 0; i < itemCount; i++) {
bool inrect = false;
if (_mouseOnItem == i)
inrect = true;
uint curItemId = _engine->getScriptManager()->getStateValue(StateKey_Inv_StartSlot + i);
if (curItemId != 0) {
if (_itemId[i] != curItemId) {
char buf[16];
Common::sprintf_s(buf, "gmzwu%2.2x1.tga", curItemId);
_items[i][0] = _engine->getRenderManager()->loadImage(buf, false);
Common::sprintf_s(buf, "gmzxu%2.2x1.tga", curItemId);
_items[i][1] = _engine->getRenderManager()->loadImage(buf, false);
_itemId[i] = curItemId;
}
if (inrect)
_engine->getRenderManager()->blitSurfaceToMenu(*_items[i][1], _itemsScroller._pos.x + itemWidth * i, 0, 0);
else
_engine->getRenderManager()->blitSurfaceToMenu(*_items[i][0], _itemsScroller._pos.x + itemWidth * i, 0, 0);
} else {
if (_items[i][0]) {
_items[i][0]->free();
delete _items[i][0];
_items[i][0] = NULL;
}
if (_items[i][1]) {
_items[i][1]->free();
delete _items[i][1];
_items[i][1] = NULL;
}
_itemId[i] = 0;
}
}
_clean = false;
}
MenuNemesis::MenuNemesis(ZVision *engine, const Common::Rect menuArea) :
MenuManager(engine, menuArea, nemesisParams) {
// Buffer menu background image
_engine->getRenderManager()->readImageToSurface("bar.tga", _mainBack, false);
char buf[24];
for (int i = 0; i < 4; i++)
// Buffer menu buttons
for (int j = 0; j < 6; j++) {
Common::sprintf_s(buf, "butfrm%d%d.tga", i + 1, j);
_engine->getRenderManager()->readImageToSurface(buf, _mainButtons[i][j], false);
}
}
MenuNemesis::~MenuNemesis() {
for (int i = 0; i < 4; i++)
for (int j = 0; j < 6; j++)
_mainButtons[i][j].free();
}
bool MenuNemesis::inMenu(const Common::Point &pos) const {
return _menuTriggerArea.contains(pos) || (_menuFocus.front() != kFocusNone);
}
void MenuNemesis::onMouseMove(const Common::Point &pos) {
// Trigger main menu scrolldown to get mouse over main trigger area
// Set focus to topmost layer of menus that mouse is currently over
if (_mainArea.contains(pos) || _menuTriggerArea.contains(pos))
setFocus(kFocusMain);
else
setFocus(kFocusNone);
MenuManager::onMouseMove(pos);
}
} // End of namespace ZVision

View File

@@ -0,0 +1,192 @@
/* 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 ZVISION_MENU_H
#define ZVISION_MENU_H
#include "common/array.h"
#include "common/bitarray.h"
#include "common/rect.h"
#include "graphics/surface.h"
#include "zvision/zvision.h"
#include "zvision/common/focus_list.h"
#include "zvision/common/scroller.h"
#include "zvision/scripting/script_manager.h"
namespace ZVision {
enum {
kMainMenuSave = 0,
kMainMenuLoad = 1,
kMainMenuPrefs = 2,
kMainMenuExit = 3,
kItemsMenu = 4,
kMagicMenu = 5
};
struct MenuParams {
int16 wxButs[4][2]; // Widths & X positions of main menu buttons; {Save, Restore, Prefs, Quit}
int16 wMain; // Width of main menu background
int8 idleFrame; // Frame to display of unselected main menu button
int8 activeFrame; // Frame to display of selected main menu button when mouse is down
int8 clickedFrame; // Frame to display of selected main menu button when mouse is down
Common::Point activePos; // Fully scrolled main menu position, relative to origin of menu area
Common::Point idlePos; // Fully retracted main menu position, relative to origin of menu area
int16 period; // Duration of main menu scrolldown
int16 triggerHeight; // Height of menu trigger area when inactive
int16 buttonPeriod; // Duration of main menu button animation
};
// NB - menu area is same width as working window.
static const MenuParams nemesisParams {
{ {120, -1}, {144, 120}, {128, 264}, {120, 392} },
512,
-1,
4,
5,
Common::Point(0, 0),
Common::Point(0, -32),
500,
2,
500
};
static const MenuParams zgiParams {
{ {135, 50}, {135, 185}, {135, 320}, {135, 455} },
580,
0,
1,
1,
Common::Point(30, 0),
Common::Point(30, -20),
250,
32,
0
};
class MenuManager {
public:
MenuManager(ZVision *engine, const Common::Rect menuArea, const MenuParams params);
virtual ~MenuManager();
virtual void onMouseMove(const Common::Point &pos);
virtual void onMouseDown(const Common::Point &pos);
virtual void onMouseUp(const Common::Point &pos);
virtual void process(uint32 deltaTimeInMillis);
bool inMenu() const {
return _prevInMenu;
}
virtual bool inMenu(const Common::Point &pos) const {
return false;
} // For widescreen mod; used to suspend panning, tilting & scripting triggers when the mouse is within the working window but also in the menu.
void mainMouseDown(const Common::Point &pos); // Show clicked graphic under selected button
bool mainMouseMove(const Common::Point &pos); // return true if selected button has changed
void setEnable(uint16 flags);
uint16 getEnable() const {
return _menuBarFlag;
}
bool getEnable(uint8 flag) const {
return _enableFlags.get(flag);
}
protected:
virtual void redrawAll() {}
void redrawMain();
int mouseOverMain(const Common::Point &pos);
void setFocus(int8 currentFocus);
bool _prevInMenu = false;
bool _redraw = true;
int _mouseOnItem = -1;
static const uint8 _hMainMenu = 32;
int8 _mainClicked = -1;
ZVision *_engine;
const MenuParams _params;
uint16 _menuBarFlag;
const Common::Rect _menuArea;
const Common::Point _menuOrigin;
const Common::Rect _menuTriggerArea;
Graphics::Surface _mainBack;
Graphics::Surface _mainButtons[4][6];
Common::BitArray _enableFlags;
Common::Rect _mainArea;
Common::Rect _menuHotspots[4];
int8 _mainFrames[4]; // Frame to display of each main menu button; first row is currently displayed, 2nd row is backbuffer for idle animations
Scroller _mainScroller;
FocusList<int8> _menuFocus; // Order in which menus have most recently had focus; determines current mouse focus & order in which to redraw them.
bool _clean = false; // Whether or not to blank
LinearScroller *_buttonAnim[4];
};
class MenuZGI: public MenuManager {
public:
MenuZGI(ZVision *engine, Common::Rect menuArea);
~MenuZGI() override;
void onMouseMove(const Common::Point &pos) override; // NB Pos is in screen coordinates
void onMouseUp(const Common::Point &pos) override;
void process(uint32 deltaTimeInMillis) override;
bool inMenu(const Common::Point &pos) const override;
private:
void redrawAll() override;
Graphics::Surface _menuBack[3];
Graphics::Surface *_items[50][2];
uint _itemId[50];
Graphics::Surface *_magic[12][2];
uint _magicId[12];
// void redrawMain(bool hasFocus) override;
void redrawItems();
void redrawMagic();
int mouseOverItem(const Common::Point &pos, int itemCount);
int mouseOverMagic(const Common::Point &pos);
static const uint16 _hSideMenu = 32;
static const uint16 _wSideMenu = 600;
static const uint16 _wSideMenuTab = 20;
static const int16 _magicWidth = 47;
const int16 _sideMenuPeriod = 300;
Scroller _itemsScroller, _magicScroller;
const Common::Point _magicOrigin;
const Common::Point _itemsOrigin;
Common::Rect _magicArea;
Common::Rect _itemsArea;
};
class MenuNemesis: public MenuManager {
public:
MenuNemesis(ZVision *engine, Common::Rect menuArea);
~MenuNemesis() override;
void onMouseMove(const Common::Point &pos) override;
bool inMenu(const Common::Point &pos) const override;
private:
void redrawAll() override;
};
} // End of namespace ZVision
#endif

View File

@@ -0,0 +1,77 @@
/* 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 ZVISION_PUZZLE_H
#define ZVISION_PUZZLE_H
#include "common/list.h"
#include "common/ptr.h"
#include "zvision/scripting/actions.h"
namespace ZVision {
struct Puzzle {
Puzzle() : key(0), addedBySetState(false) {}
~Puzzle() {
for (auto & action : resultActions)
delete action;
}
/** How criteria should be decided */
enum CriteriaOperator {
EQUAL_TO,
NOT_EQUAL_TO,
GREATER_THAN,
LESS_THAN
};
/** Criteria for a Puzzle result to be fired */
struct CriteriaEntry {
/** The key of a global state */
uint32 key;
/**
* What we're comparing the value of the global state against
* This can either be a pure value or it can be the key of another global state
*/
uint32 argument;
/** How to do the comparison */
CriteriaOperator criteriaOperator;
/** Whether 'argument' is the key of a global state (true) or a pure value (false) */
bool argumentIsAKey;
};
enum StateFlags {
ONCE_PER_INST = 0x01,
DISABLED = 0x02,
DO_ME_NOW = 0x04
};
uint32 key;
Common::List<Common::List <CriteriaEntry> > criteriaList;
// This has to be list of pointers because ResultAction is abstract
Common::List<ResultAction *> resultActions;
bool addedBySetState;
};
} // End of namespace ZVision
#endif

View File

@@ -0,0 +1,566 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "common/file.h"
#include "common/scummsys.h"
#include "common/str.h"
#include "common/textconsole.h"
#include "common/tokenizer.h"
#include "zvision/detection.h"
#include "zvision/zvision.h"
#include "zvision/scripting/actions.h"
#include "zvision/scripting/puzzle.h"
#include "zvision/scripting/script_manager.h"
#include "zvision/scripting/controls/fist_control.h"
#include "zvision/scripting/controls/hotmov_control.h"
#include "zvision/scripting/controls/input_control.h"
#include "zvision/scripting/controls/lever_control.h"
#include "zvision/scripting/controls/paint_control.h"
#include "zvision/scripting/controls/push_toggle_control.h"
#include "zvision/scripting/controls/safe_control.h"
#include "zvision/scripting/controls/save_control.h"
#include "zvision/scripting/controls/slot_control.h"
#include "zvision/scripting/controls/titler_control.h"
namespace ZVision {
void ScriptManager::parseScrFile(const Common::Path &fileName, ScriptScope &scope) {
auto parse = [&](Common::File & file) {
while (!file.eos()) {
Common::String line = file.readLine();
if (file.err())
error("Error parsing scr file: %s", fileName.toString().c_str());
trimCommentsAndWhiteSpace(&line);
if (line.empty())
continue;
if (line.matchString("puzzle:*", true)) {
Puzzle *puzzle = new Puzzle();
if (sscanf(line.c_str(), "puzzle:%u", &(puzzle->key)) != 1) {
debugC(kDebugScript, "Malformed puzzle string: %s", line.c_str());
continue;
}
if (getStateFlag(puzzle->key) & Puzzle::ONCE_PER_INST)
setStateValue(puzzle->key, 0);
parsePuzzle(puzzle, file);
scope.puzzles.push_back(puzzle);
} else if (line.matchString("control:*", true)) {
Control *ctrl = parseControl(line, file);
if (ctrl)
scope.controls.push_back(ctrl);
}
}
};
Common::File mainFile;
Common::File auxFile;
Common::String auxFileName = fileName.toString();
replace(auxFileName, Common::String(".scr"), Common::String(".aux"));
debugC(1, kDebugFile, "Auxiliary filename %s", auxFileName.c_str());
Common::Path auxFilePath(auxFileName);
debugC(1, kDebugFile, "Auxiliary path %s", auxFilePath.toString().c_str());
if (!mainFile.open(fileName))
error("Script file not found: %s", fileName.toString().c_str());
else {
debugC(1, kDebugScript, "Parsing primary script file %s", fileName.toString().c_str());
parse(mainFile);
// TODO - add config option to disable/enable auxiliary scripting
if (auxFile.exists(auxFilePath)) {
debugC(1, kDebugFile, "Auxiliary script file found");
if (auxFile.open(auxFilePath)) {
debugC(1, kDebugScript, "Parsing auxiliary script file %s", auxFilePath.toString().c_str());
parse(auxFile);
} else
debugC(1, kDebugFile, "Unable to open auxiliary script file %s", auxFilePath.toString().c_str());
}
}
scope.procCount = 0;
}
void ScriptManager::parsePuzzle(Puzzle *puzzle, Common::SeekableReadStream &stream) {
Common::String line = stream.readLine();
trimCommentsAndWhiteSpace(&line);
while (!stream.eos() && !line.contains('}')) {
if (line.matchString("criteria {", true)) {
parseCriteria(stream, puzzle->criteriaList, puzzle->key);
} else if (line.matchString("results {", true)) {
parseResults(stream, puzzle->resultActions, puzzle->key);
// WORKAROUNDS:
switch (_engine->getGameId()) {
case GID_NEMESIS:
// WORKAROUND for a script bug in Zork Nemesis, room ve5e (tuning
// fork box closeup). If the player leaves the screen while the
// box is open, puzzle 19398 shows the animation where the box
// closes, but the box state (state variable 19397) is not updated.
// We insert the missing assignment for the box state here.
// Fixes bug #6803.
if (puzzle->key == 19398)
puzzle->resultActions.push_back(new ActionAssign(_engine, 11, "19397, 0"));
break;
case GID_GRANDINQUISITOR:
switch (puzzle->key) {
case 10836:
// WORKAROUND for bug #10604. If the player is looking at the
// cigar box when Antharia Jack returns to examine the lamp,
// pp1f_video_flag remains 1. Later, when the player returns
// to pick up the lantern, the game will try to play the
// cutscene again, but since that script has already been
// run the player gets stuck in a dark room instead. We have
// to add the assignment action to the front, or it won't be
// reached because changing the location terminates the script.
//
// Fixing it this way only keeps the bug from happening. It
// will not repair old savegames.
//
// Note that the bug only affects the DVD version. The CD
// version doesn't have a separate room for the cutscene.
if (_engine->getFeatures() & ADGF_DVD)
puzzle->resultActions.push_front(new ActionAssign(_engine, 11, "10803, 0"));
break;
// WORKAROUND for a script bug in Zork: Grand Inquisitor, room dc10.
// Background heartbeat sound effect never terminates upon location change.
case 2341:
case 2344:
case 17545:
puzzle->resultActions.push_front(new ActionKill(_engine, 11, "02310"));
break;
default:
break;
}
break;
default:
break;
}
} else if (line.matchString("flags {", true)) {
setStateFlag(puzzle->key, parseFlags(stream));
}
line = stream.readLine();
trimCommentsAndWhiteSpace(&line);
}
puzzle->addedBySetState = false;
}
bool ScriptManager::parseCriteria(Common::SeekableReadStream &stream, Common::List<Common::List<Puzzle::CriteriaEntry> > &criteriaList, uint32 key) const {
// Loop until we find the closing brace
Common::String line = stream.readLine();
trimCommentsAndWhiteSpace(&line);
// Skip any commented out criteria. If all the criteria are commented out,
// we might end up with an invalid criteria list (bug #6776).
while (line.empty()) {
line = stream.readLine();
trimCommentsAndWhiteSpace(&line);
}
// Criteria can be empty
if (line.contains('}')) {
return false;
}
// Create a new List to hold the CriteriaEntries
criteriaList.push_back(Common::List<Puzzle::CriteriaEntry>());
// WORKAROUNDS
switch (_engine->getGameId()) {
case GID_NEMESIS:
// WORKAROUND for a script bug in Zork: Nemesis, room td9e (fist puzzle)
// Since we patch the script that triggers when manipulating the left fist
// (below), we add an additional check for the left fist sound, so that it
// doesn't get killed immediately when the left fist animation starts.
// Together with the workaround below, it fixes bug #6783.
if (key == 3594) {
Puzzle::CriteriaEntry entry;
entry.key = 567;
entry.criteriaOperator = Puzzle::NOT_EQUAL_TO;
entry.argumentIsAKey = false;
entry.argument = 1;
criteriaList.back().push_back(entry);
}
break;
case GID_GRANDINQUISITOR:
// WORKAROUND for a script bug in Zork: Grand Inquisitor, room me2j
// (Closing the Time Tunnels). When the time tunnel is open the game
// shows a close-up of only the tunnel, instead of showing the entire
// booth. However, the scripts that draw the lever in its correct
// state do not test this flag, causing it to be drawn when it should
// not be. This fixes bug #6770.
if (key == 9536) {
Puzzle::CriteriaEntry entry;
entry.key = 9404; // me2j_time_tunnel_open
entry.criteriaOperator = Puzzle::EQUAL_TO;
entry.argumentIsAKey = false;
entry.argument = 0;
criteriaList.back().push_back(entry);
}
break;
default:
break;
}
while (!stream.eos() && !line.contains('}')) {
Puzzle::CriteriaEntry entry;
// Split the string into tokens using ' ' as a delimiter
Common::StringTokenizer tokenizer(line);
Common::String token;
// Parse the id out of the first token
token = tokenizer.nextToken();
if (sscanf(token.c_str(), "[%u]", &(entry.key)) != 1) {
debugC(kDebugScript, "Malformed criteria ID string %s", token.c_str());
return false;
}
// WORKAROUND for a script bug in Zork: Nemesis, room td9e (fist puzzle)
// Check for the state of animation 567 (left fist) when manipulating
// the fingers of the left fist (puzzle slots 3582, 3583).
// Together with the workaround above, it fixes bug #6783.
if (_engine->getGameId() == GID_NEMESIS && (key == 3582 || key == 3583) && entry.key == 568)
entry.key = 567;
// Parse the operator out of the second token
token = tokenizer.nextToken();
if (token.c_str()[0] == '=')
entry.criteriaOperator = Puzzle::EQUAL_TO;
else if (token.c_str()[0] == '!')
entry.criteriaOperator = Puzzle::NOT_EQUAL_TO;
else if (token.c_str()[0] == '>')
entry.criteriaOperator = Puzzle::GREATER_THAN;
else if (token.c_str()[0] == '<')
entry.criteriaOperator = Puzzle::LESS_THAN;
// There are supposed to be three tokens, but there is no
// guarantee that there will be a space between the second and
// the third one (bug #6774)
if (token.size() == 1) {
token = tokenizer.nextToken();
} else {
token.deleteChar(0);
}
// First determine if the last token is an id or a value
// Then parse it into 'argument'
if (token.contains('[')) {
if (sscanf(token.c_str(), "[%u]", &(entry.argument)) != 1) {
debugC(kDebugScript, "Malformed token string %s", token.c_str());
return false;
}
entry.argumentIsAKey = true;
} else {
if (sscanf(token.c_str(), "%u", &(entry.argument)) != 1) {
debugC(kDebugScript, "Malformed token string %s", token.c_str());
return false;
}
entry.argumentIsAKey = false;
}
// WORKAROUND for a script bug in Zork: Grand Inquisitor. If the
// fire timer is killed (e.g. by the inventory screen) with less
// than 10 units left, it will get stuck and never time out. We
// work around that by changing the condition from "greater than
// 10" to "greater than 0 but not 2 (the magic time-out value)".
//
// I have a sneaking suspicion that there may be other timer
// glitches like this, but this one makes the game unplayable
// and is easy to trigger.
if (_engine->getGameId() == GID_GRANDINQUISITOR && key == 17162) {
Puzzle::CriteriaEntry entry0;
entry0.key = 17161; // pe_fire
entry0.criteriaOperator = Puzzle::GREATER_THAN;
entry0.argumentIsAKey = false;
entry0.argument = 0;
criteriaList.back().push_back(entry0);
entry.criteriaOperator = Puzzle::NOT_EQUAL_TO;
entry.argument = 2;
}
criteriaList.back().push_back(entry);
line = stream.readLine();
trimCommentsAndWhiteSpace(&line);
}
return true;
}
void ScriptManager::parseResults(Common::SeekableReadStream &stream, Common::List<ResultAction *> &actionList, uint32 key) const {
// Loop until we find the closing brace
Common::String line = stream.readLine();
trimCommentsAndWhiteSpace(&line);
line.toLowercase();
// TODO: Re-order the if-then statements in order of highest occurrence
// While within results block
while (!stream.eos() && !line.contains('}')) {
// Skip empty lines
if (line.empty()) {
line = stream.readLine();
trimCommentsAndWhiteSpace(&line);
line.toLowercase();
continue;
}
debugC(4, kDebugScript, "Result line: %s", line.c_str());
const char *chrs = line.c_str();
uint pos;
if (line.matchString("action:*", true))
pos = 6;
else if (line.matchString("event:*", true))
pos = 5;
else if (line.matchString("background:*", true))
pos = 10;
else
continue;
if (pos < line.size()) { // Stuff left
uint startpos = pos + 1; // first character after colon
// Scan for next colon or opening bracket
for (pos = startpos; pos < line.size(); pos++)
if (chrs[pos] == ':' || chrs[pos] == '(')
break;
debugC(4, kDebugScript, "startpos %d, pos %d, line.size %d", startpos, pos, line.size());
int32 slot = 11; // Non-setting default slot
Common::String args = "";
Common::String act(chrs + startpos, chrs + pos);
// Extract arguments, if any
if (pos < line.size()) {
startpos = pos + 1;
if (chrs[pos] == ':') {
for (pos = startpos; pos < line.size(); pos++)
if (chrs[pos] == '(')
break;
// Extract slotkey, if specified
Common::String strSlot(chrs + startpos, chrs + pos);
slot = atoi(strSlot.c_str());
startpos = pos + 1;
}
if (pos < line.size()) {
for (pos = startpos; pos < line.size(); pos++)
if (chrs[pos] == ')')
break;
args = Common::String(chrs + startpos, chrs + pos);
}
}
debugC(4, kDebugScript, "Action string: '%s', slot %d, arguments string '%s'", act.c_str(), slot, args.c_str());
// Parse for the action type
if (act.matchString("add", true)) {
actionList.push_back(new ActionAdd(_engine, slot, args));
} else if (act.matchString("animplay", true)) {
actionList.push_back(new ActionPlayAnimation(_engine, slot, args));
} else if (act.matchString("animpreload", true)) {
actionList.push_back(new ActionPreloadAnimation(_engine, slot, args));
} else if (act.matchString("animunload", true)) {
// Only used by ZGI (locations cd6e, cd6k, dg2f, dg4e, dv1j)
actionList.push_back(new ActionUnloadAnimation(_engine, slot, args));
} else if (act.matchString("attenuate", true)) {
actionList.push_back(new ActionAttenuate(_engine, slot, args));
} else if (act.matchString("assign", true)) {
if (_engine->getGameId() == GID_GRANDINQUISITOR && key == 17761) {
// WORKAROUND for a script bug in Zork: Grand Inquisitor, room tp1e.
// Background looping sound effect continuously restarts on every game cycle.
// This is caused by resetting itself to zero as a result of itself.
// Simple fix is to simply not generate this result assignment action at all.
} else
actionList.push_back(new ActionAssign(_engine, slot, args));
} else if (act.matchString("change_location", true)) {
actionList.push_back(new ActionChangeLocation(_engine, slot, args));
} else if (act.matchString("crossfade", true)) {
actionList.push_back(new ActionCrossfade(_engine, slot, args));
} else if (act.matchString("cursor", true)) {
actionList.push_back(new ActionCursor(_engine, slot, args));
} else if (act.matchString("debug", true)) {
// Not used. Purposely left empty
} else if (act.matchString("delay_render", true)) {
actionList.push_back(new ActionDelayRender(_engine, slot, args));
} else if (act.matchString("disable_control", true)) {
actionList.push_back(new ActionDisableControl(_engine, slot, args));
} else if (act.matchString("disable_venus", true)) {
// Not used. Purposely left empty
} else if (act.matchString("display_message", true)) {
actionList.push_back(new ActionDisplayMessage(_engine, slot, args));
} else if (act.matchString("dissolve", true)) {
actionList.push_back(new ActionDissolve(_engine));
} else if (act.matchString("distort", true)) {
// Only used by Zork: Nemesis for the "treatment" puzzle in the Sanitarium (aj30)
actionList.push_back(new ActionDistort(_engine, slot, args));
} else if (act.matchString("enable_control", true)) {
actionList.push_back(new ActionEnableControl(_engine, slot, args));
} else if (act.matchString("flush_mouse_events", true)) {
actionList.push_back(new ActionFlushMouseEvents(_engine, slot));
} else if (act.matchString("inventory", true)) {
actionList.push_back(new ActionInventory(_engine, slot, args));
} else if (act.matchString("kill", true)) {
// Only used by ZGI
actionList.push_back(new ActionKill(_engine, slot, args));
} else if (act.matchString("menu_bar_enable", true)) {
actionList.push_back(new ActionMenuBarEnable(_engine, slot, args));
} else if (act.matchString("music", true)) {
actionList.push_back(new ActionMusic(_engine, slot, args, false));
} else if (act.matchString("pan_track", true)) {
actionList.push_back(new ActionPanTrack(_engine, slot, args));
} else if (act.matchString("playpreload", true)) {
actionList.push_back(new ActionPlayPreloadAnimation(_engine, slot, args));
} else if (act.matchString("preferences", true)) {
actionList.push_back(new ActionPreferences(_engine, slot, args));
} else if (act.matchString("quit", true)) {
actionList.push_back(new ActionQuit(_engine, slot));
} else if (act.matchString("random", true)) {
actionList.push_back(new ActionRandom(_engine, slot, args));
} else if (act.matchString("region", true)) {
// Only used by Zork: Nemesis
actionList.push_back(new ActionRegion(_engine, slot, args));
} else if (act.matchString("restore_game", true)) {
// Only used by ZGI to load the restart game slot, r.svr.
// Used by the credits screen.
actionList.push_back(new ActionRestoreGame(_engine, slot, args));
} else if (act.matchString("rotate_to", true)) {
actionList.push_back(new ActionRotateTo(_engine, slot, args));
} else if (act.matchString("save_game", true)) {
// Not used. Purposely left empty
} else if (act.matchString("set_partial_screen", true)) {
actionList.push_back(new ActionSetPartialScreen(_engine, slot, args));
} else if (act.matchString("set_screen", true)) {
actionList.push_back(new ActionSetScreen(_engine, slot, args));
} else if (act.matchString("set_venus", true)) {
// Not used. Purposely left empty
} else if (act.matchString("stop", true)) {
actionList.push_back(new ActionStop(_engine, slot, args));
} else if (act.matchString("streamvideo", true)) {
actionList.push_back(new ActionStreamVideo(_engine, slot, args));
} else if (act.matchString("syncsound", true)) {
actionList.push_back(new ActionSyncSound(_engine, slot, args));
} else if (act.matchString("timer", true)) {
actionList.push_back(new ActionTimer(_engine, slot, args));
} else if (act.matchString("ttytext", true)) {
actionList.push_back(new ActionTtyText(_engine, slot, args));
} else if (act.matchString("universe_music", true)) {
actionList.push_back(new ActionMusic(_engine, slot, args, true));
} else if (act.matchString("copy_file", true)) {
// Not used. Purposely left empty
} else {
warning("Unhandled result action type: %s", line.c_str());
}
}
line = stream.readLine();
trimCommentsAndWhiteSpace(&line);
line.toLowercase();
}
return;
}
uint ScriptManager::parseFlags(Common::SeekableReadStream &stream) const {
uint flags = 0;
// Loop until we find the closing brace
Common::String line = stream.readLine();
trimCommentsAndWhiteSpace(&line);
while (!stream.eos() && !line.contains('}')) {
if (line.matchString("ONCE_PER_INST", true)) {
flags |= Puzzle::ONCE_PER_INST;
} else if (line.matchString("DO_ME_NOW", true)) {
flags |= Puzzle::DO_ME_NOW;
} else if (line.matchString("DISABLED", true)) {
flags |= Puzzle::DISABLED;
}
line = stream.readLine();
trimCommentsAndWhiteSpace(&line);
}
return flags;
}
Control *ScriptManager::parseControl(Common::String &line, Common::SeekableReadStream &stream) {
uint32 key;
char controlTypeBuffer[20];
if (sscanf(line.c_str(), "control:%u %s {", &key, controlTypeBuffer) != 2) {
debugC(kDebugScript, "Malformed control string: %s", line.c_str());
return NULL;
}
Common::String controlType(controlTypeBuffer);
if (controlType.equalsIgnoreCase("push_toggle")) {
// WORKAROUND for a script bug in ZGI: There is an invalid hotspot
// at scene em1h (bottom of tower), which points to a missing
// script em1n. This is a hotspot at the right of the screen.
// In the original, this hotspot doesn't lead anywhere anyway,
// so instead of moving to a missing scene, we just remove the
// hotspot altogether. The alternative would be to just process
// and ignore invalid scenes, but I don't think it's worth the
// effort. Fixes bug #6780.
if (_engine->getGameId() == GID_GRANDINQUISITOR && key == 5653)
return NULL;
return new PushToggleControl(_engine, key, stream);
} else if (controlType.equalsIgnoreCase("flat")) {
Control::parseFlatControl(_engine);
return NULL;
} else if (controlType.equalsIgnoreCase("pana")) {
Control::parsePanoramaControl(_engine, stream);
return NULL;
} else if (controlType.equalsIgnoreCase("tilt")) {
// Only used in Zork Nemesis, handles tilt controls (ZGI doesn't have a tilt view)
Control::parseTiltControl(_engine, stream);
return NULL;
} else if (controlType.equalsIgnoreCase("slot")) {
return new SlotControl(_engine, key, stream);
} else if (controlType.equalsIgnoreCase("input")) {
return new InputControl(_engine, key, stream);
} else if (controlType.equalsIgnoreCase("save")) {
return new SaveControl(_engine, key, stream);
} else if (controlType.equalsIgnoreCase("lever")) {
// Only used in Zork Nemesis, handles draggable levers (te2e, tm7e, tp2e, tt2e, tz2e)
return new LeverControl(_engine, key, stream);
} else if (controlType.equalsIgnoreCase("safe")) {
// Only used in Zork Nemesis, handles the safe in the Asylum (ac4g)
return new SafeControl(_engine, key, stream);
} else if (controlType.equalsIgnoreCase("hotmovie")) {
// Only used in Zork Nemesis, handles movies where the player needs to click on something (mj7g, vw3g)
return new HotMovControl(_engine, key, stream);
} else if (controlType.equalsIgnoreCase("fist")) {
// Only used in Zork Nemesis, handles the door lock puzzle with the skeletal fingers (td9e)
return new FistControl(_engine, key, stream);
} else if (controlType.equalsIgnoreCase("paint")) {
// Only used in Zork Nemesis, handles the painting puzzle screen in Lucien's room in Irondune (ch4g)
return new PaintControl(_engine, key, stream);
} else if (controlType.equalsIgnoreCase("titler")) {
// Only used in Zork Nemesis, handles the death screen with the Restore/Exit buttons (cjde)
return new TitlerControl(_engine, key, stream);
}
return NULL;
}
} // End of namespace ZVision

View File

@@ -0,0 +1,912 @@
/* 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/algorithm.h"
#include "common/config-manager.h"
#include "common/debug.h"
#include "common/hashmap.h"
#include "common/scummsys.h"
#include "common/stream.h"
#include "zvision/zvision.h"
#include "zvision/file/save_manager.h"
#include "zvision/graphics/render_manager.h"
#include "zvision/graphics/cursors/cursor_manager.h"
#include "zvision/scripting/actions.h"
#include "zvision/scripting/menu.h"
#include "zvision/scripting/script_manager.h"
#include "zvision/scripting/effects/timer_effect.h"
namespace ZVision {
ScriptManager::ScriptManager(ZVision *engine)
: _engine(engine),
_currentlyFocusedControl(0),
_activeControls(NULL) {
}
ScriptManager::~ScriptManager() {
cleanScriptScope(_universe);
cleanScriptScope(_world);
cleanScriptScope(_room);
cleanScriptScope(_nodeview);
_controlEvents.clear();
}
void ScriptManager::initialize(bool restarted) {
if (restarted) {
_globalState.clear();
_globalStateFlags.clear();
}
cleanScriptScope(_universe);
cleanScriptScope(_world);
cleanScriptScope(_room);
cleanScriptScope(_nodeview);
_currentLocation.node = 0;
_currentLocation.world = 0;
_currentLocation.room = 0;
_currentLocation.view = 0;
if (restarted) {
for (auto &fx : _activeSideFx)
delete fx;
_activeSideFx.clear();
_referenceTable.clear();
switch (_engine->getGameId()) {
case GID_GRANDINQUISITOR:
// Bypass logo video
setStateValue(16966, 1);
// Ensure post-logo screen redraw is not inhibited in CD version
setStateValue(5813, 1);
// Bypass additional logo videos in DVD version
setStateValue(19810, 1);
setStateValue(19848, 1);
break;
case GID_NEMESIS:
default:
break;
}
}
parseScrFile("universe.scr", _universe);
changeLocation('g', 'a', 'r', 'y', 0);
_controlEvents.clear();
if (restarted)
_engine->loadSettings();
}
bool ScriptManager::changingLocation() const {
return _currentLocation != _nextLocation;
}
void ScriptManager::process(uint deltaTimeMillis) {
// If the location is changing, the script that did that may have
// triggered other scripts, so we give them all a few extra cycles to
// run. This fixes some missing scoring in ZGI, and quite
// possibly other minor glitches as well.
//
// Another idea would be to change if there are pending scripts
// in the exec queues, but that could cause this to hang
// indefinitely.
for (uint8 pass = 0; pass <= (changingLocation() ? _changeLocationExtraCycles : 0); pass++) {
updateNodes(pass == 0 ? deltaTimeMillis : 0);
debugC(5, kDebugLoop, "Script nodes updated");
if (!execScope(_nodeview))
break;
if (!execScope(_room))
break;
if (!execScope(_world))
break;
if (!execScope(_universe))
break;
}
updateControls(deltaTimeMillis);
if (changingLocation())
ChangeLocationReal(false);
}
bool ScriptManager::execScope(ScriptScope &scope) {
// Swap queues
PuzzleList *tmp = scope.execQueue;
scope.execQueue = scope.scopeQueue;
scope.scopeQueue = tmp;
scope.scopeQueue->clear();
for (auto &puzzle : scope.puzzles)
puzzle->addedBySetState = false;
switch (getStateValue(StateKey_ExecScopeStyle)) {
case 0: // ZGI
if (scope.procCount < 2)
for (auto &puzzle : scope.puzzles) {
if (!checkPuzzleCriteria(puzzle, scope.procCount))
return false;
}
else
for (auto &puzzle : (*scope.execQueue)) {
if (!checkPuzzleCriteria(puzzle, scope.procCount))
return false;
}
break;
case 1: // Nemesis
default:
for (auto &puzzle : scope.puzzles) {
if (!checkPuzzleCriteria(puzzle, scope.procCount))
return false;
}
break;
}
if (scope.procCount < 2)
scope.procCount++;
return true;
}
void ScriptManager::referenceTableAddPuzzle(uint32 key, PuzzleRef ref) {
if (_referenceTable.contains(key)) {
Common::Array<PuzzleRef> *arr = &_referenceTable[key];
for (uint32 i = 0; i < arr->size(); i++) {
if ((*arr)[i].puz == ref.puz)
return;
}
}
_referenceTable[key].push_back(ref);
}
void ScriptManager::addPuzzlesToReferenceTable(ScriptScope &scope) {
// Iterate through each local Puzzle
for (auto &puzzle : scope.puzzles) {
Puzzle *puzzlePtr = puzzle;
PuzzleRef ref;
ref.scope = &scope;
ref.puz = puzzlePtr;
referenceTableAddPuzzle(puzzlePtr->key, ref);
// Iterate through each CriteriaEntry and add a reference from the criteria key to the Puzzle
for (auto &criteria : puzzle->criteriaList) {
for (auto &entry : criteria)
referenceTableAddPuzzle(entry.key, ref);
}
}
}
void ScriptManager::updateNodes(uint deltaTimeMillis) {
// If process() returns true, it means the node can be deleted
for (auto fx = _activeSideFx.begin(); fx != _activeSideFx.end();) {
if ((*fx)->process(deltaTimeMillis)) {
delete(*fx);
// Remove the node
fx = _activeSideFx.erase(fx);
}
else
++fx;
}
}
void ScriptManager::updateControls(uint deltaTimeMillis) {
if (!_activeControls)
return;
// Process only one event
if (!_controlEvents.empty()) {
Common::Event _event = _controlEvents.front();
Common::Point imageCoord;
switch (_event.type) {
case Common::EVENT_LBUTTONDOWN:
imageCoord = _engine->getRenderManager()->screenSpaceToImageSpace(_event.mouse);
onMouseDown(_event.mouse, imageCoord);
break;
case Common::EVENT_LBUTTONUP:
imageCoord = _engine->getRenderManager()->screenSpaceToImageSpace(_event.mouse);
onMouseUp(_event.mouse, imageCoord);
break;
case Common::EVENT_KEYDOWN:
onKeyDown(_event.kbd);
break;
case Common::EVENT_KEYUP:
onKeyUp(_event.kbd);
break;
default:
break;
}
_controlEvents.pop_front();
}
for (auto &control : (*_activeControls)) {
if (control->process(deltaTimeMillis))
break;
}
}
bool ScriptManager::checkPuzzleCriteria(Puzzle *puzzle, uint counter) {
// Check if the puzzle is already finished
// Also check that the puzzle isn't disabled
if (getStateValue(puzzle->key) == 1 || (getStateFlag(puzzle->key) & Puzzle::DISABLED))
return true;
// Check each Criteria
if (counter == 0 && (getStateFlag(puzzle->key) & Puzzle::DO_ME_NOW) == 0)
return true;
// WORKAROUNDS:
switch (_engine->getGameId()) {
case GID_NEMESIS:
switch (puzzle->key) {
case 16418:
// WORKAROUND for script bug in Zork Nemesis, room mc30 (Monastery Entry)
// Rumble sound effect should cease upon changing location to me10 (Hall of Masks),
// but this puzzle erroneously restarted it immediately after.
if(changingLocation())
return true;
break;
default:
break;
}
break;
default:
break;
}
bool criteriaMet = false;
for (auto &criteria : puzzle->criteriaList) {
criteriaMet = false;
for (auto &entry : criteria) {
// Get the value to compare against
int argumentValue;
if (entry.argumentIsAKey)
argumentValue = getStateValue(entry.argument);
else
argumentValue = entry.argument;
// Do the comparison
switch (entry.criteriaOperator) {
case Puzzle::EQUAL_TO:
criteriaMet = getStateValue(entry.key) == argumentValue;
break;
case Puzzle::NOT_EQUAL_TO:
criteriaMet = getStateValue(entry.key) != argumentValue;
break;
case Puzzle::GREATER_THAN:
criteriaMet = getStateValue(entry.key) > argumentValue;
break;
case Puzzle::LESS_THAN:
criteriaMet = getStateValue(entry.key) < argumentValue;
break;
default:
break;
}
// If one check returns false, don't keep checking
if (!criteriaMet)
break;
}
// If any of the Criteria are *fully* met, then execute the results
if (criteriaMet)
break;
}
// criteriaList can be empty. Aka, the puzzle should be executed immediately
if (puzzle->criteriaList.empty() || criteriaMet) {
debugC(1, kDebugPuzzle, "Puzzle %u criteria passed. Executing its ResultActions", puzzle->key);
// Set the puzzle as completed
setStateValue(puzzle->key, 1);
for (auto &result : puzzle->resultActions) {
if (!result->execute())
return false;
}
}
return true;
}
void ScriptManager::cleanStateTable() {
for (auto entry = _globalState.begin(); entry != _globalState.end(); ++entry) {
// If the value is equal to zero, we can purge it since getStateValue()
// will return zero if _globalState doesn't contain a key
if (entry->_value == 0) {
// Remove the node
_globalState.erase(entry);
}
}
}
void ScriptManager::cleanScriptScope(ScriptScope &scope) {
scope.privQueueOne.clear();
scope.privQueueTwo.clear();
scope.scopeQueue = &scope.privQueueOne;
scope.execQueue = &scope.privQueueTwo;
for (auto &puzzle : scope.puzzles)
delete(puzzle);
scope.puzzles.clear();
for (auto &control : scope.controls)
delete(control);
scope.controls.clear();
scope.procCount = 0;
}
int ScriptManager::getStateValue(uint32 key) {
if (_globalState.contains(key))
return _globalState[key];
else
return 0;
}
void ScriptManager::queuePuzzles(uint32 key) {
if (_referenceTable.contains(key)) {
Common::Array<PuzzleRef> *arr = &_referenceTable[key];
for (int32 i = arr->size() - 1; i >= 0; i--) {
if (!(*arr)[i].puz->addedBySetState) {
(*arr)[i].scope->scopeQueue->push_back((*arr)[i].puz);
(*arr)[i].puz->addedBySetState = true;
}
}
}
}
void ScriptManager::setStateValue(uint32 key, int value) {
if (value == 0)
_globalState.erase(key);
else
_globalState[key] = value;
queuePuzzles(key);
}
void ScriptManager::setStateValueSilent(uint32 key, int value) {
if (value == 0)
_globalState.erase(key);
else
_globalState[key] = value;
}
uint ScriptManager::getStateFlag(uint32 key) {
if (_globalStateFlags.contains(key))
return _globalStateFlags[key];
else
return 0;
}
void ScriptManager::setStateFlag(uint32 key, uint value) {
queuePuzzles(key);
_globalStateFlags[key] |= value;
}
void ScriptManager::setStateFlagSilent(uint32 key, uint value) {
if (value == 0)
_globalStateFlags.erase(key);
else
_globalStateFlags[key] = value;
}
void ScriptManager::unsetStateFlag(uint32 key, uint value) {
queuePuzzles(key);
if (_globalStateFlags.contains(key)) {
_globalStateFlags[key] &= ~value;
if (_globalStateFlags[key] == 0)
_globalStateFlags.erase(key);
}
}
Control *ScriptManager::getControl(uint32 key) {
for (auto &control : (*_activeControls)) {
if (control->getKey() == key)
return control;
}
return nullptr;
}
void ScriptManager::focusControl(uint32 key) {
if (!_activeControls)
return;
if (_currentlyFocusedControl == key)
return;
for (auto &control : (*_activeControls)) {
uint32 controlKey = control->getKey();
if (controlKey == key)
control->focus();
else if (controlKey == _currentlyFocusedControl)
control->unfocus();
}
_currentlyFocusedControl = key;
}
void ScriptManager::setFocusControlKey(uint32 key) {
_currentlyFocusedControl = key;
}
void ScriptManager::addSideFX(ScriptingEffect *fx) {
_activeSideFx.push_back(fx);
}
ScriptingEffect *ScriptManager::getSideFX(uint32 key) {
for (auto &fx : _activeSideFx) {
if (fx->getKey() == key)
return fx;
}
return nullptr;
}
void ScriptManager::deleteSideFx(uint32 key) {
for (auto fx = _activeSideFx.begin(); fx != _activeSideFx.end(); ++fx) {
if ((*fx)->getKey() == key) {
delete(*fx);
_activeSideFx.erase(fx);
break;
}
}
}
void ScriptManager::stopSideFx(uint32 key) {
for (auto fx = _activeSideFx.begin(); fx != _activeSideFx.end(); ++fx) {
if ((*fx)->getKey() == key) {
bool ret = (*fx)->stop();
if (ret) {
delete(*fx);
_activeSideFx.erase(fx);
}
break;
}
}
}
void ScriptManager::killSideFx(uint32 key) {
for (auto fx = _activeSideFx.begin(); fx != _activeSideFx.end(); ++fx) {
if ((*fx)->getKey() == key) {
(*fx)->kill();
delete(*fx);
_activeSideFx.erase(fx);
break;
}
}
}
void ScriptManager::killSideFxType(ScriptingEffect::ScriptingEffectType type) {
for (auto fx = _activeSideFx.begin(); fx != _activeSideFx.end();) {
if ((*fx)->getType() & type) {
(*fx)->kill();
delete(*fx);
fx = _activeSideFx.erase(fx);
} else {
++fx;
}
}
}
void ScriptManager::onMouseDown(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos) {
debugC(1, kDebugMouse, "Mouse screen coordinates: %d, %d, background/script coordinates: %d, %d", screenSpacePos.x, screenSpacePos.y, backgroundImageSpacePos.x, backgroundImageSpacePos.y);
if (!_activeControls)
return;
for (auto control = _activeControls->reverse_begin(); control != _activeControls->end(); control--) {
if ((*control)->onMouseDown(screenSpacePos, backgroundImageSpacePos))
return;
}
}
void ScriptManager::onMouseUp(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos) {
if (!_activeControls)
return;
for (auto control = _activeControls->reverse_begin(); control != _activeControls->end(); control--) {
if ((*control)->onMouseUp(screenSpacePos, backgroundImageSpacePos))
return;
}
}
bool ScriptManager::onMouseMove(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos) {
if (!_activeControls)
return false;
for (auto control = _activeControls->reverse_begin(); control != _activeControls->end(); control--) {
if ((*control)->onMouseMove(screenSpacePos, backgroundImageSpacePos))
return true;
}
return false;
}
void ScriptManager::onKeyDown(Common::KeyState keyState) {
if (!_activeControls)
return;
for (auto &control : (*_activeControls)) {
if (control->onKeyDown(keyState))
return;
}
}
void ScriptManager::onKeyUp(Common::KeyState keyState) {
if (!_activeControls)
return;
for (auto &control : (*_activeControls)) {
if (control->onKeyUp(keyState))
return;
}
}
void ScriptManager::changeLocation(const Location &_newLocation) {
changeLocation(_newLocation.world, _newLocation.room, _newLocation.node, _newLocation.view, _newLocation.offset);
}
void ScriptManager::changeLocation(char world, char room, char node, char view, uint32 offset) {
debugC(1, kDebugScript, "\tPreparing to change location");
_nextLocation.world = world;
_nextLocation.room = room;
_nextLocation.node = node;
_nextLocation.view = view;
_nextLocation.offset = offset;
// If next location is 0000, return to the previous location.
if (_nextLocation == "0000") {
if (getStateValue(StateKey_World) != 'g' || getStateValue(StateKey_Room) != 'j') {
_nextLocation.world = getStateValue(StateKey_LastWorld);
_nextLocation.room = getStateValue(StateKey_LastRoom);
_nextLocation.node = getStateValue(StateKey_LastNode);
_nextLocation.view = getStateValue(StateKey_LastView);
_nextLocation.offset = getStateValue(StateKey_LastViewPos);
} else {
_nextLocation.world = getStateValue(StateKey_Menu_LastWorld);
_nextLocation.room = getStateValue(StateKey_Menu_LastRoom);
_nextLocation.node = getStateValue(StateKey_Menu_LastNode);
_nextLocation.view = getStateValue(StateKey_Menu_LastView);
_nextLocation.offset = getStateValue(StateKey_Menu_LastViewPos);
}
}
}
void ScriptManager::ChangeLocationReal(bool isLoading) {
assert(_nextLocation.world != 0);
debugC(1, kDebugScript, "\tChanging location to: World %c, Room %c, Node %c, View %c, Offset %u", _nextLocation.world, _nextLocation.room, _nextLocation.node, _nextLocation.view, _nextLocation.offset);
const bool enteringMenu = (_nextLocation.world == 'g' && _nextLocation.room == 'j');
const bool leavingMenu = (_currentLocation.world == 'g' && _currentLocation.room == 'j');
const bool isSaveScreen = (enteringMenu && _nextLocation.node == 's' && _nextLocation.view == 'e');
const bool isRestoreScreen = (enteringMenu && _nextLocation.node == 'r' && _nextLocation.view == 'e');
if (enteringMenu && !ConfMan.getBool("originalsaveload")) {
if (isSaveScreen || isRestoreScreen) {
// Hook up the ScummVM save/restore dialog
bool gameSavedOrLoaded = _engine->getSaveManager()->scummVMSaveLoadDialog(isSaveScreen);
if (!gameSavedOrLoaded || isSaveScreen) {
// Reload the current room
_nextLocation.world = _currentLocation.world;
_nextLocation.room = _currentLocation.room;
_nextLocation.node = _currentLocation.node;
_nextLocation.view = _currentLocation.view;
_nextLocation.offset = _currentLocation.offset;
return;
} else {
_currentLocation.world = 'g';
_currentLocation.room = '0';
_currentLocation.node = '0';
_currentLocation.view = '0';
_currentLocation.offset = 0;
}
}
}
_engine->setRenderDelay(2); // Necessary to ensure proper redraw in certain locations, in particular the infinite corridor in Zork Grand Inquisitor (room th20)
if (!leavingMenu) {
if (!isLoading && !enteringMenu) {
setStateValue(StateKey_LastWorld, getStateValue(StateKey_World));
setStateValue(StateKey_LastRoom, getStateValue(StateKey_Room));
setStateValue(StateKey_LastNode, getStateValue(StateKey_Node));
setStateValue(StateKey_LastView, getStateValue(StateKey_View));
setStateValue(StateKey_LastViewPos, getStateValue(StateKey_ViewPos));
} else {
setStateValue(StateKey_Menu_LastWorld, getStateValue(StateKey_World));
setStateValue(StateKey_Menu_LastRoom, getStateValue(StateKey_Room));
setStateValue(StateKey_Menu_LastNode, getStateValue(StateKey_Node));
setStateValue(StateKey_Menu_LastView, getStateValue(StateKey_View));
setStateValue(StateKey_Menu_LastViewPos, getStateValue(StateKey_ViewPos));
}
}
if (enteringMenu) {
if (isSaveScreen && !leavingMenu)
_engine->getSaveManager()->prepareSaveBuffer();
}
else if (leavingMenu)
_engine->getSaveManager()->flushSaveBuffer();
setStateValue(StateKey_World, _nextLocation.world);
setStateValue(StateKey_Room, _nextLocation.room);
setStateValue(StateKey_Node, _nextLocation.node);
setStateValue(StateKey_View, _nextLocation.view);
setStateValue(StateKey_ViewPos, _nextLocation.offset);
_referenceTable.clear();
addPuzzlesToReferenceTable(_universe);
_engine->getMenuManager()->setEnable(0xFFFF);
TransitionLevel level = NONE;
Common::Path filePath;
if (_nextLocation.world != _currentLocation.world)
level = WORLD;
else if (_nextLocation.room != _currentLocation.room)
level = ROOM;
else if (_nextLocation.node != _currentLocation.node)
level = NODE;
else if (_nextLocation.view != _currentLocation.view)
level = VIEW;
switch (level) {
case WORLD:
cleanScriptScope(_world);
filePath = Common::Path(Common::String::format("%c.scr", _nextLocation.world));
parseScrFile(filePath, _world);
// fall through
case ROOM:
cleanScriptScope(_room);
filePath = Common::Path(Common::String::format("%c%c.scr", _nextLocation.world, _nextLocation.room));
parseScrFile(filePath, _room);
// fall through
case NODE:
case VIEW:
cleanScriptScope(_nodeview);
filePath = Common::Path(Common::String::format("%c%c%c%c.scr", _nextLocation.world, _nextLocation.room, _nextLocation.node, _nextLocation.view));
parseScrFile(filePath, _nodeview);
addPuzzlesToReferenceTable(_world);
addPuzzlesToReferenceTable(_room);
addPuzzlesToReferenceTable(_nodeview);
break;
case NONE:
default:
break;
}
_activeControls = &_nodeview.controls;
// Revert to the idle cursor
_engine->getCursorManager()->changeCursor(CursorIndex_Idle);
// Change the background position
_engine->getRenderManager()->setBackgroundPosition(_nextLocation.offset);
if (_currentLocation == "0000")
level = WORLD;
if (level != NONE)
_currentLocation = _nextLocation;
switch (level) {
case WORLD:
execScope(_world);
// fall through
case ROOM:
execScope(_room);
// fall through
case NODE:
case VIEW:
execScope(_nodeview);
break;
case NONE:
default:
break;
}
_engine->getRenderManager()->checkBorders();
_engine->onMouseMove(); // Trigger a pseudo mouse movement to change cursor if we enter the new location with it already over a hotspot
debugC(1, kDebugScript, "\tLocation change complete");
}
void ScriptManager::serialize(Common::WriteStream *stream) {
stream->writeUint32BE(MKTAG('Z', 'N', 'S', 'G'));
stream->writeUint32LE(4);
stream->writeUint32LE(0);
stream->writeUint32BE(MKTAG('L', 'O', 'C', ' '));
stream->writeUint32LE(8);
stream->writeByte(getStateValue(StateKey_World));
stream->writeByte(getStateValue(StateKey_Room));
stream->writeByte(getStateValue(StateKey_Node));
stream->writeByte(getStateValue(StateKey_View));
stream->writeUint32LE(getStateValue(StateKey_ViewPos));
for (auto &fx : _activeSideFx)
fx->serialize(stream);
stream->writeUint32BE(MKTAG('F', 'L', 'A', 'G'));
int32 slots = _engine->getGameId() == GID_NEMESIS ? 31000 : 21000;
// Original games use key values up to 29500 and 19737, respectively
// Values 30001~31000 and 20001~21000 are now set aside for auxiliary scripting to add extra directional audio effects.
stream->writeUint32LE(slots * 2);
for (int32 i = 0; i < slots; i++)
stream->writeUint16LE(getStateFlag(i));
stream->writeUint32BE(MKTAG('P', 'U', 'Z', 'Z'));
stream->writeUint32LE(slots * 2);
for (int32 i = 0; i < slots; i++)
stream->writeSint16LE(getStateValue(i));
}
void ScriptManager::deserialize(Common::SeekableReadStream *stream) {
// Clear out the current table values
_globalState.clear();
_globalStateFlags.clear();
cleanScriptScope(_nodeview);
cleanScriptScope(_room);
cleanScriptScope(_world);
_currentLocation.node = 0;
_currentLocation.world = 0;
_currentLocation.room = 0;
_currentLocation.view = 0;
for (auto &fx : _activeSideFx)
delete fx;
_activeSideFx.clear();
_referenceTable.clear();
if (stream->readUint32BE() != MKTAG('Z', 'N', 'S', 'G') || stream->readUint32LE() != 4) {
changeLocation('g', 'a', 'r', 'y', 0);
return;
}
stream->seek(4, SEEK_CUR);
if (stream->readUint32BE() != MKTAG('L', 'O', 'C', ' ') || stream->readUint32LE() != 8) {
changeLocation('g', 'a', 'r', 'y', 0);
return;
}
Location nextLocation;
nextLocation.world = stream->readByte();
nextLocation.room = stream->readByte();
nextLocation.node = stream->readByte();
nextLocation.view = stream->readByte();
nextLocation.offset = stream->readUint32LE() & 0x0000FFFF;
while (stream->pos() < stream->size()) {
uint32 tag = stream->readUint32BE();
uint32 tagSize = stream->readUint32LE();
switch (tag) {
case MKTAG('T', 'I', 'M', 'R'): {
uint32 key = stream->readUint32LE();
uint32 time = stream->readUint32LE();
if (_engine->getGameId() == GID_GRANDINQUISITOR)
time /= 100;
else if (_engine->getGameId() == GID_NEMESIS)
time /= 1000;
addSideFX(new TimerNode(_engine, key, time));
}
break;
case MKTAG('F', 'L', 'A', 'G'):
for (uint32 i = 0; i < tagSize / 2; i++)
setStateFlagSilent(i, stream->readUint16LE());
break;
case MKTAG('P', 'U', 'Z', 'Z'):
for (uint32 i = 0; i < tagSize / 2; i++)
setStateValueSilent(i, stream->readUint16LE());
break;
default:
stream->seek(tagSize, SEEK_CUR);
}
}
_nextLocation = nextLocation;
ChangeLocationReal(true);
_engine->setRenderDelay(10);
setStateValue(StateKey_RestoreFlag, 1);
_engine->loadSettings();
}
Location ScriptManager::getCurrentLocation() const {
Location location = _currentLocation;
location.offset = _engine->getRenderManager()->getCurrentBackgroundOffset();
return location;
}
Location ScriptManager::getLastLocation() {
Location location;
location.world = getStateValue(StateKey_LastWorld);
location.room = getStateValue(StateKey_LastRoom);
location.node = getStateValue(StateKey_LastNode);
location.view = getStateValue(StateKey_LastView);
location.offset = getStateValue(StateKey_LastViewPos);
return location;
}
Location ScriptManager::getLastMenuLocation() {
Location location;
location.world = getStateValue(StateKey_Menu_LastWorld);
location.room = getStateValue(StateKey_Menu_LastRoom);
location.node = getStateValue(StateKey_Menu_LastNode);
location.view = getStateValue(StateKey_Menu_LastView);
location.offset = getStateValue(StateKey_Menu_LastViewPos);
return location;
}
void ScriptManager::addEvent(Common::Event event) {
_controlEvents.push_back(event);
}
void ScriptManager::flushEvent(Common::EventType type) {
auto event = _controlEvents.begin();
while (event != _controlEvents.end()) {
if ((*event).type == type)
event = _controlEvents.erase(event);
else
event++;
}
}
void ScriptManager::trimCommentsAndWhiteSpace(Common::String *string) const {
for (int i = string->size() - 1; i >= 0; i--) {
if ((*string)[i] == '#')
string->erase(i);
}
string->trim();
}
ValueSlot::ValueSlot(ScriptManager *scriptManager, const char *slotValue):
_scriptManager(scriptManager) {
_value = 0;
_slot = false;
const char *isSlot = strstr(slotValue, "[");
if (isSlot) {
_slot = true;
_value = atoi(isSlot + 1);
} else {
_slot = false;
_value = atoi(slotValue);
}
}
int16 ValueSlot::getValue() {
if (_slot) {
if (_value >= 0)
return _scriptManager->getStateValue(_value);
else
return 0;
}
else
return _value;
}
} // End of namespace ZVision

View File

@@ -0,0 +1,412 @@
/* 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 ZVISION_SCRIPT_MANAGER_H
#define ZVISION_SCRIPT_MANAGER_H
#include "common/events.h"
#include "common/hashmap.h"
#include "common/queue.h"
#include "zvision/scripting/control.h"
#include "zvision/scripting/puzzle.h"
#include "zvision/scripting/scripting_effect.h"
namespace Common {
class String;
class SeekableReadStream;
}
namespace ZVision {
class ZVision;
enum StateKey {
StateKey_World = 3,
StateKey_Room = 4,
StateKey_Node = 5,
StateKey_View = 6,
StateKey_ViewPos = 7,
StateKey_KeyPress = 8,
StateKey_InventoryItem = 9,
StateKey_LMouse = 10,
StateKey_NotSet = 11, // This key doesn't set
StateKey_Rounds = 12,
StateKey_Venus = 13,
StateKey_RMouse = 18,
StateKey_MenuState = 19,
StateKey_RestoreFlag = 20,
StateKey_Quitting = 39,
StateKey_LastWorld = 40,
StateKey_LastRoom = 41,
StateKey_LastNode = 42,
StateKey_LastView = 43,
StateKey_LastViewPos = 44,
StateKey_Menu_LastWorld = 45,
StateKey_Menu_LastRoom = 46,
StateKey_Menu_LastNode = 47,
StateKey_Menu_LastView = 48,
StateKey_Menu_LastViewPos = 49,
StateKey_KbdRotateSpeed = 50,
StateKey_Subtitles = 51,
StateKey_StreamSkipKey = 52,
StateKey_RotateSpeed = 53,
StateKey_Volume = 56,
StateKey_Qsound = 57,
StateKey_VenusEnable = 58,
StateKey_HighQuality = 59,
StateKey_VideoLineSkip = 65,
StateKey_Platform = 66, // 0 = Windows, !0 = DOS
StateKey_InstallLevel = 67,
StateKey_CountryCode = 68,
StateKey_CPU = 69, // !1 = 486, 1 = i586/Pentium
StateKey_MovieCursor = 70,
StateKey_NoTurnAnim = 71,
StateKey_WIN958 = 72, // 0 = high system RAM, !0 = low system RAM (<8MB)
StateKey_ShowErrorDlg = 73,
StateKey_DebugCheats = 74,
StateKey_JapanFonts = 75,
StateKey_ExecScopeStyle = 76, // 0 = ZGI, 1 = Nemesis
StateKey_Brightness = 77,
StateKey_MPEGMovies = 78,
StateKey_EF9_R = 91,
StateKey_EF9_G = 92,
StateKey_EF9_B = 93,
StateKey_EF9_Speed = 94,
StateKey_Inv_Cnt_Slot = 100,
StateKey_Inv_1_Slot = 101,
StateKey_Inv_49_Slot = 149,
// ZGI only
StateKey_Inv_TotalSlots = 150,
StateKey_Inv_StartSlot = 151,
StateKey_Spell_1 = 191,
StateKey_Active_Spell = 205,
StateKey_Reversed_Spellbooc = 206
};
struct Location {
Location() : world('g'), room('a'), node('r'), view('y'), offset(0) {}
char world;
char room;
char node;
char view;
uint32 offset;
};
inline bool operator==(const Location& lhs, const Location& rhs) {
return (
lhs.world == rhs.world &&
lhs.room == rhs.room &&
lhs.node == rhs.node &&
lhs.view == rhs.view
);
}
inline bool operator==(const Location& lhs, const char* rhs) {
Common::String lhsStr = Common::String::format("%c%c%c%c", lhs.world, lhs.room, lhs.node, lhs.view);
return lhsStr == rhs;
}
inline bool operator!=(const Location& lhs, const Location& rhs) {
return !(lhs == rhs);
}
inline bool operator!=(const Location& lhs, const char* rhs) {
return !(lhs == rhs);
}
typedef Common::List<Puzzle *> PuzzleList;
typedef Common::Queue<Puzzle *> PuzzleQueue;
typedef Common::List<Control *> ControlList;
typedef Common::HashMap<uint32, int32> StateMap;
typedef Common::List<ScriptingEffect *> SideFXList;
typedef Common::List<Common::Event> EventList;
class ScriptManager {
public:
ScriptManager(ZVision *engine);
~ScriptManager();
private:
ZVision *_engine;
struct ScriptScope {
uint32 procCount;
PuzzleList *scopeQueue; // For adding puzzles to queue
PuzzleList *execQueue; // Switch to it when execute
PuzzleList privQueueOne;
PuzzleList privQueueTwo;
PuzzleList puzzles;
ControlList controls;
};
struct PuzzleRef {
Puzzle *puz;
ScriptScope *scope;
};
typedef Common::HashMap<uint32, Common::Array<PuzzleRef> > PuzzleMap;
/**
* Holds the global state variable. Do NOT directly modify this. Use the accessors and
* mutators getStateValue() and setStateValue(). This ensures that Puzzles that reference a
* particular state key are checked after the key is modified.
*/
StateMap _globalState;
/** Holds execute flags */
StateMap _globalStateFlags;
/** References _globalState keys to Puzzles */
PuzzleMap _referenceTable;
/** Holds the currently active controls */
ControlList *_activeControls;
EventList _controlEvents;
ScriptScope _universe;
ScriptScope _world;
ScriptScope _room;
ScriptScope _nodeview;
/** Holds the currently active timers, musics, other */
SideFXList _activeSideFx;
Location _currentLocation;
Location _nextLocation;
const uint8 _changeLocationExtraCycles = 16;
uint32 _currentlyFocusedControl;
public:
enum TransitionLevel {
NONE,
VIEW,
NODE,
ROOM,
WORLD
};
void initialize(bool restarted = false);
void process(uint deltaTimeMillis);
void queuePuzzles(uint32 key);
int getStateValue(uint32 key);
void setStateValue(uint32 key, int value);
uint getStateFlag(uint32 key);
void setStateFlag(uint32 key, uint value);
void unsetStateFlag(uint32 key, uint value);
void addControl(Control *control);
Control *getControl(uint32 key);
void enableControl(uint32 key);
void disableControl(uint32 key);
void focusControl(uint32 key);
// Only change focus control without call focus/unfocus.
void setFocusControlKey(uint32 key);
void addSideFX(ScriptingEffect *fx);
ScriptingEffect *getSideFX(uint32 key);
void deleteSideFx(uint32 key);
void stopSideFx(uint32 key);
void killSideFx(uint32 key);
void killSideFxType(ScriptingEffect::ScriptingEffectType type);
void addEvent(Common::Event);
void flushEvent(Common::EventType type);
/**
* Called when LeftMouse is pushed.
*
* @param screenSpacePos The position of the mouse in screen space
* @param backgroundImageSpacePos The position of the mouse in background image space
*/
void onMouseDown(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos);
/**
* Called when LeftMouse is lifted.
*
* @param screenSpacePos The position of the mouse in screen space
* @param backgroundImageSpacePos The position of the mouse in background image space
*/
void onMouseUp(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos);
/**
* Called on every MouseMove.
*
* @param screenSpacePos The position of the mouse in screen space
* @param backgroundImageSpacePos The position of the mouse in background image space
* @return Was the cursor changed?
*/
bool onMouseMove(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos);
/**
* Called when a key is pressed.
*
* @param keycode The key that was pressed
*/
void onKeyDown(Common::KeyState keyState);
/**
* Called when a key is released.
*
* @param keycode The key that was pressed
*/
void onKeyUp(Common::KeyState keyState);
/** Mark next location */
void changeLocation(char world, char room, char node, char view, uint32 offset);
void changeLocation(const Location &_newLocation);
bool changingLocation() const;
void serialize(Common::WriteStream *stream);
void deserialize(Common::SeekableReadStream *stream);
Location getCurrentLocation() const;
Location getLastLocation();
Location getLastMenuLocation();
/**
* Removes any line comments using '#' as a sequence start.
* Then removes any trailing and leading 'whitespace' using String::trim()
* Note: String::trim uses isspace() to determine what is whitespace and what is not.
*
* @param string The string to modify. It is modified in place
*/
void trimCommentsAndWhiteSpace(Common::String *string) const;
private:
void referenceTableAddPuzzle(uint32 key, PuzzleRef ref);
void addPuzzlesToReferenceTable(ScriptScope &scope);
void updateNodes(uint deltaTimeMillis);
void updateControls(uint deltaTimeMillis);
/**
* Check a puzzle's criteria; execute its actions and set its state to 1 if these critera are met.
* Will not check or execute if:
* Puzzle is disabled
* Puzzle has already triggered and has a state value of 1
* procCount has reached zero AND do_me_now is not set
*
* @param puzzle puzzle to check
* @param counter procCount from this puzzle's scope container
* Returns true if OK to keep calling this function this frame; false if we should break and start next frame (only used by RestoreGame action)
*/
bool checkPuzzleCriteria(Puzzle *puzzle, uint counter);
void cleanStateTable(); // Set all global state values to zero
void cleanScriptScope(ScriptScope &scope); // Resets everything in this scope, all lists empty, procCount to zero.
bool execScope(ScriptScope &scope);
/** Perform change location */
void ChangeLocationReal(bool isLoading);
int8 inventoryGetCount();
void inventorySetCount(int8 cnt);
int16 inventoryGetItem(int8 id);
void inventorySetItem(int8 id, int16 item);
void setStateFlagSilent(uint32 key, uint value);
void setStateValueSilent(uint32 key, int value);
public:
void inventoryAdd(int16 item);
void inventoryDrop(int16 item);
void inventoryCycle();
private:
/**
* Parses a script file into triggers and events
*
* @param fileName Name of the .scr file
* @param isGlobal Are the puzzles included in the file global (true). AKA, the won't be purged during location changes
*/
void parseScrFile(const Common::Path &fileName, ScriptScope &scope);
/**
* Parses the stream into a Puzzle object
* Helper method for parseScrFile.
*
* @param puzzle The object to store what is parsed
* @param stream Scr file stream
*/
void parsePuzzle(Puzzle *puzzle, Common::SeekableReadStream &stream);
/**
* Parses the stream into a Criteria object
* Helper method for parsePuzzle.
*
* @param criteria Pointer to the Criteria object to fill
* @param stream Scr file stream
* @param key Puzzle key (for workarounds)
* @return Whether any criteria were read
*/
bool parseCriteria(Common::SeekableReadStream &stream, Common::List<Common::List<Puzzle::CriteriaEntry> > &criteriaList, uint32 key) const;
/**
* Parses the stream into a ResultAction objects
* Helper method for parsePuzzle.
*
* @param stream Scr file stream
* @param actionList The list where the results will be added
* @param key Puzzle key (for workarounds)
* @return Created Results object
*/
void parseResults(Common::SeekableReadStream &stream, Common::List<ResultAction *> &actionList, uint32 key) const;
/**
* Helper method for parsePuzzle. Parses the stream into a bitwise or of the StateFlags enum
*
* @param stream Scr file stream
* @return Bitwise OR of all the flags set within the puzzle
*/
uint parseFlags(Common::SeekableReadStream &stream) const;
/**
* Helper method for parseScrFile. Parses the stream into a Control object
*
* @param line The line initially read
* @param stream Scr file stream
*/
Control *parseControl(Common::String &line, Common::SeekableReadStream &stream);
};
/**
* Instances of this polymorphic class function either as a store of a single value, or as a "slot" that returns a StateValue
*
* @param line The line initially read
* @param slotValue A text string containing a number, which may be enclosed within square braces.
* If square braces are not present, getValue() will return slotValue.
* If square braces are present, getValue() will return the StateValue to which slotValue is the key.
*
* Once instantiated, the value and nature of slotValue may not be changed.
*/
class ValueSlot {
public:
ValueSlot(ScriptManager *scriptManager, const char *slotValue);
int16 getValue();
private:
int16 _value;
bool _slot;
ScriptManager *_scriptManager;
};
} // End of namespace ZVision
#endif

View File

@@ -0,0 +1,123 @@
/* 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 SCRIPTING_EFFECT_H_INCLUDED
#define SCRIPTING_EFFECT_H_INCLUDED
namespace Common {
class SeekableReadStream;
struct Point;
class WriteStream;
}
namespace ZVision {
class ZVision;
/**
* The base class that represents effects created from Actions.
* This class is virtual.
*
* Detailed Description:
* A scene has Controls. By interacting with the controls, the user
* causes Actions to execute. Certain Actions create 'effects', for
* example, a sound or an animation. This is the base class for
* those effects.
*/
class ScriptingEffect {
public:
enum ScriptingEffectType {
SCRIPTING_EFFECT_ANIM = 1,
SCRIPTING_EFFECT_AUDIO = 2,
SCRIPTING_EFFECT_DISTORT = 4,
SCRIPTING_EFFECT_PANTRACK = 8,
SCRIPTING_EFFECT_REGION = 16,
SCRIPTING_EFFECT_TIMER = 32,
SCRIPTING_EFFECT_TTYTXT = 64,
SCRIPTING_EFFECT_UNKNOWN = 128,
SCRIPTING_EFFECT_ALL = 255
};
ScriptingEffect() : _engine(0), _key(0), _type(SCRIPTING_EFFECT_UNKNOWN) {}
ScriptingEffect(ZVision *engine, uint32 key, ScriptingEffectType type) : _engine(engine), _key(key), _type(type) {}
virtual ~ScriptingEffect() {}
uint32 getKey() {
return _key;
}
ScriptingEffectType getType() {
return _type;
}
virtual bool process(uint32 deltaTimeInMillis) {
return false;
}
/**
* Serialize a SideFX for save game use. This should only be used if a SideFX needs
* to save values that would be different from initialization. AKA a TimerNode needs to
* store the amount of time left on the timer. Any Controls overriding this *MUST* write
* their key as the first data outputted. The default implementation is NOP.
*
* NOTE: If this method is overridden, you MUST also override deserialize()
* and needsSerialization()
*
* @param stream Stream to write any needed data to
*/
virtual void serialize(Common::WriteStream *stream) {}
/**
* De-serialize data from a save game stream. This should only be implemented if the
* SideFX also implements serialize(). The calling method assumes the size of the
* data read from the stream exactly equals that written in serialize(). The default
* implementation is NOP.
*
* NOTE: If this method is overridden, you MUST also override serialize()
* and needsSerialization()
*
* @param stream Save game file stream
*/
virtual void deserialize(Common::SeekableReadStream *stream) {}
/**
* If a SideFX overrides serialize() and deserialize(), this should return true
*
* @return Does the SideFX need save game serialization?
*/
virtual inline bool needsSerialization() {
return false;
}
virtual bool stop() {
return true;
}
virtual void kill() {}
protected:
ZVision *_engine;
uint32 _key;
ScriptingEffectType _type;
// Static member functions
public:
};
} // End of namespace ZVision
#endif // SCRIPTING_EFFECT_H_INCLUDED

View File

@@ -0,0 +1,116 @@
/* 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 "audio/mididrv.h"
#include "common/debug.h"
#include "common/scummsys.h"
#include "common/textconsole.h"
#include "zvision/detection.h"
#include "zvision/sound/midi.h"
namespace ZVision {
MidiManager::MidiManager() {
MidiDriver::DeviceHandle dev = MidiDriver::detectDevice(MDT_MIDI | MDT_ADLIB);
_driver = MidiDriver::createMidi(dev);
if (_driver->open()) {
warning("Can't open MIDI, no MIDI output!");
_available = false;
} else {
Common::String driverName = MidiDriver::getDeviceString(dev, MidiDriver::DeviceStringType::kDriverName);
Common::String deviceName = MidiDriver::getDeviceString(dev, MidiDriver::DeviceStringType::kDeviceName);
_mt32 = MidiDriver::getMusicType(dev) == MT_MT32;
debugC(1, kDebugSound, "MIDI opened, driver type: %s, device name: %s", driverName.c_str(), deviceName.c_str());
_available = true;
_maxChannels = _driver->MIDI_CHANNEL_COUNT;
}
}
MidiManager::~MidiManager() {
stop();
_driver->close();
delete _driver;
}
void MidiManager::send(uint8 status, uint8 data1, uint8 data2) {
assert(status & 0x80 && "Malformed MIDI status byte");
assert(!(data1 & 0x80) && "Malformed MIDI data byte 1");
assert(!(data2 & 0x80) && "Malformed MIDI data byte 2");
_driver->send(status | (data1 << 8) | (data2 << 16));
}
void MidiManager::stop() {
for (uint8 i = 0; i < 16; i++)
noteOff(i);
}
void MidiManager::noteOn(uint8 channel, uint8 note, uint8 velocity) {
assert(channel <= 15);
_activeChannels[channel].playing = true;
_activeChannels[channel].note = note;
send(0x90 | channel, note, velocity);
debugC(1, kDebugSound, "MIDI note on, channel %d, note %d, velocity %d", channel, note, velocity);
}
void MidiManager::noteOff(uint8 channel) {
assert(channel <= 15);
if (_activeChannels[channel].playing) {
_activeChannels[channel].playing = false;
send(0x80 | channel, _activeChannels[channel].note);
}
}
int8 MidiManager::getFreeChannel() {
uint8 start = _mt32 ? 1 : 0; // MT-32 can be used for MIDI, but does not play anything on MIDI channel 0
for (uint8 i = start; i < 16; i++)
if (!_activeChannels[i].playing)
return i;
return -1;
}
void MidiManager::setVolume(uint8 channel, uint8 volume) {
assert(channel <= 15);
debugC(1, kDebugSound, "MIDI volume out %d", volume >> 1);
send(0xB0 | channel, 0x07, volume >> 1);
}
void MidiManager::setBalance(uint8 channel, int8 balance) {
assert(channel <= 15);
uint8 _balance = (uint8)(balance + 128);
debugC(1, kDebugSound, "MIDI balance out %d", _balance >> 1);
send(0xB0 | channel, 0x08, _balance >> 1);
}
void MidiManager::setPan(uint8 channel, int8 pan) {
assert(channel <= 15);
uint8 _pan = (uint8)(pan + 128);
debugC(1, kDebugSound, "MIDI pan in %d, out %d", pan, _pan >> 1);
send(0xB0 | channel, 0x0A, _pan >> 1);
}
void MidiManager::setProgram(uint8 channel, uint8 prog) {
assert(channel <= 15);
send(0xC0 | channel, prog);
}
} // End of namespace ZVision

View File

@@ -0,0 +1,65 @@
/* 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 ZVISION_MIDI_H
#define ZVISION_MIDI_H
class MidiDriver;
namespace ZVision {
class MidiManager {
public:
MidiManager();
~MidiManager();
void stop();
void noteOn(uint8 channel, uint8 noteNumber, uint8 velocity);
void noteOff(uint8 channel);
void setVolume(uint8 channel, uint8 volume);
void setBalance(uint8 channel, int8 balance);
void setPan(uint8 channel, int8 pan);
void setProgram(uint8 channel, uint8 prog);
int8 getFreeChannel(); // Negative if none available
bool isAvailable() const {
return _available;
}
protected:
bool _available = false;
bool _mt32 = false;
struct chan {
bool playing;
uint8 note;
chan() : playing(false), note(0) {}
};
void send(uint8 status, uint8 data1 = 0x00, uint8 data2 = 0x00);
uint8 _startChannel = 0;
uint8 _maxChannels = 16;
MidiDriver *_driver;
chan _activeChannels[16];
};
}
#endif

View File

@@ -0,0 +1,291 @@
/* 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/debug.h"
#include "zvision/detection.h"
#include "zvision/scripting/script_manager.h"
#include "zvision/sound/volume_manager.h"
namespace ZVision {
// Power law with exponent 1.5.
static constexpr uint8 powerLaw[256] = {
0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 3, 3, 3, 4,
4, 4, 5, 5, 6, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11,
11, 12, 12, 13, 14, 14, 15, 15, 16, 16, 17, 18, 18, 19, 20, 20,
21, 21, 22, 23, 23, 24, 25, 26, 26, 27, 28, 28, 29, 30, 31, 31,
32, 33, 34, 34, 35, 36, 37, 37, 38, 39, 40, 41, 41, 42, 43, 44,
45, 46, 46, 47, 48, 49, 50, 51, 52, 53, 53, 54, 55, 56, 57, 58,
59, 60, 61, 62, 63, 64, 65, 65, 66, 67, 68, 69, 70, 71, 72, 73,
74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 88, 89, 90,
91, 92, 93, 94, 95, 96, 97, 98, 99,100,102,103,104,105,106,107,
108,109,110,112,113,114,115,116,117,119,120,121,122,123,124,126,
127,128,129,130,132,133,134,135,136,138,139,140,141,142,144,145,
146,147,149,150,151,152,154,155,156,158,159,160,161,163,164,165,
167,168,169,171,172,173,174,176,177,178,180,181,182,184,185,187,
188,189,191,192,193,195,196,197,199,200,202,203,204,206,207,209,
210,211,213,214,216,217,218,220,221,223,224,226,227,228,230,231,
233,234,236,237,239,240,242,243,245,246,248,249,251,252,254,255
};
static constexpr uint8 logPower[256] = {
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3,
3, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 6, 7, 7, 7, 8,
8, 9, 9, 10, 10, 11, 11, 12, 13, 13, 14, 15, 16, 17, 18, 19,
20, 21, 22, 23, 24, 26, 27, 29, 30, 32, 34, 36, 38, 40, 42, 45,
47, 50, 52, 55, 58, 62, 65, 69, 73, 77, 81, 86, 90, 96,101,107,
113,119,126,133,140,148,156,165,174,184,194,205,217,229,241,255
};
// */
static constexpr uint8 logAmplitude[256] = {
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5,
5, 5, 5, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 8, 8,
8, 8, 8, 9, 9, 9, 9, 10, 10, 10, 10, 11, 11, 11, 12, 12,
2, 13, 13, 13, 14, 14, 15, 15, 15, 16, 16, 17, 17, 18, 18, 19,
19, 20, 20, 21, 21, 22, 23, 23, 24, 24, 25, 26, 27, 27, 28, 29,
30, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 45,
46, 47, 48, 50, 51, 52, 54, 55, 57, 58, 60, 62, 63, 65, 67, 69,
71, 73, 75, 77, 79, 81, 83, 86, 88, 90, 93, 96, 98,101,104,107,
110,113,116,119,122,126,129,133,136,140,144,148,152,156,160,165,
169,174,179,184,189,194,200,205,211,217,222,229,235,241,248,255
};
/*/
// Old system; this is wrong, caused bug #7176; cloister fountain (value 50 in-game, 127/255) inaudible
// Using linear volume served as a temporary fix, but causes other sounds not to play at correct amplitudes (e.g. beehive, door singing in ZGI)
static constexpr uint8 logAmplitude[256] = {
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4,
4, 4, 4, 5, 5, 5, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7,
8, 8, 8, 9, 9, 9, 10, 10, 11, 11, 11, 12, 12, 13, 13, 14,
14, 15, 15, 16, 16, 17, 18, 18, 19, 20, 21, 21, 22, 23, 24, 25,
26, 27, 28, 29, 30, 31, 32, 33, 34, 36, 37, 38, 40, 41, 43, 45,
46, 48, 50, 52, 53, 55, 57, 60, 62, 64, 67, 69, 72, 74, 77, 80,
83, 86, 89, 92, 96, 99,103,107,111,115,119,123,128,133,137,143,
148,153,159,165,171,177,184,191,198,205,212,220,228,237,245,255
};
// */
/*
Estimated relative amplitude of a point sound source as it circles the listener's head from front to rear, due to ear pinna shape.
Maximum attenuation -5dB when fully to rear. Seems to give a reasonably realistic effect when tested on the Nemesis cloister fountain.
Should be applied AFTER volume profile is applied to script files.
Generating function:
for 0 < theta < 90, amp = 255;
for 90 < theta < 180, amp = 255*10^(1-(cos(2*(theta-90))/4))
where theta is the azimuth, in degrees, of the sound source relative to straight ahead of listener
Source: Own work; crude and naive model that is probably not remotely scientifically accurate, but good enough for a 30-year-old game.
*/
static constexpr uint8 directionalAmplitude[181] = {
255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
255,255,255,255,255,255,255,255,255,255,255,255,255,254,254,253,
252,251,249,248,246,245,243,241,238,236,234,231,228,226,223,220,
217,214,211,208,204,201,198,195,191,188,185,181,178,175,171,168,
165,162,158,155,152,149,146,143,141,138,135,132,130,127,125,122,
120,118,116,113,111,109,108,106,104,102,101, 99, 98, 96, 95, 93,
92, 91, 90, 89, 88, 87, 86, 85, 85, 84, 83, 83, 82, 82, 82, 81,
81, 81, 81, 81, 81
};
VolumeManager::VolumeManager(ZVision *engine, volumeScaling mode) :
_mode(mode) {
}
uint8 VolumeManager::convert(uint8 inputValue) {
return convert(inputValue, _mode);
}
uint8 VolumeManager::convert(uint8 inputValue, Math::Angle azimuth, uint8 directionality) {
return convert(inputValue, _mode, azimuth, directionality);
}
uint8 VolumeManager::convert(uint8 inputValue, volumeScaling &mode, Math::Angle azimuth, uint8 directionality) {
uint8 index = abs(round(azimuth.getDegrees(-180)));
uint32 output = convert(inputValue, mode);
uint32 directionalOutput = (output * directionalAmplitude[index]) * directionality;
directionalOutput /= 0xFF;
output *= (0xFF - directionality);
output = (output + directionalOutput) / 0xFF;
debugC(4, kDebugSound, "Directionally converted output %d", output);
return output;
}
uint8 VolumeManager::convert(uint8 inputValue, volumeScaling &mode) {
if (inputValue > _scriptScale)
inputValue = _scriptScale;
uint32 scaledInput = inputValue * 0xFF;
scaledInput /= _scriptScale;
uint8 output = 0;
switch (mode) {
case kVolumeLogPower:
output = logPower[scaledInput];
break;
case kVolumeLogAmplitude:
output = logAmplitude[scaledInput];
break;
case kVolumePowerLaw:
output = powerLaw[scaledInput];
break;
case kVolumeParabolic:
scaledInput *= scaledInput;
output = scaledInput / 0xFF;
break;
case kVolumeCubic:
scaledInput *= scaledInput * scaledInput;
output = scaledInput / 0xFE01;
break;
case kVolumeQuartic:
scaledInput *= scaledInput;
scaledInput *= scaledInput;
output = scaledInput / 0xFD02FF;
break;
case kVolumeLinear:
default:
output = scaledInput;
break;
}
debugC(4, kDebugSound, "Scripted volume %d, scaled volume %d, converted output %d", inputValue, scaledInput, output);
return output;
}
#if defined(USE_MPEG2) && defined(USE_A52)
double VolumeManager::getVobAmplification(Common::String fileName) const {
// For some reason, we get much lower volume in the hi-res videos than
// in the low-res ones. So we artificially boost the volume. This is an
// approximation, but I've tried to match the old volumes reasonably
// well.
//
// Some of these will cause audio clipping. Hopefully not enough to be
// noticeable.
double amplification = 0.0;
if (fileName == "em00d011.vob") {
// The finale.
amplification = 10.0;
} else if (fileName == "em00d021.vob") {
// Jack's escape and arrival at Flathead Mesa.
amplification = 9.0;
} else if (fileName == "em00d032.vob") {
// The Grand Inquisitor's speech.
amplification = 11.0;
} else if (fileName == "em00d122.vob") {
// Jack orders you to the radio tower.
amplification = 17.0;
} else if (fileName == "em3ed012.vob") {
// The Grand Inquisitor gets the Coconut of Quendor.
amplification = 12.0;
} else if (fileName == "g000d101.vob") {
// Griff gets captured.
amplification = 11.0;
} else if (fileName == "g000d111.vob") {
// Brog gets totemized. The music seems to be mixed much softer
// in this than in the low-resolution version.
amplification = 12.0;
} else if (fileName == "g000d122.vob") {
// Lucy gets captured.
amplification = 14.0;
} else if (fileName == "g000d302.vob") {
// The Grand Inquisitor visits Jack in his cell.
amplification = 13.0;
} else if (fileName == "g000d312.vob") {
// You get captured.
amplification = 14.0;
} else if (fileName == "g000d411.vob") {
// Propaganda On Parade. No need to make it as loud as the
// low-resolution version.
amplification = 11.0;
} else if (fileName == "pe1ed012.vob") {
// Jack lets you in with the lantern.
amplification = 14.0;
} else if (fileName.hasPrefix("pe1ed")) {
// Jack answers the door. Several different ways.
amplification = 17.0;
} else if (fileName == "pe5ed052.vob") {
// You get killed by the guards
amplification = 12.0;
} else if (fileName == "pe6ed012.vob") {
// Jack gets captured by the guards
amplification = 17.0;
} else if (fileName == "pp1ed022.vob") {
// Jack examines the lantern
amplification = 10.0;
} else if (fileName == "qb1ed012.vob") {
// Lucy gets invited to the back room
amplification = 17.0;
} else if (fileName.hasPrefix("qe1ed")) {
// Floyd answers the door. Several different ways.
amplification = 17.0;
} else if (fileName == "qs1ed011.vob") {
// Jack explains the rules of the game.
amplification = 16.0;
} else if (fileName == "qs1ed021.vob") {
// Jack loses the game.
amplification = 14.0;
} else if (fileName == "uc1gd012.vob") {
// Y'Gael appears.
amplification = 12.0;
} else if (fileName == "ue1ud012.vob") {
// Jack gets totemized... or what?
amplification = 12.0;
} else if (fileName == "ue2qd012.vob") {
// Jack agrees to totemization.
amplification = 10.0;
} else if (fileName == "g000d981.vob") {
// The Enterprise logo. Has no low-res version. Its volume is
// louder than the other logo animations.
amplification = 6.2;
} else if (fileName.hasPrefix("g000d")) {
// The Dolby Digital and Activision logos. They have no low-res
// versions, but I've used the low-resolution Activision logo
// (slightly different) as reference.
amplification = 8.5;
}
return amplification;
}
#endif
} // End of namespace ZVision

View File

@@ -0,0 +1,66 @@
/* 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"
#include "math/angle.h"
#include "zvision/zvision.h"
#ifndef ZVISION_VOLUME_MANAGER
#define ZVISION_VOLUME_MANAGER
namespace ZVision {
enum volumeScaling {
kVolumeLinear,
kVolumePowerLaw,
kVolumeParabolic,
kVolumeCubic,
kVolumeQuartic,
kVolumeLogPower,
kVolumeLogAmplitude
};
class VolumeManager {
public:
VolumeManager(ZVision *engine, volumeScaling mode);
~VolumeManager() {};
volumeScaling getMode() const {
return _mode;
}
void setMode(volumeScaling mode) {
_mode = mode;
}
uint8 convert(uint8 inputValue);
uint8 convert(uint8 inputValue, volumeScaling &mode);
uint8 convert(uint8 inputValue, Math::Angle azimuth, uint8 directionality = 255);
uint8 convert(uint8 inputValue, volumeScaling &mode, Math::Angle azimuth, uint8 directionality = 255);
#if defined(USE_MPEG2) && defined(USE_A52)
double getVobAmplification(Common::String fileName) const;
#endif
private:
uint _scriptScale = 100; // Z-Vision scripts internally use a volume scale of 0-100; ScummVM uses a scale of 0-255.
volumeScaling _mode = kVolumeLinear;
};
} // End of namespace ZVision
#endif

View File

@@ -0,0 +1,278 @@
/* 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 "audio/audiostream.h"
#include "audio/decoders/raw.h"
#include "common/bufferedstream.h"
#include "common/file.h"
#include "common/memstream.h"
#include "common/scummsys.h"
#include "common/str.h"
#include "common/stream.h"
#include "common/tokenizer.h"
#include "common/util.h"
#include "zvision/zvision.h"
#include "zvision/file/file_manager.h"
#include "zvision/sound/zork_raw.h"
namespace ZVision {
const int16 RawChunkStream::_stepAdjustmentTable[8] = { -1, -1, -1, 1, 4, 7, 10, 12};
const int32 RawChunkStream::_amplitudeLookupTable[89] = {
0x0007, 0x0008, 0x0009, 0x000A, 0x000B, 0x000C, 0x000D, 0x000E,
0x0010, 0x0011, 0x0013, 0x0015, 0x0017, 0x0019, 0x001C, 0x001F,
0x0022, 0x0025, 0x0029, 0x002D, 0x0032, 0x0037, 0x003C, 0x0042,
0x0049, 0x0050, 0x0058, 0x0061, 0x006B, 0x0076, 0x0082, 0x008F,
0x009D, 0x00AD, 0x00BE, 0x00D1, 0x00E6, 0x00FD, 0x0117, 0x0133,
0x0151, 0x0173, 0x0198, 0x01C1, 0x01EE, 0x0220, 0x0256, 0x0292,
0x02D4, 0x031C, 0x036C, 0x03C3, 0x0424, 0x048E, 0x0502, 0x0583,
0x0610, 0x06AB, 0x0756, 0x0812, 0x08E0, 0x09C3, 0x0ABD, 0x0BD0,
0x0CFF, 0x0E4C, 0x0FBA, 0x114C, 0x1307, 0x14EE, 0x1706, 0x1954,
0x1BDC, 0x1EA5, 0x21B6, 0x2515, 0x28CA, 0x2CDF, 0x315B, 0x364B,
0x3BB9, 0x41B2, 0x4844, 0x4F7E, 0x5771, 0x602F, 0x69CE, 0x7462, 0x7FFF
};
RawChunkStream::RawChunkStream(bool stereo) {
if (stereo)
_stereo = 1;
else
_stereo = 0;
init();
}
void RawChunkStream::init() {
_lastSample[0].index = 0;
_lastSample[0].sample = 0;
_lastSample[1].index = 0;
_lastSample[1].sample = 0;
}
RawChunkStream::RawChunk RawChunkStream::readNextChunk(Common::SeekableReadStream *stream) {
RawChunk tmp;
tmp.size = 0;
tmp.data = NULL;
if (!stream || stream->size() == 0 || stream->eos())
return tmp;
tmp.size = (stream->size() - stream->pos()) * 2;
tmp.data = (int16 *)calloc(tmp.size, 1);
readBuffer(tmp.data, stream, stream->size() - stream->pos());
return tmp;
}
int RawChunkStream::readBuffer(int16 *buffer, Common::SeekableReadStream *stream, const int numSamples) {
int32 bytesRead = 0;
// 0: Left, 1: Right
uint channel = 0;
while (bytesRead < numSamples) {
byte encodedSample = stream->readByte();
if (stream->eos()) {
return bytesRead;
}
bytesRead++;
int16 index = _lastSample[channel].index;
uint32 lookUpSample = _amplitudeLookupTable[index];
int32 sample = 0;
if (encodedSample & 0x40)
sample += lookUpSample;
if (encodedSample & 0x20)
sample += lookUpSample >> 1;
if (encodedSample & 0x10)
sample += lookUpSample >> 2;
if (encodedSample & 8)
sample += lookUpSample >> 3;
if (encodedSample & 4)
sample += lookUpSample >> 4;
if (encodedSample & 2)
sample += lookUpSample >> 5;
if (encodedSample & 1)
sample += lookUpSample >> 6;
if (encodedSample & 0x80)
sample = -sample;
sample += _lastSample[channel].sample;
sample = CLIP<int32>(sample, -32768, 32767);
buffer[bytesRead - 1] = (int16)sample;
index += _stepAdjustmentTable[(encodedSample >> 4) & 7];
index = CLIP<int16>(index, 0, 88);
_lastSample[channel].sample = sample;
_lastSample[channel].index = index;
// Increment and wrap the channel
channel = (channel + 1) & _stereo;
}
return bytesRead;
}
const SoundParams RawZorkStream::_zNemSoundParamLookupTable[32] = {
{'0', 0x1F40, false, false, false},
{'1', 0x1F40, true, false, false},
{'2', 0x1F40, false, false, true},
{'3', 0x1F40, true, false, true},
{'4', 0x2B11, false, false, false},
{'5', 0x2B11, true, false, false},
{'6', 0x2B11, false, false, true},
{'7', 0x2B11, true, false, true},
{'8', 0x5622, false, false, false},
{'9', 0x5622, true, false, false},
{'a', 0x5622, false, false, true},
{'b', 0x5622, true, false, true},
{'c', 0xAC44, false, false, false},
{'d', 0xAC44, true, false, false},
{'e', 0xAC44, false, false, true},
{'f', 0xAC44, true, false, true},
{'g', 0x1F40, false, true, false},
{'h', 0x1F40, true, true, false},
{'j', 0x1F40, false, true, true},
{'k', 0x1F40, true, true, true},
{'l', 0x2B11, false, true, false},
{'m', 0x2B11, true, true, false},
{'n', 0x2B11, false, true, true},
{'p', 0x2B11, true, true, true},
{'q', 0x5622, false, true, false},
{'r', 0x5622, true, true, false},
{'s', 0x5622, false, true, true},
{'t', 0x5622, true, true, true},
{'u', 0xAC44, false, true, false},
{'v', 0xAC44, true, true, false},
{'w', 0xAC44, false, true, true},
{'x', 0xAC44, true, true, true}
};
const SoundParams RawZorkStream::_zgiSoundParamLookupTable[24] = {
{'4', 0x2B11, false, false, false},
{'5', 0x2B11, true, false, false},
{'6', 0x2B11, false, false, true},
{'7', 0x2B11, true, false, true},
{'8', 0x5622, false, false, false},
{'9', 0x5622, true, false, false},
{'a', 0x5622, false, false, true},
{'b', 0x5622, true, false, true},
{'c', 0xAC44, false, false, false},
{'d', 0xAC44, true, false, false},
{'e', 0xAC44, false, false, true},
{'f', 0xAC44, true, false, true},
{'g', 0x2B11, false, true, false},
{'h', 0x2B11, true, true, false},
{'j', 0x2B11, false, true, true},
{'k', 0x2B11, true, true, true},
{'m', 0x5622, false, true, false},
{'n', 0x5622, true, true, false},
{'p', 0x5622, false, true, true},
{'q', 0x5622, true, true, true},
{'r', 0xAC44, false, true, false},
{'s', 0xAC44, true, true, false},
{'t', 0xAC44, false, true, true},
{'u', 0xAC44, true, true, true}
};
RawZorkStream::RawZorkStream(uint32 rate, bool stereo, DisposeAfterUse::Flag disposeStream, Common::SeekableReadStream *stream)
: _rate(rate),
_stereo(0),
_stream(stream, disposeStream),
_endOfData(false),
_streamReader(stereo) {
if (stereo)
_stereo = 1;
// Calculate the total playtime of the stream
if (stereo)
_playtime = Audio::Timestamp(0, _stream->size() / 2, rate);
else
_playtime = Audio::Timestamp(0, _stream->size(), rate);
}
int RawZorkStream::readBuffer(int16 *buffer, const int numSamples) {
int32 bytesRead = _streamReader.readBuffer(buffer, _stream.get(), numSamples);
if (_stream->eos())
_endOfData = true;
return bytesRead;
}
bool RawZorkStream::rewind() {
_stream->seek(0, 0);
_stream->clearErr();
_endOfData = false;
_streamReader.init();
return true;
}
Audio::RewindableAudioStream *makeRawZorkStream(Common::SeekableReadStream *stream,
int rate,
bool stereo,
DisposeAfterUse::Flag disposeAfterUse) {
if (stereo)
assert(stream->size() % 2 == 0);
return new RawZorkStream(rate, stereo, disposeAfterUse, stream);
}
Audio::RewindableAudioStream *makeRawZorkStream(const Common::Path &filePath, ZVision *engine) {
Common::String baseName = filePath.baseName();
Common::File *file = engine->getFileManager()->open(filePath);
const SoundParams *soundParams = NULL;
if (engine->getGameId() == GID_NEMESIS) {
for (int i = 0; i < 32; ++i) {
if (RawZorkStream::_zNemSoundParamLookupTable[i].identifier == (baseName[6]))
soundParams = &RawZorkStream::_zNemSoundParamLookupTable[i];
}
} else if (engine->getGameId() == GID_GRANDINQUISITOR) {
for (int i = 0; i < 24; ++i) {
if (RawZorkStream::_zgiSoundParamLookupTable[i].identifier == (baseName[7]))
soundParams = &RawZorkStream::_zgiSoundParamLookupTable[i];
}
}
if (soundParams == NULL)
return NULL;
if (soundParams->packed) {
return makeRawZorkStream(wrapBufferedSeekableReadStream(file, 2048, DisposeAfterUse::YES), soundParams->rate, soundParams->stereo, DisposeAfterUse::YES);
} else {
byte flags = 0;
if (soundParams->bits16)
flags |= Audio::FLAG_16BITS | Audio::FLAG_LITTLE_ENDIAN;
if (soundParams->stereo)
flags |= Audio::FLAG_STEREO;
return Audio::makeRawStream(file, soundParams->rate, flags, DisposeAfterUse::YES);
}
}
} // End of namespace ZVision

View File

@@ -0,0 +1,141 @@
/* 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 ZVISION_ZORK_RAW_H
#define ZVISION_ZORK_RAW_H
#include "audio/audiostream.h"
namespace Common {
class SeekableReadStream;
}
namespace ZVision {
class ZVision;
struct SoundParams {
char identifier;
uint32 rate;
bool stereo;
bool packed;
bool bits16;
};
/**
* This is a ADPCM stream-reader, this class holds context for multi-chunk reading and no buffers.
*/
class RawChunkStream {
public:
RawChunkStream(bool stereo);
~RawChunkStream() {
}
private:
uint _stereo;
/**
* Holds the frequency and index from the last sample
* 0 holds the left channel, 1 holds the right channel
*/
struct {
int32 sample;
int16 index;
} _lastSample[2];
static const int16 _stepAdjustmentTable[8];
static const int32 _amplitudeLookupTable[89];
public:
struct RawChunk {
int16 *data;
uint32 size;
};
void init();
//Read next audio portion in new stream (needed for avi), return structure with buffer
RawChunk readNextChunk(Common::SeekableReadStream *stream);
//Read numSamples from stream to buffer
int readBuffer(int16 *buffer, Common::SeekableReadStream *stream, const int numSamples);
};
/**
* This is a stream, which allows for playing raw ADPCM data from a stream.
*/
class RawZorkStream : public Audio::RewindableAudioStream {
public:
RawZorkStream(uint32 rate, bool stereo, DisposeAfterUse::Flag disposeStream, Common::SeekableReadStream *stream);
~RawZorkStream() override {
}
public:
static const SoundParams _zNemSoundParamLookupTable[32];
static const SoundParams _zgiSoundParamLookupTable[24];
private:
const int _rate; // Sample rate of stream
Audio::Timestamp _playtime; // Calculated total play time
Common::DisposablePtr<Common::SeekableReadStream> _stream; // Stream to read data from
bool _endOfData; // Whether the stream end has been reached
uint _stereo;
RawChunkStream _streamReader;
public:
int readBuffer(int16 *buffer, const int numSamples) override;
bool isStereo() const override {
return _stereo;
}
bool endOfData() const override {
return _endOfData;
}
int getRate() const override {
return _rate;
}
Audio::Timestamp getLength() const {
return _playtime;
}
bool rewind() override;
};
/**
* Creates an audio stream, which plays from the given stream.
*
* @param stream Stream object to play from.
* @param rate Rate of the sound data.
* @param dispose AfterUse Whether to delete the stream after use.
* @return The new SeekableAudioStream (or 0 on failure).
*/
Audio::RewindableAudioStream *makeRawZorkStream(Common::SeekableReadStream *stream,
int rate,
bool stereo,
DisposeAfterUse::Flag disposeAfterUse = DisposeAfterUse::YES);
Audio::RewindableAudioStream *makeRawZorkStream(const Common::Path &filePath, ZVision *engine);
} // End of namespace ZVision
#endif

View File

@@ -0,0 +1,65 @@
/* 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/debug.h"
#include "common/file.h"
#include "common/scummsys.h"
#include "common/tokenizer.h"
#include "graphics/fontman.h"
#include "zvision/zvision.h"
#include "zvision/text/string_manager.h"
#include "zvision/text/text.h"
namespace ZVision {
StringManager::StringManager(ZVision *engine) {
}
StringManager::~StringManager() {
}
void StringManager::initialize(ZVisionGameId gameId) {
if (gameId == GID_NEMESIS)
loadStrFile("nemesis.str");
else if (gameId == GID_GRANDINQUISITOR)
loadStrFile("inquis.str");
}
void StringManager::loadStrFile(const Common::Path &fileName) {
Common::File file;
if (!file.open(fileName))
error("%s does not exist. String parsing failed", fileName.toString().c_str());
uint lineNumber = 0;
while (!file.eos()) {
_lines[lineNumber] = readWideLine(file).encode();
lineNumber++;
assert(lineNumber <= NUM_TEXT_LINES);
}
}
const Common::String StringManager::getTextLine(uint stringNumber) {
return _lines[stringNumber];
}
} // End of namespace ZVision

View File

@@ -0,0 +1,66 @@
/* 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 ZVISION_STRING_MANAGER_H
#define ZVISION_STRING_MANAGER_H
#include "zvision/text/truetype_font.h"
namespace Graphics {
class FontManager;
}
namespace ZVision {
class ZVision;
class StringManager {
public:
StringManager(ZVision *engine);
~StringManager();
public:
enum {
ZVISION_STR_SAVEEXIST = 23,
ZVISION_STR_SAVED = 4,
ZVISION_STR_SAVEEMPTY = 21,
ZVISION_STR_EXITPROMT = 6
};
private:
enum {
NUM_TEXT_LINES = 56 // Max number of lines in a .str file. We hardcode this number because we know ZNem uses 42 strings and ZGI uses 56
};
private:
Common::String _lines[NUM_TEXT_LINES];
public:
void initialize(ZVisionGameId gameId);
const Common::String getTextLine(uint stringNumber);
private:
void loadStrFile(const Common::Path &fileName);
};
} // End of namespace ZVision
#endif

View File

@@ -0,0 +1,430 @@
/* 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/file.h"
#include "common/system.h"
#include "zvision/detection.h"
#include "zvision/graphics/render_manager.h"
#include "zvision/scripting/script_manager.h"
#include "zvision/text/subtitle_manager.h"
#include "zvision/text/text.h"
namespace ZVision {
SubtitleManager::SubtitleManager(ZVision *engine, const ScreenLayout layout, const Graphics::PixelFormat pixelFormat, bool doubleFPS) :
_engine(engine),
_system(engine->_system),
_renderManager(engine->getRenderManager()),
_pixelFormat(pixelFormat),
_textOffset(layout.workingArea.origin() - layout.textArea.origin()),
_textArea(layout.textArea.width(), layout.textArea.height()),
_redraw(false),
_doubleFPS(doubleFPS),
_subId(0) {
}
SubtitleManager::~SubtitleManager() {
// Delete all subtitles referenced in subslist
for (auto &it : _subsList)
delete it._value;
}
void SubtitleManager::process(int32 deltatime) {
for (SubtitleMap::iterator it = _subsList.begin(); it != _subsList.end(); it++) {
// Update all automatic subtitles
if (it->_value->selfUpdate())
_redraw = true;
// Update all subtitles' respective deletion timers
if (it->_value->process(deltatime)) {
debugC(4, kDebugSubtitle, "Deleting subtitle, subId=%d", it->_key);
_subsFocus.remove(it->_key);
delete it->_value;
_subsList.erase(it);
_redraw = true;
}
}
if (_subsList.size() == 0)
if (_subId != 0) {
debugC(4, kDebugSubtitle, "Resetting subId to 0");
_subId = 0;
_subsFocus.clear();
}
if (_redraw) {
debugC(4, kDebugSubtitle, "Redrawing subtitles");
// Blank subtitle buffer
_renderManager->clearTextSurface();
// Render just the most recent subtitle
if (_subsFocus.size()) {
uint16 curSub = _subsFocus.front();
debugC(4, kDebugSubtitle, "Rendering subtitle %d", curSub);
Subtitle *sub = _subsList[curSub];
if (sub->_lineId >= 0) {
Graphics::Surface textSurface;
//TODO - make this surface a persistent member of the manager; only call create() when currently displayed subtitle is changed.
textSurface.create(sub->_textArea.width(), sub->_textArea.height(), _engine->_resourcePixelFormat);
textSurface.fillRect(Common::Rect(sub->_textArea.width(), sub->_textArea.height()), (uint32)-1); // TODO Unnecessary operation? Check later.
_engine->getTextRenderer()->drawTextWithWordWrapping(sub->_lines[sub->_lineId].subStr, textSurface, _engine->isWidescreen());
_renderManager->blitSurfaceToText(textSurface, sub->_textArea.left, sub->_textArea.top, -1);
textSurface.free();
sub->_redraw = false;
}
}
_redraw = false;
}
}
void SubtitleManager::update(int32 count, uint16 subid) {
if (_subsList.contains(subid))
if (_subsList[subid]->update(count)) {
// _subsFocus.set(subid);
_redraw = true;
}
}
uint16 SubtitleManager::create(const Common::Path &subname, bool vob) {
_subId++;
debugC(2, kDebugSubtitle, "Creating scripted subtitle, subId=%d", _subId);
_subsList[_subId] = new Subtitle(_engine, subname, vob);
_subsFocus.set(_subId);
return _subId;
}
uint16 SubtitleManager::create(const Common::Path &subname, Audio::SoundHandle handle) {
_subId++;
debugC(2, kDebugSubtitle, "Creating scripted subtitle, subId=%d", _subId);
_subsList[_subId] = new AutomaticSubtitle(_engine, subname, handle);
_subsFocus.set(_subId);
return _subId;
}
uint16 SubtitleManager::create(const Common::String &str) {
_subId++;
debugC(2, kDebugSubtitle, "Creating simple subtitle, subId=%d, message %s", _subId, str.c_str());
_subsList[_subId] = new Subtitle(_engine, str, _textArea);
_subsFocus.set(_subId);
return _subId;
}
void SubtitleManager::destroy(uint16 id) {
if (_subsList.contains(id)) {
debugC(2, kDebugSubtitle, "Marking subtitle %d for immediate deletion", id);
_subsList[id]->_toDelete = true;
}
}
void SubtitleManager::destroy(uint16 id, int16 delay) {
if (_subsList.contains(id)) {
debugC(2, kDebugSubtitle, "Marking subtitle %d for deletion in %dms", id, delay);
_subsList[id]->_timer = delay;
}
}
void SubtitleManager::timedMessage(const Common::String &str, uint16 milsecs) {
uint16 msgid = create(str);
debugC(1, kDebugSubtitle, "initiating timed message: %s to subtitle id %d, time %d", str.c_str(), msgid, milsecs);
update(0, msgid);
process(0);
destroy(msgid, milsecs);
}
bool SubtitleManager::askQuestion(const Common::String &str, bool streaming, bool safeDefault) {
uint16 msgid = create(str);
debugC(1, kDebugSubtitle, "initiating user question: %s to subtitle id %d", str.c_str(), msgid);
update(0, msgid);
process(0);
if(streaming)
_renderManager->renderSceneToScreen(true,true,true);
else
_renderManager->renderSceneToScreen(true);
_engine->stopClock();
int result = 0;
while (result == 0) {
Common::Event evnt;
while (_engine->getEventManager()->pollEvent(evnt)) {
switch (evnt.type) {
case Common::EVENT_CUSTOM_ENGINE_ACTION_START:
if ((ZVisionAction)evnt.customType != kZVisionActionQuit)
break;
// fall through
case Common::EVENT_QUIT:
debugC(1, kDebugEvent, "Attempting to quit within quit dialog!");
_engine->quit(false);
return safeDefault;
break;
case Common::EVENT_KEYDOWN:
// English: yes/no
// German: ja/nein
// Spanish: si/no
// French Nemesis: F4/any other key _engine(engine),
// French ZGI: oui/non
// TODO: Handle this using the keymapper
switch (evnt.kbd.keycode) {
case Common::KEYCODE_y:
if (_engine->getLanguage() == Common::EN_ANY)
result = 2;
break;
case Common::KEYCODE_j:
if (_engine->getLanguage() == Common::DE_DEU)
result = 2;
break;
case Common::KEYCODE_s:
if (_engine->getLanguage() == Common::ES_ESP)
result = 2;
break;
case Common::KEYCODE_o:
if (_engine->getLanguage() == Common::FR_FRA && _engine->getGameId() == GID_GRANDINQUISITOR)
result = 2;
break;
case Common::KEYCODE_F4:
if (_engine->getLanguage() == Common::FR_FRA && _engine->getGameId() == GID_NEMESIS)
result = 2;
break;
case Common::KEYCODE_n:
result = 1;
break;
default:
if (_engine->getLanguage() == Common::FR_FRA && _engine->getGameId() == GID_NEMESIS)
result = 1;
break;
}
break;
default:
break;
}
}
if(streaming)
_renderManager->renderSceneToScreen(true,true,false);
else
_renderManager->renderSceneToScreen(true);
if (_doubleFPS)
_system->delayMillis(33);
else
_system->delayMillis(66);
}
destroy(msgid);
_engine->startClock();
return result == 2;
}
void SubtitleManager::delayedMessage(const Common::String &str, uint16 milsecs) {
uint16 msgid = create(str);
debugC(1, kDebugSubtitle, "initiating delayed message: %s to subtitle id %d, delay %dms", str.c_str(), msgid, milsecs);
update(0, msgid);
process(0);
_renderManager->renderSceneToScreen(true);
_engine->stopClock();
uint32 stopTime = _system->getMillis() + milsecs;
while (_system->getMillis() < stopTime) {
Common::Event evnt;
while (_engine->getEventManager()->pollEvent(evnt)) {
switch (evnt.type) {
case Common::EVENT_KEYDOWN:
switch (evnt.kbd.keycode) {
case Common::KEYCODE_SPACE:
case Common::KEYCODE_RETURN:
case Common::KEYCODE_ESCAPE:
goto skip_delayed_message;
break;
default:
break;
}
break;
case Common::EVENT_CUSTOM_ENGINE_ACTION_START:
if ((ZVisionAction)evnt.customType != kZVisionActionQuit)
break;
// fall through
case Common::EVENT_QUIT:
if (ConfMan.hasKey("confirm_exit") && ConfMan.getBool("confirm_exit"))
_engine->quit(true);
else
_engine->quit(false);
break;
default:
break;
}
}
skip_delayed_message:
_renderManager->renderSceneToScreen(true);
if (_doubleFPS)
_system->delayMillis(17);
else
_system->delayMillis(33);
}
destroy(msgid);
_engine->startClock();
}
void SubtitleManager::showDebugMsg(const Common::String &msg, int16 delay) {
uint16 msgid = create(msg);
debugC(1, kDebugSubtitle, "initiating in-game debug message: %s to subtitle id %d, delay %dms", msg.c_str(), msgid, delay);
update(0, msgid);
process(0);
destroy(msgid, delay);
}
Subtitle::Subtitle(ZVision *engine, const Common::Path &subname, bool vob) :
_engine(engine),
_lineId(-1),
_timer(-1),
_toDelete(false),
_redraw(false) {
Common::File subFile;
Common::Point _textOffset = _engine->getSubtitleManager()->getTextOffset();
if (!subFile.open(subname)) {
warning("Failed to open subtitle %s", subname.toString().c_str());
_toDelete = true;
return;
}
// Parse subtitle parameters from script
while (!subFile.eos()) {
Common::String str = subFile.readLine();
if (str.lastChar() == '~')
str.deleteLastChar();
if (str.matchString("*Initialization*", true)) {
// Not used
} else if (str.matchString("*Rectangle*", true)) {
int32 x1, y1, x2, y2;
if (sscanf(str.c_str(), "%*[^:]:%d %d %d %d", &x1, &y1, &x2, &y2) == 4) {
_textArea = Common::Rect(x1, y1, x2, y2);
debugC(1, kDebugSubtitle, "Original subtitle script rectangle coordinates: l%d, t%d, r%d, b%d", x1, y1, x2, y2);
// Original game subtitle scripts appear to define subtitle rectangles relative to origin of working area.
// To allow arbitrary aspect ratios, we need to instead place these relative to origin of text area.
// This will allow the managed text area to then be arbitrarily placed on the screen to suit different aspect ratios.
_textArea.translate(_textOffset.x, _textOffset.y); // Convert working area coordinates to text area coordinates
debugC(1, kDebugSubtitle, "Text area coordinates: l%d, t%d, r%d, b%d", _textArea.left, _textArea.top, _textArea.right, _textArea.bottom);
}
} else if (str.matchString("*TextFile*", true)) {
char filename[64];
if (sscanf(str.c_str(), "%*[^:]:%s", filename) == 1) {
Common::File txtFile;
if (txtFile.open(Common::Path(filename))) {
while (!txtFile.eos()) {
Common::String txtline = readWideLine(txtFile).encode();
Line curLine;
curLine.start = -1;
curLine.stop = -1;
curLine.subStr = txtline;
_lines.push_back(curLine);
}
txtFile.close();
}
}
} else {
int32 st; // Line start time
int32 en; // Line end time
int32 sb; // Line number
if (sscanf(str.c_str(), "%*[^:]:(%d,%d)=%d", &st, &en, &sb) == 3) {
if (sb <= (int32)_lines.size()) {
if (vob) {
// Convert frame number from 15FPS (AVI) to 29.97FPS (VOB) to synchronise with video
// st = st * 2997 / 1500;
// en = en * 2997 / 1500;
st = st * 2900 / 1500; // TODO: Subtitles only synchronise correctly at 29fps, but vob files should be 29.97fps; check if video codec is rounding this value down!
en = en * 2900 / 1500;
}
_lines[sb].start = st;
_lines[sb].stop = en;
}
}
}
}
subFile.close();
}
Subtitle::Subtitle(ZVision *engine, const Common::String &str, const Common::Rect &textArea) :
_engine(engine),
_lineId(-1),
_timer(-1),
_toDelete(false),
_redraw(false) {
_textArea = textArea;
debugC(1, kDebugSubtitle, "Text area coordinates: l%d, t%d, r%d, b%d", _textArea.left, _textArea.top, _textArea.right, _textArea.bottom);
Line curLine;
curLine.start = -1;
curLine.stop = 0;
curLine.subStr = str;
_lines.push_back(curLine);
}
Subtitle::~Subtitle() {
_lines.clear();
}
bool Subtitle::process(int32 deltatime) {
if (_timer != -1) {
_timer -= deltatime;
if (_timer <= 0)
_toDelete = true;
}
return _toDelete;
}
bool Subtitle::update(int32 count) {
int16 j = -1;
// Search all lines to find first line that encompasses current time/framecount, set j to this
for (uint16 i = (_lineId >= 0 ? _lineId : 0); i < _lines.size(); i++)
if (count >= _lines[i].start && count <= _lines[i].stop) {
j = i;
break;
}
if (j == -1) {
// No line exists for current time/framecount
if (_lineId != -1) {
// Line is set
_lineId = -1; // Unset line
_redraw = true;
}
} else {
// Line exists for current time/framecount
if (j != _lineId && _lines[j].subStr.size()) {
// Set line is not equal to current line & current line is not blank
_lineId = j; // Set line to current
_redraw = true;
}
}
return _redraw;
}
AutomaticSubtitle::AutomaticSubtitle(ZVision *engine, const Common::Path &subname, Audio::SoundHandle handle) :
Subtitle(engine, subname, false),
_handle(handle) {
}
bool AutomaticSubtitle::selfUpdate() {
if (_engine->_mixer->isSoundHandleActive(_handle) && _engine->getScriptManager()->getStateValue(StateKey_Subtitles) == 1)
return update(_engine->_mixer->getSoundElapsedTime(_handle) / 100);
else {
_toDelete = true;
return false;
}
}
bool AutomaticSubtitle::process(int32 deltatime) {
Subtitle::process(deltatime);
if (!_engine->_mixer->isSoundHandleActive(_handle))
_toDelete = true;
return _toDelete;
}
} // End of namespace ZVision

View File

@@ -0,0 +1,128 @@
/* 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 ZVISION_SUBTITLES_H
#define ZVISION_SUBTITLES_H
#include "audio/mixer.h"
#include "zvision/zvision.h"
#include "zvision/common/focus_list.h"
namespace ZVision {
class ZVision;
class Subtitle {
friend class SubtitleManager;
public:
Subtitle(ZVision *engine, const Common::Path &subname, bool vob = false); // For scripted subtitles
Subtitle(ZVision *engine, const Common::String &str, const Common::Rect &textArea); // For other text messages
virtual ~Subtitle();
bool update(int32 count); // Return true if necessary to redraw
virtual bool selfUpdate() {
return false;
}
protected:
virtual bool process(int32 deltatime); // Return true if to be deleted
ZVision *_engine;
Common::Rect _textArea;
int16 _timer; // Always in milliseconds; countdown to deletion
bool _toDelete;
bool _redraw;
int16 _lineId;
struct Line {
int start;
int stop;
Common::String subStr;
};
// NB: start & stop do not always use the same units between different instances of this struct!
// Sound effect & music subtitles use milliseconds
// Video subtitle timings are specified in video frames at 15fps, i.e. in multiples of 66.6' milliseconds!
// AVI videos run at 15fps and can have frames counted directly
// DVD videos in VOB format run at 29.97 fps and must be converted to work with the subtitle files, which were made for AVI.
Common::Array<Line> _lines;
};
class AutomaticSubtitle : public Subtitle {
public:
AutomaticSubtitle(ZVision *engine, const Common::Path &subname, Audio::SoundHandle handle); // For scripted audio subtitles
~AutomaticSubtitle() {}
private:
bool process(int32 deltatime); // Return true if to be deleted
bool selfUpdate(); // Return true if necessary to redraw
Audio::SoundHandle _handle;
};
class SubtitleManager {
public:
SubtitleManager(ZVision *engine, const ScreenLayout layout, const Graphics::PixelFormat pixelFormat, bool doubleFPS);
~SubtitleManager();
private:
ZVision *_engine;
OSystem *_system;
RenderManager *_renderManager;
const Graphics::PixelFormat _pixelFormat;
const Common::Point _textOffset; // Position vector of text area origin relative to working window origin
const Common::Rect _textArea;
bool _redraw;
bool _doubleFPS;
// Internal subtitle ID counter
uint16 _subId;
typedef Common::HashMap<uint16, Subtitle *> SubtitleMap;
// Subtitle list
SubtitleMap _subsList;
// Subtitle focus history
FocusList<uint16> _subsFocus;
public:
// Update all subtitle objects' deletion timers, delete expired subtitles, & redraw most recent. Does NOT update any subtitle's count value or displayed string!
void process(int32 deltatime); // deltatime is always milliseconds
// Update counter value of referenced subtitle id & set current line to display, if any.
void update(int32 count, uint16 subid); // Count is milliseconds for sound & music; frames for video playback.
const Common::Point &getTextOffset() const {
return _textOffset;
}
// Create subtitle object and return ID
uint16 create(const Common::Path &subname, bool vob = false);
uint16 create(const Common::Path &subname, Audio::SoundHandle handle); // NB this creates an automatic subtitle
uint16 create(const Common::String &str);
// Delete subtitle object by ID
void destroy(uint16 id);
void destroy(uint16 id, int16 delay);
bool askQuestion(const Common::String &str, bool streaming = false, bool safeDefault = false);
void delayedMessage(const Common::String &str, uint16 milsecs);
void timedMessage(const Common::String &str, uint16 milsecs);
void showDebugMsg(const Common::String &msg, int16 delay = 3000);
};
}
#endif

View File

@@ -0,0 +1,539 @@
/* 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/debug.h"
#include "common/file.h"
#include "common/rect.h"
#include "common/scummsys.h"
#include "common/tokenizer.h"
#include "graphics/font.h"
#include "graphics/fontman.h"
#include "graphics/surface.h"
#include "graphics/fonts/ttf.h"
#include "zvision/graphics/render_manager.h"
#include "zvision/scripting/script_manager.h"
#include "zvision/text/text.h"
#include "zvision/text/truetype_font.h"
namespace ZVision {
TextStyleState::TextStyleState() {
_fontname = "Arial";
_blue = 255;
_green = 255;
_red = 255;
_bold = false;
#if 0
_newline = false;
_escapement = 0;
#endif
_italic = false;
_justification = TEXT_JUSTIFY_LEFT;
_size = 12;
#if 0
_skipcolor = false;
#endif
_strikeout = false;
_underline = false;
_statebox = 0;
_sharp = false;
}
TextChange TextStyleState::parseStyle(const Common::String &str, int16 len) {
Common::String buf = Common::String(str.c_str(), len);
uint retval = TEXT_CHANGE_NONE;
Common::StringTokenizer tokenizer(buf, " ");
Common::String token;
while (!tokenizer.empty()) {
token = tokenizer.nextToken();
if (token.matchString("font", true)) {
token = tokenizer.nextToken();
if (token[0] == '"') {
Common::String _tmp = Common::String(token.c_str() + 1);
while (token.lastChar() != '"' && !tokenizer.empty()) {
token = tokenizer.nextToken();
_tmp += " " + token;
}
if (_tmp.lastChar() == '"')
_tmp.deleteLastChar();
_fontname = _tmp;
} else {
if (!tokenizer.empty())
_fontname = token;
}
retval |= TEXT_CHANGE_FONT_TYPE;
} else if (token.matchString("blue", true)) {
if (!tokenizer.empty()) {
token = tokenizer.nextToken();
int32 tmp = atoi(token.c_str());
if (_blue != tmp) {
_blue = tmp;
retval |= TEXT_CHANGE_FONT_STYLE;
}
}
} else if (token.matchString("red", true)) {
if (!tokenizer.empty()) {
token = tokenizer.nextToken();
int32 tmp = atoi(token.c_str());
if (_red != tmp) {
_red = tmp;
retval |= TEXT_CHANGE_FONT_STYLE;
}
}
} else if (token.matchString("green", true)) {
if (!tokenizer.empty()) {
token = tokenizer.nextToken();
int32 tmp = atoi(token.c_str());
if (_green != tmp) {
_green = tmp;
retval |= TEXT_CHANGE_FONT_STYLE;
}
}
} else if (token.matchString("newline", true)) {
#if 0
if ((retval & TXT_RET_NEWLN) == 0)
_newline = 0;
_newline++;
#endif
retval |= TEXT_CHANGE_NEWLINE;
} else if (token.matchString("point", true)) {
if (!tokenizer.empty()) {
token = tokenizer.nextToken();
int32 tmp = atoi(token.c_str());
if (_size != tmp) {
_size = tmp;
retval |= TEXT_CHANGE_FONT_TYPE;
}
}
} else if (token.matchString("escapement", true)) {
if (!tokenizer.empty()) {
token = tokenizer.nextToken();
#if 0
int32 tmp = atoi(token.c_str());
_escapement = tmp;
#endif
}
} else if (token.matchString("italic", true)) {
if (!tokenizer.empty()) {
token = tokenizer.nextToken();
if (token.matchString("on", true)) {
if (_italic != true) {
_italic = true;
retval |= TEXT_CHANGE_FONT_STYLE;
}
} else if (token.matchString("off", true)) {
if (_italic != false) {
_italic = false;
retval |= TEXT_CHANGE_FONT_STYLE;
}
}
}
} else if (token.matchString("underline", true)) {
if (!tokenizer.empty()) {
token = tokenizer.nextToken();
if (token.matchString("on", true)) {
if (_underline != true) {
_underline = true;
retval |= TEXT_CHANGE_FONT_STYLE;
}
} else if (token.matchString("off", true)) {
if (_underline != false) {
_underline = false;
retval |= TEXT_CHANGE_FONT_STYLE;
}
}
}
} else if (token.matchString("strikeout", true)) {
if (!tokenizer.empty()) {
token = tokenizer.nextToken();
if (token.matchString("on", true)) {
if (_strikeout != true) {
_strikeout = true;
retval |= TEXT_CHANGE_FONT_STYLE;
}
} else if (token.matchString("off", true)) {
if (_strikeout != false) {
_strikeout = false;
retval |= TEXT_CHANGE_FONT_STYLE;
}
}
}
} else if (token.matchString("bold", true)) {
if (!tokenizer.empty()) {
token = tokenizer.nextToken();
if (token.matchString("on", true)) {
if (_bold != true) {
_bold = true;
retval |= TEXT_CHANGE_FONT_STYLE;
}
} else if (token.matchString("off", true)) {
if (_bold != false) {
_bold = false;
retval |= TEXT_CHANGE_FONT_STYLE;
}
}
}
} else if (token.matchString("skipcolor", true)) {
if (!tokenizer.empty()) {
token = tokenizer.nextToken();
#if 0
if (token.matchString("on", true)) {
_skipcolor = true;
} else if (token.matchString("off", true)) {
_skipcolor = false;
}
#endif
}
} else if (token.matchString("image", true)) {
// Not used
} else if (token.matchString("statebox", true)) {
if (!tokenizer.empty()) {
token = tokenizer.nextToken();
_statebox = atoi(token.c_str());
retval |= TEXT_CHANGE_HAS_STATE_BOX;
}
} else if (token.matchString("justify", true)) {
if (!tokenizer.empty()) {
token = tokenizer.nextToken();
if (token.matchString("center", true))
_justification = TEXT_JUSTIFY_CENTER;
else if (token.matchString("left", true))
_justification = TEXT_JUSTIFY_LEFT;
else if (token.matchString("right", true))
_justification = TEXT_JUSTIFY_RIGHT;
}
}
}
return (TextChange)retval;
}
void TextStyleState::readAllStyles(const Common::String &txt) {
int16 startTextPosition = -1;
int16 endTextPosition = -1;
for (uint16 i = 0; i < txt.size(); i++) {
if (txt[i] == '<')
startTextPosition = i;
else if (txt[i] == '>') {
endTextPosition = i;
if (startTextPosition != -1) {
if ((endTextPosition - startTextPosition - 1) > 0) {
parseStyle(Common::String(txt.c_str() + startTextPosition + 1), endTextPosition - startTextPosition - 1);
}
}
}
}
}
void TextStyleState::updateFontWithTextState(StyledTTFont &font) {
uint tempStyle = 0;
if (_bold) {
tempStyle |= StyledTTFont::TTF_STYLE_BOLD;
}
if (_italic) {
tempStyle |= StyledTTFont::TTF_STYLE_ITALIC;
}
if (_underline) {
tempStyle |= StyledTTFont::TTF_STYLE_UNDERLINE;
}
if (_strikeout) {
tempStyle |= StyledTTFont::TTF_STYLE_STRIKETHROUGH;
}
if (_sharp) {
tempStyle |= StyledTTFont::TTF_STYLE_SHARP;
}
font.loadFont(_fontname, _size, tempStyle);
}
void TextRenderer::drawTextWithJustification(const Common::String &text, StyledTTFont &font, uint32 color, Graphics::Surface &dest, int lineY, TextJustification justify) {
switch (justify) {
case TEXT_JUSTIFY_LEFT :
font.drawString(&dest, text, 0, lineY, dest.w, color, Graphics::kTextAlignLeft);
break;
case TEXT_JUSTIFY_CENTER :
font.drawString(&dest, text, 0, lineY, dest.w, color, Graphics::kTextAlignCenter);
break;
case TEXT_JUSTIFY_RIGHT :
font.drawString(&dest, text, 0, lineY, dest.w, color, Graphics::kTextAlignRight);
break;
}
}
int32 TextRenderer::drawText(const Common::String &text, TextStyleState &state, Graphics::Surface &dest) {
StyledTTFont font(_engine);
state.updateFontWithTextState(font);
uint32 color = _engine->_resourcePixelFormat.RGBToColor(state._red, state._green, state._blue);
drawTextWithJustification(text, font, color, dest, 0, state._justification);
return font.getStringWidth(text);
}
struct TextSurface {
TextSurface(Graphics::Surface *surface, Common::Point surfaceOffset, uint lineNumber)
: _surface(surface),
_surfaceOffset(surfaceOffset),
_lineNumber(lineNumber) {
}
Graphics::Surface *_surface;
Common::Point _surfaceOffset;
uint _lineNumber;
};
void TextRenderer::drawTextWithWordWrapping(const Common::String &text, Graphics::Surface &dest, bool blackFrame) {
Common::Array<TextSurface> textSurfaces;
Common::Array<uint> lineWidths;
Common::Array<TextJustification> lineJustifications;
// Create the initial text state
TextStyleState currentState;
// Create an empty font and bind it to the state
StyledTTFont font(_engine);
currentState.updateFontWithTextState(font);
Common::String currentSentence; // Not a true 'grammatical' sentence. Rather, it's just a collection of words
Common::String currentWord;
int sentenceWidth = 0;
int wordWidth = 0;
int lineWidth = 0;
int lineHeight = font.getFontHeight();
uint currentLineNumber = 0u;
uint numSpaces = 0u;
int spaceWidth = 0;
// The pixel offset to the currentSentence
Common::Point sentencePixelOffset;
uint i = 0u;
uint stringlen = text.size();
// Parse entirety of supplied text
while (i < stringlen) {
// Style tag encountered?
if (text[i] == '<') {
// Flush the currentWord to the currentSentence
currentSentence += currentWord;
sentenceWidth += wordWidth;
// Reset the word variables
currentWord.clear();
wordWidth = 0;
// Parse the style tag
uint startTextPosition = i;
while (i < stringlen && text[i] != '>') {
++i;
}
uint endTextPosition = i;
uint32 textColor = currentState.getTextColor(_engine);
uint stateChanges = 0u;
if ((endTextPosition - startTextPosition - 1) > 0) {
stateChanges = currentState.parseStyle(Common::String(text.c_str() + startTextPosition + 1), endTextPosition - startTextPosition - 1);
}
if (stateChanges & (TEXT_CHANGE_FONT_TYPE | TEXT_CHANGE_FONT_STYLE)) {
// Use the last state to render out the current sentence
// Styles apply to the text 'after' them
if (!currentSentence.empty()) {
textSurfaces.push_back(TextSurface(font.renderSolidText(currentSentence, textColor), sentencePixelOffset, currentLineNumber));
lineWidth += sentenceWidth;
sentencePixelOffset.x += sentenceWidth;
// Reset the sentence variables
currentSentence.clear();
sentenceWidth = 0;
}
// Update the current state with the style information
currentState.updateFontWithTextState(font);
lineHeight = MAX(lineHeight, font.getFontHeight());
spaceWidth = font.getCharWidth(' ');
}
if (stateChanges & TEXT_CHANGE_NEWLINE) {
// If the current sentence has content, render it out
if (!currentSentence.empty()) {
textSurfaces.push_back(TextSurface(font.renderSolidText(currentSentence, textColor), sentencePixelOffset, currentLineNumber));
}
// Set line width
lineWidths.push_back(lineWidth + sentenceWidth - (numSpaces * spaceWidth));
currentSentence.clear();
sentenceWidth = 0;
// Update the offsets
sentencePixelOffset.x = 0u;
sentencePixelOffset.y += lineHeight;
// Reset the line variables
lineHeight = font.getFontHeight();
lineWidth = 0;
++currentLineNumber;
lineJustifications.push_back(currentState._justification);
}
if (stateChanges & TEXT_CHANGE_HAS_STATE_BOX) {
Common::String temp = Common::String::format("%d", _engine->getScriptManager()->getStateValue(currentState._statebox));
wordWidth += font.getStringWidth(temp);
// If the word causes the line to overflow, render the sentence and start a new line
if (lineWidth + sentenceWidth + wordWidth > dest.w) {
textSurfaces.push_back(TextSurface(font.renderSolidText(currentSentence, textColor), sentencePixelOffset, currentLineNumber));
// Set line width
lineWidths.push_back(lineWidth + sentenceWidth - (numSpaces * spaceWidth));
currentSentence.clear();
sentenceWidth = 0;
// Update the offsets
sentencePixelOffset.x = 0u;
sentencePixelOffset.y += lineHeight;
// Reset the line variables
lineHeight = font.getFontHeight();
lineWidth = 0;
++currentLineNumber;
lineJustifications.push_back(currentState._justification);
}
}
} else {
currentWord += text[i];
wordWidth += font.getCharWidth(text[i]);
if (text[i] == ' ') {
// When we hit the first space, flush the current word to the sentence
if (!currentWord.empty()) {
currentSentence += currentWord;
sentenceWidth += wordWidth;
currentWord.clear();
wordWidth = 0;
}
// We track the number of spaces so we can disregard their width in lineWidth calculations
++numSpaces;
} else {
// If the word causes the line to overflow, render the sentence and start a new line
if (lineWidth + sentenceWidth + wordWidth > dest.w) {
// Only render out content
if (!currentSentence.empty()) {
textSurfaces.push_back(TextSurface(font.renderSolidText(currentSentence, currentState.getTextColor(_engine)), sentencePixelOffset, currentLineNumber));
}
// Set line width
lineWidths.push_back(lineWidth + sentenceWidth - (numSpaces * spaceWidth));
currentSentence.clear();
sentenceWidth = 0;
// Update the offsets
sentencePixelOffset.x = 0u;
sentencePixelOffset.y += lineHeight;
// Reset the line variables
lineHeight = font.getFontHeight();
lineWidth = 0;
++currentLineNumber;
lineJustifications.push_back(currentState._justification);
}
numSpaces = 0u;
}
}
i++;
}
// Render out any remaining words/sentences
if (!currentWord.empty() || !currentSentence.empty()) {
currentSentence += currentWord;
sentenceWidth += wordWidth;
textSurfaces.push_back(TextSurface(font.renderSolidText(currentSentence, currentState.getTextColor(_engine)), sentencePixelOffset, currentLineNumber));
}
lineWidths.push_back(lineWidth + sentenceWidth);
lineJustifications.push_back(currentState._justification);
for (Common::Array<TextSurface>::iterator iter = textSurfaces.begin(); iter != textSurfaces.end(); ++iter) {
Common::Rect empty;
int16 Xpos = iter->_surfaceOffset.x;
switch (lineJustifications[iter->_lineNumber]) {
case TEXT_JUSTIFY_LEFT :
break;
case TEXT_JUSTIFY_CENTER :
Xpos += ((dest.w - lineWidths[iter->_lineNumber]) / 2);
break;
case TEXT_JUSTIFY_RIGHT :
Xpos += dest.w - lineWidths[iter->_lineNumber];
break;
}
if (blackFrame)
_engine->getRenderManager()->blitSurfaceToSurface(*iter->_surface, empty, dest, Xpos, iter->_surfaceOffset.y);
else
_engine->getRenderManager()->blitSurfaceToSurface(*iter->_surface, empty, dest, Xpos, iter->_surfaceOffset.y, 0);
// Release memory
iter->_surface->free();
delete iter->_surface;
}
}
Common::U32String readWideLine(Common::SeekableReadStream &stream) {
Common::U32String asciiString;
while (true) {
uint32 value = stream.readUint16LE();
if (stream.eos())
break;
// Check for CRLF
if (value == 0x0A0D) {
// Read in the extra NULL char
stream.readByte(); // \0
// End of the line. Break
break;
}
asciiString += value;
}
return asciiString;
}
} // End of namespace ZVision

View File

@@ -0,0 +1,89 @@
/* 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 ZVISION_TEXT_H
#define ZVISION_TEXT_H
#include "zvision/zvision.h"
#include "zvision/text/truetype_font.h"
namespace ZVision {
class ZVision;
enum TextJustification {
TEXT_JUSTIFY_CENTER = 0,
TEXT_JUSTIFY_LEFT = 1,
TEXT_JUSTIFY_RIGHT = 2
};
enum TextChange {
TEXT_CHANGE_NONE = 0x0,
TEXT_CHANGE_FONT_TYPE = 0x1,
TEXT_CHANGE_FONT_STYLE = 0x2,
TEXT_CHANGE_NEWLINE = 0x4,
TEXT_CHANGE_HAS_STATE_BOX = 0x8
};
class TextStyleState {
public:
TextStyleState();
TextChange parseStyle(const Common::String &str, int16 len);
void readAllStyles(const Common::String &txt);
void updateFontWithTextState(StyledTTFont &font);
uint32 getTextColor(ZVision *engine) {
return engine->_resourcePixelFormat.RGBToColor(_red, _green, _blue);
}
public:
Common::String _fontname;
TextJustification _justification;
int16 _size;
uint8 _red; // 0-255
uint8 _green; // 0-255
uint8 _blue; // 0-255
bool _italic;
bool _bold;
bool _underline;
bool _strikeout;
int32 _statebox;
bool _sharp;
};
class TextRenderer {
public:
TextRenderer(ZVision *engine): _engine(engine) {};
void drawTextWithJustification(const Common::String &text, StyledTTFont &font, uint32 color, Graphics::Surface &dest, int lineY, TextJustification jusification);
int32 drawText(const Common::String &text, TextStyleState &state, Graphics::Surface &dest);
void drawTextWithWordWrapping(const Common::String &text, Graphics::Surface &dest, bool blackFrame = false);
private:
ZVision *_engine;
};
Common::U32String readWideLine(Common::SeekableReadStream &stream);
} // End of namespace ZVision
#endif

View File

@@ -0,0 +1,229 @@
/* 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/debug.h"
#include "common/file.h"
#include "common/scummsys.h"
#include "common/system.h"
#include "common/unicode-bidi.h"
#include "common/ustr.h"
#include "common/compression/unzip.h"
#include "graphics/font.h"
#include "graphics/surface.h"
#include "graphics/fonts/ttf.h"
#include "zvision/zvision.h"
#include "zvision/graphics/render_manager.h"
#include "zvision/text/truetype_font.h"
namespace ZVision {
const FontStyle systemFonts[] = {
{ "*times new roman*", "times", "LiberationSerif" },
{ "*times*", "times", "LiberationSerif" },
{ "*century schoolbook*", "censcbk", "LiberationSerif" },
{ "*garamond*", "gara", "LiberationSerif" },
{ "*courier new*", "cour", "LiberationMono" },
{ "*courier*", "cour", "LiberationMono" },
{ "*ZorkDeath*", "cour", "LiberationMono" },
{ "*arial*", "arial", "LiberationSans" },
{ "*ZorkNormal*", "arial", "LiberationSans" }
};
const FontStyle getSystemFont(int fontIndex) {
return systemFonts[fontIndex];
}
StyledTTFont::StyledTTFont(ZVision *engine) {
_engine = engine;
_style = 0;
_font = nullptr;
_lineHeight = 0;
}
StyledTTFont::~StyledTTFont() {
delete _font;
}
bool StyledTTFont::loadFont(const Common::String &fontName, int32 point, uint style) {
// Don't re-load the font if we've already loaded it
// We have to check for empty so we can default to Arial
if (!fontName.empty() && _fontName.equalsIgnoreCase(fontName) && _lineHeight == point && _style == style) {
return true;
}
_style = style;
Common::String newFontName;
Common::String liberationFontName;
for (int i = 0; i < FONT_COUNT; i++) {
FontStyle curFont = getSystemFont(i);
if (fontName.matchString(curFont.zorkFont, true)) {
newFontName = curFont.fontBase;
liberationFontName = curFont.liberationFontBase;
if ((_style & TTF_STYLE_BOLD) && (_style & TTF_STYLE_ITALIC)) {
newFontName += "bi";
liberationFontName += "-BoldItalic";
} else if (_style & TTF_STYLE_BOLD) {
newFontName += "bd";
liberationFontName += "-Bold";
} else if (_style & TTF_STYLE_ITALIC) {
newFontName += "i";
liberationFontName += "-Italic";
} else {
liberationFontName += "-Regular";
}
newFontName += ".ttf";
liberationFontName += ".ttf";
break;
}
}
if (newFontName.empty()) {
warning("Could not identify font: %s. Reverting to Arial", fontName.c_str());
newFontName = "arial.ttf";
liberationFontName = "LiberationSans-Regular.ttf";
}
bool sharp = (_style & TTF_STYLE_SHARP) == TTF_STYLE_SHARP;
Common::File *file = new Common::File();
Graphics::Font *newFont;
if (!file->open(Common::Path(newFontName)) &&
!file->open(Common::Path(liberationFontName))) {
newFont = Graphics::loadTTFFontFromArchive(liberationFontName, point, Graphics::kTTFSizeModeCell, 0, 0, (sharp ? Graphics::kTTFRenderModeMonochrome : Graphics::kTTFRenderModeNormal));
delete file;
} else {
newFont = Graphics::loadTTFFont(file, DisposeAfterUse::YES, point, Graphics::kTTFSizeModeCell, 0, 0, (sharp ? Graphics::kTTFRenderModeMonochrome : Graphics::kTTFRenderModeNormal));
}
if (newFont == nullptr) {
return false;
}
delete _font;
_font = newFont;
_fontName = fontName;
_lineHeight = point;
return true;
}
int StyledTTFont::getFontHeight() {
if (_font)
return _font->getFontHeight();
return 0;
}
int StyledTTFont::getMaxCharWidth() {
if (_font)
return _font->getMaxCharWidth();
return 0;
}
int StyledTTFont::getCharWidth(uint16 chr) {
if (_font)
return _font->getCharWidth(chr);
return 0;
}
int StyledTTFont::getKerningOffset(byte left, byte right) {
if (_font)
return _font->getKerningOffset(left, right);
return 0;
}
void StyledTTFont::drawChar(Graphics::Surface *dst, uint16 chr, int x, int y, uint32 color) {
if (_font) {
_font->drawChar(dst, chr, x, y, color);
if (_style & TTF_STYLE_UNDERLINE) {
int16 pos = (int16)floor(_font->getFontHeight() * 0.87);
int thk = MAX((int)(_font->getFontHeight() * 0.05), 1);
dst->fillRect(Common::Rect(x, y + pos, x + _font->getCharWidth(chr), y + pos + thk), color);
}
if (_style & TTF_STYLE_STRIKETHROUGH) {
int16 pos = (int16)floor(_font->getFontHeight() * 0.60);
int thk = MAX((int)(_font->getFontHeight() * 0.05), 1);
dst->fillRect(Common::Rect(x, y + pos, x + _font->getCharWidth(chr), y + pos + thk), color);
}
}
}
void StyledTTFont::drawString(Graphics::Surface *dst, const Common::String &str, int x, int y, int w, uint32 color, Graphics::TextAlign align) {
if (_font) {
Common::U32String u32str = Common::convertUtf8ToUtf32(str);
_font->drawString(dst, Common::convertBiDiU32String(u32str).visual, x, y, w, color, align);
if (_style & TTF_STYLE_UNDERLINE) {
int16 pos = (int16)floor(_font->getFontHeight() * 0.87);
int16 wd = MIN(_font->getStringWidth(u32str), w);
int16 stX = x;
if (align == Graphics::kTextAlignCenter)
stX += (w - wd) / 2;
else if (align == Graphics::kTextAlignRight)
stX += (w - wd);
int thk = MAX((int)(_font->getFontHeight() * 0.05), 1);
dst->fillRect(Common::Rect(stX, y + pos, stX + wd, y + pos + thk), color);
}
if (_style & TTF_STYLE_STRIKETHROUGH) {
int16 pos = (int16)floor(_font->getFontHeight() * 0.60);
int16 wd = MIN(_font->getStringWidth(u32str), w);
int16 stX = x;
if (align == Graphics::kTextAlignCenter)
stX += (w - wd) / 2;
else if (align == Graphics::kTextAlignRight)
stX += (w - wd);
int thk = MAX((int)(_font->getFontHeight() * 0.05), 1);
dst->fillRect(Common::Rect(stX, y + pos, stX + wd, y + pos + thk), color);
}
}
}
int StyledTTFont::getStringWidth(const Common::String &str) {
if (_font)
return _font->getStringWidth(str);
return 0;
}
Graphics::Surface *StyledTTFont::renderSolidText(const Common::String &str, uint32 color) {
Graphics::Surface *tmp = new Graphics::Surface;
if (_font) {
int16 w = _font->getStringWidth(str);
if (w && w < 1024) {
tmp->create(w, _font->getFontHeight(), _engine->_resourcePixelFormat);
drawString(tmp, str, 0, 0, w, color);
}
}
return tmp;
}
} // End of namespace ZVision

View File

@@ -0,0 +1,89 @@
/* 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/>.
*
*/
// This file is based on engines/wintermute/base/fonts/base_font_truetype.h/.cpp
#ifndef ZVISION_TRUETYPE_FONT_H
#define ZVISION_TRUETYPE_FONT_H
#include "graphics/font.h"
#include "graphics/pixelformat.h"
namespace Graphics {
struct Surface;
}
namespace ZVision {
struct FontStyle {
const char *zorkFont;
const char *fontBase;
const char *liberationFontBase;
};
#define FONT_COUNT 9
class ZVision;
// Styled TTF
class StyledTTFont {
public:
StyledTTFont(ZVision *engine);
~StyledTTFont();
enum {
TTF_STYLE_BOLD = 0x01,
TTF_STYLE_ITALIC = 0x02,
TTF_STYLE_UNDERLINE = 0x04,
TTF_STYLE_STRIKETHROUGH = 0x08,
TTF_STYLE_SHARP = 0x10
};
private:
ZVision *_engine;
Graphics::Font *_font;
int _lineHeight;
uint _style;
Common::String _fontName;
public:
bool loadFont(const Common::String &fontName, int32 point, uint style);
int getFontHeight();
int getMaxCharWidth();
int getCharWidth(uint16 chr);
int getKerningOffset(byte left, byte right);
void drawChar(Graphics::Surface *dst, uint16 chr, int x, int y, uint32 color);
void drawString(Graphics::Surface *dst, const Common::String &str, int x, int y, int w, uint32 color, Graphics::TextAlign align = Graphics::kTextAlignLeft);
int getStringWidth(const Common::String &str);
Graphics::Surface *renderSolidText(const Common::String &str, uint32 color);
bool isLoaded() {
return _font != NULL;
};
};
} // End of namespace ZVision
#endif

View File

@@ -0,0 +1,314 @@
/* 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/debug.h"
#include "common/endian.h"
#include "common/file.h"
#include "common/scummsys.h"
#include "common/str.h"
#include "common/textconsole.h"
#include "zvision/detection.h"
#include "zvision/video/rlf_decoder.h"
namespace ZVision {
RLFDecoder::~RLFDecoder() {
close();
}
bool RLFDecoder::loadStream(Common::SeekableReadStream *stream) {
debugC(5, kDebugVideo, "loadStream()");
close();
bool isValid = false;
// Check if the stream is valid
if (stream && !stream->err() && stream->readUint32BE() == MKTAG('F', 'E', 'L', 'R')) {
addTrack(new RLFVideoTrack(stream));
isValid = true;
} else {
warning("Invalid rlf stream");
}
debugC(5, kDebugVideo, "~loadStream()");
return isValid;
}
RLFDecoder::RLFVideoTrack::RLFVideoTrack(Common::SeekableReadStream *stream)
: _readStream(stream),
_lastFrameRead(0),
_frameCount(0),
_width(0),
_height(0),
_frameTime(0),
_frames(0),
_displayedFrame(-1),
_frameBufferByteSize(0) {
if (!readHeader()) {
warning("Not a RLF animation file. Wrong magic number");
return;
}
_currentFrameBuffer.create(_width, _height, getPixelFormat());
_frameBufferByteSize = _width * _height * sizeof(uint16);
_frames = new Frame[_frameCount];
// Read in each frame
for (uint i = 0; i < _frameCount; ++i) {
_frames[i] = readNextFrame();
}
}
RLFDecoder::RLFVideoTrack::~RLFVideoTrack() {
for (uint i = 0; i < _frameCount; ++i) {
delete[] _frames[i].encodedData;
}
delete[] _frames;
delete _readStream;
_currentFrameBuffer.free();
}
bool RLFDecoder::RLFVideoTrack::readHeader() {
// Read the header
_readStream->readUint32LE(); // Size1
_readStream->readUint32LE(); // Unknown1
_readStream->readUint32LE(); // Unknown2
_frameCount = _readStream->readUint32LE(); // Frame count
// Since we don't need any of the data, we can just seek right to the
// entries we need rather than read in all the individual entries.
_readStream->seek(136, SEEK_CUR);
//// Read CIN header
//_readStream->readUint32BE(); // Magic number FNIC
//_readStream->readUint32LE(); // Size2
//_readStream->readUint32LE(); // Unknown3
//_readStream->readUint32LE(); // Unknown4
//_readStream->readUint32LE(); // Unknown5
//_readStream->seek(0x18, SEEK_CUR); // VRLE
//_readStream->readUint32LE(); // LRVD
//_readStream->readUint32LE(); // Unknown6
//_readStream->seek(0x18, SEEK_CUR); // HRLE
//_readStream->readUint32LE(); // ELHD
//_readStream->readUint32LE(); // Unknown7
//_readStream->seek(0x18, SEEK_CUR); // HKEY
//_readStream->readUint32LE(); // ELRH
//// Read MIN info header
//_readStream->readUint32BE(); // Magic number FNIM
//_readStream->readUint32LE(); // Size3
//_readStream->readUint32LE(); // OEDV
//_readStream->readUint32LE(); // Unknown8
//_readStream->readUint32LE(); // Unknown9
//_readStream->readUint32LE(); // Unknown10
_width = _readStream->readUint32LE(); // Width
_height = _readStream->readUint32LE(); // Height
// Read time header
_readStream->readUint32BE(); // Magic number EMIT
_readStream->readUint32LE(); // Size4
_readStream->readUint32LE(); // Unknown11
_frameTime = _readStream->readUint32LE() / 10; // Frame time in microseconds
return true;
}
RLFDecoder::RLFVideoTrack::Frame RLFDecoder::RLFVideoTrack::readNextFrame() {
RLFDecoder::RLFVideoTrack::Frame frame;
_readStream->readUint32BE(); // Magic number MARF
uint32 size = _readStream->readUint32LE(); // Size
_readStream->readUint32LE(); // Unknown1
_readStream->readUint32LE(); // Unknown2
uint32 type = _readStream->readUint32BE(); // Either ELHD or ELRH
uint32 headerSize = _readStream->readUint32LE(); // Offset from the beginning of this frame to the frame data. Should always be 28
_readStream->readUint32LE(); // Unknown3
frame.encodedSize = size - headerSize;
frame.encodedData = new int8[frame.encodedSize];
_readStream->read(frame.encodedData, frame.encodedSize);
if (type == MKTAG('E', 'L', 'H', 'D')) {
frame.type = Masked;
} else if (type == MKTAG('E', 'L', 'R', 'H')) {
frame.type = Simple;
_completeFrames.push_back(_lastFrameRead);
} else {
warning("Frame %u doesn't have type that can be decoded", _lastFrameRead);
}
_lastFrameRead++;
return frame;
}
bool RLFDecoder::RLFVideoTrack::seek(const Audio::Timestamp &time) {
uint frame = getFrameAtTime(time);
assert(frame < _frameCount);
if ((uint)_displayedFrame == frame)
return true;
int closestFrame = _displayedFrame;
int distance = (int)frame - closestFrame;
if (distance < 0) {
for (uint i = 0; i < _completeFrames.size(); ++i) {
if (_completeFrames[i] > frame)
break;
closestFrame = _completeFrames[i];
}
} else {
for (uint i = 0; i < _completeFrames.size(); ++i) {
int newDistance = (int)frame - (int)(_completeFrames[i]);
if (newDistance < 0)
break;
if (newDistance < distance) {
closestFrame = _completeFrames[i];
distance = newDistance;
}
}
}
for (uint i = closestFrame; i < frame; ++i) {
applyFrameToCurrent(i);
}
_displayedFrame = frame - 1;
return true;
}
const Graphics::Surface *RLFDecoder::RLFVideoTrack::decodeNextFrame() {
if (_displayedFrame >= (int)_frameCount)
return NULL;
_displayedFrame++;
applyFrameToCurrent(_displayedFrame);
return &_currentFrameBuffer;
}
void RLFDecoder::RLFVideoTrack::applyFrameToCurrent(uint frameNumber) {
if (_frames[frameNumber].type == Masked) {
decodeMaskedRunLengthEncoding(_frames[frameNumber].encodedData, (int8 *)_currentFrameBuffer.getPixels(), _frames[frameNumber].encodedSize, _frameBufferByteSize);
} else if (_frames[frameNumber].type == Simple) {
decodeSimpleRunLengthEncoding(_frames[frameNumber].encodedData, (int8 *)_currentFrameBuffer.getPixels(), _frames[frameNumber].encodedSize, _frameBufferByteSize);
}
}
void RLFDecoder::RLFVideoTrack::decodeMaskedRunLengthEncoding(int8 *source, int8 *dest, uint32 sourceSize, uint32 destSize) const {
uint32 sourceOffset = 0;
uint32 destOffset = 0;
int16 numberOfCopy = 0;
while (sourceOffset < sourceSize) {
int8 numberOfSamples = source[sourceOffset];
sourceOffset++;
// If numberOfSamples is negative, the next abs(numberOfSamples) samples should
// be copied directly from source to dest
if (numberOfSamples < 0) {
numberOfCopy = -numberOfSamples;
while (numberOfCopy > 0) {
if (sourceOffset + 1 >= sourceSize) {
return;
} else if (destOffset + 1 >= destSize) {
debugC(3, kDebugVideo, "Frame decoding overflow\n\tsourceOffset=%u\tsourceSize=%u\n\tdestOffset=%u\tdestSize=%u", sourceOffset, sourceSize, destOffset, destSize);
return;
}
WRITE_UINT16(dest + destOffset, READ_LE_UINT16(source + sourceOffset));
sourceOffset += 2;
destOffset += 2;
numberOfCopy--;
}
// If numberOfSamples is >= 0, move destOffset forward ((numberOfSamples * 2) + 2)
// This function assumes the dest buffer has been memset with 0's.
} else {
if (sourceOffset + 1 >= sourceSize) {
return;
} else if (destOffset + 1 >= destSize) {
debugC(3, kDebugVideo, "Frame decoding overflow\n\tsourceOffset=%u\tsourceSize=%u\n\tdestOffset=%u\tdestSize=%u", sourceOffset, sourceSize, destOffset, destSize);
return;
}
destOffset += (numberOfSamples * 2) + 2;
}
}
}
void RLFDecoder::RLFVideoTrack::decodeSimpleRunLengthEncoding(int8 *source, int8 *dest, uint32 sourceSize, uint32 destSize) const {
uint32 sourceOffset = 0;
uint32 destOffset = 0;
int16 numberOfCopy = 0;
while (sourceOffset < sourceSize) {
int8 numberOfSamples = source[sourceOffset];
sourceOffset++;
// If numberOfSamples is negative, the next abs(numberOfSamples) samples should
// be copied directly from source to dest
if (numberOfSamples < 0) {
numberOfCopy = -numberOfSamples;
while (numberOfCopy > 0) {
if (sourceOffset + 1 >= sourceSize) {
return;
} else if (destOffset + 1 >= destSize) {
debugC(3, kDebugVideo, "Frame decoding overflow\n\tsourceOffset=%u\tsourceSize=%u\n\tdestOffset=%u\tdestSize=%u", sourceOffset, sourceSize, destOffset, destSize);
return;
}
WRITE_UINT16(dest + destOffset, READ_LE_UINT16(source + sourceOffset));
sourceOffset += 2;
destOffset += 2;
numberOfCopy--;
}
// If numberOfSamples is >= 0, copy one sample from source to the
// next (numberOfSamples + 2) dest spots
} else {
if (sourceOffset + 1 >= sourceSize) {
return;
}
uint16 sampleColor = READ_LE_UINT16(source + sourceOffset);
sourceOffset += 2;
numberOfCopy = numberOfSamples + 2;
while (numberOfCopy > 0) {
if (destOffset + 1 >= destSize) {
debugC(3, kDebugVideo, "Frame decoding overflow\n\tsourceOffset=%u\tsourceSize=%u\n\tdestOffset=%u\tdestSize=%u", sourceOffset, sourceSize, destOffset, destSize);
return;
}
WRITE_UINT16(dest + destOffset, sampleColor);
destOffset += 2;
numberOfCopy--;
}
}
}
}
} // End of namespace ZVision

Some files were not shown because too many files have changed in this diff Show More