scummvm/engines/cryomni3d/cryomni3d.cpp

509 lines
13 KiB
C++

/* 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 2
* 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, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
*/
#include "common/scummsys.h"
#include "common/error.h"
#include "common/system.h"
#include "common/textconsole.h"
#include "common/debug-channels.h"
#include "common/events.h"
#include "common/file.h"
#include "engines/util.h"
#include "audio/mixer.h"
#include "graphics/palette.h"
#include "cryomni3d/cryomni3d.h"
#include "cryomni3d/datstream.h"
#include "cryomni3d/image/hlz.h"
#include "video/hnm_decoder.h"
namespace CryOmni3D {
CryOmni3DEngine::CryOmni3DEngine(OSystem *syst,
const CryOmni3DGameDescription *gamedesc) : Engine(syst), _gameDescription(gamedesc),
_canLoadSave(false), _fontManager(), _sprites(), _dragStatus(kDragStatus_NoDrag), _lastMouseButton(0),
_autoRepeatNextEvent(uint(-1)), _hnmHasClip(false) {
if (!_mixer->isReady()) {
error("Sound initialization failed");
}
// Setup mixer
syncSoundSettings();
unlockPalette();
}
CryOmni3DEngine::~CryOmni3DEngine() {
}
Common::Error CryOmni3DEngine::run() {
return Common::kNoError;
}
void CryOmni3DEngine::pauseEngineIntern(bool pause) {
Engine::pauseEngineIntern(pause);
/*
if (pause) {
_video->pauseVideos();
} else {
_video->resumeVideos();
_system->updateScreen();
}
*/
}
DATSeekableStream *CryOmni3DEngine::getStaticData(uint32 gameId, uint16 version) const {
Common::File *datFile = new Common::File();
if (!datFile->open("cryomni3d.dat")) {
delete datFile;
error("Failed to open cryomni3d.dat file");
return nullptr;
}
DATSeekableStream *gameStream = DATSeekableStream::getGame(datFile, gameId, version, getLanguage(),
getPlatform());
if (!gameStream) {
delete datFile;
error("Failed to find game in cryomni3d.dat file");
return nullptr;
}
return gameStream;
}
Common::String CryOmni3DEngine::prepareFileName(const Common::String &baseName,
const char *const *extensions) const {
Common::String fname(baseName);
int lastDotPos = fname.size() - 1;
for (; lastDotPos >= 0; --lastDotPos) {
if (fname[lastDotPos] == '.') {
break;
}
}
int extBegin;
if (lastDotPos > -1) {
extBegin = lastDotPos + 1;
fname.erase(extBegin);
} else {
fname += ".";
extBegin = fname.size();
}
while (*extensions != nullptr) {
fname += *extensions;
debug("Trying file %s", fname.c_str());
if (Common::File::exists(fname)) {
return fname;
}
fname.erase(extBegin);
extensions++;
}
fname.deleteLastChar();
warning("Failed to find file %s/%s", baseName.c_str(), fname.c_str());
return baseName;
}
void CryOmni3DEngine::playHNM(const Common::String &filename, Audio::Mixer::SoundType soundType,
HNMCallback beforeDraw, HNMCallback afterDraw) {
const char *const extensions[] = { "hns", "hnm", "ubb", nullptr };
Common::String fname(prepareFileName(filename, extensions));
byte *currentPalette = new byte[256 * 3];
g_system->getPaletteManager()->grabPalette(currentPalette, 0, 256);
// Pass the ownership of currentPalette to HNMDecoder
Video::VideoDecoder *videoDecoder = new Video::HNMDecoder(false, currentPalette);
videoDecoder->setSoundType(soundType);
if (!videoDecoder->loadFile(fname)) {
warning("Failed to open movie file %s/%s", filename.c_str(), fname.c_str());
delete videoDecoder;
return;
}
videoDecoder->start();
uint16 width = videoDecoder->getWidth();
uint16 height = videoDecoder->getHeight();
bool skipVideo = false;
uint frameNum = 0;
while (!shouldAbort() && !videoDecoder->endOfVideo() && !skipVideo) {
if (videoDecoder->needsUpdate()) {
const Graphics::Surface *frame = videoDecoder->decodeNextFrame();
if (frame) {
if (videoDecoder->hasDirtyPalette()) {
const byte *palette = videoDecoder->getPalette();
setPalette(palette, 0, 256);
}
if (beforeDraw) {
(this->*beforeDraw)(frameNum);
}
if (_hnmHasClip) {
Common::Rect rct(width, height);
rct.clip(_hnmClipping);
g_system->copyRectToScreen(frame->getPixels(), frame->pitch, rct.left, rct.top, rct.width(),
rct.height());
} else {
g_system->copyRectToScreen(frame->getPixels(), frame->pitch, 0, 0, width, height);
}
if (afterDraw) {
(this->*afterDraw)(frameNum);
}
frameNum++;
}
}
g_system->updateScreen();
g_system->delayMillis(10);
if (pollEvents() && checkKeysPressed()) {
skipVideo = true;
}
}
delete videoDecoder;
}
Image::ImageDecoder *CryOmni3DEngine::loadHLZ(const Common::String &filename) {
Common::String fname(prepareFileName(filename, "hlz"));
Common::File file;
if (!file.open(fname)) {
warning("Failed to open hlz file %s/%s", filename.c_str(), fname.c_str());
return nullptr;
}
Image::ImageDecoder *imageDecoder = new Image::HLZFileDecoder();
if (!imageDecoder->loadStream(file)) {
warning("Failed to open hlz file %s", fname.c_str());
delete imageDecoder;
imageDecoder = 0;
return nullptr;
}
return imageDecoder;
}
bool CryOmni3DEngine::displayHLZ(const Common::String &filename, uint32 timeout) {
Image::ImageDecoder *imageDecoder = loadHLZ(filename);
if (!imageDecoder) {
return false;
}
if (imageDecoder->hasPalette()) {
const byte *palette = imageDecoder->getPalette();
setPalette(palette, imageDecoder->getPaletteStartIndex(), imageDecoder->getPaletteColorCount());
}
const Graphics::Surface *frame = imageDecoder->getSurface();
g_system->copyRectToScreen(frame->getPixels(), frame->pitch, 0, 0, frame->w, frame->h);
g_system->updateScreen();
uint32 end;
if (timeout == uint(-1)) {
end = uint(-1);
} else {
end = g_system->getMillis() + timeout;
}
bool exitImg = false;
while (!shouldAbort() && !exitImg && g_system->getMillis() < end) {
if (pollEvents()) {
if (checkKeysPressed() || getCurrentMouseButton() == 1) {
exitImg = true;
}
}
g_system->updateScreen();
g_system->delayMillis(10);
}
delete imageDecoder;
return exitImg || shouldAbort();
}
void CryOmni3DEngine::setCursor(const Graphics::Cursor &cursor) const {
CursorMan.replaceCursor(&cursor);
}
void CryOmni3DEngine::setCursor(uint cursorId) const {
const Graphics::Cursor &cursor = _sprites.getCursor(cursorId);
CursorMan.replaceCursor(&cursor);
}
bool CryOmni3DEngine::pollEvents() {
Common::Event event;
int buttonMask;
bool hasEvents = false;
// Don't take into transitional clicks for the drag
buttonMask = g_system->getEventManager()->getButtonState();
uint oldMouseButton;
if (buttonMask & 0x1) {
oldMouseButton = 1;
} else if (buttonMask & 0x2) {
oldMouseButton = 2;
} else {
oldMouseButton = 0;
}
int transitionalMask = 0;
while (g_system->getEventManager()->pollEvent(event)) {
if (event.type == Common::EVENT_KEYDOWN) {
_keysPressed.push(event.kbd);
} else if (event.type == Common::EVENT_LBUTTONDOWN) {
transitionalMask |= Common::EventManager::LBUTTON;
} else if (event.type == Common::EVENT_RBUTTONDOWN) {
transitionalMask |= Common::EventManager::RBUTTON;
}
hasEvents = true;
}
// Merge current button state with any buttons pressed since last poll
// That's to avoid missed clicks
buttonMask = g_system->getEventManager()->getButtonState() |
transitionalMask;
if (buttonMask & 0x1) {
_lastMouseButton = 1;
} else if (buttonMask & 0x2) {
_lastMouseButton = 2;
} else {
_lastMouseButton = 0;
}
_dragStatus = kDragStatus_NoDrag;
uint currentMouseButton = getCurrentMouseButton();
if (!oldMouseButton && currentMouseButton == 1) {
// Starting the drag
_dragStatus = kDragStatus_Pressed;
_dragStart = getMousePos();
} else if (oldMouseButton == 1) {
// We were already pressing
if (currentMouseButton == 1) {
// We are still pressing
Common::Point delta = _dragStart - getMousePos();
if (ABS(delta.x) > 2 || ABS(delta.y) > 2) {
// We moved from the start point
_dragStatus = kDragStatus_Dragging;
} else if (_autoRepeatNextEvent != uint(-1)) {
// Check for auto repeat duration
if (_autoRepeatNextEvent < g_system->getMillis()) {
_dragStatus = kDragStatus_Pressed;
}
}
} else {
// We just finished dragging
_dragStatus = kDragStatus_Finished;
// Cancel auto repeat
_autoRepeatNextEvent = uint(-1);
}
}
// Else we weren't dragging and still aren't
return hasEvents;
}
void CryOmni3DEngine::setAutoRepeatClick(uint millis) {
_autoRepeatNextEvent = g_system->getMillis() + millis;
}
void CryOmni3DEngine::waitMouseRelease() {
while (getCurrentMouseButton() != 0 && !shouldAbort()) {
pollEvents();
g_system->updateScreen();
g_system->delayMillis(10);
}
}
void CryOmni3DEngine::setMousePos(const Common::Point &point) {
g_system->warpMouse(point.x, point.y);
// Ensure to update mouse position in event manager
pollEvents();
}
Common::Point CryOmni3DEngine::getMousePos() {
return g_system->getEventManager()->getMousePos();
}
Common::KeyState CryOmni3DEngine::getNextKey() {
if (_keysPressed.empty()) {
return Common::KeyState();
} else {
return _keysPressed.pop();
}
}
bool CryOmni3DEngine::checkKeysPressed() {
Common::KeyCode kc = getNextKey().keycode;
if (kc != Common::KEYCODE_INVALID) {
clearKeys();
return true;
} else {
return false;
}
}
bool CryOmni3DEngine::checkKeysPressed(uint numKeys, ...) {
bool found = false;
Common::KeyCode kc = getNextKey().keycode;
while (!found && kc != Common::KEYCODE_INVALID) {
va_list va;
va_start(va, numKeys);
for (uint i = 0; i < numKeys; i++) {
// Compiler says that KeyCode is promoted to int, so we need this ugly cast
Common::KeyCode match = (Common::KeyCode) va_arg(va, int);
if (match == kc) {
found = true;
break;
}
}
va_end(va);
kc = getNextKey().keycode;
}
clearKeys();
return found;
}
void CryOmni3DEngine::copySubPalette(byte *dst, const byte *src, uint start, uint num) {
assert(start < 256);
assert(start + num < 256);
memcpy(&dst[3 * start], &src[3 * start], 3 * num * sizeof(*dst));
}
void CryOmni3DEngine::setPalette(const byte *colors, uint start, uint num) {
if (start < _lockPaletteStartRW) {
colors = colors + 3 * (_lockPaletteStartRW - start);
start = _lockPaletteStartRW;
}
uint end = start + num - 1;
if (end > _lockPaletteEndRW) {
num = num - (end - _lockPaletteEndRW);
end = _lockPaletteEndRW;
}
g_system->getPaletteManager()->setPalette(colors, start, num);
// Don't update screen there: palette will be updated with next updateScreen call
}
void CryOmni3DEngine::fadeOutPalette() {
byte palOut[256 * 3];
uint16 palWork[256 * 3];
uint16 delta[256 * 3];
g_system->getPaletteManager()->grabPalette(palOut, 0, 256);
for (uint i = 0; i < 256 * 3; i++) {
palWork[i] = palOut[i] << 8;
delta[i] = palWork[i] / 25;
}
for (uint step = 0; step < 25 && !shouldAbort(); step++) {
for (uint i = 0; i < 256 * 3; i++) {
palWork[i] -= delta[i];
palOut[i] = palWork[i] >> 8;
}
setPalette(palOut, 0, 256);
// Wait 50ms between each steps but refresh screen every 10ms
for (uint i = 0; i < 5; i++) {
pollEvents();
g_system->updateScreen();
g_system->delayMillis(10);
}
}
setBlackPalette();
pollEvents();
g_system->updateScreen();
clearKeys();
}
void CryOmni3DEngine::fadeInPalette(const byte *palette) {
byte palOut[256 * 3];
uint16 palWork[256 * 3];
uint16 delta[256 * 3];
memset(palOut, 0, sizeof(palOut));
memset(palWork, 0, sizeof(palWork));
for (uint i = 0; i < 256 * 3; i++) {
delta[i] = (palette[i] << 8) / 25;
}
setBlackPalette();
for (uint step = 0; step < 25 && !shouldAbort(); step++) {
for (uint i = 0; i < 256 * 3; i++) {
palWork[i] += delta[i];
palOut[i] = palWork[i] >> 8;
}
setPalette(palOut, 0, 256);
// Wait 50ms between each steps but refresh screen every 10ms
for (uint i = 0; i < 5; i++) {
pollEvents();
g_system->updateScreen();
g_system->delayMillis(10);
}
}
setPalette(palette, 0, 256);
pollEvents();
g_system->updateScreen();
clearKeys();
}
void CryOmni3DEngine::setBlackPalette() {
byte pal[256 * 3];
memset(pal, 0, 256 * 3);
g_system->getPaletteManager()->setPalette(pal, 0, 256);
g_system->updateScreen();
}
void CryOmni3DEngine::fillSurface(byte color) {
g_system->fillScreen(color);
g_system->updateScreen();
}
Common::Error CryOmni3DEngine_HNMPlayer::run() {
CryOmni3DEngine::run();
initGraphics(640, 480);
syncSoundSettings();
for (int i = 0; _gameDescription->desc.filesDescriptions[i].fileName; i++) {
playHNM(_gameDescription->desc.filesDescriptions[i].fileName, Audio::Mixer::kMusicSoundType);
}
return Common::kNoError;
}
} // End of namespace CryOmni3D